diff options
Diffstat (limited to 'drivers/bluetooth/btbcm.c')
| -rw-r--r-- | drivers/bluetooth/btbcm.c | 248 | 
1 files changed, 176 insertions, 72 deletions
diff --git a/drivers/bluetooth/btbcm.c b/drivers/bluetooth/btbcm.c index d0741f3ed7ec..1e1a4323a71f 100644 --- a/drivers/bluetooth/btbcm.c +++ b/drivers/bluetooth/btbcm.c @@ -33,6 +33,7 @@  #define VERSION "0.1"  #define BDADDR_BCM20702A0 (&(bdaddr_t) {{0x00, 0xa0, 0x02, 0x70, 0x20, 0x00}}) +#define BDADDR_BCM4324B3 (&(bdaddr_t) {{0x00, 0x00, 0x00, 0xb3, 0x24, 0x43}})  int btbcm_check_bdaddr(struct hci_dev *hdev)  { @@ -55,17 +56,19 @@ int btbcm_check_bdaddr(struct hci_dev *hdev)  	}  	bda = (struct hci_rp_read_bd_addr *)skb->data; -	if (bda->status) { -		BT_ERR("%s: BCM: Device address result failed (%02x)", -		       hdev->name, bda->status); -		kfree_skb(skb); -		return -bt_to_errno(bda->status); -	} -	/* The address 00:20:70:02:A0:00 indicates a BCM20702A0 controller +	/* Check if the address indicates a controller with either an +	 * invalid or default address. In both cases the device needs +	 * to be marked as not having a valid address. +	 * +	 * The address 00:20:70:02:A0:00 indicates a BCM20702A0 controller  	 * with no configured address. +	 * +	 * The address 43:24:B3:00:00:00 indicates a BCM4324B3 controller +	 * with waiting for configuration state.  	 */ -	if (!bacmp(&bda->bdaddr, BDADDR_BCM20702A0)) { +	if (!bacmp(&bda->bdaddr, BDADDR_BCM20702A0) || +	    !bacmp(&bda->bdaddr, BDADDR_BCM4324B3)) {  		BT_INFO("%s: BCM: Using default device address (%pMR)",  			hdev->name, &bda->bdaddr);  		set_bit(HCI_QUIRK_INVALID_BDADDR, &hdev->quirks); @@ -95,6 +98,69 @@ int btbcm_set_bdaddr(struct hci_dev *hdev, const bdaddr_t *bdaddr)  }  EXPORT_SYMBOL_GPL(btbcm_set_bdaddr); +int btbcm_patchram(struct hci_dev *hdev, const struct firmware *fw) +{ +	const struct hci_command_hdr *cmd; +	const u8 *fw_ptr; +	size_t fw_size; +	struct sk_buff *skb; +	u16 opcode; +	int err = 0; + +	/* Start Download */ +	skb = __hci_cmd_sync(hdev, 0xfc2e, 0, NULL, HCI_INIT_TIMEOUT); +	if (IS_ERR(skb)) { +		err = PTR_ERR(skb); +		BT_ERR("%s: BCM: Download Minidrv command failed (%d)", +		       hdev->name, err); +		goto done; +	} +	kfree_skb(skb); + +	/* 50 msec delay after Download Minidrv completes */ +	msleep(50); + +	fw_ptr = fw->data; +	fw_size = fw->size; + +	while (fw_size >= sizeof(*cmd)) { +		const u8 *cmd_param; + +		cmd = (struct hci_command_hdr *)fw_ptr; +		fw_ptr += sizeof(*cmd); +		fw_size -= sizeof(*cmd); + +		if (fw_size < cmd->plen) { +			BT_ERR("%s: BCM: Patch is corrupted", hdev->name); +			err = -EINVAL; +			goto done; +		} + +		cmd_param = fw_ptr; +		fw_ptr += cmd->plen; +		fw_size -= cmd->plen; + +		opcode = le16_to_cpu(cmd->opcode); + +		skb = __hci_cmd_sync(hdev, opcode, cmd->plen, cmd_param, +				     HCI_INIT_TIMEOUT); +		if (IS_ERR(skb)) { +			err = PTR_ERR(skb); +			BT_ERR("%s: BCM: Patch command %04x failed (%d)", +			       hdev->name, opcode, err); +			goto done; +		} +		kfree_skb(skb); +	} + +	/* 250 msec delay after Launch Ram completes */ +	msleep(250); + +done: +	return err; +} +EXPORT_SYMBOL(btbcm_patchram); +  static int btbcm_reset(struct hci_dev *hdev)  {  	struct sk_buff *skb; @@ -176,9 +242,101 @@ static const struct {  	const char *name;  } bcm_uart_subver_table[] = {  	{ 0x410e, "BCM43341B0"	},	/* 002.001.014 */ +	{ 0x4406, "BCM4324B3"	},	/* 002.004.006 */ +	{ 0x610c, "BCM4354"	},	/* 003.001.012 */  	{ }  }; +int btbcm_initialize(struct hci_dev *hdev, char *fw_name, size_t len) +{ +	u16 subver, rev; +	const char *hw_name = NULL; +	struct sk_buff *skb; +	struct hci_rp_read_local_version *ver; +	int i, err; + +	/* Reset */ +	err = btbcm_reset(hdev); +	if (err) +		return err; + +	/* Read Local Version Info */ +	skb = btbcm_read_local_version(hdev); +	if (IS_ERR(skb)) +		return PTR_ERR(skb); + +	ver = (struct hci_rp_read_local_version *)skb->data; +	rev = le16_to_cpu(ver->hci_rev); +	subver = le16_to_cpu(ver->lmp_subver); +	kfree_skb(skb); + +	/* Read Verbose Config Version Info */ +	skb = btbcm_read_verbose_config(hdev); +	if (IS_ERR(skb)) +		return PTR_ERR(skb); + +	BT_INFO("%s: BCM: chip id %u", hdev->name, skb->data[1]); +	kfree_skb(skb); + +	switch ((rev & 0xf000) >> 12) { +	case 0: +	case 1: +	case 3: +		for (i = 0; bcm_uart_subver_table[i].name; i++) { +			if (subver == bcm_uart_subver_table[i].subver) { +				hw_name = bcm_uart_subver_table[i].name; +				break; +			} +		} + +		snprintf(fw_name, len, "brcm/%s.hcd", hw_name ? : "BCM"); +		break; +	default: +		return 0; +	} + +	BT_INFO("%s: %s (%3.3u.%3.3u.%3.3u) build %4.4u", hdev->name, +		hw_name ? : "BCM", (subver & 0x7000) >> 13, +		(subver & 0x1f00) >> 8, (subver & 0x00ff), rev & 0x0fff); + +	return 0; +} +EXPORT_SYMBOL_GPL(btbcm_initialize); + +int btbcm_finalize(struct hci_dev *hdev) +{ +	struct sk_buff *skb; +	struct hci_rp_read_local_version *ver; +	u16 subver, rev; +	int err; + +	/* Reset */ +	err = btbcm_reset(hdev); +	if (err) +		return err; + +	/* Read Local Version Info */ +	skb = btbcm_read_local_version(hdev); +	if (IS_ERR(skb)) +		return PTR_ERR(skb); + +	ver = (struct hci_rp_read_local_version *)skb->data; +	rev = le16_to_cpu(ver->hci_rev); +	subver = le16_to_cpu(ver->lmp_subver); +	kfree_skb(skb); + +	BT_INFO("%s: BCM (%3.3u.%3.3u.%3.3u) build %4.4u", hdev->name, +		(subver & 0x7000) >> 13, (subver & 0x1f00) >> 8, +		(subver & 0x00ff), rev & 0x0fff); + +	btbcm_check_bdaddr(hdev); + +	set_bit(HCI_QUIRK_STRICT_DUPLICATE_FILTER, &hdev->quirks); + +	return 0; +} +EXPORT_SYMBOL_GPL(btbcm_finalize); +  static const struct {  	u16 subver;  	const char *name; @@ -198,12 +356,9 @@ static const struct {  int btbcm_setup_patchram(struct hci_dev *hdev)  { -	const struct hci_command_hdr *cmd; -	const struct firmware *fw; -	const u8 *fw_ptr; -	size_t fw_size;  	char fw_name[64]; -	u16 opcode, subver, rev, pid, vid; +	const struct firmware *fw; +	u16 subver, rev, pid, vid;  	const char *hw_name = NULL;  	struct sk_buff *skb;  	struct hci_rp_read_local_version *ver; @@ -234,6 +389,7 @@ int btbcm_setup_patchram(struct hci_dev *hdev)  	switch ((rev & 0xf000) >> 12) {  	case 0: +	case 3:  		for (i = 0; bcm_uart_subver_table[i].name; i++) {  			if (subver == bcm_uart_subver_table[i].subver) {  				hw_name = bcm_uart_subver_table[i].name; @@ -275,72 +431,23 @@ int btbcm_setup_patchram(struct hci_dev *hdev)  	err = request_firmware(&fw, fw_name, &hdev->dev);  	if (err < 0) { -		BT_INFO("%s: BCM: patch %s not found", hdev->name, fw_name); +		BT_INFO("%s: BCM: Patch %s not found", hdev->name, fw_name);  		return 0;  	} -	/* Start Download */ -	skb = __hci_cmd_sync(hdev, 0xfc2e, 0, NULL, HCI_INIT_TIMEOUT); -	if (IS_ERR(skb)) { -		err = PTR_ERR(skb); -		BT_ERR("%s: BCM: Download Minidrv command failed (%d)", -		       hdev->name, err); -		goto reset; -	} -	kfree_skb(skb); - -	/* 50 msec delay after Download Minidrv completes */ -	msleep(50); - -	fw_ptr = fw->data; -	fw_size = fw->size; +	btbcm_patchram(hdev, fw); -	while (fw_size >= sizeof(*cmd)) { -		const u8 *cmd_param; - -		cmd = (struct hci_command_hdr *)fw_ptr; -		fw_ptr += sizeof(*cmd); -		fw_size -= sizeof(*cmd); - -		if (fw_size < cmd->plen) { -			BT_ERR("%s: BCM: patch %s is corrupted", hdev->name, -			       fw_name); -			err = -EINVAL; -			goto reset; -		} - -		cmd_param = fw_ptr; -		fw_ptr += cmd->plen; -		fw_size -= cmd->plen; - -		opcode = le16_to_cpu(cmd->opcode); - -		skb = __hci_cmd_sync(hdev, opcode, cmd->plen, cmd_param, -				     HCI_INIT_TIMEOUT); -		if (IS_ERR(skb)) { -			err = PTR_ERR(skb); -			BT_ERR("%s: BCM: patch command %04x failed (%d)", -			       hdev->name, opcode, err); -			goto reset; -		} -		kfree_skb(skb); -	} - -	/* 250 msec delay after Launch Ram completes */ -	msleep(250); +	release_firmware(fw); -reset:  	/* Reset */  	err = btbcm_reset(hdev);  	if (err) -		goto done; +		return err;  	/* Read Local Version Info */  	skb = btbcm_read_local_version(hdev); -	if (IS_ERR(skb)) { -		err = PTR_ERR(skb); -		goto done; -	} +	if (IS_ERR(skb)) +		return PTR_ERR(skb);  	ver = (struct hci_rp_read_local_version *)skb->data;  	rev = le16_to_cpu(ver->hci_rev); @@ -355,10 +462,7 @@ reset:  	set_bit(HCI_QUIRK_STRICT_DUPLICATE_FILTER, &hdev->quirks); -done: -	release_firmware(fw); - -	return err; +	return 0;  }  EXPORT_SYMBOL_GPL(btbcm_setup_patchram);  |