This driver is to use UFS devices on Exynos SoC and
has been already used for many years for commercial products.

Signed-off-by: Kiwoong Kim <kwmad....@samsung.com>
---
 drivers/scsi/ufs/Kconfig      |  10 +
 drivers/scsi/ufs/Makefile     |   1 +
 drivers/scsi/ufs/ufs-exynos.c | 962 ++++++++++++++++++++++++++++++++++++++++++
 drivers/scsi/ufs/ufs-exynos.h | 351 +++++++++++++++
 drivers/scsi/ufs/ufshcd.h     |   1 +
 5 files changed, 1325 insertions(+)
 create mode 100644 drivers/scsi/ufs/ufs-exynos.c
 create mode 100644 drivers/scsi/ufs/ufs-exynos.h

diff --git a/drivers/scsi/ufs/Kconfig b/drivers/scsi/ufs/Kconfig
index e27b4d4e6ae2..7d71ad8768c3 100644
--- a/drivers/scsi/ufs/Kconfig
+++ b/drivers/scsi/ufs/Kconfig
@@ -100,3 +100,13 @@ config SCSI_UFS_QCOM
 
          Select this if you have UFS controller on QCOM chipset.
          If unsure, say N.
+
+config SCSI_UFS_EXYNOS
+       tristate "EXYNOS UFS Host Controller Driver"
+       depends on SCSI_UFSHCD && SCSI_UFSHCD_PLATFORM
+       ---help---
+         This selects the EXYNOS UFS host controller driver.
+
+         If you have a controller with this interface, say Y or M here.
+
+         If unsure, say N.
diff --git a/drivers/scsi/ufs/Makefile b/drivers/scsi/ufs/Makefile
index 9310c6c83041..3312b052dcff 100644
--- a/drivers/scsi/ufs/Makefile
+++ b/drivers/scsi/ufs/Makefile
@@ -3,6 +3,7 @@
 obj-$(CONFIG_SCSI_UFS_DWC_TC_PCI) += tc-dwc-g210-pci.o ufshcd-dwc.o 
tc-dwc-g210.o
 obj-$(CONFIG_SCSI_UFS_DWC_TC_PLATFORM) += tc-dwc-g210-pltfrm.o ufshcd-dwc.o 
tc-dwc-g210.o
 obj-$(CONFIG_SCSI_UFS_QCOM) += ufs-qcom.o
+obj-$(CONFIG_SCSI_UFS_EXYNOS) += ufs-exynos.o
 obj-$(CONFIG_SCSI_UFSHCD) += ufshcd.o
 obj-$(CONFIG_SCSI_UFSHCD_PCI) += ufshcd-pci.o
 obj-$(CONFIG_SCSI_UFSHCD_PLATFORM) += ufshcd-pltfrm.o
diff --git a/drivers/scsi/ufs/ufs-exynos.c b/drivers/scsi/ufs/ufs-exynos.c
new file mode 100644
index 000000000000..98e5aeb80b06
--- /dev/null
+++ b/drivers/scsi/ufs/ufs-exynos.c
@@ -0,0 +1,962 @@
+/*
+ * UFS Host Controller driver for Exynos specific extensions
+ *
+ * Copyright (C) 2013-2014 Samsung Electronics Co., Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/clk.h>
+#include "ufshcd.h"
+#include "ufshcd-pltfrm.h"
+#include "ufs-exynos.h"
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+#include <linux/spinlock.h>
+
+/*
+ * Debugging information, SFR/attributes/misc
+ */
+static struct exynos_ufs *ufs_host_backup[1];
+static int ufs_host_index = 0;
+
+static struct exynos_ufs_sfr_log ufs_log_std_sfr[] = {
+       {"CAPABILITIES"                 ,       REG_CONTROLLER_CAPABILITIES,    
0},
+       {"UFS VERSION"                  ,       REG_UFS_VERSION,                
0},
+       {"PRODUCT ID"                   ,       REG_CONTROLLER_DEV_ID,          
0},
+       {"MANUFACTURE ID"               ,       REG_CONTROLLER_PROD_ID,         
0},
+       {"INTERRUPT STATUS"             ,       REG_INTERRUPT_STATUS,           
0},
+       {"INTERRUPT ENABLE"             ,       REG_INTERRUPT_ENABLE,           
0},
+       {"CONTROLLER STATUS"            ,       REG_CONTROLLER_STATUS,          
0},
+       {"CONTROLLER ENABLE"            ,       REG_CONTROLLER_ENABLE,          
0},
+       {"UIC ERR PHY ADAPTER LAYER"    ,       
REG_UIC_ERROR_CODE_PHY_ADAPTER_LAYER,           0},
+       {"UIC ERR DATA LINK LAYER"      ,       
REG_UIC_ERROR_CODE_DATA_LINK_LAYER,             0},
+       {"UIC ERR NETWORK LATER"        ,       
REG_UIC_ERROR_CODE_NETWORK_LAYER,               0},
+       {"UIC ERR TRANSPORT LAYER"      ,       
REG_UIC_ERROR_CODE_TRANSPORT_LAYER,             0},
+       {"UIC ERR DME"                  ,       REG_UIC_ERROR_CODE_DME,         
0},
+       {"UTP TRANSF REQ INT AGG CNTRL" ,       
REG_UTP_TRANSFER_REQ_INT_AGG_CONTROL,           0},
+       {"UTP TRANSF REQ LIST BASE L"   ,       
REG_UTP_TRANSFER_REQ_LIST_BASE_L,               0},
+       {"UTP TRANSF REQ LIST BASE H"   ,       
REG_UTP_TRANSFER_REQ_LIST_BASE_H,               0},
+       {"UTP TRANSF REQ DOOR BELL"     ,       REG_UTP_TRANSFER_REQ_DOOR_BELL, 
        0},
+       {"UTP TRANSF REQ LIST CLEAR"    ,       
REG_UTP_TRANSFER_REQ_LIST_CLEAR,                0},
+       {"UTP TRANSF REQ LIST RUN STOP" ,       
REG_UTP_TRANSFER_REQ_LIST_RUN_STOP,             0},
+       {"UTP TASK REQ LIST BASE L"     ,       REG_UTP_TASK_REQ_LIST_BASE_L,   
        0},
+       {"UTP TASK REQ LIST BASE H"     ,       REG_UTP_TASK_REQ_LIST_BASE_H,   
        0},
+       {"UTP TASK REQ DOOR BELL"       ,       REG_UTP_TASK_REQ_DOOR_BELL,     
        0},
+       {"UTP TASK REQ LIST CLEAR"      ,       REG_UTP_TASK_REQ_LIST_CLEAR,    
        0},
+       {"UTP TASK REQ LIST RUN STOP"   ,       REG_UTP_TASK_REQ_LIST_RUN_STOP, 
        0},
+       {"UIC COMMAND"                  ,       REG_UIC_COMMAND,                
0},
+       {"UIC COMMAND ARG1"             ,       REG_UIC_COMMAND_ARG_1,          
0},
+       {"UIC COMMAND ARG2"             ,       REG_UIC_COMMAND_ARG_2,          
0},
+       {"UIC COMMAND ARG3"             ,       REG_UIC_COMMAND_ARG_3,          
0},
+
+       {},
+};
+
+/* Helper for UFS CAL interface */
+static inline int ufs_init_cal(struct exynos_ufs *ufs, int idx,
+                                       struct platform_device *pdev)
+{
+       return 0;
+}
+
+static inline int ufs_pre_link(struct exynos_ufs *ufs)
+{
+       return 0;
+}
+
+static inline int ufs_post_link(struct exynos_ufs *ufs)
+{
+       return 0;
+}
+
+static inline int ufs_pre_gear_change(struct exynos_ufs *ufs,
+                               struct uic_pwr_mode *pmd)
+{
+       return 0;
+}
+
+static inline int ufs_post_gear_change(struct exynos_ufs *ufs)
+{
+       return 0;
+}
+
+static inline int ufs_post_h8_enter(struct exynos_ufs *ufs)
+{
+       return 0;
+}
+
+static inline int ufs_pre_h8_exit(struct exynos_ufs *ufs)
+{
+       return 0;
+}
+
+static inline void exynos_ufs_ctrl_phy_pwr(struct exynos_ufs *ufs, bool en)
+{
+       int ret = 0;
+
+       if (en)
+               ret = regmap_update_bits(ufs->pmureg, ufs->cxt_iso.offset,
+                                       ufs->cxt_iso.mask, ufs->cxt_iso.val);
+       else
+               ret = regmap_update_bits(ufs->pmureg, ufs->cxt_iso.offset,
+                                       ufs->cxt_iso.mask, 0);
+
+       if (ret)
+               dev_err(ufs->dev, "Unable to update PHY ISO control\n");
+}
+
+#ifndef __EXYNOS_UFS_VS_DEBUG__
+static void exynos_ufs_dump_std_sfr(struct ufs_hba *hba)
+{
+       struct exynos_ufs *ufs = to_exynos_ufs(hba);
+       struct exynos_ufs_sfr_log* cfg = ufs->debug.std_sfr;
+
+       dev_err(hba->dev, ": 
--------------------------------------------------- \n");
+       dev_err(hba->dev, ": \t\tREGISTER DUMP\n");
+       dev_err(hba->dev, ": 
--------------------------------------------------- \n");
+
+       while(cfg) {
+               if (!cfg->name)
+                       break;
+               cfg->val = ufshcd_readl(hba, cfg->offset);
+
+               /* Dump */
+               dev_err(hba->dev, ": %s(0x%04x):\t\t\t\t0x%08x\n",
+                               cfg->name, cfg->offset, cfg->val);
+
+               /* Next SFR */
+               cfg++;
+       }
+}
+#endif
+
+/*
+ * Exynos debugging main function
+ */
+static void exynos_ufs_dump_debug_info(struct ufs_hba *hba)
+{
+#ifdef __EXYNOS_UFS_VS_DEBUG__
+#else
+       exynos_ufs_dump_std_sfr(hba);
+#endif
+}
+
+static inline void exynos_ufs_set_hwacg_control(struct exynos_ufs *ufs, bool 
en)
+{
+       u32 reg;
+       if ((ufs->hw_rev != UFS_VER_0004) && (ufs->hw_rev != UFS_VER_0005))
+               return;
+
+       /*
+        * default value 1->0 at KC. so,
+        * need to set "1(disable HWACG)" during UFS init
+        */
+       reg = hci_readl(ufs, HCI_UFS_ACG_DISABLE);
+       if (en)
+               hci_writel(ufs, reg & (~HCI_UFS_ACG_DISABLE_EN), 
HCI_UFS_ACG_DISABLE);
+       else
+               hci_writel(ufs, reg | HCI_UFS_ACG_DISABLE_EN, 
HCI_UFS_ACG_DISABLE);
+
+}
+
+static inline void exynos_ufs_ctrl_auto_hci_clk(struct exynos_ufs *ufs, bool 
en)
+{
+       u32 reg = hci_readl(ufs, HCI_FORCE_HCS);
+
+       if (en)
+               hci_writel(ufs, reg | HCI_CORECLK_STOP_EN, HCI_FORCE_HCS);
+       else
+               hci_writel(ufs, reg & ~HCI_CORECLK_STOP_EN, HCI_FORCE_HCS);
+}
+
+static inline void exynos_ufs_ctrl_clk(struct exynos_ufs *ufs, bool en)
+{
+       u32 reg = hci_readl(ufs, HCI_FORCE_HCS);
+
+       if (en)
+               hci_writel(ufs, reg | CLK_STOP_CTRL_EN_ALL, HCI_FORCE_HCS);
+       else
+               hci_writel(ufs, reg & ~CLK_STOP_CTRL_EN_ALL, HCI_FORCE_HCS);
+}
+
+static inline void exynos_ufs_gate_clk(struct exynos_ufs *ufs, bool en)
+{
+
+       u32 reg = hci_readl(ufs, HCI_CLKSTOP_CTRL);
+
+       if (en)
+               hci_writel(ufs, reg | CLK_STOP_ALL, HCI_CLKSTOP_CTRL);
+       else
+               hci_writel(ufs, reg & ~CLK_STOP_ALL, HCI_CLKSTOP_CTRL);
+}
+
+static inline void exynos_ufs_set_unipro_mclk(struct exynos_ufs *ufs)
+{
+       ufs->mclk_rate = clk_get_rate(ufs->clk_unipro);
+}
+
+static inline void exynos_ufs_fit_aggr_timeout(struct exynos_ufs *ufs)
+{
+       u32 cnt_val;
+       u32 nVal;
+
+       /* IA_TICK_SEL : 1(1us_TO_CNT_VAL) */
+       nVal = hci_readl(ufs, HCI_UFSHCI_V2P1_CTRL);
+       nVal |= IA_TICK_SEL;
+       hci_writel(ufs, nVal, HCI_UFSHCI_V2P1_CTRL);
+
+       cnt_val = ufs->mclk_rate / 1000000 ;
+       hci_writel(ufs, cnt_val & CNT_VAL_1US_MASK, HCI_1US_TO_CNT_VAL);
+}
+
+static void exynos_ufs_init_pmc_req(struct ufs_hba *hba,
+               struct ufs_pa_layer_attr *pwr_max,
+               struct ufs_pa_layer_attr *pwr_req)
+{
+
+       struct exynos_ufs *ufs = to_exynos_ufs(hba);
+       struct uic_pwr_mode *req_pmd = &ufs->req_pmd_parm;
+       struct uic_pwr_mode *act_pmd = &ufs->act_pmd_parm;
+
+       /* update lane variable after link */
+       ufs->num_rx_lanes = pwr_max->lane_rx;
+       ufs->num_tx_lanes = pwr_max->lane_tx;
+
+       pwr_req->gear_rx
+               = act_pmd->gear= min_t(u8, pwr_max->gear_rx, req_pmd->gear);
+       pwr_req->gear_tx
+               = act_pmd->gear = min_t(u8, pwr_max->gear_tx, req_pmd->gear);
+       pwr_req->lane_rx
+               = act_pmd->lane = min_t(u8, pwr_max->lane_rx, req_pmd->lane);
+       pwr_req->lane_tx
+               = act_pmd->lane = min_t(u8, pwr_max->lane_tx, req_pmd->lane);
+       pwr_req->pwr_rx = act_pmd->mode = req_pmd->mode;
+       pwr_req->pwr_tx = act_pmd->mode = req_pmd->mode;
+       pwr_req->hs_rate = act_pmd->hs_series = req_pmd->hs_series;
+}
+
+static void exynos_ufs_config_intr(struct exynos_ufs *ufs, u32 errs, u8 index)
+{
+       switch(index) {
+       case UNIP_PA_LYR:
+               hci_writel(ufs, DFES_ERR_EN | errs, HCI_ERROR_EN_PA_LAYER);
+               break;
+       case UNIP_DL_LYR:
+               hci_writel(ufs, DFES_ERR_EN | errs, HCI_ERROR_EN_DL_LAYER);
+               break;
+       case UNIP_N_LYR:
+               hci_writel(ufs, DFES_ERR_EN | errs, HCI_ERROR_EN_N_LAYER);
+               break;
+       case UNIP_T_LYR:
+               hci_writel(ufs, DFES_ERR_EN | errs, HCI_ERROR_EN_T_LAYER);
+               break;
+       case UNIP_DME_LYR:
+               hci_writel(ufs, DFES_ERR_EN | errs, HCI_ERROR_EN_DME_LAYER);
+               break;
+       }
+}
+
+static void exynos_ufs_dev_hw_reset(struct ufs_hba *hba)
+{
+       struct exynos_ufs *ufs = to_exynos_ufs(hba);
+
+       /* bit[1] for resetn */
+       hci_writel(ufs, 0 << 0, HCI_GPIO_OUT);
+       udelay(5);
+       hci_writel(ufs, 1 << 0, HCI_GPIO_OUT);
+}
+
+static void exynos_ufs_init_host(struct exynos_ufs *ufs)
+{
+       u32 reg;
+
+       /* internal clock control */
+       exynos_ufs_ctrl_auto_hci_clk(ufs, false);
+       exynos_ufs_set_unipro_mclk(ufs);
+
+       /* period for interrupt aggregation */
+       exynos_ufs_fit_aggr_timeout(ufs);
+
+       /* misc HCI configurations */
+       hci_writel(ufs, 0xA, HCI_DATA_REORDER);
+       hci_writel(ufs, PRDT_PREFECT_EN | PRDT_SET_SIZE(12),
+                       HCI_TXPRDT_ENTRY_SIZE);
+       hci_writel(ufs, PRDT_SET_SIZE(12), HCI_RXPRDT_ENTRY_SIZE);
+       hci_writel(ufs, 0xFFFFFFFF, HCI_UTRL_NEXUS_TYPE);
+       hci_writel(ufs, 0xFFFFFFFF, HCI_UTMRL_NEXUS_TYPE);
+
+       reg = hci_readl(ufs, HCI_AXIDMA_RWDATA_BURST_LEN) &
+                                       ~BURST_LEN(0);
+       hci_writel(ufs, WLU_EN | BURST_LEN(3),
+                                       HCI_AXIDMA_RWDATA_BURST_LEN);
+
+       /*
+        * Enable HWAGC control by IOP
+        *
+        * default value 1->0 at KC.
+        * always "0"(controlled by UFS_ACG_DISABLE)
+        */
+       reg = hci_readl(ufs, HCI_IOP_ACG_DISABLE);
+       hci_writel(ufs, reg & (~HCI_IOP_ACG_DISABLE_EN), HCI_IOP_ACG_DISABLE);
+}
+
+static int exynos_ufs_init_system(struct exynos_ufs *ufs)
+{
+       struct device *dev = ufs->dev;
+       int ret = 0;
+       bool is_io_coherency;
+       bool is_dma_coherent;
+
+       /* PHY isolation bypass */
+       exynos_ufs_ctrl_phy_pwr(ufs, true);
+
+       /* IO cohernecy */
+       is_io_coherency = !IS_ERR(ufs->sysreg);
+       is_dma_coherent = !!of_find_property(dev->of_node,
+                                               "dma-coherent", NULL);
+
+       if (is_io_coherency != is_dma_coherent)
+               BUG();
+
+       if (!is_io_coherency)
+               dev_err(dev, "Not configured to use IO coherency\n");
+       else
+               ret = regmap_update_bits(ufs->sysreg, ufs->cxt_coherency.offset,
+                       ufs->cxt_coherency.mask, ufs->cxt_coherency.val);
+
+       return ret;
+}
+
+static int exynos_ufs_get_clks(struct ufs_hba *hba)
+{
+       struct exynos_ufs *ufs = to_exynos_ufs(hba);
+       struct list_head *head = &hba->clk_list_head;
+       struct ufs_clk_info *clki;
+       int i = 0;
+
+       ufs_host_backup[ufs_host_index++] = ufs;
+       ufs->debug.std_sfr = ufs_log_std_sfr;
+
+       if (!head || list_empty(head))
+               goto out;
+
+       list_for_each_entry(clki, head, list) {
+               /*
+                * get clock with an order listed in device tree
+                */
+               if (i == 0)
+                       ufs->clk_hci = clki->clk;
+               else if (i == 1)
+                       ufs->clk_unipro = clki->clk;
+
+               i++;
+       }
+out:
+       if (!ufs->clk_hci || !ufs->clk_unipro)
+               return -EINVAL;
+
+       return 0;
+}
+
+static void exynos_ufs_set_features(struct ufs_hba *hba, u32 hw_rev)
+{
+       /* caps */
+       hba->caps = UFSHCD_CAP_CLK_GATING |
+                       UFSHCD_CAP_HIBERN8_WITH_CLK_GATING |
+                       UFSHCD_CAP_INTR_AGGR;
+
+       /* quirks of common driver */
+       hba->quirks = UFSHCD_QUIRK_PRDT_BYTE_GRAN;
+
+       /* quirks of exynos-specific driver */
+}
+
+/*
+ * Exynos-specific callback functions
+ *
+ * init                        | Pure SW init & system-related init
+ * host_reset          | Host SW reset & init
+ * ...
+ *
+ * Initializations for software, host controller and system
+ * should be contained only in ->host_reset() as possible.
+ */
+
+static int exynos_ufs_init(struct ufs_hba *hba)
+{
+       struct exynos_ufs *ufs = to_exynos_ufs(hba);
+       int ret;
+
+       /* set features, such as caps or quirks */
+       exynos_ufs_set_features(hba, ufs->hw_rev);
+
+       /* get some clock sources and debug infomation structures */
+       ret = exynos_ufs_get_clks(hba);
+       if (ret)
+               return ret;
+
+       /* system init */
+       ret = exynos_ufs_init_system(ufs);
+       if (ret)
+               return ret;
+
+       ufs->misc_flags = EXYNOS_UFS_MISC_TOGGLE_LOG;
+
+       return 0;
+}
+
+static void exynos_ufs_host_reset(struct ufs_hba *hba)
+{
+       struct exynos_ufs *ufs = to_exynos_ufs(hba);
+       unsigned long timeout = jiffies + msecs_to_jiffies(1);
+
+       exynos_ufs_ctrl_auto_hci_clk(ufs, false);
+
+       hci_writel(ufs, UFS_SW_RST_MASK, HCI_SW_RST);
+
+       do {
+               if (!(hci_readl(ufs, HCI_SW_RST) & UFS_SW_RST_MASK))
+                       goto success;
+       } while (time_before(jiffies, timeout));
+
+       dev_err(ufs->dev, "timeout host sw-reset\n");
+
+       goto out;
+
+success:
+       /* host init */
+       exynos_ufs_init_host(ufs);
+
+       /* device reset */
+       exynos_ufs_dev_hw_reset(hba);
+out:
+       return;
+}
+
+static inline void exynos_ufs_dev_reset_ctrl(struct exynos_ufs *ufs, bool en)
+{
+
+       if (en)
+               hci_writel(ufs, 1 << 0, HCI_GPIO_OUT);
+       else
+               hci_writel(ufs, 0 << 0, HCI_GPIO_OUT);
+}
+
+static int exynos_ufs_setup_clocks(struct ufs_hba *hba, bool on,
+                                       enum ufs_notify_change_status status)
+{
+       struct exynos_ufs *ufs = to_exynos_ufs(hba);
+       int ret = 0;
+
+       if (status == PRE_CHANGE) {
+               if (on) {
+                       /*
+                        * Now all used blocks would not be turned off in a 
host.
+                        */
+                       exynos_ufs_ctrl_auto_hci_clk(ufs, false);
+                       exynos_ufs_gate_clk(ufs, false);
+
+                       /* HWAGC disable */
+                       exynos_ufs_set_hwacg_control(ufs, false);
+               } else {
+                       ret = ufs_post_h8_enter(ufs);
+               }
+       } else {
+               if (on) {
+                       ret = ufs_pre_h8_exit(ufs);
+               } else {
+                       /*
+                        * Now all used blocks would be turned off in a host.
+                        */
+                       exynos_ufs_ctrl_auto_hci_clk(ufs, true);
+
+                       /* HWAGC enable */
+                       exynos_ufs_set_hwacg_control(ufs, true);
+               }
+       }
+
+       return ret;
+}
+
+static int exynos_ufs_link_startup_notify(struct ufs_hba *hba,
+                                       enum ufs_notify_change_status status)
+{
+       struct exynos_ufs *ufs = to_exynos_ufs(hba);
+       int ret = 0;
+
+       switch (status) {
+       case PRE_CHANGE:
+               /* refer to hba */
+               ufs->hba = hba;
+
+               /* hci */
+               exynos_ufs_config_intr(ufs, DFES_DEF_DL_ERRS, UNIP_DL_LYR);
+               exynos_ufs_config_intr(ufs, DFES_DEF_N_ERRS, UNIP_N_LYR);
+               exynos_ufs_config_intr(ufs, DFES_DEF_T_ERRS, UNIP_T_LYR);
+
+               exynos_ufs_ctrl_clk(ufs, true);
+               exynos_ufs_gate_clk(ufs, false);
+               exynos_ufs_set_hwacg_control(ufs, false);
+
+               if (ufs->num_rx_lanes == 0 || ufs->num_tx_lanes == 0) {
+                       ufshcd_dme_get(hba, UIC_ARG_MIB(PA_AVAILRXDATALANES),
+                                       &ufs->num_rx_lanes);
+                       ufshcd_dme_get(hba, UIC_ARG_MIB(PA_AVAILTXDATALANES),
+                                       &ufs->num_tx_lanes);
+                       WARN(ufs->num_rx_lanes != ufs->num_tx_lanes,
+                                       "available data lane is not 
equal(rx:%d, tx:%d)\n",
+                                       ufs->num_rx_lanes, ufs->num_tx_lanes);
+               }
+
+               ufs->mclk_rate = clk_get_rate(ufs->clk_unipro);
+
+               ret = ufs_pre_link(ufs);
+               break;
+       case POST_CHANGE:
+               /* UIC configuration table after link startup */
+               ret = ufs_post_link(ufs);
+               break;
+       default:
+               break;
+       }
+
+       return ret;
+}
+
+static int exynos_ufs_pwr_change_notify(struct ufs_hba *hba,
+                                       enum ufs_notify_change_status status,
+                                       struct ufs_pa_layer_attr *pwr_max,
+                                       struct ufs_pa_layer_attr *pwr_req)
+{
+       struct exynos_ufs *ufs = to_exynos_ufs(hba);
+       struct uic_pwr_mode *act_pmd = &ufs->act_pmd_parm;
+       int ret = 0;
+
+       switch (status) {
+       case PRE_CHANGE:
+
+               /* Set PMC parameters to be requested */
+               exynos_ufs_init_pmc_req(hba, pwr_max, pwr_req);
+
+               /* UIC configuration table before power mode change */
+               ret = ufs_pre_gear_change(ufs, act_pmd);
+
+               break;
+       case POST_CHANGE:
+               /* UIC configuration table after power mode change */
+               ret = ufs_post_gear_change(ufs);
+
+               dev_info(ufs->dev,
+                               "Power mode change(%d): 
M(%d)G(%d)L(%d)HS-series(%d)\n",
+                               ret, act_pmd->mode, act_pmd->gear,
+                               act_pmd->lane, act_pmd->hs_series);
+               break;
+       default:
+               break;
+       }
+
+       return ret;
+}
+
+static void exynos_ufs_set_nexus_t_xfer_req(struct ufs_hba *hba,
+                               int tag, bool op)
+{
+       struct exynos_ufs *ufs = to_exynos_ufs(hba);
+       u32 type;
+
+       type =  hci_readl(ufs, HCI_UTRL_NEXUS_TYPE);
+
+       if (op)
+               type |= (1 << tag);
+       else
+               type &= ~(1 << tag);
+
+       hci_writel(ufs, type, HCI_UTRL_NEXUS_TYPE);
+}
+
+static void exynos_ufs_set_nexus_t_task_mgmt(struct ufs_hba *hba, int tag, u8 
tm_func)
+{
+       struct exynos_ufs *ufs = to_exynos_ufs(hba);
+       u32 type;
+
+       type =  hci_readl(ufs, HCI_UTMRL_NEXUS_TYPE);
+
+       switch (tm_func) {
+       case UFS_ABORT_TASK:
+       case UFS_QUERY_TASK:
+               type |= (1 << tag);
+               break;
+       case UFS_ABORT_TASK_SET:
+       case UFS_CLEAR_TASK_SET:
+       case UFS_LOGICAL_RESET:
+       case UFS_QUERY_TASK_SET:
+               type &= ~(1 << tag);
+               break;
+       }
+
+       hci_writel(ufs, type, HCI_UTMRL_NEXUS_TYPE);
+}
+
+static int __exynos_ufs_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
+{
+       struct exynos_ufs *ufs = to_exynos_ufs(hba);
+
+       exynos_ufs_dev_reset_ctrl(ufs, false);
+
+       exynos_ufs_ctrl_phy_pwr(ufs, false);
+
+       return 0;
+}
+
+static int __exynos_ufs_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op)
+{
+       struct exynos_ufs *ufs = to_exynos_ufs(hba);
+       int ret = 0;
+
+       exynos_ufs_ctrl_phy_pwr(ufs, true);
+
+       /* system init */
+       ret = exynos_ufs_init_system(ufs);
+       if (ret)
+               return ret;
+
+       if (ufshcd_is_clkgating_allowed(hba))
+               clk_prepare_enable(ufs->clk_hci);
+       exynos_ufs_ctrl_auto_hci_clk(ufs, false);
+
+       if (ufshcd_is_clkgating_allowed(hba))
+               clk_disable_unprepare(ufs->clk_hci);
+
+       return 0;
+}
+
+static struct ufs_hba_variant_ops exynos_ufs_ops = {
+       .init = exynos_ufs_init,
+       .host_reset = exynos_ufs_host_reset,
+       .setup_clocks = exynos_ufs_setup_clocks,
+       .link_startup_notify = exynos_ufs_link_startup_notify,
+       .pwr_change_notify = exynos_ufs_pwr_change_notify,
+       .setup_xfer_req = exynos_ufs_set_nexus_t_xfer_req,
+       .setup_task_mgmt = exynos_ufs_set_nexus_t_task_mgmt,
+       .dbg_register_dump = exynos_ufs_dump_debug_info,
+       .suspend = __exynos_ufs_suspend,
+       .resume = __exynos_ufs_resume,
+};
+
+static int exynos_ufs_populate_dt_phy(struct device *dev, struct exynos_ufs 
*ufs)
+{
+       struct device_node *ufs_phy;
+       struct exynos_ufs_phy *phy = &ufs->phy;
+       struct resource io_res;
+       int ret;
+
+       ufs_phy = of_get_child_by_name(dev->of_node, "ufs-phy");
+       if (!ufs_phy) {
+               dev_err(dev, "failed to get ufs-phy node\n");
+               return -ENODEV;
+       }
+
+       ret = of_address_to_resource(ufs_phy, 0, &io_res);
+       if (ret) {
+               dev_err(dev, "failed to get i/o address phy pma\n");
+               goto err_0;
+       }
+
+       phy->reg_pma = devm_ioremap_resource(dev, &io_res);
+       if (!phy->reg_pma) {
+               dev_err(dev, "failed to ioremap for phy pma\n");
+               ret = -ENOMEM;
+               goto err_0;
+       }
+
+err_0:
+       of_node_put(ufs_phy);
+
+       return ret;
+}
+
+/*
+ * This function is to define offset, mask and shift to access somewhere.
+ */
+static int exynos_ufs_set_context_for_access(struct device *dev,
+                               const char *name, struct exynos_access_cxt *cxt)
+{
+       struct device_node *np;
+       int ret;
+
+       np = of_get_child_by_name(dev->of_node, name);
+       if (!np) {
+               dev_err(dev, "failed to get node(%s)\n", name);
+               return 1;
+       }
+
+       ret = of_property_read_u32(np, "offset", &cxt->offset);
+       if (IS_ERR(&cxt->offset)) {
+               dev_err(dev, "failed to set cxt(%s) offset\n", name);
+               return cxt->offset;
+       }
+
+       ret = of_property_read_u32(np, "mask", &cxt->mask);
+       if (IS_ERR(&cxt->mask)) {
+               dev_err(dev, "failed to set cxt(%s) mask\n", name);
+               return cxt->mask;
+       }
+
+       ret = of_property_read_u32(np, "val", &cxt->val);
+       if (IS_ERR(&cxt->val)) {
+               dev_err(dev, "failed to set cxt(%s) val\n", name);
+               return cxt->val;
+       }
+
+       return 0;
+}
+
+static int exynos_ufs_populate_dt_system(struct device *dev, struct exynos_ufs 
*ufs)
+{
+       int ret;
+
+       /* regmap pmureg */
+       ufs->pmureg = syscon_regmap_lookup_by_phandle(dev->of_node,
+                                        "samsung,pmu-phandle");
+       if (IS_ERR(ufs->pmureg)) {
+               /*
+                * phy isolation should be available.
+                * so this case need to be failed.
+                */
+               dev_err(dev, "pmu regmap lookup failed.\n");
+               return PTR_ERR(ufs->pmureg);
+       }
+
+       /* Set access context for phy isolation bypass */
+       ret = exynos_ufs_set_context_for_access(dev, "ufs-phy-iso",
+                                                       &ufs->cxt_iso);
+       if (ret == 1) {
+               /* no device node, default */
+               ufs->cxt_iso.offset = 0x0724;
+               ufs->cxt_iso.mask = 0x1;
+               ufs->cxt_iso.val = 0x1;
+               ret = 0;
+       }
+
+       /* regmap sysreg */
+       ufs->sysreg = syscon_regmap_lookup_by_phandle(dev->of_node,
+                                        "samsung,sysreg-fsys-phandle");
+       if (IS_ERR(ufs->sysreg)) {
+               /*
+                * Currently, ufs driver gets sysreg for io coherency.
+                * Some architecture might not support this feature.
+                * So the device node might not exist.
+                */
+               dev_err(dev, "sysreg regmap lookup failed.\n");
+               return 0;
+       }
+
+       /* Set access context for io coherency */
+       ret = exynos_ufs_set_context_for_access(dev, "ufs-dma-coherency",
+                                                       &ufs->cxt_coherency);
+       if (ret == 1) {
+               /* no device node, default */
+               ufs->cxt_coherency.offset = 0x0700;
+               ufs->cxt_coherency.mask = 0x300;        /* bit 8,9 */
+               ufs->cxt_coherency.val = 0x3;
+               ret = 0;
+       }
+
+       return ret;
+}
+
+static int exynos_ufs_get_pwr_mode(struct device_node *np,
+                               struct exynos_ufs *ufs)
+{
+       struct uic_pwr_mode *pmd = &ufs->req_pmd_parm;
+
+       pmd->mode = FAST_MODE;
+
+       if (of_property_read_u8(np, "ufs,pmd-attr-lane", &pmd->lane))
+               pmd->lane = 1;
+
+       if (of_property_read_u8(np, "ufs,pmd-attr-gear", &pmd->gear))
+               pmd->gear = 1;
+
+       pmd->hs_series = PA_HS_MODE_B;
+
+       return 0;
+}
+
+static int exynos_ufs_populate_dt(struct device *dev, struct exynos_ufs *ufs)
+{
+       struct device_node *np = dev->of_node;
+       int ret;
+
+       /* Get exynos-specific version for featuring */
+       if (of_property_read_u32(np, "hw-rev", &ufs->hw_rev))
+               ufs->hw_rev = UFS_VER_0004;
+
+       ret = exynos_ufs_populate_dt_phy(dev, ufs);
+       if (ret) {
+               dev_err(dev, "failed to populate dt-phy\n");
+               goto out;
+       }
+
+       ret = exynos_ufs_populate_dt_system(dev, ufs);
+       if (ret) {
+               dev_err(dev, "failed to populate dt-pmu\n");
+               goto out;
+       }
+
+       exynos_ufs_get_pwr_mode(np, ufs);
+
+out:
+       return ret;
+}
+
+static u64 exynos_ufs_dma_mask = DMA_BIT_MASK(32);
+
+static int exynos_ufs_probe(struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       struct exynos_ufs *ufs;
+       struct resource *res;
+       int ret;
+
+       ufs = devm_kzalloc(dev, sizeof(*ufs), GFP_KERNEL);
+       if (!ufs) {
+               dev_err(dev, "cannot allocate mem for exynos-ufs\n");
+               return -ENOMEM;
+       }
+
+       /* exynos-specific hci */
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+       ufs->reg_hci = devm_ioremap_resource(dev, res);
+       if (!ufs->reg_hci) {
+               dev_err(dev, "cannot ioremap for hci vendor register\n");
+               return -ENOMEM;
+       }
+
+       /* unipro */
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
+       ufs->reg_unipro = devm_ioremap_resource(dev, res);
+       if (!ufs->reg_unipro) {
+               dev_err(dev, "cannot ioremap for unipro register\n");
+               return -ENOMEM;
+       }
+
+       /* This must be before calling exynos_ufs_populate_dt */
+       ret = ufs_init_cal(ufs, ufs_host_index, pdev);
+       if (ret)
+               return ret;
+
+       ret = exynos_ufs_populate_dt(dev, ufs);
+       if (ret) {
+               dev_err(dev, "failed to get dt info.\n");
+               return ret;
+       }
+
+       ufs->dev = dev;
+       dev->platform_data = ufs;
+       dev->dma_mask = &exynos_ufs_dma_mask;
+
+       ret = ufshcd_pltfrm_init(pdev, &exynos_ufs_ops);
+
+       return ret;
+}
+
+static int exynos_ufs_remove(struct platform_device *pdev)
+{
+       struct ufs_hba *hba =  platform_get_drvdata(pdev);
+       struct exynos_ufs *ufs = dev_get_platdata(&pdev->dev);
+
+       ufshcd_remove(hba);
+
+       ufs->misc_flags = EXYNOS_UFS_MISC_TOGGLE_LOG;
+
+       exynos_ufs_ctrl_phy_pwr(ufs, false);
+
+       return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int exynos_ufs_suspend(struct device *dev)
+{
+       struct ufs_hba *hba = dev_get_drvdata(dev);
+
+       return ufshcd_system_suspend(hba);
+}
+
+static int exynos_ufs_resume(struct device *dev)
+{
+       struct ufs_hba *hba = dev_get_drvdata(dev);
+
+       return ufshcd_system_resume(hba);
+}
+#else
+#define exynos_ufs_suspend     NULL
+#define exynos_ufs_resume      NULL
+#endif /* CONFIG_PM_SLEEP */
+
+#ifdef CONFIG_PM_RUNTIME
+static int exynos_ufs_runtime_suspend(struct device *dev)
+{
+       return ufshcd_system_suspend(dev_get_drvdata(dev));
+}
+
+static int exynos_ufs_runtime_resume(struct device *dev)
+{
+       return ufshcd_system_resume(dev_get_drvdata(dev));
+}
+
+static int exynos_ufs_runtime_idle(struct device *dev)
+{
+       return ufshcd_runtime_idle(dev_get_drvdata(dev));
+}
+
+#else
+#define exynos_ufs_runtime_suspend     NULL
+#define exynos_ufs_runtime_resume      NULL
+#define exynos_ufs_runtime_idle                NULL
+#endif /* CONFIG_PM_RUNTIME */
+
+static void exynos_ufs_shutdown(struct platform_device *pdev)
+{
+       ufshcd_shutdown((struct ufs_hba *)platform_get_drvdata(pdev));
+}
+
+static const struct dev_pm_ops exynos_ufs_dev_pm_ops = {
+       .suspend                = exynos_ufs_suspend,
+       .resume                 = exynos_ufs_resume,
+       .runtime_suspend        = exynos_ufs_runtime_suspend,
+       .runtime_resume         = exynos_ufs_runtime_resume,
+       .runtime_idle           = exynos_ufs_runtime_idle,
+};
+
+static const struct of_device_id exynos_ufs_match[] = {
+       { .compatible = "samsung,exynos-ufs", },
+       {},
+};
+MODULE_DEVICE_TABLE(of, exynos_ufs_match);
+
+static struct platform_driver exynos_ufs_driver = {
+       .driver = {
+               .name = "exynos-ufs",
+               .owner = THIS_MODULE,
+               .pm = &exynos_ufs_dev_pm_ops,
+               .of_match_table = exynos_ufs_match,
+               .suppress_bind_attrs = true,
+       },
+       .probe = exynos_ufs_probe,
+       .remove = exynos_ufs_remove,
+       .shutdown = exynos_ufs_shutdown,
+};
+
+module_platform_driver(exynos_ufs_driver);
+MODULE_DESCRIPTION("Exynos Specific UFSHCI driver");
+MODULE_AUTHOR("Seungwon Jeon <tgih....@samsung.com>");
+MODULE_AUTHOR("Kiwoong Kim <kwmad....@samsung.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/scsi/ufs/ufs-exynos.h b/drivers/scsi/ufs/ufs-exynos.h
new file mode 100644
index 000000000000..0480fc4a8931
--- /dev/null
+++ b/drivers/scsi/ufs/ufs-exynos.h
@@ -0,0 +1,351 @@
+/*
+ * UFS Host Controller driver for Exynos specific extensions
+ *
+ * Copyright (C) 2013-2014 Samsung Electronics Co., Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef _UFS_EXYNOS_H_
+#define _UFS_EXYNOS_H_
+
+#define UFS_VER_0004   4
+#define UFS_VER_0005   5
+
+/*
+ * Exynos's Vendor specific registers for UFSHCI
+ */
+#define HCI_TXPRDT_ENTRY_SIZE          0x00
+#define HCI_RXPRDT_ENTRY_SIZE          0x04
+#define HCI_TO_CNT_DIV_VAL              0x08
+#define HCI_1US_TO_CNT_VAL             0x0C
+ #define CNT_VAL_1US_MASK      0x3ff
+#define HCI_INVALID_UPIU_CTRL          0x10
+#define HCI_INVALID_UPIU_BADDR         0x14
+#define HCI_INVALID_UPIU_UBADDR                0x18
+#define HCI_INVALID_UTMR_OFFSET_ADDR   0x1C
+#define HCI_INVALID_UTR_OFFSET_ADDR    0x20
+#define HCI_INVALID_DIN_OFFSET_ADDR    0x24
+#define HCI_VENDOR_SPECIFIC_IS         0x38
+#define HCI_VENDOR_SPECIFIC_IE         0x3C
+#define HCI_UTRL_NEXUS_TYPE            0x40
+#define HCI_UTMRL_NEXUS_TYPE           0x44
+#define HCI_E2EFC_CTRL                 0x48
+#define HCI_SW_RST                     0x50
+ #define UFS_LINK_SW_RST       (1 << 0)
+ #define UFS_UNIPRO_SW_RST     (1 << 1)
+ #define UFS_SW_RST_MASK       (UFS_UNIPRO_SW_RST | UFS_LINK_SW_RST)
+#define HCI_LINK_VERSION               0x54
+#define HCI_IDLE_TIMER_CONFIG          0x58
+#define HCI_RX_UPIU_MATCH_ERROR_CODE   0x5C
+#define HCI_DATA_REORDER               0x60
+#define HCI_MAX_DOUT_DATA_SIZE         0x64
+#define HCI_UNIPRO_APB_CLK_CTRL                0x68
+#define HCI_AXIDMA_RWDATA_BURST_LEN    0x6C
+ #define BURST_LEN(x)                  ((x) << 27 | (x))
+ #define WLU_EN                                (1 << 31)
+ #define AXIDMA_RWDATA_BURST_LEN       (0xF)
+#define HCI_GPIO_OUT                   0x70
+#define HCI_WRITE_DMA_CTRL             0x74
+#define HCI_ERROR_EN_PA_LAYER          0x78
+#define HCI_ERROR_EN_DL_LAYER          0x7C
+#define HCI_ERROR_EN_N_LAYER           0x80
+#define HCI_ERROR_EN_T_LAYER           0x84
+#define HCI_ERROR_EN_DME_LAYER         0x88
+#define HCI_UFSHCI_V2P1_CTRL                   0X8C
+#define IA_TICK_SEL                            BIT(16)
+#define HCI_REQ_HOLD_EN                        0xAC
+
+#define HCI_CLKSTOP_CTRL               0xB0
+ #define REFCLKOUT_STOP                        BIT(4)
+ #define MPHY_APBCLK_STOP              BIT(3)
+ #define REFCLK_STOP                   BIT(2)
+ #define UNIPRO_MCLK_STOP              BIT(1)
+ #define UNIPRO_PCLK_STOP              BIT(0)
+ #define CLK_STOP_ALL          (REFCLKOUT_STOP |\
+                                       REFCLK_STOP |\
+                                       UNIPRO_MCLK_STOP |\
+                                       UNIPRO_PCLK_STOP)
+
+#define HCI_FORCE_HCS                  0xB4
+ #define REFCLKOUT_STOP_EN     BIT(11)
+ #define MPHY_APBCLK_STOP_EN   BIT(10)
+ #define UFSP_DRCG_EN          BIT(8)
+ #define REFCLK_STOP_EN                BIT(7)
+ #define UNIPRO_PCLK_STOP_EN   BIT(6)
+ #define UNIPRO_MCLK_STOP_EN   BIT(5)
+ #define HCI_CORECLK_STOP_EN   BIT(4)
+ #define CLK_STOP_CTRL_EN_ALL  (UFSP_DRCG_EN |\
+                                       MPHY_APBCLK_STOP_EN |\
+                                       REFCLKOUT_STOP_EN |\
+                                       REFCLK_STOP_EN |\
+                                       UNIPRO_PCLK_STOP_EN |\
+                                       UNIPRO_MCLK_STOP_EN)
+
+#define HCI_FSM_MONITOR                        0xC0
+#define HCI_PRDT_HIT_RATIO             0xC4
+#define HCI_DMA0_MONITOR_STATE         0xC8
+#define HCI_DMA0_MONITOR_CNT           0xCC
+#define HCI_DMA1_MONITOR_STATE         0xD0
+#define HCI_DMA1_MONITOR_CNT           0xD4
+
+#define HCI_UFS_AXI_DMA_IF_CTRL                0xF8
+#define HCI_UFS_ACG_DISABLE            0xFC
+ #define HCI_UFS_ACG_DISABLE_EN                BIT(0)
+#define HCI_IOP_ACG_DISABLE            0x100
+ #define HCI_IOP_ACG_DISABLE_EN                BIT(0)
+#define HCI_MPHY_REFCLK_SEL            0x108
+ #define MPHY_REFCLK_SEL               BIT(0)
+
+/* Device fatal error */
+#define DFES_ERR_EN    BIT(31)
+#define DFES_DEF_DL_ERRS       (UIC_DATA_LINK_LAYER_ERROR_RX_BUF_OF |\
+                                UIC_DATA_LINK_LAYER_ERROR_PA_INIT)
+#define DFES_DEF_N_ERRS                (UIC_NETWORK_UNSUPPORTED_HEADER_TYPE |\
+                                UIC_NETWORK_BAD_DEVICEID_ENC |\
+                                UIC_NETWORK_LHDR_TRAP_PACKET_DROPPING)
+#define DFES_DEF_T_ERRS                (UIC_TRANSPORT_UNSUPPORTED_HEADER_TYPE 
|\
+                                UIC_TRANSPORT_UNKNOWN_CPORTID |\
+                                UIC_TRANSPORT_NO_CONNECTION_RX |\
+                                UIC_TRANSPORT_BAD_TC)
+
+/* TXPRDT defines */
+#define PRDT_PREFECT_EN                BIT(31)
+#define PRDT_SET_SIZE(x)       ((x) & 0x1F)
+
+enum {
+       UNIP_PA_LYR = 0,
+       UNIP_DL_LYR,
+       UNIP_N_LYR,
+       UNIP_T_LYR,
+       UNIP_DME_LYR,
+};
+
+/*
+ * UNIPRO registers
+ */
+#define UNIP_COMP_VERSION                      0x000
+#define UNIP_COMP_INFO                         0x004
+#define UNIP_COMP_RESET                                0x010
+
+#define UNIP_DME_POWERON_REQ                   0x7800
+#define UNIP_DME_POWERON_CNF_RESULT            0x7804
+#define UNIP_DME_POWEROFF_REQ                  0x7810
+#define UNIP_DME_POWEROFF_CNF_RESULT           0x7814
+#define UNIP_DME_RESET_REQ                     0x7820
+#define UNIP_DME_RESET_REQ_LEVEL               0x7824
+#define UNIP_DME_ENABLE_REQ                    0x7830
+#define UNIP_DME_ENABLE_CNF_RESULT             0x7834
+#define UNIP_DME_ENDPOINTRESET_REQ             0x7840
+#define UNIP_DME_ENDPOINTRESET_CNF_RESULT      0x7844
+#define UNIP_DME_LINKSTARTUP_REQ               0x7850
+#define UNIP_DME_LINKSTARTUP_CNF_RESULT                0x7854
+#define UNIP_DME_HIBERN8_ENTER_REQ             0x7860
+#define UNIP_DME_HIBERN8_ENTER_CNF_RESULT      0x7864
+#define UNIP_DME_HIBERN8_ENTER_IND_RESULT      0x7868
+#define UNIP_DME_HIBERN8_EXIT_REQ              0x7870
+#define UNIP_DME_HIBERN8_EXIT_CNF_RESULT       0x7874
+#define UNIP_DME_HIBERN8_EXIT_IND_RESULT       0x7878
+#define UNIP_DME_PWR_REQ                       0x7880
+#define UNIP_DME_PWR_REQ_POWERMODE             0x7884
+#define UNIP_DME_PWR_REQ_LOCALL2TIMER0                 0x7888
+#define UNIP_DME_PWR_REQ_LOCALL2TIMER1                 0x788C
+#define UNIP_DME_PWR_REQ_LOCALL2TIMER2                 0x7890
+#define UNIP_DME_PWR_REQ_REMOTEL2TIMER0                0x78B8
+#define UNIP_DME_PWR_REQ_REMOTEL2TIMER1                0x78BC
+#define UNIP_DME_PWR_REQ_REMOTEL2TIMER2                0x78C0
+#define UNIP_DME_PWR_CNF_RESULT                        0x78E8
+#define UNIP_DME_PWR_IND_RESULT                        0x78EC
+#define UNIP_DME_TEST_MODE_REQ                         0x7900
+#define UNIP_DME_TEST_MODE_CNF_RESULT          0x7904
+
+#define UNIP_DME_ERROR_IND_LAYER               0x0C0
+#define UNIP_DME_ERROR_IND_ERRCODE             0x0C4
+#define UNIP_DME_PACP_CNFBIT                   0x0C8
+#define UNIP_DME_DL_FRAME_IND                  0x0D0
+#define UNIP_DME_INTR_STATUS                   0x0E0
+#define UNIP_DME_INTR_ENABLE                   0x0E4
+
+#define UNIP_DME_GETSET_CONTROL                0x7A00
+#define UNIP_DME_GETSET_ADDR                   0x7A04
+#define UNIP_DME_GETSET_WDATA                  0x7A08
+#define UNIP_DME_GETSET_RDATA                  0x7A0C
+#define UNIP_DME_GETSET_RESULT                 0x7A10
+#define UNIP_DME_PEER_GETSET_CONTROL           0x7A20
+#define UNIP_DME_PEER_GETSET_ADDR              0x7A24
+#define UNIP_DME_PEER_GETSET_WDATA             0x7A28
+#define UNIP_DME_PEER_GETSET_RDATA             0x7A2C
+#define UNIP_DME_PEER_GETSET_RESULT            0x7A30
+
+#define UNIP_DME_INTR_STATUS_LSB                          0x7B00
+#define UNIP_DME_INTR_STATUS_MSB                  0x7B04
+#define UNIP_DME_INTR_ERROR_CODE                          0x7B20
+#define UNIP_DME_DISCARD_PORT_ID                  0x7B24
+#define UNIP_DME_DBG_OPTION_SUITE                         0x7C00
+#define UNIP_DME_DBG_CTRL_FSM                     0x7D00
+#define UNIP_DME_DBG_FLAG_STATUS                          0x7D14
+#define UNIP_DME_DBG_LINKCFG_FSM                  0x7D18
+
+#define UNIP_DME_INTR_ERROR_CODE               0x7B20
+#define UNIP_DME_DEEPSTALL_ENTER_REQ           0x7910
+#define UNIP_DME_DISCARD_CPORT_ID              0x7B24
+
+#define UNIP_DBG_FORCE_DME_CTRL_STATE          0x150
+#define UNIP_DBG_AUTO_DME_LINKSTARTUP          0x158
+#define UNIP_DBG_PA_CTRLSTATE                  0x15C
+#define UNIP_DBG_PA_TX_STATE                   0x160
+#define UNIP_DBG_BREAK_DME_CTRL_STATE          0x164
+#define UNIP_DBG_STEP_DME_CTRL_STATE           0x168
+#define UNIP_DBG_NEXT_DME_CTRL_STATE           0x16C
+
+/*
+ * Driver specific definitions
+ */
+struct exynos_ufs_phy {
+       void __iomem *reg_pma;
+};
+
+struct exynos_ufs_clk_info {
+       struct list_head list;
+       struct clk *clk;
+       const char *name;
+       u32 freq;
+};
+
+struct exynos_ufs_misc_log {
+       struct list_head clk_list_head;
+       bool isolation;
+};
+
+struct exynos_ufs_sfr_log {
+       const char* name;
+       const u32 offset;
+#define LOG_STD_HCI_SFR                0xFFFFFFF0
+#define LOG_VS_HCI_SFR         0xFFFFFFF1
+#define LOG_FMP_SFR            0xFFFFFFF2
+#define LOG_UNIPRO_SFR         0xFFFFFFF3
+#define LOG_PMA_SFR            0xFFFFFFF4
+       u32 val;
+};
+
+struct exynos_ufs_attr_log {
+       const u32 offset;
+       u32 res;
+       u32 val;
+};
+
+struct exynos_ufs_perf {
+       u32 opcode;     /* 0: read, 1: write */
+       u32 chunk_size;
+       ktime_t time;
+       u64 total_time;
+       u32 count;
+       u32 total_count;
+};
+
+/* Main structure for debug and performance */
+struct exynos_ufs_debug {
+       struct exynos_ufs_sfr_log* std_sfr;
+       struct exynos_ufs_sfr_log* sfr;
+       struct exynos_ufs_attr_log* attr;
+       struct exynos_ufs_misc_log misc;
+       struct exynos_ufs_perf perf;
+};
+
+struct exynos_access_cxt {
+       u32 offset;
+       u32 mask;
+       u32 val;
+};
+
+struct uic_pwr_mode {
+       u8 lane;
+       u8 gear;
+       u8 mode;
+       u8 hs_series;
+};
+
+struct exynos_ufs {
+       struct device *dev;
+       struct ufs_hba *hba;
+
+       void __iomem *reg_hci;
+       void __iomem *reg_unipro;
+
+       struct regmap *pmureg;
+       struct regmap *sysreg;
+
+       struct clk *clk_hci;
+       struct clk *pclk;
+       struct clk *clk_unipro;
+       u32 mclk_rate;
+
+       int num_rx_lanes;
+       int num_tx_lanes;
+
+       struct exynos_ufs_phy phy;
+       struct uic_pwr_mode req_pmd_parm;
+       struct uic_pwr_mode act_pmd_parm;
+
+       u32 rx_min_actv_time_cap;
+       u32 rx_hibern8_time_cap;
+       u32 tx_hibern8_time_cap;
+
+       /* for miscellaneous control */
+       u32 misc_flags;
+#define EXYNOS_UFS_MISC_TOGGLE_LOG     BIT(0)
+
+       struct exynos_ufs_debug debug;
+
+       u32 hw_rev;
+
+       struct exynos_access_cxt cxt_iso;       /* phy isolation */
+       struct exynos_access_cxt cxt_coherency; /* io coherency */
+};
+
+static inline struct exynos_ufs *to_exynos_ufs(struct ufs_hba *hba)
+{
+       return dev_get_platdata(hba->dev);
+}
+
+#ifndef __EXYNOS_UFS_MMIO_FUNC__
+#define __EXYNOS_UFS_MMIO_FUNC__
+#define EXYNOS_UFS_MMIO_FUNC(name)                                             
\
+static inline void name##_writel(struct exynos_ufs *ufs, u32 val, u32 reg)     
\
+{                                                                              
\
+       writel(val, ufs->reg_##name + reg);                                     
\
+}                                                                              
\
+                                                                               
\
+static inline u32 name##_readl(struct exynos_ufs *ufs, u32 reg)                
        \
+{                                                                              
\
+       return readl(ufs->reg_##name + reg);                                    
\
+}
+
+EXYNOS_UFS_MMIO_FUNC(hci);
+EXYNOS_UFS_MMIO_FUNC(unipro);
+
+static inline void phy_pma_writel(struct exynos_ufs *ufs, u32 val, u32 reg)
+{
+       u32 reg1 = hci_readl(ufs, HCI_CLKSTOP_CTRL);
+
+       hci_writel(ufs, reg1 & ~MPHY_APBCLK_STOP, HCI_CLKSTOP_CTRL);
+       writel(val, ufs->phy.reg_pma + reg);
+       hci_writel(ufs, reg1 | MPHY_APBCLK_STOP, HCI_CLKSTOP_CTRL);
+}
+
+static inline u32 phy_pma_readl(struct exynos_ufs *ufs, u32 reg)
+{
+       u32 reg1 = hci_readl(ufs, HCI_CLKSTOP_CTRL);
+
+       hci_writel(ufs, reg1 & ~MPHY_APBCLK_STOP, HCI_CLKSTOP_CTRL);
+       reg = readl(ufs->phy.reg_pma + reg);
+       hci_writel(ufs, reg1 | MPHY_APBCLK_STOP, HCI_CLKSTOP_CTRL);
+
+       return reg;
+}
+#endif
+
+#endif /* _UFS_EXYNOS_H_ */
diff --git a/drivers/scsi/ufs/ufshcd.h b/drivers/scsi/ufs/ufshcd.h
index 1332e544da92..1afd5ac9707c 100644
--- a/drivers/scsi/ufs/ufshcd.h
+++ b/drivers/scsi/ufs/ufshcd.h
@@ -308,6 +308,7 @@ struct ufs_hba_variant_ops {
        int     (*setup_clocks)(struct ufs_hba *, bool,
                                enum ufs_notify_change_status);
        int     (*setup_regulators)(struct ufs_hba *, bool);
+       void    (*host_reset)(struct ufs_hba *);
        int     (*hce_enable_notify)(struct ufs_hba *,
                                     enum ufs_notify_change_status);
        int     (*link_startup_notify)(struct ufs_hba *,
-- 
2.11.0

Reply via email to