1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Qualcomm Peripheral Image Loader for Q6V5 4 * 5 * Copyright (C) 2016-2018 Linaro Ltd. 6 * Copyright (C) 2014 Sony Mobile Communications AB 7 * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. 8 */ 9 #include <linux/kernel.h> 10 #include <linux/platform_device.h> 11 #include <linux/interrupt.h> 12 #include <linux/module.h> 13 #include <linux/soc/qcom/smem.h> 14 #include <linux/soc/qcom/smem_state.h> 15 #include <linux/remoteproc.h> 16 #include "qcom_q6v5.h" 17 18 /** 19 * qcom_q6v5_prepare() - reinitialize the qcom_q6v5 context before start 20 * @q6v5: reference to qcom_q6v5 context to be reinitialized 21 * 22 * Return: 0 on success, negative errno on failure 23 */ 24 int qcom_q6v5_prepare(struct qcom_q6v5 *q6v5) 25 { 26 reinit_completion(&q6v5->start_done); 27 reinit_completion(&q6v5->stop_done); 28 29 q6v5->running = true; 30 q6v5->handover_issued = false; 31 32 enable_irq(q6v5->handover_irq); 33 34 return 0; 35 } 36 EXPORT_SYMBOL_GPL(qcom_q6v5_prepare); 37 38 /** 39 * qcom_q6v5_unprepare() - unprepare the qcom_q6v5 context after stop 40 * @q6v5: reference to qcom_q6v5 context to be unprepared 41 * 42 * Return: 0 on success, 1 if handover hasn't yet been called 43 */ 44 int qcom_q6v5_unprepare(struct qcom_q6v5 *q6v5) 45 { 46 disable_irq(q6v5->handover_irq); 47 48 return !q6v5->handover_issued; 49 } 50 EXPORT_SYMBOL_GPL(qcom_q6v5_unprepare); 51 52 static irqreturn_t q6v5_wdog_interrupt(int irq, void *data) 53 { 54 struct qcom_q6v5 *q6v5 = data; 55 size_t len; 56 char *msg; 57 58 /* Sometimes the stop triggers a watchdog rather than a stop-ack */ 59 if (!q6v5->running) { 60 complete(&q6v5->stop_done); 61 return IRQ_HANDLED; 62 } 63 64 msg = qcom_smem_get(QCOM_SMEM_HOST_ANY, q6v5->crash_reason, &len); 65 if (!IS_ERR(msg) && len > 0 && msg[0]) 66 dev_err(q6v5->dev, "watchdog received: %s\n", msg); 67 else 68 dev_err(q6v5->dev, "watchdog without message\n"); 69 70 rproc_report_crash(q6v5->rproc, RPROC_WATCHDOG); 71 72 return IRQ_HANDLED; 73 } 74 75 static irqreturn_t q6v5_fatal_interrupt(int irq, void *data) 76 { 77 struct qcom_q6v5 *q6v5 = data; 78 size_t len; 79 char *msg; 80 81 msg = qcom_smem_get(QCOM_SMEM_HOST_ANY, q6v5->crash_reason, &len); 82 if (!IS_ERR(msg) && len > 0 && msg[0]) 83 dev_err(q6v5->dev, "fatal error received: %s\n", msg); 84 else 85 dev_err(q6v5->dev, "fatal error without message\n"); 86 87 rproc_report_crash(q6v5->rproc, RPROC_FATAL_ERROR); 88 89 return IRQ_HANDLED; 90 } 91 92 static irqreturn_t q6v5_ready_interrupt(int irq, void *data) 93 { 94 struct qcom_q6v5 *q6v5 = data; 95 96 complete(&q6v5->start_done); 97 98 return IRQ_HANDLED; 99 } 100 101 /** 102 * qcom_q6v5_wait_for_start() - wait for remote processor start signal 103 * @q6v5: reference to qcom_q6v5 context 104 * @timeout: timeout to wait for the event, in jiffies 105 * 106 * qcom_q6v5_unprepare() should not be called when this function fails. 107 * 108 * Return: 0 on success, -ETIMEDOUT on timeout 109 */ 110 int qcom_q6v5_wait_for_start(struct qcom_q6v5 *q6v5, int timeout) 111 { 112 int ret; 113 114 ret = wait_for_completion_timeout(&q6v5->start_done, timeout); 115 if (!ret) 116 disable_irq(q6v5->handover_irq); 117 118 return !ret ? -ETIMEDOUT : 0; 119 } 120 EXPORT_SYMBOL_GPL(qcom_q6v5_wait_for_start); 121 122 static irqreturn_t q6v5_handover_interrupt(int irq, void *data) 123 { 124 struct qcom_q6v5 *q6v5 = data; 125 126 if (q6v5->handover) 127 q6v5->handover(q6v5); 128 129 q6v5->handover_issued = true; 130 131 return IRQ_HANDLED; 132 } 133 134 static irqreturn_t q6v5_stop_interrupt(int irq, void *data) 135 { 136 struct qcom_q6v5 *q6v5 = data; 137 138 complete(&q6v5->stop_done); 139 140 return IRQ_HANDLED; 141 } 142 143 /** 144 * qcom_q6v5_request_stop() - request the remote processor to stop 145 * @q6v5: reference to qcom_q6v5 context 146 * 147 * Return: 0 on success, negative errno on failure 148 */ 149 int qcom_q6v5_request_stop(struct qcom_q6v5 *q6v5) 150 { 151 int ret; 152 153 q6v5->running = false; 154 155 qcom_smem_state_update_bits(q6v5->state, 156 BIT(q6v5->stop_bit), BIT(q6v5->stop_bit)); 157 158 ret = wait_for_completion_timeout(&q6v5->stop_done, 5 * HZ); 159 160 qcom_smem_state_update_bits(q6v5->state, BIT(q6v5->stop_bit), 0); 161 162 return ret == 0 ? -ETIMEDOUT : 0; 163 } 164 EXPORT_SYMBOL_GPL(qcom_q6v5_request_stop); 165 166 /** 167 * qcom_q6v5_init() - initializer of the q6v5 common struct 168 * @q6v5: handle to be initialized 169 * @pdev: platform_device reference for acquiring resources 170 * @rproc: associated remoteproc instance 171 * @crash_reason: SMEM id for crash reason string, or 0 if none 172 * @handover: function to be called when proxy resources should be released 173 * 174 * Return: 0 on success, negative errno on failure 175 */ 176 int qcom_q6v5_init(struct qcom_q6v5 *q6v5, struct platform_device *pdev, 177 struct rproc *rproc, int crash_reason, 178 void (*handover)(struct qcom_q6v5 *q6v5)) 179 { 180 int ret; 181 182 q6v5->rproc = rproc; 183 q6v5->dev = &pdev->dev; 184 q6v5->crash_reason = crash_reason; 185 q6v5->handover = handover; 186 187 init_completion(&q6v5->start_done); 188 init_completion(&q6v5->stop_done); 189 190 q6v5->wdog_irq = platform_get_irq_byname(pdev, "wdog"); 191 ret = devm_request_threaded_irq(&pdev->dev, q6v5->wdog_irq, 192 NULL, q6v5_wdog_interrupt, 193 IRQF_TRIGGER_RISING | IRQF_ONESHOT, 194 "q6v5 wdog", q6v5); 195 if (ret) { 196 dev_err(&pdev->dev, "failed to acquire wdog IRQ\n"); 197 return ret; 198 } 199 200 q6v5->fatal_irq = platform_get_irq_byname(pdev, "fatal"); 201 ret = devm_request_threaded_irq(&pdev->dev, q6v5->fatal_irq, 202 NULL, q6v5_fatal_interrupt, 203 IRQF_TRIGGER_RISING | IRQF_ONESHOT, 204 "q6v5 fatal", q6v5); 205 if (ret) { 206 dev_err(&pdev->dev, "failed to acquire fatal IRQ\n"); 207 return ret; 208 } 209 210 q6v5->ready_irq = platform_get_irq_byname(pdev, "ready"); 211 ret = devm_request_threaded_irq(&pdev->dev, q6v5->ready_irq, 212 NULL, q6v5_ready_interrupt, 213 IRQF_TRIGGER_RISING | IRQF_ONESHOT, 214 "q6v5 ready", q6v5); 215 if (ret) { 216 dev_err(&pdev->dev, "failed to acquire ready IRQ\n"); 217 return ret; 218 } 219 220 q6v5->handover_irq = platform_get_irq_byname(pdev, "handover"); 221 ret = devm_request_threaded_irq(&pdev->dev, q6v5->handover_irq, 222 NULL, q6v5_handover_interrupt, 223 IRQF_TRIGGER_RISING | IRQF_ONESHOT, 224 "q6v5 handover", q6v5); 225 if (ret) { 226 dev_err(&pdev->dev, "failed to acquire handover IRQ\n"); 227 return ret; 228 } 229 disable_irq(q6v5->handover_irq); 230 231 q6v5->stop_irq = platform_get_irq_byname(pdev, "stop-ack"); 232 ret = devm_request_threaded_irq(&pdev->dev, q6v5->stop_irq, 233 NULL, q6v5_stop_interrupt, 234 IRQF_TRIGGER_RISING | IRQF_ONESHOT, 235 "q6v5 stop", q6v5); 236 if (ret) { 237 dev_err(&pdev->dev, "failed to acquire stop-ack IRQ\n"); 238 return ret; 239 } 240 241 q6v5->state = qcom_smem_state_get(&pdev->dev, "stop", &q6v5->stop_bit); 242 if (IS_ERR(q6v5->state)) { 243 dev_err(&pdev->dev, "failed to acquire stop state\n"); 244 return PTR_ERR(q6v5->state); 245 } 246 247 return 0; 248 } 249 EXPORT_SYMBOL_GPL(qcom_q6v5_init); 250 251 MODULE_LICENSE("GPL v2"); 252 MODULE_DESCRIPTION("Qualcomm Peripheral Image Loader for Q6V5"); 253