MMC core:

- Add a power-off-delay-us DT property to support HW specific delays
  - Add mmc_send_tuning_timeout() to allow hosts to specify a timeout
  - Enable hosts to control the re-programming of the crypto keys
 
 MMC host:
  - dw_mmc: Add a common watchdog and convert the various timeouts to use it
  - dw_mmc: Expose the watchdog state in debugfs
  - dw_mmc-rockchip: Use a tighter per command tuning timeout
  - greybus-sdio: Convert to devm_mmc_alloc_host()
  - meson-gx: Enable the bus pipeline clock on T7
  - rtsx_usb_sdmmc: Start card power-up at 3.3V
  - sdhci-cadence: Add Altera Agilex5 SD6HC support
  - sdhci-cadence: Add Cadence SD6HC support
  - sdhci-msm: Add support for the MSM8952 and Maili variants
  - sdhci-msm: Use pm ops to better manage restoring of crypto keys
  - sdhci-of-dwcmshc: Disable CQE error halt request for Rockchip
  - sdhci-pxav3: Disable clock inversion for SD HS cards
  - sunplus: Fixup initialization for DDR modes
  - sunxi: Add support for Allwinner B288
  - vub300: Remove the driver for the obsolete HW
 -----BEGIN PGP SIGNATURE-----
 
 iQJEBAABCgAuFiEEugLDXPmKSktSkQsV/iaEJXNYjCkFAmrV/scQHHVsZmhAa2Vy
 bmVsLm9yZwAKCRD+JoQlc1iMKag2EAC5pslywPB4JC7qWsN2axZK6Gg9p0YrMUZf
 RnRVNhMWaWpqDfaSE0MoxWXEys/SMdfCbeBJVVMvyQ/ouvzbPWxO338xy56286Ug
 lPQ5HTFvHbhOTl80ou0gejVzbihnIDNjAAW27COuOtZ3nR5NQDLeee390y8dUAsI
 2MYPOORGCy2qt9lx1b+6AaKXw9M0tSpWj3ojrkWbGmxhjhfllKd4i3fjCW1lLsNJ
 o1QVc4lP5uR+qGY0xis/bGqqcb5ugHkVddL3icgE1rpoPa7mePgHh4PQJHuTQtWX
 ilcfSZojgsrBTSVptBEUZa5Mf/rqmNd/9o7Bbjq61fdJHRB/+MPTqRKhSOy03OPq
 papbIi3NSVppy4SRy1XOVkukxZxVIo+VmK0h3i13SHhhzbIAWD1ko4CAGsKrR9Zm
 ZeMAf848/RdCyGispB7kF+XShmYMfZO59NFQOcZJxZh2bFXhX7O1kmflDbAM/KjV
 19mC9rQYhQsGSF04jls0P0dhyhEn/vrOXsV8zTDNkkrIHmAD6Zk7iny1wnsyLyOc
 jAqkJbRD4atp1nB9AF1VfDvTLmoiRYsRkkx0V85S+shr6+Ijb/m9mvL4APRAaqUA
 7xsGb+UMbJFU4NGWVYpRwNQw55c4hYT4G8m3XnNHm0XlX5HJ1mPWJPfQOGSwl8Ar
 C6fst1R3hQ==
 =j2Nx
 -----END PGP SIGNATURE-----

Merge tag 'mmc-v7.4' of git://git.kernel.org/pub/scm/linux/kernel/git/ulfh/mmc

Pull MMC updates from Ulf Hansson:
 "MMC core:
   - Add a power-off-delay-us DT property to support HW specific delays
   - Add mmc_send_tuning_timeout() to allow hosts to specify a timeout
   - Enable hosts to control the re-programming of the crypto keys

  MMC host:
   - dw_mmc: Add a common watchdog and convert the various timeouts to use it
   - dw_mmc: Expose the watchdog state in debugfs
   - dw_mmc-rockchip: Use a tighter per command tuning timeout
   - greybus-sdio: Convert to devm_mmc_alloc_host()
   - meson-gx: Enable the bus pipeline clock on T7
   - rtsx_usb_sdmmc: Start card power-up at 3.3V
   - sdhci-cadence: Add Altera Agilex5 SD6HC support
   - sdhci-cadence: Add Cadence SD6HC support
   - sdhci-msm: Add support for the MSM8952 and Maili variants
   - sdhci-msm: Use pm ops to better manage restoring of crypto keys
   - sdhci-of-dwcmshc: Disable CQE error halt request for Rockchip
   - sdhci-pxav3: Disable clock inversion for SD HS cards
   - sunplus: Fixup initialization for DDR modes
   - sunxi: Add support for Allwinner B288
   - vub300: Remove the driver for the obsolete HW"

* tag 'mmc-v7.4' of git://git.kernel.org/pub/scm/linux/kernel/git/ulfh/mmc: (43 commits)
  dt-bindings: mmc: sun4i-a10-mmc: add Allwinner B288
  mmc: dw_mmc-rockchip: use a tighter per-command tuning timeout
  mmc: core: add mmc_send_tuning_timeout()
  mmc: sdhci-of-dwcmshc: Disable CQE error halt request for Rockchip
  dt-bindings: mmc: sdhci-msm: Add MSM8952 compatible
  mmc: use assign_bit() where applicable
  mmc: sdhci-pci: Replace deprecated PCI functions
  mmc: sunplus: initialise rd_crc_dly=1 for DDR modes
  mmc: core: fix stale mmc_start_req() references in comments
  docs: mmc: fix stale async request documentation
  docs: mmc: document eMMC health status support in mmc-tools
  mmc: cavium-thunderx: Drop redundant calls to get|put_device()
  mmc: core: Turn mmc_alloc|free_host() into static functions
  staging: greybus: sdio: Convert to devm_mmc_alloc_host()
  mmc: vub300: Remove the driver for the obsolete HW
  mmc: dw_mmc: remove unused slot member
  mmc: sdhci-pxav3: avoid of_node
  CREDITS: Add Pierre Ossman
  mmc: fix typos in comments
  mmc: meson-gx: enable the bus pipeline clock on T7
  ...
This commit is contained in:
Linus Torvalds 2026-10-22 07:29:07 -10:00
commit 03480653dd
47 changed files with 1825 additions and 2918 deletions

View File

@ -3113,6 +3113,11 @@ N: Peter Oruba
D: AMD Microcode loader driver
S: Germany
N: Pierre Ossman
D: MMC maintainer 2006-2009
D: First SDIO implementation
S: Sweden
N: Jens Osterkamp
E: jens@de.ibm.com
D: Maintainer of Spidernet network driver for Cell

View File

@ -32,10 +32,12 @@ properties:
- items:
- enum:
- allwinner,sun8i-a83t-mmc
- allwinner,sun8i-b288-mmc
- allwinner,suniv-f1c100s-mmc
- const: allwinner,sun7i-a20-mmc
- items:
- enum:
- allwinner,sun8i-b288-emmc
- allwinner,sun8i-r40-emmc
- allwinner,sun50i-h5-emmc
- allwinner,sun50i-h6-emmc

View File

@ -15,6 +15,23 @@ maintainers:
allOf:
- $ref: mmc-controller.yaml#
- if:
properties:
compatible:
contains:
const: amlogic,t7-mmc
then:
properties:
clocks:
minItems: 4
clock-names:
minItems: 4
else:
properties:
clocks:
maxItems: 3
clock-names:
maxItems: 3
properties:
compatible:
@ -38,13 +55,23 @@ properties:
- description: card detect
clocks:
maxItems: 3
minItems: 3
items:
- description: core clock
- description: clock input 0 of the internal mux
- description: clock input 1 of the internal mux
- description:
clock of the pipeline stage inserted in the bus path between
the controller and the DRAM. Without it, the controller cannot
complete DMA transfers.
clock-names:
minItems: 3
items:
- const: core
- const: clkin0
- const: clkin1
- const: pipeline
resets:
maxItems: 1

View File

@ -0,0 +1,105 @@
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
%YAML 1.2
---
$id: http://devicetree.org/schemas/mmc/cdns,sd6hc.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Cadence SD6HC SD/SDIO/eMMC Host Controller
maintainers:
- Tanmay Kathpalia <tanmay.kathpalia@altera.com>
description:
Cadence SD6HC is a sixth-generation SD/SDIO/eMMC host controller with an
interface to a Cadence Combo PHY. The controller has separate card interface
and bus interface clock domains.
properties:
compatible:
items:
- const: altr,agilex5-sd6hc
- const: cdns,sd6hc
reg:
maxItems: 1
interrupts:
maxItems: 1
clocks:
items:
- description: card interface unit (CIU / SDMCLK) clock
- description: bus interface unit (BIU) clock
clock-names:
items:
- const: ciu
- const: biu
resets:
items:
- description: SDHC controller reset
- description: Combo PHY reset
- description: SDMMC OCP/AXI reset
reset-names:
items:
- const: sdhc-reset
- const: combophy
- const: sdmmc-ocp
iommus:
maxItems: 1
dma-coherent: true
cdns,iocell-input-delay-ps:
description:
Input delay across the IO cells, used for SD6HC PHY timing calculations.
minimum: 0
maximum: 20000
default: 2500
cdns,iocell-output-delay-ps:
description:
Output delay across the IO cells, used for SD6HC PHY timing calculations.
minimum: 0
maximum: 20000
default: 2500
cdns,delay-element-ps:
description:
DLL delay element size, used for SD6HC PHY timing calculations.
minimum: 1
maximum: 1000
default: 24
required:
- compatible
- reg
- interrupts
- clocks
- clock-names
- resets
- reset-names
allOf:
- $ref: sdhci-common.yaml#
unevaluatedProperties: false
examples:
- |
#include <dt-bindings/interrupt-controller/arm-gic.h>
mmc@10808000 {
compatible = "altr,agilex5-sd6hc", "cdns,sd6hc";
reg = <0x10808000 0x1000>;
interrupts = <GIC_SPI 96 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clkmgr 0>, <&clkmgr 1>;
clock-names = "ciu", "biu";
resets = <&rst 0>, <&rst 1>, <&rst 2>;
reset-names = "sdhc-reset", "combophy", "sdmmc-ocp";
iommus = <&smmu 5>;
dma-coherent;
};

View File

@ -275,6 +275,16 @@ properties:
not available.
default: 10
power-off-delay-us:
description:
Delay in microseconds after card power is deasserted during a power
cycle to allow time for proper power discharge. Larger values can be
configured to work around hardware issues such as slow RC discharge
on MMC VDD rails.
minimum: 0
maximum: 10000000
default: 1000
supports-cqe:
$ref: /schemas/types.yaml#/definitions/flag
description:

View File

@ -26,6 +26,7 @@ properties:
- qcom,ipq4019-sdhci
- qcom,ipq8074-sdhci
- qcom,msm8226-sdhci
- qcom,msm8952-sdhci
- qcom,msm8953-sdhci
- qcom,msm8974-sdhci
- qcom,msm8976-sdhci
@ -47,6 +48,7 @@ properties:
- qcom,ipq9574-sdhci
- qcom,ipq9650-sdhci
- qcom,kaanapali-sdhci
- qcom,maili-sdhci
- qcom,milos-sdhci
- qcom,qcm2290-sdhci
- qcom,qcs404-sdhci

View File

@ -23,7 +23,7 @@ MMC request.
MMC block driver
================
The mmc_blk_issue_rw_rq() in the MMC block driver is made non-blocking.
The mmc_blk_mq_issue_rw_rq() in the MMC block driver is made non-blocking.
The increase in throughput is proportional to the time it takes to
prepare (major part of preparations are dma_map_sg() and dma_unmap_sg())
@ -34,21 +34,20 @@ platform. In power save mode, when clocks run on a lower frequency, the DMA
preparation may cost even more. As long as these slower preparations are run
in parallel with the transfer performance won't be affected.
Details on measurements from IOZone and mmc_test
================================================
MMC core API
============
https://wiki.linaro.org/WorkingGroups/Kernel/Specs/StoragePerfMMC-async-req
The preparation of a request is separated from starting the transfer, so
that a host can prepare a request while another one is still in progress:
MMC core API extension
======================
There is one new public function mmc_start_req().
It starts a new MMC command request for a host. The function isn't
truly non-blocking. If there is an ongoing async request it waits
for completion of that request and starts the new one and returns. It
doesn't wait for the new request to complete. If there is no ongoing
request it starts the new request and returns immediately.
* mmc_pre_req() prepares a request before it is started. It may be called
while another request is running on the host.
* mmc_start_request() starts a prepared request without waiting for it to
complete. mmc_wait_for_req() starts a request and waits for it to
complete as well.
* mmc_post_req() releases the resources allocated by mmc_pre_req() after
the request has completed. It may likewise run while another request is
active.
MMC host extensions
===================
@ -59,40 +58,3 @@ to before and after the actual mmc_host_ops.request() function is called.
In the DMA case pre_req() may do dma_map_sg() and prepare the DMA
descriptor, and post_req() runs the dma_unmap_sg().
Optimize for the first request
==============================
The first request in a series of requests can't be prepared in parallel
with the previous transfer, since there is no previous request.
The argument is_first_req in pre_req() indicates that there is no previous
request. The host driver may optimize for this scenario to minimize
the performance loss. A way to optimize for this is to split the current
request in two chunks, prepare the first chunk and start the request,
and finally prepare the second chunk and start the transfer.
Pseudocode to handle is_first_req scenario with minimal prepare overhead::
if (is_first_req && req->size > threshold)
/* start MMC transfer for the complete transfer size */
mmc_start_command(MMC_CMD_TRANSFER_FULL_SIZE);
/*
* Begin to prepare DMA while cmd is being processed by MMC.
* The first chunk of the request should take the same time
* to prepare as the "MMC process command time".
* If prepare time exceeds MMC cmd time
* the transfer is delayed, guesstimate max 4k as first chunk size.
*/
prepare_1st_chunk_for_dma(req);
/* flush pending desc to the DMAC (dmaengine.h) */
dma_issue_pending(req->dma_desc);
prepare_2nd_chunk_for_dma(req);
/*
* The second issue_pending should be called before MMC runs out
* of the first chunk. If the MMC runs out of the first data chunk
* before this call, the transfer is delayed.
*/
dma_issue_pending(req->dma_desc);

View File

@ -32,6 +32,7 @@ The mmc-utils tools can do the following:
- Write to rpmb device from data file.
- Enable the eMMC cache feature.
- Disable the eMMC cache feature.
- Print the eMMC health status.
- Print and parse CID data.
- Print and parse CSD data.
- Print and parse SCR data.

View File

@ -24807,6 +24807,13 @@ L: linux-mmc@vger.kernel.org
S: Maintained
F: drivers/mmc/host/sdhci-brcmstb*
SECURE DIGITAL HOST CONTROLLER INTERFACE (SDHCI) CADENCE DRIVER
M: Tanmay Kathpalia <tanmay.kathpalia@altera.com>
L: linux-mmc@vger.kernel.org
S: Supported
F: Documentation/devicetree/bindings/mmc/cdns,sd6hc.yaml
F: drivers/mmc/host/sdhci-cadence*
SECURE DIGITAL HOST CONTROLLER INTERFACE (SDHCI) DRIVER
M: Adrian Hunter <adrian.hunter@intel.com>
L: linux-mmc@vger.kernel.org
@ -29417,11 +29424,6 @@ L: linux-hwmon@vger.kernel.org
S: Maintained
F: drivers/hwmon/vt8231.c
VUB300 USB to SDIO/SD/MMC bridge chip
L: linux-mmc@vger.kernel.org
S: Orphan
F: drivers/mmc/host/vub300.c
W1 DALLAS'S 1-WIRE BUS
M: Krzysztof Kozlowski <krzk@kernel.org>
S: Maintained

View File

@ -720,7 +720,7 @@ static int h_msb_parallel_switch(struct memstick_dev *card,
static int msb_switch_to_parallel(struct msb_data *msb);
/* Reset the card, to guard against hw errors beeing treated as bad blocks */
/* Reset the card, to guard against hw errors being treated as bad blocks */
static int msb_reset(struct msb_data *msb, bool full)
{

View File

@ -289,7 +289,7 @@ static int r592_transfer_fifo_dma(struct r592_device *dev)
dev->dma_error = 0;
reinit_completion(&dev->dma_done);
/* TODO: hidden assumption about nenth beeing always 1 */
/* TODO: hidden assumption about nenth being always 1 */
sg_count = dma_map_sg(&dev->pci_dev->dev, &dev->req->sg, 1, is_write ?
DMA_TO_DEVICE : DMA_FROM_DEVICE);

View File

@ -86,7 +86,7 @@ MODULE_ALIAS("mmc:block");
static DEFINE_MUTEX(block_mutex);
/*
* The defaults come from config options but can be overriden by module
* The defaults come from config options but can be overridden by module
* or bootarg options.
*/
static int perdev_minors = CONFIG_MMC_BLOCK_MINORS;

View File

@ -589,8 +589,8 @@ EXPORT_SYMBOL(mmc_cqe_recovery);
* mmc_is_req_done() is used with requests that have
* mrq->cap_cmd_during_tfr = true. mmc_is_req_done() must be called after
* starting a request and before waiting for it to complete. That is,
* either in between calls to mmc_start_req(), or after mmc_wait_for_req()
* and before mmc_wait_for_req_done(). If it is called at other times the
* after mmc_start_request() or mmc_wait_for_req() and before
* mmc_wait_for_req_done(). If it is called at other times the
* result is not meaningful.
*/
bool mmc_is_req_done(struct mmc_host *host, struct mmc_request *mrq)
@ -1388,7 +1388,7 @@ void mmc_power_off(struct mmc_host *host)
* XO-1.5, require a short delay after poweroff before the card
* can be successfully turned on again.
*/
mmc_delay(1);
mmc_delay_us(host->ios.power_off_delay_us);
}
void mmc_power_cycle(struct mmc_host *host, u32 ocr)
@ -2004,7 +2004,7 @@ unsigned int mmc_calc_max_discard(struct mmc_card *card)
/*
* Without erase_group_def set, MMC erase timeout depends on clock
* frequence which can change. In that case, the best choice is
* frequency which can change. In that case, the best choice is
* just the preferred erase size.
*/
if (mmc_card_mmc(card) && !(card->ext_csd.erase_group_def & 1))

View File

@ -73,6 +73,14 @@ static inline void mmc_delay(unsigned int ms)
msleep(ms);
}
static inline void mmc_delay_us(unsigned int us)
{
if (us == 0)
return;
fsleep(us);
}
void mmc_rescan(struct work_struct *work);
void mmc_start_host(struct mmc_host *host);
void __mmc_stop_host(struct mmc_host *host);
@ -162,7 +170,7 @@ int mmc_cqe_recovery(struct mmc_host *host);
* @host: MMC host to prepare command
* @mrq: MMC request to prepare for
*
* mmc_pre_req() is called in prior to mmc_start_req() to let
* mmc_pre_req() is called in prior to mmc_start_request() to let
* host prepare for the new request. Preparation of a request may be
* performed while another request is running on the host.
*/

View File

@ -15,7 +15,7 @@
void mmc_crypto_set_initial_state(struct mmc_host *host)
{
/* Reset might clear all keys, so reprogram all the keys. */
if (host->caps2 & MMC_CAP2_CRYPTO)
if ((host->caps2 & MMC_CAP2_CRYPTO) && !(host->caps2 & MMC_CAP2_CRYPTO_NO_REPROG))
blk_crypto_reprogram_all_keys(&host->crypto_profile);
}

View File

@ -421,6 +421,9 @@ int mmc_of_parse(struct mmc_host *host)
device_property_read_u32(dev, "post-power-on-delay-ms",
&host->ios.power_delay_ms);
device_property_read_u32(dev, "power-off-delay-us",
&host->ios.power_off_delay_us);
return mmc_pwrseq_alloc(host);
}
@ -502,14 +505,7 @@ static int mmc_first_nonreserved_index(void)
return max + 1;
}
/**
* mmc_alloc_host - initialise the per-host structure.
* @extra: sizeof private data structure
* @dev: pointer to host device model structure
*
* Initialise the per-host structure.
*/
struct mmc_host *mmc_alloc_host(int extra, struct device *dev)
static struct mmc_host *mmc_alloc_host(struct device *dev, int extra)
{
int index;
struct mmc_host *host;
@ -574,12 +570,19 @@ struct mmc_host *mmc_alloc_host(int extra, struct device *dev)
host->fixed_drv_type = -EINVAL;
host->ios.power_delay_ms = 10;
host->ios.power_off_delay_us = 1000;
host->ios.power_mode = MMC_POWER_UNDEFINED;
return host;
}
EXPORT_SYMBOL(mmc_alloc_host);
static void mmc_free_host(struct mmc_host *host)
{
cancel_delayed_work_sync(&host->detect);
cancel_work_sync(&host->sdio_irq_work);
mmc_pwrseq_free(host);
put_device(&host->class_dev);
}
static void devm_mmc_host_release(struct device *dev, void *res)
{
@ -594,7 +597,7 @@ struct mmc_host *devm_mmc_alloc_host(struct device *dev, int extra)
if (!dr)
return NULL;
host = mmc_alloc_host(extra, dev);
host = mmc_alloc_host(dev, extra);
if (!host) {
devres_free(dr);
return NULL;
@ -688,19 +691,3 @@ void mmc_remove_host(struct mmc_host *host)
}
EXPORT_SYMBOL(mmc_remove_host);
/**
* mmc_free_host - free the host structure
* @host: mmc host
*
* Free the host once all references to it have been dropped.
*/
void mmc_free_host(struct mmc_host *host)
{
cancel_delayed_work_sync(&host->detect);
cancel_work_sync(&host->sdio_irq_work);
mmc_pwrseq_free(host);
put_device(&host->class_dev);
}
EXPORT_SYMBOL(mmc_free_host);

View File

@ -1015,7 +1015,7 @@ static void mmc_set_bus_speed(struct mmc_card *card)
}
/*
* Select the bus width amoung 4-bit and 8-bit(SDR).
* Select the bus width among 4-bit and 8-bit(SDR).
* If the bus width is changed successfully, return the selected width value.
* Zero is returned instead of error value if the wide width is not supported.
*/

View File

@ -673,7 +673,8 @@ int mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value,
}
EXPORT_SYMBOL_GPL(mmc_switch);
int mmc_send_tuning(struct mmc_host *host, u32 opcode, int *cmd_error)
int mmc_send_tuning_timeout(struct mmc_host *host, u32 opcode, int *cmd_error,
u32 timeout_ms)
{
struct mmc_request mrq = {};
struct mmc_command cmd = {};
@ -707,12 +708,7 @@ int mmc_send_tuning(struct mmc_host *host, u32 opcode, int *cmd_error)
data.blocks = 1;
data.flags = MMC_DATA_READ;
/*
* According to the tuning specs, Tuning process
* is normally shorter 40 executions of CMD19,
* and timeout value should be shorter than 150 ms
*/
data.timeout_ns = 150 * NSEC_PER_MSEC;
data.timeout_ns = timeout_ms * NSEC_PER_MSEC;
data.sg = &sg;
data.sg_len = 1;
@ -740,6 +736,18 @@ int mmc_send_tuning(struct mmc_host *host, u32 opcode, int *cmd_error)
kfree(data_buf);
return err;
}
EXPORT_SYMBOL_GPL(mmc_send_tuning_timeout);
int mmc_send_tuning(struct mmc_host *host, u32 opcode, int *cmd_error)
{
/*
* The eMMC and SD specs give the tuning process an overall
* budget of 150 ms. mmc_send_tuning() simply applies that as
* the timeout of every single tuning command. Drivers which
* want a different timeout can use mmc_send_tuning_timeout().
*/
return mmc_send_tuning_timeout(host, opcode, cmd_error, 150);
}
EXPORT_SYMBOL_GPL(mmc_send_tuning);
int mmc_send_abort_tuning(struct mmc_host *host, u32 opcode)

View File

@ -57,12 +57,11 @@ static int cistpl_vers_1(struct mmc_card *card, struct sdio_func *func,
return -ENOMEM;
string = (char*)(buffer + nr_strings);
memcpy(string, buf, size);
for (i = 0; i < nr_strings; i++) {
buffer[i] = string;
strcpy(string, buf);
string += strlen(string) + 1;
buf += strlen(buf) + 1;
string = strchr(string, 0) + 1;
}
if (func) {

View File

@ -917,37 +917,6 @@ config MMC_JZ4740
If you have a board based on such a SoC and with a SD/MMC slot,
say Y or M here.
config MMC_VUB300
tristate "VUB300 USB to SDIO/SD/MMC Host Controller support"
depends on USB
help
This selects support for Elan Digital Systems' VUB300 chip.
The VUB300 is a USB-SDIO Host Controller Interface chip
that enables the host computer to use SDIO/SD/MMC cards
via a USB 2.0 or USB 1.1 host.
The VUB300 chip will be found in both physically separate
USB to SDIO/SD/MMC adapters and embedded on some motherboards.
The VUB300 chip supports SD and MMC memory cards in addition
to single and multifunction SDIO cards.
Some SDIO cards will need a firmware file to be loaded and
sent to VUB300 chip in order to achieve better data throughput.
Download these "Offload Pseudocode" from Elan Digital Systems'
web-site http://www.elandigitalsystems.com/support/downloads.php
and put them in /lib/firmware. Note that without these additional
firmware files the VUB300 chip will still function, but not at
the best obtainable data rate.
To compile this mmc host controller driver as a module,
choose M here: the module will be called vub300.
If you have a computer with an embedded VUB300 chip
or if you intend connecting a USB adapter based on a
VUB300 chip say Y or M here.
config MMC_USHC
tristate "USB SD Host Controller (USHC) support"
depends on USB

View File

@ -60,7 +60,6 @@ obj-$(CONFIG_MMC_DW_ROCKCHIP) += dw_mmc-rockchip.o
obj-$(CONFIG_MMC_DW_STARFIVE) += dw_mmc-starfive.o
obj-$(CONFIG_MMC_SH_MMCIF) += sh_mmcif.o
obj-$(CONFIG_MMC_JZ4740) += jz4740_mmc.o
obj-$(CONFIG_MMC_VUB300) += vub300.o
obj-$(CONFIG_MMC_USHC) += ushc.o
obj-$(CONFIG_MMC_WMT) += wmt-sdmmc.o
obj-$(CONFIG_MMC_MESON_GX) += meson-gx-mmc.o
@ -80,6 +79,7 @@ obj-$(CONFIG_MMC_REALTEK_USB) += rtsx_usb_sdmmc.o
obj-$(CONFIG_MMC_SDHCI_PLTFM) += sdhci-pltfm.o
obj-$(CONFIG_MMC_SDHCI_CADENCE) += sdhci-cadence.o
sdhci-cadence-y += sdhci-cadence-core.o sdhci-cadence-phy-v6.o
obj-$(CONFIG_MMC_SDHCI_ESDHC_MCF) += sdhci-esdhc-mcf.o
obj-$(CONFIG_MMC_SDHCI_ESDHC_IMX) += sdhci-esdhc-imx.o
obj-$(CONFIG_MMC_SDHCI_DOVE) += sdhci-dove.o

View File

@ -1677,10 +1677,7 @@ static void atmci_detect_change(struct timer_list *t)
spin_lock(&host->lock);
if (!present)
clear_bit(ATMCI_CARD_PRESENT, &slot->flags);
else
set_bit(ATMCI_CARD_PRESENT, &slot->flags);
assign_bit(ATMCI_CARD_PRESENT, &slot->flags, present);
/* Clean up queue if present */
mrq = slot->mrq;

View File

@ -1095,7 +1095,7 @@ static void bcm2835_set_clock(struct bcm2835_host *host, unsigned int clock)
int div;
/* The SDCDIV register has 11 bits, and holds (div - 2). But
* in data mode the max is 50MHz wihout a minimum, and only
* in data mode the max is 50MHz without a minimum, and only
* the bottom 3 bits are used. Since the switch over is
* automatic (unless we have marked the card as slow...),
* chosen values have to make sense in both modes. Ident mode

View File

@ -157,11 +157,8 @@ static int thunder_mmc_probe(struct pci_dev *pdev,
for (i = 0; i < CAVIUM_MAX_MMC; i++) {
if (host->slot[i])
cvm_mmc_of_slot_remove(host->slot[i]);
if (host->slot_pdev[i]) {
get_device(&host->slot_pdev[i]->dev);
if (host->slot_pdev[i])
of_platform_device_destroy(&host->slot_pdev[i]->dev, NULL);
put_device(&host->slot_pdev[i]->dev);
}
}
clk_disable_unprepare(host->clk);
return ret;

View File

@ -1249,6 +1249,10 @@ static int davinci_mmcsd_probe(struct platform_device *pdev)
host->use_dma = use_dma;
host->mmc_irq = irq;
host->sdio_irq = platform_get_irq_optional(pdev, 1);
if (host->sdio_irq < 0 && host->sdio_irq != -ENXIO) {
ret = host->sdio_irq;
goto parse_fail;
}
if (host->use_dma) {
ret = davinci_acquire_dma_channels(host);
@ -1299,7 +1303,7 @@ static int davinci_mmcsd_probe(struct platform_device *pdev)
if (ret)
goto mmc_add_host_fail;
if (host->sdio_irq >= 0) {
if (host->sdio_irq > 0) {
ret = devm_request_irq(&pdev->dev, host->sdio_irq,
mmc_davinci_sdio_irq, 0,
mmc_hostname(mmc), host);

View File

@ -327,7 +327,7 @@ static int dw_mci_rk3288_execute_tuning(struct dw_mci *host, u32 opcode)
i,
priv->num_phases));
v = !mmc_send_tuning(mmc, opcode, NULL);
v = !mmc_send_tuning_timeout(mmc, opcode, NULL, 5);
if (i == 0)
first_v = v;

View File

@ -44,6 +44,137 @@
#define DW_MCI_FREQ_MAX 200000000 /* unit: HZ */
#define DW_MCI_FREQ_MIN 100000 /* unit: HZ */
/* Event sets guarded by the central watchdog, see below. */
#define DW_MCI_WD_CMD_EVENTS BIT(EVENT_CMD_COMPLETE)
#define DW_MCI_WD_DATA_EVENTS (BIT(EVENT_DATA_ERROR) | \
BIT(EVENT_DATA_COMPLETE))
/* Extra watch rounds granted while the completion IRQ is merely late. */
#define DW_MCI_WD_INFLIGHT_GRACE_MS 10
/*
* Central watchdog
* ================
*
* A single hrtimer guards the legs of a request (command, data, voltage
* switch) against the hardware going silent: whenever a completion event
* that was expected within a bounded time does not arrive in time, the
* callback synthesizes it so that the request state machine can error out.
*
* @wd_events holds the pending_events bits still being waited for and
* @wd_states the host->state values for which the watch is valid; both
* are protected by irq_lock. Every producer of the guarded events posts
* them under irq_lock as well, so a callback holding irq_lock can never
* miss an already-delivered event. A stale callback which raced past
* all checks therefore degrades to at most one redundant state-machine
* run instead of completing a foreign leg. Arming replaces whatever
* watch was outstanding before: command and data legs are strictly
* serial, so consecutive arms on the same path collapse onto the latest
* deadline.
*
* On expiry, before declaring a timeout, the callback double-checks that
* the completion interrupt is not already latched in hardware; if it is,
* only the interrupt latency is to blame and the watch is granted extra
* rounds instead of failing a transfer that is about to complete.
*/
static void dw_mci_wd_deliver(struct dw_mci *host, unsigned long events)
{
host->wd_events &= ~events;
if (!host->wd_events)
hrtimer_try_to_cancel(&host->wd_timer);
}
/*
* Arm a new watch for @events valid while host->state is one of @states,
* expiring after @ms milliseconds. @already lists bits whose delivery
* makes the watch pointless before it is even armed: arming is skipped
* when any of them has been posted concurrently. Must be called with
* irq_lock held.
*/
static void dw_mci_wd_arm(struct dw_mci *host, unsigned long ms,
unsigned long already, unsigned long events,
unsigned long states)
{
lockdep_assert_held(&host->irq_lock);
if (host->pending_events & already) {
/*
* The event was delivered concurrently; no need for a
* watch anymore.
*/
host->wd_events = 0;
hrtimer_try_to_cancel(&host->wd_timer);
return;
}
host->wd_events = events;
host->wd_states = states;
host->wd_deadline_ns = ktime_get_ns() + (u64)ms * NSEC_PER_MSEC;
hrtimer_start(&host->wd_timer, ms_to_ktime(ms), HRTIMER_MODE_REL);
}
/* Called from hardirq context on watchdog expiry. */
static enum hrtimer_restart dw_mci_watchdog_fn(struct hrtimer *t)
{
struct dw_mci *host = container_of(t, struct dw_mci, wd_timer);
unsigned long irqflags;
u32 pending;
spin_lock_irqsave(&host->irq_lock, irqflags);
/*
* Last line of defense against a false timeout: when the completion
* interrupt is already latched in MINTSTS but its handler has not
* been scheduled yet, the leg is about to finish normally. Warn
* about the latency and keep watching instead of failing the
* transfer; delivery by the real interrupt will stop the watch.
* The retired per-timer callbacks went passive here and relied on
* the latched interrupt arriving; rearming bounds the damage if
* that assumption is ever broken.
*/
if (host->wd_events) {
pending = mci_readl(host, MINTSTS); /* read-only mask reg */
if ((host->wd_events == DW_MCI_WD_CMD_EVENTS &&
(pending & (DW_MCI_CMD_ERROR_FLAGS |
SDMMC_INT_CMD_DONE))) ||
(host->wd_events == DW_MCI_WD_DATA_EVENTS &&
(pending & (SDMMC_INT_DATA_OVER |
DW_MCI_DATA_ERROR_FLAGS)))) {
dev_warn(host->dev,
"Interrupt latency, state %d (watched %#lx)\n",
host->state, host->wd_events);
hrtimer_forward_now(&host->wd_timer,
ms_to_ktime(DW_MCI_WD_INFLIGHT_GRACE_MS));
spin_unlock_irqrestore(&host->irq_lock, irqflags);
return HRTIMER_RESTART;
}
}
if (host->wd_events == DW_MCI_WD_DATA_EVENTS) {
dev_warn(host->dev,
"Data timeout, state %d\n", host->state);
host->data_status = SDMMC_INT_DRTO;
set_bit(EVENT_DATA_ERROR, &host->pending_events);
set_bit(EVENT_DATA_COMPLETE, &host->pending_events);
} else if (host->wd_events) {
dev_warn(host->dev,
"Command timeout, state %d (watched %#lx)\n",
host->state, host->wd_events);
host->cmd_status = SDMMC_INT_RTO;
set_bit(EVENT_CMD_COMPLETE, &host->pending_events);
}
if (host->wd_events)
queue_work(system_bh_wq, &host->bh_work);
host->wd_events = 0;
host->wd_states = 0;
host->wd_deadline_ns = 0;
spin_unlock_irqrestore(&host->irq_lock, irqflags);
return HRTIMER_NORESTART;
}
#define IDMAC_INT_CLR (SDMMC_IDMAC_INT_AI | SDMMC_IDMAC_INT_NI | \
SDMMC_IDMAC_INT_CES | SDMMC_IDMAC_INT_DU | \
SDMMC_IDMAC_INT_FBE | SDMMC_IDMAC_INT_RI | \
@ -171,6 +302,10 @@ static void dw_mci_init_debugfs(struct dw_mci *host)
&host->pending_events);
debugfs_create_xul("completed_events", 0400, root,
&host->completed_events);
debugfs_create_x64("wd_deadline_ns", 0400, root,
&host->wd_deadline_ns);
debugfs_create_xul("wd_events", 0400, root, &host->wd_events);
debugfs_create_xul("wd_states", 0400, root, &host->wd_states);
#ifdef CONFIG_FAULT_INJECTION
fault_create_debugfs_attr("fail_data_crc", root, &host->fail_data_crc);
#endif
@ -366,18 +501,17 @@ static inline void dw_mci_set_cto(struct dw_mci *host)
* extra careful about synchronization here. Specifically in hardware a
* command timeout is _at most_ 5.1 ms, so that means we expect an
* interrupt (either command done or timeout) to come rather quickly
* after the mci_writel. ...but just in case we have a long interrupt
* latency let's add a bit of paranoia.
* after the mci_writel.
*
* In general we'll assume that at least an interrupt will be asserted
* in hardware by the time the cto_timer runs. ...and if it hasn't
* been asserted in hardware by that time then we'll assume it'll never
* come.
* dw_mci_wd_arm() skips arming when the event was already delivered,
* and the watchdog callback rechecks under the same lock before
* declaring a timeout, so both orderings of the race are covered.
*/
spin_lock_irqsave(&host->irq_lock, irqflags);
if (!test_bit(EVENT_CMD_COMPLETE, &host->pending_events))
mod_timer(&host->cto_timer,
jiffies + msecs_to_jiffies(cto_ms) + 1);
dw_mci_wd_arm(host, cto_ms, DW_MCI_WD_CMD_EVENTS,
DW_MCI_WD_CMD_EVENTS,
BIT(STATE_SENDING_CMD) | BIT(STATE_SENDING_STOP) |
BIT(STATE_SENDING_CMD11));
spin_unlock_irqrestore(&host->irq_lock, irqflags);
}
@ -1265,17 +1399,16 @@ static void dw_mci_start_request(struct dw_mci *host, struct mmc_command *cmd)
/*
* Databook says to fail after 2ms w/ no response, but evidence
* shows that sometimes the cmd11 interrupt takes over 130ms.
* We'll set to 500ms, plus an extra jiffy just in case jiffies
* is just about to roll over.
* We'll set to 500ms.
*
* We do this whole thing under spinlock and only if the
* command hasn't already completed (indicating the irq
* already ran so we don't want the timeout).
*/
spin_lock_irqsave(&host->irq_lock, irqflags);
if (!test_bit(EVENT_CMD_COMPLETE, &host->pending_events))
mod_timer(&host->cmd11_timer,
jiffies + msecs_to_jiffies(500) + 1);
dw_mci_wd_arm(host, 500, DW_MCI_WD_CMD_EVENTS,
DW_MCI_WD_CMD_EVENTS,
BIT(STATE_SENDING_CMD11));
spin_unlock_irqrestore(&host->irq_lock, irqflags);
}
@ -1870,9 +2003,9 @@ static void dw_mci_set_drto(struct dw_mci *host)
drto_ms += 10;
spin_lock_irqsave(&host->irq_lock, irqflags);
if (!test_bit(EVENT_DATA_COMPLETE, &host->pending_events))
mod_timer(&host->dto_timer,
jiffies + msecs_to_jiffies(drto_ms));
dw_mci_wd_arm(host, drto_ms, BIT(EVENT_DATA_COMPLETE),
DW_MCI_WD_DATA_EVENTS,
BIT(STATE_SENDING_DATA) | BIT(STATE_DATA_BUSY));
spin_unlock_irqrestore(&host->irq_lock, irqflags);
}
@ -1881,14 +2014,6 @@ static bool dw_mci_clear_pending_cmd_complete(struct dw_mci *host)
if (!test_bit(EVENT_CMD_COMPLETE, &host->pending_events))
return false;
/*
* Really be certain that the timer has stopped. This is a bit of
* paranoia and could only really happen if we had really bad
* interrupt latency and the interrupt routine and timeout were
* running concurrently so that the timer_delete() in the interrupt
* handler couldn't run.
*/
WARN_ON(timer_delete_sync(&host->cto_timer));
clear_bit(EVENT_CMD_COMPLETE, &host->pending_events);
return true;
@ -1899,8 +2024,6 @@ static bool dw_mci_clear_pending_data_complete(struct dw_mci *host)
if (!test_bit(EVENT_DATA_COMPLETE, &host->pending_events))
return false;
/* Extra paranoia just like dw_mci_clear_pending_cmd_complete() */
WARN_ON(timer_delete_sync(&host->dto_timer));
clear_bit(EVENT_DATA_COMPLETE, &host->pending_events);
return true;
@ -2633,7 +2756,7 @@ static void dw_mci_write_data_pio(struct dw_mci *host)
static void dw_mci_cmd_interrupt(struct dw_mci *host, u32 status)
{
timer_delete(&host->cto_timer);
dw_mci_wd_deliver(host, BIT(EVENT_CMD_COMPLETE));
if (!host->cmd_status)
host->cmd_status = status;
@ -2666,21 +2789,15 @@ static irqreturn_t dw_mci_interrupt(int irq, void *dev_id)
mci_writel(host, RINTSTS, SDMMC_INT_VOLT_SWITCH);
pending &= ~SDMMC_INT_VOLT_SWITCH;
/*
* Hold the lock; we know cmd11_timer can't be kicked
* off after the lock is released, so safe to delete.
*/
spin_lock(&host->irq_lock);
dw_mci_cmd_interrupt(host, pending);
spin_unlock(&host->irq_lock);
timer_delete(&host->cmd11_timer);
}
if (pending & DW_MCI_CMD_ERROR_FLAGS) {
spin_lock(&host->irq_lock);
timer_delete(&host->cto_timer);
dw_mci_wd_deliver(host, BIT(EVENT_CMD_COMPLETE));
mci_writel(host, RINTSTS, DW_MCI_CMD_ERROR_FLAGS);
host->cmd_status = pending;
smp_wmb(); /* drain writebuffer */
@ -2693,7 +2810,7 @@ static irqreturn_t dw_mci_interrupt(int irq, void *dev_id)
spin_lock(&host->irq_lock);
if (host->quirks & DW_MMC_QUIRK_EXTENDED_TMOUT)
timer_delete(&host->dto_timer);
dw_mci_wd_deliver(host, DW_MCI_WD_DATA_EVENTS);
/* if there is an error report DATA_ERROR */
mci_writel(host, RINTSTS, DW_MCI_DATA_ERROR_FLAGS);
@ -2714,7 +2831,7 @@ static irqreturn_t dw_mci_interrupt(int irq, void *dev_id)
if (pending & SDMMC_INT_DATA_OVER) {
spin_lock(&host->irq_lock);
timer_delete(&host->dto_timer);
dw_mci_wd_deliver(host, DW_MCI_WD_DATA_EVENTS);
mci_writel(host, RINTSTS, SDMMC_INT_DATA_OVER);
if (!host->data_status)
@ -2994,126 +3111,6 @@ static void dw_mci_init_dma(struct dw_mci *host)
host->use_dma = TRANS_MODE_PIO;
}
static void dw_mci_cmd11_timer(struct timer_list *t)
{
struct dw_mci *host = timer_container_of(host, t, cmd11_timer);
if (host->state != STATE_SENDING_CMD11) {
dev_warn(host->dev, "Unexpected CMD11 timeout\n");
return;
}
host->cmd_status = SDMMC_INT_RTO;
set_bit(EVENT_CMD_COMPLETE, &host->pending_events);
queue_work(system_bh_wq, &host->bh_work);
}
static void dw_mci_cto_timer(struct timer_list *t)
{
struct dw_mci *host = timer_container_of(host, t, cto_timer);
unsigned long irqflags;
u32 pending;
spin_lock_irqsave(&host->irq_lock, irqflags);
/*
* If somehow we have very bad interrupt latency it's remotely possible
* that the timer could fire while the interrupt is still pending or
* while the interrupt is midway through running. Let's be paranoid
* and detect those two cases. Note that this is paranoia is somewhat
* justified because in this function we don't actually cancel the
* pending command in the controller--we just assume it will never come.
*/
pending = mci_readl(host, MINTSTS); /* read-only mask reg */
if (pending & (DW_MCI_CMD_ERROR_FLAGS | SDMMC_INT_CMD_DONE)) {
/* The interrupt should fire; no need to act but we can warn */
dev_warn(host->dev, "Unexpected interrupt latency\n");
goto exit;
}
if (test_bit(EVENT_CMD_COMPLETE, &host->pending_events)) {
/* Presumably interrupt handler couldn't delete the timer */
dev_warn(host->dev, "CTO timeout when already completed\n");
goto exit;
}
/*
* Continued paranoia to make sure we're in the state we expect.
* This paranoia isn't really justified but it seems good to be safe.
*/
switch (host->state) {
case STATE_SENDING_CMD11:
case STATE_SENDING_CMD:
case STATE_SENDING_STOP:
/*
* If CMD_DONE interrupt does NOT come in sending command
* state, we should notify the driver to terminate current
* transfer and report a command timeout to the core.
*/
host->cmd_status = SDMMC_INT_RTO;
set_bit(EVENT_CMD_COMPLETE, &host->pending_events);
queue_work(system_bh_wq, &host->bh_work);
break;
default:
dev_warn(host->dev, "Unexpected command timeout, state %d\n",
host->state);
break;
}
exit:
spin_unlock_irqrestore(&host->irq_lock, irqflags);
}
static void dw_mci_dto_timer(struct timer_list *t)
{
struct dw_mci *host = timer_container_of(host, t, dto_timer);
unsigned long irqflags;
u32 pending;
spin_lock_irqsave(&host->irq_lock, irqflags);
/*
* The DTO timer is much longer than the CTO timer, so it's even less
* likely that we'll these cases, but it pays to be paranoid.
*/
pending = mci_readl(host, MINTSTS); /* read-only mask reg */
if (pending & SDMMC_INT_DATA_OVER) {
/* The interrupt should fire; no need to act but we can warn */
dev_warn(host->dev, "Unexpected data interrupt latency\n");
goto exit;
}
if (test_bit(EVENT_DATA_COMPLETE, &host->pending_events)) {
/* Presumably interrupt handler couldn't delete the timer */
dev_warn(host->dev, "DTO timeout when already completed\n");
goto exit;
}
/*
* Continued paranoia to make sure we're in the state we expect.
* This paranoia isn't really justified but it seems good to be safe.
*/
switch (host->state) {
case STATE_SENDING_DATA:
case STATE_DATA_BUSY:
/*
* If DTO interrupt does NOT come in sending data state,
* we should notify the driver to terminate current transfer
* and report a data timeout to the core.
*/
host->data_status = SDMMC_INT_DRTO;
set_bit(EVENT_DATA_ERROR, &host->pending_events);
set_bit(EVENT_DATA_COMPLETE, &host->pending_events);
queue_work(system_bh_wq, &host->bh_work);
break;
default:
dev_warn(host->dev, "Unexpected data timeout, state %d\n",
host->state);
break;
}
exit:
spin_unlock_irqrestore(&host->irq_lock, irqflags);
}
static int dw_mci_parse_dt(struct dw_mci *host)
{
struct device *dev = host->dev;
@ -3261,9 +3258,8 @@ int dw_mci_probe(struct dw_mci *host)
}
}
timer_setup(&host->cmd11_timer, dw_mci_cmd11_timer, 0);
timer_setup(&host->cto_timer, dw_mci_cto_timer, 0);
timer_setup(&host->dto_timer, dw_mci_dto_timer, 0);
hrtimer_setup(&host->wd_timer, dw_mci_watchdog_fn, CLOCK_MONOTONIC,
HRTIMER_MODE_REL);
spin_lock_init(&host->lock);
spin_lock_init(&host->irq_lock);

View File

@ -69,7 +69,7 @@ struct dw_mci_dma_slave {
* @cmd: The command currently being sent to the card, or NULL.
* @data: The data currently being transferred, or NULL if no data
* transfer is in progress.
* @stop_abort: The command currently prepared for stoping transfer.
* @stop_abort: The command currently prepared for stopping transfer.
* @prev_blksz: The former transfer blksz record.
* @timing: Record of current ios timing.
* @use_dma: Which DMA channel is in use for the current transfer, zero
@ -122,9 +122,11 @@ struct dw_mci_dma_slave {
* @irq_flags: The flags to be passed to request_irq.
* @irq: The irq value to be passed to request_irq.
* @sdio_irq: SDIO interrupt bit in interrupt registers.
* @cmd11_timer: Timer for SD3.0 voltage switch over scheme.
* @cto_timer: Timer for broken command transfer over scheme.
* @dto_timer: Timer for broken data transfer over scheme.
* @wd_timer: Central watchdog guarding request legs against hardware going
* silent; fires when an expected completion event fails to arrive in
* time so that the state machine can synthesize an error.
* @wd_events: pending_events bits still awaited by the armed watch.
* @wd_states: host->state values for which the armed watch is valid.
* @mmc: The mmc_host representing this dw_mci.
* @flags: Random state bits associated with the host.
* @ctype: Card type for this host.
@ -214,7 +216,6 @@ struct dw_mci {
void *priv;
struct clk *biu_clk;
struct clk *ciu_clk;
struct dw_mci_slot *slot;
/* FIFO push and pull */
int fifo_depth;
@ -235,9 +236,10 @@ struct dw_mci {
int sdio_irq;
struct timer_list cmd11_timer;
struct timer_list cto_timer;
struct timer_list dto_timer;
struct hrtimer wd_timer;
unsigned long wd_events;
unsigned long wd_states;
u64 wd_deadline_ns;
#ifdef CONFIG_FAULT_INJECTION
struct fault_attr fail_data_crc;

View File

@ -139,6 +139,7 @@ struct meson_mmc_data {
unsigned int always_on;
unsigned int adjust;
unsigned int irq_sdio_sleep;
bool has_pipeline_clk;
};
struct sd_emmc_desc {
@ -418,7 +419,7 @@ static int meson_mmc_clk_set(struct meson_host *host, unsigned long rate,
*/
static int meson_mmc_clk_init(struct meson_host *host)
{
struct clk_init_data init;
struct clk_init_data init = {};
struct clk_mux *mux;
struct clk_divider *div;
char clk_name[32];
@ -1141,7 +1142,8 @@ static int meson_mmc_probe(struct platform_device *pdev)
struct meson_host *host;
struct mmc_host *mmc;
struct clk *core_clk;
int cd_irq, ret;
int cd_irq = -ENXIO;
int ret;
mmc = devm_mmc_alloc_host(&pdev->dev, sizeof(struct meson_host));
if (!mmc)
@ -1185,7 +1187,11 @@ static int meson_mmc_probe(struct platform_device *pdev)
if (host->irq < 0)
return host->irq;
cd_irq = platform_get_irq_optional(pdev, 1);
ret = platform_get_irq_optional(pdev, 1);
if (ret < 0 && ret != -ENXIO)
return ret;
if (ret > 0)
cd_irq = ret;
mmc_gpio_set_cd_irq(mmc, cd_irq);
host->pinctrl = devm_pinctrl_get(&pdev->dev);
@ -1204,6 +1210,15 @@ static int meson_mmc_probe(struct platform_device *pdev)
if (IS_ERR(core_clk))
return PTR_ERR(core_clk);
if (host->data->has_pipeline_clk) {
struct clk *pipe_clk;
pipe_clk = devm_clk_get_enabled(&pdev->dev, "pipeline");
if (IS_ERR(pipe_clk))
return dev_err_probe(&pdev->dev, PTR_ERR(pipe_clk),
"missing pipeline clock\n");
}
ret = meson_mmc_clk_init(host);
if (ret)
return ret;
@ -1322,12 +1337,22 @@ static const struct meson_mmc_data meson_axg_data = {
.irq_sdio_sleep = CLK_V3_IRQ_SDIO_SLEEP,
};
static const struct meson_mmc_data meson_t7_data = {
.tx_delay_mask = CLK_V3_TX_DELAY_MASK,
.rx_delay_mask = CLK_V3_RX_DELAY_MASK,
.always_on = CLK_V3_ALWAYS_ON,
.adjust = SD_EMMC_V3_ADJUST,
.irq_sdio_sleep = CLK_V3_IRQ_SDIO_SLEEP,
.has_pipeline_clk = true,
};
static const struct of_device_id meson_mmc_of_match[] = {
{ .compatible = "amlogic,meson-gx-mmc", .data = &meson_gx_data },
{ .compatible = "amlogic,meson-gxbb-mmc", .data = &meson_gx_data },
{ .compatible = "amlogic,meson-gxl-mmc", .data = &meson_gx_data },
{ .compatible = "amlogic,meson-gxm-mmc", .data = &meson_gx_data },
{ .compatible = "amlogic,meson-axg-mmc", .data = &meson_axg_data },
{ .compatible = "amlogic,t7-mmc", .data = &meson_t7_data },
{}
};
MODULE_DEVICE_TABLE(of, meson_mmc_of_match);

View File

@ -717,7 +717,7 @@ static int mvsd_probe(struct platform_device *pdev)
/*
* Some non-DT platforms do not pass a clock, and the clock
* frequency is passed through platform_data. On DT platforms,
* a clock must always be passed, even if there is no gatable
* a clock must always be passed, even if there is no gateable
* clock associated to the SDIO interface (it can simply be a
* fixed rate clock).
*/

View File

@ -980,6 +980,11 @@ static int sd_power_on(struct rtsx_usb_sdmmc *host)
}
dev_dbg(sdmmc_dev(host), "%s\n", __func__);
rtsx_usb_init_cmd(ucr);
/* Start SD init at 3.3V, like the old rts5139 driver. */
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_PAD_CTL,
SD_IO_USING_1V8, SD_IO_USING_3V3);
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, LDO_POWER_CFG,
TUNE_SD18_MASK, TUNE_SD18_3V3);
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_SELECT, 0x07, SD_MOD_SEL);
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_SHARE_MODE,
CARD_SHARE_MASK, CARD_SHARE_SD);

View File

@ -2,10 +2,12 @@
/*
* Copyright (C) 2016 Socionext Inc.
* Author: Masahiro Yamada <yamada.masahiro@socionext.com>
* Copyright (C) 2026 Altera Corporation
*/
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/dma-mapping.h>
#include <linux/iopoll.h>
#include <linux/module.h>
#include <linux/mmc/host.h>
@ -14,10 +16,10 @@
#include <linux/platform_device.h>
#include <linux/reset.h>
#include "sdhci-pltfm.h"
#include "sdhci-cadence.h"
/* HRS - Host Register Set (specific to Cadence) */
#define SDHCI_CDNS_HRS04 0x10 /* PHY access port */
/* HRS04 (PHY access) bitfields (SD4HC) */
#define SDHCI_CDNS_HRS04_ACK BIT(26)
#define SDHCI_CDNS_HRS04_RD BIT(25)
#define SDHCI_CDNS_HRS04_WR BIT(24)
@ -71,30 +73,17 @@
#define SDHCI_CDNS_PHY_DLY_HSMMC 0x0c
#define SDHCI_CDNS_PHY_DLY_STROBE 0x0d
/*
* The tuned val register is 6 bit-wide, but not the whole of the range is
* available. The range 0-42 seems to be available (then 43 wraps around to 0)
* but I am not quite sure if it is official. Use only 0 to 39 for safety.
*/
#define SDHCI_CDNS_MAX_TUNING_LOOP 40
struct sdhci_cdns_phy_param {
struct sdhci_cdns4_phy_param {
u8 addr;
u8 data;
};
struct sdhci_cdns_priv {
void __iomem *hrs_addr;
void __iomem *ctl_addr; /* write control */
spinlock_t wrlock; /* write lock */
bool enhanced_strobe;
void (*priv_writel)(struct sdhci_cdns_priv *priv, u32 val, void __iomem *reg);
struct reset_control *rst_hw;
struct sdhci_cdns4_phy {
unsigned int nr_phy_params;
struct sdhci_cdns_phy_param phy_params[];
struct sdhci_cdns4_phy_param phy_params[];
};
struct sdhci_cdns_phy_cfg {
struct sdhci_cdns4_phy_cfg {
const char *property;
u8 addr;
};
@ -102,9 +91,10 @@ struct sdhci_cdns_phy_cfg {
struct sdhci_cdns_drv_data {
int (*init)(struct platform_device *pdev);
const struct sdhci_pltfm_data pltfm_data;
u64 dma_mask;
};
static const struct sdhci_cdns_phy_cfg sdhci_cdns_phy_cfgs[] = {
static const struct sdhci_cdns4_phy_cfg sdhci_cdns4_phy_cfgs[] = {
{ "cdns,phy-input-delay-sd-highspeed", SDHCI_CDNS_PHY_DLY_SD_HS, },
{ "cdns,phy-input-delay-legacy", SDHCI_CDNS_PHY_DLY_SD_DEFAULT, },
{ "cdns,phy-input-delay-sd-uhs-sdr12", SDHCI_CDNS_PHY_DLY_UHS_SDR12, },
@ -124,8 +114,7 @@ static inline void cdns_writel(struct sdhci_cdns_priv *priv, u32 val,
writel(val, reg);
}
static int sdhci_cdns_write_phy_reg(struct sdhci_cdns_priv *priv,
u8 addr, u8 data)
static int sdhci_cdns4_write_phy_reg(struct sdhci_cdns_priv *priv, u8 addr, u8 data)
{
void __iomem *reg = priv->hrs_addr + SDHCI_CDNS_HRS04;
u32 tmp;
@ -156,44 +145,43 @@ static int sdhci_cdns_write_phy_reg(struct sdhci_cdns_priv *priv,
return ret;
}
static unsigned int sdhci_cdns_phy_param_count(struct device_node *np)
static unsigned int sdhci_cdns4_phy_param_count(struct device_node *np)
{
unsigned int count = 0;
int i;
for (i = 0; i < ARRAY_SIZE(sdhci_cdns_phy_cfgs); i++)
if (of_property_present(np, sdhci_cdns_phy_cfgs[i].property))
for (i = 0; i < ARRAY_SIZE(sdhci_cdns4_phy_cfgs); i++)
if (of_property_present(np, sdhci_cdns4_phy_cfgs[i].property))
count++;
return count;
}
static void sdhci_cdns_phy_param_parse(struct device_node *np,
struct sdhci_cdns_priv *priv)
static void sdhci_cdns4_phy_param_parse(struct device_node *np, struct sdhci_cdns4_phy *phy)
{
struct sdhci_cdns_phy_param *p = priv->phy_params;
struct sdhci_cdns4_phy_param *p = phy->phy_params;
u32 val;
int ret, i;
for (i = 0; i < ARRAY_SIZE(sdhci_cdns_phy_cfgs); i++) {
ret = of_property_read_u32(np, sdhci_cdns_phy_cfgs[i].property,
&val);
for (i = 0; i < ARRAY_SIZE(sdhci_cdns4_phy_cfgs); i++) {
ret = of_property_read_u32(np, sdhci_cdns4_phy_cfgs[i].property, &val);
if (ret)
continue;
p->addr = sdhci_cdns_phy_cfgs[i].addr;
p->addr = sdhci_cdns4_phy_cfgs[i].addr;
p->data = val;
p++;
}
}
static int sdhci_cdns_phy_init(struct sdhci_cdns_priv *priv)
static int sdhci_cdns4_phy_init(struct sdhci_cdns_priv *priv)
{
struct sdhci_cdns4_phy *phy = priv->phy;
int ret, i;
for (i = 0; i < priv->nr_phy_params; i++) {
ret = sdhci_cdns_write_phy_reg(priv, priv->phy_params[i].addr,
priv->phy_params[i].data);
for (i = 0; i < phy->nr_phy_params; i++) {
ret = sdhci_cdns4_write_phy_reg(priv, phy->phy_params[i].addr,
phy->phy_params[i].data);
if (ret)
return ret;
}
@ -201,13 +189,6 @@ static int sdhci_cdns_phy_init(struct sdhci_cdns_priv *priv)
return 0;
}
static void *sdhci_cdns_priv(struct sdhci_host *host)
{
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
return sdhci_pltfm_priv(pltfm_host);
}
static unsigned int sdhci_cdns_get_timeout_clock(struct sdhci_host *host)
{
/*
@ -217,6 +198,23 @@ static unsigned int sdhci_cdns_get_timeout_clock(struct sdhci_host *host)
return host->max_clk;
}
static int sdhci_cdns_set_dma_mask(struct sdhci_host *host)
{
const struct sdhci_cdns_drv_data *data;
struct device *dev = mmc_dev(host->mmc);
int ret;
data = of_device_get_match_data(dev);
if (!data || !data->dma_mask)
return 0;
ret = dma_set_mask_and_coherent(dev, data->dma_mask);
if (ret)
return dev_err_probe(dev, ret, "failed to set DMA mask\n");
return 0;
}
static void sdhci_cdns_set_emmc_mode(struct sdhci_cdns_priv *priv, u32 mode)
{
u32 tmp;
@ -243,6 +241,9 @@ static int sdhci_cdns_set_tune_val(struct sdhci_host *host, unsigned int val)
u32 tmp;
int i, ret;
if (host->version >= SDHCI_SPEC_420)
return sdhci_cdns6_set_tune_val(host, val);
if (WARN_ON(!FIELD_FIT(SDHCI_CDNS_HRS06_TUNE, val)))
return -EINVAL;
@ -323,8 +324,10 @@ static int sdhci_cdns_execute_tuning(struct sdhci_host *host, u32 opcode)
* The delay is set by probe, based on the DT properties.
*/
if (host->timing != MMC_TIMING_MMC_HS200 &&
host->timing != MMC_TIMING_UHS_SDR104)
host->timing != MMC_TIMING_UHS_SDR104) {
dev_dbg(mmc_dev(host->mmc), "Tuning skipped (timing: %d)\n", host->timing);
return 0;
}
for (i = 0; i < SDHCI_CDNS_MAX_TUNING_LOOP; i++) {
if (sdhci_cdns_set_tune_val(host, i) ||
@ -348,6 +351,10 @@ static int sdhci_cdns_execute_tuning(struct sdhci_host *host, u32 opcode)
if (ret)
return ret;
/* Block gap tuning is only required for SD4HC, not for SD6HC */
if (host->version >= SDHCI_SPEC_420)
return 0;
return sdhci_cdns_tune_blkgap(host->mmc);
}
@ -383,6 +390,10 @@ static void sdhci_cdns_set_uhs_signaling(struct sdhci_host *host,
/* For SD, fall back to the default handler */
if (mode == SDHCI_CDNS_HRS06_MODE_SD)
sdhci_set_uhs_signaling(host, timing);
/* For host controller V6, set SDHCI and PHY registers for UHS signaling */
if (host->version >= SDHCI_SPEC_420)
sdhci_cdns6_set_uhs_signaling(host, timing);
}
/* Elba control register bits [6:3] are byte-lane enables */
@ -470,7 +481,49 @@ static int elba_drv_init(struct platform_device *pdev)
return 0;
}
static const struct sdhci_ops sdhci_cdns_ops = {
static const struct reset_control_bulk_data sdhci_cdns6_agilex5_reset_ids[] = {
{ .id = "sdhc-reset" },
{ .id = "combophy" },
{ .id = "sdmmc-ocp" },
};
static int sdhci_cdns6_agilex5_init(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct reset_control_bulk_data *resets;
int ret;
resets = devm_kmemdup(dev, sdhci_cdns6_agilex5_reset_ids,
sizeof(sdhci_cdns6_agilex5_reset_ids), GFP_KERNEL);
if (!resets)
return -ENOMEM;
/*
* Assert SDHCI, SoftPHY (combophy), and SDMMC OCP/AXI resets together
* so their active periods overlap before all domains are released.
* SoftPHY is shared with NAND, but only one of SDMMC
* or NAND is enabled on a given board.
*/
ret = devm_reset_control_bulk_get_exclusive(dev, ARRAY_SIZE(sdhci_cdns6_agilex5_reset_ids),
resets);
if (ret)
return dev_err_probe(dev, ret, "failed to get resets\n");
ret = reset_control_bulk_assert(ARRAY_SIZE(sdhci_cdns6_agilex5_reset_ids), resets);
if (ret)
return dev_err_probe(dev, ret, "failed to assert resets\n");
/* Hold resets asserted long enough for all clock domains to capture. */
usleep_range(10, 20);
ret = reset_control_bulk_deassert(ARRAY_SIZE(sdhci_cdns6_agilex5_reset_ids), resets);
if (ret)
return dev_err_probe(dev, ret, "failed to deassert resets\n");
return 0;
}
static const struct sdhci_ops sdhci_cdns4_ops = {
.set_clock = sdhci_set_clock,
.get_timeout_clock = sdhci_cdns_get_timeout_clock,
.set_bus_width = sdhci_set_bus_width,
@ -479,9 +532,31 @@ static const struct sdhci_ops sdhci_cdns_ops = {
.set_uhs_signaling = sdhci_cdns_set_uhs_signaling,
};
static const struct sdhci_ops sdhci_cdns6_ops = {
.set_clock = sdhci_cdns6_set_clock,
.get_timeout_clock = sdhci_cdns_get_timeout_clock,
.set_bus_width = sdhci_set_bus_width,
.reset = sdhci_reset,
.platform_execute_tuning = sdhci_cdns_execute_tuning,
.set_uhs_signaling = sdhci_cdns_set_uhs_signaling,
.hw_reset = sdhci_cdns6_hw_reset,
};
static const struct sdhci_ops sdhci_cdns6_agilex5_ops = {
.set_clock = sdhci_cdns6_set_clock,
.get_max_clock = sdhci_pltfm_clk_get_max_clock,
.get_timeout_clock = sdhci_cdns_get_timeout_clock,
.set_bus_width = sdhci_set_bus_width,
.reset = sdhci_reset,
.platform_execute_tuning = sdhci_cdns_execute_tuning,
.set_uhs_signaling = sdhci_cdns_set_uhs_signaling,
.hw_reset = sdhci_cdns6_hw_reset,
.set_dma_mask = sdhci_cdns_set_dma_mask,
};
static const struct sdhci_cdns_drv_data sdhci_cdns_uniphier_drv_data = {
.pltfm_data = {
.ops = &sdhci_cdns_ops,
.ops = &sdhci_cdns4_ops,
.quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN,
},
};
@ -495,14 +570,32 @@ static const struct sdhci_cdns_drv_data sdhci_elba_drv_data = {
static const struct sdhci_cdns_drv_data sdhci_eyeq_drv_data = {
.pltfm_data = {
.ops = &sdhci_cdns_ops,
.ops = &sdhci_cdns4_ops,
.quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN,
},
};
static const struct sdhci_cdns_drv_data sdhci_cdns_drv_data = {
static const struct sdhci_cdns_drv_data sdhci_cdns4_drv_data = {
.pltfm_data = {
.ops = &sdhci_cdns_ops,
.ops = &sdhci_cdns4_ops,
},
};
static const struct sdhci_cdns_drv_data sdhci_cdns6_agilex5_drv_data = {
.init = sdhci_cdns6_agilex5_init,
.pltfm_data = {
.ops = &sdhci_cdns6_agilex5_ops,
.quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN |
SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12,
.quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN |
SDHCI_QUIRK2_ACMD23_BROKEN,
},
.dma_mask = DMA_BIT_MASK(40),
};
static const struct sdhci_cdns_drv_data sdhci_cdns6_drv_data = {
.pltfm_data = {
.ops = &sdhci_cdns6_ops,
},
};
@ -524,6 +617,14 @@ static void sdhci_cdns_hs400_enhanced_strobe(struct mmc_host *mmc,
if (mode == SDHCI_CDNS_HRS06_MODE_MMC_HS400ES && !ios->enhanced_strobe)
sdhci_cdns_set_emmc_mode(priv,
SDHCI_CDNS_HRS06_MODE_MMC_HS400);
/*
* Reprogram the SD6HC PHY for the current timing. Enhanced strobe
* affects PHY delays, and this callback may run with either HS400
* (enabling/disabling ES) or Legacy (initial bus state).
*/
if (host->version >= SDHCI_SPEC_420)
sdhci_cdns6_set_uhs_signaling(host, ios->timing);
}
static void sdhci_cdns_mmc_hw_reset(struct mmc_host *mmc)
@ -542,6 +643,24 @@ static void sdhci_cdns_mmc_hw_reset(struct mmc_host *mmc)
usleep_range(300, 1000);
}
static int sdhci_cdns4_phy_probe(struct platform_device *pdev, struct sdhci_cdns_priv *priv)
{
struct device *dev = &pdev->dev;
struct sdhci_cdns4_phy *phy;
unsigned int nr_phy_params;
nr_phy_params = sdhci_cdns4_phy_param_count(dev->of_node);
phy = devm_kzalloc(dev, struct_size(phy, phy_params, nr_phy_params), GFP_KERNEL);
if (!phy)
return -ENOMEM;
phy->nr_phy_params = nr_phy_params;
sdhci_cdns4_phy_param_parse(dev->of_node, phy);
priv->phy = phy;
return sdhci_cdns4_phy_init(priv);
}
static int sdhci_cdns_probe(struct platform_device *pdev)
{
struct sdhci_host *host;
@ -549,22 +668,30 @@ static int sdhci_cdns_probe(struct platform_device *pdev)
struct sdhci_pltfm_host *pltfm_host;
struct sdhci_cdns_priv *priv;
struct clk *clk;
unsigned int nr_phy_params;
struct clk *biu_clk;
int ret;
struct device *dev = &pdev->dev;
static const u16 version = SDHCI_SPEC_400 << SDHCI_SPEC_VER_SHIFT;
clk = devm_clk_get_enabled(dev, NULL);
if (IS_ERR(clk))
return PTR_ERR(clk);
if (of_device_is_compatible(dev->of_node, "cdns,sd6hc")) {
clk = devm_clk_get_enabled(dev, "ciu");
if (IS_ERR(clk))
return dev_err_probe(dev, PTR_ERR(clk), "failed to enable ciu clock\n");
biu_clk = devm_clk_get_enabled(dev, "biu");
if (IS_ERR(biu_clk))
return dev_err_probe(dev, PTR_ERR(biu_clk), "failed to enable biu clock\n");
} else {
clk = devm_clk_get_enabled(dev, NULL);
if (IS_ERR(clk))
return PTR_ERR(clk);
}
data = of_device_get_match_data(dev);
if (!data)
data = &sdhci_cdns_drv_data;
return dev_err_probe(dev, -EINVAL, "missing platform driver data\n");
nr_phy_params = sdhci_cdns_phy_param_count(dev->of_node);
host = sdhci_pltfm_init(pdev, &data->pltfm_data,
struct_size(priv, phy_params, nr_phy_params));
host = sdhci_pltfm_init(pdev, &data->pltfm_data, sizeof(*priv));
if (IS_ERR(host))
return PTR_ERR(host);
@ -572,7 +699,6 @@ static int sdhci_cdns_probe(struct platform_device *pdev)
pltfm_host->clk = clk;
priv = sdhci_pltfm_priv(pltfm_host);
priv->nr_phy_params = nr_phy_params;
priv->hrs_addr = host->ioaddr;
priv->enhanced_strobe = false;
priv->priv_writel = cdns_writel;
@ -585,32 +711,46 @@ static int sdhci_cdns_probe(struct platform_device *pdev)
return ret;
}
sdhci_enable_v4_mode(host);
__sdhci_read_caps(host, &version, NULL, NULL);
sdhci_get_of_property(pdev);
ret = mmc_of_parse(host->mmc);
if (ret)
return ret;
sdhci_cdns_phy_param_parse(dev->of_node, priv);
/*
* For SD4HC, read capabilities with fixed version override and set up
* the optional eMMC card RST_n reset control.
* For SD6HC, sdhci_add_host() will automatically read capabilities
* and version from the host controller registers.
*/
if (of_device_is_compatible(dev->of_node, "cdns,sd4hc")) {
__sdhci_read_caps(host, &version, NULL, NULL);
ret = sdhci_cdns4_phy_probe(pdev, priv);
if (ret)
return ret;
ret = sdhci_cdns_phy_init(priv);
if (ret)
return ret;
if (host->mmc->caps & MMC_CAP_HW_RESET) {
priv->rst_hw = devm_reset_control_get_optional_exclusive(dev, NULL);
if (IS_ERR(priv->rst_hw))
return dev_err_probe(mmc_dev(host->mmc), PTR_ERR(priv->rst_hw),
"reset controller error\n");
if (priv->rst_hw)
host->mmc_host_ops.card_hw_reset = sdhci_cdns_mmc_hw_reset;
if (host->mmc->caps & MMC_CAP_HW_RESET) {
priv->rst_hw = devm_reset_control_get_optional_exclusive(dev, NULL);
if (IS_ERR(priv->rst_hw))
return dev_err_probe(mmc_dev(host->mmc), PTR_ERR(priv->rst_hw),
"reset controller error\n");
if (priv->rst_hw)
host->mmc_host_ops.card_hw_reset = sdhci_cdns_mmc_hw_reset;
}
} else {
ret = sdhci_cdns6_phy_probe(pdev, priv);
if (ret)
return ret;
}
return sdhci_add_host(host);
}
/*
* Only the CIU clock is gated on suspend. The SD6HC "biu" clock is not
* toggled here as it may be a shared bus clock; a dedicated biu clock
* would need explicit PM gating added here.
*/
static int sdhci_cdns_resume(struct device *dev)
{
struct sdhci_host *host = dev_get_drvdata(dev);
@ -622,7 +762,11 @@ static int sdhci_cdns_resume(struct device *dev)
if (ret)
return ret;
ret = sdhci_cdns_phy_init(priv);
if (host->version >= SDHCI_SPEC_420)
ret = sdhci_cdns6_phy_init(priv);
else
ret = sdhci_cdns4_phy_init(priv);
if (ret)
goto disable_clk;
@ -653,7 +797,18 @@ static const struct of_device_id sdhci_cdns_match[] = {
.compatible = "mobileye,eyeq-sd4hc",
.data = &sdhci_eyeq_drv_data,
},
{ .compatible = "cdns,sd4hc" },
{
.compatible = "cdns,sd4hc",
.data = &sdhci_cdns4_drv_data,
},
{
.compatible = "altr,agilex5-sd6hc",
.data = &sdhci_cdns6_agilex5_drv_data,
},
{
.compatible = "cdns,sd6hc",
.data = &sdhci_cdns6_drv_data,
},
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, sdhci_cdns_match);

View File

@ -0,0 +1,975 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* PHY and host controller support for Cadence SD6HC SDHCI
*
* This file provides support for Cadence's sixth-generation SDHCI controller (SD6HC).
* Implements PHY initialization, DLL management, per-speed-mode timing calculations,
* and host controller register programming for the SD6HC integrated combo-PHY.
*
* Copyright (C) 2026 Altera Corporation
* Author: Tanmay Kathpalia <tanmay.kathpalia@altera.com>
*/
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/iopoll.h>
#include <linux/of.h>
#include "sdhci-cadence.h"
/* IO Delay Information */
#define SDHCI_CDNS_HRS07 0x1c
#define SDHCI_CDNS_HRS07_RW_COMPENSATE GENMASK(20, 16)
#define SDHCI_CDNS_HRS07_IDELAY_VAL GENMASK(4, 0)
/* PHY Control and Status */
#define SDHCI_CDNS_HRS09 0x24
#define SDHCI_CDNS_HRS09_RDDATA_EN BIT(16)
#define SDHCI_CDNS_HRS09_RDCMD_EN BIT(15)
#define SDHCI_CDNS_HRS09_EXTENDED_WR_MODE BIT(3)
#define SDHCI_CDNS_HRS09_EXTENDED_RD_MODE BIT(2)
#define SDHCI_CDNS_HRS09_PHY_INIT_COMPLETE BIT(1)
#define SDHCI_CDNS_HRS09_PHY_SW_RESET BIT(0)
/* SDCLK start point adjustment */
#define SDHCI_CDNS_HRS10 0x28
#define SDHCI_CDNS_HRS10_HCSDCLKADJ GENMASK(19, 16)
/* eMMC Control */
#define SDHCI_CDNS_HRS11 0x2c
#define SDHCI_CDNS_HRS11_EMMC_RST BIT(0) /* eMMC reset */
/* CMD/DAT output delay */
#define SDHCI_CDNS_HRS16 0x40
#define SDHCI_CDNS_HRS16_WRDATA1_SDCLK_DLY GENMASK(31, 28)
#define SDHCI_CDNS_HRS16_WRDATA0_SDCLK_DLY GENMASK(27, 24)
#define SDHCI_CDNS_HRS16_WRCMD1_SDCLK_DLY GENMASK(23, 20)
#define SDHCI_CDNS_HRS16_WRCMD0_SDCLK_DLY GENMASK(19, 16)
#define SDHCI_CDNS_HRS16_WRDATA1_DLY GENMASK(15, 12)
#define SDHCI_CDNS_HRS16_WRDATA0_DLY GENMASK(11, 8)
#define SDHCI_CDNS_HRS16_WRCMD1_DLY GENMASK(7, 4)
#define SDHCI_CDNS_HRS16_WRCMD0_DLY GENMASK(3, 0)
/* PHY Special Function Registers */
/* DQ timing */
#define SDHCI_CDNS6_PHY_DQ_TIMING_REG 0x2000
#define SDHCI_CDNS6_PHY_DQ_TIMING_IO_MASK_ALWAYS_ON BIT(31)
#define SDHCI_CDNS6_PHY_DQ_TIMING_IO_MASK_END GENMASK(29, 27)
#define SDHCI_CDNS6_PHY_DQ_TIMING_IO_MASK_START GENMASK(26, 24)
#define SDHCI_CDNS6_PHY_DQ_TIMING_DATA_SELECT_OE_END GENMASK(2, 0)
/* DQS timing */
#define SDHCI_CDNS6_PHY_DQS_TIMING_REG 0x2004
#define SDHCI_CDNS6_PHY_DQS_TIMING_USE_EXT_LPBK_DQS BIT(22)
#define SDHCI_CDNS6_PHY_DQS_TIMING_USE_LPBK_DQS BIT(21)
#define SDHCI_CDNS6_PHY_DQS_TIMING_USE_PHONY_DQS BIT(20)
#define SDHCI_CDNS6_PHY_DQS_TIMING_USE_PHONY_DQS_CMD BIT(19)
/* Gate and loopback control */
#define SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_REG 0x2008
#define SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_SYNC_METHOD BIT(31)
#define SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_RD_DEL_SEL GENMASK(24, 19)
#define SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_UNDERRUN_SUPPRESS BIT(18)
#define SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_GATE_CFG_ALWAYS_ON BIT(6)
/* Master DLL logic */
#define SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_REG 0x200c
#define SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_BYPASS_MODE BIT(23)
#define SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_PHASE_DETECT_SEL GENMASK(22, 20)
#define SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_DLL_LOCK_NUM GENMASK(18, 16)
#define SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_DLL_START_POINT GENMASK(7, 0)
/* Slave DLL logic */
#define SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_REG 0x2010
#define SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_READ_DQS_CMD_DELAY GENMASK(31, 24)
#define SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_CLK_WRDQS_DELAY GENMASK(23, 16)
#define SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_CLK_WR_DELAY GENMASK(15, 8)
#define SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_READ_DQS_DELAY GENMASK(7, 0)
/* Global control settings */
#define SDHCI_CDNS6_PHY_CTRL_REG 0x2080
#define SDHCI_CDNS6_PHY_CTRL_PHONY_DQS_TIMING GENMASK(9, 4)
/* Default PHY settings */
#define SDHCI_CDNS6_PHY_DEFAULT_IOCELL_DELAY 2500
#define SDHCI_CDNS6_PHY_DEFAULT_DELAY_ELEMENT 24
#define SDHCI_CDNS6_PHY_DEFAULT_RD_DEL_SEL 52
#define SDHCI_CDNS6_PHY_DEFAULT_DLL_START 4
#define SDHCI_CDNS6_PHY_DEFAULT_PHASE_DETECT_SEL 2
#define SDHCI_CDNS6_PHY_DEFAULT_DLL_LOCK_NUM 0
#define SDHCI_CDNS6_PHY_DEFAULT_DATA_SELECT_OE_END 1
/* Scale tuning tap (0..39) to 8-bit PHY DLL delay field (0..255) */
#define SDHCI_CDNS6_PHY_DLL_FIELD_SIZE 256
struct sdhci_cdns6_phy {
/*
* Mode-specific timing constraints (in picoseconds)
* These define valid output windows per SD/eMMC spec
*/
u32 t_cmd_output_min;
u32 t_cmd_output_max;
u32 t_dat_output_min;
u32 t_dat_output_max;
/*
* PHY delay configuration (in picoseconds)
* Derived from clock period and board-level IO cell delays
*/
u32 phy_sdclk_delay;
u32 phy_cmd_o_delay;
u32 phy_dat_o_delay;
u32 iocell_input_delay;
u32 iocell_output_delay;
/* Configured delay element (ps); preserved across clock changes */
u32 delay_element_org;
/* Active delay element (ps); doubled when one SDMCLK requires > 256 steps */
u32 delay_element;
/* PHY_DLL_SLAVE_CTRL register fields */
u8 cp_read_dqs_cmd_delay; /* bits [31:24] */
u8 cp_clk_wrdqs_delay; /* bits [23:16] */
u8 cp_clk_wr_delay; /* bits [15:8] */
u8 cp_read_dqs_delay; /* bits [7:0] */
/* PHY_DLL_MASTER_CTRL register fields */
bool cp_dll_bypass_mode; /* bit [23] */
/* PHY_DQ_TIMING register fields */
u8 cp_io_mask_end; /* bits [29:27] */
u8 cp_io_mask_start; /* bits [26:24] */
/* PHY_DQS_TIMING register fields */
bool cp_use_phony_dqs; /* bit [20] */
bool cp_use_phony_dqs_cmd; /* bit [19] */
/* HRS07 register - IO delay Information */
u8 sdhc_rw_compensate; /* bits [20:16] */
u8 sdhc_idelay_val; /* bits [4:0] */
/* HRS09 register - PHY control and Status */
bool sdhc_extended_wr_mode; /* bit [3] */
bool sdhc_extended_rd_mode; /* bit [2] */
/* HRS10 register - SDCLK start point adjustment */
u8 sdhc_hcsdclkadj; /* bits [19:16] */
/* HRS16 register fields - CMD/DAT output delay control */
u8 sdhc_wrdata1_sdclk_dly; /* bits [31:28] */
u8 sdhc_wrdata0_sdclk_dly; /* bits [27:24] */
u8 sdhc_wrcmd1_sdclk_dly; /* bits [23:20] */
u8 sdhc_wrcmd0_sdclk_dly; /* bits [19:16] */
u8 sdhc_wrdata1_dly; /* bits [15:12] */
u8 sdhc_wrdata0_dly; /* bits [11:8] */
u8 sdhc_wrcmd1_dly; /* bits [7:4] */
u8 sdhc_wrcmd0_dly; /* bits [3:0] */
/* DLL calculation intermediate values, used during PHY timing calculations */
u32 t_sdmclk_calc; /* DLL-quantized SDMCLK period */
u32 dll_max_value; /* DLL delay field ceiling */
/* Tuning value for HS200/HS400 modes */
u8 hs200_tune_val;
/* Clock periods (in picoseconds) */
u32 t_sdmclk; /* Master clock period */
u32 t_sdclk; /* SD card clock period */
/* Current operating state */
bool strobe_cmd; /* Enhanced strobe for CMD line */
unsigned int mode; /* Current MMC_TIMING_* mode */
};
/**
* init_ds() - Initialize PHY timing for Default Speed mode (25 MHz).
* @phy: Pointer to SD6HC PHY state.
* @t_sdclk: SD clock period in picoseconds.
*/
static void init_ds(struct sdhci_cdns6_phy *phy, u32 t_sdclk)
{
phy->t_cmd_output_min = 5000;
phy->t_cmd_output_max = t_sdclk - 5000;
phy->t_dat_output_min = 5000;
phy->t_dat_output_max = t_sdclk - 5000;
}
/**
* init_hs() - Initialize PHY timing for High Speed mode (50 MHz).
* @phy: Pointer to SD6HC PHY state.
* @t_sdclk: SD clock period in picoseconds.
*/
static void init_hs(struct sdhci_cdns6_phy *phy, u32 t_sdclk)
{
phy->t_cmd_output_min = 2000;
phy->t_cmd_output_max = t_sdclk - 6000;
phy->t_dat_output_min = 2000;
phy->t_dat_output_max = t_sdclk - 6000;
}
/**
* init_uhs_sdr12() - Initialize PHY timing for UHS SDR12 mode (25 MHz).
* @phy: Pointer to SD6HC PHY state.
* @t_sdclk: SD clock period in picoseconds.
*/
static void init_uhs_sdr12(struct sdhci_cdns6_phy *phy, u32 t_sdclk)
{
phy->t_cmd_output_min = 800;
phy->t_cmd_output_max = t_sdclk - 3000;
phy->t_dat_output_min = 800;
phy->t_dat_output_max = t_sdclk - 3000;
}
/**
* init_uhs_sdr25() - Initialize PHY timing for UHS SDR25 mode (50 MHz).
* @phy: Pointer to SD6HC PHY state.
* @t_sdclk: SD clock period in picoseconds.
*/
static void init_uhs_sdr25(struct sdhci_cdns6_phy *phy, u32 t_sdclk)
{
phy->t_cmd_output_min = 800;
phy->t_cmd_output_max = t_sdclk - 3000;
phy->t_dat_output_min = 800;
phy->t_dat_output_max = t_sdclk - 3000;
}
/**
* init_uhs_sdr50() - Initialize PHY timing for UHS SDR50 mode (100 MHz).
* @phy: Pointer to SD6HC PHY state.
* @t_sdclk: SD clock period in picoseconds.
*/
static void init_uhs_sdr50(struct sdhci_cdns6_phy *phy, u32 t_sdclk)
{
phy->t_cmd_output_min = 800;
phy->t_cmd_output_max = t_sdclk - 3000;
phy->t_dat_output_min = 800;
phy->t_dat_output_max = t_sdclk - 3000;
}
/**
* init_uhs_sdr104() - Initialize PHY timing for UHS SDR104 mode (200 MHz).
* @phy: Pointer to SD6HC PHY state.
* @t_sdclk: SD clock period in picoseconds.
*/
static void init_uhs_sdr104(struct sdhci_cdns6_phy *phy, u32 t_sdclk)
{
phy->t_cmd_output_min = 800;
phy->t_cmd_output_max = t_sdclk - 1400;
phy->t_dat_output_min = 800;
phy->t_dat_output_max = t_sdclk - 1400;
}
/**
* init_uhs_ddr50() - Initialize PHY timing for UHS DDR50 mode (50 MHz).
* @phy: Pointer to SD6HC PHY state.
* @t_sdclk: SD clock period in picoseconds.
*/
static void init_uhs_ddr50(struct sdhci_cdns6_phy *phy, u32 t_sdclk)
{
phy->t_cmd_output_min = 800;
phy->t_cmd_output_max = t_sdclk - 3000;
phy->t_dat_output_min = 800;
phy->t_dat_output_max = t_sdclk - 3000;
}
/**
* init_emmc_sdr() - Initialize PHY timing for eMMC legacy/SDR mode.
* @phy: Pointer to SD6HC PHY state.
* @t_sdclk: SD clock period in picoseconds.
*/
static void init_emmc_sdr(struct sdhci_cdns6_phy *phy, u32 t_sdclk)
{
phy->t_cmd_output_min = 3000;
phy->t_cmd_output_max = t_sdclk - 3000;
phy->t_dat_output_min = 3000;
phy->t_dat_output_max = t_sdclk - 3000;
}
/**
* init_emmc_ddr() - Initialize PHY timing for eMMC DDR52 mode.
* @phy: Pointer to SD6HC PHY state.
* @t_sdclk: SD clock period in picoseconds.
*/
static void init_emmc_ddr(struct sdhci_cdns6_phy *phy, u32 t_sdclk)
{
phy->t_cmd_output_min = 3000;
phy->t_cmd_output_max = t_sdclk - 3000;
phy->t_dat_output_min = 2500;
phy->t_dat_output_max = t_sdclk - 2500;
}
/**
* init_emmc_hs200() - Initialize PHY timing for eMMC HS200 mode (200 MHz).
* @phy: Pointer to SD6HC PHY state.
* @t_sdclk: SD clock period in picoseconds.
*/
static void init_emmc_hs200(struct sdhci_cdns6_phy *phy, u32 t_sdclk)
{
phy->t_cmd_output_min = 800;
phy->t_cmd_output_max = t_sdclk - 1400;
phy->t_dat_output_min = 800;
phy->t_dat_output_max = t_sdclk - 1400;
}
/**
* init_emmc_hs400() - Initialize PHY timing for eMMC HS400/HS400ES mode.
* @phy: Pointer to SD6HC PHY state.
* @t_sdclk: SD clock period in picoseconds.
*/
static void init_emmc_hs400(struct sdhci_cdns6_phy *phy, u32 t_sdclk)
{
phy->t_cmd_output_min = 800;
phy->t_cmd_output_max = t_sdclk - 1400;
phy->t_dat_output_min = 400;
phy->t_dat_output_max = t_sdclk - 400;
}
/*
* init_timings - PHY timing initializers indexed by MMC_TIMING_* value.
*
* Each entry corresponds to a MMC_TIMING_* constant and sets the appropriate cmd/dat
* output timing windows in the PHY state struct.
*/
static void (* const init_timings[])(struct sdhci_cdns6_phy *, u32) = {
[MMC_TIMING_LEGACY] = init_ds,
[MMC_TIMING_MMC_HS] = init_emmc_sdr,
[MMC_TIMING_SD_HS] = init_hs,
[MMC_TIMING_UHS_SDR12] = init_uhs_sdr12,
[MMC_TIMING_UHS_SDR25] = init_uhs_sdr25,
[MMC_TIMING_UHS_SDR50] = init_uhs_sdr50,
[MMC_TIMING_UHS_SDR104] = init_uhs_sdr104,
[MMC_TIMING_UHS_DDR50] = init_uhs_ddr50,
[MMC_TIMING_MMC_DDR52] = init_emmc_ddr,
[MMC_TIMING_MMC_HS200] = init_emmc_hs200,
[MMC_TIMING_MMC_HS400] = init_emmc_hs400,
};
static unsigned int sdhci_cdns6_read_phy_reg(struct sdhci_cdns_priv *priv, const u32 address)
{
writel(address, priv->hrs_addr + SDHCI_CDNS_HRS04);
return readl(priv->hrs_addr + SDHCI_CDNS_HRS05);
}
static void sdhci_cdns6_write_phy_reg(struct sdhci_cdns_priv *priv, const u32 address,
const u32 value)
{
writel(address, priv->hrs_addr + SDHCI_CDNS_HRS04);
writel(value, priv->hrs_addr + SDHCI_CDNS_HRS05);
}
static int sdhci_cdns6_phy_lock_dll(struct sdhci_cdns6_phy *phy)
{
u32 delay_element = phy->delay_element_org;
u32 delay_elements_in_sdmclk;
delay_elements_in_sdmclk = DIV_ROUND_UP(phy->t_sdmclk, delay_element);
if (delay_elements_in_sdmclk > 256) {
delay_element *= 2;
delay_elements_in_sdmclk = DIV_ROUND_UP(phy->t_sdmclk, delay_element);
if (delay_elements_in_sdmclk > 256)
return -EINVAL;
phy->dll_max_value = 127;
} else {
phy->dll_max_value = 255;
}
phy->t_sdmclk_calc = delay_element * delay_elements_in_sdmclk;
phy->delay_element = delay_element;
phy->cp_dll_bypass_mode = false;
return 0;
}
static void sdhci_cdns6_phy_dll_bypass(struct sdhci_cdns6_phy *phy)
{
phy->dll_max_value = 255;
phy->cp_dll_bypass_mode = true;
}
static void sdhci_cdns6_phy_configure_dll(struct sdhci_cdns6_phy *phy)
{
if (!phy->sdhc_extended_wr_mode) {
if (sdhci_cdns6_phy_lock_dll(phy) == 0)
return;
}
sdhci_cdns6_phy_dll_bypass(phy);
}
static void sdhci_cdns6_phy_calc_out(struct sdhci_cdns6_phy *phy, bool cmd_not_dat)
{
u32 wr0_dly = 0, wr1_dly = 0, output_min, output_max, phy_o_delay,
clk_wr_delay = 0, wr0_sdclk_dly = 0, wr1_sdclk_dly = 0;
bool ddr = (phy->mode == MMC_TIMING_UHS_DDR50) || (phy->mode == MMC_TIMING_MMC_DDR52) ||
(phy->mode == MMC_TIMING_MMC_HS400);
bool data_ddr = ddr && !cmd_not_dat;
int t;
if (cmd_not_dat) {
output_min = phy->t_cmd_output_min;
output_max = phy->t_cmd_output_max;
phy_o_delay = phy->phy_cmd_o_delay;
} else {
output_min = phy->t_dat_output_min;
output_max = phy->t_dat_output_max;
phy_o_delay = phy->phy_dat_o_delay;
}
if (data_ddr) {
wr0_sdclk_dly = 1;
wr1_sdclk_dly = 1;
}
t = phy_o_delay - phy->phy_sdclk_delay - output_min;
if (t < 0 && phy->sdhc_extended_wr_mode) {
u32 n_half_cycle = DIV_ROUND_UP(-t * 2, phy->t_sdmclk);
wr0_dly = (n_half_cycle + 1) / 2;
if (data_ddr)
wr1_dly = (n_half_cycle + 1) / 2;
else
wr1_dly = (n_half_cycle + 1) % 2 + wr0_dly - 1;
}
if (!phy->sdhc_extended_wr_mode) {
u32 out_hold, out_setup, out_hold_margin;
u32 n;
if (!data_ddr)
wr0_dly = 1;
out_setup = output_max;
out_hold = output_min;
out_hold_margin = DIV_ROUND_UP(out_setup - out_hold, 4);
out_hold += out_hold_margin;
if (!phy->cp_dll_bypass_mode)
n = DIV_ROUND_UP(256 * out_hold, phy->t_sdmclk_calc);
else
n = DIV_ROUND_UP(out_hold, phy->delay_element) - 1;
if (n <= phy->dll_max_value)
clk_wr_delay = n;
else
clk_wr_delay = 255;
} else {
/* sdhc_extended_wr_mode set => PHY IO cell work in SDR mode */
clk_wr_delay = 0;
}
if (cmd_not_dat) {
phy->sdhc_wrcmd0_dly = wr0_dly;
phy->sdhc_wrcmd1_dly = wr1_dly;
phy->cp_clk_wrdqs_delay = clk_wr_delay;
phy->sdhc_wrcmd0_sdclk_dly = wr0_sdclk_dly;
phy->sdhc_wrcmd1_sdclk_dly = wr1_sdclk_dly;
} else {
phy->sdhc_wrdata0_dly = wr0_dly;
phy->sdhc_wrdata1_dly = wr1_dly;
phy->cp_clk_wr_delay = clk_wr_delay;
phy->sdhc_wrdata0_sdclk_dly = wr0_sdclk_dly;
phy->sdhc_wrdata1_sdclk_dly = wr1_sdclk_dly;
}
}
static void sdhci_cdns6_phy_calc_cmd_out(struct sdhci_cdns6_phy *phy)
{
sdhci_cdns6_phy_calc_out(phy, true);
}
static void sdhci_cdns6_phy_calc_cmd_in(struct sdhci_cdns6_phy *phy)
{
phy->cp_io_mask_end = ((phy->iocell_output_delay + phy->iocell_input_delay) * 2) /
phy->t_sdmclk;
/* cp_io_mask_end is a 3-bit field, clamp to max value of 7 */
phy->cp_io_mask_end = min_t(u8, phy->cp_io_mask_end, 7);
if (phy->strobe_cmd && phy->cp_io_mask_end > 0)
phy->cp_io_mask_end--;
if (phy->strobe_cmd) {
phy->cp_use_phony_dqs_cmd = false;
phy->cp_read_dqs_cmd_delay = 64;
} else {
phy->cp_use_phony_dqs_cmd = true;
phy->cp_read_dqs_cmd_delay = 0;
}
if ((phy->mode == MMC_TIMING_MMC_HS400 && !phy->strobe_cmd) ||
phy->mode == MMC_TIMING_MMC_HS200)
phy->cp_read_dqs_cmd_delay = phy->hs200_tune_val;
}
static void sdhci_cdns6_phy_calc_dat_in(struct sdhci_cdns6_phy *phy)
{
u32 hcsdclkadj = 0;
bool strobe_dat = (phy->mode == MMC_TIMING_MMC_HS400);
if (strobe_dat) {
phy->cp_use_phony_dqs = false;
phy->cp_read_dqs_delay = 64;
} else {
phy->cp_use_phony_dqs = true;
phy->cp_read_dqs_delay = 0;
}
if (phy->mode == MMC_TIMING_MMC_HS200)
phy->cp_read_dqs_delay = phy->hs200_tune_val;
if (strobe_dat) {
/* dqs loopback input via IO cell */
hcsdclkadj += phy->iocell_input_delay;
/* dfi_dqs_in: mem_dqs -> clean_dqs_mod; delay of hic_dll_dqs_nand2 */
hcsdclkadj += phy->delay_element / 2;
/* delay line */
hcsdclkadj += phy->t_sdclk / 2;
/* PHY FIFO write pointer */
hcsdclkadj += phy->t_sdclk / 2 + phy->delay_element;
/* 1st synchronizer */
hcsdclkadj += DIV_ROUND_UP(hcsdclkadj, phy->t_sdmclk) * phy->t_sdmclk - hcsdclkadj;
/*
* 2nd synchronizer + PHY FIFO read pointer + PHY rddata
* + PHY rddata registered, + FIFO 1st ciu_en
*/
hcsdclkadj += 5 * phy->t_sdmclk;
/* FIFO 2nd ciu_en */
hcsdclkadj += phy->t_sdclk;
hcsdclkadj /= phy->t_sdclk;
} else {
u32 n;
/* rebar PHY delay */
hcsdclkadj += 2 * phy->t_sdmclk;
/* rebar output via IO cell */
hcsdclkadj += phy->iocell_output_delay;
/* dqs loopback input via IO cell */
hcsdclkadj += phy->iocell_input_delay;
/* dfi_dqs_in: mem_dqs -> clean_dqs_mod delay of hic_dll_dqs_nand2 */
hcsdclkadj += phy->delay_element / 2;
/* dll: one delay element between SIGI_0 and SIGO_0 */
hcsdclkadj += phy->delay_element;
/* dfi_dqs_in: mem_dqs_delayed -> clk_dqs delay of hic_dll_dqs_nand2 */
hcsdclkadj += phy->delay_element / 2;
/* deskew DLL: clk_dqs -> clk_dqN: one delay element */
hcsdclkadj += phy->delay_element;
if (phy->t_sdclk == phy->t_sdmclk)
n = (hcsdclkadj - 2 * phy->t_sdmclk) / phy->t_sdclk;
else
n = hcsdclkadj / phy->t_sdclk;
/* phase shift within one t_sdclk clock cycle caused by rebar - lbk dqs delay */
hcsdclkadj = hcsdclkadj % phy->t_sdclk;
/* PHY FIFO write pointer */
hcsdclkadj += phy->t_sdclk / 2;
/* 1st synchronizer */
hcsdclkadj += DIV_ROUND_UP(hcsdclkadj, phy->t_sdmclk) * phy->t_sdmclk - hcsdclkadj;
/*
* 2nd synchronizer + PHY FIFO read pointer + PHY rddata + PHY rddata registered
*/
hcsdclkadj += 4 * phy->t_sdmclk;
if ((phy->t_sdclk / phy->t_sdmclk) > 1) {
u32 tmp1, tmp2;
tmp1 = hcsdclkadj;
tmp2 = (hcsdclkadj / phy->t_sdclk) * phy->t_sdclk + phy->t_sdclk -
phy->t_sdmclk;
if (tmp1 == tmp2)
tmp2 += phy->t_sdclk;
/* FIFO aligns to clock cycle before ciu_en */
hcsdclkadj += tmp2 - tmp1;
}
/* FIFO 1st ciu_en */
hcsdclkadj += phy->t_sdmclk;
/* FIFO 2nd ciu_en */
hcsdclkadj += phy->t_sdclk;
hcsdclkadj /= phy->t_sdclk;
hcsdclkadj += n;
if ((phy->t_sdclk / phy->t_sdmclk) >= 2) {
if (phy->mode == MMC_TIMING_UHS_DDR50 || phy->mode == MMC_TIMING_MMC_DDR52)
hcsdclkadj -= 2;
else
hcsdclkadj -= 1;
} else if ((phy->t_sdclk / phy->t_sdmclk) == 1) {
hcsdclkadj += 2;
}
if (phy->mode == MMC_TIMING_UHS_SDR104 || phy->mode == MMC_TIMING_MMC_HS200)
hcsdclkadj -= 1;
}
/* hcsdclkadj is a 4-bit field, clamp to max value of 15 */
if (hcsdclkadj > 15)
hcsdclkadj = 15;
phy->sdhc_hcsdclkadj = hcsdclkadj;
}
static void sdhci_cdns6_phy_calc_dat_out(struct sdhci_cdns6_phy *phy)
{
sdhci_cdns6_phy_calc_out(phy, false);
}
static void sdhci_cdns6_phy_calc_io(struct sdhci_cdns6_phy *phy)
{
u32 rw_compensate;
rw_compensate = ((phy->iocell_input_delay + phy->iocell_output_delay) / phy->t_sdmclk) +
phy->sdhc_wrdata0_dly + 5 + 3;
phy->sdhc_idelay_val = (2 * phy->iocell_input_delay) / phy->t_sdmclk;
phy->cp_io_mask_start = 0;
if (phy->t_sdclk == phy->t_sdmclk && rw_compensate > 10)
phy->cp_io_mask_start = 2 * (rw_compensate - 10);
if (phy->mode == MMC_TIMING_UHS_SDR104)
phy->cp_io_mask_start++;
if (phy->t_sdclk == phy->t_sdmclk && phy->mode == MMC_TIMING_UHS_SDR50)
phy->cp_io_mask_start++;
/* cp_io_mask_start is a 3-bit field, clamp to max value of 7 */
phy->cp_io_mask_start = min_t(u8, phy->cp_io_mask_start, 7);
phy->sdhc_rw_compensate = rw_compensate;
}
static void sdhci_cdns6_phy_calc_settings(struct sdhci_cdns6_phy *phy)
{
sdhci_cdns6_phy_calc_cmd_out(phy);
sdhci_cdns6_phy_calc_cmd_in(phy);
sdhci_cdns6_phy_calc_dat_out(phy);
sdhci_cdns6_phy_calc_dat_in(phy);
sdhci_cdns6_phy_calc_io(phy);
}
static int sdhci_cdns6_dll_reset(struct sdhci_cdns_priv *priv, bool reset)
{
u32 reg;
int ret = 0;
reg = readl(priv->hrs_addr + SDHCI_CDNS_HRS09);
if (reset)
reg &= ~SDHCI_CDNS_HRS09_PHY_SW_RESET;
else
reg |= SDHCI_CDNS_HRS09_PHY_SW_RESET;
writel(reg, priv->hrs_addr + SDHCI_CDNS_HRS09);
/* After releasing PHY from reset, wait until PHY_INIT_COMPLETE is set within 3000us */
if (!reset) {
ret = readl_poll_timeout(priv->hrs_addr + SDHCI_CDNS_HRS09, reg, (reg &
SDHCI_CDNS_HRS09_PHY_INIT_COMPLETE), 0, 3000);
}
return ret;
}
int sdhci_cdns6_phy_init(struct sdhci_cdns_priv *priv)
{
struct sdhci_cdns6_phy *phy = priv->phy;
u32 reg;
int ret;
sdhci_cdns6_dll_reset(priv, true);
reg = sdhci_cdns6_read_phy_reg(priv, SDHCI_CDNS6_PHY_DQS_TIMING_REG);
reg &= ~SDHCI_CDNS6_PHY_DQS_TIMING_USE_PHONY_DQS;
reg &= ~SDHCI_CDNS6_PHY_DQS_TIMING_USE_PHONY_DQS_CMD;
reg |= SDHCI_CDNS6_PHY_DQS_TIMING_USE_EXT_LPBK_DQS;
reg |= SDHCI_CDNS6_PHY_DQS_TIMING_USE_LPBK_DQS;
reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DQS_TIMING_USE_PHONY_DQS, phy->cp_use_phony_dqs);
reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DQS_TIMING_USE_PHONY_DQS_CMD, phy->cp_use_phony_dqs_cmd);
sdhci_cdns6_write_phy_reg(priv, SDHCI_CDNS6_PHY_DQS_TIMING_REG, reg);
reg = sdhci_cdns6_read_phy_reg(priv, SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_REG);
reg &= ~SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_RD_DEL_SEL;
reg |= SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_UNDERRUN_SUPPRESS;
reg |= SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_GATE_CFG_ALWAYS_ON;
reg |= SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_SYNC_METHOD;
reg |= FIELD_PREP(SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_RD_DEL_SEL,
SDHCI_CDNS6_PHY_DEFAULT_RD_DEL_SEL);
sdhci_cdns6_write_phy_reg(priv, SDHCI_CDNS6_PHY_GATE_LPBK_CTRL_REG, reg);
reg = FIELD_PREP(SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_BYPASS_MODE, phy->cp_dll_bypass_mode);
reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_PHASE_DETECT_SEL,
SDHCI_CDNS6_PHY_DEFAULT_PHASE_DETECT_SEL);
reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_DLL_LOCK_NUM,
SDHCI_CDNS6_PHY_DEFAULT_DLL_LOCK_NUM);
reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_DLL_START_POINT,
SDHCI_CDNS6_PHY_DEFAULT_DLL_START);
sdhci_cdns6_write_phy_reg(priv, SDHCI_CDNS6_PHY_DLL_MASTER_CTRL_REG, reg);
reg = FIELD_PREP(SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_READ_DQS_CMD_DELAY,
phy->cp_read_dqs_cmd_delay);
reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_CLK_WRDQS_DELAY, phy->cp_clk_wrdqs_delay);
reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_CLK_WR_DELAY, phy->cp_clk_wr_delay);
reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_READ_DQS_DELAY, phy->cp_read_dqs_delay);
sdhci_cdns6_write_phy_reg(priv, SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_REG, reg);
reg = sdhci_cdns6_read_phy_reg(priv, SDHCI_CDNS6_PHY_CTRL_REG);
reg &= ~SDHCI_CDNS6_PHY_CTRL_PHONY_DQS_TIMING;
sdhci_cdns6_write_phy_reg(priv, SDHCI_CDNS6_PHY_CTRL_REG, reg);
/*
* Ensure all preceding PHY register writes complete and reach the controller before
* releasing the PHY from reset. Without this, SDR104 has been observed to fail
* intermittently on some boards.
*/
wmb();
ret = sdhci_cdns6_dll_reset(priv, false);
if (ret)
return ret;
reg = sdhci_cdns6_read_phy_reg(priv, SDHCI_CDNS6_PHY_DQ_TIMING_REG);
reg &= ~SDHCI_CDNS6_PHY_DQ_TIMING_IO_MASK_ALWAYS_ON;
reg &= ~SDHCI_CDNS6_PHY_DQ_TIMING_IO_MASK_END;
reg &= ~SDHCI_CDNS6_PHY_DQ_TIMING_IO_MASK_START;
reg &= ~SDHCI_CDNS6_PHY_DQ_TIMING_DATA_SELECT_OE_END;
reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DQ_TIMING_IO_MASK_END, phy->cp_io_mask_end);
reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DQ_TIMING_IO_MASK_START, phy->cp_io_mask_start);
reg |= FIELD_PREP(SDHCI_CDNS6_PHY_DQ_TIMING_DATA_SELECT_OE_END,
SDHCI_CDNS6_PHY_DEFAULT_DATA_SELECT_OE_END);
sdhci_cdns6_write_phy_reg(priv, SDHCI_CDNS6_PHY_DQ_TIMING_REG, reg);
/* Ensure DQ timing programming is visible before HRS09 follow-up writes */
wmb();
reg = readl(priv->hrs_addr + SDHCI_CDNS_HRS09);
if (phy->sdhc_extended_wr_mode)
reg |= SDHCI_CDNS_HRS09_EXTENDED_WR_MODE;
else
reg &= ~SDHCI_CDNS_HRS09_EXTENDED_WR_MODE;
if (phy->sdhc_extended_rd_mode)
reg |= SDHCI_CDNS_HRS09_EXTENDED_RD_MODE;
else
reg &= ~SDHCI_CDNS_HRS09_EXTENDED_RD_MODE;
reg |= SDHCI_CDNS_HRS09_RDDATA_EN;
reg |= SDHCI_CDNS_HRS09_RDCMD_EN;
writel(reg, priv->hrs_addr + SDHCI_CDNS_HRS09);
reg = FIELD_PREP(SDHCI_CDNS_HRS10_HCSDCLKADJ, phy->sdhc_hcsdclkadj);
writel(reg, priv->hrs_addr + SDHCI_CDNS_HRS10);
reg = FIELD_PREP(SDHCI_CDNS_HRS16_WRDATA1_SDCLK_DLY, phy->sdhc_wrdata1_sdclk_dly);
reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRDATA0_SDCLK_DLY, phy->sdhc_wrdata0_sdclk_dly);
reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRCMD1_SDCLK_DLY, phy->sdhc_wrcmd1_sdclk_dly);
reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRCMD0_SDCLK_DLY, phy->sdhc_wrcmd0_sdclk_dly);
reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRDATA1_DLY, phy->sdhc_wrdata1_dly);
reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRDATA0_DLY, phy->sdhc_wrdata0_dly);
reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRCMD1_DLY, phy->sdhc_wrcmd1_dly);
reg |= FIELD_PREP(SDHCI_CDNS_HRS16_WRCMD0_DLY, phy->sdhc_wrcmd0_dly);
writel(reg, priv->hrs_addr + SDHCI_CDNS_HRS16);
reg = FIELD_PREP(SDHCI_CDNS_HRS07_RW_COMPENSATE, phy->sdhc_rw_compensate);
reg |= FIELD_PREP(SDHCI_CDNS_HRS07_IDELAY_VAL, phy->sdhc_idelay_val);
writel(reg, priv->hrs_addr + SDHCI_CDNS_HRS07);
/* Allow 5 to 5.5 ms for clock and PHY signals to stabilize after configuration */
usleep_range(5000, 5500);
return 0;
}
int sdhci_cdns6_set_tune_val(struct sdhci_host *host, unsigned int val)
{
struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
struct sdhci_cdns6_phy *phy = priv->phy;
u32 tmp, tuneval;
int ret;
/*
* Scale tuning tap (val in [0, SDHCI_CDNS_MAX_TUNING_LOOP-1]) to the
* 8-bit PHY DLL slave delay field [0, 255]. With MAX_TUNING_LOOP=40
* and FIELD_SIZE=256, the result fits in 8 bits.
*/
tuneval = (val * SDHCI_CDNS6_PHY_DLL_FIELD_SIZE) / SDHCI_CDNS_MAX_TUNING_LOOP;
phy->hs200_tune_val = tuneval;
phy->cp_read_dqs_cmd_delay = tuneval;
phy->cp_read_dqs_delay = tuneval;
tmp = sdhci_cdns6_read_phy_reg(priv, SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_REG);
tmp &= ~(SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_READ_DQS_CMD_DELAY |
SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_READ_DQS_DELAY);
tmp |= FIELD_PREP(SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_READ_DQS_CMD_DELAY, tuneval);
tmp |= FIELD_PREP(SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_READ_DQS_DELAY, tuneval);
sdhci_cdns6_dll_reset(priv, true);
sdhci_cdns6_write_phy_reg(priv, SDHCI_CDNS6_PHY_DLL_SLAVE_CTRL_REG, tmp);
ret = sdhci_cdns6_dll_reset(priv, false);
if (ret)
dev_warn(mmc_dev(host->mmc), "%s: DLL reset release failed: %d\n", __func__, ret);
return ret;
}
static int sdhci_cdns6_phy_update_timings(struct sdhci_host *host)
{
struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
struct sdhci_cdns6_phy *phy = priv->phy;
u32 t_sdmclk = phy->t_sdmclk;
/* Validate mode is within supported range */
if (phy->mode >= ARRAY_SIZE(init_timings))
return -EINVAL;
/* initialize input */
init_timings[phy->mode](phy, phy->t_sdclk);
phy->strobe_cmd = false;
if (priv->enhanced_strobe)
phy->strobe_cmd = true;
phy->phy_sdclk_delay = 2 * t_sdmclk;
/*
* CMD and DAT output delays are currently identical, but kept separate to allow
* independent tuning for specific modes (e.g., HS400) or board-specific optimizations
* in the future.
*/
phy->phy_cmd_o_delay = 2 * t_sdmclk + t_sdmclk / 2;
phy->phy_dat_o_delay = 2 * t_sdmclk + t_sdmclk / 2;
if (phy->t_sdclk == phy->t_sdmclk) {
phy->sdhc_extended_wr_mode = false;
phy->sdhc_extended_rd_mode = false;
} else {
phy->sdhc_extended_wr_mode = true;
phy->sdhc_extended_rd_mode = true;
}
sdhci_cdns6_phy_configure_dll(phy);
sdhci_cdns6_phy_calc_settings(phy);
return 0;
}
int sdhci_cdns6_phy_probe(struct platform_device *pdev, struct sdhci_cdns_priv *priv)
{
struct device *dev = &pdev->dev;
struct sdhci_host *host = dev_get_drvdata(dev);
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
struct sdhci_cdns6_phy *phy;
unsigned long val;
int ret;
phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
if (!phy)
return -ENOMEM;
val = clk_get_rate(pltfm_host->clk);
if (!val)
return dev_err_probe(dev, -EINVAL, "failed to get controller clock rate\n");
phy->t_sdmclk = DIV_ROUND_DOWN_ULL(1000000000000ULL, val);
/*
* Optional board-level PHY timing properties. When omitted, use the
* Cadence-recommended defaults (2500 ps IO-cell delays, 24 ps DLL
* delay element) that are valid for typical SoC packaging.
*/
ret = of_property_read_u32(dev->of_node, "cdns,iocell-input-delay-ps",
&phy->iocell_input_delay);
if (ret)
phy->iocell_input_delay = SDHCI_CDNS6_PHY_DEFAULT_IOCELL_DELAY;
ret = of_property_read_u32(dev->of_node, "cdns,iocell-output-delay-ps",
&phy->iocell_output_delay);
if (ret)
phy->iocell_output_delay = SDHCI_CDNS6_PHY_DEFAULT_IOCELL_DELAY;
ret = of_property_read_u32(dev->of_node, "cdns,delay-element-ps", &phy->delay_element);
if (ret) {
phy->delay_element = SDHCI_CDNS6_PHY_DEFAULT_DELAY_ELEMENT;
} else if (!phy->delay_element) {
dev_warn(dev, "cdns,delay-element-ps is 0, using default %u\n",
SDHCI_CDNS6_PHY_DEFAULT_DELAY_ELEMENT);
phy->delay_element = SDHCI_CDNS6_PHY_DEFAULT_DELAY_ELEMENT;
}
phy->delay_element_org = phy->delay_element;
priv->phy = phy;
return 0;
}
static void sdhci_cdns6_phy_reconfigure(struct sdhci_host *host, unsigned int timing)
{
struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
struct sdhci_cdns6_phy *phy = priv->phy;
unsigned int rate = host->mmc->actual_clock ?: host->mmc->ios.clock;
u32 t_sdclk;
int ret;
if (!rate)
return;
t_sdclk = DIV_ROUND_DOWN_ULL(1000000000000ULL, rate);
if (phy->mode == timing && phy->t_sdclk == t_sdclk &&
phy->strobe_cmd == priv->enhanced_strobe)
return;
phy->t_sdclk = t_sdclk;
phy->mode = timing;
ret = sdhci_cdns6_phy_update_timings(host);
if (ret) {
phy->t_sdclk = 0;
dev_warn(mmc_dev(host->mmc), "%s: update timings failed: %d\n", __func__, ret);
return;
}
ret = sdhci_cdns6_phy_init(priv);
if (ret) {
phy->t_sdclk = 0;
dev_warn(mmc_dev(host->mmc), "%s: phy init failed: %d\n", __func__, ret);
}
}
void sdhci_cdns6_set_clock(struct sdhci_host *host, unsigned int clock)
{
struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
struct sdhci_cdns6_phy *phy = priv->phy;
sdhci_set_clock(host, clock);
if (!clock) {
/* Force PHY reprogramming on the next non-zero clock. */
phy->t_sdclk = 0;
return;
}
sdhci_cdns6_phy_reconfigure(host, host->mmc->ios.timing);
}
void sdhci_cdns6_set_uhs_signaling(struct sdhci_host *host, unsigned int timing)
{
sdhci_cdns6_phy_reconfigure(host, timing);
}
void sdhci_cdns6_hw_reset(struct sdhci_host *host)
{
struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
void __iomem *reg;
reg = priv->hrs_addr + SDHCI_CDNS_HRS11;
writel(SDHCI_CDNS_HRS11_EMMC_RST, reg);
/* eMMC HW reset assertion: spec requires >= 1us, give margin */
usleep_range(10, 20);
writel(0, reg);
/* For eMMC, minimum is 200us but give it 300us for good measure */
usleep_range(300, 1000);
}

View File

@ -0,0 +1,118 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2026 Altera Corporation
* Author: Tanmay Kathpalia <tanmay.kathpalia@altera.com>
*
* Cadence SD/SDIO/eMMC Host Controller driver - common header
* Shared definitions and structures for the Cadence SDHCI driver.
* Contains private data and declarations for SD6HC-specific functions
* called by the main driver in sdhci-cadence-core.c.
*/
#ifndef _MMC_HOST_SDHCI_CADENCE_H
#define _MMC_HOST_SDHCI_CADENCE_H
#include <linux/compiler.h>
#include <linux/spinlock.h>
#include <linux/types.h>
#include "sdhci-pltfm.h"
struct reset_control;
/* HRS - Host Register Set (specific to Cadence) */
#define SDHCI_CDNS_HRS04 0x10 /* PHY access: address port */
#define SDHCI_CDNS_HRS05 0x14 /* PHY access: data port */
/*
* The tuned val register is 6 bit-wide, but not the whole of the range is
* available. The range 0-42 seems to be available (then 43 wraps around to 0)
* but I am not quite sure if it is official. Use only 0 to 39 for safety.
*/
#define SDHCI_CDNS_MAX_TUNING_LOOP 40
/**
* struct sdhci_cdns_priv - Cadence SDHCI private controller data
* @hrs_addr: Base address of Cadence Host Register Set (HRS) registers.
* @ctl_addr: Base address for write control registers.
* Used only for "amd,pensando-elba-sd4hc" compatible controllers to enable
* byte-lane writes.
* @wrlock: Spinlock for protecting register writes (Elba only).
* @enhanced_strobe: Flag indicating if Enhanced Strobe (HS400ES) is enabled.
* @priv_writel: Optional SoC-specific write function for register access.
* Used for Elba to ensure correct byte-lane enable.
* @rst_hw: Hardware reset control for the eMMC card RST_n pin (SD4HC only).
* @phy: Opaque pointer to variant-specific PHY data.
* For SD4HC: points to struct sdhci_cdns4_phy.
* For SD6HC: points to struct sdhci_cdns6_phy.
*/
struct sdhci_cdns_priv {
void __iomem *hrs_addr;
void __iomem *ctl_addr; /* write control */
spinlock_t wrlock; /* write lock */
bool enhanced_strobe;
void (*priv_writel)(struct sdhci_cdns_priv *priv, u32 val, void __iomem *reg);
struct reset_control *rst_hw;
void *phy;
};
/**
* sdhci_cdns_priv - Helper to retrieve Cadence private data from sdhci_host
* @host: Pointer to struct sdhci_host.
*
* Return: Pointer to struct sdhci_cdns_priv.
*/
static inline void *sdhci_cdns_priv(struct sdhci_host *host)
{
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
return sdhci_pltfm_priv(pltfm_host);
}
/**
* sdhci_cdns6_set_clock - Set SDCLK and reprogram SD6HC PHY timings.
* @host: Pointer to struct sdhci_host.
* @clock: Requested clock frequency in Hz (0 to disable).
*/
void sdhci_cdns6_set_clock(struct sdhci_host *host, unsigned int clock);
/**
* sdhci_cdns6_set_uhs_signaling - Program PHY registers for a specific timing mode.
* @host: Pointer to struct sdhci_host.
* @timing: MMC timing mode (MMC_TIMING_*).
*/
void sdhci_cdns6_set_uhs_signaling(struct sdhci_host *host, unsigned int timing);
/**
* sdhci_cdns6_set_tune_val - Set the PHY tuning value.
* @host: Pointer to struct sdhci_host.
* @val: Tuning value to program.
*
* Return: 0 on success, -ETIMEDOUT if DLL reset release times out.
*/
int sdhci_cdns6_set_tune_val(struct sdhci_host *host, unsigned int val);
/**
* sdhci_cdns6_phy_probe - Probe and initialize Cadence SD6HC PHY parameters
* @pdev: Platform device pointer
* @priv: Pointer to Cadence private data structure
*
* Return: 0 on success or a negative error code.
*/
int sdhci_cdns6_phy_probe(struct platform_device *pdev, struct sdhci_cdns_priv *priv);
/**
* sdhci_cdns6_hw_reset - Perform hardware reset of the Cadence SDHCI controller.
* @host: Pointer to struct sdhci_host.
*/
void sdhci_cdns6_hw_reset(struct sdhci_host *host);
/**
* sdhci_cdns6_phy_init - Initialize the SD6HC PHY with current settings.
* @priv: Pointer to Cadence private data structure.
*
* Return: 0 on success, -ETIMEDOUT if PHY initialization times out.
*/
int sdhci_cdns6_phy_init(struct sdhci_cdns_priv *priv);
#endif /* _MMC_HOST_SDHCI_CADENCE_H */

View File

@ -1770,7 +1770,7 @@ static void sdhci_msm_handle_pwr_irq(struct sdhci_host *host, int irq)
/*
* The driver has to acknowledge the interrupt, switch voltages and
* report back if it succeded or not to this register. The voltage
* report back if it succeeded or not to this register. The voltage
* switches are handled by the sdhci core, so just report success.
*/
msm_host_writel(msm_host, irq_ack, host,
@ -1959,6 +1959,7 @@ static int sdhci_msm_ice_init(struct sdhci_msm_host *msm_host,
}
mmc->caps2 |= MMC_CAP2_CRYPTO;
mmc->caps2 |= MMC_CAP2_CRYPTO_NO_REPROG;
return 0;
}
@ -2055,6 +2056,12 @@ static int sdhci_msm_ice_prepare_key(struct blk_crypto_profile *profile,
return qcom_ice_prepare_key(msm_host->ice, lt_key, lt_key_size, eph_key);
}
static void sdhci_msm_ice_restore(struct sdhci_host *host)
{
if (host->mmc->caps2 & MMC_CAP2_CRYPTO)
blk_crypto_reprogram_all_keys(&host->mmc->crypto_profile);
}
static void sdhci_msm_non_cqe_ice_init(struct sdhci_host *host)
{
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
@ -2149,6 +2156,10 @@ sdhci_msm_ice_suspend(struct sdhci_msm_host *msm_host)
{
return 0;
}
static inline void sdhci_msm_ice_restore(struct sdhci_host *host)
{
}
#endif /* !CONFIG_MMC_CRYPTO */
/*****************************************************************************\
@ -2983,9 +2994,28 @@ static int sdhci_msm_runtime_resume(struct device *dev)
return ret;
}
static int sdhci_msm_restore(struct device *dev)
{
struct sdhci_host *host = dev_get_drvdata(dev);
int ret;
ret = pm_runtime_force_resume(dev);
if (ret)
return ret;
sdhci_msm_ice_restore(host);
return ret;
}
static const struct dev_pm_ops sdhci_msm_pm_ops = {
SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
RUNTIME_PM_OPS(sdhci_msm_runtime_suspend, sdhci_msm_runtime_resume, NULL)
.suspend = pm_sleep_ptr(pm_runtime_force_suspend),
.resume = pm_sleep_ptr(pm_runtime_force_resume),
.freeze = pm_sleep_ptr(pm_runtime_force_suspend),
.restore = pm_sleep_ptr(sdhci_msm_restore),
.thaw = pm_sleep_ptr(pm_runtime_force_resume),
.poweroff = pm_sleep_ptr(pm_runtime_force_suspend),
};
static struct platform_driver sdhci_msm_driver = {

View File

@ -1552,8 +1552,8 @@ sdhci_arasan_register_sdcardclk(struct sdhci_arasan_data *sdhci_arasan,
struct device *dev)
{
struct sdhci_arasan_clk_data *clk_data = &sdhci_arasan->clk_data;
struct clk_init_data sdcardclk_init = {};
struct device_node *np = dev->of_node;
struct clk_init_data sdcardclk_init;
const char *parent_clk_name;
int ret;
@ -1604,8 +1604,8 @@ sdhci_arasan_register_sampleclk(struct sdhci_arasan_data *sdhci_arasan,
struct device *dev)
{
struct sdhci_arasan_clk_data *clk_data = &sdhci_arasan->clk_data;
struct clk_init_data sampleclk_init = {};
struct device_node *np = dev->of_node;
struct clk_init_data sampleclk_init;
const char *parent_clk_name;
int ret;

View File

@ -239,6 +239,8 @@
SDHCI_TRNS_BLK_CNT_EN | \
SDHCI_TRNS_DMA)
#define CQHCI_SW_ERR_HALT_REQ_DISABLE BIT(1)
#define to_pltfm_data(priv, name) \
container_of((priv)->dwcmshc_pdata, struct name##_pltfm_data, dwcmshc_pdata)
@ -702,6 +704,8 @@ static void rk35xx_sdhci_cqe_pre_enable(struct mmc_host *mmc)
static void rk35xx_sdhci_cqe_enable(struct mmc_host *mmc)
{
struct sdhci_host *host = mmc_priv(mmc);
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
struct dwcmshc_priv *dwc_priv = sdhci_pltfm_priv(pltfm_host);
u32 reg;
reg = sdhci_readl(host, SDHCI_PRESENT_STATE);
@ -712,6 +716,15 @@ static void rk35xx_sdhci_cqe_enable(struct mmc_host *mmc)
sdhci_writew(host, DWCMSHC_SDHCI_CQE_TRNS_MODE, SDHCI_TRANSFER_MODE);
/*
* Disable the halt request raised on an I/O error, otherwise a
* doorbell write gets a slave response error while the halt request
* is pending and hangs the CPU. This bit is reserved on older SoCs,
* so the write is a no-op there.
*/
sdhci_writel(host, CQHCI_SW_ERR_HALT_REQ_DISABLE,
dwc_priv->vendor_specific_area2 + CQHCI_CTL);
sdhci_cqe_enable(mmc);
}

View File

@ -1381,7 +1381,7 @@ static void esdhc_init(struct platform_device *pdev, struct sdhci_host *host)
if (!IS_ERR(clk)) {
/*
* esdhc->peripheral_clock would be assigned with a value
* which is eSDHC base clock when use periperal clock.
* which is eSDHC base clock when use peripheral clock.
* For some platforms, the clock value got by common clk
* API is peripheral clock while the eSDHC base clock is
* 1/2 peripheral clock.

View File

@ -332,7 +332,7 @@ static int sdhci_omap_execute_tuning(struct mmc_host *mmc, u32 opcode)
u32 reg;
int i;
/* clock tuning is not needed for upto 52MHz */
/* clock tuning is not needed for up to 52MHz */
if (ios->clock <= 52000000)
return 0;

View File

@ -2170,14 +2170,12 @@ static struct sdhci_pci_slot *sdhci_pci_probe_slot(
host->irq = pdev->irq;
ret = pcim_iomap_regions(pdev, BIT(bar), mmc_hostname(host->mmc));
if (ret) {
host->ioaddr = pcim_iomap_region(pdev, bar, mmc_hostname(host->mmc));
if (IS_ERR(host->ioaddr)) {
dev_err(&pdev->dev, "cannot request region\n");
return ERR_PTR(ret);
return ERR_CAST(host->ioaddr);
}
host->ioaddr = pcim_iomap_table(pdev)[bar];
if (chip->fixes && chip->fixes->probe_slot) {
ret = chip->fixes->probe_slot(slot);
if (ret)

View File

@ -123,7 +123,7 @@ static int mv_conf_mbus_windows(struct platform_device *pdev,
static int armada_38x_quirks(struct platform_device *pdev,
struct sdhci_host *host)
{
struct device_node *np = pdev->dev.of_node;
struct device *dev = &pdev->dev;
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
struct sdhci_pxa *pxa = sdhci_pltfm_priv(pltfm_host);
struct resource *res;
@ -155,7 +155,7 @@ static int armada_38x_quirks(struct platform_device *pdev,
* controller has different capabilities than the ones shown
* in its registers
*/
if (of_property_read_bool(np, "no-1-8-v")) {
if (device_property_present(dev, "no-1-8-v")) {
host->caps &= ~SDHCI_CAN_VDD_180;
host->mmc->caps &= ~MMC_CAP_1_8V_DDR;
} else {
@ -284,7 +284,8 @@ static void pxav3_set_uhs_signaling(struct sdhci_host *host, unsigned int uhs)
uhs == MMC_TIMING_UHS_DDR50) {
reg_val &= ~SDIO3_CONF_CLK_INV;
reg_val |= SDIO3_CONF_SD_FB_CLK;
} else if (uhs == MMC_TIMING_MMC_HS) {
} else if (uhs == MMC_TIMING_MMC_HS ||
uhs == MMC_TIMING_SD_HS) {
reg_val &= ~SDIO3_CONF_CLK_INV;
reg_val &= ~SDIO3_CONF_SD_FB_CLK;
} else {
@ -363,14 +364,13 @@ MODULE_DEVICE_TABLE(of, sdhci_pxav3_of_match);
static struct sdhci_pxa_platdata *pxav3_get_mmc_pdata(struct device *dev)
{
struct sdhci_pxa_platdata *pdata;
struct device_node *np = dev->of_node;
u32 clk_delay_cycles;
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata)
return NULL;
if (!of_property_read_u32(np, "mrvl,clk-delay-cycles",
if (!device_property_read_u32(dev, "mrvl,clk-delay-cycles",
&clk_delay_cycles))
pdata->clk_delay_cycles = clk_delay_cycles;
@ -401,7 +401,6 @@ static int sdhci_pxav3_probe(struct platform_device *pdev)
struct sdhci_pltfm_host *pltfm_host;
struct sdhci_pxa_platdata *pdata = pdev->dev.platform_data;
struct device *dev = &pdev->dev;
struct device_node *np = pdev->dev.of_node;
struct sdhci_host *host = NULL;
struct sdhci_pxa *pxa = NULL;
const struct of_device_id *match;
@ -432,7 +431,7 @@ static int sdhci_pxav3_probe(struct platform_device *pdev)
/* enable 1/8V DDR capable */
host->mmc->caps |= MMC_CAP_1_8V_DDR;
if (of_device_is_compatible(np, "marvell,armada-380-sdhci")) {
if (device_is_compatible(dev, "marvell,armada-380-sdhci")) {
ret = armada_38x_quirks(pdev, host);
if (ret < 0)
goto err_mbus_win;

View File

@ -409,7 +409,7 @@ struct sdhci_adma2_64_desc {
#define SDHCI_MAX_MRQS 2
/*
* 48bit command and 136 bit response in 100KHz clock could take upto 2.48ms.
* 48bit command and 136 bit response in 100KHz clock could take up to 2.48ms.
* However since the start time of the command, the time between
* command and response, and the time between response and start of data is
* not known, set the command transfer time to 10ms.

View File

@ -294,6 +294,11 @@ static void spmmc_set_bus_timing(struct spmmc_host *host, unsigned int timing)
value = readl(host->base + SPMMC_SD_CONFIG0_REG);
value |= SPMMC_SD_DDR_MODE;
writel(value, host->base + SPMMC_SD_CONFIG0_REG);
/* DDR mode needs rd_crc_dly=1 */
value = readl(host->base + SPMMC_SD_TIMING_CONFIG0_REG);
value &= ~SPMMC_SD_READ_CRC_DELAY;
value |= FIELD_PREP(SPMMC_SD_READ_CRC_DELAY, 1);
writel(value, host->base + SPMMC_SD_TIMING_CONFIG0_REG);
} else {
value = readl(host->base + SPMMC_SD_CONFIG0_REG);
value &= ~SPMMC_SD_DDR_MODE;

View File

@ -388,10 +388,7 @@ static void ushc_enable_sdio_irq(struct mmc_host *mmc, int enable)
{
struct ushc_data *ushc = mmc_priv(mmc);
if (enable)
set_bit(INT_EN, &ushc->flags);
else
clear_bit(INT_EN, &ushc->flags);
assign_bit(INT_EN, &ushc->flags, enable);
}
static void ushc_clean_up(struct ushc_data *ushc)

File diff suppressed because it is too large Load Diff

View File

@ -767,17 +767,15 @@ static int gb_sdio_probe(struct gbphy_device *gbphy_dev,
struct gb_sdio_host *host;
int ret = 0;
mmc = mmc_alloc_host(sizeof(*host), &gbphy_dev->dev);
mmc = devm_mmc_alloc_host(&gbphy_dev->dev, sizeof(*host));
if (!mmc)
return -ENOMEM;
connection = gb_connection_create(gbphy_dev->bundle,
le16_to_cpu(gbphy_dev->cport_desc->id),
gb_sdio_request_handler);
if (IS_ERR(connection)) {
ret = PTR_ERR(connection);
goto exit_mmc_free;
}
if (IS_ERR(connection))
return PTR_ERR(connection);
host = mmc_priv(mmc);
host->mmc = mmc;
@ -835,8 +833,6 @@ static int gb_sdio_probe(struct gbphy_device *gbphy_dev,
gb_connection_disable(connection);
exit_connection_destroy:
gb_connection_destroy(connection);
exit_mmc_free:
mmc_free_host(mmc);
return ret;
}
@ -863,7 +859,6 @@ static void gb_sdio_remove(struct gbphy_device *gbphy_dev)
mmc_remove_host(mmc);
gb_connection_disable(connection);
gb_connection_destroy(connection);
mmc_free_host(mmc);
}
static const struct gbphy_device_id gb_sdio_id_table[] = {

View File

@ -23,6 +23,7 @@ struct mmc_ios {
unsigned int clock; /* clock rate */
unsigned short vdd;
unsigned int power_delay_ms; /* waiting for stable power */
unsigned int power_off_delay_us; /* waiting for power discharge */
/* vdd stores the bit number of the selected voltage range from below. */
@ -463,6 +464,7 @@ struct mmc_host {
#define MMC_CAP2_CRYPTO 0
#endif
#define MMC_CAP2_ALT_GPT_TEGRA (1 << 28) /* Host with eMMC that has GPT entry at a non-standard location */
#define MMC_CAP2_CRYPTO_NO_REPROG (1 << 29) /* Host handles inline crypto key reprogramming */
bool uhs2_sd_tran; /* UHS-II flag for SD_TRAN state */
bool uhs2_app_cmd; /* UHS-II flag for APP command */
@ -583,11 +585,9 @@ struct mmc_host {
struct device_node;
struct mmc_host *mmc_alloc_host(int extra, struct device *);
struct mmc_host *devm_mmc_alloc_host(struct device *dev, int extra);
int mmc_add_host(struct mmc_host *);
void mmc_remove_host(struct mmc_host *);
void mmc_free_host(struct mmc_host *);
void mmc_of_parse_clk_phase(struct device *dev,
struct mmc_clk_phase_map *map);
int mmc_of_parse(struct mmc_host *host);
@ -753,6 +753,8 @@ static inline int mmc_card_uhs2_hd_mode(struct mmc_host *host)
int mmc_sd_switch(struct mmc_card *card, bool mode, int group,
u8 value, u8 *resp);
int mmc_send_status(struct mmc_card *card, u32 *status);
int mmc_send_tuning_timeout(struct mmc_host *host, u32 opcode, int *cmd_error,
u32 timeout_ms);
int mmc_send_tuning(struct mmc_host *host, u32 opcode, int *cmd_error);
int mmc_send_abort_tuning(struct mmc_host *host, u32 opcode);
int mmc_get_ext_csd(struct mmc_card *card, u8 **new_ext_csd);