/* * This file contains Linux-specific MCC library functions * * Copyright (C) 2014 Freescale Semiconductor, Inc. All Rights Reserved. * * * SPDX-License-Identifier: GPL-2.0+ and/or BSD-3-Clause * The GPL-2.0+ license for this file can be found in the COPYING.GPL file * included with this distribution or at * http://www.gnu.org/licenses/gpl-2.0.html * The BSD-3-Clause License for this file can be found in the COPYING.BSD file * included with this distribution or at * http://opensource.org/licenses/BSD-3-Clause */ #include #include #include #include #include #include #include #include #include #include #include "mcc_config.h" #include #include #include #include #include #include "common.h" /* Global variables */ struct regmap *imx_mu_reg; unsigned long mcc_shm_offset; static struct imx_sema4_mutex *shm_ptr; static unsigned int cpu_to_cpu_isr_vector = MCC_VECTOR_NUMBER_INVALID; static struct delayed_work mu_work; static u32 m4_wake_irqs[4]; unsigned int m4_message; bool m4_freq_low; unsigned int imx_mcc_buffer_freed = 0, imx_mcc_buffer_queued = 0; DECLARE_WAIT_QUEUE_HEAD(buffer_freed_wait_queue); /* Used for blocking send */ DECLARE_WAIT_QUEUE_HEAD(buffer_queued_wait_queue); /* Used for blocking recv */ MCC_BOOKEEPING_STRUCT *bookeeping_data; /*! * \brief This function is the CPU-to-CPU interrupt handler. * * Each core can interrupt the other. There are two logical signals: * \n - Receive data available for (Node,Port) - signaled when a buffer * is queued to a Receive Data Queue. * \n - Buffer available - signaled when a buffer is queued to the Free * Buffer Queue. * \n It is possible that several signals can occur while one interrupt * is being processed. * Therefore, a Receive Signal Queue of received signals is also required * - one for each core. * The interrupting core queues to the tail and the interrupted core pulls * from the head. * For a circular file, no semaphore is required since only the sender * modifies the tail and only the receiver modifies the head. * * \param[in] param Pointer to data being passed to the ISR. */ static irqreturn_t mcc_cpu_to_cpu_isr(int irq, void *param) { MCC_SIGNAL serviced_signal; unsigned int irqs = mcc_get_mu_irq(); if (irqs & (1 << 31)) { /* * Try to lock the core mutex. If successfully locked, perform * mcc_dequeue_signal(), release the gate and finally clear the * interrupt flag. If trylock fails (HW semaphore already locked * by another core), do not clear the interrupt flag – this * way the CPU-to-CPU isr is re-issued again until the HW * semaphore is locked. Higher priority ISRs will be serviced * while issued at the time we are waiting for the unlocked * gate. To prevent trylog failure due to core mutex currently * locked by our own core(a task), the cpu-to-cpu isr is * temporarily disabled when mcc_get_semaphore() is called and * re-enabled again when mcc_release_semaphore() is issued. */ if (SEMA4_A9_LOCK == imx_sema4_mutex_trylock(shm_ptr)) { while (MCC_SUCCESS == mcc_dequeue_signal( MCC_CORE_NUMBER, &serviced_signal)) { if ((serviced_signal.type == BUFFER_QUEUED) && (serviced_signal.destination.core == MCC_CORE_NUMBER)) { /* * Unblock receiver, in case of * asynchronous communication */ imx_mcc_buffer_queued = 1; wake_up(&buffer_queued_wait_queue); } else if (serviced_signal.type == BUFFER_FREED) { /* * Unblock sender, in case of * asynchronous communication */ imx_mcc_buffer_freed = 1; wake_up(&buffer_freed_wait_queue); } } /* Clear the interrupt flag */ mcc_clear_cpu_to_cpu_interrupt(MCC_CORE_NUMBER); /* Unlocks the core mutex */ imx_sema4_mutex_unlock(shm_ptr); } } if (irqs & (1 << 27)) { m4_message = mcc_handle_mu_receive_irq(); schedule_delayed_work(&mu_work, 0); } return IRQ_HANDLED; } /*! * \brief This function initializes the hw semaphore (SEMA4). * * Calls core-mutex driver to create a core mutex. * * \param[in] sem_num SEMA4 gate number. */ int mcc_init_semaphore(unsigned int sem_num) { /* Create a core mutex */ shm_ptr = imx_sema4_mutex_create(0, sem_num); if (NULL == shm_ptr) return MCC_ERR_SEMAPHORE; else return MCC_SUCCESS; } /*! * \brief This function de-initializes the hw semaphore (SEMA4). * * Calls core-mutex driver to destroy a core mutex. * * \param[in] sem_num SEMA4 gate number. */ int mcc_deinit_semaphore(unsigned int sem_num) { /* Destroy the core mutex */ if (0 == imx_sema4_mutex_destroy(shm_ptr)) return MCC_SUCCESS; else return MCC_ERR_SEMAPHORE; } /*! * \brief This function locks the specified core mutex. * * Calls core-mutex driver to lock the core mutex. * */ int mcc_get_semaphore(void) { if (imx_mcc_bsp_int_disable(cpu_to_cpu_isr_vector)) { pr_err("ERR:failed to disable mcc int.\n"); return MCC_ERR_SEMAPHORE; } if (0 == imx_sema4_mutex_lock(shm_ptr)) { return MCC_SUCCESS; } else { if (imx_mcc_bsp_int_enable(cpu_to_cpu_isr_vector)) { pr_err("ERR:failed to enable mcc int.\n"); return MCC_ERR_INT; } return MCC_ERR_SEMAPHORE; } } /*! * \brief This function unlocks the specified core mutex. * * Calls core-mutex driver to unlock the core mutex. * */ int mcc_release_semaphore(void) { if (0 == imx_sema4_mutex_unlock(shm_ptr)) { /* * Enable the cpu-to-cpu isr just in case imx_semae_mutex_unlock * function has not woke up another task waiting for the core * mutex. */ if (shm_ptr->gate_val != (MCC_CORE_NUMBER + 1)) imx_mcc_bsp_int_enable(cpu_to_cpu_isr_vector); return MCC_SUCCESS; } return MCC_ERR_SEMAPHORE; } void mcc_enable_m4_irqs_in_gic(bool enable) { int i, j; for (i = 0; i < 4; i++) { if (m4_wake_irqs[i] == 0) continue; for (j = 0; j < 32; j++) { if (m4_wake_irqs[i] & (1 << j)) { if (enable) enable_irq((i + 1) * 32 + j); else disable_irq((i + 1) * 32 + j); } } } } static irqreturn_t mcc_m4_dummy_isr(int irq, void *param) { return IRQ_HANDLED; } static void mu_work_handler(struct work_struct *work) { int ret; u32 irq, enable, idx, mask; pr_debug("receive M4 message 0x%x\n", m4_message); switch (m4_message) { case MU_LPM_M4_REQUEST_HIGH_BUS: request_bus_freq(BUS_FREQ_HIGH); imx6sx_set_m4_highfreq(true); mcc_send_via_mu_buffer(MU_LPM_HANDSHAKE_INDEX, MU_LPM_BUS_HIGH_READY_FOR_M4); m4_freq_low = false; break; case MU_LPM_M4_RELEASE_HIGH_BUS: release_bus_freq(BUS_FREQ_HIGH); imx6sx_set_m4_highfreq(false); mcc_send_via_mu_buffer(MU_LPM_HANDSHAKE_INDEX, MU_LPM_M4_FREQ_CHANGE_READY); m4_freq_low = true; break; default: if ((m4_message & MU_LPM_M4_WAKEUP_SRC_MASK) == MU_LPM_M4_WAKEUP_SRC_VAL) { irq = (m4_message & MU_LPM_M4_WAKEUP_IRQ_MASK) >> MU_LPM_M4_WAKEUP_IRQ_SHIFT; enable = (m4_message & MU_LPM_M4_WAKEUP_ENABLE_MASK) >> MU_LPM_M4_WAKEUP_ENABLE_SHIFT; idx = irq / 32 - 1; mask = 1 << irq % 32; if (enable && can_request_irq(irq, 0)) { ret = request_irq(irq, mcc_m4_dummy_isr, IRQF_NO_SUSPEND, "imx-m4-dummy", NULL); if (ret) { pr_err("%s: register interrupt %d failed, rc %d\n", __func__, irq, ret); break; } disable_irq(irq); m4_wake_irqs[idx] = m4_wake_irqs[idx] | mask; } imx_gpc_add_m4_wake_up_irq(irq, enable); } break; } m4_message = 0; mcc_enable_receive_irq(1); } /*! * \brief This function registers the CPU-to-CPU interrupt. * * Calls interrupt component functions to install and enable the * CPU-to-CPU interrupt. * */ int mcc_register_cpu_to_cpu_isr(void) { int ret; unsigned int vector_number; vector_number = mcc_get_cpu_to_cpu_vector(MCC_CORE_NUMBER); if (vector_number != MCC_VECTOR_NUMBER_INVALID) { imx_mu_reg = syscon_regmap_lookup_by_compatible("fsl,imx6sx-mu"); if (IS_ERR(imx_mu_reg)) pr_err("ERR:unable to find imx mu registers\n"); ret = request_irq(vector_number, mcc_cpu_to_cpu_isr, IRQF_EARLY_RESUME, "imx-linux-mcc", NULL); if (ret) { pr_err("%s: register interrupt %d failed, rc %d\n", __func__, vector_number, ret); return MCC_ERR_INT; } INIT_DELAYED_WORK(&mu_work, mu_work_handler); /* Make sure the INT is cleared */ mcc_clear_cpu_to_cpu_interrupt(MCC_CORE_NUMBER); /* Enable INT */ ret = imx_mcc_bsp_int_enable(vector_number); if (ret < 0) { pr_err("ERR:failed to enable mcc int.\n"); return MCC_ERR_INT; } /* MU always as a wakeup source for low power mode */ imx_gpc_add_m4_wake_up_irq(vector_number, true); cpu_to_cpu_isr_vector = vector_number; return MCC_SUCCESS; } else { return MCC_ERR_INT; } } /*! * \brief This function triggers an interrupt to other core(s). * */ int mcc_generate_cpu_to_cpu_interrupt(void) { int ret; /* * Assert directed CPU interrupts for all processors except * the requesting core */ ret = mcc_triger_cpu_to_cpu_interrupt(); if (ret == 0) return MCC_SUCCESS; else return ret; } /*! * \brief This function copies data. * * Copies the number of single-addressable units from the source address * to destination address. * * \param[in] src Source address. * \param[in] dest Destination address. * \param[in] size Number of single-addressable units to copy. */ void mcc_memcpy(void *src, void *dest, unsigned int size) { memcpy(dest, src, size); } void *mcc_virt_to_phys(void *x) { if (null == x) return NULL; else return (void *)((unsigned long) (x) - mcc_shm_offset); } void *mcc_phys_to_virt(void *x) { if (null == x) return NULL; else return (void *)((unsigned long) (x) + mcc_shm_offset); } int mcc_init_os_sync(void) { /* No used in linux */ return MCC_SUCCESS; } int mcc_deinit_os_sync(void) { /* No used in linux */ return MCC_SUCCESS; } void mcc_clear_os_sync_for_ep(MCC_ENDPOINT *endpoint) { /* No used in linux */ } int mcc_wait_for_buffer_freed(MCC_RECEIVE_BUFFER **buffer, unsigned int timeout) { int return_value; unsigned long timeout_j; /* jiffies */ MCC_RECEIVE_BUFFER *buf = null; /* * Blocking calls: CPU-to-CPU ISR sets the event and thus * resumes tasks waiting for a free MCC buffer. * As the interrupt request is send to all cores when a buffer * is freed it could happen that several tasks from different * cores/nodes are waiting for a free buffer and all of them * are notified that the buffer has been freed. This function * has to check (after the wake up) that a buffer is really * available and has not been already grabbed by another * "competitor task" that has been faster. If so, it has to * wait again for the next notification. */ while (buf == null) { if (timeout == 0xffffffff) { /* * In order to level up the robust, do not always * wait event here. Wake up itself after every 1~s. */ timeout_j = usecs_to_jiffies(1000); wait_event_timeout(buffer_freed_wait_queue, imx_mcc_buffer_freed == 1, timeout_j); } else { timeout_j = msecs_to_jiffies(timeout); wait_event_timeout(buffer_freed_wait_queue, imx_mcc_buffer_freed == 1, timeout_j); } return_value = mcc_get_semaphore(); if (return_value != MCC_SUCCESS) return return_value; MCC_DCACHE_INVALIDATE_MLINES((void *) &bookeeping_data->free_list, sizeof(MCC_RECEIVE_LIST *)); buf = mcc_dequeue_buffer(&bookeeping_data->free_list); mcc_release_semaphore(); if (imx_mcc_buffer_freed) imx_mcc_buffer_freed = 0; } *buffer = buf; return MCC_SUCCESS; } int mcc_wait_for_buffer_queued(MCC_ENDPOINT *endpoint, unsigned int timeout) { unsigned long timeout_j; /* jiffies */ MCC_RECEIVE_LIST *tmp_list; /* Get list of buffers kept by the particular endpoint */ tmp_list = mcc_get_endpoint_list(*endpoint); if (timeout == 0xffffffff) { wait_event(buffer_queued_wait_queue, imx_mcc_buffer_queued == 1); mcc_get_semaphore(); /* * double check if the tmp_list head is still null * or not, if yes, wait again. */ while (tmp_list->head == null) { imx_mcc_buffer_queued = 0; mcc_release_semaphore(); wait_event(buffer_queued_wait_queue, imx_mcc_buffer_queued == 1); mcc_get_semaphore(); } } else { timeout_j = msecs_to_jiffies(timeout); wait_event_timeout(buffer_queued_wait_queue, imx_mcc_buffer_queued == 1, timeout_j); mcc_get_semaphore(); } if (imx_mcc_buffer_queued) imx_mcc_buffer_queued = 0; if (tmp_list->head == null) { pr_err("%s can't get queued buffer.\n", __func__); mcc_release_semaphore(); return MCC_ERR_TIMEOUT; } tmp_list->head = (MCC_RECEIVE_BUFFER *) MCC_MEM_PHYS_TO_VIRT(tmp_list->head); mcc_release_semaphore(); return MCC_SUCCESS; } MCC_BOOKEEPING_STRUCT *mcc_get_bookeeping_data(void) { MCC_BOOKEEPING_STRUCT *bookeeping_data; bookeeping_data = (MCC_BOOKEEPING_STRUCT *)ioremap_nocache (MCC_BASE_ADDRESS, sizeof(struct mcc_bookeeping_struct)); mcc_shm_offset = (unsigned long)bookeeping_data - (unsigned long)MCC_BASE_ADDRESS; return bookeeping_data; }