]>
Commit | Line | Data |
---|---|---|
a646d6ec AE |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | ||
3 | /* Copyright (c) 2012-2018, The Linux Foundation. All rights reserved. | |
4 | * Copyright (C) 2018-2020 Linaro Ltd. | |
5 | */ | |
6 | ||
7 | #include <linux/types.h> | |
8 | #include <linux/io.h> | |
9 | #include <linux/delay.h> | |
799c5c24 | 10 | #include <linux/pm_runtime.h> |
a646d6ec AE |
11 | |
12 | #include "ipa.h" | |
a646d6ec | 13 | #include "ipa_uc.h" |
93d415b5 | 14 | #include "ipa_power.h" |
a646d6ec AE |
15 | |
16 | /** | |
17 | * DOC: The IPA embedded microcontroller | |
18 | * | |
19 | * The IPA incorporates a microcontroller that is able to do some additional | |
20 | * handling/offloading of network activity. The current code makes | |
21 | * essentially no use of the microcontroller, but it still requires some | |
22 | * initialization. It needs to be notified in the event the AP crashes. | |
23 | * | |
24 | * The microcontroller can generate two interrupts to the AP. One interrupt | |
25 | * is used to indicate that a response to a request from the AP is available. | |
26 | * The other is used to notify the AP of the occurrence of an event. In | |
27 | * addition, the AP can interrupt the microcontroller by writing a register. | |
28 | * | |
29 | * A 128 byte block of structured memory within the IPA SRAM is used together | |
30 | * with these interrupts to implement the communication interface between the | |
31 | * AP and the IPA microcontroller. Each side writes data to the shared area | |
32 | * before interrupting its peer, which will read the written data in response | |
33 | * to the interrupt. Some information found in the shared area is currently | |
34 | * unused. All remaining space in the shared area is reserved, and must not | |
35 | * be read or written by the AP. | |
36 | */ | |
37 | /* Supports hardware interface version 0x2000 */ | |
38 | ||
a646d6ec AE |
39 | /* Delay to allow a the microcontroller to save state when crashing */ |
40 | #define IPA_SEND_DELAY 100 /* microseconds */ | |
41 | ||
42 | /** | |
43 | * struct ipa_uc_mem_area - AP/microcontroller shared memory area | |
44 | * @command: command code (AP->microcontroller) | |
e3eea08e | 45 | * @reserved0: reserved bytes; avoid reading or writing |
a646d6ec AE |
46 | * @command_param: low 32 bits of command parameter (AP->microcontroller) |
47 | * @command_param_hi: high 32 bits of command parameter (AP->microcontroller) | |
48 | * | |
49 | * @response: response code (microcontroller->AP) | |
e3eea08e | 50 | * @reserved1: reserved bytes; avoid reading or writing |
a646d6ec AE |
51 | * @response_param: response parameter (microcontroller->AP) |
52 | * | |
53 | * @event: event code (microcontroller->AP) | |
e3eea08e | 54 | * @reserved2: reserved bytes; avoid reading or writing |
a646d6ec AE |
55 | * @event_param: event parameter (microcontroller->AP) |
56 | * | |
57 | * @first_error_address: address of first error-source on SNOC | |
58 | * @hw_state: state of hardware (including error type information) | |
59 | * @warning_counter: counter of non-fatal hardware errors | |
e3eea08e | 60 | * @reserved3: reserved bytes; avoid reading or writing |
a646d6ec | 61 | * @interface_version: hardware-reported interface version |
e3eea08e | 62 | * @reserved4: reserved bytes; avoid reading or writing |
722208ea AE |
63 | * |
64 | * A shared memory area at the base of IPA resident memory is used for | |
65 | * communication with the microcontroller. The region is 128 bytes in | |
66 | * size, but only the first 40 bytes (structured this way) are used. | |
a646d6ec AE |
67 | */ |
68 | struct ipa_uc_mem_area { | |
69 | u8 command; /* enum ipa_uc_command */ | |
70 | u8 reserved0[3]; | |
71 | __le32 command_param; | |
72 | __le32 command_param_hi; | |
73 | u8 response; /* enum ipa_uc_response */ | |
74 | u8 reserved1[3]; | |
75 | __le32 response_param; | |
76 | u8 event; /* enum ipa_uc_event */ | |
77 | u8 reserved2[3]; | |
78 | ||
79 | __le32 event_param; | |
80 | __le32 first_error_address; | |
81 | u8 hw_state; | |
82 | u8 warning_counter; | |
83 | __le16 reserved3; | |
84 | __le16 interface_version; | |
85 | __le16 reserved4; | |
86 | }; | |
87 | ||
88 | /** enum ipa_uc_command - commands from the AP to the microcontroller */ | |
89 | enum ipa_uc_command { | |
8701cb00 AE |
90 | IPA_UC_COMMAND_NO_OP = 0x0, |
91 | IPA_UC_COMMAND_UPDATE_FLAGS = 0x1, | |
92 | IPA_UC_COMMAND_DEBUG_RUN_TEST = 0x2, | |
93 | IPA_UC_COMMAND_DEBUG_GET_INFO = 0x3, | |
94 | IPA_UC_COMMAND_ERR_FATAL = 0x4, | |
95 | IPA_UC_COMMAND_CLK_GATE = 0x5, | |
96 | IPA_UC_COMMAND_CLK_UNGATE = 0x6, | |
97 | IPA_UC_COMMAND_MEMCPY = 0x7, | |
98 | IPA_UC_COMMAND_RESET_PIPE = 0x8, | |
99 | IPA_UC_COMMAND_REG_WRITE = 0x9, | |
100 | IPA_UC_COMMAND_GSI_CH_EMPTY = 0xa, | |
a646d6ec AE |
101 | }; |
102 | ||
103 | /** enum ipa_uc_response - microcontroller response codes */ | |
104 | enum ipa_uc_response { | |
8701cb00 AE |
105 | IPA_UC_RESPONSE_NO_OP = 0x0, |
106 | IPA_UC_RESPONSE_INIT_COMPLETED = 0x1, | |
107 | IPA_UC_RESPONSE_CMD_COMPLETED = 0x2, | |
108 | IPA_UC_RESPONSE_DEBUG_GET_INFO = 0x3, | |
a646d6ec AE |
109 | }; |
110 | ||
111 | /** enum ipa_uc_event - common cpu events reported by the microcontroller */ | |
112 | enum ipa_uc_event { | |
8701cb00 AE |
113 | IPA_UC_EVENT_NO_OP = 0x0, |
114 | IPA_UC_EVENT_ERROR = 0x1, | |
115 | IPA_UC_EVENT_LOG_INFO = 0x2, | |
a646d6ec AE |
116 | }; |
117 | ||
118 | static struct ipa_uc_mem_area *ipa_uc_shared(struct ipa *ipa) | |
119 | { | |
5e3bc1e5 AE |
120 | const struct ipa_mem *mem = ipa_mem_find(ipa, IPA_MEM_UC_SHARED); |
121 | u32 offset = ipa->mem_offset + mem->offset; | |
a646d6ec AE |
122 | |
123 | return ipa->mem_virt + offset; | |
124 | } | |
125 | ||
126 | /* Microcontroller event IPA interrupt handler */ | |
127 | static void ipa_uc_event_handler(struct ipa *ipa, enum ipa_irq_id irq_id) | |
128 | { | |
129 | struct ipa_uc_mem_area *shared = ipa_uc_shared(ipa); | |
130 | struct device *dev = &ipa->pdev->dev; | |
131 | ||
132 | if (shared->event == IPA_UC_EVENT_ERROR) | |
133 | dev_err(dev, "microcontroller error event\n"); | |
0a5096ec | 134 | else if (shared->event != IPA_UC_EVENT_LOG_INFO) |
e2f154e6 | 135 | dev_err(dev, "unsupported microcontroller event %u\n", |
a646d6ec | 136 | shared->event); |
0a5096ec | 137 | /* The LOG_INFO event can be safely ignored */ |
a646d6ec AE |
138 | } |
139 | ||
140 | /* Microcontroller response IPA interrupt handler */ | |
141 | static void ipa_uc_response_hdlr(struct ipa *ipa, enum ipa_irq_id irq_id) | |
142 | { | |
143 | struct ipa_uc_mem_area *shared = ipa_uc_shared(ipa); | |
e2f154e6 | 144 | struct device *dev = &ipa->pdev->dev; |
a646d6ec AE |
145 | |
146 | /* An INIT_COMPLETED response message is sent to the AP by the | |
147 | * microcontroller when it is operational. Other than this, the AP | |
148 | * should only receive responses from the microcontroller when it has | |
149 | * sent it a request message. | |
150 | * | |
7aa0e8b8 | 151 | * We can drop the power reference taken in ipa_uc_power() once we |
a646d6ec AE |
152 | * know the microcontroller has finished its initialization. |
153 | */ | |
154 | switch (shared->response) { | |
155 | case IPA_UC_RESPONSE_INIT_COMPLETED: | |
7aa0e8b8 | 156 | if (ipa->uc_powered) { |
e2f154e6 | 157 | ipa->uc_loaded = true; |
93d415b5 | 158 | ipa_power_retention(ipa, true); |
1aac309d AE |
159 | pm_runtime_mark_last_busy(dev); |
160 | (void)pm_runtime_put_autosuspend(dev); | |
7aa0e8b8 | 161 | ipa->uc_powered = false; |
e2f154e6 AE |
162 | } else { |
163 | dev_warn(dev, "unexpected init_completed response\n"); | |
164 | } | |
a646d6ec AE |
165 | break; |
166 | default: | |
e2f154e6 | 167 | dev_warn(dev, "unsupported microcontroller response %u\n", |
a646d6ec AE |
168 | shared->response); |
169 | break; | |
170 | } | |
171 | } | |
172 | ||
dc8f7e39 AE |
173 | /* Configure the IPA microcontroller subsystem */ |
174 | void ipa_uc_config(struct ipa *ipa) | |
a646d6ec | 175 | { |
7aa0e8b8 | 176 | ipa->uc_powered = false; |
a646d6ec AE |
177 | ipa->uc_loaded = false; |
178 | ipa_interrupt_add(ipa->interrupt, IPA_IRQ_UC_0, ipa_uc_event_handler); | |
179 | ipa_interrupt_add(ipa->interrupt, IPA_IRQ_UC_1, ipa_uc_response_hdlr); | |
180 | } | |
181 | ||
dc8f7e39 AE |
182 | /* Inverse of ipa_uc_config() */ |
183 | void ipa_uc_deconfig(struct ipa *ipa) | |
a646d6ec | 184 | { |
1aac309d AE |
185 | struct device *dev = &ipa->pdev->dev; |
186 | ||
a646d6ec AE |
187 | ipa_interrupt_remove(ipa->interrupt, IPA_IRQ_UC_1); |
188 | ipa_interrupt_remove(ipa->interrupt, IPA_IRQ_UC_0); | |
93d415b5 AE |
189 | if (ipa->uc_loaded) |
190 | ipa_power_retention(ipa, false); | |
191 | ||
7aa0e8b8 | 192 | if (!ipa->uc_powered) |
1aac309d AE |
193 | return; |
194 | ||
195 | pm_runtime_mark_last_busy(dev); | |
196 | (void)pm_runtime_put_autosuspend(dev); | |
a646d6ec AE |
197 | } |
198 | ||
7aa0e8b8 AE |
199 | /* Take a proxy power reference for the microcontroller */ |
200 | void ipa_uc_power(struct ipa *ipa) | |
e2f154e6 AE |
201 | { |
202 | static bool already; | |
799c5c24 | 203 | struct device *dev; |
7ebd168c | 204 | int ret; |
e2f154e6 AE |
205 | |
206 | if (already) | |
207 | return; | |
208 | already = true; /* Only do this on first boot */ | |
209 | ||
799c5c24 AE |
210 | /* This power reference dropped in ipa_uc_response_hdlr() above */ |
211 | dev = &ipa->pdev->dev; | |
212 | ret = pm_runtime_get_sync(dev); | |
213 | if (ret < 0) { | |
214 | pm_runtime_put_noidle(dev); | |
215 | dev_err(dev, "error %d getting proxy power\n", ret); | |
216 | } else { | |
7aa0e8b8 | 217 | ipa->uc_powered = true; |
799c5c24 | 218 | } |
e2f154e6 AE |
219 | } |
220 | ||
a646d6ec AE |
221 | /* Send a command to the microcontroller */ |
222 | static void send_uc_command(struct ipa *ipa, u32 command, u32 command_param) | |
223 | { | |
224 | struct ipa_uc_mem_area *shared = ipa_uc_shared(ipa); | |
e666aa97 | 225 | u32 offset; |
716a115b | 226 | u32 val; |
a646d6ec | 227 | |
716a115b | 228 | /* Fill in the command data */ |
a646d6ec AE |
229 | shared->command = command; |
230 | shared->command_param = cpu_to_le32(command_param); | |
231 | shared->command_param_hi = 0; | |
232 | shared->response = 0; | |
233 | shared->response_param = 0; | |
234 | ||
716a115b AE |
235 | /* Use an interrupt to tell the microcontroller the command is ready */ |
236 | val = u32_encode_bits(1, UC_INTR_FMASK); | |
e666aa97 AE |
237 | offset = ipa_reg_irq_uc_offset(ipa->version); |
238 | iowrite32(val, ipa->reg_virt + offset); | |
a646d6ec AE |
239 | } |
240 | ||
241 | /* Tell the microcontroller the AP is shutting down */ | |
242 | void ipa_uc_panic_notifier(struct ipa *ipa) | |
243 | { | |
244 | if (!ipa->uc_loaded) | |
245 | return; | |
246 | ||
247 | send_uc_command(ipa, IPA_UC_COMMAND_ERR_FATAL, 0); | |
248 | ||
249 | /* give uc enough time to save state */ | |
250 | udelay(IPA_SEND_DELAY); | |
251 | } |