summaryrefslogtreecommitdiff
path: root/snd-alpx-dkms/snd-alpx/alpx_core.c
blob: 0fb4d8287b9de6a25930518f510593daebc05f8e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
// SPDX-License-Identifier: GPL-2.0-or-later
/*
*  Support for Digigram AlpX PCI-e boards
*
*  Copyright (c) 2024 Digigram Digital (info@digigram.com)
*/

#include "alpx.h"
#include "alpx_reg.h"
#include "alpx_mtd.h"
#include "alpx_led.h"
#include "alpx_controls.h"
#include "alpx_streams.h"
#include "alpx_version.h"
#include "alpx_variants_stereo.h"
#include "alpx_variants_stereo_apps_preFW283.h"
#include "alpx_variants_mc.h"
#include "alpx_variants_882_apps_preFW240.h"
#include "alpx_variants_dead.h"
#include "alpx_variants_madi.h"
#include "alpx_variants_dante.h"
#include "alpx_cards.h"
#include "alpx_xdma.h"
#include "snd_alpx_xdma.h"


#include <linux/version.h>
#include <linux/kmod.h>

#if !defined (CONFIG_WITHOUT_GPIO)
#define ALPX_WITH_GPIO
#include <linux/gpio.h>
#include <linux/gpio/driver.h>
#include "alpx_gpio.h"
#define ALPX_GPIO_OPTION_STRING ""
#else
#warning !! GPIOs are DISABLED !!
#define ALPX_GPIO_OPTION_STRING "(without GPIO)"
#endif


#include <linux/mtd/partitions.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <linux/version.h>
#include <sound/control.h>
#include <sound/core.h>
#include <sound/initval.h>
#include <sound/pcm.h>
#include <linux/string.h>


#if KERNEL_VERSION(6, 7, 0) > LINUX_VERSION_CODE
#include "core/generic/6.3/amd_xdma.h"
#include "include/6.3/amd_xdma.h"
#else
#include <linux/platform_data/amd_xdma.h>
#include <linux/dma/amd_xdma.h>
#endif

#ifdef WITH_REG_DEBUG
#include <linux/device.h>
#endif

/* Constants */

/* Structures */

/* Parameters */

#define ALPX_DEFAULT_CARD_NAME "Digigram AlpX"

static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
module_param_array(index, int, NULL, 0444);
MODULE_PARM_DESC(index, "Index value for " ALPX_DEFAULT_CARD_NAME " soundcard.");

static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
module_param_array(id, charp, NULL, 0444);
MODULE_PARM_DESC(id, "ID string for " ALPX_DEFAULT_CARD_NAME " soundcard.");

static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
module_param_array(enable, bool, NULL, 0444);
MODULE_PARM_DESC(enable, "Enable " ALPX_DEFAULT_CARD_NAME " soundcard.");

unsigned int log_transfers = 0;
module_param(log_transfers, uint, 0644);
MODULE_PARM_DESC(log_transfers, "0: no transfer logged(default), 1 : transfers logged");

static bool with_board_in_prod_mode = false;
module_param(with_board_in_prod_mode, bool, 0644);
MODULE_PARM_DESC(with_board_in_prod_mode, "0: board in USER mode(default) ; 1: board in PRODUCTION mode ");

static bool with_board_in_dead_mode = false;
module_param(with_board_in_dead_mode, bool, 0644);
MODULE_PARM_DESC(with_board_in_dead_mode, "0: board in NORMAL mode(default) ; 1: board in DEAD mode ");

static unsigned int dante_configured_fs = ALPDANTE_DEFAULT_FS;
module_param(dante_configured_fs, uint, 0644);
MODULE_PARM_DESC(dante_configured_fs, "Configured FS for Alp DANTE card (value in Hz, default: 48000)");

static bool dante_loopback_enabled = false;
module_param(dante_loopback_enabled, bool, 0644);
MODULE_PARM_DESC(dante_loopback_enabled, "Enable the Loopback for Alp DANTE card (default: FALSE)");

static bool alp222_pre_FW283_apps_support = false;
module_param(alp222_pre_FW283_apps_support, bool, 0644);
MODULE_PARM_DESC(alp222_pre_FW283_apps_support, "For ALp222e cards ONLY, Enable the compatibility mode for applications written for PRE V283 Firmwares (default: FALSE)");

static bool alp882_pre_FW240_apps_support = false;
module_param(alp882_pre_FW240_apps_support, bool, 0644);
MODULE_PARM_DESC(alp882_pre_FW240_apps_support, "For ALp882e cards ONLY, Enable the compatibility mode for applications written for PRE V240 Firmwares (default: FALSE)");


/* Below, this is significant only if WITH_REG_DEBUG is defined */
#ifdef WITH_REG_DEBUG
#warning BUILD with Registers Debug enabled

static ssize_t reg_offset_show(struct device* dev, struct device_attribute* attr, char* buf )
{

	//HOWTO struct dev TO struct alpx_device :
	struct snd_card* const card = dev_get_drvdata(dev);
	struct alpx_device* const alpx_dev = card->private_data;
	
	dev_dbg( dev,"%s(): CALLED for card: 0x%p : REG OFFSET: 0x%08x\n", __func__, 
		alpx_dev,
		alpx_dev->dbg_reg_offset);
	
		
	return scnprintf(buf, PAGE_SIZE, "0x%08x\n", alpx_dev->dbg_reg_offset);
}

static ssize_t reg_offset_store(struct device* dev, struct device_attribute* attr, const char* buf , size_t count)
{
	//HOWTO struct dev TO struct alpx_device :
	struct snd_card* const card = dev_get_drvdata(dev);
	struct alpx_device* const alpx_dev = card->private_data;
		
	unsigned int reg_offset = 0;
	if (kstrtou32(buf, 0, &reg_offset) < 0)
		return -EINVAL;
	
	//Check offset range
	if ((reg_offset < ALP222_MIN_REG_OFFSET) &&
		(reg_offset > ALP222_MAX_REG_OFFSET))
		return -EINVAL;
	
	alpx_dev->dbg_reg_offset = reg_offset;
	dev_dbg( dev,"%s(): CALLED for alpx: %p,  reg_offset: 0x%08x\n", __func__,  alpx_dev, alpx_dev->dbg_reg_offset);
	
	return count;
}

//static struct device_attribute dev_reg_addr = __ATTR_RW(reg_addr);
static struct device_attribute dev_reg_offset = __ATTR_RW(reg_offset);

/******************************/
static ssize_t reg_value_show(struct device* dev, struct device_attribute* attr, char* buf )
{

	//HOWTO struct dev TO struct alpx_device :
	struct snd_card* const card = dev_get_drvdata(dev);
	struct alpx_device* const alpx_dev = card->private_data;
	/* Read the register once to cope with 'clear on read' registers */
	const uint32_t reg_value = readl(alpx_dev->base+ alpx_dev->dbg_reg_offset);
	
	dev_dbg( dev,"%s(): CALLED for %p, (dev:%p): [0x%08x] => 0x%08x\n", __func__, 
		alpx_dev, dev,
		alpx_dev->dbg_reg_offset, 
		reg_value);
	
	return scnprintf(buf, PAGE_SIZE, "0x%08x\n", reg_value);
}

static ssize_t reg_value_store(struct device* dev, struct device_attribute* attr, const char* buf , size_t count)
{
	//HOWTO struct dev TO struct alpx_device :
	struct snd_card* const card = dev_get_drvdata(dev);
	struct alpx_device* const alpx_dev = card->private_data;
		
	uint32_t reg_value = 0;
	if (kstrtou32(buf, 0, &reg_value) < 0)
		return -EINVAL;
	
		
	writel(reg_value, alpx_dev->base + alpx_dev->dbg_reg_offset);
	
	dev_dbg( dev,"%s(): CALLED for alpx: %p,  [0x%08x] <= 0x%08x, Check:0x%08x\n", __func__,  
		alpx_dev, 
		alpx_dev->dbg_reg_offset,
		reg_value,
		readl(alpx_dev->base+ alpx_dev->dbg_reg_offset));
	
	return count;
}

static struct device_attribute dev_reg_value = __ATTR_RW(reg_value);

#endif

/******************************/
static ssize_t serial_number_show(struct device* dev, struct device_attribute* attr, char* buf )
{

	struct snd_card* const card = dev_get_drvdata(dev);
	struct alpx_device* alpx_dev = card->private_data;
	
	dev_dbg(alpx_dev->dev, " %s() : AFTER shift %llu / 0x%llx\n", __func__, alpx_dev->identity.serial_number, alpx_dev->identity.serial_number);
	
	return scnprintf(buf, PAGE_SIZE, "%llu\n", alpx_dev->identity.serial_number);
}

static struct device_attribute dev_serial_number = __ATTR_RO(serial_number);
/***************************************/

static ssize_t ver_fpga_show(struct device* dev, struct device_attribute* attr, char* buf )
{

	struct snd_card* const card = dev_get_drvdata(dev);
	struct alpx_device* alpx_dev = card->private_data;
	
	return scnprintf(buf, PAGE_SIZE, "%u\n", alpx_dev->identity.ver_fpga);
}

static struct device_attribute dev_ver_fpga = __ATTR_RO(ver_fpga);

/***************************************/
static ssize_t ver_mcu_show(struct device* dev, struct device_attribute* attr, char* buf )
{

	struct snd_card* const card = dev_get_drvdata(dev);
	struct alpx_device* alpx_dev = card->private_data;
	
	return scnprintf(buf, PAGE_SIZE, "%u.%lu\n", 
					ALPX_COMMON_VERSION_VERSION(alpx_dev->identity.ver_mcu),
					ALPX_COMMON_VERSION_REVISION(alpx_dev->identity.ver_mcu)	);
}

static struct device_attribute dev_ver_mcu = __ATTR_RO(ver_mcu);
/***************************************/
static ssize_t dante_card_name_show(struct device* dev, struct device_attribute* attr, char* buf )
{

	struct snd_card* const card = dev_get_drvdata(dev);
	struct alpx_device* alpx_dev = card->private_data;

	int result =  alpdante_get_dante_name(alpx_dev, buf, ALPDANTE_NETWORK_NAME_LENGTH);
	return (!result) ? strlcat(buf, "\n", PAGE_SIZE) : 0;
}

static struct device_attribute dev_dante_card_name = __ATTR_RO(dante_card_name);
/***************************************/

/* PCI */

static void alpx_card_private_free(struct snd_card *card)
{
	struct alpx_device *alpx_dev = card->private_data;

#if defined (ALPX_WITH_GPIO)
	if (alpx_dev->variant->features & ALPX_VARIANT_FEATURE_GPIOS)
		alpx_gpio_unregister(alpx_dev);
#endif	

	if (alpx_dev->controls)
			kfree(alpx_dev->controls);

	/* dirty crash avoid when dma fails and frees card before pipes are init. */
}

static inline unsigned int is_alp222_with_mic_option(struct alpx_device* alpx_dev)
{
	const u32 reg_mic = readl(alpx_dev->base + ALP222_CONTROL_BASE + ALP222_MIC_CONTROL_REG);
	dev_dbg(alpx_dev->dev, "Daughter_Control[0x%08x:%x] = 0x%08x\n",
			ALP222_CONTROL_BASE,
			ALP222_MIC_CONTROL_REG,
			reg_mic);
	return reg_mic & (1<<ALP222_MIC_HERE_POS);
}

static int alpx_core_set_pci_bus(struct alpx_device* alpx_dev)
{
	int ret = pci_enable_device(alpx_dev->pci_dev);
	if (ret) {
		dev_err(alpx_dev->dev, "failed to enable PCI device\n");
		return ret;
	}

	/* Enable PCI relaxed ordering. */
	pcie_capability_set_word(alpx_dev->pci_dev, PCI_EXP_DEVCTL,
				PCI_EXP_DEVCTL_RELAX_EN);

	/* Enable extended tagging. */
	pcie_capability_set_word(alpx_dev->pci_dev, PCI_EXP_DEVCTL,
				PCI_EXP_DEVCTL_EXT_TAG);

	/* Set maximum memory read request. */
	pcie_set_readrq(alpx_dev->pci_dev, 512);

	pci_set_master(alpx_dev->pci_dev);
	
	return ret;
}


/****************************************************************/
/*
* This function will HANDLE the XMA module dependency either from kernel or
* from the package according to the kernel's version.
* Return an error the kernel's module is in bad version (kernel <6.7) as no CYLLIC DMA
* was yet supported.
* MUST be CALLED after alpx_xdma_register() to get the xdma_dev
*/
static int alpx_core_handle_xdma_dep (struct device *dev, struct platform_device* xdma_dev)
{

	dev_info(dev, "Handling XDMA support for Linux : %d.\n", LINUX_VERSION_CODE);

#if KERNEL_VERSION(6, 3, 0) > LINUX_VERSION_CODE
	dev_info(dev, "OLD Kernel, USE snd-alpx-xdma.\n");
	//NO XDMA in the kernel Force the dependency to package's xdma module already done before call by
	// SND_ALPX_XDMA_DEP();

#elif KERNEL_VERSION(6, 7, 0) <= LINUX_VERSION_CODE
	#if IS_ENABLED(CONFIG_XILINX_XDMA)
		dev_info(dev, "Kernel XDMA Ok, USE xdma.\n");
		//NO XDMA in the kernel Force the dependency to package's xdma module.
		xdma_get_user_irq(xdma_dev, 256); //Dumb irq to for an error : no way
	#else
		dev_info(dev, "Kernel XDMA NOT enabled, USE snd-alpx-xdma.\n");
		//Dependency already handled by SND_ALPX_XDMA_DEP() call defined accordingly !!
	#endif
#elif (KERNEL_VERSION(6, 3, 0) <= LINUX_VERSION_CODE) && \
		   (KERNEL_VERSION(6, 7, 0) > LINUX_VERSION_CODE)
		   //FORCE snd-alpx-xdma use as kernel's one w/o cyclic support
			#if IS_ENABLED(CONFIG_XILINX_XDMA)
				#if IS_MODULE(CONFIG_XILINX_XDMA)
					#warning "Xilinx XDMA module must be unloaded before use : NOT COMPLIANT !!"
				#else
					#error "Xilinx XDMA moule in Kernel is not COMPLIANT, Kernel must be Reconfigured/Rebuild without it !!!!"
				#endif
			#endif
#endif
	return 0;
}
/****************************************************************************/
static int alpx_probe(struct pci_dev *pci_dev,
			const struct pci_device_id *pci_device_id)
{
	static int card_idx = 0;
	struct device *dev = &pci_dev->dev;
	struct alpx_device *alpx_dev;
	struct snd_card *card;
	struct snd_pcm *pcm;
	int ret;
	int is_update_required = 0;

	/* Module identity */

	dev_info(dev,"version %s %s build date: %s, time: %s. %s, %s\n",
		ALPX_MODULE_VERSION,
		ALPX_GPIO_OPTION_STRING,
		__DATE__ ,
		__TIME__,
		 SND_ALPX_XDMA_DEP(),
		log_transfers ? "Transfers Logged":"");

	/* Ensure dependency on AlpX XDMA support if needed*/
	SND_ALPX_XDMA_DEP();

	/* Card */

	if (card_idx >= SNDRV_CARDS)
		return -ENODEV;

	if (!enable[card_idx]) {
		card_idx++;
		return -ENOENT;
	}
	
	dev_dbg(dev,"%s() : PCI Id : Vendor: 0x04%x, Device: 0x%04x, subvendor: 0x%04x, subdevice: 0x%04x\n", __func__,
		pci_device_id->vendor,
		pci_device_id->device,
		pci_device_id->subvendor,
		pci_device_id->subdevice);
	
	dev_dbg(dev,"%s() : PCI dev: Vendor: 0x04%x, Device: 0x%04x, subvendor: 0x%04x, subdevice: 0x%04x\n", __func__,
		pci_dev->vendor,
		pci_dev->device,
		pci_dev->subsystem_vendor,
		pci_dev->subsystem_device);
		

	ret = snd_card_new(dev, index[card_idx], id[card_idx], THIS_MODULE,
			sizeof(*alpx_dev), &card);
	if (ret) {
		dev_err(dev," snd_card_new() => %d\n", ret);
		return ret;
	}

	card->private_free = alpx_card_private_free;

	/* PCI */

	pci_set_drvdata(pci_dev, card);

	alpx_dev = card->private_data;
	alpx_dev->dev = dev;
	alpx_dev->pci_dev = pci_dev;
	
	ret = alpx_core_set_pci_bus(alpx_dev);
	if (ret){
		dev_err(dev," alpx_core_set_pci_bus(alpx_dev) => %d\n", ret);
		goto error_card;
	}
	
	//Map USER BAR now to get access to all card's resources
	alpx_dev->base = pci_ioremap_bar(pci_dev, 0);
	
	/* PCI */

	ret = pci_enable_device(pci_dev);
	if (ret) {
		dev_err(dev, "failed to enable PCI device\n");
		goto error_card;
	}

	/* Enable PCI relaxed ordering. */
	pcie_capability_set_word(pci_dev, PCI_EXP_DEVCTL,
				 PCI_EXP_DEVCTL_RELAX_EN);

	/* Enable extended tagging. */
	pcie_capability_set_word(pci_dev, PCI_EXP_DEVCTL,
				 PCI_EXP_DEVCTL_EXT_TAG);

	/* Set maximum memory read request. */
	pcie_set_readrq(pci_dev, 512);

	pci_set_master(pci_dev);

	/* XDMA */

	alpx_dev->xdma_pdev = alpx_xdma_register(pci_dev);
	if (!alpx_dev->xdma_pdev) {
		dev_err(dev," alpx_xdma_register(alpx_dev) => %d\n", ret);
		goto disable_pci_dev;
	}

	/* XDMA dependency handling*/
	ret = alpx_core_handle_xdma_dep(dev, alpx_dev->xdma_pdev);
	if (ret) {
		dev_err(dev, "Error %d when handling xdma dependency !\n", ret);
		goto disable_pci_dev;
	}

	/* Variant */
	
	//Use PCI Id to select the actual variant : WARNING, MIC option is checked with user's registers !!
	switch (pci_dev->subsystem_device){
		case ALPX_PCI_ID_SUBSYSTEM_ALP222:
			/* HANDLE Alp222_MIC with LINE PCI Id : firmwares ante V272*/
			if (alp222_pre_FW283_apps_support) {
				dev_warn(alpx_dev->dev, "!!! Alp2220 Pre FW283 Application compatibility mode ACTIVATED !!!");
			}
			if (!is_alp222_with_mic_option(alpx_dev)) {
				alpx_dev->variant =  (!alp222_pre_FW283_apps_support) ? &alp222_variant : &alp222_app_preFW283_variant;
			}
			else {
				alpx_dev->variant =  (!alp222_pre_FW283_apps_support) ? &alp222_mic_variant : &alp222_app_preFW283_mic_variant;
				alp222_mic_controls_default_config(alpx_dev);
			}			
			break;
		case ALPX_PCI_ID_SUBSYSTEM_ALP222_MIC:
				if (alp222_pre_FW283_apps_support) {
					dev_warn(alpx_dev->dev, "!!! Alp222 Pre FW283 Application compatibility mode ACTIVATED !!!");
				}
				alpx_dev->variant =  (!alp222_pre_FW283_apps_support) ? &alp222_mic_variant : &alp222_app_preFW283_mic_variant;
				alp222_mic_controls_default_config(alpx_dev);
			break;
		case ALPX_PCI_ID_SUBSYSTEM_MADI:
			alpx_dev->variant = &alpx_madi_variant;
			break;
		case ALPX_PCI_ID_SUBSYSTEM_ALP882:
				if (alp882_pre_FW240_apps_support)
				dev_warn(alpx_dev->dev, "!!! Alp882 LINE with Pre FW240 Application compatibility mode ACTIVATED !!!");
				alpx_dev->variant = (!alp882_pre_FW240_apps_support) ? &alp882_line_variant : &alp882_app_preFW240_line_variant;
			break;
		case ALPX_PCI_ID_SUBSYSTEM_ALP882_MIC:
				if (alp882_pre_FW240_apps_support)
				dev_warn(alpx_dev->dev, "!!! Alp882 MIC with Pre FW240 Application compatibility mode ACTIVATED !!!");
				alpx_dev->variant = (!alp882_pre_FW240_apps_support) ? &alp882_mic_variant : &alp882_app_preFW240_mic_variant;
			break;
			case ALPX_PCI_ID_SUBSYSTEM_ALP442:
				alpx_dev->variant = &alp442_line_variant; 
			break;
		case ALPX_PCI_ID_SUBSYSTEM_ALP442_MIC:
				alpx_dev->variant = &alp442_mic_variant; 
			break;
		case ALPX_PCI_ID_SUBSYSTEM_MADI_ALP_DEAD:
			alpx_dev->variant = &alpx_dead_variant; 
			break;
		case ALPX_PCI_ID_SUBSYSTEM_ALPDANTE:
			alpx_dev->variant = &alpx_dante_variant; 
			break;
		default:
			dev_warn(alpx_dev->dev,"ALPX Driver: Model Id 0x%04x is not supported => DEAD by default Here !\n", 
				pci_dev->subsystem_device);
			alpx_dev->variant = &alpx_dead_variant; 
	}
	
	/* FORCED DEAD mode, not for DANTE card */
	if (with_board_in_dead_mode)
	{
			/* Check DEAD mode for supported variants*/
			if 	(alpx_dev->variant->model  != ALPX_VARIANT_MODEL_ALPDANTE) {
				alpx_dev->variant = &alpx_dead_variant; 
				dev_warn(alpx_dev->dev, " !!! Board forced in DEAD mode !!\n");
			}
			else {
				dev_err(alpx_dev->dev, " NO DEAD mode supported for this board : %s\n", alpx_dev->variant->shortname);
				ret = -EINVAL;
				goto unregister_xdma;
			}
	}
		
	/* MTD */
	if (alpx_dev->variant->flash_partitions.qty != 0) {
// 		/* mtd Handle the PRODUCTION access or DEAD card, BUT not for DANTE cards*/
		with_board_in_prod_mode &= (alpx_dev->variant->model  != ALPX_VARIANT_MODEL_ALPDANTE);
		
		if ( with_board_in_prod_mode  ||
				(alpx_dev->variant->model  == ALPX_VARIANT_DEAD)) {
			dev_warn( alpx_dev->dev," Flash in PRODUCTION mode or DEAD card: full access !\n");
			ret = alpx_mtd_probe(alpx_dev, alpx_dev->variant->flash_partitions.partitions ,alpx_dev->variant->flash_partitions.qty);
		}
		else {
			dev_dbg( alpx_dev->dev," Flash in USER mode: firmware update access only.\n");
			ret = alpx_mtd_probe(alpx_dev, 
							&alpx_dev->variant->flash_partitions.partitions[ALPX_FLASH_PARTITION_FW_ID] ,
						alpx_dev->variant->flash_partitions.qty_for_fw_update);
		}
		
		if (ret)
			goto unregister_xdma;
	}
	
	if (alpx_dev->variant->model  == ALPX_VARIANT_DEAD) {
		dev_warn(alpx_dev->dev," !!!! DEAD card found : Flash in PROD mode and nothing else !!\n");
		return 0;
	}
	
	/* finalize identity */
	switch (alpx_dev->variant->model) {
		case ALPX_VARIANT_MODEL_ALP882:
		case ALPX_VARIANT_MODEL_ALP882_MIC:
		case ALPX_VARIANT_MODEL_ALP442:
		case ALPX_VARIANT_MODEL_ALP442_MIC:
			alpmultichan_finalize_identity(alpx_dev);
			/* Check FW compatibility */
			if 	(alpx_dev->identity.ver_fpga < ALPMC_SUPPORTED_BASE_FW_VERSION) {
					dev_warn(alpx_dev->dev, "UNSUPPORTED MultiChannels firmware version %d (supported started at %d), UPDATE required !!\n",
						alpx_dev->identity.ver_fpga,
						ALPMC_SUPPORTED_BASE_FW_VERSION);
				is_update_required = 1;
			}

			break;
		case ALPX_VARIANT_MODEL_ALP222:
		case ALPX_VARIANT_MODEL_ALP222_MIC:
			alpstereo_finalize_identity(alpx_dev);
			/* Check FW compatibility */
			if 	(alpx_dev->identity.ver_fpga < ALP222_SUPPORTED_BASE_FW_VERSION) {
					dev_warn(alpx_dev->dev, "UNSUPPORTED Stereo firmware version %d (supported started at %d), UPDATE required !!\n",
						alpx_dev->identity.ver_fpga,
						ALP222_SUPPORTED_BASE_FW_VERSION);
//				is_update_required = 1;
			}
			break;
		case ALPX_VARIANT_MODEL_ALPDANTE:
			alpdante_finalize_identity(alpx_dev);
			/* Check FW compatibility */
			if 	(alpx_dev->identity.ver_fpga < ALPDANTE_SUPPORTED_BASE_FW_VERSION) {
					dev_warn(alpx_dev->dev, "UNSUPPORTED AlpDANTE firmware version %d (supported started at %d), UPDATE required !!\n",
						alpx_dev->identity.ver_fpga,
						ALPDANTE_SUPPORTED_BASE_FW_VERSION);
				is_update_required = 1;
			}
			break;
		default:
			dev_warn(alpx_dev->dev, " identity not finalized for this model : %d\n", alpx_dev->variant->model);
	}
	
	
	/* Remount MTD with SERIAL number added only in USER mod, to clearly identify the cards */
	if (!with_board_in_prod_mode ) {
	
		//Build a special partition table to add the serial number (A COPY) !!
		struct mtd_partition fw_partitions_table[ALPX_FLASH_PARTITION_QTY];
		char part_names[ALPX_FLASH_PARTITION_QTY][128];
		unsigned int part_idx = 0;
	
		for (part_idx = 0 ; part_idx < alpx_dev->variant->flash_partitions.qty_for_fw_update ; ++part_idx) {
			
			fw_partitions_table[part_idx] = alpx_dev->variant->flash_partitions.partitions[ALPX_FLASH_PARTITION_FW_ID + part_idx];
			scnprintf(part_names[part_idx], PAGE_SIZE, "%s-%llu", 
					fw_partitions_table[part_idx].name, 
					alpx_dev->identity.serial_number);
			
			fw_partitions_table[part_idx].name = part_names[part_idx];
		}
		
		dev_dbg( alpx_dev->dev," Flash in USER mode: firmware partitionS with S/N appened.\n");
		/* Replace the current partitions */
		alpx_mtd_remove(alpx_dev);
		ret = alpx_mtd_probe(alpx_dev, 
					fw_partitions_table,
					alpx_dev->variant->flash_partitions.qty_for_fw_update);
	}
	
	if (ret){
		dev_err(alpx_dev->dev," Error %d when re-creating Firmware partition\n", ret);
		goto unregister_xdma;
	}
	
	//Stop here if update required.
	if (is_update_required == 1)
		return 0;
	
	/* Information */

	strcpy(card->driver, "Driver AlpX");
	
	//Check if replaced by parameters, default : use the variant names
	dev_dbg( alpx_dev->dev,"%s() : id[%d] = %s\n", __func__, 
		card_idx, 
		id[card_idx] == NULL ? "NULL" : id[card_idx]);
	
	if (id[card_idx])
		sprintf(card->shortname, id[card_idx]);
	else 
		sprintf(card->shortname, alpx_dev->variant->shortname);
	
	strcpy(card->longname, alpx_dev->variant->longname);
	strcpy(card->mixername, alpx_dev->variant->mixername);

	/* Pipes */

	spin_lock_init(&alpx_dev->config.lock);

	alpx_pipe_init(&alpx_dev->playback, true);
	alpx_pipe_init(&alpx_dev->capture, false);

	/* PCM */

	ret = snd_pcm_new(card, card->shortname, 0, 1, 1, &pcm);
	if (ret) {
		dev_err(dev," snd_pcm_new(card) => %d\n", ret);
		goto unregister_xdma;
	}

	pcm->private_data = alpx_dev;

	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &alpx_playback_ops);
	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &alpx_capture_ops);

	pcm->info_flags = 0;

	strcpy(pcm->name, card->longname);

	/* Controls */

	ret = alpx_controls_register(card);
	if (ret) {
		dev_err(dev," alpx_controls_register(card) => %d\n", ret);
		goto unregister_xdma;
	}
	
	/* Buffer */

#if KERNEL_VERSION(5, 5, 0) <= LINUX_VERSION_CODE
	snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, dev,
						alpx_dev->variant->playback_hw->buffer_bytes_max,
						alpx_dev->variant->playback_hw->buffer_bytes_max);
#else
	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, dev,
						alpx_dev->variant->playback_hw->buffer_bytes_max,
						alpx_dev->variant->playback_hw->buffer_bytes_max);
#endif
#if defined (ALPX_WITH_GPIO)
	/* GPIO */
	
	if (alpx_dev->variant->features & ALPX_VARIANT_FEATURE_GPIOS)
		ret = alpx_gpio_register(alpx_dev, card->shortname);

	if (ret) {
		dev_err(dev," alpx_gpio_register(alpx_dev) => %d\n", ret);
		goto unregister_xdma;
	}
#endif	
	/* Proc */
	ret = alpx_proc_probe(alpx_dev);
	if (ret) {
		dev_err(dev," alpx_proc_probe(alpx_dev) => %d\n", ret);
		goto unregister_xdma;
	}
	
	/* Card */

	ret = snd_card_register(card);
		if (ret){
			dev_err(dev," snd_card_register(card) => %d\n", ret);
		goto error_proc;
	}

	/* Sys FS files */
	/* Reg debug */
#ifdef WITH_REG_DEBUG
	dev_info(dev,"REG_DEBUG activated\n");
	alpx_dev->dbg_reg_offset = ALP222_MIN_REG_OFFSET;
	
	ret = device_create_file(dev, &dev_reg_offset);
	if (ret)
	{
		dev_err(dev," device_create_file(addr) => %d\n", ret);
	}
	else
	{
		dev_err(dev," device_create_file(addr) : Ok\n");
	}

	ret = device_create_file(dev, &dev_reg_value);
	if (ret)
	{
		dev_err(dev," device_create_file(value) => %d\n", ret);
	}
#endif

	/* Serial number file */
	ret = device_create_file(dev, &dev_serial_number);
	if (ret)
	{
		dev_err(dev," Serial Number device_create_file() => %d\n", ret);
	}
	
	/* FPGA version file */
	ret = device_create_file(dev, &dev_ver_fpga);
	if (ret)
	{
		dev_err(dev," FPGA Version device_create_file() => %d\n", ret);
	}
	
	/* MCU version file */
	ret = device_create_file(dev, &dev_ver_mcu);
	if (ret)
	{
		dev_err(dev," MCU Version device_create_file() => %d\n", ret);
	}

	/* DANTE card's name */
	if (alpx_dev->variant->model == ALPX_VARIANT_MODEL_ALPDANTE) {
		ret = device_create_file(dev, &dev_dante_card_name);
		if (ret)
		{
			dev_err(dev," DANTE card's name device_create_file() => %d\n", ret);
		}
	}

	/* Production area replication from USER to GOLDEN if needed*/
	if (((alpx_dev->variant->model == ALPX_VARIANT_MODEL_ALP222) ||
		(alpx_dev->variant->model == ALPX_VARIANT_MODEL_ALP222_MIC)) && 
		alpx_mtd_is_available(alpx_dev) && 
		!alpx_mtd_is_golden_prod_area_valid(alpx_dev)) {
		dev_info(alpx_dev->dev," Production area in GOLDEN must be INITIALIZED\n");
		ret = alpx_mtd_replicate_prod_area(alpx_dev);
		if (!ret) {
			dev_info(alpx_dev->dev," Production area in GOLDEN  is INITIALIZED\n");
		}
		else {
			dev_warn(alpx_dev->dev," Production area in GOLDEN  NOT CORRECTLY INITIALIZED !!\n");
		}
	}

	switch (alpx_dev->variant->model){
		case ALPX_VARIANT_MODEL_ALP222:
		case ALPX_VARIANT_MODEL_ALP222_MIC:
			alpstereo_print_identity(alpx_dev, card, "Created");
			break;
		case ALPX_VARIANT_MODEL_ALP882:
		case ALPX_VARIANT_MODEL_ALP882_MIC:
		case ALPX_VARIANT_MODEL_ALP442:
		case ALPX_VARIANT_MODEL_ALP442_MIC:
			alpmultichan_print_identity(alpx_dev, card, "Created");
			break;
		case ALPX_VARIANT_MODEL_MADI:
		case ALPX_VARIANT_MODEL_MADI_LOOPBACK:
			alpmadi_print_identity(alpx_dev, card, "Created");
			break;
			case ALPX_VARIANT_MODEL_ALPDANTE:
				alpdante_print_identity(alpx_dev, card, "Created");
				alpdante_card_setup(alpx_dev, card, dante_configured_fs, dante_loopback_enabled);
				break;
		default:
			dev_warn(alpx_dev->dev," !!! UNKNOW variant identity: %d !!!!\n", alpx_dev->variant->model);
	};
	
	card_idx++;

	return 0;
	
error_proc:
	dev_err(alpx_dev->dev," %s(): Error : 0x%x when creating proc entry\n", __func__, ret);
	alpx_proc_remove(alpx_dev);
unregister_xdma:
	alpx_xdma_unregister(pci_dev, alpx_dev->xdma_pdev);
disable_pci_dev:
	pci_disable_device(pci_dev);
error_card:
	dev_err(alpx_dev->dev," %s(): Error : 0x%x when creating the card\n", __func__, ret);
	snd_card_free(card);

	return ret;
}

static void alpx_remove(struct pci_dev *pci_dev)
{
	struct snd_card *card = pci_get_drvdata(pci_dev);
	struct alpx_device *alpx_dev = card->private_data;
	
	switch (alpx_dev->variant->model){
		case ALPX_VARIANT_MODEL_ALP222:
		case ALPX_VARIANT_MODEL_ALP222_MIC:
			alpstereo_print_identity(alpx_dev, card, "Deleted");
			break;
		case ALPX_VARIANT_MODEL_ALP882:
		case ALPX_VARIANT_MODEL_ALP882_MIC:
		case ALPX_VARIANT_MODEL_ALP442:
		case ALPX_VARIANT_MODEL_ALP442_MIC:
			if (card != NULL) {
				alpmultichan_print_identity(alpx_dev, card, "Deleted");
			}
			break;
		case ALPX_VARIANT_MODEL_MADI:
		case ALPX_VARIANT_MODEL_MADI_LOOPBACK:
			alpmadi_print_identity(alpx_dev, card, "Deleted");
			break;
		case ALPX_VARIANT_MODEL_ALPDANTE:
			alpdante_print_identity(alpx_dev, card, "Deleted");
			break;
		default:
			dev_warn(alpx_dev->dev," !!! UNKNOW variant identity: %d !!!!\n", alpx_dev->variant->model);
	}
	
	/* Reg debug */
#ifdef WITH_REG_DEBUG
	device_remove_file(alpx_dev->dev, &dev_reg_offset);
	device_remove_file(alpx_dev->dev, &dev_reg_value);
#endif
	device_remove_file(alpx_dev->dev, &dev_serial_number);
	device_remove_file(alpx_dev->dev, &dev_ver_fpga);
	device_remove_file(alpx_dev->dev, &dev_ver_mcu);

	if (alpx_dev->variant->model == ALPX_VARIANT_MODEL_ALPDANTE) {
		device_remove_file(alpx_dev->dev, &dev_dante_card_name);
	}

	alpx_mtd_remove(alpx_dev);
	alpx_proc_remove(alpx_dev);

	if (alpx_dev->xdma_pdev != NULL) {
		alpx_xdma_unregister(pci_dev, alpx_dev->xdma_pdev);
	}

	if (card != NULL) {
		snd_card_free(card);
	}

	pci_disable_device(alpx_dev->pci_dev);
}

static const struct pci_device_id alpx_pci_ids[] = {
	{ PCI_DEVICE(PCI_VENDOR_ID_DIGIGRAM, ALPX_PCI_ID_DEVICE) },
	{ 0 }
};

MODULE_DEVICE_TABLE(pci, alpx_pci_ids);

static struct pci_driver alpx_driver = {
	.name		= KBUILD_MODNAME,
	.id_table	= alpx_pci_ids,
	.probe		= alpx_probe,
	.remove		= alpx_remove,
};

module_pci_driver(alpx_driver);

MODULE_DESCRIPTION("AlpX audio cards driver");
MODULE_AUTHOR("Digigram Digital");
MODULE_VERSION(ALPX_MODULE_VERSION);
MODULE_LICENSE("GPL");