1// SPDX-License-Identifier: GPL-2.0-or-later 2/* 3 * SDHCI support for SiRF primaII and marco SoCs 4 * 5 * Copyright (c) 2011 Cambridge Silicon Radio Limited, a CSR plc group company. 6 */ 7 8#include <linux/delay.h> 9#include <linux/device.h> 10#include <linux/mmc/host.h> 11#include <linux/module.h> 12#include <linux/of.h> 13#include <linux/mmc/slot-gpio.h> 14#include "sdhci-pltfm.h" 15 16#define SDHCI_CLK_DELAY_SETTING 0x4C 17#define SDHCI_SIRF_8BITBUS BIT(3) 18#define SIRF_TUNING_COUNT 16384 19 20static void sdhci_sirf_set_bus_width(struct sdhci_host *host, int width) 21{ 22 u8 ctrl; 23 24 ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL); 25 ctrl &= ~(SDHCI_CTRL_4BITBUS | SDHCI_SIRF_8BITBUS); 26 27 /* 28 * CSR atlas7 and prima2 SD host version is not 3.0 29 * 8bit-width enable bit of CSR SD hosts is 3, 30 * while stardard hosts use bit 5 31 */ 32 if (width == MMC_BUS_WIDTH_8) 33 ctrl |= SDHCI_SIRF_8BITBUS; 34 else if (width == MMC_BUS_WIDTH_4) 35 ctrl |= SDHCI_CTRL_4BITBUS; 36 37 sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL); 38} 39 40static u32 sdhci_sirf_readl_le(struct sdhci_host *host, int reg) 41{ 42 u32 val = readl(host->ioaddr + reg); 43 44 if (unlikely((reg == SDHCI_CAPABILITIES_1) && 45 (host->mmc->caps & MMC_CAP_UHS_SDR50))) { 46 /* fake CAP_1 register */ 47 val = SDHCI_SUPPORT_DDR50 | 48 SDHCI_SUPPORT_SDR50 | SDHCI_USE_SDR50_TUNING; 49 } 50 51 if (unlikely(reg == SDHCI_SLOT_INT_STATUS)) { 52 u32 prss = val; 53 /* fake chips as V3.0 host conreoller */ 54 prss &= ~(0xFF << 16); 55 val = prss | (SDHCI_SPEC_300 << 16); 56 } 57 return val; 58} 59 60static u16 sdhci_sirf_readw_le(struct sdhci_host *host, int reg) 61{ 62 u16 ret = 0; 63 64 ret = readw(host->ioaddr + reg); 65 66 if (unlikely(reg == SDHCI_HOST_VERSION)) { 67 ret = readw(host->ioaddr + SDHCI_HOST_VERSION); 68 ret |= SDHCI_SPEC_300; 69 } 70 71 return ret; 72} 73 74static int sdhci_sirf_execute_tuning(struct sdhci_host *host, u32 opcode) 75{ 76 int tuning_seq_cnt = 3; 77 int phase; 78 u8 tuned_phase_cnt = 0; 79 int rc = 0, longest_range = 0; 80 int start = -1, end = 0, tuning_value = -1, range = 0; 81 u16 clock_setting; 82 struct mmc_host *mmc = host->mmc; 83 84 clock_setting = sdhci_readw(host, SDHCI_CLK_DELAY_SETTING); 85 clock_setting &= ~0x3fff; 86 87retry: 88 phase = 0; 89 tuned_phase_cnt = 0; 90 do { 91 sdhci_writel(host, 92 clock_setting | phase, 93 SDHCI_CLK_DELAY_SETTING); 94 95 if (!mmc_send_tuning(mmc, opcode, NULL)) { 96 /* Tuning is successful at this tuning point */ 97 tuned_phase_cnt++; 98 dev_dbg(mmc_dev(mmc), "%s: Found good phase = %d\n", 99 mmc_hostname(mmc), phase); 100 if (start == -1) 101 start = phase; 102 end = phase; 103 range++; 104 if (phase == (SIRF_TUNING_COUNT - 1) 105 && range > longest_range) 106 tuning_value = (start + end) / 2; 107 } else { 108 dev_dbg(mmc_dev(mmc), "%s: Found bad phase = %d\n", 109 mmc_hostname(mmc), phase); 110 if (range > longest_range) { 111 tuning_value = (start + end) / 2; 112 longest_range = range; 113 } 114 start = -1; 115 end = range = 0; 116 } 117 } while (++phase < SIRF_TUNING_COUNT); 118 119 if (tuned_phase_cnt && tuning_value > 0) { 120 /* 121 * Finally set the selected phase in delay 122 * line hw block. 123 */ 124 phase = tuning_value; 125 sdhci_writel(host, 126 clock_setting | phase, 127 SDHCI_CLK_DELAY_SETTING); 128 129 dev_dbg(mmc_dev(mmc), "%s: Setting the tuning phase to %d\n", 130 mmc_hostname(mmc), phase); 131 } else { 132 if (--tuning_seq_cnt) 133 goto retry; 134 /* Tuning failed */ 135 dev_dbg(mmc_dev(mmc), "%s: No tuning point found\n", 136 mmc_hostname(mmc)); 137 rc = -EIO; 138 } 139 140 return rc; 141} 142 143static const struct sdhci_ops sdhci_sirf_ops = { 144 .read_l = sdhci_sirf_readl_le, 145 .read_w = sdhci_sirf_readw_le, 146 .platform_execute_tuning = sdhci_sirf_execute_tuning, 147 .set_clock = sdhci_set_clock, 148 .get_max_clock = sdhci_pltfm_clk_get_max_clock, 149 .set_bus_width = sdhci_sirf_set_bus_width, 150 .reset = sdhci_reset, 151 .set_uhs_signaling = sdhci_set_uhs_signaling, 152}; 153 154static const struct sdhci_pltfm_data sdhci_sirf_pdata = { 155 .ops = &sdhci_sirf_ops, 156 .quirks = SDHCI_QUIRK_BROKEN_TIMEOUT_VAL | 157 SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK | 158 SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN | 159 SDHCI_QUIRK_RESET_CMD_DATA_ON_IOS, 160 .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN, 161}; 162 163static int sdhci_sirf_probe(struct platform_device *pdev) 164{ 165 struct sdhci_host *host; 166 struct sdhci_pltfm_host *pltfm_host; 167 struct clk *clk; 168 int ret; 169 170 clk = devm_clk_get(&pdev->dev, NULL); 171 if (IS_ERR(clk)) { 172 dev_err(&pdev->dev, "unable to get clock"); 173 return PTR_ERR(clk); 174 } 175 176 host = sdhci_pltfm_init(pdev, &sdhci_sirf_pdata, 0); 177 if (IS_ERR(host)) 178 return PTR_ERR(host); 179 180 pltfm_host = sdhci_priv(host); 181 pltfm_host->clk = clk; 182 183 sdhci_get_of_property(pdev); 184 185 ret = clk_prepare_enable(pltfm_host->clk); 186 if (ret) 187 goto err_clk_prepare; 188 189 ret = sdhci_add_host(host); 190 if (ret) 191 goto err_sdhci_add; 192 193 /* 194 * We must request the IRQ after sdhci_add_host(), as the tasklet only 195 * gets setup in sdhci_add_host() and we oops. 196 */ 197 ret = mmc_gpiod_request_cd(host->mmc, "cd", 0, false, 0); 198 if (ret == -EPROBE_DEFER) 199 goto err_request_cd; 200 if (!ret) 201 mmc_gpiod_request_cd_irq(host->mmc); 202 203 return 0; 204 205err_request_cd: 206 sdhci_remove_host(host, 0); 207err_sdhci_add: 208 clk_disable_unprepare(pltfm_host->clk); 209err_clk_prepare: 210 sdhci_pltfm_free(pdev); 211 return ret; 212} 213 214static const struct of_device_id sdhci_sirf_of_match[] = { 215 { .compatible = "sirf,prima2-sdhc" }, 216 { } 217}; 218MODULE_DEVICE_TABLE(of, sdhci_sirf_of_match); 219 220static struct platform_driver sdhci_sirf_driver = { 221 .driver = { 222 .name = "sdhci-sirf", 223 .probe_type = PROBE_PREFER_ASYNCHRONOUS, 224 .of_match_table = sdhci_sirf_of_match, 225 .pm = &sdhci_pltfm_pmops, 226 }, 227 .probe = sdhci_sirf_probe, 228 .remove = sdhci_pltfm_unregister, 229}; 230 231module_platform_driver(sdhci_sirf_driver); 232 233MODULE_DESCRIPTION("SDHCI driver for SiRFprimaII/SiRFmarco"); 234MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); 235MODULE_LICENSE("GPL v2"); 236