mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
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:
commit
03480653dd
5
CREDITS
5
CREDITS
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
105
Documentation/devicetree/bindings/mmc/cdns,sd6hc.yaml
Normal file
105
Documentation/devicetree/bindings/mmc/cdns,sd6hc.yaml
Normal 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;
|
||||
};
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
12
MAINTAINERS
12
MAINTAINERS
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
975
drivers/mmc/host/sdhci-cadence-phy-v6.c
Normal file
975
drivers/mmc/host/sdhci-cadence-phy-v6.c
Normal 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);
|
||||
}
|
||||
118
drivers/mmc/host/sdhci-cadence.h
Normal file
118
drivers/mmc/host/sdhci-cadence.h
Normal 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 */
|
||||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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[] = {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user