mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git
synced 2025-01-07 14:32:23 +00:00
071b34dcf7
This is a relatively calm cycle, and majority of changes are about ASoC. There are little changes in the core side but we received lots of new drivers for new vendors. * ALSA Core: - The new accel operation mode for compress-offload API; only the core part, and the actual user will follow at next * ASoC: - Continued API simplification works - Renaming of the sh directory to Renesas - Factoring out of some of the common code for Realtek devices - Ussal ASoC Intel SOF, AMD and SoundWire updates - Support for Allwinner H616, AMD ACP 6.3 systems, AWInic AW88081, Cirrus Logic CS32L84, Everest ES8328, Iron Devices SMA1307, Longsoon I2S, NeoFidelity NTP8918 and NTP8835, Philips UDA1342, Qualcomm SM8750, RealTek RT721, and ST Microelectronics STM32MP25 * HD- and USB-audio: - Clean up of IRQ handling in legacy HD-audio driver - Fix soft lockup at disconnection of non-standard USB drivers - Scarlett2 mixer improvements - New quirks and cleanups in HD- and USB-audio -----BEGIN PGP SIGNATURE----- iQJCBAABCAAsFiEEIXTw5fNLNI7mMiVaLtJE4w1nLE8FAmc9yFUOHHRpd2FpQHN1 c2UuZGUACgkQLtJE4w1nLE8IMxAAoagGD3lQXQl6c7yoe0BATApiW71itLHP05SA KzVCeWtMMeROov9cWgzIJMqbj4d6fs/zxomYjYHzb+3IlTtMVkV+PWEIgtr0xck4 WRPj9V3vb68+S6tFlVrmsv4LwdWfwPwUgLJpvXZM/aFUxa+8i0lb2/g8yKPYvser dfqU1Uwi7fn3txTQiFiI61Hx7Nn3ISUhVpY3DwZTHYuLaRNfJOR0HdJTrlehnYZG KmvTg2WnYud687yEMC5AV1WTItoUUhMkuPGbpxWxGwk8bOfkbxKNh/EgNOO2Hubz E8RgYtCaq2TA0OS5YdY6Mk9blG7snOZW3pMULm1kFuLr1BD7JGz2Q5duoX6NyrrS gqzXPxPYCCCkLLkS672HMy9UzhM3j03LrgT1JxV3wbUMZoJWjD0zn1Tfv6WJ1M7g CZsjaZrqATK8bexM8QrdKSZcZOB7B3ZX0hNyx/SKXycuvg8jC8evm/qfAPKvU79O B3vivSs/YgquR62R8dp6OJQ75C3IVLv9PrLbS/RXrOgzx2hRZFG5/PWKr+pC5zIp T8qdBPuQ/f/gzbIgGkL2sRYYWGL+tg+hlD2CA5jP47DoUwcdxm15AK4Gv1qqJgFr QCmg3WuipUumJqyMcSKdyVqUytIeCBFTZqM1PUsehfdueeRkp0ZgV6hDDc+jSjWm fjycRuw= =gOHc -----END PGP SIGNATURE----- Merge tag 'sound-6.13-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound Pull sound updates from Takashi Iwai: "This is a relatively calm cycle, and majority of changes are about ASoC. There are little changes in the core side but we received lots of new drivers for new vendors. ALSA Core: - The new accel operation mode for compress-offload API; only the core part, the actual user will follow later ASoC: - Continued API simplification works - Renaming of the sh directory to Renesas - Factoring out of some of the common code for Realtek devices - Ussal ASoC Intel SOF, AMD and SoundWire updates - Support for Allwinner H616, AMD ACP 6.3 systems, AWInic AW88081, Cirrus Logic CS32L84, Everest ES8328, Iron Devices SMA1307, Longsoon I2S, NeoFidelity NTP8918 and NTP8835, Philips UDA1342, Qualcomm SM8750, RealTek RT721, and ST Microelectronics STM32MP25 HD- and USB-audio: - Clean up of IRQ handling in legacy HD-audio driver - Fix soft lockup at disconnection of non-standard USB drivers - Scarlett2 mixer improvements - New quirks and cleanups in HD- and USB-audio" * tag 'sound-6.13-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound: (278 commits) ALSA: hda: Poll jack events for LS7A HD-Audio ASoC: hdmi-codec: reorder channel allocation list ALSA: ump: Fix the wrong format specifier ASoC: Intel: soc-acpi-intel-lnl-match: add rt712_vb + rt1320 support ASoC: stm32: dfsdm: change rate upper limits ASoC: sma1307: fix uninitialized variable refence ASoC: dt-bindings: simple-mux: add idle-state property ASoc: simple-mux: add idle-state support ASoC: sdca: test adev before calling acpi_dev_for_each_child ASoC: SOF: ipc4-topology: remove redundant assignment to variable ret ASoC: amd: ps: fix the pcm device numbering for acp 6.3 platform ASoC: amd: acp: add soundwire machine driver for legacy stack ASoC: amd: acp: move get_acp63_cpu_pin_id() to common file ASoC: amd: ps: add soundwire machines for acp6.3 platform ASoC: amd: acp: add RT711, RT714 & RT1316 support for acp 6.3 platform ASoC: amd: acp: add rt722 based soundwire machines ALSA: compress_offload: Add missing descriptions in structs ALSA: 6fire: Release resources at card release ALSA: caiaq: Use snd_card_free_when_closed() at disconnection ALSA: us122l: Drop mmap_count field ...
1444 lines
34 KiB
C
1444 lines
34 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
//
|
|
// ASoC Audio Graph Card2 support
|
|
//
|
|
// Copyright (C) 2020 Renesas Electronics Corp.
|
|
// Copyright (C) 2020 Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
|
|
//
|
|
// based on ${LINUX}/sound/soc/generic/audio-graph-card.c
|
|
#include <linux/clk.h>
|
|
#include <linux/device.h>
|
|
#include <linux/gpio/consumer.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_graph.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/string.h>
|
|
#include <sound/graph_card.h>
|
|
|
|
/************************************
|
|
daifmt
|
|
************************************
|
|
ports {
|
|
format = "left_j";
|
|
port@0 {
|
|
bitclock-master;
|
|
sample0: endpoint@0 {
|
|
frame-master;
|
|
};
|
|
sample1: endpoint@1 {
|
|
format = "i2s";
|
|
};
|
|
};
|
|
...
|
|
};
|
|
|
|
You can set daifmt at ports/port/endpoint.
|
|
It uses *latest* format, and *share* master settings.
|
|
In above case,
|
|
sample0: left_j, bitclock-master, frame-master
|
|
sample1: i2s, bitclock-master
|
|
|
|
If there was no settings, *Codec* will be
|
|
bitclock/frame provider as default.
|
|
see
|
|
graph_parse_daifmt().
|
|
|
|
"format" property is no longer needed on DT if both CPU/Codec drivers are
|
|
supporting snd_soc_dai_ops :: .auto_selectable_formats.
|
|
see
|
|
snd_soc_runtime_get_dai_fmt()
|
|
|
|
sample driver
|
|
linux/sound/soc/renesas/rcar/core.c
|
|
linux/sound/soc/codecs/ak4613.c
|
|
linux/sound/soc/codecs/pcm3168a.c
|
|
linux/sound/soc/soc-utils.c
|
|
linux/sound/soc/generic/test-component.c
|
|
|
|
************************************
|
|
Normal Audio-Graph
|
|
************************************
|
|
|
|
CPU <---> Codec
|
|
|
|
sound {
|
|
compatible = "audio-graph-card2";
|
|
links = <&cpu>;
|
|
};
|
|
|
|
CPU {
|
|
cpu: port {
|
|
bitclock-master;
|
|
frame-master;
|
|
cpu_ep: endpoint { remote-endpoint = <&codec_ep>; }; };
|
|
};
|
|
|
|
Codec {
|
|
port { codec_ep: endpoint { remote-endpoint = <&cpu_ep>; }; };
|
|
};
|
|
|
|
************************************
|
|
Multi-CPU/Codec
|
|
************************************
|
|
|
|
It has link connection part (= X,x) and list part (= A,B,a,b).
|
|
"links" is connection part of CPU side (= @).
|
|
|
|
+----+ +---+
|
|
CPU1 --|A X| <-@----> |x a|-- Codec1
|
|
CPU2 --|B | | b|-- Codec2
|
|
+----+ +---+
|
|
|
|
sound {
|
|
compatible = "audio-graph-card2";
|
|
|
|
(@) links = <&mcpu>;
|
|
|
|
multi {
|
|
ports@0 {
|
|
(@) mcpu: port@0 { mcpu0_ep: endpoint { remote-endpoint = <&mcodec0_ep>; }; }; // (X) to pair
|
|
port@1 { mcpu1_ep: endpoint { remote-endpoint = <&cpu1_ep>; }; }; // (A) Multi Element
|
|
port@2 { mcpu2_ep: endpoint { remote-endpoint = <&cpu2_ep>; }; }; // (B) Multi Element
|
|
};
|
|
ports@1 {
|
|
port@0 { mcodec0_ep: endpoint { remote-endpoint = <&mcpu0_ep>; }; }; // (x) to pair
|
|
port@1 { mcodec1_ep: endpoint { remote-endpoint = <&codec1_ep>; }; }; // (a) Multi Element
|
|
port@2 { mcodec2_ep: endpoint { remote-endpoint = <&codec2_ep>; }; }; // (b) Multi Element
|
|
};
|
|
};
|
|
};
|
|
|
|
CPU {
|
|
ports {
|
|
bitclock-master;
|
|
frame-master;
|
|
port@0 { cpu1_ep: endpoint { remote-endpoint = <&mcpu1_ep>; }; };
|
|
port@1 { cpu2_ep: endpoint { remote-endpoint = <&mcpu2_ep>; }; };
|
|
};
|
|
};
|
|
|
|
Codec {
|
|
ports {
|
|
port@0 { codec1_ep: endpoint { remote-endpoint = <&mcodec1_ep>; }; };
|
|
port@1 { codec2_ep: endpoint { remote-endpoint = <&mcodec2_ep>; }; };
|
|
};
|
|
};
|
|
|
|
************************************
|
|
DPCM
|
|
************************************
|
|
|
|
DSP
|
|
************
|
|
PCM0 <--> * fe0 be0 * <--> DAI0: Codec Headset
|
|
PCM1 <--> * fe1 be1 * <--> DAI1: Codec Speakers
|
|
PCM2 <--> * fe2 be2 * <--> DAI2: MODEM
|
|
PCM3 <--> * fe3 be3 * <--> DAI3: BT
|
|
* be4 * <--> DAI4: DMIC
|
|
* be5 * <--> DAI5: FM
|
|
************
|
|
|
|
sound {
|
|
compatible = "audio-graph-card2";
|
|
|
|
// indicate routing
|
|
routing = "xxx Playback", "xxx Playback",
|
|
"xxx Playback", "xxx Playback",
|
|
"xxx Playback", "xxx Playback";
|
|
|
|
// indicate all Front-End, Back-End
|
|
links = <&fe0, &fe1, ...,
|
|
&be0, &be1, ...>;
|
|
|
|
dpcm {
|
|
// Front-End
|
|
ports@0 {
|
|
fe0: port@0 { fe0_ep: endpoint { remote-endpoint = <&pcm0_ep>; }; };
|
|
fe1: port@1 { fe1_ep: endpoint { remote-endpoint = <&pcm1_ep>; }; };
|
|
...
|
|
};
|
|
// Back-End
|
|
ports@1 {
|
|
be0: port@0 { be0_ep: endpoint { remote-endpoint = <&dai0_ep>; }; };
|
|
be1: port@1 { be1_ep: endpoint { remote-endpoint = <&dai1_ep>; }; };
|
|
...
|
|
};
|
|
};
|
|
};
|
|
|
|
CPU {
|
|
ports {
|
|
bitclock-master;
|
|
frame-master;
|
|
port@0 { pcm0_ep: endpoint { remote-endpoint = <&fe0_ep>; }; };
|
|
port@1 { pcm1_ep: endpoint { remote-endpoint = <&fe1_ep>; }; };
|
|
...
|
|
};
|
|
};
|
|
|
|
Codec {
|
|
ports {
|
|
port@0 { dai0_ep: endpoint { remote-endpoint = <&be0_ep>; }; };
|
|
port@1 { dai1_ep: endpoint { remote-endpoint = <&be1_ep>; }; };
|
|
...
|
|
};
|
|
};
|
|
|
|
************************************
|
|
Codec to Codec
|
|
************************************
|
|
|
|
+--+
|
|
| |<-- Codec0 <- IN
|
|
| |--> Codec1 -> OUT
|
|
+--+
|
|
|
|
sound {
|
|
compatible = "audio-graph-card2";
|
|
|
|
routing = "OUT" ,"DAI1 Playback",
|
|
"DAI0 Capture", "IN";
|
|
|
|
links = <&c2c>;
|
|
|
|
codec2codec {
|
|
ports {
|
|
rate = <48000>;
|
|
c2c: port@0 { c2cf_ep: endpoint { remote-endpoint = <&codec0_ep>; }; };
|
|
port@1 { c2cb_ep: endpoint { remote-endpoint = <&codec1_ep>; }; };
|
|
};
|
|
};
|
|
|
|
Codec {
|
|
ports {
|
|
port@0 {
|
|
bitclock-master;
|
|
frame-master;
|
|
codec0_ep: endpoint { remote-endpoint = <&c2cf_ep>; }; };
|
|
port@1 { codec1_ep: endpoint { remote-endpoint = <&c2cb_ep>; }; };
|
|
};
|
|
};
|
|
|
|
*/
|
|
|
|
enum graph_type {
|
|
GRAPH_NORMAL,
|
|
GRAPH_DPCM,
|
|
GRAPH_C2C,
|
|
|
|
GRAPH_MULTI, /* don't use ! Use this only in __graph_get_type() */
|
|
};
|
|
|
|
#define GRAPH_NODENAME_MULTI "multi"
|
|
#define GRAPH_NODENAME_DPCM "dpcm"
|
|
#define GRAPH_NODENAME_C2C "codec2codec"
|
|
|
|
#define ep_to_port(ep) of_get_parent(ep)
|
|
static struct device_node *port_to_ports(struct device_node *port)
|
|
{
|
|
struct device_node *ports = of_get_parent(port);
|
|
|
|
if (!of_node_name_eq(ports, "ports")) {
|
|
of_node_put(ports);
|
|
return NULL;
|
|
}
|
|
return ports;
|
|
}
|
|
|
|
static enum graph_type __graph_get_type(struct device_node *lnk)
|
|
{
|
|
struct device_node *np, *parent_np;
|
|
enum graph_type ret;
|
|
|
|
/*
|
|
* target {
|
|
* ports {
|
|
* => lnk: port@0 { ... };
|
|
* port@1 { ... };
|
|
* };
|
|
* };
|
|
*/
|
|
np = of_get_parent(lnk);
|
|
if (of_node_name_eq(np, "ports")) {
|
|
parent_np = of_get_parent(np);
|
|
of_node_put(np);
|
|
np = parent_np;
|
|
}
|
|
|
|
if (of_node_name_eq(np, GRAPH_NODENAME_MULTI)) {
|
|
ret = GRAPH_MULTI;
|
|
fw_devlink_purge_absent_suppliers(&np->fwnode);
|
|
goto out_put;
|
|
}
|
|
|
|
if (of_node_name_eq(np, GRAPH_NODENAME_DPCM)) {
|
|
ret = GRAPH_DPCM;
|
|
fw_devlink_purge_absent_suppliers(&np->fwnode);
|
|
goto out_put;
|
|
}
|
|
|
|
if (of_node_name_eq(np, GRAPH_NODENAME_C2C)) {
|
|
ret = GRAPH_C2C;
|
|
fw_devlink_purge_absent_suppliers(&np->fwnode);
|
|
goto out_put;
|
|
}
|
|
|
|
ret = GRAPH_NORMAL;
|
|
|
|
out_put:
|
|
of_node_put(np);
|
|
return ret;
|
|
|
|
}
|
|
|
|
static enum graph_type graph_get_type(struct simple_util_priv *priv,
|
|
struct device_node *lnk)
|
|
{
|
|
enum graph_type type = __graph_get_type(lnk);
|
|
|
|
/* GRAPH_MULTI here means GRAPH_NORMAL */
|
|
if (type == GRAPH_MULTI)
|
|
type = GRAPH_NORMAL;
|
|
|
|
#ifdef DEBUG
|
|
{
|
|
struct device *dev = simple_priv_to_dev(priv);
|
|
const char *str = "Normal";
|
|
|
|
switch (type) {
|
|
case GRAPH_DPCM:
|
|
if (graph_util_is_ports0(lnk))
|
|
str = "DPCM Front-End";
|
|
else
|
|
str = "DPCM Back-End";
|
|
break;
|
|
case GRAPH_C2C:
|
|
str = "Codec2Codec";
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
dev_dbg(dev, "%pOF (%s)", lnk, str);
|
|
}
|
|
#endif
|
|
return type;
|
|
}
|
|
|
|
static int graph_lnk_is_multi(struct device_node *lnk)
|
|
{
|
|
return __graph_get_type(lnk) == GRAPH_MULTI;
|
|
}
|
|
|
|
static struct device_node *graph_get_next_multi_ep(struct device_node **port)
|
|
{
|
|
struct device_node *ports = port_to_ports(*port);
|
|
struct device_node *ep = NULL;
|
|
struct device_node *rep = NULL;
|
|
|
|
/*
|
|
* multi {
|
|
* ports {
|
|
* => lnk: port@0 { ... }; // to pair
|
|
* port@1 { ep { ... = rep0 } }; // Multi Element
|
|
* port@2 { ep { ... = rep1 } }; // Multi Element
|
|
* ...
|
|
* };
|
|
* };
|
|
*
|
|
* xxx {
|
|
* port@0 { rep0 };
|
|
* port@1 { rep1 };
|
|
* };
|
|
*/
|
|
*port = of_graph_get_next_port(ports, *port);
|
|
if (*port) {
|
|
ep = of_graph_get_next_port_endpoint(*port, NULL);
|
|
rep = of_graph_get_remote_endpoint(ep);
|
|
}
|
|
|
|
of_node_put(ep);
|
|
of_node_put(ports);
|
|
|
|
return rep;
|
|
}
|
|
|
|
static const struct snd_soc_ops graph_ops = {
|
|
.startup = simple_util_startup,
|
|
.shutdown = simple_util_shutdown,
|
|
.hw_params = simple_util_hw_params,
|
|
};
|
|
|
|
static void graph_parse_convert(struct device_node *ep,
|
|
struct simple_dai_props *props)
|
|
{
|
|
struct device_node *port = ep_to_port(ep);
|
|
struct device_node *ports = port_to_ports(port);
|
|
struct simple_util_data *adata = &props->adata;
|
|
|
|
simple_util_parse_convert(ports, NULL, adata);
|
|
simple_util_parse_convert(port, NULL, adata);
|
|
simple_util_parse_convert(ep, NULL, adata);
|
|
|
|
of_node_put(port);
|
|
of_node_put(ports);
|
|
}
|
|
|
|
static int __graph_parse_node(struct simple_util_priv *priv,
|
|
enum graph_type gtype,
|
|
struct device_node *ep,
|
|
struct link_info *li,
|
|
int is_cpu, int idx)
|
|
{
|
|
struct device *dev = simple_priv_to_dev(priv);
|
|
struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link);
|
|
struct simple_dai_props *dai_props = simple_priv_to_props(priv, li->link);
|
|
struct snd_soc_dai_link_component *dlc;
|
|
struct simple_util_dai *dai;
|
|
int ret, is_single_links = 0;
|
|
|
|
if (is_cpu) {
|
|
dlc = snd_soc_link_to_cpu(dai_link, idx);
|
|
dai = simple_props_to_dai_cpu(dai_props, idx);
|
|
} else {
|
|
dlc = snd_soc_link_to_codec(dai_link, idx);
|
|
dai = simple_props_to_dai_codec(dai_props, idx);
|
|
}
|
|
|
|
ret = graph_util_parse_dai(dev, ep, dlc, &is_single_links);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = simple_util_parse_tdm(ep, dai);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = simple_util_parse_tdm_width_map(dev, ep, dai);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = simple_util_parse_clk(dev, ep, dai, dlc);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/*
|
|
* set DAI Name
|
|
*/
|
|
if (!dai_link->name) {
|
|
struct snd_soc_dai_link_component *cpus = dlc;
|
|
struct snd_soc_dai_link_component *codecs = snd_soc_link_to_codec(dai_link, idx);
|
|
char *cpu_multi = "";
|
|
char *codec_multi = "";
|
|
|
|
if (dai_link->num_cpus > 1)
|
|
cpu_multi = "_multi";
|
|
if (dai_link->num_codecs > 1)
|
|
codec_multi = "_multi";
|
|
|
|
switch (gtype) {
|
|
case GRAPH_NORMAL:
|
|
/* run is_cpu only. see audio_graph2_link_normal() */
|
|
if (is_cpu)
|
|
simple_util_set_dailink_name(dev, dai_link, "%s%s-%s%s",
|
|
cpus->dai_name, cpu_multi,
|
|
codecs->dai_name, codec_multi);
|
|
break;
|
|
case GRAPH_DPCM:
|
|
if (is_cpu)
|
|
simple_util_set_dailink_name(dev, dai_link, "fe.%pOFP.%s%s",
|
|
cpus->of_node, cpus->dai_name, cpu_multi);
|
|
else
|
|
simple_util_set_dailink_name(dev, dai_link, "be.%pOFP.%s%s",
|
|
codecs->of_node, codecs->dai_name, codec_multi);
|
|
break;
|
|
case GRAPH_C2C:
|
|
/* run is_cpu only. see audio_graph2_link_c2c() */
|
|
if (is_cpu)
|
|
simple_util_set_dailink_name(dev, dai_link, "c2c.%s%s-%s%s",
|
|
cpus->dai_name, cpu_multi,
|
|
codecs->dai_name, codec_multi);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Check "prefix" from top node
|
|
* if DPCM-BE case
|
|
*/
|
|
if (!is_cpu && gtype == GRAPH_DPCM) {
|
|
struct snd_soc_dai_link_component *codecs = snd_soc_link_to_codec(dai_link, idx);
|
|
struct snd_soc_codec_conf *cconf = simple_props_to_codec_conf(dai_props, idx);
|
|
struct device_node *rport = ep_to_port(ep);
|
|
struct device_node *rports = port_to_ports(rport);
|
|
|
|
snd_soc_of_parse_node_prefix(rports, cconf, codecs->of_node, "prefix");
|
|
snd_soc_of_parse_node_prefix(rport, cconf, codecs->of_node, "prefix");
|
|
|
|
of_node_put(rport);
|
|
of_node_put(rports);
|
|
}
|
|
|
|
if (is_cpu) {
|
|
struct snd_soc_dai_link_component *cpus = dlc;
|
|
struct snd_soc_dai_link_component *platforms = snd_soc_link_to_platform(dai_link, idx);
|
|
|
|
simple_util_canonicalize_cpu(cpus, is_single_links);
|
|
simple_util_canonicalize_platform(platforms, cpus);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int graph_parse_node_multi_nm(struct snd_soc_dai_link *dai_link,
|
|
int *nm_idx, int cpu_idx,
|
|
struct device_node *mcpu_port)
|
|
{
|
|
/*
|
|
* +---+ +---+
|
|
* | X|<-@------->|x |
|
|
* | | | |
|
|
* cpu0 <--|A 1|<--------->|4 a|-> codec0
|
|
* cpu1 <--|B 2|<-----+--->|5 b|-> codec1
|
|
* cpu2 <--|C 3|<----/ +---+
|
|
* +---+
|
|
*
|
|
* multi {
|
|
* ports {
|
|
* port@0 { mcpu_top_ep {... = mcodec_ep; }; }; // (X) to pair
|
|
* <mcpu_port> port@1 { mcpu0_ep { ... = cpu0_ep; }; // (A) Multi Element
|
|
* mcpu0_ep_0 { ... = mcodec0_ep_0; }; }; // (1) connected Codec
|
|
* port@2 { mcpu1_ep { ... = cpu1_ep; }; // (B) Multi Element
|
|
* mcpu1_ep_0 { ... = mcodec1_ep_0; }; }; // (2) connected Codec
|
|
* port@3 { mcpu2_ep { ... = cpu2_ep; }; // (C) Multi Element
|
|
* mcpu2_ep_0 { ... = mcodec1_ep_1; }; }; // (3) connected Codec
|
|
* };
|
|
*
|
|
* ports {
|
|
* port@0 { mcodec_top_ep {... = mcpu_ep; }; }; // (x) to pair
|
|
* <mcodec_port>port@1 { mcodec0_ep { ... = codec0_ep; }; // (a) Multi Element
|
|
* mcodec0_ep_0 { ... = mcpu0_ep_0; }; }; // (4) connected CPU
|
|
* port@2 { mcodec1_ep { ... = codec1_ep; }; // (b) Multi Element
|
|
* mcodec1_ep_0 { ... = mcpu1_ep_0; }; // (5) connected CPU
|
|
* mcodec1_ep_1 { ... = mcpu2_ep_0; }; }; // (5) connected CPU
|
|
* };
|
|
* };
|
|
*/
|
|
struct device_node *mcpu_ep = of_graph_get_next_port_endpoint(mcpu_port, NULL);
|
|
struct device_node *mcpu_ports = port_to_ports(mcpu_port);
|
|
struct device_node *mcpu_port_top = of_graph_get_next_port(mcpu_ports, NULL);
|
|
struct device_node *mcpu_ep_top = of_graph_get_next_port_endpoint(mcpu_port_top, NULL);
|
|
struct device_node *mcodec_ep_top = of_graph_get_remote_endpoint(mcpu_ep_top);
|
|
struct device_node *mcodec_port_top = ep_to_port(mcodec_ep_top);
|
|
struct device_node *mcodec_ports = port_to_ports(mcodec_port_top);
|
|
int nm_max = max(dai_link->num_cpus, dai_link->num_codecs);
|
|
int ret = 0;
|
|
|
|
if (cpu_idx > dai_link->num_cpus) {
|
|
ret = -EINVAL;
|
|
goto mcpu_err;
|
|
}
|
|
|
|
for_each_of_graph_port_endpoint(mcpu_port, mcpu_ep_n) {
|
|
struct device_node *mcodec_ep_n;
|
|
struct device_node *mcodec_port;
|
|
int codec_idx;
|
|
|
|
/* ignore 1st ep which is for element */
|
|
if (mcpu_ep_n == mcpu_ep)
|
|
continue;
|
|
|
|
if (*nm_idx > nm_max)
|
|
break;
|
|
|
|
mcodec_ep_n = of_graph_get_remote_endpoint(mcpu_ep_n);
|
|
mcodec_port = ep_to_port(mcodec_ep_n);
|
|
|
|
if (mcodec_ports != port_to_ports(mcodec_port)) {
|
|
ret = -EINVAL;
|
|
goto mcpu_err;
|
|
}
|
|
|
|
codec_idx = 0;
|
|
ret = -EINVAL;
|
|
for_each_of_graph_port(mcodec_ports, mcodec_port_i) {
|
|
|
|
/* ignore 1st port which is for pair connection */
|
|
if (mcodec_port_top == mcodec_port_i)
|
|
continue;
|
|
|
|
if (codec_idx > dai_link->num_codecs)
|
|
break;
|
|
|
|
if (mcodec_port_i == mcodec_port) {
|
|
dai_link->ch_maps[*nm_idx].cpu = cpu_idx;
|
|
dai_link->ch_maps[*nm_idx].codec = codec_idx;
|
|
|
|
(*nm_idx)++;
|
|
ret = 0;
|
|
break;
|
|
}
|
|
codec_idx++;
|
|
}
|
|
of_node_put(mcodec_port);
|
|
of_node_put(mcodec_ep_n);
|
|
if (ret < 0)
|
|
break;
|
|
}
|
|
mcpu_err:
|
|
of_node_put(mcpu_ep);
|
|
of_node_put(mcpu_port_top);
|
|
of_node_put(mcpu_ep_top);
|
|
of_node_put(mcodec_ep_top);
|
|
of_node_put(mcodec_port_top);
|
|
of_node_put(mcodec_ports);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int graph_parse_node_multi(struct simple_util_priv *priv,
|
|
enum graph_type gtype,
|
|
struct device_node *port,
|
|
struct link_info *li, int is_cpu)
|
|
{
|
|
struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link);
|
|
struct device *dev = simple_priv_to_dev(priv);
|
|
struct device_node *ep;
|
|
int ret = -ENOMEM;
|
|
int nm_idx = 0;
|
|
int nm_max = max(dai_link->num_cpus, dai_link->num_codecs);
|
|
|
|
/*
|
|
* create ch_maps if CPU:Codec = N:M
|
|
* DPCM is out of scope
|
|
*/
|
|
if (gtype != GRAPH_DPCM && !dai_link->ch_maps &&
|
|
dai_link->num_cpus > 1 && dai_link->num_codecs > 1 &&
|
|
dai_link->num_cpus != dai_link->num_codecs) {
|
|
|
|
dai_link->ch_maps = devm_kcalloc(dev, nm_max,
|
|
sizeof(struct snd_soc_dai_link_ch_map), GFP_KERNEL);
|
|
if (!dai_link->ch_maps)
|
|
goto multi_err;
|
|
}
|
|
|
|
for (int idx = 0;; idx++) {
|
|
/*
|
|
* multi {
|
|
* ports {
|
|
* <port> port@0 { ... }; // to pair
|
|
* port@1 { mcpu1_ep { ... = cpu1_ep };}; // Multi Element
|
|
* port@2 { mcpu2_ep { ... = cpu2_ep };}; // Multi Element
|
|
* };
|
|
* };
|
|
*
|
|
* cpu {
|
|
* ports {
|
|
* <ep> port@0 { cpu1_ep { ... = mcpu1_ep };};
|
|
* };
|
|
* };
|
|
*/
|
|
ep = graph_get_next_multi_ep(&port);
|
|
if (!ep)
|
|
break;
|
|
|
|
ret = __graph_parse_node(priv, gtype, ep, li, is_cpu, idx);
|
|
of_node_put(ep);
|
|
if (ret < 0)
|
|
goto multi_err;
|
|
|
|
/* CPU:Codec = N:M */
|
|
if (is_cpu && dai_link->ch_maps) {
|
|
ret = graph_parse_node_multi_nm(dai_link, &nm_idx, idx, port);
|
|
if (ret < 0)
|
|
goto multi_err;
|
|
}
|
|
}
|
|
|
|
if (is_cpu && dai_link->ch_maps && (nm_idx != nm_max))
|
|
ret = -EINVAL;
|
|
|
|
multi_err:
|
|
return ret;
|
|
}
|
|
|
|
static int graph_parse_node_single(struct simple_util_priv *priv,
|
|
enum graph_type gtype,
|
|
struct device_node *port,
|
|
struct link_info *li, int is_cpu)
|
|
{
|
|
struct device_node *ep = of_graph_get_next_port_endpoint(port, NULL);
|
|
int ret = __graph_parse_node(priv, gtype, ep, li, is_cpu, 0);
|
|
|
|
of_node_put(ep);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int graph_parse_node(struct simple_util_priv *priv,
|
|
enum graph_type gtype,
|
|
struct device_node *port,
|
|
struct link_info *li, int is_cpu)
|
|
{
|
|
if (graph_lnk_is_multi(port))
|
|
return graph_parse_node_multi(priv, gtype, port, li, is_cpu);
|
|
else
|
|
return graph_parse_node_single(priv, gtype, port, li, is_cpu);
|
|
}
|
|
|
|
static void graph_parse_daifmt(struct device_node *node,
|
|
unsigned int *daifmt, unsigned int *bit_frame)
|
|
{
|
|
unsigned int fmt;
|
|
|
|
if (!node)
|
|
return;
|
|
|
|
/*
|
|
* see also above "daifmt" explanation
|
|
* and samples.
|
|
*/
|
|
|
|
/*
|
|
* ports {
|
|
* (A)
|
|
* port {
|
|
* (B)
|
|
* endpoint {
|
|
* (C)
|
|
* };
|
|
* };
|
|
* };
|
|
* };
|
|
*/
|
|
|
|
/*
|
|
* clock_provider:
|
|
*
|
|
* It can be judged it is provider
|
|
* if (A) or (B) or (C) has bitclock-master / frame-master flag.
|
|
*
|
|
* use "or"
|
|
*/
|
|
*bit_frame |= snd_soc_daifmt_parse_clock_provider_as_bitmap(node, NULL);
|
|
|
|
#define update_daifmt(name) \
|
|
if (!(*daifmt & SND_SOC_DAIFMT_##name##_MASK) && \
|
|
(fmt & SND_SOC_DAIFMT_##name##_MASK)) \
|
|
*daifmt |= fmt & SND_SOC_DAIFMT_##name##_MASK
|
|
|
|
/*
|
|
* format
|
|
*
|
|
* This function is called by (C) -> (B) -> (A) order.
|
|
* Set if applicable part was not yet set.
|
|
*/
|
|
fmt = snd_soc_daifmt_parse_format(node, NULL);
|
|
update_daifmt(FORMAT);
|
|
update_daifmt(CLOCK);
|
|
update_daifmt(INV);
|
|
}
|
|
|
|
static void graph_link_init(struct simple_util_priv *priv,
|
|
struct device_node *lnk,
|
|
struct device_node *port_cpu,
|
|
struct device_node *port_codec,
|
|
struct link_info *li,
|
|
int is_cpu_node)
|
|
{
|
|
struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link);
|
|
struct simple_dai_props *dai_props = simple_priv_to_props(priv, li->link);
|
|
struct device_node *ep_cpu, *ep_codec;
|
|
struct device_node *ports_cpu, *ports_codec;
|
|
unsigned int daifmt = 0, daiclk = 0;
|
|
bool playback_only = 0, capture_only = 0;
|
|
enum snd_soc_trigger_order trigger_start = SND_SOC_TRIGGER_ORDER_DEFAULT;
|
|
enum snd_soc_trigger_order trigger_stop = SND_SOC_TRIGGER_ORDER_DEFAULT;
|
|
unsigned int bit_frame = 0;
|
|
|
|
of_node_get(port_cpu);
|
|
if (graph_lnk_is_multi(port_cpu)) {
|
|
ep_cpu = graph_get_next_multi_ep(&port_cpu);
|
|
of_node_put(port_cpu);
|
|
port_cpu = ep_to_port(ep_cpu);
|
|
} else {
|
|
ep_cpu = of_graph_get_next_port_endpoint(port_cpu, NULL);
|
|
}
|
|
ports_cpu = port_to_ports(port_cpu);
|
|
|
|
of_node_get(port_codec);
|
|
if (graph_lnk_is_multi(port_codec)) {
|
|
ep_codec = graph_get_next_multi_ep(&port_codec);
|
|
of_node_put(port_cpu);
|
|
port_codec = ep_to_port(ep_codec);
|
|
} else {
|
|
ep_codec = of_graph_get_next_port_endpoint(port_codec, NULL);
|
|
}
|
|
ports_codec = port_to_ports(port_codec);
|
|
|
|
|
|
graph_parse_daifmt(ep_cpu, &daifmt, &bit_frame);
|
|
graph_parse_daifmt(ep_codec, &daifmt, &bit_frame);
|
|
graph_parse_daifmt(port_cpu, &daifmt, &bit_frame);
|
|
graph_parse_daifmt(port_codec, &daifmt, &bit_frame);
|
|
graph_parse_daifmt(ports_cpu, &daifmt, &bit_frame);
|
|
graph_parse_daifmt(ports_codec, &daifmt, &bit_frame);
|
|
graph_parse_daifmt(lnk, &daifmt, &bit_frame);
|
|
|
|
graph_util_parse_link_direction(lnk, &playback_only, &capture_only);
|
|
graph_util_parse_link_direction(ports_cpu, &playback_only, &capture_only);
|
|
graph_util_parse_link_direction(ports_codec, &playback_only, &capture_only);
|
|
graph_util_parse_link_direction(port_cpu, &playback_only, &capture_only);
|
|
graph_util_parse_link_direction(port_codec, &playback_only, &capture_only);
|
|
graph_util_parse_link_direction(ep_cpu, &playback_only, &capture_only);
|
|
graph_util_parse_link_direction(ep_codec, &playback_only, &capture_only);
|
|
|
|
of_property_read_u32(lnk, "mclk-fs", &dai_props->mclk_fs);
|
|
of_property_read_u32(ports_cpu, "mclk-fs", &dai_props->mclk_fs);
|
|
of_property_read_u32(ports_codec, "mclk-fs", &dai_props->mclk_fs);
|
|
of_property_read_u32(port_cpu, "mclk-fs", &dai_props->mclk_fs);
|
|
of_property_read_u32(port_codec, "mclk-fs", &dai_props->mclk_fs);
|
|
of_property_read_u32(ep_cpu, "mclk-fs", &dai_props->mclk_fs);
|
|
of_property_read_u32(ep_codec, "mclk-fs", &dai_props->mclk_fs);
|
|
|
|
graph_util_parse_trigger_order(priv, lnk, &trigger_start, &trigger_stop);
|
|
graph_util_parse_trigger_order(priv, ports_cpu, &trigger_start, &trigger_stop);
|
|
graph_util_parse_trigger_order(priv, ports_codec, &trigger_start, &trigger_stop);
|
|
graph_util_parse_trigger_order(priv, port_cpu, &trigger_start, &trigger_stop);
|
|
graph_util_parse_trigger_order(priv, port_cpu, &trigger_start, &trigger_stop);
|
|
graph_util_parse_trigger_order(priv, ep_cpu, &trigger_start, &trigger_stop);
|
|
graph_util_parse_trigger_order(priv, ep_codec, &trigger_start, &trigger_stop);
|
|
|
|
/*
|
|
* convert bit_frame
|
|
* We need to flip clock_provider if it was CPU node,
|
|
* because it is Codec base.
|
|
*/
|
|
daiclk = snd_soc_daifmt_clock_provider_from_bitmap(bit_frame);
|
|
if (is_cpu_node)
|
|
daiclk = snd_soc_daifmt_clock_provider_flipped(daiclk);
|
|
|
|
dai_link->playback_only = playback_only;
|
|
dai_link->capture_only = capture_only;
|
|
|
|
dai_link->trigger_start = trigger_start;
|
|
dai_link->trigger_stop = trigger_stop;
|
|
|
|
dai_link->dai_fmt = daifmt | daiclk;
|
|
dai_link->init = simple_util_dai_init;
|
|
dai_link->ops = &graph_ops;
|
|
if (priv->ops)
|
|
dai_link->ops = priv->ops;
|
|
|
|
of_node_put(ports_cpu);
|
|
of_node_put(ports_codec);
|
|
of_node_put(port_cpu);
|
|
of_node_put(port_codec);
|
|
of_node_put(ep_cpu);
|
|
of_node_put(ep_codec);
|
|
}
|
|
|
|
int audio_graph2_link_normal(struct simple_util_priv *priv,
|
|
struct device_node *lnk,
|
|
struct link_info *li)
|
|
{
|
|
struct device_node *cpu_port = lnk;
|
|
struct device_node *cpu_ep = of_graph_get_next_port_endpoint(cpu_port, NULL);
|
|
struct device_node *codec_port = of_graph_get_remote_port(cpu_ep);
|
|
int ret;
|
|
|
|
/*
|
|
* call Codec first.
|
|
* see
|
|
* __graph_parse_node() :: DAI Naming
|
|
*/
|
|
ret = graph_parse_node(priv, GRAPH_NORMAL, codec_port, li, 0);
|
|
if (ret < 0)
|
|
goto err;
|
|
|
|
/*
|
|
* call CPU, and set DAI Name
|
|
*/
|
|
ret = graph_parse_node(priv, GRAPH_NORMAL, cpu_port, li, 1);
|
|
if (ret < 0)
|
|
goto err;
|
|
|
|
graph_link_init(priv, lnk, cpu_port, codec_port, li, 1);
|
|
err:
|
|
of_node_put(codec_port);
|
|
of_node_put(cpu_ep);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(audio_graph2_link_normal);
|
|
|
|
int audio_graph2_link_dpcm(struct simple_util_priv *priv,
|
|
struct device_node *lnk,
|
|
struct link_info *li)
|
|
{
|
|
struct device_node *ep = of_graph_get_next_port_endpoint(lnk, NULL);
|
|
struct device_node *rep = of_graph_get_remote_endpoint(ep);
|
|
struct device_node *cpu_port = NULL;
|
|
struct device_node *codec_port = NULL;
|
|
struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link);
|
|
struct simple_dai_props *dai_props = simple_priv_to_props(priv, li->link);
|
|
int is_cpu = graph_util_is_ports0(lnk);
|
|
int ret;
|
|
|
|
if (is_cpu) {
|
|
cpu_port = of_graph_get_remote_port(ep); /* rport */
|
|
|
|
/*
|
|
* dpcm {
|
|
* // Front-End
|
|
* ports@0 {
|
|
* => lnk: port@0 { ep: { ... = rep }; };
|
|
* ...
|
|
* };
|
|
* // Back-End
|
|
* ports@0 {
|
|
* ...
|
|
* };
|
|
* };
|
|
*
|
|
* CPU {
|
|
* rports: ports {
|
|
* rport: port@0 { rep: { ... = ep } };
|
|
* }
|
|
* }
|
|
*/
|
|
/*
|
|
* setup CPU here, Codec is already set as dummy.
|
|
* see
|
|
* simple_util_init_priv()
|
|
*/
|
|
dai_link->dynamic = 1;
|
|
dai_link->dpcm_merged_format = 1;
|
|
|
|
ret = graph_parse_node(priv, GRAPH_DPCM, cpu_port, li, 1);
|
|
if (ret)
|
|
goto err;
|
|
|
|
} else {
|
|
codec_port = of_graph_get_remote_port(ep); /* rport */
|
|
|
|
/*
|
|
* dpcm {
|
|
* // Front-End
|
|
* ports@0 {
|
|
* ...
|
|
* };
|
|
* // Back-End
|
|
* ports@0 {
|
|
* => lnk: port@0 { ep: { ... = rep; }; };
|
|
* ...
|
|
* };
|
|
* };
|
|
*
|
|
* Codec {
|
|
* rports: ports {
|
|
* rport: port@0 { rep: { ... = ep; }; };
|
|
* }
|
|
* }
|
|
*/
|
|
/*
|
|
* setup Codec here, CPU is already set as dummy.
|
|
* see
|
|
* simple_util_init_priv()
|
|
*/
|
|
|
|
/* BE settings */
|
|
dai_link->no_pcm = 1;
|
|
dai_link->be_hw_params_fixup = simple_util_be_hw_params_fixup;
|
|
|
|
ret = graph_parse_node(priv, GRAPH_DPCM, codec_port, li, 0);
|
|
if (ret < 0)
|
|
goto err;
|
|
}
|
|
|
|
graph_parse_convert(ep, dai_props); /* at node of <dpcm> */
|
|
graph_parse_convert(rep, dai_props); /* at node of <CPU/Codec> */
|
|
|
|
graph_link_init(priv, lnk, cpu_port, codec_port, li, is_cpu);
|
|
err:
|
|
of_node_put(ep);
|
|
of_node_put(rep);
|
|
of_node_put(cpu_port);
|
|
of_node_put(codec_port);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(audio_graph2_link_dpcm);
|
|
|
|
int audio_graph2_link_c2c(struct simple_util_priv *priv,
|
|
struct device_node *lnk,
|
|
struct link_info *li)
|
|
{
|
|
struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link);
|
|
struct device_node *port0, *port1, *ports;
|
|
struct device_node *codec0_port, *codec1_port;
|
|
struct device_node *ep0, *ep1;
|
|
u32 val = 0;
|
|
int ret = -EINVAL;
|
|
|
|
/*
|
|
* codec2codec {
|
|
* ports {
|
|
* rate = <48000>;
|
|
* => lnk: port@0 { c2c0_ep: { ... = codec0_ep; }; };
|
|
* port@1 { c2c1_ep: { ... = codec1_ep; }; };
|
|
* };
|
|
* };
|
|
*
|
|
* Codec {
|
|
* ports {
|
|
* port@0 { codec0_ep: ... }; };
|
|
* port@1 { codec1_ep: ... }; };
|
|
* };
|
|
* };
|
|
*/
|
|
of_node_get(lnk);
|
|
port0 = lnk;
|
|
ports = port_to_ports(port0);
|
|
port1 = of_graph_get_next_port(ports, port0);
|
|
|
|
/*
|
|
* Card2 can use original Codec2Codec settings if DT has.
|
|
* It will use default settings if no settings on DT.
|
|
* see
|
|
* simple_util_init_for_codec2codec()
|
|
*
|
|
* Add more settings here if needed
|
|
*/
|
|
of_property_read_u32(ports, "rate", &val);
|
|
if (val) {
|
|
struct device *dev = simple_priv_to_dev(priv);
|
|
struct snd_soc_pcm_stream *c2c_conf;
|
|
|
|
c2c_conf = devm_kzalloc(dev, sizeof(*c2c_conf), GFP_KERNEL);
|
|
if (!c2c_conf)
|
|
goto err1;
|
|
|
|
c2c_conf->formats = SNDRV_PCM_FMTBIT_S32_LE; /* update ME */
|
|
c2c_conf->rates = SNDRV_PCM_RATE_8000_384000;
|
|
c2c_conf->rate_min =
|
|
c2c_conf->rate_max = val;
|
|
c2c_conf->channels_min =
|
|
c2c_conf->channels_max = 2; /* update ME */
|
|
|
|
dai_link->c2c_params = c2c_conf;
|
|
dai_link->num_c2c_params = 1;
|
|
}
|
|
|
|
ep0 = of_graph_get_next_port_endpoint(port0, NULL);
|
|
ep1 = of_graph_get_next_port_endpoint(port1, NULL);
|
|
|
|
codec0_port = of_graph_get_remote_port(ep0);
|
|
codec1_port = of_graph_get_remote_port(ep1);
|
|
|
|
/*
|
|
* call Codec first.
|
|
* see
|
|
* __graph_parse_node() :: DAI Naming
|
|
*/
|
|
ret = graph_parse_node(priv, GRAPH_C2C, codec1_port, li, 0);
|
|
if (ret < 0)
|
|
goto err2;
|
|
|
|
/*
|
|
* call CPU, and set DAI Name
|
|
*/
|
|
ret = graph_parse_node(priv, GRAPH_C2C, codec0_port, li, 1);
|
|
if (ret < 0)
|
|
goto err2;
|
|
|
|
graph_link_init(priv, lnk, codec0_port, codec1_port, li, 1);
|
|
err2:
|
|
of_node_put(ep0);
|
|
of_node_put(ep1);
|
|
of_node_put(codec0_port);
|
|
of_node_put(codec1_port);
|
|
err1:
|
|
of_node_put(ports);
|
|
of_node_put(port0);
|
|
of_node_put(port1);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(audio_graph2_link_c2c);
|
|
|
|
static int graph_link(struct simple_util_priv *priv,
|
|
struct graph2_custom_hooks *hooks,
|
|
enum graph_type gtype,
|
|
struct device_node *lnk,
|
|
struct link_info *li)
|
|
{
|
|
struct device *dev = simple_priv_to_dev(priv);
|
|
GRAPH2_CUSTOM func = NULL;
|
|
int ret = -EINVAL;
|
|
|
|
switch (gtype) {
|
|
case GRAPH_NORMAL:
|
|
if (hooks && hooks->custom_normal)
|
|
func = hooks->custom_normal;
|
|
else
|
|
func = audio_graph2_link_normal;
|
|
break;
|
|
case GRAPH_DPCM:
|
|
if (hooks && hooks->custom_dpcm)
|
|
func = hooks->custom_dpcm;
|
|
else
|
|
func = audio_graph2_link_dpcm;
|
|
break;
|
|
case GRAPH_C2C:
|
|
if (hooks && hooks->custom_c2c)
|
|
func = hooks->custom_c2c;
|
|
else
|
|
func = audio_graph2_link_c2c;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (!func) {
|
|
dev_err(dev, "non supported gtype (%d)\n", gtype);
|
|
goto err;
|
|
}
|
|
|
|
ret = func(priv, lnk, li);
|
|
if (ret < 0)
|
|
goto err;
|
|
|
|
li->link++;
|
|
err:
|
|
return ret;
|
|
}
|
|
|
|
static int graph_counter(struct device_node *lnk)
|
|
{
|
|
/*
|
|
* Multi CPU / Codec
|
|
*
|
|
* multi {
|
|
* ports {
|
|
* => lnk: port@0 { ... }; // to pair
|
|
* port@1 { ... }; // Multi Element
|
|
* port@2 { ... }; // Multi Element
|
|
* ...
|
|
* };
|
|
* };
|
|
*
|
|
* ignore first lnk part
|
|
*/
|
|
if (graph_lnk_is_multi(lnk)) {
|
|
struct device_node *ports = port_to_ports(lnk);
|
|
|
|
/*
|
|
* CPU/Codec = N:M case has many endpoints.
|
|
* We can't use of_graph_get_endpoint_count() here
|
|
*/
|
|
return of_graph_get_port_count(ports) - 1;
|
|
}
|
|
/*
|
|
* Single CPU / Codec
|
|
*/
|
|
else
|
|
return 1;
|
|
}
|
|
|
|
static int graph_count_normal(struct simple_util_priv *priv,
|
|
struct device_node *lnk,
|
|
struct link_info *li)
|
|
{
|
|
struct device_node *cpu_port = lnk;
|
|
struct device_node *cpu_ep = of_graph_get_next_port_endpoint(cpu_port, NULL);
|
|
struct device_node *codec_port = of_graph_get_remote_port(cpu_ep);
|
|
|
|
/*
|
|
* CPU {
|
|
* => lnk: port { endpoint { .. }; };
|
|
* };
|
|
*/
|
|
/*
|
|
* DON'T REMOVE platforms
|
|
* see
|
|
* simple-card.c :: simple_count_noml()
|
|
*/
|
|
li->num[li->link].cpus =
|
|
li->num[li->link].platforms = graph_counter(cpu_port);
|
|
|
|
li->num[li->link].codecs = graph_counter(codec_port);
|
|
|
|
of_node_put(cpu_ep);
|
|
of_node_put(codec_port);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int graph_count_dpcm(struct simple_util_priv *priv,
|
|
struct device_node *lnk,
|
|
struct link_info *li)
|
|
{
|
|
struct device_node *ep = of_graph_get_next_port_endpoint(lnk, NULL);
|
|
struct device_node *rport = of_graph_get_remote_port(ep);
|
|
|
|
/*
|
|
* dpcm {
|
|
* // Front-End
|
|
* ports@0 {
|
|
* => lnk: port@0 { endpoint { ... }; };
|
|
* ...
|
|
* };
|
|
* // Back-End
|
|
* ports@1 {
|
|
* => lnk: port@0 { endpoint { ... }; };
|
|
* ...
|
|
* };
|
|
* };
|
|
*/
|
|
|
|
if (graph_util_is_ports0(lnk)) {
|
|
/*
|
|
* DON'T REMOVE platforms
|
|
* see
|
|
* simple-card.c :: simple_count_noml()
|
|
*/
|
|
li->num[li->link].cpus = graph_counter(rport); /* FE */
|
|
li->num[li->link].platforms = graph_counter(rport);
|
|
} else {
|
|
li->num[li->link].codecs = graph_counter(rport); /* BE */
|
|
}
|
|
|
|
of_node_put(ep);
|
|
of_node_put(rport);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int graph_count_c2c(struct simple_util_priv *priv,
|
|
struct device_node *lnk,
|
|
struct link_info *li)
|
|
{
|
|
struct device_node *ports = port_to_ports(lnk);
|
|
struct device_node *port0 = lnk;
|
|
struct device_node *port1 = of_graph_get_next_port(ports, of_node_get(port0));
|
|
struct device_node *ep0 = of_graph_get_next_port_endpoint(port0, NULL);
|
|
struct device_node *ep1 = of_graph_get_next_port_endpoint(port1, NULL);
|
|
struct device_node *codec0 = of_graph_get_remote_port(ep0);
|
|
struct device_node *codec1 = of_graph_get_remote_port(ep1);
|
|
|
|
/*
|
|
* codec2codec {
|
|
* ports {
|
|
* => lnk: port@0 { endpoint { ... }; };
|
|
* port@1 { endpoint { ... }; };
|
|
* };
|
|
* };
|
|
*/
|
|
/*
|
|
* DON'T REMOVE platforms
|
|
* see
|
|
* simple-card.c :: simple_count_noml()
|
|
*/
|
|
li->num[li->link].cpus =
|
|
li->num[li->link].platforms = graph_counter(codec0);
|
|
|
|
li->num[li->link].codecs = graph_counter(codec1);
|
|
|
|
of_node_put(ports);
|
|
of_node_put(port1);
|
|
of_node_put(ep0);
|
|
of_node_put(ep1);
|
|
of_node_put(codec0);
|
|
of_node_put(codec1);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int graph_count(struct simple_util_priv *priv,
|
|
struct graph2_custom_hooks *hooks,
|
|
enum graph_type gtype,
|
|
struct device_node *lnk,
|
|
struct link_info *li)
|
|
{
|
|
struct device *dev = simple_priv_to_dev(priv);
|
|
GRAPH2_CUSTOM func = NULL;
|
|
int ret = -EINVAL;
|
|
|
|
if (li->link >= SNDRV_MAX_LINKS) {
|
|
dev_err(dev, "too many links\n");
|
|
return ret;
|
|
}
|
|
|
|
switch (gtype) {
|
|
case GRAPH_NORMAL:
|
|
func = graph_count_normal;
|
|
break;
|
|
case GRAPH_DPCM:
|
|
func = graph_count_dpcm;
|
|
break;
|
|
case GRAPH_C2C:
|
|
func = graph_count_c2c;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (!func) {
|
|
dev_err(dev, "non supported gtype (%d)\n", gtype);
|
|
goto err;
|
|
}
|
|
|
|
ret = func(priv, lnk, li);
|
|
if (ret < 0)
|
|
goto err;
|
|
|
|
li->link++;
|
|
err:
|
|
return ret;
|
|
}
|
|
|
|
static int graph_for_each_link(struct simple_util_priv *priv,
|
|
struct graph2_custom_hooks *hooks,
|
|
struct link_info *li,
|
|
int (*func)(struct simple_util_priv *priv,
|
|
struct graph2_custom_hooks *hooks,
|
|
enum graph_type gtype,
|
|
struct device_node *lnk,
|
|
struct link_info *li))
|
|
{
|
|
struct of_phandle_iterator it;
|
|
struct device *dev = simple_priv_to_dev(priv);
|
|
struct device_node *node = dev->of_node;
|
|
struct device_node *lnk;
|
|
enum graph_type gtype;
|
|
int rc, ret;
|
|
|
|
/* loop for all listed CPU port */
|
|
of_for_each_phandle(&it, rc, node, "links", NULL, 0) {
|
|
lnk = it.node;
|
|
|
|
gtype = graph_get_type(priv, lnk);
|
|
|
|
ret = func(priv, hooks, gtype, lnk, li);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int audio_graph2_parse_of(struct simple_util_priv *priv, struct device *dev,
|
|
struct graph2_custom_hooks *hooks)
|
|
{
|
|
struct snd_soc_card *card = simple_priv_to_card(priv);
|
|
int ret;
|
|
|
|
struct link_info *li __free(kfree) = kzalloc(sizeof(*li), GFP_KERNEL);
|
|
if (!li)
|
|
return -ENOMEM;
|
|
|
|
card->probe = graph_util_card_probe;
|
|
card->owner = THIS_MODULE;
|
|
card->dev = dev;
|
|
|
|
if ((hooks) && (hooks)->hook_pre) {
|
|
ret = (hooks)->hook_pre(priv);
|
|
if (ret < 0)
|
|
goto err;
|
|
}
|
|
|
|
ret = graph_for_each_link(priv, hooks, li, graph_count);
|
|
if (!li->link)
|
|
ret = -EINVAL;
|
|
if (ret < 0)
|
|
goto err;
|
|
|
|
ret = simple_util_init_priv(priv, li);
|
|
if (ret < 0)
|
|
goto err;
|
|
|
|
priv->pa_gpio = devm_gpiod_get_optional(dev, "pa", GPIOD_OUT_LOW);
|
|
if (IS_ERR(priv->pa_gpio)) {
|
|
ret = PTR_ERR(priv->pa_gpio);
|
|
dev_err(dev, "failed to get amplifier gpio: %d\n", ret);
|
|
goto err;
|
|
}
|
|
|
|
ret = simple_util_parse_widgets(card, NULL);
|
|
if (ret < 0)
|
|
goto err;
|
|
|
|
ret = simple_util_parse_routing(card, NULL);
|
|
if (ret < 0)
|
|
goto err;
|
|
|
|
memset(li, 0, sizeof(*li));
|
|
ret = graph_for_each_link(priv, hooks, li, graph_link);
|
|
if (ret < 0)
|
|
goto err;
|
|
|
|
ret = simple_util_parse_card_name(card, NULL);
|
|
if (ret < 0)
|
|
goto err;
|
|
|
|
snd_soc_card_set_drvdata(card, priv);
|
|
|
|
if ((hooks) && (hooks)->hook_post) {
|
|
ret = (hooks)->hook_post(priv);
|
|
if (ret < 0)
|
|
goto err;
|
|
}
|
|
|
|
simple_util_debug_info(priv);
|
|
|
|
ret = snd_soc_of_parse_aux_devs(card, "aux-devs");
|
|
if (ret < 0)
|
|
goto err;
|
|
|
|
ret = devm_snd_soc_register_card(dev, card);
|
|
err:
|
|
if (ret < 0)
|
|
dev_err_probe(dev, ret, "parse error\n");
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(audio_graph2_parse_of);
|
|
|
|
static int graph_probe(struct platform_device *pdev)
|
|
{
|
|
struct simple_util_priv *priv;
|
|
struct device *dev = &pdev->dev;
|
|
|
|
/* Allocate the private data and the DAI link array */
|
|
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
|
if (!priv)
|
|
return -ENOMEM;
|
|
|
|
return audio_graph2_parse_of(priv, dev, NULL);
|
|
}
|
|
|
|
static const struct of_device_id graph_of_match[] = {
|
|
{ .compatible = "audio-graph-card2", },
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, graph_of_match);
|
|
|
|
static struct platform_driver graph_card = {
|
|
.driver = {
|
|
.name = "asoc-audio-graph-card2",
|
|
.pm = &snd_soc_pm_ops,
|
|
.of_match_table = graph_of_match,
|
|
},
|
|
.probe = graph_probe,
|
|
.remove = simple_util_remove,
|
|
};
|
|
module_platform_driver(graph_card);
|
|
|
|
MODULE_ALIAS("platform:asoc-audio-graph-card2");
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_DESCRIPTION("ASoC Audio Graph Card2");
|
|
MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>");
|