From patchwork Mon Sep 18 18:10:44 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ivan Orlov X-Patchwork-Id: 13390954 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 15813CD54A7 for ; Tue, 19 Sep 2023 08:13:54 +0000 (UTC) Received: from alsa1.perex.cz (alsa1.perex.cz [207.180.221.201]) (using TLSv1.2 with cipher ADH-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by alsa0.perex.cz (Postfix) with ESMTPS id 3F6D9DF1; Tue, 19 Sep 2023 10:13:02 +0200 (CEST) DKIM-Filter: OpenDKIM Filter v2.11.0 alsa0.perex.cz 3F6D9DF1 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=alsa-project.org; s=default; t=1695111232; bh=JDiYaApayy/gXeURdnS5WFZ6XqK1G9CaztBnvvuh5e8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=k6pvIQC09n+FBpWzWns9g8VZ5MqR9Q+zOli/qdo3YY/dUDmv2T+rFl+WJNBwiRMHy 6cRe1fVcnek0hBG6XWEI67bmvh3kXrYsIitbV8qOqyhSiY9IHP+xQ7/M368T9ubQmK bZJ08Pt+SUDxCT6lVbpiHioaqrr4H97hq43TiN6E= Received: by alsa1.perex.cz (Postfix, from userid 50401) id DC260F805C2; Tue, 19 Sep 2023 10:11:13 +0200 (CEST) Received: from mailman-core.alsa-project.org (mailman-core.alsa-project.org [10.254.200.10]) by alsa1.perex.cz (Postfix) with ESMTP id 3D40FF805C2; Tue, 19 Sep 2023 10:11:13 +0200 (CEST) Received: by alsa1.perex.cz (Postfix, from userid 50401) id 9A324F8025A; Mon, 18 Sep 2023 20:11:47 +0200 (CEST) Received: from mail-wm1-x32a.google.com (mail-wm1-x32a.google.com [IPv6:2a00:1450:4864:20::32a]) (using TLSv1.3 with cipher TLS_AES_128_GCM_SHA256 (128/128 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by alsa1.perex.cz (Postfix) with ESMTPS id 99A6DF800AA for ; Mon, 18 Sep 2023 20:11:39 +0200 (CEST) DKIM-Filter: OpenDKIM Filter v2.11.0 alsa1.perex.cz 99A6DF800AA Authentication-Results: alsa1.perex.cz; dkim=pass (2048-bit key, unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20230601 header.b=O7xi47Xf Received: by mail-wm1-x32a.google.com with SMTP id 5b1f17b1804b1-4046f7d49a9so14587815e9.1 for ; Mon, 18 Sep 2023 11:11:39 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1695060698; x=1695665498; darn=alsa-project.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=327BgaBWASTW1llZYnSOTFLRyf8B7+2qQcFgEaVMN4s=; b=O7xi47XfL9XRlVpxxsE26kuRjkYHl6IVd6FmP5/9TqtgKd4ij1j4VZMw5qFyaiAKgJ A35KsJ9A9bGaxSWKfTch8BlkgGqNYm0Lzx+NE16ocFoqg3yjwMepSov3sKW0rKJpIpqa sFajF6ASBNKh4BiKXgtrVDJzo8bjEHWeVA77y9d2azoOVEnB2xG+eo92nQROMtTbBo4v 7uGWh5NKAE5OBnvfWPE4ZX72Zq4UESuDx7ITRA8xm9pkwNnde2dgC4bhHEwsF3ArZSTv PuOYIzgf0ZDKH4iWtaXtdQsSv1WBssehPU2nFEZCvJoIxWFHh7hKvROdWrTG6zy83Heg tOXQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1695060698; x=1695665498; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=327BgaBWASTW1llZYnSOTFLRyf8B7+2qQcFgEaVMN4s=; b=CdA/uaP1ep1wOtrhQH1Mt9ssHiTD7qAm8eyv2YCuIhmOX+Fxf1wfL982eAVfabPtzg iXeg6DYeyKMErH5kLnz0DWkWIkPBFPBaD3+mg9DpLTeSrkEtwbsrAMP+GWVe76tZaTvi Zz6ovvJTFu26O0Rxx2XudT3/1Dbz+rVRElm+a/djj7X92g7gaEGGv3AkSYp9oo3hP4Vx O8A4NLgJ6Y9qlfL1jr1es3t+RhE5fl0a28pYuKgG8SxmKVtta8MJtrBXU9+bgHuc9Zp/ Udg9D+jiM62eVYVWhAeIztguxgcII2isMu75nfQ9z3YqrnAnztShSz4X0RQ0H1grPI9B EGoA== X-Gm-Message-State: AOJu0Yz+H2Kzu6a7K7uD72HhW2p6JZ1ggvtwYeQEE0aIE1N/dQd2ubjn fDg3yRRFbVdLBLGof0/HCOoH26fjQZI= X-Google-Smtp-Source: AGHT+IHb+TZKaXEEuBIWk9eHhUDsvh1Q+NpKN2KrtZEqm60eFzEaBzicrvscujohcRdMbeDv+mGn0g== X-Received: by 2002:a05:600c:500c:b0:401:b53e:6c3e with SMTP id n12-20020a05600c500c00b00401b53e6c3emr9545824wmr.1.1695060697938; Mon, 18 Sep 2023 11:11:37 -0700 (PDT) Received: from ivan-B550MH.domain.name ([178.160.241.68]) by smtp.gmail.com with ESMTPSA id x20-20020a05600c2a5400b003fe601a7d46sm15865769wme.45.2023.09.18.11.11.35 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 18 Sep 2023 11:11:37 -0700 (PDT) From: Ivan Orlov To: perex@perex.cz, tiwai@suse.com, corbet@lwn.net Cc: Ivan Orlov , alsa-devel@alsa-project.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, gregkh@linuxfoundation.org Subject: [PATCH v2 2/2] ALSA: Add new driver for Marian M2 sound card Date: Mon, 18 Sep 2023 22:10:44 +0400 Message-Id: <20230918181044.7257-2-ivan.orlov0322@gmail.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230918181044.7257-1-ivan.orlov0322@gmail.com> References: <20230918181044.7257-1-ivan.orlov0322@gmail.com> MIME-Version: 1.0 X-MailFrom: ivan.orlov0322@gmail.com X-Mailman-Rule-Hits: nonmember-moderation X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; header-match-alsa-devel.alsa-project.org-0; header-match-alsa-devel.alsa-project.org-1 Message-ID-Hash: KL6TPU3XHNQ6E6SX34WGNDK72NA62A2R X-Message-ID-Hash: KL6TPU3XHNQ6E6SX34WGNDK72NA62A2R X-Mailman-Approved-At: Tue, 19 Sep 2023 08:10:57 +0000 X-Mailman-Version: 3.3.8 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: The original driver was written by Florian Faber in 2012. This patch represents the updated version of the original driver. List of updates since the initial driver version: - Update deprecated API calls, so it could be sent upstream - Fix codestyle issues - Fix memory allocation issues - Add locking - Remove support for other models of Marian sound cards, which I was unable to test and which are not documented anywhere. Despite lacking of special equipment required for recording and playing MIDI/MADI, I was able to test the driver and fix some issues using the integrated and external loopbacks. Now the driver seems to work well. Signed-off-by: Ivan Orlov --- V1 -> V2: - Remove redundant documentation fix MAINTAINERS | 7 + sound/pci/Kconfig | 10 + sound/pci/Makefile | 2 + sound/pci/marianm2.c | 1785 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1804 insertions(+) create mode 100644 sound/pci/marianm2.c diff --git a/MAINTAINERS b/MAINTAINERS index 90f13281d297..1e04a6c25a55 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12621,6 +12621,13 @@ L: linux-mips@vger.kernel.org S: Maintained F: arch/mips/boot/dts/img/pistachio* +MARIAN SERAPH M2 SOUND CARD DRIVER +M: Ivan Orlov +L: alsa-devel@alsa-project.org (moderated for non-subscribers) +S: Maintained +F: Documentation/sound/cards/marian-m2.rst +F: sound/pci/marianm2.c + MARVELL 88E6XXX ETHERNET SWITCH FABRIC DRIVER M: Andrew Lunn L: netdev@vger.kernel.org diff --git a/sound/pci/Kconfig b/sound/pci/Kconfig index 787868c9e91b..e3dad79743e5 100644 --- a/sound/pci/Kconfig +++ b/sound/pci/Kconfig @@ -222,6 +222,16 @@ config SND_CMIPCI To compile this driver as a module, choose M here: the module will be called snd-cmipci. +config SND_MARIANM2 + tristate "MARIAN Seraph M2" + select SND_PCM + help + Say Y to include support for MARIAN Seraph M2 sound card + . + + To compile this driver as a module, choose M here: the module + will be called snd-marianm2 + config SND_OXYGEN_LIB tristate diff --git a/sound/pci/Makefile b/sound/pci/Makefile index 04cac7469139..4d2f52c98a74 100644 --- a/sound/pci/Makefile +++ b/sound/pci/Makefile @@ -22,6 +22,7 @@ snd-fm801-objs := fm801.o snd-intel8x0-objs := intel8x0.o snd-intel8x0m-objs := intel8x0m.o snd-maestro3-objs := maestro3.o +snd-marianm2-objs := marianm2.o snd-rme32-objs := rme32.o snd-rme96-objs := rme96.o snd-sis7019-objs := sis7019.o @@ -48,6 +49,7 @@ obj-$(CONFIG_SND_FM801) += snd-fm801.o obj-$(CONFIG_SND_INTEL8X0) += snd-intel8x0.o obj-$(CONFIG_SND_INTEL8X0M) += snd-intel8x0m.o obj-$(CONFIG_SND_MAESTRO3) += snd-maestro3.o +obj-$(CONFIG_SND_MARIANM2) += snd-marianm2.o obj-$(CONFIG_SND_RME32) += snd-rme32.o obj-$(CONFIG_SND_RME96) += snd-rme96.o obj-$(CONFIG_SND_SIS7019) += snd-sis7019.o diff --git a/sound/pci/marianm2.c b/sound/pci/marianm2.c new file mode 100644 index 000000000000..1ffa47cded3f --- /dev/null +++ b/sound/pci/marianm2.c @@ -0,0 +1,1785 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ALSA driver for MARIAN Seraph audio interfaces + * + * Copyright (c) 2012 Florian Faber , + * 2023 Ivan Orlov + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEBUG + +#define M2_CHANNELS_COUNT 128 + +#define M2_FRAME_SIZE (M2_CHANNELS_COUNT * 4) +#define SUBSTREAM_PERIOD_SIZE (2048 * M2_FRAME_SIZE) +#define SUBSTREAM_BUF_SIZE (2 * SUBSTREAM_PERIOD_SIZE) + +#define SERAPH_RD_IRQ_STATUS 0x00 +#define SERAPH_RD_HWPOINTER 0x8C + +#define SERAPH_WR_DMA_ADR 0x04 +#define SERAPH_WR_ENABLE_CAPTURE 0x08 +#define SERAPH_WR_ENABLE_PLAYBACK 0x0C +#define SERAPH_WR_DMA_BLOCKS 0x10 + +#define M2_DISABLE_PLAY_IRQ BIT(1) +#define M2_DISABLE_CAPT_IRQ BIT(2) +#define M2_ENABLE_LOOPBACK BIT(3) +#define SERAPH_WR_DMA_ENABLE 0x84 +#define SERAPH_WR_IE_ENABLE 0xAC + +#define PCI_VENDOR_ID_MARIAN 0x1382 +#define PCI_DEVICE_ID_MARIAN_SERAPH_M2 0x5021 + +#define RATE_SLOW 54000 +#define RATE_FAST 108000 + +#define SPEEDMODE_SLOW 1 +#define SPEEDMODE_FAST 2 + +#define MARIAN_PORTS_TYPE_INPUT 0 +#define MARIAN_PORTS_TYPE_OUTPUT 1 + +#define MARIAN_SPI_CLOCK_DIVIDER 0x74 + +#define ERR_DEAD_WRITE BIT(0) +#define ERR_DEAD_READ BIT(1) +#define ERR_DATA_LOST BIT(2) +#define ERR_PAGE_CONF BIT(3) +#define ERR_INT_PLAY BIT(10) +#define ERR_INT_REC BIT(13) + +#define STATUS_ST_READY BIT(4) +#define STATUS_INT_PLAY BIT(8) +#define STATUS_INT_PPLAY BIT(9) +#define STATUS_INT_REC BIT(11) +#define STATUS_INT_PREC BIT(12) +#define STATUS_INT_PREP BIT(14) +#define WCLOCK_NEW_VAL BIT(31) +#define SPI_ALL_READY BIT(31) + +#define M2_CLOCK_SRC_CNT 4 +#define M2_CLOCK_SRC_DCO 1 +#define M2_CLOCK_SRC_SYNCBUS 2 +#define M2_CLOCK_SRC_MADI1 4 +#define M2_CLOCK_SRC_MADI2 5 + +#define M2_SYNC_STATE_CNT 3 +#define M2_CHNL_MODE_CNT 2 +#define M2_FRAME_MODE_CNT 2 + +#define M2_INP1_SYNC_CTL_ID 0 +#define M2_INP1_CM_CTL_ID 0 +#define M2_INP1_FM_CTL_ID 0 +#define M2_INP1_FREQ_CTL_ID 4 +#define M2_OUT1_CM_CTL_ID 0 +#define M2_OUT1_FM_CTL_ID 0 +#define M2_INP2_SYNC_CTL_ID 1 +#define M2_INP2_CM_CTL_ID 1 +#define M2_INP2_FM_CTL_ID 1 +#define M2_INP2_FREQ_CTL_ID 5 +#define M2_OUT2_CM_CTL_ID 1 +#define M2_OUT2_FM_CTL_ID 1 + +// MADI FPGA register 0x40 +// Use internal (=0) or external PLL (=1) +#define M2_PLL 2 + +// MADI FPGA register 0x41 +// Enable both MADI transmitters (=1) +#define M2_TX_ENABLE 0 +// Use int (=0) or 32bit IEEE float (=1) +#define M2_INT_FLOAT 4 +// Big endian (=0), little endian (=1) +#define M2_ENDIANNESS 5 +// MSB first (=0), LSB first (=1) +#define M2_BIT_ORDER 6 + +// MADI FPGA register 0x42 +// Send 56ch (=0) or 64ch (=1) MADI frames +#define M2_PORT1_MODE 0 +// Send 48kHz (=0) or 96kHz (=1) MADI frames +#define M2_PORT1_FRAME 1 +// Send 56ch (=0) or 64ch (=1) MADI frames +#define M2_PORT2_MODE 2 +// Send 48kHz (=0) or 96kHz (=1) MADI frames +#define M2_PORT2_FRAME 3 + +struct marian_card_descriptor; +struct marian_card; + +struct marian_card_descriptor { + char *name; + char *port_names; + unsigned int speedmode_max; + unsigned int ch_in; + unsigned int ch_out; + unsigned int midi_in; + unsigned int midi_out; + unsigned int serial_in; + unsigned int serial_out; + unsigned int wck_in; + unsigned int wck_out; + + unsigned int dma_bufsize; + + void (*hw_constraints_func)(struct marian_card *marian, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params); + /* custom function to set up ALSA controls */ + void (*create_controls)(struct marian_card *marian); + /* init is called after probing the card */ + int (*init_card)(struct marian_card *marian); + void (*free_card)(struct marian_card *marian); + /* prepare is called when ALSA is opening the card */ + void (*prepare)(struct marian_card *marian); + void (*set_speedmode)(struct marian_card *marian, unsigned int speedmode); + void (*proc_status)(struct marian_card *marian, struct snd_info_buffer *buffer); + void (*proc_ports)(struct marian_card *marian, struct snd_info_buffer *buffer, + unsigned int type); + + struct snd_pcm_hardware info_playback; + struct snd_pcm_hardware info_capture; +}; + +struct marian_card { + struct marian_card_descriptor *desc; + + struct snd_pcm_substream *playback_substream; + struct snd_pcm_substream *capture_substream; + + struct snd_card *card; + struct snd_pcm *pcm; + struct snd_dma_buffer dmabuf; + + struct snd_dma_buffer playback_buf; + struct snd_dma_buffer capture_buf; + + struct pci_dev *pci; + unsigned long port; + void __iomem *iobase; + int irq; + + unsigned int idx; + + /* hardware registers lock */ + spinlock_t reglock; + + /* spinlock for SPI communication */ + spinlock_t spi_lock; + + /* mutex for frequency measurement */ + struct mutex freq_mutex; + + /* Enables or disables hardware loopback */ + int loopback; + + unsigned int stream_open; + + /* speed mode: 1, 2, 4 times FS */ + unsigned int speedmode; + + /* 0..15, meaning depending on the card type */ + unsigned int clock_source; + + /* Frequency of the internal oscillator (Hertz) */ + unsigned int dco; + /* [0..1) part of the internal oscillator frequency (milli Hertz) */ + unsigned int dco_millis; + + /* [-200 .. 200] Two semitone 'musical' adjustment */ + int detune; + + /* WCK input termination on (1)/off (0) */ + unsigned int wck_term; + + /* WCK output source */ + unsigned int wck_output; + + void *card_specific; +}; + +enum CLOCK_SOURCE { + CLOCK_SRC_INTERNAL = 0, + CLOCK_SRC_SYNCBUS = 1, + CLOCK_SRC_INP1 = 2, + CLOCK_SRC_INP2 = 3, + CLOCK_SRC_INP3 = 4, +}; + +enum m2_num_mode { + M2_NUM_MODE_INT = 0, + M2_NUM_MODE_FLOAT = 1, +}; + +enum m2_endianness_mode { + M2_BE = 0, + M2_LE = 1, +}; + +struct m2_specific { + u8 shadow_40; + u8 shadow_41; + u8 shadow_42; + u8 frame; +}; + +static const struct pci_device_id snd_marian_ids[] = { + {PCI_DEVICE(PCI_VENDOR_ID_MARIAN, PCI_DEVICE_ID_MARIAN_SERAPH_M2), 0, 0, 6}, + { } +}; + +MODULE_DEVICE_TABLE(pci, snd_marian_ids); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; // Index 0-MAX +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; // ID for this card + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for MARIAN PCI soundcard"); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for MARIAN PCI soundcard"); + +static void snd_marian_card_free(struct snd_card *card) +{ + struct marian_card *marian = card->private_data; + + if (!marian) + return; + + snd_dma_free_pages(&marian->dmabuf); + + if (marian->irq >= 0) + free_irq(marian->irq, (void *)marian); + + if (marian->iobase) + pci_iounmap(marian->pci, marian->iobase); + + if (marian->port) + pci_release_regions(marian->pci); + + pci_disable_device(marian->pci); +} + +static void marian_proc_status_generic(struct marian_card *marian, struct snd_info_buffer *buffer) +{ + snd_iprintf(buffer, "*** Card registers\n"); + snd_iprintf(buffer, "RD 0x064: %08x (SPI bits written)\n", readl(marian->iobase + 0x64)); + snd_iprintf(buffer, "RD 0x068: %08x (SPI bits read)\n", readl(marian->iobase + 0x68)); + snd_iprintf(buffer, "RD 0x070: %08x (SPI bits status)\n", readl(marian->iobase + 0x70)); + snd_iprintf(buffer, "RD 0x088: %08x (Super clock measurement)\n", + readl(marian->iobase + 0x88)); + snd_iprintf(buffer, "RD 0x08C: %08x (HW Pointer)\n", + readl(marian->iobase + SERAPH_RD_HWPOINTER)); + snd_iprintf(buffer, "RD 0x094: %08x (Word clock measurement)\n", + readl(marian->iobase + 0x88)); + snd_iprintf(buffer, "RD 0x0F8: %08x (Extension board)\n", + readl(marian->iobase + 0xF8)); + snd_iprintf(buffer, "RD 0x244: %08x (DMA debug)\n", + readl(marian->iobase + 0x244)); + + snd_iprintf(buffer, "\n*** Card status\n"); + snd_iprintf(buffer, "Firmware build: %08x\n", readl(marian->iobase + 0xFC)); + snd_iprintf(buffer, "Speed mode : %uFS (1..%u)\n", + marian->speedmode, marian->desc->speedmode_max); + snd_iprintf(buffer, "Clock master : %s\n", (marian->clock_source == 1) ? "yes" : "no"); + snd_iprintf(buffer, "DCO frequency: %d.%d Hz\n", marian->dco, marian->dco_millis); + snd_iprintf(buffer, "DCO detune : %d Cent\n", marian->detune); +} + +static void snd_marian_proc_status(struct snd_info_entry *entry, struct snd_info_buffer *buffer) +{ + struct marian_card *marian = entry->private_data; + + if (marian->desc->proc_status) + marian->desc->proc_status(marian, buffer); + else + marian_proc_status_generic(marian, buffer); +} + +/* + * Default port name function, outputs the static string + * port_names of the card descriptor regardless of current + * speed mode and whether input or output ports are requested. + */ +static void marian_proc_ports_generic(struct marian_card *marian, struct snd_info_buffer *buffer, + unsigned int type) +{ + snd_iprintf(buffer, marian->desc->port_names); +} + +static void snd_marian_proc_ports_in(struct snd_info_entry *entry, struct snd_info_buffer *buffer) +{ + struct marian_card *marian = entry->private_data; + + snd_iprintf(buffer, "# generated by MARIAN Seraph driver\n"); + if (marian->desc->proc_ports) + marian->desc->proc_ports(marian, buffer, MARIAN_PORTS_TYPE_INPUT); + else + marian_proc_ports_generic(marian, buffer, MARIAN_PORTS_TYPE_INPUT); +} + +static void snd_marian_proc_ports_out(struct snd_info_entry *entry, struct snd_info_buffer *buffer) +{ + struct marian_card *marian = entry->private_data; + + snd_iprintf(buffer, "# generated by MARIAN Seraph driver\n"); + if (marian->desc->proc_ports) + marian->desc->proc_ports(marian, buffer, MARIAN_PORTS_TYPE_OUTPUT); + else + marian_proc_ports_generic(marian, buffer, MARIAN_PORTS_TYPE_OUTPUT); +} + +static irqreturn_t snd_marian_interrupt(int irq, void *dev_id) +{ + struct marian_card *marian = (struct marian_card *)dev_id; + unsigned int irq_status; + + irq_status = readl(marian->iobase + SERAPH_RD_IRQ_STATUS); + + if (irq_status & 0x00004800) { + if (marian->playback_substream) + snd_pcm_period_elapsed(marian->playback_substream); + + if (marian->capture_substream) + snd_pcm_period_elapsed(marian->capture_substream); + + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static int snd_marian_playback_open(struct snd_pcm_substream *substream) +{ + struct marian_card *marian = substream->private_data; + + substream->runtime->hw = marian->desc->info_playback; + + marian->playback_substream = substream; + + snd_pcm_set_sync(substream); + + return 0; +} + +static int snd_marian_capture_open(struct snd_pcm_substream *substream) +{ + struct marian_card *marian = substream->private_data; + + substream->runtime->hw = marian->desc->info_capture; + + marian->capture_substream = substream; + + snd_pcm_set_sync(substream); + + return 0; +} + +static int snd_marian_capture_release(struct snd_pcm_substream *substream) +{ + struct marian_card *marian = snd_pcm_substream_chip(substream); + + marian->capture_substream = NULL; + + return 0; +} + +static int snd_marian_playback_release(struct snd_pcm_substream *substream) +{ + struct marian_card *marian = snd_pcm_substream_chip(substream); + + marian->playback_substream = NULL; + + return 0; +} + +static void marian_generic_set_dco(struct marian_card *marian, unsigned int freq, + unsigned int millis) +{ + u64 val, v2; + s64 detune; + + val = (freq * 1000 + millis) * marian->speedmode; + val <<= 36; + + if (marian->detune != 0) { + /* + * DCO detune active + * this calculation takes a bit of a shortcut + * - should be implemented using a logarithmic scale + */ + detune = marian->detune * 100; + v2 = val; + div_u64(v2, 138564); + detune *= v2; + val += detune; + } + + val = div_u64(val, 80000000); + val = div_u64(val, 1000); + + writel((u32)val, marian->iobase + 0x88); + + marian->dco = freq; + marian->dco_millis = millis; +} + +static void marian_generic_set_speedmode(struct marian_card *marian, unsigned int speedmode) +{ + if (speedmode > marian->desc->speedmode_max) + return; + + switch (speedmode) { + case SPEEDMODE_SLOW: + writel(0x03, marian->iobase + 0x80); + writel(0x00, marian->iobase + 0x8C); // for 48kHz in 1FS mode + marian->speedmode = SPEEDMODE_SLOW; + break; + case SPEEDMODE_FAST: + writel(0x03, marian->iobase + 0x80); + writel(0x01, marian->iobase + 0x8C); // for 96kHz in 2FS mode + marian->speedmode = SPEEDMODE_FAST; + break; + } + + marian_generic_set_dco(marian, marian->dco, marian->dco_millis); +} + +static int snd_marian_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct marian_card *marian = snd_pcm_substream_chip(substream); + unsigned int speedmode; + int buffer_frames; + + buffer_frames = SUBSTREAM_BUF_SIZE / M2_FRAME_SIZE; + + if (params_rate(params) < RATE_SLOW) + speedmode = SPEEDMODE_SLOW; + else if (params_rate(params) < RATE_FAST) + speedmode = SPEEDMODE_FAST; + + if (speedmode > marian->desc->speedmode_max) { + dev_err(marian->card->dev, + "Requested rate (%u Hz) higher than card's maximum\n", + params_rate(params)); + _snd_pcm_hw_param_setempty(params, SNDRV_PCM_HW_PARAM_RATE); + return -EBUSY; + } + + spin_lock(&marian->reglock); + if (marian->desc->set_speedmode) + marian->desc->set_speedmode(marian, speedmode); + else + marian_generic_set_speedmode(marian, speedmode); + + marian->detune = 0; + + marian_generic_set_dco(marian, params_rate(params), 0); + spin_unlock(&marian->reglock); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + snd_pcm_set_runtime_buffer(substream, &marian->playback_buf); + else + snd_pcm_set_runtime_buffer(substream, &marian->capture_buf); + + // apply optional card specific hw constraints + if (marian->desc->hw_constraints_func) + marian->desc->hw_constraints_func(marian, substream, params); + + return 0; +} + +static int snd_marian_prepare(struct snd_pcm_substream *substream) +{ + struct marian_card *marian = snd_pcm_substream_chip(substream); + + if (marian->desc->prepare) + marian->desc->prepare(marian); + + return 0; +} + +static void marian_silence(struct marian_card *marian) +{ + memset(marian->dmabuf.area, 0, marian->dmabuf.bytes); +} + +// atomic by default, no need for locking here +static int snd_marian_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct marian_card *marian = snd_pcm_substream_chip(substream); + int irq_flags; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + irq_flags = M2_DISABLE_PLAY_IRQ; + if (marian->loopback) + irq_flags |= M2_ENABLE_LOOPBACK; + + marian_silence(marian); + writel(0x3, marian->iobase + SERAPH_WR_DMA_ENABLE); + writel(irq_flags, marian->iobase + SERAPH_WR_IE_ENABLE); + break; + case SNDRV_PCM_TRIGGER_STOP: + irq_flags = M2_DISABLE_PLAY_IRQ | M2_DISABLE_CAPT_IRQ; + writel(irq_flags, marian->iobase + SERAPH_WR_IE_ENABLE); + writel(0x0, marian->iobase + SERAPH_WR_DMA_ENABLE); + marian_silence(marian); + + // unarm channels to inhibit playback from the FPGA's internal buffer + writel(0, marian->iobase + 0x08); + writel(0, marian->iobase + 0x0C); + writel(0, marian->iobase + 0x20); + writel(0, marian->iobase + 0x24); + writel(0, marian->iobase + 0x28); + writel(0, marian->iobase + 0x2C); + writel(0, marian->iobase + 0x30); + writel(0, marian->iobase + 0x34); + writel(0, marian->iobase + 0x38); + writel(0, marian->iobase + 0x3C); + break; + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t snd_marian_hw_pointer(struct snd_pcm_substream *substream) +{ + struct marian_card *marian = snd_pcm_substream_chip(substream); + + return readl(marian->iobase + SERAPH_RD_HWPOINTER); +} + +static int snd_marian_mmap(struct snd_pcm_substream *substream, struct vm_area_struct *vma) +{ + struct marian_card *marian = snd_pcm_substream_chip(substream); + + if (remap_pfn_range(vma, vma->vm_start, substream->runtime->dma_addr >> PAGE_SHIFT, + vma->vm_end - vma->vm_start, vma->vm_page_prot) < 0) { + dev_err(marian->card->dev, "remap_pfn_range failed\n"); + return -EIO; + } + + return 0; +} + +static const struct snd_pcm_ops snd_marian_playback_ops = { + .open = snd_marian_playback_open, + .close = snd_marian_playback_release, + .hw_params = snd_marian_hw_params, + .prepare = snd_marian_prepare, + .trigger = snd_marian_trigger, + .pointer = snd_marian_hw_pointer, + .mmap = snd_marian_mmap, +}; + +static const struct snd_pcm_ops snd_marian_capture_ops = { + .open = snd_marian_capture_open, + .close = snd_marian_capture_release, + .hw_params = snd_marian_hw_params, + .prepare = snd_marian_prepare, + .trigger = snd_marian_trigger, + .pointer = snd_marian_hw_pointer, + .mmap = snd_marian_mmap, +}; + +static void marian_generic_set_clock_source(struct marian_card *marian, u8 source) +{ + spin_lock(&marian->reglock); + writel(source, marian->iobase + 0x90); + marian->clock_source = source; + spin_unlock(&marian->reglock); +} + +static void marian_generic_init(struct marian_card *marian) +{ + if (!marian->desc->set_speedmode) + marian->desc->set_speedmode = marian_generic_set_speedmode; + + // reset DMA engine + writel(0x00000000, marian->iobase); + + // disable play interrupt + writel(M2_DISABLE_PLAY_IRQ, marian->iobase + SERAPH_WR_IE_ENABLE); + + // init clock mode + marian_generic_set_speedmode(marian, SPEEDMODE_SLOW); + + // init internal clock and set it as clock source + marian_generic_set_clock_source(marian, 1); + + // init SPI clock divider + writel(0x1F, marian->iobase + MARIAN_SPI_CLOCK_DIVIDER); +} + +static void construct_playback_buffer(struct marian_card *marian) +{ + marian->playback_buf = marian->dmabuf; + marian->playback_buf.area += SUBSTREAM_BUF_SIZE; + marian->playback_buf.addr += SUBSTREAM_BUF_SIZE; + marian->playback_buf.bytes = SUBSTREAM_BUF_SIZE; +} + +static void construct_capture_buffer(struct marian_card *marian) +{ + marian->capture_buf = marian->dmabuf; + marian->capture_buf.bytes = SUBSTREAM_BUF_SIZE; +} + +static int snd_marian_create(struct snd_card *card, struct pci_dev *pci, + struct marian_card_descriptor *desc, unsigned int idx) +{ + struct snd_info_entry *entry; + struct marian_card *marian = card->private_data; + int err; + unsigned int len; + + marian->desc = desc; + marian->card = card; + marian->pcm = NULL; + marian->pci = pci; + marian->port = 0; + marian->iobase = NULL; + marian->irq = -1; + marian->idx = idx; + spin_lock_init(&marian->reglock); + spin_lock_init(&marian->spi_lock); + mutex_init(&marian->freq_mutex); + + err = pci_enable_device(pci); + if (err < 0) + return err; + + if (dma_set_mask_and_coherent(&pci->dev, DMA_BIT_MASK(32))) { + dev_err(&pci->dev, "Unable to set DMA mask\n"); + return err; + } + + pci_set_master(pci); + + err = pci_request_regions(pci, "marian"); + if (err < 0) + return err; + + marian->port = pci_resource_start(pci, 0); + len = pci_resource_len(pci, 0); + marian->iobase = pci_iomap(pci, 0, 0); + if (!marian->iobase) { + dev_err(&pci->dev, "unable to grab region 0x%lx-0x%lx\n", + marian->port, marian->port + len - 1); + return -EBUSY; + } + + if (request_irq(pci->irq, snd_marian_interrupt, IRQF_SHARED, "marian", marian)) { + dev_err(&pci->dev, "unable to grab IRQ %d\n", pci->irq); + return -EBUSY; + } + marian->irq = pci->irq; + + card->private_free = snd_marian_card_free; + + strscpy(card->driver, "MARIAN FPGA", sizeof(card->driver)); + strscpy(card->shortname, marian->desc->name, sizeof(card->shortname)); + sprintf(card->longname, "%s PCIe audio at 0x%lx, irq %d", + card->shortname, marian->port, marian->irq); + + snd_card_set_dev(card, &pci->dev); + + err = snd_pcm_new(card, desc->name, 0, 1, 1, &marian->pcm); + if (err < 0) + return err; + marian->pcm->private_data = marian; + snd_pcm_set_ops(marian->pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_marian_playback_ops); + snd_pcm_set_ops(marian->pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_marian_capture_ops); + + len = PAGE_ALIGN(desc->dma_bufsize); + err = snd_dma_alloc_pages(SNDRV_DMA_TYPE_CONTINUOUS, &pci->dev, + desc->dma_bufsize, &marian->dmabuf); + if (err < 0) { + dev_err(card->dev, "Could not allocate %d Bytes (%d)\n", len, err); + + snd_dma_free_pages(&marian->dmabuf); + return err; + } + + writel((u32)marian->dmabuf.addr, + marian->iobase + SERAPH_WR_DMA_ADR); + + // Set 'block' count to buffer_frames/16 to set channel 'buffers' count (16 samples each) + writel(SUBSTREAM_BUF_SIZE / M2_FRAME_SIZE / 16, marian->iobase + SERAPH_WR_DMA_BLOCKS); + + construct_capture_buffer(marian); + construct_playback_buffer(marian); + + if (!snd_card_proc_new(card, "status", &entry)) + snd_info_set_text_ops(entry, marian, snd_marian_proc_status); + if (!snd_card_proc_new(card, "ports.in", &entry)) + snd_info_set_text_ops(entry, marian, snd_marian_proc_ports_in); + if (!snd_card_proc_new(card, "ports.out", &entry)) + snd_info_set_text_ops(entry, marian, snd_marian_proc_ports_out); + + if (marian->desc->init_card) + marian->desc->init_card(marian); + else + marian_generic_init(marian); + + if (marian->desc->create_controls) + marian->desc->create_controls(marian); + + return snd_card_register(card); +} + +/* + * Measure the frequency of a clock source. + * The measurement is triggered and the FPGA's ready + * signal polled (normally takes up to 2ms). The measurement + * has only a certainty of 10-20Hz, this function rounds it up + * to the nearest 10Hz step (in 1FS). + */ +static unsigned int marian_measure_freq(struct marian_card *marian, unsigned int source) +{ + mutex_lock(&marian->freq_mutex); + u32 val; + int tries = 5; + + writel(source & 0x7, marian->iobase + 0xC8); + + while (tries > 0) { + val = readl(marian->iobase + 0x94); + if (val & WCLOCK_NEW_VAL) + break; + + usleep_range(1000, 1200); + tries--; + } + + mutex_unlock(&marian->freq_mutex); + + if (tries > 0) + return (((1280000000 / ((val & 0x3FFFF) + 1)) + 5 * marian->speedmode) + / (10 * marian->speedmode)) * 10 * marian->speedmode; + + return 0; +} + +static int marian_generic_frequency_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 27000; + uinfo->value.integer.max = 207000; + uinfo->value.integer.step = 1; + return 0; +} + +static int marian_generic_frequency_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct marian_card *marian = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = marian_measure_freq(marian, kcontrol->private_value); + return 0; +} + +static int marian_generic_frequency_create(struct marian_card *marian, char *label, u32 idx) +{ + struct snd_kcontrol_new c = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = label, + .private_value = idx, + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = marian_generic_frequency_info, + .get = marian_generic_frequency_get + }; + + return snd_ctl_add(marian->card, snd_ctl_new1(&c, marian)); +} + +static int marian_generic_dco_int_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct marian_card *marian = snd_kcontrol_chip(kcontrol); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + if (marian->speedmode == SPEEDMODE_SLOW) { + uinfo->value.integer.min = 32000; + uinfo->value.integer.max = 54000; + } + uinfo->value.integer.step = 1; + return 0; +} + +static int marian_generic_dco_int_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct marian_card *marian = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = marian->dco; + + return 0; +} + +static int marian_generic_dco_int_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct marian_card *marian = snd_kcontrol_chip(kcontrol); + + spin_lock(&marian->reglock); + marian_generic_set_dco(marian, ucontrol->value.integer.value[0], marian->dco_millis); + spin_unlock(&marian->reglock); + + return 0; +} + +static int marian_generic_dco_int_create(struct marian_card *marian, char *label) +{ + struct snd_kcontrol_new c = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = label, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = marian_generic_dco_int_info, + .get = marian_generic_dco_int_get, + .put = marian_generic_dco_int_put + }; + + return snd_ctl_add(marian->card, snd_ctl_new1(&c, marian)); +} + +static int marian_generic_dco_millis_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 999; + uinfo->value.integer.step = 1; + + return 0; +} + +static int marian_generic_dco_millis_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct marian_card *marian = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = marian->dco_millis; + + return 0; +} + +static int marian_generic_dco_millis_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct marian_card *marian = snd_kcontrol_chip(kcontrol); + + spin_lock(&marian->reglock); + marian_generic_set_dco(marian, marian->dco, ucontrol->value.integer.value[0]); + spin_unlock(&marian->reglock); + + return 0; +} + +static int marian_generic_dco_millis_create(struct marian_card *marian, char *label) +{ + struct snd_kcontrol_new c = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = label, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = marian_generic_dco_millis_info, + .get = marian_generic_dco_millis_get, + .put = marian_generic_dco_millis_put + }; + + return snd_ctl_add(marian->card, snd_ctl_new1(&c, marian)); +} + +static int marian_generic_dco_detune_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = -200; + uinfo->value.integer.max = 200; + uinfo->value.integer.step = 1; + return 0; +} + +static int marian_generic_dco_detune_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct marian_card *marian = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = marian->detune; + + return 0; +} + +static int marian_generic_dco_detune_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct marian_card *marian = snd_kcontrol_chip(kcontrol); + + spin_lock(&marian->reglock); + marian->detune = ucontrol->value.integer.value[0]; + + marian_generic_set_dco(marian, marian->dco, marian->dco_millis); + spin_unlock(&marian->reglock); + + return 0; +} + +static int marian_generic_dco_detune_create(struct marian_card *marian, char *label) +{ + struct snd_kcontrol_new c = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = label, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = marian_generic_dco_detune_info, + .get = marian_generic_dco_detune_get, + .put = marian_generic_dco_detune_put + }; + + return snd_ctl_add(marian->card, snd_ctl_new1(&c, marian)); +} + +static int marian_control_pcm_loopback_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + + return 0; +} + +static int marian_control_pcm_loopback_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct marian_card *marian = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = marian->loopback; + + return 0; +} + +static int marian_control_pcm_loopback_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct marian_card *marian = snd_kcontrol_chip(kcontrol); + + marian->loopback = ucontrol->value.integer.value[0]; + + return 0; +} +static int marian_control_pcm_loopback_create(struct marian_card *marian) +{ + struct snd_kcontrol_new c = { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "Loopback", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = marian_control_pcm_loopback_info, + .get = marian_control_pcm_loopback_get, + .put = marian_control_pcm_loopback_put, + }; + + return snd_ctl_add(marian->card, snd_ctl_new1(&c, marian)); +} + +static void marian_generic_dco_create(struct marian_card *marian) +{ + marian_generic_dco_int_create(marian, "DCO Freq (Hz)"); + marian_generic_dco_millis_create(marian, "DCO Freq (millis)"); + marian_generic_dco_detune_create(marian, "DCO Detune (cent)"); +} + +static int marian_generic_speedmode_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct marian_card *marian = snd_kcontrol_chip(kcontrol); + static const char * const texts[] = { "1FS", "2FS", "4FS" }; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + switch (marian->desc->speedmode_max) { + case SPEEDMODE_SLOW: + uinfo->value.enumerated.items = 1; + break; + case SPEEDMODE_FAST: + uinfo->value.enumerated.items = 2; + break; + } + if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items) + uinfo->value.enumerated.item = uinfo->value.enumerated.items - 1; + strscpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item], + sizeof(uinfo->value.enumerated.name)); + return 0; +} + +static int marian_generic_speedmode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct marian_card *marian = snd_kcontrol_chip(kcontrol); + + ucontrol->value.enumerated.item[0] = marian->speedmode - 1; + + return 0; +} + +static int marian_generic_speedmode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct marian_card *marian = snd_kcontrol_chip(kcontrol); + + spin_lock(&marian->reglock); + marian->desc->set_speedmode(marian, ucontrol->value.enumerated.item[0] + 1); + spin_unlock(&marian->reglock); + + return 0; +} + +static int marian_generic_speedmode_create(struct marian_card *marian) +{ + struct snd_kcontrol_new c = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Speed Mode", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = marian_generic_speedmode_info, + .get = marian_generic_speedmode_get, + .put = marian_generic_speedmode_put, + }; + + return snd_ctl_add(marian->card, snd_ctl_new1(&c, marian)); +} + +static int spi_wait_for_ar(struct marian_card *marian) +{ + int tries = 10; + + while (tries > 0) { + if (readl(marian->iobase + 0x70) == SPI_ALL_READY) + break; + udelay(100); + tries--; + } + if (tries == 0) + return -EIO; + return 0; +} + +static int marian_spi_transfer(struct marian_card *marian, uint16_t cs, uint16_t bits_write, + u8 *data_write, uint16_t bits_read, u8 *data_read) +{ + u32 buf = 0; + unsigned int i; + int err = 0; + + spin_lock(&marian->spi_lock); + + if (spi_wait_for_ar(marian) < 0) + writel(0x1234, marian->iobase + 0x70); // Resetting SPI bus + + writel(cs, marian->iobase + 0x60); // chip select register + writel(bits_write, marian->iobase + 0x64); // number of bits to write + writel(bits_read, marian->iobase + 0x68); // number of bits to read + + if (bits_write <= 32) { + if (bits_write <= 8) + buf = data_write[0] << (32 - bits_write); + else if (bits_write <= 16) + buf = data_write[0] << 24 | data_write[1] << (32 - bits_write); + + writel(buf, marian->iobase + 0x6C); // write data left aligned + } + if (bits_read > 0 && bits_read <= 32) { + if (spi_wait_for_ar(marian) < 0) { + dev_dbg(marian->card->dev, + "Bus didn't signal AR\n"); + err = -EIO; + goto unlock_exit; + } + + buf = readl(marian->iobase + MARIAN_SPI_CLOCK_DIVIDER); + + buf <<= 32 - bits_read; + i = 0; + + while (bits_read > 0) { + data_read[i++] = (buf >> 24) & 0xFF; + buf <<= 8; + bits_read -= 8; + } + } + +unlock_exit: + spin_unlock(&marian->spi_lock); + return err; +} + +static u8 marian_m2_spi_read(struct marian_card *marian, u8 adr) +{ + u8 buf_in; + + adr = adr & 0x7F; + + if (marian_spi_transfer(marian, 0x02, 8, &adr, 8, &buf_in) == 0) + return buf_in; + + return 0; +} + +static int marian_m2_spi_write(struct marian_card *marian, u8 adr, u8 val) +{ + u8 buf_out[2]; + + buf_out[0] = 0x80 | adr; + buf_out[1] = val; + + return marian_spi_transfer(marian, 0x02, 16, (u8 *)&buf_out, 0, NULL); +} + +static int marian_m2_sync_state_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[] = { "No Signal", "Lock", "Sync" }; + + return snd_ctl_enum_info(uinfo, 1, ARRAY_SIZE(texts), texts); +} + +static int marian_m2_sync_state_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct marian_card *marian = snd_kcontrol_chip(kcontrol); + u8 v = marian_m2_spi_read(marian, 0x00); + + v = (v >> (kcontrol->private_value * 2)) & 0x3; + if (v == 3) + v--; + + ucontrol->value.enumerated.item[0] = v; + + return 0; +} + +static int marian_m2_sync_state_create(struct marian_card *marian, char *label, u32 idx) +{ + struct snd_kcontrol_new c = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = label, + .private_value = idx, + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = marian_m2_sync_state_info, + .get = marian_m2_sync_state_get + }; + + return snd_ctl_add(marian->card, snd_ctl_new1(&c, marian)); +} + +static int marian_m2_channel_mode_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[] = { "56ch", "64ch" }; + + return snd_ctl_enum_info(uinfo, 1, ARRAY_SIZE(texts), texts); +} + +static int marian_m2_input_channel_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct marian_card *marian = snd_kcontrol_chip(kcontrol); + u8 v = marian_m2_spi_read(marian, 0x01); + + v = (v >> (kcontrol->private_value * 2)) & 0x1; + ucontrol->value.enumerated.item[0] = v; + + return 0; +} + +static int marian_m2_input_channel_mode_create(struct marian_card *marian, + char *label, u32 idx) +{ + struct snd_kcontrol_new c = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = label, + .private_value = idx, + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = marian_m2_channel_mode_info, + .get = marian_m2_input_channel_mode_get + }; + + return snd_ctl_add(marian->card, snd_ctl_new1(&c, marian)); +} + +static int marian_m2_frame_mode_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[] = { "48kHz", "96kHz" }; + + return snd_ctl_enum_info(uinfo, 1, ARRAY_SIZE(texts), texts); +} + +static int marian_m2_input_frame_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct marian_card *marian = snd_kcontrol_chip(kcontrol); + u8 v = marian_m2_spi_read(marian, 0x01); + + v = (v >> ((kcontrol->private_value * 2) + 1)) & 0x1; + ucontrol->value.enumerated.item[0] = v; + + return 0; +} + +static int marian_m2_input_frame_mode_create(struct marian_card *marian, char *label, u32 idx) +{ + struct snd_kcontrol_new c = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = label, + .private_value = idx, + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = marian_m2_frame_mode_info, + .get = marian_m2_input_frame_mode_get + }; + + return snd_ctl_add(marian->card, snd_ctl_new1(&c, marian)); +} + +static u8 marian_m2_get_port_mode(struct marian_card *marian, unsigned int port) +{ + struct m2_specific *spec = marian->card_specific; + + if (port) + return (spec->shadow_42 >> M2_PORT2_MODE) & 1; + else + return (spec->shadow_42 >> M2_PORT1_MODE) & 1; +} + +static int marian_m2_output_channel_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct marian_card *marian = snd_kcontrol_chip(kcontrol); + + ucontrol->value.enumerated.item[0] = marian_m2_get_port_mode(marian, + kcontrol->private_value); + + return 0; +} + +static void marian_m2_set_port_mode(struct marian_card *marian, unsigned int port, u8 state) +{ + struct m2_specific *spec = (struct m2_specific *)marian->card_specific; + + if (port) + spec->shadow_42 = (spec->shadow_42 & ~(1 << M2_PORT2_MODE)) + | state << M2_PORT2_MODE; + else + spec->shadow_42 = (spec->shadow_42 & ~(1 << M2_PORT1_MODE)) + | state << M2_PORT1_MODE; + + marian_m2_spi_write(marian, 0x42, spec->shadow_42); +} + +static int marian_m2_output_channel_mode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct marian_card *marian = snd_kcontrol_chip(kcontrol); + + spin_lock(&marian->reglock); + marian_m2_set_port_mode(marian, kcontrol->private_value, + ucontrol->value.enumerated.item[0]); + spin_unlock(&marian->reglock); + + return 0; +} + +static int marian_m2_output_channel_mode_create(struct marian_card *marian, + char *label, u32 idx) +{ + struct snd_kcontrol_new c = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = label, + .private_value = idx, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = marian_m2_channel_mode_info, + .get = marian_m2_output_channel_mode_get, + .put = marian_m2_output_channel_mode_put + }; + + return snd_ctl_add(marian->card, snd_ctl_new1(&c, marian)); +} + +static int marian_m2_output_frame_mode_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + return snd_ctl_boolean_mono_info(kcontrol, uinfo); +} + +static int marian_m2_output_frame_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct marian_card *marian = snd_kcontrol_chip(kcontrol); + struct m2_specific *spec = (struct m2_specific *)marian->card_specific; + + ucontrol->value.enumerated.item[0] = (spec->frame >> kcontrol->private_value) & 1; + + return 0; +} + +static void marian_m2_write_port_frame(struct marian_card *marian) +{ + struct m2_specific *spec = (struct m2_specific *)marian->card_specific; + + spec->shadow_42 = spec->shadow_42 & ~((1 << M2_PORT1_FRAME) | (1 << M2_PORT2_FRAME)); + + if (marian->speedmode == 2) { + // If we are in FS2, set 96kHz mode where enabled + if (spec->frame & 1) + spec->shadow_42 |= 1 << M2_PORT1_FRAME; + if (spec->frame & 2) + spec->shadow_42 |= 1 << M2_PORT2_FRAME; + } + + marian_m2_spi_write(marian, 0x42, spec->shadow_42); +} + +static void marian_m2_set_port_frame(struct marian_card *marian, unsigned int port, u8 state) +{ + struct m2_specific *spec = (struct m2_specific *)marian->card_specific; + + spec->frame = (spec->frame & ~(1 << port)) | (state << port); + + marian_m2_write_port_frame(marian); +} + +static int marian_m2_output_frame_mode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct marian_card *marian = snd_kcontrol_chip(kcontrol); + + spin_lock(&marian->reglock); + marian_m2_set_port_frame(marian, kcontrol->private_value, + ucontrol->value.enumerated.item[0]); + spin_unlock(&marian->reglock); + + return 0; +} + +static int marian_m2_output_frame_mode_create(struct marian_card *marian, char *label, u32 idx) +{ + struct snd_kcontrol_new c = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = label, + .private_value = idx, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = marian_m2_output_frame_mode_info, + .get = marian_m2_output_frame_mode_get, + .put = marian_m2_output_frame_mode_put, + }; + + return snd_ctl_add(marian->card, snd_ctl_new1(&c, marian)); +} + +static int marian_m2_clock_source_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[] = {"Internal", "Sync Bus", + "Input Port 1", "Input Port 2"}; + + return snd_ctl_enum_info(uinfo, 1, ARRAY_SIZE(texts), texts); +} + +static int marian_m2_clock_source_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct marian_card *marian = snd_kcontrol_chip(kcontrol); + + switch (marian->clock_source) { + case M2_CLOCK_SRC_DCO: + ucontrol->value.enumerated.item[0] = CLOCK_SRC_INTERNAL; + break; + case M2_CLOCK_SRC_SYNCBUS: + ucontrol->value.enumerated.item[0] = CLOCK_SRC_SYNCBUS; + break; + case M2_CLOCK_SRC_MADI1: + ucontrol->value.enumerated.item[0] = CLOCK_SRC_INP1; + break; + case M2_CLOCK_SRC_MADI2: + ucontrol->value.enumerated.item[0] = CLOCK_SRC_INP2; + break; + default: + dev_dbg(marian->card->dev, + "Illegal value for clock_source! (%d)\n", + marian->clock_source); + return -EINVAL; + } + + return 0; +} + +static int marian_m2_clock_source_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct marian_card *marian = snd_kcontrol_chip(kcontrol); + + switch (ucontrol->value.enumerated.item[0]) { + case CLOCK_SRC_INTERNAL: + marian_generic_set_clock_source(marian, M2_CLOCK_SRC_DCO); + break; + case CLOCK_SRC_SYNCBUS: + marian_generic_set_clock_source(marian, M2_CLOCK_SRC_SYNCBUS); + break; + case CLOCK_SRC_INP1: + marian_generic_set_clock_source(marian, M2_CLOCK_SRC_MADI1); + break; + case CLOCK_SRC_INP2: + marian_generic_set_clock_source(marian, M2_CLOCK_SRC_MADI2); + break; + } + + return 0; +} + +static int marian_m2_clock_source_create(struct marian_card *marian) +{ + struct snd_kcontrol_new c = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Clock Source", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = marian_m2_clock_source_info, + .get = marian_m2_clock_source_get, + .put = marian_m2_clock_source_put, + }; + + return snd_ctl_add(marian->card, snd_ctl_new1(&c, marian)); +} + +/* + * Controls: + * + * RO: + * - Input 1 sync state (no signal, lock, sync) + * - Input 1 channel mode (56/64ch) + * - Input 1 frame mode (48/96kHz) + * - Input 1 frequency + * - Input 2 sync state (no signal, lock, sync) + * - Input 2 channel mode (56/64ch) + * - Input 2 frame mode (48/96kHz) + * - Input 2 frequency + * + * RW: + * - Output 1 channel mode (56/64ch) + * - Output 1 frame mode (48/96kHz) + * - Output 2 channel mode (56/64ch) + * - Output 2 frame mode (48/96kHz) + * - Word clock source (Port 1, Port 2, Internal, Sync port, WCK input) + * - Speed mode (1, 2, 4FS) + * - DCO frequency (1 Hertz) + * - DCO frequency (1/1000th) + */ +static void marian_m2_create_controls(struct marian_card *marian) +{ + marian_m2_sync_state_create(marian, "Input 1 Sync", M2_INP1_SYNC_CTL_ID); + marian_m2_sync_state_create(marian, "Input 2 Sync", M2_INP2_SYNC_CTL_ID); + marian_m2_input_channel_mode_create(marian, "Input 1 Channel Mode", + M2_INP1_CM_CTL_ID); + marian_m2_input_channel_mode_create(marian, "Input 2 Channel Mode", + M2_INP2_CM_CTL_ID); + marian_m2_input_frame_mode_create(marian, "Input 1 Frame Mode", + M2_INP1_FM_CTL_ID); + marian_m2_input_frame_mode_create(marian, "Input 2 Frame Mode", + M2_INP2_FM_CTL_ID); + marian_generic_frequency_create(marian, "Input 1 Frequency", + M2_INP1_FREQ_CTL_ID); + marian_generic_frequency_create(marian, "Input 2 Frequency", + M2_INP2_FREQ_CTL_ID); + marian_m2_output_channel_mode_create(marian, "Output 1 Channel Mode", + M2_OUT1_CM_CTL_ID); + marian_m2_output_channel_mode_create(marian, "Output 2 Channel Mode", + M2_OUT2_CM_CTL_ID); + marian_m2_output_frame_mode_create(marian, "Output 1 96kHz Frame", + M2_OUT1_FM_CTL_ID); + marian_m2_output_frame_mode_create(marian, "Output 2 96kHz Frame", + M2_OUT2_FM_CTL_ID); + marian_m2_clock_source_create(marian); + marian_generic_speedmode_create(marian); + marian_generic_dco_create(marian); + marian_control_pcm_loopback_create(marian); +} + +static void marian_m2_set_float(struct marian_card *marian, enum m2_num_mode state) +{ + struct m2_specific *spec = (struct m2_specific *)marian->card_specific; + + spec->shadow_41 = (spec->shadow_41 & ~(1 << M2_INT_FLOAT)) | state << M2_INT_FLOAT; + marian_m2_spi_write(marian, 0x41, spec->shadow_41); +} + +static void marian_m2_set_endianness(struct marian_card *marian, enum m2_endianness_mode state) +{ + struct m2_specific *spec = (struct m2_specific *)marian->card_specific; + + spec->shadow_41 = (spec->shadow_41 & ~(1 << M2_ENDIANNESS)) | state << M2_ENDIANNESS; + marian_m2_spi_write(marian, 0x41, spec->shadow_41); +} + +static void marian_m2_set_speedmode(struct marian_card *marian, unsigned int speedmode) +{ + marian_generic_set_speedmode(marian, speedmode); + marian_m2_write_port_frame(marian); +} + +static int marian_m2_init(struct marian_card *marian) +{ + struct m2_specific *spec; + + marian_generic_init(marian); + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (!spec) { + dev_dbg(marian->card->dev, + "Cannot allocate card specific structure\n"); + return -ENOMEM; + } + + spec->shadow_40 = 0x00; + spec->shadow_41 = (1 << M2_TX_ENABLE); + spec->shadow_42 = (1 << M2_PORT1_MODE) | (1 << M2_PORT2_MODE); + + marian_m2_spi_write(marian, 0x40, spec->shadow_40); + marian_m2_spi_write(marian, 0x41, spec->shadow_41); + marian_m2_spi_write(marian, 0x42, spec->shadow_42); + + marian->card_specific = spec; + return 0; +} + +static void marian_m2_free(struct marian_card *marian) +{ + kfree(marian->card_specific); +} + +static void marian_m2_prepare(struct marian_card *marian) +{ + u32 mask = 0xFFFFFFFF; + + spin_lock(&marian->reglock); + writel(mask, marian->iobase + 0x20); + writel(mask, marian->iobase + 0x24); + writel(mask, marian->iobase + 0x28); + writel(mask, marian->iobase + 0x2C); + writel(mask, marian->iobase + 0x30); + writel(mask, marian->iobase + 0x34); + writel(mask, marian->iobase + 0x38); + writel(mask, marian->iobase + 0x3C); + spin_unlock(&marian->reglock); +} + +static void marian_m2_proc_status(struct marian_card *marian, struct snd_info_buffer *buffer) +{ + struct m2_specific *spec = (struct m2_specific *)marian->card_specific; + u8 v1, v2; + + marian_proc_status_generic(marian, buffer); + + snd_iprintf(buffer, "\n*** MADI FPGA registers\n"); + snd_iprintf(buffer, "M2 MADI 00h: %02x\n", marian_m2_spi_read(marian, 0x00)); + snd_iprintf(buffer, "M2 MADI 01h: %02x\n", marian_m2_spi_read(marian, 0x01)); + snd_iprintf(buffer, "M2 MADI 02h: %02x\n", marian_m2_spi_read(marian, 0x02)); + snd_iprintf(buffer, "M2 MADI 40h: %02x\n", spec->shadow_40); + snd_iprintf(buffer, "M2 MADI 41h: %02x\n", spec->shadow_41); + snd_iprintf(buffer, "M2 MADI 42h: %02x\n", spec->shadow_42); + + snd_iprintf(buffer, "\n*** MADI FPGA status\n"); + snd_iprintf(buffer, "MADI FPGA firmware: 0x%02x\n", marian_m2_spi_read(marian, 0x02)); + + snd_iprintf(buffer, "Clock source: "); + switch (marian->clock_source) { + case 1: + snd_iprintf(buffer, "Internal DCO\n"); + break; + case 2: + snd_iprintf(buffer, "Sync bus\n"); + break; + case 4: + snd_iprintf(buffer, "MADI Input 1\n"); + break; + case 5: + snd_iprintf(buffer, "MADI Input 2\n"); + break; + default: + snd_iprintf(buffer, "UNKNOWN\n"); + break; + } + + snd_iprintf(buffer, "Sample format: %s, %s Endian, %s first\n", + (spec->shadow_41 & (1 << M2_INT_FLOAT)) ? "Float" : "Integer", + (spec->shadow_41 & (1 << M2_ENDIANNESS)) ? "Little" : "Big", + (spec->shadow_41 & (1 << M2_BIT_ORDER)) ? "LSB" : "MSB"); + + v1 = marian_m2_spi_read(marian, 0x00); + v2 = marian_m2_spi_read(marian, 0x01); + + snd_iprintf(buffer, "MADI port 1 input: "); + if (!(v1 & 0x03)) + snd_iprintf(buffer, "No signal\n"); + else + snd_iprintf(buffer, "%s, %dch, %dkHz frame, %u Hz\n", + (v1 & 0x02) ? "sync" : "lock", (v2 & 0x01) ? 64 : 56, + (v2 & 0x02) ? 96 : 48, marian_measure_freq(marian, 4)); + + snd_iprintf(buffer, "MADI port 2 input: "); + if (!(v1 & 0x0C)) + snd_iprintf(buffer, "No signal\n"); + else + snd_iprintf(buffer, "%s, %dch, %dkHz frame, %u Hz\n", + (v1 & 0x08) ? "sync" : "lock", + (v2 & 0x04) ? 64 : 56, (v2 & 0x08) ? 96 : 48, + marian_measure_freq(marian, 5)); +} + +static void marian_m2_proc_ports(struct marian_card *marian, + struct snd_info_buffer *buffer, unsigned int type) +{ + int i; + + for (i = 0; i < M2_CHANNELS_COUNT; i++) + snd_iprintf(buffer, "%d=MADI p%dch%02d\n", i + 1, i / 64 + 1, i % 64 + 1); +} + +static void marian_m2_constraints(struct marian_card *marian, struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + spin_lock(&marian->reglock); + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_FLOAT_BE: + marian_m2_set_float(marian, M2_NUM_MODE_FLOAT); + marian_m2_set_endianness(marian, M2_BE); + break; + case SNDRV_PCM_FORMAT_FLOAT_LE: + marian_m2_set_float(marian, M2_NUM_MODE_FLOAT); + marian_m2_set_endianness(marian, M2_LE); + break; + case SNDRV_PCM_FORMAT_S32_BE: + marian_m2_set_float(marian, M2_NUM_MODE_INT); + marian_m2_set_endianness(marian, M2_BE); + break; + case SNDRV_PCM_FORMAT_S32_LE: + marian_m2_set_float(marian, M2_NUM_MODE_INT); + marian_m2_set_endianness(marian, M2_LE); + break; + } + spin_unlock(&marian->reglock); +} + +static struct marian_card_descriptor descriptor = { + .name = "Seraph M2", + .speedmode_max = 2, + .ch_in = M2_CHANNELS_COUNT, + .ch_out = M2_CHANNELS_COUNT, + .dma_bufsize = 2 * SUBSTREAM_BUF_SIZE, + .hw_constraints_func = marian_m2_constraints, + .create_controls = marian_m2_create_controls, + .init_card = marian_m2_init, + .free_card = marian_m2_free, + .prepare = marian_m2_prepare, + .set_speedmode = marian_m2_set_speedmode, + .proc_status = marian_m2_proc_status, + .proc_ports = marian_m2_proc_ports, + .info_playback = { + .info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_NONINTERLEAVED + | SNDRV_PCM_INFO_JOINT_DUPLEX | SNDRV_PCM_INFO_SYNC_START, + .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S32_BE + | SNDRV_PCM_FMTBIT_FLOAT_LE | SNDRV_PCM_FMTBIT_FLOAT_BE, + .rates = (SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_44100 + | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 + | SNDRV_PCM_RATE_96000), + .rate_min = 28000, + .rate_max = RATE_FAST, + .channels_min = M2_CHANNELS_COUNT, + .channels_max = M2_CHANNELS_COUNT, + .buffer_bytes_max = SUBSTREAM_BUF_SIZE, + .period_bytes_min = SUBSTREAM_PERIOD_SIZE, + .period_bytes_max = SUBSTREAM_PERIOD_SIZE, + .periods_min = 2, + .periods_max = 2 + }, + .info_capture = { + .info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_NONINTERLEAVED + | SNDRV_PCM_INFO_SYNC_START | SNDRV_PCM_INFO_JOINT_DUPLEX, + .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S32_BE + | SNDRV_PCM_FMTBIT_FLOAT_LE | SNDRV_PCM_FMTBIT_FLOAT_BE, + .rates = (SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_44100 + | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 + | SNDRV_PCM_RATE_96000), + .rate_min = 28000, + .rate_max = RATE_FAST, + .channels_min = M2_CHANNELS_COUNT, + .channels_max = M2_CHANNELS_COUNT, + .buffer_bytes_max = SUBSTREAM_BUF_SIZE, + .period_bytes_min = SUBSTREAM_PERIOD_SIZE, + .period_bytes_max = SUBSTREAM_PERIOD_SIZE, + .periods_min = 2, + .periods_max = 2 + } +}; + +static int snd_marian_m2_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) +{ + static unsigned int dev; + struct snd_card *card; + int err; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + + err = snd_card_new(&pci->dev, index[dev], id[dev], + THIS_MODULE, sizeof(struct marian_card), &card); + if (err < 0) + return err; + + err = snd_marian_create(card, pci, &descriptor, dev); + if (err < 0) { + snd_card_free(card); + return err; + } + + pci_set_drvdata(pci, card); + + dev++; + + return 0; +} + +static void snd_marian_m2_remove(struct pci_dev *pci) +{ + snd_card_free(pci_get_drvdata(pci)); + pci_set_drvdata(pci, NULL); +} + +static struct pci_driver marian_driver = { + .name = "MARIAN", + .id_table = snd_marian_ids, + .probe = snd_marian_m2_probe, + .remove = snd_marian_m2_remove, +}; + +module_pci_driver(marian_driver); + +MODULE_AUTHOR("Florin Faber "); +MODULE_DESCRIPTION("MARIAN Seraph series"); +MODULE_LICENSE("GPL");