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), ®map_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