Commit | Line | Data |
---|---|---|
5ae70296 PL |
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 | |
0527a60c | 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 | |
5ae70296 PL |
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 | * | |
0527a60c | 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. | |
5ae70296 PL |
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 | ||
1f090bf5 PL |
47 | static int ricoh_mmc_disable(struct pci_dev *fw_dev) |
48 | { | |
49 | u8 write_enable; | |
882c4916 | 50 | u8 write_target; |
1f090bf5 PL |
51 | u8 disable; |
52 | ||
882c4916 FS |
53 | if (fw_dev->device == PCI_DEVICE_ID_RICOH_RL5C476) { |
54 | /* via RL5C476 */ | |
1f090bf5 | 55 | |
882c4916 FS |
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 | } | |
1f090bf5 PL |
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; | |
882c4916 | 97 | u8 write_target; |
1f090bf5 PL |
98 | u8 disable; |
99 | ||
882c4916 FS |
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 | } | |
1f090bf5 PL |
120 | |
121 | printk(KERN_INFO DRIVER_NAME | |
122 | ": Controller is now re-enabled.\n"); | |
123 | ||
124 | return 0; | |
125 | } | |
126 | ||
5ae70296 PL |
127 | static int __devinit ricoh_mmc_probe(struct pci_dev *pdev, |
128 | const struct pci_device_id *ent) | |
129 | { | |
130 | u8 rev; | |
882c4916 | 131 | u8 ctrlfound = 0; |
5ae70296 PL |
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 | ||
882c4916 FS |
145 | while ((fw_dev = |
146 | pci_get_device(PCI_VENDOR_ID_RICOH, | |
147 | PCI_DEVICE_ID_RICOH_RL5C476, fw_dev))) { | |
5ae70296 | 148 | if (PCI_SLOT(pdev->devfn) == PCI_SLOT(fw_dev->devfn) && |
0527a60c | 149 | PCI_FUNC(fw_dev->devfn) == 0 && |
5ae70296 | 150 | pdev->bus == fw_dev->bus) { |
882c4916 | 151 | if (ricoh_mmc_disable(fw_dev) != 0) |
5ae70296 | 152 | return -ENODEV; |
5ae70296 | 153 | |
5ae70296 PL |
154 | pci_set_drvdata(pdev, fw_dev); |
155 | ||
882c4916 | 156 | ++ctrlfound; |
5ae70296 PL |
157 | break; |
158 | } | |
159 | } | |
160 | ||
882c4916 FS |
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) && | |
0527a60c | 167 | PCI_FUNC(fw_dev->devfn) == 0 && |
882c4916 FS |
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) { | |
5ae70296 | 179 | printk(KERN_WARNING DRIVER_NAME |
0527a60c | 180 | ": Main Ricoh function not found. Cannot disable controller.\n"); |
5ae70296 PL |
181 | return -ENODEV; |
182 | } | |
183 | ||
184 | return 0; | |
185 | } | |
186 | ||
187 | static void __devexit ricoh_mmc_remove(struct pci_dev *pdev) | |
188 | { | |
5ae70296 PL |
189 | struct pci_dev *fw_dev = NULL; |
190 | ||
191 | fw_dev = pci_get_drvdata(pdev); | |
192 | BUG_ON(fw_dev == NULL); | |
193 | ||
1f090bf5 | 194 | ricoh_mmc_enable(fw_dev); |
5ae70296 PL |
195 | |
196 | pci_set_drvdata(pdev, NULL); | |
197 | } | |
198 | ||
06cc1c88 | 199 | static int ricoh_mmc_suspend_late(struct pci_dev *pdev, pm_message_t state) |
1f090bf5 PL |
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 | ||
06cc1c88 | 213 | static int ricoh_mmc_resume_early(struct pci_dev *pdev) |
1f090bf5 PL |
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 | ||
5ae70296 PL |
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), | |
06cc1c88 | 232 | .suspend_late = ricoh_mmc_suspend_late, |
233 | .resume_early = ricoh_mmc_resume_early, | |
5ae70296 PL |
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 |