This driver patch includes a core driver and glue drivers for pci and platform for the DesignWare UFS Host IP. Signed-off-by: Joao Pinto <jpinto@xxxxxxxxxxxx> --- .../devicetree/bindings/ufs/dwc_ufshcd-pltfm.txt | 17 + MAINTAINERS | 7 + drivers/scsi/Makefile | 1 + drivers/scsi/ufs/Kconfig | 3 + drivers/scsi/ufs/dwufs/Kconfig | 53 + drivers/scsi/ufs/dwufs/Makefile | 3 + drivers/scsi/ufs/dwufs/dwc_ufshcd-core.c | 4000 ++++++++++++++++++++ drivers/scsi/ufs/dwufs/dwc_ufshcd-core.h | 61 + drivers/scsi/ufs/dwufs/dwc_ufshcd-hci.h | 958 +++++ drivers/scsi/ufs/dwufs/dwc_ufshcd-mphy.h | 55 + drivers/scsi/ufs/dwufs/dwc_ufshcd-pci.c | 280 ++ drivers/scsi/ufs/dwufs/dwc_ufshcd-pltfm.c | 168 + drivers/scsi/ufs/dwufs/dwc_ufshcd-scsi.c | 780 ++++ drivers/scsi/ufs/dwufs/dwc_ufshcd-scsi.h | 29 + drivers/scsi/ufs/dwufs/dwc_ufshcd-ufs.h | 457 +++ drivers/scsi/ufs/dwufs/dwc_ufshcd-unipro.h | 40 + 16 files changed, 6912 insertions(+) create mode 100644 Documentation/devicetree/bindings/ufs/dwc_ufshcd-pltfm.txt create mode 100644 drivers/scsi/ufs/dwufs/Kconfig create mode 100644 drivers/scsi/ufs/dwufs/Makefile create mode 100644 drivers/scsi/ufs/dwufs/dwc_ufshcd-core.c create mode 100644 drivers/scsi/ufs/dwufs/dwc_ufshcd-core.h create mode 100644 drivers/scsi/ufs/dwufs/dwc_ufshcd-hci.h create mode 100644 drivers/scsi/ufs/dwufs/dwc_ufshcd-mphy.h create mode 100644 drivers/scsi/ufs/dwufs/dwc_ufshcd-pci.c create mode 100644 drivers/scsi/ufs/dwufs/dwc_ufshcd-pltfm.c create mode 100644 drivers/scsi/ufs/dwufs/dwc_ufshcd-scsi.c create mode 100644 drivers/scsi/ufs/dwufs/dwc_ufshcd-scsi.h create mode 100644 drivers/scsi/ufs/dwufs/dwc_ufshcd-ufs.h create mode 100644 drivers/scsi/ufs/dwufs/dwc_ufshcd-unipro.h diff --git a/Documentation/devicetree/bindings/ufs/dwc_ufshcd-pltfm.txt b/Documentation/devicetree/bindings/ufs/dwc_ufshcd-pltfm.txt new file mode 100644 index 0000000..cd387e4 --- /dev/null +++ b/Documentation/devicetree/bindings/ufs/dwc_ufshcd-pltfm.txt @@ -0,0 +1,17 @@ +* Universal Flash Storage (UFS) DesignWare Host Controller + +DWC_UFSHC nodes are defined to describe on-chip UFS host controllers. +Each UFS controller instance should have its own node. + +Required properties: +- compatible : compatible list, contains "snps,dwc_ufshcd" +- reg : <registers mapping> +- interrupts : <interrupt mapping for UFS host controller IRQ> + +Example: + dwc_ufshcd@0xD0000000 { + compatible = "synopsys,dwc_ufshcd"; + reg = < 0xD0000000 0x10000 >; + interrupts = < 24 >; + }; + diff --git a/MAINTAINERS b/MAINTAINERS index 2d3d55c..a83a5d7 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -10534,6 +10534,13 @@ S: Supported F: Documentation/scsi/ufs.txt F: drivers/scsi/ufs/ +DESIGNWARE UNIVERSAL FLASH STORAGE HOST CONTROLLER DRIVER +M: Joao Pinto <Joao.Pinto@xxxxxxxxxxxx> +L: linux-scsi@xxxxxxxxxxxxxxx +S: Supported +F: Documentation/scsi/ufs.txt +F: drivers/scsi/ufs/dwufs/ + UNSORTED BLOCK IMAGES (UBI) M: Artem Bityutskiy <dedekind1@xxxxxxxxx> M: Richard Weinberger <richard@xxxxxx> diff --git a/drivers/scsi/Makefile b/drivers/scsi/Makefile index 91209e3..eddfddc 100644 --- a/drivers/scsi/Makefile +++ b/drivers/scsi/Makefile @@ -108,6 +108,7 @@ obj-$(CONFIG_MEGARAID_SAS) += megaraid/ obj-$(CONFIG_SCSI_MPT2SAS) += mpt2sas/ obj-$(CONFIG_SCSI_MPT3SAS) += mpt3sas/ obj-$(CONFIG_SCSI_UFSHCD) += ufs/ +obj-$(CONFIG_SCSI_DWC_UFS_HOST) += ufs/dwufs/ obj-$(CONFIG_SCSI_ACARD) += atp870u.o obj-$(CONFIG_SCSI_SUNESP) += esp_scsi.o sun_esp.o obj-$(CONFIG_SCSI_GDTH) += gdth.o diff --git a/drivers/scsi/ufs/Kconfig b/drivers/scsi/ufs/Kconfig index e945383..9eac4be 100644 --- a/drivers/scsi/ufs/Kconfig +++ b/drivers/scsi/ufs/Kconfig @@ -83,3 +83,6 @@ config SCSI_UFS_QCOM Select this if you have UFS controller on QCOM chipset. If unsure, say N. + +source "drivers/scsi/ufs/dwufs/Kconfig" + diff --git a/drivers/scsi/ufs/dwufs/Kconfig b/drivers/scsi/ufs/dwufs/Kconfig new file mode 100644 index 0000000..747578f --- /dev/null +++ b/drivers/scsi/ufs/dwufs/Kconfig @@ -0,0 +1,53 @@ +# +# UFS Host driver for Synopsys Designware Core +# +# Copyright (C) 2015-2016 Synopsys, Inc. (www.synopsys.com) +# +# Authors: Joao Pinto <jpinto@xxxxxxxxxxxx> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# + +config SCSI_DWC_UFS_HOST + bool "Designware UFS Host Controller" + depends on SCSI + +config SCSI_DWC_UFS_PCI + tristate "PCI bus based Designware UFS Controller support" + depends on SCSI_DWC_UFS_HOST && PCI + +config SCSI_DWC_UFS_64BIT + bool "Use 64-bit addressing" + depends on SCSI_DWC_UFS_PCI + ---help--- + Enables 64-bit addressing support if available; otherwise reverts to + 32-bit addressing mode. + +config SCSI_DWC_UFS_PLATFORM + tristate "Platform bus based Designware UFS Controller support" + depends on SCSI_DWC_UFS_HOST + +config SCSI_DWC_UFS_MPHY_TC + bool "Synopsys MPHY testchip as physical interface" + depends on SCSI_DWC_UFS_HOST + ---help--- + Enables the driver to use Synopsys MPHY testchip as physical interface. + Without this option, it assumes TMPI interface as the underlying PHY + interface. + +config SCSI_DWC_UFS_MPHY_TC_GEN2 + bool "Synopsys MPHY testchip Gen2 as physical interface" + depends on SCSI_DWC_UFS_MPHY_TC + ---help--- + Enables the driver to work with Synopsys MPHY Test Chips on both host + and device side. + +config SCSI_DWC_UFS_40BIT_RMMI + bool "40-bit RMMI interface" + depends on SCSI_DWC_UFS_MPHY_TC + ---help--- + Enables the driver to use 40-bit RMMI interface. + + diff --git a/drivers/scsi/ufs/dwufs/Makefile b/drivers/scsi/ufs/dwufs/Makefile new file mode 100644 index 0000000..31aeedd --- /dev/null +++ b/drivers/scsi/ufs/dwufs/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_SCSI_DWC_UFS_HOST) += dwc_ufshcd-scsi.o dwc_ufshcd-core.o +obj-$(CONFIG_SCSI_DWC_UFS_PCI) += dwc_ufshcd-pci.o +obj-$(CONFIG_SCSI_DWC_UFS_PLATFORM) += dwc_ufshcd-pltfm.o diff --git a/drivers/scsi/ufs/dwufs/dwc_ufshcd-core.c b/drivers/scsi/ufs/dwufs/dwc_ufshcd-core.c new file mode 100644 index 0000000..1a21f1a --- /dev/null +++ b/drivers/scsi/ufs/dwufs/dwc_ufshcd-core.c @@ -0,0 +1,4000 @@ +/* + * UFS Host driver for Synopsys Designware Core + * + * Copyright (C) 2015-2016 Synopsys, Inc. (www.synopsys.com) + * + * Authors: Manjunath Bettegowda <manjumb@xxxxxxxxxxxx>, + * Joao Pinto <jpinto@xxxxxxxxxxxx> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/delay.h> /* Linux delays */ + +#include <asm/byteorder.h> /* cpu_to_bexx(), cpu_to_lexx()*/ +#include <scsi/scsi_host.h> /* Linux Scsi Subsytem */ +#include <scsi/scsi_tcq.h> /* Linux scsi_init_shared_tag_map() */ + +#include "dwc_ufshcd-ufs.h" +#include "dwc_ufshcd-hci.h" +#include "dwc_ufshcd-scsi.h" +#include "dwc_ufshcd-core.h" +#include "dwc_ufshcd-unipro.h" +#include "dwc_ufshcd-mphy.h" + +#define dwc_ufs_hostname(x) (dev_name((x)->gdev)) + +static struct dwc_ufs_hba *static_ufs_hba; + +/** + * dwc_ufs_readl + * The Low level function to read long or double word (32 bit value). + * @param[in] Base address pointer + * @param[in] Offset from the base + * \return Returns the double word value from provided address + */ +static inline u32 dwc_ufs_readl(void *ioaddr, u32 offset) +{ + return readl(ioaddr + offset); +} + +/** + * dwc_ufs_writel + * The Low level function to write a double word or long (32 bit value). + * @param[in] Byte data to be written + * @param[in] Base address pointer + * @param[in] Offset from the base + */ +static inline void dwc_ufs_writel(u32 data, void *ioaddr, u32 offset) +{ + writel(data, (ioaddr + offset)); +} + +/** + * dwc_ufshcd_get_utrd_ocs() + * Get the OCS value from utrd + * @lrb: pointer to local command reference block containing + * required UTRD + * + * Returns the OCS field in the UTRD + */ +static inline u8 dwc_ufshcd_get_utrd_ocs(struct dwc_ufs_hcd_lrb *lrb) +{ + return lrb->utrd->ocs; +} + +/** + * dwc_ufshcd_get_xfer_req_free_slot() + * This function returns the first (lower index) free slot available to issue + * Device management command. + * @hba: Pointer to drivers structure + * + * Returns nutrs number if all slots or full else returns the index lower free + * slot on if available + */ +static inline int dwc_ufshcd_get_xfer_req_free_slot(struct dwc_ufs_hba *hba) +{ + return find_first_zero_bit(&hba->outstanding_dm_requests, hba->nutrs); +} + +/** + * dwc_ufshcd_send_dm_req_command() + * Send Device management command by setting the door bell for appropirate + * slot. + * Note: This driver architectures treats DM command outside data transfer + * command. Dvice management command is supposed to be sent when there is + * no data transfer commands are pending; If this is not taken care of + * result/behavior of the driver is undefined. + * @hba: Pointer to drivers structure + * @free_slot: index of free slot in transfer req list + * + */ +static inline void dwc_ufshcd_send_dm_req_command(struct dwc_ufs_hba *hba, + unsigned int free_slot) +{ + set_bit(free_slot, &hba->outstanding_dm_requests); + dwc_ufs_writel((1 << free_slot), hba->mmio_base, DWC_UFS_REG_UTRLDBR); +} + +/** + * dwc_ufshcd_send_tm_req_command() + * Send TM Request command by setting the door bell for appropirate + * slot in UTMR doorbell register. + * @hba: Pointer to drivers structure + * @free_slot: index of free slot in TM Req Queue + * + */ +static inline void dwc_ufshcd_send_tm_req_command(struct dwc_ufs_hba *hba, + unsigned int free_slot) +{ + set_bit(free_slot, &hba->outstanding_tm_tasks); + dwc_ufs_writel((1 << free_slot), hba->mmio_base, DWC_UFS_REG_UTMRLDBR); +} + +/** + * dwc_ufshcd_get_tm_free_slot + * This function returns the first (lower index) free slot available to issue + * task management command. + * @hba: Pointer to drivers structure + * + * Returns nutmrs number if all slots or full else returns the index lower free + * slot on if available + */ +static inline int dwc_ufshcd_get_tm_free_slot(struct dwc_ufs_hba *hba) +{ + return find_first_zero_bit(&hba->outstanding_tm_tasks, hba->nutmrs); +} + +/** + * dwc_ufshcd_send_xfer_req_command() + * Send SCSI/Native UFS Commands (not device management command) by setting + * the door bell for appropriate slot + * Note: This driver architectures treats DM command outside data transfer + * command. Dvice management command is supposed to be sent when there is + * no data transfer commands are pending; If this is not taken care of + * result/behavior of the driver is undefined. + * @hba: Pointer to drivers structure + * @task_tag: Task tag of the command; used as slot index + * + */ +void dwc_ufshcd_send_xfer_req_command(struct dwc_ufs_hba *hba, + unsigned int task_tag) +{ + set_bit(task_tag, &hba->outstanding_xfer_reqs); + dwc_ufs_writel((1 << task_tag), hba->mmio_base, DWC_UFS_REG_UTRLDBR); +} + +/** + * dwc_ufshcd_get_hba_handle() + * This function returns the handle to hba. + * The need for this approach of maintining the static reference to hba + * arises to support debugging. + * + * Returns reference to drivers private structure + */ +struct dwc_ufs_hba *dwc_ufshcd_get_hba_handle(void) +{ + return static_ufs_hba; +} + +/** + * dwc_ufshcd_prepare_xfer_cmd_upiu() + * Prepare UFS Protocol Information Unit(UPIU) + * Generally this function is called by adaptation layers such as SCSI. + * This function still knows about the scsi information. + * This function + * Updates the UTRD with: + * - command type and flags + * - ocs is programmed to invalid status + * Updates the UPIU with: + * - transaction type + * - flags + * - lun + * - task_tag(For SCSI Midlayer provides the unique tag) + * - command set type + * - expected transfer length + * Copies the CDB to cmd_upiu->cdb + * @lrb - pointer to local reference block + * + */ +void dwc_ufshcd_prepare_xfer_cmd_upiu(struct dwc_ufs_hcd_lrb *lrb) +{ + struct dwc_ufs_utrd *utrd; + struct dwc_ufs_cmd_upiu *cmd_upiu; + + cmd_upiu = lrb->cmd_upiu; + + utrd = lrb->utrd; + + switch (lrb->command_type) { + case DWC_UTRD_CMD_TYPE_SCSI: + case DWC_UTRD_CMD_TYPE_UFS_STORAGE: + /* Set Command Type and Flags */ + utrd->ct_and_flags = lrb->data_direction | + lrb->command_type; + + /* Set OCS to invalid value */ + utrd->ocs = OCS_INVALID_COMMAND_STATUS; + + /* UPIU Command formation */ + memset(cmd_upiu, 0, sizeof(struct dwc_ufs_cmd_upiu)); + cmd_upiu->trans_type = UPIU_TRANS_CODE_CMD; + cmd_upiu->flags = lrb->read_write_flags; + cmd_upiu->lun = lrb->lun; + cmd_upiu->task_tag = lrb->task_tag; + + cmd_upiu->cmd_set_type = UPIU_CMD_SET_TYPE_SCSI; + + /* Total EHS length and Data segment length will be + * zero + */ + cmd_upiu->tot_ehs_len = 0; + cmd_upiu->data_seg_len = 0; + + /* Data Transfer Length is given by scsi command */ + cmd_upiu->exp_data_xfer_len = + cpu_to_be32(lrb->transfer_size); + /* Copy the CDB */ + dwc_ufshcd_copy_cdb(cmd_upiu->cdb, lrb); + + /* As per JESD220 ModeSense10 DBD should be '1' */ + if (cmd_upiu->cdb[0] == 0x5a) + cmd_upiu->cdb[1] |= 0x08; /* Set DBD to ‘1’ */ + break; + default: + break; + } +} + +/** + * dwc_ufshcd_clear_xfer_req() + * This function is to abort the pending task by clearing the UTP Transfer + * Request list clear register forcibly. + * @hba: Pointer to drivers structure + * @slot_index: slot_index to be cleared + * + */ +void dwc_ufshcd_clear_xfre_req(struct dwc_ufs_hba *hba, u32 slot_index) +{ + dwc_ufs_writel(~(1 << slot_index), hba->mmio_base, + DWC_UFS_REG_UTRLCLR); +} + +/** + * dwc_ufshcd_clear_tm_task_req() + * This function is to abort the pending task by clearing the UTP Task + * management request list celar register forcibly. + * @hba: Pointer to drivers structure + * @slot_index: slot_index to be cleared + */ +void dwc_ufshcd_clear_tm_task_req(struct dwc_ufs_hba *hba, u32 slot_index) +{ + dwc_ufs_writel(~(1 << slot_index), hba->mmio_base, + DWC_UFS_REG_UTMRLCLR); +} + +/** + * dwc_ufshcd_tm_req_completion_handler() + * This function handles task management request completion. The function + * executes when wait queue is woken up from ISR or timeout happens. + * it clears + * - the outstanding request and the condition for the slot in question + * - updates the result value based on OCS and response + * @hba: Pointer to drivers structure + * @tm_slot: index of the completed request + * + * Returns zero on success or non zero value on failure + */ +int dwc_ufshcd_tm_req_completion_handler(struct dwc_ufs_hba *hba, u32 tm_slot) +{ + int result = SUCCESS; + struct dwc_ufs_utmrd *utmrd; + struct dwc_ufs_tm_resp_upiu *tm_resp_upiu; + struct dwc_ufs_tm_desc_hdr *tm_desc_hdr; + unsigned long flags_irq; + u8 ocs; + u8 response = 0; + + spin_lock_irqsave(hba->shost->host_lock, flags_irq); + + /* Clear completed tasks from outstanding_tasks */ + clear_bit(tm_slot, &hba->outstanding_tm_tasks); + clear_bit(tm_slot, &hba->dwc_ufshcd_tm_condition); + + utmrd = hba->utmrl_base_addr; + tm_desc_hdr = &(utmrd->tm_desc_hdr); + ocs = tm_desc_hdr->ocs; + + if (ocs == OCS_SUCCESS) { + tm_resp_upiu = &(utmrd[tm_slot].tm_resp_upiu); + response = tm_resp_upiu->response; + + if ((response != UPIU_TM_FUNC_COMPL) || + (response == UPIU_TM_FUNC_SUCCEEDED)) { + result = FAILED; + } + } else { + result = FAILED; + } + + spin_unlock_irqrestore(hba->shost->host_lock, flags_irq); + return result; +} + +/** + * dwc_ufshcd_issue_tm_cmd + * Issues task management commands to controller + * This function calls the function to set task managemetn request + * descriptor doorbell after + * - setting up task management request descriptor for the slot + * - preparing the task management request upiu + * After sending the command, the control waits on wait queue + * Once control resumes, dwc_ufshcd_tm_req_completion_handler() function is + * called. Return value from the handler function is returned as is it to the + * caller. + * @hba: Pointer to drivers structure + * @lrb: pointer to local reference block + * @tm_fn: task management function to be issued + * + * Note: Not to be called when blocking is not allowed (e.g: ISR) + * + * Returns 0 on success, non zero value on failure + */ +int dwc_ufshcd_issue_tm_cmd(struct dwc_ufs_hba *hba, + struct dwc_ufs_hcd_lrb *lrb, u8 tm_fn) +{ + int result = 0; + int free_tm_slot = 0; + struct dwc_ufs_utmrd *utmrd; + struct dwc_ufs_tm_req_upiu *tm_req_upiu; + struct dwc_ufs_tm_desc_hdr *tm_desc_hdr; + struct Scsi_Host *shost; + unsigned long flags_irq; + + shost = hba->shost; + + spin_lock_irqsave(shost->host_lock, flags_irq); + + /* If task management queue is full */ + free_tm_slot = dwc_ufshcd_get_tm_free_slot(hba); + if (free_tm_slot >= hba->nutmrs) { + spin_unlock_irqrestore(shost->host_lock, flags_irq); + pr_err("TM queue is full\n"); + result = FAILED; + goto err_tm_queue_is_full; + } + + /* Get the base address of TM Req list base address */ + utmrd = hba->utmrl_base_addr; + /* Get the descriptor at the free slot */ + utmrd += free_tm_slot; + + /* Configure task management request descriptor */ + tm_desc_hdr = &(utmrd->tm_desc_hdr); + tm_desc_hdr->intr_flag = DWC_UTMRD_INTR; + tm_desc_hdr->ocs = OCS_INVALID_COMMAND_STATUS; + + /* Configure task management request UPIU */ + tm_req_upiu = &(utmrd->tm_req_upiu); + tm_req_upiu->trans_type = UPIU_TRANS_CODE_TASK_REQ; + tm_req_upiu->flags = 0; + tm_req_upiu->lun = lrb->lun; + tm_req_upiu->task_tag = lrb->task_tag; + tm_req_upiu->tm_fn = tm_fn; + tm_req_upiu->tot_ehs_len = 0; + tm_req_upiu->input_param_1[3] = lrb->lun; + tm_req_upiu->input_param_2[3] = lrb->task_tag; + + /* send command to the controller */ + dwc_ufshcd_send_tm_req_command(hba, free_tm_slot); + + spin_unlock_irqrestore(shost->host_lock, flags_irq); + + /* wait until the task management command is completed */ + /*TODO: Is this architecture can blocks multiple tm requests? */ + result = + wait_event_interruptible_timeout(hba->dwc_ufshcd_tm_wait_queue, + (test_bit(free_tm_slot, + &hba->dwc_ufshcd_tm_condition) != 0), + DWC_UFS_TM_TIMEOUT); + + if (result == 0) { + pr_err("Task Management Req Timed-out\n"); + + /* Clear TM request, Condition, doorbell */ + clear_bit(free_tm_slot, &hba->dwc_ufshcd_tm_condition); + clear_bit(free_tm_slot, &hba->outstanding_tm_tasks); + dwc_ufshcd_clear_tm_task_req(hba, free_tm_slot); + + result = FAILED; + goto err_tm_req_timeout; + } else + result = + dwc_ufshcd_tm_req_completion_handler(hba, free_tm_slot); + +err_tm_req_timeout: +err_tm_queue_is_full: + return result; +} + +/** + * dwc_ufshcd_reset_hba() + * This is generally called by higher layer e.g scsi to abort all + * outstanding commands and do the host controller reset and reinitialization + * This function + * - Blocks the higher layer requests (scsi here) + * - driver state is changed to reset state + * - stop the host by clearing the enable bit in HCE + * - Abort all outstanding transfers + * - starts initialization be enabling the HC + * - program the clock divider + * - Reinitialize the descriptor pointers to HC registers + * - Initiate link startup + * - Do connection setup + * - Kick start HBA + * - Initialize ufs device + * - kick start stack + * @hba: Pointer to drivers structure + * + * Returns Zero on success, non zero value on failure + */ +int dwc_ufshcd_reset_hba(struct dwc_ufs_hba *hba) +{ + int result = 0; + int link_startup_retries = 0; + struct dwc_ufs_hcd_lrb *lrb; + unsigned long flags_irq; + int tag; + + spin_lock_irqsave(hba->shost->host_lock, flags_irq); + dwc_ufshcd_set_hcd_state(hba, DWC_UFSHCD_STATE_RESET); + + /* send controller to reset state */ + dwc_ufshcd_stop_host(hba); + + /* This is delay is used as stop host doesnot wait until + * HCE bit is zero */ + mdelay(100); + spin_unlock_irqrestore(hba->shost->host_lock, flags_irq); + + /* Clear any interrupts if any*/ + dwc_ufs_readl(hba->mmio_base, DWC_UFS_REG_IS); + + /* FOR UFS2.0 above Host controllers, the interrupt status is Write + * to Clear + */ + if (hba->dwc_ufs_version == DWC_UFS_HC_VERSION_2_0) + dwc_ufs_writel(0xffffffff, hba->mmio_base, DWC_UFS_REG_IS); + + /* abort outstanding commands */ + for (tag = 0; tag < hba->nutrs; tag++) { + if (test_bit(tag, &hba->outstanding_xfer_reqs)) { + lrb = &hba->lrb[tag]; + dwc_ufshcd_unmap_dma(lrb); + lrb->scmd->result = DID_RESET << 16; + lrb->scmd->scsi_done(lrb->scmd); + lrb->scmd = NULL; + } + } + + /*TODO: is scanning over outstanding tm tasks required? */ + /* clear outstanding request/task bit maps */ + hba->outstanding_xfer_reqs = 0; + hba->outstanding_dm_requests = 0; + hba->outstanding_tm_tasks = 0; + + for (link_startup_retries = 0; + link_startup_retries < DWC_UFS_RETRY_COUNT_LINKSTARTUP; + link_startup_retries++) { + + /* start the initialization process */ + if (dwc_ufshcd_host_enable(hba)) + return FAILED; + + /* Program Clock Divider Value */ + dwc_ufshcd_program_clk_div(hba, DWC_UFSHCD_CLK_DIV_125); + + if (dwc_ufshcd_initialize_hba_desc_pointers(hba)) { + pr_err("Desc Base-Address Initialization Failed\n"); + return FAILED; + } + +#ifdef CONFIG_SCSI_DWC_UFS_MPHY_TC + /* Initiate Synopsys MPHY */ + result = dwc_ufs_dme_setup_snps_mphy(hba, + DWC_UFS_HCD_DME_INTR_DISABLE); + if (result != 0) + goto err_dme_setup_snps_mphy; + + pr_info("SNPS Mphy Setup Successful\n"); +#endif + /* Initiate Unipro Link Startup Procedure */ + result = dwc_ufs_dme_linkstartup(hba, + DWC_UFS_HCD_DME_INTR_ENABLE); + if (result != 0) + pr_info("Link Startup Failed %d times\n", + link_startup_retries+1); + + /* IF the UIC is busy no point in retrying */ + if (result == -EIO) + goto err_dme_link_startup_cmd_sending; + else if (result == 0) { + result = dwc_ufshcd_get_dme_command_results(hba); + if (result == 0) { + + dwc_ufs_dme_get(hba, UFS_LINK_STATUS, 0, + DWC_UFS_HCD_DME_INTR_DISABLE); + + dwc_ufshcd_get_dme_command_results(hba); + if (hba->active_dme_cmd.argument3 != 2) + goto err_dme_link_startup; + + break; + } + } + } + + if (link_startup_retries == DWC_UFS_RETRY_COUNT_LINKSTARTUP) { + pr_info("LinkStartup Failed after %d attempts\n", + DWC_UFS_RETRY_COUNT_LINKSTARTUP); + goto err_dme_link_startup; + } + + /* Update the Link information */ + pr_debug("Updating the Link Informatin\n"); + result = dwc_ufs_update_link_info(hba, DWC_UFS_HCD_DME_INTR_DISABLE); + + /* Do the connection setup on both Host and Device + * This may be redundant operation with any UFS device, but executing + * this is no harm */ + result = dwc_ufs_do_dme_connection_setup(hba, + DWC_UFS_HCD_DME_INTR_DISABLE); + if (result != 0) + goto err_dme_connection_setup; + + /* Kick start host bus adapter for data transfer operation */ + if (dwc_ufshcd_kick_start_hba(hba) != 0) { + pr_err("Hba is not operational\n"); + goto err_ufs_kick_start_hba; + } + + /* Initialize the device */ + result = dwc_ufs_initialize_ufs_device(hba); + + if (result != 0) + goto err_ufs_initialize_device; + + /* Set the state of the hba as operational */ + dwc_ufshcd_set_hcd_state(hba, DWC_UFSHCD_STATE_OPERATIONAL); + + goto successful_return; + +#ifdef CONFIG_SCSI_DWC_UFS_MPHY_TC +err_dme_setup_snps_mphy: +#endif +err_dme_link_startup: +err_dme_link_startup_cmd_sending: +err_ufs_kick_start_hba: +err_ufs_initialize_device: +err_dme_connection_setup: + dwc_ufshcd_set_hcd_state(hba, DWC_UFSHCD_STATE_ERROR); +successful_return: + return result; +} + +/** + * dwc_ufshcd_read_caps() + * Read DWC UFS controller capabilities. + * the driver's private element are updated with supported number of transfer + * request and task management slots. + * @hba: Pointer to drivers structure + * + */ +void dwc_ufshcd_read_caps(struct dwc_ufs_hba *hba) +{ + hba->caps = dwc_ufs_readl(hba->mmio_base, DWC_UFS_REG_CAP); + + /* Update number of UTP Task management Request Slots(NUTMRS) */ + hba->nutrs = (hba->caps & DWC_UFS_NUTRS_MASK) + 1; + + /* Update number of of UTP Transfer Request Slots (NUTRS) */ + hba->nprtts = ((hba->caps & DWC_UFS_NUTRS_MASK) >> + DWC_UFS_NPRTTS_SHIFT) + 1; + + /* Update number of of UTP Transfer Request Slots (NUTMRS) */ + hba->nutmrs = ((hba->caps & DWC_UFS_NUTMRS_MASK) >> + DWC_UFS_NUTMRS_SHIFT) + 1; + + /* Update Auto Hibernate Support */ + hba->autoh8 = ((hba->caps & DWC_UFS_AUTOH8) >> DWC_UFS_AUTOH8_SHIFT); + + /* Update 64 bit Addressing Support */ + hba->as64 = ((hba->caps & DWC_UFS_64AS) >> DWC_UFS_64AS_SHIFT); + + /* Update Out Of Order Data Delivery Support */ + hba->oodds = ((hba->caps & DWC_UFS_OODDS) >> DWC_UFS_OODDS_SHIFT); + + /* UIC DME TEST MODE support */ + hba->uicdmetms = ((hba->caps & DWC_UFS_UICDMETMS) >> + DWC_UFS_UICDMETMS_SHIFT); +} + +/** + * dwc_ufshcd_get_dwc_ufs_version() + * Gets the UFS version supported by the Host controller + * @hba: Pointer to drivers structure + * + * Returns DWC UFS HC version + */ +u32 dwc_ufshcd_get_dwc_ufs_version(struct dwc_ufs_hba *hba) +{ + return dwc_ufs_readl(hba->mmio_base, DWC_UFS_REG_VER); +} + +/** + * dwc_ufshcd_configure_interface_memory() + * This function configures interface memory + * - For every UTRD, + * - initializes the Command UPIU base address (Lo and High) + * - response upiu length and offset + * - prdt offset + * - Some key fields are updated in respective lrbs + * - utrd addresses + * - command upiu addresses + * - response upiu addresses + * - prdt base address + * @hba: Pointer to drivers structure + * + */ +void dwc_ufshcd_configure_interface_memory(struct dwc_ufs_hba *hba) +{ + int req; + struct dwc_ufs_utrd *utrl; /* Pointer to UTR List */ + struct dwc_ufs_ucd *ucdl; /* Pointer to UCD List */ + dma_addr_t ucdl_dma_addr; + dma_addr_t ucd_dma_addr; + u32 response_upiu_length; + u32 response_upiu_offset; + u32 prdt_offset; + u32 ucd_size; + + utrl = hba->utrl_base_addr; + ucdl = hba->ucdl_base_addr; + + /* Response offset and prdt offset are common for all the + * UTP Transfer Request + */ + response_upiu_offset = offsetof(struct dwc_ufs_ucd, resp_upiu); + response_upiu_length = DWC_UCD_ALIGN; + prdt_offset = offsetof(struct dwc_ufs_ucd, prdt); + + ucd_size = sizeof(struct dwc_ufs_ucd); + ucdl_dma_addr = hba->ucdl_dma_addr; /* UCD list Base address */ + + /* For as many UTP Transfer Requests in the list */ + for (req = 0; req < hba->nutrs; req++) { + + /* Configure UTRD with UCD base address */ + ucd_dma_addr = ucdl_dma_addr + (ucd_size * req); + utrl[req].ucdba = cpu_to_le32(lower_32_bits(ucd_dma_addr)); + utrl[req].ucdbau = cpu_to_le32(upper_32_bits(ucd_dma_addr)); + + /* Configure Response UPIU offset and length */ + /* These fields are in Dword format */ + utrl[req].resp_upiu_offset = + cpu_to_le16(response_upiu_offset >> 2); + utrl[req].resp_upiu_length = + cpu_to_le16(response_upiu_length >> 2); + + /* Configure prdt length and offset */ + utrl[req].prdt_offset = cpu_to_le16(prdt_offset >> 2); + utrl[req].prdt_length = cpu_to_le16(0); + + /* Update LRB */ + hba->lrb[req].utrd = (utrl + req); + hba->lrb[req].cmd_upiu = + (struct dwc_ufs_cmd_upiu *)(ucdl + req); + hba->lrb[req].resp_upiu = + (struct dwc_ufs_resp_upiu *)(ucdl[req].resp_upiu); + hba->lrb[req].prdt = + (struct dwc_ufs_prd *) (ucdl[req].prdt); + } + + pr_debug("Interface memory Configured\n"); +} + +/** + * dwc_ufshcd_free_interface_memory() + * Frees the allocated interface memory + * - Free DMA'able memory allocated for UTP command tables + * - Free DMA'able memory allocated for + * - UTP transfer descriptors + * - UTP task management descriptors + * @hba: Pointer to drivers structure + * + */ +void dwc_ufshcd_free_interface_memory(struct dwc_ufs_hba *hba) +{ + size_t utrl_size, utmrl_size, ucdl_size; + + /* Unconditionally free the LRB memory */ + kfree(hba->lrb); + + /* Free UCD memory list/block */ + if (hba->ucdl_base_addr != NULL) { + ucdl_size = (sizeof(struct dwc_ufs_ucd) * hba->nutrs); + dma_free_coherent(hba->gdev, ucdl_size, hba->ucdl_base_addr, + hba->ucdl_dma_addr); + } + + /* Free UTRDL memory */ + if (hba->utrl_base_addr != NULL) { + utrl_size = (sizeof(struct dwc_ufs_utrd) * hba->nutrs); + dma_free_coherent(hba->gdev, utrl_size, hba->utrl_base_addr, + hba->utrl_dma_addr); + } + + /* Free UTMRDL memory */ + if (hba->utmrl_base_addr != NULL) { + utmrl_size = (sizeof(struct dwc_ufs_utmrd) * hba->nutrs); + dma_free_coherent(hba->gdev, utmrl_size, hba->utmrl_base_addr, + hba->utmrl_dma_addr); + } +} + +/** + * dwc_ufshcd_alloc_interface_memory() + * Allocate memory for Host controller interface. + * Following are the memories allocation by this function. + * - DMA'able memory for UTP transfer request descriptor list + * - DMA'able memory for UTP task management request list + * - DMA'able memory for command table + * - Command UPIU's + * - Response UPIU's + * - PRD tables + * - Non-DMA'able memory for local reference blocks; House keeping + * @hba: Pointer to drivers structure + * + * Returns 0 for success, non-zero in case of failure + */ +int dwc_ufshcd_alloc_interface_memory(struct dwc_ufs_hba *hba) +{ + size_t utrl_size, utmrl_size, ucdl_size; + + /* + * Allocate Dma'able memory for UTP Transfer Request List + * UFS spec constraints: Base of List shoudl be aligned to 1024 byte + * (1K boundary) + */ + utrl_size = (sizeof(struct dwc_ufs_utrd) * hba->nutrs); + hba->utrl_base_addr = dma_alloc_coherent(hba->gdev, utrl_size, + &hba->utrl_dma_addr, GFP_KERNEL); + + if (hba->utrl_base_addr == NULL) { + pr_warn("UTRL Memory Allocation Failed\n"); + goto err_utrl_alloc; + } + + /* Allocate Dma'able memory for UTP Task management Request list + * UFS spec constraints: base of list should be aligned to 1024 bytes + * (1K boundary) + */ + utmrl_size = (sizeof(struct dwc_ufs_utmrd) * hba->nutmrs); + hba->utmrl_base_addr = dma_alloc_coherent(hba->gdev, utmrl_size, + &hba->utmrl_dma_addr, GFP_KERNEL); + + if (hba->utmrl_base_addr == NULL) { + pr_warn("UTMRL Memory Allocation Failed\n"); + goto err_utmrl_alloc; + } + + /* Allocate Dma'able memory for UTP Command Descriptor structures (UCDs) + * Every Command Descriptor block should be aligned to 128 bytes + * (6:0 being) reserved + */ + ucdl_size = (sizeof(struct dwc_ufs_ucd) * hba->nutrs); + hba->ucdl_base_addr = dma_alloc_coherent(hba->gdev, ucdl_size, + &hba->ucdl_dma_addr, GFP_KERNEL); + + if (hba->ucdl_base_addr == NULL) { + pr_warn("UCD Memory Allocation Failed\n"); + goto err_ucdl_alloc; + } + + /* Allocate memory for local reference block */ + hba->lrb = kcalloc(hba->nutrs, sizeof(struct dwc_ufs_hcd_lrb), + GFP_KERNEL); + + if (hba->lrb == NULL) { + pr_err("LRB Memory Allocation Failed\n"); + goto err_lrb_alloc; + } + + return 0; + +err_ucdl_alloc: +err_utrl_alloc: +err_utmrl_alloc: +err_lrb_alloc: + dwc_ufshcd_free_interface_memory(hba); + return -ENOMEM; +} + +/** + * dwc_ufshcd_is_device_present() + * This function returns '1' if device is present (link is up) + * '0' if device is not present (link is down) + * + * Returns bool value of device present status + */ +bool dwc_ufshcd_is_device_present(u32 hcs) +{ + return(hcs & DWC_UFS_DP); +} + +/** + * dwc_ufshcd_is_utrl_ready() + * This function returns '1' if UTP transfer list is ready + * '0' if UTP transfer list is not ready + * + * Returns bool value of UTP transfer list status + */ +bool dwc_ufshcd_is_utrl_ready(u32 hcs) +{ + return(hcs & DWC_UFS_UTRL_READY); +} + +/** + * dwc_ufshcd_is_utmrl_ready() + * This function returns '1' if UTP task management list is ready + * '0' if UTP task management list is not ready + * + * Returns bool value of UTP task management list status + */ +bool dwc_ufshcd_is_utmrl_ready(u32 hcs) +{ + return(hcs & DWC_UFS_UTMRL_READY); +} + +/** + * dwc_ufshcd_is_dme_sap_ready() + * Returns true if DME is ready to accept uio command + * + * Returns bool value of DME command acceptance status + */ +bool dwc_ufshcd_is_dme_sap_ready(u32 hcs) +{ + return(hcs & DWC_UFS_UIC_CMD_READY); +} + +/** + * dwc_ufshcd_is_host_controller_ready() + * This function returns the status of + * - device present status + * - UTP transfer list status + * - UTP task management list status + * - dme sap status + * + * Returns '0' if all the above are true, else negative error number + */ +int dwc_ufshcd_is_host_controller_ready(struct dwc_ufs_hba *hba) +{ + u32 hcs = dwc_ufs_readl(hba->mmio_base, DWC_UFS_REG_HCS); + + /* Check if Device Present */ + if (dwc_ufshcd_is_device_present(hcs) == false) { + pr_err("Device is not attached to DWC UFS HC\n"); + return -ENXIO; + } + + /* Check if UTR List Ready */ + if (dwc_ufshcd_is_utrl_ready(hcs) == false) { + pr_err("UTRDL is not ready\n"); + return -EIO; + } + + /* Check if UTMR List Ready */ + if (dwc_ufshcd_is_utmrl_ready(hcs) == false) { + pr_err("UTMRL is not ready\n"); + return -EIO; + } + + /* Check if UTR List Ready */ + if (dwc_ufshcd_is_dme_sap_ready(hcs) == false) { + pr_err("DME Sap not ready\n"); + return -EIO; + } + + pr_debug("DWC UFS HC is ready\n"); + + return 0; +} + +/** + * dwc_ufshcd_configure_interrupt() + * This function configures host controller Interrupt enable register + * with the value programmed in the intr_enable_mask in drivers private data + * @hba: Pointer to drivers structure + * @option: enable or disable interrupts + * + */ +void dwc_ufshcd_configure_interrupt(struct dwc_ufs_hba *hba, u32 option) +{ + switch (option) { + case DWC_UFS_HCD_INTR_ENABLE: + dwc_ufs_writel(hba->intr_enable_mask, + hba->mmio_base, DWC_UFS_REG_IE); + break; + case DWC_UFS_HCD_INTR_DISABLE: + dwc_ufs_writel(0x0000, + hba->mmio_base, DWC_UFS_REG_IE); + break; + } +} + +/** + * dwc_ufshcd_config_intr_aggregation() + * Configures UTP transfer request interrupt aggregation control register. + * Aggregation is programmed using the intr_aggr_cntr_thr for threshold and + * intr_aggr_timeout_val for time out programmed in drivers private data + * structure. + * @hba: Pointer to drivers structure + * @option: Interrupt aggregation option either to enable or disable + * + */ +static inline void dwc_ufshcd_config_intr_aggregation(struct dwc_ufs_hba *hba, + int option) +{ + switch (option) { + case INTR_AGGR_RESET: + dwc_ufs_writel((DWC_UFS_IAEN | DWC_UFS_CTR), + hba->mmio_base, DWC_UFS_REG_UTRIACR); + break; + case INTR_AGGR_CONFIG: + dwc_ufs_writel((DWC_UFS_IAEN | DWC_UFS_IAPWEN | + (((u32)(hba->intr_aggr_cntr_thr) << DWC_UFS_IACTH_SH) & + DWC_UFS_IACTH_MASK) | (u32)(hba->intr_aggr_timout_val)), + hba->mmio_base, DWC_UFS_REG_UTRIACR); + break; + } +} + +/** + * dwc_ufshcd_enable_xfer_list_processing() + * This function enables the host controller to start processing UTP transfer + * request descriptor. This function programs the UTP transfer request + * list run stop register. + * @hba: Pointer to drivers structure + * + */ +void dwc_ufshcd_enable_xfer_list_processing(struct dwc_ufs_hba *hba) +{ + dwc_ufs_writel(0x01, hba->mmio_base, DWC_UFS_REG_UTRLRSR); +} + +/** + * dwc_ufshcd_enable_tm_list_processing() + * This function enables the host controller to start processing UTP TM + * request descriptor.This function programs the UTP task management request + * list run stop register. + * @hba: Pointer to drivers structure + * + */ +void dwc_ufshcd_enable_tm_list_processing(struct dwc_ufs_hba *hba) +{ + dwc_ufs_writel(0x01, hba->mmio_base, DWC_UFS_REG_UTMRLRSR); +} + +/** + * dwc_ufshcd_kick_start_stack() + * Kick start stack for device scanning and command sending. + * This function unblocks the applicatin requests (eg: Scsi) if already + * blocked. Sets the hcd state to operational and starts scanning for + * devices attached to HC. + * @hba: Pointer to drivers structure + * + * Returns 0 on success, non-zero value on failure + */ +int dwc_ufshcd_kick_start_stack(struct dwc_ufs_hba *hba) +{ + int result = 0; + + /* If the hba state is in reset unblock the scsi requests */ + if (hba->dwc_ufshcd_state == DWC_UFSHCD_STATE_RESET) + scsi_unblock_requests(hba->shost); + + dwc_ufshcd_set_hcd_state(hba, DWC_UFSHCD_STATE_OPERATIONAL); + + /* scan all the devices attached to this host */ + dwc_ufshcd_scan_scsi_devices_attached_to_host(hba); + + return result; +} + +/** + * dwc_ufshcd_set_hcd_state() + * Sets the state of host controller driver. + * @hba: Pointer to drivers structure + * @state: state of the driver to be programmed + * + */ +void dwc_ufshcd_set_hcd_state(struct dwc_ufs_hba *hba, u32 state) +{ + hba->dwc_ufshcd_state = state; +} + +/** + * dwc_ufshcd_kick_start_hba() + * Kick start DWC UFS controller. + * After checking the HC is ready for operation (Device Present, UTRL ready, + * UTMRL ready, UIC ready), this function performs + * - Enables the Transfer list processing by setting Run Stop bit + * - Enables the Task Management list processing by setting Run Stop bit + * - Sets the interrupt mask in driver private structure + * - Configure the interrupts in HC + * - Configures the interrupt aggregation + * @hba: Pointer to drivers structure + * + * Returns 0 on success, non-zero value on failure + */ +int dwc_ufshcd_kick_start_hba(struct dwc_ufs_hba *hba) +{ + int result = 0; + + /* Checking if Device Present */ + result = dwc_ufshcd_is_host_controller_ready(hba); + + if (result != 0) + goto err_host_controller_ready; + + /* Enable the Xfer and TM lists */ + dwc_ufshcd_enable_xfer_list_processing(hba); + dwc_ufshcd_enable_tm_list_processing(hba); + + /* Enable required interrupts */ + hba->intr_enable_mask |= (DWC_UFS_UTRCE | DWC_UFS_UEE | DWC_UFS_UTMRCE | + DWC_UFS_DFEE | DWC_UFS_HCFEE | DWC_UFS_SBFEE); + /* Configure HC for programmed interrupts */ + dwc_ufshcd_configure_interrupt(hba, DWC_UFS_HCD_INTR_ENABLE); + + /* Configure interrupt aggregation */ + hba->intr_aggr_cntr_thr = DWC_INTR_AGGR_COUNTER_THRESHOLD_VALUE; + hba->intr_aggr_timout_val = DWC_INTR_AGGR_TIMEOUT_VALUE; + dwc_ufshcd_config_intr_aggregation(hba, INTR_AGGR_CONFIG); + +err_host_controller_ready: + return result; +} + +/** + * dwc_ufshcd_update_dme_cmd_results() + * This function updates the active_dme_cmd fields from the uio registers + * Along with this it also update the cmd_active field to zero. Last action + * acts as command completion flat to wake up the wait queue. + * @hba: Pointer to drivers structure + * + */ +void dwc_ufshcd_update_dme_cmd_results(struct dwc_ufs_hba *hba) +{ + hba->active_dme_cmd.command = dwc_ufs_readl(hba->mmio_base, + DWC_UFS_REG_UICCMDR); + + hba->active_dme_cmd.argument1 = dwc_ufs_readl(hba->mmio_base, + DWC_UFS_REG_UCMDARG1); + + hba->active_dme_cmd.argument2 = dwc_ufs_readl(hba->mmio_base, + DWC_UFS_REG_UCMDARG2); + + hba->active_dme_cmd.argument3 = dwc_ufs_readl(hba->mmio_base, + DWC_UFS_REG_UCMDARG3); + + hba->active_dme_cmd.cmd_active = 0; +} + +/** + * dwc_ufshcd_dme_sap_work() + * The work to be performed after sending UIO command through UIO_SAP + * At present only commannd status is updated for link startup command + * For other commands function does nothing. + * @work: pointer to a work queue structure + * + */ +static void dwc_ufshcd_dme_sap_work(struct work_struct *work) +{ + int result; + struct dwc_ufs_hba *hba; + + hba = container_of(work, struct dwc_ufs_hba, dme_sap_workq); + + if (hba->active_dme_cmd.command == DWC_UFS_DME_LINKSTARTUP) { + dwc_ufshcd_update_dme_cmd_results(hba); + result = dwc_ufshcd_get_dme_command_results(hba); + } +} + +/** + * dwc_ufshcd_dme_sap_work() + * The work to be performed on seeing fatal erros as reported by HC + * TODO: yet to be implemented + * @work: pointer to a work queue structure + * + */ +static void dwc_ufshcd_fatal_error_handler_work(struct work_struct *work) +{ + struct dwc_ufs_hba *hba; + int result = 0; + + hba = container_of(work, struct dwc_ufs_hba, fatal_error_handler_workq); + + /* set the state to error state */ + dwc_ufshcd_set_hcd_state(hba, DWC_UFSHCD_STATE_ERROR); + + /* check if reset is already in progress */ + if (hba->dwc_ufshcd_state != DWC_UFSHCD_STATE_RESET) + result = dwc_ufshcd_reset_hba(hba); +} + +#ifdef CONFIG_PM +/** + * dwc_ufshcd_suspend() + * This function will be called from OS issues the suspend + * TODO: To be implemented and comeup with scheme to verify the flow + * @pdev: pointer to PCI device handle + * + * Returns -ENOSYS + */ +int dwc_ufshcd_suspend(struct dwc_ufs_hba *hba) +{ + /* + * 1. Block SCSI requests from SCSI midlayer + * 2. Change the internal driver state to non operational + * 3. Set UTRLRSR and UTMRLRSR bits to zero + * 4. Wait until outstanding commands are completed + * 5. Set HCE to zero to send the UFS host controller to reset state + */ + + return -ENOSYS; +} + +/** + * dwc_ufshcd_resume() + * Resumes the DWC UFS HC from suspend + * TODO: To be implemented and comeup with scheme to verify the flow + * @pdev: pointer to PCI device handle + * + * Returns -ENOSYS + */ +int dwc_ufshcd_resume(struct dwc_ufs_hba *hba) +{ + /* + * 1. Set HCE to 1, to start the UFS host controller + * initialization process + * 2. Set UTRLRSR and UTMRLRSR bits to 1 + * 3. Change the internal driver state to operational + * 4. Unblock SCSI requests from SCSI midlayer + */ + + return -ENOSYS; +} +#endif /* CONFIG_PM */ + +/** + * dwc_ufshcd_get_xfer_cmd_status() + * Get the command status from the response upiu. + * @resp_upiu: Pointer to response UPIU + * + * Returns the response_upiu->status. + */ +static inline int +dwc_ufshcd_get_xfer_cmd_status(struct dwc_ufs_resp_upiu *resp_upiu) { + return resp_upiu->status; +} + +/** + * dwc_ufshcd_xfer_resp_status() + * This populates the ocs and xfer_command_status field of lrb + * from utrd->ocs and response_upiu->status + * @hba: Pointer to drivers structure + * @lrb: pointer to local reference block of completed command + * + */ +void dwc_ufshcd_xfer_resp_status(struct dwc_ufs_hba *hba, + struct dwc_ufs_hcd_lrb *lrb) { + + /* overall command status of utrd */ + lrb->ocs = dwc_ufshcd_get_utrd_ocs(lrb); + + /* Update transfer command status */ + lrb->xfer_command_status = + dwc_ufshcd_get_xfer_cmd_status(lrb->resp_upiu); +} + +/** + * dwc_ufshcd_dm_command_completion_handler() + * This function handles device management request completion. The function + * wakes up the the wait queue. + * @hba: Pointer to drivers structure + * + * Returns zero on success or non zero value on failure + */ +int dwc_ufshcd_dm_command_completion_handler(struct dwc_ufs_hba *hba) +{ + u32 dm_req_doorbell; + unsigned long host_completed_dm_requests; + int result = 0; + + /* Get all the outstanding TM tasks with Host controller */ + dm_req_doorbell = dwc_ufs_readl(hba->mmio_base, DWC_UFS_REG_UTRLDBR); + host_completed_dm_requests = dm_req_doorbell ^ + hba->outstanding_dm_requests; + + hba->dwc_ufshcd_dm_condition = host_completed_dm_requests; + + if (hba->dm_completion_handler_type == + DWC_UFS_HANDLER_WAIT_QUEUE_TYPE) { + pr_debug("Waking up DM wait queue\n"); + wake_up_interruptible(&hba->dwc_ufshcd_dm_wait_queue); + } + + return result; +} + +/** + * dwc_ufshcd_xfer_req_completion_handler() + * This function handles the application commands (eg:scsi) completion. + * The function + * - Scans through all the slots to identify the completed commands + * - Updates the transfer response status + * - calls appropriate application specific functions + * - unmapping the dma (scsi) + * - invokes scsi completion handler + * Note: This function will run in interrupt context. Modification to this + * routine and any other function called from this routine should not block. + * @hba: Pointer to drivers structure + * + */ +static void dwc_ufshcd_xfer_req_completion_handler(struct dwc_ufs_hba *hba) +{ + struct dwc_ufs_hcd_lrb *lrb; + unsigned long host_completed_reqs; + u32 xfer_req_doorbell; + int nutr; + + lrb = hba->lrb; + xfer_req_doorbell = dwc_ufs_readl(hba->mmio_base, DWC_UFS_REG_UTRLDBR); + host_completed_reqs = xfer_req_doorbell ^ hba->outstanding_xfer_reqs; + + /* Handle all completed UTRDs one by one */ + for (nutr = 0; nutr < hba->nutrs; nutr++) { + if (test_bit(nutr, &host_completed_reqs)) { + dwc_ufshcd_xfer_resp_status(hba, &lrb[nutr]); + + if (lrb[nutr].command_type == DWC_UTRD_CMD_TYPE_SCSI || + lrb[nutr].command_type == DWC_UTRD_CMD_TYPE_UFS_STORAGE) { + + if (lrb[nutr].scmd != NULL) { + + /* Free DMA mapping for this + * cmd's SG list + */ + dwc_ufshcd_unmap_dma(&lrb[nutr]); + + /* Get result based on SCSI status + * response + */ + dwc_ufshcd_do_scsi_completion_with_results(&lrb[nutr]); + } + } + } + } + + /* clear corresponding bits of completed commands */ + hba->outstanding_xfer_reqs ^= host_completed_reqs; + + /* Reset interrupt aggregation counters */ + dwc_ufshcd_config_intr_aggregation(hba, INTR_AGGR_RESET); +} + +/** + * dwc_ufshcd_dme_completion_handler() + * This function implements the completion handler for dme commands. + * First it will update the command resulst in the active dme command + * structure in drivers private data. + * Then it will + * - schedule the work; if work queue is implemented or + * - wakes up the wait queue + * @hba: Pointer to drivers structure + * + */ +static void dwc_ufshcd_dme_completion_handler(struct dwc_ufs_hba *hba) +{ + dwc_ufshcd_update_dme_cmd_results(hba); + + /* if waitqueue implementation */ + if (hba->dme_completion_handler_type == + DWC_UFS_HANDLER_WAIT_QUEUE_TYPE) { + pr_debug("Waking up the dme wait queue\n"); + wake_up_interruptible(&hba->dwc_ufshcd_dme_wait_queue); + } + /* if work implementation */ + else if (hba->dme_completion_handler_type == + DWC_UFS_HANDLER_WORK_QUEUE_TYPE) { + schedule_work(&hba->dme_sap_workq); + } + + /* After completing one request unmaslk the interrupt enable bit */ + hba->intr_enable_mask &= ~DWC_UFS_UCCE; + dwc_ufshcd_configure_interrupt(hba, DWC_UFS_HCD_INTR_ENABLE); +} + +/** + * dwc_ufshcd_tm_completion_handler() + * This function handles task management function completion. + * After setting the condition variable for respective slot, + * it wakes up the wait queue for further processing + * @hba: Pointer to drivers structure + * + */ +static void dwc_ufshcd_tm_completion_handler(struct dwc_ufs_hba *hba) +{ + u32 tm_tasks_doorbell; + unsigned long host_completed_tm_tasks; + + /* Get all the outstanding TM tasks with Host controller */ + tm_tasks_doorbell = dwc_ufs_readl(hba->mmio_base, DWC_UFS_REG_UTMRLDBR); + host_completed_tm_tasks = tm_tasks_doorbell ^ hba->outstanding_tm_tasks; + + hba->dwc_ufshcd_tm_condition = host_completed_tm_tasks; + + /* TODO: It seems the wait queue implementaion can handle only one. + * review and implement a different scheme if needed */ + if (hba->tm_completion_handler_type == DWC_UFS_HANDLER_WAIT_QUEUE_TYPE) + wake_up_interruptible(&hba->dwc_ufshcd_tm_wait_queue); +} + +void dwc_ufshcd_error_handler(struct dwc_ufs_hba *hba) +{ + u32 reg; + + /* If fatal Errors; Handle them first at highest priority */ + if (hba->dwc_ufshcd_interrupts & DWC_UFS_ENABLED_FE_ERROR_MASK) + goto handle_fatal_errors; + + if (hba->dwc_ufshcd_interrupts & DWC_UFS_UEE) { + reg = dwc_ufs_readl(hba->mmio_base, DWC_UFS_REG_UECPA); + pr_info("DWC_UFS_REG_UECPA : %08x\n", reg); + reg = dwc_ufs_readl(hba->mmio_base, DWC_UFS_REG_UECDL); + pr_info("DWC_UFS_REG_UECDL : %08x\n", reg); + reg = dwc_ufs_readl(hba->mmio_base, DWC_UFS_REG_UECN); + pr_info("DWC_UFS_REG_UECN : %08x\n", reg); + reg = dwc_ufs_readl(hba->mmio_base, DWC_UFS_REG_UECT); + pr_info("DWC_UFS_REG_UECT : %08x\n", reg); + reg = dwc_ufs_readl(hba->mmio_base, DWC_UFS_REG_UECDME); + pr_info("DWC_UFS_REG_UECDME: %08x\n", reg); + } + + /* Handle Hibernate Entry Exit Or Powermode Change */ + if (hba->dwc_ufshcd_interrupts & (DWC_UFS_UHES | DWC_UFS_UHXS | + DWC_UFS_UPMS)) { + reg = dwc_ufs_readl(hba->mmio_base, DWC_UFS_REG_AHIT); + pr_info("DWC_UFS_REG_AHIT : %08x\n", reg); + reg = dwc_ufs_readl(hba->mmio_base, DWC_UFS_REG_HCS); + pr_info("DWC_UFS_REG_HCS : %08x\n", reg); + } + + return; + +handle_fatal_errors: + if (hba->fatal_error_handler_type == DWC_UFS_HANDLER_WORK_QUEUE_TYPE) { + dwc_ufshcd_set_hcd_state(hba, DWC_UFSHCD_STATE_ERROR); + schedule_work(&hba->fatal_error_handler_workq); + } +} + +static void dwc_ufshcd_handle_interrupt_in_debug_mode(struct dwc_ufs_hba *hba) +{ + getnstimeofday(&hba->pstats.end_time); + hba->pstats.state = DWC_UFS_STATS_DISABLED; + hba->debug_mode = 0; +} + +/** + * dwc_ufshcd_handle_interrupts() + * This is invoked from the top half to handle interrupts + * This function inturn calls appropriate handlers + * - errored interrupts are handled at most priority + * + * @hba: Pointer to drivers structure + * @intr_status: Interrupt status register value + * + */ +static void dwc_ufshcd_handle_interrupts(struct dwc_ufs_hba *hba, + u32 intr_status) +{ + /* Handle Errors at High Priority */ + if (((intr_status & hba->intr_enable_mask) & + DWC_UFS_ENABLED_ERROR_MASK) != 0) + dwc_ufshcd_error_handler(hba); + + /* DME/UIC Command complete interrupt */ + if (((intr_status & hba->intr_enable_mask) & DWC_UFS_UCCS) != 0) + dwc_ufshcd_dme_completion_handler(hba); + + /* UTP Task Management Request Completion interrupt */ + if (((intr_status & hba->intr_enable_mask) & DWC_UFS_UTMRCS) != 0) + dwc_ufshcd_tm_completion_handler(hba); + + /* UTP Transfer Request Completion interrupt */ + if (((intr_status & hba->intr_enable_mask) & DWC_UFS_UTRCS) != 0) { + if (hba->outstanding_dm_requests != 0) + dwc_ufshcd_dm_command_completion_handler(hba); + + dwc_ufshcd_xfer_req_completion_handler(hba); + } +} + +/** + * dwc_ufshcd_isr() + * Main/top half of interrupt service routine + * This function inturn invokes the individual handlers + * @irq: irq number + * @dwc_ufs_hcd_data: pointer to driver's private data + * + * Returns IRQ_HANDLED - If interrupt is valid + * IRQ_NONE - If invalid interrupt + */ +static irqreturn_t dwc_ufshcd_isr(int irq, void *dwc_ufs_hcd_data) +{ + u32 intr_status; + irqreturn_t irq_ret = IRQ_NONE; + struct dwc_ufs_hba *hba = dwc_ufs_hcd_data; + + spin_lock(hba->shost->host_lock); + intr_status = dwc_ufs_readl(hba->mmio_base, DWC_UFS_REG_IS); + + /* FOR UFS2.0 above Hostcontrollers, the interrupt status is Write to + * Clear + */ + if (hba->dwc_ufs_version == DWC_UFS_HC_VERSION_2_0) + dwc_ufs_writel(intr_status, hba->mmio_base, DWC_UFS_REG_IS); + + /* If Debug mode is on the interrupt is due to request through debug + * access. Handle this without going through stack + */ + if (hba->debug_mode == 1) { + if (dwc_ufs_readl(hba->mmio_base, DWC_UFS_REG_UTRLDBR) == 0x0) + dwc_ufshcd_handle_interrupt_in_debug_mode(hba); + } else if (intr_status != 0) { + + if ((hba->pstats.state == DWC_UFS_STATS_RUNNING) && + (intr_status & DWC_UFS_UTRCE)) + getnstimeofday(&hba->pstats.end_time); + + hba->dwc_ufshcd_interrupts = intr_status; + dwc_ufshcd_handle_interrupts(hba, intr_status); + irq_ret = IRQ_HANDLED; + } + + spin_unlock(hba->shost->host_lock); + + return irq_ret; +} + +/** + * dwc_ufshcd_drv_exit() + * PCI/Platform independent exit routine for driver + * This function + * - Disables the interrupt of DWC UFS HC + * - free's the irq + * - Stops/Disables the DWC UFS HC + * - Frees up all the memory allocated to interface with HC + * - removes the scsi host structure registered with scsi mid layer + * @hba: Pointer to drivers structure + * + */ +void dwc_ufshcd_drv_exit(struct dwc_ufs_hba *hba) +{ + /* Remove the scsi host from the scsi mid layer */ + dwc_ufshcd_scsi_remove_host(hba); + mdelay(100); + + /* disable interrupts */ + dwc_ufshcd_configure_interrupt(hba, DWC_UFS_HCD_INTR_DISABLE); + free_irq(hba->irq, hba); + + dwc_ufshcd_stop_host(hba); + mdelay(100); + + /* TODO: Validate the following assumptions + * 1. there is nothing on the waitqueues + * 2. No pending work in workqueue + */ + dwc_ufshcd_free_interface_memory(hba); +} + +/** + * dwc_ufshcd_drv_init() + + * PCI/Platform independent initialization routine for driver. + * The action performed here are + * - Do scsi adaptation by allocating scsi host structure + * - Populates private structure with + * - generic device pointer + * - irq no + * - mmio_base + * - Reads the DWC UFS HC's capability and populates it in priv structure + * - Allocates interface memory required + * - Initializes static_hba static variable needed for debug infrastructure + * - Updates the scsi attributes with HC's capabilities + * - Initializes different interrupt completion handlers + * - Registers the ISR to the irq no + * - does scsi tag mapping; tags to be shared among many LUNs + * - Add DWC UFS HC to scsi mid layer * + * @ptr_to_hba: double pointer driver's private data + * @gdev: generic device pointer + * @irq_no: irq line, the host controller interrupts are mapped to + * @mmio_base: Host controller's memory/IO base address + * + * Returns 0 on success, non-zero value on failure + */ +int dwc_ufshcd_drv_init(struct dwc_ufs_hba **ptr_to_hba, struct device *gdev, + int irq_no, void __iomem *mmio_base) +{ + int result = 0; + struct dwc_ufs_hba *hba; + + /* Function below does the scsi adaptation for UFS + * This function adds scsi_host_template to scsi subystem + * (Mid level). In addition this allocates the memory required + * for our-host's/driver's private data + */ + hba = dwc_ufshcd_alloc_scsi_host(); + + if (hba == NULL) { + pr_err("SCSI Host Allocation Failed\n"); + result = -ENOMEM; + goto err_scsi_host_alloc; + } + + /* Populate required private fields */ + hba->gdev = gdev; + hba->irq = irq_no; + hba->mmio_base = mmio_base; + *ptr_to_hba = hba; + + /* Read host Controller Capabilities */ + dwc_ufshcd_read_caps(hba); + + hba->dwc_ufs_version = dwc_ufshcd_get_dwc_ufs_version(hba); + + /* Allocate memory required to interface with host */ + result = dwc_ufshcd_alloc_interface_memory(hba); + + /* Update the static hba pointer for user space access */ + static_ufs_hba = hba; + + if (result != 0) { + pr_err("Error Allocating the required memory to"/ + "interface DWC UFS HC\n"); + goto err_alloc_ufshcd_interface_memory; + } + + /* Configure the HC memory with required information and the LRB */ + dwc_ufshcd_configure_interface_memory(hba); + + /* Disable debug mode and stop capturing statistics */ + hba->debug_mode = 0; + hba->pstats.state = DWC_UFS_STATS_DISABLED; + + /* Update Scsi related attributes/parameters */ + dwc_ufshcd_update_scsi_attributes(hba); + + /* Initailize wait queue for task management, Device Management, + * Dme-Access + */ + hba->dme_completion_handler_type = DWC_UFS_HANDLER_WAIT_QUEUE_TYPE; + hba->tm_completion_handler_type = DWC_UFS_HANDLER_WAIT_QUEUE_TYPE; + hba->dm_completion_handler_type = DWC_UFS_HANDLER_WAIT_QUEUE_TYPE; + hba->fatal_error_handler_type = DWC_UFS_HANDLER_WORK_QUEUE_TYPE; + + if (hba->tm_completion_handler_type == + DWC_UFS_HANDLER_WAIT_QUEUE_TYPE) + init_waitqueue_head(&hba->dwc_ufshcd_tm_wait_queue); + + if (hba->dm_completion_handler_type == + DWC_UFS_HANDLER_WAIT_QUEUE_TYPE) + init_waitqueue_head(&hba->dwc_ufshcd_dm_wait_queue); + + if (hba->dme_completion_handler_type == + DWC_UFS_HANDLER_WORK_QUEUE_TYPE) + INIT_WORK(&hba->dme_sap_workq, dwc_ufshcd_dme_sap_work); + else if (hba->dme_completion_handler_type == + DWC_UFS_HANDLER_WAIT_QUEUE_TYPE) + init_waitqueue_head(&hba->dwc_ufshcd_dme_wait_queue); + + if (hba->fatal_error_handler_type == DWC_UFS_HANDLER_WORK_QUEUE_TYPE) + INIT_WORK(&hba->fatal_error_handler_workq, + dwc_ufshcd_fatal_error_handler_work); + + /* ISR registration */ + result = request_irq(hba->irq, dwc_ufshcd_isr, IRQF_SHARED, + dwc_ufs_hostname(hba), hba); + + if (result != 0) { + pr_err("ISR Registration Failed\n"); + goto err_request_irq; + } + + /* Enable SCSI tag mapping: to use unique tags for all LUNs + * That will manage a joint tag space for N queues without driver + * having to partition it arbitrarily + */ + result = dwc_ufshcd_do_scsi_tag_mapping(hba); + if (result != 0) { + pr_err("Tag to Queue Mapping Failed\n"); + goto err_scsi_init_shared_tag_map; + } + + /* Add our adapted scsi_host structure to SCSI subsystem */ + result = dwc_ufshcd_scsi_add_host(hba); + if (result != 0) { + pr_err("Adding Scsi host Failed\n"); + goto err_scsi_add_host; + } + + return 0; + +err_scsi_add_host: +err_scsi_init_shared_tag_map: + free_irq(hba->irq, hba); +err_request_irq: + dwc_ufshcd_free_interface_memory(hba); +err_alloc_ufshcd_interface_memory: +err_scsi_host_alloc: + return result; +} + +/** + * dwc_ufshcd_stop_host() + * Resets/Stops the Host controller by writing HCE register bit 0 with + * a value of '0' + * @hba: Pointer to drivers structure + * + */ +void dwc_ufshcd_stop_host(struct dwc_ufs_hba *hba) +{ + dwc_ufs_writel(DWC_UFS_DISABLE, hba->mmio_base, DWC_UFS_REG_HCE); +} + +/** + * dwc_ufshcd_is_hba_active + * Get UFS controller state + * @hba: Pointer to drivers structure + * + * Returns TRUE if controller is active, FALSE otherwise + */ +bool dwc_ufshcd_is_hba_active(struct dwc_ufs_hba *hba) +{ + return (dwc_ufs_readl(hba->mmio_base, DWC_UFS_REG_HCE) & 0x1) ? + true : false; +} + +/** + * dwc_ufshcd_reset_host() + * Resets/Stops the Host controller + * Writing HCE register bit 0 with value of 1 initiates the reset to the HC. + * Note that this function returns immediately. it is the responsibility of + * the caller to ensure the reset/initialization is successful + * @hba: Pointer to drivers structure + * + */ +void dwc_ufshcd_reset_host(struct dwc_ufs_hba *hba) +{ + dwc_ufs_writel(DWC_UFS_ENABLE, hba->mmio_base, DWC_UFS_REG_HCE); +} + +/** + * dwc_ufshcd_program_clk_div() + * This function programs the clk divider value. this value is needed to + * provide 1 microsecond tick to unipro layer. + * @hba: Pointer to drivers structure + * @divider_val: clock divider value to be programmed + * + */ +void dwc_ufshcd_program_clk_div(struct dwc_ufs_hba *hba, u32 divider_val) +{ + dwc_ufs_writel(divider_val, hba->mmio_base, DWC_UFS_REG_HCLKDIV); +} + +/** + * dwc_ufshcd_host_enable() + * Enables the DWC UFS HC for operation + * This function starts the contorller intialization procedure (By Setting + * the HCE bit 0). The initialization might take some time as it involvs + * initializing local UIC layer (dme_reset and dme_enable). + * This function implements busy wait loop till the host controller becomes + * ready for operation + * @hba: Pointer to drivers structure + * + * Returns 0 on success, non-zero value on failure + */ +int dwc_ufshcd_host_enable(struct dwc_ufs_hba *hba) +{ + int retry_count; + int result = 0; + + /* Start host controller initialization sequence */ + pr_debug("Stopping the DWC UFS HC\n"); + dwc_ufshcd_stop_host(hba); + mdelay(10); + + pr_debug("Enabling the DWC UFS HC\n"); + dwc_ufshcd_reset_host(hba); + + mdelay(10); + + /* Wait till host controller initialization completes */ + retry_count = DWC_UFS_RETRY_COUNT_GENERIC; + while ((dwc_ufshcd_is_hba_active(hba) == false) && (retry_count != 0)) { + retry_count--; + mdelay(10); + } + + if (retry_count == 0) { + pr_err("Failed to enable the Host Controller\n"); + result = -EIO; + } + + return result; +} + +/** + * dwc_ufshcd_get_dme_command_results() + * This function gets the UIO command results from the + * driver's private structure's active dme command. + * It is callers responsibility to ensure the results are updated + * before invoking this function. + * @hba: Pointer to drivers structure + * + * Returns dme result code + */ +u8 dwc_ufshcd_get_dme_command_results(struct dwc_ufs_hba *hba) +{ + return ((hba->active_dme_cmd.argument2) & + DWC_UFS_DME_RESULT_CODE_MASK); +} + +/** + * dwc_ufshcd_send_dme_command() + * This function issues the uio command. it uses the programmed + * values available in active_dme_command structure in drivers private + * data. + * @hba: Pointer to drivers structure + * + * Returns 0 - success + */ +int dwc_ufshcd_send_dme_command(struct dwc_ufs_hba *hba) +{ + int result = 0; + + /* Argument registers to be written first */ + dwc_ufs_writel(hba->active_dme_cmd.argument1, + hba->mmio_base, DWC_UFS_REG_UCMDARG1); + + dwc_ufs_writel(hba->active_dme_cmd.argument2, + hba->mmio_base, DWC_UFS_REG_UCMDARG2); + + dwc_ufs_writel(hba->active_dme_cmd.argument3, + hba->mmio_base, DWC_UFS_REG_UCMDARG3); + + hba->active_dme_cmd.cmd_active = 1; + + /* Write UIC/DME Cmd */ + dwc_ufs_writel(hba->active_dme_cmd.command, + hba->mmio_base, DWC_UFS_REG_UICCMDR); + + return result; +} + +/** + * dwc_ufshcd_handle_dme_command_completion_wo_intr() + * This function is called when the dme command is sent without + * enabling the interrupt. + * This function polls the Interrupt status register for Uio command + * completion status for programmed number of retries + * Then independent of interrupt is set or not, it updates the dme_results + * Its the responsibility of the caller to interpret the data and + * take a call on success or failure. + * @hba: Pointer to drivers structure + * + */ +void dwc_ufshcd_handle_dme_command_completion_wo_intr(struct dwc_ufs_hba *hba) +{ + u32 retry_count = DWC_UFS_RETRY_COUNT_GENERIC; + u32 intr_status = 0; + + do { + intr_status = dwc_ufs_readl(hba->mmio_base, DWC_UFS_REG_IS); + mdelay(1); + + if (retry_count-- == 0) + break; + } while ((intr_status & DWC_UFS_UCCS) == 0); + + /* FOR UFS2.0 above Hostcontrollers, the interrupt status is Write + * to Clear + */ + if (hba->dwc_ufs_version == DWC_UFS_HC_VERSION_2_0) + dwc_ufs_writel(DWC_UFS_UCCS, hba->mmio_base, DWC_UFS_REG_IS); + + /* At present for polling mode only dme registers are dumped out */ + dwc_ufshcd_update_dme_cmd_results(hba); +} + +/** + * dwc_ufs_dme_get() + * The function to get the local unipor attribute + * It is caller's responsibility ensure HC is enabled before invoking this + * function. + * If intr_mode indicates that interrupt is not required, the functin blocks + * until the command completes. + * If intr_mode indicates that interrupt is required, and the interrupt + * handling is wait queue type + * - execution thread waits on the wait queue until one of the following + * occurs + * - woken up by ISR after receiving the interrupt or times-out + * @hba: Pointer to drivers structure + * @attr_id: unipro attribure id + * @sttr_set_type: local or static + * @intr_mode: interrupt to be enabled or not + * + * Returns 0 for success and non-zero value for failure + */ +int dwc_ufs_dme_get(struct dwc_ufs_hba *hba, u32 attr_id, u32 attr_set_type, + u32 intr_mode) +{ + unsigned long flags_irq; + u32 hcs; + int result = 0; + + /* Check if DWC HC is ready to accept DME commands */ + hcs = dwc_ufs_readl(hba->mmio_base, DWC_UFS_REG_HCS); + if (dwc_ufshcd_is_dme_sap_ready(hcs) == false) { + pr_err("Hardware (DME) Busy\n"); + return -EIO; + } + + /* Only One DME command at a time */ + spin_lock_irqsave(hba->shost->host_lock, flags_irq); + + /* Prepare the dme_get command */ + hba->active_dme_cmd.command = DWC_UFS_DME_GET; + hba->active_dme_cmd.argument1 = attr_id << 16; + hba->active_dme_cmd.argument2 = 0; + + if (attr_set_type == DWC_UFS_DME_ATTR_SET_TYPE_STATIC) + hba->active_dme_cmd.argument2 = + DWC_UFS_DME_ATTR_SET_TYPE_STATIC << 16; + + hba->active_dme_cmd.argument3 = 0; + + /* Enable UIC Interrupt if required */ + if (intr_mode == DWC_UFS_HCD_DME_INTR_ENABLE) { + hba->intr_enable_mask |= DWC_UFS_UCCE; + dwc_ufshcd_configure_interrupt(hba, DWC_UFS_HCD_INTR_ENABLE); + } + + /* Send UIC Command */ + dwc_ufshcd_send_dme_command(hba); + + /* if Interrupt is not enabled use polling mode */ + if (intr_mode == DWC_UFS_HCD_DME_INTR_DISABLE) + dwc_ufshcd_handle_dme_command_completion_wo_intr(hba); + + /* With this lock held is DME access atomic on SMP? */ + spin_unlock_irqrestore(hba->shost->host_lock, flags_irq); + + /* If interrupt is enabled and the handler type is wait queue, block + * here till interrupt wakes it up */ + if ((intr_mode == DWC_UFS_HCD_DME_INTR_ENABLE) && + (hba->dme_completion_handler_type == + DWC_UFS_HANDLER_WAIT_QUEUE_TYPE)) { + result = wait_event_interruptible_timeout(hba->dwc_ufshcd_dme_wait_queue, + (hba->active_dme_cmd.cmd_active == 0), + DWC_UFS_DME_TIMEOUT); + + if (result == 0) { + pr_err("DME Command Timed-out\n"); + result = FAILED; + } else { + dwc_ufshcd_update_dme_cmd_results(hba); + result = 0; + } + } + + return result; +} + +/** + * dwc_ufs_dme_set() + * The function to set the local unipro attribute + * It is caller's responsibility ensure HC is enabled before invoking this + * function. + * If intr_mode indicates that interrupt is not required, the functin blocks + * untile the command completes. + * If intr_mode indicates that interrupt is required, and the interrupt + * handling is wait queue type + * - execution thread waits on the wait queue until one of the following + * occurs + * - woken up by ISR after receiving the interrupt or times-out + * @hba: Pointer to drivers structure + * @attr_id: unipro attribure id + * @gen_sel_index: selector + * @attr_val: attribute value + * @sttr_set_type: local or static + * @intr_mode: interrupt to be enabled or not + * + * Returns 0 for success and non-zero value for failure + */ +int dwc_ufs_dme_set(struct dwc_ufs_hba *hba, u32 attr_id, u32 gen_sel_index, + u32 attr_val, u32 attr_set_type, u32 intr_mode) +{ + unsigned long flags_irq; + u32 hcs; + int result = 0; + + /* Check if DWC HC is ready to accept DME commands */ + hcs = dwc_ufs_readl(hba->mmio_base, DWC_UFS_REG_HCS); + if (dwc_ufshcd_is_dme_sap_ready(hcs) == false) { + pr_err("Hardware (DME) Busy\n"); + return -EIO; + } + + /* Only One DME command at a time */ + spin_lock_irqsave(hba->shost->host_lock, flags_irq); + + /* Prepare the dme_set command */ + hba->active_dme_cmd.command = DWC_UFS_DME_SET; + hba->active_dme_cmd.argument1 = ((attr_id << 16) | gen_sel_index); + hba->active_dme_cmd.argument2 = 0; + if (attr_set_type == DWC_UFS_DME_ATTR_SET_TYPE_STATIC) + hba->active_dme_cmd.argument2 = + DWC_UFS_DME_ATTR_SET_TYPE_STATIC << 16; + hba->active_dme_cmd.argument3 = attr_val; + + /* Enable UIC Interrupt if required */ + if (intr_mode == DWC_UFS_HCD_DME_INTR_ENABLE) { + hba->intr_enable_mask |= DWC_UFS_UCCE; + dwc_ufshcd_configure_interrupt(hba, DWC_UFS_HCD_INTR_ENABLE); + } + + /* Send UIC Command */ + dwc_ufshcd_send_dme_command(hba); + + /* if Interrupt is not enabled use polling mode */ + if (intr_mode == DWC_UFS_HCD_DME_INTR_DISABLE) + dwc_ufshcd_handle_dme_command_completion_wo_intr(hba); + + /* With this lock held is DME access atomic on SMP? */ + spin_unlock_irqrestore(hba->shost->host_lock, flags_irq); + + /* If interrupt is enabled and the handler type is wait queue, block + * here till interrupt wakes it up */ + if ((intr_mode == DWC_UFS_HCD_DME_INTR_ENABLE) && + (hba->dme_completion_handler_type == + DWC_UFS_HANDLER_WAIT_QUEUE_TYPE)) { + result = wait_event_interruptible_timeout(hba->dwc_ufshcd_dme_wait_queue, + (hba->active_dme_cmd.cmd_active == 0), + DWC_UFS_DME_TIMEOUT); + + if (result == 0) { + pr_err("DME Command Timed-out\n"); + result = FAILED; + } else { + dwc_ufshcd_update_dme_cmd_results(hba); + result = 0; + } + } + + return result; +} + +/** + * dwc_ufs_dme_peer_get() + * The function to gett the peer unipro attribute + * It is caller's responsibility ensure HC is enabled before invoking this + * function. + * If intr_mode indicates that interrupt is not required, the functin blocks + * untile the command completes. + * If intr_mode indicates that interrupt is required, and the interrupt + * handling is wait queue type + * - execution thread waits on the wait queue until one of the following + * occurs + * - woken up by ISR after receiving the interrupt or times-out + * @hba: Pointer to drivers structure + * @attr_id: unipro attribure id + * @sttr_set_type: local or static + * @intr_mode: interrupt to be enabled or not + * + * Returns 0 for success and non-zero value for failure + */ +int dwc_ufs_dme_peer_get(struct dwc_ufs_hba *hba, u32 attr_id, + u32 attr_set_type, u32 intr_mode) +{ + unsigned long flags_irq; + u32 hcs; + int result = 0; + + /* Check if DWC HC is ready to accept DME commands */ + hcs = dwc_ufs_readl(hba->mmio_base, DWC_UFS_REG_HCS); + if (dwc_ufshcd_is_dme_sap_ready(hcs) == false) { + pr_err("Hardware (DME) Busy\n"); + return -EIO; + } + + /* Only One DME command at a time */ + spin_lock_irqsave(hba->shost->host_lock, flags_irq); + + /* Prepare the dme_peer_get command */ + hba->active_dme_cmd.command = DWC_UFS_DME_PEER_GET; + hba->active_dme_cmd.argument1 = attr_id << 16; + hba->active_dme_cmd.argument2 = 0; + + if (attr_set_type == DWC_UFS_DME_ATTR_SET_TYPE_STATIC) + hba->active_dme_cmd.argument2 = + DWC_UFS_DME_ATTR_SET_TYPE_STATIC << 16; + + hba->active_dme_cmd.argument3 = 0; + + /* Enable UIC Interrupt if required */ + if (intr_mode == DWC_UFS_HCD_DME_INTR_ENABLE) { + hba->intr_enable_mask |= DWC_UFS_UCCE; + dwc_ufshcd_configure_interrupt(hba, DWC_UFS_HCD_INTR_ENABLE); + } + + /* Send UIC Command */ + dwc_ufshcd_send_dme_command(hba); + + /* if Interrupt is not enabled use polling mode */ + if (intr_mode == DWC_UFS_HCD_DME_INTR_DISABLE) + dwc_ufshcd_handle_dme_command_completion_wo_intr(hba); + + /* With this lock held is DME access atomic on SMP? */ + spin_unlock_irqrestore(hba->shost->host_lock, flags_irq); + + /* If interrupt is enabled and the handler type is wait queue, block + * here till interrupt wakes it up */ + if ((intr_mode == DWC_UFS_HCD_DME_INTR_ENABLE) && + (hba->dme_completion_handler_type == + DWC_UFS_HANDLER_WAIT_QUEUE_TYPE)) { + result = wait_event_interruptible_timeout(hba->dwc_ufshcd_dme_wait_queue, + (hba->active_dme_cmd.cmd_active == 0), + DWC_UFS_DME_TIMEOUT); + + if (result == 0) { + pr_err("DME Command Timed-out\n"); + result = FAILED; + } else { + dwc_ufshcd_update_dme_cmd_results(hba); + result = 0; + } + } + + return result; +} + +/** + * dwc_ufs_dme_peer_set() + * The function to set the peer side unipro attribute + * It is caller's responsibility ensure HC is enabled before invoking this + * function. + * If intr_mode indicates that interrupt is not required, the functin blocks + * untile the command completes. + * If intr_mode indicates that interrupt is required, and the interrupt + * handling is wait queue type + * - execution thread waits on the wait queue until one of the following + * occurs + * - woken up by ISR after receiving the interrupt or times-out + * @hba: Pointer to drivers structure + * @attr_id: unipro attribure id + * @gen_sel_index: selector + * @attr_val: attribute value + * @sttr_set_type: local or static + * @intr_mode: interrupt to be enabled or not + * + * Returns 0 for success and non-zero value for failure + */ +int dwc_ufs_dme_peer_set(struct dwc_ufs_hba *hba, u32 attr_id, + u32 gen_sel_index, u32 attr_val, u32 attr_set_type, u32 intr_mode) +{ + unsigned long flags_irq; + u32 hcs; + int result = 0; + + /* Check if DWC HC is ready to accept DME commands */ + hcs = dwc_ufs_readl(hba->mmio_base, DWC_UFS_REG_HCS); + if (dwc_ufshcd_is_dme_sap_ready(hcs) == false) { + pr_err("Hardware (DME) Busy\n"); + return -EIO; + } + + /* Only One DME command at a time */ + spin_lock_irqsave(hba->shost->host_lock, flags_irq); + + /* Prepare the dme_set command */ + hba->active_dme_cmd.command = DWC_UFS_DME_PEER_SET; + hba->active_dme_cmd.argument1 = ((attr_id << 16) | gen_sel_index); + hba->active_dme_cmd.argument2 = 0; + + if (attr_set_type == DWC_UFS_DME_ATTR_SET_TYPE_STATIC) + hba->active_dme_cmd.argument2 = + DWC_UFS_DME_ATTR_SET_TYPE_STATIC << 16; + + hba->active_dme_cmd.argument3 = attr_val; + + /* Enable UIC Interrupt if required */ + if (intr_mode == DWC_UFS_HCD_DME_INTR_ENABLE) { + hba->intr_enable_mask |= DWC_UFS_UCCE; + dwc_ufshcd_configure_interrupt(hba, DWC_UFS_HCD_INTR_ENABLE); + } + + /* Send UIC Command */ + dwc_ufshcd_send_dme_command(hba); + + /* if Interrupt is not enabled use polling mode */ + if (intr_mode == DWC_UFS_HCD_DME_INTR_DISABLE) + dwc_ufshcd_handle_dme_command_completion_wo_intr(hba); + + /* With this lock held is DME access atomic on SMP? */ + spin_unlock_irqrestore(hba->shost->host_lock, flags_irq); + + /* If interrupt is enabled and the handler type is wait queue, block + * here till interrupt wakes it up */ + if ((intr_mode == DWC_UFS_HCD_DME_INTR_ENABLE) && + (hba->dme_completion_handler_type == + DWC_UFS_HANDLER_WAIT_QUEUE_TYPE)) { + result = wait_event_interruptible_timeout(hba->dwc_ufshcd_dme_wait_queue, + (hba->active_dme_cmd.cmd_active == 0), + DWC_UFS_DME_TIMEOUT); + + if (result == 0) { + pr_err("DME Command Timed-out\n"); + result = FAILED; + } else { + dwc_ufshcd_update_dme_cmd_results(hba); + result = 0; + } + } + + return result; +} + +/** + * dwc_ufs_dme_enable() + * The function to perform the dme_enable operation + * It is caller's responsibility ensure HC is enabled before invoking this + * function. + * If intr_mode indicates that interrupt is not required, the functin blocks + * untile the command completes. + * If intr_mode indicates that interrupt is required, and the interrupt + * handling is wait queue type + * - execution thread waits on the wait queue until one of the following + * occurs + * - woken up by ISR after receiving the interrupt or + * - times-out + * @hba: Pointer to drivers structure + * @intr_mode: interrupt to be enabled or not + * + * Returns 0 for success and non-zero value for failure + */ +int dwc_ufs_dme_enable(struct dwc_ufs_hba *hba, u32 intr_mode) +{ + unsigned long flags_irq; + u32 hcs; + int result = 0; + + /* Check if DWC HC is ready to accept DME commands */ + hcs = dwc_ufs_readl(hba->mmio_base, DWC_UFS_REG_HCS); + if (dwc_ufshcd_is_dme_sap_ready(hcs) == false) { + pr_err("Hardware (DME) Busy\n"); + return -EIO; + } + + /* Only One DME command at a time */ + spin_lock_irqsave(hba->shost->host_lock, flags_irq); + + /* Prepare the dme_enable command */ + hba->active_dme_cmd.command = DWC_UFS_DME_ENABLE; + hba->active_dme_cmd.argument1 = 0; + hba->active_dme_cmd.argument2 = 0; + hba->active_dme_cmd.argument3 = 0; + + /* Enable UIC Interrupt if required */ + if (intr_mode == DWC_UFS_HCD_DME_INTR_ENABLE) { + hba->intr_enable_mask |= DWC_UFS_UCCE; + dwc_ufshcd_configure_interrupt(hba, DWC_UFS_HCD_INTR_ENABLE); + } + + /* Send UIC Command */ + dwc_ufshcd_send_dme_command(hba); + + /* if Interrupt is not enabled use polling mode */ + if (intr_mode == DWC_UFS_HCD_DME_INTR_DISABLE) + dwc_ufshcd_handle_dme_command_completion_wo_intr(hba); + + /* With this lock held is DME access atomic on SMP? */ + spin_unlock_irqrestore(hba->shost->host_lock, flags_irq); + + /* If interrupt is enabled and the handler type is wait queue, block + * here till interrupt wakes it up */ + if ((intr_mode == DWC_UFS_HCD_DME_INTR_ENABLE) && + (hba->dme_completion_handler_type == + DWC_UFS_HANDLER_WAIT_QUEUE_TYPE)) { + result = wait_event_interruptible_timeout(hba->dwc_ufshcd_dme_wait_queue, + (hba->active_dme_cmd.cmd_active == 0), + DWC_UFS_DME_TIMEOUT); + + if (result == 0) { + pr_err("DME Command Timed-out\n"); + result = FAILED; + } else { + dwc_ufshcd_update_dme_cmd_results(hba); + result = 0; + } + } + + return result; +} + +/** + * dwc_ufs_dme_reset() + * The function to reset unipro stack + * It is caller's responsibility ensure HC is enabled before invoking this + * function. + * If intr_mode indicates that interrupt is not required, the functin blocks + * untile the command completes. + * If intr_mode indicates that interrupt is required, and the interrupt + * handling is wait queue type + * - execution thread waits on the wait queue until one of the following + * occurs + * - woken up by ISR after receiving the interrupt or times-out + * @hba: Pointer to drivers structure + * @reset_level: Warm reset or cold reset + * @intr_mode: interrupt to be enabled or not + * + * Returns 0 for success and non-zero value for failure + */ +int dwc_ufs_dme_reset(struct dwc_ufs_hba *hba, u32 reset_level, u32 intr_mode) +{ + unsigned long flags_irq; + u32 hcs; + int result = 0; + + /* Check if DWC HC is ready to accept DME commands */ + hcs = dwc_ufs_readl(hba->mmio_base, DWC_UFS_REG_HCS); + if (dwc_ufshcd_is_dme_sap_ready(hcs) == false) { + pr_err("Hardware (DME) Busy\n"); + return -EIO; + } + + /* Only One DME command at a time */ + spin_lock_irqsave(hba->shost->host_lock, flags_irq); + + /* Prepare the dme_reset command */ + hba->active_dme_cmd.command = DWC_UFS_DME_RESET; + if (reset_level == DWC_UFS_DME_RESET_LEVEL_WARM) + hba->active_dme_cmd.argument1 = DWC_UFS_DME_RESET_LEVEL_WARM; + else if (reset_level == DWC_UFS_DME_RESET_LEVEL_COLD) + hba->active_dme_cmd.argument1 = DWC_UFS_DME_RESET_LEVEL_COLD; + + /* Enable UIC Interrupt if required */ + if (intr_mode == DWC_UFS_HCD_DME_INTR_ENABLE) { + hba->intr_enable_mask |= DWC_UFS_UCCE; + dwc_ufshcd_configure_interrupt(hba, DWC_UFS_HCD_INTR_ENABLE); + } + + /* Send UIC Command */ + dwc_ufshcd_send_dme_command(hba); + + /* if Interrupt is not enabled use polling mode */ + if (intr_mode == DWC_UFS_HCD_DME_INTR_DISABLE) + dwc_ufshcd_handle_dme_command_completion_wo_intr(hba); + + /* With this lock held is DME access atomic on SMP? */ + spin_unlock_irqrestore(hba->shost->host_lock, flags_irq); + + /* If interrupt is enabled and the handler type is wait queue, block + * here till interrupt wakes it up */ + if ((intr_mode == DWC_UFS_HCD_DME_INTR_ENABLE) && + (hba->dme_completion_handler_type == + DWC_UFS_HANDLER_WAIT_QUEUE_TYPE)) { + result = wait_event_interruptible_timeout(hba->dwc_ufshcd_dme_wait_queue, + (hba->active_dme_cmd.cmd_active == 0), + DWC_UFS_DME_TIMEOUT); + + if (result == 0) { + pr_err("DME Command Timed-out\n"); + result = FAILED; + } else { + dwc_ufshcd_update_dme_cmd_results(hba); + result = 0; + } + } + + return result; +} + +/** + * dwc_ufs_dme_endpointreset() + * The function to reset the endpoint + * It is caller's responsibility ensure HC is enabled before invoking this + * function. + * If intr_mode indicates that interrupt is not required, the functin blocks + * untile the command completes. + * If intr_mode indicates that interrupt is required, and the interrupt + * handling is wait queue type + * - execution thread waits on the wait queue until one of the following + * occurs + * - woken up by ISR after receiving the interrupt or times-out + * @hba: Pointer to drivers structure + * @intr_mode: interrupt to be enabled or not + * + * Returns 0 for success and non-zero value for failure + */ +int dwc_ufs_dme_endpointreset(struct dwc_ufs_hba *hba, u32 intr_mode) +{ + unsigned long flags_irq; + u32 hcs; + int result = 0; + + /* Check if DWC HC is ready to accept DME commands */ + hcs = dwc_ufs_readl(hba->mmio_base, DWC_UFS_REG_HCS); + + if (dwc_ufshcd_is_dme_sap_ready(hcs) == false) { + pr_err("Hardware (DME) Busy\n"); + return -EIO; + } + + /* Only One DME command at a time */ + spin_lock_irqsave(hba->shost->host_lock, flags_irq); + + /* Prepare the dme_endpointreset command */ + hba->active_dme_cmd.command = DWC_UFS_DME_ENDPOINTRESET; + hba->active_dme_cmd.argument1 = 0; + hba->active_dme_cmd.argument2 = 0; + hba->active_dme_cmd.argument3 = 0; + + /* Enable UIC Interrupt if required */ + if (intr_mode == DWC_UFS_HCD_DME_INTR_ENABLE) { + hba->intr_enable_mask |= DWC_UFS_UCCE; + dwc_ufshcd_configure_interrupt(hba, DWC_UFS_HCD_INTR_ENABLE); + } + + /* Send UIC Command */ + dwc_ufshcd_send_dme_command(hba); + + /* if Interrupt is not enabled use polling mode */ + if (intr_mode == DWC_UFS_HCD_DME_INTR_DISABLE) + dwc_ufshcd_handle_dme_command_completion_wo_intr(hba); + + /* With this lock held is DME access atomic on SMP? */ + spin_unlock_irqrestore(hba->shost->host_lock, flags_irq); + + /* If interrupt is enabled and the handler type is wait queue, block + * here till interrupt wakes it up */ + if ((intr_mode == DWC_UFS_HCD_DME_INTR_ENABLE) && + (hba->dme_completion_handler_type == + DWC_UFS_HANDLER_WAIT_QUEUE_TYPE)) { + result = wait_event_interruptible_timeout(hba->dwc_ufshcd_dme_wait_queue, + (hba->active_dme_cmd.cmd_active == 0), + DWC_UFS_DME_TIMEOUT); + if (result == 0) { + pr_err("DME Command Timed-out\n"); + result = FAILED; + } else { + dwc_ufshcd_update_dme_cmd_results(hba); + result = 0; + } + } + + return result; +} + +/** + * dwc_ufs_dme_hibernate_enter() + * The function to put the unipro to hibernate stste + * It is caller's responsibility ensure HC is enabled before invoking this + * function. + * If intr_mode indicates that interrupt is not required, the functin blocks + * untile the command completes. + * If intr_mode indicates that interrupt is required, and the interrupt + * handling is wait queue type + * - execution thread waits on the wait queue until one of the following + * occurs + * - woken up by ISR after receiving the interrupt or times-out + * @hba: Pointer to drivers structure + * @intr_mode: interrupt to be enabled or not + * + * Returns 0 for success and non-zero value for failure + */ +int dwc_ufs_dme_hibernate_enter(struct dwc_ufs_hba *hba, u32 intr_mode) +{ + int result = 0; + unsigned long flags_irq; + u32 hcs; + + /* Check if DWC HC is ready to accept DME commands */ + hcs = dwc_ufs_readl(hba->mmio_base, DWC_UFS_REG_HCS); + if (dwc_ufshcd_is_dme_sap_ready(hcs) == false) { + pr_err("Hardware (DME) Busy\n"); + return -EIO; + } + + /* Only One DME command at a time */ + spin_lock_irqsave(hba->shost->host_lock, flags_irq); + + /* Prepare the dme_hibernate_enter command */ + hba->active_dme_cmd.command = DWC_UFS_DME_HIBERNATE_ENTER; + hba->active_dme_cmd.argument1 = 0; + hba->active_dme_cmd.argument2 = 0; + hba->active_dme_cmd.argument3 = 0; + + /* Enable UIC Interrupt if required */ + if (intr_mode == DWC_UFS_HCD_DME_INTR_ENABLE) { + hba->intr_enable_mask |= DWC_UFS_UCCE; + dwc_ufshcd_configure_interrupt(hba, DWC_UFS_HCD_INTR_ENABLE); + } + + /* Send UIC Command */ + dwc_ufshcd_send_dme_command(hba); + + /* if Interrupt is not enabled use polling mode */ + if (intr_mode == DWC_UFS_HCD_DME_INTR_DISABLE) + dwc_ufshcd_handle_dme_command_completion_wo_intr(hba); + + /* With this lock held is DME access atomic on SMP? */ + spin_unlock_irqrestore(hba->shost->host_lock, flags_irq); + + /* If interrupt is enabled and the handler type is wait queue, block + * here till interrupt wakes it up */ + if ((intr_mode == DWC_UFS_HCD_DME_INTR_ENABLE) && + (hba->dme_completion_handler_type == + DWC_UFS_HANDLER_WAIT_QUEUE_TYPE)) { + result = wait_event_interruptible_timeout(hba->dwc_ufshcd_dme_wait_queue, + (hba->active_dme_cmd.cmd_active == 0), + DWC_UFS_DME_TIMEOUT); + + if (result == 0) { + pr_err("DME Command Timed-out\n"); + result = FAILED; + } else { + dwc_ufshcd_update_dme_cmd_results(hba); + result = 0; + } + } + + return result; +} + +/** + * dwc_ufs_dme_hibernate_exit() + * The function to exit the hibernate state for unipro + * It is caller's responsibility ensure HC is enabled before invoking this + * function. + * If intr_mode indicates that interrupt is not required, the functin blocks + * untile the command completes. + * If intr_mode indicates that interrupt is required, and the interrupt + * handling is wait queue type + * - execution thread waits on the wait queue until one of the following + * occurs + * - woken up by ISR after receiving the interrupt or times-out + * @hba: Pointer to drivers structure + * @intr_mode: interrupt to be enabled or not + * + * Returns 0 for success and non-zero value for failure + */ +int dwc_ufs_dme_hibernate_exit(struct dwc_ufs_hba *hba, u32 intr_mode) +{ + int result = 0; + unsigned long flags_irq; + u32 hcs; + + /* Check if DWC HC is ready to accept DME commands */ + hcs = dwc_ufs_readl(hba->mmio_base, DWC_UFS_REG_HCS); + if (dwc_ufshcd_is_dme_sap_ready(hcs) == false) { + pr_err("Hardware (DME) Busy\n"); + return -EIO; + } + + /* Only One DME command at a time */ + spin_lock_irqsave(hba->shost->host_lock, flags_irq); + + /* Prepare the dme_hibernate_exit command */ + hba->active_dme_cmd.command = DWC_UFS_DME_HIBERNATE_EXIT; + hba->active_dme_cmd.argument1 = 0; + hba->active_dme_cmd.argument2 = 0; + hba->active_dme_cmd.argument3 = 0; + + /* Enable UIC Interrupt if required */ + if (intr_mode == DWC_UFS_HCD_DME_INTR_ENABLE) { + hba->intr_enable_mask |= DWC_UFS_UCCE; + dwc_ufshcd_configure_interrupt(hba, DWC_UFS_HCD_INTR_ENABLE); + } + + /* Send UIC Command */ + dwc_ufshcd_send_dme_command(hba); + + /* if Interrupt is not enabled use polling mode */ + if (intr_mode == DWC_UFS_HCD_DME_INTR_DISABLE) + dwc_ufshcd_handle_dme_command_completion_wo_intr(hba); + + /* With this lock held is DME access atomic on SMP? */ + spin_unlock_irqrestore(hba->shost->host_lock, flags_irq); + + /* If interrupt is enabled and the handler type is wait queue, block + * here till interrupt wakes it up */ + if ((intr_mode == DWC_UFS_HCD_DME_INTR_ENABLE) && + (hba->dme_completion_handler_type == + DWC_UFS_HANDLER_WAIT_QUEUE_TYPE)) { + result = wait_event_interruptible_timeout(hba->dwc_ufshcd_dme_wait_queue, + (hba->active_dme_cmd.cmd_active == 0), + DWC_UFS_DME_TIMEOUT); + + if (result == 0) { + pr_err("DME Command Timed-out\n"); + result = FAILED; + } else { + dwc_ufshcd_update_dme_cmd_results(hba); + result = 0; + } + } + + return result; +} + +/** + * dwc_ufs_dme_link_startup() + * This function initiate the Unipro Link startup procedure. + * It is caller's responsibility ensure HC is enabled before invoking this + * function. + * If intr_mode indicates that interrupt is not required, the functin blocks + * untile the command completes. + * If intr_mode indicates that interrupt is required, and the interrupt + * handling is wait queue type + * - execution thread waits on the wait queue until one of the following + * occurs + * - woken up by ISR after receiving the interrupt or times-out + * @hba: Pointer to drivers structure + * @intr_mode: indicates whether interrupt to be enabled or not + * + * Returns 0 on success non-zero value on failure + */ +int dwc_ufs_dme_linkstartup(struct dwc_ufs_hba *hba, u32 intr_mode) +{ + unsigned long flags_irq; + u32 hcs; + int result = 0; + + /* Check if DWC HC is ready to accept DME commands */ + hcs = dwc_ufs_readl(hba->mmio_base, DWC_UFS_REG_HCS); + if (dwc_ufshcd_is_dme_sap_ready(hcs) == false) { + pr_err("Hardware (DME) Busy\n"); + return -EIO; + } + + /* Only One DME command at a time */ + spin_lock_irqsave(hba->shost->host_lock, flags_irq); + + /* Prepare the link_startup command */ + hba->active_dme_cmd.command = DWC_UFS_DME_LINKSTARTUP; + hba->active_dme_cmd.argument1 = 0; + hba->active_dme_cmd.argument2 = 0; + hba->active_dme_cmd.argument3 = 0; + + /* Enable UIC Interrupt if required */ + if (intr_mode == DWC_UFS_HCD_DME_INTR_ENABLE) { + hba->intr_enable_mask |= DWC_UFS_UCCE; + dwc_ufshcd_configure_interrupt(hba, DWC_UFS_HCD_INTR_ENABLE); + } + + /* Send UIC Command */ + dwc_ufshcd_send_dme_command(hba); + + /* if Interrupt is not enabled use polling mode */ + if (intr_mode == DWC_UFS_HCD_DME_INTR_DISABLE) + dwc_ufshcd_handle_dme_command_completion_wo_intr(hba); + + /* With this lock held is DME access atomic on SMP? */ + spin_unlock_irqrestore(hba->shost->host_lock, flags_irq); + + /* If interrupt is enabled and the handler type is wait queue, block + * here till interrupt wakes it up */ + if ((intr_mode == DWC_UFS_HCD_DME_INTR_ENABLE) && + (hba->dme_completion_handler_type == + DWC_UFS_HANDLER_WAIT_QUEUE_TYPE)) { + result = wait_event_interruptible_timeout(hba->dwc_ufshcd_dme_wait_queue, + (hba->active_dme_cmd.cmd_active == 0), + DWC_UFS_DME_TIMEOUT); + + if (result == 0) { + pr_err("DME Command Timed-out\n"); + result = FAILED; + } else { + dwc_ufshcd_update_dme_cmd_results(hba); + result = 0; + } + } + + return result; +} + +#ifdef CONFIG_SCSI_DWC_UFS_MPHY_TC +/** + * dwc_ufs_dme_setup_snps_mphy() + * This function configures Local (host) Synopsys MPHY specific + * attributes + * @hba: Pointer to drivers structure + * @intr_mode: indicates whether interrupt to be enabled or not + * + * Returns 0 on success non-zero value on failure + */ +int dwc_ufs_dme_setup_snps_mphy(struct dwc_ufs_hba *hba, u32 intr_mode) +{ + int result = 0; + + /* Before doing mphy attribute programming; understand about + * the available lanes + */ + dwc_ufs_dme_get(hba, UNIPRO_TX_LANES, 0, intr_mode); + hba->linfo.pa_ava_tx_data_lanes_local = hba->active_dme_cmd.argument3; + dwc_ufs_dme_get(hba, UNIPRO_RX_LANES, 0, intr_mode); + hba->linfo.pa_ava_rx_data_lanes_local = hba->active_dme_cmd.argument3; + + /* Synopsys MPHY Configuration */ +#ifdef CONFIG_SCSI_DWC_UFS_40BIT_RMMI + /******************** Common Block *******************/ + + /* Common block Tx Global Hibernate Exit */ + if (dwc_ufs_dme_set(hba, UNIPRO_COMMON_BLK|MPHY_CB_GLOBAL_HIBERNATE, + SEL_INDEX_LANE0_TX, 0x00, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* Common block Reference Clock Mode 26MHzt */ + if (dwc_ufs_dme_set(hba, UNIPRO_COMMON_BLK|MPHY_CB_REF_CLOCK, + SEL_INDEX_LANE0_TX, 0x01, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* Common block DCO Target Frequency MAX PWM G1:7Mpbs */ + if (dwc_ufs_dme_set(hba, UNIPRO_COMMON_BLK|MPHY_CB_TARGET_FREQ, + SEL_INDEX_LANE0_TX, 0x80, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* Common block TX and RX Div Factor is 4 7Mbps/40 = 175KHz */ + if (dwc_ufs_dme_set(hba, UNIPRO_COMMON_BLK|MPHY_CB_CLK_DIV_RATIO, + SEL_INDEX_LANE0_TX, 0x08, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* Common Block */ + if (dwc_ufs_dme_set(hba, UNIPRO_COMMON_BLK|MPHY_CB_DCO_CTRL_5, + SEL_INDEX_LANE0_TX, 0x64, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* Common Block */ + if (dwc_ufs_dme_set(hba, UNIPRO_COMMON_BLK|MPHY_CB_PRG_TUNNING, + SEL_INDEX_LANE0_TX, 0x09, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* Common Block Real Time Observe Select; For debugging */ + if (dwc_ufs_dme_set(hba, UNIPRO_COMMON_BLK|MPHY_CB_REAL_TIME_OBS, + SEL_INDEX_LANE0_TX, 0x00, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + + /********************** Lane 0 *******************/ + + /* Tx0 Reference Clock 26MHz */ + if (dwc_ufs_dme_set(hba, MPHY_TARGET_REF_CLK, SEL_INDEX_LANE0_TX, + 0x01, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* TX0 Configuration Clock Frequency Val; Divider setting */ + if (dwc_ufs_dme_set(hba, MPHY_SETS_INTERNAL_DIV, SEL_INDEX_LANE0_TX, + 0x19, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* TX0 40bit RMMI Interface */ + if (dwc_ufs_dme_set(hba, MPHY_SEV_CTRL_PARM, SEL_INDEX_LANE0_TX, + 0x14, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* TX0 */ + if (dwc_ufs_dme_set(hba, MPHY_TX_INT_EXT_DIFF, SEL_INDEX_LANE0_TX, + 0xd6, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* Rx0 Reference Clock 26MHz */ + if (dwc_ufs_dme_set(hba, MPHY_TARGET_REF_CLK, SEL_INDEX_LANE0_RX, + 0x01, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* RX0 Configuration Clock Frequency Val; Divider setting */ + if (dwc_ufs_dme_set(hba, MPHY_SETS_INTERNAL_DIV, SEL_INDEX_LANE0_RX, + 0x19, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* RX0 40bit RMMI Interface */ + if (dwc_ufs_dme_set(hba, MPHY_SEV_CTRL_PARM, SEL_INDEX_LANE0_RX, + 4, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* RX0 Squelch Detector output is routed to RX hibernate Exit + * Type1 signal + */ + if (dwc_ufs_dme_set(hba, MPHY_RX_HIBERN8, SEL_INDEX_LANE0_RX, + 0x80, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* DIRECTCTRL10 */ + if (dwc_ufs_dme_set(hba, UNIPRO_COMMON_BLK|MPHY_CB_DIRECTCTRL10, + SEL_INDEX_LANE0_TX, 0x04, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* DIRECTCTRL19 */ + if (dwc_ufs_dme_set(hba, UNIPRO_COMMON_BLK|MPHY_CB_DIRECTCTRL19, + SEL_INDEX_LANE0_TX, 0x02, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* ENARXDIRECTCFG4 */ + if (dwc_ufs_dme_set(hba, MPHY_RX_SEV_CDR_CFG4, SEL_INDEX_LANE0_RX, + 0x03, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* CFGRXOVR8 */ + if (dwc_ufs_dme_set(hba, MPHY_RX_CFG_OVER8, SEL_INDEX_LANE0_RX, + 0x16, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* RXDIRECTCTRL2 */ + if (dwc_ufs_dme_set(hba, MPHY_RX_DIRECT_CTRL2, SEL_INDEX_LANE0_RX, + 0x42, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* ENARXDIRECTCFG3 */ + if (dwc_ufs_dme_set(hba, MPHY_RX_SEV_CDR_CFG3, SEL_INDEX_LANE0_RX, + 0xa4, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* RXCALCTRL */ + if (dwc_ufs_dme_set(hba, MPHY_RX_CAL_CTRL, SEL_INDEX_LANE0_RX, + 0x01, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* ENARXDIRECTCFG2 */ + if (dwc_ufs_dme_set(hba, MPHY_RX_SEV_CDR_CFG2, SEL_INDEX_LANE0_RX, + 0x01, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* CFGOVR4 */ + if (dwc_ufs_dme_set(hba, MPHY_RX_CFGRXOVR4, SEL_INDEX_LANE0_RX, + 0x28, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* RXSQCTRL */ + if (dwc_ufs_dme_set(hba, MPHY_RX_RXSQCTRL, SEL_INDEX_LANE0_RX, + 0x1E, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* CFGOVR6 */ + if (dwc_ufs_dme_set(hba, MPHY_RX_CFGRXOVR6, SEL_INDEX_LANE0_RX, + 0x2f, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* CBPRGPLL2 */ + if (dwc_ufs_dme_set(hba, UNIPRO_COMMON_BLK|MPHY_CB_CFG_PRG_PLL2, + SEL_INDEX_LANE0_TX, 0x00, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + +#else + + /******************** Common Block *******************/ + + /* Common block Tx Global Hibernate Exit */ + if (dwc_ufs_dme_set(hba, UNIPRO_COMMON_BLK|MPHY_CB_GLOBAL_HIBERNATE, + SEL_INDEX_LANE0_TX, 0x00, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* Common block Reference Clokc Mode 26MHzt */ + if (dwc_ufs_dme_set(hba, UNIPRO_COMMON_BLK|MPHY_CB_REF_CLOCK, + SEL_INDEX_LANE0_TX, 0x01, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* Common block DCO Target Frequency MAX PWM G1:9Mpbs */ + if (dwc_ufs_dme_set(hba, UNIPRO_COMMON_BLK|MPHY_CB_TARGET_FREQ, + SEL_INDEX_LANE0_TX, 0xc0, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + + /* Common block TX and RX Div Factor is 4 7Mbps/20 = 350KHz */ + if (dwc_ufs_dme_set(hba, UNIPRO_COMMON_BLK|MPHY_CB_CLK_DIV_RATIO, + SEL_INDEX_LANE0_TX, 0x44, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* Common Block */ + if (dwc_ufs_dme_set(hba, UNIPRO_COMMON_BLK|MPHY_CB_DCO_CTRL_5, + SEL_INDEX_LANE0_TX, 0x64, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* Common Block */ + if (dwc_ufs_dme_set(hba, UNIPRO_COMMON_BLK|MPHY_CB_PRG_TUNNING, + SEL_INDEX_LANE0_TX, 0x09, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* Common Block Real Time Observe Select; For debugging */ + if (dwc_ufs_dme_set(hba, UNIPRO_COMMON_BLK|MPHY_CB_REAL_TIME_OBS, + SEL_INDEX_LANE0_TX, 0x00, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + + /********************** Lane 0 *******************/ + + /* Tx0 Reference Clock 26MHz */ + if (dwc_ufs_dme_set(hba, MPHY_TARGET_REF_CLK, SEL_INDEX_LANE0_TX, + 0x0d, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + +#ifdef CONFIG_SCSI_DWC_UFS_MPHY_TC_GEN2 + /* TX0 Configuration Clock Frequency Val; Divider setting */ + if (dwc_ufs_dme_set(hba, MPHY_SETS_INTERNAL_DIV, SEL_INDEX_LANE0_TX, + 0x19, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* RX0 Configuration Clock Frequency Val; Divider setting */ + if (dwc_ufs_dme_set(hba, MPHY_SETS_INTERNAL_DIV, SEL_INDEX_LANE0_RX, + 0x19, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + +#else + /* TX0 Configuration Clock Frequency Val; Divider setting */ + if (dwc_ufs_dme_set(hba, MPHY_SETS_INTERNAL_DIV, SEL_INDEX_LANE0_TX, + 0x1b, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* RX0 Configuration Clock Frequency Val; Divider setting */ + if (dwc_ufs_dme_set(hba, MPHY_SETS_INTERNAL_DIV, SEL_INDEX_LANE0_RX, + 0x1b, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + +#endif + + /* TX0 20bit RMMI Interface */ + if (dwc_ufs_dme_set(hba, MPHY_SEV_CTRL_PARM, SEL_INDEX_LANE0_TX, + 0x12, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* TX0 */ + if (dwc_ufs_dme_set(hba, MPHY_TX_INT_EXT_DIFF, SEL_INDEX_LANE0_TX, + 0xd6, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* Rx0 Reference Clock 26MHz */ + if (dwc_ufs_dme_set(hba, MPHY_TARGET_REF_CLK, SEL_INDEX_LANE0_RX, + 0x01, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* RX0 20bit RMMI Interface */ + if (dwc_ufs_dme_set(hba, MPHY_SEV_CTRL_PARM, SEL_INDEX_LANE0_RX, + 2, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* RX0 Squelch Detector output is routed to RX hibernate Exit + * Type1 signal + */ + if (dwc_ufs_dme_set(hba, MPHY_RX_HIBERN8, SEL_INDEX_LANE0_RX, + 0x80, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + +#ifdef CONFIG_SCSI_DWC_UFS_MPHY_TC_GEN2 + /* DIRECTCTRL10 */ + if (dwc_ufs_dme_set(hba, UNIPRO_COMMON_BLK|MPHY_CB_DIRECTCTRL10, + SEL_INDEX_LANE0_TX, 0x04, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* DIRECTCTRL19 */ + if (dwc_ufs_dme_set(hba, UNIPRO_COMMON_BLK|MPHY_CB_DIRECTCTRL19, + SEL_INDEX_LANE0_TX, 0x02, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* ENARXDIRECTCFG4 */ + if (dwc_ufs_dme_set(hba, MPHY_RX_SEV_CDR_CFG4, SEL_INDEX_LANE0_RX, + 0x03, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* CFGRXOVR8 */ + if (dwc_ufs_dme_set(hba, MPHY_RX_CFG_OVER8, SEL_INDEX_LANE0_RX, + 0x16, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* RXDIRECTCTRL2 */ + if (dwc_ufs_dme_set(hba, MPHY_RX_DIRECT_CTRL2, SEL_INDEX_LANE0_RX, + 0x42, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* ENARXDIRECTCFG3 */ + if (dwc_ufs_dme_set(hba, MPHY_RX_SEV_CDR_CFG3, SEL_INDEX_LANE0_RX, + 0xa4, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* RXCALCTRL */ + if (dwc_ufs_dme_set(hba, MPHY_RX_CAL_CTRL, SEL_INDEX_LANE0_RX, + 0x01, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* ENARXDIRECTCFG2 */ + if (dwc_ufs_dme_set(hba, MPHY_RX_SEV_CDR_CFG2, SEL_INDEX_LANE0_RX, + 0x01, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* CFGOVR4 */ + if (dwc_ufs_dme_set(hba, MPHY_RX_CFGRXOVR4, SEL_INDEX_LANE0_RX, + 0x28, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* RXSQCTRL */ + if (dwc_ufs_dme_set(hba, MPHY_RX_RXSQCTRL, SEL_INDEX_LANE0_RX, + 0x1E, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* CFGOVR6 */ + if (dwc_ufs_dme_set(hba, MPHY_RX_CFGRXOVR6, SEL_INDEX_LANE0_RX, + 0x2f, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* CBPRGPLL2 */ + if (dwc_ufs_dme_set(hba, UNIPRO_COMMON_BLK|MPHY_CB_CFG_PRG_PLL2, + SEL_INDEX_LANE0_TX, 0x00, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; +#endif + + /********************** Lane 1 *******************/ + + if (hba->linfo.pa_ava_tx_data_lanes_local == 2) { + /* Tx1 Reference Clock 26MHz */ + if (dwc_ufs_dme_set(hba, MPHY_TARGET_REF_CLK, + SEL_INDEX_LANE1_TX, 0x0d, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + +#ifdef CONFIG_SCSI_DWC_UFS_MPHY_TC_GEN2 + /* TX1 Configuration Clock Frequency Val; Divider setting */ + if (dwc_ufs_dme_set(hba, MPHY_SETS_INTERNAL_DIV, + SEL_INDEX_LANE1_TX, 0x19, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; +#else + /* TX1 Configuration Clock Frequency Val; Divider setting */ + if (dwc_ufs_dme_set(hba, MPHY_SETS_INTERNAL_DIV, + SEL_INDEX_LANE1_TX, 0x1b, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; +#endif + + /* TX1 20bit RMMI Interface */ + if (dwc_ufs_dme_set(hba, MPHY_SEV_CTRL_PARM, SEL_INDEX_LANE1_TX, + 0x12, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* TX1 */ + if (dwc_ufs_dme_set(hba, MPHY_TX_INT_EXT_DIFF, + SEL_INDEX_LANE1_TX, 0xd6, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + } + + if (hba->linfo.pa_ava_rx_data_lanes_local == 2) { + /* Rx1 Reference Clock 26MHz */ + if (dwc_ufs_dme_set(hba, MPHY_TARGET_REF_CLK, + SEL_INDEX_LANE1_RX, 0x01, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + +#ifdef CONFIG_SCSI_DWC_UFS_MPHY_TC_GEN2 + /* RX1 Configuration Clock Frequency Val; Divider setting */ + if (dwc_ufs_dme_set(hba, MPHY_SETS_INTERNAL_DIV, + SEL_INDEX_LANE1_RX, 0x19, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; +#else + /* RX1 Configuration Clock Frequency Val; Divider setting */ + if (dwc_ufs_dme_set(hba, MPHY_SETS_INTERNAL_DIV, + SEL_INDEX_LANE1_RX, 0x1b, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; +#endif + + /* RX1 20bit RMMI Interface */ + if (dwc_ufs_dme_set(hba, MPHY_SEV_CTRL_PARM, SEL_INDEX_LANE1_RX, + 2, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* RX1 Squelch Detector output is routed to RX hibernate Exit + * Type1 signal + */ + if (dwc_ufs_dme_set(hba, MPHY_RX_HIBERN8, SEL_INDEX_LANE1_RX, + 0x80, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + +#ifdef CONFIG_SCSI_DWC_UFS_MPHY_TC_GEN2 + + /* ENARXDIRECTCFG4 */ + if (dwc_ufs_dme_set(hba, MPHY_RX_SEV_CDR_CFG4, + SEL_INDEX_LANE1_RX, 0x03, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* CFGRXOVR8 */ + if (dwc_ufs_dme_set(hba, MPHY_RX_CFG_OVER8, SEL_INDEX_LANE1_RX, + 0x16, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* RXDIRECTCTRL2 */ + if (dwc_ufs_dme_set(hba, MPHY_RX_DIRECT_CTRL2, + SEL_INDEX_LANE1_RX, 0x42, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* ENARXDIRECTCFG3 */ + if (dwc_ufs_dme_set(hba, MPHY_RX_SEV_CDR_CFG3, + SEL_INDEX_LANE1_RX, 0xa4, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* RXCALCTRL */ + if (dwc_ufs_dme_set(hba, MPHY_RX_CAL_CTRL, SEL_INDEX_LANE1_RX, + 0x01, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* ENARXDIRECTCFG2 */ + if (dwc_ufs_dme_set(hba, MPHY_RX_SEV_CDR_CFG2, + SEL_INDEX_LANE1_RX, 0x01, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* CFGOVR4 */ + if (dwc_ufs_dme_set(hba, MPHY_RX_CFGRXOVR4, SEL_INDEX_LANE1_RX, + 0x28, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* RXSQCTRL */ + if (dwc_ufs_dme_set(hba, MPHY_RX_RXSQCTRL, SEL_INDEX_LANE1_RX, + 0x1E, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* CFGOVR6 */ + if (dwc_ufs_dme_set(hba, MPHY_RX_CFGRXOVR6, SEL_INDEX_LANE1_RX, + 0x2f, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; +#endif + } + +#endif + /* To write Shadow register bank to effective configuration block */ + if (dwc_ufs_dme_set(hba, UNIPRO_CFG_UPDATE, SEL_INDEX_LANE0_TX, + 0x01, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* To configure Debug OMC */ + if (dwc_ufs_dme_set(hba, UNIPRO_DBG_OMC, SEL_INDEX_LANE0_TX, + 0x01, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + pr_info("Mphy Setup completed\n"); + + return result; + +err_dme_set: + result = FAILED; + return result; +} +#endif + +/** + * dwc_ufs_do_dme_connection_setup() + * This function configures both the local side (host) and the peer side + * (device) unipro attributes to establish the connection to application/ + * cport. + * This function is not required if the hardware is properly configured to + * have this connection setup on reset. But invoking this function does no + * harm and should be fine even working with any ufs device. + * The Unipro attributes programmed here are in line with the JEDEC specs. + * This function inturn invokes series of dme_set functions set unipro + * attributes. + * @hba: Pointer to drivers structure + * @intr_mode: indicates whether interrupt to be enabled or not + * + * Returns 0 on success non-zero value on failure + */ +int dwc_ufs_do_dme_connection_setup(struct dwc_ufs_hba *hba, u32 intr_mode) +{ + int result = 0; + + /* Local side Configuration */ + /* Before setting other attributes connection state to be set to 0 */ + if (dwc_ufs_dme_set(hba, UNIPRO_CPORT_CON_STAT, SEL_INDEX_LANE0_TX, + 0, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + if (dwc_ufs_dme_set(hba, UNIPRO_LOCAL_DEV_ID, SEL_INDEX_LANE0_TX, + 0, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + if (dwc_ufs_dme_set(hba, UNIPRO_LOCAL_DEV_ID_VAL, SEL_INDEX_LANE0_TX, + 0, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + if (dwc_ufs_dme_set(hba, UNIPRO_PEER_DEV_ID, SEL_INDEX_LANE0_TX, + 1, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + if (dwc_ufs_dme_set(hba, UNIPRO_PEER_CPORT_ID, SEL_INDEX_LANE0_TX, + 0, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + if (dwc_ufs_dme_set(hba, UNIPRO_TRAFFIC_CLASS, SEL_INDEX_LANE0_TX, + 0, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + if (dwc_ufs_dme_set(hba, UNIPRO_CPORT_FLAGS, SEL_INDEX_LANE0_TX, + 0x6, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + if (dwc_ufs_dme_set(hba, UNIPRO_CPORT_MODE, SEL_INDEX_LANE0_TX, + 1, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + if (dwc_ufs_dme_set(hba, UNIPRO_CPORT_CON_STAT, SEL_INDEX_LANE0_TX, + 1, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + /* Peer side Configuration */ + /* Before setting other attributes connection state to be set to 0 */ + if (dwc_ufs_dme_peer_set(hba, UNIPRO_CPORT_CON_STAT, SEL_INDEX_LANE0_TX, + 0, 0, intr_mode) != 0) + goto err_dme_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + if (dwc_ufs_dme_peer_set(hba, UNIPRO_LOCAL_DEV_ID, SEL_INDEX_LANE0_TX, + 1, 0, intr_mode) != 0) + goto err_dme_peer_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + if (dwc_ufs_dme_peer_set(hba, UNIPRO_LOCAL_DEV_ID_VAL, + SEL_INDEX_LANE0_TX, 1, 0, intr_mode) != 0) + goto err_dme_peer_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + if (dwc_ufs_dme_peer_set(hba, UNIPRO_PEER_DEV_ID, SEL_INDEX_LANE0_TX, + 1, 0, intr_mode) != 0) + goto err_dme_peer_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + if (dwc_ufs_dme_peer_set(hba, UNIPRO_PEER_CPORT_ID, SEL_INDEX_LANE0_TX, + 0, 0, intr_mode) != 0) + goto err_dme_peer_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + if (dwc_ufs_dme_peer_set(hba, UNIPRO_TRAFFIC_CLASS, SEL_INDEX_LANE0_TX, + 0, 0, intr_mode) != 0) + goto err_dme_peer_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + if (dwc_ufs_dme_peer_set(hba, UNIPRO_CPORT_FLAGS, SEL_INDEX_LANE0_TX, + 0x6, 0, intr_mode) != 0) + goto err_dme_peer_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + if (dwc_ufs_dme_peer_set(hba, UNIPRO_CPORT_MODE, SEL_INDEX_LANE0_TX, + 1, 0, intr_mode) != 0) + goto err_dme_peer_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + if (dwc_ufs_dme_peer_set(hba, UNIPRO_CPORT_CON_STAT, SEL_INDEX_LANE0_TX, + 1, 0, intr_mode) != 0) + goto err_dme_peer_set; + if (dwc_ufshcd_get_dme_command_results(hba) != 0) + goto err_dme_set; + + pr_info("Connection has been established\n"); + + return result; + +err_dme_set: +err_dme_peer_set: + result = FAILED; + return result; +} + +/** + * dwc_ufs_update_link_info() + * This function doesnot check for errors. + * We always expect this function works without errors. And this is a valid + * assumption to make as the link is in pwm gear and we are just reading + * attributes. + * + */ +int dwc_ufs_update_link_info(struct dwc_ufs_hba *hba, u32 intr_mode) +{ + int result = 0; + + dwc_ufs_dme_get(hba, UNIPRO_RX_LANES, 0, intr_mode); + hba->linfo.pa_ava_tx_data_lanes_local = hba->active_dme_cmd.argument3; + dwc_ufs_dme_peer_get(hba, UNIPRO_RX_LANES, 0, intr_mode); + hba->linfo.pa_ava_tx_data_lanes_peer = hba->active_dme_cmd.argument3; + + dwc_ufs_dme_get(hba, UNIPRO_TX_LANES, 0, intr_mode); + hba->linfo.pa_ava_rx_data_lanes_local = hba->active_dme_cmd.argument3; + dwc_ufs_dme_peer_get(hba, UNIPRO_TX_LANES, 0, intr_mode); + + hba->linfo.pa_ava_rx_data_lanes_peer = hba->active_dme_cmd.argument3; + dwc_ufs_dme_get(hba, UNIPRO_CON_TX_LANES, 0, intr_mode); + hba->linfo.pa_con_tx_data_lanes_local = hba->active_dme_cmd.argument3; + dwc_ufs_dme_peer_get(hba, UNIPRO_CON_TX_LANES, 0, intr_mode); + hba->linfo.pa_con_tx_data_lanes_peer = hba->active_dme_cmd.argument3; + + dwc_ufs_dme_get(hba, UNIPRO_CON_RX_LANES, 0, intr_mode); + hba->linfo.pa_con_rx_data_lanes_local = hba->active_dme_cmd.argument3; + dwc_ufs_dme_peer_get(hba, UNIPRO_CON_RX_LANES, 0, intr_mode); + hba->linfo.pa_con_rx_data_lanes_peer = hba->active_dme_cmd.argument3; + + dwc_ufs_dme_get(hba, UNIPRO_ACTIVE_TX_LANES, 0, intr_mode); + hba->linfo.pa_act_tx_data_lanes_local = hba->active_dme_cmd.argument3; + dwc_ufs_dme_peer_get(hba, UNIPRO_ACTIVE_TX_LANES, 0, intr_mode); + hba->linfo.pa_act_tx_data_lanes_peer = hba->active_dme_cmd.argument3; + + dwc_ufs_dme_get(hba, UNIPRO_ACTIVE_RX_LANES, 0, intr_mode); + hba->linfo.pa_act_rx_data_lanes_local = hba->active_dme_cmd.argument3; + dwc_ufs_dme_peer_get(hba, UNIPRO_ACTIVE_RX_LANES, 0, intr_mode); + hba->linfo.pa_act_rx_data_lanes_peer = hba->active_dme_cmd.argument3; + + dwc_ufs_dme_get(hba, UNIPRO_RX_MAX_PWM_GEAR, 0, intr_mode); + hba->linfo.pa_max_rx_pwm_gear_local = hba->active_dme_cmd.argument3; + dwc_ufs_dme_peer_get(hba, UNIPRO_RX_MAX_PWM_GEAR, 0, intr_mode); + hba->linfo.pa_max_rx_pwm_gear_peer = hba->active_dme_cmd.argument3; + + dwc_ufs_dme_get(hba, UNIPRO_RX_MAX_HS_GEAR, 0, intr_mode); + hba->linfo.pa_max_rx_hs_gear_local = hba->active_dme_cmd.argument3; + dwc_ufs_dme_peer_get(hba, UNIPRO_RX_MAX_HS_GEAR, 0, intr_mode); + hba->linfo.pa_max_rx_hs_gear_peer = hba->active_dme_cmd.argument3; + + dwc_ufs_dme_get(hba, UNIPRO_HS_SERIES, 0, intr_mode); + hba->linfo.pa_hs_series_local = hba->active_dme_cmd.argument3; + dwc_ufs_dme_peer_get(hba, UNIPRO_HS_SERIES, 0, intr_mode); + hba->linfo.pa_hs_series_peer = hba->active_dme_cmd.argument3; + + dwc_ufs_dme_get(hba, UNIPRO_PWR_MODE, 0, intr_mode); + hba->linfo.pa_pwr_mode_local = hba->active_dme_cmd.argument3; + dwc_ufs_dme_peer_get(hba, UNIPRO_PWR_MODE, 0, intr_mode); + hba->linfo.pa_pwr_mode_peer = hba->active_dme_cmd.argument3; + + dwc_ufs_dme_get(hba, UNIPRO_TX_PWR_STATUS, 0, intr_mode); + hba->linfo.pa_tx_pwr_status_local = hba->active_dme_cmd.argument3; + dwc_ufs_dme_peer_get(hba, UNIPRO_TX_PWR_STATUS, 0, intr_mode); + hba->linfo.pa_tx_pwr_status_peer = hba->active_dme_cmd.argument3; + + dwc_ufs_dme_get(hba, UNIPRO_RX_PWR_STATUS, 0, intr_mode); + hba->linfo.pa_rx_pwr_status_local = hba->active_dme_cmd.argument3; + dwc_ufs_dme_peer_get(hba, UNIPRO_RX_PWR_STATUS, 0, intr_mode); + hba->linfo.pa_rx_pwr_status_peer = hba->active_dme_cmd.argument3; + + return result; +} + +/** + * dwc_ufshcd_update_dm_results() + * This function does the post processing for the sent device management + * command. + * Only Nopout and flag operations are supported as of now + * TODO: Update for other query commands if required + */ +int dwc_ufshcd_update_dm_results(struct dwc_ufs_hba *hba, u32 slot_index) +{ + int result = 0; + struct dwc_ufs_nopin_upiu *nopin_upiu = NULL; + struct dwc_ufs_query_resp_upiu *query_resp_upiu = NULL; + unsigned long flags_irq; + + clear_bit(slot_index, &hba->outstanding_dm_requests); + clear_bit(slot_index, &hba->dwc_ufshcd_dm_condition); + + spin_lock_irqsave(hba->shost->host_lock, flags_irq); + + switch (hba->dm_lrb.trans_type) { + case UPIU_TRANS_CODE_NOP_OUT: + nopin_upiu = (struct dwc_ufs_nopin_upiu *) + (hba->ucdl_base_addr + slot_index)->resp_upiu; + + if (nopin_upiu->trans_type == UPIU_TRANS_CODE_NOP_IN) + hba->dm_lrb.dm_cmd_results = 0; + break; + case UPIU_TRANS_CODE_QUERY_REQ: + query_resp_upiu = (struct dwc_ufs_query_resp_upiu *) + (hba->ucdl_base_addr + slot_index)->resp_upiu; + + if (query_resp_upiu->txn_specific_flds_1[0] == + UPIU_QUERY_OPCODE_READ_FLAG) { + hba->dm_lrb.dm_cmd_results = + query_resp_upiu->txn_specific_flds_3[3]; + } else if (query_resp_upiu->txn_specific_flds_1[0] == + UPIU_QUERY_OPCODE_SET_FLAG) { + /*TODO: Fix this once clarified by JEDEC + * FLAG VALUE after setting is not clear: + * whether it shoudl be new updated value + * or the old value? + */ + hba->dm_lrb.dm_cmd_results = + DWC_UFS_SET_FLAG_RESULT_EXPECTED; + } else + pr_err("Not implemented opcode code:%x\n", + query_resp_upiu->txn_specific_flds_1[0]); + + break; + + default: + pr_err("Unknown transaction code:%x\n", + hba->dm_lrb.trans_type); + } + + spin_unlock_irqrestore(hba->shost->host_lock, flags_irq); + + return result; +} + +/** + * dwc_ufshcd_prepare_and_send_dm_cmd_upiu() + * This function prepares and send the dm command + * Pre-requisite to this function invocation is to popoulate dm_lrb + * structure of drivers private data with appropriate data using the + * helper function provided. + * This function + * - gets the free slot for dm command sending + * - populates the required elements of + * - utrd + * - cmd upiu + * - invokes the send command function + * - Blocks itself on a wait queue + * - inovkes the function to update dm command results + * @hba: Pointer to drivers structure + * + * Returns zero on success, non-zero value on failure + */ +int dwc_ufshcd_prepare_and_send_dm_cmd_upiu(struct dwc_ufs_hba *hba) +{ + int result = 0; + int free_slot = 0; + struct dwc_ufs_utrd *utrd; + struct dwc_ufs_query_req_upiu *query_req_upiu = NULL; + struct dwc_ufs_nopout_upiu *nopout_upiu = NULL; + unsigned long flags_irq; + + spin_lock_irqsave(hba->shost->host_lock, flags_irq); + + /* If task management queue is full */ + free_slot = dwc_ufshcd_get_xfer_req_free_slot(hba); + if (free_slot >= hba->nutmrs) { + spin_unlock_irqrestore(hba->shost->host_lock, flags_irq); + pr_err("Xfer Req queue is full\n"); + result = FAILED; + goto err_dm_req_queue_is_full; + } + + /* Get the base address of Xfer Req list base address */ + utrd = hba->utrl_base_addr; + + /* Get the xfer descriptor of the free slot */ + utrd += free_slot; + + /* Configure xfer request descriptor */ + /* Updating to ensure backward compatibility with older host + * controllers + */ + if (hba->dwc_ufs_version == DWC_UFS_HC_VERSION_2_0) + utrd->ct_and_flags = DWC_UTRD_CMD_TYPE_UFS_STORAGE | + DWC_UTRD_INTR_CMD; + else + utrd->ct_and_flags = DWC_UTRD_CMD_TYPE_DEV_MANAGE_FN | + DWC_UTRD_INTR_CMD; + + utrd->ocs = OCS_INVALID_COMMAND_STATUS; + + switch (hba->dm_lrb.trans_type) { + case UPIU_TRANS_CODE_NOP_OUT: + nopout_upiu = (struct dwc_ufs_nopout_upiu *) + (hba->ucdl_base_addr + free_slot)->cmd_upiu; + memset(nopout_upiu, 0, DWC_UCD_ALIGN * 2); + nopout_upiu->trans_type = hba->dm_lrb.trans_type; + nopout_upiu->flags = hba->dm_lrb.flags; + + /* TODO: the task tag may conflict with transport + * layer commands task_tag + */ + nopout_upiu->task_tag = free_slot; + break; + case UPIU_TRANS_CODE_QUERY_REQ: + /* For UFS native command implementation */ + /* UPIU Command formation */ + query_req_upiu = (struct dwc_ufs_query_req_upiu *) + (hba->ucdl_base_addr + free_slot)->cmd_upiu; + memset(query_req_upiu, 0, DWC_UCD_ALIGN * 2); + query_req_upiu->trans_type = hba->dm_lrb.trans_type; + query_req_upiu->flags = hba->dm_lrb.flags; + + /* TODO: the task tag may conflict with transport + * layer commands task_tag */ + query_req_upiu->task_tag = free_slot; + query_req_upiu->query_fn = hba->dm_lrb.query_fn; + query_req_upiu->tot_ehs_len = 0; + query_req_upiu->data_seg_len = cpu_to_be16(0); + memcpy(query_req_upiu->txn_specific_flds_1, + hba->dm_lrb.tsf, DWC_UFS_TSF_SIZE); + break; + default: + break; + } + + /* send command to the controller */ + dwc_ufshcd_send_dm_req_command(hba, free_slot); + + spin_unlock_irqrestore(hba->shost->host_lock, flags_irq); + + /* wait until the task management command is completed */ + result = + wait_event_interruptible_timeout(hba->dwc_ufshcd_dm_wait_queue, + (test_bit(free_slot, &hba->dwc_ufshcd_dm_condition) != 0), + DWC_UFS_DM_TIMEOUT); + if (result == 0) { + pr_err("Device Management Req Timed-out\n"); + clear_bit(free_slot, &hba->dwc_ufshcd_dm_condition); + clear_bit(free_slot, &hba->outstanding_dm_requests); + + /* Force Clear the Doorbell */ + dwc_ufshcd_clear_xfre_req(hba, free_slot); + result = FAILED; + goto err_dm_req_timeout; + } else + result = dwc_ufshcd_update_dm_results(hba, free_slot); + +err_dm_req_timeout: +err_dm_req_queue_is_full: + return result; +} + +/** + * dwc_ufshcd_prepare_nopout_cmd() + * Helper function to prepare the nopout command + * @hba: Pointer to drivers structure + * + * Return zero on success; Non zero value on failure + */ +int dwc_ufshcd_prepare_nopout_cmd(struct dwc_ufs_hba *hba) +{ + unsigned long flags_irq; + int result = 0; + + spin_lock_irqsave(hba->shost->host_lock, flags_irq); + memset(&hba->dm_lrb, 0, sizeof(struct dwc_ufshcd_dm_lrb)); + + hba->dm_lrb.trans_type = UPIU_TRANS_CODE_NOP_OUT; + hba->dm_lrb.flags = 0; + hba->dm_lrb.lun = 0; + hba->dm_lrb.dm_cmd_results = -1; + spin_unlock_irqrestore(hba->shost->host_lock, flags_irq); + + return result; +} + +/** + * dwc_ufshcd_prepare_read_flag_cmd() + * Helper function to prepare the read_flag command + * @hba: Pointer to drivers structure + * @flag_type: flag types as per JEDEC spec + * + * Return zero on success; Non zero value on failure + */ +int dwc_ufshcd_prepare_read_flag_cmd(struct dwc_ufs_hba *hba, u8 flag_type) +{ + unsigned long flags_irq; + int result = 0; + + spin_lock_irqsave(hba->shost->host_lock, flags_irq); + memset(&hba->dm_lrb, 0, sizeof(struct dwc_ufshcd_dm_lrb)); + + hba->dm_lrb.trans_type = UPIU_TRANS_CODE_QUERY_REQ; + hba->dm_lrb.flags = 0; + hba->dm_lrb.query_fn = UPIU_QUERY_STD_READ_REQUEST; + hba->dm_lrb.tsf[0] = UPIU_QUERY_OPCODE_READ_FLAG; + hba->dm_lrb.tsf[1] = flag_type; + + hba->dm_lrb.dm_cmd_results = -1; + spin_unlock_irqrestore(hba->shost->host_lock, flags_irq); + + return result; +} + +/** + * dwc_ufshcd_prepare_set_flag_cmd() + * Helper function to prepare the set_flag command + * @hba: Pointer to drivers structure + * @flag_type: flag types as per JEDEC spec + * + * Return zero on success; Non zero value on failure + */ +int dwc_ufshcd_prepare_set_flag_cmd(struct dwc_ufs_hba *hba, u8 flag_type) +{ + unsigned long flags_irq; + int result = 0; + + spin_lock_irqsave(hba->shost->host_lock, flags_irq); + memset(&hba->dm_lrb, 0, sizeof(struct dwc_ufshcd_dm_lrb)); + + hba->dm_lrb.trans_type = UPIU_TRANS_CODE_QUERY_REQ; + hba->dm_lrb.flags = 0; + hba->dm_lrb.query_fn = UPIU_QUERY_STD_WRITE_REQUEST; + hba->dm_lrb.tsf[0] = UPIU_QUERY_OPCODE_SET_FLAG; + hba->dm_lrb.tsf[1] = flag_type; + + hba->dm_lrb.dm_cmd_results = -1; + spin_unlock_irqrestore(hba->shost->host_lock, flags_irq); + + return result; +} + +/** + * dwc_ufshcd_prepare_clear_flag_cmd() + * Helper function to prepare the clear_flag command + * @hba: Pointer to drivers structure + * @flag_type: flag types as per JEDEC spec + * + * Return zero on success; Non zero value on failure + */ +int dwc_ufshcd_prepare_clear_flag_cmd(struct dwc_ufs_hba *hba, u8 flag_type) +{ + unsigned long flags_irq; + int result = 0; + + spin_lock_irqsave(hba->shost->host_lock, flags_irq); + memset(&hba->dm_lrb, 0, sizeof(struct dwc_ufshcd_dm_lrb)); + + hba->dm_lrb.trans_type = UPIU_TRANS_CODE_QUERY_REQ; + hba->dm_lrb.flags = 0; + hba->dm_lrb.query_fn = UPIU_QUERY_STD_WRITE_REQUEST; + hba->dm_lrb.tsf[0] = UPIU_QUERY_OPCODE_CLEAR_FLAG; + hba->dm_lrb.tsf[1] = flag_type; + + hba->dm_lrb.dm_cmd_results = -1; + spin_unlock_irqrestore(hba->shost->host_lock, flags_irq); + + return result; +} + +/** + * dwc_ufshcd_prepare_toggle_flag_cmd() + * Helper function to prepare the toggle_flag command + * @hba: Pointer to drivers structure + * @flag_type: flag types as per JEDEC spec + * + * Return zero on success; Non zero value on failure + */ +int dwc_ufshcd_prepare_toggle_flag_cmd(struct dwc_ufs_hba *hba, u8 flag_type) +{ + unsigned long flags_irq; + int result = 0; + + spin_lock_irqsave(hba->shost->host_lock, flags_irq); + memset(&hba->dm_lrb, 0, sizeof(struct dwc_ufshcd_dm_lrb)); + hba->dm_lrb.trans_type = UPIU_TRANS_CODE_QUERY_REQ; + hba->dm_lrb.flags = 0; + hba->dm_lrb.query_fn = UPIU_QUERY_STD_WRITE_REQUEST; + hba->dm_lrb.tsf[0] = UPIU_QUERY_OPCODE_TOGGLE_FLAG; + hba->dm_lrb.tsf[1] = flag_type; + + hba->dm_lrb.dm_cmd_results = -1; + spin_unlock_irqrestore(hba->shost->host_lock, flags_irq); + + return result; +} + +/** + * dwc_ufs_initialize_ufs_device() + * This function performs the device initialization as per JEDEC spec. + * As part of this initialization + * - It prepares and sends nopout command till + * - it receives the nopin response from device or + * - programmed retries exhaust + * - It prepares and sends set_flag command for fdeviceInit + * - It reads and checks fDeviceInit flag till + * - the flag is clear/reset + * - programmed retries exhaust + * @hba: Pointer to drivers structure + * + * Returns 0 on success, non-zero value on failure + */ +int dwc_ufs_initialize_ufs_device(struct dwc_ufs_hba *hba) +{ + int result = 0; + int retry_count = DWC_UFS_RETRY_COUNT_NOPOUT; + + /* As Per JEDEC220 Device initialization starts with nopout */ + do { + pr_debug("Sending Nopout command\n"); + dwc_ufshcd_prepare_nopout_cmd(hba); + dwc_ufshcd_prepare_and_send_dm_cmd_upiu(hba); + retry_count--; + } while ((hba->dm_lrb.dm_cmd_results != 0) && (retry_count != 0)); + + if (retry_count == 0) { + result = FAILED; + goto err_nopout; + } + + pr_debug("Nopout sent successfully\n"); + + /* Only upon successful reception of nopin proceed further */ + + /* Prepare and send fDeviceInit flag */ + pr_debug("Sending set flag for fDeviceInit\n"); + dwc_ufshcd_prepare_set_flag_cmd(hba, UPIU_QUERY_FLAG_FDEVICEINIT); + dwc_ufshcd_prepare_and_send_dm_cmd_upiu(hba); + + if (hba->dm_lrb.dm_cmd_results != DWC_UFS_SET_FLAG_RESULT_EXPECTED) { + result = FAILED; + goto err_set_fdevice_init; + } + pr_debug("fDeviceInit flag set successfully\n"); + + /* Check for fDeviceInit flag by reading back the flag */ + retry_count = DWC_UFS_RETRY_COUNT_FLAGS; + + do { + pr_debug("Sending Read flag for fDeviceInit\n"); + dwc_ufshcd_prepare_read_flag_cmd(hba, + UPIU_QUERY_FLAG_FDEVICEINIT); + dwc_ufshcd_prepare_and_send_dm_cmd_upiu(hba); + retry_count--; + } while ((hba->dm_lrb.dm_cmd_results != 0) && (retry_count != 0)); + + if (retry_count == 0) { + pr_err("fDeviceInit is still set:%d\n", + hba->dm_lrb.dm_cmd_results); + result = FAILED; + goto err_read_fdevice_init; + } + + pr_debug("fDeviceInit flag read successfully\n"); + + /* This completes the device initialization */ + +err_read_fdevice_init: +err_set_fdevice_init: +err_nopout: + return result; +} + +/** + * dwc_ufshcd_initialize_hba_desc_pointers() + * This function performs the initialization of DWC UFS HC descriptors + * with memory base addresses + * Before updating the descriptor addresses, it checks host controller is + * enabled. If not returns error. If enabled, both transfer descriptor + * pointers and tm descriptor pointers are programmed from the drivers + * private structure + * @hba: Pointer to drivers structure + * + * Returns 0 on success, non-zero value on failure + */ +int dwc_ufshcd_initialize_hba_desc_pointers(struct dwc_ufs_hba *hba) +{ + int result = 0; + + /* If the Host Controller is not active, return error */ + if (dwc_ufshcd_is_hba_active(hba) == false) + return -EIO; + + /* Configure UTRL and UTMRL base address registers */ + dwc_ufs_writel(lower_32_bits(hba->utrl_dma_addr), hba->mmio_base, + DWC_UFS_REG_UTRLBA); + + dwc_ufs_writel(upper_32_bits(hba->utrl_dma_addr), hba->mmio_base, + DWC_UFS_REG_UTRLBAU); + + dwc_ufs_writel(lower_32_bits(hba->utmrl_dma_addr), hba->mmio_base, + DWC_UFS_REG_UTMRLBA); + + dwc_ufs_writel(upper_32_bits(hba->utmrl_dma_addr), hba->mmio_base, + DWC_UFS_REG_UTMRLBAU); + + return result; +} + +/** + * dwc_ufshcd_connect() + * This function performs the host enable, phy configuration, link startup + * and connection setup + * @hba: Pointer to drivers structure + * + * Returns 0 on success, non-zero value on failure + */ +int dwc_ufshcd_connect(struct dwc_ufs_hba *hba) +{ + int link_startup_retries = 0; + int result = 0; + + for (link_startup_retries = 0; + link_startup_retries < DWC_UFS_RETRY_COUNT_LINKSTARTUP; + link_startup_retries++) { + + /* Enable Host Controller */ + dwc_ufshcd_host_enable(hba); + + /* Program Clock Divider Value */ + dwc_ufshcd_program_clk_div(hba, DWC_UFSHCD_CLK_DIV_125); + + /* Provide some delay: TODO: How to quantify this delay */ + mdelay(10); + + /* Initialization routine */ + result = dwc_ufshcd_initialize_hba_desc_pointers(hba); + + if (result != 0) { + pr_err("UFSHCD Initialization Failed\n"); + goto err_hcd_init; + } + +#ifdef CONFIG_SCSI_DWC_UFS_MPHY_TC + /* Initiate Synopsys MPHY */ + result = dwc_ufs_dme_setup_snps_mphy(hba, + DWC_UFS_HCD_DME_INTR_DISABLE); + if (result != 0) + goto err_dme_setup_snps_mphy; + + pr_info("SNPS Mphy Setup Successful\n"); +#endif + + result = dwc_ufs_dme_linkstartup(hba, + DWC_UFS_HCD_DME_INTR_ENABLE); + + if (result != 0) + pr_info("Link Startup Failed %d times\n", + link_startup_retries+1); + + /* IF the UIC is busy no point in retrying */ + if (result == -EIO) + goto err_dme_link_startup_cmd_sending; + if (result == 0) { + result = dwc_ufshcd_get_dme_command_results(hba); + if (result == 0) { + + dwc_ufs_dme_get(hba, UFS_LINK_STATUS, 0, + DWC_UFS_HCD_DME_INTR_DISABLE); + + dwc_ufshcd_get_dme_command_results(hba); + + if (hba->active_dme_cmd.argument3 != 2) { + pr_err("Link is not up on %d iteration\n", + link_startup_retries + 1); + goto err_dme_link_startup; + } + + break; + } else + result = 0; + } + } + + if (link_startup_retries == DWC_UFS_RETRY_COUNT_LINKSTARTUP) { + pr_info("LinkStartup Failed after %d attempts\n", + DWC_UFS_RETRY_COUNT_LINKSTARTUP); + goto err_dme_link_startup; + } + + /* Update the Link information */ + pr_debug("Updating the Link information\n"); + result = dwc_ufs_update_link_info(hba, DWC_UFS_HCD_DME_INTR_DISABLE); + + /* Do the connection setup on both Host and Device + * This may be redundant operation with any UFS device, but executing + * this is no harm */ + result = dwc_ufs_do_dme_connection_setup(hba, + DWC_UFS_HCD_DME_INTR_DISABLE); + if (result != 0) + goto err_dme_connection_setup; + + pr_info("Connection Setup Successful\n"); + + /* Kick start host bus adapter for data transfer operation */ + if (dwc_ufshcd_kick_start_hba(hba) != 0) { + pr_err("Hba is not operational\n"); + goto err_ufs_kick_start_hba; + } + + /* Initialize the device */ + result = dwc_ufs_initialize_ufs_device(hba); + if (result != 0) + goto err_ufs_initialize_device; + + /* Kick start host bus adapter for data transfer operation */ + if (dwc_ufshcd_kick_start_stack(hba) != 0) { + pr_err("Stack is not operational\n"); + goto err_ufs_kick_start_stack; + } + + goto successful_return; + +#ifdef CONFIG_SCSI_DWC_UFS_MPHY_TC +err_dme_setup_snps_mphy: +#endif +err_dme_link_startup: +err_dme_link_startup_cmd_sending: +err_ufs_kick_start_stack: +err_ufs_kick_start_hba: +err_ufs_initialize_device: +err_dme_connection_setup: + /* Failing due to above reasons is not fatal; Lets continue + * as if everyting is fine */ + goto unsuccessful_but_not_fatal_return; +err_hcd_init: + dwc_ufshcd_drv_exit(hba); + +unsuccessful_but_not_fatal_return: +successful_return: + return result; +} diff --git a/drivers/scsi/ufs/dwufs/dwc_ufshcd-core.h b/drivers/scsi/ufs/dwufs/dwc_ufshcd-core.h new file mode 100644 index 0000000..a85ab88 --- /dev/null +++ b/drivers/scsi/ufs/dwufs/dwc_ufshcd-core.h @@ -0,0 +1,61 @@ +/* + * UFS Host driver for Synopsys Designware Core + * + * Copyright (C) 2015-2016 Synopsys, Inc. (www.synopsys.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __DWC_UFSHCD_CORE_H__ +#define __DWC_UFSHCD_CORE_H__ + +struct dwc_ufs_hba *dwc_ufshcd_get_hba_handle(void); +void dwc_ufshcd_read_caps(struct dwc_ufs_hba *); +int dwc_ufshcd_drv_init(struct dwc_ufs_hba **, struct device *, + int, void __iomem *); +void dwc_ufshcd_drv_exit(struct dwc_ufs_hba *); +int dwc_ufshcd_host_enable(struct dwc_ufs_hba *); +void dwc_ufshcd_program_clk_div(struct dwc_ufs_hba *, u32); +int dwc_ufshcd_initialize_hba_desc_pointers(struct dwc_ufs_hba *); +void dwc_ufshcd_start_init(struct dwc_ufs_hba *); +bool dwc_ufshcd_is_hba_active(struct dwc_ufs_hba *); +void dwc_ufshcd_reset_host(struct dwc_ufs_hba *); +void dwc_ufshcd_stop_host(struct dwc_ufs_hba *); +void dwc_ufshcd_host_start_init(struct dwc_ufs_hba *); +int dwc_ufshcd_enable_host(struct dwc_ufs_hba *); +int dwc_ufshcd_kick_start_hba(struct dwc_ufs_hba *); +int dwc_ufshcd_kick_start_stack(struct dwc_ufs_hba *); +void dwc_ufshcd_set_hcd_state(struct dwc_ufs_hba *, u32); +bool dwc_ufshcd_is_dme_sap_ready(u32); +bool dwc_ufshcd_is_utrl_ready(u32); +bool dwc_ufshcd_is_utmrl_ready(u32); +bool dwc_ufshcd_is_device_present(u32); +void dwc_ufshcd_configure_interrupt(struct dwc_ufs_hba *, u32); +void dwc_ufshcd_configure_intr_aggregation(struct dwc_ufs_hba *, u32); + +/* DME Access Functions */ +int dwc_ufs_dme_get(struct dwc_ufs_hba *, u32, u32, u32); +int dwc_ufs_dme_set(struct dwc_ufs_hba *, u32, u32, u32, u32, u32); +int dwc_ufs_update_link_info(struct dwc_ufs_hba *, u32); +int dwc_ufs_switch_speed_gear(struct dwc_ufs_hba *); + +u8 dwc_ufshcd_get_dme_command_results(struct dwc_ufs_hba *); +int dwc_ufs_dme_linkstartup(struct dwc_ufs_hba *, u32); +int dwc_ufs_dme_setup_snps_mphy(struct dwc_ufs_hba *, u32); +int dwc_ufs_do_dme_connection_setup(struct dwc_ufs_hba *, u32); +int dwc_ufs_initialize_ufs_device(struct dwc_ufs_hba *); + +/* Power Management Functions */ +int dwc_ufshcd_suspend(struct dwc_ufs_hba *); +int dwc_ufshcd_resume(struct dwc_ufs_hba *); +void dwc_ufshcd_prepare_xfer_cmd_upiu(struct dwc_ufs_hcd_lrb *); +void dwc_ufshcd_send_xfer_req_command(struct dwc_ufs_hba *, + unsigned int); +int dwc_ufshcd_issue_tm_cmd(struct dwc_ufs_hba *, + struct dwc_ufs_hcd_lrb *, u8); +void dwc_ufshcd_clear_xfre_req(struct dwc_ufs_hba *, u32); +int dwc_ufshcd_reset_hba(struct dwc_ufs_hba *); +int dwc_ufshcd_connect(struct dwc_ufs_hba *); +#endif diff --git a/drivers/scsi/ufs/dwufs/dwc_ufshcd-hci.h b/drivers/scsi/ufs/dwufs/dwc_ufshcd-hci.h new file mode 100644 index 0000000..ce6f251 --- /dev/null +++ b/drivers/scsi/ufs/dwufs/dwc_ufshcd-hci.h @@ -0,0 +1,958 @@ +/* + * UFS Host driver for Synopsys Designware Core + * + * Copyright (C) 2015-2016 Synopsys, Inc. (www.synopsys.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __DWC_UFSHCD_HCI_H__ +#define __DWC_UFSHCD_HCI_H__ + +/* Different retry Counts */ +#define DWC_UFS_RETRY_COUNT_GENERIC 100 +#define DWC_UFS_RETRY_COUNT_NOPOUT 5 +#define DWC_UFS_RETRY_COUNT_FLAGS 5 +#define DWC_UFS_RETRY_COUNT_LINKSTARTUP 3 + +#define DWC_UFS_READ_ONLY_LUN 0x00 +#define DWC_UFS_WRITE_ONLY_LUN 0x06 +#define DWC_UFS_LOOPBACK_LUN 0x07 + +/* Host Controller Versions */ +#define DWC_UFS_HC_VERSION_1_1 0x00010100 +#define DWC_UFS_HC_VERSION_2_0 0x00000200 + +/* Different Timeouts in Seconds */ +#define DWC_UFS_TM_TIMEOUT (1 * HZ) +#define DWC_UFS_DM_TIMEOUT (1 * HZ) +#define DWC_UFS_DME_TIMEOUT (1 * HZ) + +enum { + DWC_UFS_HANDLER_WAIT_QUEUE_TYPE = 0, + DWC_UFS_HANDLER_WORK_QUEUE_TYPE = 1, +}; + +/* DME Command Specific enums */ +enum { + DWC_UFS_HCD_DME_INTR_ENABLE = 1, + DWC_UFS_HCD_DME_INTR_DISABLE = 0, +}; + +/* Interrupt aggregation options */ +enum { + INTR_AGGR_RESET, + INTR_AGGR_CONFIG, +}; + +/* Interrupt configuration options */ +enum { + DWC_UFS_HCD_INTR_DISABLE = 0, + DWC_UFS_HCD_INTR_ENABLE = 1, +}; + +/* UFSHCD states */ +enum { + DWC_UFSHCD_STATE_ERROR = -1, + DWC_UFSHCD_STATE_RESET = 0, + DWC_UFSHCD_STATE_OPERATIONAL = 1, +}; + +enum { + DWC_UFSHCD_MAX_CHANNEL = 0, + DWC_UFSHCD_MAX_ID = 1, + DWC_UFSHCD_MAX_LUNS = 8, + DWC_UFSHCD_CMDS_PER_LUN = 32, + DWC_UFSHCD_CAN_QUEUE = 32, +}; + +/* Data structure sizes in bytes */ +enum { + DWC_UFS_BASIC_UPIU_SIZE = 32, + DWC_UFS_UTRD_SIZE = 32, + DWC_UFS_TSF_SIZE = 16, + DWC_UFS_PRD_SIZE = 16, + DWC_UFSHCD_MAX_PRD_SIZE = 128, /* 128 (Linux) */ + DWC_MAX_QUERY_DATA = 256, + DWC_UFS_CDB_SIZE = 16, + DWC_UFS_SENSE_DATA_SIZE = 18, + DWC_UFS_UTMRD_HDR_SIZE = 16, +}; + +#define DWC_INTR_AGGR_COUNTER_THRESHOLD_VALUE (0x1F) +#define DWC_INTR_AGGR_TIMEOUT_VALUE (0x02) +/* HCE register bit 0 value to reset/stop the core */ +#define DWC_UFS_ENABLE 1 +/* HCE register bit 0 value to initiate the core reset */ +#define DWC_UFS_DISABLE 0 + +/** + * struct uic_command - UIC command structure + * @command: UIC command + * @argument1: UIC command argument 1 + * @argument2: UIC command argument 2 + * @argument3: UIC command argument 3 + * @cmd_active: Indicate if UIC command is outstanding. the same variable + * as condition variable to wake up dme wait queue + */ +struct dwc_ufs_dme_sap_cmd { + u32 command; + u32 argument1; + u32 argument2; + u32 argument3; + int cmd_active; +}; + +/** + * struct dwc_ufshcd_link_info + * @pa_con_tx_data_lanes_local: Physically connected lanes at host + * @pa_con_tx_data_lanes_peer: Physically connected lanes at device + * + */ +struct dwc_ufshcd_link_info { + u8 pa_ava_tx_data_lanes_local; /* Attr: 1540 */ + u8 pa_ava_tx_data_lanes_peer; /* Attr: 1540 */ + u8 pa_ava_rx_data_lanes_local; /* Attr: 1520 */ + u8 pa_ava_rx_data_lanes_peer; /* Attr: 1520 */ + u8 pa_con_tx_data_lanes_local; /* Attr: 1561 */ + u8 pa_con_tx_data_lanes_peer; /* Attr: 1561 */ + u8 pa_con_rx_data_lanes_local; /* Attr: 1581 */ + u8 pa_con_rx_data_lanes_peer; /* Attr: 1581 */ + u8 pa_act_tx_data_lanes_local; /* Attr: 1560 */ + u8 pa_act_tx_data_lanes_peer; /* Attr: 1560 */ + u8 pa_act_rx_data_lanes_local; /* Attr: 1580 */ + u8 pa_act_rx_data_lanes_peer; /* Attr: 1580 */ + u8 pa_max_rx_pwm_gear_local; /* Attr: 1586 */ + u8 pa_max_rx_pwm_gear_peer; /* Attr: 1586 */ + u8 pa_max_rx_hs_gear_local; /* Attr: 1587 */ + u8 pa_max_rx_hs_gear_peer; /* Attr: 1587 */ + u8 pa_hs_series_local; /* Attr: 156a */ + u8 pa_hs_series_peer; /* Attr: 156a */ + u8 pa_pwr_mode_local; /* Attr: 1571 */ + u8 pa_pwr_mode_peer; /* Attr: 1571 */ + u8 pa_tx_pwr_status_local; /* Attr: 1567 */ + u8 pa_tx_pwr_status_peer; /* Attr: 1567 */ + u8 pa_rx_pwr_status_local; /* Attr: 1582 */ + u8 pa_rx_pwr_status_peer; /* Attr: 1582 */ +}; + +/** + * struct dwc_ufsufs_hba + * @1total_read_bytes: Total number bytes read during testing + * @1total_write_bytes: Total number bytes written during testing + * Structure to time and event and calculate performance numbers + * @total_no_of_prds: Total number of PRDs used for testing + * @total_cmd_read16: Total number of read16 commnads used during testing + * @total_cmd_read10: Total number of read10 commnads used during testing + * @1total_cmd_write16: Total number of write16 commnads used during testing + * @1total_cmd_write10: Total number of write10 commnads used during testing + * @1total_cmd_write6: Total number of write6 commnads used during testing + * @1total_cmd_others: Total number of any other commnads used during testing + * @start_time: Start time of any event (read/write) + * @enc_time: End time of started event (read/write) + * @state: state of the statistic counters + * + */ +struct dwc_ufshcd_pstats { + + u64 total_read_bytes; + u64 total_write_bytes; + u32 total_no_of_prds; + u32 total_cmd_read16; + u32 total_cmd_read10; + u32 total_cmd_read6; + u32 total_cmd_write16; + u32 total_cmd_write10; + u32 total_cmd_write6; + u32 total_cmd_others; + + struct timespec start_time; + struct timespec end_time; + + s32 state; +#define DWC_UFS_STATS_DISABLED -1 /* should be -1 for debug access */ +#define DWC_UFS_STATS_STOP -1 +#define DWC_UFS_STATS_START 0 +#define DWC_UFS_STATS_RUNNING 1 +}; + +/** + * struct dwc_ufs_hcd_lrb + * Local Reference Block for application commands (eg:scsi) + * Maintained for every utrd slot + * @command_type: Maintained to abstract the application layer out of core + * @data_direction: whether command is a read/write or no-data command + * @ocs: ocs from utrd is read and kept here for future analysis + * @xfer_command_status: holds the response from response-upiu(eg: Scsi status) + * @transfer_size: total size of transfer in bytes + * @task_tag: task_tag + * @lun: lun + * @scmd: scsi command; should be null if its not a scsi command + * @utrd: transfer descriptor address pointer + * @cmd_upiu: address of command upiu + * @resp_upiu: address of response upiu + * @prdt: base address of prd table + * @sense_buffer_len: sense buffer length in bytes + * @snese_buffer: pointer to sense buffer for the command + */ +struct dwc_ufs_hcd_lrb { + /* Understood */ + u8 command_type; /* UFS, SCSI or Device Management */ + u8 data_direction; + u8 read_write_flags; + u8 ocs; + u8 xfer_command_status; + + u32 transfer_size; + u32 task_tag; + u32 lun; + + struct scsi_cmnd *scmd; + struct dwc_ufs_utrd *utrd; + struct dwc_ufs_cmd_upiu *cmd_upiu; + struct dwc_ufs_resp_upiu *resp_upiu; + struct dwc_ufs_prd *prdt; + + u16 sense_buffer_len; + u8 *sense_buffer; +}; + +/** + * struct dwc_ufshcd_dm_lrb + * Local Reference Block for Device management commands (eg: nopout, query ..) + * Maintained one per driver instance + * @trans_type: Transaction Type (query/nopout ..) + * @flags:flags indicating Read/Write + * @lun: lun to be addressed through this command + * @query_fn: query_function + * @tot_ehs_len: total ehs length + * @data_seg_len: data segment length for this command + * @tsf: transaction specific field for this command + * @dm_cmd_results: Device management function result updated after + * post processing + */ +struct dwc_ufshcd_dm_lrb { + u8 trans_type; + u8 flags; + u8 lun; + u8 query_fn; + u8 tot_ehs_len; + u16 data_seg_len; + u8 tsf[DWC_UFS_TSF_SIZE]; + s32 dm_cmd_results; +}; + +/** + * struct dwc_ufs_hba + * Private structure of the host bus adapter + * @mmio_base: DWC UFS HCI base register address + * @irq: Irq no to be used for ISR registration + * @gdev: Generic device structure for pci/platform device abstraction + * @shost: scsi host structure for scsi mid layer + * @caps: DWC UFS HC capabilities stored here for reference + * @ufs_version: UFS version read adn kept here + * @nutrs: Transfer Request Queue depth supported by DWC UFS HC + * @nutmrs: Task Management Queue depth supported by DWC UFS HC + * @utrl_base_addr: UTP Transfer Request Descriptor List base address (virtual) + * @utmrl_base_addr: UTP Task Management Descriptor List base address (virtual) + * @ucdl_base_addr: UFS Command Descriptor List Base Address (virtual) + * @utrl_dma_addr: UTRDL DMA address List Base Address (DMA/Physical) + * @utmrl_dma_addr: UTMRDL DMA address List Base Address (DMA/Physical) + * @ucdl_dma_addr: UFS Command Descriptor List Base Address (DMA/Physical) + * @lrb: pointer to local reference block list + * @dm_lrb: local reference block device management commands + * @dme_completion_handler_type: work queue type or wait queue type + * @tm_completion_handler_type: work queue type or wait queue type + * @dm_completion_handler_type: work queue type or wait queue type + * @fatal_error_handler_type: work queue type or wait queue type + * @dme_sap_workq: work quue for DME/UIO commands + * @active_dme_cmd: Active dme command is kept in this structure + * @intr_enable_mask: Enable mask for the interrupts + * @intr_aggr_cntr_thr: Interrupt aggregatin counter threshold + * @intr_aggr_timout_val: Interrupt aggregation timeout value + * @outstanding_xfer_reqs: outstanding transfer requests to be processed + * @outstanding_dm_requests: outstanding DM requests to be processes + * (Only One as of now) + * @outstanding_tm_tasks: outstanding TM tasks to be processed + * @dwc_ufshcd_tm_wait_queue: wait queue head to keep tm task commands + * @dwc_ufshcd_dm_wait_queue: wait queue head to keep dm commands + * @dwc_ufshcd_dme_wait_queue: wait queue head to keep dme commands + * @dwc_ufshcd_tm_condition: condition variable to wake up tm wait queue + * @dwc_ufshcd_dm_condition: condition variable to wake up dm wait queue + * @fatal_error_handler_workq: work queue for handling Fatal errors + * @dwc_ufshcd_state: state of the driver/hardware + * @dwc_ufshcd_interrupts: keeps the snapshot of interrupt status register + */ +struct dwc_ufs_hba { + + void __iomem *mmio_base; + int irq; + + struct device *gdev; + struct Scsi_Host *shost; + + u32 caps; + u32 dwc_ufs_version; + + u8 nutrs; + u8 nprtts; + u8 nutmrs; + u8 autoh8; + u8 as64; + u8 oodds; + u8 uicdmetms; + + /* Virtual memory reference for driver */ + struct dwc_ufs_utrd *utrl_base_addr; + struct dwc_ufs_utmrd *utmrl_base_addr; + struct dwc_ufs_ucd *ucdl_base_addr; + + /* DMA memory reference for above mentioned virtual memory + * for Host Controller reference */ + dma_addr_t utrl_dma_addr; + dma_addr_t utmrl_dma_addr; + dma_addr_t ucdl_dma_addr; + + struct dwc_ufs_hcd_lrb *lrb; + struct dwc_ufshcd_dm_lrb dm_lrb; + + u32 dme_completion_handler_type; + u32 tm_completion_handler_type; + u32 dm_completion_handler_type; + u32 fatal_error_handler_type; + + /* Work to handle dme operation */ + struct work_struct dme_sap_workq; + struct dwc_ufs_dme_sap_cmd active_dme_cmd; + + /* Interrupt aggregation parameters */ + u32 intr_enable_mask; + u8 intr_aggr_cntr_thr; + u8 intr_aggr_timout_val; + + /* Outstanding requests Xfer: 32 Max, dm: 1, tm:8 */ + unsigned long outstanding_xfer_reqs; + unsigned long outstanding_dm_requests; + unsigned long outstanding_tm_tasks; + + wait_queue_head_t dwc_ufshcd_tm_wait_queue; + wait_queue_head_t dwc_ufshcd_dm_wait_queue; + wait_queue_head_t dwc_ufshcd_dme_wait_queue; + + unsigned long dwc_ufshcd_tm_condition; + unsigned long dwc_ufshcd_dm_condition; + + struct work_struct fatal_error_handler_workq; + + /* DWC UFS HC state */ + u32 dwc_ufshcd_state; + + /* HBA interrupts */ + u32 dwc_ufshcd_interrupts; + + /* Read Write Statistics */ + u32 debug_mode; + struct dwc_ufshcd_pstats pstats; + struct dwc_ufshcd_link_info linfo; +}; + +/* Clock Divider Values: Hex equivalent of frequency in MHz */ +enum { + DWC_UFSHCD_CLK_DIV_62_5 = 0x3e, + DWC_UFSHCD_CLK_DIV_125 = 0x7d, + DWC_UFSHCD_CLK_DIV_200 = 0xc8, +}; + +/* Alignment Requirement in bytes */ +enum { + DWC_UTRL_BASE_ALIGN = 1024, + DWC_UCD_ALIGN = 512, +}; + +/* Command Type : UTRD - 8bits */ +enum { + DWC_UTRD_CMD_TYPE_SCSI = 0x00, + DWC_UTRD_CMD_TYPE_UFS = 0x10, + DWC_UTRD_CMD_TYPE_DEV_MANAGE_FN = 0x20, +}; + +/* To accommodate UFS2.0 required Command type */ +enum { + DWC_UTRD_CMD_TYPE_UFS_STORAGE = 0x10, +}; + +/* Interrupt or Regula Command : UTRD (DD)-8bits */ +enum { + DWC_UTRD_REG_CMD = 0x00, + DWC_UTRD_INTR_CMD = 0x01, +}; + +/* Data Direction : UTRD (DD)-8bits */ +enum { + DWC_UTRD_NO_DATA_TRANSFER = 0x00, + DWC_UTRD_HOST_TO_DEVICE = 0x02, + DWC_UTRD_DEVICE_TO_HOST = 0x04, +}; + +/* Interrupt or no Interrupt for TM func: UTMRD (I)-8bits */ +enum { + DWC_UTMRD_INTR = 0x01, +}; + +/* Overall command status values */ +enum { + OCS_SUCCESS = 0x00, + OCS_INVALID_CMD_TABLE_ATTR = 0x01, + OCS_INVALID_PRDT_ATTR = 0x02, + OCS_MISMATCH_DATA_BUF_SIZE = 0x03, + OCS_MISMATCH_RESP_UPIU_SIZE = 0x04, + OCS_PEER_COMM_FAILURE = 0x05, + OCS_ABORTED = 0x06, + OCS_FATAL_ERROR = 0x07, + OCS_INVALID_COMMAND_STATUS = 0x0F, + OCS_MASK = 0x0F, +}; + +/** + * UFS Host controller Registers + * All the registers are laid out on a 32bit wide memory space + */ +enum { + DWC_UFS_REG_CAP = 0x00, + DWC_UFS_REG_VER = 0x08, + DWC_UFS_REG_HCDDID = 0x10, + DWC_UFS_REG_HCPMID = 0x14, + DWC_UFS_REG_AHIT = 0x18, /* UFS2.0 Specific*/ + DWC_UFS_REG_IS = 0x20, + DWC_UFS_REG_IE = 0x24, + DWC_UFS_REG_HCS = 0x30, + DWC_UFS_REG_HCE = 0x34, + DWC_UFS_REG_UECPA = 0x38, + DWC_UFS_REG_UECDL = 0x3c, + DWC_UFS_REG_UECN = 0x40, + DWC_UFS_REG_UECT = 0x44, + DWC_UFS_REG_UECDME = 0x48, + DWC_UFS_REG_UTRIACR = 0x4c, + DWC_UFS_REG_UTRLBA = 0x50, + DWC_UFS_REG_UTRLBAU = 0x54, + DWC_UFS_REG_UTRLDBR = 0x58, + DWC_UFS_REG_UTRLCLR = 0x5c, + DWC_UFS_REG_UTRLRSR = 0x60, + DWC_UFS_REG_UTMRLBA = 0x70, + DWC_UFS_REG_UTMRLBAU = 0x74, + DWC_UFS_REG_UTMRLDBR = 0x78, + DWC_UFS_REG_UTMRLCLR = 0x7c, + DWC_UFS_REG_UTMRLRSR = 0x80, + DWC_UFS_REG_UICCMDR = 0x90, + DWC_UFS_REG_UCMDARG1 = 0x94, + DWC_UFS_REG_UCMDARG2 = 0x98, + DWC_UFS_REG_UCMDARG3 = 0x9c, + + /* DWC UFS HC specific Registers */ + DWC_UFS_REG_OCPTHRTL = 0xc0, + DWC_UFS_REG_OOCPR = 0xc4, + DWC_UFS_REG_CDACFG = 0xd0, + DWC_UFS_REG_CDATX1 = 0xd4, + DWC_UFS_REG_CDATX2 = 0xd8, + DWC_UFS_REG_CDARX1 = 0xdc, + DWC_UFS_REG_CDARX2 = 0xe0, + DWC_UFS_REG_CDASTA = 0xe4, + DWC_UFS_REG_LBMCFG = 0xf0, + DWC_UFS_REG_LBMSTA = 0xf4, + DWC_UFS_REG_UFSMODE = 0xf8, + DWC_UFS_REG_HCLKDIV = 0xfc, +}; + +/* Host Controller CPORT Direct Access Flags */ +#define DWC_UFS_C_PORT_DIRECT_ACCESS_EN 0x10000000 +#define DWC_UFS_C_PORT_TX_BUSY 0x00080000 +#define DWC_UFS_C_PORT_TX_EOM 0x00010000 +#define DWC_UFS_C_PORT_RX_DATA_VALID 0x00040000 +#define DWC_UFS_C_PORT_RX_EOM 0x00020000 +#define DWC_UFS_C_PORT_RX_SOM 0x00010000 +#define DWC_UFS_C_PORT_BYTE_EN 0x0000ff00 +#define DWC_UFS_C_PORT_BYTE0_EN 0x00008000 + +/* Host Controller Status DWC_UFS_REG_HCS */ +#define DWC_UFS_DP 0x00000001 +#define DWC_UFS_UTRL_READY 0x00000002 +#define DWC_UFS_UTMRL_READY 0x00000004 +#define DWC_UFS_UIC_CMD_READY 0x00000008 +#define DWC_UFS_UPMCRS_MASK 0x00000700 + +#define DWC_UFS_UPMCRS_PWR_OK 0x00000000 +#define DWC_UFS_UPMCRS_PWR_LOCAL 0x00000100 +#define DWC_UFS_UPMCRS_PWR_REMOTE 0x00000200 +#define DWC_UFS_UPMCRS_PWR_BUSY 0x00000300 +#define DWC_UFS_UPMCRS_PWR_ERROR_CAP 0x00000400 +#define DWC_UFS_UPMCRS_PWR_FATAL_ERROR 0x00000500 + +/* DWC_UFS_REG_IS Bitfields */ +#define DWC_UFS_UTRCS 0x00000001 +#define DWC_UFS_UDEPRI 0x00000002 +#define DWC_UFS_UES 0x00000004 +#define DWC_UFS_UTMS 0x00000008 +#define DWC_UFS_UPMS 0x00000010 +#define DWC_UFS_UHXS 0x00000020 +#define DWC_UFS_UHES 0x00000040 +#define DWC_UFS_ULLS 0x00000080 +#define DWC_UFS_ULSS 0x00000100 +#define DWC_UFS_UTMRCS 0x00000200 +#define DWC_UFS_UCCS 0x00000400 +#define DWC_UFS_DFES 0x00000800 +#define DWC_UFS_UTPES 0x00001000 +#define DWC_UFS_HCFES 0x00010000 +#define DWC_UFS_SBFES 0x00020000 + +/* DWC_UFS_REG_IE Bitfields */ +#define DWC_UFS_UTRCE 0x00000001 +#define DWC_UFS_UDEPRIE 0x00000002 +#define DWC_UFS_UEE 0x00000004 +#define DWC_UFS_UTMSE 0x00000008 +#define DWC_UFS_UPMSE 0x00000010 +#define DWC_UFS_UHXSE 0x00000020 +#define DWC_UFS_UHESE 0x00000040 +#define DWC_UFS_ULLSE 0x00000080 +#define DWC_UFS_ULSSE 0x00000100 +#define DWC_UFS_UTMRCE 0x00000200 +#define DWC_UFS_UCCE 0x00000400 +#define DWC_UFS_DFEE 0x00000800 +#define DWC_UFS_UTPEE 0x00001000 +#define DWC_UFS_HCFEE 0x00010000 +#define DWC_UFS_SBFEE 0x00020000 + +#define DWC_UFS_ENABLED_ERROR_MASK (DWC_UFS_UEE |\ + DWC_UFS_DFEE |\ + DWC_UFS_HCFEE |\ + DWC_UFS_SBFEE) + +#define DWC_UFS_ENABLED_FE_ERROR_MASK (DWC_UFS_DFEE |\ + DWC_UFS_HCFEE |\ + DWC_UFS_SBFEE) + +/* DWC_UFS_REG_UTRIACR Bitfields and masks and shift value */ +#define DWC_UFS_IAEN 0x80000000 +#define DWC_UFS_IAPWEN 0x01000000 +#define DWC_UFS_IASB 0x00100000 +#define DWC_UFS_CTR 0x00010000 +#define DWC_UFS_IACTH_MASK 0x00001f00 +#define DWC_UFS_IACTH_SH 8 +#define DWC_UFS_IATOVAL_MASK 0x000000ff + +/* Controller capability masks and shift value */ +#define DWC_UFS_NUTRS_MASK 0x0000001f +#define DWC_UFS_NPRTTS_MASK 0x0000ff00 +#define DWC_UFS_NPRTTS_SHIFT 8 +#define DWC_UFS_NUTMRS_MASK 0x00070000 +#define DWC_UFS_NUTMRS_SHIFT 16 +#define DWC_UFS_AUTOH8 0x00800000 +#define DWC_UFS_AUTOH8_SHIFT 23 +#define DWC_UFS_64AS 0x01000000 +#define DWC_UFS_64AS_SHIFT 24 +#define DWC_UFS_OODDS 0x02000000 +#define DWC_UFS_OODDS_SHIFT 25 +#define DWC_UFS_UICDMETMS 0x04000000 +#define DWC_UFS_UICDMETMS_SHIFT 26 + +/* Auto Hibernate Feature */ +/* Auto Hibernate IDLE Timer Scale Value */ +#define DWC_UFS_AHIT_1US 0x00000000 +#define DWC_UFS_AHIT_10US 0x00000400 +#define DWC_UFS_AHIT_100US 0x00000800 +#define DWC_UFS_AHIT_1MS 0x00000c00 +#define DWC_UFS_AHIT_10MS 0x00001000 +#define DWC_UFS_AHIT_100MS 0x00001400 + +/* Auto Hibernate IDLE Timer Value */ +#define DWC_UFS_AHIT_IDLE_TIME_MASK 0x000003ff + +/** + * struct dwc_ufs_utrd + * UTP Transfer request descriptor structure + * Size of this descriptor is 32 bytes. + * The data structure should support 32 bit or 64 bit memory buffer address + * space. + * This structure is in LITTLE ENDIAN Format. + */ +struct dwc_ufs_utrd { + u8 reserved_1[3]; + u8 ct_and_flags; + u8 reserved_2[4]; + u8 ocs; + u8 reserved_3[3]; + u8 reserved_4[4]; + u32 ucdba; + u32 ucdbau; + u16 resp_upiu_length; + u16 resp_upiu_offset; + u16 prdt_length; + u16 prdt_offset; +}; + +/** + * dwc_ufs_prd + * This is Physical Region Description for data Scatter Gather buffers + * The structure is of 32 bytes. + * The data structure should support 32 bit or 64 bit memory buffer address + * space. + * This structure is in LITTLE ENDIAN Format. + */ +struct dwc_ufs_prd { + u32 base_addr_lo; + u32 base_addr_hi; + u8 reserved_1[4]; + u32 byte_count; +}; + +/** + * dwc_ufs_ucd + * UTP Command Descriptor (UCD) structure. + * Every UTRD contains a pointer for this data structure + * This structure logically consists of 3 parts + * 1. "Transfer Request" or "Command UPIU" (SCSI, Native UFS & DM) + * 2. "Transfer Response" or "Response UPIU" + * 3. "Physical Region Description Table"(PRDT). + * The data structure should support 32 bit or 64 bit memory buffer address + * space. + * "Transfer Request" and "Transfer Response" are in BIG ENDIAN Format + * "PRDT" is in LITTLE ENDIAN Format + */ +struct dwc_ufs_ucd { + u8 cmd_upiu[DWC_UCD_ALIGN]; + u8 resp_upiu[DWC_UCD_ALIGN]; + struct dwc_ufs_prd prdt[DWC_UFSHCD_MAX_PRD_SIZE]; +}; + +/** + * dwc_ufs_upiu + * structure conaining the UFS Basic Upiu. This structure is defined such that + * it can accommodate any type of upiu defined in UFS specifications + * The allocation is DWC_UCD_ALIGN bytes + * The data structure should support 32 bit or 64 bit memory buffer address + * space. + * The Structure is in BIG ENDIAN FORMAT + */ +struct dwc_ufs_upiu { + u8 trans_type; + u8 flags; + u8 lun; + u8 task_tag; + u8 cmd_set_type; + u8 reserved_1[3]; + u8 tot_ehs_len; + u8 reserved_2; + u16 data_seg_len; + u32 exp_data_xfer_len; + u8 data[DWC_UCD_ALIGN - DWC_UFS_BASIC_UPIU_SIZE]; +}; + +/** + * dwc_ufs_cmd_upiu + * structure for UFS transfer request command upiu. + * The data structure should support 32 bit or 64 bit memory buffer address + * space. + * This structure is in BIG ENDIAN FORMAT + */ +struct dwc_ufs_cmd_upiu { + u8 trans_type; + u8 flags; + u8 lun; + u8 task_tag; + u8 cmd_set_type; + u8 reserved_1[3]; + u8 tot_ehs_len; + u8 reserved_2; + u16 data_seg_len; + u32 exp_data_xfer_len; + u8 cdb[DWC_UFS_CDB_SIZE]; +}; + +/** + * dwc_ufs_resp_upiu + * This structure is to hold the Response UPIU from device. Host controller + * stores all the relevant response information that has been sent by device. + * The data structure should support 32 bit or 64 bit memory buffer address + * space. + * This structure is in BIG ENDIAN Format + */ +struct dwc_ufs_resp_upiu { + u8 trans_type; + u8 flags; + u8 lun; + u8 task_tag; + u8 cmd_set_type; + u8 reserved_1; + u8 response; + u8 status; + u8 tot_ehs_len; + u8 device_info; + u16 data_seg_len; + u32 residual_xfer_count; + u8 reserved_2[4]; + u8 reserved_3[4]; + u8 reserved_4[4]; + u8 reserved_5[4]; + u16 sense_data_len; + u8 sense_data[DWC_UFS_SENSE_DATA_SIZE]; +}; + +/** + * dwc_ufs_nopout_upiu + * This structure is for nopout upiu from host. + * The data structure should support 32 bit or 64 bit memory buffer address + * space. + * This structure is in BIG ENDIAN Format + */ +struct dwc_ufs_nopout_upiu { + u8 trans_type; + u8 flags; + u8 reserved_1; + u8 task_tag; + u8 reserved_2[4]; + u8 tot_ehs_len; + u8 reserved_3; + u16 data_seg_len; + u8 reserved_4[4]; + u8 reserved_5[4]; + u8 reserved_6[4]; + u8 reserved_7[4]; + u8 reserved_8[4]; +}; + +/** + * dwc_ufs_nopin_upiu + * This structure is for nopin upiu (response to nopout) from device. + * The data structure should support 32 bit or 64 bit memory buffer address + * space. + * This structure is in BIG ENDIAN Format + */ +struct dwc_ufs_nopin_upiu { + u8 trans_type; + u8 flags; + u8 reserved_1; + u8 task_tag; + u8 reserved_2[4]; + u8 tot_ehs_len; + u8 reserved_3; + u16 data_seg_len; + u8 reserved_4[4]; + u8 reserved_5[4]; + u8 reserved_6[4]; + u8 reserved_7[4]; + u8 reserved_8[4]; +}; + +/** + * dwc_ufs_query_req_upiu + * This structure is for query_request_upiu (DM) from host. + * The data structure should support 32 bit or 64 bit memory buffer address + * space. + * This structure is in BIG ENDIAN Format + */ +struct dwc_ufs_query_req_upiu { + u8 trans_type; + u8 flags; + u8 reserved_1; + u8 task_tag; + u8 reserved_2; + u8 query_fn; + u8 reserved_3[2]; + u8 tot_ehs_len; + u8 reserved_4; + u16 data_seg_len; + u8 txn_specific_flds_1[4]; + u8 txn_specific_flds_2[4]; + u8 txn_specific_flds_3[4]; + u8 txn_specific_flds_4[4]; + u8 reserved_5[4]; + u8 data[DWC_MAX_QUERY_DATA]; +}; + +/** + * dwc_ufs_query_resp_upiu + * This structure is for query_response_upiu (DM) from device. + * The data structure should support 32 bit or 64 bit memory buffer address + * space. + * This structure is in BIG ENDIAN Format + */ +struct dwc_ufs_query_resp_upiu { + u8 trans_type; + u8 flags; + u8 reserved_1; + u8 task_tag; + u8 reserved_2; + u8 query_fn; + u8 query_resp; + u8 reserved_3; + u8 tot_ehs_len; + u8 reserved_4; + u16 data_seg_len; + u8 txn_specific_flds_1[4]; + u8 txn_specific_flds_2[4]; + u8 txn_specific_flds_3[4]; + u8 txn_specific_flds_4[4]; + u8 reserved_5[4]; + u8 data[DWC_MAX_QUERY_DATA]; +}; + +/** + * dwc_ufs_reject_upiu is sent by the device + * The Device shall send this upius if it receives and UPIU with + * invalid transaction type + * The data structure should support 32 bit or 64 bit memory buffer address + * space. + * This structure is in BIG ENDIAN Format + */ +struct dwc_ufs_reject_upiu { + u8 trans_type; + u8 flags; + u8 lun; + u8 task_tag; + u8 reserved_1[2]; + u8 resp; + u8 reserved_2; + u8 tot_ehs_len; + u8 device_info; + u8 basic_hdr_status; + u8 reserved_3; + u8 e_to_e_status; + u8 reserved; + u8 reserved_4[4]; + u8 reserved_5[4]; + u8 reserved_6[4]; + u8 reserved_7[4]; + u8 reserved_8[4]; +}; + +/** + * struct dwc_ufs_tm_desc_hdr + * The HC specification does not clearly differentiate this field. + * Every entry in Task management Request List has this structure + * (4 dword) preceding the tm_req_upiu. + * This Structure is in Little ENDIAN Format + */ +struct dwc_ufs_tm_desc_hdr { + u8 reserved_1[3]; + u8 intr_flag; + u8 reserved_2[4]; + u8 ocs; + u8 reserved_3[3]; + u8 reserved_4[4]; +}; + +/** + * dwc_ufs_tm_req_upiu + * Task Management Request UPIU structure + * Size of this structure is 32 bytes + * The data structure should support 32 bit or 64 bit memory buffer address + * space. + * This structure is in BIG ENDIAN Format + */ +struct dwc_ufs_tm_req_upiu { + u8 trans_type; + u8 flags; + u8 lun; + u8 task_tag; + u8 reserved_1; + u8 tm_fn; + u8 reserved_2[2]; + u8 tot_ehs_len; + u8 reserved_3; + u16 data_seg_len; + u8 input_param_1[4]; + u8 input_param_2[4]; + u8 input_param_3[4]; + u8 reserved_4[4]; + u8 reserved_5[4]; +}; + +/** + * dwc_ufs_tm_resp_upiu + * Task Management Response UPIU structure + * Size of this structure is 32 bytes + * The data structure should support 32 bit or 64 bit memory buffer address + * space. + * This structure is in BIG ENDIAN Format + */ +struct dwc_ufs_tm_resp_upiu { + u8 trans_type; + u8 flags; + u8 lun; + u8 task_tag; + u8 reserved_1[2]; + u8 response; + u8 reserved_2; + u8 tot_ehs_len; + u8 reserved_3; + u16 data_seg_len; + u32 output_param_1; + u32 output_param_2; + u8 reserved_4[4]; + u8 reserved_5[4]; + u8 reserved_6[4]; +}; + +/** + * struct dwc_ufs_utmrd + * UTP Task Management Request Descriptor structure + * Size of this descriptor is 80 byte [8 Bytes of Task Managemetn Descriptor + * Header + 36 Bytes of Request UPIU + 36 Bytes of Response UPIU] + * The data structure should support 32 bit or 64 bit memory buffer address + * space. + * The Task Management Descriptor header portion is in LITTLE ENDIAN Format + * But, task management request upiu and task management response upiu are + * in BIG ENDIAN Format + */ +struct dwc_ufs_utmrd { + struct dwc_ufs_tm_desc_hdr tm_desc_hdr; + struct dwc_ufs_tm_req_upiu tm_req_upiu; + struct dwc_ufs_tm_resp_upiu tm_resp_upiu; +}; + +/* DME Commands */ +enum { + DWC_UFS_DME_GET = 0x01, + DWC_UFS_DME_SET = 0x02, + DWC_UFS_DME_PEER_GET = 0x03, + DWC_UFS_DME_PEER_SET = 0x04, + DWC_UFS_DME_POWERON = 0x10, + DWC_UFS_DME_POWEROFF = 0x11, + DWC_UFS_DME_ENABLE = 0x12, + DWC_UFS_DME_RESET = 0x14, + DWC_UFS_DME_ENDPOINTRESET = 0x15, + DWC_UFS_DME_LINKSTARTUP = 0x16, + DWC_UFS_DME_HIBERNATE_ENTER = 0x17, + DWC_UFS_DME_HIBERNATE_EXIT = 0x18, + DWC_UFS_DME_TEST_MODE = 0x1A, +}; + +/* DME Result Codes */ +enum { + DWC_UFS_DME_SUCCESS = 0x00, + DWC_UFS_DME_INV_MIB_ATTR = 0x01, + DWC_UFS_DME_INV_MIB_ATTR_VAL = 0x02, + DWC_UFS_DME_READ_ONLY_MIB_ATTR = 0x03, + DWC_UFS_DME_WRITE_ONLY_MIB_ATTR = 0x04, + DWC_UFS_DME_BAD_INDEX = 0x05, + DWC_UFS_DME_LOCKED_MIB_ATTR = 0x06, + DWC_UFS_DME_BAD_TEST_FEAT_INDEX = 0x07, + DWC_UFS_DME_PEER_COMM_FAILURE = 0x08, + DWC_UFS_DME_BUSY = 0x09, + DWC_UFS_DME_FAILURE = 0x0a, + DWC_UFS_DME_RESULT_CODE_MASK = 0xff, +}; + +/* DME Reset type */ +enum { + DWC_UFS_DME_RESET_LEVEL_COLD = 0, + DWC_UFS_DME_RESET_LEVEL_WARM = 1, +}; + +/* DME attribute whether normal or static */ +enum { + DWC_UFS_DME_ATTR_SET_TYPE_NORMAL = 0, + DWC_UFS_DME_ATTR_SET_TYPE_STATIC = 1, +}; +#endif diff --git a/drivers/scsi/ufs/dwufs/dwc_ufshcd-mphy.h b/drivers/scsi/ufs/dwufs/dwc_ufshcd-mphy.h new file mode 100644 index 0000000..6519369 --- /dev/null +++ b/drivers/scsi/ufs/dwufs/dwc_ufshcd-mphy.h @@ -0,0 +1,55 @@ +/* + * UFS Host driver for Synopsys Designware Core + * + * Copyright (C) 2015-2016 Synopsys, Inc. (www.synopsys.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _DWC_MPHY_H_ +#define _DWC_MPHY_H_ + +/* MPHY Selected Index */ + +#define SEL_INDEX_LANE0_TX 0x00 +#define SEL_INDEX_LANE1_TX 0x01 +#define SEL_INDEX_LANE0_RX 0x04 +#define SEL_INDEX_LANE1_RX 0x05 + +/* MPHY Attributes */ + +/* Common Block*/ +#define MPHY_CB_GLOBAL_HIBERNATE 0x002b +#define MPHY_CB_REF_CLOCK 0x00bf +#define MPHY_CB_DIRECTCTRL19 0x00cd +#define MPHY_CB_DIRECTCTRL10 0x00e6 +#define MPHY_CB_TARGET_FREQ 0x00ea +#define MPHY_CB_REAL_TIME_OBS 0x00f0 +#define MPHY_CB_CLK_DIV_RATIO 0x00f1 +#define MPHY_CB_DCO_CTRL_5 0x00f3 +#define MPHY_CB_CFG_PRG_PLL2 0x00f8 +#define MPHY_CB_PRG_TUNNING 0x00fb + +/* RX */ +#define MPHY_RX_CAL_CTRL 0x00b4 +#define MPHY_RX_RXSQCTRL 0x00b5 +#define MPHY_RX_HIBERN8 0x00ba +#define MPHY_RX_CFG_OVER8 0x00bd +#define MPHY_RX_CFGRXOVR6 0x00bf +#define MPHY_RX_DIRECT_CTRL2 0x00c7 +#define MPHY_RX_CFGRXOVR4 0x00e9 +#define MPHY_RX_SEV_CDR_CFG4 0x00f2 +#define MPHY_RX_SEV_CDR_CFG3 0x00f3 +#define MPHY_RX_SEV_CDR_CFG2 0x00f4 + +/* TX */ +#define MPHY_TX_INT_EXT_DIFF 0x00f1 + +/* RX/TX*/ +#define MPHY_TARGET_REF_CLK 0x00eb +#define MPHY_SETS_INTERNAL_DIV 0x00ec +#define MPHY_SEV_CTRL_PARM 0x00f0 + +#endif /* _DWC_MPHY_H_ */ diff --git a/drivers/scsi/ufs/dwufs/dwc_ufshcd-pci.c b/drivers/scsi/ufs/dwufs/dwc_ufshcd-pci.c new file mode 100644 index 0000000..5ed90fa --- /dev/null +++ b/drivers/scsi/ufs/dwufs/dwc_ufshcd-pci.c @@ -0,0 +1,280 @@ +/* + * UFS Host driver for Synopsys Designware Core + * + * Copyright (C) 2015-2016 Synopsys, Inc. (www.synopsys.com) + * + * Authors: Manjunath Bettegowda <manjumb@xxxxxxxxxxxx>, + * Joao Pinto <jpinto@xxxxxxxxxxxx> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/io.h> +#include <linux/pci.h> +#include <linux/version.h> + +#include "dwc_ufshcd-ufs.h" +#include "dwc_ufshcd-hci.h" +#include "dwc_ufshcd-core.h" + +#define BAR0 0 +#define DWC_UFS_BAR BAR0 + +u8 dwc_ufshcd_pci_driver_name[] = "dwc_ufshcd_pci"; + +/** + * dwc_ufshcd_pci_set_dma_mask() + * Set dma mask based on the controller addressing capability + * @pdev: pointer to pci device + * + * Returns 0 for success, non-zero for failure + */ +static int dwc_ufshcd_pci_set_dma_mask(struct pci_dev *pdev) +{ + int result = 0; + u64 dma_mask; + +#ifdef CONFIG_SCSI_DWC_UFS_64BIT + /* + * If controller supports 64 bit addressing mode, then set the DMA + * mask to 64-bit, else set the DMA mask to 32-bit + */ + dma_mask = DMA_BIT_MASK(64); + result = pci_set_dma_mask(pdev, dma_mask); + if (result != 0) { + pr_info("The device %s doesnot support DAC", pci_name(pdev)); + dma_mask = DMA_BIT_MASK(32); + result = pci_set_dma_mask(pdev, dma_mask); + } +#else + /* DWC UFS HC Cannot do 64 bit addressing */ + dma_mask = DMA_BIT_MASK(32); + result = pci_set_dma_mask(pdev, dma_mask); + + if (result != 0) { + pr_err("The device %s doesnt have DMA Capability", + pci_name(pdev)); + return result; + } +#endif + pr_info("DMA MASK is set to %llx\n", dma_mask); + result = pci_set_consistent_dma_mask(pdev, dma_mask); + return result; +} + +/** + * dwc_ufshcd_pci_probe() + * Probe routine of the pci driver + * In Linux, probing function gets called for all PCI devices (during + * execution of pci_register_driver()), which match the dwc_ufshcd_pci_id + * entries and are not owned by other driver yet. The function passed a + * "struct pci_dev *" for each device whose entry in the ID table matches + * the device's ID. + * @pdev: pointer to PCI device handle + * @entries: PCI device id table + * + * Returns 0 on success, non-zero value on failure + */ +static int dwc_ufshcd_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *entries) { + + int result = 0; + struct dwc_ufs_hba *uninitialized_var(hba); + void __iomem *mmio_base = NULL; + + /* Enable the device for both IO and MEMORY */ + result = pci_enable_device(pdev); + if (result != 0) { + pr_err("Failed to Enable %s device\n", pci_name(pdev)); + goto err_pci_enable; + } + + /* Enable Bus mastering for the Pci Device */ + pci_set_master(pdev); + + /* + * Request MMIO/IO resources. This reserves all the IO and + * Memory resources of our pci device + **/ + result = pci_request_regions(pdev, dwc_ufshcd_pci_driver_name); + if (result != 0) { + pr_err("Failed to get PCI Regions for %s device\n", + pci_name(pdev)); + result = -ENODEV; + goto err_request_regions_failed; + } + + /* Obtain the Virtual Address mapping for required BAR's + * Physical Address + */ + mmio_base = pci_ioremap_bar(pdev, DWC_UFS_BAR); + if (mmio_base == NULL) { + pr_err("Memory/IO map failed for %s device\n", pci_name(pdev)); + result = -ENOMEM; + goto err_ioremap; + } + + result = dwc_ufshcd_pci_set_dma_mask(pdev); + if (result != 0) { + pr_err("Setting Dma Mask failed for %s device", pci_name(pdev)); + goto err_pci_set_dma_mask; + } + + result = dwc_ufshcd_drv_init(&hba, &pdev->dev, pdev->irq, mmio_base); + if (result != 0) { + pr_err("Driver Initialization Failed\n"); + goto err_drv_init; + } + + /* The hba structure is the private data for pci device */ + pci_set_drvdata(pdev, hba); + + /* do the link startup, phy setup and connection establishment */ + result = dwc_ufshcd_connect(hba); + + goto successful_return; + +err_drv_init: +err_pci_set_dma_mask: + pr_debug("Unmapping the base address\n"); + iounmap(hba->mmio_base); +err_ioremap: + pr_debug("Calling pci_release_regions\n"); + pci_release_regions(pdev); +err_request_regions_failed: + pr_debug("Calling pci_clear_master\n"); + pci_clear_master(pdev); + pr_debug("Calling pci_disable_device\n"); + pci_disable_device(pdev); +err_pci_enable: + +successful_return: + return result; +} + +/** + * dwc_ufshcd_pci_remove() + * remove function of Linux pci driver. + * - Calls the DWC UFS HC driver's exit routine + * - unmaps the pci memory resources + * - Clears the Master capability of the pci device + * - Releases the locked regions of pci + * - Disable the pci device + * @pdev: pointer to PCI device handle + * + */ +static void dwc_ufshcd_pci_remove(struct pci_dev *pdev) +{ + struct dwc_ufs_hba *hba = pci_get_drvdata(pdev); + + dwc_ufshcd_drv_exit(hba); + iounmap(hba->mmio_base); + pci_set_drvdata(pdev, NULL); + pci_clear_master(pdev); + pci_release_regions(pdev); + pci_disable_device(pdev); +} + +#define PCI_VENDOR_ID_SNPS 0x16c3 /* Snps Vendor Id PCI-SIG */ +#define PCI_DEVICE_ID_SNPS_HAPS_UFS 0x101a /* Snps UFS Device ID */ + +static const struct pci_device_id dwc_ufshcd_pci_id[] = { + { + PCI_DEVICE(PCI_VENDOR_ID_SNPS, PCI_DEVICE_ID_SNPS_HAPS_UFS), + }, + { } +}; +MODULE_DEVICE_TABLE(pci, dwc3_pci_id_table); + +#ifdef CONFIG_PM +/** + * dwc_ufshcd_generic_suspend() + * generic suspend function common to both pci and platform arcthitecture + * @dev: pointer to generic device structure + * + */ +static int dwc_ufshcd_generic_suspend(struct device *dev) +{ + int result; + struct pci_dev *pdev = to_pci_dev(dev); + struct dwc_ufs_hba *hba = pci_get_drvdata(pdev); + + result = dwc_ufshcd_suspend(hba); + + return result; +} + +/** + * dwc_ufshcd_generic_resume() + * generic resume function common to both pci and platform arcthitecture + * @dev: pointer to generic device structure + * + */ +static int dwc_ufshcd_generic_resume(struct device *dev) +{ + int result; + struct pci_dev *pdev = to_pci_dev(dev); + struct dwc_ufs_hba *hba = pci_get_drvdata(pdev); + + result = dwc_ufshcd_resume(hba); + return result; +} +#else +#define dwc_ufshcd_generic_suspend NULL +#define dwc_ufshcd_generic_resume NULL +#endif /* CONFIG_PM */ + +static SIMPLE_DEV_PM_OPS(dwc_ufshcd_generic_pmops, + dwc_ufshcd_generic_suspend, + dwc_ufshcd_generic_resume); + + +static struct pci_driver dwc_ufshcd_pci_driver = { + .name = "dwc_ufshcd-pci", + .id_table = dwc_ufshcd_pci_id, + .probe = dwc_ufshcd_pci_probe, + .remove = dwc_ufshcd_pci_remove, + .driver = { + .pm = &dwc_ufshcd_generic_pmops + }, +}; + +/** + * dwc_ufshcd_pci_driver_init() + * Entry point for the pci driver + * This function is the entry point for the pci driver.The + * function registers the dwc_ufshcd_pci_driver structure with the kernel. + * Once registered, kernel is free to call the driver's probe function. + * + * Returns Negative value on error + */ +int __init dwc_ufshcd_pci_driver_init(void) +{ + return pci_register_driver(&dwc_ufshcd_pci_driver); +} + +/** + * dwc_ufshcd_pci_driver_exit() + * Cleanup routine for the pci driver + * Description: Majority of the cleanup activity is handled by the driver's + * remove function. when this function is invoked, the driver unregisters the + * already registered dwc_ufshcd_pci_driver from the kernel. Kernel, inturn + * calls the driver's remove function before unregistering. this function is + * suppose to execute without any errors. + * + */ +void __exit dwc_ufshcd_pci_driver_exit(void) +{ + pci_unregister_driver(&dwc_ufshcd_pci_driver); +} + +module_init(dwc_ufshcd_pci_driver_init); +module_exit(dwc_ufshcd_pci_driver_exit); + +MODULE_AUTHOR("Manjunath M B <manjumb@xxxxxxxxxxxx>"); +MODULE_DESCRIPTION("DesignWare UFS PCI Glue Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/scsi/ufs/dwufs/dwc_ufshcd-pltfm.c b/drivers/scsi/ufs/dwufs/dwc_ufshcd-pltfm.c new file mode 100644 index 0000000..4b68ce7 --- /dev/null +++ b/drivers/scsi/ufs/dwufs/dwc_ufshcd-pltfm.c @@ -0,0 +1,168 @@ +/* + * UFS Host driver for Synopsys Designware Core + * + * Copyright (C) 2015-2016 Synopsys, Inc. (www.synopsys.com) + * + * Authors: Manjunath Bettegowda <manjumb@xxxxxxxxxxxx>, + * Joao Pinto <jpinto@xxxxxxxxxxxx> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/version.h> +#include <linux/io.h> + +#include "dwc_ufshcd-ufs.h" +#include "dwc_ufshcd-hci.h" +#include "dwc_ufshcd-core.h" + +/** + * dwc_ufshcd_probe() + * + * @pdev: pointer to platform device structure + * + * This routine creates the driver components required to control the device + * and initializes the device. + */ +static int dwc_ufshcd_probe(struct platform_device *pdev) +{ + int irq, result = 0; + void __iomem *mmio_base = NULL; + struct resource *mem_res = NULL; + struct dwc_ufs_hba *uninitialized_var(hba); + + mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem_res) { + dev_err(&pdev->dev, + "No memory resource for platform device!\n"); + return -ENODEV; + } + + mmio_base = devm_ioremap_resource(&pdev->dev, mem_res); + + if (!mmio_base) { + dev_err(&pdev->dev, "Memory mapping failed\n"); + return -ENOMEM; + } + + dev_dbg(&pdev->dev, "memory start=0x%08x\n", (unsigned)mem_res->start); + dev_dbg(&pdev->dev, + "memory size=0x%08x\n", (unsigned)resource_size(mem_res)); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "IRQ resource not available\n"); + return -ENODEV; + } + + result = dwc_ufshcd_drv_init(&hba, &pdev->dev, irq, mmio_base); + if (result != 0) { + pr_err("Driver Initialization Failed\n"); + goto err_drv_init; + } + + platform_set_drvdata(pdev, hba); + + /* do the link startup, phy setup and connection establishment */ + result = dwc_ufshcd_connect(hba); + + goto successful_return; + +err_drv_init: + pr_debug("Unmapping the base address\n"); + iounmap(hba->mmio_base); + +successful_return: + return result; +} + +/** + * dwc_ufshcd_remove() + * + * @pdev: pointer to platform device structure + * + * This routine is called, for example, when the rmmod command is executed. The + * device may or may not be electrically present. If it is present, the driver + * stops device processing. Any resources used on behalf of this device are + * freed. + */ +static int dwc_ufshcd_remove(struct platform_device *pdev) +{ + struct dwc_ufs_hba *hba = platform_get_drvdata(pdev); + + dwc_ufshcd_drv_exit(hba); + + return 0; +} + +#ifdef CONFIG_PM +/** + * dwc_ufshcd_generic_suspend() + * generic suspend function common to both pci and platform arcthitecture + * @pdev: pointer to platform device structure + * + */ +static int dwc_ufshcd_generic_suspend(struct device *dev) +{ + struct dwc_ufs_hba *hba = dev_get_drvdata(dev); + + int result = dwc_ufshcd_suspend(hba); + + return result; +} + +/** + * dwc_ufshcd_generic_resume() + * generic resume function common to both pci and platform arcthitecture + * @pdev: pointer to generic device structure + * + */ +static int dwc_ufshcd_generic_resume(struct device *dev) +{ + struct dwc_ufs_hba *hba = dev_get_drvdata(dev); + + int result = dwc_ufshcd_resume(hba); + + return result; +} +#else +#define dwc_ufshcd_generic_suspend NULL +#define dwc_ufshcd_generic_resume NULL +#endif /* CONFIG_PM */ + +static SIMPLE_DEV_PM_OPS(dwc_ufshcd_generic_pmops, + dwc_ufshcd_generic_suspend, + dwc_ufshcd_generic_resume); + +#ifdef CONFIG_OF +static const struct of_device_id dwc_ufshcd_match[] = { + { + .compatible = "snps,dwc_ufshcd" + }, + { }, +}; +MODULE_DEVICE_TABLE(of, dwc_ufshcd_match); +#endif + +static struct platform_driver dwc_ufshcd_driver = { + .probe = dwc_ufshcd_probe, + .remove = dwc_ufshcd_remove, + .driver = { + .name = "dwc_ufshcd_pltfm", + .of_match_table = of_match_ptr(dwc_ufshcd_match), + .pm = &dwc_ufshcd_generic_pmops, + }, +}; + +module_platform_driver(dwc_ufshcd_driver); + +MODULE_ALIAS("platform:dwc_ufshcd_pltfm"); +MODULE_DESCRIPTION("DESIGNWARE UFS Host platform glue driver"); +MODULE_AUTHOR("Joao Pinto <Joao.Pinto@xxxxxxxxxxxx>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/scsi/ufs/dwufs/dwc_ufshcd-scsi.c b/drivers/scsi/ufs/dwufs/dwc_ufshcd-scsi.c new file mode 100644 index 0000000..0218827 --- /dev/null +++ b/drivers/scsi/ufs/dwufs/dwc_ufshcd-scsi.c @@ -0,0 +1,780 @@ +/* + * UFS Host driver for Synopsys Designware Core + * + * Copyright (C) 2015-2016 Synopsys, Inc. (www.synopsys.com) + * + * Authors: Manjunath Bettegowda <manjumb@xxxxxxxxxxxx>, + * Joao Pinto <jpinto@xxxxxxxxxxxx> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <scsi/scsi_host.h> /* Linux Scsi Subsytem */ +#include <scsi/scsi_tcq.h> /* Linux scsi_init_shared_tag_map() */ + +#include "dwc_ufshcd-hci.h" +#include "dwc_ufshcd-ufs.h" +#include "dwc_ufshcd-core.h" +#include "dwc_ufshcd-scsi.h" + +/** + * dwc_ufshcd_cp_scsi_sense_data() + * Copy sense data from the response upiu to applications/ operating systems + * sense data + * @lrb: pointer to local reference block + * + */ +static inline void dwc_ufshcd_cp_scsi_sense_data(struct dwc_ufs_hcd_lrb *lrb) +{ + int sense_buffer_len; + + if (lrb->sense_buffer != NULL) { + sense_buffer_len = be16_to_cpu(lrb->resp_upiu->sense_data_len); + memcpy(lrb->sense_buffer, + lrb->resp_upiu->sense_data, + min_t(int, sense_buffer_len, SCSI_SENSE_BUFFERSIZE)); + } +} + +static inline void dwc_ufshcd_program_lun_q_depth(struct scsi_device *sdev, + int depth) { + scsi_change_queue_depth(sdev, depth); +} + +static inline void dwc_ufshcd_adjust_lun_q_depth(struct dwc_ufs_hcd_lrb *lrb) +{ + int nutrs; + struct dwc_ufs_hba *hba; + int lun_qdepth = 0; + + hba = shost_priv(lrb->scmd->device->host); + + /* + * LUN queue depth can be obtained by counting outstanding commands + * on the LUN. + */ + for (nutrs = 0; nutrs < hba->nutrs; nutrs++) { + if (test_bit(nutrs, &hba->outstanding_xfer_reqs)) { + + /* + * Check if the outstanding command belongs + * to the LUN which reported SAM_STAT_TASK_SET_FULL. + */ + if (lrb->scmd->device->lun == hba->lrb[nutrs].lun) + lun_qdepth++; + } + } + + /* + * LUN queue depth will be total outstanding commands, except the + * command for which the LUN reported SAM_STAT_TASK_SET_FULL. + */ + scsi_change_queue_depth(lrb->scmd->device, lun_qdepth - 1); +} + +/** + * dwc_ufshcd_get_xfer_size_and_map_scsi() + * It updates the transfer_size of lrb with the total length of the transfer. + * In addition, it map scatter-gather of scsi command to prdt + * @lrb: pointer to local reference block + * + * Returns 0 in case of success, non-zero value in case of failure + */ +int dwc_ufshcd_get_xfer_size_and_map_scsi(struct dwc_ufs_hcd_lrb *lrb) +{ + struct dwc_ufs_prd *prdt; + struct scatterlist *sg; + struct scsi_cmnd *scmd; + int nr_sg_segments; + int seg; + + /* Get the Scsi command from lrb */ + scmd = lrb->scmd; + + /* Perform the DMA mapping */ + nr_sg_segments = scsi_dma_map(scmd); + + /* if scsi_dma_map returns negative number its an error */ + if (nr_sg_segments < 0) + return nr_sg_segments; + + if (nr_sg_segments != 0) { + + /* update prdt length in the utrd */ + lrb->utrd->prdt_length = cpu_to_le16((u16)(nr_sg_segments)); + + /* Get the prdt base pointer */ + prdt = (struct dwc_ufs_prd *)lrb->prdt; + + /* for every sg-element */ + scsi_for_each_sg(scmd, sg, nr_sg_segments, seg) { + prdt[seg].byte_count = + cpu_to_le32(((u32) sg_dma_len(sg))-1); + + /* Update the transfer size in lrb */ + lrb->transfer_size += sg_dma_len(sg); + + prdt[seg].base_addr_lo = + cpu_to_le32(lower_32_bits(sg->dma_address)); + prdt[seg].base_addr_hi = + cpu_to_le32(upper_32_bits(sg->dma_address)); + } + } else { + lrb->utrd->prdt_length = 0; + + /* Update the transfer size to zero */ + lrb->transfer_size = 0; + } + + return 0; +} + +/** + * dwc_ufshcd_scsi_queuecommand() + * Entry point for SCSI requests + * This function gets the elements from the scsi command and + * update the local reference block for ufs driver layer. The important + * elements are + * - tag + * - sense_buffer_len + * - sense_buffer + * - lun + * - direction + * in addition, it invokes the helper function to + * - get the transfer size + * - map the sg list to prdt + * It invokes the upiu preparation function + * Then finally invokes the function to send the command + * @host: host structure registered with scsi + * @scmd: Scsi command from SCSI Midlayer + * + * Returns 0 for success, non-zero in case of failure + */ +static int dwc_ufshcd_scsi_queuecommand(struct Scsi_Host *host, + struct scsi_cmnd *scmd) +{ + struct dwc_ufs_hcd_lrb *lrb; + struct dwc_ufs_hba *hba; + unsigned long flags_irq; + int result = 0; + int tag; + u32 scsi_command = 0; + + scsi_command = scmd->cmnd[0]; + + if (scsi_command == 0x35) { + scmd->scsi_done(scmd); + return 0; + } + + /* setting the FUA bit for read/write (10)(16) */ + if (scsi_command == 0x28 || scsi_command == 0x88 || scsi_command == 0x2a + || scsi_command == 0x8a) + scmd->cmnd[1] = scmd->cmnd[1] | 0x08; + + /* get the private structure from SCSI host */ + hba = shost_priv(host); + + /* Get the tag from the request */ + tag = scmd->request->tag; + + if (hba->dwc_ufshcd_state != DWC_UFSHCD_STATE_OPERATIONAL) { + result = SCSI_MLQUEUE_HOST_BUSY; + scmd->result |= DID_ERROR << 16; + goto err_dwc_ufshcd_not_operational; + } + + /* tag and xfer request association */ + lrb = &hba->lrb[tag]; + + /* Updating to ensure backward compatibility with older + * host controllers + */ + if (hba->dwc_ufs_version == DWC_UFS_HC_VERSION_2_0) + lrb->command_type = DWC_UTRD_CMD_TYPE_UFS_STORAGE; + else + lrb->command_type = DWC_UTRD_CMD_TYPE_SCSI; + + lrb->scmd = scmd; + lrb->sense_buffer_len = SCSI_SENSE_BUFFERSIZE; + lrb->sense_buffer = scmd->sense_buffer; + lrb->task_tag = tag; + + /* UFS Specific Hacks + * 1. if REPORT LUNS command UFS uses */ + + if (scsi_command == 0xa0) + lrb->lun = WELL_KNOWN_LUN_REPORT_LUNS; + else + lrb->lun = scmd->device->lun; + + /* Initialize ocs and status to zero*/ + lrb->xfer_command_status = 0; + lrb->ocs = 0; + + /* Initialize the transfer size */ + lrb->transfer_size = 0; + + /* Update the data direction and upiu flags needed */ + if (scmd->sc_data_direction == DMA_FROM_DEVICE) { + lrb->data_direction = DWC_UTRD_DEVICE_TO_HOST; + lrb->read_write_flags = UPIU_CMD_FLAGS_READ; + } else if (scmd->sc_data_direction == DMA_TO_DEVICE) { + lrb->data_direction = DWC_UTRD_HOST_TO_DEVICE; + lrb->read_write_flags = UPIU_CMD_FLAGS_WRITE; + } else { + lrb->data_direction = DWC_UTRD_NO_DATA_TRANSFER; + lrb->read_write_flags = UPIU_CMD_FLAGS_NONE; + } + + result = dwc_ufshcd_get_xfer_size_and_map_scsi(lrb); + + if (result != 0) + goto err_dwc_ufshcd_map_scsi_sg_to_prdt; + + /* form UPIU before issuing the command */ + dwc_ufshcd_prepare_xfer_cmd_upiu(lrb); + + if (hba->pstats.state == DWC_UFS_STATS_START) { + hba->pstats.state = DWC_UFS_STATS_RUNNING; + getnstimeofday(&hba->pstats.start_time); + } + + if (hba->pstats.state == DWC_UFS_STATS_RUNNING) { + if (scsi_command == 0x08) { /* Read6*/ + hba->pstats.total_read_bytes += lrb->transfer_size; + hba->pstats.total_cmd_read6++; + hba->pstats.total_no_of_prds += scsi_sg_count(scmd); + } else if (scsi_command == 0x28) { /* Read10 */ + hba->pstats.total_read_bytes += lrb->transfer_size; + hba->pstats.total_cmd_read10++; + hba->pstats.total_no_of_prds += scsi_sg_count(scmd); + } else if (scsi_command == 0x88) { /* Read16*/ + hba->pstats.total_read_bytes += lrb->transfer_size; + hba->pstats.total_cmd_read16++; + hba->pstats.total_no_of_prds += scsi_sg_count(scmd); + } else if (scsi_command == 0x0a) { /* write6*/ + hba->pstats.total_write_bytes += lrb->transfer_size; + hba->pstats.total_cmd_write6++; + hba->pstats.total_no_of_prds += scsi_sg_count(scmd); + } else if (scsi_command == 0x2a) { /* write10 */ + hba->pstats.total_write_bytes += lrb->transfer_size; + hba->pstats.total_cmd_write10++; + hba->pstats.total_no_of_prds += scsi_sg_count(scmd); + } else if (scsi_command == 0x8a) { /* write16*/ + hba->pstats.total_write_bytes += lrb->transfer_size; + hba->pstats.total_cmd_write16++; + hba->pstats.total_no_of_prds += scsi_sg_count(scmd); + } else { + hba->pstats.total_cmd_others++; + hba->pstats.total_no_of_prds += scsi_sg_count(scmd); + if (scmd->sc_data_direction == DMA_FROM_DEVICE) + hba->pstats.total_read_bytes += + lrb->transfer_size; + else + hba->pstats.total_read_bytes += + lrb->transfer_size; + } + } + + /* issue command to the controller */ + spin_lock_irqsave(hba->shost->host_lock, flags_irq); + dwc_ufshcd_send_xfer_req_command(hba, tag); + spin_unlock_irqrestore(hba->shost->host_lock, flags_irq); + +err_dwc_ufshcd_map_scsi_sg_to_prdt: +err_dwc_ufshcd_not_operational: + return result; +} + +/** + * dwc_ufshcd_copy_cdb() + * This function copies the cdb information available in the scsi command + * embedded in local reference block + * @cdb: buffer to hold cdb + * @lrb: local reference block + * + */ +void dwc_ufshcd_copy_cdb(u8 *cdb, struct dwc_ufs_hcd_lrb *lrb) +{ + memcpy(cdb, lrb->scmd->cmnd, + min_t(u16, lrb->scmd->cmd_len, UFS_MAX_CDB_SIZE)); +} + +/** + * dwc_ufshcd_scsi_slave_alloc() + * Handle initial SCSI device configurations + * @sdev: pointer to SCSI device + * + * Returns zero on success; non-zero value on failure + */ +static int dwc_ufshcd_scsi_slave_alloc(struct scsi_device *sdev) +{ + struct dwc_ufs_hba *hba; + + /* Get driver's private structure */ + hba = shost_priv(sdev->host); + sdev->tagged_supported = 1; + + /* Mode sense(6) is not supported by UFS, so use Mode sense(10) */ + sdev->use_10_for_ms = 1; + + scsi_change_queue_depth(sdev, hba->nutrs); + + dwc_ufshcd_program_lun_q_depth(sdev, 8); + + return 0; +} + +/** + * dwc_ufshcd_scsi_slave_destroy() + * Remove SCSI device configurations + * @sdev: pointer to SCSI device + * + */ +static void dwc_ufshcd_scsi_slave_destroy(struct scsi_device *sdev) +{ + struct dwc_ufs_hba *hba; + + hba = shost_priv(sdev->host); +} + +/** + * dwc_ufshcd_scsi_abort_handler() + * Abort a specific command + * @scmd: SCSI command pointer + * + * Returns 0 on success non zero value on error + */ +static int dwc_ufshcd_scsi_abort_handler(struct scsi_cmnd *scmd) +{ + struct Scsi_Host *shost; + struct dwc_ufs_hba *hba; + unsigned long flags_irq; + unsigned int tag; + int result = 0; + + shost = scmd->device->host; + hba = shost_priv(shost); + tag = scmd->request->tag; + + spin_lock_irqsave(shost->host_lock, flags_irq); + + /* check if command is still pending */ + if (!(test_bit(tag, &hba->outstanding_xfer_reqs))) { + + result = FAILED; + pr_err("Slot %d is not empty, Cannot abort\n", tag); + spin_unlock_irqrestore(shost->host_lock, flags_irq); + + /* Xfer req has been completed. No need to send abort cmd */ + goto err_outstanding_xfer_reqs_complete; + } + + spin_unlock_irqrestore(shost->host_lock, flags_irq); + + /* Issue task management command to device to ABORT the task */ + result = dwc_ufshcd_issue_tm_cmd(hba, &hba->lrb[tag], UFS_ABORT_TASK); + + if (result != SUCCESS) { + pr_err("Task management command to %dth slot failed\n", tag); + result = FAILED; + } + + /* Since the command has been aborted, unmap the prdt */ + scsi_dma_unmap(scmd); + + spin_lock_irqsave(shost->host_lock, flags_irq); + + /* clear the respective Xfer Req Doorbell along with Outstanding + * Xfer req bit + */ + dwc_ufshcd_clear_xfre_req(hba, tag); + clear_bit(tag, &hba->outstanding_xfer_reqs); + + hba->lrb[tag].scmd = NULL; + spin_unlock_irqrestore(shost->host_lock, flags_irq); + +err_outstanding_xfer_reqs_complete: + return result; +} + +/** + * dwc_ufshcd_scsi_lun_reset_handler() + * Reset the scsi device and abort all the pending commands + * @scmd: SCSI command pointer + * + * Returns zero on success, non-zero value on failure + */ +static int dwc_ufshcd_scsi_lun_reset_handler(struct scsi_cmnd *scmd) +{ + int index; + struct Scsi_Host *shost; + struct dwc_ufs_hba *hba; + unsigned int tag; + int result = 0; + + shost = scmd->device->host; + hba = shost_priv(shost); + tag = scmd->request->tag; + + /* Issue task management command to reset the device */ + result = dwc_ufshcd_issue_tm_cmd(hba, &hba->lrb[tag], UFS_LUN_RESET); + + if (result != 0) + goto err_issue_tm_cmd; + + /* Now device(here it is lun) is reset, clear pending xfer requests */ + for (index = 0; index < hba->nutrs; index++) { + /* Any match to scmd->lun is pending command for the same lun */ + if (test_bit(index, &hba->outstanding_xfer_reqs) && + (hba->lrb[tag].lun == hba->lrb[index].lun)) { + + /* clear the respective UTRLCLR register bit */ + dwc_ufshcd_clear_xfre_req(hba, index); + clear_bit(index, &hba->outstanding_xfer_reqs); + + if (hba->lrb[index].scmd != NULL) { + scsi_dma_unmap(hba->lrb[index].scmd); + hba->lrb[index].scmd->result = + DID_ABORT << 16; + hba->lrb[index].scmd->scsi_done(scmd); + hba->lrb[index].scmd = NULL; + } + } + } + +err_issue_tm_cmd: + result = 0; + return result; +} + +/** + * dwc_ufshcd_host_reset_handler() + * Main host reset function registered with scsi layer + * @scmd: SCSI command pointer + * + * Returns zero on success, non zero value on failure + */ +static int dwc_ufshcd_host_reset_handler(struct scsi_cmnd *scmd) +{ + int result = 0; + int scsi_cmd_result = 0; + struct dwc_ufs_hba *hba; + + hba = shost_priv(scmd->device->host); + + /* If host controller is already in reset state nothing to do */ + if (hba->dwc_ufshcd_state == DWC_UFSHCD_STATE_RESET) + return SUCCESS; + + if (dwc_ufshcd_reset_hba(hba) == 0) { + scsi_cmd_result |= DID_OK << 16 | COMMAND_COMPLETE << 8 | + SAM_STAT_GOOD; + scmd->result = scsi_cmd_result; + result = SUCCESS; + } else { + scsi_cmd_result |= DID_ERROR << 16; + result = FAILED; + } + + return result; +} + +static struct scsi_host_template dwc_ufshcd_scsi_host_template = { + .module = THIS_MODULE, + .name = DWC_UFSHCD, + .proc_name = DWC_UFSHCD, + .queuecommand = dwc_ufshcd_scsi_queuecommand, + .slave_alloc = dwc_ufshcd_scsi_slave_alloc, + .slave_destroy = dwc_ufshcd_scsi_slave_destroy, + .eh_abort_handler = dwc_ufshcd_scsi_abort_handler, + .eh_device_reset_handler = dwc_ufshcd_scsi_lun_reset_handler, + .eh_host_reset_handler = dwc_ufshcd_host_reset_handler, + .this_id = -1, + .sg_tablesize = DWC_UFSHCD_MAX_PRD_SIZE, + .cmd_per_lun = DWC_UFSHCD_CMDS_PER_LUN, + .can_queue = DWC_UFSHCD_CAN_QUEUE, +}; + +/** + * dwc_ufshcd_alloc_scsi_host() + * This allocates the scshi host structure. + * While allocating, template containing scsi helper functions and attributes + * are passed to scsi (scsi_alloc_host) layer. After successful allocation, + * scsi_alloc_host function allocates required memory for driver's private + * structure and returns the pointer to the same. + * Driver's private strucuture is updated with scsi host reference before + * returning. + * + * Returns the pointer to host's private structure on success, NULL value + * on failure + */ +struct dwc_ufs_hba *dwc_ufshcd_alloc_scsi_host(void) +{ + struct Scsi_Host *shost; + struct dwc_ufs_hba *hba; + + shost = scsi_host_alloc(&dwc_ufshcd_scsi_host_template, + sizeof(struct dwc_ufs_hba)); + if (shost == NULL) { + pr_err("SCSI Host Allocation Failed\n"); + goto err_scsi_host_alloc; + } + + /* Get the driver's private data embedded in scsi_host structure */ + hba = shost_priv(shost); + + hba->shost = shost; + + return hba; + +err_scsi_host_alloc: + scsi_host_put(shost); + return NULL; +} + +/** + * dwc_ufshcd_update_scsi_attributes() + * Following attributes are updated to scsi midlayer. + * Some of the attributes are obtained from the DWC UFS HC capability register + * and some are defined as required from JEDEC specifications + * - Supported number of transfer request slots + * - Supported number of task management task slots + * - Maximum number of LUNs supported by Host/Device + * - Maximum CDB size supported for scsi commands by Host/Device + * @hba: Pointer to drivers structure + * + */ +void dwc_ufshcd_update_scsi_attributes(struct dwc_ufs_hba *hba) +{ + struct Scsi_Host *shost; + + shost = hba->shost; + + if (shost == NULL) + return; + + shost->can_queue = hba->nutrs; + shost->cmd_per_lun = hba->nutrs; + shost->max_id = DWC_UFSHCD_MAX_ID; + shost->max_lun = DWC_UFSHCD_MAX_LUNS; + shost->max_channel = DWC_UFSHCD_MAX_CHANNEL; + shost->unique_id = shost->host_no; + shost->max_cmd_len = UFS_MAX_CDB_SIZE; +} + +/** + * dwc_ufshcd_do_scsi_tag_mapping() + * This function enable SCSI tag mapping: to use unique tags for all LUNs + * By doing this Scsi midlayer will manage a joint tag space for N queues + * without driver having to partition it arbitrarily. + * @hba: Pointer to drivers structure + * + * Returns zero on success; non-zero value on failure + */ +int dwc_ufshcd_do_scsi_tag_mapping(struct dwc_ufs_hba *hba) +{ + int result = 0; + + result = scsi_init_shared_tag_map(hba->shost, hba->shost->can_queue); + + if (result != 0) + pr_err("Init Shared Queue Failed\n"); + + return result; +} + +/** + * dwc_ufshcd_scsi_add_host() + * This function adds the populated scsi host structure to the scsi midlayer + * By doing this, enables the scsi midlayer to start using the helper + * functions to work with UFS device. + * @hba: Pointer to drivers structure + * + * Returns zero on success; non-zero value on failure + */ +int dwc_ufshcd_scsi_add_host(struct dwc_ufs_hba *hba) +{ + int result = 0; + + result = scsi_add_host(hba->shost, hba->gdev); + if (result != 0) + pr_err("scsi_add_host Failed\n"); + + return result; +} + +/** + * dwc_ufshcd_scsi_remove_host() + * This function reverses the action performed by dwc_ufshcd_scsi_add_host() + * In addition to this it also decrements the scsi host's reference count + * by calling scsi_host_put() + * @hba: Pointer to drivers structure + * + * Returns zero on success; non-zero value on failure + */ +int dwc_ufshcd_scsi_remove_host(struct dwc_ufs_hba *hba) +{ + int result = 0; + + if (hba->shost == NULL) + return result; + + scsi_remove_host(hba->shost); + scsi_host_put(hba->shost); + + return 0; +} + +/** + * dwc_ufshcd_scan_scsi_devices_attached_to_host() + * scsi adaptation function to do the scanning of devices attached to + * host controller + * @hba: Pointer to drivers structure + * + * Returns zero on success; non-zero value on failure + */ +int dwc_ufshcd_scan_scsi_devices_attached_to_host(struct dwc_ufs_hba *hba) +{ + if (hba->shost == NULL) + return -1; + + scsi_scan_host(hba->shost); + + return 0; +} + +/** + * dwc_ufshcd_block_scsi_requests() + * scsi adaptation function to block the requests from stack to + * host controller driver + * @hba: Pointer to drivers structure + * + * Returns zero on success; non-zero value on failure + */ +int dwc_ufshcd_block_scsi_requests(struct dwc_ufs_hba *hba) +{ + if (hba->shost == NULL) + return -1; + + scsi_block_requests(hba->shost); + + return 0; +} + +/** + * dwc_ufshcd_unblock_scsi_requests() + * scsi adaptation function to un-block the request from stack to + * host controller driver + * @hba: Pointer to drivers structure + * + * Returns zero on success; non-zero value on failure + */ +int dwc_ufshcd_unblock_scsi_requests(struct dwc_ufs_hba *hba) +{ + if (hba->shost == NULL) + return -1; + + scsi_unblock_requests(hba->shost); + + return 0; +} + +/** + * dwc_ufshcd_unmap_dma() + * scsi adaptation function to perform the sg unmapping. + * @lrb: pointer to local reference block + * + * Returns zero on success; non-zero value on failure + */ +int dwc_ufshcd_unmap_dma(struct dwc_ufs_hcd_lrb *lrb) +{ + if (lrb->scmd == NULL) + return -1; + + scsi_dma_unmap(lrb->scmd); + + return 0; +} + +/** + * dwc_ufshcd_get_cmd_status_with_sense_data() + * compute the SCSI command result based on the transfer status and + * ocs value stored in lrb + * @lrb: pointer to local reference block of completed command + * + * Returns value base on SCSI command status + */ +int dwc_ufshcd_get_cmd_status_with_sense_data(struct dwc_ufs_hcd_lrb *lrb) +{ + int result = 0; + + u8 ocs = lrb->ocs; + u8 xfer_command_status = lrb->xfer_command_status; + + if (ocs == OCS_SUCCESS) + result = 0; + else if (ocs == OCS_ABORTED) + result |= DID_ABORT << 16; + else + result |= DID_ERROR << 16; + + switch (xfer_command_status) { + case SAM_STAT_GOOD: + /* No Sense data with this status*/ + result |= DID_OK << 16 | + COMMAND_COMPLETE << 8 | + SAM_STAT_GOOD; + break; + case SAM_STAT_CHECK_CONDITION: + /* Contains sense data to SCSI command */ + result |= DID_OK << 16 | + COMMAND_COMPLETE << 8 | + SAM_STAT_CHECK_CONDITION; + dwc_ufshcd_cp_scsi_sense_data(lrb); + break; + case SAM_STAT_BUSY: + /* May Contains sense data to SCSI command */ + result |= SAM_STAT_BUSY; + dwc_ufshcd_cp_scsi_sense_data(lrb); + break; + case SAM_STAT_TASK_SET_FULL: + result |= SAM_STAT_TASK_SET_FULL; + dwc_ufshcd_adjust_lun_q_depth(lrb); + break; + case SAM_STAT_TASK_ABORTED: + result |= SAM_STAT_TASK_ABORTED; + break; + default: + result |= DID_ERROR << 16; + break; + } + + return result; +} + +/** + * dwc_ufshcd_do_scsi_completion_with_results() + * completion handler for scsi transfer command. + * @lrb: pointer to local reference block of completed command + * + * Returns value base on SCSI command status + */ +void dwc_ufshcd_do_scsi_completion_with_results(struct dwc_ufs_hcd_lrb *lrb) +{ + int scsi_cmd_result = 0; + + scsi_cmd_result = dwc_ufshcd_get_cmd_status_with_sense_data(lrb); + + lrb->scmd->result = scsi_cmd_result; + + /* Command is handed over to SCSI layer */ + lrb->scmd->scsi_done(lrb->scmd); + + /* Mark completed command as NULL in LRB */ + lrb->scmd = NULL; +} diff --git a/drivers/scsi/ufs/dwufs/dwc_ufshcd-scsi.h b/drivers/scsi/ufs/dwufs/dwc_ufshcd-scsi.h new file mode 100644 index 0000000..a1b72fd --- /dev/null +++ b/drivers/scsi/ufs/dwufs/dwc_ufshcd-scsi.h @@ -0,0 +1,29 @@ +/* + * UFS Host driver for Synopsys Designware Core + * + * Copyright (C) 2015-2016 Synopsys, Inc. (www.synopsys.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __DWC_UFSHCD_SCSI_H__ +#define __DWC_UFSHCD_SCSI_H__ + +#define DWC_UFSHCD "dwc_ufshcd" + +int dwc_ufshcd_scsi_add_host(struct dwc_ufs_hba *); +int dwc_ufshcd_block_scsi_requests(struct dwc_ufs_hba *); +int dwc_ufshcd_unblock_scsi_requests(struct dwc_ufs_hba *); +int dwc_ufshcd_unmap_dma(struct dwc_ufs_hcd_lrb *); +int dwc_ufshcd_scan_scsi_devices_attached_to_host(struct dwc_ufs_hba *); +int dwc_ufshcd_scsi_remove_host(struct dwc_ufs_hba *); +void dwc_ufshcd_copy_cdb(u8 *, struct dwc_ufs_hcd_lrb *); +void dwc_ufshcd_do_scsi_completion_with_results(struct dwc_ufs_hcd_lrb *); +struct dwc_ufs_hba *dwc_ufshcd_alloc_scsi_host(void); +void dwc_ufshcd_update_scsi_attributes(struct dwc_ufs_hba *); +int dwc_ufshcd_do_scsi_tag_mapping(struct dwc_ufs_hba *); + +#endif + diff --git a/drivers/scsi/ufs/dwufs/dwc_ufshcd-ufs.h b/drivers/scsi/ufs/dwufs/dwc_ufshcd-ufs.h new file mode 100644 index 0000000..1ea3a31 --- /dev/null +++ b/drivers/scsi/ufs/dwufs/dwc_ufshcd-ufs.h @@ -0,0 +1,457 @@ +/* + * UFS Host driver for Synopsys Designware Core + * + * Copyright (C) 2015-2016 Synopsys, Inc. (www.synopsys.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __DWC_UFSHCD_UFS_H__ +#define __DWC_UFSHCD_UFS_H__ + +#define UFS_MAX_NUTRS_ALLOWED 32 +#define UFS_MAX_NUTMRS_ALLOWED 8 +#define UFS_MAX_CDB_SIZE 16 +#define UFS_MAX_LU 8 + +#define DWC_UFSHCD_STUFF_BYTES_SIZE 196 +#define DWC_UFSHCD_MAC_KEY_LEN 32 +#define DWC_UFSHCD_RPMB_DATA_SIZE 256 +#define DWC_UFSHCD_NONCE_SIZE 16 + +/* UFS Registers */ +#define UFS_LINK_STATUS 0xd083 + +/* Some Expected Results from UFS Device or Device Model */ +#define DWC_UFS_SET_FLAG_RESULT_EXPECTED 0 + +/* UFS Device Spec Versions */ +enum { + UFS_DEV_SPEC_VERSION_1_1 = 0x0110, + UFS_DEV_SPEC_VERSION_2_0 = 0x0200, + +}; + +/* Well Known LUNs */ +enum { + WELL_KNOWN_LUN_REPORT_LUNS = 0x81, + WELL_KNOWN_LUN_UFS_DEVICE = 0xd0, + WELL_KNOWN_LUN_BOOT = 0xb0, + WELL_KNOWN_LUN_RPMB = 0xc4, +}; + +/** + * Every UPIU data structure contains a Transaction Code. This code defines + * the content and implied function of use of the UPIU data structure. The + * Transaction code fills in the lower 6 bits of Transaction Type filed of + * UPIU header + */ +enum { + UPIU_TRANS_CODE_NOP_OUT = 0x00, + UPIU_TRANS_CODE_CMD = 0x01, + UPIU_TRANS_CODE_DATA_OUT = 0x02, + UPIU_TRANS_CODE_TASK_REQ = 0x04, + UPIU_TRANS_CODE_QUERY_REQ = 0x16, +}; + +/* UTP UPIU Transaction Codes Target to Initiator */ +enum { + UPIU_TRANS_CODE_NOP_IN = 0x20, + UPIU_TRANS_CODE_RSP = 0x21, + UPIU_TRANS_CODE_DATA_IN = 0x22, + UPIU_TRANS_CODE_TM_RSP = 0x24, + UPIU_TRANS_CODE_RTT = 0x31, + UPIU_TRANS_CODE_QUERY_RESP = 0x36, + UPIU_TRANS_CODE_REJECT_UPIU = 0x3f, +}; + +/** + * UPIU Command Flags: + * The Response UPIU Flags enum are handled separtely + * Bit 5 => Write Flag + * Bit 6 => Read Flag + * Bits [1:0] indicate the Task Attributes + */ +/* Read/Write Flags: UPIU - 8bits */ +enum { + UPIU_CMD_FLAGS_NONE = 0x00, + UPIU_CMD_FLAGS_WRITE = 0x20, + UPIU_CMD_FLAGS_READ = 0x40, +}; + +/* UPIU Task Attributes */ +enum { + UPIU_TASK_ATTR_SIMPLE = 0x00, + UPIU_TASK_ATTR_ORDERED = 0x01, + UPIU_TASK_ATTR_HEADQ = 0x02, + UPIU_TASK_ATTR_ACA = 0x03, +}; + +/** + * Response field + * One byte field indicating Success or Failure + * of requested function + */ +enum { + UPIU_RESP_TARGET_SUCCESS = 0x00, + UPIU_RESP_TARGET_FAILURE = 0x01, +}; + +/** + * UPIU Command Set Type + * Lower 4 bits of this byte indicates the Encapsulated Commnad for this UPIU + */ +enum { + UPIU_CMD_SET_TYPE_SCSI = 0x0, + UPIU_CMD_SET_TYPE_UFS = 0x1, +}; + +/* Task management response codes */ +enum { + UPIU_TM_FUNC_COMPL = 0x00, + UPIU_TM_FUNC_NOT_SUPPORTED = 0x04, + UPIU_TM_FUNC_FAILED = 0x05, + UPIU_TM_FUNC_SUCCEEDED = 0x08, + UPIU_TM_INCORRECT_LUN = 0x09, +}; + +/** + * UPIU Query Function, functions + * Query Function is valid when UPIU transaction code indicates it's a + * Query Request UPIU. + * One byte field used in case query functions such as QUERY_REQUEST & + * QUERY_RESPONSE UPIU's and task management functions such as + * TASK_MANAGEMENT_REQUEST & TAK_MANAGEMENT_RESPONSE UPIU's + * + **/ +enum { + UPIU_QUERY_RESERVED = 0x00, + UPIU_QUERY_STD_READ_REQUEST = 0x01, + UPIU_QUERY_STD_WRITE_REQUEST = 0x81, +}; + +/** + * UTP Transaction Specific OpCode for QUERY Functions + * One byte Opcode indicating transaction specific operations for Standard + * QUERY Functions + */ +enum { + UPIU_QUERY_OPCODE_NOP = 0x00, + UPIU_QUERY_OPCODE_READ_DESC = 0x01, + UPIU_QUERY_OPCODE_WRITE_DESC = 0x02, + UPIU_QUERY_OPCODE_READ_ATTR = 0x03, + UPIU_QUERY_OPCODE_WRITE_ATTR = 0x04, + UPIU_QUERY_OPCODE_READ_FLAG = 0x05, + UPIU_QUERY_OPCODE_SET_FLAG = 0x06, + UPIU_QUERY_OPCODE_CLEAR_FLAG = 0x07, + UPIU_QUERY_OPCODE_TOGGLE_FLAG = 0x08, +}; + +/* Descriptor Type Identifiers */ +enum { + UFS_DEVICE_DESCRIPTOR_TYPE = 0x00, + UFS_CONFIG_DESCRIPTOR_TYPE = 0x01, + UFS_UNIT_DESCRIPTOR_TYPE = 0x02, + UFS_INTERCONNECT_DESCRIPTOR_TYPE = 0x04, + UFS_STRING_DESCRIPTOR_TYPE = 0x05, + UFS_GEOMETRY_DESCRIPTOR_TYPE = 0x07, + UFS_POWER_DESCRIPTOR_TYPE = 0x08, +}; + +/* Descriptor Lenghts */ +enum { + UFS_DEVICE_DESCRIPTOR_LENGTH = 0x1f, + UFS_DEVICE_DESCRIPTOR_LENGTH_2_0 = 0x40, + UFS_CONFIG_DESCRIPTOR_LENGTH = 0x90, + UFS_GEOMETRY_DESCRIPTOR_LENGTH = 0x44, + UFS_UNIT_DESCRIPTOR_LENGTH = 0x23, + UFS_RPMB_UNIT_DESCRIPTOR_LENGTH = 0x23, + UFS_POWER_DESCRIPTOR_LENGTH = 0x62, + UFS_INTERCONNECT_DESCRIPTOR_LENGTH = 0x06, + UFS_STRING_DESCRIPTOR_LENGTH = 0xfe, +}; + +/* Query FLAGS definition */ +enum { + UPIU_QUERY_FLAG_FDEVICEINIT = 0x01, + UPIU_QUERY_FLAG_FPERMANENTWPEN = 0x02, + UPIU_QUERY_FLAG_FPOWERONWPEN = 0x03, + UPIU_QUERY_FLAG_FBACKGROUNDOPSEN = 0x04, + UPIU_QUERY_FLAG_FPURGEENABLE = 0x06, + UPIU_QUERY_FLAG_FPHYRESOURCEREMOVAL = 0x08, + UPIU_QUERY_FLAG_FBUSYRTC = 0x09, + UPIU_QUERY_FLAG_FPERMANENTLYDISALBLEFWUPDATE = 0x0b, +}; + +/** + * UTP Query Response to QUERY_REQUEST_UPIU + * One byte field indicaing the Completion code for QUERY REQUEST UPIU + * Transaction code + */ +enum { + UPIU_QUERY_RESPONSE_PARAM_NOT_READABLE = 0xf6, + UPIU_QUERY_RESPONSE_PARAM_NOT_WRITABLE = 0xf7, + UPIU_QUERY_RESPONSE_PARAM_ALREADY_WRITTEN = 0xf8, + UPIU_QUERY_RESPONSE_INVALID_LENGTH = 0xf9, + UPIU_QUERY_RESPONSE_INVALID_VALUE = 0xfa, + UPIU_QUERY_RESPONSE_INVALID_SELECTOR = 0xfb, + UPIU_QUERY_RESPONSE_INVALID_IDN = 0xfc, + UPIU_QUERY_RESPONSE_INVALID_OPCODE = 0xfd, + UPIU_QUERY_RESPONSE_GENERAL_FAILURE = 0xff, +}; + +/** + * Valid Failures when the UPIU transaction for target initiated REJECT UPIU + * one of the field is set in Basic Header status when target is not able to + * interpret and/or execute an UPIU due to wrong values in some of its fields + * + */ +enum { + UPIU_REJECT_HDRSTATUS_GENERAL_FAILURE = 0x00, + UPIU_REJECT_HDRSTATUS_INV_TRANS_TYPE = 0x01, + UPIU_REJECT_HDRSTATUS_INV_FLAGS = 0x02, + UPIU_REJECT_HDRSTATUS_INV_LUN = 0x03, + UPIU_REJECT_HDRSTATUS_INV_TASK_TAG = 0x04, + UPIU_REJECT_HDRSTATUS_INV_CMD_SET_TYPE = 0x05, + UPIU_REJECT_HDRSTATUS_INV_QUERY_FN = 0x06, + UPIU_REJECT_HDRSTATUS_INV_TM_FN = 0x06, + UPIU_REJECT_HDRSTATUS_INV_TOT_EHS_LEN = 0x07, + UPIU_REJECT_HDRSTATUS_INV_DAT_SEG_LEN = 0x08, +}; + +/** + * Following are the UFS Task Managemetn Functions + * Are valid when the transaction code field is UPIU_TRANSACTION_TASK_REQ + * These task management tasks are borrwoed from SAM-5 model of SCSI + */ +enum { + UFS_ABORT_TASK = 0x01, + UFS_ABORT_TASK_SET = 0x02, + UFS_CLEAR_TASK_SET = 0x04, + UFS_LUN_RESET = 0x08, + UFS_QUERY_TASK = 0x80, + UFS_QUERY_TASK_SET = 0x81, +}; + +struct ufs_device_descriptor_t { + + u8 bLength; + u8 bDescriptorType; + u8 bDevice; + u8 bDeviceClass; + u8 bDeviceSubClass; + u8 bProtocol; + u8 bNumberLU; + u8 bNumberWLU; + u8 bBootEnable; + u8 bDescrAccessEnable; + u8 bInitPowerMode; + u8 bHighPriorityLUN; + u8 bSecureRemovalType; + u8 bSecurityLU; + + /* u8 reserved_1; */ + u8 bBackgroundOpsTermLat; /* UFS 2.0 */ + + u8 bInitActiveICCLevel; + u8 wSpecVersion[2]; + u8 wManufactureDate[2]; + u8 iManufacturerName; + u8 iProductName; + u8 iSerialNumber; + u8 iOemID; + u8 wmanufacturerID[2]; + u8 bUD0BaseOffset; + u8 bUDConfigPLength; + u8 bDeviceRTTCap; + u8 wPeriodicRTCUpdate[2]; + + u8 reserved_2[17]; + u8 reserved_3[16]; +} ufs_device_descriptor; + +struct ufs_unit_descriptor_config_param_t { + + u8 bLuEnable; + u8 bBootLunID; + u8 bLuWriteProtect; + u8 bMemoryType; + u8 dNumAllocUnits[4]; + u8 bDataReliability; + u8 bLogicalBlockSize; + u8 bProvisioningType; + u8 wContextCapabilities[2]; + u8 reserved_1[3]; +} ufs_unit_descriptor_config_param; + +struct configuration_descriptor_t { + + u8 bLength; + u8 bDescriptorType; + u8 bNumberLU; + u8 bBootEnable; + u8 bDescrAccessEnable; + u8 bInitPowerMode; + u8 bHighPriorityLUN; + u8 bSecureRemovalType; + u8 bInitActiveICCLevel; + u8 wPeriodicRTCUpdate[2]; + u8 reserved_1[5]; + + struct ufs_unit_descriptor_config_param lu_config_params[UFS_MAX_LU]; +} ufs_configuration_descriptor; + +struct ufs_geometry_descriptor_t { + + u8 bLength; + u8 bDescriptorType; + u8 bMediaTechnology; + u8 reserved_1; + u8 bTotalRawDeviceCapacity[8]; + u8 reserved_2; + u8 dSegmentSize[4]; + u8 bAllocationUnitSize; + u8 bMinAddrBlockSize; + u8 bOptimalReadBlockSize; + u8 bOptimalWriteBlockSize; + u8 bMaxInBufferSize; + u8 bMaxOutBufferSize; + u8 bRPMB_ReadWriteSize; + u8 reserved_3; + u8 bDataOrdering; + u8 bMaxContextIDNumber; + u8 bSysDataTagUnitSize; + u8 bSysDataTagResSize; + u8 bSupportedSecRTypes; + u8 wSupportedMemoryTypes[2]; + u8 dSystemCodeMaxNAllocU[4]; + u8 wSystemCodeCapAdjFac[2]; + u8 dNonPersistMaxNAllocU[4]; + u8 wNonPersistCapAdjFac[2]; + u8 dEnhanced1MaxNAllocU[4]; + u8 wEnhanced1CapAdjFac[2]; + u8 dEnhanced2MaxNAllocU[4]; + u8 wEnhanced2CapAdjFac[2]; + u8 dEnhanced3MaxNAllocU[4]; + u8 wEnhanced3CapAdjFac[2]; + u8 dEnhanced4MaxNAllocU[4]; + u8 wEnhanced4CapAdjFac[2]; +} ufs_geometry_descriptor; + +struct unit_descriptor_t { + + u8 bLength; + u8 bDescriptorType; + u8 bUnitIndex; + u8 bLuEnable; + u8 bBootLunID; + u8 bLUWriteProtect; + u8 bLUQueueDepth; + u8 reserved_1; + u8 bMemoryType; + u8 bDataReliability; + u8 bLogicalBlockSize; + u8 qLogicalBlockCount[8]; + u8 dEraseBlockSize[4]; + u8 bProvisioningType; + u8 qPhyMemResourceCount[8]; + u8 wContextCapabilities[2]; + u8 bLargeUnitSize_M1; +} ufs_unit_descriptor; + +struct rpmb_unit_descriptor_t { + + u8 bLength; + u8 bDescriptorType; + u8 bUnitIndex; + u8 bLuEnable; + u8 bBootLunID; + u8 bLUWriteProtect; + u8 bLUQueueDepth; + u8 reserved_1; + u8 bMemoryType; + u8 reserved_2; + u8 bLogicalBlockSize; + u8 qLogicalBlockCount[8]; + u8 dEraseBlockSize[4]; + u8 bProvisioningType; + u8 qPhyMemResourceCount[8]; + u8 reserved_3[3]; +} ufs_rpmb_unit_descriptor; + +struct power_parameters_descriptor_t { + + u8 bLength; + u8 bDescriptorType; + u8 bActiveConsumptionVCC15_0[32]; + u8 bActiveConsumptionVCCQ15_0[32]; + u8 bActiveConsumptionVCCQ215_0[32]; +} ufs_power_parameters_descriptor; + +struct ufs_interconnect_descriptor_t { + + u8 bLength; + u8 bDescriptorType; + u8 bcdUniproVersion[2]; + u8 bcdMphyVersion[2]; +} ufs_interconnect_descriptor; + +struct string_descriptor_t { + + u8 bLength; + u8 bDescriptorType; + u8 uc_string[252]; +} ufs_string_descriptor; + +/* RPMB Related */ +/* RPMB frame : Big Endian Format */ + +struct rpmb_frame { + + u8 stuff_bytes[DWC_UFSHCD_STUFF_BYTES_SIZE]; /* 196 */ + u8 mac_key[DWC_UFSHCD_MAC_KEY_LEN]; /* 32*/ + u8 data[DWC_UFSHCD_RPMB_DATA_SIZE]; /* 256 */ + u8 nonce[DWC_UFSHCD_NONCE_SIZE]; /* 16 */ + u32 write_counter; + u16 address; + u16 block_count; + u16 result; + u16 request_response; +}; + +enum { + AUTH_KEY_PROG_REQ = 0x0001, + READ_WR_COUNTER_VAL_REQ = 0x0002, + AUTH_DATA_WRITE_REQ = 0x0003, + AUTH_DATA_READ_REQ = 0x0004, + RESULT_READ_REQ = 0x0005, +}; + +enum { + AUTH_KEY_PROG_RESP = 0x0100, + READ_WR_COUNTER_VAL_RESP = 0x0200, + AUTH_DATA_WRITE_RESP = 0x0300, + AUTH_DATA_READ_RESP = 0x0400, +}; + +enum { + RPMB_OPERATION_OK = 0x00, + RPMB_GENERAL_FAILURE = 0x01, + RPMB_AUTHENTICATION_FAILURE = 0x02, + RPMB_COUNTER_FAILURE = 0x03, + RPMB_ADDRESS_FAILURE = 0x04, + RPMB_WRITE_FAILURE = 0x05, + RPMB_READ_FILURE = 0x06, + RPMB_AUTH_KEY_NOT_PROGRAMMED = 0x07, + RPMB_OPERATION_OK_WCE = 0x80, + RPMB_GENERAL_FAILURE_WCE = 0x81, + RPMB_AUTHENTICATION_FAILURE_WCE = 0x82, + RPMB_COUNTER_FAILURE_WCE = 0x83, + RPMB_ADDRESS_FAILURE_WCE = 0x84, + RPMB_WRITE_FAILURE_WCE = 0x85, + RPMB_READ_FILURE_WCE = 0x86, +}; + +#endif diff --git a/drivers/scsi/ufs/dwufs/dwc_ufshcd-unipro.h b/drivers/scsi/ufs/dwufs/dwc_ufshcd-unipro.h new file mode 100644 index 0000000..5cc533b --- /dev/null +++ b/drivers/scsi/ufs/dwufs/dwc_ufshcd-unipro.h @@ -0,0 +1,40 @@ +/* + * UFS Host driver for Synopsys Designware Core + * + * Copyright (C) 2015-2016 Synopsys, Inc. (www.synopsys.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _DWC_UNIPRO_H_ +#define _DWC_UNIPRO_H_ + +/* UNIPRO Attributes */ +#define UNIPRO_TX_LANES 0x1520 +#define UNIPRO_RX_LANES 0x1540 +#define UNIPRO_ACTIVE_TX_LANES 0x1560 +#define UNIPRO_CON_TX_LANES 0x1561 +#define UNIPRO_TX_PWR_STATUS 0x1567 +#define UNIPRO_HS_SERIES 0x156a +#define UNIPRO_PWR_MODE 0x1571 +#define UNIPRO_ACTIVE_RX_LANES 0x1580 +#define UNIPRO_CON_RX_LANES 0x1581 +#define UNIPRO_RX_MAX_PWM_GEAR 0x1586 +#define UNIPRO_RX_MAX_HS_GEAR 0x1587 +#define UNIPRO_RX_PWR_STATUS 0x1582 +#define UNIPRO_LOCAL_DEV_ID 0x3000 +#define UNIPRO_LOCAL_DEV_ID_VAL 0x3001 +#define UNIPRO_CPORT_CON_STAT 0x4020 +#define UNIPRO_PEER_DEV_ID 0x4021 +#define UNIPRO_PEER_CPORT_ID 0x4022 +#define UNIPRO_TRAFFIC_CLASS 0x4023 +#define UNIPRO_CPORT_FLAGS 0x4025 +#define UNIPRO_CPORT_MODE 0x402b +#define UNIPRO_CFG_UPDATE 0xd085 +#define UNIPRO_DBG_OMC 0xd09e + +#define UNIPRO_COMMON_BLK 0x8000 /* Common Block Offset*/ + +#endif /* _DWC_UNIPRO_H_ */ -- 1.8.1.5 -- To unsubscribe from this list: send the line "unsubscribe linux-scsi" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html