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