/* * sdhci-pltfm.c Support for SDHCI platform devices * Copyright (c) 2009 Intel Corporation * * Copyright (c) 2007 Freescale Semiconductor, Inc. * Copyright (c) 2009 MontaVista Software, Inc. * * Authors: Xiaobo Xie * Anton Vorontsov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * 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. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* Supports: * SDHCI platform devices * * Inspired by sdhci-pci.c, by Pierre Ossman */ #include #include #ifdef CONFIG_PPC #include #endif #include "sdhci.h" #include "sdhci-pltfm.h" static struct sdhci_ops sdhci_pltfm_ops = { }; #ifdef CONFIG_MMC_SDHCI_BIG_ENDIAN_32BIT_BYTE_SWAPPER /* * These accessors are designed for big endian hosts doing I/O to * little endian controllers incorporating a 32-bit hardware byte swapper. */ u32 sdhci_be32bs_readl(struct sdhci_host *host, int reg) { return in_be32(host->ioaddr + reg); } u16 sdhci_be32bs_readw(struct sdhci_host *host, int reg) { return in_be16(host->ioaddr + (reg ^ 0x2)); } u8 sdhci_be32bs_readb(struct sdhci_host *host, int reg) { return in_8(host->ioaddr + (reg ^ 0x3)); } void sdhci_be32bs_writel(struct sdhci_host *host, u32 val, int reg) { out_be32(host->ioaddr + reg, val); } void sdhci_be32bs_writew(struct sdhci_host *host, u16 val, int reg) { struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); int base = reg & ~0x3; int shift = (reg & 0x2) * 8; switch (reg) { case SDHCI_TRANSFER_MODE: /* * Postpone this write, we must do it together with a * command write that is down below. */ pltfm_host->xfer_mode_shadow = val; return; case SDHCI_COMMAND: sdhci_be32bs_writel(host, val << 16 | pltfm_host->xfer_mode_shadow, SDHCI_TRANSFER_MODE); return; } clrsetbits_be32(host->ioaddr + base, 0xffff << shift, val << shift); } void sdhci_be32bs_writeb(struct sdhci_host *host, u8 val, int reg) { int base = reg & ~0x3; int shift = (reg & 0x3) * 8; clrsetbits_be32(host->ioaddr + base , 0xff << shift, val << shift); } #endif /* CONFIG_MMC_SDHCI_BIG_ENDIAN_32BIT_BYTE_SWAPPER */ #ifdef CONFIG_OF static bool sdhci_of_wp_inverted(struct device_node *np) { if (of_get_property(np, "sdhci,wp-inverted", NULL)) return true; /* Old device trees don't have the wp-inverted property. */ #ifdef CONFIG_PPC return machine_is(mpc837x_rdb) || machine_is(mpc837x_mds); #else return false; #endif /* CONFIG_PPC */ } void sdhci_get_of_property(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; struct sdhci_host *host = platform_get_drvdata(pdev); struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); const __be32 *clk; int size; if (of_device_is_available(np)) { if (of_get_property(np, "sdhci,auto-cmd12", NULL)) host->quirks |= SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12; if (of_get_property(np, "sdhci,1-bit-only", NULL)) host->quirks |= SDHCI_QUIRK_FORCE_1_BIT_DATA; if (sdhci_of_wp_inverted(np)) host->quirks |= SDHCI_QUIRK_INVERTED_WRITE_PROTECT; clk = of_get_property(np, "clock-frequency", &size); if (clk && size == sizeof(*clk) && *clk) pltfm_host->clock = be32_to_cpup(clk); } } #else void sdhci_get_of_property(struct platform_device *pdev) {} #endif /* CONFIG_OF */ struct sdhci_host *sdhci_pltfm_init(struct platform_device *pdev, struct sdhci_pltfm_data *pdata) { struct sdhci_host *host; struct sdhci_pltfm_host *pltfm_host; struct resource *iomem; int ret; iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!iomem) { ret = -ENOMEM; goto err; } if (resource_size(iomem) < 0x100) dev_err(&pdev->dev, "Invalid iomem size!\n"); /* Some PCI-based MFD need the parent here */ if (pdev->dev.parent != &platform_bus) host = sdhci_alloc_host(pdev->dev.parent, sizeof(*pltfm_host)); else host = sdhci_alloc_host(&pdev->dev, sizeof(*pltfm_host)); if (IS_ERR(host)) { ret = PTR_ERR(host); goto err; } pltfm_host = sdhci_priv(host); host->hw_name = dev_name(&pdev->dev); if (pdata && pdata->ops) host->ops = pdata->ops; else host->ops = &sdhci_pltfm_ops; if (pdata) host->quirks = pdata->quirks; host->irq = platform_get_irq(pdev, 0); if (!request_mem_region(iomem->start, resource_size(iomem), mmc_hostname(host->mmc))) { dev_err(&pdev->dev, "cannot request region\n"); ret = -EBUSY; goto err_request; } host->ioaddr = ioremap(iomem->start, resource_size(iomem)); if (!host->ioaddr) { dev_err(&pdev->dev, "failed to remap registers\n"); ret = -ENOMEM; goto err_remap; } platform_set_drvdata(pdev, host); return host; err_remap: release_mem_region(iomem->start, resource_size(iomem)); err_request: sdhci_free_host(host); err: dev_err(&pdev->dev, "%s failed %d\n", __func__, ret); return ERR_PTR(ret); } void sdhci_pltfm_free(struct platform_device *pdev) { struct sdhci_host *host = platform_get_drvdata(pdev); struct resource *iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0); iounmap(host->ioaddr); release_mem_region(iomem->start, resource_size(iomem)); sdhci_free_host(host); platform_set_drvdata(pdev, NULL); } int sdhci_pltfm_register(struct platform_device *pdev, struct sdhci_pltfm_data *pdata) { struct sdhci_host *host; int ret = 0; host = sdhci_pltfm_init(pdev, pdata); if (IS_ERR(host)) return PTR_ERR(host); sdhci_get_of_property(pdev); ret = sdhci_add_host(host); if (ret) sdhci_pltfm_free(pdev); return ret; } int sdhci_pltfm_unregister(struct platform_device *pdev) { struct sdhci_host *host = platform_get_drvdata(pdev); int dead = (readl(host->ioaddr + SDHCI_INT_STATUS) == 0xffffffff); sdhci_remove_host(host, dead); sdhci_pltfm_free(pdev); return 0; } #ifdef CONFIG_PM int sdhci_pltfm_suspend(struct platform_device *dev, pm_message_t state) { struct sdhci_host *host = platform_get_drvdata(dev); return sdhci_suspend_host(host, state); } int sdhci_pltfm_resume(struct platform_device *dev) { struct sdhci_host *host = platform_get_drvdata(dev); return sdhci_resume_host(host); } #endif /* CONFIG_PM */