public inbox for devel@edk2.groups.io
 help / color / mirror / Atom feed
From: "Ard Biesheuvel" <ard.biesheuvel@arm.com>
To: devel@edk2.groups.io
Cc: leif@nuviainc.com, Ard Biesheuvel <ard.biesheuvel@arm.com>
Subject: [PATCH edk2-platforms v3 4/8] Silicon/Synopsys/DesignWare: import eMMC DXE driver from EmbeddedPkg
Date: Thu, 30 Apr 2020 19:16:45 +0200	[thread overview]
Message-ID: <20200430171650.24139-5-ard.biesheuvel@arm.com> (raw)
In-Reply-To: <20200430171650.24139-1-ard.biesheuvel@arm.com>

Incorporate the driver for the DesignWare eMMC host controller that is
based on the obsolete MMC host controller protocol that is defined in
EmbeddedPkg.

This driver does not follow the UEFI driver model, and is only kept
around for its only users, which is the HiKey platform, which is
rapidly reaching obsolescence itself, at which point this driver may
be removed again.

To prevent inadvertent use in new platforms, add a PCD that needs to
be changed from its default value in order for the driver to be
functional.

Signed-off-by: Ard Biesheuvel <ard.biesheuvel@arm.com>
---
 Silicon/Synopsys/DesignWare/DesignWare.dec                  |   9 +
 Silicon/Synopsys/DesignWare/DesignWare.dsc                  |   2 +
 Silicon/Synopsys/DesignWare/Drivers/DwEmmcDxe/DwEmmc.h      | 132 ++++
 Silicon/Synopsys/DesignWare/Drivers/DwEmmcDxe/DwEmmcDxe.c   | 693 ++++++++++++++++++++
 Silicon/Synopsys/DesignWare/Drivers/DwEmmcDxe/DwEmmcDxe.inf |  56 ++
 5 files changed, 892 insertions(+)

diff --git a/Silicon/Synopsys/DesignWare/DesignWare.dec b/Silicon/Synopsys/DesignWare/DesignWare.dec
index 71ddd24b7404..f7ec7927543c 100755
--- a/Silicon/Synopsys/DesignWare/DesignWare.dec
+++ b/Silicon/Synopsys/DesignWare/DesignWare.dec
@@ -21,4 +21,13 @@ [Guids.common]
   gDesignWareTokenSpaceGuid = { 0x89cb1241, 0xd283, 0x4543, { 0x88, 0x9c, 0x6b, 0x62, 0x36, 0x1a, 0x95, 0x7a } }
   gDwEmacNetNonDiscoverableDeviceGuid = { 0x401950CD, 0xF9CD, 0x4A65, { 0xAD, 0x8E, 0x84, 0x9F, 0x3B, 0xAF, 0x23, 0x04 } }
 
+[PcdsFixedAtBuild.common]
+  #
+  # Permit the use of obsolete drivers in this package
+  #
+  gDesignWareTokenSpaceGuid.PcdDwPermitObsoleteDrivers|FALSE|BOOLEAN|0x00000001
 
+  gDesignWareTokenSpaceGuid.PcdDwEmmcDxeBaseAddress|0x0|UINT32|0x00000002
+  gDesignWareTokenSpaceGuid.PcdDwEmmcDxeClockFrequencyInHz|0x0|UINT32|0x00000003
+  gDesignWareTokenSpaceGuid.PcdDwEmmcDxeMaxClockFreqInHz|0x0|UINT32|0x00000004
+  gDesignWareTokenSpaceGuid.PcdDwEmmcDxeFifoDepth|0x0|UINT32|0x00000005
diff --git a/Silicon/Synopsys/DesignWare/DesignWare.dsc b/Silicon/Synopsys/DesignWare/DesignWare.dsc
index ad6a5ede4ae0..098bba3f7d68 100755
--- a/Silicon/Synopsys/DesignWare/DesignWare.dsc
+++ b/Silicon/Synopsys/DesignWare/DesignWare.dsc
@@ -20,6 +20,7 @@ [LibraryClasses]
   ArmLib|ArmPkg/Library/ArmLib/ArmBaseLib.inf
   BaseLib|MdePkg/Library/BaseLib/BaseLib.inf
   BaseMemoryLib|MdePkg/Library/BaseMemoryLib/BaseMemoryLib.inf
+  CacheMaintenanceLib|MdePkg/Library/BaseCacheMaintenanceLib/BaseCacheMaintenanceLib.inf
   DebugLib|MdePkg/Library/BaseDebugLibNull/BaseDebugLibNull.inf
   DevicePathLib|MdePkg/Library/UefiDevicePathLib/UefiDevicePathLib.inf
   DmaLib|EmbeddedPkg/Library/NonCoherentDmaLib/NonCoherentDmaLib.inf
@@ -39,3 +40,4 @@ [LibraryClasses]
 
 [Components]
   Silicon/Synopsys/DesignWare/Drivers/DwEmacSnpDxe/DwEmacSnpDxe.inf
+  Silicon/Synopsys/DesignWare/Drivers/DwEmmcDxe/DwEmmcDxe.inf
diff --git a/Silicon/Synopsys/DesignWare/Drivers/DwEmmcDxe/DwEmmc.h b/Silicon/Synopsys/DesignWare/Drivers/DwEmmcDxe/DwEmmc.h
new file mode 100644
index 000000000000..09ad9b8428c4
--- /dev/null
+++ b/Silicon/Synopsys/DesignWare/Drivers/DwEmmcDxe/DwEmmc.h
@@ -0,0 +1,132 @@
+/** @file
+*
+*  WARNING:
+*  This driver fails to follow the UEFI driver model without a good
+*  reason, and only remains in the tree because it is still used by
+*  a small number of platforms. It will be removed when no longer used.
+*
+*  Copyright (c) 2014-2017, Linaro Limited. All rights reserved.
+*
+*  SPDX-License-Identifier: BSD-2-Clause-Patent
+*
+**/
+
+
+#ifndef __DWEMMC_H__
+#define __DWEMMC_H__
+
+#include <Protocol/EmbeddedGpio.h>
+
+// DW MMC Registers
+#define DWEMMC_CTRL             ((UINT32)PcdGet32 (PcdDwEmmcDxeBaseAddress) + 0x000)
+#define DWEMMC_PWREN            ((UINT32)PcdGet32 (PcdDwEmmcDxeBaseAddress) + 0x004)
+#define DWEMMC_CLKDIV           ((UINT32)PcdGet32 (PcdDwEmmcDxeBaseAddress) + 0x008)
+#define DWEMMC_CLKSRC           ((UINT32)PcdGet32 (PcdDwEmmcDxeBaseAddress) + 0x00c)
+#define DWEMMC_CLKENA           ((UINT32)PcdGet32 (PcdDwEmmcDxeBaseAddress) + 0x010)
+#define DWEMMC_TMOUT            ((UINT32)PcdGet32 (PcdDwEmmcDxeBaseAddress) + 0x014)
+#define DWEMMC_CTYPE            ((UINT32)PcdGet32 (PcdDwEmmcDxeBaseAddress) + 0x018)
+#define DWEMMC_BLKSIZ           ((UINT32)PcdGet32 (PcdDwEmmcDxeBaseAddress) + 0x01c)
+#define DWEMMC_BYTCNT           ((UINT32)PcdGet32 (PcdDwEmmcDxeBaseAddress) + 0x020)
+#define DWEMMC_INTMASK          ((UINT32)PcdGet32 (PcdDwEmmcDxeBaseAddress) + 0x024)
+#define DWEMMC_CMDARG           ((UINT32)PcdGet32 (PcdDwEmmcDxeBaseAddress) + 0x028)
+#define DWEMMC_CMD              ((UINT32)PcdGet32 (PcdDwEmmcDxeBaseAddress) + 0x02c)
+#define DWEMMC_RESP0            ((UINT32)PcdGet32 (PcdDwEmmcDxeBaseAddress) + 0x030)
+#define DWEMMC_RESP1            ((UINT32)PcdGet32 (PcdDwEmmcDxeBaseAddress) + 0x034)
+#define DWEMMC_RESP2            ((UINT32)PcdGet32 (PcdDwEmmcDxeBaseAddress) + 0x038)
+#define DWEMMC_RESP3            ((UINT32)PcdGet32 (PcdDwEmmcDxeBaseAddress) + 0x03c)
+#define DWEMMC_RINTSTS          ((UINT32)PcdGet32 (PcdDwEmmcDxeBaseAddress) + 0x044)
+#define DWEMMC_STATUS           ((UINT32)PcdGet32 (PcdDwEmmcDxeBaseAddress) + 0x048)
+#define DWEMMC_FIFOTH           ((UINT32)PcdGet32 (PcdDwEmmcDxeBaseAddress) + 0x04c)
+#define DWEMMC_TCBCNT           ((UINT32)PcdGet32 (PcdDwEmmcDxeBaseAddress) + 0x05c)
+#define DWEMMC_TBBCNT           ((UINT32)PcdGet32 (PcdDwEmmcDxeBaseAddress) + 0x060)
+#define DWEMMC_DEBNCE           ((UINT32)PcdGet32 (PcdDwEmmcDxeBaseAddress) + 0x064)
+#define DWEMMC_HCON             ((UINT32)PcdGet32 (PcdDwEmmcDxeBaseAddress) + 0x070)
+#define DWEMMC_UHSREG           ((UINT32)PcdGet32 (PcdDwEmmcDxeBaseAddress) + 0x074)
+#define DWEMMC_BMOD             ((UINT32)PcdGet32 (PcdDwEmmcDxeBaseAddress) + 0x080)
+#define DWEMMC_DBADDR           ((UINT32)PcdGet32 (PcdDwEmmcDxeBaseAddress) + 0x088)
+#define DWEMMC_IDSTS            ((UINT32)PcdGet32 (PcdDwEmmcDxeBaseAddress) + 0x08c)
+#define DWEMMC_IDINTEN          ((UINT32)PcdGet32 (PcdDwEmmcDxeBaseAddress) + 0x090)
+#define DWEMMC_DSCADDR          ((UINT32)PcdGet32 (PcdDwEmmcDxeBaseAddress) + 0x094)
+#define DWEMMC_BUFADDR          ((UINT32)PcdGet32 (PcdDwEmmcDxeBaseAddress) + 0x098)
+#define DWEMMC_CARDTHRCTL       ((UINT32)PcdGet32 (PcdDwEmmcDxeBaseAddress) + 0X100)
+#define DWEMMC_DATA             ((UINT32)PcdGet32 (PcdDwEmmcDxeBaseAddress) + 0X200)
+
+#define CMD_UPDATE_CLK                          0x80202000
+#define CMD_START_BIT                           (1 << 31)
+
+#define MMC_8BIT_MODE                           (1 << 16)
+
+#define BIT_CMD_RESPONSE_EXPECT                 (1 << 6)
+#define BIT_CMD_LONG_RESPONSE                   (1 << 7)
+#define BIT_CMD_CHECK_RESPONSE_CRC              (1 << 8)
+#define BIT_CMD_DATA_EXPECTED                   (1 << 9)
+#define BIT_CMD_READ                            (0 << 10)
+#define BIT_CMD_WRITE                           (1 << 10)
+#define BIT_CMD_BLOCK_TRANSFER                  (0 << 11)
+#define BIT_CMD_STREAM_TRANSFER                 (1 << 11)
+#define BIT_CMD_SEND_AUTO_STOP                  (1 << 12)
+#define BIT_CMD_WAIT_PRVDATA_COMPLETE           (1 << 13)
+#define BIT_CMD_STOP_ABORT_CMD                  (1 << 14)
+#define BIT_CMD_SEND_INIT                       (1 << 15)
+#define BIT_CMD_UPDATE_CLOCK_ONLY               (1 << 21)
+#define BIT_CMD_READ_CEATA_DEVICE               (1 << 22)
+#define BIT_CMD_CCS_EXPECTED                    (1 << 23)
+#define BIT_CMD_ENABLE_BOOT                     (1 << 24)
+#define BIT_CMD_EXPECT_BOOT_ACK                 (1 << 25)
+#define BIT_CMD_DISABLE_BOOT                    (1 << 26)
+#define BIT_CMD_MANDATORY_BOOT                  (0 << 27)
+#define BIT_CMD_ALTERNATE_BOOT                  (1 << 27)
+#define BIT_CMD_VOLT_SWITCH                     (1 << 28)
+#define BIT_CMD_USE_HOLD_REG                    (1 << 29)
+#define BIT_CMD_START                           (1 << 31)
+
+#define DWEMMC_INT_EBE                          (1 << 15)       /* End-bit Err */
+#define DWEMMC_INT_SBE                          (1 << 13)       /* Start-bit  Err */
+#define DWEMMC_INT_HLE                          (1 << 12)       /* Hardware-lock Err */
+#define DWEMMC_INT_FRUN                         (1 << 11)       /* FIFO UN/OV RUN */
+#define DWEMMC_INT_DRT                          (1 << 9)        /* Data timeout */
+#define DWEMMC_INT_RTO                          (1 << 8)        /* Response timeout */
+#define DWEMMC_INT_DCRC                         (1 << 7)        /* Data CRC err */
+#define DWEMMC_INT_RCRC                         (1 << 6)        /* Response CRC err */
+#define DWEMMC_INT_RXDR                         (1 << 5)
+#define DWEMMC_INT_TXDR                         (1 << 4)
+#define DWEMMC_INT_DTO                          (1 << 3)        /* Data trans over */
+#define DWEMMC_INT_CMD_DONE                     (1 << 2)
+#define DWEMMC_INT_RE                           (1 << 1)
+
+#define DWEMMC_IDMAC_DES0_DIC                   (1 << 1)
+#define DWEMMC_IDMAC_DES0_LD                    (1 << 2)
+#define DWEMMC_IDMAC_DES0_FS                    (1 << 3)
+#define DWEMMC_IDMAC_DES0_CH                    (1 << 4)
+#define DWEMMC_IDMAC_DES0_ER                    (1 << 5)
+#define DWEMMC_IDMAC_DES0_CES                   (1 << 30)
+#define DWEMMC_IDMAC_DES0_OWN                   (1 << 31)
+#define DWEMMC_IDMAC_DES1_BS1(x)                ((x) & 0x1fff)
+#define DWEMMC_IDMAC_DES2_BS2(x)                (((x) & 0x1fff) << 13)
+#define DWEMMC_IDMAC_SWRESET                    (1 << 0)
+#define DWEMMC_IDMAC_FB                         (1 << 1)
+#define DWEMMC_IDMAC_ENABLE                     (1 << 7)
+
+#define EMMC_FIX_RCA                            6
+
+/* bits in MMC0_CTRL */
+#define DWEMMC_CTRL_RESET                       (1 << 0)
+#define DWEMMC_CTRL_FIFO_RESET                  (1 << 1)
+#define DWEMMC_CTRL_DMA_RESET                   (1 << 2)
+#define DWEMMC_CTRL_INT_EN                      (1 << 4)
+#define DWEMMC_CTRL_DMA_EN                      (1 << 5)
+#define DWEMMC_CTRL_IDMAC_EN                    (1 << 25)
+#define DWEMMC_CTRL_RESET_ALL                   (DWEMMC_CTRL_RESET | DWEMMC_CTRL_FIFO_RESET | DWEMMC_CTRL_DMA_RESET)
+
+#define DWEMMC_STS_DATA_BUSY                    (1 << 9)
+
+#define DWEMMC_FIFO_TWMARK(x)                   (x & 0xfff)
+#define DWEMMC_FIFO_RWMARK(x)                   ((x & 0x1ff) << 16)
+#define DWEMMC_DMA_BURST_SIZE(x)                ((x & 0x7) << 28)
+
+#define DWEMMC_CARD_RD_THR(x)                   ((x & 0xfff) << 16)
+#define DWEMMC_CARD_RD_THR_EN                   (1 << 0)
+
+#define DWEMMC_GET_HDATA_WIDTH(x)               (((x) >> 7) & 0x7)
+
+#endif  // __DWEMMC_H__
diff --git a/Silicon/Synopsys/DesignWare/Drivers/DwEmmcDxe/DwEmmcDxe.c b/Silicon/Synopsys/DesignWare/Drivers/DwEmmcDxe/DwEmmcDxe.c
new file mode 100644
index 000000000000..eed5fc57fc22
--- /dev/null
+++ b/Silicon/Synopsys/DesignWare/Drivers/DwEmmcDxe/DwEmmcDxe.c
@@ -0,0 +1,693 @@
+/** @file
+  This file implement the MMC Host Protocol for the DesignWare eMMC.
+
+  Copyright (c) 2014-2017, Linaro Limited. All rights reserved.
+
+  SPDX-License-Identifier: BSD-2-Clause-Patent
+
+**/
+
+#include <Library/BaseMemoryLib.h>
+#include <Library/CacheMaintenanceLib.h>
+#include <Library/DebugLib.h>
+#include <Library/DevicePathLib.h>
+#include <Library/IoLib.h>
+#include <Library/MemoryAllocationLib.h>
+#include <Library/PcdLib.h>
+#include <Library/TimerLib.h>
+#include <Library/UefiBootServicesTableLib.h>
+#include <Library/UefiLib.h>
+
+#include <Protocol/MmcHost.h>
+
+#include "DwEmmc.h"
+
+#define DWEMMC_DESC_PAGE                1
+#define DWEMMC_BLOCK_SIZE               512
+#define DWEMMC_DMA_BUF_SIZE             (512 * 8)
+#define DWEMMC_MAX_DESC_PAGES           512
+
+typedef struct {
+  UINT32                        Des0;
+  UINT32                        Des1;
+  UINT32                        Des2;
+  UINT32                        Des3;
+} DWEMMC_IDMAC_DESCRIPTOR;
+
+EFI_MMC_HOST_PROTOCOL     *gpMmcHost;
+DWEMMC_IDMAC_DESCRIPTOR   *gpIdmacDesc;
+EFI_GUID mDwEmmcDevicePathGuid = EFI_CALLER_ID_GUID;
+STATIC UINT32 mDwEmmcCommand;
+STATIC UINT32 mDwEmmcArgument;
+
+EFI_STATUS
+DwEmmcReadBlockData (
+  IN EFI_MMC_HOST_PROTOCOL     *This,
+  IN EFI_LBA                    Lba,
+  IN UINTN                      Length,
+  IN UINT32*                    Buffer
+  );
+
+BOOLEAN
+DwEmmcIsPowerOn (
+  VOID
+  )
+{
+    return TRUE;
+}
+
+EFI_STATUS
+DwEmmcInitialize (
+  VOID
+  )
+{
+    DEBUG ((DEBUG_BLKIO, "DwEmmcInitialize()"));
+    return EFI_SUCCESS;
+}
+
+BOOLEAN
+DwEmmcIsCardPresent (
+  IN EFI_MMC_HOST_PROTOCOL     *This
+  )
+{
+  return TRUE;
+}
+
+BOOLEAN
+DwEmmcIsReadOnly (
+  IN EFI_MMC_HOST_PROTOCOL     *This
+  )
+{
+  return FALSE;
+}
+
+BOOLEAN
+DwEmmcIsDmaSupported (
+  IN EFI_MMC_HOST_PROTOCOL     *This
+  )
+{
+  return TRUE;
+}
+
+EFI_STATUS
+DwEmmcBuildDevicePath (
+  IN EFI_MMC_HOST_PROTOCOL      *This,
+  IN EFI_DEVICE_PATH_PROTOCOL   **DevicePath
+  )
+{
+  EFI_DEVICE_PATH_PROTOCOL *NewDevicePathNode;
+
+  NewDevicePathNode = CreateDeviceNode (HARDWARE_DEVICE_PATH, HW_VENDOR_DP, sizeof (VENDOR_DEVICE_PATH));
+  CopyGuid (& ((VENDOR_DEVICE_PATH*)NewDevicePathNode)->Guid, &mDwEmmcDevicePathGuid);
+
+  *DevicePath = NewDevicePathNode;
+  return EFI_SUCCESS;
+}
+
+EFI_STATUS
+DwEmmcUpdateClock (
+  VOID
+  )
+{
+  UINT32 Data;
+
+  /* CMD_UPDATE_CLK */
+  Data = BIT_CMD_WAIT_PRVDATA_COMPLETE | BIT_CMD_UPDATE_CLOCK_ONLY |
+         BIT_CMD_START;
+  MmioWrite32 (DWEMMC_CMD, Data);
+  while (1) {
+    Data = MmioRead32 (DWEMMC_CMD);
+    if (!(Data & CMD_START_BIT)) {
+      break;
+    }
+    Data = MmioRead32 (DWEMMC_RINTSTS);
+    if (Data & DWEMMC_INT_HLE) {
+      Print (L"failed to update mmc clock frequency\n");
+      return EFI_DEVICE_ERROR;
+    }
+  }
+  return EFI_SUCCESS;
+}
+
+EFI_STATUS
+DwEmmcSetClock (
+  IN UINTN                     ClockFreq
+  )
+{
+  UINT32 Divider, Rate, Data;
+  EFI_STATUS Status;
+  BOOLEAN Found = FALSE;
+
+  for (Divider = 1; Divider < 256; Divider++) {
+    Rate = PcdGet32 (PcdDwEmmcDxeClockFrequencyInHz);
+    if ((Rate / (2 * Divider)) <= ClockFreq) {
+      Found = TRUE;
+      break;
+    }
+  }
+  if (Found == FALSE) {
+    return EFI_NOT_FOUND;
+  }
+
+  // Wait until MMC is idle
+  do {
+    Data = MmioRead32 (DWEMMC_STATUS);
+  } while (Data & DWEMMC_STS_DATA_BUSY);
+
+  // Disable MMC clock first
+  MmioWrite32 (DWEMMC_CLKENA, 0);
+  Status = DwEmmcUpdateClock ();
+  ASSERT (!EFI_ERROR (Status));
+
+  MmioWrite32 (DWEMMC_CLKDIV, Divider);
+  Status = DwEmmcUpdateClock ();
+  ASSERT (!EFI_ERROR (Status));
+
+  // Enable MMC clock
+  MmioWrite32 (DWEMMC_CLKENA, 1);
+  MmioWrite32 (DWEMMC_CLKSRC, 0);
+  Status = DwEmmcUpdateClock ();
+  ASSERT (!EFI_ERROR (Status));
+  return EFI_SUCCESS;
+}
+
+EFI_STATUS
+DwEmmcNotifyState (
+  IN EFI_MMC_HOST_PROTOCOL     *This,
+  IN MMC_STATE                 State
+  )
+{
+  UINT32      Data;
+  EFI_STATUS  Status;
+
+  switch (State) {
+  case MmcInvalidState:
+    return EFI_INVALID_PARAMETER;
+  case MmcHwInitializationState:
+    MmioWrite32 (DWEMMC_PWREN, 1);
+
+    // If device already turn on then restart it
+    Data = DWEMMC_CTRL_RESET_ALL;
+    MmioWrite32 (DWEMMC_CTRL, Data);
+    do {
+      // Wait until reset operation finished
+      Data = MmioRead32 (DWEMMC_CTRL);
+    } while (Data & DWEMMC_CTRL_RESET_ALL);
+
+    // Setup clock that could not be higher than 400KHz.
+    Status = DwEmmcSetClock (400000);
+    ASSERT (!EFI_ERROR (Status));
+    // Wait clock stable
+    MicroSecondDelay (100);
+
+    MmioWrite32 (DWEMMC_RINTSTS, ~0);
+    MmioWrite32 (DWEMMC_INTMASK, 0);
+    MmioWrite32 (DWEMMC_TMOUT, ~0);
+    MmioWrite32 (DWEMMC_IDINTEN, 0);
+    MmioWrite32 (DWEMMC_BMOD, DWEMMC_IDMAC_SWRESET);
+
+    MmioWrite32 (DWEMMC_BLKSIZ, DWEMMC_BLOCK_SIZE);
+    do {
+      Data = MmioRead32 (DWEMMC_BMOD);
+    } while (Data & DWEMMC_IDMAC_SWRESET);
+    break;
+  case MmcIdleState:
+    break;
+  case MmcReadyState:
+    break;
+  case MmcIdentificationState:
+    break;
+  case MmcStandByState:
+    break;
+  case MmcTransferState:
+    break;
+  case MmcSendingDataState:
+    break;
+  case MmcReceiveDataState:
+    break;
+  case MmcProgrammingState:
+    break;
+  case MmcDisconnectState:
+    break;
+  default:
+    return EFI_INVALID_PARAMETER;
+  }
+  return EFI_SUCCESS;
+}
+
+// Need to prepare DMA buffer first before sending commands to MMC card
+BOOLEAN
+IsPendingReadCommand (
+  IN MMC_CMD                    MmcCmd
+  )
+{
+  UINTN  Mask;
+
+  Mask = BIT_CMD_DATA_EXPECTED | BIT_CMD_READ;
+  if ((MmcCmd & Mask) == Mask) {
+    return TRUE;
+  }
+  return FALSE;
+}
+
+BOOLEAN
+IsPendingWriteCommand (
+  IN MMC_CMD                    MmcCmd
+  )
+{
+  UINTN  Mask;
+
+  Mask = BIT_CMD_DATA_EXPECTED | BIT_CMD_WRITE;
+  if ((MmcCmd & Mask) == Mask) {
+    return TRUE;
+  }
+  return FALSE;
+}
+
+EFI_STATUS
+SendCommand (
+  IN MMC_CMD                    MmcCmd,
+  IN UINT32                     Argument
+  )
+{
+  UINT32      Data, ErrMask;
+
+  // Wait until MMC is idle
+  do {
+    Data = MmioRead32 (DWEMMC_STATUS);
+  } while (Data & DWEMMC_STS_DATA_BUSY);
+
+  MmioWrite32 (DWEMMC_RINTSTS, ~0);
+  MmioWrite32 (DWEMMC_CMDARG, Argument);
+  MmioWrite32 (DWEMMC_CMD, MmcCmd);
+
+  ErrMask = DWEMMC_INT_EBE | DWEMMC_INT_HLE | DWEMMC_INT_RTO |
+            DWEMMC_INT_RCRC | DWEMMC_INT_RE;
+  ErrMask |= DWEMMC_INT_DCRC | DWEMMC_INT_DRT | DWEMMC_INT_SBE;
+  do {
+    MicroSecondDelay(500);
+    Data = MmioRead32 (DWEMMC_RINTSTS);
+
+    if (Data & ErrMask) {
+      return EFI_DEVICE_ERROR;
+    }
+    if (Data & DWEMMC_INT_DTO) {     // Transfer Done
+      break;
+    }
+  } while (!(Data & DWEMMC_INT_CMD_DONE));
+  return EFI_SUCCESS;
+}
+
+EFI_STATUS
+DwEmmcSendCommand (
+  IN EFI_MMC_HOST_PROTOCOL     *This,
+  IN MMC_CMD                    MmcCmd,
+  IN UINT32                     Argument
+  )
+{
+  UINT32       Cmd = 0;
+  EFI_STATUS   Status = EFI_SUCCESS;
+
+  switch (MMC_GET_INDX(MmcCmd)) {
+  case MMC_INDX(0):
+    Cmd = BIT_CMD_SEND_INIT;
+    break;
+  case MMC_INDX(1):
+    Cmd = BIT_CMD_RESPONSE_EXPECT;
+    break;
+  case MMC_INDX(2):
+    Cmd = BIT_CMD_RESPONSE_EXPECT | BIT_CMD_LONG_RESPONSE |
+           BIT_CMD_CHECK_RESPONSE_CRC | BIT_CMD_SEND_INIT;
+    break;
+  case MMC_INDX(3):
+    Cmd = BIT_CMD_RESPONSE_EXPECT | BIT_CMD_CHECK_RESPONSE_CRC |
+           BIT_CMD_SEND_INIT;
+    break;
+  case MMC_INDX(7):
+    if (Argument)
+        Cmd = BIT_CMD_RESPONSE_EXPECT | BIT_CMD_CHECK_RESPONSE_CRC;
+    else
+        Cmd = 0;
+    break;
+  case MMC_INDX(8):
+    Cmd = BIT_CMD_RESPONSE_EXPECT | BIT_CMD_CHECK_RESPONSE_CRC |
+           BIT_CMD_DATA_EXPECTED | BIT_CMD_READ |
+           BIT_CMD_WAIT_PRVDATA_COMPLETE;
+    break;
+  case MMC_INDX(9):
+    Cmd = BIT_CMD_RESPONSE_EXPECT | BIT_CMD_CHECK_RESPONSE_CRC |
+           BIT_CMD_LONG_RESPONSE;
+    break;
+  case MMC_INDX(12):
+    Cmd = BIT_CMD_RESPONSE_EXPECT | BIT_CMD_CHECK_RESPONSE_CRC |
+           BIT_CMD_STOP_ABORT_CMD;
+    break;
+  case MMC_INDX(13):
+    Cmd = BIT_CMD_RESPONSE_EXPECT | BIT_CMD_CHECK_RESPONSE_CRC |
+           BIT_CMD_WAIT_PRVDATA_COMPLETE;
+    break;
+  case MMC_INDX(16):
+    Cmd = BIT_CMD_RESPONSE_EXPECT | BIT_CMD_CHECK_RESPONSE_CRC |
+           BIT_CMD_DATA_EXPECTED | BIT_CMD_READ |
+           BIT_CMD_WAIT_PRVDATA_COMPLETE;
+    break;
+  case MMC_INDX(17):
+  case MMC_INDX(18):
+    Cmd = BIT_CMD_RESPONSE_EXPECT | BIT_CMD_CHECK_RESPONSE_CRC |
+           BIT_CMD_DATA_EXPECTED | BIT_CMD_READ |
+           BIT_CMD_WAIT_PRVDATA_COMPLETE;
+    break;
+  case MMC_INDX(24):
+  case MMC_INDX(25):
+    Cmd = BIT_CMD_RESPONSE_EXPECT | BIT_CMD_CHECK_RESPONSE_CRC |
+           BIT_CMD_DATA_EXPECTED | BIT_CMD_WRITE |
+           BIT_CMD_WAIT_PRVDATA_COMPLETE;
+    break;
+  case MMC_INDX(30):
+    Cmd = BIT_CMD_RESPONSE_EXPECT | BIT_CMD_CHECK_RESPONSE_CRC |
+           BIT_CMD_DATA_EXPECTED;
+    break;
+  default:
+    Cmd = BIT_CMD_RESPONSE_EXPECT | BIT_CMD_CHECK_RESPONSE_CRC;
+    break;
+  }
+
+  Cmd |= MMC_GET_INDX(MmcCmd) | BIT_CMD_USE_HOLD_REG | BIT_CMD_START;
+  if (IsPendingReadCommand (Cmd) || IsPendingWriteCommand (Cmd)) {
+    mDwEmmcCommand = Cmd;
+    mDwEmmcArgument = Argument;
+  } else {
+    Status = SendCommand (Cmd, Argument);
+  }
+  return Status;
+}
+
+EFI_STATUS
+DwEmmcReceiveResponse (
+  IN EFI_MMC_HOST_PROTOCOL     *This,
+  IN MMC_RESPONSE_TYPE          Type,
+  IN UINT32*                    Buffer
+  )
+{
+  if (Buffer == NULL) {
+    return EFI_INVALID_PARAMETER;
+  }
+
+  if (   (Type == MMC_RESPONSE_TYPE_R1)
+      || (Type == MMC_RESPONSE_TYPE_R1b)
+      || (Type == MMC_RESPONSE_TYPE_R3)
+      || (Type == MMC_RESPONSE_TYPE_R6)
+      || (Type == MMC_RESPONSE_TYPE_R7))
+  {
+    Buffer[0] = MmioRead32 (DWEMMC_RESP0);
+  } else if (Type == MMC_RESPONSE_TYPE_R2) {
+    Buffer[0] = MmioRead32 (DWEMMC_RESP0);
+    Buffer[1] = MmioRead32 (DWEMMC_RESP1);
+    Buffer[2] = MmioRead32 (DWEMMC_RESP2);
+    Buffer[3] = MmioRead32 (DWEMMC_RESP3);
+  }
+  return EFI_SUCCESS;
+}
+
+VOID
+DwEmmcAdjustFifoThreshold (
+  VOID
+  )
+{
+  /* DMA multiple transaction size map to reg value as array index */
+  CONST UINT32 BurstSize[] = {1, 4, 8, 16, 32, 64, 128, 256};
+  UINT32 BlkDepthInFifo, FifoThreshold, FifoWidth, FifoDepth;
+  UINT32 BlkSize = DWEMMC_BLOCK_SIZE, Idx = 0, RxWatermark = 1, TxWatermark, TxWatermarkInvers;
+
+  /* Skip FIFO adjustment if we do not have platform FIFO depth info */
+  FifoDepth = PcdGet32 (PcdDwEmmcDxeFifoDepth);
+  if (!FifoDepth) {
+    return;
+  }
+
+  TxWatermark = FifoDepth / 2;
+  TxWatermarkInvers = FifoDepth - TxWatermark;
+
+  FifoWidth = DWEMMC_GET_HDATA_WIDTH (MmioRead32 (DWEMMC_HCON));
+  if (!FifoWidth) {
+    FifoWidth = 2;
+  } else if (FifoWidth == 2) {
+    FifoWidth = 8;
+  } else {
+    FifoWidth = 4;
+  }
+
+  BlkDepthInFifo = BlkSize / FifoWidth;
+
+  Idx = ARRAY_SIZE (BurstSize) - 1;
+  while (Idx && ((BlkDepthInFifo % BurstSize[Idx]) || (TxWatermarkInvers % BurstSize[Idx]))) {
+    Idx--;
+  }
+
+  RxWatermark = BurstSize[Idx] - 1;
+  FifoThreshold = DWEMMC_DMA_BURST_SIZE (Idx) | DWEMMC_FIFO_TWMARK (TxWatermark)
+           | DWEMMC_FIFO_RWMARK (RxWatermark);
+  MmioWrite32 (DWEMMC_FIFOTH, FifoThreshold);
+}
+
+EFI_STATUS
+PrepareDmaData (
+  IN DWEMMC_IDMAC_DESCRIPTOR*    IdmacDesc,
+  IN UINTN                      Length,
+  IN UINT32*                    Buffer
+  )
+{
+  UINTN  Cnt, Blks, Idx, LastIdx;
+
+  Cnt = (Length + DWEMMC_DMA_BUF_SIZE - 1) / DWEMMC_DMA_BUF_SIZE;
+  Blks = (Length + DWEMMC_BLOCK_SIZE - 1) / DWEMMC_BLOCK_SIZE;
+  Length = DWEMMC_BLOCK_SIZE * Blks;
+
+  for (Idx = 0; Idx < Cnt; Idx++) {
+    (IdmacDesc + Idx)->Des0 = DWEMMC_IDMAC_DES0_OWN | DWEMMC_IDMAC_DES0_CH |
+                              DWEMMC_IDMAC_DES0_DIC;
+    (IdmacDesc + Idx)->Des1 = DWEMMC_IDMAC_DES1_BS1(DWEMMC_DMA_BUF_SIZE);
+    /* Buffer Address */
+    (IdmacDesc + Idx)->Des2 = (UINT32)((UINTN)Buffer + DWEMMC_DMA_BUF_SIZE * Idx);
+    /* Next Descriptor Address */
+    (IdmacDesc + Idx)->Des3 = (UINT32)((UINTN)IdmacDesc +
+                                       (sizeof(DWEMMC_IDMAC_DESCRIPTOR) * (Idx + 1)));
+  }
+  /* First Descriptor */
+  IdmacDesc->Des0 |= DWEMMC_IDMAC_DES0_FS;
+  /* Last Descriptor */
+  LastIdx = Cnt - 1;
+  (IdmacDesc + LastIdx)->Des0 |= DWEMMC_IDMAC_DES0_LD;
+  (IdmacDesc + LastIdx)->Des0 &= ~(DWEMMC_IDMAC_DES0_DIC | DWEMMC_IDMAC_DES0_CH);
+  (IdmacDesc + LastIdx)->Des1 = DWEMMC_IDMAC_DES1_BS1(Length -
+                                                      (LastIdx * DWEMMC_DMA_BUF_SIZE));
+  /* Set the Next field of Last Descriptor */
+  (IdmacDesc + LastIdx)->Des3 = 0;
+  MmioWrite32 (DWEMMC_DBADDR, (UINT32)((UINTN)IdmacDesc));
+
+  return EFI_SUCCESS;
+}
+
+VOID
+StartDma (
+  UINTN    Length
+  )
+{
+  UINT32 Data;
+
+  Data = MmioRead32 (DWEMMC_CTRL);
+  Data |= DWEMMC_CTRL_INT_EN | DWEMMC_CTRL_DMA_EN | DWEMMC_CTRL_IDMAC_EN;
+  MmioWrite32 (DWEMMC_CTRL, Data);
+  Data = MmioRead32 (DWEMMC_BMOD);
+  Data |= DWEMMC_IDMAC_ENABLE | DWEMMC_IDMAC_FB;
+  MmioWrite32 (DWEMMC_BMOD, Data);
+
+  MmioWrite32 (DWEMMC_BLKSIZ, DWEMMC_BLOCK_SIZE);
+  MmioWrite32 (DWEMMC_BYTCNT, Length);
+}
+
+EFI_STATUS
+DwEmmcReadBlockData (
+  IN EFI_MMC_HOST_PROTOCOL     *This,
+  IN EFI_LBA                    Lba,
+  IN UINTN                      Length,
+  IN UINT32*                   Buffer
+  )
+{
+  EFI_STATUS  Status;
+  UINT32      DescPages, CountPerPage, Count;
+  EFI_TPL     Tpl;
+
+  Tpl = gBS->RaiseTPL (TPL_NOTIFY);
+
+  CountPerPage = EFI_PAGE_SIZE / 16;
+  Count = (Length + DWEMMC_DMA_BUF_SIZE - 1) / DWEMMC_DMA_BUF_SIZE;
+  DescPages = (Count + CountPerPage - 1) / CountPerPage;
+
+  InvalidateDataCacheRange (Buffer, Length);
+
+  Status = PrepareDmaData (gpIdmacDesc, Length, Buffer);
+  if (EFI_ERROR (Status)) {
+    goto out;
+  }
+
+  WriteBackDataCacheRange (gpIdmacDesc, DescPages * EFI_PAGE_SIZE);
+  StartDma (Length);
+
+  Status = SendCommand (mDwEmmcCommand, mDwEmmcArgument);
+  if (EFI_ERROR (Status)) {
+    DEBUG ((DEBUG_ERROR, "Failed to read data, mDwEmmcCommand:%x, mDwEmmcArgument:%x, Status:%r\n", mDwEmmcCommand, mDwEmmcArgument, Status));
+    goto out;
+  }
+out:
+  // Restore Tpl
+  gBS->RestoreTPL (Tpl);
+  return Status;
+}
+
+EFI_STATUS
+DwEmmcWriteBlockData (
+  IN EFI_MMC_HOST_PROTOCOL     *This,
+  IN EFI_LBA                    Lba,
+  IN UINTN                      Length,
+  IN UINT32*                    Buffer
+  )
+{
+  EFI_STATUS  Status;
+  UINT32      DescPages, CountPerPage, Count;
+  EFI_TPL     Tpl;
+
+  Tpl = gBS->RaiseTPL (TPL_NOTIFY);
+
+  CountPerPage = EFI_PAGE_SIZE / 16;
+  Count = (Length + DWEMMC_DMA_BUF_SIZE - 1) / DWEMMC_DMA_BUF_SIZE;
+  DescPages = (Count + CountPerPage - 1) / CountPerPage;
+
+  WriteBackDataCacheRange (Buffer, Length);
+
+  Status = PrepareDmaData (gpIdmacDesc, Length, Buffer);
+  if (EFI_ERROR (Status)) {
+    goto out;
+  }
+
+  WriteBackDataCacheRange (gpIdmacDesc, DescPages * EFI_PAGE_SIZE);
+  StartDma (Length);
+
+  Status = SendCommand (mDwEmmcCommand, mDwEmmcArgument);
+  if (EFI_ERROR (Status)) {
+    DEBUG ((DEBUG_ERROR, "Failed to write data, mDwEmmcCommand:%x, mDwEmmcArgument:%x, Status:%r\n", mDwEmmcCommand, mDwEmmcArgument, Status));
+    goto out;
+  }
+out:
+  // Restore Tpl
+  gBS->RestoreTPL (Tpl);
+  return Status;
+}
+
+EFI_STATUS
+DwEmmcSetIos (
+  IN EFI_MMC_HOST_PROTOCOL      *This,
+  IN  UINT32                    BusClockFreq,
+  IN  UINT32                    BusWidth,
+  IN  UINT32                    TimingMode
+  )
+{
+  EFI_STATUS Status = EFI_SUCCESS;
+  UINT32    Data;
+
+  if ((PcdGet32 (PcdDwEmmcDxeMaxClockFreqInHz) != 0) &&
+      (BusClockFreq > PcdGet32 (PcdDwEmmcDxeMaxClockFreqInHz))) {
+    return EFI_UNSUPPORTED;
+  }
+  if (TimingMode != EMMCBACKWARD) {
+    Data = MmioRead32 (DWEMMC_UHSREG);
+    switch (TimingMode) {
+    case EMMCHS52DDR1V2:
+    case EMMCHS52DDR1V8:
+      Data |= 1 << 16;
+      break;
+    case EMMCHS52:
+    case EMMCHS26:
+      Data &= ~(1 << 16);
+      break;
+    default:
+      return EFI_UNSUPPORTED;
+    }
+    MmioWrite32 (DWEMMC_UHSREG, Data);
+  }
+
+  switch (BusWidth) {
+  case 1:
+    MmioWrite32 (DWEMMC_CTYPE, 0);
+    break;
+  case 4:
+    MmioWrite32 (DWEMMC_CTYPE, 1);
+    break;
+  case 8:
+    MmioWrite32 (DWEMMC_CTYPE, 1 << 16);
+    break;
+  default:
+    return EFI_UNSUPPORTED;
+  }
+  if (BusClockFreq) {
+    Status = DwEmmcSetClock (BusClockFreq);
+  }
+  return Status;
+}
+
+BOOLEAN
+DwEmmcIsMultiBlock (
+  IN EFI_MMC_HOST_PROTOCOL      *This
+  )
+{
+  return TRUE;
+}
+
+EFI_MMC_HOST_PROTOCOL gMciHost = {
+  MMC_HOST_PROTOCOL_REVISION,
+  DwEmmcIsCardPresent,
+  DwEmmcIsReadOnly,
+  DwEmmcBuildDevicePath,
+  DwEmmcNotifyState,
+  DwEmmcSendCommand,
+  DwEmmcReceiveResponse,
+  DwEmmcReadBlockData,
+  DwEmmcWriteBlockData,
+  DwEmmcSetIos,
+  DwEmmcIsMultiBlock
+};
+
+EFI_STATUS
+DwEmmcDxeInitialize (
+  IN EFI_HANDLE         ImageHandle,
+  IN EFI_SYSTEM_TABLE   *SystemTable
+  )
+{
+  EFI_STATUS    Status;
+  EFI_HANDLE    Handle;
+
+  if (!FixedPcdGetBool (PcdDwPermitObsoleteDrivers)) {
+    ASSERT (FALSE);
+    return EFI_UNSUPPORTED;
+  }
+
+  Handle = NULL;
+
+  DwEmmcAdjustFifoThreshold ();
+  gpIdmacDesc = (DWEMMC_IDMAC_DESCRIPTOR *)AllocatePages (DWEMMC_MAX_DESC_PAGES);
+  if (gpIdmacDesc == NULL) {
+    return EFI_BUFFER_TOO_SMALL;
+  }
+
+  DEBUG ((DEBUG_BLKIO, "DwEmmcDxeInitialize()\n"));
+
+  //Publish Component Name, BlockIO protocol interfaces
+  Status = gBS->InstallMultipleProtocolInterfaces (
+                  &Handle,
+                  &gEmbeddedMmcHostProtocolGuid,    &gMciHost,
+                  NULL
+                  );
+  ASSERT_EFI_ERROR (Status);
+
+  return EFI_SUCCESS;
+}
diff --git a/Silicon/Synopsys/DesignWare/Drivers/DwEmmcDxe/DwEmmcDxe.inf b/Silicon/Synopsys/DesignWare/Drivers/DwEmmcDxe/DwEmmcDxe.inf
new file mode 100644
index 000000000000..7f70fe1e2a38
--- /dev/null
+++ b/Silicon/Synopsys/DesignWare/Drivers/DwEmmcDxe/DwEmmcDxe.inf
@@ -0,0 +1,56 @@
+#/** @file
+#  INF file for the eMMC Host Protocol implementation for the DesignWare MMC.
+#
+#  WARNING:
+#  This driver fails to follow the UEFI driver model without a good
+#  reason, and only remains in the tree because it is still used by
+#  a small number of platforms. It will be removed when no longer used.
+#
+#  Copyright (c) 2014-2017, Linaro Limited. All rights reserved.
+#
+#  SPDX-License-Identifier: BSD-2-Clause-Patent
+#
+#**/
+
+[Defines]
+  INF_VERSION                    = 0x00010019
+  BASE_NAME                      = DwEmmcDxe
+  FILE_GUID                      = b549f005-4bd4-4020-a0cb-06f42bda68c3
+  MODULE_TYPE                    = DXE_DRIVER
+  VERSION_STRING                 = 1.0
+
+  ENTRY_POINT                    = DwEmmcDxeInitialize
+
+[Sources.common]
+  DwEmmcDxe.c
+
+[Packages]
+  EmbeddedPkg/EmbeddedPkg.dec
+  MdePkg/MdePkg.dec
+  Silicon/Synopsys/DesignWare/DesignWare.dec
+
+[LibraryClasses]
+  ArmLib
+  BaseLib
+  BaseMemoryLib
+  CacheMaintenanceLib
+  IoLib
+  MemoryAllocationLib
+  TimerLib
+  UefiDriverEntryPoint
+  UefiLib
+
+[Protocols]
+  gEfiCpuArchProtocolGuid
+  gEfiDevicePathProtocolGuid
+  gEmbeddedMmcHostProtocolGuid
+
+[Pcd]
+  gDesignWareTokenSpaceGuid.PcdDwEmmcDxeBaseAddress
+  gDesignWareTokenSpaceGuid.PcdDwEmmcDxeClockFrequencyInHz
+  gDesignWareTokenSpaceGuid.PcdDwEmmcDxeMaxClockFreqInHz
+  gDesignWareTokenSpaceGuid.PcdDwEmmcDxeFifoDepth
+  gDesignWareTokenSpaceGuid.PcdDwPermitObsoleteDrivers
+
+[Depex]
+  TRUE
-- 
2.17.1


  parent reply	other threads:[~2020-04-30 17:17 UTC|newest]

Thread overview: 20+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-04-30 17:16 [PATCH edk2-platforms v3 0/8] move obsolete platform drivers out of core edk2 Ard Biesheuvel
2020-04-30 17:16 ` [PATCH edk2-platforms v3 1/8] Omap35xxPkg/LcdGraphicsOutputDxe: add missing protocol reference Ard Biesheuvel
2020-04-30 17:16 ` [PATCH edk2-platforms v3 2/8] Platform/ARM/VExpressPkg: incorporate PL180 driver Ard Biesheuvel
2020-04-30 17:16 ` [PATCH edk2-platforms v3 3/8] Platform/ARM/JunoPkg: incorporate SiI3132 SATA controller driver Ard Biesheuvel
2020-05-04 10:57   ` Leif Lindholm
2020-04-30 17:16 ` Ard Biesheuvel [this message]
2020-05-04 10:58   ` [PATCH edk2-platforms v3 4/8] Silicon/Synopsys/DesignWare: import eMMC DXE driver from EmbeddedPkg Leif Lindholm
2020-05-04 11:40     ` [edk2-devel] " Philippe Mathieu-Daudé
2020-05-04 11:44       ` Philippe Mathieu-Daudé
2020-04-30 17:16 ` [PATCH edk2-platforms v3 5/8] Platform/HiKey: switch to relocated version of eMMC driver Ard Biesheuvel
2020-04-30 17:16 ` [PATCH edk2-platforms v3 6/8] Platform/ARM/VExpressPkg: incorporate Lan91x driver Ard Biesheuvel
2020-05-04 11:41   ` [edk2-devel] " Philippe Mathieu-Daudé
2020-05-04 11:44     ` Philippe Mathieu-Daudé
2020-04-30 17:16 ` [PATCH edk2-platforms v3 7/8] Platform/ARM/VExpressPkg: incorporate Lan9118 driver Ard Biesheuvel
2020-05-04 11:42   ` [edk2-devel] " Philippe Mathieu-Daudé
2020-05-04 11:44     ` Philippe Mathieu-Daudé
2020-04-30 17:16 ` [PATCH edk2-platforms v3 8/8] Platform/ARM/VExpressPkg: incorporate ISP 1761 USB host driver Ard Biesheuvel
2020-05-04 11:00   ` Leif Lindholm
2020-05-04 11:00 ` [PATCH edk2-platforms v3 0/8] move obsolete platform drivers out of core edk2 Leif Lindholm
2020-05-04 13:19   ` Ard Biesheuvel

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-list from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20200430171650.24139-5-ard.biesheuvel@arm.com \
    --to=devel@edk2.groups.io \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox