Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

mmc: sdhci-acpi: add SDHCI ACPI driver

Add a driver for SDHCI controllers enumerated via ACPI and identified
by the ACPI Compatibility ID PNP0D40 (or other SDHCI-specific ACPI
hardware IDs in the future).

[rjw: Added the changelog.]
Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
Acked-by: Chris Ball <cjb@laptop.org>
Reviewed-by: Mika Westerberg <mika.westerberg@linux.intel.com>
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>

authored by

Adrian Hunter and committed by
Rafael J. Wysocki
c4e05037 142b007b

+317
+12
drivers/mmc/host/Kconfig
··· 81 81 82 82 If unsure, say Y. 83 83 84 + config MMC_SDHCI_ACPI 85 + tristate "SDHCI support for ACPI enumerated SDHCI controllers" 86 + depends on MMC_SDHCI && ACPI 87 + help 88 + This selects support for ACPI enumerated SDHCI controllers, 89 + identified by ACPI Compatibility ID PNP0D40 or specific 90 + ACPI Hardware IDs. 91 + 92 + If you have a controller with this interface, say Y or M here. 93 + 94 + If unsure, say N. 95 + 84 96 config MMC_SDHCI_PLTFM 85 97 tristate "SDHCI platform and OF driver helper" 86 98 depends on MMC_SDHCI
+1
drivers/mmc/host/Makefile
··· 9 9 obj-$(CONFIG_MMC_SDHCI) += sdhci.o 10 10 obj-$(CONFIG_MMC_SDHCI_PCI) += sdhci-pci.o 11 11 obj-$(subst m,y,$(CONFIG_MMC_SDHCI_PCI)) += sdhci-pci-data.o 12 + obj-$(CONFIG_MMC_SDHCI_ACPI) += sdhci-acpi.o 12 13 obj-$(CONFIG_MMC_SDHCI_PXAV3) += sdhci-pxav3.o 13 14 obj-$(CONFIG_MMC_SDHCI_PXAV2) += sdhci-pxav2.o 14 15 obj-$(CONFIG_MMC_SDHCI_S3C) += sdhci-s3c.o
+304
drivers/mmc/host/sdhci-acpi.c
··· 1 + /* 2 + * Secure Digital Host Controller Interface ACPI driver. 3 + * 4 + * Copyright (c) 2012, Intel Corporation. 5 + * 6 + * This program is free software; you can redistribute it and/or modify it 7 + * under the terms and conditions of the GNU General Public License, 8 + * version 2, as published by the Free Software Foundation. 9 + * 10 + * This program is distributed in the hope it will be useful, but WITHOUT 11 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 12 + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 13 + * more details. 14 + * 15 + * You should have received a copy of the GNU General Public License along with 16 + * this program; if not, write to the Free Software Foundation, Inc., 17 + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. 18 + * 19 + */ 20 + 21 + #include <linux/init.h> 22 + #include <linux/export.h> 23 + #include <linux/module.h> 24 + #include <linux/device.h> 25 + #include <linux/platform_device.h> 26 + #include <linux/ioport.h> 27 + #include <linux/io.h> 28 + #include <linux/dma-mapping.h> 29 + #include <linux/compiler.h> 30 + #include <linux/stddef.h> 31 + #include <linux/bitops.h> 32 + #include <linux/types.h> 33 + #include <linux/err.h> 34 + #include <linux/interrupt.h> 35 + #include <linux/acpi.h> 36 + #include <linux/pm.h> 37 + #include <linux/pm_runtime.h> 38 + 39 + #include <linux/mmc/host.h> 40 + #include <linux/mmc/pm.h> 41 + #include <linux/mmc/sdhci.h> 42 + 43 + #include "sdhci.h" 44 + 45 + enum { 46 + SDHCI_ACPI_SD_CD = BIT(0), 47 + SDHCI_ACPI_RUNTIME_PM = BIT(1), 48 + }; 49 + 50 + struct sdhci_acpi_chip { 51 + const struct sdhci_ops *ops; 52 + unsigned int quirks; 53 + unsigned int quirks2; 54 + unsigned long caps; 55 + unsigned int caps2; 56 + mmc_pm_flag_t pm_caps; 57 + }; 58 + 59 + struct sdhci_acpi_slot { 60 + const struct sdhci_acpi_chip *chip; 61 + unsigned int quirks; 62 + unsigned int quirks2; 63 + unsigned long caps; 64 + unsigned int caps2; 65 + mmc_pm_flag_t pm_caps; 66 + unsigned int flags; 67 + }; 68 + 69 + struct sdhci_acpi_host { 70 + struct sdhci_host *host; 71 + const struct sdhci_acpi_slot *slot; 72 + struct platform_device *pdev; 73 + bool use_runtime_pm; 74 + }; 75 + 76 + static inline bool sdhci_acpi_flag(struct sdhci_acpi_host *c, unsigned int flag) 77 + { 78 + return c->slot && (c->slot->flags & flag); 79 + } 80 + 81 + static int sdhci_acpi_enable_dma(struct sdhci_host *host) 82 + { 83 + return 0; 84 + } 85 + 86 + static const struct sdhci_ops sdhci_acpi_ops_dflt = { 87 + .enable_dma = sdhci_acpi_enable_dma, 88 + }; 89 + 90 + static const struct acpi_device_id sdhci_acpi_ids[] = { 91 + { "PNP0D40" }, 92 + { }, 93 + }; 94 + MODULE_DEVICE_TABLE(acpi, sdhci_acpi_ids); 95 + 96 + static const struct sdhci_acpi_slot *sdhci_acpi_get_slot(const char *hid) 97 + { 98 + const struct acpi_device_id *id; 99 + 100 + for (id = sdhci_acpi_ids; id->id[0]; id++) 101 + if (!strcmp(id->id, hid)) 102 + return (const struct sdhci_acpi_slot *)id->driver_data; 103 + return NULL; 104 + } 105 + 106 + static int __devinit sdhci_acpi_probe(struct platform_device *pdev) 107 + { 108 + struct device *dev = &pdev->dev; 109 + acpi_handle handle = ACPI_HANDLE(dev); 110 + struct acpi_device *device; 111 + struct sdhci_acpi_host *c; 112 + struct sdhci_host *host; 113 + struct resource *iomem; 114 + resource_size_t len; 115 + const char *hid; 116 + int err; 117 + 118 + if (acpi_bus_get_device(handle, &device)) 119 + return -ENODEV; 120 + 121 + if (acpi_bus_get_status(device) || !device->status.present) 122 + return -ENODEV; 123 + 124 + hid = acpi_device_hid(device); 125 + 126 + iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0); 127 + if (!iomem) 128 + return -ENOMEM; 129 + 130 + len = resource_size(iomem); 131 + if (len < 0x100) 132 + dev_err(dev, "Invalid iomem size!\n"); 133 + 134 + if (!devm_request_mem_region(dev, iomem->start, len, dev_name(dev))) 135 + return -ENOMEM; 136 + 137 + host = sdhci_alloc_host(dev, sizeof(struct sdhci_acpi_host)); 138 + if (IS_ERR(host)) 139 + return PTR_ERR(host); 140 + 141 + c = sdhci_priv(host); 142 + c->host = host; 143 + c->slot = sdhci_acpi_get_slot(hid); 144 + c->pdev = pdev; 145 + c->use_runtime_pm = sdhci_acpi_flag(c, SDHCI_ACPI_RUNTIME_PM); 146 + 147 + platform_set_drvdata(pdev, c); 148 + 149 + host->hw_name = "ACPI"; 150 + host->ops = &sdhci_acpi_ops_dflt; 151 + host->irq = platform_get_irq(pdev, 0); 152 + 153 + host->ioaddr = devm_ioremap_nocache(dev, iomem->start, 154 + resource_size(iomem)); 155 + if (host->ioaddr == NULL) { 156 + err = -ENOMEM; 157 + goto err_free; 158 + } 159 + 160 + if (!dev->dma_mask) { 161 + u64 dma_mask; 162 + 163 + if (sdhci_readl(host, SDHCI_CAPABILITIES) & SDHCI_CAN_64BIT) { 164 + /* 64-bit DMA is not supported at present */ 165 + dma_mask = DMA_BIT_MASK(32); 166 + } else { 167 + dma_mask = DMA_BIT_MASK(32); 168 + } 169 + 170 + dev->dma_mask = &dev->coherent_dma_mask; 171 + dev->coherent_dma_mask = dma_mask; 172 + } 173 + 174 + if (c->slot) { 175 + if (c->slot->chip) { 176 + host->ops = c->slot->chip->ops; 177 + host->quirks |= c->slot->chip->quirks; 178 + host->quirks2 |= c->slot->chip->quirks2; 179 + host->mmc->caps |= c->slot->chip->caps; 180 + host->mmc->caps2 |= c->slot->chip->caps2; 181 + host->mmc->pm_caps |= c->slot->chip->pm_caps; 182 + } 183 + host->quirks |= c->slot->quirks; 184 + host->quirks2 |= c->slot->quirks2; 185 + host->mmc->caps |= c->slot->caps; 186 + host->mmc->caps2 |= c->slot->caps2; 187 + host->mmc->pm_caps |= c->slot->pm_caps; 188 + } 189 + 190 + err = sdhci_add_host(host); 191 + if (err) 192 + goto err_free; 193 + 194 + if (c->use_runtime_pm) { 195 + pm_suspend_ignore_children(dev, 1); 196 + pm_runtime_set_autosuspend_delay(dev, 50); 197 + pm_runtime_use_autosuspend(dev); 198 + pm_runtime_enable(dev); 199 + } 200 + 201 + return 0; 202 + 203 + err_free: 204 + platform_set_drvdata(pdev, NULL); 205 + sdhci_free_host(c->host); 206 + return err; 207 + } 208 + 209 + static int __devexit sdhci_acpi_remove(struct platform_device *pdev) 210 + { 211 + struct sdhci_acpi_host *c = platform_get_drvdata(pdev); 212 + struct device *dev = &pdev->dev; 213 + int dead; 214 + 215 + if (c->use_runtime_pm) { 216 + pm_runtime_get_sync(dev); 217 + pm_runtime_disable(dev); 218 + pm_runtime_put_noidle(dev); 219 + } 220 + 221 + dead = (sdhci_readl(c->host, SDHCI_INT_STATUS) == ~0); 222 + sdhci_remove_host(c->host, dead); 223 + platform_set_drvdata(pdev, NULL); 224 + sdhci_free_host(c->host); 225 + 226 + return 0; 227 + } 228 + 229 + #ifdef CONFIG_PM_SLEEP 230 + 231 + static int sdhci_acpi_suspend(struct device *dev) 232 + { 233 + struct sdhci_acpi_host *c = dev_get_drvdata(dev); 234 + 235 + return sdhci_suspend_host(c->host); 236 + } 237 + 238 + static int sdhci_acpi_resume(struct device *dev) 239 + { 240 + struct sdhci_acpi_host *c = dev_get_drvdata(dev); 241 + 242 + return sdhci_resume_host(c->host); 243 + } 244 + 245 + #else 246 + 247 + #define sdhci_acpi_suspend NULL 248 + #define sdhci_acpi_resume NULL 249 + 250 + #endif 251 + 252 + #ifdef CONFIG_PM_RUNTIME 253 + 254 + static int sdhci_acpi_runtime_suspend(struct device *dev) 255 + { 256 + struct sdhci_acpi_host *c = dev_get_drvdata(dev); 257 + 258 + return sdhci_runtime_suspend_host(c->host); 259 + } 260 + 261 + static int sdhci_acpi_runtime_resume(struct device *dev) 262 + { 263 + struct sdhci_acpi_host *c = dev_get_drvdata(dev); 264 + 265 + return sdhci_runtime_resume_host(c->host); 266 + } 267 + 268 + static int sdhci_acpi_runtime_idle(struct device *dev) 269 + { 270 + return 0; 271 + } 272 + 273 + #else 274 + 275 + #define sdhci_acpi_runtime_suspend NULL 276 + #define sdhci_acpi_runtime_resume NULL 277 + #define sdhci_acpi_runtime_idle NULL 278 + 279 + #endif 280 + 281 + static const struct dev_pm_ops sdhci_acpi_pm_ops = { 282 + .suspend = sdhci_acpi_suspend, 283 + .resume = sdhci_acpi_resume, 284 + .runtime_suspend = sdhci_acpi_runtime_suspend, 285 + .runtime_resume = sdhci_acpi_runtime_resume, 286 + .runtime_idle = sdhci_acpi_runtime_idle, 287 + }; 288 + 289 + static struct platform_driver sdhci_acpi_driver = { 290 + .driver = { 291 + .name = "sdhci-acpi", 292 + .owner = THIS_MODULE, 293 + .acpi_match_table = sdhci_acpi_ids, 294 + .pm = &sdhci_acpi_pm_ops, 295 + }, 296 + .probe = sdhci_acpi_probe, 297 + .remove = __devexit_p(sdhci_acpi_remove), 298 + }; 299 + 300 + module_platform_driver(sdhci_acpi_driver); 301 + 302 + MODULE_DESCRIPTION("Secure Digital Host Controller Interface ACPI driver"); 303 + MODULE_AUTHOR("Adrian Hunter"); 304 + MODULE_LICENSE("GPL v2");