cff2f741b8
Here's the large driver core updates for 3.8-rc1. The biggest thing here is the various __dev* marking removals. This is going to be a pain for the merge with different subsystem trees, I know, but all of the patches included here have been ACKed by their various subsystem maintainers, as they wanted them to go through here. If this is too much of a pain, I can pull all of them out of this tree and just send you one with the other fixes/updates and then, after 3.8-rc1 is out, do the rest of the removals to ensure we catch them all, it's up to you. The merges should all be trivial, and Stephen has been doing them all in linux-next for a few weeks now quite easily. Other than the __dev* marking removals, there's nothing major here, some firmware loading updates and other minor things in the driver core. All of these have (much to Stephen's annoyance), been in linux-next for a while. Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> -----BEGIN PGP SIGNATURE----- Version: GnuPG v2.0.19 (GNU/Linux) iEYEABECAAYFAlDHkPkACgkQMUfUDdst+ykaWgCfW7AM30cv0nzoVO08ax6KjlG1 KVYAn3z/KYazvp4B6LMvrW9y0G34Wmad =yvVr -----END PGP SIGNATURE----- Merge tag 'driver-core-3.8-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/driver-core Pull driver core updates from Greg Kroah-Hartman: "Here's the large driver core updates for 3.8-rc1. The biggest thing here is the various __dev* marking removals. This is going to be a pain for the merge with different subsystem trees, I know, but all of the patches included here have been ACKed by their various subsystem maintainers, as they wanted them to go through here. If this is too much of a pain, I can pull all of them out of this tree and just send you one with the other fixes/updates and then, after 3.8-rc1 is out, do the rest of the removals to ensure we catch them all, it's up to you. The merges should all be trivial, and Stephen has been doing them all in linux-next for a few weeks now quite easily. Other than the __dev* marking removals, there's nothing major here, some firmware loading updates and other minor things in the driver core. All of these have (much to Stephen's annoyance), been in linux-next for a while. Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>" Fixed up trivial conflicts in drivers/gpio/gpio-{em,stmpe}.c due to gpio update. * tag 'driver-core-3.8-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/driver-core: (93 commits) modpost.c: Stop checking __dev* section mismatches init.h: Remove __dev* sections from the kernel acpi: remove use of __devinit PCI: Remove __dev* markings PCI: Always build setup-bus when PCI is enabled PCI: Move pci_uevent into pci-driver.c PCI: Remove CONFIG_HOTPLUG ifdefs unicore32/PCI: Remove CONFIG_HOTPLUG ifdefs sh/PCI: Remove CONFIG_HOTPLUG ifdefs powerpc/PCI: Remove CONFIG_HOTPLUG ifdefs mips/PCI: Remove CONFIG_HOTPLUG ifdefs microblaze/PCI: Remove CONFIG_HOTPLUG ifdefs dma: remove use of __devinit dma: remove use of __devexit_p firewire: remove use of __devinitdata firewire: remove use of __devinit leds: remove use of __devexit leds: remove use of __devinit leds: remove use of __devexit_p mmc: remove use of __devexit ...
369 lines
9.2 KiB
C
369 lines
9.2 KiB
C
/*
|
|
* Copyright (C) 2010 Marvell International Ltd.
|
|
* Zhangfei Gao <zhangfei.gao@marvell.com>
|
|
* Kevin Wang <dwang4@marvell.com>
|
|
* Mingwei Wang <mwwang@marvell.com>
|
|
* Philip Rakity <prakity@marvell.com>
|
|
* Mark Brown <markb@marvell.com>
|
|
*
|
|
* This software is licensed under the terms of the GNU General Public
|
|
* License version 2, as published by the Free Software Foundation, and
|
|
* may be copied, distributed, and modified under those terms.
|
|
*
|
|
* This 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.
|
|
*
|
|
*/
|
|
#include <linux/err.h>
|
|
#include <linux/init.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/io.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/mmc/card.h>
|
|
#include <linux/mmc/host.h>
|
|
#include <linux/mmc/slot-gpio.h>
|
|
#include <linux/platform_data/pxa_sdhci.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/of_gpio.h>
|
|
|
|
#include "sdhci.h"
|
|
#include "sdhci-pltfm.h"
|
|
|
|
#define SD_CLOCK_BURST_SIZE_SETUP 0x10A
|
|
#define SDCLK_SEL 0x100
|
|
#define SDCLK_DELAY_SHIFT 9
|
|
#define SDCLK_DELAY_MASK 0x1f
|
|
|
|
#define SD_CFG_FIFO_PARAM 0x100
|
|
#define SDCFG_GEN_PAD_CLK_ON (1<<6)
|
|
#define SDCFG_GEN_PAD_CLK_CNT_MASK 0xFF
|
|
#define SDCFG_GEN_PAD_CLK_CNT_SHIFT 24
|
|
|
|
#define SD_SPI_MODE 0x108
|
|
#define SD_CE_ATA_1 0x10C
|
|
|
|
#define SD_CE_ATA_2 0x10E
|
|
#define SDCE_MISC_INT (1<<2)
|
|
#define SDCE_MISC_INT_EN (1<<1)
|
|
|
|
static void pxav3_set_private_registers(struct sdhci_host *host, u8 mask)
|
|
{
|
|
struct platform_device *pdev = to_platform_device(mmc_dev(host->mmc));
|
|
struct sdhci_pxa_platdata *pdata = pdev->dev.platform_data;
|
|
|
|
if (mask == SDHCI_RESET_ALL) {
|
|
/*
|
|
* tune timing of read data/command when crc error happen
|
|
* no performance impact
|
|
*/
|
|
if (pdata && 0 != pdata->clk_delay_cycles) {
|
|
u16 tmp;
|
|
|
|
tmp = readw(host->ioaddr + SD_CLOCK_BURST_SIZE_SETUP);
|
|
tmp |= (pdata->clk_delay_cycles & SDCLK_DELAY_MASK)
|
|
<< SDCLK_DELAY_SHIFT;
|
|
tmp |= SDCLK_SEL;
|
|
writew(tmp, host->ioaddr + SD_CLOCK_BURST_SIZE_SETUP);
|
|
}
|
|
}
|
|
}
|
|
|
|
#define MAX_WAIT_COUNT 5
|
|
static void pxav3_gen_init_74_clocks(struct sdhci_host *host, u8 power_mode)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_pxa *pxa = pltfm_host->priv;
|
|
u16 tmp;
|
|
int count;
|
|
|
|
if (pxa->power_mode == MMC_POWER_UP
|
|
&& power_mode == MMC_POWER_ON) {
|
|
|
|
dev_dbg(mmc_dev(host->mmc),
|
|
"%s: slot->power_mode = %d,"
|
|
"ios->power_mode = %d\n",
|
|
__func__,
|
|
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;
|
|
|
|
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)
|
|
dev_warn(mmc_dev(host->mmc), "74 clock interrupt not cleared\n");
|
|
|
|
/* 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 int pxav3_set_uhs_signaling(struct sdhci_host *host, unsigned int uhs)
|
|
{
|
|
u16 ctrl_2;
|
|
|
|
/*
|
|
* Set V18_EN -- UHS modes do not work without this.
|
|
* does not change signaling voltage
|
|
*/
|
|
ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
|
|
|
|
/* Select Bus Speed Mode for host */
|
|
ctrl_2 &= ~SDHCI_CTRL_UHS_MASK;
|
|
switch (uhs) {
|
|
case MMC_TIMING_UHS_SDR12:
|
|
ctrl_2 |= SDHCI_CTRL_UHS_SDR12;
|
|
break;
|
|
case MMC_TIMING_UHS_SDR25:
|
|
ctrl_2 |= SDHCI_CTRL_UHS_SDR25;
|
|
break;
|
|
case MMC_TIMING_UHS_SDR50:
|
|
ctrl_2 |= SDHCI_CTRL_UHS_SDR50 | SDHCI_CTRL_VDD_180;
|
|
break;
|
|
case MMC_TIMING_UHS_SDR104:
|
|
ctrl_2 |= SDHCI_CTRL_UHS_SDR104 | SDHCI_CTRL_VDD_180;
|
|
break;
|
|
case MMC_TIMING_UHS_DDR50:
|
|
ctrl_2 |= SDHCI_CTRL_UHS_DDR50 | SDHCI_CTRL_VDD_180;
|
|
break;
|
|
}
|
|
|
|
sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2);
|
|
dev_dbg(mmc_dev(host->mmc),
|
|
"%s uhs = %d, ctrl_2 = %04X\n",
|
|
__func__, uhs, ctrl_2);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static u32 pxav3_get_max_clock(struct sdhci_host *host)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
|
|
return clk_get_rate(pltfm_host->clk);
|
|
}
|
|
|
|
static struct sdhci_ops pxav3_sdhci_ops = {
|
|
.platform_reset_exit = pxav3_set_private_registers,
|
|
.set_uhs_signaling = pxav3_set_uhs_signaling,
|
|
.platform_send_init_74_clocks = pxav3_gen_init_74_clocks,
|
|
.get_max_clock = pxav3_get_max_clock,
|
|
};
|
|
|
|
#ifdef CONFIG_OF
|
|
static const struct of_device_id sdhci_pxav3_of_match[] = {
|
|
{
|
|
.compatible = "mrvl,pxav3-mmc",
|
|
},
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, sdhci_pxav3_of_match);
|
|
|
|
static struct sdhci_pxa_platdata *pxav3_get_mmc_pdata(struct device *dev)
|
|
{
|
|
struct sdhci_pxa_platdata *pdata;
|
|
struct device_node *np = dev->of_node;
|
|
u32 bus_width;
|
|
u32 clk_delay_cycles;
|
|
enum of_gpio_flags gpio_flags;
|
|
|
|
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
|
|
if (!pdata)
|
|
return NULL;
|
|
|
|
if (of_find_property(np, "non-removable", NULL))
|
|
pdata->flags |= PXA_FLAG_CARD_PERMANENT;
|
|
|
|
of_property_read_u32(np, "bus-width", &bus_width);
|
|
if (bus_width == 8)
|
|
pdata->flags |= PXA_FLAG_SD_8_BIT_CAPABLE_SLOT;
|
|
|
|
of_property_read_u32(np, "mrvl,clk-delay-cycles", &clk_delay_cycles);
|
|
if (clk_delay_cycles > 0)
|
|
pdata->clk_delay_cycles = clk_delay_cycles;
|
|
|
|
pdata->ext_cd_gpio = of_get_named_gpio_flags(np, "cd-gpios", 0, &gpio_flags);
|
|
if (gpio_flags != OF_GPIO_ACTIVE_LOW)
|
|
pdata->host_caps2 |= MMC_CAP2_CD_ACTIVE_HIGH;
|
|
|
|
return pdata;
|
|
}
|
|
#else
|
|
static inline struct sdhci_pxa_platdata *pxav3_get_mmc_pdata(struct device *dev)
|
|
{
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
static int sdhci_pxav3_probe(struct platform_device *pdev)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host;
|
|
struct sdhci_pxa_platdata *pdata = pdev->dev.platform_data;
|
|
struct device *dev = &pdev->dev;
|
|
struct sdhci_host *host = NULL;
|
|
struct sdhci_pxa *pxa = NULL;
|
|
const struct of_device_id *match;
|
|
|
|
int ret;
|
|
struct clk *clk;
|
|
|
|
pxa = kzalloc(sizeof(struct sdhci_pxa), GFP_KERNEL);
|
|
if (!pxa)
|
|
return -ENOMEM;
|
|
|
|
host = sdhci_pltfm_init(pdev, NULL);
|
|
if (IS_ERR(host)) {
|
|
kfree(pxa);
|
|
return PTR_ERR(host);
|
|
}
|
|
pltfm_host = sdhci_priv(host);
|
|
pltfm_host->priv = pxa;
|
|
|
|
clk = clk_get(dev, NULL);
|
|
if (IS_ERR(clk)) {
|
|
dev_err(dev, "failed to get io clock\n");
|
|
ret = PTR_ERR(clk);
|
|
goto err_clk_get;
|
|
}
|
|
pltfm_host->clk = clk;
|
|
clk_prepare_enable(clk);
|
|
|
|
host->quirks = SDHCI_QUIRK_BROKEN_TIMEOUT_VAL
|
|
| SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC
|
|
| SDHCI_QUIRK_32BIT_ADMA_SIZE
|
|
| SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN;
|
|
|
|
/* enable 1/8V DDR capable */
|
|
host->mmc->caps |= MMC_CAP_1_8V_DDR;
|
|
|
|
match = of_match_device(of_match_ptr(sdhci_pxav3_of_match), &pdev->dev);
|
|
if (match)
|
|
pdata = pxav3_get_mmc_pdata(dev);
|
|
|
|
if (pdata) {
|
|
if (pdata->flags & PXA_FLAG_CARD_PERMANENT) {
|
|
/* on-chip device */
|
|
host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION;
|
|
host->mmc->caps |= MMC_CAP_NONREMOVABLE;
|
|
}
|
|
|
|
/* 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->quirks)
|
|
host->quirks |= pdata->quirks;
|
|
if (pdata->quirks2)
|
|
host->quirks2 |= pdata->quirks2;
|
|
if (pdata->host_caps)
|
|
host->mmc->caps |= pdata->host_caps;
|
|
if (pdata->host_caps2)
|
|
host->mmc->caps2 |= pdata->host_caps2;
|
|
if (pdata->pm_caps)
|
|
host->mmc->pm_caps |= pdata->pm_caps;
|
|
|
|
if (gpio_is_valid(pdata->ext_cd_gpio)) {
|
|
ret = mmc_gpio_request_cd(host->mmc, pdata->ext_cd_gpio);
|
|
if (ret) {
|
|
dev_err(mmc_dev(host->mmc),
|
|
"failed to allocate card detect gpio\n");
|
|
goto err_cd_req;
|
|
}
|
|
}
|
|
}
|
|
|
|
host->ops = &pxav3_sdhci_ops;
|
|
|
|
sdhci_get_of_property(pdev);
|
|
|
|
ret = sdhci_add_host(host);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "failed to add host\n");
|
|
goto err_add_host;
|
|
}
|
|
|
|
platform_set_drvdata(pdev, host);
|
|
|
|
return 0;
|
|
|
|
err_add_host:
|
|
clk_disable_unprepare(clk);
|
|
clk_put(clk);
|
|
mmc_gpio_free_cd(host->mmc);
|
|
err_cd_req:
|
|
err_clk_get:
|
|
sdhci_pltfm_free(pdev);
|
|
kfree(pxa);
|
|
return ret;
|
|
}
|
|
|
|
static int sdhci_pxav3_remove(struct platform_device *pdev)
|
|
{
|
|
struct sdhci_host *host = platform_get_drvdata(pdev);
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct sdhci_pxa *pxa = pltfm_host->priv;
|
|
struct sdhci_pxa_platdata *pdata = pdev->dev.platform_data;
|
|
|
|
sdhci_remove_host(host, 1);
|
|
|
|
clk_disable_unprepare(pltfm_host->clk);
|
|
clk_put(pltfm_host->clk);
|
|
|
|
if (gpio_is_valid(pdata->ext_cd_gpio))
|
|
mmc_gpio_free_cd(host->mmc);
|
|
|
|
sdhci_pltfm_free(pdev);
|
|
kfree(pxa);
|
|
|
|
platform_set_drvdata(pdev, NULL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver sdhci_pxav3_driver = {
|
|
.driver = {
|
|
.name = "sdhci-pxav3",
|
|
#ifdef CONFIG_OF
|
|
.of_match_table = sdhci_pxav3_of_match,
|
|
#endif
|
|
.owner = THIS_MODULE,
|
|
.pm = SDHCI_PLTFM_PMOPS,
|
|
},
|
|
.probe = sdhci_pxav3_probe,
|
|
.remove = sdhci_pxav3_remove,
|
|
};
|
|
|
|
module_platform_driver(sdhci_pxav3_driver);
|
|
|
|
MODULE_DESCRIPTION("SDHCI driver for pxav3");
|
|
MODULE_AUTHOR("Marvell International Ltd.");
|
|
MODULE_LICENSE("GPL v2");
|
|
|