// SPDX-License-Identifier: GPL-2.0 /* * sdhci-pci-arasan.c - Driver for Arasan PCI Controller with * integrated phy. * * Copyright (C) 2017 Arasan Chip Systems Inc. * * Author: Atul Garg */ #include #include #include "sdhci.h" #include "sdhci-pci.h" /* Extra registers for Arasan SD/SDIO/MMC Host Controller with PHY */ #define PHY_ADDR_REG 0x300 #define PHY_DAT_REG 0x304 #define PHY_WRITE BIT(8) #define PHY_BUSY BIT(9) #define DATA_MASK 0xFF /* PHY Specific Registers */ #define DLL_STATUS 0x00 #define IPAD_CTRL1 0x01 #define IPAD_CTRL2 0x02 #define IPAD_STS 0x03 #define IOREN_CTRL1 0x06 #define IOREN_CTRL2 0x07 #define IOPU_CTRL1 0x08 #define IOPU_CTRL2 0x09 #define ITAP_DELAY 0x0C #define OTAP_DELAY 0x0D #define STRB_SEL 0x0E #define CLKBUF_SEL 0x0F #define MODE_CTRL 0x11 #define DLL_TRIM 0x12 #define CMD_CTRL 0x20 #define DATA_CTRL 0x21 #define STRB_CTRL 0x22 #define CLK_CTRL 0x23 #define PHY_CTRL 0x24 #define DLL_ENBL BIT(3) #define RTRIM_EN BIT(1) #define PDB_ENBL BIT(1) #define RETB_ENBL BIT(6) #define ODEN_CMD BIT(1) #define ODEN_DAT 0xFF #define REN_STRB BIT(0) #define REN_CMND BIT(1) #define REN_DATA 0xFF #define PU_CMD BIT(1) #define PU_DAT 0xFF #define ITAPDLY_EN BIT(0) #define OTAPDLY_EN BIT(0) #define OD_REL_CMD BIT(1) #define OD_REL_DAT 0xFF #define DLLTRM_ICP 0x8 #define PDB_CMND BIT(0) #define PDB_DATA 0xFF #define PDB_STRB BIT(0) #define PDB_CLOCK BIT(0) #define CALDONE_MASK 0x10 #define DLL_RDY_MASK 0x10 #define MAX_CLK_BUF 0x7 /* Mode Controls */ #define ENHSTRB_MODE BIT(0) #define HS400_MODE BIT(1) #define LEGACY_MODE BIT(2) #define DDR50_MODE BIT(3) /* * Controller has no specific bits for HS200/HS. * Used BIT(4), BIT(5) for software programming. */ #define HS200_MODE BIT(4) #define HISPD_MODE BIT(5) #define OTAPDLY(x) (((x) << 1) | OTAPDLY_EN) #define ITAPDLY(x) (((x) << 1) | ITAPDLY_EN) #define FREQSEL(x) (((x) << 5) | DLL_ENBL) #define IOPAD(x, y) ((x) | ((y) << 2)) /* Arasan private data */ struct arasan_host { u32 chg_clk; }; static int arasan_phy_addr_poll(struct sdhci_host *host, u32 offset, u32 mask) { ktime_t timeout = ktime_add_us(ktime_get(), 100); bool failed; u8 val = 0; while (1) { failed = ktime_after(ktime_get(), timeout); val = sdhci_readw(host, PHY_ADDR_REG); if (!(val & mask)) return 0; if (failed) return -EBUSY; } } static int arasan_phy_write(struct sdhci_host *host, u8 data, u8 offset) { sdhci_writew(host, data, PHY_DAT_REG); sdhci_writew(host, (PHY_WRITE | offset), PHY_ADDR_REG); return arasan_phy_addr_poll(host, PHY_ADDR_REG, PHY_BUSY); } static int arasan_phy_read(struct sdhci_host *host, u8 offset, u8 *data) { int ret; sdhci_writew(host, 0, PHY_DAT_REG); sdhci_writew(host, offset, PHY_ADDR_REG); ret = arasan_phy_addr_poll(host, PHY_ADDR_REG, PHY_BUSY); /* Masking valid data bits */ *data = sdhci_readw(host, PHY_DAT_REG) & DATA_MASK; return ret; } static int arasan_phy_sts_poll(struct sdhci_host *host, u32 offset, u32 mask) { int ret; ktime_t timeout = ktime_add_us(ktime_get(), 100); bool failed; u8 val = 0; while (1) { failed = ktime_after(ktime_get(), timeout); ret = arasan_phy_read(host, offset, &val); if (ret) return -EBUSY; else if (val & mask) return 0; if (failed) return -EBUSY; } } /* Initialize the Arasan PHY */ static int arasan_phy_init(struct sdhci_host *host) { int ret; u8 val; /* Program IOPADs and wait for calibration to be done */ if (arasan_phy_read(host, IPAD_CTRL1, &val) || arasan_phy_write(host, val | RETB_ENBL | PDB_ENBL, IPAD_CTRL1) || arasan_phy_read(host, IPAD_CTRL2, &val) || arasan_phy_write(host, val | RTRIM_EN, IPAD_CTRL2)) return -EBUSY; ret = arasan_phy_sts_poll(host, IPAD_STS, CALDONE_MASK); if (ret) return -EBUSY; /* Program CMD/Data lines */ if (arasan_phy_read(host, IOREN_CTRL1, &val) || arasan_phy_write(host, val | REN_CMND | REN_STRB, IOREN_CTRL1) || arasan_phy_read(host, IOPU_CTRL1, &val) || arasan_phy_write(host, val | PU_CMD, IOPU_CTRL1) || arasan_phy_read(host, CMD_CTRL, &val) || arasan_phy_write(host, val | PDB_CMND, CMD_CTRL) || arasan_phy_read(host, IOREN_CTRL2, &val) || arasan_phy_write(host, val | REN_DATA, IOREN_CTRL2) || arasan_phy_read(host, IOPU_CTRL2, &val) || arasan_phy_write(host, val | PU_DAT, IOPU_CTRL2) || arasan_phy_read(host, DATA_CTRL, &val) || arasan_phy_write(host, val | PDB_DATA, DATA_CTRL) || arasan_phy_read(host, STRB_CTRL, &val) || arasan_phy_write(host, val | PDB_STRB, STRB_CTRL) || arasan_phy_read(host, CLK_CTRL, &val) || arasan_phy_write(host, val | PDB_CLOCK, CLK_CTRL) || arasan_phy_read(host, CLKBUF_SEL, &val) || arasan_phy_write(host, val | MAX_CLK_BUF, CLKBUF_SEL) || arasan_phy_write(host, LEGACY_MODE, MODE_CTRL)) return -EBUSY; return 0; } /* Set Arasan PHY for different modes */ static int arasan_phy_set(struct sdhci_host *host, u8 mode, u8 otap, u8 drv_type, u8 itap, u8 trim, u8 clk) { u8 val; int ret; if (mode == HISPD_MODE || mode == HS200_MODE) ret = arasan_phy_write(host, 0x0, MODE_CTRL); else ret = arasan_phy_write(host, mode, MODE_CTRL); if (ret) return ret; if (mode == HS400_MODE || mode == HS200_MODE) { ret = arasan_phy_read(host, IPAD_CTRL1, &val); if (ret) return ret; ret = arasan_phy_write(host, IOPAD(val, drv_type), IPAD_CTRL1); if (ret) return ret; } if (mode == LEGACY_MODE) { ret = arasan_phy_write(host, 0x0, OTAP_DELAY); if (ret) return ret; ret = arasan_phy_write(host, 0x0, ITAP_DELAY); } else { ret = arasan_phy_write(host, OTAPDLY(otap), OTAP_DELAY); if (ret) return ret; if (mode != HS200_MODE) ret = arasan_phy_write(host, ITAPDLY(itap), ITAP_DELAY); else ret = arasan_phy_write(host, 0x0, ITAP_DELAY); } if (ret) return ret; if (mode != LEGACY_MODE) { ret = arasan_phy_write(host, trim, DLL_TRIM); if (ret) return ret; } ret = arasan_phy_write(host, 0, DLL_STATUS); if (ret) return ret; if (mode != LEGACY_MODE) { ret = arasan_phy_write(host, FREQSEL(clk), DLL_STATUS); if (ret) return ret; ret = arasan_phy_sts_poll(host, DLL_STATUS, DLL_RDY_MASK); if (ret) return -EBUSY; } return 0; } static int arasan_select_phy_clock(struct sdhci_host *host) { struct sdhci_pci_slot *slot = sdhci_priv(host); struct arasan_host *arasan_host = sdhci_pci_priv(slot); u8 clk; if (arasan_host->chg_clk == host->mmc->ios.clock) return 0; arasan_host->chg_clk = host->mmc->ios.clock; if (host->mmc->ios.clock == 200000000) clk = 0x0; else if (host->mmc->ios.clock == 100000000) clk = 0x2; else if (host->mmc->ios.clock == 50000000) clk = 0x1; else clk = 0x0; if (host->mmc_host_ops.hs400_enhanced_strobe) { arasan_phy_set(host, ENHSTRB_MODE, 1, 0x0, 0x0, DLLTRM_ICP, clk); } else { switch (host->mmc->ios.timing) { case MMC_TIMING_LEGACY: arasan_phy_set(host, LEGACY_MODE, 0x0, 0x0, 0x0, 0x0, 0x0); break; case MMC_TIMING_MMC_HS: case MMC_TIMING_SD_HS: arasan_phy_set(host, HISPD_MODE, 0x3, 0x0, 0x2, DLLTRM_ICP, clk); break; case MMC_TIMING_MMC_HS200: case MMC_TIMING_UHS_SDR104: arasan_phy_set(host, HS200_MODE, 0x2, host->mmc->ios.drv_type, 0x0, DLLTRM_ICP, clk); break; case MMC_TIMING_MMC_DDR52: case MMC_TIMING_UHS_DDR50: arasan_phy_set(host, DDR50_MODE, 0x1, 0x0, 0x0, DLLTRM_ICP, clk); break; case MMC_TIMING_MMC_HS400: arasan_phy_set(host, HS400_MODE, 0x1, host->mmc->ios.drv_type, 0xa, DLLTRM_ICP, clk); break; default: break; } } return 0; } static int arasan_pci_probe_slot(struct sdhci_pci_slot *slot) { int err; slot->host->mmc->caps |= MMC_CAP_NONREMOVABLE | MMC_CAP_8_BIT_DATA; err = arasan_phy_init(slot->host); if (err) return -ENODEV; return 0; } static void arasan_sdhci_set_clock(struct sdhci_host *host, unsigned int clock) { sdhci_set_clock(host, clock); /* Change phy settings for the new clock */ arasan_select_phy_clock(host); } static const struct sdhci_ops arasan_sdhci_pci_ops = { .set_clock = arasan_sdhci_set_clock, .enable_dma = sdhci_pci_enable_dma, .set_bus_width = sdhci_set_bus_width, .reset = sdhci_reset, .set_uhs_signaling = sdhci_set_uhs_signaling, }; const struct sdhci_pci_fixes sdhci_arasan = { .probe_slot = arasan_pci_probe_slot, .ops = &arasan_sdhci_pci_ops, .priv_size = sizeof(struct arasan_host), };