162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * IPWireless 3G PCMCIA Network Driver 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Original code 662306a36Sopenharmony_ci * by Stephen Blackheath <stephen@blacksapphire.com>, 762306a36Sopenharmony_ci * Ben Martel <benm@symmetric.co.nz> 862306a36Sopenharmony_ci * 962306a36Sopenharmony_ci * Copyrighted as follows: 1062306a36Sopenharmony_ci * Copyright (C) 2004 by Symmetric Systems Ltd (NZ) 1162306a36Sopenharmony_ci * 1262306a36Sopenharmony_ci * Various driver changes and rewrites, port to new kernels 1362306a36Sopenharmony_ci * Copyright (C) 2006-2007 Jiri Kosina 1462306a36Sopenharmony_ci * 1562306a36Sopenharmony_ci * Misc code cleanups and updates 1662306a36Sopenharmony_ci * Copyright (C) 2007 David Sterba 1762306a36Sopenharmony_ci */ 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci#include "hardware.h" 2062306a36Sopenharmony_ci#include "network.h" 2162306a36Sopenharmony_ci#include "main.h" 2262306a36Sopenharmony_ci#include "tty.h" 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ci#include <linux/delay.h> 2562306a36Sopenharmony_ci#include <linux/init.h> 2662306a36Sopenharmony_ci#include <linux/io.h> 2762306a36Sopenharmony_ci#include <linux/kernel.h> 2862306a36Sopenharmony_ci#include <linux/module.h> 2962306a36Sopenharmony_ci#include <linux/sched.h> 3062306a36Sopenharmony_ci#include <linux/slab.h> 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_ci#include <pcmcia/cisreg.h> 3362306a36Sopenharmony_ci#include <pcmcia/device_id.h> 3462306a36Sopenharmony_ci#include <pcmcia/ss.h> 3562306a36Sopenharmony_ci#include <pcmcia/ds.h> 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_cistatic const struct pcmcia_device_id ipw_ids[] = { 3862306a36Sopenharmony_ci PCMCIA_DEVICE_MANF_CARD(0x02f2, 0x0100), 3962306a36Sopenharmony_ci PCMCIA_DEVICE_MANF_CARD(0x02f2, 0x0200), 4062306a36Sopenharmony_ci PCMCIA_DEVICE_NULL 4162306a36Sopenharmony_ci}; 4262306a36Sopenharmony_ciMODULE_DEVICE_TABLE(pcmcia, ipw_ids); 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_cistatic void ipwireless_detach(struct pcmcia_device *link); 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ci/* 4762306a36Sopenharmony_ci * Module params 4862306a36Sopenharmony_ci */ 4962306a36Sopenharmony_ci/* Debug mode: more verbose, print sent/recv bytes */ 5062306a36Sopenharmony_ciint ipwireless_debug; 5162306a36Sopenharmony_ciint ipwireless_loopback; 5262306a36Sopenharmony_ciint ipwireless_out_queue = 10; 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_cimodule_param_named(debug, ipwireless_debug, int, 0); 5562306a36Sopenharmony_cimodule_param_named(loopback, ipwireless_loopback, int, 0); 5662306a36Sopenharmony_cimodule_param_named(out_queue, ipwireless_out_queue, int, 0); 5762306a36Sopenharmony_ciMODULE_PARM_DESC(debug, "switch on debug messages [0]"); 5862306a36Sopenharmony_ciMODULE_PARM_DESC(loopback, 5962306a36Sopenharmony_ci "debug: enable ras_raw channel [0]"); 6062306a36Sopenharmony_ciMODULE_PARM_DESC(out_queue, "debug: set size of outgoing PPP queue [10]"); 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci/* Executes in process context. */ 6362306a36Sopenharmony_cistatic void signalled_reboot_work(struct work_struct *work_reboot) 6462306a36Sopenharmony_ci{ 6562306a36Sopenharmony_ci struct ipw_dev *ipw = container_of(work_reboot, struct ipw_dev, 6662306a36Sopenharmony_ci work_reboot); 6762306a36Sopenharmony_ci struct pcmcia_device *link = ipw->link; 6862306a36Sopenharmony_ci pcmcia_reset_card(link->socket); 6962306a36Sopenharmony_ci} 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_cistatic void signalled_reboot_callback(void *callback_data) 7262306a36Sopenharmony_ci{ 7362306a36Sopenharmony_ci struct ipw_dev *ipw = (struct ipw_dev *) callback_data; 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci /* Delegate to process context. */ 7662306a36Sopenharmony_ci schedule_work(&ipw->work_reboot); 7762306a36Sopenharmony_ci} 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_cistatic int ipwireless_probe(struct pcmcia_device *p_dev, void *priv_data) 8062306a36Sopenharmony_ci{ 8162306a36Sopenharmony_ci struct ipw_dev *ipw = priv_data; 8262306a36Sopenharmony_ci int ret; 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH; 8562306a36Sopenharmony_ci p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_AUTO; 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci /* 0x40 causes it to generate level mode interrupts. */ 8862306a36Sopenharmony_ci /* 0x04 enables IREQ pin. */ 8962306a36Sopenharmony_ci p_dev->config_index |= 0x44; 9062306a36Sopenharmony_ci p_dev->io_lines = 16; 9162306a36Sopenharmony_ci ret = pcmcia_request_io(p_dev); 9262306a36Sopenharmony_ci if (ret) 9362306a36Sopenharmony_ci return ret; 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci if (!request_region(p_dev->resource[0]->start, 9662306a36Sopenharmony_ci resource_size(p_dev->resource[0]), 9762306a36Sopenharmony_ci IPWIRELESS_PCCARD_NAME)) { 9862306a36Sopenharmony_ci ret = -EBUSY; 9962306a36Sopenharmony_ci goto exit; 10062306a36Sopenharmony_ci } 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci p_dev->resource[2]->flags |= 10362306a36Sopenharmony_ci WIN_DATA_WIDTH_16 | WIN_MEMORY_TYPE_CM | WIN_ENABLE; 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci ret = pcmcia_request_window(p_dev, p_dev->resource[2], 0); 10662306a36Sopenharmony_ci if (ret != 0) 10762306a36Sopenharmony_ci goto exit1; 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci ret = pcmcia_map_mem_page(p_dev, p_dev->resource[2], p_dev->card_addr); 11062306a36Sopenharmony_ci if (ret != 0) 11162306a36Sopenharmony_ci goto exit1; 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci ipw->is_v2_card = resource_size(p_dev->resource[2]) == 0x100; 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci ipw->common_memory = ioremap(p_dev->resource[2]->start, 11662306a36Sopenharmony_ci resource_size(p_dev->resource[2])); 11762306a36Sopenharmony_ci if (!ipw->common_memory) { 11862306a36Sopenharmony_ci ret = -ENOMEM; 11962306a36Sopenharmony_ci goto exit1; 12062306a36Sopenharmony_ci } 12162306a36Sopenharmony_ci if (!request_mem_region(p_dev->resource[2]->start, 12262306a36Sopenharmony_ci resource_size(p_dev->resource[2]), 12362306a36Sopenharmony_ci IPWIRELESS_PCCARD_NAME)) { 12462306a36Sopenharmony_ci ret = -EBUSY; 12562306a36Sopenharmony_ci goto exit2; 12662306a36Sopenharmony_ci } 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci p_dev->resource[3]->flags |= WIN_DATA_WIDTH_16 | WIN_MEMORY_TYPE_AM | 12962306a36Sopenharmony_ci WIN_ENABLE; 13062306a36Sopenharmony_ci p_dev->resource[3]->end = 0; /* this used to be 0x1000 */ 13162306a36Sopenharmony_ci ret = pcmcia_request_window(p_dev, p_dev->resource[3], 0); 13262306a36Sopenharmony_ci if (ret != 0) 13362306a36Sopenharmony_ci goto exit3; 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci ret = pcmcia_map_mem_page(p_dev, p_dev->resource[3], 0); 13662306a36Sopenharmony_ci if (ret != 0) 13762306a36Sopenharmony_ci goto exit3; 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci ipw->attr_memory = ioremap(p_dev->resource[3]->start, 14062306a36Sopenharmony_ci resource_size(p_dev->resource[3])); 14162306a36Sopenharmony_ci if (!ipw->attr_memory) { 14262306a36Sopenharmony_ci ret = -ENOMEM; 14362306a36Sopenharmony_ci goto exit3; 14462306a36Sopenharmony_ci } 14562306a36Sopenharmony_ci if (!request_mem_region(p_dev->resource[3]->start, 14662306a36Sopenharmony_ci resource_size(p_dev->resource[3]), 14762306a36Sopenharmony_ci IPWIRELESS_PCCARD_NAME)) { 14862306a36Sopenharmony_ci ret = -EBUSY; 14962306a36Sopenharmony_ci goto exit4; 15062306a36Sopenharmony_ci } 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci return 0; 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ciexit4: 15562306a36Sopenharmony_ci iounmap(ipw->attr_memory); 15662306a36Sopenharmony_ciexit3: 15762306a36Sopenharmony_ci release_mem_region(p_dev->resource[2]->start, 15862306a36Sopenharmony_ci resource_size(p_dev->resource[2])); 15962306a36Sopenharmony_ciexit2: 16062306a36Sopenharmony_ci iounmap(ipw->common_memory); 16162306a36Sopenharmony_ciexit1: 16262306a36Sopenharmony_ci release_region(p_dev->resource[0]->start, 16362306a36Sopenharmony_ci resource_size(p_dev->resource[0])); 16462306a36Sopenharmony_ciexit: 16562306a36Sopenharmony_ci pcmcia_disable_device(p_dev); 16662306a36Sopenharmony_ci return ret; 16762306a36Sopenharmony_ci} 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_cistatic int config_ipwireless(struct ipw_dev *ipw) 17062306a36Sopenharmony_ci{ 17162306a36Sopenharmony_ci struct pcmcia_device *link = ipw->link; 17262306a36Sopenharmony_ci int ret = 0; 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci ipw->is_v2_card = 0; 17562306a36Sopenharmony_ci link->config_flags |= CONF_AUTO_SET_IO | CONF_AUTO_SET_IOMEM | 17662306a36Sopenharmony_ci CONF_ENABLE_IRQ; 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci ret = pcmcia_loop_config(link, ipwireless_probe, ipw); 17962306a36Sopenharmony_ci if (ret != 0) 18062306a36Sopenharmony_ci return ret; 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci INIT_WORK(&ipw->work_reboot, signalled_reboot_work); 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci ipwireless_init_hardware_v1(ipw->hardware, link->resource[0]->start, 18562306a36Sopenharmony_ci ipw->attr_memory, ipw->common_memory, 18662306a36Sopenharmony_ci ipw->is_v2_card, signalled_reboot_callback, 18762306a36Sopenharmony_ci ipw); 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci ret = pcmcia_request_irq(link, ipwireless_interrupt); 19062306a36Sopenharmony_ci if (ret != 0) 19162306a36Sopenharmony_ci goto exit; 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci printk(KERN_INFO IPWIRELESS_PCCARD_NAME ": Card type %s\n", 19462306a36Sopenharmony_ci ipw->is_v2_card ? "V2/V3" : "V1"); 19562306a36Sopenharmony_ci printk(KERN_INFO IPWIRELESS_PCCARD_NAME 19662306a36Sopenharmony_ci ": I/O ports %pR, irq %d\n", link->resource[0], 19762306a36Sopenharmony_ci (unsigned int) link->irq); 19862306a36Sopenharmony_ci if (ipw->attr_memory && ipw->common_memory) 19962306a36Sopenharmony_ci printk(KERN_INFO IPWIRELESS_PCCARD_NAME 20062306a36Sopenharmony_ci ": attr memory %pR, common memory %pR\n", 20162306a36Sopenharmony_ci link->resource[3], 20262306a36Sopenharmony_ci link->resource[2]); 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci ipw->network = ipwireless_network_create(ipw->hardware); 20562306a36Sopenharmony_ci if (!ipw->network) 20662306a36Sopenharmony_ci goto exit; 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_ci ipw->tty = ipwireless_tty_create(ipw->hardware, ipw->network); 20962306a36Sopenharmony_ci if (!ipw->tty) 21062306a36Sopenharmony_ci goto exit; 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci ipwireless_init_hardware_v2_v3(ipw->hardware); 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_ci /* 21562306a36Sopenharmony_ci * Do the RequestConfiguration last, because it enables interrupts. 21662306a36Sopenharmony_ci * Then we don't get any interrupts before we're ready for them. 21762306a36Sopenharmony_ci */ 21862306a36Sopenharmony_ci ret = pcmcia_enable_device(link); 21962306a36Sopenharmony_ci if (ret != 0) 22062306a36Sopenharmony_ci goto exit; 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci return 0; 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_ciexit: 22562306a36Sopenharmony_ci if (ipw->common_memory) { 22662306a36Sopenharmony_ci release_mem_region(link->resource[2]->start, 22762306a36Sopenharmony_ci resource_size(link->resource[2])); 22862306a36Sopenharmony_ci iounmap(ipw->common_memory); 22962306a36Sopenharmony_ci } 23062306a36Sopenharmony_ci if (ipw->attr_memory) { 23162306a36Sopenharmony_ci release_mem_region(link->resource[3]->start, 23262306a36Sopenharmony_ci resource_size(link->resource[3])); 23362306a36Sopenharmony_ci iounmap(ipw->attr_memory); 23462306a36Sopenharmony_ci } 23562306a36Sopenharmony_ci pcmcia_disable_device(link); 23662306a36Sopenharmony_ci return -1; 23762306a36Sopenharmony_ci} 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_cistatic void release_ipwireless(struct ipw_dev *ipw) 24062306a36Sopenharmony_ci{ 24162306a36Sopenharmony_ci release_region(ipw->link->resource[0]->start, 24262306a36Sopenharmony_ci resource_size(ipw->link->resource[0])); 24362306a36Sopenharmony_ci if (ipw->common_memory) { 24462306a36Sopenharmony_ci release_mem_region(ipw->link->resource[2]->start, 24562306a36Sopenharmony_ci resource_size(ipw->link->resource[2])); 24662306a36Sopenharmony_ci iounmap(ipw->common_memory); 24762306a36Sopenharmony_ci } 24862306a36Sopenharmony_ci if (ipw->attr_memory) { 24962306a36Sopenharmony_ci release_mem_region(ipw->link->resource[3]->start, 25062306a36Sopenharmony_ci resource_size(ipw->link->resource[3])); 25162306a36Sopenharmony_ci iounmap(ipw->attr_memory); 25262306a36Sopenharmony_ci } 25362306a36Sopenharmony_ci pcmcia_disable_device(ipw->link); 25462306a36Sopenharmony_ci} 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci/* 25762306a36Sopenharmony_ci * ipwireless_attach() creates an "instance" of the driver, allocating 25862306a36Sopenharmony_ci * local data structures for one device (one interface). The device 25962306a36Sopenharmony_ci * is registered with Card Services. 26062306a36Sopenharmony_ci * 26162306a36Sopenharmony_ci * The pcmcia_device structure is initialized, but we don't actually 26262306a36Sopenharmony_ci * configure the card at this point -- we wait until we receive a 26362306a36Sopenharmony_ci * card insertion event. 26462306a36Sopenharmony_ci */ 26562306a36Sopenharmony_cistatic int ipwireless_attach(struct pcmcia_device *link) 26662306a36Sopenharmony_ci{ 26762306a36Sopenharmony_ci struct ipw_dev *ipw; 26862306a36Sopenharmony_ci int ret; 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci ipw = kzalloc(sizeof(struct ipw_dev), GFP_KERNEL); 27162306a36Sopenharmony_ci if (!ipw) 27262306a36Sopenharmony_ci return -ENOMEM; 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_ci ipw->link = link; 27562306a36Sopenharmony_ci link->priv = ipw; 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_ci ipw->hardware = ipwireless_hardware_create(); 27862306a36Sopenharmony_ci if (!ipw->hardware) { 27962306a36Sopenharmony_ci kfree(ipw); 28062306a36Sopenharmony_ci return -ENOMEM; 28162306a36Sopenharmony_ci } 28262306a36Sopenharmony_ci /* RegisterClient will call config_ipwireless */ 28362306a36Sopenharmony_ci 28462306a36Sopenharmony_ci ret = config_ipwireless(ipw); 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_ci if (ret != 0) { 28762306a36Sopenharmony_ci ipwireless_detach(link); 28862306a36Sopenharmony_ci return ret; 28962306a36Sopenharmony_ci } 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_ci return 0; 29262306a36Sopenharmony_ci} 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_ci/* 29562306a36Sopenharmony_ci * This deletes a driver "instance". The device is de-registered with 29662306a36Sopenharmony_ci * Card Services. If it has been released, all local data structures 29762306a36Sopenharmony_ci * are freed. Otherwise, the structures will be freed when the device 29862306a36Sopenharmony_ci * is released. 29962306a36Sopenharmony_ci */ 30062306a36Sopenharmony_cistatic void ipwireless_detach(struct pcmcia_device *link) 30162306a36Sopenharmony_ci{ 30262306a36Sopenharmony_ci struct ipw_dev *ipw = link->priv; 30362306a36Sopenharmony_ci 30462306a36Sopenharmony_ci release_ipwireless(ipw); 30562306a36Sopenharmony_ci 30662306a36Sopenharmony_ci if (ipw->tty != NULL) 30762306a36Sopenharmony_ci ipwireless_tty_free(ipw->tty); 30862306a36Sopenharmony_ci if (ipw->network != NULL) 30962306a36Sopenharmony_ci ipwireless_network_free(ipw->network); 31062306a36Sopenharmony_ci if (ipw->hardware != NULL) 31162306a36Sopenharmony_ci ipwireless_hardware_free(ipw->hardware); 31262306a36Sopenharmony_ci kfree(ipw); 31362306a36Sopenharmony_ci} 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_cistatic struct pcmcia_driver me = { 31662306a36Sopenharmony_ci .owner = THIS_MODULE, 31762306a36Sopenharmony_ci .probe = ipwireless_attach, 31862306a36Sopenharmony_ci .remove = ipwireless_detach, 31962306a36Sopenharmony_ci .name = IPWIRELESS_PCCARD_NAME, 32062306a36Sopenharmony_ci .id_table = ipw_ids 32162306a36Sopenharmony_ci}; 32262306a36Sopenharmony_ci 32362306a36Sopenharmony_ci/* 32462306a36Sopenharmony_ci * Module insertion : initialisation of the module. 32562306a36Sopenharmony_ci * Register the card with cardmgr... 32662306a36Sopenharmony_ci */ 32762306a36Sopenharmony_cistatic int __init init_ipwireless(void) 32862306a36Sopenharmony_ci{ 32962306a36Sopenharmony_ci int ret; 33062306a36Sopenharmony_ci 33162306a36Sopenharmony_ci ret = ipwireless_tty_init(); 33262306a36Sopenharmony_ci if (ret != 0) 33362306a36Sopenharmony_ci return ret; 33462306a36Sopenharmony_ci 33562306a36Sopenharmony_ci ret = pcmcia_register_driver(&me); 33662306a36Sopenharmony_ci if (ret != 0) 33762306a36Sopenharmony_ci ipwireless_tty_release(); 33862306a36Sopenharmony_ci 33962306a36Sopenharmony_ci return ret; 34062306a36Sopenharmony_ci} 34162306a36Sopenharmony_ci 34262306a36Sopenharmony_ci/* 34362306a36Sopenharmony_ci * Module removal 34462306a36Sopenharmony_ci */ 34562306a36Sopenharmony_cistatic void __exit exit_ipwireless(void) 34662306a36Sopenharmony_ci{ 34762306a36Sopenharmony_ci pcmcia_unregister_driver(&me); 34862306a36Sopenharmony_ci ipwireless_tty_release(); 34962306a36Sopenharmony_ci} 35062306a36Sopenharmony_ci 35162306a36Sopenharmony_cimodule_init(init_ipwireless); 35262306a36Sopenharmony_cimodule_exit(exit_ipwireless); 35362306a36Sopenharmony_ci 35462306a36Sopenharmony_ciMODULE_AUTHOR(IPWIRELESS_PCMCIA_AUTHOR); 35562306a36Sopenharmony_ciMODULE_DESCRIPTION(IPWIRELESS_PCCARD_NAME " " IPWIRELESS_PCMCIA_VERSION); 35662306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 357