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