From patchwork Fri Jan 13 10:37:50 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Herve Codina X-Patchwork-Id: 642235 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 vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 18B51C677F1 for ; Fri, 13 Jan 2023 10:39:29 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S241351AbjAMKj1 (ORCPT ); Fri, 13 Jan 2023 05:39:27 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:38548 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S241349AbjAMKie (ORCPT ); Fri, 13 Jan 2023 05:38:34 -0500 Received: from relay9-d.mail.gandi.net (relay9-d.mail.gandi.net [IPv6:2001:4b98:dc4:8::229]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id EB99A3F10D; Fri, 13 Jan 2023 02:38:10 -0800 (PST) Received: (Authenticated sender: herve.codina@bootlin.com) by mail.gandi.net (Postfix) with ESMTPA id 36677FF812; Fri, 13 Jan 2023 10:38:06 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=gm1; t=1673606289; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=e9+cZzguFSROPV4tCZLD7SaeW4MY38Tng5UQywdd1Oo=; b=jS1gu5zvJRx9adaZfWf64nRyetSxBLADYSnm1YSic+J+cl+2Vz+aILzoDS+QEqraJBobg1 O9jX1WxQrV4+Gc33rnj2RNubSIAyKoKK+7EKRXFizUBWWb7Jo+YXgae/HkPJCwPjAE0o9/ lMhSvev9094xq5e747WPtNp2Oh4qCOUENIzTfqr/KIr381ttlNaYkmlKnZlHzTX0+Bm738 xqza2VCN2rdRWHLBYdWHeqpjyS+ylHZqBgmcI01X6l0g0dUUhafOgNwjlQug8wx42/aoVp DFHxEggGylNZ+yxM+KxwzTKrmuxluVmqd3TbcVB74lTEVwDrFP/VPXHjY75+VA== From: Herve Codina To: Herve Codina , Li Yang , Rob Herring , Krzysztof Kozlowski , Liam Girdwood , Mark Brown , Christophe Leroy , Michael Ellerman , Nicholas Piggin , Qiang Zhao , Jaroslav Kysela , Takashi Iwai , Shengjiu Wang , Xiubo Li , Fabio Estevam , Nicolin Chen Cc: linuxppc-dev@lists.ozlabs.org, linux-arm-kernel@lists.infradead.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, alsa-devel@alsa-project.org, Thomas Petazzoni Subject: [PATCH v3 01/10] dt-bindings: soc: fsl: cpm_qe: Add TSA controller Date: Fri, 13 Jan 2023 11:37:50 +0100 Message-Id: <20230113103759.327698-2-herve.codina@bootlin.com> X-Mailer: git-send-email 2.38.1 In-Reply-To: <20230113103759.327698-1-herve.codina@bootlin.com> References: <20230113103759.327698-1-herve.codina@bootlin.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: devicetree@vger.kernel.org Add support for the time slot assigner (TSA) available in some PowerQUICC SoC such as MPC885 or MPC866. Signed-off-by: Herve Codina --- .../bindings/soc/fsl/cpm_qe/fsl,tsa.yaml | 260 ++++++++++++++++++ include/dt-bindings/soc/fsl,tsa.h | 13 + 2 files changed, 273 insertions(+) create mode 100644 Documentation/devicetree/bindings/soc/fsl/cpm_qe/fsl,tsa.yaml create mode 100644 include/dt-bindings/soc/fsl,tsa.h diff --git a/Documentation/devicetree/bindings/soc/fsl/cpm_qe/fsl,tsa.yaml b/Documentation/devicetree/bindings/soc/fsl/cpm_qe/fsl,tsa.yaml new file mode 100644 index 000000000000..eb17b6119abd --- /dev/null +++ b/Documentation/devicetree/bindings/soc/fsl/cpm_qe/fsl,tsa.yaml @@ -0,0 +1,260 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/soc/fsl/cpm_qe/fsl,tsa.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: PowerQUICC CPM Time-slot assigner (TSA) controller + +maintainers: + - Herve Codina + +description: | + The TSA is the time-slot assigner that can be found on some + PowerQUICC SoC. + Its purpose is to route some TDM time-slots to other internal + serial controllers. + +properties: + compatible: + items: + - enum: + - fsl,mpc885-tsa + - fsl,mpc866-tsa + - const: fsl,cpm1-tsa + + reg: + items: + - description: SI (Serial Interface) register base + - description: SI RAM base + + reg-names: + items: + - const: si_regs + - const: si_ram + + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + +patternProperties: + '^tdm@[0-1]$': + description: + The TDM managed by this controller + type: object + + properties: + reg: + minimum: 0 + maximum: 1 + description: + The TDM number for this TDM, 0 for TDMa and 1 for TDMb + + fsl,common-rxtx-pins: + $ref: /schemas/types.yaml#/definitions/flag + description: + The hardware can use four dedicated pins for Tx clock, + Tx sync, Rx clock and Rx sync or use only two pins, + Tx/Rx clock and Rx/Rx sync. + Without the 'fsl,common-rxtx-pins' property, the four + pins are used. With the 'fsl,common-rxtx-pins' property, + two pins are used. + + clocks: + minItems: 2 + maxItems: 4 + + clock-names: + minItems: 2 + maxItems: 4 + + fsl,mode: + $ref: /schemas/types.yaml#/definitions/string + enum: [normal, echo, internal-loopback, control-loopback] + default: normal + description: | + Operational mode: + - normal: + Normal operation + - echo: + Automatic echo. Rx data is resent on Tx + - internal-loopback: + The TDM transmitter is connected to the receiver. + Data appears on Tx pin. + - control-loopback: + The TDM transmitter is connected to the receiver. + The Tx pin is disconnected. + + fsl,rx-frame-sync-delay-bits: + enum: [0, 1, 2, 3] + default: 0 + description: | + Receive frame sync delay in number of bits. + Indicates the delay between the Rx sync and the first bit of the + Rx frame. 0 for no bit delay. 1, 2 or 3 for 1, 2 or 3 bits delay. + + fsl,tx-frame-sync-delay-bits: + enum: [0, 1, 2, 3] + default: 0 + description: | + Transmit frame sync delay in number of bits. + Indicates the delay between the Tx sync and the first bit of the + Tx frame. 0 for no bit delay. 1, 2 or 3 for 1, 2 or 3 bits delay. + + fsl,clock-falling-edge: + $ref: /schemas/types.yaml#/definitions/flag + description: | + Data is sent on falling edge of the clock (and received on the + rising edge). + If 'clock-falling-edge' is not present, data is sent on the + rising edge (and received on the falling edge). + + fsl,fsync-rising-edge: + $ref: /schemas/types.yaml#/definitions/flag + description: + Frame sync pulses are sampled with the rising edge of the channel + clock. If 'fsync-rising-edge' is not present, pulses are sample + with e falling edge. + + fsl,double-speed-clock: + $ref: /schemas/types.yaml#/definitions/flag + description: + The channel clock is twice the data rate. + + fsl,tx-ts-routes: + $ref: /schemas/types.yaml#/definitions/uint32-matrix + description: | + A list of tupple that indicates the Tx time-slots routes. + tx_ts_routes = + < 2 0 >, /* The first 2 time slots are not used */ + < 3 1 >, /* The next 3 ones are route to SCC2 */ + < 4 0 >, /* The next 4 ones are not used */ + < 2 2 >; /* The nest 2 ones are route to SCC3 */ + items: + items: + - description: + The number of time-slots + minimum: 1 + maximum: 64 + - description: | + The source serial interface (dt-bindings/soc/fsl,tsa.h + defines these values) + - 0: No destination + - 1: SCC2 + - 2: SCC3 + - 3: SCC4 + - 4: SMC1 + - 5: SMC2 + enum: [0, 1, 2, 3, 4, 5] + minItems: 1 + maxItems: 64 + + fsl,rx-ts-routes: + $ref: /schemas/types.yaml#/definitions/uint32-matrix + description: | + A list of tupple that indicates the Rx time-slots routes. + tx_ts_routes = + < 2 0 >, /* The first 2 time slots are not used */ + < 3 1 >, /* The next 3 ones are route from SCC2 */ + < 4 0 >, /* The next 4 ones are not used */ + < 2 2 >; /* The nest 2 ones are route from SCC3 */ + items: + items: + - description: + The number of time-slots + minimum: 1 + maximum: 64 + - description: | + The destination serial interface (dt-bindings/soc/fsl,tsa.h + defines these values) + - 0: No destination + - 1: SCC2 + - 2: SCC3 + - 3: SCC4 + - 4: SMC1 + - 5: SMC2 + enum: [0, 1, 2, 3, 4, 5] + minItems: 1 + maxItems: 64 + + allOf: + # If fsl,common-rxtx-pins is present, only 2 clocks are needed. + # Else, the 4 clocks must be present. + - if: + required: + - fsl,common-rxtx-pins + then: + properties: + clocks: + items: + - description: External clock connected to L1RSYNC pin + - description: External clock connected to L1RCLK pin + clock-names: + items: + - const: l1rsync + - const: l1rclk + else: + properties: + clocks: + items: + - description: External clock connected to L1RSYNC pin + - description: External clock connected to L1RCLK pin + - description: External clock connected to L1TSYNC pin + - description: External clock connected to L1TCLK pin + clock-names: + items: + - const: l1rsync + - const: l1rclk + - const: l1tsync + - const: l1tclk + + required: + - reg + - clocks + - clock-names + +required: + - compatible + - reg + - reg-names + - '#address-cells' + - '#size-cells' + +additionalProperties: false + +examples: + - | + #include + + tsa@ae0 { + compatible = "fsl,mpc885-tsa", "fsl,cpm1-tsa"; + reg = <0xae0 0x10>, + <0xc00 0x200>; + reg-names = "si_regs", "si_ram"; + + #address-cells = <1>; + #size-cells = <0>; + + tdm@0 { + /* TDMa */ + reg = <0>; + + clocks = <&clk_l1rsynca>, <&clk_l1rclka>; + clock-names = "l1rsync", "l1rclk"; + + fsl,common-rxtx-pins; + fsl,fsync-rising-edge; + + fsl,tx-ts-routes = < 2 0 >, /* TS 0..1 */ + < 24 FSL_CPM_TSA_SCC4 >, /* TS 2..25 */ + < 1 0 >, /* TS 26 */ + < 5 FSL_CPM_TSA_SCC3 >; /* TS 27..31 */ + + fsl,rx-ts-routes = < 2 0 >, /* TS 0..1 */ + < 24 FSL_CPM_TSA_SCC4 >, /* 2..25 */ + < 1 0 >, /* TS 26 */ + < 5 FSL_CPM_TSA_SCC3 >; /* TS 27..31 */ + }; + }; diff --git a/include/dt-bindings/soc/fsl,tsa.h b/include/dt-bindings/soc/fsl,tsa.h new file mode 100644 index 000000000000..2cc44e867dbe --- /dev/null +++ b/include/dt-bindings/soc/fsl,tsa.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause */ + +#ifndef __DT_BINDINGS_SOC_FSL_TSA_H +#define __DT_BINDINGS_SOC_FSL_TSA_H + +#define FSL_CPM_TSA_NU 0 /* Pseuso Cell Id for not used item */ +#define FSL_CPM_TSA_SCC2 1 +#define FSL_CPM_TSA_SCC3 2 +#define FSL_CPM_TSA_SCC4 3 +#define FSL_CPM_TSA_SMC1 4 +#define FSL_CPM_TSA_SMC2 5 + +#endif From patchwork Fri Jan 13 10:37:51 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Herve Codina X-Patchwork-Id: 642234 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 vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 1F6E7C54EBD for ; Fri, 13 Jan 2023 10:39:30 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S241050AbjAMKj2 (ORCPT ); Fri, 13 Jan 2023 05:39:28 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:39298 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S241366AbjAMKik (ORCPT ); Fri, 13 Jan 2023 05:38:40 -0500 Received: from relay9-d.mail.gandi.net (relay9-d.mail.gandi.net [IPv6:2001:4b98:dc4:8::229]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 1643276AEC; Fri, 13 Jan 2023 02:38:13 -0800 (PST) Received: (Authenticated sender: herve.codina@bootlin.com) by mail.gandi.net (Postfix) with ESMTPA id 9E0EDFF818; Fri, 13 Jan 2023 10:38:09 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=gm1; t=1673606292; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=qXHsf9Wa19yWoxs/is4qp2YNvyczCu5koOilPNP2fN0=; b=JP/ZT7qdAQ3+eMrjq47/PzD/qK24UGLV3AxCORYMUo3Nzob63KQC1zEdbTLQVjKAJJJtnW a6Hb2GVuUYX6IBHfWsFkfzfpBdK61NzzeO/5Rg/4XNCfmGV+7q5VmBs9lU6vKwcefVjwic sH58y9gc8NbNHMzTB0sGpx/h/5ENwGox5K4T39w8uG76NeRx6iBIH4GXCiEsaLMhiVL2zV 9D5wvqNFVsr322m8RoP7nlcJXBmCsANjx4t7AMNKnFRhyE+0seUWPNPFeR4939wgbkbRsj 3lmnONTj5tXQYk6cmgfWzFa9tH4395mevkzwrxuwAcu0+WfM/y+2fIyjefOjaQ== From: Herve Codina To: Herve Codina , Li Yang , Rob Herring , Krzysztof Kozlowski , Liam Girdwood , Mark Brown , Christophe Leroy , Michael Ellerman , Nicholas Piggin , Qiang Zhao , Jaroslav Kysela , Takashi Iwai , Shengjiu Wang , Xiubo Li , Fabio Estevam , Nicolin Chen Cc: linuxppc-dev@lists.ozlabs.org, linux-arm-kernel@lists.infradead.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, alsa-devel@alsa-project.org, Thomas Petazzoni Subject: [PATCH v3 02/10] soc: fsl: cpm1: Add support for TSA Date: Fri, 13 Jan 2023 11:37:51 +0100 Message-Id: <20230113103759.327698-3-herve.codina@bootlin.com> X-Mailer: git-send-email 2.38.1 In-Reply-To: <20230113103759.327698-1-herve.codina@bootlin.com> References: <20230113103759.327698-1-herve.codina@bootlin.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: devicetree@vger.kernel.org The TSA (Time Slot Assigner) purpose is to route some TDM time-slots to other internal serial controllers. It is available in some PowerQUICC SoC such as the MPC885 or MPC866. It is also available on some Quicc Engine SoCs. This current version support CPM1 SoCs only and some enhancement are needed to support Quicc Engine SoCs. Signed-off-by: Herve Codina --- drivers/soc/fsl/qe/Kconfig | 11 + drivers/soc/fsl/qe/Makefile | 1 + drivers/soc/fsl/qe/tsa.c | 810 ++++++++++++++++++++++++++++++++++++ drivers/soc/fsl/qe/tsa.h | 43 ++ 4 files changed, 865 insertions(+) create mode 100644 drivers/soc/fsl/qe/tsa.c create mode 100644 drivers/soc/fsl/qe/tsa.h diff --git a/drivers/soc/fsl/qe/Kconfig b/drivers/soc/fsl/qe/Kconfig index 357c5800b112..60ec11c9f4d9 100644 --- a/drivers/soc/fsl/qe/Kconfig +++ b/drivers/soc/fsl/qe/Kconfig @@ -33,6 +33,17 @@ config UCC bool default y if UCC_FAST || UCC_SLOW +config CPM_TSA + tristate "CPM TSA support" + depends on OF && HAS_IOMEM + depends on CPM1 || (PPC && COMPILE_TEST) + help + Freescale CPM Time Slot Assigner (TSA) + controller. + + This option enables support for this + controller + config QE_TDM bool default y if FSL_UCC_HDLC diff --git a/drivers/soc/fsl/qe/Makefile b/drivers/soc/fsl/qe/Makefile index 55a555304f3a..45c961acc81b 100644 --- a/drivers/soc/fsl/qe/Makefile +++ b/drivers/soc/fsl/qe/Makefile @@ -4,6 +4,7 @@ # obj-$(CONFIG_QUICC_ENGINE)+= qe.o qe_common.o qe_ic.o qe_io.o obj-$(CONFIG_CPM) += qe_common.o +obj-$(CONFIG_CPM_TSA) += tsa.o obj-$(CONFIG_UCC) += ucc.o obj-$(CONFIG_UCC_SLOW) += ucc_slow.o obj-$(CONFIG_UCC_FAST) += ucc_fast.o diff --git a/drivers/soc/fsl/qe/tsa.c b/drivers/soc/fsl/qe/tsa.c new file mode 100644 index 000000000000..58b574cf37e2 --- /dev/null +++ b/drivers/soc/fsl/qe/tsa.c @@ -0,0 +1,810 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * TSA driver + * + * Copyright 2022 CS GROUP France + * + * Author: Herve Codina + */ + +#include "tsa.h" +#include +#include +#include +#include +#include +#include +#include + +/* TSA SI RAM routing tables entry */ +#define TSA_SIRAM_ENTRY_LAST (1 << 16) +#define TSA_SIRAM_ENTRY_BYTE (1 << 17) +#define TSA_SIRAM_ENTRY_CNT(x) (((x) & 0x0f) << 18) +#define TSA_SIRAM_ENTRY_CSEL_MASK (0x7 << 22) +#define TSA_SIRAM_ENTRY_CSEL_NU (0x0 << 22) +#define TSA_SIRAM_ENTRY_CSEL_SCC2 (0x2 << 22) +#define TSA_SIRAM_ENTRY_CSEL_SCC3 (0x3 << 22) +#define TSA_SIRAM_ENTRY_CSEL_SCC4 (0x4 << 22) +#define TSA_SIRAM_ENTRY_CSEL_SMC1 (0x5 << 22) +#define TSA_SIRAM_ENTRY_CSEL_SMC2 (0x6 << 22) + +/* SI mode register (32 bits) */ +#define TSA_SIMODE 0x00 +#define TSA_SIMODE_SMC2 0x80000000 +#define TSA_SIMODE_SMC1 0x00008000 +#define TSA_SIMODE_TDMA(x) ((x) << 0) +#define TSA_SIMODE_TDMB(x) ((x) << 16) +#define TSA_SIMODE_TDM_MASK 0x0fff +#define TSA_SIMODE_TDM_SDM_MASK 0x0c00 +#define TSA_SIMODE_TDM_SDM_NORM 0x0000 +#define TSA_SIMODE_TDM_SDM_ECHO 0x0400 +#define TSA_SIMODE_TDM_SDM_INTL_LOOP 0x0800 +#define TSA_SIMODE_TDM_SDM_LOOP_CTRL 0x0c00 +#define TSA_SIMODE_TDM_RFSD(x) ((x) << 8) +#define TSA_SIMODE_TDM_DSC 0x0080 +#define TSA_SIMODE_TDM_CRT 0x0040 +#define TSA_SIMODE_TDM_STZ 0x0020 +#define TSA_SIMODE_TDM_CE 0x0010 +#define TSA_SIMODE_TDM_FE 0x0008 +#define TSA_SIMODE_TDM_GM 0x0004 +#define TSA_SIMODE_TDM_TFSD(x) ((x) << 0) + +/* SI global mode register (8 bits) */ +#define TSA_SIGMR 0x04 +#define TSA_SIGMR_ENB (1<<3) +#define TSA_SIGMR_ENA (1<<2) +#define TSA_SIGMR_RDM_MASK 0x03 +#define TSA_SIGMR_RDM_STATIC_TDMA 0x00 +#define TSA_SIGMR_RDM_DYN_TDMA 0x01 +#define TSA_SIGMR_RDM_STATIC_TDMAB 0x02 +#define TSA_SIGMR_RDM_DYN_TDMAB 0x03 + +/* SI status register (8 bits) */ +#define TSA_SISTR 0x06 + +/* SI command register (8 bits) */ +#define TSA_SICMR 0x07 + +/* SI clock route register (32 bits) */ +#define TSA_SICR 0x0C +#define TSA_SICR_SCC2(x) ((x) << 8) +#define TSA_SICR_SCC3(x) ((x) << 16) +#define TSA_SICR_SCC4(x) ((x) << 24) +#define TSA_SICR_SCC_MASK 0x0ff +#define TSA_SICR_SCC_GRX (1 << 7) +#define TSA_SICR_SCC_SCX_TSA (1 << 6) +#define TSA_SICR_SCC_RXCS_MASK (0x7 << 3) +#define TSA_SICR_SCC_RXCS_BRG1 (0x0 << 3) +#define TSA_SICR_SCC_RXCS_BRG2 (0x1 << 3) +#define TSA_SICR_SCC_RXCS_BRG3 (0x2 << 3) +#define TSA_SICR_SCC_RXCS_BRG4 (0x3 << 3) +#define TSA_SICR_SCC_RXCS_CLK15 (0x4 << 3) +#define TSA_SICR_SCC_RXCS_CLK26 (0x5 << 3) +#define TSA_SICR_SCC_RXCS_CLK37 (0x6 << 3) +#define TSA_SICR_SCC_RXCS_CLK48 (0x7 << 3) +#define TSA_SICR_SCC_TXCS_MASK (0x7 << 0) +#define TSA_SICR_SCC_TXCS_BRG1 (0x0 << 0) +#define TSA_SICR_SCC_TXCS_BRG2 (0x1 << 0) +#define TSA_SICR_SCC_TXCS_BRG3 (0x2 << 0) +#define TSA_SICR_SCC_TXCS_BRG4 (0x3 << 0) +#define TSA_SICR_SCC_TXCS_CLK15 (0x4 << 0) +#define TSA_SICR_SCC_TXCS_CLK26 (0x5 << 0) +#define TSA_SICR_SCC_TXCS_CLK37 (0x6 << 0) +#define TSA_SICR_SCC_TXCS_CLK48 (0x7 << 0) + +/* Serial interface RAM pointer register (32 bits) */ +#define TSA_SIRP 0x10 + +struct tsa_entries_area { + void *__iomem entries_start; + void *__iomem entries_next; + void *__iomem last_entry; +}; + +struct tsa_tdm { + bool is_enable; + struct clk *l1rclk_clk; + struct clk *l1rsync_clk; + struct clk *l1tclk_clk; + struct clk *l1tsync_clk; + u32 simode_tdm; +}; + +#define TSA_TDMA 0 +#define TSA_TDMB 1 + +struct tsa { + struct device *dev; + void *__iomem si_regs; + void *__iomem si_ram; + resource_size_t si_ram_sz; + int tdms; /* TSA_TDMx ORed */ + struct tsa_tdm tdm[2]; /* TDMa and TDMb */ + struct tsa_cell_info cell_infos[6]; +}; + +static inline void tsa_write32(void *__iomem addr, u32 val) +{ + iowrite32be(val, addr); +} + +static inline u32 tsa_read32(void *__iomem addr) +{ + return ioread32be(addr); +} + +static inline void tsa_clrbits32(void *__iomem addr, u32 clr) +{ + tsa_write32(addr, tsa_read32(addr) & ~clr); +} + +static inline void tsa_clrsetbits32(void *__iomem addr, u32 clr, u32 set) +{ + tsa_write32(addr, (tsa_read32(addr) & ~clr) | set); +} + +int tsa_connect(struct tsa *tsa, unsigned int cell_index) +{ + u32 clear; + u32 set; + + switch (cell_index) { + case FSL_CPM_TSA_SCC2: + clear = TSA_SICR_SCC2(TSA_SICR_SCC_MASK); + set = TSA_SICR_SCC2(TSA_SICR_SCC_SCX_TSA); + break; + case FSL_CPM_TSA_SCC3: + clear = TSA_SICR_SCC3(TSA_SICR_SCC_MASK); + set = TSA_SICR_SCC3(TSA_SICR_SCC_SCX_TSA); + break; + case FSL_CPM_TSA_SCC4: + clear = TSA_SICR_SCC4(TSA_SICR_SCC_MASK); + set = TSA_SICR_SCC4(TSA_SICR_SCC_SCX_TSA); + break; + default: + dev_err(tsa->dev, "Unsupported cell-index %u\n", cell_index); + return -EINVAL; + } + + tsa_clrsetbits32(tsa->si_regs + TSA_SICR, clear, set); + return 0; +} +EXPORT_SYMBOL(tsa_connect); + +int tsa_disconnect(struct tsa *tsa, unsigned int cell_index) +{ + u32 clear; + + switch (cell_index) { + case 2: + clear = TSA_SICR_SCC2(TSA_SICR_SCC_MASK); + break; + case 3: + clear = TSA_SICR_SCC3(TSA_SICR_SCC_MASK); + break; + case 4: + clear = TSA_SICR_SCC4(TSA_SICR_SCC_MASK); + break; + default: + dev_err(tsa->dev, "Unsupported cell-index %u\n", cell_index); + return -EINVAL; + } + + tsa_clrsetbits32(tsa->si_regs + TSA_SICR, clear, 0); + return 0; +} +EXPORT_SYMBOL(tsa_disconnect); + +int tsa_get_info(struct tsa *tsa, unsigned int cell_id, struct tsa_cell_info *info) +{ + if (cell_id >= ARRAY_SIZE(tsa->cell_infos)) + return -EINVAL; + + memcpy(info, &tsa->cell_infos[cell_id], sizeof(*info)); + return 0; +} +EXPORT_SYMBOL(tsa_get_info); + +static void tsa_init_entries_area(struct tsa *tsa, struct tsa_entries_area *area, + u32 tdms, u32 tdm_id, bool is_rx) +{ + resource_size_t quarter; + resource_size_t half; + + quarter = tsa->si_ram_sz/4; + half = tsa->si_ram_sz/2; + + if (tdms == BIT(TSA_TDMA)) { + /* Only TDMA */ + if (is_rx) { + /* First half of si_ram */ + area->entries_start = tsa->si_ram; + area->entries_next = area->entries_start + half; + area->last_entry = NULL; + } else { + /* Second half of si_ram */ + area->entries_start = tsa->si_ram + half; + area->entries_next = area->entries_start + half; + area->last_entry = NULL; + } + } else { + /* Only TDMB or both TDMs */ + if (tdm_id == TSA_TDMA) { + if (is_rx) { + /* First half of first half of si_ram */ + area->entries_start = tsa->si_ram; + area->entries_next = area->entries_start + quarter; + area->last_entry = NULL; + } else { + /* First half of second half of si_ram */ + area->entries_start = tsa->si_ram + (2 * quarter); + area->entries_next = area->entries_start + quarter; + area->last_entry = NULL; + } + } else { + if (is_rx) { + /* Second half of first half of si_ram */ + area->entries_start = tsa->si_ram + quarter; + area->entries_next = area->entries_start + quarter; + area->last_entry = NULL; + } else { + /* Second half of second half of si_ram */ + area->entries_start = tsa->si_ram + (3 * quarter); + area->entries_next = area->entries_start + quarter; + area->last_entry = NULL; + } + } + } +} + +static const char *tsa_cell_id2name(struct tsa *tsa, u32 cell_id) +{ + switch (cell_id) { + case FSL_CPM_TSA_NU: return "Not used"; + case FSL_CPM_TSA_SCC2: return "SCC2"; + case FSL_CPM_TSA_SCC3: return "SCC3"; + case FSL_CPM_TSA_SCC4: return "SCC4"; + case FSL_CPM_TSA_SMC1: return "SMC1"; + case FSL_CPM_TSA_SMC2: return "SMC2"; + default: + break; + } + return NULL; +} + +static u32 tsa_cell_id2csel(struct tsa *tsa, u32 cell_id) +{ + switch (cell_id) { + case FSL_CPM_TSA_SCC2: return TSA_SIRAM_ENTRY_CSEL_SCC2; + case FSL_CPM_TSA_SCC3: return TSA_SIRAM_ENTRY_CSEL_SCC3; + case FSL_CPM_TSA_SCC4: return TSA_SIRAM_ENTRY_CSEL_SCC4; + case FSL_CPM_TSA_SMC1: return TSA_SIRAM_ENTRY_CSEL_SMC1; + case FSL_CPM_TSA_SMC2: return TSA_SIRAM_ENTRY_CSEL_SMC2; + default: + break; + } + return TSA_SIRAM_ENTRY_CSEL_NU; +} + +static int tsa_add_entry(struct tsa *tsa, struct tsa_entries_area *area, + u32 count, u32 cell_id) +{ + void *__iomem addr; + u32 left; + u32 val; + u32 cnt; + u32 nb; + + addr = area->last_entry ? area->last_entry + 4 : area->entries_start; + + nb = DIV_ROUND_UP(count, 8); + if ((addr + (nb * 4)) > area->entries_next) { + dev_err(tsa->dev, "si ram area full\n"); + return -ENOSPC; + } + + if (area->last_entry) { + /* Clear last flag */ + tsa_clrbits32(area->last_entry, TSA_SIRAM_ENTRY_LAST); + } + + left = count; + while (left) { + val = TSA_SIRAM_ENTRY_BYTE | tsa_cell_id2csel(tsa, cell_id); + + if (left > 16) { + cnt = 16; + } else { + cnt = left; + val |= TSA_SIRAM_ENTRY_LAST; + area->last_entry = addr; + } + val |= TSA_SIRAM_ENTRY_CNT(cnt - 1); + + tsa_write32(addr, val); + addr += 4; + left -= cnt; + } + + return 0; +} + +static int tsa_of_parse_tdm_route(struct tsa *tsa, struct device_node *tdm_np, + u32 tdms, u32 tdm_id, bool is_rx) +{ + struct tsa_entries_area area; + const char *route_name; + int len, i; + u32 count; + u32 cell_id; + const char *cell_name; + struct tsa_cell_info *cell_info; + struct tsa_tdm *tdm; + int ret; + u32 ts; + + route_name = is_rx ? "fsl,rx-ts-routes" : "fsl,tx-ts-routes"; + + len = of_property_count_u32_elems(tdm_np, route_name); + if (len < 0) { + dev_err(tsa->dev, "%pOF: failed to read %s\n", tdm_np, route_name); + return len; + } + if (len % 2 != 0) { + dev_err(tsa->dev, "%pOF: wrong %s format\n", tdm_np, route_name); + return -EINVAL; + } + + tsa_init_entries_area(tsa, &area, tdms, tdm_id, is_rx); + ts = 0; + for (i = 0; i < len; i += 2) { + of_property_read_u32_index(tdm_np, route_name, i, &count); + of_property_read_u32_index(tdm_np, route_name, i + 1, &cell_id); + + if (cell_id >= ARRAY_SIZE(tsa->cell_infos)) { + dev_err(tsa->dev, "%pOF: invalid cell id (%u)\n", tdm_np, + cell_id); + return -EINVAL; + } + + cell_name = tsa_cell_id2name(tsa, cell_id); + if (!cell_name) { + dev_err(tsa->dev, "%pOF: unsupported cell id (%u)\n", tdm_np, + cell_id); + return -EINVAL; + } + + dev_dbg(tsa->dev, "tdm_id=%u, %s ts %u..%u -> %s\n", + tdm_id, route_name, ts, ts+count-1, cell_name); + ts += count; + + ret = tsa_add_entry(tsa, &area, count, cell_id); + if (ret) + return ret; + + cell_info = &tsa->cell_infos[cell_id]; + tdm = &tsa->tdm[tdm_id]; + if (is_rx) { + cell_info->rx_fs_rate = clk_get_rate(tdm->l1rsync_clk); + cell_info->rx_bit_rate = clk_get_rate(tdm->l1rclk_clk); + cell_info->nb_rx_ts += count; + } else { + cell_info->tx_fs_rate = tdm->l1tsync_clk ? + clk_get_rate(tdm->l1tsync_clk) : + clk_get_rate(tdm->l1rsync_clk); + cell_info->tx_bit_rate = tdm->l1tclk_clk ? + clk_get_rate(tdm->l1tclk_clk) : + clk_get_rate(tdm->l1rclk_clk); + cell_info->nb_tx_ts += count; + } + } + return 0; +} + +static inline int tsa_of_parse_tdm_rx_route(struct tsa *tsa, + struct device_node *tdm_np, + u32 tdms, u32 tdm_id) +{ + return tsa_of_parse_tdm_route(tsa, tdm_np, tdms, tdm_id, true); +} + +static inline int tsa_of_parse_tdm_tx_route(struct tsa *tsa, + struct device_node *tdm_np, + u32 tdms, u32 tdm_id) +{ + return tsa_of_parse_tdm_route(tsa, tdm_np, tdms, tdm_id, false); +} + +static int tsa_of_parse_tdms(struct tsa *tsa, struct device_node *np) +{ + struct device_node *tdm_np; + struct tsa_tdm *tdm; + struct clk *clk; + const char *mode; + u32 tdm_id, val; + int ret; + int i; + + tsa->tdms = 0; + tsa->tdm[0].is_enable = false; + tsa->tdm[1].is_enable = false; + + for_each_available_child_of_node(np, tdm_np) { + ret = of_property_read_u32(tdm_np, "reg", &tdm_id); + if (ret) { + dev_err(tsa->dev, "%pOF: failed to read reg\n", tdm_np); + of_node_put(tdm_np); + return ret; + } + switch (tdm_id) { + case 0: + tsa->tdms |= BIT(TSA_TDMA); + break; + case 1: + tsa->tdms |= BIT(TSA_TDMB); + break; + default: + dev_err(tsa->dev, "%pOF: Invalid tdm_id (%u)\n", tdm_np, + tdm_id); + of_node_put(tdm_np); + return -EINVAL; + } + } + + for_each_available_child_of_node(np, tdm_np) { + ret = of_property_read_u32(tdm_np, "reg", &tdm_id); + if (ret) { + dev_err(tsa->dev, "%pOF: failed to read reg\n", tdm_np); + of_node_put(tdm_np); + return ret; + } + + tdm = &tsa->tdm[tdm_id]; + + mode = "normal"; + ret = of_property_read_string(tdm_np, "fsl,mode", &mode); + if (ret && ret != -EINVAL) { + dev_err(tsa->dev, "%pOF: failed to read fsl,mode\n", tdm_np); + of_node_put(tdm_np); + return ret; + } + if (!strcmp(mode, "normal")) { + tdm->simode_tdm |= TSA_SIMODE_TDM_SDM_NORM; + } else if (!strcmp(mode, "echo")) { + tdm->simode_tdm |= TSA_SIMODE_TDM_SDM_ECHO; + } else if (!strcmp(mode, "internal-loopback")) { + tdm->simode_tdm |= TSA_SIMODE_TDM_SDM_INTL_LOOP; + } else if (!strcmp(mode, "control-loopback")) { + tdm->simode_tdm |= TSA_SIMODE_TDM_SDM_LOOP_CTRL; + } else { + dev_err(tsa->dev, "%pOF: Invalid fsl,mode (%s)\n", tdm_np, + mode); + of_node_put(tdm_np); + return -EINVAL; + } + + val = 0; + ret = of_property_read_u32(tdm_np, "fsl,rx-frame-sync-delay-bits", + &val); + if (ret && ret != -EINVAL) { + dev_err(tsa->dev, + "%pOF: failed to read fsl,rx-frame-sync-delay-bits\n", + tdm_np); + of_node_put(tdm_np); + return ret; + } + if (val > 3) { + dev_err(tsa->dev, + "%pOF: Invalid fsl,rx-frame-sync-delay-bits (%u)\n", + tdm_np, val); + of_node_put(tdm_np); + return -EINVAL; + } + tdm->simode_tdm |= TSA_SIMODE_TDM_RFSD(val); + + val = 0; + ret = of_property_read_u32(tdm_np, "fsl,tx-frame-sync-delay-bits", + &val); + if (ret && ret != -EINVAL) { + dev_err(tsa->dev, + "%pOF: failed to read fsl,tx-frame-sync-delay-bits\n", + tdm_np); + of_node_put(tdm_np); + return ret; + } + if (val > 3) { + dev_err(tsa->dev, + "%pOF: Invalid fsl,tx-frame-sync-delay-bits (%u)\n", + tdm_np, val); + of_node_put(tdm_np); + return -EINVAL; + } + tdm->simode_tdm |= TSA_SIMODE_TDM_TFSD(val); + + if (of_property_read_bool(tdm_np, "fsl,common-rxtx-pins")) + tdm->simode_tdm |= TSA_SIMODE_TDM_CRT; + + if (of_property_read_bool(tdm_np, "fsl,clock-falling-edge")) + tdm->simode_tdm |= TSA_SIMODE_TDM_CE; + + if (of_property_read_bool(tdm_np, "fsl,fsync-rising-edge")) + tdm->simode_tdm |= TSA_SIMODE_TDM_FE; + + if (of_property_read_bool(tdm_np, "fsl,double-speed-clock")) + tdm->simode_tdm |= TSA_SIMODE_TDM_DSC; + + clk = of_clk_get_by_name(tdm_np, "l1rsync"); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + of_node_put(tdm_np); + goto err; + } + ret = clk_prepare_enable(clk); + if (ret) { + clk_put(clk); + of_node_put(tdm_np); + goto err; + } + tdm->l1rsync_clk = clk; + + clk = of_clk_get_by_name(tdm_np, "l1rclk"); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + of_node_put(tdm_np); + goto err; + } + ret = clk_prepare_enable(clk); + if (ret) { + clk_put(clk); + of_node_put(tdm_np); + goto err; + } + tdm->l1rclk_clk = clk; + + if (!(tdm->simode_tdm & TSA_SIMODE_TDM_CRT)) { + clk = of_clk_get_by_name(tdm_np, "l1tsync"); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + of_node_put(tdm_np); + goto err; + } + ret = clk_prepare_enable(clk); + if (ret) { + clk_put(clk); + of_node_put(tdm_np); + goto err; + } + tdm->l1tsync_clk = clk; + + clk = of_clk_get_by_name(tdm_np, "l1tclk"); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + of_node_put(tdm_np); + goto err; + } + ret = clk_prepare_enable(clk); + if (ret) { + clk_put(clk); + of_node_put(tdm_np); + goto err; + } + tdm->l1tclk_clk = clk; + } + + ret = tsa_of_parse_tdm_rx_route(tsa, tdm_np, tsa->tdms, tdm_id); + if (ret) { + of_node_put(tdm_np); + goto err; + } + + ret = tsa_of_parse_tdm_tx_route(tsa, tdm_np, tsa->tdms, tdm_id); + if (ret) { + of_node_put(tdm_np); + goto err; + } + + tdm->is_enable = true; + } + return 0; + +err: + for (i = 0; i < 2; i++) { + if (tsa->tdm[i].l1rsync_clk) { + clk_disable_unprepare(tsa->tdm[i].l1rsync_clk); + clk_put(tsa->tdm[i].l1rsync_clk); + } + if (tsa->tdm[i].l1rclk_clk) { + clk_disable_unprepare(tsa->tdm[i].l1rclk_clk); + clk_put(tsa->tdm[i].l1rclk_clk); + } + if (tsa->tdm[i].l1tsync_clk) { + clk_disable_unprepare(tsa->tdm[i].l1rsync_clk); + clk_put(tsa->tdm[i].l1rsync_clk); + } + if (tsa->tdm[i].l1tclk_clk) { + clk_disable_unprepare(tsa->tdm[i].l1rclk_clk); + clk_put(tsa->tdm[i].l1rclk_clk); + } + } + return ret; +} + +static void tsa_init_si_ram(struct tsa *tsa) +{ + resource_size_t i; + + /* Fill all entries as the last one */ + for (i = 0; i < tsa->si_ram_sz; i += 4) + tsa_write32(tsa->si_ram + i, TSA_SIRAM_ENTRY_LAST); +} + +static int tsa_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct resource *res; + struct tsa *tsa; + u32 val; + int ret; + + tsa = devm_kzalloc(&pdev->dev, sizeof(*tsa), GFP_KERNEL); + if (!tsa) + return -ENOMEM; + + tsa->dev = &pdev->dev; + + tsa->si_regs = devm_platform_ioremap_resource_byname(pdev, "si_regs"); + if (IS_ERR(tsa->si_regs)) + return PTR_ERR(tsa->si_regs); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "si_ram"); + if (!res) { + dev_err(tsa->dev, "si_ram resource missing\n"); + return -EINVAL; + } + tsa->si_ram_sz = resource_size(res); + tsa->si_ram = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(tsa->si_ram)) + return PTR_ERR(tsa->si_ram); + + tsa_init_si_ram(tsa); + + ret = tsa_of_parse_tdms(tsa, np); + if (ret) + return ret; + + /* Set SIMODE */ + val = 0; + if (tsa->tdm[0].is_enable) + val |= TSA_SIMODE_TDMA(tsa->tdm[0].simode_tdm); + if (tsa->tdm[1].is_enable) + val |= TSA_SIMODE_TDMB(tsa->tdm[1].simode_tdm); + + tsa_clrsetbits32(tsa->si_regs + TSA_SIMODE, + TSA_SIMODE_TDMA(TSA_SIMODE_TDM_MASK) | + TSA_SIMODE_TDMB(TSA_SIMODE_TDM_MASK), + val); + + /* Set SIGMR */ + val = (tsa->tdms == BIT(TSA_TDMA)) ? + TSA_SIGMR_RDM_STATIC_TDMA : TSA_SIGMR_RDM_STATIC_TDMAB; + if (tsa->tdms & BIT(TSA_TDMA)) + val |= TSA_SIGMR_ENA; + if (tsa->tdms & BIT(TSA_TDMB)) + val |= TSA_SIGMR_ENB; + out_8(tsa->si_regs + TSA_SIGMR, val); + + platform_set_drvdata(pdev, tsa); + + return 0; +} + +static int tsa_remove(struct platform_device *pdev) +{ + struct tsa *tsa = platform_get_drvdata(pdev); + int i; + + for (i = 0; i < 2; i++) { + if (tsa->tdm[i].l1rsync_clk) { + clk_disable_unprepare(tsa->tdm[i].l1rsync_clk); + clk_put(tsa->tdm[i].l1rsync_clk); + } + if (tsa->tdm[i].l1rclk_clk) { + clk_disable_unprepare(tsa->tdm[i].l1rclk_clk); + clk_put(tsa->tdm[i].l1rclk_clk); + } + if (tsa->tdm[i].l1tsync_clk) { + clk_disable_unprepare(tsa->tdm[i].l1rsync_clk); + clk_put(tsa->tdm[i].l1rsync_clk); + } + if (tsa->tdm[i].l1tclk_clk) { + clk_disable_unprepare(tsa->tdm[i].l1rclk_clk); + clk_put(tsa->tdm[i].l1rclk_clk); + } + } + return 0; +} + +static const struct of_device_id tsa_id_table[] = { + { .compatible = "fsl,cpm1-tsa" }, + {} /* sentinel */ +}; +MODULE_DEVICE_TABLE(of, tsa_id_table); + +static struct platform_driver tsa_driver = { + .driver = { + .name = "fsl-tsa", + .of_match_table = of_match_ptr(tsa_id_table), + }, + .probe = tsa_probe, + .remove = tsa_remove, +}; +module_platform_driver(tsa_driver); + +struct tsa *tsa_get_byphandle(struct device_node *np, const char *phandle_name) +{ + struct platform_device *pdev; + struct device_node *tsa_np; + struct tsa *tsa; + + tsa_np = of_parse_phandle(np, phandle_name, 0); + if (!tsa_np) + return ERR_PTR(-EINVAL); + + if (!of_match_node(tsa_driver.driver.of_match_table, tsa_np)) { + of_node_put(tsa_np); + return ERR_PTR(-EINVAL); + } + + pdev = of_find_device_by_node(tsa_np); + of_node_put(tsa_np); + if (!pdev) + return ERR_PTR(-ENODEV); + + tsa = platform_get_drvdata(pdev); + if (!tsa) { + platform_device_put(pdev); + return ERR_PTR(-EPROBE_DEFER); + } + + return tsa; +} +EXPORT_SYMBOL(tsa_get_byphandle); + +void tsa_put(struct tsa *tsa) +{ + put_device(tsa->dev); +} +EXPORT_SYMBOL(tsa_put); + +static void devm_tsa_release(struct device *dev, void *res) +{ + struct tsa **tsa = res; + + tsa_put(*tsa); +} + +struct tsa *devm_tsa_get_byphandle(struct device *dev, struct device_node *np, + const char *phandle_name) +{ + struct tsa *tsa; + struct tsa **dr; + + dr = devres_alloc(devm_tsa_release, sizeof(*dr), GFP_KERNEL); + if (!dr) + return ERR_PTR(-ENOMEM); + + tsa = tsa_get_byphandle(np, phandle_name); + if (!IS_ERR(tsa)) { + *dr = tsa; + devres_add(dev, dr); + } else { + devres_free(dr); + } + + return tsa; +} +EXPORT_SYMBOL(devm_tsa_get_byphandle); + +MODULE_AUTHOR("Herve Codina "); +MODULE_DESCRIPTION("CPM TSA driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/soc/fsl/qe/tsa.h b/drivers/soc/fsl/qe/tsa.h new file mode 100644 index 000000000000..b253d2a17955 --- /dev/null +++ b/drivers/soc/fsl/qe/tsa.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * TSA management + * + * Copyright 2022 CS GROUP France + * + * Author: Herve Codina + */ +#ifndef __SOC_FSL_TSA_H__ +#define __SOC_FSL_TSA_H__ + +#include +#include + +struct device_node; +struct device; +struct tsa; + +struct tsa *tsa_get_byphandle(struct device_node *np, const char *phandle_name); +void tsa_put(struct tsa *tsa); +struct tsa *devm_tsa_get_byphandle(struct device *dev, struct device_node *np, + const char *phandle_name); + +/* Connect and disconnect. cell_id is one of FSL_CPM_TSA_* available in + * dt-bindings/soc/fsl-fsa.h + */ +int tsa_connect(struct tsa *tsa, unsigned int cell_id); +int tsa_disconnect(struct tsa *tsa, unsigned int cell_id); + +/* Cell information */ +struct tsa_cell_info { + unsigned long rx_fs_rate; + unsigned long rx_bit_rate; + u8 nb_rx_ts; + unsigned long tx_fs_rate; + unsigned long tx_bit_rate; + u8 nb_tx_ts; +}; + +/* Get information */ +int tsa_get_info(struct tsa *tsa, unsigned int cell_id, struct tsa_cell_info *info); + +#endif /* __SOC_FSL_TSA_H__ */ From patchwork Fri Jan 13 10:37:54 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Herve Codina X-Patchwork-Id: 642233 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 vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id CA0EAC61DB3 for ; Fri, 13 Jan 2023 10:40:09 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S241386AbjAMKkH (ORCPT ); Fri, 13 Jan 2023 05:40:07 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:37518 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S241219AbjAMKjF (ORCPT ); Fri, 13 Jan 2023 05:39:05 -0500 Received: from relay9-d.mail.gandi.net (relay9-d.mail.gandi.net [217.70.183.199]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id BC49176EC1; Fri, 13 Jan 2023 02:38:25 -0800 (PST) Received: (Authenticated sender: herve.codina@bootlin.com) by mail.gandi.net (Postfix) with ESMTPA id 92BDDFF805; Fri, 13 Jan 2023 10:38:20 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=gm1; t=1673606304; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=A3gOwLKCbvhD9y82aQ+8CVVXVgQCqL1sYfV2qaO4J0s=; b=oEwq0mwR++GxfPwOyL9g/gotpNb4BJb18kxYNZqdo29VkXCw957txr1TDP4S28C0D3Z1Ck OtBDsMhwgkJOTHQ7+IF/53mZfL5b+Ik+F7LdLSOHlpU2zZFK8y4ZlMC7y1vGqxqe0ucE2C eBfmph+BIUGVPRWxMkufn3TtXfnwUsHqgEyeN2GZqgFXwdGyXM4+EycQNM2YUbHYUHmUtj Loizg7LqRputAm5V6siUc0eNyey2vsFdZnmfs4xO+xRqJUEegv5ats9LjjbU5+nDnaaGga 12xyQm6++hEIxNOrvmBygua6G3bp6dELzTFn4dg7FkXKf2Fpjet66kImXRqhrQ== From: Herve Codina To: Herve Codina , Li Yang , Rob Herring , Krzysztof Kozlowski , Liam Girdwood , Mark Brown , Christophe Leroy , Michael Ellerman , Nicholas Piggin , Qiang Zhao , Jaroslav Kysela , Takashi Iwai , Shengjiu Wang , Xiubo Li , Fabio Estevam , Nicolin Chen Cc: linuxppc-dev@lists.ozlabs.org, linux-arm-kernel@lists.infradead.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, alsa-devel@alsa-project.org, Thomas Petazzoni Subject: [PATCH v3 05/10] dt-bindings: soc: fsl: cpm_qe: Add QMC controller Date: Fri, 13 Jan 2023 11:37:54 +0100 Message-Id: <20230113103759.327698-6-herve.codina@bootlin.com> X-Mailer: git-send-email 2.38.1 In-Reply-To: <20230113103759.327698-1-herve.codina@bootlin.com> References: <20230113103759.327698-1-herve.codina@bootlin.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: devicetree@vger.kernel.org Add support for the QMC (QUICC Multichannel Controller) available in some PowerQUICC SoC such as MPC885 or MPC866. Signed-off-by: Herve Codina --- .../bindings/soc/fsl/cpm_qe/fsl,qmc.yaml | 164 ++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 Documentation/devicetree/bindings/soc/fsl/cpm_qe/fsl,qmc.yaml diff --git a/Documentation/devicetree/bindings/soc/fsl/cpm_qe/fsl,qmc.yaml b/Documentation/devicetree/bindings/soc/fsl/cpm_qe/fsl,qmc.yaml new file mode 100644 index 000000000000..3ec52f1635c8 --- /dev/null +++ b/Documentation/devicetree/bindings/soc/fsl/cpm_qe/fsl,qmc.yaml @@ -0,0 +1,164 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/soc/fsl/cpm_qe/fsl,qmc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: PowerQUICC CPM QUICC Multichannel Controller (QMC) + +maintainers: + - Herve Codina + +description: | + The QMC (QUICC Multichannel Controller) emulates up to 64 channels within + one serial controller using the same TDM physical interface routed from + TSA. + +properties: + compatible: + items: + - enum: + - fsl,mpc885-scc-qmc + - fsl,mpc866-scc-qmc + - const: fsl,cpm1-scc-qmc + + reg: + items: + - description: SCC (Serial communication controller) register base + - description: SCC parameter ram base + - description: Dual port ram base + + reg-names: + items: + - const: scc_regs + - const: scc_pram + - const: dpram + + interrupts: + maxItems: 1 + description: SCC interrupt line in the CPM interrupt controller + + fsl,tsa: + $ref: /schemas/types.yaml#/definitions/phandle + description: phandle to the TSA + + fsl,tsa-cell-id: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [1, 2, 3] + description: | + TSA cell ID (dt-bindings/soc/fsl,tsa.h defines these values) + - 1: SCC2 + - 2: SCC3 + - 3: SCC4 + + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + + '#chan-cells': + const: 1 + +patternProperties: + '^channel@([0-9]|[1-5][0-9]|6[0-3])$': + description: + A channel managed by this controller + type: object + + properties: + reg: + minimum: 0 + maximum: 63 + description: + The channel number + + fsl,mode: + $ref: /schemas/types.yaml#/definitions/string + enum: [transparent, hdlc] + default: transparent + description: Operational mode + + fsl,reverse-data: + $ref: /schemas/types.yaml#/definitions/flag + description: + The bit order as seen on the channels is reversed, + transmitting/receiving the MSB of each octet first. + This flag is used only in 'transparent' mode. + + fsl,tx-ts-mask: + $ref: /schemas/types.yaml#/definitions/uint64 + description: + Channel assigned Tx time-slots within the Tx time-slots routed + by the TSA to this cell. + + fsl,rx-ts-mask: + $ref: /schemas/types.yaml#/definitions/uint64 + description: + Channel assigned Rx time-slots within the Rx time-slots routed + by the TSA to this cell. + + required: + - reg + - fsl,tx-ts-mask + - fsl,rx-ts-mask + +required: + - compatible + - reg + - reg-names + - interrupts + - fsl,tsa + - fsl,tsa-cell-id + - '#address-cells' + - '#size-cells' + - '#chan-cells' + +additionalProperties: false + +examples: + - | + #include + + qmc@a60 { + compatible = "fsl,mpc885-scc-qmc", "fsl,cpm1-scc-qmc"; + reg = <0xa60 0x20>, + <0x3f00 0xc0>, + <0x2000 0x1000>; + reg-names = "scc_regs", "scc_pram", "dpram"; + interrupts = <27>; + interrupt-parent = <&CPM_PIC>; + + #address-cells = <1>; + #size-cells = <0>; + #chan-cells = <1>; + + fsl,tsa = <&tsa>; + fsl,tsa-cell-id = ; + + channel@16 { + /* Ch16 : First 4 even TS from all routed from TSA */ + reg = <16>; + fsl,mode = "transparent"; + fsl,reverse-data; + fsl,tx-ts-mask = <0x00000000 0x000000aa>; + fsl,rx-ts-mask = <0x00000000 0x000000aa>; + }; + + channel@17 { + /* Ch17 : First 4 odd TS from all routed from TSA */ + reg = <17>; + fsl,mode = "transparent"; + fsl,reverse-data; + fsl,tx-ts-mask = <0x00000000 0x00000055>; + fsl,rx-ts-mask = <0x00000000 0x00000055>; + }; + + channel@19 { + /* Ch19 : 8 TS (TS 8..15) from all routed from TSA */ + reg = <19>; + fsl,mode = "hdlc"; + fsl,tx-ts-mask = <0x00000000 0x0000ff00>; + fsl,rx-ts-mask = <0x00000000 0x0000ff00>; + }; + }; From patchwork Fri Jan 13 10:37:57 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Herve Codina X-Patchwork-Id: 642232 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 vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id B6875C677F1 for ; Fri, 13 Jan 2023 10:40:14 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S241219AbjAMKkM (ORCPT ); Fri, 13 Jan 2023 05:40:12 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:39092 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S241331AbjAMKjH (ORCPT ); Fri, 13 Jan 2023 05:39:07 -0500 Received: from relay9-d.mail.gandi.net (relay9-d.mail.gandi.net [217.70.183.199]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id A150076AEB; Fri, 13 Jan 2023 02:38:36 -0800 (PST) Received: (Authenticated sender: herve.codina@bootlin.com) by mail.gandi.net (Postfix) with ESMTPA id 069CAFF81B; Fri, 13 Jan 2023 10:38:32 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=gm1; t=1673606315; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=ZqR1sbzJlEP7+ZXzooqfoTsDW4e3sPJzR7dwt0b3cSs=; b=XlOprv3ZimuihHQZvNbxcgHZYx3GRRS/kt8qkLz0LvmzfBkZGHV0LeXQScDykpF5WgsIgA q8/sJmyiBZIUk6l74Xw/l4ejMwY9VUtgzSK13vex1i25TlV0kcQfnU8PYwsE+ZCAoT7eKs NqZbpfcOqzCVN7Ozg21RoPdQhcHEg9G2bRTyi4etOE5eVz3ZtangySUvdVqnzadhheKqE6 jLK/Wza3mDbkPajmXlphBWit9011Pfjsyw07xo/ruvsPhH1F+hMxz+NaxqzyhJATnYIP1r CyomeAJ07UihdEIpNnzot0t13kCHgFyShvQPu9dK+vqCMcrPLtjGx3jsFsMJYw== From: Herve Codina To: Herve Codina , Li Yang , Rob Herring , Krzysztof Kozlowski , Liam Girdwood , Mark Brown , Christophe Leroy , Michael Ellerman , Nicholas Piggin , Qiang Zhao , Jaroslav Kysela , Takashi Iwai , Shengjiu Wang , Xiubo Li , Fabio Estevam , Nicolin Chen Cc: linuxppc-dev@lists.ozlabs.org, linux-arm-kernel@lists.infradead.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, alsa-devel@alsa-project.org, Thomas Petazzoni Subject: [PATCH v3 08/10] dt-bindings: sound: Add support for QMC audio Date: Fri, 13 Jan 2023 11:37:57 +0100 Message-Id: <20230113103759.327698-9-herve.codina@bootlin.com> X-Mailer: git-send-email 2.38.1 In-Reply-To: <20230113103759.327698-1-herve.codina@bootlin.com> References: <20230113103759.327698-1-herve.codina@bootlin.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: devicetree@vger.kernel.org The QMC (QUICC mutichannel controller) is a controller present in some PowerQUICC SoC such as MPC885. The QMC audio is an ASoC component that uses the QMC controller to transfer the audio data. Signed-off-by: Herve Codina --- .../bindings/sound/fsl,qmc-audio.yaml | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/fsl,qmc-audio.yaml diff --git a/Documentation/devicetree/bindings/sound/fsl,qmc-audio.yaml b/Documentation/devicetree/bindings/sound/fsl,qmc-audio.yaml new file mode 100644 index 000000000000..ff5cd9241941 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/fsl,qmc-audio.yaml @@ -0,0 +1,117 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/fsl,qmc-audio.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: QMC audio + +maintainers: + - Herve Codina + +description: | + The QMC audio is an ASoC component which uses QMC (QUICC Multichannel + Controller) channels to transfer the audio data. + It provides as many DAI as the number of QMC channel used. + +allOf: + - $ref: dai-common.yaml# + +properties: + compatible: + const: fsl,qmc-audio + + '#address-cells': + const: 1 + '#size-cells': + const: 0 + '#sound-dai-cells': + const: 1 + +patternProperties: + '^dai@([0-9]|[1-5][0-9]|6[0-3])$': + description: + A DAI managed by this controller + type: object + + properties: + reg: + minimum: 0 + maximum: 63 + description: + The DAI number + + fsl,qmc-chan: + $ref: /schemas/types.yaml#/definitions/phandle-array + items: + - items: + - description: phandle to QMC node + - description: Channel number + description: + Should be a phandle/number pair. The phandle to QMC node and the QMC + channel to use for this DAI. + + required: + - reg + - fsl,qmc-chan + +required: + - compatible + - '#address-cells' + - '#size-cells' + - '#sound-dai-cells' + +additionalProperties: false + +examples: + - | + audio_controller: audio-controller { + compatible = "fsl,qmc-audio"; + #address-cells = <1>; + #size-cells = <0>; + #sound-dai-cells = <1>; + dai@16 { + reg = <16>; + fsl,qmc-chan = <&qmc 16>; + }; + dai@17 { + reg = <17>; + fsl,qmc-chan = <&qmc 17>; + }; + }; + + sound { + compatible = "simple-audio-card"; + #address-cells = <1>; + #size-cells = <0>; + simple-audio-card,dai-link@0 { + reg = <0>; + format = "dsp_b"; + cpu { + sound-dai = <&audio_controller 16>; + }; + codec { + sound-dai = <&codec1>; + dai-tdm-slot-num = <4>; + dai-tdm-slot-width = <8>; + /* TS 3, 5, 7, 9 */ + dai-tdm-slot-tx-mask = <0 0 0 1 0 1 0 1 0 1>; + dai-tdm-slot-rx-mask = <0 0 0 1 0 1 0 1 0 1>; + }; + }; + simple-audio-card,dai-link@1 { + reg = <1>; + format = "dsp_b"; + cpu { + sound-dai = <&audio_controller 17>; + }; + codec { + sound-dai = <&codec2>; + dai-tdm-slot-num = <4>; + dai-tdm-slot-width = <8>; + /* TS 2, 4, 6, 8 */ + dai-tdm-slot-tx-mask = <0 0 1 0 1 0 1 0 1>; + dai-tdm-slot-rx-mask = <0 0 1 0 1 0 1 0 1>; + }; + }; + }; From patchwork Fri Jan 13 10:37:58 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Herve Codina X-Patchwork-Id: 642231 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 vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id C6A56C678D7 for ; Fri, 13 Jan 2023 10:40:14 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S241394AbjAMKkN (ORCPT ); Fri, 13 Jan 2023 05:40:13 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:39100 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S241332AbjAMKjH (ORCPT ); Fri, 13 Jan 2023 05:39:07 -0500 Received: from relay9-d.mail.gandi.net (relay9-d.mail.gandi.net [IPv6:2001:4b98:dc4:8::229]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 49EA0777F1; Fri, 13 Jan 2023 02:38:39 -0800 (PST) Received: (Authenticated sender: herve.codina@bootlin.com) by mail.gandi.net (Postfix) with ESMTPA id 9797BFF813; Fri, 13 Jan 2023 10:38:35 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=gm1; t=1673606317; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=rmeWtejsq2XACoU1Lth9h7lWU+kTS31A/bDs6sCOZkk=; b=n4moYuGtoypT1LCFB44E4smDUwfEfuasVjcRaAv+C5PtbPyUoDTXQEzYuErebNncQDfPnF E3rq40frjuTrsF/ykqqU5vto3CveIblAwhaO0z75ogtQqhrQZq7WxWwwB0msWIJecfFLD4 BPs00PrSRJBQwpRx9WVVd45nMuFSws2CT4GkOg6MtCX/Crrw6u7cxqfbt9x/bn5KYrjPAF emVbR27ZnXohV6IyufvI0xoc80aNhZ3V74WVwy1KC3kWgMtexq6AsHA6u24/N8rK70cskF 3y1NhEI29Bi7EvCip68VWqB8CwkCaDWHLZ7Vpfatlh+fDjGeGMrQmzergWzp/A== From: Herve Codina To: Herve Codina , Li Yang , Rob Herring , Krzysztof Kozlowski , Liam Girdwood , Mark Brown , Christophe Leroy , Michael Ellerman , Nicholas Piggin , Qiang Zhao , Jaroslav Kysela , Takashi Iwai , Shengjiu Wang , Xiubo Li , Fabio Estevam , Nicolin Chen Cc: linuxppc-dev@lists.ozlabs.org, linux-arm-kernel@lists.infradead.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, alsa-devel@alsa-project.org, Thomas Petazzoni Subject: [PATCH v3 09/10] ASoC: fsl: Add support for QMC audio Date: Fri, 13 Jan 2023 11:37:58 +0100 Message-Id: <20230113103759.327698-10-herve.codina@bootlin.com> X-Mailer: git-send-email 2.38.1 In-Reply-To: <20230113103759.327698-1-herve.codina@bootlin.com> References: <20230113103759.327698-1-herve.codina@bootlin.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: devicetree@vger.kernel.org The QMC audio is an ASoC component which provides DAIs that use the QMC (QUICC Multichannel Controller) to transfer the audio data. It provides as many DAIs as the number of QMC channels it references. Signed-off-by: Herve Codina --- sound/soc/fsl/Kconfig | 9 + sound/soc/fsl/Makefile | 2 + sound/soc/fsl/fsl_qmc_audio.c | 732 ++++++++++++++++++++++++++++++++++ 3 files changed, 743 insertions(+) create mode 100644 sound/soc/fsl/fsl_qmc_audio.c diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig index 614eceda6b9e..17db29c25d96 100644 --- a/sound/soc/fsl/Kconfig +++ b/sound/soc/fsl/Kconfig @@ -172,6 +172,15 @@ config SND_MPC52xx_DMA config SND_SOC_POWERPC_DMA tristate +config SND_SOC_POWERPC_QMC_AUDIO + tristate "QMC ALSA SoC support" + depends on CPM_QMC + help + ALSA SoC Audio support using the Freescale QUICC Multichannel + Controller (QMC). + Say Y or M if you want to add support for SoC audio using Freescale + QMC. + comment "SoC Audio support for Freescale PPC boards:" config SND_SOC_MPC8610_HPCD diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile index b54beb1a66fa..8db7e97d0bd5 100644 --- a/sound/soc/fsl/Makefile +++ b/sound/soc/fsl/Makefile @@ -28,6 +28,7 @@ snd-soc-fsl-easrc-objs := fsl_easrc.o snd-soc-fsl-xcvr-objs := fsl_xcvr.o snd-soc-fsl-aud2htx-objs := fsl_aud2htx.o snd-soc-fsl-rpmsg-objs := fsl_rpmsg.o +snd-soc-fsl-qmc-audio-objs := fsl_qmc_audio.o obj-$(CONFIG_SND_SOC_FSL_AUDMIX) += snd-soc-fsl-audmix.o obj-$(CONFIG_SND_SOC_FSL_ASOC_CARD) += snd-soc-fsl-asoc-card.o @@ -44,6 +45,7 @@ obj-$(CONFIG_SND_SOC_POWERPC_DMA) += snd-soc-fsl-dma.o obj-$(CONFIG_SND_SOC_FSL_XCVR) += snd-soc-fsl-xcvr.o obj-$(CONFIG_SND_SOC_FSL_AUD2HTX) += snd-soc-fsl-aud2htx.o obj-$(CONFIG_SND_SOC_FSL_RPMSG) += snd-soc-fsl-rpmsg.o +obj-$(CONFIG_SND_SOC_POWERPC_QMC_AUDIO) += snd-soc-fsl-qmc-audio.o # MPC5200 Platform Support obj-$(CONFIG_SND_MPC52xx_DMA) += mpc5200_dma.o diff --git a/sound/soc/fsl/fsl_qmc_audio.c b/sound/soc/fsl/fsl_qmc_audio.c new file mode 100644 index 000000000000..c36b493da96a --- /dev/null +++ b/sound/soc/fsl/fsl_qmc_audio.c @@ -0,0 +1,732 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ALSA SoC using the QUICC Multichannel Controller (QMC) + * + * Copyright 2022 CS GROUP France + * + * Author: Herve Codina + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct qmc_dai { + char *name; + int id; + struct device *dev; + struct qmc_chan *qmc_chan; + unsigned int nb_tx_ts; + unsigned int nb_rx_ts; +}; + +struct qmc_audio { + struct device *dev; + unsigned int num_dais; + struct qmc_dai *dais; + struct snd_soc_dai_driver *dai_drivers; +}; + +struct qmc_dai_prtd { + struct qmc_dai *qmc_dai; + dma_addr_t dma_buffer_start; + dma_addr_t period_ptr_submitted; + dma_addr_t period_ptr_ended; + dma_addr_t dma_buffer_end; + size_t period_size; + struct snd_pcm_substream *substream; +}; + +static int qmc_audio_pcm_construct(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + int ret; + + ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + snd_pcm_set_managed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV, card->dev, + 64*1024, 64*1024); + return 0; +} + +static int qmc_audio_pcm_hw_params(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct qmc_dai_prtd *prtd = substream->runtime->private_data; + + prtd->dma_buffer_start = runtime->dma_addr; + prtd->dma_buffer_end = runtime->dma_addr + params_buffer_bytes(params); + prtd->period_size = params_period_bytes(params); + prtd->period_ptr_submitted = prtd->dma_buffer_start; + prtd->period_ptr_ended = prtd->dma_buffer_start; + prtd->substream = substream; + + return 0; +} + +static void qmc_audio_pcm_write_complete(void *context) +{ + struct qmc_dai_prtd *prtd = context; + int ret; + + prtd->period_ptr_ended += prtd->period_size; + if (prtd->period_ptr_ended >= prtd->dma_buffer_end) + prtd->period_ptr_ended = prtd->dma_buffer_start; + + prtd->period_ptr_submitted += prtd->period_size; + if (prtd->period_ptr_submitted >= prtd->dma_buffer_end) + prtd->period_ptr_submitted = prtd->dma_buffer_start; + + ret = qmc_chan_write_submit(prtd->qmc_dai->qmc_chan, + prtd->period_ptr_submitted, prtd->period_size, + qmc_audio_pcm_write_complete, prtd); + if (ret) { + dev_err(prtd->qmc_dai->dev, "write_submit failed %d\n", + ret); + } + + snd_pcm_period_elapsed(prtd->substream); +} + +static void qmc_audio_pcm_read_complete(void *context, size_t length) +{ + struct qmc_dai_prtd *prtd = context; + int ret; + + if (length != prtd->period_size) { + dev_err(prtd->qmc_dai->dev, "read complete length = %zu, exp %zu\n", + length, prtd->period_size); + } + + prtd->period_ptr_ended += prtd->period_size; + if (prtd->period_ptr_ended >= prtd->dma_buffer_end) + prtd->period_ptr_ended = prtd->dma_buffer_start; + + prtd->period_ptr_submitted += prtd->period_size; + if (prtd->period_ptr_submitted >= prtd->dma_buffer_end) + prtd->period_ptr_submitted = prtd->dma_buffer_start; + + ret = qmc_chan_read_submit(prtd->qmc_dai->qmc_chan, + prtd->period_ptr_submitted, prtd->period_size, + qmc_audio_pcm_read_complete, prtd); + if (ret) { + dev_err(prtd->qmc_dai->dev, "read_submit failed %d\n", + ret); + } + + snd_pcm_period_elapsed(prtd->substream); +} + +static int qmc_audio_pcm_trigger(struct snd_soc_component *component, + struct snd_pcm_substream *substream, int cmd) +{ + struct qmc_dai_prtd *prtd = substream->runtime->private_data; + int ret; + + if (!prtd->qmc_dai) { + dev_err(component->dev, "qmc_dai is not set\n"); + return -EINVAL; + } + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* Submit first chunk ... */ + ret = qmc_chan_write_submit(prtd->qmc_dai->qmc_chan, + prtd->period_ptr_submitted, prtd->period_size, + qmc_audio_pcm_write_complete, prtd); + if (ret) { + dev_err(component->dev, "write_submit failed %d\n", + ret); + return ret; + } + + /* ... prepare next one ... */ + prtd->period_ptr_submitted += prtd->period_size; + if (prtd->period_ptr_submitted >= prtd->dma_buffer_end) + prtd->period_ptr_submitted = prtd->dma_buffer_start; + + /* ... and send it */ + ret = qmc_chan_write_submit(prtd->qmc_dai->qmc_chan, + prtd->period_ptr_submitted, prtd->period_size, + qmc_audio_pcm_write_complete, prtd); + if (ret) { + dev_err(component->dev, "write_submit failed %d\n", + ret); + return ret; + } + } else { + /* Submit first chunk ... */ + ret = qmc_chan_read_submit(prtd->qmc_dai->qmc_chan, + prtd->period_ptr_submitted, prtd->period_size, + qmc_audio_pcm_read_complete, prtd); + if (ret) { + dev_err(component->dev, "read_submit failed %d\n", + ret); + return ret; + } + + /* ... prepare next one ... */ + prtd->period_ptr_submitted += prtd->period_size; + if (prtd->period_ptr_submitted >= prtd->dma_buffer_end) + prtd->period_ptr_submitted = prtd->dma_buffer_start; + + /* ... and send it */ + ret = qmc_chan_read_submit(prtd->qmc_dai->qmc_chan, + prtd->period_ptr_submitted, prtd->period_size, + qmc_audio_pcm_read_complete, prtd); + if (ret) { + dev_err(component->dev, "write_submit failed %d\n", + ret); + return ret; + } + } + break; + + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + break; + + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t qmc_audio_pcm_pointer(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct qmc_dai_prtd *prtd = substream->runtime->private_data; + + return bytes_to_frames(substream->runtime, + prtd->period_ptr_ended - prtd->dma_buffer_start); +} + +static int qmc_audio_of_xlate_dai_name(struct snd_soc_component *component, + const struct of_phandle_args *args, + const char **dai_name) +{ + struct qmc_audio *qmc_audio = dev_get_drvdata(component->dev); + struct snd_soc_dai_driver *dai_driver; + int id = args->args[0]; + int i; + + for (i = 0; i < qmc_audio->num_dais; i++) { + dai_driver = qmc_audio->dai_drivers + i; + if (dai_driver->id == id) { + *dai_name = dai_driver->name; + return 0; + } + } + + return -EINVAL; +} + +static const struct snd_pcm_hardware qmc_audio_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE, + .period_bytes_min = 32, + .period_bytes_max = 64*1024, + .periods_min = 2, + .periods_max = 2*1024, + .buffer_bytes_max = 64*1024, +}; + +static int qmc_audio_pcm_open(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct qmc_dai_prtd *prtd; + int ret; + + snd_soc_set_runtime_hwparams(substream, &qmc_audio_pcm_hardware); + + /* ensure that buffer size is a multiple of period size */ + ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + return ret; + + prtd = kzalloc(sizeof(*prtd), GFP_KERNEL); + if (prtd == NULL) + return -ENOMEM; + + runtime->private_data = prtd; + + return 0; +} + +static int qmc_audio_pcm_close(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct qmc_dai_prtd *prtd = substream->runtime->private_data; + + kfree(prtd); + return 0; +} + +static const struct snd_soc_component_driver qmc_audio_soc_platform = { + .open = qmc_audio_pcm_open, + .close = qmc_audio_pcm_close, + .hw_params = qmc_audio_pcm_hw_params, + .trigger = qmc_audio_pcm_trigger, + .pointer = qmc_audio_pcm_pointer, + .pcm_construct = qmc_audio_pcm_construct, + .of_xlate_dai_name = qmc_audio_of_xlate_dai_name, +}; + +static unsigned int qmc_dai_get_index(struct snd_soc_dai *dai) +{ + struct qmc_audio *qmc_audio = snd_soc_dai_get_drvdata(dai); + + return dai->driver - qmc_audio->dai_drivers; +} + +static struct qmc_dai *qmc_dai_get_data(struct snd_soc_dai *dai) +{ + struct qmc_audio *qmc_audio = snd_soc_dai_get_drvdata(dai); + unsigned int index; + + index = qmc_dai_get_index(dai); + if (index > qmc_audio->num_dais) + return NULL; + + return qmc_audio->dais + index; +} + +/* The constraints for format/channel is to match with the number of 8bit + * time-slots available. + */ +static int qmc_dai_hw_rule_channels_by_format(struct qmc_dai *qmc_dai, + struct snd_pcm_hw_params *params, + unsigned int nb_ts) +{ + struct snd_interval *c = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + snd_pcm_format_t format = params_format(params); + struct snd_interval ch = {0}; + + switch (snd_pcm_format_physical_width(format)) { + case 8: + ch.max = nb_ts; + break; + case 16: + ch.max = nb_ts/2; + break; + case 32: + ch.max = nb_ts/4; + break; + case 64: + ch.max = nb_ts/8; + break; + default: + dev_err(qmc_dai->dev, "format physical width %u not supported\n", + snd_pcm_format_physical_width(format)); + return -EINVAL; + } + + ch.min = ch.max ? 1 : 0; + + return snd_interval_refine(c, &ch); +} + +static int qmc_dai_hw_rule_playback_channels_by_format(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct qmc_dai *qmc_dai = rule->private; + + return qmc_dai_hw_rule_channels_by_format(qmc_dai, params, qmc_dai->nb_tx_ts); +} + +static int qmc_dai_hw_rule_capture_channels_by_format( + struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct qmc_dai *qmc_dai = rule->private; + + return qmc_dai_hw_rule_channels_by_format(qmc_dai, params, qmc_dai->nb_rx_ts); +} + +static int qmc_dai_hw_rule_format_by_channels(struct qmc_dai *qmc_dai, + struct snd_pcm_hw_params *params, + unsigned int nb_ts) +{ + struct snd_mask *f_old = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + unsigned int channels = params_channels(params); + unsigned int slot_width; + struct snd_mask f_new; + unsigned int i; + + if (!channels || channels > nb_ts) { + dev_err(qmc_dai->dev, "channels %u not supported\n", + nb_ts); + return -EINVAL; + } + + slot_width = (nb_ts / channels) * 8; + + snd_mask_none(&f_new); + for (i = 0; i <= SNDRV_PCM_FORMAT_LAST; i++) { + if (snd_mask_test(f_old, i)) { + if (snd_pcm_format_physical_width(i) <= slot_width) + snd_mask_set(&f_new, i); + } + } + + return snd_mask_refine(f_old, &f_new); +} + +static int qmc_dai_hw_rule_playback_format_by_channels( + struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct qmc_dai *qmc_dai = rule->private; + + return qmc_dai_hw_rule_format_by_channels(qmc_dai, params, qmc_dai->nb_tx_ts); +} + +static int qmc_dai_hw_rule_capture_format_by_channels( + struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct qmc_dai *qmc_dai = rule->private; + + return qmc_dai_hw_rule_format_by_channels(qmc_dai, params, qmc_dai->nb_rx_ts); +} + +static int qmc_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct qmc_dai_prtd *prtd = substream->runtime->private_data; + snd_pcm_hw_rule_func_t hw_rule_channels_by_format; + snd_pcm_hw_rule_func_t hw_rule_format_by_channels; + struct qmc_dai *qmc_dai; + unsigned int frame_bits; + int ret; + + qmc_dai = qmc_dai_get_data(dai); + if (!qmc_dai) { + dev_err(dai->dev, "Invalid dai\n"); + return -EINVAL; + } + + prtd->qmc_dai = qmc_dai; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + hw_rule_channels_by_format = qmc_dai_hw_rule_capture_channels_by_format; + hw_rule_format_by_channels = qmc_dai_hw_rule_capture_format_by_channels; + frame_bits = qmc_dai->nb_rx_ts * 8; + } else { + hw_rule_channels_by_format = qmc_dai_hw_rule_playback_channels_by_format; + hw_rule_format_by_channels = qmc_dai_hw_rule_playback_format_by_channels; + frame_bits = qmc_dai->nb_tx_ts * 8; + } + + ret = snd_pcm_hw_rule_add(substream->runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + hw_rule_channels_by_format, qmc_dai, + SNDRV_PCM_HW_PARAM_FORMAT, -1); + if (ret) { + dev_err(dai->dev, "Failed to add channels rule (%d)\n", ret); + return ret; + } + + ret = snd_pcm_hw_rule_add(substream->runtime, 0, SNDRV_PCM_HW_PARAM_FORMAT, + hw_rule_format_by_channels, qmc_dai, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (ret) { + dev_err(dai->dev, "Failed to add format rule (%d)\n", ret); + return ret; + } + + ret = snd_pcm_hw_constraint_single(substream->runtime, + SNDRV_PCM_HW_PARAM_FRAME_BITS, + frame_bits); + if (ret < 0) { + dev_err(dai->dev, "Failed to add frame_bits constraint (%d)\n", ret); + return ret; + } + + return 0; +} + +static int qmc_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct qmc_chan_param chan_param = {0}; + struct qmc_dai *qmc_dai; + int ret; + + qmc_dai = qmc_dai_get_data(dai); + if (!qmc_dai) { + dev_err(dai->dev, "Invalid dai\n"); + return -EINVAL; + } + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + chan_param.mode = QMC_TRANSPARENT; + chan_param.transp.max_rx_buf_size = params_period_bytes(params); + ret = qmc_chan_set_param(qmc_dai->qmc_chan, &chan_param); + if (ret) { + dev_err(dai->dev, "set param failed %d\n", + ret); + return ret; + } + } + + return 0; +} + +static int qmc_dai_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct qmc_dai *qmc_dai; + int direction; + int ret; + + qmc_dai = qmc_dai_get_data(dai); + if (!qmc_dai) { + dev_err(dai->dev, "Invalid dai\n"); + return -EINVAL; + } + + direction = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? + QMC_CHAN_WRITE : QMC_CHAN_READ; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ret = qmc_chan_start(qmc_dai->qmc_chan, direction); + if (ret) + return ret; + break; + + case SNDRV_PCM_TRIGGER_STOP: + ret = qmc_chan_stop(qmc_dai->qmc_chan, direction); + if (ret) + return ret; + ret = qmc_chan_reset(qmc_dai->qmc_chan, direction); + if (ret) + return ret; + break; + + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ret = qmc_chan_stop(qmc_dai->qmc_chan, direction); + if (ret) + return ret; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static const struct snd_soc_dai_ops qmc_dai_ops = { + .startup = qmc_dai_startup, + .trigger = qmc_dai_trigger, + .hw_params = qmc_dai_hw_params, +}; + +static u64 qmc_audio_formats(u8 nb_ts) +{ + u64 formats; + unsigned int chan_width; + unsigned int format_width; + int i; + + if (!nb_ts) + return 0; + + formats = 0; + chan_width = nb_ts * 8; + for (i = 0; i <= SNDRV_PCM_FORMAT_LAST; i++) { + /* Support format other than little-endian (ie big-endian or + * without endianness such as 8bit formats) + */ + if (snd_pcm_format_little_endian(i) == 1) + continue; + + /* Support physical width multiple of 8bit */ + format_width = snd_pcm_format_physical_width(i); + if (format_width == 0 || format_width % 8) + continue; + + /* And support physical width that can fit N times in the + * channel + */ + if (format_width > chan_width || chan_width % format_width) + continue; + + formats |= (1ULL << i); + } + return formats; +} + +static int qmc_audio_dai_parse(struct qmc_audio *qmc_audio, struct device_node *np, + struct qmc_dai *qmc_dai, struct snd_soc_dai_driver *qmc_soc_dai_driver) +{ + struct qmc_chan_info info; + u32 val; + int ret; + + qmc_dai->dev = qmc_audio->dev; + + ret = of_property_read_u32(np, "reg", &val); + if (ret) { + dev_err(qmc_audio->dev, "%pOF: failed to read reg\n", np); + return ret; + } + qmc_dai->id = val; + + qmc_dai->name = devm_kasprintf(qmc_audio->dev, GFP_KERNEL, "%s.%d", + np->parent->name, qmc_dai->id); + + qmc_dai->qmc_chan = devm_qmc_chan_get_byphandle(qmc_audio->dev, np, + "fsl,qmc-chan"); + if (IS_ERR(qmc_dai->qmc_chan)) { + ret = PTR_ERR(qmc_dai->qmc_chan); + return dev_err_probe(qmc_audio->dev, ret, + "dai %d get QMC channel failed\n", qmc_dai->id); + } + + qmc_soc_dai_driver->id = qmc_dai->id; + qmc_soc_dai_driver->name = qmc_dai->name; + + ret = qmc_chan_get_info(qmc_dai->qmc_chan, &info); + if (ret) { + dev_err(qmc_audio->dev, "dai %d get QMC channel info failed %d\n", + qmc_dai->id, ret); + return ret; + } + dev_info(qmc_audio->dev, "dai %d QMC channel mode %d, nb_tx_ts %u, nb_rx_ts %u\n", + qmc_dai->id, info.mode, info.nb_tx_ts, info.nb_rx_ts); + + if (info.mode != QMC_TRANSPARENT) { + dev_err(qmc_audio->dev, "dai %d QMC chan mode %d is not QMC_TRANSPARENT\n", + qmc_dai->id, info.mode); + return -EINVAL; + } + qmc_dai->nb_tx_ts = info.nb_tx_ts; + qmc_dai->nb_rx_ts = info.nb_rx_ts; + + qmc_soc_dai_driver->playback.channels_min = 0; + qmc_soc_dai_driver->playback.channels_max = 0; + if (qmc_dai->nb_tx_ts) { + qmc_soc_dai_driver->playback.channels_min = 1; + qmc_soc_dai_driver->playback.channels_max = qmc_dai->nb_tx_ts; + } + qmc_soc_dai_driver->playback.formats = qmc_audio_formats(qmc_dai->nb_tx_ts); + + qmc_soc_dai_driver->capture.channels_min = 0; + qmc_soc_dai_driver->capture.channels_max = 0; + if (qmc_dai->nb_rx_ts) { + qmc_soc_dai_driver->capture.channels_min = 1; + qmc_soc_dai_driver->capture.channels_max = qmc_dai->nb_rx_ts; + } + qmc_soc_dai_driver->capture.formats = qmc_audio_formats(qmc_dai->nb_rx_ts); + + qmc_soc_dai_driver->playback.rates = snd_pcm_rate_to_rate_bit(info.tx_fs_rate); + qmc_soc_dai_driver->playback.rate_min = info.tx_fs_rate; + qmc_soc_dai_driver->playback.rate_max = info.tx_fs_rate; + qmc_soc_dai_driver->capture.rates = snd_pcm_rate_to_rate_bit(info.rx_fs_rate); + qmc_soc_dai_driver->capture.rate_min = info.rx_fs_rate; + qmc_soc_dai_driver->capture.rate_max = info.rx_fs_rate; + + qmc_soc_dai_driver->ops = &qmc_dai_ops; + + return 0; +} + +static int qmc_audio_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct qmc_audio *qmc_audio; + struct device_node *child; + unsigned int i; + int ret; + + qmc_audio = devm_kzalloc(&pdev->dev, sizeof(*qmc_audio), GFP_KERNEL); + if (!qmc_audio) + return -ENOMEM; + + qmc_audio->dev = &pdev->dev; + + qmc_audio->num_dais = of_get_available_child_count(np); + if (qmc_audio->num_dais) { + qmc_audio->dais = devm_kcalloc(&pdev->dev, qmc_audio->num_dais, + sizeof(*qmc_audio->dais), + GFP_KERNEL); + if (!qmc_audio->dais) + return -ENOMEM; + + qmc_audio->dai_drivers = devm_kcalloc(&pdev->dev, qmc_audio->num_dais, + sizeof(*qmc_audio->dai_drivers), + GFP_KERNEL); + if (!qmc_audio->dai_drivers) + return -ENOMEM; + } + + i = 0; + for_each_available_child_of_node(np, child) { + ret = qmc_audio_dai_parse(qmc_audio, child, + qmc_audio->dais + i, + qmc_audio->dai_drivers + i); + if (ret) { + of_node_put(child); + return ret; + } + i++; + } + + + platform_set_drvdata(pdev, qmc_audio); + + ret = devm_snd_soc_register_component(qmc_audio->dev, + &qmc_audio_soc_platform, + qmc_audio->dai_drivers, + qmc_audio->num_dais); + if (ret) + return ret; + + return 0; +} + +static const struct of_device_id qmc_audio_id_table[] = { + { .compatible = "fsl,qmc-audio" }, + {} /* sentinel */ +}; +MODULE_DEVICE_TABLE(of, qmc_audio_id_table); + +static struct platform_driver qmc_audio_driver = { + .driver = { + .name = "fsl-qmc-audio", + .of_match_table = of_match_ptr(qmc_audio_id_table), + }, + .probe = qmc_audio_probe, +}; +module_platform_driver(qmc_audio_driver); + +MODULE_AUTHOR("Herve Codina "); +MODULE_DESCRIPTION("CPM/QE QMC audio driver"); +MODULE_LICENSE("GPL");