mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2025-12-22 12:17:45 +01:00
Merge tag 'mmc-v6.8' of git://git.kernel.org/pub/scm/linux/kernel/git/ulfh/mmc
Pull MMC updates from Ulf Hansson: "MMC core: - Don't force a retune before eMMC RPMB switch - Add optional HS400 tuning in HS400es initialization - Add a sysfs node to for write-protect-group-size - Add re-tuning test to the mmc-test module - Use mrq.sbc to support close-ended ioctl requests MMC host: - mmci: Add support for SDIO in-band irqs for the stm32 variant - mmc_spi: Remove broken support custom DMA mapped buffers - mtk-sd: Improve and extend the support for tunings - renesas_sdhi: Document support for the RZ/Five variant - sdhci_am654: Drop support for the ti,otap-del-sel DT property - sdhci-brcmstb: Add support for the brcm 74165b0 variant - sdhci-msm: Add compatibles for IPQ4019 and IPQ8074 - sdhci-of-dwcmshc: Add support for the T-Head TH1520 variant - sdhci-xenon: Add support for the Marvell ac5 variant" * tag 'mmc-v6.8' of git://git.kernel.org/pub/scm/linux/kernel/git/ulfh/mmc: (27 commits) mmc: xenon: Add ac5 support via bounce buffer dt-bindings: mmc: add Marvell ac5 mmc: sdhci-brcmstb: add new sdhci reset sequence for brcm 74165b0 dt-bindings: mmc: brcm,sdhci-brcmstb: Add support for 74165b0 mmc: core: Do not force a retune before RPMB switch mmc: core: Add HS400 tuning in HS400es initialization mmc: sdhci_omap: Fix TI SoC dependencies mmc: sdhci_am654: Fix TI SoC dependencies mmc: core: Add wp_grp_size sysfs node mmc: mmc_test: Add re-tuning test mmc: mmc_spi: remove custom DMA mapped buffers dt-bindings: mmc: sdhci-msm: document dedicated IPQ4019 and IPQ8074 dt-bindings: mmc: synopsys-dw-mshc: add iommus for Intel SocFPGA mmc: mtk-sd: Extend number of tuning steps dt-bindings: mmc: mtk-sd: add tuning steps related property mmc: sdhci-omap: don't misuse kernel-doc marker mmc: mtk-sd: Increase the verbosity of msdc_track_cmd_data mmc: core: Use mrq.sbc in close-ended ffu mmc: sdhci_am654: Drop lookup for deprecated ti,otap-del-sel mmc: sdhci-of-dwcmshc: Use logical OR instead of bitwise OR in dwcmshc_probe() ...
This commit is contained in:
@@ -226,8 +226,8 @@ examples:
|
||||
interrupt-parent = <&gic>;
|
||||
interrupts = <0 48 4>;
|
||||
reg = <0xff160000 0x1000>;
|
||||
clocks = <&clk200>, <&clk200>;
|
||||
clock-names = "clk_xin", "clk_ahb";
|
||||
clocks = <&clk200>, <&clk200>, <&clk1200>;
|
||||
clock-names = "clk_xin", "clk_ahb", "gate";
|
||||
clock-output-names = "clk_out_sd0", "clk_in_sd0";
|
||||
#clock-cells = <1>;
|
||||
clk-phase-sd-hs = <63>, <72>;
|
||||
@@ -239,8 +239,8 @@ examples:
|
||||
interrupt-parent = <&gic>;
|
||||
interrupts = <0 126 4>;
|
||||
reg = <0xf1040000 0x10000>;
|
||||
clocks = <&clk200>, <&clk200>;
|
||||
clock-names = "clk_xin", "clk_ahb";
|
||||
clocks = <&clk200>, <&clk200>, <&clk1200>;
|
||||
clock-names = "clk_xin", "clk_ahb", "gate";
|
||||
clock-output-names = "clk_out_sd0", "clk_in_sd0";
|
||||
#clock-cells = <1>;
|
||||
clk-phase-sd-hs = <132>, <60>;
|
||||
|
||||
@@ -20,10 +20,8 @@ properties:
|
||||
- const: brcm,sdhci-brcmstb
|
||||
- items:
|
||||
- enum:
|
||||
- brcm,bcm74165b0-sdhci
|
||||
- brcm,bcm7445-sdhci
|
||||
- const: brcm,sdhci-brcmstb
|
||||
- items:
|
||||
- enum:
|
||||
- brcm,bcm7425-sdhci
|
||||
- const: brcm,sdhci-brcmstb
|
||||
|
||||
|
||||
@@ -27,7 +27,9 @@ properties:
|
||||
- marvell,armada-ap806-sdhci
|
||||
|
||||
- items:
|
||||
- const: marvell,armada-ap807-sdhci
|
||||
- enum:
|
||||
- marvell,armada-ap807-sdhci
|
||||
- marvell,ac5-sdhci
|
||||
- const: marvell,armada-ap806-sdhci
|
||||
|
||||
- items:
|
||||
|
||||
@@ -145,6 +145,15 @@ properties:
|
||||
minimum: 0
|
||||
maximum: 7
|
||||
|
||||
mediatek,tuning-step:
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
description:
|
||||
Some SoCs need extend tuning step for better delay value to avoid CRC issue.
|
||||
If not present, default tuning step is 32. For eMMC and SD, this can yield
|
||||
satisfactory calibration results in most cases.
|
||||
enum: [32, 64]
|
||||
default: 32
|
||||
|
||||
resets:
|
||||
maxItems: 1
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ properties:
|
||||
- renesas,sdhi-r8a77980 # R-Car V3H
|
||||
- renesas,sdhi-r8a77990 # R-Car E3
|
||||
- renesas,sdhi-r8a77995 # R-Car D3
|
||||
- renesas,sdhi-r9a07g043 # RZ/G2UL
|
||||
- renesas,sdhi-r9a07g043 # RZ/G2UL and RZ/Five
|
||||
- renesas,sdhi-r9a07g044 # RZ/G2{L,LC}
|
||||
- renesas,sdhi-r9a07g054 # RZ/V2L
|
||||
- renesas,sdhi-r9a08g045 # RZ/G3S
|
||||
|
||||
@@ -22,6 +22,8 @@ properties:
|
||||
- items:
|
||||
- enum:
|
||||
- qcom,apq8084-sdhci
|
||||
- qcom,ipq4019-sdhci
|
||||
- qcom,ipq8074-sdhci
|
||||
- qcom,msm8226-sdhci
|
||||
- qcom,msm8953-sdhci
|
||||
- qcom,msm8974-sdhci
|
||||
|
||||
@@ -19,6 +19,7 @@ properties:
|
||||
- rockchip,rk3568-dwcmshc
|
||||
- rockchip,rk3588-dwcmshc
|
||||
- snps,dwcmshc-sdhci
|
||||
- thead,th1520-dwcmshc
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
@@ -35,6 +35,9 @@ properties:
|
||||
- const: biu
|
||||
- const: ciu
|
||||
|
||||
iommus:
|
||||
maxItems: 1
|
||||
|
||||
altr,sysmgr-syscon:
|
||||
$ref: /schemas/types.yaml#/definitions/phandle-array
|
||||
items:
|
||||
@@ -62,6 +65,7 @@ allOf:
|
||||
altr,sysmgr-syscon: true
|
||||
else:
|
||||
properties:
|
||||
iommus: false
|
||||
altr,sysmgr-syscon: false
|
||||
|
||||
required:
|
||||
|
||||
@@ -400,6 +400,10 @@ struct mmc_blk_ioc_data {
|
||||
struct mmc_ioc_cmd ic;
|
||||
unsigned char *buf;
|
||||
u64 buf_bytes;
|
||||
unsigned int flags;
|
||||
#define MMC_BLK_IOC_DROP BIT(0) /* drop this mrq */
|
||||
#define MMC_BLK_IOC_SBC BIT(1) /* use mrq.sbc */
|
||||
|
||||
struct mmc_rpmb_data *rpmb;
|
||||
};
|
||||
|
||||
@@ -465,7 +469,7 @@ static int mmc_blk_ioctl_copy_to_user(struct mmc_ioc_cmd __user *ic_ptr,
|
||||
}
|
||||
|
||||
static int __mmc_blk_ioctl_cmd(struct mmc_card *card, struct mmc_blk_data *md,
|
||||
struct mmc_blk_ioc_data *idata)
|
||||
struct mmc_blk_ioc_data **idatas, int i)
|
||||
{
|
||||
struct mmc_command cmd = {}, sbc = {};
|
||||
struct mmc_data data = {};
|
||||
@@ -475,10 +479,18 @@ static int __mmc_blk_ioctl_cmd(struct mmc_card *card, struct mmc_blk_data *md,
|
||||
unsigned int busy_timeout_ms;
|
||||
int err;
|
||||
unsigned int target_part;
|
||||
struct mmc_blk_ioc_data *idata = idatas[i];
|
||||
struct mmc_blk_ioc_data *prev_idata = NULL;
|
||||
|
||||
if (!card || !md || !idata)
|
||||
return -EINVAL;
|
||||
|
||||
if (idata->flags & MMC_BLK_IOC_DROP)
|
||||
return 0;
|
||||
|
||||
if (idata->flags & MMC_BLK_IOC_SBC)
|
||||
prev_idata = idatas[i - 1];
|
||||
|
||||
/*
|
||||
* The RPMB accesses comes in from the character device, so we
|
||||
* need to target these explicitly. Else we just target the
|
||||
@@ -532,7 +544,7 @@ static int __mmc_blk_ioctl_cmd(struct mmc_card *card, struct mmc_blk_data *md,
|
||||
return err;
|
||||
}
|
||||
|
||||
if (idata->rpmb) {
|
||||
if (idata->rpmb || prev_idata) {
|
||||
sbc.opcode = MMC_SET_BLOCK_COUNT;
|
||||
/*
|
||||
* We don't do any blockcount validation because the max size
|
||||
@@ -540,6 +552,8 @@ static int __mmc_blk_ioctl_cmd(struct mmc_card *card, struct mmc_blk_data *md,
|
||||
* 'Reliable Write' bit here.
|
||||
*/
|
||||
sbc.arg = data.blocks | (idata->ic.write_flag & BIT(31));
|
||||
if (prev_idata)
|
||||
sbc.arg = prev_idata->ic.arg;
|
||||
sbc.flags = MMC_RSP_R1 | MMC_CMD_AC;
|
||||
mrq.sbc = &sbc;
|
||||
}
|
||||
@@ -557,6 +571,15 @@ static int __mmc_blk_ioctl_cmd(struct mmc_card *card, struct mmc_blk_data *md,
|
||||
mmc_wait_for_req(card->host, &mrq);
|
||||
memcpy(&idata->ic.response, cmd.resp, sizeof(cmd.resp));
|
||||
|
||||
if (prev_idata) {
|
||||
memcpy(&prev_idata->ic.response, sbc.resp, sizeof(sbc.resp));
|
||||
if (sbc.error) {
|
||||
dev_err(mmc_dev(card->host), "%s: sbc error %d\n",
|
||||
__func__, sbc.error);
|
||||
return sbc.error;
|
||||
}
|
||||
}
|
||||
|
||||
if (cmd.error) {
|
||||
dev_err(mmc_dev(card->host), "%s: cmd error %d\n",
|
||||
__func__, cmd.error);
|
||||
@@ -1034,6 +1057,20 @@ static inline void mmc_blk_reset_success(struct mmc_blk_data *md, int type)
|
||||
md->reset_done &= ~type;
|
||||
}
|
||||
|
||||
static void mmc_blk_check_sbc(struct mmc_queue_req *mq_rq)
|
||||
{
|
||||
struct mmc_blk_ioc_data **idata = mq_rq->drv_op_data;
|
||||
int i;
|
||||
|
||||
for (i = 1; i < mq_rq->ioc_count; i++) {
|
||||
if (idata[i - 1]->ic.opcode == MMC_SET_BLOCK_COUNT &&
|
||||
mmc_op_multi(idata[i]->ic.opcode)) {
|
||||
idata[i - 1]->flags |= MMC_BLK_IOC_DROP;
|
||||
idata[i]->flags |= MMC_BLK_IOC_SBC;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* The non-block commands come back from the block layer after it queued it and
|
||||
* processed it with all other requests and then they get issued in this
|
||||
@@ -1061,11 +1098,14 @@ static void mmc_blk_issue_drv_op(struct mmc_queue *mq, struct request *req)
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
|
||||
mmc_blk_check_sbc(mq_rq);
|
||||
|
||||
fallthrough;
|
||||
case MMC_DRV_OP_IOCTL_RPMB:
|
||||
idata = mq_rq->drv_op_data;
|
||||
for (i = 0, ret = 0; i < mq_rq->ioc_count; i++) {
|
||||
ret = __mmc_blk_ioctl_cmd(card, md, idata[i]);
|
||||
ret = __mmc_blk_ioctl_cmd(card, md, idata, i);
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -119,13 +119,12 @@ void mmc_retune_enable(struct mmc_host *host)
|
||||
|
||||
/*
|
||||
* Pause re-tuning for a small set of operations. The pause begins after the
|
||||
* next command and after first doing re-tuning.
|
||||
* next command.
|
||||
*/
|
||||
void mmc_retune_pause(struct mmc_host *host)
|
||||
{
|
||||
if (!host->retune_paused) {
|
||||
host->retune_paused = 1;
|
||||
mmc_retune_needed(host);
|
||||
mmc_retune_hold(host);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,6 +136,17 @@ static void mmc_set_erase_size(struct mmc_card *card)
|
||||
mmc_init_erase(card);
|
||||
}
|
||||
|
||||
|
||||
static void mmc_set_wp_grp_size(struct mmc_card *card)
|
||||
{
|
||||
if (card->ext_csd.erase_group_def & 1)
|
||||
card->wp_grp_size = card->ext_csd.hc_erase_size *
|
||||
card->ext_csd.raw_hc_erase_gap_size;
|
||||
else
|
||||
card->wp_grp_size = card->csd.erase_size *
|
||||
(card->csd.wp_grp_size + 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Given a 128-bit response, decode to our card CSD structure.
|
||||
*/
|
||||
@@ -186,6 +197,7 @@ static int mmc_decode_csd(struct mmc_card *card)
|
||||
b = UNSTUFF_BITS(resp, 37, 5);
|
||||
csd->erase_size = (a + 1) * (b + 1);
|
||||
csd->erase_size <<= csd->write_blkbits - 9;
|
||||
csd->wp_grp_size = UNSTUFF_BITS(resp, 32, 5);
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -613,11 +625,6 @@ static int mmc_decode_ext_csd(struct mmc_card *card, u8 *ext_csd)
|
||||
} else {
|
||||
card->ext_csd.data_tag_unit_size = 0;
|
||||
}
|
||||
|
||||
card->ext_csd.max_packed_writes =
|
||||
ext_csd[EXT_CSD_MAX_PACKED_WRITES];
|
||||
card->ext_csd.max_packed_reads =
|
||||
ext_csd[EXT_CSD_MAX_PACKED_READS];
|
||||
} else {
|
||||
card->ext_csd.data_sector_size = 512;
|
||||
}
|
||||
@@ -790,6 +797,7 @@ MMC_DEV_ATTR(csd, "%08x%08x%08x%08x\n", card->raw_csd[0], card->raw_csd[1],
|
||||
MMC_DEV_ATTR(date, "%02d/%04d\n", card->cid.month, card->cid.year);
|
||||
MMC_DEV_ATTR(erase_size, "%u\n", card->erase_size << 9);
|
||||
MMC_DEV_ATTR(preferred_erase_size, "%u\n", card->pref_erase << 9);
|
||||
MMC_DEV_ATTR(wp_grp_size, "%u\n", card->wp_grp_size << 9);
|
||||
MMC_DEV_ATTR(ffu_capable, "%d\n", card->ext_csd.ffu_capable);
|
||||
MMC_DEV_ATTR(hwrev, "0x%x\n", card->cid.hwrev);
|
||||
MMC_DEV_ATTR(manfid, "0x%06x\n", card->cid.manfid);
|
||||
@@ -850,6 +858,7 @@ static struct attribute *mmc_std_attrs[] = {
|
||||
&dev_attr_date.attr,
|
||||
&dev_attr_erase_size.attr,
|
||||
&dev_attr_preferred_erase_size.attr,
|
||||
&dev_attr_wp_grp_size.attr,
|
||||
&dev_attr_fwrev.attr,
|
||||
&dev_attr_ffu_capable.attr,
|
||||
&dev_attr_hwrev.attr,
|
||||
@@ -1764,7 +1773,7 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr,
|
||||
mmc_set_erase_size(card);
|
||||
}
|
||||
}
|
||||
|
||||
mmc_set_wp_grp_size(card);
|
||||
/*
|
||||
* Ensure eMMC user default partition is enabled
|
||||
*/
|
||||
@@ -1822,8 +1831,13 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr,
|
||||
|
||||
if (err)
|
||||
goto free_card;
|
||||
|
||||
} else if (!mmc_card_hs400es(card)) {
|
||||
} else if (mmc_card_hs400es(card)) {
|
||||
if (host->ops->execute_hs400_tuning) {
|
||||
err = host->ops->execute_hs400_tuning(host, card);
|
||||
if (err)
|
||||
goto free_card;
|
||||
}
|
||||
} else {
|
||||
/* Select the desired bus width optionally */
|
||||
err = mmc_select_bus_width(card);
|
||||
if (err > 0 && mmc_card_hs(card)) {
|
||||
|
||||
@@ -1904,7 +1904,7 @@ static unsigned int mmc_test_rnd_num(unsigned int rnd_cnt)
|
||||
}
|
||||
|
||||
static int mmc_test_rnd_perf(struct mmc_test_card *test, int write, int print,
|
||||
unsigned long sz)
|
||||
unsigned long sz, int secs, int force_retuning)
|
||||
{
|
||||
unsigned int dev_addr, cnt, rnd_addr, range1, range2, last_ea = 0, ea;
|
||||
unsigned int ssz;
|
||||
@@ -1921,7 +1921,7 @@ static int mmc_test_rnd_perf(struct mmc_test_card *test, int write, int print,
|
||||
for (cnt = 0; cnt < UINT_MAX; cnt++) {
|
||||
ktime_get_ts64(&ts2);
|
||||
ts = timespec64_sub(ts2, ts1);
|
||||
if (ts.tv_sec >= 10)
|
||||
if (ts.tv_sec >= secs)
|
||||
break;
|
||||
ea = mmc_test_rnd_num(range1);
|
||||
if (ea == last_ea)
|
||||
@@ -1929,6 +1929,8 @@ static int mmc_test_rnd_perf(struct mmc_test_card *test, int write, int print,
|
||||
last_ea = ea;
|
||||
dev_addr = rnd_addr + test->card->pref_erase * ea +
|
||||
ssz * mmc_test_rnd_num(range2);
|
||||
if (force_retuning)
|
||||
mmc_retune_needed(test->card->host);
|
||||
ret = mmc_test_area_io(test, sz, dev_addr, write, 0, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
@@ -1953,24 +1955,35 @@ static int mmc_test_random_perf(struct mmc_test_card *test, int write)
|
||||
*/
|
||||
if (write) {
|
||||
next = rnd_next;
|
||||
ret = mmc_test_rnd_perf(test, write, 0, sz);
|
||||
ret = mmc_test_rnd_perf(test, write, 0, sz, 10, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
rnd_next = next;
|
||||
}
|
||||
ret = mmc_test_rnd_perf(test, write, 1, sz);
|
||||
ret = mmc_test_rnd_perf(test, write, 1, sz, 10, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
sz = t->max_tfr;
|
||||
if (write) {
|
||||
next = rnd_next;
|
||||
ret = mmc_test_rnd_perf(test, write, 0, sz);
|
||||
ret = mmc_test_rnd_perf(test, write, 0, sz, 10, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
rnd_next = next;
|
||||
}
|
||||
return mmc_test_rnd_perf(test, write, 1, sz);
|
||||
return mmc_test_rnd_perf(test, write, 1, sz, 10, 0);
|
||||
}
|
||||
|
||||
static int mmc_test_retuning(struct mmc_test_card *test)
|
||||
{
|
||||
if (!mmc_can_retune(test->card->host)) {
|
||||
pr_info("%s: No retuning - test skipped\n",
|
||||
mmc_hostname(test->card->host));
|
||||
return RESULT_UNSUP_HOST;
|
||||
}
|
||||
|
||||
return mmc_test_rnd_perf(test, 0, 0, 8192, 30, 1);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -2921,6 +2934,14 @@ static const struct mmc_test_case mmc_test_cases[] = {
|
||||
.run = mmc_test_cmds_during_write_cmd23_nonblock,
|
||||
.cleanup = mmc_test_area_cleanup,
|
||||
},
|
||||
|
||||
{
|
||||
.name = "Re-tuning reliability",
|
||||
.prepare = mmc_test_area_prepare,
|
||||
.run = mmc_test_retuning,
|
||||
.cleanup = mmc_test_area_cleanup,
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
static DEFINE_MUTEX(mmc_test_lock);
|
||||
|
||||
@@ -1026,14 +1026,15 @@ config MMC_SDHCI_XENON
|
||||
|
||||
config MMC_SDHCI_OMAP
|
||||
tristate "TI SDHCI Controller Support"
|
||||
depends on ARCH_OMAP2PLUS || ARCH_KEYSTONE || COMPILE_TEST
|
||||
depends on MMC_SDHCI_PLTFM && OF
|
||||
select THERMAL
|
||||
imply TI_SOC_THERMAL
|
||||
select MMC_SDHCI_EXTERNAL_DMA if DMA_ENGINE
|
||||
help
|
||||
This selects the Secure Digital Host Controller Interface (SDHCI)
|
||||
support present in TI's DRA7 SOCs. The controller supports
|
||||
SD/MMC/SDIO devices.
|
||||
support present in TI's Keystone/OMAP2+/DRA7 SOCs. The controller
|
||||
supports SD/MMC/SDIO devices.
|
||||
|
||||
If you have a controller with this interface, say Y or M here.
|
||||
|
||||
@@ -1041,14 +1042,15 @@ config MMC_SDHCI_OMAP
|
||||
|
||||
config MMC_SDHCI_AM654
|
||||
tristate "Support for the SDHCI Controller in TI's AM654 SOCs"
|
||||
depends on ARCH_K3 || COMPILE_TEST
|
||||
depends on MMC_SDHCI_PLTFM && OF
|
||||
select MMC_SDHCI_IO_ACCESSORS
|
||||
select MMC_CQHCI
|
||||
select REGMAP_MMIO
|
||||
help
|
||||
This selects the Secure Digital Host Controller Interface (SDHCI)
|
||||
support present in TI's AM654 SOCs. The controller supports
|
||||
SD/MMC/SDIO devices.
|
||||
support present in TI's AM65x/AM64x/AM62x/J721E SOCs. The controller
|
||||
supports SD/MMC/SDIO devices.
|
||||
|
||||
If you have a controller with this interface, say Y or M here.
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/bio.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/dma-direction.h>
|
||||
#include <linux/crc7.h>
|
||||
#include <linux/crc-itu-t.h>
|
||||
#include <linux/scatterlist.h>
|
||||
@@ -119,19 +119,14 @@ struct mmc_spi_host {
|
||||
struct spi_transfer status;
|
||||
struct spi_message readback;
|
||||
|
||||
/* underlying DMA-aware controller, or null */
|
||||
struct device *dma_dev;
|
||||
|
||||
/* buffer used for commands and for message "overhead" */
|
||||
struct scratch *data;
|
||||
dma_addr_t data_dma;
|
||||
|
||||
/* Specs say to write ones most of the time, even when the card
|
||||
* has no need to read its input data; and many cards won't care.
|
||||
* This is our source of those ones.
|
||||
*/
|
||||
void *ones;
|
||||
dma_addr_t ones_dma;
|
||||
};
|
||||
|
||||
|
||||
@@ -147,11 +142,8 @@ static inline int mmc_cs_off(struct mmc_spi_host *host)
|
||||
return spi_setup(host->spi);
|
||||
}
|
||||
|
||||
static int
|
||||
mmc_spi_readbytes(struct mmc_spi_host *host, unsigned len)
|
||||
static int mmc_spi_readbytes(struct mmc_spi_host *host, unsigned int len)
|
||||
{
|
||||
int status;
|
||||
|
||||
if (len > sizeof(*host->data)) {
|
||||
WARN_ON(1);
|
||||
return -EIO;
|
||||
@@ -159,19 +151,7 @@ mmc_spi_readbytes(struct mmc_spi_host *host, unsigned len)
|
||||
|
||||
host->status.len = len;
|
||||
|
||||
if (host->dma_dev)
|
||||
dma_sync_single_for_device(host->dma_dev,
|
||||
host->data_dma, sizeof(*host->data),
|
||||
DMA_FROM_DEVICE);
|
||||
|
||||
status = spi_sync_locked(host->spi, &host->readback);
|
||||
|
||||
if (host->dma_dev)
|
||||
dma_sync_single_for_cpu(host->dma_dev,
|
||||
host->data_dma, sizeof(*host->data),
|
||||
DMA_FROM_DEVICE);
|
||||
|
||||
return status;
|
||||
return spi_sync_locked(host->spi, &host->readback);
|
||||
}
|
||||
|
||||
static int mmc_spi_skip(struct mmc_spi_host *host, unsigned long timeout,
|
||||
@@ -506,23 +486,11 @@ mmc_spi_command_send(struct mmc_spi_host *host,
|
||||
t = &host->t;
|
||||
memset(t, 0, sizeof(*t));
|
||||
t->tx_buf = t->rx_buf = data->status;
|
||||
t->tx_dma = t->rx_dma = host->data_dma;
|
||||
t->len = cp - data->status;
|
||||
t->cs_change = 1;
|
||||
spi_message_add_tail(t, &host->m);
|
||||
|
||||
if (host->dma_dev) {
|
||||
host->m.is_dma_mapped = 1;
|
||||
dma_sync_single_for_device(host->dma_dev,
|
||||
host->data_dma, sizeof(*host->data),
|
||||
DMA_BIDIRECTIONAL);
|
||||
}
|
||||
status = spi_sync_locked(host->spi, &host->m);
|
||||
|
||||
if (host->dma_dev)
|
||||
dma_sync_single_for_cpu(host->dma_dev,
|
||||
host->data_dma, sizeof(*host->data),
|
||||
DMA_BIDIRECTIONAL);
|
||||
if (status < 0) {
|
||||
dev_dbg(&host->spi->dev, " ... write returned %d\n", status);
|
||||
cmd->error = status;
|
||||
@@ -540,9 +508,6 @@ mmc_spi_command_send(struct mmc_spi_host *host,
|
||||
* We always provide TX data for data and CRC. The MMC/SD protocol
|
||||
* requires us to write ones; but Linux defaults to writing zeroes;
|
||||
* so we explicitly initialize it to all ones on RX paths.
|
||||
*
|
||||
* We also handle DMA mapping, so the underlying SPI controller does
|
||||
* not need to (re)do it for each message.
|
||||
*/
|
||||
static void
|
||||
mmc_spi_setup_data_message(
|
||||
@@ -552,11 +517,8 @@ mmc_spi_setup_data_message(
|
||||
{
|
||||
struct spi_transfer *t;
|
||||
struct scratch *scratch = host->data;
|
||||
dma_addr_t dma = host->data_dma;
|
||||
|
||||
spi_message_init(&host->m);
|
||||
if (dma)
|
||||
host->m.is_dma_mapped = 1;
|
||||
|
||||
/* for reads, readblock() skips 0xff bytes before finding
|
||||
* the token; for writes, this transfer issues that token.
|
||||
@@ -570,8 +532,6 @@ mmc_spi_setup_data_message(
|
||||
else
|
||||
scratch->data_token = SPI_TOKEN_SINGLE;
|
||||
t->tx_buf = &scratch->data_token;
|
||||
if (dma)
|
||||
t->tx_dma = dma + offsetof(struct scratch, data_token);
|
||||
spi_message_add_tail(t, &host->m);
|
||||
}
|
||||
|
||||
@@ -581,7 +541,6 @@ mmc_spi_setup_data_message(
|
||||
t = &host->t;
|
||||
memset(t, 0, sizeof(*t));
|
||||
t->tx_buf = host->ones;
|
||||
t->tx_dma = host->ones_dma;
|
||||
/* length and actual buffer info are written later */
|
||||
spi_message_add_tail(t, &host->m);
|
||||
|
||||
@@ -591,14 +550,9 @@ mmc_spi_setup_data_message(
|
||||
if (direction == DMA_TO_DEVICE) {
|
||||
/* the actual CRC may get written later */
|
||||
t->tx_buf = &scratch->crc_val;
|
||||
if (dma)
|
||||
t->tx_dma = dma + offsetof(struct scratch, crc_val);
|
||||
} else {
|
||||
t->tx_buf = host->ones;
|
||||
t->tx_dma = host->ones_dma;
|
||||
t->rx_buf = &scratch->crc_val;
|
||||
if (dma)
|
||||
t->rx_dma = dma + offsetof(struct scratch, crc_val);
|
||||
}
|
||||
spi_message_add_tail(t, &host->m);
|
||||
|
||||
@@ -621,10 +575,7 @@ mmc_spi_setup_data_message(
|
||||
memset(t, 0, sizeof(*t));
|
||||
t->len = (direction == DMA_TO_DEVICE) ? sizeof(scratch->status) : 1;
|
||||
t->tx_buf = host->ones;
|
||||
t->tx_dma = host->ones_dma;
|
||||
t->rx_buf = scratch->status;
|
||||
if (dma)
|
||||
t->rx_dma = dma + offsetof(struct scratch, status);
|
||||
t->cs_change = 1;
|
||||
spi_message_add_tail(t, &host->m);
|
||||
}
|
||||
@@ -653,23 +604,13 @@ mmc_spi_writeblock(struct mmc_spi_host *host, struct spi_transfer *t,
|
||||
|
||||
if (host->mmc->use_spi_crc)
|
||||
scratch->crc_val = cpu_to_be16(crc_itu_t(0, t->tx_buf, t->len));
|
||||
if (host->dma_dev)
|
||||
dma_sync_single_for_device(host->dma_dev,
|
||||
host->data_dma, sizeof(*scratch),
|
||||
DMA_BIDIRECTIONAL);
|
||||
|
||||
status = spi_sync_locked(spi, &host->m);
|
||||
|
||||
if (status != 0) {
|
||||
dev_dbg(&spi->dev, "write error (%d)\n", status);
|
||||
return status;
|
||||
}
|
||||
|
||||
if (host->dma_dev)
|
||||
dma_sync_single_for_cpu(host->dma_dev,
|
||||
host->data_dma, sizeof(*scratch),
|
||||
DMA_BIDIRECTIONAL);
|
||||
|
||||
/*
|
||||
* Get the transmission data-response reply. It must follow
|
||||
* immediately after the data block we transferred. This reply
|
||||
@@ -718,8 +659,6 @@ mmc_spi_writeblock(struct mmc_spi_host *host, struct spi_transfer *t,
|
||||
}
|
||||
|
||||
t->tx_buf += t->len;
|
||||
if (host->dma_dev)
|
||||
t->tx_dma += t->len;
|
||||
|
||||
/* Return when not busy. If we didn't collect that status yet,
|
||||
* we'll need some more I/O.
|
||||
@@ -783,30 +722,12 @@ mmc_spi_readblock(struct mmc_spi_host *host, struct spi_transfer *t,
|
||||
}
|
||||
leftover = status << 1;
|
||||
|
||||
if (host->dma_dev) {
|
||||
dma_sync_single_for_device(host->dma_dev,
|
||||
host->data_dma, sizeof(*scratch),
|
||||
DMA_BIDIRECTIONAL);
|
||||
dma_sync_single_for_device(host->dma_dev,
|
||||
t->rx_dma, t->len,
|
||||
DMA_FROM_DEVICE);
|
||||
}
|
||||
|
||||
status = spi_sync_locked(spi, &host->m);
|
||||
if (status < 0) {
|
||||
dev_dbg(&spi->dev, "read error %d\n", status);
|
||||
return status;
|
||||
}
|
||||
|
||||
if (host->dma_dev) {
|
||||
dma_sync_single_for_cpu(host->dma_dev,
|
||||
host->data_dma, sizeof(*scratch),
|
||||
DMA_BIDIRECTIONAL);
|
||||
dma_sync_single_for_cpu(host->dma_dev,
|
||||
t->rx_dma, t->len,
|
||||
DMA_FROM_DEVICE);
|
||||
}
|
||||
|
||||
if (bitshift) {
|
||||
/* Walk through the data and the crc and do
|
||||
* all the magic to get byte-aligned data.
|
||||
@@ -841,8 +762,6 @@ mmc_spi_readblock(struct mmc_spi_host *host, struct spi_transfer *t,
|
||||
}
|
||||
|
||||
t->rx_buf += t->len;
|
||||
if (host->dma_dev)
|
||||
t->rx_dma += t->len;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -857,7 +776,6 @@ mmc_spi_data_do(struct mmc_spi_host *host, struct mmc_command *cmd,
|
||||
struct mmc_data *data, u32 blk_size)
|
||||
{
|
||||
struct spi_device *spi = host->spi;
|
||||
struct device *dma_dev = host->dma_dev;
|
||||
struct spi_transfer *t;
|
||||
enum dma_data_direction direction = mmc_get_dma_dir(data);
|
||||
struct scatterlist *sg;
|
||||
@@ -884,31 +802,8 @@ mmc_spi_data_do(struct mmc_spi_host *host, struct mmc_command *cmd,
|
||||
*/
|
||||
for_each_sg(data->sg, sg, data->sg_len, n_sg) {
|
||||
int status = 0;
|
||||
dma_addr_t dma_addr = 0;
|
||||
void *kmap_addr;
|
||||
unsigned length = sg->length;
|
||||
enum dma_data_direction dir = direction;
|
||||
|
||||
/* set up dma mapping for controller drivers that might
|
||||
* use DMA ... though they may fall back to PIO
|
||||
*/
|
||||
if (dma_dev) {
|
||||
/* never invalidate whole *shared* pages ... */
|
||||
if ((sg->offset != 0 || length != PAGE_SIZE)
|
||||
&& dir == DMA_FROM_DEVICE)
|
||||
dir = DMA_BIDIRECTIONAL;
|
||||
|
||||
dma_addr = dma_map_page(dma_dev, sg_page(sg), 0,
|
||||
PAGE_SIZE, dir);
|
||||
if (dma_mapping_error(dma_dev, dma_addr)) {
|
||||
data->error = -EFAULT;
|
||||
break;
|
||||
}
|
||||
if (direction == DMA_TO_DEVICE)
|
||||
t->tx_dma = dma_addr + sg->offset;
|
||||
else
|
||||
t->rx_dma = dma_addr + sg->offset;
|
||||
}
|
||||
|
||||
/* allow pio too; we don't allow highmem */
|
||||
kmap_addr = kmap(sg_page(sg));
|
||||
@@ -941,8 +836,6 @@ mmc_spi_data_do(struct mmc_spi_host *host, struct mmc_command *cmd,
|
||||
if (direction == DMA_FROM_DEVICE)
|
||||
flush_dcache_page(sg_page(sg));
|
||||
kunmap(sg_page(sg));
|
||||
if (dma_dev)
|
||||
dma_unmap_page(dma_dev, dma_addr, PAGE_SIZE, dir);
|
||||
|
||||
if (status < 0) {
|
||||
data->error = status;
|
||||
@@ -977,21 +870,9 @@ mmc_spi_data_do(struct mmc_spi_host *host, struct mmc_command *cmd,
|
||||
scratch->status[0] = SPI_TOKEN_STOP_TRAN;
|
||||
|
||||
host->early_status.tx_buf = host->early_status.rx_buf;
|
||||
host->early_status.tx_dma = host->early_status.rx_dma;
|
||||
host->early_status.len = statlen;
|
||||
|
||||
if (host->dma_dev)
|
||||
dma_sync_single_for_device(host->dma_dev,
|
||||
host->data_dma, sizeof(*scratch),
|
||||
DMA_BIDIRECTIONAL);
|
||||
|
||||
tmp = spi_sync_locked(spi, &host->m);
|
||||
|
||||
if (host->dma_dev)
|
||||
dma_sync_single_for_cpu(host->dma_dev,
|
||||
host->data_dma, sizeof(*scratch),
|
||||
DMA_BIDIRECTIONAL);
|
||||
|
||||
if (tmp < 0) {
|
||||
if (!data->error)
|
||||
data->error = tmp;
|
||||
@@ -1265,52 +1146,6 @@ mmc_spi_detect_irq(int irq, void *mmc)
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_HAS_DMA
|
||||
static int mmc_spi_dma_alloc(struct mmc_spi_host *host)
|
||||
{
|
||||
struct spi_device *spi = host->spi;
|
||||
struct device *dev;
|
||||
|
||||
if (!spi->master->dev.parent->dma_mask)
|
||||
return 0;
|
||||
|
||||
dev = spi->master->dev.parent;
|
||||
|
||||
host->ones_dma = dma_map_single(dev, host->ones, MMC_SPI_BLOCKSIZE,
|
||||
DMA_TO_DEVICE);
|
||||
if (dma_mapping_error(dev, host->ones_dma))
|
||||
return -ENOMEM;
|
||||
|
||||
host->data_dma = dma_map_single(dev, host->data, sizeof(*host->data),
|
||||
DMA_BIDIRECTIONAL);
|
||||
if (dma_mapping_error(dev, host->data_dma)) {
|
||||
dma_unmap_single(dev, host->ones_dma, MMC_SPI_BLOCKSIZE,
|
||||
DMA_TO_DEVICE);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
dma_sync_single_for_cpu(dev, host->data_dma, sizeof(*host->data),
|
||||
DMA_BIDIRECTIONAL);
|
||||
|
||||
host->dma_dev = dev;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mmc_spi_dma_free(struct mmc_spi_host *host)
|
||||
{
|
||||
if (!host->dma_dev)
|
||||
return;
|
||||
|
||||
dma_unmap_single(host->dma_dev, host->ones_dma, MMC_SPI_BLOCKSIZE,
|
||||
DMA_TO_DEVICE);
|
||||
dma_unmap_single(host->dma_dev, host->data_dma, sizeof(*host->data),
|
||||
DMA_BIDIRECTIONAL);
|
||||
}
|
||||
#else
|
||||
static inline int mmc_spi_dma_alloc(struct mmc_spi_host *host) { return 0; }
|
||||
static inline void mmc_spi_dma_free(struct mmc_spi_host *host) {}
|
||||
#endif
|
||||
|
||||
static int mmc_spi_probe(struct spi_device *spi)
|
||||
{
|
||||
void *ones;
|
||||
@@ -1402,24 +1237,17 @@ static int mmc_spi_probe(struct spi_device *spi)
|
||||
host->powerup_msecs = 250;
|
||||
}
|
||||
|
||||
/* preallocate dma buffers */
|
||||
/* Preallocate buffers */
|
||||
host->data = kmalloc(sizeof(*host->data), GFP_KERNEL);
|
||||
if (!host->data)
|
||||
goto fail_nobuf1;
|
||||
|
||||
status = mmc_spi_dma_alloc(host);
|
||||
if (status)
|
||||
goto fail_dma;
|
||||
|
||||
/* setup message for status/busy readback */
|
||||
spi_message_init(&host->readback);
|
||||
host->readback.is_dma_mapped = (host->dma_dev != NULL);
|
||||
|
||||
spi_message_add_tail(&host->status, &host->readback);
|
||||
host->status.tx_buf = host->ones;
|
||||
host->status.tx_dma = host->ones_dma;
|
||||
host->status.rx_buf = &host->data->status;
|
||||
host->status.rx_dma = host->data_dma + offsetof(struct scratch, status);
|
||||
host->status.cs_change = 1;
|
||||
|
||||
/* register card detect irq */
|
||||
@@ -1464,9 +1292,8 @@ static int mmc_spi_probe(struct spi_device *spi)
|
||||
if (!status)
|
||||
has_ro = true;
|
||||
|
||||
dev_info(&spi->dev, "SD/MMC host %s%s%s%s%s\n",
|
||||
dev_info(&spi->dev, "SD/MMC host %s%s%s%s\n",
|
||||
dev_name(&mmc->class_dev),
|
||||
host->dma_dev ? "" : ", no DMA",
|
||||
has_ro ? "" : ", no WP",
|
||||
(host->pdata && host->pdata->setpower)
|
||||
? "" : ", no poweroff",
|
||||
@@ -1477,8 +1304,6 @@ static int mmc_spi_probe(struct spi_device *spi)
|
||||
fail_gpiod_request:
|
||||
mmc_remove_host(mmc);
|
||||
fail_glue_init:
|
||||
mmc_spi_dma_free(host);
|
||||
fail_dma:
|
||||
kfree(host->data);
|
||||
fail_nobuf1:
|
||||
mmc_spi_put_pdata(spi);
|
||||
@@ -1500,7 +1325,6 @@ static void mmc_spi_remove(struct spi_device *spi)
|
||||
|
||||
mmc_remove_host(mmc);
|
||||
|
||||
mmc_spi_dma_free(host);
|
||||
kfree(host->data);
|
||||
kfree(host->ones);
|
||||
|
||||
|
||||
@@ -273,6 +273,7 @@ static struct variant_data variant_stm32_sdmmc = {
|
||||
.datactrl_mask_sdio = MCI_DPSM_ST_SDIOEN,
|
||||
.stm32_idmabsize_mask = GENMASK(12, 5),
|
||||
.stm32_idmabsize_align = BIT(5),
|
||||
.supports_sdio_irq = true,
|
||||
.busy_timeout = true,
|
||||
.busy_detect = true,
|
||||
.busy_detect_flag = MCI_STM32_BUSYD0,
|
||||
@@ -300,6 +301,7 @@ static struct variant_data variant_stm32_sdmmcv2 = {
|
||||
.datactrl_mask_sdio = MCI_DPSM_ST_SDIOEN,
|
||||
.stm32_idmabsize_mask = GENMASK(16, 5),
|
||||
.stm32_idmabsize_align = BIT(5),
|
||||
.supports_sdio_irq = true,
|
||||
.dma_lli = true,
|
||||
.busy_timeout = true,
|
||||
.busy_detect = true,
|
||||
@@ -328,6 +330,7 @@ static struct variant_data variant_stm32_sdmmcv3 = {
|
||||
.datactrl_mask_sdio = MCI_DPSM_ST_SDIOEN,
|
||||
.stm32_idmabsize_mask = GENMASK(16, 6),
|
||||
.stm32_idmabsize_align = BIT(6),
|
||||
.supports_sdio_irq = true,
|
||||
.dma_lli = true,
|
||||
.busy_timeout = true,
|
||||
.busy_detect = true,
|
||||
@@ -421,8 +424,9 @@ void mmci_write_pwrreg(struct mmci_host *host, u32 pwr)
|
||||
*/
|
||||
static void mmci_write_datactrlreg(struct mmci_host *host, u32 datactrl)
|
||||
{
|
||||
/* Keep busy mode in DPSM if enabled */
|
||||
datactrl |= host->datactrl_reg & host->variant->busy_dpsm_flag;
|
||||
/* Keep busy mode in DPSM and SDIO mask if enabled */
|
||||
datactrl |= host->datactrl_reg & (host->variant->busy_dpsm_flag |
|
||||
host->variant->datactrl_mask_sdio);
|
||||
|
||||
if (host->datactrl_reg != datactrl) {
|
||||
host->datactrl_reg = datactrl;
|
||||
@@ -1762,6 +1766,25 @@ static irqreturn_t mmci_pio_irq(int irq, void *dev_id)
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static void mmci_write_sdio_irq_bit(struct mmci_host *host, int enable)
|
||||
{
|
||||
void __iomem *base = host->base;
|
||||
u32 mask = readl_relaxed(base + MMCIMASK0);
|
||||
|
||||
if (enable)
|
||||
writel_relaxed(mask | MCI_ST_SDIOITMASK, base + MMCIMASK0);
|
||||
else
|
||||
writel_relaxed(mask & ~MCI_ST_SDIOITMASK, base + MMCIMASK0);
|
||||
}
|
||||
|
||||
static void mmci_signal_sdio_irq(struct mmci_host *host, u32 status)
|
||||
{
|
||||
if (status & MCI_ST_SDIOIT) {
|
||||
mmci_write_sdio_irq_bit(host, 0);
|
||||
sdio_signal_irq(host->mmc);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle completion of command and data transfers.
|
||||
*/
|
||||
@@ -1806,6 +1829,9 @@ static irqreturn_t mmci_irq(int irq, void *dev_id)
|
||||
mmci_data_irq(host, host->data, status);
|
||||
}
|
||||
|
||||
if (host->variant->supports_sdio_irq)
|
||||
mmci_signal_sdio_irq(host, status);
|
||||
|
||||
/*
|
||||
* Busy detection has been handled by mmci_cmd_irq() above.
|
||||
* Clear the status bit to prevent polling in IRQ context.
|
||||
@@ -2042,6 +2068,35 @@ static int mmci_sig_volt_switch(struct mmc_host *mmc, struct mmc_ios *ios)
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void mmci_enable_sdio_irq(struct mmc_host *mmc, int enable)
|
||||
{
|
||||
struct mmci_host *host = mmc_priv(mmc);
|
||||
unsigned long flags;
|
||||
|
||||
if (enable)
|
||||
/* Keep the SDIO mode bit if SDIO irqs are enabled */
|
||||
pm_runtime_get_sync(mmc_dev(mmc));
|
||||
|
||||
spin_lock_irqsave(&host->lock, flags);
|
||||
mmci_write_sdio_irq_bit(host, enable);
|
||||
spin_unlock_irqrestore(&host->lock, flags);
|
||||
|
||||
if (!enable) {
|
||||
pm_runtime_mark_last_busy(mmc_dev(mmc));
|
||||
pm_runtime_put_autosuspend(mmc_dev(mmc));
|
||||
}
|
||||
}
|
||||
|
||||
static void mmci_ack_sdio_irq(struct mmc_host *mmc)
|
||||
{
|
||||
struct mmci_host *host = mmc_priv(mmc);
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&host->lock, flags);
|
||||
mmci_write_sdio_irq_bit(host, 1);
|
||||
spin_unlock_irqrestore(&host->lock, flags);
|
||||
}
|
||||
|
||||
static struct mmc_host_ops mmci_ops = {
|
||||
.request = mmci_request,
|
||||
.pre_req = mmci_pre_request,
|
||||
@@ -2317,6 +2372,16 @@ static int mmci_probe(struct amba_device *dev,
|
||||
mmc->caps |= MMC_CAP_WAIT_WHILE_BUSY;
|
||||
}
|
||||
|
||||
if (variant->supports_sdio_irq && host->mmc->caps & MMC_CAP_SDIO_IRQ) {
|
||||
mmc->caps2 |= MMC_CAP2_SDIO_IRQ_NOTHREAD;
|
||||
|
||||
mmci_ops.enable_sdio_irq = mmci_enable_sdio_irq;
|
||||
mmci_ops.ack_sdio_irq = mmci_ack_sdio_irq;
|
||||
|
||||
mmci_write_datactrlreg(host,
|
||||
host->variant->datactrl_mask_sdio);
|
||||
}
|
||||
|
||||
/* Variants with mandatory busy timeout in HW needs R1B responses. */
|
||||
if (variant->busy_timeout)
|
||||
mmc->caps |= MMC_CAP_NEED_RSP_BUSY;
|
||||
|
||||
@@ -331,6 +331,7 @@ enum mmci_busy_state {
|
||||
* register.
|
||||
* @opendrain: bitmask identifying the OPENDRAIN bit inside MMCIPOWER register
|
||||
* @dma_lli: true if variant has dma link list feature.
|
||||
* @supports_sdio_irq: allow SD I/O card to interrupt the host
|
||||
* @stm32_idmabsize_mask: stm32 sdmmc idma buffer size.
|
||||
* @dma_flow_controller: use peripheral as flow controller for DMA.
|
||||
*/
|
||||
@@ -377,6 +378,7 @@ struct variant_data {
|
||||
u32 start_err;
|
||||
u32 opendrain;
|
||||
u8 dma_lli:1;
|
||||
bool supports_sdio_irq;
|
||||
u32 stm32_idmabsize_mask;
|
||||
u32 stm32_idmabsize_align;
|
||||
bool dma_flow_controller;
|
||||
|
||||
@@ -252,12 +252,16 @@
|
||||
|
||||
#define MSDC_PAD_TUNE_DATWRDLY GENMASK(4, 0) /* RW */
|
||||
#define MSDC_PAD_TUNE_DATRRDLY GENMASK(12, 8) /* RW */
|
||||
#define MSDC_PAD_TUNE_DATRRDLY2 GENMASK(12, 8) /* RW */
|
||||
#define MSDC_PAD_TUNE_CMDRDLY GENMASK(20, 16) /* RW */
|
||||
#define MSDC_PAD_TUNE_CMDRDLY2 GENMASK(20, 16) /* RW */
|
||||
#define MSDC_PAD_TUNE_CMDRRDLY GENMASK(26, 22) /* RW */
|
||||
#define MSDC_PAD_TUNE_CLKTDLY GENMASK(31, 27) /* RW */
|
||||
#define MSDC_PAD_TUNE_RXDLYSEL BIT(15) /* RW */
|
||||
#define MSDC_PAD_TUNE_RD_SEL BIT(13) /* RW */
|
||||
#define MSDC_PAD_TUNE_CMD_SEL BIT(21) /* RW */
|
||||
#define MSDC_PAD_TUNE_RD2_SEL BIT(13) /* RW */
|
||||
#define MSDC_PAD_TUNE_CMD2_SEL BIT(21) /* RW */
|
||||
|
||||
#define PAD_DS_TUNE_DLY_SEL BIT(0) /* RW */
|
||||
#define PAD_DS_TUNE_DLY1 GENMASK(6, 2) /* RW */
|
||||
@@ -325,7 +329,9 @@
|
||||
|
||||
#define DEFAULT_DEBOUNCE (8) /* 8 cycles CD debounce */
|
||||
|
||||
#define PAD_DELAY_MAX 32 /* PAD delay cells */
|
||||
#define TUNING_REG2_FIXED_OFFEST 4
|
||||
#define PAD_DELAY_HALF 32 /* PAD delay cells */
|
||||
#define PAD_DELAY_FULL 64
|
||||
/*--------------------------------------------------------------------------*/
|
||||
/* Descriptor Structure */
|
||||
/*--------------------------------------------------------------------------*/
|
||||
@@ -461,6 +467,7 @@ struct msdc_host {
|
||||
u32 hs400_ds_dly3;
|
||||
u32 hs200_cmd_int_delay; /* cmd internal delay for HS200/SDR104 */
|
||||
u32 hs400_cmd_int_delay; /* cmd internal delay for HS400 */
|
||||
u32 tuning_step;
|
||||
bool hs400_cmd_resp_sel_rising;
|
||||
/* cmd response sample selection for HS400 */
|
||||
bool hs400_mode; /* current eMMC will run at hs400 mode */
|
||||
@@ -1149,9 +1156,11 @@ static void msdc_recheck_sdio_irq(struct msdc_host *host)
|
||||
|
||||
static void msdc_track_cmd_data(struct msdc_host *host, struct mmc_command *cmd)
|
||||
{
|
||||
if (host->error)
|
||||
dev_dbg(host->dev, "%s: cmd=%d arg=%08X; host->error=0x%08X\n",
|
||||
__func__, cmd->opcode, cmd->arg, host->error);
|
||||
if (host->error &&
|
||||
((!mmc_op_tuning(cmd->opcode) && !host->hs400_tuning) ||
|
||||
cmd->error == -ETIMEDOUT))
|
||||
dev_warn(host->dev, "%s: cmd=%d arg=%08X; host->error=0x%08X\n",
|
||||
__func__, cmd->opcode, cmd->arg, host->error);
|
||||
}
|
||||
|
||||
static void msdc_request_done(struct msdc_host *host, struct mmc_request *mrq)
|
||||
@@ -1615,7 +1624,7 @@ static irqreturn_t msdc_cmdq_irq(struct msdc_host *host, u32 intsts)
|
||||
}
|
||||
|
||||
if (cmd_err || dat_err) {
|
||||
dev_err(host->dev, "cmd_err = %d, dat_err =%d, intsts = 0x%x",
|
||||
dev_err(host->dev, "cmd_err = %d, dat_err = %d, intsts = 0x%x",
|
||||
cmd_err, dat_err, intsts);
|
||||
}
|
||||
|
||||
@@ -1780,10 +1789,20 @@ static void msdc_init_hw(struct msdc_host *host)
|
||||
DATA_K_VALUE_SEL);
|
||||
sdr_set_bits(host->top_base + EMMC_TOP_CMD,
|
||||
PAD_CMD_RD_RXDLY_SEL);
|
||||
if (host->tuning_step > PAD_DELAY_HALF) {
|
||||
sdr_set_bits(host->top_base + EMMC_TOP_CONTROL,
|
||||
PAD_DAT_RD_RXDLY2_SEL);
|
||||
sdr_set_bits(host->top_base + EMMC_TOP_CMD,
|
||||
PAD_CMD_RD_RXDLY2_SEL);
|
||||
}
|
||||
} else {
|
||||
sdr_set_bits(host->base + tune_reg,
|
||||
MSDC_PAD_TUNE_RD_SEL |
|
||||
MSDC_PAD_TUNE_CMD_SEL);
|
||||
if (host->tuning_step > PAD_DELAY_HALF)
|
||||
sdr_set_bits(host->base + tune_reg + TUNING_REG2_FIXED_OFFEST,
|
||||
MSDC_PAD_TUNE_RD2_SEL |
|
||||
MSDC_PAD_TUNE_CMD2_SEL);
|
||||
}
|
||||
} else {
|
||||
/* choose clock tune */
|
||||
@@ -1925,24 +1944,24 @@ static void msdc_ops_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
|
||||
msdc_set_mclk(host, ios->timing, ios->clock);
|
||||
}
|
||||
|
||||
static u32 test_delay_bit(u32 delay, u32 bit)
|
||||
static u64 test_delay_bit(u64 delay, u32 bit)
|
||||
{
|
||||
bit %= PAD_DELAY_MAX;
|
||||
return delay & BIT(bit);
|
||||
bit %= PAD_DELAY_FULL;
|
||||
return delay & BIT_ULL(bit);
|
||||
}
|
||||
|
||||
static int get_delay_len(u32 delay, u32 start_bit)
|
||||
static int get_delay_len(u64 delay, u32 start_bit)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < (PAD_DELAY_MAX - start_bit); i++) {
|
||||
for (i = 0; i < (PAD_DELAY_FULL - start_bit); i++) {
|
||||
if (test_delay_bit(delay, start_bit + i) == 0)
|
||||
return i;
|
||||
}
|
||||
return PAD_DELAY_MAX - start_bit;
|
||||
return PAD_DELAY_FULL - start_bit;
|
||||
}
|
||||
|
||||
static struct msdc_delay_phase get_best_delay(struct msdc_host *host, u32 delay)
|
||||
static struct msdc_delay_phase get_best_delay(struct msdc_host *host, u64 delay)
|
||||
{
|
||||
int start = 0, len = 0;
|
||||
int start_final = 0, len_final = 0;
|
||||
@@ -1950,28 +1969,28 @@ static struct msdc_delay_phase get_best_delay(struct msdc_host *host, u32 delay)
|
||||
struct msdc_delay_phase delay_phase = { 0, };
|
||||
|
||||
if (delay == 0) {
|
||||
dev_err(host->dev, "phase error: [map:%x]\n", delay);
|
||||
dev_err(host->dev, "phase error: [map:%016llx]\n", delay);
|
||||
delay_phase.final_phase = final_phase;
|
||||
return delay_phase;
|
||||
}
|
||||
|
||||
while (start < PAD_DELAY_MAX) {
|
||||
while (start < PAD_DELAY_FULL) {
|
||||
len = get_delay_len(delay, start);
|
||||
if (len_final < len) {
|
||||
start_final = start;
|
||||
len_final = len;
|
||||
}
|
||||
start += len ? len : 1;
|
||||
if (len >= 12 && start_final < 4)
|
||||
if (!upper_32_bits(delay) && len >= 12 && start_final < 4)
|
||||
break;
|
||||
}
|
||||
|
||||
/* The rule is that to find the smallest delay cell */
|
||||
if (start_final == 0)
|
||||
final_phase = (start_final + len_final / 3) % PAD_DELAY_MAX;
|
||||
final_phase = (start_final + len_final / 3) % PAD_DELAY_FULL;
|
||||
else
|
||||
final_phase = (start_final + len_final / 2) % PAD_DELAY_MAX;
|
||||
dev_dbg(host->dev, "phase: [map:%x] [maxlen:%d] [final:%d]\n",
|
||||
final_phase = (start_final + len_final / 2) % PAD_DELAY_FULL;
|
||||
dev_dbg(host->dev, "phase: [map:%016llx] [maxlen:%d] [final:%d]\n",
|
||||
delay, len_final, final_phase);
|
||||
|
||||
delay_phase.maxlen = len_final;
|
||||
@@ -1984,30 +2003,64 @@ static inline void msdc_set_cmd_delay(struct msdc_host *host, u32 value)
|
||||
{
|
||||
u32 tune_reg = host->dev_comp->pad_tune_reg;
|
||||
|
||||
if (host->top_base)
|
||||
sdr_set_field(host->top_base + EMMC_TOP_CMD, PAD_CMD_RXDLY,
|
||||
value);
|
||||
else
|
||||
sdr_set_field(host->base + tune_reg, MSDC_PAD_TUNE_CMDRDLY,
|
||||
value);
|
||||
if (host->top_base) {
|
||||
if (value < PAD_DELAY_HALF) {
|
||||
sdr_set_field(host->top_base + EMMC_TOP_CMD, PAD_CMD_RXDLY, value);
|
||||
sdr_set_field(host->top_base + EMMC_TOP_CMD, PAD_CMD_RXDLY2, 0);
|
||||
} else {
|
||||
sdr_set_field(host->top_base + EMMC_TOP_CMD, PAD_CMD_RXDLY,
|
||||
PAD_DELAY_HALF - 1);
|
||||
sdr_set_field(host->top_base + EMMC_TOP_CMD, PAD_CMD_RXDLY2,
|
||||
value - PAD_DELAY_HALF);
|
||||
}
|
||||
} else {
|
||||
if (value < PAD_DELAY_HALF) {
|
||||
sdr_set_field(host->base + tune_reg, MSDC_PAD_TUNE_CMDRDLY, value);
|
||||
sdr_set_field(host->base + tune_reg + TUNING_REG2_FIXED_OFFEST,
|
||||
MSDC_PAD_TUNE_CMDRDLY2, 0);
|
||||
} else {
|
||||
sdr_set_field(host->base + tune_reg, MSDC_PAD_TUNE_CMDRDLY,
|
||||
PAD_DELAY_HALF - 1);
|
||||
sdr_set_field(host->base + tune_reg + TUNING_REG2_FIXED_OFFEST,
|
||||
MSDC_PAD_TUNE_CMDRDLY2, value - PAD_DELAY_HALF);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline void msdc_set_data_delay(struct msdc_host *host, u32 value)
|
||||
{
|
||||
u32 tune_reg = host->dev_comp->pad_tune_reg;
|
||||
|
||||
if (host->top_base)
|
||||
sdr_set_field(host->top_base + EMMC_TOP_CONTROL,
|
||||
PAD_DAT_RD_RXDLY, value);
|
||||
else
|
||||
sdr_set_field(host->base + tune_reg, MSDC_PAD_TUNE_DATRRDLY,
|
||||
value);
|
||||
if (host->top_base) {
|
||||
if (value < PAD_DELAY_HALF) {
|
||||
sdr_set_field(host->top_base + EMMC_TOP_CONTROL,
|
||||
PAD_DAT_RD_RXDLY, value);
|
||||
sdr_set_field(host->top_base + EMMC_TOP_CONTROL,
|
||||
PAD_DAT_RD_RXDLY2, 0);
|
||||
} else {
|
||||
sdr_set_field(host->top_base + EMMC_TOP_CONTROL,
|
||||
PAD_DAT_RD_RXDLY, PAD_DELAY_HALF - 1);
|
||||
sdr_set_field(host->top_base + EMMC_TOP_CONTROL,
|
||||
PAD_DAT_RD_RXDLY2, value - PAD_DELAY_HALF);
|
||||
}
|
||||
} else {
|
||||
if (value < PAD_DELAY_HALF) {
|
||||
sdr_set_field(host->base + tune_reg, MSDC_PAD_TUNE_DATRRDLY, value);
|
||||
sdr_set_field(host->base + tune_reg + TUNING_REG2_FIXED_OFFEST,
|
||||
MSDC_PAD_TUNE_DATRRDLY2, 0);
|
||||
} else {
|
||||
sdr_set_field(host->base + tune_reg, MSDC_PAD_TUNE_DATRRDLY,
|
||||
PAD_DELAY_HALF - 1);
|
||||
sdr_set_field(host->base + tune_reg + TUNING_REG2_FIXED_OFFEST,
|
||||
MSDC_PAD_TUNE_DATRRDLY2, value - PAD_DELAY_HALF);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int msdc_tune_response(struct mmc_host *mmc, u32 opcode)
|
||||
{
|
||||
struct msdc_host *host = mmc_priv(mmc);
|
||||
u32 rise_delay = 0, fall_delay = 0;
|
||||
u64 rise_delay = 0, fall_delay = 0;
|
||||
struct msdc_delay_phase final_rise_delay, final_fall_delay = { 0,};
|
||||
struct msdc_delay_phase internal_delay_phase;
|
||||
u8 final_delay, final_maxlen;
|
||||
@@ -2023,7 +2076,7 @@ static int msdc_tune_response(struct mmc_host *mmc, u32 opcode)
|
||||
host->hs200_cmd_int_delay);
|
||||
|
||||
sdr_clr_bits(host->base + MSDC_IOCON, MSDC_IOCON_RSPL);
|
||||
for (i = 0 ; i < PAD_DELAY_MAX; i++) {
|
||||
for (i = 0; i < host->tuning_step; i++) {
|
||||
msdc_set_cmd_delay(host, i);
|
||||
/*
|
||||
* Using the same parameters, it may sometimes pass the test,
|
||||
@@ -2033,9 +2086,9 @@ static int msdc_tune_response(struct mmc_host *mmc, u32 opcode)
|
||||
for (j = 0; j < 3; j++) {
|
||||
mmc_send_tuning(mmc, opcode, &cmd_err);
|
||||
if (!cmd_err) {
|
||||
rise_delay |= BIT(i);
|
||||
rise_delay |= BIT_ULL(i);
|
||||
} else {
|
||||
rise_delay &= ~BIT(i);
|
||||
rise_delay &= ~BIT_ULL(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -2047,7 +2100,7 @@ static int msdc_tune_response(struct mmc_host *mmc, u32 opcode)
|
||||
goto skip_fall;
|
||||
|
||||
sdr_set_bits(host->base + MSDC_IOCON, MSDC_IOCON_RSPL);
|
||||
for (i = 0; i < PAD_DELAY_MAX; i++) {
|
||||
for (i = 0; i < host->tuning_step; i++) {
|
||||
msdc_set_cmd_delay(host, i);
|
||||
/*
|
||||
* Using the same parameters, it may sometimes pass the test,
|
||||
@@ -2057,9 +2110,9 @@ static int msdc_tune_response(struct mmc_host *mmc, u32 opcode)
|
||||
for (j = 0; j < 3; j++) {
|
||||
mmc_send_tuning(mmc, opcode, &cmd_err);
|
||||
if (!cmd_err) {
|
||||
fall_delay |= BIT(i);
|
||||
fall_delay |= BIT_ULL(i);
|
||||
} else {
|
||||
fall_delay &= ~BIT(i);
|
||||
fall_delay &= ~BIT_ULL(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -2082,12 +2135,12 @@ skip_fall:
|
||||
if (host->dev_comp->async_fifo || host->hs200_cmd_int_delay)
|
||||
goto skip_internal;
|
||||
|
||||
for (i = 0; i < PAD_DELAY_MAX; i++) {
|
||||
for (i = 0; i < host->tuning_step; i++) {
|
||||
sdr_set_field(host->base + tune_reg,
|
||||
MSDC_PAD_TUNE_CMDRRDLY, i);
|
||||
mmc_send_tuning(mmc, opcode, &cmd_err);
|
||||
if (!cmd_err)
|
||||
internal_delay |= BIT(i);
|
||||
internal_delay |= BIT_ULL(i);
|
||||
}
|
||||
dev_dbg(host->dev, "Final internal delay: 0x%x\n", internal_delay);
|
||||
internal_delay_phase = get_best_delay(host, internal_delay);
|
||||
@@ -2121,7 +2174,8 @@ static int hs400_tune_response(struct mmc_host *mmc, u32 opcode)
|
||||
sdr_clr_bits(host->base + MSDC_IOCON, MSDC_IOCON_RSPL);
|
||||
else
|
||||
sdr_set_bits(host->base + MSDC_IOCON, MSDC_IOCON_RSPL);
|
||||
for (i = 0 ; i < PAD_DELAY_MAX; i++) {
|
||||
|
||||
for (i = 0; i < PAD_DELAY_HALF; i++) {
|
||||
sdr_set_field(host->base + PAD_CMD_TUNE,
|
||||
PAD_CMD_TUNE_RX_DLY3, i);
|
||||
/*
|
||||
@@ -2151,7 +2205,7 @@ static int hs400_tune_response(struct mmc_host *mmc, u32 opcode)
|
||||
static int msdc_tune_data(struct mmc_host *mmc, u32 opcode)
|
||||
{
|
||||
struct msdc_host *host = mmc_priv(mmc);
|
||||
u32 rise_delay = 0, fall_delay = 0;
|
||||
u64 rise_delay = 0, fall_delay = 0;
|
||||
struct msdc_delay_phase final_rise_delay, final_fall_delay = { 0,};
|
||||
u8 final_delay, final_maxlen;
|
||||
int i, ret;
|
||||
@@ -2160,11 +2214,11 @@ static int msdc_tune_data(struct mmc_host *mmc, u32 opcode)
|
||||
host->latch_ck);
|
||||
sdr_clr_bits(host->base + MSDC_IOCON, MSDC_IOCON_DSPL);
|
||||
sdr_clr_bits(host->base + MSDC_IOCON, MSDC_IOCON_W_DSPL);
|
||||
for (i = 0 ; i < PAD_DELAY_MAX; i++) {
|
||||
for (i = 0; i < host->tuning_step; i++) {
|
||||
msdc_set_data_delay(host, i);
|
||||
ret = mmc_send_tuning(mmc, opcode, NULL);
|
||||
if (!ret)
|
||||
rise_delay |= BIT(i);
|
||||
rise_delay |= BIT_ULL(i);
|
||||
}
|
||||
final_rise_delay = get_best_delay(host, rise_delay);
|
||||
/* if rising edge has enough margin, then do not scan falling edge */
|
||||
@@ -2174,11 +2228,11 @@ static int msdc_tune_data(struct mmc_host *mmc, u32 opcode)
|
||||
|
||||
sdr_set_bits(host->base + MSDC_IOCON, MSDC_IOCON_DSPL);
|
||||
sdr_set_bits(host->base + MSDC_IOCON, MSDC_IOCON_W_DSPL);
|
||||
for (i = 0; i < PAD_DELAY_MAX; i++) {
|
||||
for (i = 0; i < host->tuning_step; i++) {
|
||||
msdc_set_data_delay(host, i);
|
||||
ret = mmc_send_tuning(mmc, opcode, NULL);
|
||||
if (!ret)
|
||||
fall_delay |= BIT(i);
|
||||
fall_delay |= BIT_ULL(i);
|
||||
}
|
||||
final_fall_delay = get_best_delay(host, fall_delay);
|
||||
|
||||
@@ -2206,7 +2260,7 @@ skip_fall:
|
||||
static int msdc_tune_together(struct mmc_host *mmc, u32 opcode)
|
||||
{
|
||||
struct msdc_host *host = mmc_priv(mmc);
|
||||
u32 rise_delay = 0, fall_delay = 0;
|
||||
u64 rise_delay = 0, fall_delay = 0;
|
||||
struct msdc_delay_phase final_rise_delay, final_fall_delay = { 0,};
|
||||
u8 final_delay, final_maxlen;
|
||||
int i, ret;
|
||||
@@ -2217,12 +2271,12 @@ static int msdc_tune_together(struct mmc_host *mmc, u32 opcode)
|
||||
sdr_clr_bits(host->base + MSDC_IOCON, MSDC_IOCON_RSPL);
|
||||
sdr_clr_bits(host->base + MSDC_IOCON,
|
||||
MSDC_IOCON_DSPL | MSDC_IOCON_W_DSPL);
|
||||
for (i = 0 ; i < PAD_DELAY_MAX; i++) {
|
||||
for (i = 0; i < host->tuning_step; i++) {
|
||||
msdc_set_cmd_delay(host, i);
|
||||
msdc_set_data_delay(host, i);
|
||||
ret = mmc_send_tuning(mmc, opcode, NULL);
|
||||
if (!ret)
|
||||
rise_delay |= BIT(i);
|
||||
rise_delay |= BIT_ULL(i);
|
||||
}
|
||||
final_rise_delay = get_best_delay(host, rise_delay);
|
||||
/* if rising edge has enough margin, then do not scan falling edge */
|
||||
@@ -2233,12 +2287,12 @@ static int msdc_tune_together(struct mmc_host *mmc, u32 opcode)
|
||||
sdr_set_bits(host->base + MSDC_IOCON, MSDC_IOCON_RSPL);
|
||||
sdr_set_bits(host->base + MSDC_IOCON,
|
||||
MSDC_IOCON_DSPL | MSDC_IOCON_W_DSPL);
|
||||
for (i = 0; i < PAD_DELAY_MAX; i++) {
|
||||
for (i = 0; i < host->tuning_step; i++) {
|
||||
msdc_set_cmd_delay(host, i);
|
||||
msdc_set_data_delay(host, i);
|
||||
ret = mmc_send_tuning(mmc, opcode, NULL);
|
||||
if (!ret)
|
||||
fall_delay |= BIT(i);
|
||||
fall_delay |= BIT_ULL(i);
|
||||
}
|
||||
final_fall_delay = get_best_delay(host, fall_delay);
|
||||
|
||||
@@ -2346,7 +2400,7 @@ static int msdc_execute_hs400_tuning(struct mmc_host *mmc, struct mmc_card *card
|
||||
}
|
||||
|
||||
host->hs400_tuning = true;
|
||||
for (i = 0; i < PAD_DELAY_MAX; i++) {
|
||||
for (i = 0; i < PAD_DELAY_HALF; i++) {
|
||||
if (host->top_base)
|
||||
sdr_set_field(host->top_base + EMMC50_PAD_DS_TUNE,
|
||||
PAD_DS_DLY1, i);
|
||||
@@ -2580,6 +2634,8 @@ static const struct cqhci_host_ops msdc_cmdq_ops = {
|
||||
static void msdc_of_property_parse(struct platform_device *pdev,
|
||||
struct msdc_host *host)
|
||||
{
|
||||
struct mmc_host *mmc = mmc_from_priv(host);
|
||||
|
||||
of_property_read_u32(pdev->dev.of_node, "mediatek,latch-ck",
|
||||
&host->latch_ck);
|
||||
|
||||
@@ -2601,6 +2657,14 @@ static void msdc_of_property_parse(struct platform_device *pdev,
|
||||
else
|
||||
host->hs400_cmd_resp_sel_rising = false;
|
||||
|
||||
if (of_property_read_u32(pdev->dev.of_node, "mediatek,tuning-step",
|
||||
&host->tuning_step)) {
|
||||
if (mmc->caps2 & MMC_CAP2_NO_MMC)
|
||||
host->tuning_step = PAD_DELAY_FULL;
|
||||
else
|
||||
host->tuning_step = PAD_DELAY_HALF;
|
||||
}
|
||||
|
||||
if (of_property_read_bool(pdev->dev.of_node,
|
||||
"supports-cqe"))
|
||||
host->cqhci = true;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
*/
|
||||
|
||||
#include <linux/io.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/mmc/host.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
@@ -44,8 +45,13 @@ struct brcmstb_match_priv {
|
||||
|
||||
static inline void enable_clock_gating(struct sdhci_host *host)
|
||||
{
|
||||
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
||||
struct sdhci_brcmstb_priv *priv = sdhci_pltfm_priv(pltfm_host);
|
||||
u32 reg;
|
||||
|
||||
if (!(priv->flags & BRCMSTB_PRIV_FLAGS_GATE_CLOCK))
|
||||
return;
|
||||
|
||||
reg = sdhci_readl(host, SDHCI_VENDOR);
|
||||
reg |= SDHCI_VENDOR_GATE_SDCLK_EN;
|
||||
sdhci_writel(host, reg, SDHCI_VENDOR);
|
||||
@@ -53,14 +59,53 @@ static inline void enable_clock_gating(struct sdhci_host *host)
|
||||
|
||||
static void brcmstb_reset(struct sdhci_host *host, u8 mask)
|
||||
{
|
||||
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
||||
struct sdhci_brcmstb_priv *priv = sdhci_pltfm_priv(pltfm_host);
|
||||
|
||||
sdhci_and_cqhci_reset(host, mask);
|
||||
|
||||
/* Reset will clear this, so re-enable it */
|
||||
if (priv->flags & BRCMSTB_PRIV_FLAGS_GATE_CLOCK)
|
||||
enable_clock_gating(host);
|
||||
enable_clock_gating(host);
|
||||
}
|
||||
|
||||
static void brcmstb_sdhci_reset_cmd_data(struct sdhci_host *host, u8 mask)
|
||||
{
|
||||
u32 new_mask = (mask & (SDHCI_RESET_CMD | SDHCI_RESET_DATA)) << 24;
|
||||
int ret;
|
||||
u32 reg;
|
||||
|
||||
/*
|
||||
* SDHCI_CLOCK_CONTROL register CARD_EN and CLOCK_INT_EN bits shall
|
||||
* be set along with SOFTWARE_RESET register RESET_CMD or RESET_DATA
|
||||
* bits, hence access SDHCI_CLOCK_CONTROL register as 32-bit register
|
||||
*/
|
||||
new_mask |= SDHCI_CLOCK_CARD_EN | SDHCI_CLOCK_INT_EN;
|
||||
reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL);
|
||||
sdhci_writel(host, reg | new_mask, SDHCI_CLOCK_CONTROL);
|
||||
|
||||
reg = sdhci_readb(host, SDHCI_SOFTWARE_RESET);
|
||||
|
||||
ret = read_poll_timeout_atomic(sdhci_readb, reg, !(reg & mask),
|
||||
10, 10000, false,
|
||||
host, SDHCI_SOFTWARE_RESET);
|
||||
|
||||
if (ret) {
|
||||
pr_err("%s: Reset 0x%x never completed.\n",
|
||||
mmc_hostname(host->mmc), (int)mask);
|
||||
sdhci_err_stats_inc(host, CTRL_TIMEOUT);
|
||||
sdhci_dumpregs(host);
|
||||
}
|
||||
}
|
||||
|
||||
static void brcmstb_reset_74165b0(struct sdhci_host *host, u8 mask)
|
||||
{
|
||||
/* take care of RESET_ALL as usual */
|
||||
if (mask & SDHCI_RESET_ALL)
|
||||
sdhci_and_cqhci_reset(host, SDHCI_RESET_ALL);
|
||||
|
||||
/* cmd and/or data treated differently on this core */
|
||||
if (mask & (SDHCI_RESET_CMD | SDHCI_RESET_DATA))
|
||||
brcmstb_sdhci_reset_cmd_data(host, mask);
|
||||
|
||||
/* Reset will clear this, so re-enable it */
|
||||
enable_clock_gating(host);
|
||||
}
|
||||
|
||||
static void sdhci_brcmstb_hs400es(struct mmc_host *mmc, struct mmc_ios *ios)
|
||||
@@ -162,6 +207,13 @@ static struct sdhci_ops sdhci_brcmstb_ops_7216 = {
|
||||
.set_uhs_signaling = sdhci_brcmstb_set_uhs_signaling,
|
||||
};
|
||||
|
||||
static struct sdhci_ops sdhci_brcmstb_ops_74165b0 = {
|
||||
.set_clock = sdhci_brcmstb_set_clock,
|
||||
.set_bus_width = sdhci_set_bus_width,
|
||||
.reset = brcmstb_reset_74165b0,
|
||||
.set_uhs_signaling = sdhci_brcmstb_set_uhs_signaling,
|
||||
};
|
||||
|
||||
static struct brcmstb_match_priv match_priv_7425 = {
|
||||
.flags = BRCMSTB_MATCH_FLAGS_NO_64BIT |
|
||||
BRCMSTB_MATCH_FLAGS_BROKEN_TIMEOUT,
|
||||
@@ -179,10 +231,17 @@ static const struct brcmstb_match_priv match_priv_7216 = {
|
||||
.ops = &sdhci_brcmstb_ops_7216,
|
||||
};
|
||||
|
||||
static struct brcmstb_match_priv match_priv_74165b0 = {
|
||||
.flags = BRCMSTB_MATCH_FLAGS_HAS_CLOCK_GATE,
|
||||
.hs400es = sdhci_brcmstb_hs400es,
|
||||
.ops = &sdhci_brcmstb_ops_74165b0,
|
||||
};
|
||||
|
||||
static const struct of_device_id __maybe_unused sdhci_brcm_of_match[] = {
|
||||
{ .compatible = "brcm,bcm7425-sdhci", .data = &match_priv_7425 },
|
||||
{ .compatible = "brcm,bcm7445-sdhci", .data = &match_priv_7445 },
|
||||
{ .compatible = "brcm,bcm7216-sdhci", .data = &match_priv_7216 },
|
||||
{ .compatible = "brcm,bcm74165b0-sdhci", .data = &match_priv_74165b0 },
|
||||
{},
|
||||
};
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
*/
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/iopoll.h>
|
||||
@@ -35,6 +36,21 @@
|
||||
#define DWCMSHC_CARD_IS_EMMC BIT(0)
|
||||
#define DWCMSHC_ENHANCED_STROBE BIT(8)
|
||||
#define DWCMSHC_EMMC_ATCTRL 0x40
|
||||
/* Tuning and auto-tuning fields in AT_CTRL_R control register */
|
||||
#define AT_CTRL_AT_EN BIT(0) /* autotuning is enabled */
|
||||
#define AT_CTRL_CI_SEL BIT(1) /* interval to drive center phase select */
|
||||
#define AT_CTRL_SWIN_TH_EN BIT(2) /* sampling window threshold enable */
|
||||
#define AT_CTRL_RPT_TUNE_ERR BIT(3) /* enable reporting framing errors */
|
||||
#define AT_CTRL_SW_TUNE_EN BIT(4) /* enable software managed tuning */
|
||||
#define AT_CTRL_WIN_EDGE_SEL_MASK GENMASK(11, 8) /* bits [11:8] */
|
||||
#define AT_CTRL_WIN_EDGE_SEL 0xf /* sampling window edge select */
|
||||
#define AT_CTRL_TUNE_CLK_STOP_EN BIT(16) /* clocks stopped during phase code change */
|
||||
#define AT_CTRL_PRE_CHANGE_DLY_MASK GENMASK(18, 17) /* bits [18:17] */
|
||||
#define AT_CTRL_PRE_CHANGE_DLY 0x1 /* 2-cycle latency */
|
||||
#define AT_CTRL_POST_CHANGE_DLY_MASK GENMASK(20, 19) /* bits [20:19] */
|
||||
#define AT_CTRL_POST_CHANGE_DLY 0x3 /* 4-cycle latency */
|
||||
#define AT_CTRL_SWIN_TH_VAL_MASK GENMASK(31, 24) /* bits [31:24] */
|
||||
#define AT_CTRL_SWIN_TH_VAL 0x9 /* sampling window threshold */
|
||||
|
||||
/* Rockchip specific Registers */
|
||||
#define DWCMSHC_EMMC_DLL_CTRL 0x800
|
||||
@@ -72,6 +88,82 @@
|
||||
(((x) & DWCMSHC_EMMC_DLL_TIMEOUT) == 0))
|
||||
#define RK35xx_MAX_CLKS 3
|
||||
|
||||
/* PHY register area pointer */
|
||||
#define DWC_MSHC_PTR_PHY_R 0x300
|
||||
|
||||
/* PHY general configuration */
|
||||
#define PHY_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x00)
|
||||
#define PHY_CNFG_RSTN_DEASSERT 0x1 /* Deassert PHY reset */
|
||||
#define PHY_CNFG_PAD_SP_MASK GENMASK(19, 16) /* bits [19:16] */
|
||||
#define PHY_CNFG_PAD_SP 0x0c /* PMOS TX drive strength */
|
||||
#define PHY_CNFG_PAD_SN_MASK GENMASK(23, 20) /* bits [23:20] */
|
||||
#define PHY_CNFG_PAD_SN 0x0c /* NMOS TX drive strength */
|
||||
|
||||
/* PHY command/response pad settings */
|
||||
#define PHY_CMDPAD_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x04)
|
||||
|
||||
/* PHY data pad settings */
|
||||
#define PHY_DATAPAD_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x06)
|
||||
|
||||
/* PHY clock pad settings */
|
||||
#define PHY_CLKPAD_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x08)
|
||||
|
||||
/* PHY strobe pad settings */
|
||||
#define PHY_STBPAD_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x0a)
|
||||
|
||||
/* PHY reset pad settings */
|
||||
#define PHY_RSTNPAD_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x0c)
|
||||
|
||||
/* Bitfields are common for all pad settings */
|
||||
#define PHY_PAD_RXSEL_1V8 0x1 /* Receiver type select for 1.8V */
|
||||
#define PHY_PAD_RXSEL_3V3 0x2 /* Receiver type select for 3.3V */
|
||||
|
||||
#define PHY_PAD_WEAKPULL_MASK GENMASK(4, 3) /* bits [4:3] */
|
||||
#define PHY_PAD_WEAKPULL_PULLUP 0x1 /* Weak pull up enabled */
|
||||
#define PHY_PAD_WEAKPULL_PULLDOWN 0x2 /* Weak pull down enabled */
|
||||
|
||||
#define PHY_PAD_TXSLEW_CTRL_P_MASK GENMASK(8, 5) /* bits [8:5] */
|
||||
#define PHY_PAD_TXSLEW_CTRL_P 0x3 /* Slew control for P-Type pad TX */
|
||||
#define PHY_PAD_TXSLEW_CTRL_N_MASK GENMASK(12, 9) /* bits [12:9] */
|
||||
#define PHY_PAD_TXSLEW_CTRL_N 0x3 /* Slew control for N-Type pad TX */
|
||||
|
||||
/* PHY CLK delay line settings */
|
||||
#define PHY_SDCLKDL_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x1d)
|
||||
#define PHY_SDCLKDL_CNFG_UPDATE BIT(4) /* set before writing to SDCLKDL_DC */
|
||||
|
||||
/* PHY CLK delay line delay code */
|
||||
#define PHY_SDCLKDL_DC_R (DWC_MSHC_PTR_PHY_R + 0x1e)
|
||||
#define PHY_SDCLKDL_DC_INITIAL 0x40 /* initial delay code */
|
||||
#define PHY_SDCLKDL_DC_DEFAULT 0x32 /* default delay code */
|
||||
#define PHY_SDCLKDL_DC_HS400 0x18 /* delay code for HS400 mode */
|
||||
|
||||
/* PHY drift_cclk_rx delay line configuration setting */
|
||||
#define PHY_ATDL_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x21)
|
||||
#define PHY_ATDL_CNFG_INPSEL_MASK GENMASK(3, 2) /* bits [3:2] */
|
||||
#define PHY_ATDL_CNFG_INPSEL 0x3 /* delay line input source */
|
||||
|
||||
/* PHY DLL control settings */
|
||||
#define PHY_DLL_CTRL_R (DWC_MSHC_PTR_PHY_R + 0x24)
|
||||
#define PHY_DLL_CTRL_DISABLE 0x0 /* PHY DLL is enabled */
|
||||
#define PHY_DLL_CTRL_ENABLE 0x1 /* PHY DLL is disabled */
|
||||
|
||||
/* PHY DLL configuration register 1 */
|
||||
#define PHY_DLL_CNFG1_R (DWC_MSHC_PTR_PHY_R + 0x25)
|
||||
#define PHY_DLL_CNFG1_SLVDLY_MASK GENMASK(5, 4) /* bits [5:4] */
|
||||
#define PHY_DLL_CNFG1_SLVDLY 0x2 /* DLL slave update delay input */
|
||||
#define PHY_DLL_CNFG1_WAITCYCLE 0x5 /* DLL wait cycle input */
|
||||
|
||||
/* PHY DLL configuration register 2 */
|
||||
#define PHY_DLL_CNFG2_R (DWC_MSHC_PTR_PHY_R + 0x26)
|
||||
#define PHY_DLL_CNFG2_JUMPSTEP 0xa /* DLL jump step input */
|
||||
|
||||
/* PHY DLL master and slave delay line configuration settings */
|
||||
#define PHY_DLLDL_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x28)
|
||||
#define PHY_DLLDL_CNFG_SLV_INPSEL_MASK GENMASK(6, 5) /* bits [6:5] */
|
||||
#define PHY_DLLDL_CNFG_SLV_INPSEL 0x3 /* clock source select for slave DL */
|
||||
|
||||
#define FLAG_IO_FIXED_1V8 BIT(0)
|
||||
|
||||
#define BOUNDARY_OK(addr, len) \
|
||||
((addr | (SZ_128M - 1)) == ((addr + len - 1) | (SZ_128M - 1)))
|
||||
|
||||
@@ -92,6 +184,8 @@ struct dwcmshc_priv {
|
||||
struct clk *bus_clk;
|
||||
int vendor_specific_area1; /* P_VENDOR_SPECIFIC_AREA reg */
|
||||
void *priv; /* pointer to SoC private stuff */
|
||||
u16 delay_line;
|
||||
u16 flags;
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -157,6 +251,127 @@ static void dwcmshc_request(struct mmc_host *mmc, struct mmc_request *mrq)
|
||||
sdhci_request(mmc, mrq);
|
||||
}
|
||||
|
||||
static void dwcmshc_phy_1_8v_init(struct sdhci_host *host)
|
||||
{
|
||||
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
||||
struct dwcmshc_priv *priv = sdhci_pltfm_priv(pltfm_host);
|
||||
u32 val;
|
||||
|
||||
/* deassert phy reset & set tx drive strength */
|
||||
val = PHY_CNFG_RSTN_DEASSERT;
|
||||
val |= FIELD_PREP(PHY_CNFG_PAD_SP_MASK, PHY_CNFG_PAD_SP);
|
||||
val |= FIELD_PREP(PHY_CNFG_PAD_SN_MASK, PHY_CNFG_PAD_SN);
|
||||
sdhci_writel(host, val, PHY_CNFG_R);
|
||||
|
||||
/* disable delay line */
|
||||
sdhci_writeb(host, PHY_SDCLKDL_CNFG_UPDATE, PHY_SDCLKDL_CNFG_R);
|
||||
|
||||
/* set delay line */
|
||||
sdhci_writeb(host, priv->delay_line, PHY_SDCLKDL_DC_R);
|
||||
sdhci_writeb(host, PHY_DLL_CNFG2_JUMPSTEP, PHY_DLL_CNFG2_R);
|
||||
|
||||
/* enable delay lane */
|
||||
val = sdhci_readb(host, PHY_SDCLKDL_CNFG_R);
|
||||
val &= ~(PHY_SDCLKDL_CNFG_UPDATE);
|
||||
sdhci_writeb(host, val, PHY_SDCLKDL_CNFG_R);
|
||||
|
||||
/* configure phy pads */
|
||||
val = PHY_PAD_RXSEL_1V8;
|
||||
val |= FIELD_PREP(PHY_PAD_WEAKPULL_MASK, PHY_PAD_WEAKPULL_PULLUP);
|
||||
val |= FIELD_PREP(PHY_PAD_TXSLEW_CTRL_P_MASK, PHY_PAD_TXSLEW_CTRL_P);
|
||||
val |= FIELD_PREP(PHY_PAD_TXSLEW_CTRL_N_MASK, PHY_PAD_TXSLEW_CTRL_N);
|
||||
sdhci_writew(host, val, PHY_CMDPAD_CNFG_R);
|
||||
sdhci_writew(host, val, PHY_DATAPAD_CNFG_R);
|
||||
sdhci_writew(host, val, PHY_RSTNPAD_CNFG_R);
|
||||
|
||||
val = FIELD_PREP(PHY_PAD_TXSLEW_CTRL_P_MASK, PHY_PAD_TXSLEW_CTRL_P);
|
||||
val |= FIELD_PREP(PHY_PAD_TXSLEW_CTRL_N_MASK, PHY_PAD_TXSLEW_CTRL_N);
|
||||
sdhci_writew(host, val, PHY_CLKPAD_CNFG_R);
|
||||
|
||||
val = PHY_PAD_RXSEL_1V8;
|
||||
val |= FIELD_PREP(PHY_PAD_WEAKPULL_MASK, PHY_PAD_WEAKPULL_PULLDOWN);
|
||||
val |= FIELD_PREP(PHY_PAD_TXSLEW_CTRL_P_MASK, PHY_PAD_TXSLEW_CTRL_P);
|
||||
val |= FIELD_PREP(PHY_PAD_TXSLEW_CTRL_N_MASK, PHY_PAD_TXSLEW_CTRL_N);
|
||||
sdhci_writew(host, val, PHY_STBPAD_CNFG_R);
|
||||
|
||||
/* enable data strobe mode */
|
||||
sdhci_writeb(host, FIELD_PREP(PHY_DLLDL_CNFG_SLV_INPSEL_MASK, PHY_DLLDL_CNFG_SLV_INPSEL),
|
||||
PHY_DLLDL_CNFG_R);
|
||||
|
||||
/* enable phy dll */
|
||||
sdhci_writeb(host, PHY_DLL_CTRL_ENABLE, PHY_DLL_CTRL_R);
|
||||
}
|
||||
|
||||
static void dwcmshc_phy_3_3v_init(struct sdhci_host *host)
|
||||
{
|
||||
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
||||
struct dwcmshc_priv *priv = sdhci_pltfm_priv(pltfm_host);
|
||||
u32 val;
|
||||
|
||||
/* deassert phy reset & set tx drive strength */
|
||||
val = PHY_CNFG_RSTN_DEASSERT;
|
||||
val |= FIELD_PREP(PHY_CNFG_PAD_SP_MASK, PHY_CNFG_PAD_SP);
|
||||
val |= FIELD_PREP(PHY_CNFG_PAD_SN_MASK, PHY_CNFG_PAD_SN);
|
||||
sdhci_writel(host, val, PHY_CNFG_R);
|
||||
|
||||
/* disable delay line */
|
||||
sdhci_writeb(host, PHY_SDCLKDL_CNFG_UPDATE, PHY_SDCLKDL_CNFG_R);
|
||||
|
||||
/* set delay line */
|
||||
sdhci_writeb(host, priv->delay_line, PHY_SDCLKDL_DC_R);
|
||||
sdhci_writeb(host, PHY_DLL_CNFG2_JUMPSTEP, PHY_DLL_CNFG2_R);
|
||||
|
||||
/* enable delay lane */
|
||||
val = sdhci_readb(host, PHY_SDCLKDL_CNFG_R);
|
||||
val &= ~(PHY_SDCLKDL_CNFG_UPDATE);
|
||||
sdhci_writeb(host, val, PHY_SDCLKDL_CNFG_R);
|
||||
|
||||
/* configure phy pads */
|
||||
val = PHY_PAD_RXSEL_3V3;
|
||||
val |= FIELD_PREP(PHY_PAD_WEAKPULL_MASK, PHY_PAD_WEAKPULL_PULLUP);
|
||||
val |= FIELD_PREP(PHY_PAD_TXSLEW_CTRL_P_MASK, PHY_PAD_TXSLEW_CTRL_P);
|
||||
val |= FIELD_PREP(PHY_PAD_TXSLEW_CTRL_N_MASK, PHY_PAD_TXSLEW_CTRL_N);
|
||||
sdhci_writew(host, val, PHY_CMDPAD_CNFG_R);
|
||||
sdhci_writew(host, val, PHY_DATAPAD_CNFG_R);
|
||||
sdhci_writew(host, val, PHY_RSTNPAD_CNFG_R);
|
||||
|
||||
val = FIELD_PREP(PHY_PAD_TXSLEW_CTRL_P_MASK, PHY_PAD_TXSLEW_CTRL_P);
|
||||
val |= FIELD_PREP(PHY_PAD_TXSLEW_CTRL_N_MASK, PHY_PAD_TXSLEW_CTRL_N);
|
||||
sdhci_writew(host, val, PHY_CLKPAD_CNFG_R);
|
||||
|
||||
val = PHY_PAD_RXSEL_3V3;
|
||||
val |= FIELD_PREP(PHY_PAD_WEAKPULL_MASK, PHY_PAD_WEAKPULL_PULLDOWN);
|
||||
val |= FIELD_PREP(PHY_PAD_TXSLEW_CTRL_P_MASK, PHY_PAD_TXSLEW_CTRL_P);
|
||||
val |= FIELD_PREP(PHY_PAD_TXSLEW_CTRL_N_MASK, PHY_PAD_TXSLEW_CTRL_N);
|
||||
sdhci_writew(host, val, PHY_STBPAD_CNFG_R);
|
||||
|
||||
/* enable phy dll */
|
||||
sdhci_writeb(host, PHY_DLL_CTRL_ENABLE, PHY_DLL_CTRL_R);
|
||||
}
|
||||
|
||||
static void th1520_sdhci_set_phy(struct sdhci_host *host)
|
||||
{
|
||||
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
||||
struct dwcmshc_priv *priv = sdhci_pltfm_priv(pltfm_host);
|
||||
u32 emmc_caps = MMC_CAP2_NO_SD | MMC_CAP2_NO_SDIO;
|
||||
u16 emmc_ctrl;
|
||||
|
||||
/* Before power on, set PHY configs */
|
||||
if (priv->flags & FLAG_IO_FIXED_1V8)
|
||||
dwcmshc_phy_1_8v_init(host);
|
||||
else
|
||||
dwcmshc_phy_3_3v_init(host);
|
||||
|
||||
if ((host->mmc->caps2 & emmc_caps) == emmc_caps) {
|
||||
emmc_ctrl = sdhci_readw(host, priv->vendor_specific_area1 + DWCMSHC_EMMC_CONTROL);
|
||||
emmc_ctrl |= DWCMSHC_CARD_IS_EMMC;
|
||||
sdhci_writew(host, emmc_ctrl, priv->vendor_specific_area1 + DWCMSHC_EMMC_CONTROL);
|
||||
}
|
||||
|
||||
sdhci_writeb(host, FIELD_PREP(PHY_DLL_CNFG1_SLVDLY_MASK, PHY_DLL_CNFG1_SLVDLY) |
|
||||
PHY_DLL_CNFG1_WAITCYCLE, PHY_DLL_CNFG1_R);
|
||||
}
|
||||
|
||||
static void dwcmshc_set_uhs_signaling(struct sdhci_host *host,
|
||||
unsigned int timing)
|
||||
{
|
||||
@@ -189,9 +404,25 @@ static void dwcmshc_set_uhs_signaling(struct sdhci_host *host,
|
||||
ctrl_2 |= DWCMSHC_CTRL_HS400;
|
||||
}
|
||||
|
||||
if (priv->flags & FLAG_IO_FIXED_1V8)
|
||||
ctrl_2 |= SDHCI_CTRL_VDD_180;
|
||||
sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2);
|
||||
}
|
||||
|
||||
static void th1520_set_uhs_signaling(struct sdhci_host *host,
|
||||
unsigned int timing)
|
||||
{
|
||||
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
||||
struct dwcmshc_priv *priv = sdhci_pltfm_priv(pltfm_host);
|
||||
|
||||
dwcmshc_set_uhs_signaling(host, timing);
|
||||
if (timing == MMC_TIMING_MMC_HS400)
|
||||
priv->delay_line = PHY_SDCLKDL_DC_HS400;
|
||||
else
|
||||
sdhci_writeb(host, 0, PHY_DLLDL_CNFG_R);
|
||||
th1520_sdhci_set_phy(host);
|
||||
}
|
||||
|
||||
static void dwcmshc_hs400_enhanced_strobe(struct mmc_host *mmc,
|
||||
struct mmc_ios *ios)
|
||||
{
|
||||
@@ -338,6 +569,79 @@ static void rk35xx_sdhci_reset(struct sdhci_host *host, u8 mask)
|
||||
sdhci_reset(host, mask);
|
||||
}
|
||||
|
||||
static int th1520_execute_tuning(struct sdhci_host *host, u32 opcode)
|
||||
{
|
||||
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
||||
struct dwcmshc_priv *priv = sdhci_pltfm_priv(pltfm_host);
|
||||
u32 val = 0;
|
||||
|
||||
if (host->flags & SDHCI_HS400_TUNING)
|
||||
return 0;
|
||||
|
||||
sdhci_writeb(host, FIELD_PREP(PHY_ATDL_CNFG_INPSEL_MASK, PHY_ATDL_CNFG_INPSEL),
|
||||
PHY_ATDL_CNFG_R);
|
||||
val = sdhci_readl(host, priv->vendor_specific_area1 + DWCMSHC_EMMC_ATCTRL);
|
||||
|
||||
/*
|
||||
* configure tuning settings:
|
||||
* - center phase select code driven in block gap interval
|
||||
* - disable reporting of framing errors
|
||||
* - disable software managed tuning
|
||||
* - disable user selection of sampling window edges,
|
||||
* instead tuning calculated edges are used
|
||||
*/
|
||||
val &= ~(AT_CTRL_CI_SEL | AT_CTRL_RPT_TUNE_ERR | AT_CTRL_SW_TUNE_EN |
|
||||
FIELD_PREP(AT_CTRL_WIN_EDGE_SEL_MASK, AT_CTRL_WIN_EDGE_SEL));
|
||||
|
||||
/*
|
||||
* configure tuning settings:
|
||||
* - enable auto-tuning
|
||||
* - enable sampling window threshold
|
||||
* - stop clocks during phase code change
|
||||
* - set max latency in cycles between tx and rx clocks
|
||||
* - set max latency in cycles to switch output phase
|
||||
* - set max sampling window threshold value
|
||||
*/
|
||||
val |= AT_CTRL_AT_EN | AT_CTRL_SWIN_TH_EN | AT_CTRL_TUNE_CLK_STOP_EN;
|
||||
val |= FIELD_PREP(AT_CTRL_PRE_CHANGE_DLY_MASK, AT_CTRL_PRE_CHANGE_DLY);
|
||||
val |= FIELD_PREP(AT_CTRL_POST_CHANGE_DLY_MASK, AT_CTRL_POST_CHANGE_DLY);
|
||||
val |= FIELD_PREP(AT_CTRL_SWIN_TH_VAL_MASK, AT_CTRL_SWIN_TH_VAL);
|
||||
|
||||
sdhci_writel(host, val, priv->vendor_specific_area1 + DWCMSHC_EMMC_ATCTRL);
|
||||
val = sdhci_readl(host, priv->vendor_specific_area1 + DWCMSHC_EMMC_ATCTRL);
|
||||
|
||||
/* perform tuning */
|
||||
sdhci_start_tuning(host);
|
||||
host->tuning_err = __sdhci_execute_tuning(host, opcode);
|
||||
if (host->tuning_err) {
|
||||
/* disable auto-tuning upon tuning error */
|
||||
val &= ~AT_CTRL_AT_EN;
|
||||
sdhci_writel(host, val, priv->vendor_specific_area1 + DWCMSHC_EMMC_ATCTRL);
|
||||
dev_err(mmc_dev(host->mmc), "tuning failed: %d\n", host->tuning_err);
|
||||
return -EIO;
|
||||
}
|
||||
sdhci_end_tuning(host);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void th1520_sdhci_reset(struct sdhci_host *host, u8 mask)
|
||||
{
|
||||
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
||||
struct dwcmshc_priv *priv = sdhci_pltfm_priv(pltfm_host);
|
||||
u16 ctrl_2;
|
||||
|
||||
sdhci_reset(host, mask);
|
||||
|
||||
if (priv->flags & FLAG_IO_FIXED_1V8) {
|
||||
ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
|
||||
if (!(ctrl_2 & SDHCI_CTRL_VDD_180)) {
|
||||
ctrl_2 |= SDHCI_CTRL_VDD_180;
|
||||
sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static const struct sdhci_ops sdhci_dwcmshc_ops = {
|
||||
.set_clock = sdhci_set_clock,
|
||||
.set_bus_width = sdhci_set_bus_width,
|
||||
@@ -356,6 +660,17 @@ static const struct sdhci_ops sdhci_dwcmshc_rk35xx_ops = {
|
||||
.adma_write_desc = dwcmshc_adma_write_desc,
|
||||
};
|
||||
|
||||
static const struct sdhci_ops sdhci_dwcmshc_th1520_ops = {
|
||||
.set_clock = sdhci_set_clock,
|
||||
.set_bus_width = sdhci_set_bus_width,
|
||||
.set_uhs_signaling = th1520_set_uhs_signaling,
|
||||
.get_max_clock = dwcmshc_get_max_clock,
|
||||
.reset = th1520_sdhci_reset,
|
||||
.adma_write_desc = dwcmshc_adma_write_desc,
|
||||
.voltage_switch = dwcmshc_phy_1_8v_init,
|
||||
.platform_execute_tuning = &th1520_execute_tuning,
|
||||
};
|
||||
|
||||
static const struct sdhci_pltfm_data sdhci_dwcmshc_pdata = {
|
||||
.ops = &sdhci_dwcmshc_ops,
|
||||
.quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN,
|
||||
@@ -379,6 +694,12 @@ static const struct sdhci_pltfm_data sdhci_dwcmshc_rk35xx_pdata = {
|
||||
SDHCI_QUIRK2_CLOCK_DIV_ZERO_BROKEN,
|
||||
};
|
||||
|
||||
static const struct sdhci_pltfm_data sdhci_dwcmshc_th1520_pdata = {
|
||||
.ops = &sdhci_dwcmshc_th1520_ops,
|
||||
.quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN,
|
||||
.quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN,
|
||||
};
|
||||
|
||||
static int dwcmshc_rk35xx_init(struct sdhci_host *host, struct dwcmshc_priv *dwc_priv)
|
||||
{
|
||||
int err;
|
||||
@@ -447,6 +768,10 @@ static const struct of_device_id sdhci_dwcmshc_dt_ids[] = {
|
||||
.compatible = "snps,dwcmshc-sdhci",
|
||||
.data = &sdhci_dwcmshc_pdata,
|
||||
},
|
||||
{
|
||||
.compatible = "thead,th1520-dwcmshc",
|
||||
.data = &sdhci_dwcmshc_th1520_pdata,
|
||||
},
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, sdhci_dwcmshc_dt_ids);
|
||||
@@ -542,6 +867,30 @@ static int dwcmshc_probe(struct platform_device *pdev)
|
||||
goto err_clk;
|
||||
}
|
||||
|
||||
if (pltfm_data == &sdhci_dwcmshc_th1520_pdata) {
|
||||
priv->delay_line = PHY_SDCLKDL_DC_DEFAULT;
|
||||
|
||||
if (device_property_read_bool(dev, "mmc-ddr-1_8v") ||
|
||||
device_property_read_bool(dev, "mmc-hs200-1_8v") ||
|
||||
device_property_read_bool(dev, "mmc-hs400-1_8v"))
|
||||
priv->flags |= FLAG_IO_FIXED_1V8;
|
||||
else
|
||||
priv->flags &= ~FLAG_IO_FIXED_1V8;
|
||||
|
||||
/*
|
||||
* start_signal_voltage_switch() will try 3.3V first
|
||||
* then 1.8V. Use SDHCI_SIGNALING_180 rather than
|
||||
* SDHCI_SIGNALING_330 to avoid setting voltage to 3.3V
|
||||
* in sdhci_start_signal_voltage_switch().
|
||||
*/
|
||||
if (priv->flags & FLAG_IO_FIXED_1V8) {
|
||||
host->flags &= ~SDHCI_SIGNALING_330;
|
||||
host->flags |= SDHCI_SIGNALING_180;
|
||||
}
|
||||
|
||||
sdhci_enable_v4_mode(host);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_ACPI
|
||||
if (pltfm_data == &sdhci_dwcmshc_bf3_pdata)
|
||||
sdhci_enable_v4_mode(host);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/**
|
||||
/*
|
||||
* SDHCI Controller driver for TI's OMAP SoCs
|
||||
*
|
||||
* Copyright (C) 2017 Texas Instruments
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
#include <linux/of.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
|
||||
#include "sdhci-pltfm.h"
|
||||
#include "sdhci-xenon.h"
|
||||
@@ -422,6 +424,7 @@ static int xenon_probe_params(struct platform_device *pdev)
|
||||
struct xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
|
||||
u32 sdhc_id, nr_sdhc;
|
||||
u32 tuning_count;
|
||||
struct sysinfo si;
|
||||
|
||||
/* Disable HS200 on Armada AP806 */
|
||||
if (priv->hw_version == XENON_AP806)
|
||||
@@ -450,6 +453,23 @@ static int xenon_probe_params(struct platform_device *pdev)
|
||||
}
|
||||
priv->tuning_count = tuning_count;
|
||||
|
||||
/*
|
||||
* AC5/X/IM HW has only 31-bits passed in the crossbar switch.
|
||||
* If we have more than 2GB of memory, this means we might pass
|
||||
* memory pointers which are above 2GB and which cannot be properly
|
||||
* represented. In this case, disable ADMA, 64-bit DMA and allow only SDMA.
|
||||
* This effectively will enable bounce buffer quirk in the
|
||||
* generic SDHCI driver, which will make sure DMA is only done
|
||||
* from supported memory regions:
|
||||
*/
|
||||
if (priv->hw_version == XENON_AC5) {
|
||||
si_meminfo(&si);
|
||||
if (si.totalram * si.mem_unit > SZ_2G) {
|
||||
host->quirks |= SDHCI_QUIRK_BROKEN_ADMA;
|
||||
host->quirks2 |= SDHCI_QUIRK2_BROKEN_64_BIT_DMA;
|
||||
}
|
||||
}
|
||||
|
||||
return xenon_phy_parse_params(dev, host);
|
||||
}
|
||||
|
||||
@@ -562,6 +582,16 @@ static int xenon_probe(struct platform_device *pdev)
|
||||
goto remove_sdhc;
|
||||
|
||||
pm_runtime_put_autosuspend(&pdev->dev);
|
||||
/*
|
||||
* If we previously detected AC5 with over 2GB of memory,
|
||||
* then we disable ADMA and 64-bit DMA.
|
||||
* This means generic SDHCI driver has set the DMA mask to
|
||||
* 32-bit. Since DDR starts at 0x2_0000_0000, we must use
|
||||
* 34-bit DMA mask to access this DDR memory:
|
||||
*/
|
||||
if (priv->hw_version == XENON_AC5 &&
|
||||
host->quirks2 & SDHCI_QUIRK2_BROKEN_64_BIT_DMA)
|
||||
dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(34));
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -680,6 +710,7 @@ static const struct of_device_id sdhci_xenon_dt_ids[] = {
|
||||
{ .compatible = "marvell,armada-ap807-sdhci", .data = (void *)XENON_AP807},
|
||||
{ .compatible = "marvell,armada-cp110-sdhci", .data = (void *)XENON_CP110},
|
||||
{ .compatible = "marvell,armada-3700-sdhci", .data = (void *)XENON_A3700},
|
||||
{ .compatible = "marvell,ac5-sdhci", .data = (void *)XENON_AC5},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, sdhci_xenon_dt_ids);
|
||||
|
||||
@@ -57,7 +57,8 @@ enum xenon_variant {
|
||||
XENON_A3700,
|
||||
XENON_AP806,
|
||||
XENON_AP807,
|
||||
XENON_CP110
|
||||
XENON_CP110,
|
||||
XENON_AC5
|
||||
};
|
||||
|
||||
struct xenon_priv {
|
||||
|
||||
@@ -2841,7 +2841,7 @@ void sdhci_send_tuning(struct sdhci_host *host, u32 opcode)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sdhci_send_tuning);
|
||||
|
||||
static int __sdhci_execute_tuning(struct sdhci_host *host, u32 opcode)
|
||||
int __sdhci_execute_tuning(struct sdhci_host *host, u32 opcode)
|
||||
{
|
||||
int i;
|
||||
|
||||
@@ -2879,6 +2879,7 @@ static int __sdhci_execute_tuning(struct sdhci_host *host, u32 opcode)
|
||||
sdhci_reset_tuning(host);
|
||||
return -EAGAIN;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__sdhci_execute_tuning);
|
||||
|
||||
int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode)
|
||||
{
|
||||
|
||||
@@ -793,6 +793,7 @@ void sdhci_set_bus_width(struct sdhci_host *host, int width);
|
||||
void sdhci_reset(struct sdhci_host *host, u8 mask);
|
||||
void sdhci_set_uhs_signaling(struct sdhci_host *host, unsigned timing);
|
||||
int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode);
|
||||
int __sdhci_execute_tuning(struct sdhci_host *host, u32 opcode);
|
||||
void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios);
|
||||
int sdhci_start_signal_voltage_switch(struct mmc_host *mmc,
|
||||
struct mmc_ios *ios);
|
||||
|
||||
@@ -141,7 +141,6 @@ static const struct timing_data td[] = {
|
||||
|
||||
struct sdhci_am654_data {
|
||||
struct regmap *base;
|
||||
bool legacy_otapdly;
|
||||
int otap_del_sel[ARRAY_SIZE(td)];
|
||||
int itap_del_sel[ARRAY_SIZE(td)];
|
||||
int clkbuf_sel;
|
||||
@@ -272,11 +271,7 @@ static void sdhci_am654_set_clock(struct sdhci_host *host, unsigned int clock)
|
||||
sdhci_set_clock(host, clock);
|
||||
|
||||
/* Setup DLL Output TAP delay */
|
||||
if (sdhci_am654->legacy_otapdly)
|
||||
otap_del_sel = sdhci_am654->otap_del_sel[0];
|
||||
else
|
||||
otap_del_sel = sdhci_am654->otap_del_sel[timing];
|
||||
|
||||
otap_del_sel = sdhci_am654->otap_del_sel[timing];
|
||||
otap_del_ena = (timing > MMC_TIMING_UHS_SDR25) ? 1 : 0;
|
||||
|
||||
mask = OTAPDLYENA_MASK | OTAPDLYSEL_MASK;
|
||||
@@ -314,10 +309,7 @@ static void sdhci_j721e_4bit_set_clock(struct sdhci_host *host,
|
||||
u32 mask, val;
|
||||
|
||||
/* Setup DLL Output TAP delay */
|
||||
if (sdhci_am654->legacy_otapdly)
|
||||
otap_del_sel = sdhci_am654->otap_del_sel[0];
|
||||
else
|
||||
otap_del_sel = sdhci_am654->otap_del_sel[timing];
|
||||
otap_del_sel = sdhci_am654->otap_del_sel[timing];
|
||||
|
||||
mask = OTAPDLYENA_MASK | OTAPDLYSEL_MASK;
|
||||
val = (0x1 << OTAPDLYENA_SHIFT) |
|
||||
@@ -577,32 +569,15 @@ static int sdhci_am654_get_otap_delay(struct sdhci_host *host,
|
||||
int i;
|
||||
int ret;
|
||||
|
||||
ret = device_property_read_u32(dev, td[MMC_TIMING_LEGACY].otap_binding,
|
||||
&sdhci_am654->otap_del_sel[MMC_TIMING_LEGACY]);
|
||||
if (ret) {
|
||||
/*
|
||||
* ti,otap-del-sel-legacy is mandatory, look for old binding
|
||||
* if not found.
|
||||
*/
|
||||
ret = device_property_read_u32(dev, "ti,otap-del-sel",
|
||||
&sdhci_am654->otap_del_sel[0]);
|
||||
if (ret) {
|
||||
dev_err(dev, "Couldn't find otap-del-sel\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
dev_info(dev, "Using legacy binding ti,otap-del-sel\n");
|
||||
sdhci_am654->legacy_otapdly = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (i = MMC_TIMING_LEGACY; i <= MMC_TIMING_MMC_HS400; i++) {
|
||||
|
||||
ret = device_property_read_u32(dev, td[i].otap_binding,
|
||||
&sdhci_am654->otap_del_sel[i]);
|
||||
if (ret) {
|
||||
if (i == MMC_TIMING_LEGACY) {
|
||||
dev_err(dev, "Couldn't find mandatory ti,otap-del-sel-legacy\n");
|
||||
return ret;
|
||||
}
|
||||
dev_dbg(dev, "Couldn't find %s\n",
|
||||
td[i].otap_binding);
|
||||
/*
|
||||
|
||||
@@ -32,6 +32,7 @@ struct mmc_csd {
|
||||
unsigned int r2w_factor;
|
||||
unsigned int max_dtr;
|
||||
unsigned int erase_size; /* In sectors */
|
||||
unsigned int wp_grp_size;
|
||||
unsigned int read_blkbits;
|
||||
unsigned int write_blkbits;
|
||||
unsigned int capacity;
|
||||
@@ -52,9 +53,6 @@ struct mmc_ext_csd {
|
||||
u8 part_config;
|
||||
u8 cache_ctrl;
|
||||
u8 rst_n_function;
|
||||
u8 max_packed_writes;
|
||||
u8 max_packed_reads;
|
||||
u8 packed_event_en;
|
||||
unsigned int part_time; /* Units: ms */
|
||||
unsigned int sa_timeout; /* Units: 100ns */
|
||||
unsigned int generic_cmd6_time; /* Units: 10ms */
|
||||
@@ -306,6 +304,7 @@ struct mmc_card {
|
||||
unsigned int eg_boundary; /* don't cross erase-group boundaries */
|
||||
unsigned int erase_arg; /* erase / trim / discard */
|
||||
u8 erased_byte; /* value of erased bytes */
|
||||
unsigned int wp_grp_size; /* write group size in sectors */
|
||||
|
||||
u32 raw_cid[4]; /* raw card CID */
|
||||
u32 raw_csd[4]; /* raw card CSD */
|
||||
|
||||
@@ -27,7 +27,6 @@ struct mmc_command {
|
||||
u32 opcode;
|
||||
u32 arg;
|
||||
#define MMC_CMD23_ARG_REL_WR (1 << 31)
|
||||
#define MMC_CMD23_ARG_PACKED ((0 << 31) | (1 << 30))
|
||||
#define MMC_CMD23_ARG_TAG_REQ (1 << 29)
|
||||
u32 resp[4];
|
||||
unsigned int flags; /* expected response type */
|
||||
|
||||
@@ -257,8 +257,6 @@ static inline bool mmc_ready_for_data(u32 status)
|
||||
#define EXT_CSD_FLUSH_CACHE 32 /* W */
|
||||
#define EXT_CSD_CACHE_CTRL 33 /* R/W */
|
||||
#define EXT_CSD_POWER_OFF_NOTIFICATION 34 /* R/W */
|
||||
#define EXT_CSD_PACKED_FAILURE_INDEX 35 /* RO */
|
||||
#define EXT_CSD_PACKED_CMD_STATUS 36 /* RO */
|
||||
#define EXT_CSD_EXP_EVENTS_STATUS 54 /* RO, 2 bytes */
|
||||
#define EXT_CSD_EXP_EVENTS_CTRL 56 /* R/W, 2 bytes */
|
||||
#define EXT_CSD_DATA_SECTOR_SIZE 61 /* R */
|
||||
@@ -321,8 +319,6 @@ static inline bool mmc_ready_for_data(u32 status)
|
||||
#define EXT_CSD_SUPPORTED_MODE 493 /* RO */
|
||||
#define EXT_CSD_TAG_UNIT_SIZE 498 /* RO */
|
||||
#define EXT_CSD_DATA_TAG_SUPPORT 499 /* RO */
|
||||
#define EXT_CSD_MAX_PACKED_WRITES 500 /* RO */
|
||||
#define EXT_CSD_MAX_PACKED_READS 501 /* RO */
|
||||
#define EXT_CSD_BKOPS_SUPPORT 502 /* RO */
|
||||
#define EXT_CSD_HPI_FEATURES 503 /* RO */
|
||||
|
||||
@@ -402,18 +398,12 @@ static inline bool mmc_ready_for_data(u32 status)
|
||||
#define EXT_CSD_PWR_CL_8BIT_SHIFT 4
|
||||
#define EXT_CSD_PWR_CL_4BIT_SHIFT 0
|
||||
|
||||
#define EXT_CSD_PACKED_EVENT_EN BIT(3)
|
||||
|
||||
/*
|
||||
* EXCEPTION_EVENT_STATUS field
|
||||
*/
|
||||
#define EXT_CSD_URGENT_BKOPS BIT(0)
|
||||
#define EXT_CSD_DYNCAP_NEEDED BIT(1)
|
||||
#define EXT_CSD_SYSPOOL_EXHAUSTED BIT(2)
|
||||
#define EXT_CSD_PACKED_FAILURE BIT(3)
|
||||
|
||||
#define EXT_CSD_PACKED_GENERIC_ERROR BIT(0)
|
||||
#define EXT_CSD_PACKED_INDEXED_ERROR BIT(1)
|
||||
|
||||
/*
|
||||
* BKOPS status level
|
||||
|
||||
Reference in New Issue
Block a user