1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * tifm_7xx1.c - TI FlashMedia driver |
4 | * |
5 | * Copyright (C) 2006 Alex Dubov <oakad@yahoo.com> |
6 | */ |
7 | |
8 | #include <linux/tifm.h> |
9 | #include <linux/dma-mapping.h> |
10 | #include <linux/module.h> |
11 | |
12 | #define DRIVER_NAME "tifm_7xx1" |
13 | #define DRIVER_VERSION "0.8" |
14 | |
15 | #define TIFM_IRQ_ENABLE 0x80000000 |
16 | #define TIFM_IRQ_SOCKMASK(x) (x) |
17 | #define TIFM_IRQ_CARDMASK(x) ((x) << 8) |
18 | #define TIFM_IRQ_FIFOMASK(x) ((x) << 16) |
19 | #define TIFM_IRQ_SETALL 0xffffffff |
20 | |
21 | static void tifm_7xx1_dummy_eject(struct tifm_adapter *fm, |
22 | struct tifm_dev *sock) |
23 | { |
24 | } |
25 | |
26 | static void tifm_7xx1_eject(struct tifm_adapter *fm, struct tifm_dev *sock) |
27 | { |
28 | unsigned long flags; |
29 | |
30 | spin_lock_irqsave(&fm->lock, flags); |
31 | fm->socket_change_set |= 1 << sock->socket_id; |
32 | tifm_queue_work(work: &fm->media_switcher); |
33 | spin_unlock_irqrestore(lock: &fm->lock, flags); |
34 | } |
35 | |
36 | static irqreturn_t tifm_7xx1_isr(int irq, void *dev_id) |
37 | { |
38 | struct tifm_adapter *fm = dev_id; |
39 | struct tifm_dev *sock; |
40 | unsigned int irq_status, cnt; |
41 | |
42 | spin_lock(lock: &fm->lock); |
43 | irq_status = readl(addr: fm->addr + FM_INTERRUPT_STATUS); |
44 | if (irq_status == 0 || irq_status == (~0)) { |
45 | spin_unlock(lock: &fm->lock); |
46 | return IRQ_NONE; |
47 | } |
48 | |
49 | if (irq_status & TIFM_IRQ_ENABLE) { |
50 | writel(TIFM_IRQ_ENABLE, addr: fm->addr + FM_CLEAR_INTERRUPT_ENABLE); |
51 | |
52 | for (cnt = 0; cnt < fm->num_sockets; cnt++) { |
53 | sock = fm->sockets[cnt]; |
54 | if (sock) { |
55 | if ((irq_status >> cnt) & TIFM_IRQ_FIFOMASK(1)) |
56 | sock->data_event(sock); |
57 | if ((irq_status >> cnt) & TIFM_IRQ_CARDMASK(1)) |
58 | sock->card_event(sock); |
59 | } |
60 | } |
61 | |
62 | fm->socket_change_set |= irq_status |
63 | & ((1 << fm->num_sockets) - 1); |
64 | } |
65 | writel(val: irq_status, addr: fm->addr + FM_INTERRUPT_STATUS); |
66 | |
67 | if (fm->finish_me) |
68 | complete_all(fm->finish_me); |
69 | else if (!fm->socket_change_set) |
70 | writel(TIFM_IRQ_ENABLE, addr: fm->addr + FM_SET_INTERRUPT_ENABLE); |
71 | else |
72 | tifm_queue_work(work: &fm->media_switcher); |
73 | |
74 | spin_unlock(lock: &fm->lock); |
75 | return IRQ_HANDLED; |
76 | } |
77 | |
78 | static unsigned char tifm_7xx1_toggle_sock_power(char __iomem *sock_addr) |
79 | { |
80 | unsigned int s_state; |
81 | int cnt; |
82 | |
83 | writel(val: 0x0e00, addr: sock_addr + SOCK_CONTROL); |
84 | |
85 | for (cnt = 16; cnt <= 256; cnt <<= 1) { |
86 | if (!(TIFM_SOCK_STATE_POWERED |
87 | & readl(addr: sock_addr + SOCK_PRESENT_STATE))) |
88 | break; |
89 | |
90 | msleep(msecs: cnt); |
91 | } |
92 | |
93 | s_state = readl(addr: sock_addr + SOCK_PRESENT_STATE); |
94 | if (!(TIFM_SOCK_STATE_OCCUPIED & s_state)) |
95 | return 0; |
96 | |
97 | writel(readl(addr: sock_addr + SOCK_CONTROL) | TIFM_CTRL_LED, |
98 | addr: sock_addr + SOCK_CONTROL); |
99 | |
100 | /* xd needs some extra time before power on */ |
101 | if (((readl(addr: sock_addr + SOCK_PRESENT_STATE) >> 4) & 7) |
102 | == TIFM_TYPE_XD) |
103 | msleep(msecs: 40); |
104 | |
105 | writel(val: (s_state & TIFM_CTRL_POWER_MASK) | 0x0c00, |
106 | addr: sock_addr + SOCK_CONTROL); |
107 | /* wait for power to stabilize */ |
108 | msleep(msecs: 20); |
109 | for (cnt = 16; cnt <= 256; cnt <<= 1) { |
110 | if ((TIFM_SOCK_STATE_POWERED |
111 | & readl(addr: sock_addr + SOCK_PRESENT_STATE))) |
112 | break; |
113 | |
114 | msleep(msecs: cnt); |
115 | } |
116 | |
117 | writel(readl(addr: sock_addr + SOCK_CONTROL) & (~TIFM_CTRL_LED), |
118 | addr: sock_addr + SOCK_CONTROL); |
119 | |
120 | return (readl(addr: sock_addr + SOCK_PRESENT_STATE) >> 4) & 7; |
121 | } |
122 | |
123 | inline static void tifm_7xx1_sock_power_off(char __iomem *sock_addr) |
124 | { |
125 | writel(val: (~TIFM_CTRL_POWER_MASK) & readl(addr: sock_addr + SOCK_CONTROL), |
126 | addr: sock_addr + SOCK_CONTROL); |
127 | } |
128 | |
129 | inline static char __iomem * |
130 | tifm_7xx1_sock_addr(char __iomem *base_addr, unsigned int sock_num) |
131 | { |
132 | return base_addr + ((sock_num + 1) << 10); |
133 | } |
134 | |
135 | static void tifm_7xx1_switch_media(struct work_struct *work) |
136 | { |
137 | struct tifm_adapter *fm = container_of(work, struct tifm_adapter, |
138 | media_switcher); |
139 | struct tifm_dev *sock; |
140 | char __iomem *sock_addr; |
141 | unsigned long flags; |
142 | unsigned char media_id; |
143 | unsigned int socket_change_set, cnt; |
144 | |
145 | spin_lock_irqsave(&fm->lock, flags); |
146 | socket_change_set = fm->socket_change_set; |
147 | fm->socket_change_set = 0; |
148 | |
149 | dev_dbg(fm->dev.parent, "checking media set %x\n" , |
150 | socket_change_set); |
151 | |
152 | if (!socket_change_set) { |
153 | spin_unlock_irqrestore(lock: &fm->lock, flags); |
154 | return; |
155 | } |
156 | |
157 | for (cnt = 0; cnt < fm->num_sockets; cnt++) { |
158 | if (!(socket_change_set & (1 << cnt))) |
159 | continue; |
160 | sock = fm->sockets[cnt]; |
161 | if (sock) { |
162 | printk(KERN_INFO |
163 | "%s : demand removing card from socket %u:%u\n" , |
164 | dev_name(&fm->dev), fm->id, cnt); |
165 | fm->sockets[cnt] = NULL; |
166 | sock_addr = sock->addr; |
167 | spin_unlock_irqrestore(lock: &fm->lock, flags); |
168 | device_unregister(dev: &sock->dev); |
169 | spin_lock_irqsave(&fm->lock, flags); |
170 | tifm_7xx1_sock_power_off(sock_addr); |
171 | writel(val: 0x0e00, addr: sock_addr + SOCK_CONTROL); |
172 | } |
173 | |
174 | spin_unlock_irqrestore(lock: &fm->lock, flags); |
175 | |
176 | media_id = tifm_7xx1_toggle_sock_power( |
177 | sock_addr: tifm_7xx1_sock_addr(base_addr: fm->addr, sock_num: cnt)); |
178 | |
179 | // tifm_alloc_device will check if media_id is valid |
180 | sock = tifm_alloc_device(fm, id: cnt, type: media_id); |
181 | if (sock) { |
182 | sock->addr = tifm_7xx1_sock_addr(base_addr: fm->addr, sock_num: cnt); |
183 | |
184 | if (!device_register(dev: &sock->dev)) { |
185 | spin_lock_irqsave(&fm->lock, flags); |
186 | if (!fm->sockets[cnt]) { |
187 | fm->sockets[cnt] = sock; |
188 | sock = NULL; |
189 | } |
190 | spin_unlock_irqrestore(lock: &fm->lock, flags); |
191 | } |
192 | if (sock) |
193 | put_device(dev: &sock->dev); |
194 | } |
195 | spin_lock_irqsave(&fm->lock, flags); |
196 | } |
197 | |
198 | writel(TIFM_IRQ_FIFOMASK(socket_change_set) |
199 | | TIFM_IRQ_CARDMASK(socket_change_set), |
200 | addr: fm->addr + FM_CLEAR_INTERRUPT_ENABLE); |
201 | |
202 | writel(TIFM_IRQ_FIFOMASK(socket_change_set) |
203 | | TIFM_IRQ_CARDMASK(socket_change_set), |
204 | addr: fm->addr + FM_SET_INTERRUPT_ENABLE); |
205 | |
206 | writel(TIFM_IRQ_ENABLE, addr: fm->addr + FM_SET_INTERRUPT_ENABLE); |
207 | spin_unlock_irqrestore(lock: &fm->lock, flags); |
208 | } |
209 | |
210 | static int __maybe_unused tifm_7xx1_suspend(struct device *dev_d) |
211 | { |
212 | struct pci_dev *dev = to_pci_dev(dev_d); |
213 | struct tifm_adapter *fm = pci_get_drvdata(pdev: dev); |
214 | int cnt; |
215 | |
216 | dev_dbg(&dev->dev, "suspending host\n" ); |
217 | |
218 | for (cnt = 0; cnt < fm->num_sockets; cnt++) { |
219 | if (fm->sockets[cnt]) |
220 | tifm_7xx1_sock_power_off(sock_addr: fm->sockets[cnt]->addr); |
221 | } |
222 | |
223 | device_wakeup_disable(dev: dev_d); |
224 | return 0; |
225 | } |
226 | |
227 | static int __maybe_unused tifm_7xx1_resume(struct device *dev_d) |
228 | { |
229 | struct pci_dev *dev = to_pci_dev(dev_d); |
230 | struct tifm_adapter *fm = pci_get_drvdata(pdev: dev); |
231 | int rc; |
232 | unsigned long timeout; |
233 | unsigned int good_sockets = 0, bad_sockets = 0; |
234 | unsigned long flags; |
235 | /* Maximum number of entries is 4 */ |
236 | unsigned char new_ids[4]; |
237 | DECLARE_COMPLETION_ONSTACK(finish_resume); |
238 | |
239 | if (WARN_ON(fm->num_sockets > ARRAY_SIZE(new_ids))) |
240 | return -ENXIO; |
241 | |
242 | pci_set_master(dev); |
243 | |
244 | dev_dbg(&dev->dev, "resuming host\n" ); |
245 | |
246 | for (rc = 0; rc < fm->num_sockets; rc++) |
247 | new_ids[rc] = tifm_7xx1_toggle_sock_power( |
248 | sock_addr: tifm_7xx1_sock_addr(base_addr: fm->addr, sock_num: rc)); |
249 | spin_lock_irqsave(&fm->lock, flags); |
250 | for (rc = 0; rc < fm->num_sockets; rc++) { |
251 | if (fm->sockets[rc]) { |
252 | if (fm->sockets[rc]->type == new_ids[rc]) |
253 | good_sockets |= 1 << rc; |
254 | else |
255 | bad_sockets |= 1 << rc; |
256 | } |
257 | } |
258 | |
259 | writel(TIFM_IRQ_ENABLE | TIFM_IRQ_SOCKMASK((1 << fm->num_sockets) - 1), |
260 | addr: fm->addr + FM_SET_INTERRUPT_ENABLE); |
261 | dev_dbg(&dev->dev, "change sets on resume: good %x, bad %x\n" , |
262 | good_sockets, bad_sockets); |
263 | |
264 | fm->socket_change_set = 0; |
265 | if (good_sockets) { |
266 | fm->finish_me = &finish_resume; |
267 | spin_unlock_irqrestore(lock: &fm->lock, flags); |
268 | timeout = wait_for_completion_timeout(x: &finish_resume, HZ); |
269 | dev_dbg(&dev->dev, "wait returned %lu\n" , timeout); |
270 | writel(TIFM_IRQ_FIFOMASK(good_sockets) |
271 | | TIFM_IRQ_CARDMASK(good_sockets), |
272 | addr: fm->addr + FM_CLEAR_INTERRUPT_ENABLE); |
273 | writel(TIFM_IRQ_FIFOMASK(good_sockets) |
274 | | TIFM_IRQ_CARDMASK(good_sockets), |
275 | addr: fm->addr + FM_SET_INTERRUPT_ENABLE); |
276 | spin_lock_irqsave(&fm->lock, flags); |
277 | fm->finish_me = NULL; |
278 | fm->socket_change_set ^= good_sockets & fm->socket_change_set; |
279 | } |
280 | |
281 | fm->socket_change_set |= bad_sockets; |
282 | if (fm->socket_change_set) |
283 | tifm_queue_work(work: &fm->media_switcher); |
284 | |
285 | spin_unlock_irqrestore(lock: &fm->lock, flags); |
286 | writel(TIFM_IRQ_ENABLE, |
287 | addr: fm->addr + FM_SET_INTERRUPT_ENABLE); |
288 | |
289 | return 0; |
290 | } |
291 | |
292 | static int tifm_7xx1_dummy_has_ms_pif(struct tifm_adapter *fm, |
293 | struct tifm_dev *sock) |
294 | { |
295 | return 0; |
296 | } |
297 | |
298 | static int tifm_7xx1_has_ms_pif(struct tifm_adapter *fm, struct tifm_dev *sock) |
299 | { |
300 | if (((fm->num_sockets == 4) && (sock->socket_id == 2)) |
301 | || ((fm->num_sockets == 2) && (sock->socket_id == 0))) |
302 | return 1; |
303 | |
304 | return 0; |
305 | } |
306 | |
307 | static int tifm_7xx1_probe(struct pci_dev *dev, |
308 | const struct pci_device_id *dev_id) |
309 | { |
310 | struct tifm_adapter *fm; |
311 | int pci_dev_busy = 0; |
312 | int rc; |
313 | |
314 | rc = dma_set_mask(dev: &dev->dev, DMA_BIT_MASK(32)); |
315 | if (rc) |
316 | return rc; |
317 | |
318 | rc = pci_enable_device(dev); |
319 | if (rc) |
320 | return rc; |
321 | |
322 | pci_set_master(dev); |
323 | |
324 | rc = pci_request_regions(dev, DRIVER_NAME); |
325 | if (rc) { |
326 | pci_dev_busy = 1; |
327 | goto err_out; |
328 | } |
329 | |
330 | pci_intx(dev, enable: 1); |
331 | |
332 | fm = tifm_alloc_adapter(num_sockets: dev->device == PCI_DEVICE_ID_TI_XX21_XX11_FM |
333 | ? 4 : 2, dev: &dev->dev); |
334 | if (!fm) { |
335 | rc = -ENOMEM; |
336 | goto err_out_int; |
337 | } |
338 | |
339 | INIT_WORK(&fm->media_switcher, tifm_7xx1_switch_media); |
340 | fm->eject = tifm_7xx1_eject; |
341 | fm->has_ms_pif = tifm_7xx1_has_ms_pif; |
342 | pci_set_drvdata(pdev: dev, data: fm); |
343 | |
344 | fm->addr = pci_ioremap_bar(pdev: dev, bar: 0); |
345 | if (!fm->addr) { |
346 | rc = -ENODEV; |
347 | goto err_out_free; |
348 | } |
349 | |
350 | rc = request_irq(irq: dev->irq, handler: tifm_7xx1_isr, IRQF_SHARED, DRIVER_NAME, dev: fm); |
351 | if (rc) |
352 | goto err_out_unmap; |
353 | |
354 | rc = tifm_add_adapter(fm); |
355 | if (rc) |
356 | goto err_out_irq; |
357 | |
358 | writel(TIFM_IRQ_ENABLE | TIFM_IRQ_SOCKMASK((1 << fm->num_sockets) - 1), |
359 | addr: fm->addr + FM_CLEAR_INTERRUPT_ENABLE); |
360 | writel(TIFM_IRQ_ENABLE | TIFM_IRQ_SOCKMASK((1 << fm->num_sockets) - 1), |
361 | addr: fm->addr + FM_SET_INTERRUPT_ENABLE); |
362 | return 0; |
363 | |
364 | err_out_irq: |
365 | free_irq(dev->irq, fm); |
366 | err_out_unmap: |
367 | iounmap(addr: fm->addr); |
368 | err_out_free: |
369 | tifm_free_adapter(fm); |
370 | err_out_int: |
371 | pci_intx(dev, enable: 0); |
372 | pci_release_regions(dev); |
373 | err_out: |
374 | if (!pci_dev_busy) |
375 | pci_disable_device(dev); |
376 | return rc; |
377 | } |
378 | |
379 | static void tifm_7xx1_remove(struct pci_dev *dev) |
380 | { |
381 | struct tifm_adapter *fm = pci_get_drvdata(pdev: dev); |
382 | int cnt; |
383 | |
384 | fm->eject = tifm_7xx1_dummy_eject; |
385 | fm->has_ms_pif = tifm_7xx1_dummy_has_ms_pif; |
386 | writel(TIFM_IRQ_SETALL, addr: fm->addr + FM_CLEAR_INTERRUPT_ENABLE); |
387 | free_irq(dev->irq, fm); |
388 | |
389 | tifm_remove_adapter(fm); |
390 | |
391 | for (cnt = 0; cnt < fm->num_sockets; cnt++) |
392 | tifm_7xx1_sock_power_off(sock_addr: tifm_7xx1_sock_addr(base_addr: fm->addr, sock_num: cnt)); |
393 | |
394 | iounmap(addr: fm->addr); |
395 | pci_intx(dev, enable: 0); |
396 | pci_release_regions(dev); |
397 | |
398 | pci_disable_device(dev); |
399 | tifm_free_adapter(fm); |
400 | } |
401 | |
402 | static const struct pci_device_id tifm_7xx1_pci_tbl[] = { |
403 | { PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_XX21_XX11_FM, PCI_ANY_ID, |
404 | PCI_ANY_ID, 0, 0, 0 }, /* xx21 - the one I have */ |
405 | { PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_XX12_FM, PCI_ANY_ID, |
406 | PCI_ANY_ID, 0, 0, 0 }, |
407 | { PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_XX20_FM, PCI_ANY_ID, |
408 | PCI_ANY_ID, 0, 0, 0 }, |
409 | { } |
410 | }; |
411 | |
412 | static SIMPLE_DEV_PM_OPS(tifm_7xx1_pm_ops, tifm_7xx1_suspend, tifm_7xx1_resume); |
413 | |
414 | static struct pci_driver tifm_7xx1_driver = { |
415 | .name = DRIVER_NAME, |
416 | .id_table = tifm_7xx1_pci_tbl, |
417 | .probe = tifm_7xx1_probe, |
418 | .remove = tifm_7xx1_remove, |
419 | .driver.pm = &tifm_7xx1_pm_ops, |
420 | }; |
421 | |
422 | module_pci_driver(tifm_7xx1_driver); |
423 | MODULE_AUTHOR("Alex Dubov" ); |
424 | MODULE_DESCRIPTION("TI FlashMedia host driver" ); |
425 | MODULE_LICENSE("GPL" ); |
426 | MODULE_DEVICE_TABLE(pci, tifm_7xx1_pci_tbl); |
427 | MODULE_VERSION(DRIVER_VERSION); |
428 | |