| /** |
| * Copyright (c) 2018 MediaTek Inc. |
| * |
| * 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. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
| * See http://www.gnu.org/licenses/gpl-2.0.html for more details. |
| */ |
| |
| #include "btmtk_chip_reset.h" |
| |
| #if (KERNEL_VERSION(4, 15, 0) > LINUX_VERSION_CODE) |
| static void btmtk_reset_timer(unsigned long arg) |
| { |
| struct btmtk_dev *bdev = (struct btmtk_dev *)arg; |
| |
| BTMTK_INFO("%s: chip_reset not trigger in %d seconds, trigger it directly", |
| __func__, CHIP_RESET_TIMEOUT); |
| schedule_work(&bdev->reset_waker); |
| } |
| #else |
| static void btmtk_reset_timer(struct timer_list *timer) |
| { |
| struct btmtk_dev *bdev = from_timer(bdev, timer, chip_reset_timer); |
| |
| BTMTK_INFO("%s: chip_reset not trigger in %d seconds, trigger it directly", |
| __func__, CHIP_RESET_TIMEOUT); |
| schedule_work(&bdev->reset_waker); |
| } |
| #endif |
| |
| void btmtk_reset_timer_add(struct btmtk_dev *bdev) |
| { |
| BTMTK_INFO("%s: create chip_reset timer", __func__); |
| #if (KERNEL_VERSION(4, 15, 0) > LINUX_VERSION_CODE) |
| init_timer(&bdev->chip_reset_timer); |
| bdev->chip_reset_timer.function = btmtk_reset_timer; |
| bdev->chip_reset_timer.data = (unsigned long)bdev; |
| #else |
| timer_setup(&bdev->chip_reset_timer, btmtk_reset_timer, 0); |
| #endif |
| } |
| |
| void btmtk_reset_timer_update(struct btmtk_dev *bdev) |
| { |
| mod_timer(&bdev->chip_reset_timer, jiffies + CHIP_RESET_TIMEOUT * HZ); |
| } |
| |
| void btmtk_reset_timer_del(struct btmtk_dev *bdev) |
| { |
| if (timer_pending(&bdev->chip_reset_timer)) { |
| del_timer_sync(&bdev->chip_reset_timer); |
| BTMTK_INFO("%s exit", __func__); |
| } |
| } |
| |
| void btmtk_reset_waker(struct work_struct *work) |
| { |
| struct btmtk_dev *bdev = container_of(work, struct btmtk_dev, reset_waker); |
| struct btmtk_cif_state *cif_state = NULL; |
| struct btmtk_main_info *bmain_info = btmtk_get_main_info(); |
| int state = BTMTK_STATE_INIT; |
| int cif_event = 0, err = 0; |
| int cur = 0; |
| unsigned char fstate = BTMTK_FOPS_STATE_INIT; |
| |
| /* Check chip state is ok to do reset or not */ |
| state = btmtk_get_chip_state(bdev); |
| if (state == BTMTK_STATE_SUSPEND) { |
| BTMTK_INFO("%s suspend state don't do chip reset!", __func__); |
| return; |
| } |
| |
| if (state == BTMTK_STATE_PROBE) { |
| bmain_info->chip_reset_flag = 1; |
| BTMTK_INFO("%s just do whole chip reset in probe stage!", __func__); |
| } |
| |
| if (state == BTMTK_STATE_CLOSED) { |
| BTMTK_WARN("%s: chip is closed(%d), not trigger reset", __func__, state); |
| return; |
| } |
| |
| fstate = btmtk_fops_get_state(bdev); |
| if (fstate == BTMTK_FOPS_STATE_CLOSED) { |
| BTMTK_WARN("%s: fops is closed(%d), not trigger reset", __func__, fstate); |
| return; |
| } |
| |
| btmtk_reset_timer_del(bdev); |
| if (atomic_read(&bmain_info->chip_reset) || |
| atomic_read(&bmain_info->subsys_reset)) { |
| BTMTK_INFO("%s return, chip_reset = %d, subsys_reset = %d!", __func__, |
| atomic_read(&bmain_info->chip_reset), atomic_read(&bmain_info->subsys_reset)); |
| return; |
| } |
| |
| #if (USE_DEVICE_NODE == 0) |
| if (bmain_info->hif_hook.dump_debug_sop) |
| bmain_info->hif_hook.dump_debug_sop(bdev); |
| #endif |
| |
| DUMP_TIME_STAMP("chip_reset_start"); |
| cif_event = HIF_EVENT_SUBSYS_RESET; |
| if (BTMTK_CIF_IS_NULL(bdev, cif_event)) { |
| /* Error */ |
| BTMTK_WARN("%s priv setting is NULL", __func__); |
| return; |
| } |
| |
| #if (USE_DEVICE_NODE == 0) |
| if (!bdev->bt_cfg.support_dongle_reset) { |
| BTMTK_ERR("%s chip_reset is not support", __func__); |
| return; |
| } |
| #else |
| /* for only reset but no coredump */ |
| reinit_completion(&bdev->dump_comp); |
| #endif |
| |
| cif_state = &bdev->cif_state[cif_event]; |
| |
| /* Set Entering state */ |
| btmtk_set_chip_state((void *)bdev, cif_state->ops_enter); |
| |
| #if (USE_DEVICE_NODE == 1) |
| /* put after set chip state to avoid bt close without wait dump cr */ |
| if (bmain_info->hif_hook.dump_debug_sop) |
| bmain_info->hif_hook.dump_debug_sop(bdev); |
| #endif |
| |
| BTMTK_INFO("%s: Receive a byte (0xFF)", __func__); |
| /* read interrupt EP15 CR */ |
| |
| bdev->sco_num = 0; |
| |
| if (bmain_info->chip_reset_flag == 0 && |
| atomic_read(&bmain_info->subsys_reset_conti_count) < BTMTK_MAX_SUBSYS_RESET_COUNT) { |
| if (bmain_info->hif_hook.subsys_reset) { |
| cur = atomic_cmpxchg(&bmain_info->subsys_reset, BTMTK_RESET_DONE, BTMTK_RESET_DOING); |
| if (cur == BTMTK_RESET_DOING) { |
| BTMTK_INFO("%s: subsys reset in progress, return", __func__); |
| return; |
| } |
| DUMP_TIME_STAMP("subsys_chip_reset_start"); |
| /* |
| * Discard this part for SP platform since Consys power is off after BT off, |
| * Nothing is remain on memory after BT off, so leave do this at BT on |
| * for SP platform |
| */ |
| err = bmain_info->hif_hook.subsys_reset(bdev); |
| atomic_set(&bmain_info->subsys_reset, BTMTK_RESET_DONE); |
| if (err < 0) { |
| BTMTK_INFO("subsys reset failed, do whole chip reset!"); |
| goto L0RESET; |
| } |
| atomic_inc(&bmain_info->subsys_reset_count); |
| atomic_inc(&bmain_info->subsys_reset_conti_count); |
| DUMP_TIME_STAMP("subsys_chip_reset_end"); |
| |
| bmain_info->reset_stack_flag = HW_ERR_CODE_CHIP_RESET; |
| |
| err = btmtk_cap_init(bdev); |
| if (err < 0) { |
| BTMTK_ERR("btmtk init failed!"); |
| goto L0RESET; |
| } |
| #if (USE_DEVICE_NODE == 0) |
| |
| err = btmtk_load_rom_patch(bdev); |
| if (err < 0) { |
| BTMTK_INFO("btmtk load rom patch failed!"); |
| goto L0RESET; |
| } |
| #endif |
| btmtk_send_hw_err_to_host(bdev); |
| btmtk_woble_wake_unlock(bdev); |
| if (bmain_info->hif_hook.chip_reset_notify) |
| bmain_info->hif_hook.chip_reset_notify(bdev); |
| } else { |
| err = -1; |
| BTMTK_INFO("%s: Not support subsys chip reset", __func__); |
| goto L0RESET; |
| } |
| } else { |
| err = -1; |
| BTMTK_INFO("%s: chip_reset_flag is %d, subsys_reset_count %d", |
| __func__, |
| bmain_info->chip_reset_flag, |
| atomic_read(&bmain_info->subsys_reset_conti_count)); |
| } |
| |
| L0RESET: |
| if (err < 0) { |
| /* L0.5 reset failed or not support, do whole chip reset */ |
| /* TODO: need to confirm with usb host when suspend fail, to do chip reset, |
| * because usb3.0 need to toggle reset pin after hub_event unfreeze, |
| * otherwise, it will not occur disconnect on Capy Platform. When Mstar |
| * chip has usb3.0 port, we will use Mstar platform to do comparison |
| * test, then found the final solution. |
| */ |
| /* msleep(2000); */ |
| if (bmain_info->hif_hook.whole_reset) { |
| DUMP_TIME_STAMP("whole_chip_reset_start"); |
| bmain_info->hif_hook.whole_reset(bdev); |
| atomic_inc(&bmain_info->whole_reset_count); |
| DUMP_TIME_STAMP("whole_chip_reset_end"); |
| } else { |
| BTMTK_INFO("%s: Not support whole chip reset, reset reset_conti_count to 0", __func__); |
| atomic_set(&bmain_info->subsys_reset_conti_count, 0); |
| #if (USE_DEVICE_NODE == 1) |
| btmtk_send_hw_err_to_host(bdev); |
| #endif |
| } |
| } |
| |
| DUMP_TIME_STAMP("chip_reset_end"); |
| /* Set End/Error state */ |
| if (err < 0) |
| btmtk_set_chip_state((void *)bdev, cif_state->ops_error); |
| else |
| btmtk_set_chip_state((void *)bdev, cif_state->ops_end); |
| |
| #if (USE_DEVICE_NODE == 1) |
| complete(&bdev->dump_comp); |
| #endif |
| } |
| |
| void btmtk_reset_trigger(struct btmtk_dev *bdev) |
| { |
| struct btmtk_main_info *bmain_info = btmtk_get_main_info(); |
| |
| if (atomic_read(&bmain_info->chip_reset) || |
| atomic_read(&bmain_info->subsys_reset)) { |
| BTMTK_INFO("%s return, chip_reset = %d, subsys_reset = %d!", __func__, |
| atomic_read(&bmain_info->chip_reset), atomic_read(&bmain_info->subsys_reset)); |
| return; |
| } |
| |
| schedule_work(&bdev->reset_waker); |
| } |
| |