[PATCH 16/22] mci: Add am654 SDHCI driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Signed-off-by: Sascha Hauer <s.hauer@xxxxxxxxxxxxxx>
---
 drivers/mci/Makefile      |   1 +
 drivers/mci/am654-sdhci.c | 680 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 681 insertions(+)
 create mode 100644 drivers/mci/am654-sdhci.c

diff --git a/drivers/mci/Makefile b/drivers/mci/Makefile
index e3dc5ad8ae..b7fdddeaed 100644
--- a/drivers/mci/Makefile
+++ b/drivers/mci/Makefile
@@ -21,3 +21,4 @@ obj-$(CONFIG_MCI_DW)		+= dw_mmc.o
 obj-$(CONFIG_MCI_MMCI)		+= mmci.o
 obj-$(CONFIG_MCI_STM32_SDMMC2)	+= stm32_sdmmc2.o
 obj-pbl-$(CONFIG_MCI_SDHCI)	+= sdhci.o
+obj-y += am654-sdhci.o
diff --git a/drivers/mci/am654-sdhci.c b/drivers/mci/am654-sdhci.c
new file mode 100644
index 0000000000..1d94834c1a
--- /dev/null
+++ b/drivers/mci/am654-sdhci.c
@@ -0,0 +1,680 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ *
+ * Texas Instruments' K3 SD Host Controller Interface
+ */
+#include <common.h>
+#include <init.h>
+#include <driver.h>
+#include <mci.h>
+#include <clock.h>
+#include <errno.h>
+#include <io.h>
+#include <regmap.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include "sdhci.h"
+
+/* CTL_CFG Registers */
+#define CTL_CFG_2		0x14
+
+#define SLOTTYPE_MASK		GENMASK(31, 30)
+#define SLOTTYPE_EMBEDDED	BIT(30)
+
+/* PHY Registers */
+#define PHY_CTRL1	0x100
+#define PHY_CTRL2	0x104
+#define PHY_CTRL3	0x108
+#define PHY_CTRL4	0x10C
+#define PHY_CTRL5	0x110
+#define PHY_CTRL6	0x114
+#define PHY_STAT1	0x130
+#define PHY_STAT2	0x134
+
+#define IOMUX_ENABLE_SHIFT	31
+#define IOMUX_ENABLE_MASK	BIT(IOMUX_ENABLE_SHIFT)
+#define OTAPDLYENA_SHIFT	20
+#define OTAPDLYENA_MASK		BIT(OTAPDLYENA_SHIFT)
+#define OTAPDLYSEL_SHIFT	12
+#define OTAPDLYSEL_MASK		GENMASK(15, 12)
+#define STRBSEL_SHIFT		24
+#define STRBSEL_4BIT_MASK	GENMASK(27, 24)
+#define STRBSEL_8BIT_MASK	GENMASK(31, 24)
+#define SEL50_SHIFT		8
+#define SEL50_MASK		BIT(SEL50_SHIFT)
+#define SEL100_SHIFT		9
+#define SEL100_MASK		BIT(SEL100_SHIFT)
+#define FREQSEL_SHIFT		8
+#define FREQSEL_MASK		GENMASK(10, 8)
+#define CLKBUFSEL_SHIFT		0
+#define CLKBUFSEL_MASK		GENMASK(2, 0)
+#define DLL_TRIM_ICP_SHIFT	4
+#define DLL_TRIM_ICP_MASK	GENMASK(7, 4)
+#define DR_TY_SHIFT		20
+#define DR_TY_MASK		GENMASK(22, 20)
+#define ENDLL_SHIFT		1
+#define ENDLL_MASK		BIT(ENDLL_SHIFT)
+#define DLLRDY_SHIFT		0
+#define DLLRDY_MASK		BIT(DLLRDY_SHIFT)
+#define PDB_SHIFT		0
+#define PDB_MASK		BIT(PDB_SHIFT)
+#define CALDONE_SHIFT		1
+#define CALDONE_MASK		BIT(CALDONE_SHIFT)
+#define RETRIM_SHIFT		17
+#define RETRIM_MASK		BIT(RETRIM_SHIFT)
+#define SELDLYTXCLK_SHIFT	17
+#define SELDLYTXCLK_MASK	BIT(SELDLYTXCLK_SHIFT)
+#define SELDLYRXCLK_SHIFT	16
+#define SELDLYRXCLK_MASK	BIT(SELDLYRXCLK_SHIFT)
+#define ITAPDLYSEL_SHIFT	0
+#define ITAPDLYSEL_MASK		GENMASK(4, 0)
+#define ITAPDLYENA_SHIFT	8
+#define ITAPDLYENA_MASK		BIT(ITAPDLYENA_SHIFT)
+#define ITAPCHGWIN_SHIFT	9
+#define ITAPCHGWIN_MASK		BIT(ITAPCHGWIN_SHIFT)
+
+#define DRIVER_STRENGTH_50_OHM	0x0
+#define DRIVER_STRENGTH_33_OHM	0x1
+#define DRIVER_STRENGTH_66_OHM	0x2
+#define DRIVER_STRENGTH_100_OHM	0x3
+#define DRIVER_STRENGTH_40_OHM	0x4
+
+#define AM654_SDHCI_MIN_FREQ	400000
+#define CLOCK_TOO_SLOW_HZ	50000000
+
+#define MMC_CAP2_HS200 0
+#define MMC_CAP2_HS400 0
+#define MMC_CAP_UHS_SDR104 0
+#define MMC_CAP_UHS_SDR12 0
+#define MMC_CAP_UHS_DDR50 0
+#define MMC_CAP_UHS_SDR25 0
+#define MMC_CAP_UHS_SDR50 0
+
+struct timing_data {
+	const char *otap_binding;
+	const char *itap_binding;
+	u32 capability;
+};
+
+static const struct timing_data td[] = {
+	[MMC_TIMING_LEGACY]	= {
+		"ti,otap-del-sel-legacy",
+		"ti,itap-del-sel-legacy",
+		0
+	},
+	[MMC_TIMING_MMC_HS]	= {
+		"ti,otap-del-sel-mmc-hs",
+		"ti,itap-del-sel-mms-hs",
+		MMC_CAP_MMC_HIGHSPEED
+	},
+	[MMC_TIMING_SD_HS]	= {
+		"ti,otap-del-sel-sd-hs",
+		"ti,itap-del-sel-sd-hs",
+		MMC_CAP_SD_HIGHSPEED
+	},
+	[MMC_TIMING_UHS_SDR12]	= {
+		"ti,otap-del-sel-sdr12",
+		"ti,itap-del-sel-sdr12",
+		MMC_CAP_UHS_SDR12
+	},
+	[MMC_TIMING_UHS_SDR25]	= {
+		"ti,otap-del-sel-sdr25",
+		"ti,itap-del-sel-sdr25",
+		MMC_CAP_UHS_SDR25
+	},
+	[MMC_TIMING_UHS_SDR50]	= {
+		"ti,otap-del-sel-sdr50",
+		NULL,
+		MMC_CAP_UHS_SDR50
+	},
+	[MMC_TIMING_UHS_SDR104]	= {
+		"ti,otap-del-sel-sdr104",
+		NULL,
+		MMC_CAP_UHS_SDR104
+	},
+	[MMC_TIMING_UHS_DDR50]	= {
+		"ti,otap-del-sel-ddr50",
+		NULL,
+		MMC_CAP_UHS_DDR50
+	},
+	[MMC_TIMING_MMC_DDR52]	= {
+		"ti,otap-del-sel-ddr52",
+		"ti,itap-del-sel-ddr52",
+		MMC_CAP_DDR
+	},
+	[MMC_TIMING_MMC_HS200]	= {
+		"ti,otap-del-sel-hs200",
+		NULL,
+		MMC_CAP2_HS200
+	},
+	[MMC_TIMING_MMC_HS400]	= {
+		"ti,otap-del-sel-hs400",
+		NULL,
+		MMC_CAP2_HS400
+	},
+};
+
+struct am654_sdhci_plat {
+	struct sdhci sdhci;
+	struct mci_host mci;
+	struct clk *clk;
+	struct clk *clk_ahb;
+	const struct am654_driver_data *soc_data;
+	bool fails_without_test_cd;
+
+	struct regmap *base;
+	bool non_removable;
+	u32 otap_del_sel[ARRAY_SIZE(td)];
+	u32 itap_del_sel[ARRAY_SIZE(td)];
+	u32 trm_icp;
+	u32 drv_strength;
+	u32 strb_sel;
+	u32 clkbuf_sel;
+#define DLL_PRESENT	BIT(0)
+#define IOMUX_PRESENT	BIT(1)
+#define FREQSEL_2_BIT	BIT(2)
+#define STRBSEL_4_BIT	BIT(3)
+#define DLL_CALIB	BIT(4)
+};
+
+struct am654_driver_data {
+	int (*set_ios_post)(struct am654_sdhci_plat *plat, struct mci_ios *ios);
+	u32 flags;
+};
+
+static int am654_sdhci_setup_dll(struct am654_sdhci_plat *plat,
+				 unsigned int speed)
+{
+	int sel50, sel100, freqsel;
+	u32 mask, val;
+	int ret;
+
+	/* Disable delay chain mode */
+	regmap_update_bits(plat->base, PHY_CTRL5,
+			   SELDLYTXCLK_MASK | SELDLYRXCLK_MASK, 0);
+
+	if (plat->soc_data->flags & FREQSEL_2_BIT) {
+		switch (speed) {
+		case 200000000:
+			sel50 = 0;
+			sel100 = 0;
+			break;
+		case 100000000:
+			sel50 = 0;
+			sel100 = 1;
+			break;
+		default:
+			sel50 = 1;
+			sel100 = 0;
+		}
+
+		/* Configure PHY DLL frequency */
+		mask = SEL50_MASK | SEL100_MASK;
+		val = (sel50 << SEL50_SHIFT) | (sel100 << SEL100_SHIFT);
+		regmap_update_bits(plat->base, PHY_CTRL5, mask, val);
+	} else {
+		switch (speed) {
+		case 200000000:
+			freqsel = 0x0;
+			break;
+		default:
+			freqsel = 0x4;
+		}
+		regmap_update_bits(plat->base, PHY_CTRL5, FREQSEL_MASK,
+				   freqsel << FREQSEL_SHIFT);
+	}
+
+	/* Configure DLL TRIM */
+	mask = DLL_TRIM_ICP_MASK;
+	val = plat->trm_icp << DLL_TRIM_ICP_SHIFT;
+
+	/* Configure DLL driver strength */
+	mask |= DR_TY_MASK;
+	val |= plat->drv_strength << DR_TY_SHIFT;
+	regmap_update_bits(plat->base, PHY_CTRL1, mask, val);
+
+	/* Enable DLL */
+	regmap_update_bits(plat->base, PHY_CTRL1, ENDLL_MASK,
+			   0x1 << ENDLL_SHIFT);
+	/*
+	 * Poll for DLL ready. Use a one second timeout.
+	 * Works in all experiments done so far
+	 */
+	ret = regmap_read_poll_timeout(plat->base, PHY_STAT1, val,
+				       val & DLLRDY_MASK, 1000000);
+
+	return ret;
+}
+
+static void am654_sdhci_write_itapdly(struct am654_sdhci_plat *plat,
+				      u32 itapdly)
+{
+	/* Set ITAPCHGWIN before writing to ITAPDLY */
+	regmap_update_bits(plat->base, PHY_CTRL4, ITAPCHGWIN_MASK,
+			   1 << ITAPCHGWIN_SHIFT);
+	regmap_update_bits(plat->base, PHY_CTRL4, ITAPDLYSEL_MASK,
+			   itapdly << ITAPDLYSEL_SHIFT);
+	regmap_update_bits(plat->base, PHY_CTRL4, ITAPCHGWIN_MASK, 0);
+}
+
+static void am654_sdhci_setup_delay_chain(struct am654_sdhci_plat *plat,
+					  int mode)
+{
+	u32 mask, val;
+
+	val = 1 << SELDLYTXCLK_SHIFT | 1 << SELDLYRXCLK_SHIFT;
+	mask = SELDLYTXCLK_MASK | SELDLYRXCLK_MASK;
+	regmap_update_bits(plat->base, PHY_CTRL5, mask, val);
+
+	am654_sdhci_write_itapdly(plat, plat->itap_del_sel[mode]);
+}
+
+static int am654_sdhci_set_ios_post(struct am654_sdhci_plat *plat, struct mci_ios *ios)
+{
+	unsigned int speed = ios->clock;
+	int mode = ios->timing;
+	u32 otap_del_sel;
+	u32 mask, val;
+	int ret;
+
+	/* Reset SD Clock Enable */
+	val = sdhci_read16(&plat->sdhci, SDHCI_CLOCK_CONTROL);
+	val &= ~SDHCI_CLOCK_CARD_EN;
+	sdhci_write16(&plat->sdhci, SDHCI_CLOCK_CONTROL, val);
+
+	regmap_update_bits(plat->base, PHY_CTRL1, ENDLL_MASK, 0);
+
+	/* restart clock */
+	sdhci_set_clock(&plat->sdhci, speed, clk_get_rate(plat->clk));
+
+	/* switch phy back on */
+	otap_del_sel = plat->otap_del_sel[mode];
+	mask = OTAPDLYENA_MASK | OTAPDLYSEL_MASK;
+	val = (1 << OTAPDLYENA_SHIFT) |
+	      (otap_del_sel << OTAPDLYSEL_SHIFT);
+
+	/* Write to STRBSEL for HS400 speed mode */
+	if (mode == MMC_TIMING_MMC_HS400) {
+		if (plat->soc_data->flags & STRBSEL_4_BIT)
+			mask |= STRBSEL_4BIT_MASK;
+		else
+			mask |= STRBSEL_8BIT_MASK;
+
+		val |= plat->strb_sel << STRBSEL_SHIFT;
+	}
+
+	regmap_update_bits(plat->base, PHY_CTRL4, mask, val);
+
+	if (mode > MMC_TIMING_UHS_SDR25 && speed >= CLOCK_TOO_SLOW_HZ) {
+		ret = am654_sdhci_setup_dll(plat, speed);
+		if (ret)
+			return ret;
+	} else {
+		am654_sdhci_setup_delay_chain(plat, mode);
+	}
+
+	regmap_update_bits(plat->base, PHY_CTRL5, CLKBUFSEL_MASK,
+			   plat->clkbuf_sel);
+
+	return 0;
+}
+
+static int am654_sdhci_init(struct mci_host *mci, struct device *dev)
+{
+	struct am654_sdhci_plat *plat = container_of(mci, struct am654_sdhci_plat, mci);
+	u32 ctl_cfg_2 = 0;
+	u32 mask, val;
+	int ret;
+
+	ret = sdhci_reset(&plat->sdhci, SDHCI_RESET_ALL);
+	if (ret)
+		return ret;
+
+	if (plat->fails_without_test_cd) {
+		val = sdhci_read8(&plat->sdhci, SDHCI_HOST_CONTROL);
+		val |= SDHCI_CTRL_CDTEST_INS | SDHCI_CTRL_CDTEST_EN;
+		sdhci_write8(&plat->sdhci, SDHCI_HOST_CONTROL, val);
+	}
+
+	sdhci_write8(&plat->sdhci, SDHCI_POWER_CONTROL,
+				SDHCI_BUS_VOLTAGE_330 | SDHCI_BUS_POWER_EN);
+	udelay(400);
+
+	sdhci_write32(&plat->sdhci, SDHCI_INT_ENABLE, ~0);
+	sdhci_write32(&plat->sdhci, SDHCI_SIGNAL_ENABLE, 0x00);
+
+	/* Reset OTAP to default value */
+	mask = OTAPDLYENA_MASK | OTAPDLYSEL_MASK;
+	regmap_update_bits(plat->base, PHY_CTRL4, mask, 0x0);
+
+	if (plat->soc_data->flags & DLL_CALIB) {
+		regmap_read(plat->base, PHY_STAT1, &val);
+		if (~val & CALDONE_MASK) {
+			/* Calibrate IO lines */
+			regmap_update_bits(plat->base, PHY_CTRL1, PDB_MASK,
+					   PDB_MASK);
+			ret = regmap_read_poll_timeout(plat->base, PHY_STAT1,
+						       val, val & CALDONE_MASK,
+						       20);
+			if (ret)
+				return ret;
+		}
+	}
+
+	/* Enable pins by setting IO mux to 0 */
+	if (plat->soc_data->flags & IOMUX_PRESENT)
+		regmap_update_bits(plat->base, PHY_CTRL1, IOMUX_ENABLE_MASK, 0);
+
+	/* Set slot type based on SD or eMMC */
+	if (plat->non_removable)
+		ctl_cfg_2 = SLOTTYPE_EMBEDDED;
+
+	regmap_update_bits(plat->base, CTL_CFG_2, SLOTTYPE_MASK, ctl_cfg_2);
+
+	return 0;
+}
+
+const struct am654_driver_data am654_drv_data = {
+	.flags = DLL_PRESENT | IOMUX_PRESENT | FREQSEL_2_BIT | STRBSEL_4_BIT,
+};
+
+const struct am654_driver_data am654_sr1_drv_data = {
+	.flags = IOMUX_PRESENT | FREQSEL_2_BIT | DLL_PRESENT | DLL_CALIB |
+		 STRBSEL_4_BIT,
+};
+
+const struct am654_driver_data j721e_8bit_drv_data = {
+	.flags = DLL_PRESENT | DLL_CALIB,
+};
+
+static int j721e_4bit_sdhci_set_ios_post(struct am654_sdhci_plat *plat, struct mci_ios *ios)
+{
+	u32 otap_del_sel, mask, val;
+
+	otap_del_sel = plat->otap_del_sel[ios->timing];
+	mask = OTAPDLYENA_MASK | OTAPDLYSEL_MASK;
+	val = (1 << OTAPDLYENA_SHIFT) | (otap_del_sel << OTAPDLYSEL_SHIFT);
+	regmap_update_bits(plat->base, PHY_CTRL4, mask, val);
+
+	regmap_update_bits(plat->base, PHY_CTRL5, CLKBUFSEL_MASK,
+			   plat->clkbuf_sel);
+
+	return 0;
+}
+
+const struct am654_driver_data j721e_4bit_drv_data = {
+	.set_ios_post		= &j721e_4bit_sdhci_set_ios_post,
+	.flags = IOMUX_PRESENT,
+};
+
+static const struct am654_driver_data sdhci_am64_8bit_drvdata = {
+	.set_ios_post		= &am654_sdhci_set_ios_post,
+	.flags = DLL_PRESENT | DLL_CALIB,
+};
+
+static const struct am654_driver_data sdhci_am64_4bit_drvdata = {
+	.set_ios_post		= &j721e_4bit_sdhci_set_ios_post,
+	.flags = IOMUX_PRESENT,
+};
+
+static int sdhci_am654_get_otap_delay(struct am654_sdhci_plat *plat)
+{
+	struct device *dev = plat->mci.hw_dev;
+	struct device_node *np = dev->of_node;
+	int ret;
+	int i;
+
+	/* ti,otap-del-sel-legacy is mandatory */
+	ret = of_property_read_u32(np, "ti,otap-del-sel-legacy",
+			   &plat->otap_del_sel[0]);
+	if (ret)
+		return ret;
+	/*
+	 * Remove the corresponding capability if an otap-del-sel
+	 * value is not found
+	 */
+	for (i = MMC_TIMING_MMC_HS; i <= MMC_TIMING_MMC_HS400; i++) {
+		ret = of_property_read_u32(np, td[i].otap_binding,
+				   &plat->otap_del_sel[i]);
+		if (ret) {
+			dev_dbg(dev, "Couldn't find %s\n", td[i].otap_binding);
+			/*
+			 * Remove the corresponding capability
+			 * if an otap-del-sel value is not found
+			 */
+			plat->mci.host_caps &= ~td[i].capability;
+		}
+
+		if (td[i].itap_binding)
+			of_property_read_u32(np, td[i].itap_binding,
+				     &plat->itap_del_sel[i]);
+	}
+
+	return 0;
+}
+
+static void print_error(struct am654_sdhci_plat *host, int cmdidx)
+{
+	dev_dbg(host->mci.hw_dev,
+		"error while transfering data for command %d\n", cmdidx);
+	dev_dbg(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 am654_sdhci_send_cmd(struct mci_host *mci, struct mci_cmd *cmd,
+				struct mci_data *data)
+{
+	struct am654_sdhci_plat *host = container_of(mci, struct am654_sdhci_plat, mci);
+	u32 command, xfer;
+	int ret;
+	dma_addr_t dma;
+
+	ret = sdhci_wait_idle(&host->sdhci, cmd);
+	if (ret)
+		return ret;
+
+	sdhci_write32(&host->sdhci, SDHCI_INT_STATUS, ~0);
+
+	sdhci_write8(&host->sdhci, SDHCI_TIMEOUT_CONTROL, 0xe);
+
+	sdhci_setup_data_dma(&host->sdhci, data, &dma);
+
+	sdhci_set_cmd_xfer_mode(&host->sdhci, cmd, data,
+				dma == SDHCI_NO_DMA ? false : true,
+				&command, &xfer);
+
+	sdhci_write16(&host->sdhci, SDHCI_TRANSFER_MODE, xfer);
+	sdhci_write32(&host->sdhci, SDHCI_ARGUMENT, cmd->cmdarg);
+	sdhci_write16(&host->sdhci, SDHCI_COMMAND, command);
+
+	ret = sdhci_wait_for_done(&host->sdhci, SDHCI_INT_CMD_COMPLETE);
+	if (ret)
+		goto error;
+
+	sdhci_read_response(&host->sdhci, cmd);
+	sdhci_write32(&host->sdhci, SDHCI_INT_STATUS, SDHCI_INT_CMD_COMPLETE);
+
+	ret = sdhci_transfer_data_dma(&host->sdhci, data, dma);
+
+error:
+	if (ret) {
+		print_error(host, cmd->cmdidx);
+		sdhci_reset(&host->sdhci, SDHCI_RESET_CMD);
+		sdhci_reset(&host->sdhci, SDHCI_RESET_DATA);
+	}
+
+	sdhci_write32(&host->sdhci, SDHCI_INT_STATUS, ~0);
+
+	return ret;
+}
+
+static void am654_sdhci_set_ios(struct mci_host *mci, struct mci_ios *ios)
+{
+	struct am654_sdhci_plat *plat = container_of(mci, struct am654_sdhci_plat, mci);
+	u32 val;
+
+	if (ios->clock)
+		sdhci_set_clock(&plat->sdhci, ios->clock, plat->sdhci.max_clk);
+
+	sdhci_set_bus_width(&plat->sdhci, ios->bus_width);
+
+	val = sdhci_read8(&plat->sdhci, SDHCI_HOST_CONTROL);
+
+	if (ios->clock > 26000000)
+		val |= SDHCI_CTRL_HISPD;
+	else
+		val &= ~SDHCI_CTRL_HISPD;
+
+	sdhci_write8(&plat->sdhci, SDHCI_HOST_CONTROL, val);
+
+	plat->soc_data->set_ios_post(plat, ios);
+}
+
+static const struct regmap_config regmap_config = {
+	.reg_bits	= 32,
+	.val_bits	= 32,
+	.reg_stride	= 4,
+	.max_register	= 0x400,
+};
+
+static int am654_sdhci_probe(struct device *dev)
+{
+	struct device_node *np = dev->of_node;
+	struct am654_sdhci_plat *plat;
+	struct mci_host *mci;
+	struct resource *iores;
+	u32 drv_strength;
+	int ret;
+
+	plat = xzalloc(sizeof(*plat));
+
+	ret = dev_get_drvdata(dev, (const void **)&plat->soc_data);
+	if (ret)
+		return ret;
+
+	iores = dev_request_mem_resource(dev, 0);
+	if (IS_ERR(iores))
+		return PTR_ERR(iores);
+
+	plat->sdhci.base = IOMEM(iores->start);
+
+	iores = dev_request_mem_resource(dev, 1);
+	if (IS_ERR(iores))
+		return PTR_ERR(iores);
+
+	plat->base = regmap_init_mmio(dev, IOMEM(iores->start), &regmap_config);
+        if (IS_ERR(plat->base)) {
+                dev_err(dev, "failed to init regmap: %ld\n",
+                                PTR_ERR(plat->base));
+                return PTR_ERR(plat->base);
+        }
+
+	plat->clk = clk_get(dev, "clk_xin");
+	if (IS_ERR(plat->clk)) {
+		dev_err(dev, "failed to get clock\n");
+		return ret;
+	}
+
+	clk_enable(plat->clk);
+
+	plat->clk_ahb = clk_get(dev, "clk_ahb");
+	if (IS_ERR(plat->clk_ahb)) {
+		dev_err(dev, "failed to get ahb clock\n");
+		return ret;
+	}
+
+	clk_enable(plat->clk_ahb);
+
+	mci = &plat->mci;
+	mci->f_max = clk_get_rate(plat->clk);
+	mci->f_min = 50000000 / 256;
+
+	if (plat->soc_data->flags & DLL_PRESENT) {
+		ret = of_property_read_u32(np, "ti,trm-icp", &plat->trm_icp);
+		if (ret)
+			return ret;
+
+		ret = of_property_read_u32(np, "ti,driver-strength-ohm",
+				   &drv_strength);
+		if (ret)
+			return ret;
+
+		switch (drv_strength) {
+		case 50:
+			plat->drv_strength = DRIVER_STRENGTH_50_OHM;
+			break;
+		case 33:
+			plat->drv_strength = DRIVER_STRENGTH_33_OHM;
+			break;
+		case 66:
+			plat->drv_strength = DRIVER_STRENGTH_66_OHM;
+			break;
+		case 100:
+			plat->drv_strength = DRIVER_STRENGTH_100_OHM;
+			break;
+		case 40:
+			plat->drv_strength = DRIVER_STRENGTH_40_OHM;
+			break;
+		default:
+			dev_err(dev, "Invalid driver strength\n");
+			return -EINVAL;
+		}
+	}
+
+	mci->send_cmd = am654_sdhci_send_cmd;
+	mci->set_ios = am654_sdhci_set_ios;
+	mci->init = am654_sdhci_init;
+	mci->hw_dev = dev;
+
+	of_property_read_u32(np, "ti,strobe-sel", &plat->strb_sel);
+	of_property_read_u32(np, "ti,clkbuf-sel", &plat->clkbuf_sel);
+
+	plat->fails_without_test_cd = of_property_read_bool(np, "ti,fails-without-test-cd");
+
+	mci_of_parse(&plat->mci);
+
+	ret = sdhci_am654_get_otap_delay(plat);
+	if (ret)
+		return ret;
+
+	plat->sdhci.mci = mci;
+	sdhci_setup_host(&plat->sdhci);
+
+	dev->priv = plat;
+
+	return mci_register(&plat->mci);
+}
+
+static const struct of_device_id am654_sdhci_ids[] = {
+	{
+		.compatible = "ti,am654-sdhci-5.1",
+		.data = &am654_drv_data,
+	}, {
+		.compatible = "ti,j721e-sdhci-8bit",
+		.data = &j721e_8bit_drv_data,
+	}, {
+		.compatible = "ti,j721e-sdhci-4bit",
+		.data = &j721e_4bit_drv_data,
+	}, {
+		.compatible = "ti,am64-sdhci-8bit",
+		.data = &sdhci_am64_8bit_drvdata,
+	}, {
+		.compatible = "ti,am64-sdhci-4bit",
+		.data = &sdhci_am64_4bit_drvdata,
+	}, {
+		.compatible = "ti,am62-sdhci",
+		.data = &sdhci_am64_4bit_drvdata,
+	}, {
+		/* sentinel */
+	}
+};
+MODULE_DEVICE_TABLE(of, am654_sdhci_ids);
+
+static struct driver am654_sdhc_driver = {
+        .name = "am654-sdhci",
+        .probe = am654_sdhci_probe,
+        .of_compatible = DRV_OF_COMPAT(am654_sdhci_ids),
+};
+device_platform_driver(am654_sdhc_driver);
-- 
2.39.2





[Index of Archives]     [Linux Embedded]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [XFree86]

  Powered by Linux