diff options
Diffstat (limited to 'drivers/usb/host/sl811_cs.c')
-rw-r--r-- | drivers/usb/host/sl811_cs.c | 442 |
1 files changed, 442 insertions, 0 deletions
diff --git a/drivers/usb/host/sl811_cs.c b/drivers/usb/host/sl811_cs.c new file mode 100644 index 000000000000..6e173265095c --- /dev/null +++ b/drivers/usb/host/sl811_cs.c | |||
@@ -0,0 +1,442 @@ | |||
1 | /* | ||
2 | * PCMCIA driver for SL811HS (as found in REX-CFU1U) | ||
3 | * Filename: sl811_cs.c | ||
4 | * Author: Yukio Yamamoto | ||
5 | * | ||
6 | * Port to sl811-hcd and 2.6.x by | ||
7 | * Botond Botyanszki <boti@rocketmail.com> | ||
8 | * Simon Pickering | ||
9 | * | ||
10 | * Last update: 2005-05-12 | ||
11 | */ | ||
12 | |||
13 | #include <linux/kernel.h> | ||
14 | #include <linux/module.h> | ||
15 | #include <linux/init.h> | ||
16 | #include <linux/sched.h> | ||
17 | #include <linux/ptrace.h> | ||
18 | #include <linux/slab.h> | ||
19 | #include <linux/string.h> | ||
20 | #include <linux/timer.h> | ||
21 | #include <linux/ioport.h> | ||
22 | |||
23 | #include <pcmcia/version.h> | ||
24 | #include <pcmcia/cs_types.h> | ||
25 | #include <pcmcia/cs.h> | ||
26 | #include <pcmcia/cistpl.h> | ||
27 | #include <pcmcia/cisreg.h> | ||
28 | #include <pcmcia/ds.h> | ||
29 | |||
30 | #include <linux/usb_sl811.h> | ||
31 | |||
32 | MODULE_AUTHOR("Botond Botyanszki"); | ||
33 | MODULE_DESCRIPTION("REX-CFU1U PCMCIA driver for 2.6"); | ||
34 | MODULE_LICENSE("GPL"); | ||
35 | |||
36 | |||
37 | /*====================================================================*/ | ||
38 | /* MACROS */ | ||
39 | /*====================================================================*/ | ||
40 | |||
41 | #if defined(DEBUG) || defined(CONFIG_USB_DEBUG) || defined(PCMCIA_DEBUG) | ||
42 | |||
43 | static int pc_debug = 0; | ||
44 | module_param(pc_debug, int, 0644); | ||
45 | |||
46 | #define DBG(n, args...) if (pc_debug>(n)) printk(KERN_DEBUG "sl811_cs: " args) | ||
47 | |||
48 | #else | ||
49 | #define DBG(n, args...) do{}while(0) | ||
50 | #endif /* no debugging */ | ||
51 | |||
52 | #define INFO(args...) printk(KERN_INFO "sl811_cs: " args) | ||
53 | |||
54 | #define INT_MODULE_PARM(n, v) static int n = v; module_param(n, int, 0444) | ||
55 | |||
56 | #define CS_CHECK(fn, ret) \ | ||
57 | do { \ | ||
58 | last_fn = (fn); \ | ||
59 | if ((last_ret = (ret)) != 0) \ | ||
60 | goto cs_failed; \ | ||
61 | } while (0) | ||
62 | |||
63 | /*====================================================================*/ | ||
64 | /* VARIABLES */ | ||
65 | /*====================================================================*/ | ||
66 | |||
67 | static const char driver_name[DEV_NAME_LEN] = "sl811_cs"; | ||
68 | |||
69 | static dev_link_t *dev_list = NULL; | ||
70 | |||
71 | static int irq_list[4] = { -1 }; | ||
72 | static int irq_list_count; | ||
73 | |||
74 | module_param_array(irq_list, int, &irq_list_count, 0444); | ||
75 | |||
76 | INT_MODULE_PARM(irq_mask, 0xdeb8); | ||
77 | |||
78 | typedef struct local_info_t { | ||
79 | dev_link_t link; | ||
80 | dev_node_t node; | ||
81 | } local_info_t; | ||
82 | |||
83 | /*====================================================================*/ | ||
84 | |||
85 | static void release_platform_dev(struct device * dev) | ||
86 | { | ||
87 | DBG(0, "sl811_cs platform_dev release\n"); | ||
88 | dev->parent = NULL; | ||
89 | } | ||
90 | |||
91 | static struct sl811_platform_data platform_data = { | ||
92 | .potpg = 100, | ||
93 | .power = 50, /* == 100mA */ | ||
94 | // .reset = ... FIXME: invoke CF reset on the card | ||
95 | }; | ||
96 | |||
97 | static struct resource resources[] = { | ||
98 | [0] = { | ||
99 | .flags = IORESOURCE_IRQ, | ||
100 | }, | ||
101 | [1] = { | ||
102 | // .name = "address", | ||
103 | .flags = IORESOURCE_IO, | ||
104 | }, | ||
105 | [2] = { | ||
106 | // .name = "data", | ||
107 | .flags = IORESOURCE_IO, | ||
108 | }, | ||
109 | }; | ||
110 | |||
111 | extern struct device_driver sl811h_driver; | ||
112 | |||
113 | static struct platform_device platform_dev = { | ||
114 | .id = -1, | ||
115 | .dev = { | ||
116 | .platform_data = &platform_data, | ||
117 | .release = release_platform_dev, | ||
118 | }, | ||
119 | .resource = resources, | ||
120 | .num_resources = ARRAY_SIZE(resources), | ||
121 | }; | ||
122 | |||
123 | static int sl811_hc_init(struct device *parent, ioaddr_t base_addr, int irq) | ||
124 | { | ||
125 | if (platform_dev.dev.parent) | ||
126 | return -EBUSY; | ||
127 | platform_dev.dev.parent = parent; | ||
128 | |||
129 | /* finish seting up the platform device */ | ||
130 | resources[0].start = irq; | ||
131 | |||
132 | resources[1].start = base_addr; | ||
133 | resources[1].end = base_addr; | ||
134 | |||
135 | resources[2].start = base_addr + 1; | ||
136 | resources[2].end = base_addr + 1; | ||
137 | |||
138 | /* The driver core will probe for us. We know sl811-hcd has been | ||
139 | * initialized already because of the link order dependency. | ||
140 | */ | ||
141 | platform_dev.name = sl811h_driver.name; | ||
142 | return platform_device_register(&platform_dev); | ||
143 | } | ||
144 | |||
145 | /*====================================================================*/ | ||
146 | |||
147 | static void sl811_cs_detach(dev_link_t *link) | ||
148 | { | ||
149 | dev_link_t **linkp; | ||
150 | |||
151 | DBG(0, "sl811_cs_detach(0x%p)\n", link); | ||
152 | |||
153 | /* Locate device structure */ | ||
154 | for (linkp = &dev_list; *linkp; linkp = &(*linkp)->next) { | ||
155 | if (*linkp == link) | ||
156 | break; | ||
157 | } | ||
158 | if (*linkp == NULL) | ||
159 | return; | ||
160 | |||
161 | /* Break the link with Card Services */ | ||
162 | if (link->handle) | ||
163 | pcmcia_deregister_client(link->handle); | ||
164 | |||
165 | /* Unlink device structure, and free it */ | ||
166 | *linkp = link->next; | ||
167 | /* This points to the parent local_info_t struct */ | ||
168 | kfree(link->priv); | ||
169 | } | ||
170 | |||
171 | static void sl811_cs_release(dev_link_t * link) | ||
172 | { | ||
173 | |||
174 | DBG(0, "sl811_cs_release(0x%p)\n", link); | ||
175 | |||
176 | if (link->open) { | ||
177 | DBG(1, "sl811_cs: release postponed, '%s' still open\n", | ||
178 | link->dev->dev_name); | ||
179 | link->state |= DEV_STALE_CONFIG; | ||
180 | return; | ||
181 | } | ||
182 | |||
183 | /* Unlink the device chain */ | ||
184 | link->dev = NULL; | ||
185 | |||
186 | platform_device_unregister(&platform_dev); | ||
187 | pcmcia_release_configuration(link->handle); | ||
188 | if (link->io.NumPorts1) | ||
189 | pcmcia_release_io(link->handle, &link->io); | ||
190 | if (link->irq.AssignedIRQ) | ||
191 | pcmcia_release_irq(link->handle, &link->irq); | ||
192 | link->state &= ~DEV_CONFIG; | ||
193 | |||
194 | if (link->state & DEV_STALE_LINK) | ||
195 | sl811_cs_detach(link); | ||
196 | } | ||
197 | |||
198 | static void sl811_cs_config(dev_link_t *link) | ||
199 | { | ||
200 | client_handle_t handle = link->handle; | ||
201 | struct device *parent = &handle_to_dev(handle); | ||
202 | local_info_t *dev = link->priv; | ||
203 | tuple_t tuple; | ||
204 | cisparse_t parse; | ||
205 | int last_fn, last_ret; | ||
206 | u_char buf[64]; | ||
207 | config_info_t conf; | ||
208 | cistpl_cftable_entry_t dflt = { 0 }; | ||
209 | |||
210 | DBG(0, "sl811_cs_config(0x%p)\n", link); | ||
211 | |||
212 | tuple.DesiredTuple = CISTPL_CONFIG; | ||
213 | tuple.Attributes = 0; | ||
214 | tuple.TupleData = buf; | ||
215 | tuple.TupleDataMax = sizeof(buf); | ||
216 | tuple.TupleOffset = 0; | ||
217 | CS_CHECK(GetFirstTuple, pcmcia_get_first_tuple(handle, &tuple)); | ||
218 | CS_CHECK(GetTupleData, pcmcia_get_tuple_data(handle, &tuple)); | ||
219 | CS_CHECK(ParseTuple, pcmcia_parse_tuple(handle, &tuple, &parse)); | ||
220 | link->conf.ConfigBase = parse.config.base; | ||
221 | link->conf.Present = parse.config.rmask[0]; | ||
222 | |||
223 | /* Configure card */ | ||
224 | link->state |= DEV_CONFIG; | ||
225 | |||
226 | /* Look up the current Vcc */ | ||
227 | CS_CHECK(GetConfigurationInfo, | ||
228 | pcmcia_get_configuration_info(handle, &conf)); | ||
229 | link->conf.Vcc = conf.Vcc; | ||
230 | |||
231 | tuple.DesiredTuple = CISTPL_CFTABLE_ENTRY; | ||
232 | CS_CHECK(GetFirstTuple, pcmcia_get_first_tuple(handle, &tuple)); | ||
233 | while (1) { | ||
234 | cistpl_cftable_entry_t *cfg = &(parse.cftable_entry); | ||
235 | |||
236 | if (pcmcia_get_tuple_data(handle, &tuple) != 0 | ||
237 | || pcmcia_parse_tuple(handle, &tuple, &parse) | ||
238 | != 0) | ||
239 | goto next_entry; | ||
240 | |||
241 | if (cfg->flags & CISTPL_CFTABLE_DEFAULT) { | ||
242 | dflt = *cfg; | ||
243 | } | ||
244 | |||
245 | if (cfg->index == 0) | ||
246 | goto next_entry; | ||
247 | |||
248 | link->conf.ConfigIndex = cfg->index; | ||
249 | |||
250 | /* Use power settings for Vcc and Vpp if present */ | ||
251 | /* Note that the CIS values need to be rescaled */ | ||
252 | if (cfg->vcc.present & (1<<CISTPL_POWER_VNOM)) { | ||
253 | if (cfg->vcc.param[CISTPL_POWER_VNOM]/10000 | ||
254 | != conf.Vcc) | ||
255 | goto next_entry; | ||
256 | } else if (dflt.vcc.present & (1<<CISTPL_POWER_VNOM)) { | ||
257 | if (dflt.vcc.param[CISTPL_POWER_VNOM]/10000 | ||
258 | != conf.Vcc) | ||
259 | goto next_entry; | ||
260 | } | ||
261 | |||
262 | if (cfg->vpp1.present & (1<<CISTPL_POWER_VNOM)) | ||
263 | link->conf.Vpp1 = link->conf.Vpp2 = | ||
264 | cfg->vpp1.param[CISTPL_POWER_VNOM]/10000; | ||
265 | else if (dflt.vpp1.present & (1<<CISTPL_POWER_VNOM)) | ||
266 | link->conf.Vpp1 = link->conf.Vpp2 = | ||
267 | dflt.vpp1.param[CISTPL_POWER_VNOM]/10000; | ||
268 | |||
269 | /* we need an interrupt */ | ||
270 | if (cfg->irq.IRQInfo1 || dflt.irq.IRQInfo1) | ||
271 | link->conf.Attributes |= CONF_ENABLE_IRQ; | ||
272 | |||
273 | /* IO window settings */ | ||
274 | link->io.NumPorts1 = link->io.NumPorts2 = 0; | ||
275 | if ((cfg->io.nwin > 0) || (dflt.io.nwin > 0)) { | ||
276 | cistpl_io_t *io = (cfg->io.nwin) ? &cfg->io : &dflt.io; | ||
277 | |||
278 | link->io.Attributes1 = IO_DATA_PATH_WIDTH_8; | ||
279 | link->io.IOAddrLines = io->flags & CISTPL_IO_LINES_MASK; | ||
280 | link->io.BasePort1 = io->win[0].base; | ||
281 | link->io.NumPorts1 = io->win[0].len; | ||
282 | |||
283 | if (pcmcia_request_io(link->handle, &link->io) != 0) | ||
284 | goto next_entry; | ||
285 | } | ||
286 | break; | ||
287 | |||
288 | next_entry: | ||
289 | if (link->io.NumPorts1) | ||
290 | pcmcia_release_io(link->handle, &link->io); | ||
291 | last_ret = pcmcia_get_next_tuple(handle, &tuple); | ||
292 | } | ||
293 | |||
294 | /* require an IRQ and two registers */ | ||
295 | if (!link->io.NumPorts1 || link->io.NumPorts1 < 2) | ||
296 | goto cs_failed; | ||
297 | if (link->conf.Attributes & CONF_ENABLE_IRQ) | ||
298 | CS_CHECK(RequestIRQ, | ||
299 | pcmcia_request_irq(link->handle, &link->irq)); | ||
300 | else | ||
301 | goto cs_failed; | ||
302 | |||
303 | CS_CHECK(RequestConfiguration, | ||
304 | pcmcia_request_configuration(link->handle, &link->conf)); | ||
305 | |||
306 | sprintf(dev->node.dev_name, driver_name); | ||
307 | dev->node.major = dev->node.minor = 0; | ||
308 | link->dev = &dev->node; | ||
309 | |||
310 | printk(KERN_INFO "%s: index 0x%02x: Vcc %d.%d", | ||
311 | dev->node.dev_name, link->conf.ConfigIndex, | ||
312 | link->conf.Vcc/10, link->conf.Vcc%10); | ||
313 | if (link->conf.Vpp1) | ||
314 | printk(", Vpp %d.%d", link->conf.Vpp1/10, link->conf.Vpp1%10); | ||
315 | printk(", irq %d", link->irq.AssignedIRQ); | ||
316 | printk(", io 0x%04x-0x%04x", link->io.BasePort1, | ||
317 | link->io.BasePort1+link->io.NumPorts1-1); | ||
318 | printk("\n"); | ||
319 | |||
320 | link->state &= ~DEV_CONFIG_PENDING; | ||
321 | |||
322 | if (sl811_hc_init(parent, link->io.BasePort1, link->irq.AssignedIRQ) | ||
323 | < 0) { | ||
324 | cs_failed: | ||
325 | printk("sl811_cs_config failed\n"); | ||
326 | cs_error(link->handle, last_fn, last_ret); | ||
327 | sl811_cs_release(link); | ||
328 | link->state &= ~DEV_CONFIG_PENDING; | ||
329 | } | ||
330 | } | ||
331 | |||
332 | static int | ||
333 | sl811_cs_event(event_t event, int priority, event_callback_args_t *args) | ||
334 | { | ||
335 | dev_link_t *link = args->client_data; | ||
336 | |||
337 | DBG(1, "sl811_cs_event(0x%06x)\n", event); | ||
338 | |||
339 | switch (event) { | ||
340 | case CS_EVENT_CARD_REMOVAL: | ||
341 | link->state &= ~DEV_PRESENT; | ||
342 | if (link->state & DEV_CONFIG) | ||
343 | sl811_cs_release(link); | ||
344 | break; | ||
345 | |||
346 | case CS_EVENT_CARD_INSERTION: | ||
347 | link->state |= DEV_PRESENT | DEV_CONFIG_PENDING; | ||
348 | sl811_cs_config(link); | ||
349 | break; | ||
350 | |||
351 | case CS_EVENT_PM_SUSPEND: | ||
352 | link->state |= DEV_SUSPEND; | ||
353 | /* Fall through... */ | ||
354 | case CS_EVENT_RESET_PHYSICAL: | ||
355 | if (link->state & DEV_CONFIG) | ||
356 | pcmcia_release_configuration(link->handle); | ||
357 | break; | ||
358 | |||
359 | case CS_EVENT_PM_RESUME: | ||
360 | link->state &= ~DEV_SUSPEND; | ||
361 | /* Fall through... */ | ||
362 | case CS_EVENT_CARD_RESET: | ||
363 | if (link->state & DEV_CONFIG) | ||
364 | pcmcia_request_configuration(link->handle, &link->conf); | ||
365 | DBG(0, "reset sl811-hcd here?\n"); | ||
366 | break; | ||
367 | } | ||
368 | return 0; | ||
369 | } | ||
370 | |||
371 | static dev_link_t *sl811_cs_attach(void) | ||
372 | { | ||
373 | local_info_t *local; | ||
374 | dev_link_t *link; | ||
375 | client_reg_t client_reg; | ||
376 | int ret, i; | ||
377 | |||
378 | local = kmalloc(sizeof(local_info_t), GFP_KERNEL); | ||
379 | if (!local) | ||
380 | return NULL; | ||
381 | memset(local, 0, sizeof(local_info_t)); | ||
382 | link = &local->link; | ||
383 | link->priv = local; | ||
384 | |||
385 | /* Initialize */ | ||
386 | link->irq.Attributes = IRQ_TYPE_EXCLUSIVE; | ||
387 | link->irq.IRQInfo1 = IRQ_INFO2_VALID|IRQ_LEVEL_ID; | ||
388 | if (irq_list[0] == -1) | ||
389 | link->irq.IRQInfo2 = irq_mask; | ||
390 | else | ||
391 | for (i = 0; i < irq_list_count; i++) | ||
392 | link->irq.IRQInfo2 |= 1 << irq_list[i]; | ||
393 | link->irq.Handler = NULL; | ||
394 | |||
395 | link->conf.Attributes = 0; | ||
396 | link->conf.Vcc = 33; | ||
397 | link->conf.IntType = INT_MEMORY_AND_IO; | ||
398 | |||
399 | /* Register with Card Services */ | ||
400 | link->next = dev_list; | ||
401 | dev_list = link; | ||
402 | client_reg.dev_info = (dev_info_t *) &driver_name; | ||
403 | client_reg.Attributes = INFO_IO_CLIENT | INFO_CARD_SHARE; | ||
404 | client_reg.EventMask = | ||
405 | CS_EVENT_CARD_INSERTION | CS_EVENT_CARD_REMOVAL | | ||
406 | CS_EVENT_RESET_PHYSICAL | CS_EVENT_CARD_RESET | | ||
407 | CS_EVENT_PM_SUSPEND | CS_EVENT_PM_RESUME; | ||
408 | client_reg.event_handler = &sl811_cs_event; | ||
409 | client_reg.Version = 0x0210; | ||
410 | client_reg.event_callback_args.client_data = link; | ||
411 | ret = pcmcia_register_client(&link->handle, &client_reg); | ||
412 | if (ret != CS_SUCCESS) { | ||
413 | cs_error(link->handle, RegisterClient, ret); | ||
414 | sl811_cs_detach(link); | ||
415 | return NULL; | ||
416 | } | ||
417 | |||
418 | return link; | ||
419 | } | ||
420 | |||
421 | static struct pcmcia_driver sl811_cs_driver = { | ||
422 | .owner = THIS_MODULE, | ||
423 | .drv = { | ||
424 | .name = (char *)driver_name, | ||
425 | }, | ||
426 | .attach = sl811_cs_attach, | ||
427 | .detach = sl811_cs_detach, | ||
428 | }; | ||
429 | |||
430 | /*====================================================================*/ | ||
431 | |||
432 | static int __init init_sl811_cs(void) | ||
433 | { | ||
434 | return pcmcia_register_driver(&sl811_cs_driver); | ||
435 | } | ||
436 | module_init(init_sl811_cs); | ||
437 | |||
438 | static void __exit exit_sl811_cs(void) | ||
439 | { | ||
440 | pcmcia_unregister_driver(&sl811_cs_driver); | ||
441 | } | ||
442 | module_exit(exit_sl811_cs); | ||