aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/platform/x86/toshiba_bluetooth.c
blob: 944068611919394eaa42c6337e96b2e1e02b9fee (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
/*
 * Toshiba Bluetooth Enable Driver
 *
 * Copyright (C) 2009 Jes Sorensen <Jes.Sorensen@gmail.com>
 *
 * Thanks to Matthew Garrett for background info on ACPI innards which
 * normal people aren't meant to understand :-)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * Note the Toshiba Bluetooth RFKill switch seems to be a strange
 * fish. It only provides a BT event when the switch is flipped to
 * the 'on' position. When flipping it to 'off', the USB device is
 * simply pulled away underneath us, without any BT event being
 * delivered.
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <acpi/acpi_bus.h>
#include <acpi/acpi_drivers.h>

MODULE_AUTHOR("Jes Sorensen <Jes.Sorensen@gmail.com>");
MODULE_DESCRIPTION("Toshiba Laptop ACPI Bluetooth Enable Driver");
MODULE_LICENSE("GPL");


static int toshiba_bt_rfkill_add(struct acpi_device *device);
static int toshiba_bt_rfkill_remove(struct acpi_device *device, int type);
static void toshiba_bt_rfkill_notify(struct acpi_device *device, u32 event);
static int toshiba_bt_resume(struct acpi_device *device);

static const struct acpi_device_id bt_device_ids[] = {
	{ "TOS6205", 0},
	{ "", 0},
};
MODULE_DEVICE_TABLE(acpi, bt_device_ids);

static struct acpi_driver toshiba_bt_rfkill_driver = {
	.name =		"Toshiba BT",
	.class =	"Toshiba",
	.ids =		bt_device_ids,
	.ops =		{
				.add =		toshiba_bt_rfkill_add,
				.remove =	toshiba_bt_rfkill_remove,
				.notify =	toshiba_bt_rfkill_notify,
				.resume =	toshiba_bt_resume,
			},
	.owner = 	THIS_MODULE,
};


static int toshiba_bluetooth_enable(acpi_handle handle)
{
	acpi_status res1, res2;
	u64 result;

	/*
	 * Query ACPI to verify RFKill switch is set to 'on'.
	 * If not, we return silently, no need to report it as
	 * an error.
	 */
	res1 = acpi_evaluate_integer(handle, "BTST", NULL, &result);
	if (ACPI_FAILURE(res1))
		return res1;
	if (!(result & 0x01))
		return 0;

	printk(KERN_INFO "toshiba_bluetooth: Re-enabling Toshiba Bluetooth\n");
	res1 = acpi_evaluate_object(handle, "AUSB", NULL, NULL);
	res2 = acpi_evaluate_object(handle, "BTPO", NULL, NULL);
	if (!ACPI_FAILURE(res1) || !ACPI_FAILURE(res2))
		return 0;

	printk(KERN_WARNING "toshiba_bluetooth: Failed to re-enable "
	       "Toshiba Bluetooth\n");

	return -ENODEV;
}

static void toshiba_bt_rfkill_notify(struct acpi_device *device, u32 event)
{
	toshiba_bluetooth_enable(device->handle);
}

static int toshiba_bt_resume(struct acpi_device *device)
{
	return toshiba_bluetooth_enable(device->handle);
}

static int toshiba_bt_rfkill_add(struct acpi_device *device)
{
	acpi_status status;
	u64 bt_present;
	int result = -ENODEV;

	/*
	 * Some Toshiba laptops may have a fake TOS6205 device in
	 * their ACPI BIOS, so query the _STA method to see if there
	 * is really anything there, before trying to enable it.
	 */
	status = acpi_evaluate_integer(device->handle, "_STA", NULL,
				       &bt_present);

	if (!ACPI_FAILURE(status) && bt_present) {
		printk(KERN_INFO "Detected Toshiba ACPI Bluetooth device - "
		      "installing RFKill handler\n");
		result = toshiba_bluetooth_enable(device->handle);
	}

	return result;
}

static int __init toshiba_bt_rfkill_init(void)
{
	int result;

	result = acpi_bus_register_driver(&toshiba_bt_rfkill_driver);
	if (result < 0) {
		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
				  "Error registering driver\n"));
		return result;
	}

	return 0;
}

static int toshiba_bt_rfkill_remove(struct acpi_device *device, int type)
{
	/* clean up */
	return 0;
}

static void __exit toshiba_bt_rfkill_exit(void)
{
	acpi_bus_unregister_driver(&toshiba_bt_rfkill_driver);
}

module_init(toshiba_bt_rfkill_init);
module_exit(toshiba_bt_rfkill_exit);
ass="hl opt">(!netif_running(lapbeth->axdev)) goto drop_unlock; lapbeth->stats.rx_packets++; len = skb->data[0] + skb->data[1] * 256; lapbeth->stats.rx_bytes += len; skb_pull(skb, 2); /* Remove the length bytes */ skb_trim(skb, len); /* Set the length of the data */ if ((err = lapb_data_received(lapbeth->axdev, skb)) != LAPB_OK) { printk(KERN_DEBUG "lapbether: lapb_data_received err - %d\n", err); goto drop_unlock; } out: rcu_read_unlock(); return 0; drop_unlock: kfree_skb(skb); goto out; drop: kfree_skb(skb); return 0; } static int lapbeth_data_indication(struct net_device *dev, struct sk_buff *skb) { unsigned char *ptr; skb_push(skb, 1); if (skb_cow(skb, 1)) return NET_RX_DROP; ptr = skb->data; *ptr = 0x00; skb->protocol = x25_type_trans(skb, dev); skb->dev->last_rx = jiffies; return netif_rx(skb); } /* * Send a LAPB frame via an ethernet interface */ static int lapbeth_xmit(struct sk_buff *skb, struct net_device *dev) { int err = -ENODEV; /* * Just to be *really* sure not to send anything if the interface * is down, the ethernet device may have gone. */ if (!netif_running(dev)) { goto drop; } switch (skb->data[0]) { case 0x00: err = 0; break; case 0x01: if ((err = lapb_connect_request(dev)) != LAPB_OK) printk(KERN_ERR "lapbeth: lapb_connect_request " "error: %d\n", err); goto drop_ok; case 0x02: if ((err = lapb_disconnect_request(dev)) != LAPB_OK) printk(KERN_ERR "lapbeth: lapb_disconnect_request " "err: %d\n", err); /* Fall thru */ default: goto drop_ok; } skb_pull(skb, 1); if ((err = lapb_data_request(dev, skb)) != LAPB_OK) { printk(KERN_ERR "lapbeth: lapb_data_request error - %d\n", err); err = -ENOMEM; goto drop; } err = 0; out: return err; drop_ok: err = 0; drop: kfree_skb(skb); goto out; } static void lapbeth_data_transmit(struct net_device *ndev, struct sk_buff *skb) { struct lapbethdev *lapbeth = netdev_priv(ndev); unsigned char *ptr; struct net_device *dev; int size = skb->len; skb->protocol = htons(ETH_P_X25); ptr = skb_push(skb, 2); *ptr++ = size % 256; *ptr++ = size / 256; lapbeth->stats.tx_packets++; lapbeth->stats.tx_bytes += size; skb->dev = dev = lapbeth->ethdev; dev->hard_header(skb, dev, ETH_P_DEC, bcast_addr, NULL, 0); dev_queue_xmit(skb); } static void lapbeth_connected(struct net_device *dev, int reason) { unsigned char *ptr; struct sk_buff *skb = dev_alloc_skb(1); if (!skb) { printk(KERN_ERR "lapbeth: out of memory\n"); return; } ptr = skb_put(skb, 1); *ptr = 0x01; skb->protocol = x25_type_trans(skb, dev); skb->dev->last_rx = jiffies; netif_rx(skb); } static void lapbeth_disconnected(struct net_device *dev, int reason) { unsigned char *ptr; struct sk_buff *skb = dev_alloc_skb(1); if (!skb) { printk(KERN_ERR "lapbeth: out of memory\n"); return; } ptr = skb_put(skb, 1); *ptr = 0x02; skb->protocol = x25_type_trans(skb, dev); skb->dev->last_rx = jiffies; netif_rx(skb); } /* * Statistics */ static struct net_device_stats *lapbeth_get_stats(struct net_device *dev) { struct lapbethdev *lapbeth = netdev_priv(dev); return &lapbeth->stats; } /* * Set AX.25 callsign */ static int lapbeth_set_mac_address(struct net_device *dev, void *addr) { struct sockaddr *sa = addr; memcpy(dev->dev_addr, sa->sa_data, dev->addr_len); return 0; } static struct lapb_register_struct lapbeth_callbacks = { .connect_confirmation = lapbeth_connected, .connect_indication = lapbeth_connected, .disconnect_confirmation = lapbeth_disconnected, .disconnect_indication = lapbeth_disconnected, .data_indication = lapbeth_data_indication, .data_transmit = lapbeth_data_transmit, }; /* * open/close a device */ static int lapbeth_open(struct net_device *dev) { int err; if ((err = lapb_register(dev, &lapbeth_callbacks)) != LAPB_OK) { printk(KERN_ERR "lapbeth: lapb_register error - %d\n", err); return -ENODEV; } netif_start_queue(dev); return 0; } static int lapbeth_close(struct net_device *dev) { int err; netif_stop_queue(dev); if ((err = lapb_unregister(dev)) != LAPB_OK) printk(KERN_ERR "lapbeth: lapb_unregister error - %d\n", err); return 0; } /* ------------------------------------------------------------------------ */ static void lapbeth_setup(struct net_device *dev) { dev->hard_start_xmit = lapbeth_xmit; dev->open = lapbeth_open; dev->stop = lapbeth_close; dev->destructor = free_netdev; dev->set_mac_address = lapbeth_set_mac_address; dev->get_stats = lapbeth_get_stats; dev->type = ARPHRD_X25; dev->hard_header_len = 3; dev->mtu = 1000; dev->addr_len = 0; SET_MODULE_OWNER(dev); } /* * Setup a new device. */ static int lapbeth_new_device(struct net_device *dev) { struct net_device *ndev; struct lapbethdev *lapbeth; int rc = -ENOMEM; ASSERT_RTNL(); ndev = alloc_netdev(sizeof(*lapbeth), "lapb%d", lapbeth_setup); if (!ndev) goto out; lapbeth = netdev_priv(ndev); lapbeth->axdev = ndev; dev_hold(dev); lapbeth->ethdev = dev; rc = dev_alloc_name(ndev, ndev->name); if (rc < 0) goto fail; rc = -EIO; if (register_netdevice(ndev)) goto fail; list_add_rcu(&lapbeth->node, &lapbeth_devices); rc = 0; out: return rc; fail: dev_put(dev); free_netdev(ndev); kfree(lapbeth); goto out; } /* * Free a lapb network device. */ static void lapbeth_free_device(struct lapbethdev *lapbeth) { dev_put(lapbeth->ethdev); list_del_rcu(&lapbeth->node); unregister_netdevice(lapbeth->axdev); } /* * Handle device status changes. * * Called from notifier with RTNL held. */ static int lapbeth_device_event(struct notifier_block *this, unsigned long event, void *ptr) { struct lapbethdev *lapbeth; struct net_device *dev = ptr; if (!dev_is_ethdev(dev)) return NOTIFY_DONE; switch (event) { case NETDEV_UP: /* New ethernet device -> new LAPB interface */ if (lapbeth_get_x25_dev(dev) == NULL) lapbeth_new_device(dev); break; case NETDEV_DOWN: /* ethernet device closed -> close LAPB interface */ lapbeth = lapbeth_get_x25_dev(dev); if (lapbeth) dev_close(lapbeth->axdev); break; case NETDEV_UNREGISTER: /* ethernet device disappears -> remove LAPB interface */ lapbeth = lapbeth_get_x25_dev(dev); if (lapbeth) lapbeth_free_device(lapbeth); break; } return NOTIFY_DONE; } /* ------------------------------------------------------------------------ */ static struct packet_type lapbeth_packet_type = { .type = __constant_htons(ETH_P_DEC), .func = lapbeth_rcv, }; static struct notifier_block lapbeth_dev_notifier = { .notifier_call = lapbeth_device_event, }; static char banner[] __initdata = KERN_INFO "LAPB Ethernet driver version 0.02\n"; static int __init lapbeth_init_driver(void) { dev_add_pack(&lapbeth_packet_type); register_netdevice_notifier(&lapbeth_dev_notifier); printk(banner); return 0; } module_init(lapbeth_init_driver); static void __exit lapbeth_cleanup_driver(void) { struct lapbethdev *lapbeth; struct list_head *entry, *tmp; dev_remove_pack(&lapbeth_packet_type); unregister_netdevice_notifier(&lapbeth_dev_notifier); rtnl_lock(); list_for_each_safe(entry, tmp, &lapbeth_devices) { lapbeth = list_entry(entry, struct lapbethdev, node); unregister_netdevice(lapbeth->axdev); } rtnl_unlock(); } module_exit(lapbeth_cleanup_driver); MODULE_AUTHOR("Jonathan Naylor <g4klx@g4klx.demon.co.uk>"); MODULE_DESCRIPTION("The unofficial LAPB over Ethernet driver"); MODULE_LICENSE("GPL");