From patchwork Tue Jul 17 15:42:57 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jerome Brunet X-Patchwork-Id: 142175 Delivered-To: patch@linaro.org Received: by 2002:a2e:9754:0:0:0:0:0 with SMTP id f20-v6csp3820760ljj; Tue, 17 Jul 2018 08:43:27 -0700 (PDT) X-Google-Smtp-Source: AAOMgpcvikNsbUaCHXSirmrFg24A0+pONHLKNqjmk5TkS0wuxPn3l0QinqThDmKYGEV8TnY+08f1 X-Received: by 2002:a17:902:564:: with SMTP id 91-v6mr2111492plf.155.1531842207600; Tue, 17 Jul 2018 08:43:27 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1531842207; cv=none; d=google.com; s=arc-20160816; b=KG2UiIeKKs7DxwlygccYrmzKqoUsBbIKeUBwQDlKUCpzazgB54OXK1/ld90cB7Dm8L MnKAYxXY3kXASok6rF850pswuxi4B3eJrq2Vfip1NVmmncKHt1w5qUd7ouCijCAUbqad 9JpeM+z21VvYe+/AAGX4mmplvyk1q7xCuGdI5KrHNY8/1Ngu6ARiq8mdY+D3yG6k8fbP cS89QZU3v4yrmDJPTakEFzpU0x94/uiSX3LJDVLPLXovcIiY8C6uaf7sc9YZ/xM7W/Sb kNnsaWb4XB+x2dtp31R64vpgYW8NncHVOyRvpVV2LPD1kvvg4SEF8qttdGvs5iXFZduG GDwg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:references:in-reply-to:message-id:date :subject:cc:to:from:dkim-signature:arc-authentication-results; bh=44PzRR/dUtlpvs9tMhs6tm63s9VvXyUaROmWQCYD1C0=; b=PLXILRm6FusjzhON4gPdCFyL3qGk+15Uz03T6Rk5CvaoRtPfVgnzFq8HxD6sWkZ5tW bir0WPGWeCKb/Njoh4eNkW3mH5Y2uD6m60znMmeHCIZseqUGF9LS60Hc1QjfSVaDfLXN fft287afvldzAXJ/DDM97MCoF032iCsYB0Igjs8ZgDilPsBYmZa5usYTishr+Afc3YRe rhNMiv6oVtvs0azCBAW3L2WCf4lGIdCgLBQdE/oqGtjoDiwEFTSZq2H2tdyNVOWNfPDN EEJXS0lrXMCNUQzN1mzhU0lUiABpreZryr9lMErl8ODfxDbX6DU46znAvrrUpa6Y47YP EYeg== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@baylibre-com.20150623.gappssmtp.com header.s=20150623 header.b=YM9cOJrA; spf=pass (google.com: best guess record for domain of devicetree-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=devicetree-owner@vger.kernel.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id i28-v6si1198207pfi.105.2018.07.17.08.43.27; Tue, 17 Jul 2018 08:43:27 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of devicetree-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@baylibre-com.20150623.gappssmtp.com header.s=20150623 header.b=YM9cOJrA; spf=pass (google.com: best guess record for domain of devicetree-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=devicetree-owner@vger.kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1731324AbeGQQQi (ORCPT + 5 others); Tue, 17 Jul 2018 12:16:38 -0400 Received: from mail-wm0-f66.google.com ([74.125.82.66]:53690 "EHLO mail-wm0-f66.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1731416AbeGQQQi (ORCPT ); Tue, 17 Jul 2018 12:16:38 -0400 Received: by mail-wm0-f66.google.com with SMTP id s9-v6so1996360wmh.3 for ; Tue, 17 Jul 2018 08:43:22 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=baylibre-com.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=2aX4y48Nr4mxrNaEm41GH+dJkSDI+nV532cD2B8cBj8=; b=YM9cOJrAEiWK6K8XGSYa4RTz2z9cC5aiKtQ0BA1F/uoapQLGZPJoEGSJLEwvRUdkbf DIsyaYrRdjtifEH6v8sNoDr+AkV7gCtOPzrvd+/2noRwizI4XXjHGnU+1iBJgASSdwDo nGDhcy5JOkH81+P/KdggE/SnP/g3w8yQsTGyliTGXxmRckZ9ZvNy8LIWwdfhAIqT+pyf Pfncxu6iTlHQPSrWshe0FhuM+F9x1tKs8exRJm5KY4NXpT9xbYpY0d9W7G98yizjJmSO kr0PF9jhh7M4Nu3493ISYY003IySbEoDDz7McJnYDw93AeWFvmjNLsEIo0Zb1Kd/MQkw K6Tg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=2aX4y48Nr4mxrNaEm41GH+dJkSDI+nV532cD2B8cBj8=; b=PMrIY8Gt6wP7dBd+vECmf1zUxpXcmyfmbSorf1r7yyJQ35raoxmRFOOHPboz9y37aB UDPFft4XmkTYujKgu0A/pa4gdjI0ZPe16Fb7e0JlrRE5xXebtBUy/8wrbKjiFC3zKUMe wRRrmDJ4U5bgOfof9fsDRuicktZmakhrLHYvwXSBIgXQ04ZbbVrjE0aCPiD6Z/Z8J/Gh UMh1zSgh3Ljl3Kb/E0cT048uF/TmfcGk9CZqwNmw+hOaYgvIEPsw9o2NCJtWOVCHzso7 0PjW2+f3ab2Mzaxfj7nFrYMpBKemmj6Cbsg60uRlpqYh6bPfQiZ7td3ArcT8L1Oy0orA yq5A== X-Gm-Message-State: AOUpUlG11ek156v70fNvTe49YZsv/06MMjmw1d9lGVJ7YC4+kziPOk4P nblaPGfpoAlKEfxCKVPjLtjptByd X-Received: by 2002:a1c:dcf:: with SMTP id 198-v6mr1720662wmn.131.1531842202160; Tue, 17 Jul 2018 08:43:22 -0700 (PDT) Received: from boomer.baylibre.local ([90.63.244.31]) by smtp.googlemail.com with ESMTPSA id s12-v6sm17598252wmc.2.2018.07.17.08.43.21 (version=TLS1_2 cipher=ECDHE-RSA-CHACHA20-POLY1305 bits=256/256); Tue, 17 Jul 2018 08:43:21 -0700 (PDT) From: Jerome Brunet To: Mark Brown , Liam Girdwood , Kevin Hilman , Carlo Caione Cc: Jerome Brunet , alsa-devel@alsa-project.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-amlogic@lists.infradead.org Subject: [PATCH RESEND 08/15] ASoC: meson: add axg tdm interface DT bindings documentation Date: Tue, 17 Jul 2018 17:42:57 +0200 Message-Id: <20180717154304.9973-9-jbrunet@baylibre.com> X-Mailer: git-send-email 2.14.4 In-Reply-To: <20180717154304.9973-1-jbrunet@baylibre.com> References: <20180717154304.9973-1-jbrunet@baylibre.com> Sender: devicetree-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: devicetree@vger.kernel.org Add the DT bindings documentation for axg's TDM interfaces Signed-off-by: Jerome Brunet --- .../bindings/sound/amlogic,axg-tdm-iface.txt | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/amlogic,axg-tdm-iface.txt -- 2.14.4 -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html diff --git a/Documentation/devicetree/bindings/sound/amlogic,axg-tdm-iface.txt b/Documentation/devicetree/bindings/sound/amlogic,axg-tdm-iface.txt new file mode 100644 index 000000000000..cabfb26a5f22 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/amlogic,axg-tdm-iface.txt @@ -0,0 +1,22 @@ +* Amlogic Audio TDM Interfaces + +Required properties: +- compatible: 'amlogic,axg-tdm-iface' +- clocks: list of clock phandle, one for each entry clock-names. +- clock-names: should contain the following: + * "sclk" : bit clock. + * "lrclk": sample clock + * "mclk" : master clock + -> optional if the interface is in clock slave mode. +- #sound-dai-cells: must be 0. + +Example of TDM_A on the A113 SoC: + +tdmif_a: audio-controller@0 { + compatible = "amlogic,axg-tdm-iface"; + #sound-dai-cells = <0>; + clocks = <&clkc_audio AUD_CLKID_MST_A_MCLK>, + <&clkc_audio AUD_CLKID_MST_A_SCLK>, + <&clkc_audio AUD_CLKID_MST_A_LRCLK>; + clock-names = "mclk", "sclk", "lrclk"; +}; From patchwork Tue Jul 17 15:42:59 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jerome Brunet X-Patchwork-Id: 142182 Delivered-To: patch@linaro.org Received: by 2002:a2e:9754:0:0:0:0:0 with SMTP id f20-v6csp3821445ljj; Tue, 17 Jul 2018 08:44:07 -0700 (PDT) X-Google-Smtp-Source: AAOMgpdnDCG8koaCenM3iZyAT4YXoWCQd+rkTB0i1f67i2O8neEcAxQnDqgWolOcQ6qkIJdomydN X-Received: by 2002:a63:1546:: with SMTP id 6-v6mr2111266pgv.271.1531842247193; Tue, 17 Jul 2018 08:44:07 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1531842247; cv=none; d=google.com; s=arc-20160816; b=ADhAUZEvdXV6fwLQ/OTnPIFMAPKhz54fLKNEDpP8glFVI3kghprvbm/gXde9jsRcAB 4NaMFwdIQ8/UWcwbPgrF3T/P24cg32IXxIVTe1kTxBH8iQDUdK8yPqk/sFK93Y8fyTYT eeBQfRS0DqtyfC0UOnbTcuYQoKETuNDRzUfiWEDUozDA/T2F4k9pV3Azqct5XaWznaKD LPDWIbWKjWfHjer9KtRk1zcXe0NiozsNuUuLEhWArkHuBx0AGCb0nxINXNPzSY2i6WG9 1wMuV++cyMGuBheUCskM2KgsfzKerhwXeeq567TtY9ZWCdubfWTg0j3zgmrr297Sgu5V 88Qw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:references:in-reply-to:message-id:date :subject:cc:to:from:dkim-signature:arc-authentication-results; bh=vH8jbp8vbbEvaoJrnOy1AvUfHJuKJMTvjGRXSo+FEdk=; b=JideVXyTpNDN5bkvHPsaN/eg3duHDDcUuaTDHEaAOLYLmCHLxMIB2roRh3/yY92Til dM2lU2mktbR0n7g+cvao5b7DfAowr4huim+xTsIg7SO5u9qkq6Jjkcaexsv5w0WidrO6 f3sF6PdV6UmLJqyd0MsKoV37dS3OREhDAOsKdDF1uLheB22Ej1PkPJ0Mgm0P1JrLHbp6 OMusGq8eBN7quzh9kDMs1VH7MCvA0WNk9MOhl6JPlTeVjXSrG/WQjKhx8en6ML7/M8rI j9zojwVyvlPXHqvFjPnBvCi42xA5mtC4Dq5sndUUT7/4hn5BHpKgPYuw+V9ekigxsry/ QbAA== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@baylibre-com.20150623.gappssmtp.com header.s=20150623 header.b=NdvQ6BqZ; spf=pass (google.com: best guess record for domain of devicetree-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=devicetree-owner@vger.kernel.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id h12-v6si1163412pfk.156.2018.07.17.08.44.06; Tue, 17 Jul 2018 08:44:07 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of devicetree-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@baylibre-com.20150623.gappssmtp.com header.s=20150623 header.b=NdvQ6BqZ; spf=pass (google.com: best guess record for domain of devicetree-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=devicetree-owner@vger.kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1731477AbeGQQRU (ORCPT + 5 others); Tue, 17 Jul 2018 12:17:20 -0400 Received: from mail-wm0-f66.google.com ([74.125.82.66]:52363 "EHLO mail-wm0-f66.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1731416AbeGQQQk (ORCPT ); Tue, 17 Jul 2018 12:16:40 -0400 Received: by mail-wm0-f66.google.com with SMTP id o11-v6so1982348wmh.2 for ; Tue, 17 Jul 2018 08:43:25 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=baylibre-com.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=YWzx6WQNW9Bzj3tVg8EpFPsjv6MBgDW2jvjTa29j8eg=; b=NdvQ6BqZ5ZcWvMsAUkeUoSCwHl5a9cj4pfiNLGwB1o/oRq4aTNxEvMQ07hvdZCqA/w dBoPby7OeYbA6spuLbilnSlaJerzJ9pesJBSUgJm9R28xxyz8zwKrGDArkgxkmyMWh1J 5/h6Quxu0Uf6Cjokfadv6+/8RsqNwi7n/xWUxpLhVpTqPQyKI11GodhzSO9BBVevQmOB AtQFcgp9yjMWc8kdUR34gNzUvoi10iit6a0/1geYlJYYrkMGINi3nIhsDmZg4Q8sTXn8 xyQ41dZnuANLVbK2/jEVOghvi88qcND2ffYWpwXKCDIGCy6let/A7vd5oa6xCnvX9dKF OLYg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=YWzx6WQNW9Bzj3tVg8EpFPsjv6MBgDW2jvjTa29j8eg=; b=qUuCd0qsre87THETqDDGmil9u+nFzcwb0EUDSkdHs77+u389/pnkXNU7Su37jBPe5w DIZOOv2Vq8raIAgolU+KBbmRj7Nv5TnQo94+gvl4GjQiPboVGR+pRIbuPS/0auYUHAAL juyCqZFsBxyaRyiRYr/9ZcRiJPBLUuskgL6/FAXMwsJrxy7cKP2iVMHg3VgzLnMGmTLg iBBEJGXZIQxUCMj57Cf5WBkwQ3jR17BbHVPwTApq0fDjFQFR1Ce9Y//QgBYM5BxgUZuj qxdknsPkdWk1a4QVyFwdC2z8Sbjji6BXlpkkBH3ia2z89ny+scfMj0/7uxzSZKh/AOWq TJXw== X-Gm-Message-State: AOUpUlH2zgKlnezP4dHLG44xqjDIIBLLImVrvUwMIS67A0cTQZlNgAte mab4W+Hl5mrd3J5S9SnkS0hLpg== X-Received: by 2002:a1c:20cb:: with SMTP id g194-v6mr1781819wmg.102.1531842204247; Tue, 17 Jul 2018 08:43:24 -0700 (PDT) Received: from boomer.baylibre.local ([90.63.244.31]) by smtp.googlemail.com with ESMTPSA id s12-v6sm17598252wmc.2.2018.07.17.08.43.23 (version=TLS1_2 cipher=ECDHE-RSA-CHACHA20-POLY1305 bits=256/256); Tue, 17 Jul 2018 08:43:23 -0700 (PDT) From: Jerome Brunet To: Mark Brown , Liam Girdwood , Kevin Hilman , Carlo Caione Cc: Jerome Brunet , alsa-devel@alsa-project.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-amlogic@lists.infradead.org Subject: [PATCH RESEND 10/15] ASoC: meson: add tdm interface driver Date: Tue, 17 Jul 2018 17:42:59 +0200 Message-Id: <20180717154304.9973-11-jbrunet@baylibre.com> X-Mailer: git-send-email 2.14.4 In-Reply-To: <20180717154304.9973-1-jbrunet@baylibre.com> References: <20180717154304.9973-1-jbrunet@baylibre.com> Sender: devicetree-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: devicetree@vger.kernel.org Add Amlogic's axg TDM interface driver. This driver manages the format and clocks provided on the pads. On this SoC, each stream direction provides 4 serial lanes. This makes a maximum of 8 channels in i2s modes and 128 channels in DSP modes. While each lanes operate on the same slot number (same bit clock), they may have different TDM masks. This requires to provide a function to let the card set the 4 masks, in lieu of the usual set_tdm_slots() callback of the dai driver. Signed-off-by: Jerome Brunet --- sound/soc/meson/Kconfig | 4 + sound/soc/meson/Makefile | 2 + sound/soc/meson/axg-tdm-interface.c | 542 ++++++++++++++++++++++++++++++++++++ sound/soc/meson/axg-tdm.h | 4 + 4 files changed, 552 insertions(+) create mode 100644 sound/soc/meson/axg-tdm-interface.c -- 2.14.4 -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html diff --git a/sound/soc/meson/Kconfig b/sound/soc/meson/Kconfig index 80a88689491f..869b359c2ce7 100644 --- a/sound/soc/meson/Kconfig +++ b/sound/soc/meson/Kconfig @@ -23,6 +23,10 @@ config SND_MESON_AXG_TDM_FORMATTER tristate select REGMAP_MMIO +config SND_MESON_AXG_TDM_INTERFACE + tristate + select SND_MESON_AXG_TDM_FORMATTER + config SND_MESON_AXG_SPDIFOUT tristate "Amlogic AXG SPDIF Output Support" imply SND_SOC_SPDIF diff --git a/sound/soc/meson/Makefile b/sound/soc/meson/Makefile index a06b56a1c995..1a8eb77402e3 100644 --- a/sound/soc/meson/Makefile +++ b/sound/soc/meson/Makefile @@ -4,10 +4,12 @@ snd-soc-meson-axg-fifo-objs := axg-fifo.o snd-soc-meson-axg-frddr-objs := axg-frddr.o snd-soc-meson-axg-toddr-objs := axg-toddr.o snd-soc-meson-axg-tdm-formatter-objs := axg-tdm-formatter.o +snd-soc-meson-axg-tdm-interface-objs := axg-tdm-interface.o snd-soc-meson-axg-spdifout-objs := axg-spdifout.o obj-$(CONFIG_SND_MESON_AXG_FIFO) += snd-soc-meson-axg-fifo.o obj-$(CONFIG_SND_MESON_AXG_FRDDR) += snd-soc-meson-axg-frddr.o obj-$(CONFIG_SND_MESON_AXG_TODDR) += snd-soc-meson-axg-toddr.o obj-$(CONFIG_SND_MESON_AXG_TDM_FORMATTER) += snd-soc-meson-axg-tdm-formatter.o +obj-$(CONFIG_SND_MESON_AXG_TDM_INTERFACE) += snd-soc-meson-axg-tdm-interface.o obj-$(CONFIG_SND_MESON_AXG_SPDIFOUT) += snd-soc-meson-axg-spdifout.o diff --git a/sound/soc/meson/axg-tdm-interface.c b/sound/soc/meson/axg-tdm-interface.c new file mode 100644 index 000000000000..7b8baf46d968 --- /dev/null +++ b/sound/soc/meson/axg-tdm-interface.c @@ -0,0 +1,542 @@ +// SPDX-License-Identifier: (GPL-2.0 OR MIT) +// +// Copyright (c) 2018 BayLibre, SAS. +// Author: Jerome Brunet + +#include +#include +#include +#include +#include +#include + +#include "axg-tdm.h" + +enum { + TDM_IFACE_PAD, + TDM_IFACE_LOOPBACK, +}; + +static unsigned int axg_tdm_slots_total(u32 *mask) +{ + unsigned int slots = 0; + int i; + + if (!mask) + return 0; + + /* Count the total number of slots provided by all 4 lanes */ + for (i = 0; i < AXG_TDM_NUM_LANES; i++) + slots += hweight32(mask[i]); + + return slots; +} + +int axg_tdm_set_tdm_slots(struct snd_soc_dai *dai, u32 *tx_mask, + u32 *rx_mask, unsigned int slots, + unsigned int slot_width) +{ + struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai); + struct axg_tdm_stream *tx = (struct axg_tdm_stream *) + dai->playback_dma_data; + struct axg_tdm_stream *rx = (struct axg_tdm_stream *) + dai->capture_dma_data; + unsigned int tx_slots, rx_slots; + + tx_slots = axg_tdm_slots_total(tx_mask); + rx_slots = axg_tdm_slots_total(rx_mask); + + /* We should at least have a slot for a valid interface */ + if (!tx_slots && !rx_slots) { + dev_err(dai->dev, "interface has no slot\n"); + return -EINVAL; + } + + /* + * Amend the dai driver channel number and let dpcm channel merge do + * its job + */ + if (tx) { + tx->mask = tx_mask; + dai->driver->playback.channels_max = tx_slots; + } + + if (rx) { + rx->mask = rx_mask; + dai->driver->capture.channels_max = rx_slots; + } + + iface->slots = slots; + + switch (slot_width) { + case 0: + /* defaults width to 32 if not provided */ + iface->slot_width = 32; + break; + case 8: + case 16: + case 24: + case 32: + iface->slot_width = slot_width; + break; + default: + dev_err(dai->dev, "unsupported slot width: %d\n", slot_width); + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL_GPL(axg_tdm_set_tdm_slots); + +static int axg_tdm_iface_set_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai); + int ret = -ENOTSUPP; + + if (dir == SND_SOC_CLOCK_OUT && clk_id == 0) { + if (!iface->mclk) { + dev_warn(dai->dev, "master clock not provided\n"); + } else { + ret = clk_set_rate(iface->mclk, freq); + if (!ret) + iface->mclk_rate = freq; + } + } + + return ret; +} + +static int axg_tdm_iface_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai); + + /* These modes are not supported */ + if (fmt & (SND_SOC_DAIFMT_CBS_CFM | SND_SOC_DAIFMT_CBM_CFS)) { + dev_err(dai->dev, "only CBS_CFS and CBM_CFM are supported\n"); + return -EINVAL; + } + + /* If the TDM interface is the clock master, it requires mclk */ + if (!iface->mclk && (fmt & SND_SOC_DAIFMT_CBS_CFS)) { + dev_err(dai->dev, "cpu clock master: mclk missing\n"); + return -ENODEV; + } + + iface->fmt = fmt; + return 0; +} + +static int axg_tdm_iface_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai); + struct axg_tdm_stream *ts = + snd_soc_dai_get_dma_data(dai, substream); + int ret; + + if (!axg_tdm_slots_total(ts->mask)) { + dev_err(dai->dev, "interface has not slots\n"); + return -EINVAL; + } + + /* Apply component wide rate symmetry */ + if (dai->component->active) { + ret = snd_pcm_hw_constraint_single(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, + iface->rate); + if (ret < 0) { + dev_err(dai->dev, + "can't set iface rate constraint\n"); + return ret; + } + } + + return 0; +} + +static int axg_tdm_iface_set_stream(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai); + struct axg_tdm_stream *ts = snd_soc_dai_get_dma_data(dai, substream); + unsigned int channels = params_channels(params); + unsigned int width = params_width(params); + + /* Save rate and sample_bits for component symmetry */ + iface->rate = params_rate(params); + + /* Make sure this interface can cope with the stream */ + if (axg_tdm_slots_total(ts->mask) < channels) { + dev_err(dai->dev, "not enough slots for channels\n"); + return -EINVAL; + } + + if (iface->slot_width < width) { + dev_err(dai->dev, "incompatible slots width for stream\n"); + return -EINVAL; + } + + /* Save the parameter for tdmout/tdmin widgets */ + ts->physical_width = params_physical_width(params); + ts->width = params_width(params); + ts->channels = params_channels(params); + + return 0; +} + +static int axg_tdm_iface_set_lrclk(struct snd_soc_dai *dai, + struct snd_pcm_hw_params *params) +{ + struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai); + unsigned int ratio_num; + int ret; + + ret = clk_set_rate(iface->lrclk, params_rate(params)); + if (ret) { + dev_err(dai->dev, "setting sample clock failed: %d\n", ret); + return ret; + } + + switch (iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_LEFT_J: + case SND_SOC_DAIFMT_RIGHT_J: + /* 50% duty cycle ratio */ + ratio_num = 1; + break; + + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: + /* + * A zero duty cycle ratio will result in setting the mininum + * ratio possible which, for this clock, is 1 cycle of the + * parent bclk clock high and the rest low, This is exactly + * what we want here. + */ + ratio_num = 0; + break; + + default: + return -EINVAL; + } + + ret = clk_set_duty_cycle(iface->lrclk, ratio_num, 2); + if (ret) { + dev_err(dai->dev, + "setting sample clock duty cycle failed: %d\n", ret); + return ret; + } + + /* Set sample clock inversion */ + ret = clk_set_phase(iface->lrclk, + axg_tdm_lrclk_invert(iface->fmt) ? 180 : 0); + if (ret) { + dev_err(dai->dev, + "setting sample clock phase failed: %d\n", ret); + return ret; + } + + return 0; +} + +static int axg_tdm_iface_set_sclk(struct snd_soc_dai *dai, + struct snd_pcm_hw_params *params) +{ + struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai); + unsigned long srate; + int ret; + + srate = iface->slots * iface->slot_width * params_rate(params); + + if (!iface->mclk_rate) { + /* If no specific mclk is requested, default to bit clock * 4 */ + clk_set_rate(iface->mclk, 4 * srate); + } else { + /* Check if we can actually get the bit clock from mclk */ + if (iface->mclk_rate % srate) { + dev_err(dai->dev, + "can't derive sclk %lu from mclk %lu\n", + srate, iface->mclk_rate); + return -EINVAL; + } + } + + ret = clk_set_rate(iface->sclk, srate); + if (ret) { + dev_err(dai->dev, "setting bit clock failed: %d\n", ret); + return ret; + } + + /* Set the bit clock inversion */ + ret = clk_set_phase(iface->sclk, + axg_tdm_sclk_invert(iface->fmt) ? 0 : 180); + if (ret) { + dev_err(dai->dev, "setting bit clock phase failed: %d\n", ret); + return ret; + } + + return ret; +} + +static int axg_tdm_iface_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai); + int ret; + + switch (iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_LEFT_J: + case SND_SOC_DAIFMT_RIGHT_J: + if (iface->slots > 2) { + dev_err(dai->dev, "bad slot number for format: %d\n", + iface->slots); + return -EINVAL; + } + break; + + case SND_SOC_DAI_FORMAT_DSP_A: + case SND_SOC_DAI_FORMAT_DSP_B: + break; + + default: + dev_err(dai->dev, "unsupported dai format\n"); + return -EINVAL; + } + + ret = axg_tdm_iface_set_stream(substream, params, dai); + if (ret) + return ret; + + if (iface->fmt & SND_SOC_DAIFMT_CBS_CFS) { + ret = axg_tdm_iface_set_sclk(dai, params); + if (ret) + return ret; + + ret = axg_tdm_iface_set_lrclk(dai, params); + if (ret) + return ret; + } + + return 0; +} + +static int axg_tdm_iface_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct axg_tdm_stream *ts = snd_soc_dai_get_dma_data(dai, substream); + + /* Stop all attached formatters */ + axg_tdm_stream_stop(ts); + + return 0; +} + +static int axg_tdm_iface_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct axg_tdm_stream *ts = snd_soc_dai_get_dma_data(dai, substream); + + /* Force all attached formatters to update */ + return axg_tdm_stream_reset(ts); +} + +static int axg_tdm_iface_remove_dai(struct snd_soc_dai *dai) +{ + if (dai->capture_dma_data) + axg_tdm_stream_free(dai->capture_dma_data); + + if (dai->playback_dma_data) + axg_tdm_stream_free(dai->playback_dma_data); + + return 0; +} + +static int axg_tdm_iface_probe_dai(struct snd_soc_dai *dai) +{ + struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai); + + if (dai->capture_widget) { + dai->capture_dma_data = axg_tdm_stream_alloc(iface); + if (!dai->capture_dma_data) + return -ENOMEM; + } + + if (dai->playback_widget) { + dai->playback_dma_data = axg_tdm_stream_alloc(iface); + if (!dai->playback_dma_data) { + axg_tdm_iface_remove_dai(dai); + return -ENOMEM; + } + } + + return 0; +} + +static const struct snd_soc_dai_ops axg_tdm_iface_ops = { + .set_sysclk = axg_tdm_iface_set_sysclk, + .set_fmt = axg_tdm_iface_set_fmt, + .startup = axg_tdm_iface_startup, + .hw_params = axg_tdm_iface_hw_params, + .prepare = axg_tdm_iface_prepare, + .hw_free = axg_tdm_iface_hw_free, +}; + +/* TDM Backend DAIs */ +static const struct snd_soc_dai_driver axg_tdm_iface_dai_drv[] = { + [TDM_IFACE_PAD] = { + .name = "TDM Pad", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = AXG_TDM_CHANNEL_MAX, + .rates = AXG_TDM_RATES, + .formats = AXG_TDM_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = AXG_TDM_CHANNEL_MAX, + .rates = AXG_TDM_RATES, + .formats = AXG_TDM_FORMATS, + }, + .id = TDM_IFACE_PAD, + .ops = &axg_tdm_iface_ops, + .probe = axg_tdm_iface_probe_dai, + .remove = axg_tdm_iface_remove_dai, + }, + [TDM_IFACE_LOOPBACK] = { + .name = "TDM Loopback", + .capture = { + .stream_name = "Loopback", + .channels_min = 1, + .channels_max = AXG_TDM_CHANNEL_MAX, + .rates = AXG_TDM_RATES, + .formats = AXG_TDM_FORMATS, + }, + .id = TDM_IFACE_LOOPBACK, + .ops = &axg_tdm_iface_ops, + .probe = axg_tdm_iface_probe_dai, + .remove = axg_tdm_iface_remove_dai, + }, +}; + +static int axg_tdm_iface_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) +{ + struct axg_tdm_iface *iface = snd_soc_component_get_drvdata(component); + enum snd_soc_bias_level now = + snd_soc_component_get_bias_level(component); + int ret = 0; + + switch (level) { + case SND_SOC_BIAS_PREPARE: + if (now == SND_SOC_BIAS_STANDBY) + ret = clk_prepare_enable(iface->mclk); + break; + + case SND_SOC_BIAS_STANDBY: + if (now == SND_SOC_BIAS_PREPARE) + clk_disable_unprepare(iface->mclk); + break; + + case SND_SOC_BIAS_OFF: + case SND_SOC_BIAS_ON: + break; + } + + return ret; +} + +static const struct snd_soc_component_driver axg_tdm_iface_component_drv = { + .set_bias_level = axg_tdm_iface_set_bias_level, +}; + +static const struct of_device_id axg_tdm_iface_of_match[] = { + { .compatible = "amlogic,axg-tdm-iface", }, + {} +}; +MODULE_DEVICE_TABLE(of, axg_tdm_iface_of_match); + +static int axg_tdm_iface_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct snd_soc_dai_driver *dai_drv; + struct axg_tdm_iface *iface; + int ret, i; + + iface = devm_kzalloc(dev, sizeof(*iface), GFP_KERNEL); + if (!iface) + return -ENOMEM; + platform_set_drvdata(pdev, iface); + + /* + * Duplicate dai driver: depending on the slot masks configuration + * We'll change the number of channel provided by DAI stream, so dpcm + * channel merge can be done properly + */ + dai_drv = devm_kcalloc(dev, ARRAY_SIZE(axg_tdm_iface_dai_drv), + sizeof(*dai_drv), GFP_KERNEL); + if (!dai_drv) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(axg_tdm_iface_dai_drv); i++) + memcpy(&dai_drv[i], &axg_tdm_iface_dai_drv[i], + sizeof(*dai_drv)); + + /* Bit clock provided on the pad */ + iface->sclk = devm_clk_get(dev, "sclk"); + if (IS_ERR(iface->sclk)) { + ret = PTR_ERR(iface->sclk); + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to get sclk: %d\n", ret); + return ret; + } + + /* Sample clock provided on the pad */ + iface->lrclk = devm_clk_get(dev, "lrclk"); + if (IS_ERR(iface->lrclk)) { + ret = PTR_ERR(iface->lrclk); + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to get lrclk: %d\n", ret); + return ret; + } + + /* + * mclk maybe be missing when the cpu dai is in slave mode and + * the codec does not require it to provide a master clock. + * At this point, ignore the error if mclk is missing. We'll + * throw an error if the cpu dai is master and mclk is missing + */ + iface->mclk = devm_clk_get(dev, "mclk"); + if (IS_ERR(iface->mclk)) { + ret = PTR_ERR(iface->mclk); + if (ret == -ENOENT) { + iface->mclk = NULL; + } else { + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to get mclk: %d\n", ret); + return ret; + } + } + + return devm_snd_soc_register_component(dev, + &axg_tdm_iface_component_drv, dai_drv, + ARRAY_SIZE(axg_tdm_iface_dai_drv)); +} + +static struct platform_driver axg_tdm_iface_pdrv = { + .probe = axg_tdm_iface_probe, + .driver = { + .name = "axg-tdm-iface", + .of_match_table = axg_tdm_iface_of_match, + }, +}; +module_platform_driver(axg_tdm_iface_pdrv); + +MODULE_DESCRIPTION("Amlogic AXG TDM interface driver"); +MODULE_AUTHOR("Jerome Brunet "); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/meson/axg-tdm.h b/sound/soc/meson/axg-tdm.h index 435d95b86457..e578b6f40a07 100644 --- a/sound/soc/meson/axg-tdm.h +++ b/sound/soc/meson/axg-tdm.h @@ -71,4 +71,8 @@ static inline int axg_tdm_stream_reset(struct axg_tdm_stream *ts) return axg_tdm_stream_start(ts); } +int axg_tdm_set_tdm_slots(struct snd_soc_dai *dai, u32 *tx_mask, + u32 *rx_mask, unsigned int slots, + unsigned int slot_width); + #endif /* _MESON_AXG_TDM_H */ From patchwork Tue Jul 17 15:43:03 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jerome Brunet X-Patchwork-Id: 142181 Delivered-To: patch@linaro.org Received: by 2002:a2e:9754:0:0:0:0:0 with SMTP id f20-v6csp3821289ljj; Tue, 17 Jul 2018 08:43:57 -0700 (PDT) X-Google-Smtp-Source: AAOMgpdxZeCtWs6Cpzapz4nx0/TIJNupKko2YXiGHtz3qjsabcdhS3Xs6QeWsuuSfOwIOI/FA0qp X-Received: by 2002:a63:1015:: with SMTP id f21-v6mr2126667pgl.354.1531842237849; Tue, 17 Jul 2018 08:43:57 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1531842237; cv=none; d=google.com; s=arc-20160816; b=PJIJWm7xpGDOgONzKBvGwOLt8wAq8KV9cRzwJ8BdfrSjqyKKoVSqbECsbfuWRJYwK8 uuSxDlFb7q4ITEekl0na6sSz0qrDFZXnxWU8JyqMwfJnIZcWt/m6XBx8i9ZPqK65cmiU 8AitH1gtn131D2m+oHYLOGwkO1y4YPTdkdf234D8CRbrCEriWK9A4A5l1mOaDUwzGFwU 7CT3KKv7IWCBDsu1yTs7DzQwctgS3egep4jxTghdzsq2hRRP2wer84MV9Ood8M8Vz54t 6cWW56ffLVWjy3rPuSM40j73B3/swZjLDN5VV2uELdSX1umjerSOeQmX1vHaPU3qcjdD gVOA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:references:in-reply-to:message-id:date :subject:cc:to:from:dkim-signature:arc-authentication-results; bh=VBv8/QDWk+qHjJZaxe5JulpWKLqIxa2ERSD6Eqkd9xk=; b=GyO3txfJybHt/WrcyAiBGZ/TgxbpapTTIWQFIkv2cW6Ii+PFXi/g3V7aqTH3981baV lGHwFOfAwWEqpHRx973FinnAWt8sQTv05s5ud8LN5SzT2w5teovKrXmOhSDmSRYpIAGj Iy2XgzLTyUruDJSPYphRSEYti8fmX4F7EjZfpv3UWTrGWn6kAK24lQ+aRQlUP0JHUoaL h0OoXY7fHlTj7sjP+T+bafDjLjqp4/CenOjalOHGNJqEr+u/LiuV+I5w9vTApStP4t8V H64p8feFGZozZbkd4Bd9+di1YMdjSzjaKK9pNYzBW3mrEBJs6YeqeQum885LaSVnRd8h Dapg== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@baylibre-com.20150623.gappssmtp.com header.s=20150623 header.b=gZXCDJkb; spf=pass (google.com: best guess record for domain of devicetree-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=devicetree-owner@vger.kernel.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id q10-v6si1108536pge.674.2018.07.17.08.43.57; Tue, 17 Jul 2018 08:43:57 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of devicetree-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@baylibre-com.20150623.gappssmtp.com header.s=20150623 header.b=gZXCDJkb; spf=pass (google.com: best guess record for domain of devicetree-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=devicetree-owner@vger.kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1731613AbeGQQRL (ORCPT + 5 others); Tue, 17 Jul 2018 12:17:11 -0400 Received: from mail-wr1-f67.google.com ([209.85.221.67]:33776 "EHLO mail-wr1-f67.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1731545AbeGQQQo (ORCPT ); Tue, 17 Jul 2018 12:16:44 -0400 Received: by mail-wr1-f67.google.com with SMTP id g6-v6so1745872wrp.0 for ; Tue, 17 Jul 2018 08:43:29 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=baylibre-com.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=N6GTffq4VROTez5mVO69vAu2OUH/qIKN2B9n0MrCC48=; b=gZXCDJkbbxdMAlykgi/nKAkQ+iKUivQ0erasXVwOHa1GW1ovrHFawFOioCSN7CYHxz mFEQj91aGz86zm8Q960qzh3Mgz0Q6azu0TdcMALA1ViqJtQhGo0a4oh3PfL48IzIMxc/ S+OAgmcUP474AxciyUlA9Owfzm4vlJtvdTfXDKoBZjYRmr1ddT3lAFLHSFZNEUqZi35t MniAqp8LlEQE1tgPbCd+qPDShQd8FVf6leJjrioudKW5UfBX/1UtDPxagpUlXrPnAXQW 0Qig/bfpwUSAKJuDmH4MRM+hbd9SOCxF4qroZK4ZjtIO4Xnv10RAvU/lNmh+po3b5ME6 W8fg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=N6GTffq4VROTez5mVO69vAu2OUH/qIKN2B9n0MrCC48=; b=G3mOq0a9RDBRXA6+EiPT7Zghbkd/w3oF3BzU2fuhB3IoWc5bfE0576/Vnzsmnf+hQh iULJwRJ+vqSk3d2S6tbPGFiMzXRRLYHtTjeTOn76fMhmDT5h5kplAgYB8/A+YLOrP1NO 4Aw9lzddAqypmHkroAfdtQx6tsaQUgucZJNkfI1TjFJFzZ71ibm9DI+k7kOB3smgXNbG YGgr3aLnvfpryS6J4JVLgS3Rvquj3GF7Wa9CHvGf4vPLkz5/b1anzzZvVVg/5KXaL9ZZ qS+BG35y15e9mVW11afJjJya0hYhSKZXI6GJhSF6Al6+3P3L1vH0hvgyRWT8UW68g4LX vYHw== X-Gm-Message-State: AOUpUlEhHj016U8c3BFgAMiRWT9ff+/Oa1ZVpHviSHUebC5HG0f8oXI8 7umdZeD6qRHFU7AIdvfTHdeg1g== X-Received: by 2002:adf:f210:: with SMTP id p16-v6mr1827816wro.184.1531842208515; Tue, 17 Jul 2018 08:43:28 -0700 (PDT) Received: from boomer.baylibre.local ([90.63.244.31]) by smtp.googlemail.com with ESMTPSA id s12-v6sm17598252wmc.2.2018.07.17.08.43.27 (version=TLS1_2 cipher=ECDHE-RSA-CHACHA20-POLY1305 bits=256/256); Tue, 17 Jul 2018 08:43:27 -0700 (PDT) From: Jerome Brunet To: Mark Brown , Liam Girdwood , Kevin Hilman , Carlo Caione Cc: Jerome Brunet , alsa-devel@alsa-project.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-amlogic@lists.infradead.org Subject: [PATCH RESEND 14/15] ASoC: meson: add axg sound card DT bindings documentation Date: Tue, 17 Jul 2018 17:43:03 +0200 Message-Id: <20180717154304.9973-15-jbrunet@baylibre.com> X-Mailer: git-send-email 2.14.4 In-Reply-To: <20180717154304.9973-1-jbrunet@baylibre.com> References: <20180717154304.9973-1-jbrunet@baylibre.com> Sender: devicetree-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: devicetree@vger.kernel.org Add the DT bindings documentation for axg sound card Signed-off-by: Jerome Brunet --- .../bindings/sound/amlogic,axg-sound-card.txt | 124 +++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/amlogic,axg-sound-card.txt -- 2.14.4 -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html diff --git a/Documentation/devicetree/bindings/sound/amlogic,axg-sound-card.txt b/Documentation/devicetree/bindings/sound/amlogic,axg-sound-card.txt new file mode 100644 index 000000000000..39e005da0407 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/amlogic,axg-sound-card.txt @@ -0,0 +1,124 @@ +Amlogic AXG sound card: + +Required properties: + +- compatible: "amlogic,axg-sound-card" +- amlogic,name : User specified audio sound card name, one string + +Optional properties: + +- amlogic,aux-devs : List of phandles pointing to auxiliary devices +- amlogic,widgets : Please refer to widgets.txt. +- amlogic,routing : A list of the connections between audio components. + +Subnodes: + +- amlogic,dai-link: Container for dai-link level properties and the + CODEC sub-nodes. There should be at least one (and + probably) subnode of this type. + +Required dai-link properties: + +- sound-dai: phandle and port of the CPU DAI. + +Required TDM Backend dai-link properties: +- dai-format : CPU/CODEC common audio format + +Optional TDM Backend dai-link properties: +- dai-tdm-slot-rx-mask-{0,1,2,3}: Receive direction slot masks +- dai-tdm-slot-tx-mask-{0,1,2,3}: Transmit direction slot masks + When omitted, mask is assumed to have to no + slots. A valid must have at one slot, so at + least one these mask should be provided with + an enabled slot. +- dai-tdm-slot-num : Please refer to tdm-slot.txt. + If omitted, slot number is set to accommodate the largest + mask provided. +- dai-tdm-slot-width : Please refer to tdm-slot.txt. default to 32 if omitted. +- mclk-fs : Multiplication factor between stream rate and mclk + +Backend dai-link subnodes: + +- codec: dai-link representing backend links should have at least one subnode. + One subnode for each codec of the dai-link. + dai-link representing frontend links have no codec, therefore have no + subnodes + +Required codec subnodes properties: + +- sound-dai: phandle and port of the CODEC DAI. + +Optional codec subnodes properties: + +- dai-tdm-slot-tx-mask : Please refer to tdm-slot.txt. +- dai-tdm-slot-rx-mask : Please refer to tdm-slot.txt. + +Example: + +sound { + compatible = "amlogic,axg-sound-card"; + amlogic,name = "AXG-S420"; + amlogic,aux-devs = <&tdmin_a>, <&tdmout_c>; + amlogic,widgets = "Line", "Lineout", + "Line", "Linein", + "Speaker", "Speaker1 Left", + "Speaker", "Speaker1 Right"; + "Speaker", "Speaker2 Left", + "Speaker", "Speaker2 Right"; + amlogic,routing = "TDMOUT_C IN 0", "FRDDR_A OUT 2", + "SPDIFOUT IN 0", "FRDDR_A OUT 3", + "TDM_C Playback", "TDMOUT_C OUT", + "TDMIN_A IN 2", "TDM_C Capture", + "TDMIN_A IN 5", "TDM_C Loopback", + "TODDR_A IN 0", "TDMIN_A OUT", + "Lineout", "Lineout AOUTL", + "Lineout", "Lineout AOUTR", + "Speaker1 Left", "SPK1 OUT_A", + "Speaker2 Left", "SPK2 OUT_A", + "Speaker1 Right", "SPK1 OUT_B", + "Speaker2 Right", "SPK2 OUT_B", + "Linein AINL", "Linein", + "Linein AINR", "Linein"; + + amlogic,dai-link@0 { + sound-dai = <&frddr_a>; + }; + + amlogic,dai-link@1 { + sound-dai = <&toddr_a>; + }; + + amlogic,dai-link@2 { + sound-dai = <&tdmif_c>; + dai-format = "i2s"; + dai-tdm-slot-tx-mask-2 = <1 1>; + dai-tdm-slot-tx-mask-3 = <1 1>; + dai-tdm-slot-rx-mask-1 = <1 1>; + mclk-fs = <256>; + + codec@0 { + sound-dai = <&lineout>; + }; + + codec@1 { + sound-dai = <&speaker_amp1>; + }; + + codec@2 { + sound-dai = <&speaker_amp2>; + }; + + codec@3 { + sound-dai = <&linein>; + }; + + }; + + amlogic,dai-link@4 { + sound-dai = <&spdifout>; + + codec { + sound-dai = <&spdif_dit>; + }; + }; +}; From patchwork Tue Jul 17 15:43:04 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jerome Brunet X-Patchwork-Id: 142179 Delivered-To: patch@linaro.org Received: by 2002:a2e:9754:0:0:0:0:0 with SMTP id f20-v6csp3820926ljj; Tue, 17 Jul 2018 08:43:35 -0700 (PDT) X-Google-Smtp-Source: AAOMgpfRodErGiTC5H2FJlFQ2NuFlAU/nvjJinYwMWQmEpc/Ji39GeiYk2+U0OBtL/lqFzB2BSXl X-Received: by 2002:a17:902:d807:: with SMTP id a7-v6mr2121143plz.214.1531842215107; Tue, 17 Jul 2018 08:43:35 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1531842215; cv=none; d=google.com; s=arc-20160816; b=CHr2IxPJt2RZXjGRGaP4Co9gTfash3sqz5S6+5z8VsJamMvv2g+n+vDpYKMfiQe4sj 03h6u/M88uOjfvcwv8ViKilQlkwhwF4yvDdS3giHof7rY33LWncM4C4RuccEoRB8Hgoh 3SUOBObUsy5LX1li1YFywYU0+ZO39JhI67RGwHnTziOUWHsHZ4+eZv3gGvXtCz8dxcPZ hywlBwftcGw+UPwRr5+Nj31+4U4wuSaatyxLYNipSaWXbz9SFcFoeJGkNTT7sZ5y7yQu 2LJjOg/jS55BmbjIVQiypBeX2L/WMg3QSiHh9FUbf4h0+E32aPb6QJOFlYw8wtmF3BqE fa0g== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:references:in-reply-to:message-id:date :subject:cc:to:from:dkim-signature:arc-authentication-results; bh=eQH5FfxfpVEVcJwDqD+dgaqcqqPgOyvW3VUc5DaZVv0=; b=UirmeIQlKqH5xt+en8qZN4PdnxK2NlvzdxE+OhC4szfYDA20XXguJrHKppdM98hydj qUs6HvS8Z91fteoyWvsfs80gGU0Rz4wvn2T0iaSjcAjS3xHw+Pq06AgPt+vB6Cc29YNg 9BHVAw1O+O5jlOlUOufXk5poX8ci5ljddf9Kr09Ci+uBFT9TVlabmMoXq7CnGzp1nJ4/ DxqW71w2fgCYiOzYJS7uL5s4Q8J0rVYwcHZuuhcdOwOpos+c7ajbMnQkZ4HiHzhYK4AJ f67U+7AynFksZciq7UzqUvA7Gb9ifxiHyl5BejqAQXVtw6Jbl3i+BlsLJi2iHCly1p1u yGIg== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@baylibre-com.20150623.gappssmtp.com header.s=20150623 header.b=jVB6AvTS; spf=pass (google.com: best guess record for domain of devicetree-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=devicetree-owner@vger.kernel.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id j67-v6si1260057pgc.186.2018.07.17.08.43.34; Tue, 17 Jul 2018 08:43:35 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of devicetree-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@baylibre-com.20150623.gappssmtp.com header.s=20150623 header.b=jVB6AvTS; spf=pass (google.com: best guess record for domain of devicetree-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=devicetree-owner@vger.kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1729683AbeGQQQs (ORCPT + 5 others); Tue, 17 Jul 2018 12:16:48 -0400 Received: from mail-wm0-f65.google.com ([74.125.82.65]:52370 "EHLO mail-wm0-f65.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1730107AbeGQQQr (ORCPT ); Tue, 17 Jul 2018 12:16:47 -0400 Received: by mail-wm0-f65.google.com with SMTP id o11-v6so1982632wmh.2 for ; Tue, 17 Jul 2018 08:43:30 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=baylibre-com.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=G74vTuT1DlINnuNuMu1EDadR9iCBF+SYA+UnFGqXqHY=; b=jVB6AvTSEV0dMnhLKSy1Lhwfz+mJSlRPpx3difAM3gkr2s/rJkPEbeSgqp1zCu3aN+ zzHnY8ZHTdhPF0rFjW42jmVtFuNxDKKrQwtrGPrEDDXxsvIdv7bpp/3XSQVkiJhRTsTc V+TMO4HY/lBu3svTEtzqiL8HVQbKkS0SLSqd5MiTI7NX3t4I9loj1hc0ifM+pnqGcEuQ ioKcH46argyLk3AAuo8qWUpc0fywNr17AlYeo3jK2YDlt2+JBliQoQULLORS08YpsYa+ fem92LgdRyTSC+jTShpk+WoWDmG+PeADsfyM4bh+atM7adAY657Wvl6p7pyrjhQuiZmj IUsg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=G74vTuT1DlINnuNuMu1EDadR9iCBF+SYA+UnFGqXqHY=; b=UrvHlrW5wSp3ixfyHdLMkt4ex76UoVKfTMB2jnXAqVNmjAUglZ5Ob8NREgSaGpQQuN nU9RKhWvNjGrdBlJwD+bnjJ5otiwd74BNGWtUeziyBE0UCCWSI5LE46DdNEiVsY6oDq4 0sQVVPAyWPDNn9yIRuqWrBko/wQilkL131fXlvJCcbMexNTulnW37vN30SCe7VWMN7qa dQeVtNPDFFmVXdY0Q7nkNfYEh7Tv1nLJKBlUCG3KjOSkrMFBZCV143AHg14TyO70Nuwy AWRE9SPINQHM1JBEmQSYSwa2QJDxc5Ex6w4qFkWxf+45xE7LmQs0mhMF17SZNk2XFUV0 cCjA== X-Gm-Message-State: AOUpUlHzDR6wSrBkHeNVW5Or70nF7Pi7p6rek6I99qu3oHWJWob2iMmE hrBxOb09U1wRfygM+0qorh1k/6Ro X-Received: by 2002:a1c:b788:: with SMTP id h130-v6mr1810138wmf.27.1531842209548; Tue, 17 Jul 2018 08:43:29 -0700 (PDT) Received: from boomer.baylibre.local ([90.63.244.31]) by smtp.googlemail.com with ESMTPSA id s12-v6sm17598252wmc.2.2018.07.17.08.43.28 (version=TLS1_2 cipher=ECDHE-RSA-CHACHA20-POLY1305 bits=256/256); Tue, 17 Jul 2018 08:43:28 -0700 (PDT) From: Jerome Brunet To: Mark Brown , Liam Girdwood , Kevin Hilman , Carlo Caione Cc: Jerome Brunet , alsa-devel@alsa-project.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-amlogic@lists.infradead.org Subject: [PATCH RESEND 15/15] ASoC: meson: add axg sound card support Date: Tue, 17 Jul 2018 17:43:04 +0200 Message-Id: <20180717154304.9973-16-jbrunet@baylibre.com> X-Mailer: git-send-email 2.14.4 In-Reply-To: <20180717154304.9973-1-jbrunet@baylibre.com> References: <20180717154304.9973-1-jbrunet@baylibre.com> Sender: devicetree-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: devicetree@vger.kernel.org Add the axg sound card to handle the specifities of the axg audio sub system. This card is required to: * setup the dpcm links specific to the AXG (with a cpu sound dai) * handle the 4 lanes masks of the tdm interfaces * add the loopback link when a tdm pad interface has a playback stream * handle multi-codec links Signed-off-by: Jerome Brunet --- sound/soc/meson/Kconfig | 11 + sound/soc/meson/Makefile | 2 + sound/soc/meson/axg-card.c | 671 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 684 insertions(+) create mode 100644 sound/soc/meson/axg-card.c -- 2.14.4 -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html diff --git a/sound/soc/meson/Kconfig b/sound/soc/meson/Kconfig index 00d05df67b52..4cf93c05a982 100644 --- a/sound/soc/meson/Kconfig +++ b/sound/soc/meson/Kconfig @@ -43,6 +43,17 @@ config SND_MESON_AXG_TDMOUT Select Y or M to add support for TDM output formatter embedded in the Amlogic AXG SoC family +config SND_MESON_AXG_SOUND_CARD + tristate "Amlogic AXG Sound Card Support" + select SND_MESON_AXG_TDM_INTERFACE + imply SND_MESON_AXG_FRDDR + imply SND_MESON_AXG_TODDR + imply SND_MESON_AXG_TDMIN + imply SND_MESON_AXG_TDMOUT + imply SND_MESON_AXG_SPDIFOUT + help + Select Y or M to add support for the AXG SoC sound card + config SND_MESON_AXG_SPDIFOUT tristate "Amlogic AXG SPDIF Output Support" imply SND_SOC_SPDIF diff --git a/sound/soc/meson/Makefile b/sound/soc/meson/Makefile index f62833fb44d8..c5e003b093db 100644 --- a/sound/soc/meson/Makefile +++ b/sound/soc/meson/Makefile @@ -7,6 +7,7 @@ snd-soc-meson-axg-tdm-formatter-objs := axg-tdm-formatter.o snd-soc-meson-axg-tdm-interface-objs := axg-tdm-interface.o snd-soc-meson-axg-tdmin-objs := axg-tdmin.o snd-soc-meson-axg-tdmout-objs := axg-tdmout.o +snd-soc-meson-axg-sound-card-objs := axg-card.o snd-soc-meson-axg-spdifout-objs := axg-spdifout.o obj-$(CONFIG_SND_MESON_AXG_FIFO) += snd-soc-meson-axg-fifo.o @@ -16,4 +17,5 @@ obj-$(CONFIG_SND_MESON_AXG_TDM_FORMATTER) += snd-soc-meson-axg-tdm-formatter.o obj-$(CONFIG_SND_MESON_AXG_TDM_INTERFACE) += snd-soc-meson-axg-tdm-interface.o obj-$(CONFIG_SND_MESON_AXG_TDMIN) += snd-soc-meson-axg-tdmin.o obj-$(CONFIG_SND_MESON_AXG_TDMOUT) += snd-soc-meson-axg-tdmout.o +obj-$(CONFIG_SND_MESON_AXG_SOUND_CARD) += snd-soc-meson-axg-sound-card.o obj-$(CONFIG_SND_MESON_AXG_SPDIFOUT) += snd-soc-meson-axg-spdifout.o diff --git a/sound/soc/meson/axg-card.c b/sound/soc/meson/axg-card.c new file mode 100644 index 000000000000..d6d1081d94ad --- /dev/null +++ b/sound/soc/meson/axg-card.c @@ -0,0 +1,671 @@ +// SPDX-License-Identifier: (GPL-2.0 OR MIT) +// +// Copyright (c) 2018 BayLibre, SAS. +// Author: Jerome Brunet + +#include +#include +#include +#include + +#include "axg-tdm.h" + +struct axg_card { + struct snd_soc_card card; + void **link_data; +}; + +struct axg_dai_link_tdm_mask { + u32 tx; + u32 rx; +}; + +struct axg_dai_link_tdm_data { + unsigned int mclk_fs; + unsigned int slots; + unsigned int slot_width; + u32 *tx_mask; + u32 *rx_mask; + struct axg_dai_link_tdm_mask *codec_masks; +}; + +#define PREFIX "amlogic," + +static int axg_card_reallocate_links(struct axg_card *priv, + unsigned int num_links) +{ + struct snd_soc_dai_link *links; + void **ldata; + + links = krealloc(priv->card.dai_link, + num_links * sizeof(*priv->card.dai_link), + GFP_KERNEL | __GFP_ZERO); + ldata = krealloc(priv->link_data, + num_links * sizeof(*priv->link_data), + GFP_KERNEL | __GFP_ZERO); + + if (!links || !ldata) { + dev_err(priv->card.dev, "failed to allocate links\n"); + return -ENOMEM; + } + + priv->card.dai_link = links; + priv->link_data = ldata; + priv->card.num_links = num_links; + return 0; +} + +static int axg_card_parse_dai(struct snd_soc_card *card, + struct device_node *node, + struct device_node **dai_of_node, + const char **dai_name) +{ + struct of_phandle_args args; + int ret; + + if (!dai_name || !dai_of_node || !node) + return -EINVAL; + + ret = of_parse_phandle_with_args(node, "sound-dai", + "#sound-dai-cells", 0, &args); + if (ret) { + if (ret != -EPROBE_DEFER) + dev_err(card->dev, "can't parse dai %d\n", ret); + return ret; + } + *dai_of_node = args.np; + + return snd_soc_get_dai_name(&args, dai_name); +} + +static int axg_card_set_link_name(struct snd_soc_card *card, + struct snd_soc_dai_link *link, + const char *prefix) +{ + char *name = devm_kasprintf(card->dev, GFP_KERNEL, "%s.%s", + prefix, link->cpu_of_node->full_name); + if (!name) + return -ENOMEM; + + link->name = name; + link->stream_name = name; + + return 0; +} + +static void axg_card_clean_references(struct axg_card *priv) +{ + struct snd_soc_card *card = &priv->card; + struct snd_soc_dai_link *link; + int i, j; + + if (card->dai_link) { + for (i = 0; i < card->num_links; i++) { + link = &card->dai_link[i]; + of_node_put(link->cpu_of_node); + for (j = 0; j < link->num_codecs; j++) + of_node_put(link->codecs[j].of_node); + } + } + + if (card->aux_dev) { + for (i = 0; i < card->num_aux_devs; i++) + of_node_put(card->aux_dev[i].codec_of_node); + } + + kfree(card->dai_link); + kfree(priv->link_data); +} + +static int axg_card_add_aux_devices(struct snd_soc_card *card) +{ + struct device_node *node = card->dev->of_node; + struct snd_soc_aux_dev *aux; + int num, i; + + num = of_count_phandle_with_args(node, PREFIX "aux-devs", NULL); + if (num == -ENOENT) { + /* + * It is ok to have no auxiliary devices but for this card it + * is a strange situtation. Let's warn the about it. + */ + dev_warn(card->dev, "card has no auxiliary devices\n"); + return 0; + } else if (num < 0) { + dev_err(card->dev, "error getting auxiliary devices: %d\n", + num); + return num; + } + + aux = devm_kcalloc(card->dev, num, sizeof(*aux), GFP_KERNEL); + if (!aux) + return -ENOMEM; + card->aux_dev = aux; + card->num_aux_devs = num; + + for (i = 0; i < card->num_aux_devs; i++, aux++) { + aux->codec_of_node = of_parse_phandle(node, + PREFIX "aux-devs", i); + if (!aux->codec_of_node) + return -EINVAL; + } + + return 0; +} + +static int axg_card_tdm_be_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct axg_card *priv = snd_soc_card_get_drvdata(rtd->card); + struct axg_dai_link_tdm_data *be = + (struct axg_dai_link_tdm_data *)priv->link_data[rtd->num]; + struct snd_soc_dai *codec_dai; + unsigned int mclk; + int ret, i; + + if (be->mclk_fs) { + mclk = params_rate(params) * be->mclk_fs; + + for (i = 0; i < rtd->num_codecs; i++) { + codec_dai = rtd->codec_dais[i]; + ret = snd_soc_dai_set_sysclk(codec_dai, 0, mclk, + SND_SOC_CLOCK_IN); + if (ret && ret != -ENOTSUPP) + return ret; + } + + ret = snd_soc_dai_set_sysclk(rtd->cpu_dai, 0, mclk, + SND_SOC_CLOCK_OUT); + if (ret && ret != -ENOTSUPP) + return ret; + } + + return 0; +} + +static const struct snd_soc_ops axg_card_tdm_be_ops = { + .hw_params = axg_card_tdm_be_hw_params, +}; + +static int axg_card_tdm_dai_init(struct snd_soc_pcm_runtime *rtd) +{ + struct axg_card *priv = snd_soc_card_get_drvdata(rtd->card); + struct axg_dai_link_tdm_data *be = + (struct axg_dai_link_tdm_data *)priv->link_data[rtd->num]; + struct snd_soc_dai *codec_dai; + int ret, i; + + for (i = 0; i < rtd->num_codecs; i++) { + codec_dai = rtd->codec_dais[i]; + ret = snd_soc_dai_set_tdm_slot(codec_dai, + be->codec_masks[i].tx, + be->codec_masks[i].rx, + be->slots, be->slot_width); + if (ret && ret != -ENOTSUPP) { + dev_err(codec_dai->dev, + "setting tdm link slots failed\n"); + return ret; + } + } + + ret = axg_tdm_set_tdm_slots(rtd->cpu_dai, be->tx_mask, be->rx_mask, + be->slots, be->slot_width); + if (ret) { + dev_err(rtd->cpu_dai->dev, "setting tdm link slots failed\n"); + return ret; + } + + return 0; +} + +static int axg_card_tdm_dai_lb_init(struct snd_soc_pcm_runtime *rtd) +{ + struct axg_card *priv = snd_soc_card_get_drvdata(rtd->card); + struct axg_dai_link_tdm_data *be = + (struct axg_dai_link_tdm_data *)priv->link_data[rtd->num]; + int ret; + + /* The loopback rx_mask is the pad tx_mask */ + ret = axg_tdm_set_tdm_slots(rtd->cpu_dai, NULL, be->tx_mask, + be->slots, be->slot_width); + if (ret) { + dev_err(rtd->cpu_dai->dev, "setting tdm link slots failed\n"); + return ret; + } + + return 0; +} + +static int axg_card_add_tdm_loopback(struct snd_soc_card *card, + int *index) +{ + struct axg_card *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_dai_link *pad = &card->dai_link[*index]; + struct snd_soc_dai_link *lb; + int ret; + + /* extend links */ + ret = axg_card_reallocate_links(priv, card->num_links + 1); + if (ret) + return ret; + + lb = &card->dai_link[*index + 1]; + + lb->name = kasprintf(GFP_KERNEL, "%s-lb", pad->name); + if (!lb->name) + return -ENOMEM; + + lb->stream_name = lb->name; + lb->cpu_of_node = pad->cpu_of_node; + lb->cpu_dai_name = "TDM Loopback"; + lb->codec_name = "snd-soc-dummy"; + lb->codec_dai_name = "snd-soc-dummy-dai"; + lb->dpcm_capture = 1; + lb->no_pcm = 1; + lb->ops = &axg_card_tdm_be_ops; + lb->init = axg_card_tdm_dai_lb_init; + + /* Provide the same link data to the loopback */ + priv->link_data[*index + 1] = priv->link_data[*index]; + + /* + * axg_card_clean_references() will iterate over this link, + * make sure the node count is balanced + */ + of_node_get(lb->cpu_of_node); + + /* Let add_links continue where it should */ + *index += 1; + + return 0; +} + +static unsigned int axg_card_parse_daifmt(struct device_node *node, + struct device_node *cpu_node) +{ + struct device_node *bitclkmaster = NULL; + struct device_node *framemaster = NULL; + unsigned int daifmt; + + daifmt = snd_soc_of_parse_daifmt(node, PREFIX, + &bitclkmaster, &framemaster); + daifmt &= ~SND_SOC_DAIFMT_MASTER_MASK; + + /* If no master is provided, default to cpu master */ + if (!bitclkmaster || bitclkmaster == cpu_node) { + daifmt |= (!framemaster || framemaster == cpu_node) ? + SND_SOC_DAIFMT_CBS_CFS : SND_SOC_DAIFMT_CBS_CFM; + } else { + daifmt |= (!framemaster || framemaster == cpu_node) ? + SND_SOC_DAIFMT_CBM_CFS : SND_SOC_DAIFMT_CBM_CFM; + } + + of_node_put(bitclkmaster); + of_node_put(framemaster); + + return daifmt; +} + +static int axg_card_parse_cpu_tdm_slots(struct snd_soc_card *card, + struct snd_soc_dai_link *link, + struct device_node *node, + struct axg_dai_link_tdm_data *be) +{ + char propname[32]; + u32 tx, rx; + int i; + + be->tx_mask = devm_kcalloc(card->dev, AXG_TDM_NUM_LANES, + sizeof(*be->tx_mask), GFP_KERNEL); + be->rx_mask = devm_kcalloc(card->dev, AXG_TDM_NUM_LANES, + sizeof(*be->rx_mask), GFP_KERNEL); + if (!be->tx_mask || !be->rx_mask) + return -ENOMEM; + + for (i = 0, tx = 0; i < AXG_TDM_NUM_LANES; i++) { + snprintf(propname, 32, "dai-tdm-slot-tx-mask-%d", i); + snd_soc_of_get_slot_mask(node, propname, &be->tx_mask[i]); + tx = max(tx, be->tx_mask[i]); + } + + /* Disable playback is the interface has no tx slots */ + if (!tx) + link->dpcm_playback = 0; + + for (i = 0, rx = 0; i < AXG_TDM_NUM_LANES; i++) { + snprintf(propname, 32, "dai-tdm-slot-rx-mask-%d", i); + snd_soc_of_get_slot_mask(node, propname, &be->rx_mask[i]); + rx = max(rx, be->rx_mask[i]); + } + + /* Disable capture is the interface has no rx slots */ + if (!rx) + link->dpcm_capture = 0; + + /* ... but the interface should at least have one of them */ + if (!tx && !rx) { + dev_err(card->dev, "tdm link has no cpu slots\n"); + return -EINVAL; + } + + of_property_read_u32(node, "dai-tdm-slot-num", &be->slots); + if (!be->slots) { + /* + * If the slot number is not provided, set it such as it + * accommodates the largest mask + */ + be->slots = fls(max(tx, rx)); + } else if (be->slots < fls(max(tx, rx)) || be->slots > 32) { + /* + * Error if the slots can't accommodate the largest mask or + * if it is just too big + */ + dev_err(card->dev, "bad slot number\n"); + return -EINVAL; + } + + of_property_read_u32(node, "dai-tdm-slot-width", &be->slot_width); + + return 0; +} + +static int axg_card_parse_codecs_masks(struct snd_soc_card *card, + struct snd_soc_dai_link *link, + struct device_node *node, + struct axg_dai_link_tdm_data *be) +{ + struct axg_dai_link_tdm_mask *codec_mask; + struct device_node *np; + + codec_mask = devm_kcalloc(card->dev, link->num_codecs, + sizeof(*codec_mask), GFP_KERNEL); + if (!codec_mask) + return -ENOMEM; + + be->codec_masks = codec_mask; + + for_each_child_of_node(node, np) { + snd_soc_of_get_slot_mask(np, "dai-tdm-slot-rx-mask", + &codec_mask->rx); + snd_soc_of_get_slot_mask(np, "dai-tdm-slot-tx-mask", + &codec_mask->tx); + + codec_mask++; + } + + return 0; +} + +static int axg_card_parse_tdm(struct snd_soc_card *card, + struct device_node *node, + int *index) +{ + struct axg_card *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_dai_link *link = &card->dai_link[*index]; + struct axg_dai_link_tdm_data *be; + int ret; + + /* Allocate tdm link parameters */ + be = devm_kzalloc(card->dev, sizeof(*be), GFP_KERNEL); + if (!be) + return -ENOMEM; + priv->link_data[*index] = be; + + /* Setup tdm link */ + link->ops = &axg_card_tdm_be_ops; + link->init = axg_card_tdm_dai_init; + link->dai_fmt = axg_card_parse_daifmt(node, link->cpu_of_node); + + of_property_read_u32(node, "mclk-fs", &be->mclk_fs); + + ret = axg_card_parse_cpu_tdm_slots(card, link, node, be); + if (ret) { + dev_err(card->dev, "error parsing tdm link slots\n"); + return ret; + } + + ret = axg_card_parse_codecs_masks(card, link, node, be); + if (ret) + return ret; + + /* Add loopback if the pad dai has playback */ + if (link->dpcm_playback) { + ret = axg_card_add_tdm_loopback(card, index); + if (ret) + return ret; + } + + return 0; +} + +static int axg_card_set_be_link(struct snd_soc_card *card, + struct snd_soc_dai_link *link, + struct device_node *node) +{ + struct snd_soc_dai_link_component *codec; + struct device_node *np; + int ret, num_codecs; + + link->no_pcm = 1; + link->dpcm_playback = 1; + link->dpcm_capture = 1; + + num_codecs = of_get_child_count(node); + if (!num_codecs) { + dev_err(card->dev, "be link %s has no codec\n", + node->full_name); + return -EINVAL; + } + + codec = devm_kcalloc(card->dev, num_codecs, sizeof(*codec), GFP_KERNEL); + if (!codec) + return -ENOMEM; + + link->codecs = codec; + link->num_codecs = num_codecs; + + for_each_child_of_node(node, np) { + ret = axg_card_parse_dai(card, np, &codec->of_node, + &codec->dai_name); + if (ret) { + of_node_put(np); + return ret; + } + + codec++; + } + + ret = axg_card_set_link_name(card, link, "be"); + if (ret) + dev_err(card->dev, "error setting %s link name\n", np->name); + + return ret; +} + +static int axg_card_set_fe_link(struct snd_soc_card *card, + struct snd_soc_dai_link *link, + bool is_playback) +{ + link->dynamic = 1; + link->dpcm_merged_format = 1; + link->dpcm_merged_chan = 1; + link->dpcm_merged_rate = 1; + link->codec_dai_name = "snd-soc-dummy-dai"; + link->codec_name = "snd-soc-dummy"; + + if (is_playback) + link->dpcm_playback = 1; + else + link->dpcm_capture = 1; + + return axg_card_set_link_name(card, link, "fe"); +} + +static int axg_card_cpu_is_capture_fe(struct device_node *np) +{ + return of_device_is_compatible(np, PREFIX "axg-toddr"); +} + +static int axg_card_cpu_is_playback_fe(struct device_node *np) +{ + return of_device_is_compatible(np, PREFIX "axg-frddr"); +} + +static int axg_card_cpu_is_tdm_iface(struct device_node *np) +{ + return of_device_is_compatible(np, PREFIX "axg-tdm-iface"); +} + +static int axg_card_add_link(struct snd_soc_card *card, struct device_node *np, + int *index) +{ + struct snd_soc_dai_link *dai_link = &card->dai_link[*index]; + int ret; + + ret = axg_card_parse_dai(card, np, &dai_link->cpu_of_node, + &dai_link->cpu_dai_name); + if (ret) + return ret; + + if (axg_card_cpu_is_playback_fe(dai_link->cpu_of_node)) + ret = axg_card_set_fe_link(card, dai_link, true); + else if (axg_card_cpu_is_capture_fe(dai_link->cpu_of_node)) + ret = axg_card_set_fe_link(card, dai_link, false); + else + ret = axg_card_set_be_link(card, dai_link, np); + + if (ret) + return ret; + + if (axg_card_cpu_is_tdm_iface(dai_link->cpu_of_node)) + ret = axg_card_parse_tdm(card, np, index); + + return ret; +} + +static int axg_card_add_links(struct snd_soc_card *card) +{ + struct axg_card *priv = snd_soc_card_get_drvdata(card); + struct device_node *node = card->dev->of_node; + struct device_node *np; + int num, i, ret; + + num = of_get_child_count(node); + if (!num) { + dev_err(card->dev, "card has no links\n"); + return -EINVAL; + } + + ret = axg_card_reallocate_links(priv, num); + if (ret) + return ret; + + i = 0; + for_each_child_of_node(node, np) { + ret = axg_card_add_link(card, np, &i); + if (ret) { + of_node_put(np); + return ret; + } + + i++; + } + + return 0; +} + +static int axg_card_parse_of_optional(struct snd_soc_card *card, + const char *propname, + int (*func)(struct snd_soc_card *c, + const char *p)) +{ + /* If property is not provided, don't fail ... */ + if (!of_property_read_bool(card->dev->of_node, propname)) + return 0; + + /* ... but do fail if it is provided and the parsing fails */ + return func(card, propname); +} + +static const struct of_device_id axg_card_of_match[] = { + { .compatible = "amlogic,axg-sound-card", }, + {} +}; +MODULE_DEVICE_TABLE(of, axg_card_of_match); + +static int axg_card_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct axg_card *priv; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + platform_set_drvdata(pdev, priv); + snd_soc_card_set_drvdata(&priv->card, priv); + + priv->card.owner = THIS_MODULE; + priv->card.dev = dev; + + ret = snd_soc_of_parse_card_name(&priv->card, PREFIX "name"); + if (ret < 0) + return ret; + + ret = axg_card_parse_of_optional(&priv->card, PREFIX "routing", + snd_soc_of_parse_audio_routing); + if (ret) { + dev_err(dev, "error while parsing routing\n"); + return ret; + } + + ret = axg_card_parse_of_optional(&priv->card, PREFIX "widgets", + snd_soc_of_parse_audio_simple_widgets); + if (ret) { + dev_err(dev, "error while parsing widgets\n"); + return ret; + } + + ret = axg_card_add_links(&priv->card); + if (ret) + goto out_err; + + ret = axg_card_add_aux_devices(&priv->card); + if (ret) + goto out_err; + + ret = devm_snd_soc_register_card(dev, &priv->card); + if (ret) + goto out_err; + + return 0; + +out_err: + axg_card_clean_references(priv); + return ret; +} + +static int axg_card_remove(struct platform_device *pdev) +{ + struct axg_card *priv = platform_get_drvdata(pdev); + + axg_card_clean_references(priv); + + return 0; +} + +static struct platform_driver axg_card_pdrv = { + .probe = axg_card_probe, + .remove = axg_card_remove, + .driver = { + .name = "axg-sound-card", + .of_match_table = axg_card_of_match, + }, +}; +module_platform_driver(axg_card_pdrv); + +MODULE_DESCRIPTION("Amlogic AXG ALSA machine driver"); +MODULE_AUTHOR("Jerome Brunet "); +MODULE_LICENSE("GPL v2");