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 #define Q6V5_PANIC_DELAY_MS 200 19 20 /** 21 * qcom_q6v5_prepare() - reinitialize the qcom_q6v5 context before start 22 * @q6v5: reference to qcom_q6v5 context to be reinitialized 23 * 24 * Return: 0 on success, negative errno on failure 25 */ 26 int qcom_q6v5_prepare(struct qcom_q6v5 *q6v5) 27 { 28 reinit_completion(&q6v5->start_done); 29 reinit_completion(&q6v5->stop_done); 30 31 q6v5->running = true; 32 q6v5->handover_issued = false; 33 34 enable_irq(q6v5->handover_irq); 35 36 return 0; 37 } 38 EXPORT_SYMBOL_GPL(qcom_q6v5_prepare); 39 40 /** 41 * qcom_q6v5_unprepare() - unprepare the qcom_q6v5 context after stop 42 * @q6v5: reference to qcom_q6v5 context to be unprepared 43 * 44 * Return: 0 on success, 1 if handover hasn't yet been called 45 */ 46 int qcom_q6v5_unprepare(struct qcom_q6v5 *q6v5) 47 { 48 disable_irq(q6v5->handover_irq); 49 50 return !q6v5->handover_issued; 51 } 52 EXPORT_SYMBOL_GPL(qcom_q6v5_unprepare); 53 54 static irqreturn_t q6v5_wdog_interrupt(int irq, void *data) 55 { 56 struct qcom_q6v5 *q6v5 = data; 57 size_t len; 58 char *msg; 59 60 /* Sometimes the stop triggers a watchdog rather than a stop-ack */ 61 if (!q6v5->running) { 62 complete(&q6v5->stop_done); 63 return IRQ_HANDLED; 64 } 65 66 msg = qcom_smem_get(QCOM_SMEM_HOST_ANY, q6v5->crash_reason, &len); 67 if (!IS_ERR(msg) && len > 0 && msg[0]) 68 dev_err(q6v5->dev, "watchdog received: %s\n", msg); 69 else 70 dev_err(q6v5->dev, "watchdog without message\n"); 71 72 rproc_report_crash(q6v5->rproc, RPROC_WATCHDOG); 73 74 return IRQ_HANDLED; 75 } 76 77 static irqreturn_t q6v5_fatal_interrupt(int irq, void *data) 78 { 79 struct qcom_q6v5 *q6v5 = data; 80 size_t len; 81 char *msg; 82 83 msg = qcom_smem_get(QCOM_SMEM_HOST_ANY, q6v5->crash_reason, &len); 84 if (!IS_ERR(msg) && len > 0 && msg[0]) 85 dev_err(q6v5->dev, "fatal error received: %s\n", msg); 86 else 87 dev_err(q6v5->dev, "fatal error without message\n"); 88 89 q6v5->running = false; 90 rproc_report_crash(q6v5->rproc, RPROC_FATAL_ERROR); 91 92 return IRQ_HANDLED; 93 } 94 95 static irqreturn_t q6v5_ready_interrupt(int irq, void *data) 96 { 97 struct qcom_q6v5 *q6v5 = data; 98 99 complete(&q6v5->start_done); 100 101 return IRQ_HANDLED; 102 } 103 104 /** 105 * qcom_q6v5_wait_for_start() - wait for remote processor start signal 106 * @q6v5: reference to qcom_q6v5 context 107 * @timeout: timeout to wait for the event, in jiffies 108 * 109 * qcom_q6v5_unprepare() should not be called when this function fails. 110 * 111 * Return: 0 on success, -ETIMEDOUT on timeout 112 */ 113 int qcom_q6v5_wait_for_start(struct qcom_q6v5 *q6v5, int timeout) 114 { 115 int ret; 116 117 ret = wait_for_completion_timeout(&q6v5->start_done, timeout); 118 if (!ret) 119 disable_irq(q6v5->handover_irq); 120 121 return !ret ? -ETIMEDOUT : 0; 122 } 123 EXPORT_SYMBOL_GPL(qcom_q6v5_wait_for_start); 124 125 static irqreturn_t q6v5_handover_interrupt(int irq, void *data) 126 { 127 struct qcom_q6v5 *q6v5 = data; 128 129 if (q6v5->handover) 130 q6v5->handover(q6v5); 131 132 q6v5->handover_issued = true; 133 134 return IRQ_HANDLED; 135 } 136 137 static irqreturn_t q6v5_stop_interrupt(int irq, void *data) 138 { 139 struct qcom_q6v5 *q6v5 = data; 140 141 complete(&q6v5->stop_done); 142 143 return IRQ_HANDLED; 144 } 145 146 /** 147 * qcom_q6v5_request_stop() - request the remote processor to stop 148 * @q6v5: reference to qcom_q6v5 context 149 * 150 * Return: 0 on success, negative errno on failure 151 */ 152 int qcom_q6v5_request_stop(struct qcom_q6v5 *q6v5) 153 { 154 int ret; 155 156 q6v5->running = false; 157 158 qcom_smem_state_update_bits(q6v5->state, 159 BIT(q6v5->stop_bit), BIT(q6v5->stop_bit)); 160 161 ret = wait_for_completion_timeout(&q6v5->stop_done, 5 * HZ); 162 163 qcom_smem_state_update_bits(q6v5->state, BIT(q6v5->stop_bit), 0); 164 165 return ret == 0 ? -ETIMEDOUT : 0; 166 } 167 EXPORT_SYMBOL_GPL(qcom_q6v5_request_stop); 168 169 /** 170 * qcom_q6v5_panic() - panic handler to invoke a stop on the remote 171 * @q6v5: reference to qcom_q6v5 context 172 * 173 * Set the stop bit and sleep in order to allow the remote processor to flush 174 * its caches etc for post mortem debugging. 175 * 176 * Return: 200ms 177 */ 178 unsigned long qcom_q6v5_panic(struct qcom_q6v5 *q6v5) 179 { 180 qcom_smem_state_update_bits(q6v5->state, 181 BIT(q6v5->stop_bit), BIT(q6v5->stop_bit)); 182 183 return Q6V5_PANIC_DELAY_MS; 184 } 185 EXPORT_SYMBOL_GPL(qcom_q6v5_panic); 186 187 /** 188 * qcom_q6v5_init() - initializer of the q6v5 common struct 189 * @q6v5: handle to be initialized 190 * @pdev: platform_device reference for acquiring resources 191 * @rproc: associated remoteproc instance 192 * @crash_reason: SMEM id for crash reason string, or 0 if none 193 * @handover: function to be called when proxy resources should be released 194 * 195 * Return: 0 on success, negative errno on failure 196 */ 197 int qcom_q6v5_init(struct qcom_q6v5 *q6v5, struct platform_device *pdev, 198 struct rproc *rproc, int crash_reason, 199 void (*handover)(struct qcom_q6v5 *q6v5)) 200 { 201 int ret; 202 203 q6v5->rproc = rproc; 204 q6v5->dev = &pdev->dev; 205 q6v5->crash_reason = crash_reason; 206 q6v5->handover = handover; 207 208 init_completion(&q6v5->start_done); 209 init_completion(&q6v5->stop_done); 210 211 q6v5->wdog_irq = platform_get_irq_byname(pdev, "wdog"); 212 if (q6v5->wdog_irq < 0) 213 return q6v5->wdog_irq; 214 215 ret = devm_request_threaded_irq(&pdev->dev, q6v5->wdog_irq, 216 NULL, q6v5_wdog_interrupt, 217 IRQF_TRIGGER_RISING | IRQF_ONESHOT, 218 "q6v5 wdog", q6v5); 219 if (ret) { 220 dev_err(&pdev->dev, "failed to acquire wdog IRQ\n"); 221 return ret; 222 } 223 224 q6v5->fatal_irq = platform_get_irq_byname(pdev, "fatal"); 225 if (q6v5->fatal_irq < 0) 226 return q6v5->fatal_irq; 227 228 ret = devm_request_threaded_irq(&pdev->dev, q6v5->fatal_irq, 229 NULL, q6v5_fatal_interrupt, 230 IRQF_TRIGGER_RISING | IRQF_ONESHOT, 231 "q6v5 fatal", q6v5); 232 if (ret) { 233 dev_err(&pdev->dev, "failed to acquire fatal IRQ\n"); 234 return ret; 235 } 236 237 q6v5->ready_irq = platform_get_irq_byname(pdev, "ready"); 238 if (q6v5->ready_irq < 0) 239 return q6v5->ready_irq; 240 241 ret = devm_request_threaded_irq(&pdev->dev, q6v5->ready_irq, 242 NULL, q6v5_ready_interrupt, 243 IRQF_TRIGGER_RISING | IRQF_ONESHOT, 244 "q6v5 ready", q6v5); 245 if (ret) { 246 dev_err(&pdev->dev, "failed to acquire ready IRQ\n"); 247 return ret; 248 } 249 250 q6v5->handover_irq = platform_get_irq_byname(pdev, "handover"); 251 if (q6v5->handover_irq < 0) 252 return q6v5->handover_irq; 253 254 ret = devm_request_threaded_irq(&pdev->dev, q6v5->handover_irq, 255 NULL, q6v5_handover_interrupt, 256 IRQF_TRIGGER_RISING | IRQF_ONESHOT, 257 "q6v5 handover", q6v5); 258 if (ret) { 259 dev_err(&pdev->dev, "failed to acquire handover IRQ\n"); 260 return ret; 261 } 262 disable_irq(q6v5->handover_irq); 263 264 q6v5->stop_irq = platform_get_irq_byname(pdev, "stop-ack"); 265 if (q6v5->stop_irq < 0) 266 return q6v5->stop_irq; 267 268 ret = devm_request_threaded_irq(&pdev->dev, q6v5->stop_irq, 269 NULL, q6v5_stop_interrupt, 270 IRQF_TRIGGER_RISING | IRQF_ONESHOT, 271 "q6v5 stop", q6v5); 272 if (ret) { 273 dev_err(&pdev->dev, "failed to acquire stop-ack IRQ\n"); 274 return ret; 275 } 276 277 q6v5->state = qcom_smem_state_get(&pdev->dev, "stop", &q6v5->stop_bit); 278 if (IS_ERR(q6v5->state)) { 279 dev_err(&pdev->dev, "failed to acquire stop state\n"); 280 return PTR_ERR(q6v5->state); 281 } 282 283 return 0; 284 } 285 EXPORT_SYMBOL_GPL(qcom_q6v5_init); 286 287 MODULE_LICENSE("GPL v2"); 288 MODULE_DESCRIPTION("Qualcomm Peripheral Image Loader for Q6V5"); 289