qcom_q6v5.c (9634B)
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/interconnect.h> 12#include <linux/interrupt.h> 13#include <linux/module.h> 14#include <linux/soc/qcom/qcom_aoss.h> 15#include <linux/soc/qcom/smem.h> 16#include <linux/soc/qcom/smem_state.h> 17#include <linux/remoteproc.h> 18#include "qcom_common.h" 19#include "qcom_q6v5.h" 20 21#define Q6V5_LOAD_STATE_MSG_LEN 64 22#define Q6V5_PANIC_DELAY_MS 200 23 24static int q6v5_load_state_toggle(struct qcom_q6v5 *q6v5, bool enable) 25{ 26 char buf[Q6V5_LOAD_STATE_MSG_LEN]; 27 int ret; 28 29 if (!q6v5->qmp) 30 return 0; 31 32 ret = snprintf(buf, sizeof(buf), 33 "{class: image, res: load_state, name: %s, val: %s}", 34 q6v5->load_state, enable ? "on" : "off"); 35 36 WARN_ON(ret >= Q6V5_LOAD_STATE_MSG_LEN); 37 38 ret = qmp_send(q6v5->qmp, buf, sizeof(buf)); 39 if (ret) 40 dev_err(q6v5->dev, "failed to toggle load state\n"); 41 42 return ret; 43} 44 45/** 46 * qcom_q6v5_prepare() - reinitialize the qcom_q6v5 context before start 47 * @q6v5: reference to qcom_q6v5 context to be reinitialized 48 * 49 * Return: 0 on success, negative errno on failure 50 */ 51int qcom_q6v5_prepare(struct qcom_q6v5 *q6v5) 52{ 53 int ret; 54 55 ret = icc_set_bw(q6v5->path, 0, UINT_MAX); 56 if (ret < 0) { 57 dev_err(q6v5->dev, "failed to set bandwidth request\n"); 58 return ret; 59 } 60 61 ret = q6v5_load_state_toggle(q6v5, true); 62 if (ret) { 63 icc_set_bw(q6v5->path, 0, 0); 64 return ret; 65 } 66 67 reinit_completion(&q6v5->start_done); 68 reinit_completion(&q6v5->stop_done); 69 70 q6v5->running = true; 71 q6v5->handover_issued = false; 72 73 enable_irq(q6v5->handover_irq); 74 75 return 0; 76} 77EXPORT_SYMBOL_GPL(qcom_q6v5_prepare); 78 79/** 80 * qcom_q6v5_unprepare() - unprepare the qcom_q6v5 context after stop 81 * @q6v5: reference to qcom_q6v5 context to be unprepared 82 * 83 * Return: 0 on success, 1 if handover hasn't yet been called 84 */ 85int qcom_q6v5_unprepare(struct qcom_q6v5 *q6v5) 86{ 87 disable_irq(q6v5->handover_irq); 88 q6v5_load_state_toggle(q6v5, false); 89 90 /* Disable interconnect vote, in case handover never happened */ 91 icc_set_bw(q6v5->path, 0, 0); 92 93 return !q6v5->handover_issued; 94} 95EXPORT_SYMBOL_GPL(qcom_q6v5_unprepare); 96 97static irqreturn_t q6v5_wdog_interrupt(int irq, void *data) 98{ 99 struct qcom_q6v5 *q6v5 = data; 100 size_t len; 101 char *msg; 102 103 /* Sometimes the stop triggers a watchdog rather than a stop-ack */ 104 if (!q6v5->running) { 105 complete(&q6v5->stop_done); 106 return IRQ_HANDLED; 107 } 108 109 msg = qcom_smem_get(QCOM_SMEM_HOST_ANY, q6v5->crash_reason, &len); 110 if (!IS_ERR(msg) && len > 0 && msg[0]) 111 dev_err(q6v5->dev, "watchdog received: %s\n", msg); 112 else 113 dev_err(q6v5->dev, "watchdog without message\n"); 114 115 rproc_report_crash(q6v5->rproc, RPROC_WATCHDOG); 116 117 return IRQ_HANDLED; 118} 119 120static irqreturn_t q6v5_fatal_interrupt(int irq, void *data) 121{ 122 struct qcom_q6v5 *q6v5 = data; 123 size_t len; 124 char *msg; 125 126 msg = qcom_smem_get(QCOM_SMEM_HOST_ANY, q6v5->crash_reason, &len); 127 if (!IS_ERR(msg) && len > 0 && msg[0]) 128 dev_err(q6v5->dev, "fatal error received: %s\n", msg); 129 else 130 dev_err(q6v5->dev, "fatal error without message\n"); 131 132 q6v5->running = false; 133 rproc_report_crash(q6v5->rproc, RPROC_FATAL_ERROR); 134 135 return IRQ_HANDLED; 136} 137 138static irqreturn_t q6v5_ready_interrupt(int irq, void *data) 139{ 140 struct qcom_q6v5 *q6v5 = data; 141 142 complete(&q6v5->start_done); 143 144 return IRQ_HANDLED; 145} 146 147/** 148 * qcom_q6v5_wait_for_start() - wait for remote processor start signal 149 * @q6v5: reference to qcom_q6v5 context 150 * @timeout: timeout to wait for the event, in jiffies 151 * 152 * qcom_q6v5_unprepare() should not be called when this function fails. 153 * 154 * Return: 0 on success, -ETIMEDOUT on timeout 155 */ 156int qcom_q6v5_wait_for_start(struct qcom_q6v5 *q6v5, int timeout) 157{ 158 int ret; 159 160 ret = wait_for_completion_timeout(&q6v5->start_done, timeout); 161 if (!ret) 162 disable_irq(q6v5->handover_irq); 163 164 return !ret ? -ETIMEDOUT : 0; 165} 166EXPORT_SYMBOL_GPL(qcom_q6v5_wait_for_start); 167 168static irqreturn_t q6v5_handover_interrupt(int irq, void *data) 169{ 170 struct qcom_q6v5 *q6v5 = data; 171 172 if (q6v5->handover) 173 q6v5->handover(q6v5); 174 175 icc_set_bw(q6v5->path, 0, 0); 176 177 q6v5->handover_issued = true; 178 179 return IRQ_HANDLED; 180} 181 182static irqreturn_t q6v5_stop_interrupt(int irq, void *data) 183{ 184 struct qcom_q6v5 *q6v5 = data; 185 186 complete(&q6v5->stop_done); 187 188 return IRQ_HANDLED; 189} 190 191/** 192 * qcom_q6v5_request_stop() - request the remote processor to stop 193 * @q6v5: reference to qcom_q6v5 context 194 * @sysmon: reference to the remote's sysmon instance, or NULL 195 * 196 * Return: 0 on success, negative errno on failure 197 */ 198int qcom_q6v5_request_stop(struct qcom_q6v5 *q6v5, struct qcom_sysmon *sysmon) 199{ 200 int ret; 201 202 q6v5->running = false; 203 204 /* Don't perform SMP2P dance if sysmon already shut down the remote */ 205 if (qcom_sysmon_shutdown_acked(sysmon)) 206 return 0; 207 208 qcom_smem_state_update_bits(q6v5->state, 209 BIT(q6v5->stop_bit), BIT(q6v5->stop_bit)); 210 211 ret = wait_for_completion_timeout(&q6v5->stop_done, 5 * HZ); 212 213 qcom_smem_state_update_bits(q6v5->state, BIT(q6v5->stop_bit), 0); 214 215 return ret == 0 ? -ETIMEDOUT : 0; 216} 217EXPORT_SYMBOL_GPL(qcom_q6v5_request_stop); 218 219/** 220 * qcom_q6v5_panic() - panic handler to invoke a stop on the remote 221 * @q6v5: reference to qcom_q6v5 context 222 * 223 * Set the stop bit and sleep in order to allow the remote processor to flush 224 * its caches etc for post mortem debugging. 225 * 226 * Return: 200ms 227 */ 228unsigned long qcom_q6v5_panic(struct qcom_q6v5 *q6v5) 229{ 230 qcom_smem_state_update_bits(q6v5->state, 231 BIT(q6v5->stop_bit), BIT(q6v5->stop_bit)); 232 233 return Q6V5_PANIC_DELAY_MS; 234} 235EXPORT_SYMBOL_GPL(qcom_q6v5_panic); 236 237/** 238 * qcom_q6v5_init() - initializer of the q6v5 common struct 239 * @q6v5: handle to be initialized 240 * @pdev: platform_device reference for acquiring resources 241 * @rproc: associated remoteproc instance 242 * @crash_reason: SMEM id for crash reason string, or 0 if none 243 * @load_state: load state resource string 244 * @handover: function to be called when proxy resources should be released 245 * 246 * Return: 0 on success, negative errno on failure 247 */ 248int qcom_q6v5_init(struct qcom_q6v5 *q6v5, struct platform_device *pdev, 249 struct rproc *rproc, int crash_reason, const char *load_state, 250 void (*handover)(struct qcom_q6v5 *q6v5)) 251{ 252 int ret; 253 254 q6v5->rproc = rproc; 255 q6v5->dev = &pdev->dev; 256 q6v5->crash_reason = crash_reason; 257 q6v5->handover = handover; 258 259 init_completion(&q6v5->start_done); 260 init_completion(&q6v5->stop_done); 261 262 q6v5->wdog_irq = platform_get_irq_byname(pdev, "wdog"); 263 if (q6v5->wdog_irq < 0) 264 return q6v5->wdog_irq; 265 266 ret = devm_request_threaded_irq(&pdev->dev, q6v5->wdog_irq, 267 NULL, q6v5_wdog_interrupt, 268 IRQF_TRIGGER_RISING | IRQF_ONESHOT, 269 "q6v5 wdog", q6v5); 270 if (ret) { 271 dev_err(&pdev->dev, "failed to acquire wdog IRQ\n"); 272 return ret; 273 } 274 275 q6v5->fatal_irq = platform_get_irq_byname(pdev, "fatal"); 276 if (q6v5->fatal_irq < 0) 277 return q6v5->fatal_irq; 278 279 ret = devm_request_threaded_irq(&pdev->dev, q6v5->fatal_irq, 280 NULL, q6v5_fatal_interrupt, 281 IRQF_TRIGGER_RISING | IRQF_ONESHOT, 282 "q6v5 fatal", q6v5); 283 if (ret) { 284 dev_err(&pdev->dev, "failed to acquire fatal IRQ\n"); 285 return ret; 286 } 287 288 q6v5->ready_irq = platform_get_irq_byname(pdev, "ready"); 289 if (q6v5->ready_irq < 0) 290 return q6v5->ready_irq; 291 292 ret = devm_request_threaded_irq(&pdev->dev, q6v5->ready_irq, 293 NULL, q6v5_ready_interrupt, 294 IRQF_TRIGGER_RISING | IRQF_ONESHOT, 295 "q6v5 ready", q6v5); 296 if (ret) { 297 dev_err(&pdev->dev, "failed to acquire ready IRQ\n"); 298 return ret; 299 } 300 301 q6v5->handover_irq = platform_get_irq_byname(pdev, "handover"); 302 if (q6v5->handover_irq < 0) 303 return q6v5->handover_irq; 304 305 ret = devm_request_threaded_irq(&pdev->dev, q6v5->handover_irq, 306 NULL, q6v5_handover_interrupt, 307 IRQF_TRIGGER_RISING | IRQF_ONESHOT, 308 "q6v5 handover", q6v5); 309 if (ret) { 310 dev_err(&pdev->dev, "failed to acquire handover IRQ\n"); 311 return ret; 312 } 313 disable_irq(q6v5->handover_irq); 314 315 q6v5->stop_irq = platform_get_irq_byname(pdev, "stop-ack"); 316 if (q6v5->stop_irq < 0) 317 return q6v5->stop_irq; 318 319 ret = devm_request_threaded_irq(&pdev->dev, q6v5->stop_irq, 320 NULL, q6v5_stop_interrupt, 321 IRQF_TRIGGER_RISING | IRQF_ONESHOT, 322 "q6v5 stop", q6v5); 323 if (ret) { 324 dev_err(&pdev->dev, "failed to acquire stop-ack IRQ\n"); 325 return ret; 326 } 327 328 q6v5->state = devm_qcom_smem_state_get(&pdev->dev, "stop", &q6v5->stop_bit); 329 if (IS_ERR(q6v5->state)) { 330 dev_err(&pdev->dev, "failed to acquire stop state\n"); 331 return PTR_ERR(q6v5->state); 332 } 333 334 q6v5->load_state = devm_kstrdup_const(&pdev->dev, load_state, GFP_KERNEL); 335 q6v5->qmp = qmp_get(&pdev->dev); 336 if (IS_ERR(q6v5->qmp)) { 337 if (PTR_ERR(q6v5->qmp) != -ENODEV) 338 return dev_err_probe(&pdev->dev, PTR_ERR(q6v5->qmp), 339 "failed to acquire load state\n"); 340 q6v5->qmp = NULL; 341 } else if (!q6v5->load_state) { 342 if (!load_state) 343 dev_err(&pdev->dev, "load state resource string empty\n"); 344 345 qmp_put(q6v5->qmp); 346 return load_state ? -ENOMEM : -EINVAL; 347 } 348 349 q6v5->path = devm_of_icc_get(&pdev->dev, NULL); 350 if (IS_ERR(q6v5->path)) 351 return dev_err_probe(&pdev->dev, PTR_ERR(q6v5->path), 352 "failed to acquire interconnect path\n"); 353 354 return 0; 355} 356EXPORT_SYMBOL_GPL(qcom_q6v5_init); 357 358/** 359 * qcom_q6v5_deinit() - deinitialize the q6v5 common struct 360 * @q6v5: reference to qcom_q6v5 context to be deinitialized 361 */ 362void qcom_q6v5_deinit(struct qcom_q6v5 *q6v5) 363{ 364 qmp_put(q6v5->qmp); 365} 366EXPORT_SYMBOL_GPL(qcom_q6v5_deinit); 367 368MODULE_LICENSE("GPL v2"); 369MODULE_DESCRIPTION("Qualcomm Peripheral Image Loader for Q6V5");