| /****************************************************************************** |
| * |
| * This file is provided under a dual license. When you use or |
| * distribute this software, you may choose to be licensed under |
| * version 2 of the GNU General Public License ("GPLv2 License") |
| * or BSD License. |
| * |
| * GPLv2 License |
| * |
| * Copyright(C) 2017 MediaTek Inc. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of version 2 of the GNU General Public License 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. |
| * |
| * BSD LICENSE |
| * |
| * Copyright(C) 2017 MediaTek Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in |
| * the documentation and/or other materials provided with the |
| * distribution. |
| * * Neither the name of the copyright holder nor the names of its |
| * contributors may be used to endorse or promote products derived |
| * from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| * |
| *****************************************************************************/ |
| /*! \file reset.c |
| * \brief reset module |
| * |
| * This file contains all implementations of reset module |
| */ |
| |
| |
| /******************************************************************************* |
| * C O M P I L E R F L A G S |
| ******************************************************************************** |
| */ |
| |
| /******************************************************************************* |
| * E X T E R N A L R E F E R E N C E S |
| ******************************************************************************** |
| */ |
| #include <linux/kernel.h> |
| #include <linux/version.h> |
| #include <linux/slab.h> |
| #include <linux/wait.h> |
| #include <linux/list.h> |
| #include <linux/mutex.h> |
| #include <linux/kthread.h> |
| #include <linux/timer.h> |
| #include "precomp.h" |
| #include "reset.h" |
| |
| |
| |
| /********************************************************************** |
| * M A C R O S |
| *********************************************************************** |
| */ |
| #ifndef MSEC_PER_SEC |
| #define MSEC_PER_SEC 1000 |
| #endif |
| |
| /********************************************************************** |
| * C O N S T A N T S |
| *********************************************************************** |
| */ |
| MODULE_LICENSE("Dual BSD/GPL"); |
| |
| const char *eventName[RFSM_EVENT_MAX + 1] = { |
| "RFSM_EVENT_TRIGGER_RESET", |
| "RFSM_EVENT_TIMEOUT", |
| "RFSM_EVENT_PROBE_START", |
| "RFSM_EVENT_PROBE_FAIL", |
| "RFSM_EVENT_PROBE_SUCCESS", |
| "RFSM_EVENT_REMOVE", |
| "RFSM_EVENT_L0_RESET_READY", |
| "RFSM_EVENT_L0_RESET_GOING", |
| "RFSM_EVENT_L0_RESET_DONE", |
| "RFSM_EVENT_All" |
| }; |
| |
| |
| /********************************************************************** |
| * D A T A T Y P E S |
| *********************************************************************** |
| */ |
| struct ResetInfo { |
| wait_queue_head_t resetko_waitq; |
| struct task_struct *resetko_thread; |
| |
| struct mutex moduleMutex; |
| struct mutex eventMutex; |
| struct list_head moduleList; |
| struct list_head eventList; |
| }; |
| |
| struct ResetEvent { |
| struct list_head node; |
| enum ModuleType module; |
| enum ResetFsmEvent event; |
| }; |
| |
| |
| /********************************************************************** |
| * F U N C T I O N D E C L A R A T I O N S |
| *********************************************************************** |
| */ |
| |
| /********************************************************************** |
| * P U B L I C D A T A |
| *********************************************************************** |
| */ |
| uint8_t fgIsResetTrigger = false; |
| uint8_t fgIsBtTriggered = false; |
| EXPORT_SYMBOL(fgIsResetTrigger); |
| EXPORT_SYMBOL(fgIsBtTriggered); |
| |
| /********************************************************************** |
| * P R I V A T E D A T A |
| *********************************************************************** |
| */ |
| static struct ResetInfo resetInfo = {0}; |
| static char moduleName[RESET_MODULE_TYPE_MAX][RFSM_NAME_MAX_LEN]; |
| static bool fgL0ResetDone; |
| static bool fgExit; |
| |
| /********************************************************************** |
| * F U N C T I O N S |
| **********************************************************************/ |
| static struct FsmEntity *findResetFsm(enum ModuleType module) |
| { |
| struct FsmEntity *fsm, *next_fsm; |
| |
| list_for_each_entry_safe(fsm, next_fsm, &resetInfo.moduleList, node) { |
| if (fsm->eModuleType == module) |
| return fsm; |
| } |
| |
| return NULL; |
| } |
| |
| static void removeResetFsm(enum ModuleType module) |
| { |
| struct FsmEntity *fsm, *next_fsm; |
| |
| list_for_each_entry_safe(fsm, next_fsm, &resetInfo.moduleList, node) { |
| if (fsm->eModuleType == module) { |
| list_del(&fsm->node); |
| freeResetFsm(fsm); |
| } |
| } |
| } |
| |
| static void addResetFsm(struct FsmEntity *fsm) |
| { |
| if (!fsm) |
| return; |
| |
| list_add_tail(&fsm->node, &resetInfo.moduleList); |
| } |
| |
| static struct ResetEvent *allocResetEvent(void) |
| { |
| return kmalloc(sizeof(struct ResetEvent), GFP_KERNEL); |
| } |
| |
| static void freeResetEvent(struct ResetEvent *event) |
| { |
| if (!event) |
| kfree(event); |
| } |
| |
| static bool isEventEmpty(void) |
| { |
| mutex_lock(&resetInfo.eventMutex); |
| if (list_empty(&resetInfo.eventList)) { |
| mutex_unlock(&resetInfo.eventMutex); |
| return true; |
| } |
| mutex_unlock(&resetInfo.eventMutex); |
| return false; |
| } |
| |
| static void pushResetEvent(struct ResetEvent *event) |
| { |
| if (!event || |
| ((unsigned int)event->module >= RESET_MODULE_TYPE_MAX) || |
| ((unsigned int)event->event >= RFSM_EVENT_MAX)) { |
| MR_Err("%s: argument error\n", __func__); |
| return; |
| } |
| |
| mutex_lock(&resetInfo.eventMutex); |
| list_add_tail(&event->node, &resetInfo.eventList); |
| mutex_unlock(&resetInfo.eventMutex); |
| } |
| |
| static struct ResetEvent *popResetEvent(void) |
| { |
| struct ResetEvent *event; |
| |
| mutex_lock(&resetInfo.eventMutex); |
| if (list_empty(&resetInfo.eventList)) { |
| mutex_unlock(&resetInfo.eventMutex); |
| return NULL; |
| } |
| event = list_first_entry(&resetInfo.eventList, struct ResetEvent, node); |
| list_del(&event->node); |
| mutex_unlock(&resetInfo.eventMutex); |
| return event; |
| } |
| |
| static void removeResetEvent(enum ModuleType module, |
| enum ResetFsmEvent event) |
| { |
| struct ResetEvent *cur, *next; |
| |
| mutex_lock(&resetInfo.eventMutex); |
| if (list_empty(&resetInfo.eventList)) { |
| mutex_unlock(&resetInfo.eventMutex); |
| return; |
| } |
| list_for_each_entry_safe(cur, next, &resetInfo.eventList, node) { |
| if ((cur->module == module) && |
| ((cur->event == event) || (event == RFSM_EVENT_All))) { |
| list_del(&cur->node); |
| freeResetEvent(cur); |
| } |
| } |
| mutex_unlock(&resetInfo.eventMutex); |
| } |
| |
| static int resetko_thread_main(void *data) |
| { |
| struct FsmEntity *fsm; |
| struct ResetEvent *resetEvent; |
| enum ModuleType module, begin, end; |
| int ret = 0; |
| unsigned int evt; |
| |
| MR_Info("%s: start\n", __func__); |
| |
| while (!fgExit) { |
| do { |
| ret = wait_event_interruptible(resetInfo.resetko_waitq, |
| isEventEmpty() == false); |
| } while (ret != 0); |
| |
| while (resetEvent = popResetEvent(), resetEvent != NULL) { |
| evt = (unsigned int)resetEvent->event; |
| if (evt > RFSM_EVENT_MAX) |
| continue; |
| mutex_lock(&resetInfo.moduleMutex); |
| /* check if reset is triggered by BT */ |
| if ((evt == RFSM_EVENT_TRIGGER_RESET) && |
| (fgIsResetTrigger == false)) { |
| if (resetEvent->module == RESET_MODULE_TYPE_BT) { |
| fgIsResetTrigger = true; |
| fgIsBtTriggered = true; |
| } else { |
| fgIsResetTrigger = true; |
| } |
| } |
| /* loop for all related module */ |
| if ((evt == RFSM_EVENT_TRIGGER_RESET) || |
| (evt == RFSM_EVENT_L0_RESET_GOING) || |
| (evt == RFSM_EVENT_L0_RESET_DONE)) { |
| begin = 0; |
| end = RESET_MODULE_TYPE_MAX - 1; |
| } else { |
| begin = resetEvent->module; |
| end = resetEvent->module; |
| } |
| for (module = begin; module <= end; module++) { |
| fsm = findResetFsm(module); |
| if (fsm != NULL) { |
| if (evt == RFSM_EVENT_L0_RESET_READY) |
| fsm->fgReadyForReset = ~false; |
| MR_Info("[%s] in [%s] state rcv [%s]\n", |
| fsm->name, |
| fsm->fsmState->name, |
| eventName[evt]); |
| resetFsmHandlevent(fsm, evt); |
| } |
| } |
| mutex_unlock(&resetInfo.moduleMutex); |
| freeResetEvent(resetEvent); |
| } |
| } |
| |
| MR_Info("%s: stop\n", __func__); |
| |
| return 0; |
| } |
| |
| void resetkoNotifyEvent(struct FsmEntity *fsm, enum ModuleNotifyEvent event) |
| { |
| if (!fsm) { |
| MR_Err("%s: fsm is NULL\n", __func__); |
| return; |
| } |
| if (fsm->notifyFunc != NULL) { |
| MR_Info("[%s] %s %d\n", fsm->name, __func__, event); |
| fsm->notifyFunc((unsigned int)event, NULL); |
| } |
| } |
| |
| void clearAllModuleReadyForReset(void) |
| { |
| struct FsmEntity *fsm, *next_fsm; |
| |
| /* mutex is hold in function resetko_thread_main */ |
| list_for_each_entry_safe(fsm, next_fsm, &resetInfo.moduleList, node) { |
| fsm->fgReadyForReset = false; |
| } |
| } |
| |
| bool isAllModuleReadyForReset(void) |
| { |
| struct FsmEntity *fsm, *next_fsm; |
| bool ret = ~false; |
| |
| /* mutex is hold in function resetko_thread_main */ |
| list_for_each_entry_safe(fsm, next_fsm, &resetInfo.moduleList, node) { |
| MR_Info("[%s] %s: %s\n", fsm->name, __func__, |
| fsm->fgReadyForReset ? "ready" : "not ready"); |
| if (!fsm->fgReadyForReset) |
| ret = false; |
| } |
| |
| /* clear L0ResetDone flag when check all module is ready for reset */ |
| fgL0ResetDone = false; |
| |
| return ret; |
| } |
| |
| void callResetFuncByResetApiType(struct FsmEntity *fsm) |
| { |
| struct FsmEntity *cur_fsm, *next_fsm; |
| unsigned int i; |
| |
| if (fgL0ResetDone) { |
| MR_Info("[%s] %s L0ResetDone\n", fsm->name, __func__); |
| return; |
| } |
| |
| for (i = TRIGGER_RESET_TYPE_UNSUPPORT; i < TRIGGER_RESET_API_TYPE_MAX; |
| i++) { |
| list_for_each_entry_safe(cur_fsm, next_fsm, |
| &resetInfo.moduleList, node) { |
| if (cur_fsm->resetApiType == |
| TRIGGER_RESET_TYPE_UNSUPPORT){ |
| MR_Err("[%s] %s module don't support reset\n", |
| cur_fsm->name, __func__); |
| fgL0ResetDone = true; |
| return; |
| } |
| if ((cur_fsm->resetApiType == i) && |
| (cur_fsm->resetFunc != NULL)) { |
| MR_Info("[%s] %s\n", cur_fsm->name, __func__); |
| cur_fsm->resetFunc(); |
| break; |
| } |
| } |
| } |
| fgL0ResetDone = true; |
| |
| /* internal send reset done event */ |
| send_reset_event(fsm->eModuleType, RFSM_EVENT_L0_RESET_DONE); |
| } |
| |
| |
| #if KERNEL_VERSION(4, 15, 0) <= LINUX_VERSION_CODE |
| static void resetkoTimeoutHandler(struct timer_list *timer) |
| #else |
| static void resetkoTimeoutHandler(unsigned long arg) |
| #endif |
| { |
| #if KERNEL_VERSION(4, 15, 0) <= LINUX_VERSION_CODE |
| struct FsmEntity *fsm = from_timer(fsm, timer, resetTimer); |
| #else |
| struct FsmEntity *fsm = (struct FsmEntity *)arg; |
| #endif |
| if (!fsm) { |
| MR_Err("%s: fsm is null\n", __func__); |
| return; |
| } |
| |
| MR_Info("[%s] %s\n", fsm->name, __func__); |
| send_reset_event(fsm->eModuleType, RFSM_EVENT_TIMEOUT); |
| } |
| |
| void resetkoStartTimer(struct FsmEntity *fsm, unsigned int ms) |
| { |
| if (!fsm) { |
| MR_Err("%s: fsm is null\n", __func__); |
| return; |
| } |
| MR_Info("[%s] %s %dms\n", fsm->name, __func__, ms); |
| if (ms == 0) |
| return; |
| |
| mod_timer(&fsm->resetTimer, jiffies + ms * HZ / MSEC_PER_SEC); |
| } |
| |
| void resetkoCancleTimer(struct FsmEntity *fsm) |
| { |
| if (!fsm) { |
| MR_Err("%s: fsm is null\n", __func__); |
| return; |
| } |
| MR_Info("[%s] %s\n", fsm->name, __func__); |
| |
| del_timer(&fsm->resetTimer); |
| removeResetEvent(fsm->eModuleType, RFSM_EVENT_TIMEOUT); |
| } |
| |
| enum ReturnStatus send_reset_event(enum ModuleType module, |
| enum ResetFsmEvent event) |
| { |
| struct ResetEvent *resetEvent; |
| |
| if (((unsigned int)module >= RESET_MODULE_TYPE_MAX) || |
| ((unsigned int)event >= RFSM_EVENT_MAX)) { |
| MR_Err("%s: args error %d %d\n", __func__, module, event); |
| return RESET_RETURN_STATUS_FAIL; |
| } |
| |
| resetEvent = allocResetEvent(); |
| if (!resetEvent) { |
| MR_Err("%s: allocResetEvent fail\n", __func__); |
| return RESET_RETURN_STATUS_FAIL; |
| } |
| |
| MR_Info("[%s] %s %s\n", moduleName[module], __func__, eventName[event]); |
| resetEvent->module = module; |
| resetEvent->event = event; |
| pushResetEvent(resetEvent); |
| |
| wake_up_interruptible(&resetInfo.resetko_waitq); |
| |
| return RESET_RETURN_STATUS_SUCCESS; |
| } |
| EXPORT_SYMBOL(send_reset_event); |
| |
| enum ReturnStatus send_msg_to_module(enum ModuleType srcModule, |
| enum ModuleType dstModule, |
| void *msg) |
| { |
| struct FsmEntity *srcfsm, *dstfsm; |
| |
| if (!msg) { |
| MR_Err("%s: %d -> %d, msg is NULL\n", |
| __func__, srcModule, dstModule); |
| } |
| |
| mutex_lock(&resetInfo.moduleMutex); |
| srcfsm = findResetFsm(srcModule); |
| if (!srcfsm) { |
| MR_Err("%s: src module (%d) not exist\n", |
| __func__, srcModule); |
| goto SEND_MSG_FAIL; |
| } |
| dstfsm = findResetFsm(dstModule); |
| if (!dstfsm) { |
| MR_Err("%s: dst module (%d) not exist\n", |
| __func__, dstModule); |
| goto SEND_MSG_FAIL; |
| } |
| if (!dstfsm->notifyFunc) { |
| MR_Err("%s: dst module (%d) notifyFunc is NULL\n", |
| __func__, dstModule); |
| goto SEND_MSG_FAIL; |
| } |
| if (dstfsm->notifyFunc != NULL) { |
| MR_Info("%s: module(%s) -> module(%s)\n", |
| __func__, srcfsm->name, dstfsm->name); |
| dstfsm->notifyFunc((unsigned int)MODULE_NOTIFY_MESSAGE, msg); |
| } |
| mutex_unlock(&resetInfo.moduleMutex); |
| return RESET_RETURN_STATUS_SUCCESS; |
| |
| SEND_MSG_FAIL: |
| mutex_unlock(&resetInfo.moduleMutex); |
| return RESET_RETURN_STATUS_FAIL; |
| } |
| EXPORT_SYMBOL(send_msg_to_module); |
| |
| |
| enum ReturnStatus resetko_register_module(enum ModuleType module, |
| char *name, |
| enum TriggerResetApiType resetApiType, |
| void *resetFunc, |
| void *notifyFunc) |
| { |
| struct FsmEntity *fsm; |
| |
| if (!name) { |
| MR_Err("%s: insmod module(%d) with no name\n", |
| __func__, module); |
| return RESET_RETURN_STATUS_FAIL; |
| } |
| fsm = allocResetFsm(name, module, resetApiType); |
| if (!fsm) { |
| MR_Err("%s: allocResetFsm module(%d) fail\n", __func__, module); |
| return RESET_RETURN_STATUS_FAIL; |
| } |
| fsm->notifyFunc = (NotifyFunc)notifyFunc; |
| fsm->resetFunc = (ResetFunc)resetFunc; |
| |
| mutex_lock(&resetInfo.moduleMutex); |
| if (findResetFsm(module) != NULL) { |
| freeResetFsm(fsm); |
| mutex_unlock(&resetInfo.moduleMutex); |
| MR_Err("%s: insmod module(%d) existed\n", |
| __func__, module); |
| return RESET_RETURN_STATUS_FAIL; |
| } |
| |
| #if (KERNEL_VERSION(4, 15, 0) <= LINUX_VERSION_CODE) |
| timer_setup(&fsm->resetTimer, resetkoTimeoutHandler, 0); |
| #else |
| init_timer(&fsm->resetTimer); |
| fsm->resetTimer.function = resetkoTimeoutHandler; |
| fsm->resetTimer.data = (unsigned long)fsm; |
| #endif |
| memcpy(&moduleName[module][0], fsm->name, RFSM_NAME_MAX_LEN); |
| addResetFsm(fsm); |
| mutex_unlock(&resetInfo.moduleMutex); |
| |
| MR_Info("[%s] %s, module type %d, reset type %d\n", |
| name, __func__, module, resetApiType); |
| |
| return RESET_RETURN_STATUS_SUCCESS; |
| } |
| EXPORT_SYMBOL(resetko_register_module); |
| |
| |
| enum ReturnStatus resetko_unregister_module(enum ModuleType module) |
| { |
| struct FsmEntity *fsm; |
| |
| mutex_lock(&resetInfo.moduleMutex); |
| fsm = findResetFsm(module); |
| if (!fsm) { |
| mutex_unlock(&resetInfo.moduleMutex); |
| MR_Err("%s: rmmod module(%d) not exist\n", |
| __func__, module); |
| return RESET_RETURN_STATUS_SUCCESS; |
| } |
| resetkoCancleTimer(fsm); |
| removeResetEvent(module, RFSM_EVENT_All); |
| |
| MR_Info("[%s] %s, module type %d\n", fsm->name, __func__, module); |
| removeResetFsm(module); |
| mutex_unlock(&resetInfo.moduleMutex); |
| |
| return RESET_RETURN_STATUS_SUCCESS; |
| } |
| EXPORT_SYMBOL(resetko_unregister_module); |
| |
| |
| static int __init resetInit(void) |
| { |
| MR_Info("%s\n", __func__); |
| |
| fgL0ResetDone = false; |
| fgExit = false; |
| |
| mutex_init(&resetInfo.moduleMutex); |
| mutex_init(&resetInfo.eventMutex); |
| INIT_LIST_HEAD(&resetInfo.moduleList); |
| INIT_LIST_HEAD(&resetInfo.eventList); |
| init_waitqueue_head(&resetInfo.resetko_waitq); |
| resetInfo.resetko_thread = kthread_run(resetko_thread_main, |
| NULL, "resetko_thread"); |
| |
| return 0; |
| } |
| |
| static void __exit resetExit(void) |
| { |
| int i; |
| |
| for (i = 0; i < RESET_MODULE_TYPE_MAX; i++) |
| resetko_unregister_module((enum ModuleType)i); |
| fgExit = true; |
| |
| MR_Info("%s\n", __func__); |
| } |
| |
| module_init(resetInit); |
| module_exit(resetExit); |
| |