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