Commit 491f9eff authored by Neeraj Sanjay Kale's avatar Neeraj Sanjay Kale Committed by Luiz Augusto von Dentz

Bluetooth: btnxpuart: Improve inband Independent Reset handling

This improves the inband IR command handling for NXP BT chipsets.
When the IR vendor command is received, the driver injects a HW
error event, which causes a reset sequence in hci_error_reset().
The vendor IR command is sent to the controller while hci dev
is been closed, and FW is re-downloaded when nxp_setup() is
called during hci_dev_do_open().
The HCI_SETUP flag is set in nxp_hw_err() to make sure that
nxp_setup() is been called during hci_dev_do_open().

This also makes the nxp_setup() and power save functions more
generic.
Signed-off-by: default avatarNeeraj Sanjay Kale <neeraj.sanjaykale@nxp.com>
Signed-off-by: default avatarLuiz Augusto von Dentz <luiz.von.dentz@intel.com>
parent 8b7630de
...@@ -28,6 +28,10 @@ ...@@ -28,6 +28,10 @@
#define BTNXPUART_FW_DOWNLOADING 2 #define BTNXPUART_FW_DOWNLOADING 2
#define BTNXPUART_CHECK_BOOT_SIGNATURE 3 #define BTNXPUART_CHECK_BOOT_SIGNATURE 3
#define BTNXPUART_SERDEV_OPEN 4 #define BTNXPUART_SERDEV_OPEN 4
#define BTNXPUART_IR_IN_PROGRESS 5
/* NXP HW err codes */
#define BTNXPUART_IR_HW_ERR 0xb0
#define FIRMWARE_W8987 "nxp/uartuart8987_bt.bin" #define FIRMWARE_W8987 "nxp/uartuart8987_bt.bin"
#define FIRMWARE_W8997 "nxp/uartuart8997_bt_v4.bin" #define FIRMWARE_W8997 "nxp/uartuart8997_bt_v4.bin"
...@@ -380,39 +384,13 @@ static void ps_timeout_func(struct timer_list *t) ...@@ -380,39 +384,13 @@ static void ps_timeout_func(struct timer_list *t)
} }
} }
static int ps_init_work(struct hci_dev *hdev) static void ps_setup(struct hci_dev *hdev)
{ {
struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev); struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev);
struct ps_data *psdata = &nxpdev->psdata; struct ps_data *psdata = &nxpdev->psdata;
psdata->h2c_ps_interval = PS_DEFAULT_TIMEOUT_PERIOD_MS;
psdata->ps_state = PS_STATE_AWAKE;
psdata->target_ps_mode = DEFAULT_PS_MODE;
psdata->hdev = hdev; psdata->hdev = hdev;
psdata->c2h_wakeupmode = BT_HOST_WAKEUP_METHOD_NONE;
psdata->c2h_wakeup_gpio = 0xff;
switch (DEFAULT_H2C_WAKEUP_MODE) {
case WAKEUP_METHOD_DTR:
psdata->h2c_wakeupmode = WAKEUP_METHOD_DTR;
break;
case WAKEUP_METHOD_BREAK:
default:
psdata->h2c_wakeupmode = WAKEUP_METHOD_BREAK;
break;
}
psdata->cur_psmode = PS_MODE_DISABLE;
psdata->cur_h2c_wakeupmode = WAKEUP_METHOD_INVALID;
INIT_WORK(&psdata->work, ps_work_func); INIT_WORK(&psdata->work, ps_work_func);
return 0;
}
static void ps_init_timer(struct hci_dev *hdev)
{
struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev);
struct ps_data *psdata = &nxpdev->psdata;
timer_setup(&psdata->ps_timer, ps_timeout_func, 0); timer_setup(&psdata->ps_timer, ps_timeout_func, 0);
} }
...@@ -515,19 +493,31 @@ static void ps_init(struct hci_dev *hdev) ...@@ -515,19 +493,31 @@ static void ps_init(struct hci_dev *hdev)
serdev_device_set_tiocm(nxpdev->serdev, TIOCM_RTS, 0); serdev_device_set_tiocm(nxpdev->serdev, TIOCM_RTS, 0);
usleep_range(5000, 10000); usleep_range(5000, 10000);
switch (psdata->h2c_wakeupmode) { psdata->ps_state = PS_STATE_AWAKE;
psdata->c2h_wakeupmode = BT_HOST_WAKEUP_METHOD_NONE;
psdata->c2h_wakeup_gpio = 0xff;
psdata->cur_h2c_wakeupmode = WAKEUP_METHOD_INVALID;
psdata->h2c_ps_interval = PS_DEFAULT_TIMEOUT_PERIOD_MS;
switch (DEFAULT_H2C_WAKEUP_MODE) {
case WAKEUP_METHOD_DTR: case WAKEUP_METHOD_DTR:
psdata->h2c_wakeupmode = WAKEUP_METHOD_DTR;
serdev_device_set_tiocm(nxpdev->serdev, 0, TIOCM_DTR); serdev_device_set_tiocm(nxpdev->serdev, 0, TIOCM_DTR);
serdev_device_set_tiocm(nxpdev->serdev, TIOCM_DTR, 0); serdev_device_set_tiocm(nxpdev->serdev, TIOCM_DTR, 0);
break; break;
case WAKEUP_METHOD_BREAK: case WAKEUP_METHOD_BREAK:
default: default:
psdata->h2c_wakeupmode = WAKEUP_METHOD_BREAK;
serdev_device_break_ctl(nxpdev->serdev, -1); serdev_device_break_ctl(nxpdev->serdev, -1);
usleep_range(5000, 10000); usleep_range(5000, 10000);
serdev_device_break_ctl(nxpdev->serdev, 0); serdev_device_break_ctl(nxpdev->serdev, 0);
usleep_range(5000, 10000); usleep_range(5000, 10000);
break; break;
} }
psdata->cur_psmode = PS_MODE_DISABLE;
psdata->target_ps_mode = DEFAULT_PS_MODE;
if (psdata->cur_h2c_wakeupmode != psdata->h2c_wakeupmode) if (psdata->cur_h2c_wakeupmode != psdata->h2c_wakeupmode)
hci_cmd_sync_queue(hdev, send_wakeup_method_cmd, NULL, NULL); hci_cmd_sync_queue(hdev, send_wakeup_method_cmd, NULL, NULL);
if (psdata->cur_psmode != psdata->target_ps_mode) if (psdata->cur_psmode != psdata->target_ps_mode)
...@@ -709,7 +699,7 @@ static int nxp_recv_chip_ver_v1(struct hci_dev *hdev, struct sk_buff *skb) ...@@ -709,7 +699,7 @@ static int nxp_recv_chip_ver_v1(struct hci_dev *hdev, struct sk_buff *skb)
goto free_skb; goto free_skb;
chip_id = le16_to_cpu(req->chip_id ^ req->chip_id_comp); chip_id = le16_to_cpu(req->chip_id ^ req->chip_id_comp);
if (chip_id == 0xffff) { if (chip_id == 0xffff && nxpdev->fw_dnld_v1_offset) {
nxpdev->fw_dnld_v1_offset = 0; nxpdev->fw_dnld_v1_offset = 0;
nxpdev->fw_v1_sent_bytes = 0; nxpdev->fw_v1_sent_bytes = 0;
nxpdev->fw_v1_expected_len = HDR_LEN; nxpdev->fw_v1_expected_len = HDR_LEN;
...@@ -987,45 +977,13 @@ static int nxp_set_baudrate_cmd(struct hci_dev *hdev, void *data) ...@@ -987,45 +977,13 @@ static int nxp_set_baudrate_cmd(struct hci_dev *hdev, void *data)
return 0; return 0;
} }
static int nxp_set_ind_reset(struct hci_dev *hdev, void *data)
{
struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev);
struct sk_buff *skb;
u8 *status;
u8 pcmd = 0;
int err = 0;
skb = nxp_drv_send_cmd(hdev, HCI_NXP_IND_RESET, 1, &pcmd);
if (IS_ERR(skb))
return PTR_ERR(skb);
status = skb_pull_data(skb, 1);
if (!status || *status)
goto free_skb;
set_bit(BTNXPUART_FW_DOWNLOADING, &nxpdev->tx_state);
err = nxp_download_firmware(hdev);
if (err < 0)
goto free_skb;
serdev_device_set_baudrate(nxpdev->serdev, nxpdev->fw_init_baudrate);
nxpdev->current_baudrate = nxpdev->fw_init_baudrate;
if (nxpdev->current_baudrate != HCI_NXP_SEC_BAUDRATE) {
nxpdev->new_baudrate = HCI_NXP_SEC_BAUDRATE;
nxp_set_baudrate_cmd(hdev, NULL);
}
hci_cmd_sync_queue(hdev, send_wakeup_method_cmd, NULL, NULL);
hci_cmd_sync_queue(hdev, send_ps_cmd, NULL, NULL);
free_skb:
kfree_skb(skb);
return err;
}
/* NXP protocol */
static int nxp_check_boot_sign(struct btnxpuart_dev *nxpdev) static int nxp_check_boot_sign(struct btnxpuart_dev *nxpdev)
{ {
serdev_device_set_baudrate(nxpdev->serdev, HCI_NXP_PRI_BAUDRATE); serdev_device_set_baudrate(nxpdev->serdev, HCI_NXP_PRI_BAUDRATE);
serdev_device_set_flow_control(nxpdev->serdev, true); if (test_bit(BTNXPUART_IR_IN_PROGRESS, &nxpdev->tx_state))
serdev_device_set_flow_control(nxpdev->serdev, false);
else
serdev_device_set_flow_control(nxpdev->serdev, true);
set_bit(BTNXPUART_CHECK_BOOT_SIGNATURE, &nxpdev->tx_state); set_bit(BTNXPUART_CHECK_BOOT_SIGNATURE, &nxpdev->tx_state);
return wait_event_interruptible_timeout(nxpdev->check_boot_sign_wait_q, return wait_event_interruptible_timeout(nxpdev->check_boot_sign_wait_q,
...@@ -1034,15 +992,29 @@ static int nxp_check_boot_sign(struct btnxpuart_dev *nxpdev) ...@@ -1034,15 +992,29 @@ static int nxp_check_boot_sign(struct btnxpuart_dev *nxpdev)
msecs_to_jiffies(1000)); msecs_to_jiffies(1000));
} }
static int nxp_set_ind_reset(struct hci_dev *hdev, void *data)
{
static const u8 ir_hw_err[] = { HCI_EV_HARDWARE_ERROR,
0x01, BTNXPUART_IR_HW_ERR };
struct sk_buff *skb;
skb = bt_skb_alloc(3, GFP_ATOMIC);
if (!skb)
return -ENOMEM;
hci_skb_pkt_type(skb) = HCI_EVENT_PKT;
skb_put_data(skb, ir_hw_err, 3);
/* Inject Hardware Error to upper stack */
return hci_recv_frame(hdev, skb);
}
/* NXP protocol */
static int nxp_setup(struct hci_dev *hdev) static int nxp_setup(struct hci_dev *hdev)
{ {
struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev); struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev);
int err = 0; int err = 0;
set_bit(BTNXPUART_FW_DOWNLOADING, &nxpdev->tx_state);
init_waitqueue_head(&nxpdev->fw_dnld_done_wait_q);
init_waitqueue_head(&nxpdev->check_boot_sign_wait_q);
if (nxp_check_boot_sign(nxpdev)) { if (nxp_check_boot_sign(nxpdev)) {
bt_dev_dbg(hdev, "Need FW Download."); bt_dev_dbg(hdev, "Need FW Download.");
err = nxp_download_firmware(hdev); err = nxp_download_firmware(hdev);
...@@ -1053,10 +1025,6 @@ static int nxp_setup(struct hci_dev *hdev) ...@@ -1053,10 +1025,6 @@ static int nxp_setup(struct hci_dev *hdev)
clear_bit(BTNXPUART_FW_DOWNLOADING, &nxpdev->tx_state); clear_bit(BTNXPUART_FW_DOWNLOADING, &nxpdev->tx_state);
} }
device_property_read_u32(&nxpdev->serdev->dev, "fw-init-baudrate",
&nxpdev->fw_init_baudrate);
if (!nxpdev->fw_init_baudrate)
nxpdev->fw_init_baudrate = FW_INIT_BAUDRATE;
serdev_device_set_baudrate(nxpdev->serdev, nxpdev->fw_init_baudrate); serdev_device_set_baudrate(nxpdev->serdev, nxpdev->fw_init_baudrate);
nxpdev->current_baudrate = nxpdev->fw_init_baudrate; nxpdev->current_baudrate = nxpdev->fw_init_baudrate;
...@@ -1067,6 +1035,46 @@ static int nxp_setup(struct hci_dev *hdev) ...@@ -1067,6 +1035,46 @@ static int nxp_setup(struct hci_dev *hdev)
ps_init(hdev); ps_init(hdev);
if (test_and_clear_bit(BTNXPUART_IR_IN_PROGRESS, &nxpdev->tx_state))
hci_dev_clear_flag(hdev, HCI_SETUP);
return 0;
}
static void nxp_hw_err(struct hci_dev *hdev, u8 code)
{
struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev);
switch (code) {
case BTNXPUART_IR_HW_ERR:
set_bit(BTNXPUART_IR_IN_PROGRESS, &nxpdev->tx_state);
hci_dev_set_flag(hdev, HCI_SETUP);
break;
default:
break;
}
}
static int nxp_shutdown(struct hci_dev *hdev)
{
struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev);
struct sk_buff *skb;
u8 *status;
u8 pcmd = 0;
if (test_bit(BTNXPUART_IR_IN_PROGRESS, &nxpdev->tx_state)) {
skb = nxp_drv_send_cmd(hdev, HCI_NXP_IND_RESET, 1, &pcmd);
if (IS_ERR(skb))
return PTR_ERR(skb);
status = skb_pull_data(skb, 1);
if (status) {
serdev_device_set_flow_control(nxpdev->serdev, false);
set_bit(BTNXPUART_FW_DOWNLOADING, &nxpdev->tx_state);
}
kfree_skb(skb);
}
return 0; return 0;
} }
...@@ -1274,7 +1282,8 @@ static int btnxpuart_receive_buf(struct serdev_device *serdev, const u8 *data, ...@@ -1274,7 +1282,8 @@ static int btnxpuart_receive_buf(struct serdev_device *serdev, const u8 *data,
nxpdev->rx_skb = NULL; nxpdev->rx_skb = NULL;
return err; return err;
} }
nxpdev->hdev->stat.byte_rx += count; if (!is_fw_downloading(nxpdev))
nxpdev->hdev->stat.byte_rx += count;
return count; return count;
} }
...@@ -1307,6 +1316,16 @@ static int nxp_serdev_probe(struct serdev_device *serdev) ...@@ -1307,6 +1316,16 @@ static int nxp_serdev_probe(struct serdev_device *serdev)
INIT_WORK(&nxpdev->tx_work, btnxpuart_tx_work); INIT_WORK(&nxpdev->tx_work, btnxpuart_tx_work);
skb_queue_head_init(&nxpdev->txq); skb_queue_head_init(&nxpdev->txq);
init_waitqueue_head(&nxpdev->fw_dnld_done_wait_q);
init_waitqueue_head(&nxpdev->check_boot_sign_wait_q);
device_property_read_u32(&nxpdev->serdev->dev, "fw-init-baudrate",
&nxpdev->fw_init_baudrate);
if (!nxpdev->fw_init_baudrate)
nxpdev->fw_init_baudrate = FW_INIT_BAUDRATE;
set_bit(BTNXPUART_FW_DOWNLOADING, &nxpdev->tx_state);
crc8_populate_msb(crc8_table, POLYNOMIAL8); crc8_populate_msb(crc8_table, POLYNOMIAL8);
/* Initialize and register HCI device */ /* Initialize and register HCI device */
...@@ -1327,6 +1346,8 @@ static int nxp_serdev_probe(struct serdev_device *serdev) ...@@ -1327,6 +1346,8 @@ static int nxp_serdev_probe(struct serdev_device *serdev)
hdev->flush = btnxpuart_flush; hdev->flush = btnxpuart_flush;
hdev->setup = nxp_setup; hdev->setup = nxp_setup;
hdev->send = nxp_enqueue; hdev->send = nxp_enqueue;
hdev->hw_error = nxp_hw_err;
hdev->shutdown = nxp_shutdown;
SET_HCIDEV_DEV(hdev, &serdev->dev); SET_HCIDEV_DEV(hdev, &serdev->dev);
if (hci_register_dev(hdev) < 0) { if (hci_register_dev(hdev) < 0) {
...@@ -1335,8 +1356,7 @@ static int nxp_serdev_probe(struct serdev_device *serdev) ...@@ -1335,8 +1356,7 @@ static int nxp_serdev_probe(struct serdev_device *serdev)
return -ENODEV; return -ENODEV;
} }
ps_init_work(hdev); ps_setup(hdev);
ps_init_timer(hdev);
return 0; return 0;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment