// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2015, The Linux Foundation. All rights reserved.
*/
#include <linux/delay.h>
#include <linux/highmem.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/module.h>
#include <linux/dma-mapping.h>
#include <linux/slab.h>
#include <linux/scatterlist.h>
#include <linux/platform_device.h>
#include <linux/ktime.h>
#include <linux/mmc/mmc.h>
#include <linux/mmc/host.h>
#include <linux/mmc/card.h>
#include "cqhci.h"
#include "cqhci-crypto.h"
#define DCMD_SLOT 31
#define NUM_SLOTS 32
struct cqhci_slot {
struct mmc_request *mrq;
unsigned int flags;
#define CQHCI_EXTERNAL_TIMEOUT BIT(0)
#define CQHCI_COMPLETED BIT(1)
#define CQHCI_HOST_CRC BIT(2)
#define CQHCI_HOST_TIMEOUT BIT(3)
#define CQHCI_HOST_OTHER BIT(4)
};
static bool cqhci_halted(struct cqhci_host *cq_host)
{
return cqhci_readl(cq_host, CQHCI_CTL) & CQHCI_HALT;
}
static inline u8 *get_desc(struct cqhci_host *cq_host, u8 tag)
{
return cq_host->desc_base + (tag * cq_host->slot_sz);
}
static inline u8 *get_link_desc(struct cqhci_host *cq_host, u8 tag)
{
u8 *desc = get_desc(cq_host, tag);
return desc + cq_host->task_desc_len;
}
static inline size_t get_trans_desc_offset(struct cqhci_host *cq_host, u8 tag)
{
return cq_host->trans_desc_len * cq_host->mmc->max_segs * tag;
}
static inline dma_addr_t get_trans_desc_dma(struct cqhci_host *cq_host, u8 tag)
{
size_t offset = get_trans_desc_offset(cq_host, tag);
return cq_host->trans_desc_dma_base + offset;
}
static inline u8 *get_trans_desc(struct cqhci_host *cq_host, u8 tag)
{
size_t offset = get_trans_desc_offset(cq_host, tag);
return cq_host->trans_desc_base + offset;
}
static void setup_trans_desc(struct cqhci_host *cq_host, u8 tag)
{
u8 *link_temp;
dma_addr_t trans_temp;
link_temp = get_link_desc(cq_host, tag);
trans_temp = get_trans_desc_dma(cq_host, tag);
memset(link_temp, 0, cq_host->link_desc_len);
if (cq_host->link_desc_len > 8)
*(link_temp + 8) = 0;
if (tag == DCMD_SLOT && (cq_host->mmc->caps2 & MMC_CAP2_CQE_DCMD)) {
*link_temp = CQHCI_VALID(0) | CQHCI_ACT(0) | CQHCI_END(1);
return;
}
*link_temp = CQHCI_VALID(1) | CQHCI_ACT(0x6) | CQHCI_END(0);
if (cq_host->dma64) {
__le64 *data_addr = (__le64 __force *)(link_temp + 4);
data_addr[0] = cpu_to_le64(trans_temp);
} else {
__le32 *data_addr = (__le32 __force *)(link_temp + 4);
data_addr[0] = cpu_to_le32(trans_temp);
}
}
static void cqhci_set_irqs(struct cqhci_host *cq_host, u32 set)
{
cqhci_writel(cq_host, set, CQHCI_ISTE);
cqhci_writel(cq_host, set, CQHCI_ISGE);
}
#define DRV_NAME "cqhci"
#define CQHCI_DUMP(f, x...) \
pr_err("%s: " DRV_NAME ": " f, mmc_hostname(mmc), ## x)
static void cqhci_dumpregs(struct cqhci_host *cq_host)
{
struct mmc_host *mmc = cq_host->mmc;
CQHCI_DUMP("============ CQHCI REGISTER DUMP ===========\n");
CQHCI_DUMP("Caps: 0x%08x | Version: 0x%08x\n",
cqhci_readl(cq_host, CQHCI_CAP),
cqhci_readl(cq_host, CQHCI_VER));
CQHCI_DUMP("Config: 0x%08x | Control: 0x%08x\n"