// SPDX-License-Identifier: GPL-2.0 // // Copyright (c) 2021 MediaTek Inc. #include #include #include #include #include #include "mtk_ccu_common.h" #include "mtk_ccu_isp71.h" #if defined(SECURE_CCU) #include #include #endif #define MTK_CCU_IPI_TAG "[ccu_ipc]" #define LOG_DBG_IPI(format, args...) \ pr_info(MTK_CCU_IPI_TAG "[%s] " format, __func__, ##args) #define MTK_CCU_IPC_NO_ACK 0x66669999 #define MTK_CCU_IPC_CMD_TIMEOUT_SPEC 30 //3000us = 3ms #define MTK_CCU_IPC_IOBUF_CAPACITY (1024*4-16-64-64) struct __aligned(8) shared_buf_map { /*** from CCU->APMCU ***/ uint32_t ipc_data_addr_ccu; uint32_t ipc_data_base_offset; uint32_t ipc_base_offset; uint32_t ipc_data_size; }; static int mtk_ccu_copyCmdInData(struct mtk_ccu *ccu, void *inDataPtr, uint32_t inDataSize) { uint32_t i; uint32_t *realIoBuf = (uint32_t *)ccu->ccu_ipc.ipcDataPtr; if (inDataSize == 0) return 0; if (inDataSize >= ccu->ccu_ipc.ipcDataSize) { dev_err(ccu->dev, "failed: data length over capacity(%d <= %d)\n", ccu->ccu_ipc.ipcDataSize, inDataSize); return -EINVAL; } for (i = 0; i < (inDataSize/4) ; i++) writel(((uint32_t *)inDataPtr)[i], &realIoBuf[i]); return 0; } static int mtk_ccu_copyCmdOutData(struct mtk_ccu *ccu, void *outDataPtr, uint32_t outDataSize) { uint32_t i; uint32_t *realIoBuf = (uint32_t *)ccu->ccu_ipc.ipcDataPtr; if (outDataSize == 0) return 0; if (outDataSize >= ccu->ccu_ipc.ipcDataSize) { dev_err(ccu->dev, "failed: data length over capacity(%d <= %d)\n", ccu->ccu_ipc.ipcDataSize, outDataSize); return -EINVAL; } for (i = 0; i < (outDataSize/4); i++) writel(realIoBuf[i], &((uint32_t *)outDataPtr)[i]); return 0; } static int mtk_ccu_rproc_ipc_trigger(struct mtk_ccu *ccu, struct mtk_ccu_msg *msg) { uint32_t loop_cnt = 0; bool ackValue = 0; bool timeout = false; uint32_t write_cnt = 0; uint32_t read_cnt = 0; /* LOG_DBG_IPI("+, ftype(%d), msg_id(%d)", msg->feature_type, msg->msg_id); */ if (ccu->ccu_ipc.is_initialized == false) { dev_err(ccu->dev, "IPC not initialized, invalid operation."); return -EINVAL; } write_cnt = readl(&ccu->ccu_ipc.ccuIpcPtr->write_cnt); read_cnt = readl(&ccu->ccu_ipc.ccuIpcPtr->read_cnt); //since ipc is synchronized, check no previous ipc if (read_cnt != write_cnt) { dev_err(ccu->dev, "CCU IPC violation, rcnt:%d, wcnt:%d, tout_fid:%d, tout_mid:%d", read_cnt, write_cnt, ccu->ipc_tout_fid, ccu->ipc_tout_mid); dev_err(ccu->dev, "r_assert:0x%x, i28:0x%x, i29:0x%x, i30:0x%x, i31:0x%x", readl(ccu->ccu_base + MTK_CCU_SPARE_REG20), readl(ccu->ccu_base + MTK_CCU_SPARE_REG28), readl(ccu->ccu_base + MTK_CCU_SPARE_REG29), readl(ccu->ccu_base + MTK_CCU_SPARE_REG30), readl(ccu->ccu_base + MTK_CCU_SPARE_REG31)); return -EINVAL; } writel(MTK_CCU_IPC_NO_ACK, &ccu->ccu_ipc.ccuIpcPtr->ack); write_cnt = readl(&ccu->ccu_ipc.ccuIpcPtr->write_cnt); writel(write_cnt + 1, &ccu->ccu_ipc.ccuIpcPtr->write_cnt); writel(msg->feature_type, &ccu->ccu_ipc.ccuIpcPtr->msg.feature_type); writel(msg->msg_id, &ccu->ccu_ipc.ccuIpcPtr->msg.msg_id); writel(msg->in_data_ptr, &ccu->ccu_ipc.ccuIpcPtr->msg.in_data_ptr); writel(msg->tg_info, &ccu->ccu_ipc.ccuIpcPtr->msg.tg_info); writel(msg->sensor_idx, &ccu->ccu_ipc.ccuIpcPtr->msg.sensor_idx); writel(1, ccu->ccu_ipc.ccuIntTrigPtr); while (readl(&ccu->ccu_ipc.ccuIpcPtr->ack) == MTK_CCU_IPC_NO_ACK) { loop_cnt++; udelay(100); if (loop_cnt > MTK_CCU_IPC_CMD_TIMEOUT_SPEC) { timeout = true; break; } } if (timeout) { dev_err(ccu->dev, "CCU IPC timeout, ft(%d), mid(%d), ack(%d), cnt(%d)\n", msg->feature_type, msg->msg_id, ackValue, loop_cnt); dev_err(ccu->dev, "r_assert:0x%x, i28:0x%x, i29:0x%x, i30:0x%x, i31:0x%x", readl(ccu->ccu_base + MTK_CCU_SPARE_REG20), readl(ccu->ccu_base + MTK_CCU_SPARE_REG28), readl(ccu->ccu_base + MTK_CCU_SPARE_REG29), readl(ccu->ccu_base + MTK_CCU_SPARE_REG30), readl(ccu->ccu_base + MTK_CCU_SPARE_REG31)); ccu->ipc_tout_fid = msg->feature_type; ccu->ipc_tout_mid = msg->msg_id; return -ETIMEDOUT; } /* * LOG_DBG_IPI("-, ftype(%d), msg_id(%d), loop_cnt(%d), ackValue(%d)", * msg->feature_type, msg->msg_id, loop_cnt, ackValue); */ return 0; } static int mtk_ccu_mb_rx(struct mtk_ccu *ccu, struct mtk_ccu_msg *task) { int ret = 0; uint32_t rear; uint32_t front; uint32_t next; if (!spin_trylock(&ccu->ccu_poweron_lock)) return 0; if (!ccu->poweron) { spin_unlock(&ccu->ccu_poweron_lock); return 0; } rear = readl(&ccu->mb->rear); front = readl(&ccu->mb->front); /*Check if queue is empty*/ /*empty when front=rear*/ if (rear != front) { /*modulus add: rear+1 = rear+1 % CCU_MAILBOX_QUEUE_SIZE*/ next = (ccu->mb->front + 1) & (MTK_CCU_MAILBOX_QUEUE_SIZE - 1); memcpy(task, &(ccu->mb->queue[next]), sizeof(struct mtk_ccu_msg)); ccu->mb->front = next; LOG_DBG_IPI( "[%u] received cmd: f(%d), r(%d), cmd(%d), in(%x)\n", (uint32_t)arch_timer_read_counter(), ccu->mb->front, ccu->mb->rear, ccu->mb->queue[next].msg_id, ccu->mb->queue[next].in_data_ptr); ret = rear - front + 1; #if IS_ENABLED(CONFIG_MTK_CCU_DEBUG) LOG_DBG_IPI("fs[%d,%d,%d,%d,%d],sc[%d,%d,%d,%d,%d],cam[%d,%d,%d,%d,%d]\n", ccu->rproc->bootcnt[0][0].counter, ccu->rproc->bootcnt[0][1].counter, ccu->rproc->bootcnt[0][2].counter, ccu->rproc->bootcnt[0][3].counter, ccu->rproc->bootcnt[0][4].counter, ccu->rproc->bootcnt[1][0].counter, ccu->rproc->bootcnt[1][1].counter, ccu->rproc->bootcnt[1][2].counter, ccu->rproc->bootcnt[1][3].counter, ccu->rproc->bootcnt[1][4].counter, ccu->rproc->bootcnt[2][0].counter, ccu->rproc->bootcnt[2][1].counter, ccu->rproc->bootcnt[2][2].counter, ccu->rproc->bootcnt[2][3].counter, ccu->rproc->bootcnt[2][4].counter); LOG_DBG_IPI("senif[%d,%d,%d,%d,%d],img[%d,%d,%d,%d,%d],icmdq[%d,%d,%d,%d,%d]\n", ccu->rproc->bootcnt[3][0].counter, ccu->rproc->bootcnt[3][1].counter, ccu->rproc->bootcnt[3][2].counter, ccu->rproc->bootcnt[3][3].counter, ccu->rproc->bootcnt[3][4].counter, ccu->rproc->bootcnt[4][0].counter, ccu->rproc->bootcnt[4][1].counter, ccu->rproc->bootcnt[4][2].counter, ccu->rproc->bootcnt[4][3].counter, ccu->rproc->bootcnt[4][4].counter, ccu->rproc->bootcnt[5][0].counter, ccu->rproc->bootcnt[5][1].counter, ccu->rproc->bootcnt[5][2].counter, ccu->rproc->bootcnt[5][3].counter, ccu->rproc->bootcnt[5][4].counter); LOG_DBG_IPI("dvfs[%d,%d,%d,%d,%d],gce[%d,%d,%d,%d,%d]\n", ccu->rproc->bootcnt[6][0].counter, ccu->rproc->bootcnt[6][1].counter, ccu->rproc->bootcnt[6][2].counter, ccu->rproc->bootcnt[6][3].counter, ccu->rproc->bootcnt[6][4].counter, ccu->rproc->bootcnt[7][0].counter, ccu->rproc->bootcnt[7][1].counter, ccu->rproc->bootcnt[7][2].counter, ccu->rproc->bootcnt[7][3].counter, ccu->rproc->bootcnt[7][4].counter); #endif } else ret = 0; spin_unlock(&ccu->ccu_poweron_lock); return ret; } irqreturn_t mtk_ccu_isr_handler(int irq, void *priv) { int mb_cnt; static struct mtk_ccu_msg msg; struct mtk_ccu *ccu = (struct mtk_ccu *)priv; mtk_ccu_ipc_handle_t handler; #if defined(SECURE_CCU) struct arm_smccc_res res; #endif if (!spin_trylock(&ccu->ccu_poweron_lock)) { LOG_DBG_IPI("trylock failed.\n"); goto ISR_EXIT; } if (!ccu->poweron) { LOG_DBG_IPI("ccu->poweron false.\n"); #ifdef REQUEST_IRQ_IN_INIT if (spin_trylock(&ccu->ccu_irq_lock)) { if (ccu->disirq) { ccu->disirq = false; spin_unlock(&ccu->ccu_irq_lock); disable_irq_nosync(ccu->irq_num); } else spin_unlock(&ccu->ccu_irq_lock); } #endif spin_unlock(&ccu->ccu_poweron_lock); goto ISR_EXIT; } /*clear interrupt status*/ #if defined(SECURE_CCU) if (ccu->ccu_version > CCU_VER_ISP71) writel(1, ccu->ccu_base + MTK_CCU_INT_CLR_EXCH); else { #ifdef CONFIG_ARM64 arm_smccc_smc(MTK_SIP_KERNEL_CCU_CONTROL, (u64) CCU_SMC_REQ_CLEAR_INT, (u64)1, 0, 0, 0, 0, 0, &res); #endif #ifdef CONFIG_ARM_PSCI arm_smccc_smc(MTK_SIP_KERNEL_CCU_CONTROL, (u32) CCU_SMC_REQ_CLEAR_INT, (u32)1, 0, 0, 0, 0, 0, &res); #endif } #else writel(0xFF, ccu->ccu_base + MTK_CCU_INT_CLR); // int_st = readl(ccu->ccu_base + MTK_CCU_INT_ST); #endif spin_unlock(&ccu->ccu_poweron_lock); while (1) { mb_cnt = mtk_ccu_mb_rx(ccu, &msg); if (mb_cnt == 0) { if (spin_trylock(&ccu->ccu_irq_lock)) { if (ccu->disirq) { ccu->disirq = false; spin_unlock(&ccu->ccu_irq_lock); disable_irq_nosync(ccu->irq_num); } else spin_unlock(&ccu->ccu_irq_lock); } goto ISR_EXIT; } if (msg.msg_id >= MTK_CCU_MSG_TO_APMCU_MAX) continue; mutex_lock(&ccu->ipc_desc_lock); handler = ccu->ipc_desc[msg.msg_id].handler; mutex_unlock(&ccu->ipc_desc_lock); if (ccu->ipc_desc[msg.msg_id].handler != NULL) { ccu->ipc_desc[msg.msg_id].handler(msg.in_data_ptr, msg.inDataSize, ccu->ipc_desc[msg.msg_id].priv); } } ISR_EXIT: return IRQ_HANDLED; } void mtk_ccu_rproc_ipc_init(struct mtk_ccu *ccu) { uint8_t *dmbase = (uint8_t *)ccu->dmem_base; uint8_t *ctrlbase = (uint8_t *)ccu->ccu_base; struct shared_buf_map *sb_map_ptr = (struct shared_buf_map *)(dmbase + MTK_CCU_SHARED_BUF_OFFSET); ccu->ccu_ipc.ccuIntTrigPtr = (uint32_t *)(ctrlbase + MTK_CCU_INT_TRG); ccu->ccu_ipc.ccuIpcPtr = (struct ap2ccu_ipc *)(dmbase + sb_map_ptr->ipc_base_offset); ccu->ccu_ipc.ipcDataPtr = dmbase + sb_map_ptr->ipc_data_base_offset; ccu->ccu_ipc.ipcDataAddrCcu = sb_map_ptr->ipc_data_addr_ccu; ccu->ccu_ipc.ipcDataSize = sb_map_ptr->ipc_data_size; if (ccu->ccu_ipc.ipcDataSize == 0) ccu->ccu_ipc.ipcDataSize = MTK_CCU_IPC_IOBUF_CAPACITY; ccu->ccu_ipc.is_initialized = true; LOG_DBG_IPI("IPC max size %d bytes", ccu->ccu_ipc.ipcDataSize); } void mtk_ccu_rproc_ipc_uninit(struct mtk_ccu *ccu) { if (!ccu) return; ccu->ccu_ipc.is_initialized = false; } int mtk_ccu_ipc_register(struct platform_device *pdev, enum mtk_ccu_to_ap_msg_id id, mtk_ccu_ipc_handle_t handle, void *priv) { struct mtk_ccu *ccu = platform_get_drvdata(pdev); if (!ccu) { dev_err(&pdev->dev, "ccu device is not ready\n"); return -EPROBE_DEFER; } if (WARN_ON(id < 0) || WARN_ON(id >= MTK_CCU_MSG_TO_APMCU_MAX) || WARN_ON(handle == NULL)) return -EINVAL; mutex_lock(&ccu->ipc_desc_lock); ccu->ipc_desc[id].handler = handle; ccu->ipc_desc[id].priv = priv; mutex_unlock(&ccu->ipc_desc_lock); return 0; } EXPORT_SYMBOL_GPL(mtk_ccu_ipc_register); void mtk_ccu_ipc_unregister(struct platform_device *pdev, enum mtk_ccu_to_ap_msg_id id) { struct mtk_ccu *ccu = platform_get_drvdata(pdev); if (!ccu) return; if (WARN_ON(id < 0) || WARN_ON(id >= MTK_CCU_MSG_TO_APMCU_MAX)) return; mutex_lock(&ccu->ipc_desc_lock); ccu->ipc_desc[id].handler = NULL; ccu->ipc_desc[id].priv = NULL; mutex_unlock(&ccu->ipc_desc_lock); } EXPORT_SYMBOL_GPL(mtk_ccu_ipc_unregister); int mtk_ccu_rproc_ipc_send(struct platform_device *pdev, enum mtk_ccu_feature_type featureType, uint32_t msgId, void *inDataPtr, uint32_t inDataSize) { struct mtk_ccu_msg msg = {0}; uint32_t ret; struct mtk_ccu *ccu; if (!pdev) return -EINVAL; ccu = platform_get_drvdata(pdev); if (!ccu) { dev_err(&pdev->dev, "ccu device is not ready\n"); return -EPROBE_DEFER; } if (!ccu->ccu_ipc.is_initialized) { dev_err(ccu->dev, "sendCcuCommnadIpc failed, ft (%d) msgId(%d)", featureType, msgId); return -EINVAL; } if (featureType == MTK_CCU_FEATURE_SYSCTRL) LOG_DBG_IPI("[%u] ft(%d), msgId(%d)\n", (uint32_t)arch_timer_read_counter(), featureType, msgId); if ((inDataSize) && (!inDataPtr)) { dev_err(ccu->dev, "inDataPtr is NULL\n"); return -EINVAL; } spin_lock(&ccu->ipc_send_lock); //Check if need input data ret = mtk_ccu_copyCmdInData(ccu, inDataPtr, inDataSize); if (ret != 0) { spin_unlock(&ccu->ipc_send_lock); return -EINVAL; } msg.feature_type = featureType; msg.msg_id = msgId; msg.in_data_ptr = ccu->ccu_ipc.ipcDataAddrCcu; msg.inDataSize = inDataSize; ret = mtk_ccu_rproc_ipc_trigger(ccu, &msg); if (ret != 0) { dev_err(ccu->dev, "sendCcuCommnadIpc failed, msgId(%d)", msgId); spin_unlock(&ccu->ipc_send_lock); return -EINVAL; } //check if need to copy output data ret = mtk_ccu_copyCmdOutData(ccu, inDataPtr, inDataSize); spin_unlock(&ccu->ipc_send_lock); /* LOG_DBG_IPI("-"); */ return ret; } EXPORT_SYMBOL_GPL(mtk_ccu_rproc_ipc_send); int mtk_ccu_rproc_get_inforeg(struct platform_device *pdev, uint32_t regno, uint32_t *data) { struct mtk_ccu *ccu; if ((!pdev) || (!data)) return -EINVAL; ccu = platform_get_drvdata(pdev); if (!ccu) return -EPROBE_DEFER; if ((regno < 28) || (regno > 31)) return -EINVAL; if (!spin_trylock(&ccu->ccu_poweron_lock)) return -EINVAL; if (!ccu->poweron) { spin_unlock(&ccu->ccu_poweron_lock); return -EINVAL; } regno <<= 2; *data = readl(ccu->ccu_base + MTK_CCU_SPARE_REG00 + regno); spin_unlock(&ccu->ccu_poweron_lock); return 0; } EXPORT_SYMBOL_GPL(mtk_ccu_rproc_get_inforeg); MODULE_DESCRIPTION("MTK CCU Rproc Driver"); MODULE_LICENSE("GPL v2");