1 /*
2 * Copyright (c) 2016-2020 The Linux Foundation. All rights reserved.
3 * Copyright (c) 2021-2023 Qualcomm Innovation Center, Inc. All rights reserved.
4 *
5 * Permission to use, copy, modify, and/or distribute this software for
6 * any purpose with or without fee is hereby granted, provided that the
7 * above copyright notice and this permission notice appear in all
8 * copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
11 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
12 * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
13 * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
14 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
15 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
16 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
17 * PERFORMANCE OF THIS SOFTWARE.
18 */
19
20 #include <linux/platform_device.h>
21 #include <linux/err.h>
22 #include <linux/list.h>
23 #include <linux/slab.h>
24
25 #ifdef CONFIG_CNSS_OUT_OF_TREE
26 #ifdef CONFIG_PLD_SNOC_ICNSS
27 #ifdef CONFIG_PLD_SNOC_ICNSS2
28 #include "icnss2.h"
29 #else
30 #include "icnss.h"
31 #endif
32 #endif
33 #else
34 #ifdef CONFIG_PLD_SNOC_ICNSS
35 #ifdef CONFIG_PLD_SNOC_ICNSS2
36 #include <soc/qcom/icnss2.h>
37 #else
38 #include <soc/qcom/icnss.h>
39 #endif
40 #endif
41 #endif
42
43 #include "pld_internal.h"
44 #include "pld_snoc.h"
45 #include "osif_psoc_sync.h"
46
47 #ifdef CONFIG_PLD_SNOC_ICNSS
48
49 #define ADRASTEA_DEVICE_ID 0xabcd
50
51 /**
52 * pld_snoc_idle_restart_cb() - Perform idle restart
53 * @dev: platform device
54 *
55 * This function will be called if there is an idle restart request
56 *
57 * Return: int
58 **/
pld_snoc_idle_restart_cb(struct device * dev)59 static int pld_snoc_idle_restart_cb(struct device *dev)
60 {
61 struct pld_context *pld_context;
62
63 pld_context = pld_get_global_context();
64 if (pld_context->ops->idle_restart)
65 return pld_context->ops->idle_restart(dev, PLD_BUS_TYPE_SNOC);
66
67 return -ENODEV;
68 }
69
70 /**
71 * pld_snoc_idle_shutdown_cb() - Perform idle shutdown
72 * @dev: PCIE device
73 *
74 * This function will be called if there is an idle shutdown request
75 *
76 * Return: int
77 */
pld_snoc_idle_shutdown_cb(struct device * dev)78 static int pld_snoc_idle_shutdown_cb(struct device *dev)
79 {
80 struct pld_context *pld_context;
81
82 pld_context = pld_get_global_context();
83 if (pld_context->ops->shutdown)
84 return pld_context->ops->idle_shutdown(dev, PLD_BUS_TYPE_SNOC);
85
86 return -ENODEV;
87 }
88
89 /**
90 * pld_snoc_probe() - Probe function for platform driver
91 * @dev: device
92 *
93 * The probe function will be called when platform device
94 * is detected.
95 *
96 * Return: int
97 */
pld_snoc_probe(struct device * dev)98 static int pld_snoc_probe(struct device *dev)
99 {
100 struct pld_context *pld_context;
101 int ret = 0;
102
103 pld_context = pld_get_global_context();
104 if (!pld_context) {
105 ret = -ENODEV;
106 goto out;
107 }
108
109 ret = pld_add_dev(pld_context, dev, NULL, PLD_BUS_TYPE_SNOC);
110 if (ret)
111 goto out;
112
113 return pld_context->ops->probe(dev, PLD_BUS_TYPE_SNOC,
114 NULL, NULL);
115
116 out:
117 return ret;
118 }
119
120 /**
121 * pld_snoc_remove() - Remove function for platform device
122 * @dev: device
123 *
124 * The remove function will be called when platform device
125 * is disconnected
126 *
127 * Return: void
128 */
pld_snoc_remove(struct device * dev)129 static void pld_snoc_remove(struct device *dev)
130 {
131 struct pld_context *pld_context;
132 int errno;
133 struct osif_psoc_sync *psoc_sync;
134
135 errno = osif_psoc_sync_trans_start_wait(dev, &psoc_sync);
136 if (errno)
137 return;
138
139 osif_psoc_sync_unregister(dev);
140 osif_psoc_sync_wait_for_ops(psoc_sync);
141
142 pld_context = pld_get_global_context();
143
144 if (!pld_context)
145 goto out;
146
147 pld_context->ops->remove(dev, PLD_BUS_TYPE_SNOC);
148
149 pld_del_dev(pld_context, dev);
150
151 out:
152 osif_psoc_sync_trans_stop(psoc_sync);
153 osif_psoc_sync_destroy(psoc_sync);
154 }
155
156 /**
157 * pld_snoc_reinit() - SSR re-initialize function for platform device
158 * @dev: device
159 *
160 * During subsystem restart(SSR), this function will be called to
161 * re-initialize platform device.
162 *
163 * Return: int
164 */
pld_snoc_reinit(struct device * dev)165 static int pld_snoc_reinit(struct device *dev)
166 {
167 struct pld_context *pld_context;
168
169 pld_context = pld_get_global_context();
170 if (pld_context->ops->reinit)
171 return pld_context->ops->reinit(dev, PLD_BUS_TYPE_SNOC,
172 NULL, NULL);
173
174 return -ENODEV;
175 }
176
177 /**
178 * pld_snoc_shutdown() - SSR shutdown function for platform device
179 * @dev: device
180 *
181 * During SSR, this function will be called to shutdown platform device.
182 *
183 * Return: void
184 */
pld_snoc_shutdown(struct device * dev)185 static void pld_snoc_shutdown(struct device *dev)
186 {
187 struct pld_context *pld_context;
188
189 pld_context = pld_get_global_context();
190 if (pld_context->ops->shutdown)
191 pld_context->ops->shutdown(dev, PLD_BUS_TYPE_SNOC);
192 }
193
194 /**
195 * pld_snoc_crash_shutdown() - Crash shutdown function for platform device
196 * @dev: device
197 *
198 * This function will be called when a crash is detected, it will shutdown
199 * platform device.
200 *
201 * Return: void
202 */
pld_snoc_crash_shutdown(void * dev)203 static void pld_snoc_crash_shutdown(void *dev)
204 {
205 struct pld_context *pld_context;
206
207 pld_context = pld_get_global_context();
208 if (pld_context->ops->crash_shutdown)
209 pld_context->ops->crash_shutdown(dev, PLD_BUS_TYPE_SNOC);
210 }
211
212 /**
213 * pld_snoc_pm_suspend() - PM suspend callback function for power management
214 * @dev: device
215 *
216 * This function is to suspend the platform device when power management
217 * is enabled.
218 *
219 * Return: void
220 */
pld_snoc_pm_suspend(struct device * dev)221 static int pld_snoc_pm_suspend(struct device *dev)
222 {
223 struct pld_context *pld_context;
224 pm_message_t state;
225
226 state.event = PM_EVENT_SUSPEND;
227 pld_context = pld_get_global_context();
228 return pld_context->ops->suspend(dev, PLD_BUS_TYPE_SNOC, state);
229 }
230
231 /**
232 * pld_snoc_pm_resume() - PM resume callback function for power management
233 * @dev: device
234 *
235 * This function is to resume the platform device when power management
236 * is enabled.
237 *
238 * Return: void
239 */
pld_snoc_pm_resume(struct device * dev)240 static int pld_snoc_pm_resume(struct device *dev)
241 {
242 struct pld_context *pld_context;
243
244 pld_context = pld_get_global_context();
245 return pld_context->ops->resume(dev, PLD_BUS_TYPE_SNOC);
246 }
247
248 /**
249 * pld_snoc_suspend_noirq() - Complete the actions started by suspend()
250 * @dev: device
251 *
252 * Complete the actions started by suspend(). Carry out any
253 * additional operations required for suspending the device that might be
254 * racing with its driver's interrupt handler, which is guaranteed not to
255 * run while suspend_noirq() is being executed.
256 *
257 * Return: 0 for success
258 * Non zero failure code for errors
259 */
pld_snoc_suspend_noirq(struct device * dev)260 static int pld_snoc_suspend_noirq(struct device *dev)
261 {
262 struct pld_context *pld_context;
263
264 pld_context = pld_get_global_context();
265 if (!pld_context)
266 return -EINVAL;
267
268 if (pld_context->ops->suspend_noirq)
269 return pld_context->ops->suspend_noirq(dev, PLD_BUS_TYPE_SNOC);
270 return 0;
271 }
272
273 /**
274 * pld_snoc_resume_noirq() - Prepare for the execution of resume()
275 * @dev: device
276 *
277 * Prepare for the execution of resume() by carrying out any
278 * operations required for resuming the device that might be racing with
279 * its driver's interrupt handler, which is guaranteed not to run while
280 * resume_noirq() is being executed.
281 *
282 * Return: 0 for success
283 * Non zero failure code for errors
284 */
pld_snoc_resume_noirq(struct device * dev)285 static int pld_snoc_resume_noirq(struct device *dev)
286 {
287 struct pld_context *pld_context;
288
289 pld_context = pld_get_global_context();
290 if (!pld_context)
291 return -EINVAL;
292
293 if (pld_context->ops->resume_noirq)
294 return pld_context->ops->resume_noirq(dev, PLD_BUS_TYPE_SNOC);
295
296 return 0;
297 }
298
299 #if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0))
pld_update_hang_evt_data(struct icnss_uevent_hang_data * evt_data,struct pld_uevent_data * data)300 static int pld_update_hang_evt_data(struct icnss_uevent_hang_data *evt_data,
301 struct pld_uevent_data *data)
302 {
303 if (!evt_data || !data)
304 return -EINVAL;
305
306 data->hang_data.hang_event_data = evt_data->hang_event_data;
307 data->hang_data.hang_event_data_len = evt_data->hang_event_data_len;
308 return 0;
309 }
310
pld_snoc_uevent(struct device * dev,struct icnss_uevent_data * uevent)311 static int pld_snoc_uevent(struct device *dev,
312 struct icnss_uevent_data *uevent)
313 {
314 struct pld_context *pld_context;
315 struct icnss_uevent_fw_down_data *fw_down_data = NULL;
316 struct icnss_uevent_hang_data *hang_data = NULL;
317 struct pld_uevent_data data = {0};
318
319 pld_context = pld_get_global_context();
320 if (!pld_context)
321 return -EINVAL;
322
323 if (!pld_context->ops->uevent)
324 goto out;
325
326 if (!uevent)
327 return -EINVAL;
328
329 switch (uevent->uevent) {
330 case ICNSS_UEVENT_FW_CRASHED:
331 data.uevent = PLD_FW_CRASHED;
332 break;
333 case ICNSS_UEVENT_FW_DOWN:
334 if (!uevent->data)
335 return -EINVAL;
336 fw_down_data = (struct icnss_uevent_fw_down_data *)uevent->data;
337 data.uevent = PLD_FW_DOWN;
338 data.fw_down.crashed = fw_down_data->crashed;
339 break;
340 case ICNSS_UEVENT_HANG_DATA:
341 if (!uevent->data)
342 return -EINVAL;
343 hang_data = (struct icnss_uevent_hang_data *)uevent->data;
344 data.uevent = PLD_FW_HANG_EVENT;
345 pld_update_hang_evt_data(hang_data, &data);
346 break;
347 default:
348 goto out;
349 }
350
351 pld_context->ops->uevent(dev, &data);
352 out:
353 return 0;
354 }
355 #else
pld_snoc_uevent(struct device * dev,struct icnss_uevent_data * uevent)356 static int pld_snoc_uevent(struct device *dev,
357 struct icnss_uevent_data *uevent)
358 {
359 struct pld_context *pld_context;
360 struct icnss_uevent_fw_down_data *fw_down_data = NULL;
361 struct pld_uevent_data data = {0};
362
363 pld_context = pld_get_global_context();
364 if (!pld_context)
365 return -EINVAL;
366
367 if (!pld_context->ops->uevent)
368 goto out;
369
370 if (!uevent)
371 return -EINVAL;
372
373 switch (uevent->uevent) {
374 case ICNSS_UEVENT_FW_CRASHED:
375 data.uevent = PLD_FW_CRASHED;
376 break;
377 case ICNSS_UEVENT_FW_DOWN:
378 if (!uevent->data)
379 return -EINVAL;
380 fw_down_data = (struct icnss_uevent_fw_down_data *)uevent->data;
381 data.uevent = PLD_FW_DOWN;
382 data.fw_down.crashed = fw_down_data->crashed;
383 break;
384 default:
385 goto out;
386 }
387
388 pld_context->ops->uevent(dev, &data);
389 out:
390 return 0;
391 }
392 #endif
393
394 #ifdef MULTI_IF_NAME
395 #define PLD_SNOC_OPS_NAME "pld_snoc_" MULTI_IF_NAME
396 #else
397 #define PLD_SNOC_OPS_NAME "pld_snoc"
398 #endif
399
400 #if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0))
401 static struct device_info pld_snoc_dev_info[] = {
402 { "ADRASTEA", ADRASTEA_DEVICE_ID },
403 { 0 }
404 };
405 #endif
406
407 struct icnss_driver_ops pld_snoc_ops = {
408 .name = PLD_SNOC_OPS_NAME,
409 #if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0))
410 .dev_info = pld_snoc_dev_info,
411 #endif
412 .probe = pld_snoc_probe,
413 .remove = pld_snoc_remove,
414 .shutdown = pld_snoc_shutdown,
415 .reinit = pld_snoc_reinit,
416 .crash_shutdown = pld_snoc_crash_shutdown,
417 .pm_suspend = pld_snoc_pm_suspend,
418 .pm_resume = pld_snoc_pm_resume,
419 .suspend_noirq = pld_snoc_suspend_noirq,
420 .resume_noirq = pld_snoc_resume_noirq,
421 .uevent = pld_snoc_uevent,
422 .idle_restart = pld_snoc_idle_restart_cb,
423 .idle_shutdown = pld_snoc_idle_shutdown_cb,
424 };
425
pld_snoc_register_driver(void)426 int pld_snoc_register_driver(void)
427 {
428 return icnss_register_driver(&pld_snoc_ops);
429 }
430
pld_snoc_unregister_driver(void)431 void pld_snoc_unregister_driver(void)
432 {
433 icnss_unregister_driver(&pld_snoc_ops);
434 }
435
pld_snoc_wlan_enable(struct device * dev,struct pld_wlan_enable_cfg * config,enum pld_driver_mode mode,const char * host_version)436 int pld_snoc_wlan_enable(struct device *dev, struct pld_wlan_enable_cfg *config,
437 enum pld_driver_mode mode, const char *host_version)
438 {
439 struct icnss_wlan_enable_cfg cfg;
440 enum icnss_driver_mode icnss_mode;
441
442 if (!dev)
443 return -ENODEV;
444
445 cfg.num_ce_tgt_cfg = config->num_ce_tgt_cfg;
446 cfg.ce_tgt_cfg = (struct ce_tgt_pipe_cfg *)
447 config->ce_tgt_cfg;
448 cfg.num_ce_svc_pipe_cfg = config->num_ce_svc_pipe_cfg;
449 cfg.ce_svc_cfg = (struct ce_svc_pipe_cfg *)
450 config->ce_svc_cfg;
451 cfg.num_shadow_reg_cfg = config->num_shadow_reg_cfg;
452 cfg.shadow_reg_cfg = (struct icnss_shadow_reg_cfg *)
453 config->shadow_reg_cfg;
454
455 switch (mode) {
456 case PLD_FTM:
457 icnss_mode = ICNSS_FTM;
458 break;
459 case PLD_EPPING:
460 icnss_mode = ICNSS_EPPING;
461 break;
462 default:
463 icnss_mode = ICNSS_MISSION;
464 break;
465 }
466
467 return icnss_wlan_enable(dev, &cfg, icnss_mode, host_version);
468 }
469
pld_snoc_wlan_disable(struct device * dev,enum pld_driver_mode mode)470 int pld_snoc_wlan_disable(struct device *dev, enum pld_driver_mode mode)
471 {
472 if (!dev)
473 return -ENODEV;
474
475 return icnss_wlan_disable(dev, ICNSS_OFF);
476 }
477
pld_snoc_get_soc_info(struct device * dev,struct pld_soc_info * info)478 int pld_snoc_get_soc_info(struct device *dev, struct pld_soc_info *info)
479 {
480 int errno;
481 struct icnss_soc_info icnss_info = {0};
482
483 if (!info || !dev)
484 return -ENODEV;
485
486 errno = icnss_get_soc_info(dev, &icnss_info);
487 if (errno)
488 return errno;
489
490 info->v_addr = icnss_info.v_addr;
491 info->p_addr = icnss_info.p_addr;
492 info->chip_id = icnss_info.chip_id;
493 info->chip_family = icnss_info.chip_family;
494 info->board_id = icnss_info.board_id;
495 info->soc_id = icnss_info.soc_id;
496 info->fw_version = icnss_info.fw_version;
497 strlcpy(info->fw_build_timestamp, icnss_info.fw_build_timestamp,
498 sizeof(info->fw_build_timestamp));
499
500 return 0;
501 }
502 #endif
503