Select pxa controller based on CPU model: MMP2, PXA168, PXA910 Three new SoC specific files added: sdhci-pxa168.c sdhci-pxa910.c sdhci-mmp2.c These files control the platform specific behavior of the SD controller. MMP2 and the PXAxxx controllers use different hardware registers to control platform specific behavior. Platform flags come from arch/mach-mmp/ files for board design (brownstone, jasper, aspenite etc) settings (8 bit capable slot / card permanent). quirks for SD/SoC specific behaviro defined in specific platform files (sdhci-pxa168.c, sdhci-pxa910.c, sdhci-mmp2.c) The correct SD controller is now shown Kconfig. sdhci-pxa.c changed to act as a shim for the platform specific code. Only generic operations are handled in sdhci-pxa.c. All platform operations are passed to the platform code. reset_enter() and reset_exit() used for silicon control. Signed-off-by: Philip Rakity <prakity@xxxxxxxxxxx> --- drivers/mmc/host/Kconfig | 42 +++++- drivers/mmc/host/Makefile | 5 +- drivers/mmc/host/sdhci-mmp2.c | 265 ++++++++++++++++++++++++++++++ drivers/mmc/host/sdhci-pxa.c | 151 +++--------------- drivers/mmc/host/sdhci-pxa168.c | 343 +++++++++++++++++++++++++++++++++++++++ drivers/mmc/host/sdhci-pxa910.c | 272 +++++++++++++++++++++++++++++++ drivers/mmc/host/sdhci.c | 15 ++- 7 files changed, 953 insertions(+), 140 deletions(-) create mode 100644 drivers/mmc/host/sdhci-mmp2.c create mode 100644 drivers/mmc/host/sdhci-pxa168.c create mode 100644 drivers/mmc/host/sdhci-pxa910.c diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index d42fe49..d9eddbd 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -166,18 +166,52 @@ config MMC_SDHCI_S3C If unsure, say N. + +config MMC_SDHCI_PXA_CORE + tristate + help + This is silent Kconfig symbol that is selected by the drivers that + need PXA driver base support + + config MMC_SDHCI_PXA - tristate "Marvell PXA168/PXA910/MMP2 SD Host Controller support" - depends on ARCH_PXA || ARCH_MMP + tristate "Marvell MMP2 SD Host Controller support" + depends on CPU_MMP2 + select MMC_SDHCI + select MMC_SDHCI_PXA_CORE + help + This selects the Marvell(R) MMP2 SD Host Controller. + If you have a MMP2 platform with SD Host Controller + and a card slot, say Y or M here. + + If unsure, say N. + +config MMC_SDHCI_PXA9xx + tristate "Marvell PXA9xx SD Host Controller support" + depends on CPU_PXA910 select MMC_SDHCI + select MMC_SDHCI_PXA_CORE + help + This selects the Marvell(R) PXA910 SD Host Controller. + If you have a PXA910 platform with SD Host Controller + and a card slot, say Y or M here. + + If unsure, say N. + +config MMC_SDHCI_PXA168 + tristate "Marvell PXA168 SD Host Controller support" + depends on CPU_PXA168 + select MMC_SDHCI + select MMC_SDHCI_PXA_CORE select MMC_SDHCI_IO_ACCESSORS help - This selects the Marvell(R) PXA168/PXA910/MMP2 SD Host Controller. - If you have a PXA168/PXA910/MMP2 platform with SD Host Controller + This selects the Marvell(R) PXA168 SD Host Controller. + If you have a PXA168 platform with SD Host Controller and a card slot, say Y or M here. If unsure, say N. + config MMC_SDHCI_SPEAR tristate "SDHCI support on ST SPEAr platform" depends on MMC_SDHCI && PLAT_SPEAR diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index d91364d..1de1c77 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -8,7 +8,10 @@ obj-$(CONFIG_MMC_IMX) += imxmmc.o obj-$(CONFIG_MMC_MXC) += mxcmmc.o obj-$(CONFIG_MMC_SDHCI) += sdhci.o obj-$(CONFIG_MMC_SDHCI_PCI) += sdhci-pci.o -obj-$(CONFIG_MMC_SDHCI_PXA) += sdhci-pxa.o +obj-$(CONFIG_MMC_SDHCI_PXA_CORE) += sdhci-pxa.o +obj-$(CONFIG_MMC_SDHCI_PXA) += sdhci-mmp2.o +obj-$(CONFIG_MMC_SDHCI_PXA168) += sdhci-pxa168.o +obj-$(CONFIG_MMC_SDHCI_PXA9xx) += sdhci-pxa910.o obj-$(CONFIG_MMC_SDHCI_S3C) += sdhci-s3c.o obj-$(CONFIG_MMC_SDHCI_SPEAR) += sdhci-spear.o obj-$(CONFIG_MMC_WBSD) += wbsd.o diff --git a/drivers/mmc/host/sdhci-mmp2.c b/drivers/mmc/host/sdhci-mmp2.c new file mode 100644 index 0000000..45e29f5 --- /dev/null +++ b/drivers/mmc/host/sdhci-mmp2.c @@ -0,0 +1,265 @@ +/************************************************************************** + * + * Copyright (c) 2009, 2010 Marvell International Ltd. + * Philip Rakity <prakity@xxxxxxxxxxx> + * Mark F. Brown <markb@xxxxxxxxxxx> + * + * This file is part of GNU program. + * + * GNU program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 2 of the License, or (at your + * option) any later version. + * + * GNU program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. + * + * If not, see http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * + *************************************************************************/ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/mmc/host.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <plat/sdhci.h> +#include "sdhci-pxa.h" +#include "sdhci.h" + +#define DEBUG + +#define DRIVER_NAME "sdhci-mmp2" + +#define SD_CFG_FIFO_PARAM 0x100 +#define SDCFG_GEN_PAD_CLK_ON (1<<6) + +#define SD_CLOCK_AND_BURST_SIZE_SETUP 0x10A +#define SDCLK_DELAY_MASK 0x1F +#define SDCLK_SEL_MASK 0x1 +#define SDCLK_DELAY_SHIFT 9 +#define SDCLK_SEL_SHIFT 8 + +#define SD_CE_ATA_2 0x10E +#define SDCE_MISC_INT (1<<2) +#define SDCE_MISC_INT_EN (1<<1) + +#define DISABLE_CLOCK_GATING 0 + + +static int platform_mmp2_probe(struct sdhci_host *host); + +/* + * MMC spec calls for the host to send 74 clocks to the card + * during initialization, right after voltage stabilization. + * the pxa168 controller has no easy way to generate those clocks. + * create the clocks manually right here. + */ +static void generate_initial_74_clocks(struct sdhci_host *host, u8 power_mode) +{ + struct sdhci_pxa *pxa = sdhci_priv(host); + u16 tmp; + int count; + + if (pxa->power_mode == MMC_POWER_UP + && power_mode == MMC_POWER_ON) { + + pr_debug("%s:%s ENTER: slot->power_mode = %d," + "ios->power_mode = %d\n", + __func__, + mmc_hostname(host->mmc), + pxa->power_mode, + power_mode); + + /* set we want notice of when 74 clocks are sent */ + tmp = readw(host->ioaddr + SD_CE_ATA_2); + tmp |= SDCE_MISC_INT_EN; + writew(tmp, host->ioaddr + SD_CE_ATA_2); + + /* start sending the 74 clocks */ + tmp = readw(host->ioaddr + SD_CFG_FIFO_PARAM); + tmp |= SDCFG_GEN_PAD_CLK_ON; + writew(tmp, host->ioaddr + SD_CFG_FIFO_PARAM); + + /* slowest speed is about 100KHz or 10usec per clock */ + udelay(740); + count = 0; +#define MAX_WAIT_COUNT 5 + while (count++ < MAX_WAIT_COUNT) { + if ((readw(host->ioaddr + SD_CE_ATA_2) + & SDCE_MISC_INT) == 0) + break; + udelay(10); + } + + if (count == MAX_WAIT_COUNT) + printk(KERN_WARNING"%s: %s: 74 clock interrupt " + "not cleared\n", + __func__, mmc_hostname(host->mmc)); + /* clear the interrupt bit if posted */ + tmp = readw(host->ioaddr + SD_CE_ATA_2); + tmp |= SDCE_MISC_INT; + writew(tmp, host->ioaddr + SD_CE_ATA_2); + } + pxa->power_mode = power_mode; +} + +static void set_clock_and_burst_size(struct sdhci_host *host) +{ + u16 tmp; + struct sdhci_pxa *pxa = sdhci_priv(host); + + pr_debug("%s:%s: adjust = %d\n", + __func__, mmc_hostname(host->mmc), pxa->pdata->adjust_clocks); + + if (pxa->pdata->adjust_clocks) { + tmp = readw(host->ioaddr + SD_CLOCK_AND_BURST_SIZE_SETUP); + pr_debug("%s:%s: (B) SD_CLOCK_AND_BURST = %04X, " + "delay = %d, sel = %d\n", + __func__, mmc_hostname(host->mmc), tmp, + pxa->pdata->clk_delay, pxa->pdata->clk_select); + tmp &= ~(SDCLK_DELAY_MASK << SDCLK_DELAY_SHIFT); + tmp &= ~(SDCLK_SEL_MASK << SDCLK_SEL_SHIFT); + tmp |= (pxa->pdata->clk_delay & SDCLK_DELAY_MASK) << + SDCLK_DELAY_SHIFT; + tmp |= (pxa->pdata->clk_select & SDCLK_SEL_MASK) << + SDCLK_SEL_SHIFT; + writew(tmp, host->ioaddr + SD_CLOCK_AND_BURST_SIZE_SETUP); + pr_debug("%s:%s: (A) SD_CLOCK_AND_BURST_SIZE_SETUP = %04X\n", + __func__, mmc_hostname(host->mmc), tmp); + } +} + +static void programFIFO(struct sdhci_host *host, int enable) +{ + unsigned short tmp; + + tmp = readw(host->ioaddr + SDHCI_HOST_CONTROL_2); + + if (enable) + tmp |= SDCTRL_2_ASYNC_INT_EN; + else + tmp &= ~SDCTRL_2_ASYNC_INT_EN; + + writew(tmp, host->ioaddr + SDHCI_HOST_CONTROL_2); +} + +static void platform_reset_exit(struct sdhci_host *host, u8 mask) +{ + if (mask == SDHCI_RESET_ALL) { + /* reset private registers */ + programFIFO(host, DISABLE_CLOCK_GATING); + set_clock_and_burst_size(host); + } +} + + +#ifdef CONFIG_MMC_CLKGATE +static void platform_hw_clk_gate(struct sdhci_host *host) +{ + int enable; + + enable = host->mmc->clk_gated; + programFIFO(host, enable); + pr_debug("%s:%s: enable = %d\n", + __func__, mmc_hostname(host->mmc), enable); +} +#endif + +static unsigned int get_f_max_clock(struct sdhci_host *host) +{ + struct sdhci_pxa *pxa = sdhci_priv(host); + + pr_debug("%s:%s f_max = %d\n", + __func__, mmc_hostname(host->mmc), + pxa->pdata->max_speed); + + return pxa->pdata->max_speed; +} + +static unsigned int set_signaling_voltage(struct sdhci_host *host, + unsigned int ddr) +{ + u16 con; + + pr_debug("%s:%s\n", __func__, mmc_hostname(host->mmc)); + /* + * Set V18_EN -- DDR does not work without this. + * does not change signaling voltage + */ + con = readw(host->ioaddr + SDHCI_HOST_CONTROL_2); + con |= SDCTRL_2_SDH_V18_EN; + writew(con, host->ioaddr + SDHCI_HOST_CONTROL_2); + return 0; +} + +struct sdhci_pxa_data sdhci_platform_data = { + .ops = { + .platform_reset_exit = platform_reset_exit, + .platform_send_init_74_clocks = generate_initial_74_clocks, + .set_signaling_voltage = set_signaling_voltage, + .get_f_max_clock = NULL, +#ifdef CONFIG_MMC_CLKGATE + .platform_hw_clk_gate = platform_hw_clk_gate, +#endif + }, +#ifdef CONFIG_MMC_CLKGATE + .mmc_caps = MMC_CAP_HW_CLOCK_GATING | MMC_CAP_BUS_WIDTH_TEST, +#else + .mmc_caps = MMC_CAP_BUS_WIDTH_TEST, +#endif + .platform_probe = platform_mmp2_probe, + .quirks = 0, +}; +EXPORT_SYMBOL_GPL(sdhci_platform_data); + +static int platform_mmp2_probe(struct sdhci_host *host) +{ + struct sdhci_pxa *pxa = sdhci_priv(host); + struct sdhci_pxa_platdata *pdata = pxa->pdata; + struct sdhci_ops *p_ops; + + p_ops = kmalloc(sizeof(struct sdhci_ops), GFP_KERNEL); + if (!p_ops) { + printk(KERN_ERR "no memory"); + return -ENOMEM; + } + + /* + * we cannot directly copy our operations into host->ops + * since it is read only. So we do this indirectly. + */ + memcpy((void *)p_ops, (void *)&sdhci_platform_data.ops, + sizeof(struct sdhci_ops)); + + if (pxa->pdata->max_speed) + p_ops->get_f_max_clock = get_f_max_clock; + + host->quirks |= sdhci_platform_data.quirks; + host->mmc->caps |= sdhci_platform_data.mmc_caps; + + /* If slot design supports 8 bit data, indicate this to MMC. */ + if (pdata->flags & PXA_FLAG_SD_8_BIT_CAPABLE_SLOT) + host->mmc->caps |= MMC_CAP_8_BIT_DATA; + + if (pdata->flags & PXA_FLAG_CARD_PERMANENT) { + host->mmc->caps |= MMC_CAP_NONREMOVABLE; + host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION; + } + + pr_debug("%s:%s: host->quirks = %08X, mmc->caps = %08lX\n", + __func__, mmc_hostname(host->mmc), + host->quirks, host->mmc->caps); + + memcpy((void *)host->ops, (void *)p_ops, sizeof(struct sdhci_ops)); + kfree(p_ops); + return 0; +} diff --git a/drivers/mmc/host/sdhci-pxa.c b/drivers/mmc/host/sdhci-pxa.c index b31d3d5..3b965ce 100644 --- a/drivers/mmc/host/sdhci-pxa.c +++ b/drivers/mmc/host/sdhci-pxa.c @@ -18,40 +18,21 @@ * Refer to sdhci-s3c.c. */ +#include <linux/module.h> #include <linux/delay.h> #include <linux/platform_device.h> #include <linux/mmc/host.h> #include <linux/clk.h> #include <linux/io.h> #include <linux/err.h> +#include <linux/slab.h> #include <plat/sdhci.h> +#include "sdhci-pxa.h" +#include <mach/cputype.h> #include "sdhci.h" #define DRIVER_NAME "sdhci-pxa" -#define SD_CFG_FIFO_PARAM 0x100 -#define SDCFG_GEN_PAD_CLK_ON (1<<6) - -#define SD_CLOCK_AND_BURST_SIZE_SETUP 0x10A -#define SDCLK_DELAY_MASK 0x1F -#define SDCLK_SEL_MASK 0x1 -#define SDCLK_DELAY_SHIFT 9 -#define SDCLK_SEL_SHIFT 8 - -#define SD_CE_ATA_2 0x10E -#define SDCE_MISC_INT (1<<2) -#define SDCE_MISC_INT_EN (1<<1) - -#define DISABLE_CLOCK_GATING 0 - -struct sdhci_pxa { - struct sdhci_host *host; - struct sdhci_pxa_platdata *pdata; - struct clk *clk; - struct resource *res; - - u8 clk_enable; -}; /*****************************************************************************\ * * @@ -68,94 +49,6 @@ static void enable_clock(struct sdhci_host *host) } } -static unsigned int get_f_max_clock(struct sdhci_host *host) -{ - struct sdhci_pxa *pxa = sdhci_priv(host); - - return pxa->pdata->max_speed; -} - -static unsigned int set_signaling_voltage(struct sdhci_host *host, - unsigned int ddr) -{ - u16 con; - /* - * Set V18_EN -- DDR does not work without this. - * does not change signaling voltage - */ - con = readw(host->ioaddr + SDHCI_HOST_CONTROL_2); - con |= SDCTRL_2_SDH_V18_EN; - writew(con, host->ioaddr + SDHCI_HOST_CONTROL_2); - return 0; -} - - -static void set_clock_and_burst_size(struct sdhci_host *host) -{ - u16 tmp; - struct sdhci_pxa *pxa = sdhci_priv(host); - - pr_debug("%s:%s: adjust = %d\n", - __func__, mmc_hostname(host->mmc), pxa->pdata->adjust_clocks); - - if (pxa->pdata->adjust_clocks) { - tmp = readw(host->ioaddr + SD_CLOCK_AND_BURST_SIZE_SETUP); - pr_debug("%s:%s: (B) SD_CLOCK_AND_BURST = %04X, " - "delay = %d, sel = %d\n", - __func__, mmc_hostname(host->mmc), tmp, - pxa->pdata->clk_delay, pxa->pdata->clk_select); - tmp &= ~(SDCLK_DELAY_MASK << SDCLK_DELAY_SHIFT); - tmp &= ~(SDCLK_SEL_MASK << SDCLK_SEL_SHIFT); - tmp |= (pxa->pdata->clk_delay & SDCLK_DELAY_MASK) << - SDCLK_DELAY_SHIFT; - tmp |= (pxa->pdata->clk_select & SDCLK_SEL_MASK) << - SDCLK_SEL_SHIFT; - writew(tmp, host->ioaddr + SD_CLOCK_AND_BURST_SIZE_SETUP); - pr_debug("%s:%s: (A) SD_CLOCK_AND_BURST_SIZE_SETUP = %04X\n", - __func__, mmc_hostname(host->mmc), tmp); - } -} - -static void programFIFO(struct sdhci_host *host, int enable) -{ - unsigned short tmp; - - tmp = readw(host->ioaddr + SDHCI_HOST_CONTROL_2); - - if (enable) - tmp |= SDCTRL_2_ASYNC_INT_EN; - else - tmp &= ~SDCTRL_2_ASYNC_INT_EN; - - writew(tmp, host->ioaddr + SDHCI_HOST_CONTROL_2); -} - -static void platform_reset_exit(struct sdhci_host *host, u8 mask) -{ - if (mask == SDHCI_RESET_ALL) { - programFIFO(host, DISABLE_CLOCK_GATING); - set_clock_and_burst_size(host); - } -} - -#ifdef CONFIG_MMC_CLKGATE -static void platform_hw_clk_gate(struct sdhci_host *host) -{ - int enable; - - enable = host->mmc->clk_gated; - programFIFO(host, enable); -} -#endif - -static struct sdhci_ops sdhci_pxa_ops = { - .get_f_max_clock = NULL, - .set_signaling_voltage = set_signaling_voltage, - .platform_reset_exit = platform_reset_exit, -#ifdef CONFIG_MMC_CLKGATE - .platform_hw_clk_gate = platform_hw_clk_gate, -#endif -}; /*****************************************************************************\ * * @@ -218,32 +111,23 @@ static int __devinit sdhci_pxa_probe(struct platform_device *pdev) } host->hw_name = "MMC"; - host->ops = &sdhci_pxa_ops; - host->irq = irq; - host->quirks = SDHCI_QUIRK_BROKEN_ADMA | SDHCI_QUIRK_BROKEN_TIMEOUT_VAL; - -#ifdef CONFIG_MMC_CLKGATE - host->mmc->caps |= MMC_CAP_HW_CLOCK_GATING | MMC_CAP_BUS_WIDTH_TEST; -#else - host->mmc->caps |= MMC_CAP_BUS_WIDTH_TEST; -#endif - - /* If slot design supports 8 bit data, indicate this to MMC. */ - if (pdata->flags & PXA_FLAG_SD_8_BIT_CAPABLE_SLOT) - host->mmc->caps |= MMC_CAP_8_BIT_DATA; - - if (pdata->flags & PXA_FLAG_CARD_PERMANENT) { - host->mmc->caps |= MMC_CAP_NONREMOVABLE; - host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION; + host->ops = kmalloc(sizeof(struct sdhci_ops), GFP_KERNEL); + if (!host->ops) { + dev_err(&pdev->dev, "no memory for host->ops\n"); + ret = -ENOMEM; + goto out; } + host->irq = irq; + host->quirks = SDHCI_QUIRK_BROKEN_TIMEOUT_VAL; /* do not rely on u-boot to enable the clocks */ enable_clock(host); - if (pxa->pdata->max_speed) - sdhci_pxa_ops.get_f_max_clock = get_f_max_clock; - else - sdhci_pxa_ops.get_f_max_clock = NULL; + if (sdhci_platform_data.platform_probe) { + ret = sdhci_platform_data.platform_probe(host); + if (ret) + goto out; + } ret = sdhci_add_host(host); if (ret) { @@ -257,6 +141,7 @@ static int __devinit sdhci_pxa_probe(struct platform_device *pdev) out: if (host) { clk_put(pxa->clk); + kfree(host->ops); if (host->ioaddr) iounmap(host->ioaddr); if (pxa->res) @@ -289,6 +174,7 @@ static int __devexit sdhci_pxa_remove(struct platform_device *pdev) resource_size(pxa->res)); clk_put(pxa->clk); + kfree(host->ops); sdhci_free_host(host); platform_set_drvdata(pdev, NULL); } @@ -348,4 +234,5 @@ module_exit(sdhci_pxa_exit); MODULE_DESCRIPTION("SDH controller driver for PXA168/PXA910/MMP2"); MODULE_AUTHOR("Zhangfei Gao <zhangfei.gao@xxxxxxxxxxx>"); +MODULE_AUTHOR("Philp Rakity <prakity@xxxxxxxxxxx>"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/mmc/host/sdhci-pxa168.c b/drivers/mmc/host/sdhci-pxa168.c new file mode 100644 index 0000000..696862f --- /dev/null +++ b/drivers/mmc/host/sdhci-pxa168.c @@ -0,0 +1,343 @@ +/************************************************************************** + * + * Copyright (c) 2009, 2010 Marvell International Ltd. + * Philip Rakity <prakity@xxxxxxxxxxx> + * Mark F. Brown <markb@xxxxxxxxxxx> + * + * This file is part of GNU program. + * + * GNU program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 2 of the License, or (at your + * option) any later version. + * + * GNU program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. + * + * If not, see http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * + *************************************************************************/ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/mmc/host.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <plat/sdhci.h> +#include "sdhci-pxa.h" +#include "sdhci.h" + +#define DRIVER_NAME "sdhci-pxa168" + +#define SD_FIFO_PARAM 0xE0 +#define DIS_PAD_SD_CLK_GATE (1<<10) /* Turn on/off Dynamic Clock Gating */ + +#define SD_CLOCK_AND_BURST_SIZE_SETUP 0xE6 +#define SDCLK_DELAY_MASK 0xF +#define SDCLK_SEL_MASK 0x3 +#define SDCLK_DELAY_SHIFT 10 +#define SDCLK_SEL_SHIFT 8 + +#define SD_CE_ATA_2 0xEA +#define SDCE_MMC_WIDTH (1<<8) +#define SDCE_MMC_CARD (1<<12) + +#define DISABLE_CLOCK_GATING 0 + +static int platform_pxa168_probe(struct sdhci_host *host); + +/* + * MMC spec calls for the host to send 74 clocks to the card + * during initialization, right after voltage stabilization. + * the pxa168 controller has no easy way to generate those clocks. + * create the clocks manually right here. + */ +#if 0 +static void generate_init_clocks(struct sdhci_host *host, u8 power_mode) +{ + struct sdhci_pxa *pxa = sdhci_priv(host); + struct pfn_cfg *cfg = pxa->pdata->pfn_table; + mfp_cfg_t clk_pin; + int i; + + + if (cfg == NULL) { + DBG("Cannot generate init clocks!\n"); + return; + } + + if (pxa->power_mode == MMC_POWER_UP + && power_mode == MMC_POWER_ON) { + + DBG("%s: ENTER %s: power_mode = %d, ios_.power_mode = %d\n", + __func__, + mmc_hostname(host->mmc), + pxa->power_mode, + power_mode); + /* CMD/CLK pin to gpio mode. */ + mfp_config(pfn_lookup(cfg, PFN_GPIO, PIN_MMC_CMD), 1); + mfp_config(pfn_lookup(cfg, PFN_GPIO, PIN_MMC_CLK), 1); + + /* ensure at least 1/2 period stable to prevent runt pulse.*/ + udelay(3); + + clk_pin = *(pfn_lookup(cfg, PFN_GPIO, PIN_MMC_CLK)); + if (gpio_request(MFP_PIN(clk_pin), "MMC_CLK")) { + printk(KERN_ERR "Cannot obtain MMC_CLK GPIO %ld\n", + MFP_PIN(clk_pin)); + goto err; + } + + DBG("Generating init clocks on pins CLK %ld\n", + MFP_PIN(clk_pin)); + + for (i = 0; i < INIT_CLOCKS; i++) { + gpio_direction_output(MFP_PIN(clk_pin), 0); /* low */ + udelay(3); + gpio_direction_output(MFP_PIN(clk_pin), 1); /* high */ + udelay(3); + } + + gpio_free(MFP_PIN(clk_pin)); + } + +err: + pxa->power_mode = power_mode; + + /* CMD/CLK pin back MMC mode. */ + mfp_config(pfn_lookup(cfg, PFN_FN, PIN_MMC_CMD), 1); + mfp_config(pfn_lookup(cfg, PFN_FN, PIN_MMC_CLK), 1); +} +#endif + +/* + * we cannot talk to controller for 8 bus cycles according to sdio spec + * at lowest speed this is 100,000 HZ per cycle or 800,000 cycles + * which is quite a LONG TIME on a fast cpu -- so delay if needed + */ +static void platform_specific_delay(struct sdhci_host *host) +{ + struct sdhci_pxa *pxa = sdhci_priv(host); + + mdelay(pxa->delay_in_ms); + udelay(pxa->delay_in_us); + ndelay(pxa->delay_in_ns); +} + +static void set_clock(struct sdhci_host *host, unsigned int clock) +{ + struct sdhci_pxa *pxa = sdhci_priv(host); + + if (clock != 0) { + pxa->delay_in_ns = (1000000000/clock); + + /* need to delay 12 clocks for 8787/8786 */ + /* need to delay 8 clocks for controller -- so just use 12 */ + + pxa->delay_in_ns = pxa->delay_in_ns * 12; + + pxa->delay_in_ms = pxa->delay_in_ns / 1000000; + pxa->delay_in_ns = pxa->delay_in_ns % 1000000; + pxa->delay_in_us = pxa->delay_in_ns / 1000; + pxa->delay_in_ns = pxa->delay_in_ns % 1000; + } else { + pxa->delay_in_ns = 0; + pxa->delay_in_us = 0; + pxa->delay_in_ms = 0; + } +} + +static void set_clock_and_burst_size(struct sdhci_host *host) +{ + u16 tmp; + struct sdhci_pxa *pxa = sdhci_priv(host); + + if (pxa->pdata->adjust_clocks) { + tmp = readw(host->ioaddr + SD_CLOCK_AND_BURST_SIZE_SETUP); + pr_debug("%s:%s: (B) SD_CLOCK_AND_BURST = %04X" + ", delay = %d, sel = %d\n", + __func__, mmc_hostname(host->mmc), tmp, + pxa->pdata->clk_delay, pxa->pdata->clk_select); + tmp &= ~(SDCLK_DELAY_MASK << SDCLK_DELAY_SHIFT); + tmp &= ~(SDCLK_SEL_MASK << SDCLK_SEL_SHIFT); + tmp |= (pxa->pdata->clk_delay & SDCLK_DELAY_MASK) << + SDCLK_DELAY_SHIFT; + tmp |= (pxa->pdata->clk_select & SDCLK_SEL_MASK) << + SDCLK_SEL_SHIFT; + writew(tmp, host->ioaddr + SD_CLOCK_AND_BURST_SIZE_SETUP); + pr_debug("%s:%s: (A) SD_CLOCK_AND_BURST = %04X\n", + __func__, mmc_hostname(host->mmc), tmp); + } +} + +static void programFIFO(struct sdhci_host *host, int enable) +{ + unsigned short tmp; + + tmp = readw(host->ioaddr + SD_FIFO_PARAM); + if (enable) + tmp &= ~DIS_PAD_SD_CLK_GATE; + else + tmp |= DIS_PAD_SD_CLK_GATE; + writew(tmp, host->ioaddr + SD_FIFO_PARAM); +} + +static void platform_reset_enter(struct sdhci_host *host, u8 mask) +{ + /* Before RESET_DATA we need to wait at least 10 sd cycles */ + if (mask == SDHCI_RESET_DATA) + platform_specific_delay(host); +} + + +static void platform_reset_exit(struct sdhci_host *host, u8 mask) +{ + if (mask == SDHCI_RESET_ALL) { + /* reset private registers */ + programFIFO(host, DISABLE_CLOCK_GATING); + set_clock_and_burst_size(host); + } +} + + +#ifdef CONFIG_MMC_CLKGATE +static void platform_hw_clk_gate(struct sdhci_host *host) +{ + int enable; + + enable = host->mmc->clk_gated; + programFIFO(host, enable); + pr_debug("%s:%s: enable = %d\n", + __func__, mmc_hostname(host->mmc), enable); +} +#endif + +static unsigned int get_max_clock(struct sdhci_host *host) +{ + struct sdhci_pxa *pxa = sdhci_priv(host); + + pr_debug("%s:%s clk_get_rate = %lu\n", + __func__, mmc_hostname(host->mmc), + clk_get_rate(pxa->clk)); + + return clk_get_rate(pxa->clk); +} + +static unsigned int get_f_max_clock(struct sdhci_host *host) +{ + struct sdhci_pxa *pxa = sdhci_priv(host); + + pr_debug("%s:%s f_max = %d\n", + __func__, mmc_hostname(host->mmc), + pxa->pdata->max_speed); + + return pxa->pdata->max_speed; +} + +static int platform_supports_8_bit(struct sdhci_host *host, int width) +{ + u16 tmp; + + tmp = readw(host->ioaddr + SD_CE_ATA_2); + tmp |= SDCE_MMC_CARD | SDCE_MMC_WIDTH; + writew(tmp, host->ioaddr + SD_CE_ATA_2); + + tmp = readw(host->ioaddr + SD_CE_ATA_2); + if (width != 8) + tmp &= ~(SDCE_MMC_CARD | SDCE_MMC_WIDTH); + else + tmp |= SDCE_MMC_CARD | SDCE_MMC_WIDTH; + writew(tmp, host->ioaddr + SD_CE_ATA_2); + return 0; +} + +static u16 pxa168_readw(struct sdhci_host *host, int reg) +{ + u32 temp; + + if (reg == SDHCI_HOST_VERSION) { + temp = readl(host->ioaddr + SDHCI_HOST_VERSION - 2) >> 16; + return temp & 0xffff; + } + + return readw(host->ioaddr + reg); +} + +struct sdhci_pxa_data sdhci_platform_data = { + .ops = { + .platform_reset_enter = platform_reset_enter, + .platform_reset_exit = platform_reset_exit, + .get_max_clock = get_max_clock, + .set_clock = set_clock, + .platform_specific_delay = platform_specific_delay, + .platform_send_init_74_clocks = NULL, + .get_f_max_clock = NULL, + .platform_8bit_width = NULL, +#ifdef CONFIG_MMC_CLKGATE + .platform_hw_clk_gate = platform_hw_clk_gate, +#endif + .read_w = pxa168_readw, + }, +#ifdef CONFIG_MMC_CLKGATE + .mmc_caps = MMC_CAP_HW_CLOCK_GATING | MMC_CAP_BUS_WIDTH_TEST, +#else + .mmc_caps = MMC_CAP_BUS_WIDTH_TEST, +#endif + .platform_probe = platform_pxa168_probe, + .quirks = SDHCI_QUIRK_32BIT_DMA_ADDR | SDHCI_QUIRK_32BIT_DMA_SIZE + | SDHCI_QUIRK_NO_BUSY_IRQ | SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN + | SDHCI_QUIRK_NO_HISPD_BIT, + +}; +EXPORT_SYMBOL_GPL(sdhci_platform_data); + +static int platform_pxa168_probe(struct sdhci_host *host) +{ + struct sdhci_pxa *pxa = sdhci_priv(host); + struct sdhci_pxa_platdata *pdata = pxa->pdata; + struct sdhci_ops *p_ops; + + p_ops = kmalloc(sizeof(struct sdhci_ops), GFP_KERNEL); + if (!p_ops) + return -ENOMEM; + + /* + * we cannot directly copy our operations into host->ops + * since it is read only. So we do this indirectly. + */ + memcpy((void *)p_ops, (void *)&sdhci_platform_data.ops, + sizeof(struct sdhci_ops)); + + if (pxa->pdata->max_speed) + p_ops->get_f_max_clock = get_f_max_clock; + + host->quirks |= sdhci_platform_data.quirks; + host->mmc->caps |= sdhci_platform_data.mmc_caps; + + /* If slot design supports 8 bit data, indicate this to MMC. */ + if (pdata->flags & PXA_FLAG_SD_8_BIT_CAPABLE_SLOT) { + host->mmc->caps |= MMC_CAP_8_BIT_DATA; + p_ops->platform_8bit_width = platform_supports_8_bit; + } + + if (pdata->flags & PXA_FLAG_CARD_PERMANENT) { + host->mmc->caps |= MMC_CAP_NONREMOVABLE; + host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION; + } + + pr_debug("%s:%s: host->quirks = %08X, mmc->caps = %08lX\n", + __func__, mmc_hostname(host->mmc), + host->quirks, host->mmc->caps); + + memcpy((void *)host->ops, (void *)p_ops, sizeof(struct sdhci_ops)); + kfree(p_ops); + return 0; +} diff --git a/drivers/mmc/host/sdhci-pxa910.c b/drivers/mmc/host/sdhci-pxa910.c new file mode 100644 index 0000000..18cdf7a --- /dev/null +++ b/drivers/mmc/host/sdhci-pxa910.c @@ -0,0 +1,272 @@ +/************************************************************************** + * + * Copyright (c) 2009, 2010 Marvell International Ltd. + * Philip Rakity <prakity@xxxxxxxxxxx> + * Mark F. Brown <markb@xxxxxxxxxxx> + * + * This file is part of GNU program. + * + * GNU program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 2 of the License, or (at your + * option) any later version. + * + * GNU program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. + * + * If not, see http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * + *************************************************************************/ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/mmc/host.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <plat/sdhci.h> +#include "sdhci-pxa.h" +#include "sdhci.h" + +#define DRIVER_NAME "sdhci-pxa910" + +#define SD_FIFO_PARAM 0xE0 +#define DIS_PAD_SD_CLK_GATE (1<<10) /* Turn on/off Dynamic Clock Gating */ + +#define SD_CLOCK_AND_BURST_SIZE_SETUP 0xE6 +#define SDCLK_DELAY_MASK 0xF +#define SDCLK_SEL_MASK 0x3 +#define SDCLK_DELAY_SHIFT 10 +#define SDCLK_SEL_SHIFT 8 + +#define SD_CE_ATA_2 0xEA +#define SDCE_MMC_WIDTH (1<<8) +#define SDCE_MMC_CARD (1<<12) + +#define DISABLE_CLOCK_GATING 0 + +static int platform_pxa910_probe(struct sdhci_host *host); + +/* + * MMC spec calls for the host to send 74 clocks to the card + * during initialization, right after voltage stabilization. + * the pxa168 controller has no easy way to generate those clocks. + * create the clocks manually right here. + */ +#if 0 +static void generate_init_clocks(struct sdhci_host *host, u8 power_mode) +{ + struct sdhci_pxa *pxa = sdhci_priv(host); + struct pfn_cfg *cfg = pxa->pdata->pfn_table; + mfp_cfg_t clk_pin; + int i; + + + if (cfg == NULL) { + DBG("Cannot generate init clocks!\n"); + return; + } + + if (pxa->power_mode == MMC_POWER_UP + && power_mode == MMC_POWER_ON) { + + DBG("%s: ENTER %s: power_mode = %d, ios_.power_mode = %d\n", + __func__, + mmc_hostname(host->mmc), + pxa->power_mode, + power_mode); + /* CMD/CLK pin to gpio mode. */ + mfp_config(pfn_lookup(cfg, PFN_GPIO, PIN_MMC_CMD), 1); + mfp_config(pfn_lookup(cfg, PFN_GPIO, PIN_MMC_CLK), 1); + + /* ensure at least 1/2 period stable to prevent runt pulse.*/ + udelay(3); + + clk_pin = *(pfn_lookup(cfg, PFN_GPIO, PIN_MMC_CLK)); + if (gpio_request(MFP_PIN(clk_pin), "MMC_CLK")) { + printk(KERN_ERR "Cannot obtain MMC_CLK GPIO %ld\n", + MFP_PIN(clk_pin)); + goto err; + } + + DBG("Generate 74 clocks on pins CLK %ld\n", MFP_PIN(clk_pin)); + + for (i = 0; i < INIT_CLOCKS; i++) { + gpio_direction_output(MFP_PIN(clk_pin), 0); /* low */ + udelay(3); + gpio_direction_output(MFP_PIN(clk_pin), 1); /* high */ + udelay(3); + } + + gpio_free(MFP_PIN(clk_pin)); + } + +err: + pxa->power_mode = power_mode; + + /* CMD/CLK pin back MMC mode. */ + mfp_config(pfn_lookup(cfg, PFN_FN, PIN_MMC_CMD), 1); + mfp_config(pfn_lookup(cfg, PFN_FN, PIN_MMC_CLK), 1); +} +#endif + +static void set_clock_and_burst_size(struct sdhci_host *host) +{ + u16 tmp; + struct sdhci_pxa *pxa = sdhci_priv(host); + + if (pxa->pdata->adjust_clocks) { + tmp = readw(host->ioaddr + SD_CLOCK_AND_BURST_SIZE_SETUP); + pr_debug("%s:%s: (B) SD_CLOCK_AND_BURST = %04X, " + "delay = %d, sel = %d\n", + __func__, mmc_hostname(host->mmc), tmp, + pxa->pdata->clk_delay, pxa->pdata->clk_select); + tmp &= ~(SDCLK_DELAY_MASK << SDCLK_DELAY_SHIFT); + tmp &= ~(SDCLK_SEL_MASK << SDCLK_SEL_SHIFT); + tmp |= (pxa->pdata->clk_delay & SDCLK_DELAY_MASK) << + SDCLK_DELAY_SHIFT; + tmp |= (pxa->pdata->clk_select & SDCLK_SEL_MASK) << + SDCLK_SEL_SHIFT; + writew(tmp, host->ioaddr + SD_CLOCK_AND_BURST_SIZE_SETUP); + pr_debug("%s:%s: (A) SD_CLOCK_AND_BURST = %04X\n", + __func__, mmc_hostname(host->mmc), tmp); + } +} + +static void programFIFO(struct sdhci_host *host, int enable) +{ + unsigned short tmp; + + tmp = readw(host->ioaddr + SD_FIFO_PARAM); + if (enable) + tmp &= ~DIS_PAD_SD_CLK_GATE; + else + tmp |= DIS_PAD_SD_CLK_GATE; + writew(tmp, host->ioaddr + SD_FIFO_PARAM); +} + +static void platform_reset_exit(struct sdhci_host *host, u8 mask) +{ + if (mask == SDHCI_RESET_ALL) { + programFIFO(host, DISABLE_CLOCK_GATING); + set_clock_and_burst_size(host); + } +} + +#ifdef CONFIG_MMC_CLKGATE +static void platform_hw_clk_gate(struct sdhci_host *host) +{ + int enable; + + enable = host->mmc->clk_gated; + programFIFO(host, enable); +} +#endif + +static unsigned int get_max_clock(struct sdhci_host *host) +{ + struct sdhci_pxa *pxa = sdhci_priv(host); + + pr_debug("%s:%s clk_get_rate = %lu\n", + __func__, mmc_hostname(host->mmc), + clk_get_rate(pxa->clk)); + + return clk_get_rate(pxa->clk); +} + +static unsigned int get_f_max_clock(struct sdhci_host *host) +{ + struct sdhci_pxa *pxa = sdhci_priv(host); + + pr_debug("%s:%s f_max = %d\n", + __func__, mmc_hostname(host->mmc), + pxa->pdata->max_speed); + + return pxa->pdata->max_speed; +} + +static int platform_supports_8_bit(struct sdhci_host *host, int width) +{ + u16 tmp; + + tmp = readw(host->ioaddr + SD_CE_ATA_2); + tmp |= SDCE_MMC_CARD | SDCE_MMC_WIDTH; + writew(tmp, host->ioaddr + SD_CE_ATA_2); + + tmp = readw(host->ioaddr + SD_CE_ATA_2); + if (width != 8) + tmp &= ~(SDCE_MMC_CARD | SDCE_MMC_WIDTH); + else + tmp |= SDCE_MMC_CARD | SDCE_MMC_WIDTH; + writew(tmp, host->ioaddr + SD_CE_ATA_2); + return 0; +} + +struct sdhci_pxa_data sdhci_platform_data = { + .ops = { + .platform_reset_exit = platform_reset_exit, + .get_max_clock = get_max_clock, + .platform_send_init_74_clocks = NULL, + .get_f_max_clock = NULL, + .platform_8bit_width = NULL, +#ifdef CONFIG_MMC_CLKGATE + .platform_hw_clk_gate = platform_hw_clk_gate, +#endif + }, +#ifdef CONFIG_MMC_CLKGATE + .mmc_caps = MMC_CAP_HW_CLOCK_GATING | MMC_CAP_BUS_WIDTH_TEST, +#else + .mmc_caps = MMC_CAP_BUS_WIDTH_TEST, +#endif + .platform_probe = platform_pxa910_probe, + .quirks = SDHCI_QUIRK_32BIT_DMA_ADDR | SDHCI_QUIRK_32BIT_DMA_SIZE + | SDHCI_QUIRK_NO_BUSY_IRQ | SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN + | SDHCI_QUIRK_NO_HISPD_BIT, +}; +EXPORT_SYMBOL_GPL(sdhci_platform_data); + +static int platform_pxa910_probe(struct sdhci_host *host) +{ + struct sdhci_pxa *pxa = sdhci_priv(host); + struct sdhci_pxa_platdata *pdata = pxa->pdata; + struct sdhci_ops *p_ops; + + /* + * we cannot directly copy our operations into host->ops + * since it is read only. So we do this indirectly. + */ + p_ops = kmalloc(sizeof(struct sdhci_ops), GFP_KERNEL); + if (!p_ops) + return -ENOMEM; + + memcpy((void *)p_ops, (void *)&sdhci_platform_data.ops, + sizeof(struct sdhci_ops)); + + host->quirks |= sdhci_platform_data.quirks; + host->mmc->caps |= sdhci_platform_data.mmc_caps; + + /* If slot design supports 8 bit data, indicate this to MMC. */ + if (pdata->flags & PXA_FLAG_SD_8_BIT_CAPABLE_SLOT) { + host->mmc->caps |= MMC_CAP_8_BIT_DATA; + p_ops->platform_8bit_width = platform_supports_8_bit; + } + + if (pdata->flags & PXA_FLAG_CARD_PERMANENT) { + host->mmc->caps |= MMC_CAP_NONREMOVABLE; + host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION; + } + + if (pxa->pdata->max_speed) + p_ops->get_f_max_clock = get_f_max_clock; + + memcpy((void *)host->ops, (void *)p_ops, sizeof(struct sdhci_ops)); + kfree(p_ops); + return 0; +} diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 611322c..627c8cf 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1236,9 +1236,9 @@ static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) host->ops->platform_send_init_74_clocks(host, ios->power_mode); #ifdef CONFIG_MMC_CLKGATE - if ((mmc->caps & MMC_CAP_HW_CLOCK_GATING) - && host->ops->platform_hw_clk_gate) - host->ops->platform_hw_clk_gate(host); + if ((mmc->caps & MMC_CAP_HW_CLOCK_GATING) && + host->ops->platform_hw_clk_gate) + host->ops->platform_hw_clk_gate(host); #endif /* @@ -1948,6 +1948,15 @@ int sdhci_add_host(struct sdhci_host *host) } else mmc->f_max = host->max_clk; +#ifdef CONFIG_MMC_CLKGATE + /* + * Configure MMC_CAP_HW_CLOCK_GATING in platform code. + * This is done to allow fine grain tuning of this parameter + * as some host controllers may indicate h/w clock gating + * works, but in fact does not. Avoids need for a quirk. + */ +#endif + mmc->caps |= MMC_CAP_SDIO_IRQ; /* -- 1.7.0.4 -- To unsubscribe from this list: send the line "unsubscribe linux-mmc" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html