18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Qualcomm Peripheral Image Loader for Q6V5 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2016-2018 Linaro Ltd. 68c2ecf20Sopenharmony_ci * Copyright (C) 2014 Sony Mobile Communications AB 78c2ecf20Sopenharmony_ci * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. 88c2ecf20Sopenharmony_ci */ 98c2ecf20Sopenharmony_ci#include <linux/kernel.h> 108c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 118c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 128c2ecf20Sopenharmony_ci#include <linux/module.h> 138c2ecf20Sopenharmony_ci#include <linux/soc/qcom/smem.h> 148c2ecf20Sopenharmony_ci#include <linux/soc/qcom/smem_state.h> 158c2ecf20Sopenharmony_ci#include <linux/remoteproc.h> 168c2ecf20Sopenharmony_ci#include "qcom_q6v5.h" 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_ci#define Q6V5_PANIC_DELAY_MS 200 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_ci/** 218c2ecf20Sopenharmony_ci * qcom_q6v5_prepare() - reinitialize the qcom_q6v5 context before start 228c2ecf20Sopenharmony_ci * @q6v5: reference to qcom_q6v5 context to be reinitialized 238c2ecf20Sopenharmony_ci * 248c2ecf20Sopenharmony_ci * Return: 0 on success, negative errno on failure 258c2ecf20Sopenharmony_ci */ 268c2ecf20Sopenharmony_ciint qcom_q6v5_prepare(struct qcom_q6v5 *q6v5) 278c2ecf20Sopenharmony_ci{ 288c2ecf20Sopenharmony_ci reinit_completion(&q6v5->start_done); 298c2ecf20Sopenharmony_ci reinit_completion(&q6v5->stop_done); 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_ci q6v5->running = true; 328c2ecf20Sopenharmony_ci q6v5->handover_issued = false; 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_ci enable_irq(q6v5->handover_irq); 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_ci return 0; 378c2ecf20Sopenharmony_ci} 388c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_q6v5_prepare); 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_ci/** 418c2ecf20Sopenharmony_ci * qcom_q6v5_unprepare() - unprepare the qcom_q6v5 context after stop 428c2ecf20Sopenharmony_ci * @q6v5: reference to qcom_q6v5 context to be unprepared 438c2ecf20Sopenharmony_ci * 448c2ecf20Sopenharmony_ci * Return: 0 on success, 1 if handover hasn't yet been called 458c2ecf20Sopenharmony_ci */ 468c2ecf20Sopenharmony_ciint qcom_q6v5_unprepare(struct qcom_q6v5 *q6v5) 478c2ecf20Sopenharmony_ci{ 488c2ecf20Sopenharmony_ci disable_irq(q6v5->handover_irq); 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ci return !q6v5->handover_issued; 518c2ecf20Sopenharmony_ci} 528c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_q6v5_unprepare); 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_cistatic irqreturn_t q6v5_wdog_interrupt(int irq, void *data) 558c2ecf20Sopenharmony_ci{ 568c2ecf20Sopenharmony_ci struct qcom_q6v5 *q6v5 = data; 578c2ecf20Sopenharmony_ci size_t len; 588c2ecf20Sopenharmony_ci char *msg; 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_ci /* Sometimes the stop triggers a watchdog rather than a stop-ack */ 618c2ecf20Sopenharmony_ci if (!q6v5->running) { 628c2ecf20Sopenharmony_ci complete(&q6v5->stop_done); 638c2ecf20Sopenharmony_ci return IRQ_HANDLED; 648c2ecf20Sopenharmony_ci } 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_ci msg = qcom_smem_get(QCOM_SMEM_HOST_ANY, q6v5->crash_reason, &len); 678c2ecf20Sopenharmony_ci if (!IS_ERR(msg) && len > 0 && msg[0]) 688c2ecf20Sopenharmony_ci dev_err(q6v5->dev, "watchdog received: %s\n", msg); 698c2ecf20Sopenharmony_ci else 708c2ecf20Sopenharmony_ci dev_err(q6v5->dev, "watchdog without message\n"); 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci rproc_report_crash(q6v5->rproc, RPROC_WATCHDOG); 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci return IRQ_HANDLED; 758c2ecf20Sopenharmony_ci} 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_cistatic irqreturn_t q6v5_fatal_interrupt(int irq, void *data) 788c2ecf20Sopenharmony_ci{ 798c2ecf20Sopenharmony_ci struct qcom_q6v5 *q6v5 = data; 808c2ecf20Sopenharmony_ci size_t len; 818c2ecf20Sopenharmony_ci char *msg; 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci msg = qcom_smem_get(QCOM_SMEM_HOST_ANY, q6v5->crash_reason, &len); 848c2ecf20Sopenharmony_ci if (!IS_ERR(msg) && len > 0 && msg[0]) 858c2ecf20Sopenharmony_ci dev_err(q6v5->dev, "fatal error received: %s\n", msg); 868c2ecf20Sopenharmony_ci else 878c2ecf20Sopenharmony_ci dev_err(q6v5->dev, "fatal error without message\n"); 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci q6v5->running = false; 908c2ecf20Sopenharmony_ci rproc_report_crash(q6v5->rproc, RPROC_FATAL_ERROR); 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_ci return IRQ_HANDLED; 938c2ecf20Sopenharmony_ci} 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_cistatic irqreturn_t q6v5_ready_interrupt(int irq, void *data) 968c2ecf20Sopenharmony_ci{ 978c2ecf20Sopenharmony_ci struct qcom_q6v5 *q6v5 = data; 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci complete(&q6v5->start_done); 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci return IRQ_HANDLED; 1028c2ecf20Sopenharmony_ci} 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci/** 1058c2ecf20Sopenharmony_ci * qcom_q6v5_wait_for_start() - wait for remote processor start signal 1068c2ecf20Sopenharmony_ci * @q6v5: reference to qcom_q6v5 context 1078c2ecf20Sopenharmony_ci * @timeout: timeout to wait for the event, in jiffies 1088c2ecf20Sopenharmony_ci * 1098c2ecf20Sopenharmony_ci * qcom_q6v5_unprepare() should not be called when this function fails. 1108c2ecf20Sopenharmony_ci * 1118c2ecf20Sopenharmony_ci * Return: 0 on success, -ETIMEDOUT on timeout 1128c2ecf20Sopenharmony_ci */ 1138c2ecf20Sopenharmony_ciint qcom_q6v5_wait_for_start(struct qcom_q6v5 *q6v5, int timeout) 1148c2ecf20Sopenharmony_ci{ 1158c2ecf20Sopenharmony_ci int ret; 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci ret = wait_for_completion_timeout(&q6v5->start_done, timeout); 1188c2ecf20Sopenharmony_ci if (!ret) 1198c2ecf20Sopenharmony_ci disable_irq(q6v5->handover_irq); 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ci return !ret ? -ETIMEDOUT : 0; 1228c2ecf20Sopenharmony_ci} 1238c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_q6v5_wait_for_start); 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_cistatic irqreturn_t q6v5_handover_interrupt(int irq, void *data) 1268c2ecf20Sopenharmony_ci{ 1278c2ecf20Sopenharmony_ci struct qcom_q6v5 *q6v5 = data; 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_ci if (q6v5->handover) 1308c2ecf20Sopenharmony_ci q6v5->handover(q6v5); 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_ci q6v5->handover_issued = true; 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_ci return IRQ_HANDLED; 1358c2ecf20Sopenharmony_ci} 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_cistatic irqreturn_t q6v5_stop_interrupt(int irq, void *data) 1388c2ecf20Sopenharmony_ci{ 1398c2ecf20Sopenharmony_ci struct qcom_q6v5 *q6v5 = data; 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci complete(&q6v5->stop_done); 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci return IRQ_HANDLED; 1448c2ecf20Sopenharmony_ci} 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_ci/** 1478c2ecf20Sopenharmony_ci * qcom_q6v5_request_stop() - request the remote processor to stop 1488c2ecf20Sopenharmony_ci * @q6v5: reference to qcom_q6v5 context 1498c2ecf20Sopenharmony_ci * 1508c2ecf20Sopenharmony_ci * Return: 0 on success, negative errno on failure 1518c2ecf20Sopenharmony_ci */ 1528c2ecf20Sopenharmony_ciint qcom_q6v5_request_stop(struct qcom_q6v5 *q6v5) 1538c2ecf20Sopenharmony_ci{ 1548c2ecf20Sopenharmony_ci int ret; 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_ci q6v5->running = false; 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci qcom_smem_state_update_bits(q6v5->state, 1598c2ecf20Sopenharmony_ci BIT(q6v5->stop_bit), BIT(q6v5->stop_bit)); 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci ret = wait_for_completion_timeout(&q6v5->stop_done, 5 * HZ); 1628c2ecf20Sopenharmony_ci 1638c2ecf20Sopenharmony_ci qcom_smem_state_update_bits(q6v5->state, BIT(q6v5->stop_bit), 0); 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci return ret == 0 ? -ETIMEDOUT : 0; 1668c2ecf20Sopenharmony_ci} 1678c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_q6v5_request_stop); 1688c2ecf20Sopenharmony_ci 1698c2ecf20Sopenharmony_ci/** 1708c2ecf20Sopenharmony_ci * qcom_q6v5_panic() - panic handler to invoke a stop on the remote 1718c2ecf20Sopenharmony_ci * @q6v5: reference to qcom_q6v5 context 1728c2ecf20Sopenharmony_ci * 1738c2ecf20Sopenharmony_ci * Set the stop bit and sleep in order to allow the remote processor to flush 1748c2ecf20Sopenharmony_ci * its caches etc for post mortem debugging. 1758c2ecf20Sopenharmony_ci * 1768c2ecf20Sopenharmony_ci * Return: 200ms 1778c2ecf20Sopenharmony_ci */ 1788c2ecf20Sopenharmony_ciunsigned long qcom_q6v5_panic(struct qcom_q6v5 *q6v5) 1798c2ecf20Sopenharmony_ci{ 1808c2ecf20Sopenharmony_ci qcom_smem_state_update_bits(q6v5->state, 1818c2ecf20Sopenharmony_ci BIT(q6v5->stop_bit), BIT(q6v5->stop_bit)); 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci return Q6V5_PANIC_DELAY_MS; 1848c2ecf20Sopenharmony_ci} 1858c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_q6v5_panic); 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_ci/** 1888c2ecf20Sopenharmony_ci * qcom_q6v5_init() - initializer of the q6v5 common struct 1898c2ecf20Sopenharmony_ci * @q6v5: handle to be initialized 1908c2ecf20Sopenharmony_ci * @pdev: platform_device reference for acquiring resources 1918c2ecf20Sopenharmony_ci * @rproc: associated remoteproc instance 1928c2ecf20Sopenharmony_ci * @crash_reason: SMEM id for crash reason string, or 0 if none 1938c2ecf20Sopenharmony_ci * @handover: function to be called when proxy resources should be released 1948c2ecf20Sopenharmony_ci * 1958c2ecf20Sopenharmony_ci * Return: 0 on success, negative errno on failure 1968c2ecf20Sopenharmony_ci */ 1978c2ecf20Sopenharmony_ciint qcom_q6v5_init(struct qcom_q6v5 *q6v5, struct platform_device *pdev, 1988c2ecf20Sopenharmony_ci struct rproc *rproc, int crash_reason, 1998c2ecf20Sopenharmony_ci void (*handover)(struct qcom_q6v5 *q6v5)) 2008c2ecf20Sopenharmony_ci{ 2018c2ecf20Sopenharmony_ci int ret; 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_ci q6v5->rproc = rproc; 2048c2ecf20Sopenharmony_ci q6v5->dev = &pdev->dev; 2058c2ecf20Sopenharmony_ci q6v5->crash_reason = crash_reason; 2068c2ecf20Sopenharmony_ci q6v5->handover = handover; 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_ci init_completion(&q6v5->start_done); 2098c2ecf20Sopenharmony_ci init_completion(&q6v5->stop_done); 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_ci q6v5->wdog_irq = platform_get_irq_byname(pdev, "wdog"); 2128c2ecf20Sopenharmony_ci if (q6v5->wdog_irq < 0) 2138c2ecf20Sopenharmony_ci return q6v5->wdog_irq; 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ci ret = devm_request_threaded_irq(&pdev->dev, q6v5->wdog_irq, 2168c2ecf20Sopenharmony_ci NULL, q6v5_wdog_interrupt, 2178c2ecf20Sopenharmony_ci IRQF_TRIGGER_RISING | IRQF_ONESHOT, 2188c2ecf20Sopenharmony_ci "q6v5 wdog", q6v5); 2198c2ecf20Sopenharmony_ci if (ret) { 2208c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to acquire wdog IRQ\n"); 2218c2ecf20Sopenharmony_ci return ret; 2228c2ecf20Sopenharmony_ci } 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_ci q6v5->fatal_irq = platform_get_irq_byname(pdev, "fatal"); 2258c2ecf20Sopenharmony_ci if (q6v5->fatal_irq < 0) 2268c2ecf20Sopenharmony_ci return q6v5->fatal_irq; 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_ci ret = devm_request_threaded_irq(&pdev->dev, q6v5->fatal_irq, 2298c2ecf20Sopenharmony_ci NULL, q6v5_fatal_interrupt, 2308c2ecf20Sopenharmony_ci IRQF_TRIGGER_RISING | IRQF_ONESHOT, 2318c2ecf20Sopenharmony_ci "q6v5 fatal", q6v5); 2328c2ecf20Sopenharmony_ci if (ret) { 2338c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to acquire fatal IRQ\n"); 2348c2ecf20Sopenharmony_ci return ret; 2358c2ecf20Sopenharmony_ci } 2368c2ecf20Sopenharmony_ci 2378c2ecf20Sopenharmony_ci q6v5->ready_irq = platform_get_irq_byname(pdev, "ready"); 2388c2ecf20Sopenharmony_ci if (q6v5->ready_irq < 0) 2398c2ecf20Sopenharmony_ci return q6v5->ready_irq; 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_ci ret = devm_request_threaded_irq(&pdev->dev, q6v5->ready_irq, 2428c2ecf20Sopenharmony_ci NULL, q6v5_ready_interrupt, 2438c2ecf20Sopenharmony_ci IRQF_TRIGGER_RISING | IRQF_ONESHOT, 2448c2ecf20Sopenharmony_ci "q6v5 ready", q6v5); 2458c2ecf20Sopenharmony_ci if (ret) { 2468c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to acquire ready IRQ\n"); 2478c2ecf20Sopenharmony_ci return ret; 2488c2ecf20Sopenharmony_ci } 2498c2ecf20Sopenharmony_ci 2508c2ecf20Sopenharmony_ci q6v5->handover_irq = platform_get_irq_byname(pdev, "handover"); 2518c2ecf20Sopenharmony_ci if (q6v5->handover_irq < 0) 2528c2ecf20Sopenharmony_ci return q6v5->handover_irq; 2538c2ecf20Sopenharmony_ci 2548c2ecf20Sopenharmony_ci ret = devm_request_threaded_irq(&pdev->dev, q6v5->handover_irq, 2558c2ecf20Sopenharmony_ci NULL, q6v5_handover_interrupt, 2568c2ecf20Sopenharmony_ci IRQF_TRIGGER_RISING | IRQF_ONESHOT, 2578c2ecf20Sopenharmony_ci "q6v5 handover", q6v5); 2588c2ecf20Sopenharmony_ci if (ret) { 2598c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to acquire handover IRQ\n"); 2608c2ecf20Sopenharmony_ci return ret; 2618c2ecf20Sopenharmony_ci } 2628c2ecf20Sopenharmony_ci disable_irq(q6v5->handover_irq); 2638c2ecf20Sopenharmony_ci 2648c2ecf20Sopenharmony_ci q6v5->stop_irq = platform_get_irq_byname(pdev, "stop-ack"); 2658c2ecf20Sopenharmony_ci if (q6v5->stop_irq < 0) 2668c2ecf20Sopenharmony_ci return q6v5->stop_irq; 2678c2ecf20Sopenharmony_ci 2688c2ecf20Sopenharmony_ci ret = devm_request_threaded_irq(&pdev->dev, q6v5->stop_irq, 2698c2ecf20Sopenharmony_ci NULL, q6v5_stop_interrupt, 2708c2ecf20Sopenharmony_ci IRQF_TRIGGER_RISING | IRQF_ONESHOT, 2718c2ecf20Sopenharmony_ci "q6v5 stop", q6v5); 2728c2ecf20Sopenharmony_ci if (ret) { 2738c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to acquire stop-ack IRQ\n"); 2748c2ecf20Sopenharmony_ci return ret; 2758c2ecf20Sopenharmony_ci } 2768c2ecf20Sopenharmony_ci 2778c2ecf20Sopenharmony_ci q6v5->state = qcom_smem_state_get(&pdev->dev, "stop", &q6v5->stop_bit); 2788c2ecf20Sopenharmony_ci if (IS_ERR(q6v5->state)) { 2798c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to acquire stop state\n"); 2808c2ecf20Sopenharmony_ci return PTR_ERR(q6v5->state); 2818c2ecf20Sopenharmony_ci } 2828c2ecf20Sopenharmony_ci 2838c2ecf20Sopenharmony_ci return 0; 2848c2ecf20Sopenharmony_ci} 2858c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_q6v5_init); 2868c2ecf20Sopenharmony_ci 2878c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 2888c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Qualcomm Peripheral Image Loader for Q6V5"); 289