1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * IPWireless 3G PCMCIA Network Driver |
4 | * |
5 | * Original code |
6 | * by Stephen Blackheath <stephen@blacksapphire.com>, |
7 | * Ben Martel <benm@symmetric.co.nz> |
8 | * |
9 | * Copyrighted as follows: |
10 | * Copyright (C) 2004 by Symmetric Systems Ltd (NZ) |
11 | * |
12 | * Various driver changes and rewrites, port to new kernels |
13 | * Copyright (C) 2006-2007 Jiri Kosina |
14 | * |
15 | * Misc code cleanups and updates |
16 | * Copyright (C) 2007 David Sterba |
17 | */ |
18 | |
19 | #include "hardware.h" |
20 | #include "network.h" |
21 | #include "main.h" |
22 | #include "tty.h" |
23 | |
24 | #include <linux/delay.h> |
25 | #include <linux/init.h> |
26 | #include <linux/io.h> |
27 | #include <linux/kernel.h> |
28 | #include <linux/module.h> |
29 | #include <linux/sched.h> |
30 | #include <linux/slab.h> |
31 | |
32 | #include <pcmcia/cisreg.h> |
33 | #include <pcmcia/device_id.h> |
34 | #include <pcmcia/ss.h> |
35 | #include <pcmcia/ds.h> |
36 | |
37 | static const struct pcmcia_device_id ipw_ids[] = { |
38 | PCMCIA_DEVICE_MANF_CARD(0x02f2, 0x0100), |
39 | PCMCIA_DEVICE_MANF_CARD(0x02f2, 0x0200), |
40 | PCMCIA_DEVICE_NULL |
41 | }; |
42 | MODULE_DEVICE_TABLE(pcmcia, ipw_ids); |
43 | |
44 | static void ipwireless_detach(struct pcmcia_device *link); |
45 | |
46 | /* |
47 | * Module params |
48 | */ |
49 | /* Debug mode: more verbose, print sent/recv bytes */ |
50 | int ipwireless_debug; |
51 | int ipwireless_loopback; |
52 | int ipwireless_out_queue = 10; |
53 | |
54 | module_param_named(debug, ipwireless_debug, int, 0); |
55 | module_param_named(loopback, ipwireless_loopback, int, 0); |
56 | module_param_named(out_queue, ipwireless_out_queue, int, 0); |
57 | MODULE_PARM_DESC(debug, "switch on debug messages [0]" ); |
58 | MODULE_PARM_DESC(loopback, |
59 | "debug: enable ras_raw channel [0]" ); |
60 | MODULE_PARM_DESC(out_queue, "debug: set size of outgoing PPP queue [10]" ); |
61 | |
62 | /* Executes in process context. */ |
63 | static void signalled_reboot_work(struct work_struct *work_reboot) |
64 | { |
65 | struct ipw_dev *ipw = container_of(work_reboot, struct ipw_dev, |
66 | work_reboot); |
67 | struct pcmcia_device *link = ipw->link; |
68 | pcmcia_reset_card(skt: link->socket); |
69 | } |
70 | |
71 | static void signalled_reboot_callback(void *callback_data) |
72 | { |
73 | struct ipw_dev *ipw = (struct ipw_dev *) callback_data; |
74 | |
75 | /* Delegate to process context. */ |
76 | schedule_work(work: &ipw->work_reboot); |
77 | } |
78 | |
79 | static int ipwireless_probe(struct pcmcia_device *p_dev, void *priv_data) |
80 | { |
81 | struct ipw_dev *ipw = priv_data; |
82 | int ret; |
83 | |
84 | p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH; |
85 | p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_AUTO; |
86 | |
87 | /* 0x40 causes it to generate level mode interrupts. */ |
88 | /* 0x04 enables IREQ pin. */ |
89 | p_dev->config_index |= 0x44; |
90 | p_dev->io_lines = 16; |
91 | ret = pcmcia_request_io(p_dev); |
92 | if (ret) |
93 | return ret; |
94 | |
95 | if (!request_region(p_dev->resource[0]->start, |
96 | resource_size(p_dev->resource[0]), |
97 | IPWIRELESS_PCCARD_NAME)) { |
98 | ret = -EBUSY; |
99 | goto exit; |
100 | } |
101 | |
102 | p_dev->resource[2]->flags |= |
103 | WIN_DATA_WIDTH_16 | WIN_MEMORY_TYPE_CM | WIN_ENABLE; |
104 | |
105 | ret = pcmcia_request_window(p_dev, res: p_dev->resource[2], speed: 0); |
106 | if (ret != 0) |
107 | goto exit1; |
108 | |
109 | ret = pcmcia_map_mem_page(p_dev, res: p_dev->resource[2], offset: p_dev->card_addr); |
110 | if (ret != 0) |
111 | goto exit1; |
112 | |
113 | ipw->is_v2_card = resource_size(res: p_dev->resource[2]) == 0x100; |
114 | |
115 | ipw->common_memory = ioremap(offset: p_dev->resource[2]->start, |
116 | size: resource_size(res: p_dev->resource[2])); |
117 | if (!ipw->common_memory) { |
118 | ret = -ENOMEM; |
119 | goto exit1; |
120 | } |
121 | if (!request_mem_region(p_dev->resource[2]->start, |
122 | resource_size(p_dev->resource[2]), |
123 | IPWIRELESS_PCCARD_NAME)) { |
124 | ret = -EBUSY; |
125 | goto exit2; |
126 | } |
127 | |
128 | p_dev->resource[3]->flags |= WIN_DATA_WIDTH_16 | WIN_MEMORY_TYPE_AM | |
129 | WIN_ENABLE; |
130 | p_dev->resource[3]->end = 0; /* this used to be 0x1000 */ |
131 | ret = pcmcia_request_window(p_dev, res: p_dev->resource[3], speed: 0); |
132 | if (ret != 0) |
133 | goto exit3; |
134 | |
135 | ret = pcmcia_map_mem_page(p_dev, res: p_dev->resource[3], offset: 0); |
136 | if (ret != 0) |
137 | goto exit3; |
138 | |
139 | ipw->attr_memory = ioremap(offset: p_dev->resource[3]->start, |
140 | size: resource_size(res: p_dev->resource[3])); |
141 | if (!ipw->attr_memory) { |
142 | ret = -ENOMEM; |
143 | goto exit3; |
144 | } |
145 | if (!request_mem_region(p_dev->resource[3]->start, |
146 | resource_size(p_dev->resource[3]), |
147 | IPWIRELESS_PCCARD_NAME)) { |
148 | ret = -EBUSY; |
149 | goto exit4; |
150 | } |
151 | |
152 | return 0; |
153 | |
154 | exit4: |
155 | iounmap(addr: ipw->attr_memory); |
156 | exit3: |
157 | release_mem_region(p_dev->resource[2]->start, |
158 | resource_size(p_dev->resource[2])); |
159 | exit2: |
160 | iounmap(addr: ipw->common_memory); |
161 | exit1: |
162 | release_region(p_dev->resource[0]->start, |
163 | resource_size(p_dev->resource[0])); |
164 | exit: |
165 | pcmcia_disable_device(p_dev); |
166 | return ret; |
167 | } |
168 | |
169 | static int config_ipwireless(struct ipw_dev *ipw) |
170 | { |
171 | struct pcmcia_device *link = ipw->link; |
172 | int ret = 0; |
173 | |
174 | ipw->is_v2_card = 0; |
175 | link->config_flags |= CONF_AUTO_SET_IO | CONF_AUTO_SET_IOMEM | |
176 | CONF_ENABLE_IRQ; |
177 | |
178 | ret = pcmcia_loop_config(p_dev: link, conf_check: ipwireless_probe, priv_data: ipw); |
179 | if (ret != 0) |
180 | return ret; |
181 | |
182 | INIT_WORK(&ipw->work_reboot, signalled_reboot_work); |
183 | |
184 | ipwireless_init_hardware_v1(hw: ipw->hardware, base_port: link->resource[0]->start, |
185 | attr_memory: ipw->attr_memory, common_memory: ipw->common_memory, |
186 | is_v2_card: ipw->is_v2_card, reboot_cb: signalled_reboot_callback, |
187 | reboot_cb_data: ipw); |
188 | |
189 | ret = pcmcia_request_irq(p_dev: link, handler: ipwireless_interrupt); |
190 | if (ret != 0) |
191 | goto exit; |
192 | |
193 | printk(KERN_INFO IPWIRELESS_PCCARD_NAME ": Card type %s\n" , |
194 | ipw->is_v2_card ? "V2/V3" : "V1" ); |
195 | printk(KERN_INFO IPWIRELESS_PCCARD_NAME |
196 | ": I/O ports %pR, irq %d\n" , link->resource[0], |
197 | (unsigned int) link->irq); |
198 | if (ipw->attr_memory && ipw->common_memory) |
199 | printk(KERN_INFO IPWIRELESS_PCCARD_NAME |
200 | ": attr memory %pR, common memory %pR\n" , |
201 | link->resource[3], |
202 | link->resource[2]); |
203 | |
204 | ipw->network = ipwireless_network_create(hw: ipw->hardware); |
205 | if (!ipw->network) |
206 | goto exit; |
207 | |
208 | ipw->tty = ipwireless_tty_create(hw: ipw->hardware, net: ipw->network); |
209 | if (!ipw->tty) |
210 | goto exit; |
211 | |
212 | ipwireless_init_hardware_v2_v3(hw: ipw->hardware); |
213 | |
214 | /* |
215 | * Do the RequestConfiguration last, because it enables interrupts. |
216 | * Then we don't get any interrupts before we're ready for them. |
217 | */ |
218 | ret = pcmcia_enable_device(p_dev: link); |
219 | if (ret != 0) |
220 | goto exit; |
221 | |
222 | return 0; |
223 | |
224 | exit: |
225 | if (ipw->common_memory) { |
226 | release_mem_region(link->resource[2]->start, |
227 | resource_size(link->resource[2])); |
228 | iounmap(addr: ipw->common_memory); |
229 | } |
230 | if (ipw->attr_memory) { |
231 | release_mem_region(link->resource[3]->start, |
232 | resource_size(link->resource[3])); |
233 | iounmap(addr: ipw->attr_memory); |
234 | } |
235 | pcmcia_disable_device(p_dev: link); |
236 | return -1; |
237 | } |
238 | |
239 | static void release_ipwireless(struct ipw_dev *ipw) |
240 | { |
241 | release_region(ipw->link->resource[0]->start, |
242 | resource_size(ipw->link->resource[0])); |
243 | if (ipw->common_memory) { |
244 | release_mem_region(ipw->link->resource[2]->start, |
245 | resource_size(ipw->link->resource[2])); |
246 | iounmap(addr: ipw->common_memory); |
247 | } |
248 | if (ipw->attr_memory) { |
249 | release_mem_region(ipw->link->resource[3]->start, |
250 | resource_size(ipw->link->resource[3])); |
251 | iounmap(addr: ipw->attr_memory); |
252 | } |
253 | pcmcia_disable_device(p_dev: ipw->link); |
254 | } |
255 | |
256 | /* |
257 | * ipwireless_attach() creates an "instance" of the driver, allocating |
258 | * local data structures for one device (one interface). The device |
259 | * is registered with Card Services. |
260 | * |
261 | * The pcmcia_device structure is initialized, but we don't actually |
262 | * configure the card at this point -- we wait until we receive a |
263 | * card insertion event. |
264 | */ |
265 | static int ipwireless_attach(struct pcmcia_device *link) |
266 | { |
267 | struct ipw_dev *ipw; |
268 | int ret; |
269 | |
270 | ipw = kzalloc(size: sizeof(struct ipw_dev), GFP_KERNEL); |
271 | if (!ipw) |
272 | return -ENOMEM; |
273 | |
274 | ipw->link = link; |
275 | link->priv = ipw; |
276 | |
277 | ipw->hardware = ipwireless_hardware_create(); |
278 | if (!ipw->hardware) { |
279 | kfree(objp: ipw); |
280 | return -ENOMEM; |
281 | } |
282 | /* RegisterClient will call config_ipwireless */ |
283 | |
284 | ret = config_ipwireless(ipw); |
285 | |
286 | if (ret != 0) { |
287 | ipwireless_detach(link); |
288 | return ret; |
289 | } |
290 | |
291 | return 0; |
292 | } |
293 | |
294 | /* |
295 | * This deletes a driver "instance". The device is de-registered with |
296 | * Card Services. If it has been released, all local data structures |
297 | * are freed. Otherwise, the structures will be freed when the device |
298 | * is released. |
299 | */ |
300 | static void ipwireless_detach(struct pcmcia_device *link) |
301 | { |
302 | struct ipw_dev *ipw = link->priv; |
303 | |
304 | release_ipwireless(ipw); |
305 | |
306 | if (ipw->tty != NULL) |
307 | ipwireless_tty_free(tty: ipw->tty); |
308 | if (ipw->network != NULL) |
309 | ipwireless_network_free(net: ipw->network); |
310 | if (ipw->hardware != NULL) |
311 | ipwireless_hardware_free(hw: ipw->hardware); |
312 | kfree(objp: ipw); |
313 | } |
314 | |
315 | static struct pcmcia_driver me = { |
316 | .owner = THIS_MODULE, |
317 | .probe = ipwireless_attach, |
318 | .remove = ipwireless_detach, |
319 | .name = IPWIRELESS_PCCARD_NAME, |
320 | .id_table = ipw_ids |
321 | }; |
322 | |
323 | /* |
324 | * Module insertion : initialisation of the module. |
325 | * Register the card with cardmgr... |
326 | */ |
327 | static int __init init_ipwireless(void) |
328 | { |
329 | int ret; |
330 | |
331 | ret = ipwireless_tty_init(); |
332 | if (ret != 0) |
333 | return ret; |
334 | |
335 | ret = pcmcia_register_driver(driver: &me); |
336 | if (ret != 0) |
337 | ipwireless_tty_release(); |
338 | |
339 | return ret; |
340 | } |
341 | |
342 | /* |
343 | * Module removal |
344 | */ |
345 | static void __exit exit_ipwireless(void) |
346 | { |
347 | pcmcia_unregister_driver(driver: &me); |
348 | ipwireless_tty_release(); |
349 | } |
350 | |
351 | module_init(init_ipwireless); |
352 | module_exit(exit_ipwireless); |
353 | |
354 | MODULE_AUTHOR(IPWIRELESS_PCMCIA_AUTHOR); |
355 | MODULE_DESCRIPTION(IPWIRELESS_PCCARD_NAME " " IPWIRELESS_PCMCIA_VERSION); |
356 | MODULE_LICENSE("GPL" ); |
357 | |