Commit | Line | Data |
---|---|---|
5f5bac82 MM |
1 | /* |
2 | * cb710/core.c | |
3 | * | |
4 | * Copyright by Michał Mirosław, 2008-2009 | |
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 version 2 as | |
8 | * published by the Free Software Foundation. | |
9 | */ | |
10 | #include <linux/kernel.h> | |
11 | #include <linux/module.h> | |
5f5bac82 MM |
12 | #include <linux/pci.h> |
13 | #include <linux/spinlock.h> | |
14 | #include <linux/idr.h> | |
15 | #include <linux/cb710.h> | |
5a0e3ad6 | 16 | #include <linux/gfp.h> |
5f5bac82 MM |
17 | |
18 | static DEFINE_IDA(cb710_ida); | |
19 | static DEFINE_SPINLOCK(cb710_ida_lock); | |
20 | ||
21 | void cb710_pci_update_config_reg(struct pci_dev *pdev, | |
22 | int reg, uint32_t mask, uint32_t xor) | |
23 | { | |
24 | u32 rval; | |
25 | ||
26 | pci_read_config_dword(pdev, reg, &rval); | |
27 | rval = (rval & mask) ^ xor; | |
28 | pci_write_config_dword(pdev, reg, rval); | |
29 | } | |
30 | EXPORT_SYMBOL_GPL(cb710_pci_update_config_reg); | |
31 | ||
32 | /* Some magic writes based on Windows driver init code */ | |
80c8ae28 | 33 | static int cb710_pci_configure(struct pci_dev *pdev) |
5f5bac82 MM |
34 | { |
35 | unsigned int devfn = PCI_DEVFN(PCI_SLOT(pdev->devfn), 0); | |
1ccd4b7b | 36 | struct pci_dev *pdev0; |
5f5bac82 MM |
37 | u32 val; |
38 | ||
39 | cb710_pci_update_config_reg(pdev, 0x48, | |
40 | ~0x000000FF, 0x0000003F); | |
41 | ||
42 | pci_read_config_dword(pdev, 0x48, &val); | |
43 | if (val & 0x80000000) | |
44 | return 0; | |
45 | ||
1ccd4b7b | 46 | pdev0 = pci_get_slot(pdev->bus, devfn); |
5f5bac82 MM |
47 | if (!pdev0) |
48 | return -ENODEV; | |
49 | ||
50 | if (pdev0->vendor == PCI_VENDOR_ID_ENE | |
51 | && pdev0->device == PCI_DEVICE_ID_ENE_720) { | |
52 | cb710_pci_update_config_reg(pdev0, 0x8C, | |
53 | ~0x00F00000, 0x00100000); | |
54 | cb710_pci_update_config_reg(pdev0, 0xB0, | |
55 | ~0x08000000, 0x08000000); | |
56 | } | |
57 | ||
58 | cb710_pci_update_config_reg(pdev0, 0x8C, | |
59 | ~0x00000F00, 0x00000200); | |
60 | cb710_pci_update_config_reg(pdev0, 0x90, | |
61 | ~0x00060000, 0x00040000); | |
62 | ||
63 | pci_dev_put(pdev0); | |
64 | ||
65 | return 0; | |
66 | } | |
67 | ||
68 | static irqreturn_t cb710_irq_handler(int irq, void *data) | |
69 | { | |
70 | struct cb710_chip *chip = data; | |
71 | struct cb710_slot *slot = &chip->slot[0]; | |
72 | irqreturn_t handled = IRQ_NONE; | |
73 | unsigned nr; | |
74 | ||
75 | spin_lock(&chip->irq_lock); /* incl. smp_rmb() */ | |
76 | ||
77 | for (nr = chip->slots; nr; ++slot, --nr) { | |
78 | cb710_irq_handler_t handler_func = slot->irq_handler; | |
79 | if (handler_func && handler_func(slot)) | |
80 | handled = IRQ_HANDLED; | |
81 | } | |
82 | ||
83 | spin_unlock(&chip->irq_lock); | |
84 | ||
85 | return handled; | |
86 | } | |
87 | ||
88 | static void cb710_release_slot(struct device *dev) | |
89 | { | |
90 | #ifdef CONFIG_CB710_DEBUG_ASSUMPTIONS | |
91 | struct cb710_slot *slot = cb710_pdev_to_slot(to_platform_device(dev)); | |
92 | struct cb710_chip *chip = cb710_slot_to_chip(slot); | |
93 | ||
94 | /* slot struct can be freed now */ | |
95 | atomic_dec(&chip->slot_refs_count); | |
96 | #endif | |
97 | } | |
98 | ||
80c8ae28 | 99 | static int cb710_register_slot(struct cb710_chip *chip, |
5f5bac82 MM |
100 | unsigned slot_mask, unsigned io_offset, const char *name) |
101 | { | |
102 | int nr = chip->slots; | |
103 | struct cb710_slot *slot = &chip->slot[nr]; | |
104 | int err; | |
105 | ||
106 | dev_dbg(cb710_chip_dev(chip), | |
107 | "register: %s.%d; slot %d; mask %d; IO offset: 0x%02X\n", | |
108 | name, chip->platform_id, nr, slot_mask, io_offset); | |
109 | ||
110 | /* slot->irq_handler == NULL here; this needs to be | |
111 | * seen before platform_device_register() */ | |
112 | ++chip->slots; | |
113 | smp_wmb(); | |
114 | ||
115 | slot->iobase = chip->iobase + io_offset; | |
116 | slot->pdev.name = name; | |
117 | slot->pdev.id = chip->platform_id; | |
118 | slot->pdev.dev.parent = &chip->pdev->dev; | |
119 | slot->pdev.dev.release = cb710_release_slot; | |
120 | ||
121 | err = platform_device_register(&slot->pdev); | |
122 | ||
123 | #ifdef CONFIG_CB710_DEBUG_ASSUMPTIONS | |
124 | atomic_inc(&chip->slot_refs_count); | |
125 | #endif | |
126 | ||
127 | if (err) { | |
128 | /* device_initialize() called from platform_device_register() | |
129 | * wants this on error path */ | |
130 | platform_device_put(&slot->pdev); | |
131 | ||
132 | /* slot->irq_handler == NULL here anyway, so no lock needed */ | |
133 | --chip->slots; | |
134 | return err; | |
135 | } | |
136 | ||
137 | chip->slot_mask |= slot_mask; | |
138 | ||
139 | return 0; | |
140 | } | |
141 | ||
142 | static void cb710_unregister_slot(struct cb710_chip *chip, | |
143 | unsigned slot_mask) | |
144 | { | |
145 | int nr = chip->slots - 1; | |
146 | ||
147 | if (!(chip->slot_mask & slot_mask)) | |
148 | return; | |
149 | ||
150 | platform_device_unregister(&chip->slot[nr].pdev); | |
151 | ||
152 | /* complementary to spin_unlock() in cb710_set_irq_handler() */ | |
153 | smp_rmb(); | |
154 | BUG_ON(chip->slot[nr].irq_handler != NULL); | |
155 | ||
156 | /* slot->irq_handler == NULL here, so no lock needed */ | |
157 | --chip->slots; | |
158 | chip->slot_mask &= ~slot_mask; | |
159 | } | |
160 | ||
161 | void cb710_set_irq_handler(struct cb710_slot *slot, | |
162 | cb710_irq_handler_t handler) | |
163 | { | |
164 | struct cb710_chip *chip = cb710_slot_to_chip(slot); | |
165 | unsigned long flags; | |
166 | ||
167 | spin_lock_irqsave(&chip->irq_lock, flags); | |
168 | slot->irq_handler = handler; | |
169 | spin_unlock_irqrestore(&chip->irq_lock, flags); | |
170 | } | |
171 | EXPORT_SYMBOL_GPL(cb710_set_irq_handler); | |
172 | ||
173 | #ifdef CONFIG_PM | |
174 | ||
175 | static int cb710_suspend(struct pci_dev *pdev, pm_message_t state) | |
176 | { | |
177 | struct cb710_chip *chip = pci_get_drvdata(pdev); | |
178 | ||
179 | free_irq(pdev->irq, chip); | |
180 | pci_save_state(pdev); | |
181 | pci_disable_device(pdev); | |
182 | if (state.event & PM_EVENT_SLEEP) | |
8497f696 | 183 | pci_set_power_state(pdev, PCI_D3hot); |
5f5bac82 MM |
184 | return 0; |
185 | } | |
186 | ||
187 | static int cb710_resume(struct pci_dev *pdev) | |
188 | { | |
189 | struct cb710_chip *chip = pci_get_drvdata(pdev); | |
190 | int err; | |
191 | ||
192 | pci_set_power_state(pdev, PCI_D0); | |
193 | pci_restore_state(pdev); | |
194 | err = pcim_enable_device(pdev); | |
195 | if (err) | |
196 | return err; | |
197 | ||
198 | return devm_request_irq(&pdev->dev, pdev->irq, | |
199 | cb710_irq_handler, IRQF_SHARED, KBUILD_MODNAME, chip); | |
200 | } | |
201 | ||
202 | #endif /* CONFIG_PM */ | |
203 | ||
80c8ae28 | 204 | static int cb710_probe(struct pci_dev *pdev, |
5f5bac82 MM |
205 | const struct pci_device_id *ent) |
206 | { | |
207 | struct cb710_chip *chip; | |
208 | unsigned long flags; | |
209 | u32 val; | |
210 | int err; | |
211 | int n = 0; | |
212 | ||
213 | err = cb710_pci_configure(pdev); | |
214 | if (err) | |
215 | return err; | |
216 | ||
217 | /* this is actually magic... */ | |
218 | pci_read_config_dword(pdev, 0x48, &val); | |
219 | if (!(val & 0x80000000)) { | |
220 | pci_write_config_dword(pdev, 0x48, val|0x71000000); | |
221 | pci_read_config_dword(pdev, 0x48, &val); | |
222 | } | |
223 | ||
224 | dev_dbg(&pdev->dev, "PCI config[0x48] = 0x%08X\n", val); | |
225 | if (!(val & 0x70000000)) | |
226 | return -ENODEV; | |
227 | val = (val >> 28) & 7; | |
228 | if (val & CB710_SLOT_MMC) | |
229 | ++n; | |
230 | if (val & CB710_SLOT_MS) | |
231 | ++n; | |
232 | if (val & CB710_SLOT_SM) | |
233 | ++n; | |
234 | ||
235 | chip = devm_kzalloc(&pdev->dev, | |
236 | sizeof(*chip) + n * sizeof(*chip->slot), GFP_KERNEL); | |
237 | if (!chip) | |
238 | return -ENOMEM; | |
239 | ||
240 | err = pcim_enable_device(pdev); | |
241 | if (err) | |
242 | return err; | |
243 | ||
244 | err = pcim_iomap_regions(pdev, 0x0001, KBUILD_MODNAME); | |
245 | if (err) | |
246 | return err; | |
247 | ||
b5266ea6 | 248 | spin_lock_init(&chip->irq_lock); |
5f5bac82 MM |
249 | chip->pdev = pdev; |
250 | chip->iobase = pcim_iomap_table(pdev)[0]; | |
251 | ||
252 | pci_set_drvdata(pdev, chip); | |
253 | ||
254 | err = devm_request_irq(&pdev->dev, pdev->irq, | |
255 | cb710_irq_handler, IRQF_SHARED, KBUILD_MODNAME, chip); | |
256 | if (err) | |
257 | return err; | |
258 | ||
259 | do { | |
260 | if (!ida_pre_get(&cb710_ida, GFP_KERNEL)) | |
261 | return -ENOMEM; | |
262 | ||
263 | spin_lock_irqsave(&cb710_ida_lock, flags); | |
264 | err = ida_get_new(&cb710_ida, &chip->platform_id); | |
265 | spin_unlock_irqrestore(&cb710_ida_lock, flags); | |
266 | ||
267 | if (err && err != -EAGAIN) | |
268 | return err; | |
269 | } while (err); | |
270 | ||
271 | ||
272 | dev_info(&pdev->dev, "id %d, IO 0x%p, IRQ %d\n", | |
273 | chip->platform_id, chip->iobase, pdev->irq); | |
274 | ||
275 | if (val & CB710_SLOT_MMC) { /* MMC/SD slot */ | |
276 | err = cb710_register_slot(chip, | |
277 | CB710_SLOT_MMC, 0x00, "cb710-mmc"); | |
278 | if (err) | |
279 | return err; | |
280 | } | |
281 | ||
282 | if (val & CB710_SLOT_MS) { /* MemoryStick slot */ | |
283 | err = cb710_register_slot(chip, | |
284 | CB710_SLOT_MS, 0x40, "cb710-ms"); | |
285 | if (err) | |
286 | goto unreg_mmc; | |
287 | } | |
288 | ||
289 | if (val & CB710_SLOT_SM) { /* SmartMedia slot */ | |
290 | err = cb710_register_slot(chip, | |
291 | CB710_SLOT_SM, 0x60, "cb710-sm"); | |
292 | if (err) | |
293 | goto unreg_ms; | |
294 | } | |
295 | ||
296 | return 0; | |
297 | unreg_ms: | |
298 | cb710_unregister_slot(chip, CB710_SLOT_MS); | |
299 | unreg_mmc: | |
300 | cb710_unregister_slot(chip, CB710_SLOT_MMC); | |
301 | ||
302 | #ifdef CONFIG_CB710_DEBUG_ASSUMPTIONS | |
303 | BUG_ON(atomic_read(&chip->slot_refs_count) != 0); | |
304 | #endif | |
305 | return err; | |
306 | } | |
307 | ||
486a5c28 | 308 | static void cb710_remove_one(struct pci_dev *pdev) |
5f5bac82 MM |
309 | { |
310 | struct cb710_chip *chip = pci_get_drvdata(pdev); | |
311 | unsigned long flags; | |
312 | ||
313 | cb710_unregister_slot(chip, CB710_SLOT_SM); | |
314 | cb710_unregister_slot(chip, CB710_SLOT_MS); | |
315 | cb710_unregister_slot(chip, CB710_SLOT_MMC); | |
316 | #ifdef CONFIG_CB710_DEBUG_ASSUMPTIONS | |
317 | BUG_ON(atomic_read(&chip->slot_refs_count) != 0); | |
318 | #endif | |
319 | ||
320 | spin_lock_irqsave(&cb710_ida_lock, flags); | |
321 | ida_remove(&cb710_ida, chip->platform_id); | |
322 | spin_unlock_irqrestore(&cb710_ida_lock, flags); | |
323 | } | |
324 | ||
325 | static const struct pci_device_id cb710_pci_tbl[] = { | |
326 | { PCI_VENDOR_ID_ENE, PCI_DEVICE_ID_ENE_CB710_FLASH, | |
327 | PCI_ANY_ID, PCI_ANY_ID, }, | |
328 | { 0, } | |
329 | }; | |
330 | ||
331 | static struct pci_driver cb710_driver = { | |
332 | .name = KBUILD_MODNAME, | |
333 | .id_table = cb710_pci_tbl, | |
334 | .probe = cb710_probe, | |
2d6bed9c | 335 | .remove = cb710_remove_one, |
5f5bac82 MM |
336 | #ifdef CONFIG_PM |
337 | .suspend = cb710_suspend, | |
338 | .resume = cb710_resume, | |
339 | #endif | |
340 | }; | |
341 | ||
342 | static int __init cb710_init_module(void) | |
343 | { | |
344 | return pci_register_driver(&cb710_driver); | |
345 | } | |
346 | ||
347 | static void __exit cb710_cleanup_module(void) | |
348 | { | |
349 | pci_unregister_driver(&cb710_driver); | |
350 | ida_destroy(&cb710_ida); | |
351 | } | |
352 | ||
353 | module_init(cb710_init_module); | |
354 | module_exit(cb710_cleanup_module); | |
355 | ||
356 | MODULE_AUTHOR("Michał Mirosław <mirq-linux@rere.qmqm.pl>"); | |
357 | MODULE_DESCRIPTION("ENE CB710 memory card reader driver"); | |
358 | MODULE_LICENSE("GPL"); | |
359 | MODULE_DEVICE_TABLE(pci, cb710_pci_tbl); |