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