diff options
Diffstat (limited to 'drivers/mmc/host/sdhci-sirf.c')
-rw-r--r-- | drivers/mmc/host/sdhci-sirf.c | 253 |
1 files changed, 253 insertions, 0 deletions
diff --git a/drivers/mmc/host/sdhci-sirf.c b/drivers/mmc/host/sdhci-sirf.c new file mode 100644 index 000000000..391d52b46 --- /dev/null +++ b/drivers/mmc/host/sdhci-sirf.c @@ -0,0 +1,253 @@ +/* + * SDHCI support for SiRF primaII and marco SoCs + * + * Copyright (c) 2011 Cambridge Silicon Radio Limited, a CSR plc group company. + * + * Licensed under GPLv2 or later. + */ + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/mmc/host.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <linux/mmc/slot-gpio.h> +#include "sdhci-pltfm.h" + +#define SDHCI_CLK_DELAY_SETTING 0x4C +#define SDHCI_SIRF_8BITBUS BIT(3) +#define SIRF_TUNING_COUNT 16384 + +struct sdhci_sirf_priv { + int gpio_cd; +}; + +static void sdhci_sirf_set_bus_width(struct sdhci_host *host, int width) +{ + u8 ctrl; + + ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL); + ctrl &= ~(SDHCI_CTRL_4BITBUS | SDHCI_SIRF_8BITBUS); + + /* + * CSR atlas7 and prima2 SD host version is not 3.0 + * 8bit-width enable bit of CSR SD hosts is 3, + * while stardard hosts use bit 5 + */ + if (width == MMC_BUS_WIDTH_8) + ctrl |= SDHCI_SIRF_8BITBUS; + else if (width == MMC_BUS_WIDTH_4) + ctrl |= SDHCI_CTRL_4BITBUS; + + sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL); +} + +static u32 sdhci_sirf_readl_le(struct sdhci_host *host, int reg) +{ + u32 val = readl(host->ioaddr + reg); + + if (unlikely((reg == SDHCI_CAPABILITIES_1) && + (host->mmc->caps & MMC_CAP_UHS_SDR50))) { + /* fake CAP_1 register */ + val = SDHCI_SUPPORT_DDR50 | + SDHCI_SUPPORT_SDR50 | SDHCI_USE_SDR50_TUNING; + } + + if (unlikely(reg == SDHCI_SLOT_INT_STATUS)) { + u32 prss = val; + /* fake chips as V3.0 host conreoller */ + prss &= ~(0xFF << 16); + val = prss | (SDHCI_SPEC_300 << 16); + } + return val; +} + +static u16 sdhci_sirf_readw_le(struct sdhci_host *host, int reg) +{ + u16 ret = 0; + + ret = readw(host->ioaddr + reg); + + if (unlikely(reg == SDHCI_HOST_VERSION)) { + ret = readw(host->ioaddr + SDHCI_HOST_VERSION); + ret |= SDHCI_SPEC_300; + } + + return ret; +} + +static int sdhci_sirf_execute_tuning(struct sdhci_host *host, u32 opcode) +{ + int tuning_seq_cnt = 3; + int phase; + u8 tuned_phase_cnt = 0; + int rc = 0, longest_range = 0; + int start = -1, end = 0, tuning_value = -1, range = 0; + u16 clock_setting; + struct mmc_host *mmc = host->mmc; + + clock_setting = sdhci_readw(host, SDHCI_CLK_DELAY_SETTING); + clock_setting &= ~0x3fff; + +retry: + phase = 0; + tuned_phase_cnt = 0; + do { + sdhci_writel(host, + clock_setting | phase, + SDHCI_CLK_DELAY_SETTING); + + if (!mmc_send_tuning(mmc, opcode, NULL)) { + /* Tuning is successful at this tuning point */ + tuned_phase_cnt++; + dev_dbg(mmc_dev(mmc), "%s: Found good phase = %d\n", + mmc_hostname(mmc), phase); + if (start == -1) + start = phase; + end = phase; + range++; + if (phase == (SIRF_TUNING_COUNT - 1) + && range > longest_range) + tuning_value = (start + end) / 2; + } else { + dev_dbg(mmc_dev(mmc), "%s: Found bad phase = %d\n", + mmc_hostname(mmc), phase); + if (range > longest_range) { + tuning_value = (start + end) / 2; + longest_range = range; + } + start = -1; + end = range = 0; + } + } while (++phase < SIRF_TUNING_COUNT); + + if (tuned_phase_cnt && tuning_value > 0) { + /* + * Finally set the selected phase in delay + * line hw block. + */ + phase = tuning_value; + sdhci_writel(host, + clock_setting | phase, + SDHCI_CLK_DELAY_SETTING); + + dev_dbg(mmc_dev(mmc), "%s: Setting the tuning phase to %d\n", + mmc_hostname(mmc), phase); + } else { + if (--tuning_seq_cnt) + goto retry; + /* Tuning failed */ + dev_dbg(mmc_dev(mmc), "%s: No tuning point found\n", + mmc_hostname(mmc)); + rc = -EIO; + } + + return rc; +} + +static const struct sdhci_ops sdhci_sirf_ops = { + .read_l = sdhci_sirf_readl_le, + .read_w = sdhci_sirf_readw_le, + .platform_execute_tuning = sdhci_sirf_execute_tuning, + .set_clock = sdhci_set_clock, + .get_max_clock = sdhci_pltfm_clk_get_max_clock, + .set_bus_width = sdhci_sirf_set_bus_width, + .reset = sdhci_reset, + .set_uhs_signaling = sdhci_set_uhs_signaling, +}; + +static const struct sdhci_pltfm_data sdhci_sirf_pdata = { + .ops = &sdhci_sirf_ops, + .quirks = SDHCI_QUIRK_BROKEN_TIMEOUT_VAL | + SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK | + SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN | + SDHCI_QUIRK_RESET_CMD_DATA_ON_IOS, + .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN, +}; + +static int sdhci_sirf_probe(struct platform_device *pdev) +{ + struct sdhci_host *host; + struct sdhci_pltfm_host *pltfm_host; + struct sdhci_sirf_priv *priv; + struct clk *clk; + int gpio_cd; + int ret; + + clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "unable to get clock"); + return PTR_ERR(clk); + } + + if (pdev->dev.of_node) + gpio_cd = of_get_named_gpio(pdev->dev.of_node, "cd-gpios", 0); + else + gpio_cd = -EINVAL; + + host = sdhci_pltfm_init(pdev, &sdhci_sirf_pdata, sizeof(struct sdhci_sirf_priv)); + if (IS_ERR(host)) + return PTR_ERR(host); + + pltfm_host = sdhci_priv(host); + pltfm_host->clk = clk; + priv = sdhci_pltfm_priv(pltfm_host); + priv->gpio_cd = gpio_cd; + + sdhci_get_of_property(pdev); + + ret = clk_prepare_enable(pltfm_host->clk); + if (ret) + goto err_clk_prepare; + + ret = sdhci_add_host(host); + if (ret) + goto err_sdhci_add; + + /* + * We must request the IRQ after sdhci_add_host(), as the tasklet only + * gets setup in sdhci_add_host() and we oops. + */ + if (gpio_is_valid(priv->gpio_cd)) { + ret = mmc_gpio_request_cd(host->mmc, priv->gpio_cd, 0); + if (ret) { + dev_err(&pdev->dev, "card detect irq request failed: %d\n", + ret); + goto err_request_cd; + } + mmc_gpiod_request_cd_irq(host->mmc); + } + + return 0; + +err_request_cd: + sdhci_remove_host(host, 0); +err_sdhci_add: + clk_disable_unprepare(pltfm_host->clk); +err_clk_prepare: + sdhci_pltfm_free(pdev); + return ret; +} + +static const struct of_device_id sdhci_sirf_of_match[] = { + { .compatible = "sirf,prima2-sdhc" }, + { } +}; +MODULE_DEVICE_TABLE(of, sdhci_sirf_of_match); + +static struct platform_driver sdhci_sirf_driver = { + .driver = { + .name = "sdhci-sirf", + .of_match_table = sdhci_sirf_of_match, + .pm = &sdhci_pltfm_pmops, + }, + .probe = sdhci_sirf_probe, + .remove = sdhci_pltfm_unregister, +}; + +module_platform_driver(sdhci_sirf_driver); + +MODULE_DESCRIPTION("SDHCI driver for SiRFprimaII/SiRFmarco"); +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_LICENSE("GPL v2"); |