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