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