summaryrefslogtreecommitdiff
path: root/snd-alpx/snd-alpx/alpx_core.c
diff options
context:
space:
mode:
Diffstat (limited to 'snd-alpx/snd-alpx/alpx_core.c')
-rw-r--r--snd-alpx/snd-alpx/alpx_core.c901
1 files changed, 901 insertions, 0 deletions
diff --git a/snd-alpx/snd-alpx/alpx_core.c b/snd-alpx/snd-alpx/alpx_core.c
new file mode 100644
index 0000000..0fb4d82
--- /dev/null
+++ b/snd-alpx/snd-alpx/alpx_core.c
@@ -0,0 +1,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");