diff options
Diffstat (limited to 'drivers/mmc/host/sdhci-esdhc-imx.c')
| -rw-r--r-- | drivers/mmc/host/sdhci-esdhc-imx.c | 298 | 
1 files changed, 235 insertions, 63 deletions
diff --git a/drivers/mmc/host/sdhci-esdhc-imx.c b/drivers/mmc/host/sdhci-esdhc-imx.c index 710b706f4fc..4dc0028086a 100644 --- a/drivers/mmc/host/sdhci-esdhc-imx.c +++ b/drivers/mmc/host/sdhci-esdhc-imx.c @@ -16,20 +16,23 @@  #include <linux/err.h>  #include <linux/clk.h>  #include <linux/gpio.h> +#include <linux/module.h>  #include <linux/slab.h>  #include <linux/mmc/host.h>  #include <linux/mmc/mmc.h>  #include <linux/mmc/sdio.h> -#include <mach/hardware.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h>  #include <mach/esdhc.h>  #include "sdhci-pltfm.h"  #include "sdhci-esdhc.h" +#define	SDHCI_CTRL_D3CD			0x08  /* VENDOR SPEC register */  #define SDHCI_VENDOR_SPEC		0xC0  #define  SDHCI_VENDOR_SPEC_SDIO_QUIRK	0x00000002 -#define ESDHC_FLAG_GPIO_FOR_CD		(1 << 0)  /*   * The CMDTYPE of the CMD register (offset 0xE) should be set to   * "11" when the STOP CMD12 is issued on imx53 to abort one @@ -43,10 +46,67 @@   */  #define ESDHC_FLAG_MULTIBLK_NO_INT	(1 << 1) +enum imx_esdhc_type { +	IMX25_ESDHC, +	IMX35_ESDHC, +	IMX51_ESDHC, +	IMX53_ESDHC, +}; +  struct pltfm_imx_data {  	int flags;  	u32 scratchpad; +	enum imx_esdhc_type devtype; +	struct esdhc_platform_data boarddata; +}; + +static struct platform_device_id imx_esdhc_devtype[] = { +	{ +		.name = "sdhci-esdhc-imx25", +		.driver_data = IMX25_ESDHC, +	}, { +		.name = "sdhci-esdhc-imx35", +		.driver_data = IMX35_ESDHC, +	}, { +		.name = "sdhci-esdhc-imx51", +		.driver_data = IMX51_ESDHC, +	}, { +		.name = "sdhci-esdhc-imx53", +		.driver_data = IMX53_ESDHC, +	}, { +		/* sentinel */ +	}  }; +MODULE_DEVICE_TABLE(platform, imx_esdhc_devtype); + +static const struct of_device_id imx_esdhc_dt_ids[] = { +	{ .compatible = "fsl,imx25-esdhc", .data = &imx_esdhc_devtype[IMX25_ESDHC], }, +	{ .compatible = "fsl,imx35-esdhc", .data = &imx_esdhc_devtype[IMX35_ESDHC], }, +	{ .compatible = "fsl,imx51-esdhc", .data = &imx_esdhc_devtype[IMX51_ESDHC], }, +	{ .compatible = "fsl,imx53-esdhc", .data = &imx_esdhc_devtype[IMX53_ESDHC], }, +	{ /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_esdhc_dt_ids); + +static inline int is_imx25_esdhc(struct pltfm_imx_data *data) +{ +	return data->devtype == IMX25_ESDHC; +} + +static inline int is_imx35_esdhc(struct pltfm_imx_data *data) +{ +	return data->devtype == IMX35_ESDHC; +} + +static inline int is_imx51_esdhc(struct pltfm_imx_data *data) +{ +	return data->devtype == IMX51_ESDHC; +} + +static inline int is_imx53_esdhc(struct pltfm_imx_data *data) +{ +	return data->devtype == IMX53_ESDHC; +}  static inline void esdhc_clrset_le(struct sdhci_host *host, u32 mask, u32 val, int reg)  { @@ -60,17 +120,14 @@ static u32 esdhc_readl_le(struct sdhci_host *host, int reg)  {  	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);  	struct pltfm_imx_data *imx_data = pltfm_host->priv; +	struct esdhc_platform_data *boarddata = &imx_data->boarddata; -	/* fake CARD_PRESENT flag on mx25/35 */ +	/* fake CARD_PRESENT flag */  	u32 val = readl(host->ioaddr + reg);  	if (unlikely((reg == SDHCI_PRESENT_STATE) -			&& (imx_data->flags & ESDHC_FLAG_GPIO_FOR_CD))) { -		struct esdhc_platform_data *boarddata = -				host->mmc->parent->platform_data; - -		if (boarddata && gpio_is_valid(boarddata->cd_gpio) -				&& gpio_get_value(boarddata->cd_gpio)) +			&& gpio_is_valid(boarddata->cd_gpio))) { +		if (gpio_get_value(boarddata->cd_gpio))  			/* no card, if a valid gpio says so... */  			val &= ~SDHCI_CARD_PRESENT;  		else @@ -85,14 +142,33 @@ static void esdhc_writel_le(struct sdhci_host *host, u32 val, int reg)  {  	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);  	struct pltfm_imx_data *imx_data = pltfm_host->priv; +	struct esdhc_platform_data *boarddata = &imx_data->boarddata; +	u32 data; -	if (unlikely((reg == SDHCI_INT_ENABLE || reg == SDHCI_SIGNAL_ENABLE) -			&& (imx_data->flags & ESDHC_FLAG_GPIO_FOR_CD))) -		/* -		 * these interrupts won't work with a custom card_detect gpio -		 * (only applied to mx25/35) -		 */ -		val &= ~(SDHCI_INT_CARD_REMOVE | SDHCI_INT_CARD_INSERT); +	if (unlikely(reg == SDHCI_INT_ENABLE || reg == SDHCI_SIGNAL_ENABLE)) { +		if (boarddata->cd_type == ESDHC_CD_GPIO) +			/* +			 * These interrupts won't work with a custom +			 * card_detect gpio (only applied to mx25/35) +			 */ +			val &= ~(SDHCI_INT_CARD_REMOVE | SDHCI_INT_CARD_INSERT); + +		if (val & SDHCI_INT_CARD_INT) { +			/* +			 * Clear and then set D3CD bit to avoid missing the +			 * card interrupt.  This is a eSDHC controller problem +			 * so we need to apply the following workaround: clear +			 * and set D3CD bit will make eSDHC re-sample the card +			 * interrupt. In case a card interrupt was lost, +			 * re-sample it by the following steps. +			 */ +			data = readl(host->ioaddr + SDHCI_HOST_CONTROL); +			data &= ~SDHCI_CTRL_D3CD; +			writel(data, host->ioaddr + SDHCI_HOST_CONTROL); +			data |= SDHCI_CTRL_D3CD; +			writel(data, host->ioaddr + SDHCI_HOST_CONTROL); +		} +	}  	if (unlikely((imx_data->flags & ESDHC_FLAG_MULTIBLK_NO_INT)  				&& (reg == SDHCI_INT_STATUS) @@ -162,8 +238,10 @@ static void esdhc_writeb_le(struct sdhci_host *host, u8 val, int reg)  		 */  		return;  	case SDHCI_HOST_CONTROL: -		/* FSL messed up here, so we can just keep those two */ -		new_val = val & (SDHCI_CTRL_LED | SDHCI_CTRL_4BITBUS); +		/* FSL messed up here, so we can just keep those three */ +		new_val = val & (SDHCI_CTRL_LED | \ +				SDHCI_CTRL_4BITBUS | \ +				SDHCI_CTRL_D3CD);  		/* ensure the endianess */  		new_val |= ESDHC_HOST_CONTROL_LE;  		/* DMA mode bits are shifted */ @@ -173,6 +251,17 @@ static void esdhc_writeb_le(struct sdhci_host *host, u8 val, int reg)  		return;  	}  	esdhc_clrset_le(host, 0xff, val, reg); + +	/* +	 * The esdhc has a design violation to SDHC spec which tells +	 * that software reset should not affect card detection circuit. +	 * But esdhc clears its SYSCTL register bits [0..2] during the +	 * software reset.  This will stop those clocks that card detection +	 * circuit relies on.  To work around it, we turn the clocks on back +	 * to keep card detection circuit functional. +	 */ +	if ((reg == SDHCI_SOFTWARE_RESET) && (val & 1)) +		esdhc_clrset_le(host, 0x7, 0x7, ESDHC_SYSTEM_CONTROL);  }  static unsigned int esdhc_pltfm_get_max_clock(struct sdhci_host *host) @@ -189,6 +278,26 @@ static unsigned int esdhc_pltfm_get_min_clock(struct sdhci_host *host)  	return clk_get_rate(pltfm_host->clk) / 256 / 16;  } +static unsigned int esdhc_pltfm_get_ro(struct sdhci_host *host) +{ +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); +	struct pltfm_imx_data *imx_data = pltfm_host->priv; +	struct esdhc_platform_data *boarddata = &imx_data->boarddata; + +	switch (boarddata->wp_type) { +	case ESDHC_WP_GPIO: +		if (gpio_is_valid(boarddata->wp_gpio)) +			return gpio_get_value(boarddata->wp_gpio); +	case ESDHC_WP_CONTROLLER: +		return !(readl(host->ioaddr + SDHCI_PRESENT_STATE) & +			       SDHCI_WRITE_PROTECT); +	case ESDHC_WP_NONE: +		break; +	} + +	return -ENOSYS; +} +  static struct sdhci_ops sdhci_esdhc_ops = {  	.read_l = esdhc_readl_le,  	.read_w = esdhc_readw_le, @@ -198,6 +307,7 @@ static struct sdhci_ops sdhci_esdhc_ops = {  	.set_clock = esdhc_set_clock,  	.get_max_clock = esdhc_pltfm_get_max_clock,  	.get_min_clock = esdhc_pltfm_get_min_clock, +	.get_ro = esdhc_pltfm_get_ro,  };  static struct sdhci_pltfm_data sdhci_esdhc_imx_pdata = { @@ -207,17 +317,6 @@ static struct sdhci_pltfm_data sdhci_esdhc_imx_pdata = {  	.ops = &sdhci_esdhc_ops,  }; -static unsigned int esdhc_pltfm_get_ro(struct sdhci_host *host) -{ -	struct esdhc_platform_data *boarddata = -			host->mmc->parent->platform_data; - -	if (boarddata && gpio_is_valid(boarddata->wp_gpio)) -		return gpio_get_value(boarddata->wp_gpio); -	else -		return -ENOSYS; -} -  static irqreturn_t cd_irq(int irq, void *data)  {  	struct sdhci_host *sdhost = (struct sdhci_host *)data; @@ -226,8 +325,48 @@ static irqreturn_t cd_irq(int irq, void *data)  	return IRQ_HANDLED;  }; +#ifdef CONFIG_OF +static int __devinit +sdhci_esdhc_imx_probe_dt(struct platform_device *pdev, +			 struct esdhc_platform_data *boarddata) +{ +	struct device_node *np = pdev->dev.of_node; + +	if (!np) +		return -ENODEV; + +	if (of_get_property(np, "fsl,card-wired", NULL)) +		boarddata->cd_type = ESDHC_CD_PERMANENT; + +	if (of_get_property(np, "fsl,cd-controller", NULL)) +		boarddata->cd_type = ESDHC_CD_CONTROLLER; + +	if (of_get_property(np, "fsl,wp-controller", NULL)) +		boarddata->wp_type = ESDHC_WP_CONTROLLER; + +	boarddata->cd_gpio = of_get_named_gpio(np, "cd-gpios", 0); +	if (gpio_is_valid(boarddata->cd_gpio)) +		boarddata->cd_type = ESDHC_CD_GPIO; + +	boarddata->wp_gpio = of_get_named_gpio(np, "wp-gpios", 0); +	if (gpio_is_valid(boarddata->wp_gpio)) +		boarddata->wp_type = ESDHC_WP_GPIO; + +	return 0; +} +#else +static inline int +sdhci_esdhc_imx_probe_dt(struct platform_device *pdev, +			 struct esdhc_platform_data *boarddata) +{ +	return -ENODEV; +} +#endif +  static int __devinit sdhci_esdhc_imx_probe(struct platform_device *pdev)  { +	const struct of_device_id *of_id = +			of_match_device(imx_esdhc_dt_ids, &pdev->dev);  	struct sdhci_pltfm_host *pltfm_host;  	struct sdhci_host *host;  	struct esdhc_platform_data *boarddata; @@ -242,8 +381,14 @@ static int __devinit sdhci_esdhc_imx_probe(struct platform_device *pdev)  	pltfm_host = sdhci_priv(host);  	imx_data = kzalloc(sizeof(struct pltfm_imx_data), GFP_KERNEL); -	if (!imx_data) -		return -ENOMEM; +	if (!imx_data) { +		err = -ENOMEM; +		goto err_imx_data; +	} + +	if (of_id) +		pdev->id_entry = of_id->data; +	imx_data->devtype = pdev->id_entry->driver_data;  	pltfm_host->priv = imx_data;  	clk = clk_get(mmc_dev(host->mmc), NULL); @@ -255,50 +400,72 @@ static int __devinit sdhci_esdhc_imx_probe(struct platform_device *pdev)  	clk_enable(clk);  	pltfm_host->clk = clk; -	if (!cpu_is_mx25()) +	if (!is_imx25_esdhc(imx_data))  		host->quirks |= SDHCI_QUIRK_BROKEN_TIMEOUT_VAL; -	if (cpu_is_mx25() || cpu_is_mx35()) { +	if (is_imx25_esdhc(imx_data) || is_imx35_esdhc(imx_data))  		/* Fix errata ENGcm07207 present on i.MX25 and i.MX35 */  		host->quirks |= SDHCI_QUIRK_NO_MULTIBLOCK; -		/* write_protect can't be routed to controller, use gpio */ -		sdhci_esdhc_ops.get_ro = esdhc_pltfm_get_ro; -	} -	if (!(cpu_is_mx25() || cpu_is_mx35() || cpu_is_mx51())) +	if (is_imx53_esdhc(imx_data))  		imx_data->flags |= ESDHC_FLAG_MULTIBLK_NO_INT; -	boarddata = host->mmc->parent->platform_data; -	if (boarddata) { +	boarddata = &imx_data->boarddata; +	if (sdhci_esdhc_imx_probe_dt(pdev, boarddata) < 0) { +		if (!host->mmc->parent->platform_data) { +			dev_err(mmc_dev(host->mmc), "no board data!\n"); +			err = -EINVAL; +			goto no_board_data; +		} +		imx_data->boarddata = *((struct esdhc_platform_data *) +					host->mmc->parent->platform_data); +	} + +	/* write_protect */ +	if (boarddata->wp_type == ESDHC_WP_GPIO) {  		err = gpio_request_one(boarddata->wp_gpio, GPIOF_IN, "ESDHC_WP");  		if (err) {  			dev_warn(mmc_dev(host->mmc), -				"no write-protect pin available!\n"); -			boarddata->wp_gpio = err; +				 "no write-protect pin available!\n"); +			boarddata->wp_gpio = -EINVAL;  		} +	} else { +		boarddata->wp_gpio = -EINVAL; +	} +	/* card_detect */ +	if (boarddata->cd_type != ESDHC_CD_GPIO) +		boarddata->cd_gpio = -EINVAL; + +	switch (boarddata->cd_type) { +	case ESDHC_CD_GPIO:  		err = gpio_request_one(boarddata->cd_gpio, GPIOF_IN, "ESDHC_CD");  		if (err) { -			dev_warn(mmc_dev(host->mmc), +			dev_err(mmc_dev(host->mmc),  				"no card-detect pin available!\n");  			goto no_card_detect_pin;  		} -		/* i.MX5x has issues to be researched */ -		if (!cpu_is_mx25() && !cpu_is_mx35()) -			goto not_supported; -  		err = request_irq(gpio_to_irq(boarddata->cd_gpio), cd_irq,  				 IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,  				 mmc_hostname(host->mmc), host);  		if (err) { -			dev_warn(mmc_dev(host->mmc), "request irq error\n"); +			dev_err(mmc_dev(host->mmc), "request irq error\n");  			goto no_card_detect_irq;  		} +		/* fall through */ -		imx_data->flags |= ESDHC_FLAG_GPIO_FOR_CD; -		/* Now we have a working card_detect again */ +	case ESDHC_CD_CONTROLLER: +		/* we have a working card_detect back */  		host->quirks &= ~SDHCI_QUIRK_BROKEN_CARD_DETECTION; +		break; + +	case ESDHC_CD_PERMANENT: +		host->mmc->caps = MMC_CAP_NONREMOVABLE; +		break; + +	case ESDHC_CD_NONE: +		break;  	}  	err = sdhci_add_host(host); @@ -307,16 +474,21 @@ static int __devinit sdhci_esdhc_imx_probe(struct platform_device *pdev)  	return 0; - no_card_detect_irq: -	gpio_free(boarddata->cd_gpio); - no_card_detect_pin: -	boarddata->cd_gpio = err; - not_supported: -	kfree(imx_data); - err_add_host: +err_add_host: +	if (gpio_is_valid(boarddata->cd_gpio)) +		free_irq(gpio_to_irq(boarddata->cd_gpio), host); +no_card_detect_irq: +	if (gpio_is_valid(boarddata->cd_gpio)) +		gpio_free(boarddata->cd_gpio); +	if (gpio_is_valid(boarddata->wp_gpio)) +		gpio_free(boarddata->wp_gpio); +no_card_detect_pin: +no_board_data:  	clk_disable(pltfm_host->clk);  	clk_put(pltfm_host->clk); - err_clk_get: +err_clk_get: +	kfree(imx_data); +err_imx_data:  	sdhci_pltfm_free(pdev);  	return err;  } @@ -325,20 +497,18 @@ static int __devexit sdhci_esdhc_imx_remove(struct platform_device *pdev)  {  	struct sdhci_host *host = platform_get_drvdata(pdev);  	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); -	struct esdhc_platform_data *boarddata = host->mmc->parent->platform_data;  	struct pltfm_imx_data *imx_data = pltfm_host->priv; +	struct esdhc_platform_data *boarddata = &imx_data->boarddata;  	int dead = (readl(host->ioaddr + SDHCI_INT_STATUS) == 0xffffffff);  	sdhci_remove_host(host, dead); -	if (boarddata && gpio_is_valid(boarddata->wp_gpio)) +	if (gpio_is_valid(boarddata->wp_gpio))  		gpio_free(boarddata->wp_gpio); -	if (boarddata && gpio_is_valid(boarddata->cd_gpio)) { +	if (gpio_is_valid(boarddata->cd_gpio)) { +		free_irq(gpio_to_irq(boarddata->cd_gpio), host);  		gpio_free(boarddata->cd_gpio); - -		if (!(host->quirks & SDHCI_QUIRK_BROKEN_CARD_DETECTION)) -			free_irq(gpio_to_irq(boarddata->cd_gpio), host);  	}  	clk_disable(pltfm_host->clk); @@ -354,7 +524,9 @@ static struct platform_driver sdhci_esdhc_imx_driver = {  	.driver		= {  		.name	= "sdhci-esdhc-imx",  		.owner	= THIS_MODULE, +		.of_match_table = imx_esdhc_dt_ids,  	}, +	.id_table	= imx_esdhc_devtype,  	.probe		= sdhci_esdhc_imx_probe,  	.remove		= __devexit_p(sdhci_esdhc_imx_remove),  #ifdef CONFIG_PM  |