From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-wr2-f12.google.com (mail-wr2-f12.google.com [74.125.225.76]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 0031D448CE9 for ; Wed, 30 Sep 2026 07:07:30 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.225.76 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790752062; cv=none; b=CuQfGDcNVTzKmPET5Q9JjRj15Q3MWBCYI8uw3SZbgk9dFNdZAu65GY6wUzGnZ0A9riRghLcTCJUS2KJ7+9rR8G81bmWXv8CM72qb/EEGXMJCOv3hGaVBtB8A8cuNGe8r3Yb1OqU84fyngoHnd+BXSOBFhjK/AT+XQoPRzFrcSc8= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790752062; c=relaxed/simple; bh=OvEfRxqBiuXZ7fj2OQXWfJzPtAVIy4eq2mJb2W+I5xM=; h=From:To:Subject:Date:Message-ID:MIME-Version; b=O+QltBdhus/3GT8Xs4o+JIbk++YFBtxx0GMx5+1fyzbx1GMiVctJaYs/y6nH1GQK4+HAcCRUjfADoREc1zRd8hWOnb9RzFNVIWD+SsDeB+Za28GQApIqTj1lGOVipSO/Cs+6XUtZ4sIDEuEu5TGdL964llw++/SBChBMB1SDE0Q= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=FVuHc8gT; arc=none smtp.client-ip=74.125.225.76 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="FVuHc8gT" Received: by mail-wr2-f12.google.com with SMTP id ffacd0b85a97d-482f635552aso3866640f8f.2 for ; Wed, 30 Sep 2026 00:07:30 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1790752046; x=1791356846; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:message-id:date:subject:to :from:from:to:cc:subject:date:message-id:reply-to:content-type; bh=UWw5SxeIesDb8i4hVk7AR6PIMjO+M5CQOFiQNVuzOHw=; b=FVuHc8gT6eeOFZKkzf/RD9mOFuHjT6cxcKCKz2qxt5PtsDLBmTbl7CqrxwXm1B1Wtl 9BazziBb7frU8K1ajdZvijbRaRTj3NkNpQlgE4lLz94mppTzlgXTzJWDlhxRbNBJdMgc Z2SCrrjec5ULjYC1s+T7vNdN/6k5u9JQvuFdSzXmJku3u3u01mDaIgm6yDJGwOCHwG0V z1aj7QjUng3lHA5Z9p/v3Mj7jCBbcO9jR99bo25Jjpp60T8paguGtx+P9MNJUNJ+mmmw C3+Xpj0q4r62BVoBi3D+8xEkUFs1i+ZBkWgXH2MyHLtg+jMkDH3AC+4fcCWaxNfo5lvx oVWA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1790752046; x=1791356846; h=content-transfer-encoding:mime-version:message-id:date:subject:to :from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to:content-type; bh=UWw5SxeIesDb8i4hVk7AR6PIMjO+M5CQOFiQNVuzOHw=; b=N4CzFx+/3EXUVypRf7haI3de1GVYigMA+zhfVAoJg0E9xcAPp7HpqitvF2IHlcXLKJ WPhLARcQJoJi/4MLNKEFFtxo5+2C/ktCYRVhdcSyQX4YNTej8K3F0iLCRgVnjIGji7ds 71lxAI3K0AqC2Xo/A56GMBSJx5H6nZUuhSpl/FPOKpgDI3zv41M4YMCh5FRFCfkQaN3a b2WTPa9mUMl++hSalqYUJTocYLvVKS7MkQbauKnjoZYI4SGOE9SqpR8kaSAhrGzEw+9W 64Tb1f/Fh73vWRuS52AFHe3Z0SRMl5ar79WgEIQChNJ1VChi0230zG4ZFnjaSw3KKkNt Y5Tg== X-Forwarded-Encrypted: i=1; AKwUvBx6yc8vmijmnCRh5c8hqvsSoSwer1RbuqWPi4mLP2pyxGf8A0oU5Ir7qGaDMm8pJUqexGBHHHLyvcVLYPY=@vger.kernel.org X-Gm-Message-State: AFq9FYIdzPsc6HDhq099ap8HZpm2FRfzo7mjP1qMx/QZXW3n4WnfxWZ6 vL038PeLYVR+yH+ihIqvqkN2WlWxgBP2/beUa8PbUQVdyu7hZi/Y89rh X-Gm-Gg: AYBFou0DVTdufwp07Wma3ET/PeoH6qV2J7bz3dnOavMBXr32LS/ULyAYpZGtRNOqaen fwhqRpjCl4zsNf7hPxXOOga0Rgb27dkCa8mjaElucWx2XB66RjFdC83r5/q2HebgqTzpw1g9u3N ReO31d8jW3wha/dpvyNttFVqZ61t7Zt42WmBvOCHAdTUsBvqxYPMfvFWTAlLAgGacMKJ5WKAO8F KOn84M36rrJuIe1vM/wLkpv1TRk4ZMqcLlsGYoOxypCEN9qo4a0WiuheHSL5YSQu2iej404yBZY W65op0u2jz1AnI3h6sqEtfjyHx0U6M1lQxXfj7zeMtpqhS3ESi8onPO/Pp3o4Ni3AjthS44MOsC w9tOre6PqcItMmtM7ufRPaKq5v9cdHQQNyBNtGbtstPjGw32OEiZFbuAkw78qztUIwgz8AZu2yW NL8pXg8GvFc/Zh47QQ13Zt/ngwXgcdf9mFbTKFpC599SDzuKyJFjfX6/pv16CiFqzD4Rbt+06Xv p5LBO5jH2Vhc0610VNEsnPuB4AHDPzwrq8Wpqwl9kVKMUBm4pXW7qbnCefP/F7x X-Received: by 2002:a5d:5887:0:b0:488:7f5f:7caf with SMTP id ffacd0b85a97d-48b024d5864mr1100766f8f.23.1790752046141; Wed, 30 Sep 2026 00:07:26 -0700 (PDT) Received: from Ansuel-XPS24.localdomain (host-82-57-191-234.retail.telecomitalia.it. [82.57.191.234]) by smtp.googlemail.com with ESMTPSA id ffacd0b85a97d-48b029be7e4sm1211461f8f.12.2026.09.30.00.07.24 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 30 Sep 2026 00:07:25 -0700 (PDT) From: Christian Marangi To: Liam Girdwood , Mark Brown , Jaroslav Kysela , Takashi Iwai , Philipp Zabel , Christian Marangi , linux-kernel@vger.kernel.org, linux-sound@vger.kernel.org Subject: [RFC PATCH v2] sound: airoha: add support for AN7581 PCM driver Date: Wed, 30 Sep 2026 09:07:15 +0200 Message-ID: <20260930070717.47487-1-ansuelsmth@gmail.com> X-Mailer: git-send-email 2.55.0 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit The Airoha AN7581 SoC have alternative Sound Card that expose PCM OPs dedicated for VoIP application. Signed-off-by: Christian Marangi --- This is posted as RFC waiting for Airoha to verify correct functionality with ZSI or SLIC Voip chip. Some register/compatible related stuff will be dropped in the final version. Driver has been verified by enabling internal loopback mode. Changes v2: - Rentirely rework IRQ handling - Fix problem for timeslow configuration - Better support duplex mode - Better handle descriptor tracking - Move to Soc directory - General code fixup and cleanup sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/airoha/Kconfig | 10 + sound/soc/airoha/Makefile | 3 + sound/soc/airoha/an7581-pcm.c | 926 ++++++++++++++++++++++++++++++++++ 5 files changed, 941 insertions(+) create mode 100644 sound/soc/airoha/Kconfig create mode 100644 sound/soc/airoha/Makefile create mode 100644 sound/soc/airoha/an7581-pcm.c diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index edfdcbf734fe..27fc13e11b77 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -103,6 +103,7 @@ config SND_SOC_USB # All the supported SoCs source "sound/soc/adi/Kconfig" +source "sound/soc/airoha/Kconfig" source "sound/soc/amd/Kconfig" source "sound/soc/apple/Kconfig" source "sound/soc/atmel/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 21d8406767fc..ea581930bb3d 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -46,6 +46,7 @@ obj-$(CONFIG_SND_SOC) += codecs/ obj-$(CONFIG_SND_SOC) += generic/ obj-$(CONFIG_SND_SOC) += apple/ obj-$(CONFIG_SND_SOC) += adi/ +obj-$(CONFIG_SND_SOC) += airoha/ obj-$(CONFIG_SND_SOC) += amd/ obj-$(CONFIG_SND_SOC) += atmel/ obj-$(CONFIG_SND_SOC) += au1x/ diff --git a/sound/soc/airoha/Kconfig b/sound/soc/airoha/Kconfig new file mode 100644 index 000000000000..20c9211430c9 --- /dev/null +++ b/sound/soc/airoha/Kconfig @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config SND_SOC_AN7581_PCM + tristate "PCM support for Airoha AN7581 chip" + depends on ARCH_AIROHA || COMPILE_TEST + help + This adds PCM driver for Airoha AN7581 boards + that can be used with other codecs. + Select Y if you have such device. + If unsure select "N". diff --git a/sound/soc/airoha/Makefile b/sound/soc/airoha/Makefile new file mode 100644 index 000000000000..2522fe705d9a --- /dev/null +++ b/sound/soc/airoha/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_SND_SOC_AN7581_PCM) += an7581-pcm.o diff --git a/sound/soc/airoha/an7581-pcm.c b/sound/soc/airoha/an7581-pcm.c new file mode 100644 index 000000000000..11b27ec57d42 --- /dev/null +++ b/sound/soc/airoha/an7581-pcm.c @@ -0,0 +1,926 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Airoha ALSA SoC PCM platform driver for AN7581 + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define AN7581_PCM_PICR 0x0 +#define AN7581_PCM_CFG_VALID BIT(26) +#define AN7581_PCM_LPBK_ENABLE BIT(25) +#define AN7581_PCM_SOFT_RESET BIT(24) +#define AN7581_PCM_FRAME_COUNT GENMASK(22, 18) +#define AN7581_PCM_BIT_ORDER BIT(16) +#define AN7581_PCM_ONE_BIT_DELAY BIT(12) +#define AN7581_PCM_FS_EDGE BIT(10) +#define AN7581_PCM_FS_LEN GENMASK(9, 8) +#define AN7581_PCM_FS_LEN_8 FIELD_PREP_CONST(AN7581_PCM_FS_LEN, 0x2) +#define AN7581_PCM_FS_LEN_16 FIELD_PREP_CONST(AN7581_PCM_FS_LEN, 0x3) +#define AN7581_PCM_SAMPLE_CLK BIT(4) +#define AN7581_PCM_SAMPLE_CLK_8KHZ FIELD_PREP_CONST(AN7581_PCM_SAMPLE_CLK, 0x0) +#define AN7581_PCM_SAMPLE_CLK_16KHZ FIELD_PREP_CONST(AN7581_PCM_SAMPLE_CLK, 0x1) +#define AN7581_PCM_BIT_CLK GENMASK(3, 1) +#define AN7581_PCM_BIT_CLK_256KHZ FIELD_PREP_CONST(AN7581_PCM_BIT_CLK, 0x0) +#define AN7581_PCM_BIT_CLK_512KHZ FIELD_PREP_CONST(AN7581_PCM_BIT_CLK, 0x1) +#define AN7581_PCM_BIT_CLK_1024KHZ FIELD_PREP_CONST(AN7581_PCM_BIT_CLK, 0x2) +#define AN7581_PCM_BIT_CLK_2048HZ FIELD_PREP_CONST(AN7581_PCM_BIT_CLK, 0x3) +#define AN7581_PCM_BIT_CLK_4096KHZ FIELD_PREP_CONST(AN7581_PCM_BIT_CLK, 0x4) +#define AN7581_PCM_BIT_CLK_8192KHZ FIELD_PREP_CONST(AN7581_PCM_BIT_CLK, 0x5) +#define AN7581_PCM_BIT_MSTSLV_MODE BIT(0) +#define AN7581_PCM_BIT_MSTSLV_MODE_MASTER FIELD_PREP_CONST(AN7581_PCM_BIT_MSTSLV_MODE, 0x0) +#define AN7581_PCM_BIT_MSTSLV_MODE_SLAVE FIELD_PREP_CONST(AN7581_PCM_BIT_MSTSLV_MODE, 0x1) +/* Same bitmap for all TX timeslot reg */ +#define AN7581_PCM_PTTSCR0 0x4 +#define AN7581_PCM_TXTS1_BW BIT(28) +#define AN7581_PCM_TXTS1_BW_8BIT FIELD_PREP_CONST(AN7581_PCM_TXTS1_BW, 0x0) +#define AN7581_PCM_TXTS1_BW_16BIT FIELD_PREP_CONST(AN7581_PCM_TXTS1_BW, 0x1) +#define AN7581_PCM_TXTS1_START GENMASK(25, 16) +#define AN7581_PCM_TXTS0_BW BIT(12) +#define AN7581_PCM_TXTS0_BW_8BIT FIELD_PREP_CONST(AN7581_PCM_TXTS0_BW, 0x0) +#define AN7581_PCM_TXTS0_BW_16BIT FIELD_PREP_CONST(AN7581_PCM_TXTS0_BW, 0x1) +#define AN7581_PCM_TXTS0_START GENMASK(9, 0) +#define AN7581_PCM_PTTSCR1 0x8 +#define AN7581_PCM_PTTSCR2 0xc +#define AN7581_PCM_PTTSCR3 0x10 +/* Same bitmap for all RX timeslot reg */ +#define AN7581_PCM_PRTSCR0 0x14 +#define AN7581_PCM_RXTS1_BW BIT(28) +#define AN7581_PCM_RXTS1_BW_8BIT FIELD_PREP_CONST(AN7581_PCM_RXTS1_BW, 0x0) +#define AN7581_PCM_RXTS1_BW_16BIT FIELD_PREP_CONST(AN7581_PCM_RXTS1_BW, 0x1) +#define AN7581_PCM_RXTS1_START GENMASK(25, 16) +#define AN7581_PCM_RXTS0_BW BIT(12) +#define AN7581_PCM_RXTS0_BW_8BIT FIELD_PREP_CONST(AN7581_PCM_RXTS0_BW, 0x0) +#define AN7581_PCM_RXTS0_BW_16BIT FIELD_PREP_CONST(AN7581_PCM_RXTS0_BW, 0x1) +#define AN7581_PCM_RXTS0_START GENMASK(9, 0) +#define AN7581_PCM_PRTSCR1 0x18 +#define AN7581_PCM_PRTSCR2 0x1c +#define AN7581_PCM_PRTSCR3 0x20 +#define AN7581_PCM_ISR 0x24 +#define AN7581_PCM_IMR 0x28 +#define AN7581_PCM_IMR_ISI2_INT BIT(16) +#define AN7581_PCM_IMR_ZSI2_INT BIT(15) +#define AN7581_PCM_IMR_SLIC_INT BIT(14) +#define AN7581_PCM_IMR_SFC_INT BIT(13) +#define AN7581_PCM_IMR_ISI1_INT BIT(12) +#define AN7581_PCM_IMR_ZSI1_INT BIT(11) +#define AN7581_PCM_IMR_HUNT_OVERTIME_INT BIT(10) +#define AN7581_PCM_IMR_HUNT_ERR_INT BIT(9) +#define AN7581_PCM_IMR_AHB_BUS_ERR_INT BIT(8) +#define AN7581_PCM_IMR_RBUF_OVERRUN_INT BIT(7) +#define AN7581_PCM_IMR_TBUF_UNDERRUN_INT BIT(6) +#define AN7581_PCM_IMR_RDESC_END_INT BIT(5) +#define AN7581_PCM_IMR_TDESC_END_INT BIT(4) +#define AN7581_PCM_IMR_RDESC_UPDATE_INT BIT(3) +#define AN7581_PCM_IMR_TDESC_UPDATE_INT BIT(2) +#define AN7581_PCM_IMR_TXRX_FB_INT BIT(0) +#define AN7581_PCM_TPDR 0x2c +#define AN7581_PCM_TX_POLLING BIT(0) +#define AN7581_PCM_RPDR 0x30 +#define AN7581_PCM_RX_POLLING BIT(0) +#define AN7581_PCM_TDRBAR 0x34 +#define AN7581_TDESC_BASE GENMASK(31, 0) +#define AN7581_PCM_RDRBAR 0x38 +#define AN7581_RDESC_BASE GENMASK(31, 0) +#define AN7581_PCM_TRDRSR 0x3c +#define AN7581_DESC_OFFSET GENMASK(7, 4) +#define AN7581_DESC_SIZE GENMASK(3, 0) +#define AN7581_PCM_TRDCR 0x40 +#define AN7581_PCM_DMA_POLICY GENMASK(3, 2) +#define AN7581_PCM_DMA_POLICY_RR FIELD_PREP_CONST(AN7581_PCM_DMA_POLICY, 0x0) +#define AN7581_PCM_DMA_POLICY_RSVD FIELD_PREP_CONST(AN7581_PCM_DMA_POLICY, 0x1) +#define AN7581_PCM_DMA_POLICY_TX_PRIORITY FIELD_PREP_CONST(AN7581_PCM_DMA_POLICY, 0x2) +#define AN7581_PCM_DMA_POLICY_RX_PRIORITY FIELD_PREP_CONST(AN7581_PCM_DMA_POLICY, 0x3) +#define AN7581_PCM_RXDMA_ENABLE BIT(1) +#define AN7581_PCM_TXDMA_ENABLE BIT(0) +#define AN7581_PCM_PTTSCR4 0x48 +#define AN7581_PCM_PTTSCR5 0x4c +#define AN7581_PCM_PTTSCR6 0x50 +#define AN7581_PCM_PTTSCR7 0x54 +#define AN7581_PCM_PTTSCR8 0x58 +#define AN7581_PCM_PTTSCR9 0x5c +#define AN7581_PCM_PTTSCR10 0x60 +#define AN7581_PCM_PTTSCR11 0x64 +#define AN7581_PCM_PTTSCR12 0x68 +#define AN7581_PCM_PTTSCR13 0x6c +#define AN7581_PCM_PTTSCR14 0x70 +#define AN7581_PCM_PTTSCR15 0x74 +#define AN7581_PCM_PTTSCR(chan) ((chan) < 8 ? \ + (AN7581_PCM_PTTSCR0 + ((chan) / 2) * 4) : \ + (AN7581_PCM_PTTSCR4 + (((chan) - 8) / 2) * 4)) +#define AN7581_PCM_TXTS_BW(chan) ((chan) % 2 ? AN7581_PCM_TXTS1_BW : AN7581_PCM_TXTS0_BW) +#define AN7581_PCM_TXTS_BW_8BIT 0x0 +#define AN7581_PCM_TXTS_BW_16BIT(chan) AN7581_PCM_TXTS_BW(chan) +#define AN7581_PCM_TXTS_START(chan) ((chan) % 2 ? AN7581_PCM_TXTS1_START : AN7581_PCM_TXTS0_START) +#define AN7581_PCM_PRTSCR4 0x78 +#define AN7581_PCM_PRTSCR5 0x7c +#define AN7581_PCM_PRTSCR6 0x80 +#define AN7581_PCM_PRTSCR7 0x84 +#define AN7581_PCM_PRTSCR8 0x88 +#define AN7581_PCM_PRTSCR9 0x8c +#define AN7581_PCM_PRTSCR10 0x90 +#define AN7581_PCM_PRTSCR11 0x94 +#define AN7581_PCM_PRTSCR12 0x98 +#define AN7581_PCM_PRTSCR13 0x9c +#define AN7581_PCM_PRTSCR14 0xa0 +#define AN7581_PCM_PRTSCR15 0xa4 +#define AN7581_PCM_PRTSCR(chan) ((chan) < 8 ? \ + (AN7581_PCM_PRTSCR0 + ((chan) / 2) * 4) : \ + (AN7581_PCM_PRTSCR4 + (((chan) - 8) / 2) * 4)) +#define AN7581_PCM_RXTS_BW(chan) ((chan) % 2 ? AN7581_PCM_RXTS1_BW : AN7581_PCM_RXTS0_BW) +#define AN7581_PCM_RXTS_BW_8BIT 0x0 +#define AN7581_PCM_RXTS_BW_16BIT(chan) AN7581_PCM_RXTS_BW(chan) +#define AN7581_PCM_RXTS_START(chan) ((chan) % 2 ? AN7581_PCM_RXTS1_START : AN7581_PCM_RXTS0_START) +#define AN7581_PCM_CHBFOSR 0xa8 +#define AN7581_PCM_DESC_CHBF_OFFSET GENMASK(15, 0) +#define AN7581_PCM_DCHENR 0xac +/* Each channel correspond to a bit. + * Example: + * BIT(0) -> chan 0 + * BIT(1) -> chan 1 + * ... + * + * Notice that it's expected the channel are sequential + * aka it's not possible to have empty channel in between. + * (example BIT(0) | BIT(2)) + */ +#define AN7581_PCM_CHAN_EN GENMASK(31, 0) + +#define AN7581_PCM_MAX_CHANNELS 32 +#define AN7581_PCM_TX_DESCRIPTORS 15 +#define AN7581_PCM_RX_DESCRIPTORS 15 +#define AN7581_PCM_DESCRIPTORS (AN7581_PCM_TX_DESCRIPTORS + AN7581_PCM_RX_DESCRIPTORS) +#define AN7581_PCM_FRAMES_PER_DESC 80 +#define AN7581_PCM_MAX_SAMPLE_BYTES 2 +#define AN7581_PCM_MAX_PERIOD_BYTES (AN7581_PCM_FRAMES_PER_DESC * \ + AN7581_PCM_MAX_CHANNELS * \ + AN7581_PCM_MAX_SAMPLE_BYTES) +#define AN7581_PCM_MAX_BUFFER_BYTES (AN7581_PCM_MAX_PERIOD_BYTES * \ + AN7581_PCM_TX_DESCRIPTORS) + +#define AN7581_PCM_DESC_STATUS_OWNERSHIP BIT(31) +#define AN7581_PCM_DESC_STATUS_OWNERSHIP_CPU FIELD_PREP_CONST(AN7581_PCM_DESC_STATUS_OWNERSHIP, 0x0) +#define AN7581_PCM_DESC_STATUS_OWNERSHIP_DMA FIELD_PREP_CONST(AN7581_PCM_DESC_STATUS_OWNERSHIP, 0x1) +#define AN7581_PCM_DESC_STATUS_SAMPLE_SIZE GENMASK(9, 0) + +struct an7581_pcm_desc { + u32 status; + u32 channel_mask; + u32 dma_addr; +} __packed; + +struct an7581_pcm_priv { + struct regmap *map; + struct reset_control *reset; + + /* Cached current stream for IRQ */ + struct snd_pcm_substream *stream[SNDRV_PCM_STREAM_LAST + 1]; + + /* Protect descriptor idx */ + spinlock_t desc_lock; + /* Protect configuration shared by playback and capture. */ + struct mutex user_lock; + unsigned int users; + unsigned int current_rate; + unsigned int current_channels; + + struct an7581_pcm_desc *tx_descs; + int tx_desc_idx; + dma_addr_t tx_dma; + + struct an7581_pcm_desc *rx_descs; + int rx_desc_idx; + dma_addr_t rx_dma; + + unsigned int tx_pos; + unsigned int rx_pos; +}; + +static const struct snd_pcm_hardware an7581_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED, + .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_U16_LE, + + .period_bytes_min = AN7581_PCM_FRAMES_PER_DESC, + .period_bytes_max = AN7581_PCM_MAX_PERIOD_BYTES, + + .buffer_bytes_max = AN7581_PCM_MAX_BUFFER_BYTES, + + .periods_min = AN7581_PCM_TX_DESCRIPTORS, + .periods_max = AN7581_PCM_TX_DESCRIPTORS, +}; + +static int an7581_pcm_setup_time_slot(struct an7581_pcm_priv *priv, + int direction, int bit_width) +{ + int bit_counter = 0; + int chan; + + for (chan = 0; chan < AN7581_PCM_MAX_CHANNELS; chan++) { + u32 mask, val; + u32 reg; + + if (direction == SNDRV_PCM_STREAM_PLAYBACK) { + reg = AN7581_PCM_PTTSCR(chan); + mask = AN7581_PCM_TXTS_BW(chan) | + AN7581_PCM_TXTS_START(chan); + + if (bit_width == 8) + val = AN7581_PCM_TXTS_BW_8BIT; + else + val = AN7581_PCM_TXTS_BW_16BIT(chan); + val |= bit_counter << __bf_shf(AN7581_PCM_TXTS_START(chan)); + } else { + reg = AN7581_PCM_PRTSCR(chan); + mask = AN7581_PCM_RXTS_BW(chan) | + AN7581_PCM_RXTS_START(chan); + + if (bit_width == 8) + val = AN7581_PCM_RXTS_BW_8BIT; + else + val = AN7581_PCM_RXTS_BW_16BIT(chan); + val |= bit_counter << __bf_shf(AN7581_PCM_RXTS_START(chan)); + } + + regmap_update_bits(priv->map, reg, mask, val); + + bit_counter += bit_width; + } + + return 0; +} + +static int an7581_pcm_fe_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct an7581_pcm_priv *priv = snd_soc_card_get_drvdata(rtd->card); + struct snd_pcm_runtime *runtime = substream->runtime; + int ret; + + /* Cache stream substream for IRQ handling */ + priv->stream[substream->stream] = substream; + + /* On open mask interrupt and disable DMA */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + regmap_clear_bits(priv->map, AN7581_PCM_IMR, + AN7581_PCM_IMR_TDESC_UPDATE_INT); + regmap_clear_bits(priv->map, AN7581_PCM_TRDCR, + AN7581_PCM_TXDMA_ENABLE); + } else { + regmap_clear_bits(priv->map, AN7581_PCM_IMR, + AN7581_PCM_IMR_RDESC_UPDATE_INT); + regmap_clear_bits(priv->map, AN7581_PCM_TRDCR, + AN7581_PCM_RXDMA_ENABLE); + } + + usleep_range(10000, 20000); + + mutex_lock(&priv->user_lock); + if (!priv->users) { + regmap_clear_bits(priv->map, AN7581_PCM_PICR, + AN7581_PCM_CFG_VALID); + + usleep_range(5000, 10000); + } + mutex_unlock(&priv->user_lock); + + snd_soc_set_runtime_hwparams(substream, &an7581_pcm_hardware); + + ret = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + AN7581_PCM_FRAMES_PER_DESC, + AN7581_PCM_FRAMES_PER_DESC); + if (ret < 0) { + dev_err(rtd->card->dev, "failed to constrain period to 80 frames\n"); + return ret; + } + + ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + dev_err(rtd->card->dev, "snd_pcm_hw_constraint_integer failed\n"); + + return ret; +} + +static void an7581_pcm_fe_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct an7581_pcm_priv *priv = snd_soc_card_get_drvdata(rtd->card); + + /* On close mask interrupt and disable DMA */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + regmap_clear_bits(priv->map, AN7581_PCM_IMR, + AN7581_PCM_IMR_TDESC_UPDATE_INT); + regmap_clear_bits(priv->map, AN7581_PCM_TRDCR, + AN7581_PCM_TXDMA_ENABLE); + } else { + regmap_clear_bits(priv->map, AN7581_PCM_IMR, + AN7581_PCM_IMR_RDESC_UPDATE_INT); + regmap_clear_bits(priv->map, AN7581_PCM_TRDCR, + AN7581_PCM_RXDMA_ENABLE); + } + + priv->stream[substream->stream] = NULL; + + usleep_range(10000, 20000); + + mutex_lock(&priv->user_lock); + + if (priv->users) + priv->users--; + + if (!priv->users) + regmap_clear_bits(priv->map, AN7581_PCM_PICR, + AN7581_PCM_CFG_VALID); + + mutex_unlock(&priv->user_lock); +} + +static int an7581_pcm_fe_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct an7581_pcm_priv *priv = snd_soc_card_get_drvdata(rtd->card); + struct snd_pcm_runtime *runtime = substream->runtime; + struct an7581_pcm_desc *desc; + unsigned int period_bytes; + int num_desc, i; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + priv->tx_pos = 0; + priv->tx_desc_idx = 0; + desc = priv->tx_descs; + num_desc = AN7581_PCM_TX_DESCRIPTORS; + } else { + priv->rx_pos = 0; + priv->rx_desc_idx = 0; + desc = priv->rx_descs; + num_desc = AN7581_PCM_RX_DESCRIPTORS; + } + + period_bytes = frames_to_bytes(runtime, AN7581_PCM_FRAMES_PER_DESC); + for (i = 0; i < num_desc; i++) { + desc->status |= AN7581_PCM_DESC_STATUS_OWNERSHIP_DMA; + desc->dma_addr = runtime->dma_addr + i * period_bytes; + desc++; + } + + return 0; +} + +static int an7581_pcm_fe_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct an7581_pcm_priv *priv = snd_soc_card_get_drvdata(rtd->card); + unsigned int channels = params_channels(params); + unsigned int rate = params_rate(params); + int bit_width = params_width(params); + struct an7581_pcm_desc *desc; + int num_desc, i; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + desc = priv->tx_descs; + num_desc = AN7581_PCM_TX_DESCRIPTORS; + } else { + desc = priv->rx_descs; + num_desc = AN7581_PCM_RX_DESCRIPTORS; + } + + mutex_lock(&priv->user_lock); + if (priv->users && + (priv->current_rate != rate || + priv->current_channels != channels)) { + dev_err(rtd->card->dev, "playback/capture rates/channels must match in full-duplex mode\n"); + mutex_unlock(&priv->user_lock); + return -EBUSY; + } + + if (!priv->users) { + /* one bit delay for ZSI */ + regmap_set_bits(priv->map, AN7581_PCM_PICR, AN7581_PCM_ONE_BIT_DELAY); + + regmap_update_bits(priv->map, AN7581_PCM_PICR, + AN7581_PCM_BIT_MSTSLV_MODE, + AN7581_PCM_BIT_MSTSLV_MODE_MASTER); + + regmap_update_bits(priv->map, AN7581_PCM_PICR, + AN7581_PCM_BIT_CLK, + AN7581_PCM_BIT_CLK_2048HZ); + + regmap_update_bits(priv->map, AN7581_PCM_PICR, + AN7581_PCM_SAMPLE_CLK, + (rate == 16000 ? AN7581_PCM_SAMPLE_CLK_16KHZ : + AN7581_PCM_SAMPLE_CLK_8KHZ)); + + regmap_update_bits(priv->map, AN7581_PCM_PICR, + AN7581_PCM_FS_LEN, + AN7581_PCM_FS_LEN_16); + + regmap_set_bits(priv->map, AN7581_PCM_PICR, AN7581_PCM_BIT_ORDER); + + regmap_clear_bits(priv->map, AN7581_PCM_PICR, AN7581_PCM_FS_EDGE); + } + + /* Setup timeslot */ + an7581_pcm_setup_time_slot(priv, substream->stream, bit_width); + + if (!priv->users) { + /* Setup Descriptor size and number */ + regmap_update_bits(priv->map, AN7581_PCM_TRDRSR, + AN7581_DESC_OFFSET | AN7581_DESC_SIZE, + FIELD_PREP(AN7581_DESC_OFFSET, sizeof(*desc) / sizeof(u32)) | + FIELD_PREP(AN7581_DESC_SIZE, num_desc)); + + regmap_update_bits(priv->map, AN7581_PCM_CHBFOSR, + AN7581_PCM_DESC_CHBF_OFFSET, + FIELD_PREP(AN7581_PCM_DESC_CHBF_OFFSET, + AN7581_PCM_FRAMES_PER_DESC * bit_width / 8)); + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + regmap_write(priv->map, AN7581_PCM_TDRBAR, priv->tx_dma); + else + regmap_write(priv->map, AN7581_PCM_RDRBAR, priv->rx_dma); + + /* Configure descriptors for DMA ownership and sample size */ + for (i = 0; i < num_desc; i++) { + /* Assign to each descriptor the dma_addr + offset of period bytes */ + desc[i].status = FIELD_PREP(AN7581_PCM_DESC_STATUS_SAMPLE_SIZE, + AN7581_PCM_FRAMES_PER_DESC); + + desc[i].channel_mask = GENMASK(channels - 1, 0); + } + + if (!priv->users) { + /* Enable channels */ + regmap_update_bits(priv->map, AN7581_PCM_DCHENR, + AN7581_PCM_CHAN_EN, + FIELD_PREP(AN7581_PCM_CHAN_EN, GENMASK(channels - 1, 0))); + + /* Signal PCM configuration is now valid */ + regmap_set_bits(priv->map, AN7581_PCM_PICR, + AN7581_PCM_CFG_VALID); + } + + priv->current_rate = rate; + priv->current_channels = channels; + priv->users++; + + mutex_unlock(&priv->user_lock); + + return 0; +} + +static int an7581_pcm_fe_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct an7581_pcm_priv *priv = snd_soc_card_get_drvdata(rtd->card); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + /* Trigger PCM to POLL new descriptor */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + regmap_set_bits(priv->map, AN7581_PCM_TRDCR, + AN7581_PCM_TXDMA_ENABLE); + + regmap_set_bits(priv->map, AN7581_PCM_IMR, + AN7581_PCM_IMR_TDESC_UPDATE_INT); + + regmap_write(priv->map, AN7581_PCM_TPDR, + AN7581_PCM_TX_POLLING); + } else { + regmap_set_bits(priv->map, AN7581_PCM_TRDCR, + AN7581_PCM_RXDMA_ENABLE); + + regmap_set_bits(priv->map, AN7581_PCM_IMR, + AN7581_PCM_IMR_RDESC_UPDATE_INT); + + regmap_write(priv->map, AN7581_PCM_RPDR, + AN7581_PCM_RX_POLLING); + } + + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + regmap_clear_bits(priv->map, AN7581_PCM_IMR, + AN7581_PCM_IMR_TDESC_UPDATE_INT); + regmap_clear_bits(priv->map, AN7581_PCM_TRDCR, + AN7581_PCM_TXDMA_ENABLE); + } else { + regmap_clear_bits(priv->map, AN7581_PCM_IMR, + AN7581_PCM_IMR_RDESC_UPDATE_INT); + regmap_clear_bits(priv->map, AN7581_PCM_TRDCR, + AN7581_PCM_RXDMA_ENABLE); + } + + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct snd_soc_dai_ops an7581_pcm_fe_ops = { + .startup = an7581_pcm_fe_startup, + .shutdown = an7581_pcm_fe_shutdown, + .hw_params = an7581_pcm_fe_hw_params, + .prepare = an7581_pcm_fe_prepare, + .trigger = an7581_pcm_fe_trigger, +}; + +static struct snd_soc_dai_driver an7581_pcm_dai_driver[] = { + { + .name = "DLPCM", + .id = 0, + .playback = { + .stream_name = "DLPCM", + .channels_min = 1, + .channels_max = 32, + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_U16_LE, + }, + .ops = &an7581_pcm_fe_ops, + }, + { + .name = "ULPCM", + .id = 1, + .capture = { + .stream_name = "ULPCM", + .channels_min = 1, + .channels_max = 32, + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_U16_LE, + }, + .ops = &an7581_pcm_fe_ops, + }, +}; + +static const struct snd_soc_component_driver an7581_pcm_dai_component = { + .name = "an7581-pcm-dai", +}; + +static snd_pcm_uframes_t an7581_pcm_pointer(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_soc_card *card = component->card; + struct snd_pcm_runtime *runtime = substream->runtime; + struct an7581_pcm_priv *priv; + unsigned long flags; + unsigned int pos; + + priv = snd_soc_card_get_drvdata(card); + + spin_lock_irqsave(&priv->desc_lock, flags); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + pos = bytes_to_frames(runtime, priv->tx_pos); + else + pos = bytes_to_frames(runtime, priv->rx_pos); + spin_unlock_irqrestore(&priv->desc_lock, flags); + + return pos % runtime->buffer_size; +} + +static int an7581_pcm_new(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *rtd) +{ + struct snd_pcm *pcm = rtd->pcm; + + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, component->dev, + 0, + AN7581_PCM_MAX_BUFFER_BYTES); + + return 0; +} + +static const struct snd_soc_component_driver an7581_pcm_platform = { + .name = "an7581-pcm-audio", + .pointer = an7581_pcm_pointer, + .pcm_construct = an7581_pcm_new, +}; + +SND_SOC_DAILINK_DEFS(playback, + DAILINK_COMP_ARRAY(COMP_CPU("DLPCM")), + DAILINK_COMP_ARRAY(COMP_DUMMY()), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(capture, + DAILINK_COMP_ARRAY(COMP_CPU("ULPCM")), + DAILINK_COMP_ARRAY(COMP_DUMMY()), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link an7581_pcm_dai_links[] = { + /* FE */ + { + .name = "an7581-playback", + .stream_name = "an7581-playback", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, + SND_SOC_DPCM_TRIGGER_POST}, + .playback_only = 1, + SND_SOC_DAILINK_REG(playback), + }, + { + .name = "an7581-capture", + .stream_name = "an7581-capture", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, + SND_SOC_DPCM_TRIGGER_POST}, + .capture_only = 1, + SND_SOC_DAILINK_REG(capture), + }, +}; + +static struct snd_soc_card an7581_pcm_card = { + .name = "an7581-pcm", + .owner = THIS_MODULE, + .dai_link = an7581_pcm_dai_links, + .num_links = ARRAY_SIZE(an7581_pcm_dai_links), +}; + +static irqreturn_t an7581_pcm_irq_handler(int irq, void *data) +{ + struct snd_pcm_substream *substream; + struct an7581_pcm_priv *priv = data; + unsigned long flags; + u32 status, isr = 0; + + regmap_read(priv->map, AN7581_PCM_ISR, &isr); + + /* Scan descriptor on receiving RX desc update interrupt */ + if (isr & AN7581_PCM_IMR_RDESC_UPDATE_INT) { + struct snd_pcm_runtime *runtime; + struct an7581_pcm_desc *desc; + unsigned int sample_size; + bool rx_poll = false; + int i; + + substream = priv->stream[SNDRV_PCM_STREAM_CAPTURE]; + if (!substream) + goto handle_tx; + + runtime = substream->runtime; + + for (i = 0; i < AN7581_PCM_RX_DESCRIPTORS; i++) { + spin_lock_irqsave(&priv->desc_lock, flags); + + desc = &priv->rx_descs[priv->rx_desc_idx]; + status = desc->status; + if ((status & AN7581_PCM_DESC_STATUS_OWNERSHIP) == + AN7581_PCM_DESC_STATUS_OWNERSHIP_DMA) { + spin_unlock_irqrestore(&priv->desc_lock, + flags); + break; + } + + sample_size = FIELD_GET(AN7581_PCM_DESC_STATUS_SAMPLE_SIZE, + status); + + priv->rx_pos += frames_to_bytes(runtime, sample_size); + priv->rx_desc_idx++; + if (priv->rx_desc_idx == AN7581_PCM_RX_DESCRIPTORS) + priv->rx_desc_idx = 0; + + spin_unlock_irqrestore(&priv->desc_lock, flags); + + snd_pcm_period_elapsed(substream); + + desc->status |= AN7581_PCM_DESC_STATUS_OWNERSHIP_DMA; + + if (snd_pcm_capture_hw_avail(runtime) < runtime->period_size) + goto handle_tx; + + rx_poll = true; + } + + if (rx_poll) + regmap_write(priv->map, AN7581_PCM_RPDR, AN7581_PCM_RX_POLLING); + } + +handle_tx: + /* Scan descriptor on receiving TX desc update interrupt */ + if (isr & AN7581_PCM_IMR_TDESC_UPDATE_INT) { + struct snd_pcm_runtime *runtime; + struct an7581_pcm_desc *desc; + unsigned int sample_size; + bool tx_poll = false; + int i; + + substream = priv->stream[SNDRV_PCM_STREAM_PLAYBACK]; + if (!substream) + return IRQ_HANDLED; + + runtime = substream->runtime; + + for (i = 0; i < AN7581_PCM_TX_DESCRIPTORS; i++) { + spin_lock_irqsave(&priv->desc_lock, flags); + + desc = &priv->tx_descs[priv->tx_desc_idx]; + status = desc->status; + if ((status & AN7581_PCM_DESC_STATUS_OWNERSHIP) == + AN7581_PCM_DESC_STATUS_OWNERSHIP_DMA) { + spin_unlock_irqrestore(&priv->desc_lock, + flags); + break; + } + + sample_size = FIELD_GET(AN7581_PCM_DESC_STATUS_SAMPLE_SIZE, + status); + + priv->tx_pos += frames_to_bytes(runtime, sample_size); + priv->tx_desc_idx++; + if (priv->tx_desc_idx == AN7581_PCM_TX_DESCRIPTORS) + priv->tx_desc_idx = 0; + + spin_unlock_irqrestore(&priv->desc_lock, flags); + + snd_pcm_period_elapsed(substream); + + desc->status |= AN7581_PCM_DESC_STATUS_OWNERSHIP_DMA; + + if (snd_pcm_playback_hw_avail(runtime) < runtime->period_size) + return IRQ_HANDLED; + + tx_poll = true; + } + + if (tx_poll) + regmap_write(priv->map, AN7581_PCM_TPDR, + AN7581_PCM_TX_POLLING); + } + + return IRQ_HANDLED; +} + +static int an7581_pcm_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &an7581_pcm_card; + struct snd_soc_dai_link *dai_link; + struct device *dev = &pdev->dev; + struct an7581_pcm_priv *priv; + const char *connection_type; + int irq; + int ret; + int i; + + struct regmap *chip_scu; + + chip_scu = syscon_regmap_lookup_by_compatible("airoha,en7581-chip-scu"); + + /* set clock source to ZSI and enable fractional divider */ + regmap_set_bits(chip_scu, 0x1d0, BIT(10) | BIT(0)); + + if (!device_property_read_string(dev, "airoha,connection-type", + &connection_type)) { + if (!strcmp(connection_type, "slic")) { + struct pinctrl *pinctrl; + + pinctrl = devm_pinctrl_get_select(dev, "pcm_spi"); + if (IS_ERR(pinctrl)) + dev_err(dev, "Failed to setup PCM SPI pinctrl\n"); + } + } + + card->dev = dev; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + snd_soc_card_set_drvdata(card, priv); + + priv->map = device_node_to_regmap(dev->of_node); + if (IS_ERR(priv->map)) + return PTR_ERR(priv->map); + + priv->reset = devm_reset_control_get_exclusive(dev, "pcm1"); + if (IS_ERR(priv->reset)) + return PTR_ERR(priv->reset); + + irq = of_irq_get(dev_of_node(dev), 0); + if (irq < 0) + return irq; + + ret = devm_request_threaded_irq(dev, irq, an7581_pcm_irq_handler, + NULL, IRQF_ONESHOT, dev_name(dev), priv); + if (ret) + return ret; + + spin_lock_init(&priv->desc_lock); + mutex_init(&priv->user_lock); + + /* Reset the PCM interface */ + reset_control_assert(priv->reset); + usleep_range(5000, 10000); + reset_control_deassert(priv->reset); + usleep_range(5000, 10000); + + ret = dma_set_mask(dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + priv->tx_descs = dma_alloc_coherent(dev, sizeof(struct an7581_pcm_desc) * + AN7581_PCM_TX_DESCRIPTORS, &priv->tx_dma, + GFP_KERNEL); + if (!priv->tx_descs) + return -ENOMEM; + + priv->rx_descs = dma_alloc_coherent(dev, sizeof(struct an7581_pcm_desc) * + AN7581_PCM_RX_DESCRIPTORS, &priv->rx_dma, + GFP_KERNEL); + if (!priv->rx_descs) { + ret = -ENOMEM; + goto err_free_tx; + } + + for_each_card_prelinks(card, i, dai_link) + dai_link->platforms->of_node = dev->of_node; + + ret = devm_snd_soc_register_component(dev, + &an7581_pcm_platform, + NULL, 0); + if (ret) { + dev_err_probe(dev, ret, "Cannot register PCM component\n"); + goto err_free_rx; + } + + ret = devm_snd_soc_register_component(dev, + &an7581_pcm_dai_component, + an7581_pcm_dai_driver, + ARRAY_SIZE(an7581_pcm_dai_driver)); + if (ret) { + dev_err_probe(dev, ret, "Cannot register PCM DAI component\n"); + goto err_free_rx; + } + + ret = devm_snd_soc_register_card(dev, card); + if (ret) { + dev_err_probe(dev, ret, "%s snd_soc_register_card fail\n", __func__); + goto err_free_rx; + } + + return 0; + +err_free_rx: + dma_free_coherent(dev, sizeof(struct an7581_pcm_desc) * + AN7581_PCM_RX_DESCRIPTORS, priv->rx_descs, priv->rx_dma); +err_free_tx: + dma_free_coherent(dev, sizeof(struct an7581_pcm_desc) * + AN7581_PCM_TX_DESCRIPTORS, priv->tx_descs, priv->tx_dma); + + return ret; +} + +static void an7581_pcm_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = &an7581_pcm_card; + struct device *dev = &pdev->dev; + struct an7581_pcm_priv *priv; + + priv = snd_soc_card_get_drvdata(card); + regmap_clear_bits(priv->map, AN7581_PCM_IMR, + AN7581_PCM_IMR_TDESC_UPDATE_INT | + AN7581_PCM_IMR_RDESC_UPDATE_INT); + regmap_clear_bits(priv->map, AN7581_PCM_TRDCR, + AN7581_PCM_TXDMA_ENABLE | + AN7581_PCM_RXDMA_ENABLE); + + dma_free_coherent(dev, sizeof(struct an7581_pcm_desc) * + AN7581_PCM_RX_DESCRIPTORS, priv->rx_descs, priv->rx_dma); + dma_free_coherent(dev, sizeof(struct an7581_pcm_desc) * + AN7581_PCM_TX_DESCRIPTORS, priv->tx_descs, priv->tx_dma); +} + +static const struct of_device_id an7581_pcm_dt_match[] = { + { .compatible = "airoha,an7581-pcm" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, an7581_pcm_dt_match); + +static struct platform_driver an7581_pcm_driver = { + .driver = { + .name = "an7581-pcm", + .of_match_table = an7581_pcm_dt_match, + }, + .probe = an7581_pcm_probe, + .remove = an7581_pcm_remove, +}; +module_platform_driver(an7581_pcm_driver); + +MODULE_DESCRIPTION("Airoha SoC PCM platform driver for ALSA AN7581"); +MODULE_LICENSE("GPL"); -- 2.55.0