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