| Philip Langdale | 5ae7029 | 2007-09-15 12:54:08 -0700 | [diff] [blame] | 1 | /* | 
|  | 2 | *  ricoh_mmc.c - Dummy driver to disable the Rioch MMC controller. | 
|  | 3 | * | 
|  | 4 | *  Copyright (C) 2007 Philip Langdale, All Rights Reserved. | 
|  | 5 | * | 
|  | 6 | * This program is free software; you can redistribute it and/or modify | 
|  | 7 | * it under the terms of the GNU General Public License as published by | 
|  | 8 | * the Free Software Foundation; either version 2 of the License, or (at | 
|  | 9 | * your option) any later version. | 
|  | 10 | */ | 
|  | 11 |  | 
|  | 12 | /* | 
|  | 13 | * This is a conceptually ridiculous driver, but it is required by the way | 
| philipl@overt.org | 0527a60 | 2008-11-30 20:27:50 -0500 | [diff] [blame] | 14 | * the Ricoh multi-function chips (R5CXXX) work. These chips implement | 
|  | 15 | * the four main memory card controllers (SD, MMC, MS, xD) and one or both | 
|  | 16 | * of cardbus or firewire. It happens that they implement SD and MMC | 
|  | 17 | * support as separate controllers (and PCI functions). The linux SDHCI | 
| Philip Langdale | 5ae7029 | 2007-09-15 12:54:08 -0700 | [diff] [blame] | 18 | * driver supports MMC cards but the chip detects MMC cards in hardware | 
|  | 19 | * and directs them to the MMC controller - so the SDHCI driver never sees | 
|  | 20 | * them. To get around this, we must disable the useless MMC controller. | 
|  | 21 | * At that point, the SDHCI controller will start seeing them. As a bonus, | 
|  | 22 | * a detection event occurs immediately, even if the MMC card is already | 
|  | 23 | * in the reader. | 
|  | 24 | * | 
| philipl@overt.org | 0527a60 | 2008-11-30 20:27:50 -0500 | [diff] [blame] | 25 | * It seems to be the case that the relevant PCI registers to deactivate the | 
|  | 26 | * MMC controller live on PCI function 0, which might be the cardbus controller | 
|  | 27 | * or the firewire controller, depending on the particular chip in question. As | 
|  | 28 | * such, it makes what this driver has to do unavoidably ugly. Such is life. | 
| Philip Langdale | 5ae7029 | 2007-09-15 12:54:08 -0700 | [diff] [blame] | 29 | */ | 
|  | 30 |  | 
|  | 31 | #include <linux/pci.h> | 
|  | 32 |  | 
|  | 33 | #define DRIVER_NAME "ricoh-mmc" | 
|  | 34 |  | 
|  | 35 | static const struct pci_device_id pci_ids[] __devinitdata = { | 
|  | 36 | { | 
|  | 37 | .vendor		= PCI_VENDOR_ID_RICOH, | 
|  | 38 | .device		= PCI_DEVICE_ID_RICOH_R5C843, | 
|  | 39 | .subvendor	= PCI_ANY_ID, | 
|  | 40 | .subdevice	= PCI_ANY_ID, | 
|  | 41 | }, | 
|  | 42 | { /* end: all zeroes */ }, | 
|  | 43 | }; | 
|  | 44 |  | 
|  | 45 | MODULE_DEVICE_TABLE(pci, pci_ids); | 
|  | 46 |  | 
| Philip Langdale | 1f090bf | 2007-12-29 00:11:42 -0800 | [diff] [blame] | 47 | static int ricoh_mmc_disable(struct pci_dev *fw_dev) | 
|  | 48 | { | 
|  | 49 | u8 write_enable; | 
| Frank Seidel | 882c491 | 2008-02-04 19:25:42 +0100 | [diff] [blame] | 50 | u8 write_target; | 
| Philip Langdale | 1f090bf | 2007-12-29 00:11:42 -0800 | [diff] [blame] | 51 | u8 disable; | 
|  | 52 |  | 
| Frank Seidel | 882c491 | 2008-02-04 19:25:42 +0100 | [diff] [blame] | 53 | if (fw_dev->device == PCI_DEVICE_ID_RICOH_RL5C476) { | 
|  | 54 | /* via RL5C476 */ | 
| Philip Langdale | 1f090bf | 2007-12-29 00:11:42 -0800 | [diff] [blame] | 55 |  | 
| Frank Seidel | 882c491 | 2008-02-04 19:25:42 +0100 | [diff] [blame] | 56 | pci_read_config_byte(fw_dev, 0xB7, &disable); | 
|  | 57 | if (disable & 0x02) { | 
|  | 58 | printk(KERN_INFO DRIVER_NAME | 
|  | 59 | ": Controller already disabled. " \ | 
|  | 60 | "Nothing to do.\n"); | 
|  | 61 | return -ENODEV; | 
|  | 62 | } | 
|  | 63 |  | 
|  | 64 | pci_read_config_byte(fw_dev, 0x8E, &write_enable); | 
|  | 65 | pci_write_config_byte(fw_dev, 0x8E, 0xAA); | 
|  | 66 | pci_read_config_byte(fw_dev, 0x8D, &write_target); | 
|  | 67 | pci_write_config_byte(fw_dev, 0x8D, 0xB7); | 
|  | 68 | pci_write_config_byte(fw_dev, 0xB7, disable | 0x02); | 
|  | 69 | pci_write_config_byte(fw_dev, 0x8E, write_enable); | 
|  | 70 | pci_write_config_byte(fw_dev, 0x8D, write_target); | 
|  | 71 | } else { | 
|  | 72 | /* via R5C832 */ | 
|  | 73 |  | 
|  | 74 | pci_read_config_byte(fw_dev, 0xCB, &disable); | 
|  | 75 | if (disable & 0x02) { | 
|  | 76 | printk(KERN_INFO DRIVER_NAME | 
|  | 77 | ": Controller already disabled. " \ | 
|  | 78 | "Nothing to do.\n"); | 
|  | 79 | return -ENODEV; | 
|  | 80 | } | 
|  | 81 |  | 
|  | 82 | pci_read_config_byte(fw_dev, 0xCA, &write_enable); | 
|  | 83 | pci_write_config_byte(fw_dev, 0xCA, 0x57); | 
|  | 84 | pci_write_config_byte(fw_dev, 0xCB, disable | 0x02); | 
|  | 85 | pci_write_config_byte(fw_dev, 0xCA, write_enable); | 
|  | 86 | } | 
| Philip Langdale | 1f090bf | 2007-12-29 00:11:42 -0800 | [diff] [blame] | 87 |  | 
|  | 88 | printk(KERN_INFO DRIVER_NAME | 
|  | 89 | ": Controller is now disabled.\n"); | 
|  | 90 |  | 
|  | 91 | return 0; | 
|  | 92 | } | 
|  | 93 |  | 
|  | 94 | static int ricoh_mmc_enable(struct pci_dev *fw_dev) | 
|  | 95 | { | 
|  | 96 | u8 write_enable; | 
| Frank Seidel | 882c491 | 2008-02-04 19:25:42 +0100 | [diff] [blame] | 97 | u8 write_target; | 
| Philip Langdale | 1f090bf | 2007-12-29 00:11:42 -0800 | [diff] [blame] | 98 | u8 disable; | 
|  | 99 |  | 
| Frank Seidel | 882c491 | 2008-02-04 19:25:42 +0100 | [diff] [blame] | 100 | if (fw_dev->device == PCI_DEVICE_ID_RICOH_RL5C476) { | 
|  | 101 | /* via RL5C476 */ | 
|  | 102 |  | 
|  | 103 | pci_read_config_byte(fw_dev, 0x8E, &write_enable); | 
|  | 104 | pci_write_config_byte(fw_dev, 0x8E, 0xAA); | 
|  | 105 | pci_read_config_byte(fw_dev, 0x8D, &write_target); | 
|  | 106 | pci_write_config_byte(fw_dev, 0x8D, 0xB7); | 
|  | 107 | pci_read_config_byte(fw_dev, 0xB7, &disable); | 
|  | 108 | pci_write_config_byte(fw_dev, 0xB7, disable & ~0x02); | 
|  | 109 | pci_write_config_byte(fw_dev, 0x8E, write_enable); | 
|  | 110 | pci_write_config_byte(fw_dev, 0x8D, write_target); | 
|  | 111 | } else { | 
|  | 112 | /* via R5C832 */ | 
|  | 113 |  | 
|  | 114 | pci_read_config_byte(fw_dev, 0xCA, &write_enable); | 
|  | 115 | pci_read_config_byte(fw_dev, 0xCB, &disable); | 
|  | 116 | pci_write_config_byte(fw_dev, 0xCA, 0x57); | 
|  | 117 | pci_write_config_byte(fw_dev, 0xCB, disable & ~0x02); | 
|  | 118 | pci_write_config_byte(fw_dev, 0xCA, write_enable); | 
|  | 119 | } | 
| Philip Langdale | 1f090bf | 2007-12-29 00:11:42 -0800 | [diff] [blame] | 120 |  | 
|  | 121 | printk(KERN_INFO DRIVER_NAME | 
|  | 122 | ": Controller is now re-enabled.\n"); | 
|  | 123 |  | 
|  | 124 | return 0; | 
|  | 125 | } | 
|  | 126 |  | 
| Philip Langdale | 5ae7029 | 2007-09-15 12:54:08 -0700 | [diff] [blame] | 127 | static int __devinit ricoh_mmc_probe(struct pci_dev *pdev, | 
|  | 128 | const struct pci_device_id *ent) | 
|  | 129 | { | 
|  | 130 | u8 rev; | 
| Frank Seidel | 882c491 | 2008-02-04 19:25:42 +0100 | [diff] [blame] | 131 | u8 ctrlfound = 0; | 
| Philip Langdale | 5ae7029 | 2007-09-15 12:54:08 -0700 | [diff] [blame] | 132 |  | 
|  | 133 | struct pci_dev *fw_dev = NULL; | 
|  | 134 |  | 
|  | 135 | BUG_ON(pdev == NULL); | 
|  | 136 | BUG_ON(ent == NULL); | 
|  | 137 |  | 
|  | 138 | pci_read_config_byte(pdev, PCI_CLASS_REVISION, &rev); | 
|  | 139 |  | 
|  | 140 | printk(KERN_INFO DRIVER_NAME | 
|  | 141 | ": Ricoh MMC controller found at %s [%04x:%04x] (rev %x)\n", | 
|  | 142 | pci_name(pdev), (int)pdev->vendor, (int)pdev->device, | 
|  | 143 | (int)rev); | 
|  | 144 |  | 
| Frank Seidel | 882c491 | 2008-02-04 19:25:42 +0100 | [diff] [blame] | 145 | while ((fw_dev = | 
|  | 146 | pci_get_device(PCI_VENDOR_ID_RICOH, | 
|  | 147 | PCI_DEVICE_ID_RICOH_RL5C476, fw_dev))) { | 
| Philip Langdale | 5ae7029 | 2007-09-15 12:54:08 -0700 | [diff] [blame] | 148 | if (PCI_SLOT(pdev->devfn) == PCI_SLOT(fw_dev->devfn) && | 
| philipl@overt.org | 0527a60 | 2008-11-30 20:27:50 -0500 | [diff] [blame] | 149 | PCI_FUNC(fw_dev->devfn) == 0 && | 
| Philip Langdale | 5ae7029 | 2007-09-15 12:54:08 -0700 | [diff] [blame] | 150 | pdev->bus == fw_dev->bus) { | 
| Frank Seidel | 882c491 | 2008-02-04 19:25:42 +0100 | [diff] [blame] | 151 | if (ricoh_mmc_disable(fw_dev) != 0) | 
| Philip Langdale | 5ae7029 | 2007-09-15 12:54:08 -0700 | [diff] [blame] | 152 | return -ENODEV; | 
| Philip Langdale | 5ae7029 | 2007-09-15 12:54:08 -0700 | [diff] [blame] | 153 |  | 
| Philip Langdale | 5ae7029 | 2007-09-15 12:54:08 -0700 | [diff] [blame] | 154 | pci_set_drvdata(pdev, fw_dev); | 
|  | 155 |  | 
| Frank Seidel | 882c491 | 2008-02-04 19:25:42 +0100 | [diff] [blame] | 156 | ++ctrlfound; | 
| Philip Langdale | 5ae7029 | 2007-09-15 12:54:08 -0700 | [diff] [blame] | 157 | break; | 
|  | 158 | } | 
|  | 159 | } | 
|  | 160 |  | 
| Frank Seidel | 882c491 | 2008-02-04 19:25:42 +0100 | [diff] [blame] | 161 | fw_dev = NULL; | 
|  | 162 |  | 
|  | 163 | while (!ctrlfound && | 
|  | 164 | (fw_dev = pci_get_device(PCI_VENDOR_ID_RICOH, | 
|  | 165 | PCI_DEVICE_ID_RICOH_R5C832, fw_dev))) { | 
|  | 166 | if (PCI_SLOT(pdev->devfn) == PCI_SLOT(fw_dev->devfn) && | 
| philipl@overt.org | 0527a60 | 2008-11-30 20:27:50 -0500 | [diff] [blame] | 167 | PCI_FUNC(fw_dev->devfn) == 0 && | 
| Frank Seidel | 882c491 | 2008-02-04 19:25:42 +0100 | [diff] [blame] | 168 | pdev->bus == fw_dev->bus) { | 
|  | 169 | if (ricoh_mmc_disable(fw_dev) != 0) | 
|  | 170 | return -ENODEV; | 
|  | 171 |  | 
|  | 172 | pci_set_drvdata(pdev, fw_dev); | 
|  | 173 |  | 
|  | 174 | ++ctrlfound; | 
|  | 175 | } | 
|  | 176 | } | 
|  | 177 |  | 
|  | 178 | if (!ctrlfound) { | 
| Philip Langdale | 5ae7029 | 2007-09-15 12:54:08 -0700 | [diff] [blame] | 179 | printk(KERN_WARNING DRIVER_NAME | 
| philipl@overt.org | 0527a60 | 2008-11-30 20:27:50 -0500 | [diff] [blame] | 180 | ": Main Ricoh function not found. Cannot disable controller.\n"); | 
| Philip Langdale | 5ae7029 | 2007-09-15 12:54:08 -0700 | [diff] [blame] | 181 | return -ENODEV; | 
|  | 182 | } | 
|  | 183 |  | 
|  | 184 | return 0; | 
|  | 185 | } | 
|  | 186 |  | 
|  | 187 | static void __devexit ricoh_mmc_remove(struct pci_dev *pdev) | 
|  | 188 | { | 
| Philip Langdale | 5ae7029 | 2007-09-15 12:54:08 -0700 | [diff] [blame] | 189 | struct pci_dev *fw_dev = NULL; | 
|  | 190 |  | 
|  | 191 | fw_dev = pci_get_drvdata(pdev); | 
|  | 192 | BUG_ON(fw_dev == NULL); | 
|  | 193 |  | 
| Philip Langdale | 1f090bf | 2007-12-29 00:11:42 -0800 | [diff] [blame] | 194 | ricoh_mmc_enable(fw_dev); | 
| Philip Langdale | 5ae7029 | 2007-09-15 12:54:08 -0700 | [diff] [blame] | 195 |  | 
|  | 196 | pci_set_drvdata(pdev, NULL); | 
|  | 197 | } | 
|  | 198 |  | 
| philipl@overt.org | 06cc1c8 | 2009-01-18 14:11:20 -0500 | [diff] [blame] | 199 | static int ricoh_mmc_suspend_late(struct pci_dev *pdev, pm_message_t state) | 
| Philip Langdale | 1f090bf | 2007-12-29 00:11:42 -0800 | [diff] [blame] | 200 | { | 
|  | 201 | struct pci_dev *fw_dev = NULL; | 
|  | 202 |  | 
|  | 203 | fw_dev = pci_get_drvdata(pdev); | 
|  | 204 | BUG_ON(fw_dev == NULL); | 
|  | 205 |  | 
|  | 206 | printk(KERN_INFO DRIVER_NAME ": Suspending.\n"); | 
|  | 207 |  | 
|  | 208 | ricoh_mmc_enable(fw_dev); | 
|  | 209 |  | 
|  | 210 | return 0; | 
|  | 211 | } | 
|  | 212 |  | 
| philipl@overt.org | 06cc1c8 | 2009-01-18 14:11:20 -0500 | [diff] [blame] | 213 | static int ricoh_mmc_resume_early(struct pci_dev *pdev) | 
| Philip Langdale | 1f090bf | 2007-12-29 00:11:42 -0800 | [diff] [blame] | 214 | { | 
|  | 215 | struct pci_dev *fw_dev = NULL; | 
|  | 216 |  | 
|  | 217 | fw_dev = pci_get_drvdata(pdev); | 
|  | 218 | BUG_ON(fw_dev == NULL); | 
|  | 219 |  | 
|  | 220 | printk(KERN_INFO DRIVER_NAME ": Resuming.\n"); | 
|  | 221 |  | 
|  | 222 | ricoh_mmc_disable(fw_dev); | 
|  | 223 |  | 
|  | 224 | return 0; | 
|  | 225 | } | 
|  | 226 |  | 
| Philip Langdale | 5ae7029 | 2007-09-15 12:54:08 -0700 | [diff] [blame] | 227 | static struct pci_driver ricoh_mmc_driver = { | 
|  | 228 | .name = 	DRIVER_NAME, | 
|  | 229 | .id_table =	pci_ids, | 
|  | 230 | .probe = 	ricoh_mmc_probe, | 
|  | 231 | .remove =	__devexit_p(ricoh_mmc_remove), | 
| philipl@overt.org | 06cc1c8 | 2009-01-18 14:11:20 -0500 | [diff] [blame] | 232 | .suspend_late =	ricoh_mmc_suspend_late, | 
|  | 233 | .resume_early =	ricoh_mmc_resume_early, | 
| Philip Langdale | 5ae7029 | 2007-09-15 12:54:08 -0700 | [diff] [blame] | 234 | }; | 
|  | 235 |  | 
|  | 236 | /*****************************************************************************\ | 
|  | 237 | *                                                                           * | 
|  | 238 | * Driver init/exit                                                          * | 
|  | 239 | *                                                                           * | 
|  | 240 | \*****************************************************************************/ | 
|  | 241 |  | 
|  | 242 | static int __init ricoh_mmc_drv_init(void) | 
|  | 243 | { | 
|  | 244 | printk(KERN_INFO DRIVER_NAME | 
|  | 245 | ": Ricoh MMC Controller disabling driver\n"); | 
|  | 246 | printk(KERN_INFO DRIVER_NAME ": Copyright(c) Philip Langdale\n"); | 
|  | 247 |  | 
|  | 248 | return pci_register_driver(&ricoh_mmc_driver); | 
|  | 249 | } | 
|  | 250 |  | 
|  | 251 | static void __exit ricoh_mmc_drv_exit(void) | 
|  | 252 | { | 
|  | 253 | pci_unregister_driver(&ricoh_mmc_driver); | 
|  | 254 | } | 
|  | 255 |  | 
|  | 256 | module_init(ricoh_mmc_drv_init); | 
|  | 257 | module_exit(ricoh_mmc_drv_exit); | 
|  | 258 |  | 
|  | 259 | MODULE_AUTHOR("Philip Langdale <philipl@alumni.utexas.net>"); | 
|  | 260 | MODULE_DESCRIPTION("Ricoh MMC Controller disabling driver"); | 
|  | 261 | MODULE_LICENSE("GPL"); | 
|  | 262 |  |