summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--drivers/mci/Kconfig6
-rw-r--r--drivers/mci/Makefile1
-rw-r--r--drivers/mci/arasan-sdhci.c423
-rw-r--r--drivers/mci/sdhci.h3
4 files changed, 433 insertions, 0 deletions
diff --git a/drivers/mci/Kconfig b/drivers/mci/Kconfig
index 526600556b..c269b71e89 100644
--- a/drivers/mci/Kconfig
+++ b/drivers/mci/Kconfig
@@ -142,6 +142,12 @@ config MCI_TEGRA
Enable this to support SD and MMC card read/write on a Tegra based
systems.
+config MCI_ARASAN
+ bool "Arasan SDHCI Controller"
+ help
+ Enable this to support SD and MMC card read/write on systems with
+ the Arasan SD3.0 / SDIO3.0 / eMMC4.51 host controller.
+
config MCI_SPI
bool "MMC/SD over SPI"
select CRC7
diff --git a/drivers/mci/Makefile b/drivers/mci/Makefile
index 9efdbd651e..e2e3ba7ef4 100644
--- a/drivers/mci/Makefile
+++ b/drivers/mci/Makefile
@@ -1,4 +1,5 @@
obj-$(CONFIG_MCI) += mci-core.o
+obj-$(CONFIG_MCI_ARASAN) += arasan-sdhci.o
obj-$(CONFIG_MCI_ATMEL) += atmel_mci.o
obj-$(CONFIG_MCI_BCM283X) += mci-bcm2835.o
obj-$(CONFIG_MCI_BCM283X_SDHOST) += bcm2835-sdhost.o
diff --git a/drivers/mci/arasan-sdhci.c b/drivers/mci/arasan-sdhci.c
new file mode 100644
index 0000000000..b43a4f8ddf
--- /dev/null
+++ b/drivers/mci/arasan-sdhci.c
@@ -0,0 +1,423 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <clock.h>
+#include <common.h>
+#include <driver.h>
+#include <init.h>
+#include <linux/clk.h>
+#include <mci.h>
+
+#include "sdhci.h"
+
+#define SDHCI_ARASAN_HCAP_CLK_FREQ_MASK 0xFF00
+#define SDHCI_ARASAN_HCAP_CLK_FREQ_SHIFT 8
+#define SDHCI_INT_ADMAE BIT(29)
+#define SDHCI_ARASAN_INT_DATA_MASK SDHCI_INT_XFER_COMPLETE | \
+ SDHCI_INT_DMA | \
+ SDHCI_INT_SPACE_AVAIL | \
+ SDHCI_INT_DATA_AVAIL | \
+ SDHCI_INT_DATA_TIMEOUT | \
+ SDHCI_INT_DATA_CRC | \
+ SDHCI_INT_DATA_END_BIT | \
+ SDHCI_INT_ADMAE
+
+#define SDHCI_ARASAN_INT_CMD_MASK SDHCI_INT_CMD_COMPLETE | \
+ SDHCI_INT_TIMEOUT | \
+ SDHCI_INT_CRC | \
+ SDHCI_INT_END_BIT | \
+ SDHCI_INT_INDEX
+
+#define SDHCI_ARASAN_BUS_WIDTH 4
+#define TIMEOUT_VAL 0xE
+
+struct arasan_sdhci_host {
+ struct mci_host mci;
+ struct sdhci sdhci;
+ void __iomem *ioaddr;
+ unsigned int quirks; /* Arasan deviations from spec */
+/* Controller does not have CD wired and will not function normally without */
+#define SDHCI_ARASAN_QUIRK_FORCE_CDTEST BIT(0)
+#define SDHCI_ARASAN_QUIRK_NO_1_8_V BIT(1)
+};
+
+
+static inline
+struct arasan_sdhci_host *to_arasan_sdhci_host(struct mci_host *mci)
+{
+ return container_of(mci, struct arasan_sdhci_host, mci);
+}
+
+static inline
+struct arasan_sdhci_host *sdhci_to_arasan(struct sdhci *sdhci)
+{
+ return container_of(sdhci, struct arasan_sdhci_host, sdhci);
+}
+
+static void arasan_sdhci_writel(struct sdhci *sdhci, int reg, u32 val)
+{
+ struct arasan_sdhci_host *p = sdhci_to_arasan(sdhci);
+
+ writel(val, p->ioaddr + reg);
+}
+
+static void arasan_sdhci_writew(struct sdhci *sdhci, int reg, u16 val)
+{
+ struct arasan_sdhci_host *p = sdhci_to_arasan(sdhci);
+
+ writew(val, p->ioaddr + reg);
+}
+
+static void arasan_sdhci_writeb(struct sdhci *sdhci, int reg, u8 val)
+{
+ struct arasan_sdhci_host *p = sdhci_to_arasan(sdhci);
+
+ writeb(val, p->ioaddr + reg);
+}
+
+static u32 arasan_sdhci_readl(struct sdhci *sdhci, int reg)
+{
+ struct arasan_sdhci_host *p = sdhci_to_arasan(sdhci);
+
+ return readl(p->ioaddr + reg);
+}
+
+static u16 arasan_sdhci_readw(struct sdhci *sdhci, int reg)
+{
+ struct arasan_sdhci_host *p = sdhci_to_arasan(sdhci);
+
+ return readw(p->ioaddr + reg);
+}
+
+static u8 arasan_sdhci_readb(struct sdhci *sdhci, int reg)
+{
+ struct arasan_sdhci_host *p = sdhci_to_arasan(sdhci);
+
+ return readb(p->ioaddr + reg);
+}
+
+static int arasan_sdhci_card_present(struct mci_host *mci)
+{
+ struct arasan_sdhci_host *host = to_arasan_sdhci_host(mci);
+
+ return !!(sdhci_read32(&host->sdhci, SDHCI_PRESENT_STATE) & SDHCI_CARD_DETECT);
+}
+
+static int arasan_sdhci_card_write_protected(struct mci_host *mci)
+{
+ struct arasan_sdhci_host *host = to_arasan_sdhci_host(mci);
+
+ return !(sdhci_read32(&host->sdhci, SDHCI_PRESENT_STATE) & SDHCI_WRITE_PROTECT);
+}
+
+static int arasan_sdhci_reset(struct arasan_sdhci_host *host, u8 mask)
+{
+ sdhci_write8(&host->sdhci, SDHCI_SOFTWARE_RESET, mask);
+
+ /* wait for reset completion */
+ if (wait_on_timeout(100 * MSECOND,
+ !(sdhci_read8(&host->sdhci, SDHCI_SOFTWARE_RESET) & mask))){
+ dev_err(host->mci.hw_dev, "SDHCI reset timeout\n");
+ return -ETIMEDOUT;
+ }
+
+ if (host->quirks & SDHCI_ARASAN_QUIRK_FORCE_CDTEST) {
+ u8 ctrl;
+
+ ctrl = sdhci_read8(&host->sdhci, SDHCI_HOST_CONTROL);
+ ctrl |= SDHCI_CARD_DETECT_TEST_LEVEL | SDHCI_CARD_DETECT_SIGNAL_SELECTION;
+ sdhci_write8(&host->sdhci, ctrl, SDHCI_HOST_CONTROL);
+ }
+
+ return 0;
+}
+
+static int arasan_sdhci_init(struct mci_host *mci, struct device_d *dev)
+{
+ struct arasan_sdhci_host *host = to_arasan_sdhci_host(mci);
+ int ret;
+
+ ret = arasan_sdhci_reset(host, SDHCI_RESET_ALL);
+ if (ret)
+ return ret;
+
+ sdhci_write8(&host->sdhci, SDHCI_POWER_CONTROL,
+ SDHCI_BUS_VOLTAGE_330 | SDHCI_BUS_POWER_EN);
+ udelay(400);
+
+ sdhci_write32(&host->sdhci, SDHCI_INT_ENABLE,
+ SDHCI_ARASAN_INT_DATA_MASK |
+ SDHCI_ARASAN_INT_CMD_MASK);
+ sdhci_write32(&host->sdhci, SDHCI_SIGNAL_ENABLE, 0x00);
+
+ return 0;
+}
+
+#define SDHCI_MAX_DIV_SPEC_300 2046
+
+static u16 arasan_sdhci_get_clock_divider(struct arasan_sdhci_host *host,
+ unsigned int reqclk)
+{
+ u16 div;
+
+ for (div = 1; div < SDHCI_MAX_DIV_SPEC_300; div += 2)
+ if ((host->mci.f_max / div) <= reqclk)
+ break;
+ div /= 2;
+
+ return div;
+}
+
+#define SDHCI_FREQ_SEL_10_BIT(x) (((x) & 0x300) >> 2)
+
+static void arasan_sdhci_set_ios(struct mci_host *mci, struct mci_ios *ios)
+{
+ struct arasan_sdhci_host *host = to_arasan_sdhci_host(mci);
+ u16 val;
+
+ /* stop clock */
+ sdhci_write16(&host->sdhci, SDHCI_CLOCK_CONTROL, 0);
+
+ if (ios->clock) {
+ u64 start;
+
+ /* set & start clock */
+ val = arasan_sdhci_get_clock_divider(host, ios->clock);
+ /* Bit 6 & 7 are upperbits of 10bit divider */
+ val = SDHCI_FREQ_SEL(val) | SDHCI_FREQ_SEL_10_BIT(val);
+ val |= SDHCI_INTCLOCK_EN;
+ sdhci_write16(&host->sdhci, SDHCI_CLOCK_CONTROL, val);
+
+ start = get_time_ns();
+ while (!(sdhci_read16(&host->sdhci, SDHCI_CLOCK_CONTROL) &
+ SDHCI_INTCLOCK_STABLE)) {
+ if (is_timeout(start, 20 * MSECOND)) {
+ dev_err(host->mci.hw_dev,
+ "SDHCI clock stable timeout\n");
+ return;
+ }
+ }
+ /* enable bus clock */
+ sdhci_write16(&host->sdhci, SDHCI_CLOCK_CONTROL,
+ val | SDHCI_SDCLOCK_EN);
+ }
+
+ val = sdhci_read8(&host->sdhci, SDHCI_HOST_CONTROL) &
+ ~(SDHCI_DATA_WIDTH_4BIT | SDHCI_DATA_WIDTH_8BIT);
+
+ switch (ios->bus_width) {
+ case MMC_BUS_WIDTH_8:
+ val |= SDHCI_DATA_WIDTH_8BIT;
+ break;
+ case MMC_BUS_WIDTH_4:
+ val |= SDHCI_DATA_WIDTH_4BIT;
+ break;
+ }
+
+ if (ios->clock > 26000000)
+ val |= SDHCI_HIGHSPEED_EN;
+ else
+ val &= ~SDHCI_HIGHSPEED_EN;
+
+ sdhci_write8(&host->sdhci, SDHCI_HOST_CONTROL, val);
+}
+
+static int arasan_sdhci_wait_for_done(struct arasan_sdhci_host *host, u32 mask)
+{
+ u64 start = get_time_ns();
+ u16 stat;
+
+ do {
+ stat = sdhci_read16(&host->sdhci, SDHCI_INT_NORMAL_STATUS);
+ if (stat & SDHCI_INT_ERROR) {
+ dev_err(host->mci.hw_dev, "SDHCI_INT_ERROR: 0x%08x\n",
+ sdhci_read16(&host->sdhci, SDHCI_INT_ERROR_STATUS));
+ return -EPERM;
+ }
+
+ if (is_timeout(start, 1000 * MSECOND)) {
+ dev_err(host->mci.hw_dev,
+ "SDHCI timeout while waiting for done\n");
+ return -ETIMEDOUT;
+ }
+ } while ((stat & mask) != mask);
+
+ return 0;
+}
+
+static void print_error(struct arasan_sdhci_host *host, int cmdidx)
+{
+ dev_err(host->mci.hw_dev,
+ "error while transfering data for command %d\n", cmdidx);
+ dev_err(host->mci.hw_dev, "state = 0x%08x , interrupt = 0x%08x\n",
+ sdhci_read32(&host->sdhci, SDHCI_PRESENT_STATE),
+ sdhci_read32(&host->sdhci, SDHCI_INT_NORMAL_STATUS));
+}
+
+static int arasan_sdhci_send_cmd(struct mci_host *mci, struct mci_cmd *cmd,
+ struct mci_data *data)
+{
+ struct arasan_sdhci_host *host = to_arasan_sdhci_host(mci);
+ u32 mask, command, xfer;
+ int ret;
+
+ /* Wait for idle before next command */
+ mask = SDHCI_CMD_INHIBIT_CMD;
+ if (cmd->cmdidx != MMC_CMD_STOP_TRANSMISSION)
+ mask |= SDHCI_CMD_INHIBIT_DATA;
+
+ ret = wait_on_timeout(10 * MSECOND,
+ !(sdhci_read32(&host->sdhci, SDHCI_PRESENT_STATE) & mask));
+
+ if (ret) {
+ dev_err(host->mci.hw_dev,
+ "SDHCI timeout while waiting for idle\n");
+ return ret;
+ }
+
+ sdhci_write32(&host->sdhci, SDHCI_INT_STATUS, ~0);
+
+ mask = SDHCI_INT_CMD_COMPLETE;
+ if (data)
+ mask |= SDHCI_INT_XFER_COMPLETE;
+
+ sdhci_set_cmd_xfer_mode(&host->sdhci, cmd, data, false, &command, &xfer);
+
+ sdhci_write8(&host->sdhci, SDHCI_TIMEOUT_CONTROL, TIMEOUT_VAL);
+ sdhci_write16(&host->sdhci, SDHCI_TRANSFER_MODE, xfer);
+ sdhci_write16(&host->sdhci, SDHCI_BLOCK_SIZE, SDHCI_DMA_BOUNDARY_512K |
+ SDHCI_TRANSFER_BLOCK_SIZE(data->blocksize));
+ sdhci_write16(&host->sdhci, SDHCI_BLOCK_COUNT, data->blocks);
+ sdhci_write32(&host->sdhci, SDHCI_ARGUMENT, cmd->cmdarg);
+ sdhci_write16(&host->sdhci, SDHCI_COMMAND, command);
+
+ ret = arasan_sdhci_wait_for_done(host, mask);
+ if (ret == -EPERM)
+ goto error;
+ else if (ret)
+ return ret;
+
+ sdhci_read_response(&host->sdhci, cmd);
+ sdhci_write32(&host->sdhci, SDHCI_INT_STATUS, mask);
+
+ if (data)
+ ret = sdhci_transfer_data(&host->sdhci, data);
+
+error:
+ if (ret) {
+ print_error(host, cmd->cmdidx);
+ arasan_sdhci_reset(host, BIT(1)); // SDHCI_RESET_CMD
+ arasan_sdhci_reset(host, BIT(2)); // SDHCI_RESET_DATA
+ }
+
+ sdhci_write32(&host->sdhci, SDHCI_INT_STATUS, ~0);
+ return ret;
+}
+
+
+static void arasan_sdhci_set_mci_caps(struct arasan_sdhci_host *host)
+{
+ u16 caps = sdhci_read16(&host->sdhci, SDHCI_CAPABILITIES_1);
+
+ if ((caps & SDHCI_HOSTCAP_VOLTAGE_180) &&
+ !(host->quirks & SDHCI_ARASAN_QUIRK_NO_1_8_V))
+ host->mci.voltages |= MMC_VDD_165_195;
+ if (caps & SDHCI_HOSTCAP_VOLTAGE_300)
+ host->mci.voltages |= MMC_VDD_29_30 | MMC_VDD_30_31;
+ if (caps & SDHCI_HOSTCAP_VOLTAGE_330)
+ host->mci.voltages |= MMC_VDD_32_33 | MMC_VDD_33_34;
+
+ if (caps & SDHCI_HOSTCAP_HIGHSPEED)
+ host->mci.host_caps |= (MMC_CAP_MMC_HIGHSPEED_52MHZ |
+ MMC_CAP_MMC_HIGHSPEED |
+ MMC_CAP_SD_HIGHSPEED);
+
+ /* parse board supported bus width capabilities */
+ mci_of_parse(&host->mci);
+
+ /* limit bus widths to controller capabilities */
+ if (!(caps & SDHCI_HOSTCAP_8BIT))
+ host->mci.host_caps &= ~MMC_CAP_8_BIT_DATA;
+}
+
+static int arasan_sdhci_probe(struct device_d *dev)
+{
+ struct device_node *np = dev->device_node;
+ struct arasan_sdhci_host *arasan_sdhci;
+ struct clk *clk_xin, *clk_ahb;
+ struct resource *iores;
+ struct mci_host *mci;
+ int ret;
+
+ arasan_sdhci = xzalloc(sizeof(*arasan_sdhci));
+
+ mci = &arasan_sdhci->mci;
+
+ iores = dev_request_mem_resource(dev, 0);
+ if (IS_ERR(iores))
+ return PTR_ERR(iores);
+ arasan_sdhci->ioaddr = IOMEM(iores->start);
+
+ clk_ahb = clk_get(dev, "clk_ahb");
+ if (IS_ERR(clk_ahb)) {
+ dev_err(dev, "clk_ahb clock not found.\n");
+ return PTR_ERR(clk_ahb);
+ }
+
+ clk_xin = clk_get(dev, "clk_xin");
+ if (IS_ERR(clk_xin)) {
+ dev_err(dev, "clk_xin clock not found.\n");
+ return PTR_ERR(clk_xin);
+ }
+ ret = clk_enable(clk_ahb);
+ if (ret) {
+ dev_err(dev, "Failed to enable AHB clock: %s\n",
+ strerror(ret));
+ return ret;
+ }
+
+ ret = clk_enable(clk_xin);
+ if (ret) {
+ dev_err(dev, "Failed to enable SD clock: %s\n", strerror(ret));
+ return ret;
+ }
+
+ if (of_property_read_bool(np, "xlnx,fails-without-test-cd"))
+ arasan_sdhci->quirks |= SDHCI_ARASAN_QUIRK_FORCE_CDTEST;
+
+ if (of_property_read_bool(np, "no-1-8-v"))
+ arasan_sdhci->quirks |= SDHCI_ARASAN_QUIRK_NO_1_8_V;
+
+ arasan_sdhci->sdhci.read32 = arasan_sdhci_readl;
+ arasan_sdhci->sdhci.read16 = arasan_sdhci_readw;
+ arasan_sdhci->sdhci.read8 = arasan_sdhci_readb;
+ arasan_sdhci->sdhci.write32 = arasan_sdhci_writel;
+ arasan_sdhci->sdhci.write16 = arasan_sdhci_writew;
+ arasan_sdhci->sdhci.write8 = arasan_sdhci_writeb;
+ mci->send_cmd = arasan_sdhci_send_cmd;
+ mci->set_ios = arasan_sdhci_set_ios;
+ mci->init = arasan_sdhci_init;
+ mci->card_present = arasan_sdhci_card_present;
+ mci->card_write_protected = arasan_sdhci_card_write_protected;
+ mci->hw_dev = dev;
+
+ mci->f_max = clk_get_rate(clk_xin);
+ mci->f_min = 50000000 / 256;
+
+ arasan_sdhci_set_mci_caps(arasan_sdhci);
+
+ dev->priv = arasan_sdhci;
+
+ return mci_register(&arasan_sdhci->mci);
+}
+
+static __maybe_unused struct of_device_id arasan_sdhci_compatible[] = {
+ { .compatible = "arasan,sdhci-8.9a" },
+ { /* sentinel */ }
+};
+
+static struct driver_d arasan_sdhci_driver = {
+ .name = "arasan-sdhci",
+ .probe = arasan_sdhci_probe,
+ .of_compatible = DRV_OF_COMPAT(arasan_sdhci_compatible),
+};
+device_platform_driver(arasan_sdhci_driver);
diff --git a/drivers/mci/sdhci.h b/drivers/mci/sdhci.h
index 7071fc8d52..a307dc97cd 100644
--- a/drivers/mci/sdhci.h
+++ b/drivers/mci/sdhci.h
@@ -41,6 +41,7 @@
#define SDHCI_BUFFER 0x20
#define SDHCI_PRESENT_STATE 0x24
#define SDHCI_WRITE_PROTECT BIT(19)
+#define SDHCI_CARD_DETECT BIT(18)
#define SDHCI_BUFFER_READ_ENABLE BIT(11)
#define SDHCI_BUFFER_WRITE_ENABLE BIT(10)
#define SDHCI_DATA_LINE_ACTIVE BIT(2)
@@ -49,6 +50,8 @@
#define SDHCI_PRESENT_STATE1 0x26
#define SDHCI_HOST_CONTROL__POWER_CONTROL__BLOCK_GAP_CONTROL 0x28
#define SDHCI_HOST_CONTROL 0x28
+#define SDHCI_CARD_DETECT_SIGNAL_SELECTION BIT(7)
+#define SDHCI_CARD_DETECT_TEST_LEVEL BIT(6)
#define SDHCI_DATA_WIDTH_8BIT BIT(5)
#define SDHCI_HIGHSPEED_EN BIT(2)
#define SDHCI_DATA_WIDTH_4BIT BIT(1)