public inbox for devel@edk2.groups.io
 help / color / mirror / Atom feed
From: "Leif Lindholm" <leif@nuviainc.com>
To: Ard Biesheuvel <ard.biesheuvel@arm.com>
Cc: devel@edk2.groups.io
Subject: Re: [PATCH edk2-platforms 2/2] Platform/ARM/VExpressPkg: incorporate PL180 driver
Date: Wed, 29 Apr 2020 22:51:01 +0100	[thread overview]
Message-ID: <20200429215101.GO21486@vanye> (raw)
In-Reply-To: <20200429184705.20934-2-ard.biesheuvel@arm.com>

On Wed, Apr 29, 2020 at 20:47:05 +0200, Ard Biesheuvel wrote:
> The PL180 SD host controller driver is only used on emulated ARM
> platforms, uses an obsolete version of the MMC host protocol and
> does not adhere to the UEFI driver model.
> 
> Given the above, let's just move it into VExpressPkg where it
> belongs.
> 
> Signed-off-by: Ard Biesheuvel <ard.biesheuvel@arm.com>

I agree with the change, but will want the warning message added
wherever we decide in the separate bikeshedding thread.

/
    Leif

> ---
>  Platform/ARM/VExpressPkg/ArmVExpress-CTA15-A7.dsc            |   6 +-
>  Platform/ARM/VExpressPkg/ArmVExpress-CTA15-A7.fdf            |   2 +-
>  Platform/ARM/VExpressPkg/ArmVExpress-FVP-AArch64.dsc         |   6 +-
>  Platform/ARM/VExpressPkg/ArmVExpress-FVP-AArch64.fdf         |   2 +-
>  Platform/ARM/VExpressPkg/ArmVExpressPkg.dec                  |   4 +
>  Platform/ARM/VExpressPkg/Drivers/PL180MciDxe/PL180Mci.c      | 563 ++++++++++++++++++++
>  Platform/ARM/VExpressPkg/Drivers/PL180MciDxe/PL180Mci.h      | 162 ++++++
>  Platform/ARM/VExpressPkg/Drivers/PL180MciDxe/PL180MciDxe.inf |  46 ++
>  8 files changed, 783 insertions(+), 8 deletions(-)
> 
> diff --git a/Platform/ARM/VExpressPkg/ArmVExpress-CTA15-A7.dsc b/Platform/ARM/VExpressPkg/ArmVExpress-CTA15-A7.dsc
> index 2f8021d3eabc..6dad31026aa5 100644
> --- a/Platform/ARM/VExpressPkg/ArmVExpress-CTA15-A7.dsc
> +++ b/Platform/ARM/VExpressPkg/ArmVExpress-CTA15-A7.dsc
> @@ -146,8 +146,8 @@ [PcdsFixedAtBuild.common]
>    #
>    # PL180 MMC/SD card controller
>    #
> -  gArmPlatformTokenSpaceGuid.PcdPL180SysMciRegAddress|0x1C010048
> -  gArmPlatformTokenSpaceGuid.PcdPL180MciBaseAddress|0x1C050000
> +  gArmVExpressTokenSpaceGuid.PcdPL180SysMciRegAddress|0x1C010048
> +  gArmVExpressTokenSpaceGuid.PcdPL180MciBaseAddress|0x1C050000
>  
>  
>    #
> @@ -249,7 +249,7 @@ [Components.common]
>    # Multimedia Card Interface
>    #
>    EmbeddedPkg/Universal/MmcDxe/MmcDxe.inf
> -  ArmPlatformPkg/Drivers/PL180MciDxe/PL180MciDxe.inf
> +  Platform/ARM/VExpressPkg/Drivers/PL180MciDxe/PL180MciDxe.inf
>  
>    # SMSC LAN 9118
>    EmbeddedPkg/Drivers/Lan9118Dxe/Lan9118Dxe.inf
> diff --git a/Platform/ARM/VExpressPkg/ArmVExpress-CTA15-A7.fdf b/Platform/ARM/VExpressPkg/ArmVExpress-CTA15-A7.fdf
> index 082f80d9996d..64da1102ff07 100644
> --- a/Platform/ARM/VExpressPkg/ArmVExpress-CTA15-A7.fdf
> +++ b/Platform/ARM/VExpressPkg/ArmVExpress-CTA15-A7.fdf
> @@ -103,7 +103,7 @@ [FV.FvMain]
>    # Multimedia Card Interface
>    #
>    INF EmbeddedPkg/Universal/MmcDxe/MmcDxe.inf
> -  INF ArmPlatformPkg/Drivers/PL180MciDxe/PL180MciDxe.inf
> +  INF Platform/ARM/VExpressPkg/Drivers/PL180MciDxe/PL180MciDxe.inf
>  
>    #
>    # Filesystems
> diff --git a/Platform/ARM/VExpressPkg/ArmVExpress-FVP-AArch64.dsc b/Platform/ARM/VExpressPkg/ArmVExpress-FVP-AArch64.dsc
> index 63d79a488500..a6f536a33228 100644
> --- a/Platform/ARM/VExpressPkg/ArmVExpress-FVP-AArch64.dsc
> +++ b/Platform/ARM/VExpressPkg/ArmVExpress-FVP-AArch64.dsc
> @@ -151,8 +151,8 @@ [PcdsFixedAtBuild.common]
>  !endif
>  
>    ## PL180 MMC/SD card controller
> -  gArmPlatformTokenSpaceGuid.PcdPL180SysMciRegAddress|0x1C010048
> -  gArmPlatformTokenSpaceGuid.PcdPL180MciBaseAddress|0x1C050000
> +  gArmVExpressTokenSpaceGuid.PcdPL180SysMciRegAddress|0x1C010048
> +  gArmVExpressTokenSpaceGuid.PcdPL180MciBaseAddress|0x1C050000
>  
>    #
>    # ARM Generic Interrupt Controller
> @@ -290,7 +290,7 @@ [Components.common]
>    # Multimedia Card Interface
>    #
>    EmbeddedPkg/Universal/MmcDxe/MmcDxe.inf
> -  ArmPlatformPkg/Drivers/PL180MciDxe/PL180MciDxe.inf
> +  Platform/ARM/VExpressPkg/Drivers/PL180MciDxe/PL180MciDxe.inf
>  
>    #
>    # Platform Driver
> diff --git a/Platform/ARM/VExpressPkg/ArmVExpress-FVP-AArch64.fdf b/Platform/ARM/VExpressPkg/ArmVExpress-FVP-AArch64.fdf
> index 8f49ed3dba3c..f18ead75eaec 100644
> --- a/Platform/ARM/VExpressPkg/ArmVExpress-FVP-AArch64.fdf
> +++ b/Platform/ARM/VExpressPkg/ArmVExpress-FVP-AArch64.fdf
> @@ -139,7 +139,7 @@ [FV.FvMain]
>    # Multimedia Card Interface
>    #
>    INF EmbeddedPkg/Universal/MmcDxe/MmcDxe.inf
> -  INF ArmPlatformPkg/Drivers/PL180MciDxe/PL180MciDxe.inf
> +  INF Platform/ARM/VExpressPkg/Drivers/PL180MciDxe/PL180MciDxe.inf
>  
>    #
>    # SMBIOS Support
> diff --git a/Platform/ARM/VExpressPkg/ArmVExpressPkg.dec b/Platform/ARM/VExpressPkg/ArmVExpressPkg.dec
> index a659cda2e44a..a4e9bfd73eb6 100644
> --- a/Platform/ARM/VExpressPkg/ArmVExpressPkg.dec
> +++ b/Platform/ARM/VExpressPkg/ArmVExpressPkg.dec
> @@ -52,3 +52,7 @@ [PcdsFixedAtBuild.common]
>    #
>    gArmVExpressTokenSpaceGuid.PcdAndroidFastbootNvmDevicePath|""|VOID*|0x00000006
>    gArmVExpressTokenSpaceGuid.PcdAndroidFastbootProductName|""|VOID*|0x00000007
> +
> +  ## PL180 MCI
> +  gArmVExpressTokenSpaceGuid.PcdPL180SysMciRegAddress|0x00000000|UINT32|0x00000009
> +  gArmVExpressTokenSpaceGuid.PcdPL180MciBaseAddress|0x00000000|UINT32|0x0000000A
> diff --git a/Platform/ARM/VExpressPkg/Drivers/PL180MciDxe/PL180Mci.c b/Platform/ARM/VExpressPkg/Drivers/PL180MciDxe/PL180Mci.c
> new file mode 100644
> index 000000000000..0c9e4b7ce15e
> --- /dev/null
> +++ b/Platform/ARM/VExpressPkg/Drivers/PL180MciDxe/PL180Mci.c
> @@ -0,0 +1,563 @@
> +/** @file
> +  This file implement the MMC Host Protocol for the ARM PrimeCell PL180.
> +
> +  Copyright (c) 2011-2012, ARM Limited. All rights reserved.
> +
> +  SPDX-License-Identifier: BSD-2-Clause-Patent
> +
> +**/
> +
> +#include "PL180Mci.h"
> +
> +#include <Library/DevicePathLib.h>
> +#include <Library/BaseMemoryLib.h>
> +
> +EFI_MMC_HOST_PROTOCOL     *gpMmcHost;
> +
> +// Untested ...
> +//#define USE_STREAM
> +
> +#define MMCI0_BLOCKLEN 512
> +#define MMCI0_POW2_BLOCKLEN     9
> +#define MMCI0_TIMEOUT           1000
> +
> +#define SYS_MCI_CARDIN          BIT0
> +#define SYS_MCI_WPROT           BIT1
> +
> +BOOLEAN
> +MciIsPowerOn (
> +  VOID
> +  )
> +{
> +  return ((MmioRead32 (MCI_POWER_CONTROL_REG) & MCI_POWER_ON) == MCI_POWER_ON);
> +}
> +
> +EFI_STATUS
> +MciInitialize (
> +  VOID
> +  )
> +{
> +  MCI_TRACE ("MciInitialize()");
> +  return EFI_SUCCESS;
> +}
> +
> +BOOLEAN
> +MciIsCardPresent (
> +  IN EFI_MMC_HOST_PROTOCOL     *This
> +  )
> +{
> +  return (MmioRead32 (FixedPcdGet32 (PcdPL180SysMciRegAddress)) & SYS_MCI_CARDIN);
> +}
> +
> +BOOLEAN
> +MciIsReadOnly (
> +  IN EFI_MMC_HOST_PROTOCOL     *This
> +  )
> +{
> +  return (MmioRead32 (FixedPcdGet32 (PcdPL180SysMciRegAddress)) & SYS_MCI_WPROT);
> +}
> +
> +// Convert block size to 2^n
> +STATIC
> +UINT32
> +GetPow2BlockLen (
> +  IN UINT32 BlockLen
> +  )
> +{
> +  UINTN Loop;
> +  UINTN Pow2BlockLen;
> +
> +  Loop    = 0x8000;
> +  Pow2BlockLen = 15;
> +  do {
> +    Loop = (Loop >> 1) & 0xFFFF;
> +    Pow2BlockLen--;
> +  } while (Pow2BlockLen && (!(Loop & BlockLen)));
> +
> +  return Pow2BlockLen;
> +}
> +
> +VOID
> +MciPrepareDataPath (
> +  IN UINTN TransferDirection
> +  )
> +{
> +  // Set Data Length & Data Timer
> +  MmioWrite32 (MCI_DATA_TIMER_REG, 0xFFFFFFF);
> +  MmioWrite32 (MCI_DATA_LENGTH_REG, MMCI0_BLOCKLEN);
> +
> +#ifndef USE_STREAM
> +  //Note: we are using a hardcoded BlockLen (==512). If we decide to use a variable size, we could
> +  // compute the pow2 of BlockLen with the above function GetPow2BlockLen ()
> +  MmioWrite32 (MCI_DATA_CTL_REG, MCI_DATACTL_ENABLE | MCI_DATACTL_DMA_ENABLE | TransferDirection | (MMCI0_POW2_BLOCKLEN << 4));
> +#else
> +  MmioWrite32 (MCI_DATA_CTL_REG, MCI_DATACTL_ENABLE | MCI_DATACTL_DMA_ENABLE | TransferDirection | MCI_DATACTL_STREAM_TRANS);
> +#endif
> +}
> +
> +EFI_STATUS
> +MciSendCommand (
> +  IN EFI_MMC_HOST_PROTOCOL     *This,
> +  IN MMC_CMD                    MmcCmd,
> +  IN UINT32                     Argument
> +  )
> +{
> +  UINT32  Status;
> +  UINT32  Cmd;
> +  UINTN   RetVal;
> +  UINTN   CmdCtrlReg;
> +  UINT32  DoneMask;
> +
> +  RetVal = EFI_SUCCESS;
> +
> +  if ((MmcCmd == MMC_CMD17) || (MmcCmd == MMC_CMD11)) {
> +    MciPrepareDataPath (MCI_DATACTL_CARD_TO_CONT);
> +  } else if ((MmcCmd == MMC_CMD24) || (MmcCmd == MMC_CMD20)) {
> +    MciPrepareDataPath (MCI_DATACTL_CONT_TO_CARD);
> +  } else if (MmcCmd == MMC_CMD6) {
> +    MmioWrite32 (MCI_DATA_TIMER_REG, 0xFFFFFFF);
> +    MmioWrite32 (MCI_DATA_LENGTH_REG, 64);
> +#ifndef USE_STREAM
> +    MmioWrite32 (MCI_DATA_CTL_REG, MCI_DATACTL_ENABLE | MCI_DATACTL_CARD_TO_CONT | GetPow2BlockLen (64));
> +#else
> +    MmioWrite32 (MCI_DATA_CTL_REG, MCI_DATACTL_ENABLE | MCI_DATACTL_CARD_TO_CONT | MCI_DATACTL_STREAM_TRANS);
> +#endif
> +  } else if (MmcCmd == MMC_ACMD51) {
> +    MmioWrite32 (MCI_DATA_TIMER_REG, 0xFFFFFFF);
> +    /* SCR register is 8 bytes long. */
> +    MmioWrite32 (MCI_DATA_LENGTH_REG, 8);
> +#ifndef USE_STREAM
> +    MmioWrite32 (MCI_DATA_CTL_REG, MCI_DATACTL_ENABLE | MCI_DATACTL_CARD_TO_CONT | GetPow2BlockLen (8));
> +#else
> +    MmioWrite32 (MCI_DATA_CTL_REG, MCI_DATACTL_ENABLE | MCI_DATACTL_CARD_TO_CONT | MCI_DATACTL_STREAM_TRANS);
> +#endif
> +  }
> +
> +  // Create Command for PL180
> +  Cmd = (MMC_GET_INDX (MmcCmd) & INDX_MASK)  | MCI_CPSM_ENABLE;
> +  if (MmcCmd & MMC_CMD_WAIT_RESPONSE) {
> +    Cmd |= MCI_CPSM_WAIT_RESPONSE;
> +  }
> +
> +  if (MmcCmd & MMC_CMD_LONG_RESPONSE) {
> +    Cmd |= MCI_CPSM_LONG_RESPONSE;
> +  }
> +
> +  // Clear Status register static flags
> +  MmioWrite32 (MCI_CLEAR_STATUS_REG, MCI_CLR_ALL_STATUS);
> +
> +  // Write to command argument register
> +  MmioWrite32 (MCI_ARGUMENT_REG, Argument);
> +
> +  // Write to command register
> +  MmioWrite32 (MCI_COMMAND_REG, Cmd);
> +
> +  DoneMask  = (Cmd & MCI_CPSM_WAIT_RESPONSE)
> +                ? (MCI_STATUS_CMD_RESPEND | MCI_STATUS_CMD_ERROR)
> +                : (MCI_STATUS_CMD_SENT    | MCI_STATUS_CMD_ERROR);
> +  do {
> +    Status = MmioRead32 (MCI_STATUS_REG);
> +  } while (! (Status & DoneMask));
> +
> +  if ((Status & MCI_STATUS_CMD_ERROR)) {
> +    // Clear Status register error flags
> +    MmioWrite32 (MCI_CLEAR_STATUS_REG, MCI_STATUS_CMD_ERROR);
> +
> +    if ((Status & MCI_STATUS_CMD_START_BIT_ERROR)) {
> +      DEBUG ((EFI_D_ERROR, "MciSendCommand(CmdIndex:%d) Start bit Error! Response:0x%X Status:0x%x\n", (Cmd & 0x3F), MmioRead32 (MCI_RESPONSE0_REG), Status));
> +      RetVal = EFI_NO_RESPONSE;
> +    } else if ((Status & MCI_STATUS_CMD_CMDTIMEOUT)) {
> +      //DEBUG ((EFI_D_ERROR, "MciSendCommand(CmdIndex:%d) TIMEOUT! Response:0x%X Status:0x%x\n", (Cmd & 0x3F), MmioRead32 (MCI_RESPONSE0_REG), Status));
> +      RetVal = EFI_TIMEOUT;
> +    } else if ((!(MmcCmd & MMC_CMD_NO_CRC_RESPONSE)) && (Status & MCI_STATUS_CMD_CMDCRCFAIL)) {
> +      // The CMD1 and response type R3 do not contain CRC. We should ignore the CRC failed Status.
> +      RetVal = EFI_CRC_ERROR;
> +    }
> +  }
> +
> +  // Disable Command Path
> +  CmdCtrlReg = MmioRead32 (MCI_COMMAND_REG);
> +  MmioWrite32 (MCI_COMMAND_REG, (CmdCtrlReg & ~MCI_CPSM_ENABLE));
> +  return RetVal;
> +}
> +
> +EFI_STATUS
> +MciReceiveResponse (
> +  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 (MCI_RESPONSE3_REG);
> +  } else if (Type == MMC_RESPONSE_TYPE_R2) {
> +    Buffer[0] = MmioRead32 (MCI_RESPONSE0_REG);
> +    Buffer[1] = MmioRead32 (MCI_RESPONSE1_REG);
> +    Buffer[2] = MmioRead32 (MCI_RESPONSE2_REG);
> +    Buffer[3] = MmioRead32 (MCI_RESPONSE3_REG);
> +  }
> +
> +  return EFI_SUCCESS;
> +}
> +
> +EFI_STATUS
> +MciReadBlockData (
> +  IN EFI_MMC_HOST_PROTOCOL     *This,
> +  IN EFI_LBA                    Lba,
> +  IN UINTN                      Length,
> +  IN UINT32*                    Buffer
> +  )
> +{
> +  UINTN Loop;
> +  UINTN Finish;
> +  UINTN Status;
> +  EFI_STATUS RetVal;
> +  UINTN  DataCtrlReg;
> +  EFI_TPL Tpl;
> +
> +  RetVal = EFI_SUCCESS;
> +
> +  // Read data from the RX FIFO
> +  Loop   = 0;
> +  if (Length < MMCI0_BLOCKLEN) {
> +    Finish = Length / 4;
> +  } else {
> +    Finish = MMCI0_BLOCKLEN / 4;
> +  }
> +
> +  // Raise the TPL at the highest level to disable Interrupts.
> +  Tpl = gBS->RaiseTPL (TPL_HIGH_LEVEL);
> +
> +  do {
> +    // Read the Status flags
> +    Status = MmioRead32 (MCI_STATUS_REG);
> +
> +    // Do eight reads if possible else a single read
> +    if (Status & MCI_STATUS_CMD_RXFIFOHALFFULL) {
> +      Buffer[Loop] = MmioRead32(MCI_FIFO_REG);
> +      Loop++;
> +      Buffer[Loop] = MmioRead32(MCI_FIFO_REG);
> +      Loop++;
> +      Buffer[Loop] = MmioRead32(MCI_FIFO_REG);
> +      Loop++;
> +      Buffer[Loop] = MmioRead32(MCI_FIFO_REG);
> +      Loop++;
> +      Buffer[Loop] = MmioRead32(MCI_FIFO_REG);
> +      Loop++;
> +      Buffer[Loop] = MmioRead32(MCI_FIFO_REG);
> +      Loop++;
> +      Buffer[Loop] = MmioRead32(MCI_FIFO_REG);
> +      Loop++;
> +      Buffer[Loop] = MmioRead32(MCI_FIFO_REG);
> +      Loop++;
> +    } else if (Status & MCI_STATUS_CMD_RXDATAAVAILBL) {
> +      Buffer[Loop] = MmioRead32(MCI_FIFO_REG);
> +      Loop++;
> +    } else {
> +      //Check for error conditions and timeouts
> +      if (Status & MCI_STATUS_CMD_DATATIMEOUT) {
> +        DEBUG ((EFI_D_ERROR, "MciReadBlockData(): TIMEOUT! Response:0x%X Status:0x%x\n", MmioRead32 (MCI_RESPONSE0_REG), Status));
> +        RetVal = EFI_TIMEOUT;
> +        break;
> +      } else if (Status & MCI_STATUS_CMD_DATACRCFAIL) {
> +        DEBUG ((EFI_D_ERROR, "MciReadBlockData(): CRC Error! Response:0x%X Status:0x%x\n", MmioRead32 (MCI_RESPONSE0_REG), Status));
> +        RetVal = EFI_CRC_ERROR;
> +        break;
> +      } else if (Status & MCI_STATUS_CMD_START_BIT_ERROR) {
> +        DEBUG ((EFI_D_ERROR, "MciReadBlockData(): Start-bit Error! Response:0x%X Status:0x%x\n", MmioRead32 (MCI_RESPONSE0_REG), Status));
> +        RetVal = EFI_NO_RESPONSE;
> +        break;
> +      }
> +    }
> +    //clear RX over run flag
> +    if(Status & MCI_STATUS_CMD_RXOVERRUN) {
> +      MmioWrite32(MCI_CLEAR_STATUS_REG, MCI_STATUS_CMD_RXOVERRUN);
> +    }
> +  } while ((Loop < Finish));
> +
> +  // Restore Tpl
> +  gBS->RestoreTPL (Tpl);
> +
> +  // Clear Status flags
> +  MmioWrite32 (MCI_CLEAR_STATUS_REG, MCI_CLR_ALL_STATUS);
> +
> +  //Disable Data path
> +  DataCtrlReg = MmioRead32 (MCI_DATA_CTL_REG);
> +  MmioWrite32 (MCI_DATA_CTL_REG, (DataCtrlReg & MCI_DATACTL_DISABLE_MASK));
> +
> +  return RetVal;
> +}
> +
> +EFI_STATUS
> +MciWriteBlockData (
> +  IN EFI_MMC_HOST_PROTOCOL     *This,
> +  IN EFI_LBA                   Lba,
> +  IN UINTN                     Length,
> +  IN UINT32*                   Buffer
> +  )
> +{
> +  UINTN Loop;
> +  UINTN Finish;
> +  UINTN Timer;
> +  UINTN Status;
> +  EFI_STATUS RetVal;
> +  UINTN  DataCtrlReg;
> +  EFI_TPL Tpl;
> +
> +  RetVal = EFI_SUCCESS;
> +
> +  // Write the data to the TX FIFO
> +  Loop   = 0;
> +  Finish = MMCI0_BLOCKLEN / 4;
> +  Timer  = MMCI0_TIMEOUT * 100;
> +
> +  // Raise the TPL at the highest level to disable Interrupts.
> +  Tpl = gBS->RaiseTPL (TPL_HIGH_LEVEL);
> +
> +  do {
> +    // Read the Status flags
> +    Status = MmioRead32 (MCI_STATUS_REG);
> +
> +    // Do eight writes if possible else a single write
> +    if (Status & MCI_STATUS_CMD_TXFIFOHALFEMPTY) {
> +      MmioWrite32(MCI_FIFO_REG, Buffer[Loop]);
> +      Loop++;
> +      MmioWrite32(MCI_FIFO_REG, Buffer[Loop]);
> +      Loop++;
> +      MmioWrite32(MCI_FIFO_REG, Buffer[Loop]);
> +      Loop++;
> +      MmioWrite32(MCI_FIFO_REG, Buffer[Loop]);
> +      Loop++;
> +      MmioWrite32(MCI_FIFO_REG, Buffer[Loop]);
> +      Loop++;
> +      MmioWrite32(MCI_FIFO_REG, Buffer[Loop]);
> +      Loop++;
> +      MmioWrite32(MCI_FIFO_REG, Buffer[Loop]);
> +      Loop++;
> +      MmioWrite32(MCI_FIFO_REG, Buffer[Loop]);
> +      Loop++;
> +    } else if (!(Status & MCI_STATUS_CMD_TXFIFOFULL)) {
> +        MmioWrite32(MCI_FIFO_REG, Buffer[Loop]);
> +        Loop++;
> +    } else {
> +      // Check for error conditions and timeouts
> +      if (Status & MCI_STATUS_CMD_DATATIMEOUT) {
> +        DEBUG ((EFI_D_ERROR, "MciWriteBlockData(): TIMEOUT! Response:0x%X Status:0x%x\n", MmioRead32 (MCI_RESPONSE0_REG), Status));
> +        RetVal = EFI_TIMEOUT;
> +        goto Exit;
> +      } else if (Status & MCI_STATUS_CMD_DATACRCFAIL) {
> +        DEBUG ((EFI_D_ERROR, "MciWriteBlockData(): CRC Error! Response:0x%X Status:0x%x\n", MmioRead32 (MCI_RESPONSE0_REG), Status));
> +        RetVal = EFI_CRC_ERROR;
> +        goto Exit;
> +      } else if (Status & MCI_STATUS_CMD_TX_UNDERRUN) {
> +        DEBUG ((EFI_D_ERROR, "MciWriteBlockData(): TX buffer Underrun! Response:0x%X Status:0x%x, Number of bytes written 0x%x\n",MmioRead32(MCI_RESPONSE0_REG),Status, Loop));
> +        RetVal = EFI_BUFFER_TOO_SMALL;
> +        ASSERT(0);
> +        goto Exit;
> +      }
> +    }
> +  } while (Loop < Finish);
> +
> +  // Restore Tpl
> +  gBS->RestoreTPL (Tpl);
> +
> +  // Wait for FIFO to drain
> +  Timer  = MMCI0_TIMEOUT * 60;
> +  Status = MmioRead32 (MCI_STATUS_REG);
> +#ifndef USE_STREAM
> +  // Single block
> +  while (((Status & MCI_STATUS_TXDONE) != MCI_STATUS_TXDONE) && Timer) {
> +#else
> +  // Stream
> +  while (((Status & MCI_STATUS_CMD_DATAEND) != MCI_STATUS_CMD_DATAEND) && Timer) {
> +#endif
> +    NanoSecondDelay(10);
> +    Status = MmioRead32 (MCI_STATUS_REG);
> +    Timer--;
> +  }
> +
> +  // Clear Status flags
> +  MmioWrite32 (MCI_CLEAR_STATUS_REG, MCI_CLR_ALL_STATUS);
> +
> +  if (Timer == 0) {
> +    DEBUG ((EFI_D_ERROR, "MciWriteBlockData(): Data End timeout Number of words written 0x%x\n", Loop));
> +    RetVal = EFI_TIMEOUT;
> +  }
> +
> +Exit:
> +  // Disable Data path
> +  DataCtrlReg = MmioRead32 (MCI_DATA_CTL_REG);
> +  MmioWrite32 (MCI_DATA_CTL_REG, (DataCtrlReg & MCI_DATACTL_DISABLE_MASK));
> +  return RetVal;
> +}
> +
> +EFI_STATUS
> +MciNotifyState (
> +  IN  EFI_MMC_HOST_PROTOCOL     *This,
> +  IN MMC_STATE                  State
> +  )
> +{
> +  UINT32      Data32;
> +
> +  switch (State) {
> +  case MmcInvalidState:
> +    ASSERT (0);
> +    break;
> +  case MmcHwInitializationState:
> +    // If device already turn on then restart it
> +    Data32 = MmioRead32 (MCI_POWER_CONTROL_REG);
> +    if ((Data32 & 0x2) == MCI_POWER_UP) {
> +      MCI_TRACE ("MciNotifyState(MmcHwInitializationState): TurnOff MCI");
> +
> +      // Turn off
> +      MmioWrite32 (MCI_CLOCK_CONTROL_REG, 0);
> +      MmioWrite32 (MCI_POWER_CONTROL_REG, 0);
> +      MicroSecondDelay (100);
> +    }
> +
> +    MCI_TRACE ("MciNotifyState(MmcHwInitializationState): TurnOn MCI");
> +    // Setup clock
> +    //  - 0x1D = 29 => should be the clock divider to be less than 400kHz at MCLK = 24Mhz
> +    MmioWrite32 (MCI_CLOCK_CONTROL_REG, 0x1D | MCI_CLOCK_ENABLE | MCI_CLOCK_POWERSAVE);
> +
> +    // Set the voltage
> +    MmioWrite32 (MCI_POWER_CONTROL_REG, MCI_POWER_OPENDRAIN | (15<<2));
> +    MmioWrite32 (MCI_POWER_CONTROL_REG, MCI_POWER_ROD | MCI_POWER_OPENDRAIN | (15<<2) | MCI_POWER_UP);
> +    MicroSecondDelay (10);
> +    MmioWrite32 (MCI_POWER_CONTROL_REG, MCI_POWER_ROD | MCI_POWER_OPENDRAIN | (15<<2) | MCI_POWER_ON);
> +    MicroSecondDelay (100);
> +
> +    // Set Data Length & Data Timer
> +    MmioWrite32 (MCI_DATA_TIMER_REG, 0xFFFFF);
> +    MmioWrite32 (MCI_DATA_LENGTH_REG, 8);
> +
> +    ASSERT ((MmioRead32 (MCI_POWER_CONTROL_REG) & 0x3) == MCI_POWER_ON);
> +    break;
> +  case MmcIdleState:
> +    MCI_TRACE ("MciNotifyState(MmcIdleState)");
> +    break;
> +  case MmcReadyState:
> +    MCI_TRACE ("MciNotifyState(MmcReadyState)");
> +    break;
> +  case MmcIdentificationState:
> +    MCI_TRACE ("MciNotifyState (MmcIdentificationState)");
> +    break;
> +  case MmcStandByState:{
> +    volatile UINT32 PwrCtrlReg;
> +    MCI_TRACE ("MciNotifyState (MmcStandByState)");
> +
> +    // Enable MCICMD push-pull drive
> +    PwrCtrlReg = MmioRead32 (MCI_POWER_CONTROL_REG);
> +    //Disable Open Drain output
> +    PwrCtrlReg &= ~ (MCI_POWER_OPENDRAIN);
> +    MmioWrite32 (MCI_POWER_CONTROL_REG, PwrCtrlReg);
> +
> +    // Set MMCI0 clock to 4MHz (24MHz may be possible with cache enabled)
> +    //
> +    // Note: Increasing clock speed causes TX FIFO under-run errors.
> +    //       So careful when optimising this driver for higher performance.
> +    //
> +    MmioWrite32(MCI_CLOCK_CONTROL_REG,0x02 | MCI_CLOCK_ENABLE | MCI_CLOCK_POWERSAVE);
> +    // Set MMCI0 clock to 24MHz (by bypassing the divider)
> +    //MmioWrite32(MCI_CLOCK_CONTROL_REG,MCI_CLOCK_BYPASS | MCI_CLOCK_ENABLE);
> +    break;
> +  }
> +  case MmcTransferState:
> +    //MCI_TRACE ("MciNotifyState(MmcTransferState)");
> +    break;
> +  case MmcSendingDataState:
> +    MCI_TRACE ("MciNotifyState(MmcSendingDataState)");
> +    break;
> +  case MmcReceiveDataState:
> +    MCI_TRACE ("MciNotifyState(MmcReceiveDataState)");
> +    break;
> +  case MmcProgrammingState:
> +    MCI_TRACE ("MciNotifyState(MmcProgrammingState)");
> +    break;
> +  case MmcDisconnectState:
> +    MCI_TRACE ("MciNotifyState(MmcDisconnectState)");
> +    break;
> +  default:
> +    ASSERT (0);
> +  }
> +  return EFI_SUCCESS;
> +}
> +
> +EFI_GUID mPL180MciDevicePathGuid = EFI_CALLER_ID_GUID;
> +
> +EFI_STATUS
> +MciBuildDevicePath (
> +  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, &mPL180MciDevicePathGuid);
> +
> +  *DevicePath = NewDevicePathNode;
> +  return EFI_SUCCESS;
> +}
> +
> +EFI_MMC_HOST_PROTOCOL gMciHost = {
> +  MMC_HOST_PROTOCOL_REVISION,
> +  MciIsCardPresent,
> +  MciIsReadOnly,
> +  MciBuildDevicePath,
> +  MciNotifyState,
> +  MciSendCommand,
> +  MciReceiveResponse,
> +  MciReadBlockData,
> +  MciWriteBlockData
> +};
> +
> +EFI_STATUS
> +PL180MciDxeInitialize (
> +  IN EFI_HANDLE         ImageHandle,
> +  IN EFI_SYSTEM_TABLE   *SystemTable
> +  )
> +{
> +  EFI_STATUS    Status;
> +  EFI_HANDLE    Handle;
> +
> +  DEBUG ((EFI_D_WARN, "Probing ID registers at 0x%lx for a PL180\n",
> +    MCI_PERIPH_ID_REG0));
> +
> +  // Check if this is a PL180
> +  if (MmioRead8 (MCI_PERIPH_ID_REG0) != MCI_PERIPH_ID0 ||
> +      MmioRead8 (MCI_PERIPH_ID_REG1) != MCI_PERIPH_ID1 ||
> +      MmioRead8 (MCI_PERIPH_ID_REG2) != MCI_PERIPH_ID2 ||
> +      MmioRead8 (MCI_PCELL_ID_REG0)  != MCI_PCELL_ID0  ||
> +      MmioRead8 (MCI_PCELL_ID_REG1)  != MCI_PCELL_ID1  ||
> +      MmioRead8 (MCI_PCELL_ID_REG2)  != MCI_PCELL_ID2  ||
> +      MmioRead8 (MCI_PCELL_ID_REG3)  != MCI_PCELL_ID3) {
> +
> +    DEBUG ((EFI_D_WARN, "Probing ID registers at 0x%lx for a PL180"
> +      " failed\n", MCI_PERIPH_ID_REG0));
> +    return EFI_NOT_FOUND;
> +  }
> +
> +  Handle = NULL;
> +
> +  MCI_TRACE ("PL180MciDxeInitialize()");
> +
> +  //Publish Component Name, BlockIO protocol interfaces
> +  Status = gBS->InstallMultipleProtocolInterfaces (
> +                  &Handle,
> +                  &gEdkiiMmcHostProtocolGuid,       &gMciHost,
> +                  NULL
> +                  );
> +  ASSERT_EFI_ERROR (Status);
> +
> +  return EFI_SUCCESS;
> +}
> diff --git a/Platform/ARM/VExpressPkg/Drivers/PL180MciDxe/PL180Mci.h b/Platform/ARM/VExpressPkg/Drivers/PL180MciDxe/PL180Mci.h
> new file mode 100644
> index 000000000000..a5bfe0b2eceb
> --- /dev/null
> +++ b/Platform/ARM/VExpressPkg/Drivers/PL180MciDxe/PL180Mci.h
> @@ -0,0 +1,162 @@
> +/** @file
> +  Header for the MMC Host Protocol implementation for the ARM PrimeCell PL180.
> +
> +  Copyright (c) 2011-2012, ARM Limited. All rights reserved.
> +
> +  SPDX-License-Identifier: BSD-2-Clause-Patent
> +
> +**/
> +
> +#ifndef __PL180_MCI_H
> +#define __PL180_MCI_H
> +
> +#include <Uefi.h>
> +
> +#include <Protocol/MmcHost.h>
> +
> +#include <Library/UefiLib.h>
> +#include <Library/DebugLib.h>
> +#include <Library/UefiBootServicesTableLib.h>
> +#include <Library/IoLib.h>
> +#include <Library/TimerLib.h>
> +#include <Library/PcdLib.h>
> +
> +#define PL180_MCI_DXE_VERSION           0x10
> +
> +#define MCI_SYSCTL  FixedPcdGet32 (PcdPL180MciBaseAddress)
> +
> +#define MCI_POWER_CONTROL_REG           (MCI_SYSCTL + 0x000)
> +#define MCI_CLOCK_CONTROL_REG           (MCI_SYSCTL + 0x004)
> +#define MCI_ARGUMENT_REG                (MCI_SYSCTL + 0x008)
> +#define MCI_COMMAND_REG                 (MCI_SYSCTL + 0x00C)
> +#define MCI_RESPCMD_REG                 (MCI_SYSCTL + 0x010)
> +#define MCI_RESPONSE3_REG               (MCI_SYSCTL + 0x014)
> +#define MCI_RESPONSE2_REG               (MCI_SYSCTL + 0x018)
> +#define MCI_RESPONSE1_REG               (MCI_SYSCTL + 0x01C)
> +#define MCI_RESPONSE0_REG               (MCI_SYSCTL + 0x020)
> +#define MCI_DATA_TIMER_REG              (MCI_SYSCTL + 0x024)
> +#define MCI_DATA_LENGTH_REG             (MCI_SYSCTL + 0x028)
> +#define MCI_DATA_CTL_REG                (MCI_SYSCTL + 0x02C)
> +#define MCI_DATA_COUNTER                (MCI_SYSCTL + 0x030)
> +#define MCI_STATUS_REG                  (MCI_SYSCTL + 0x034)
> +#define MCI_CLEAR_STATUS_REG            (MCI_SYSCTL + 0x038)
> +#define MCI_INT0_MASK_REG               (MCI_SYSCTL + 0x03C)
> +#define MCI_INT1_MASK_REG               (MCI_SYSCTL + 0x040)
> +#define MCI_SELECT_REG                  (MCI_SYSCTL + 0x044)
> +#define MCI_FIFOCOUNT_REG               (MCI_SYSCTL + 0x048)
> +#define MCI_FIFO_REG                    (MCI_SYSCTL + 0x080)
> +#define MCI_PERIPH_ID_REG0              (MCI_SYSCTL + 0xFE0)
> +#define MCI_PERIPH_ID_REG1              (MCI_SYSCTL + 0xFE4)
> +#define MCI_PERIPH_ID_REG2              (MCI_SYSCTL + 0xFE8)
> +#define MCI_PERIPH_ID_REG3              (MCI_SYSCTL + 0xFEC)
> +#define MCI_PCELL_ID_REG0               (MCI_SYSCTL + 0xFF0)
> +#define MCI_PCELL_ID_REG1               (MCI_SYSCTL + 0xFF4)
> +#define MCI_PCELL_ID_REG2               (MCI_SYSCTL + 0xFF8)
> +#define MCI_PCELL_ID_REG3               (MCI_SYSCTL + 0xFFC)
> +
> +#define MCI_PERIPH_ID0                  0x80
> +#define MCI_PERIPH_ID1                  0x11
> +#define MCI_PERIPH_ID2                  0x04
> +#define MCI_PERIPH_ID3                  0x00
> +#define MCI_PCELL_ID0                   0x0D
> +#define MCI_PCELL_ID1                   0xF0
> +#define MCI_PCELL_ID2                   0x05
> +#define MCI_PCELL_ID3                   0xB1
> +
> +#define MCI_POWER_OFF                   0
> +#define MCI_POWER_UP                    BIT1
> +#define MCI_POWER_ON                    (BIT1 | BIT0)
> +#define MCI_POWER_OPENDRAIN             BIT6
> +#define MCI_POWER_ROD                   BIT7
> +
> +#define MCI_CLOCK_ENABLE                BIT8
> +#define MCI_CLOCK_POWERSAVE             BIT9
> +#define MCI_CLOCK_BYPASS                BIT10
> +#define MCI_CLOCK_WIDEBUS               BIT11
> +
> +#define MCI_STATUS_CMD_CMDCRCFAIL       BIT0
> +#define MCI_STATUS_CMD_DATACRCFAIL      BIT1
> +#define MCI_STATUS_CMD_CMDTIMEOUT       BIT2
> +#define MCI_STATUS_CMD_DATATIMEOUT      BIT3
> +#define MCI_STATUS_CMD_TX_UNDERRUN      BIT4
> +#define MCI_STATUS_CMD_RXOVERRUN        BIT5
> +#define MCI_STATUS_CMD_RESPEND          BIT6
> +#define MCI_STATUS_CMD_SENT             BIT7
> +#define MCI_STATUS_CMD_DATAEND          BIT8
> +#define MCI_STATUS_CMD_START_BIT_ERROR  BIT9
> +#define MCI_STATUS_CMD_DATABLOCKEND     BIT10
> +#define MCI_STATUS_CMD_ACTIVE           BIT11
> +#define MCI_STATUS_CMD_TXACTIVE         BIT12
> +#define MCI_STATUS_CMD_RXACTIVE         BIT13
> +#define MCI_STATUS_CMD_TXFIFOHALFEMPTY  BIT14
> +#define MCI_STATUS_CMD_RXFIFOHALFFULL   BIT15
> +#define MCI_STATUS_CMD_TXFIFOFULL       BIT16
> +#define MCI_STATUS_CMD_RXFIFOFULL       BIT17
> +#define MCI_STATUS_CMD_TXFIFOEMPTY      BIT18
> +#define MCI_STATUS_CMD_RXFIFOEMPTY      BIT19
> +#define MCI_STATUS_CMD_TXDATAAVAILBL    BIT20
> +#define MCI_STATUS_CMD_RXDATAAVAILBL    BIT21
> +
> +#define MCI_STATUS_TXDONE               (MCI_STATUS_CMD_DATAEND | MCI_STATUS_CMD_DATABLOCKEND)
> +#define MCI_STATUS_RXDONE               (MCI_STATUS_CMD_DATAEND | MCI_STATUS_CMD_DATABLOCKEND)
> +#define MCI_STATUS_READ_ERROR           (  MCI_STATUS_CMD_DATACRCFAIL     \
> +                                         | MCI_STATUS_CMD_DATATIMEOUT     \
> +                                         | MCI_STATUS_CMD_RXOVERRUN       \
> +                                         | MCI_STATUS_CMD_START_BIT_ERROR )
> +#define MCI_STATUS_WRITE_ERROR          (  MCI_STATUS_CMD_DATACRCFAIL \
> +                                         | MCI_STATUS_CMD_DATATIMEOUT \
> +                                         | MCI_STATUS_CMD_TX_UNDERRUN )
> +#define MCI_STATUS_CMD_ERROR            (  MCI_STATUS_CMD_CMDCRCFAIL      \
> +                                         | MCI_STATUS_CMD_CMDTIMEOUT      \
> +                                         | MCI_STATUS_CMD_START_BIT_ERROR )
> +
> +#define MCI_CLR_CMD_STATUS              (  MCI_STATUS_CMD_RESPEND \
> +                                         | MCI_STATUS_CMD_SENT    \
> +                                         | MCI_STATUS_CMD_ERROR )
> +
> +#define MCI_CLR_READ_STATUS             (  MCI_STATUS_RXDONE     \
> +                                         | MCI_STATUS_READ_ERROR )
> +
> +#define MCI_CLR_WRITE_STATUS            (  MCI_STATUS_TXDONE      \
> +                                         | MCI_STATUS_WRITE_ERROR )
> +
> +#define MCI_CLR_ALL_STATUS              (BIT11 - 1)
> +
> +#define MCI_DATACTL_DISABLE_MASK        0xFE
> +#define MCI_DATACTL_ENABLE              BIT0
> +#define MCI_DATACTL_CONT_TO_CARD        0
> +#define MCI_DATACTL_CARD_TO_CONT        BIT1
> +#define MCI_DATACTL_BLOCK_TRANS         0
> +#define MCI_DATACTL_STREAM_TRANS        BIT2
> +#define MCI_DATACTL_DMA_DISABLED        0
> +#define MCI_DATACTL_DMA_ENABLE          BIT3
> +
> +#define INDX_MASK                       0x3F
> +
> +#define MCI_CPSM_WAIT_RESPONSE          BIT6
> +#define MCI_CPSM_LONG_RESPONSE          BIT7
> +#define MCI_CPSM_LONG_INTERRUPT         BIT8
> +#define MCI_CPSM_LONG_PENDING           BIT9
> +#define MCI_CPSM_ENABLE                 BIT10
> +
> +#define MCI_TRACE(txt)                  DEBUG ((EFI_D_BLKIO, "ARM_MCI: " txt "\n"))
> +
> +EFI_STATUS
> +EFIAPI
> +MciGetDriverName (
> +  IN  EFI_COMPONENT_NAME_PROTOCOL  *This,
> +  IN  CHAR8                        *Language,
> +  OUT CHAR16                       **DriverName
> +  );
> +
> +EFI_STATUS
> +EFIAPI
> +MciGetControllerName (
> +  IN  EFI_COMPONENT_NAME_PROTOCOL                     *This,
> +  IN  EFI_HANDLE                                      ControllerHandle,
> +  IN  EFI_HANDLE                                      ChildHandle        OPTIONAL,
> +  IN  CHAR8                                           *Language,
> +  OUT CHAR16                                          **ControllerName
> +  );
> +
> +#endif
> diff --git a/Platform/ARM/VExpressPkg/Drivers/PL180MciDxe/PL180MciDxe.inf b/Platform/ARM/VExpressPkg/Drivers/PL180MciDxe/PL180MciDxe.inf
> new file mode 100644
> index 000000000000..3519de857ebb
> --- /dev/null
> +++ b/Platform/ARM/VExpressPkg/Drivers/PL180MciDxe/PL180MciDxe.inf
> @@ -0,0 +1,46 @@
> +#/** @file
> +#  INF file for the MMC Host Protocol implementation for the ARM PrimeCell PL180.
> +#
> +#  Copyright (c) 2011, ARM Limited. All rights reserved.
> +#
> +#  SPDX-License-Identifier: BSD-2-Clause-Patent
> +#
> +#**/
> +
> +[Defines]
> +  INF_VERSION                    = 0x00010005
> +  BASE_NAME                      = PL180MciDxe
> +  FILE_GUID                      = 09831032-6fa3-4484-af4f-0a000a8d3a82
> +  MODULE_TYPE                    = DXE_DRIVER
> +  VERSION_STRING                 = 1.0
> +
> +  ENTRY_POINT                    = PL180MciDxeInitialize
> +
> +[Sources.common]
> +  PL180Mci.c
> +
> +[Packages]
> +  EmbeddedPkg/EmbeddedPkg.dec
> +  MdePkg/MdePkg.dec
> +  Platform/ARM/VExpressPkg/ArmVExpressPkg.dec
> +
> +[LibraryClasses]
> +  BaseLib
> +  UefiLib
> +  UefiDriverEntryPoint
> +  BaseMemoryLib
> +  ArmLib
> +  IoLib
> +  TimerLib
> +
> +[Protocols]
> +  gEfiCpuArchProtocolGuid
> +  gEfiDevicePathProtocolGuid
> +  gEdkiiMmcHostProtocolGuid
> +
> +[Pcd]
> +  gArmVExpressTokenSpaceGuid.PcdPL180SysMciRegAddress
> +  gArmVExpressTokenSpaceGuid.PcdPL180MciBaseAddress
> +
> +[Depex]
> +  gEfiCpuArchProtocolGuid
> -- 
> 2.17.1
> 

  reply	other threads:[~2020-04-29 21:51 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-04-29 18:47 [PATCH edk2-platforms 1/2] Silicon/TexasInstruments/Omap35xxPkg: switch to new MMC host protocol name Ard Biesheuvel
2020-04-29 18:47 ` [PATCH edk2-platforms 2/2] Platform/ARM/VExpressPkg: incorporate PL180 driver Ard Biesheuvel
2020-04-29 21:51   ` Leif Lindholm [this message]
2020-04-29 21:37 ` [PATCH edk2-platforms 1/2] Silicon/TexasInstruments/Omap35xxPkg: switch to new MMC host protocol name Leif Lindholm
2020-04-30  8:13   ` 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=20200429215101.GO21486@vanye \
    --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