| // 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 <agarg@arasan.com> |
| */ |
| |
| #include <linux/pci.h> |
| #include <linux/delay.h> |
| |
| #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), |
| }; |