Path: blob/master/sound/soc/generic/audio-graph-card2.c
26424 views
// SPDX-License-Identifier: GPL-2.01//2// ASoC Audio Graph Card2 support3//4// Copyright (C) 2020 Renesas Electronics Corp.5// Copyright (C) 2020 Kuninori Morimoto <[email protected]>6//7// based on ${LINUX}/sound/soc/generic/audio-graph-card.c8#include <linux/clk.h>9#include <linux/device.h>10#include <linux/gpio/consumer.h>11#include <linux/module.h>12#include <linux/of.h>13#include <linux/of_graph.h>14#include <linux/platform_device.h>15#include <linux/string.h>16#include <sound/graph_card.h>1718/************************************19daifmt20************************************21ports {22format = "left_j";23port@0 {24bitclock-master;25sample0: endpoint@0 {26frame-master;27};28sample1: endpoint@1 {29format = "i2s";30};31};32...33};3435You can set daifmt at ports/port/endpoint.36It uses *latest* format, and *share* master settings.37In above case,38sample0: left_j, bitclock-master, frame-master39sample1: i2s, bitclock-master4041If there was no settings, *Codec* will be42bitclock/frame provider as default.43see44graph_parse_daifmt().4546"format" property is no longer needed on DT if both CPU/Codec drivers are47supporting snd_soc_dai_ops :: .auto_selectable_formats.48see49snd_soc_runtime_get_dai_fmt()5051sample driver52linux/sound/soc/renesas/rcar/core.c53linux/sound/soc/codecs/ak4613.c54linux/sound/soc/codecs/pcm3168a.c55linux/sound/soc/soc-utils.c56linux/sound/soc/generic/test-component.c5758************************************59Normal Audio-Graph60************************************6162CPU <---> Codec6364sound {65compatible = "audio-graph-card2";66links = <&cpu>;67};6869CPU {70cpu: port {71bitclock-master;72frame-master;73cpu_ep: endpoint { remote-endpoint = <&codec_ep>; }; };74};7576Codec {77port { codec_ep: endpoint { remote-endpoint = <&cpu_ep>; }; };78};7980************************************81Multi-CPU/Codec82************************************8384It has link connection part (= X,x) and list part (= A,B,a,b).85"links" is connection part of CPU side (= @).8687+----+ +---+88CPU1 --|A X| <-@----> |x a|-- Codec189CPU2 --|B | | b|-- Codec290+----+ +---+9192sound {93compatible = "audio-graph-card2";9495(@) links = <&mcpu>;9697multi {98ports@0 {99(@) mcpu: port@0 { mcpu0_ep: endpoint { remote-endpoint = <&mcodec0_ep>; }; }; // (X) to pair100port@1 { mcpu1_ep: endpoint { remote-endpoint = <&cpu1_ep>; }; }; // (A) Multi Element101port@2 { mcpu2_ep: endpoint { remote-endpoint = <&cpu2_ep>; }; }; // (B) Multi Element102};103ports@1 {104port@0 { mcodec0_ep: endpoint { remote-endpoint = <&mcpu0_ep>; }; }; // (x) to pair105port@1 { mcodec1_ep: endpoint { remote-endpoint = <&codec1_ep>; }; }; // (a) Multi Element106port@2 { mcodec2_ep: endpoint { remote-endpoint = <&codec2_ep>; }; }; // (b) Multi Element107};108};109};110111CPU {112ports {113bitclock-master;114frame-master;115port@0 { cpu1_ep: endpoint { remote-endpoint = <&mcpu1_ep>; }; };116port@1 { cpu2_ep: endpoint { remote-endpoint = <&mcpu2_ep>; }; };117};118};119120Codec {121ports {122port@0 { codec1_ep: endpoint { remote-endpoint = <&mcodec1_ep>; }; };123port@1 { codec2_ep: endpoint { remote-endpoint = <&mcodec2_ep>; }; };124};125};126127************************************128DPCM129************************************130131DSP132************133PCM0 <--> * fe0 be0 * <--> DAI0: Codec Headset134PCM1 <--> * fe1 be1 * <--> DAI1: Codec Speakers135PCM2 <--> * fe2 be2 * <--> DAI2: MODEM136PCM3 <--> * fe3 be3 * <--> DAI3: BT137* be4 * <--> DAI4: DMIC138* be5 * <--> DAI5: FM139************140141sound {142compatible = "audio-graph-card2";143144// indicate routing145routing = "xxx Playback", "xxx Playback",146"xxx Playback", "xxx Playback",147"xxx Playback", "xxx Playback";148149// indicate all Front-End, Back-End150links = <&fe0, &fe1, ...,151&be0, &be1, ...>;152153dpcm {154// Front-End155ports@0 {156fe0: port@0 { fe0_ep: endpoint { remote-endpoint = <&pcm0_ep>; }; };157fe1: port@1 { fe1_ep: endpoint { remote-endpoint = <&pcm1_ep>; }; };158...159};160// Back-End161ports@1 {162be0: port@0 { be0_ep: endpoint { remote-endpoint = <&dai0_ep>; }; };163be1: port@1 { be1_ep: endpoint { remote-endpoint = <&dai1_ep>; }; };164...165};166};167};168169CPU {170ports {171bitclock-master;172frame-master;173port@0 { pcm0_ep: endpoint { remote-endpoint = <&fe0_ep>; }; };174port@1 { pcm1_ep: endpoint { remote-endpoint = <&fe1_ep>; }; };175...176};177};178179Codec {180ports {181port@0 { dai0_ep: endpoint { remote-endpoint = <&be0_ep>; }; };182port@1 { dai1_ep: endpoint { remote-endpoint = <&be1_ep>; }; };183...184};185};186187************************************188Codec to Codec189************************************190191+--+192| |<-- Codec0 <- IN193| |--> Codec1 -> OUT194+--+195196sound {197compatible = "audio-graph-card2";198199routing = "OUT" ,"DAI1 Playback",200"DAI0 Capture", "IN";201202links = <&c2c>;203204codec2codec {205ports {206rate = <48000>;207c2c: port@0 { c2cf_ep: endpoint { remote-endpoint = <&codec0_ep>; }; };208port@1 { c2cb_ep: endpoint { remote-endpoint = <&codec1_ep>; }; };209};210};211212Codec {213ports {214port@0 {215bitclock-master;216frame-master;217codec0_ep: endpoint { remote-endpoint = <&c2cf_ep>; }; };218port@1 { codec1_ep: endpoint { remote-endpoint = <&c2cb_ep>; }; };219};220};221222*/223224enum graph_type {225GRAPH_NORMAL,226GRAPH_DPCM,227GRAPH_C2C,228229GRAPH_MULTI, /* don't use ! Use this only in __graph_get_type() */230};231232#define GRAPH_NODENAME_MULTI "multi"233#define GRAPH_NODENAME_DPCM "dpcm"234#define GRAPH_NODENAME_C2C "codec2codec"235236#define graph_ret(priv, ret) _graph_ret(priv, __func__, ret)237static inline int _graph_ret(struct simple_util_priv *priv,238const char *func, int ret)239{240return snd_soc_ret(simple_priv_to_dev(priv), ret, "at %s()\n", func);241}242243#define ep_to_port(ep) of_get_parent(ep)244static struct device_node *port_to_ports(struct device_node *port)245{246struct device_node *ports = of_get_parent(port);247248if (!of_node_name_eq(ports, "ports")) {249of_node_put(ports);250return NULL;251}252return ports;253}254255static enum graph_type __graph_get_type(struct device_node *lnk)256{257struct device_node *np, *parent_np;258enum graph_type ret;259260/*261* target {262* ports {263* => lnk: port@0 { ... };264* port@1 { ... };265* };266* };267*/268np = of_get_parent(lnk);269if (of_node_name_eq(np, "ports")) {270parent_np = of_get_parent(np);271of_node_put(np);272np = parent_np;273}274275if (of_node_name_eq(np, GRAPH_NODENAME_MULTI)) {276ret = GRAPH_MULTI;277fw_devlink_purge_absent_suppliers(&np->fwnode);278goto out_put;279}280281if (of_node_name_eq(np, GRAPH_NODENAME_DPCM)) {282ret = GRAPH_DPCM;283fw_devlink_purge_absent_suppliers(&np->fwnode);284goto out_put;285}286287if (of_node_name_eq(np, GRAPH_NODENAME_C2C)) {288ret = GRAPH_C2C;289fw_devlink_purge_absent_suppliers(&np->fwnode);290goto out_put;291}292293ret = GRAPH_NORMAL;294295out_put:296of_node_put(np);297return ret;298299}300301static enum graph_type graph_get_type(struct simple_util_priv *priv,302struct device_node *lnk)303{304enum graph_type type = __graph_get_type(lnk);305306/* GRAPH_MULTI here means GRAPH_NORMAL */307if (type == GRAPH_MULTI)308type = GRAPH_NORMAL;309310#ifdef DEBUG311{312struct device *dev = simple_priv_to_dev(priv);313const char *str = "Normal";314315switch (type) {316case GRAPH_DPCM:317if (graph_util_is_ports0(lnk))318str = "DPCM Front-End";319else320str = "DPCM Back-End";321break;322case GRAPH_C2C:323str = "Codec2Codec";324break;325default:326break;327}328329dev_dbg(dev, "%pOF (%s)", lnk, str);330}331#endif332return type;333}334335static int graph_lnk_is_multi(struct device_node *lnk)336{337return __graph_get_type(lnk) == GRAPH_MULTI;338}339340static struct device_node *graph_get_next_multi_ep(struct device_node **port, int idx)341{342struct device_node *ports __free(device_node) = port_to_ports(*port);343struct device_node *rep = NULL;344345/*346* multi {347* ports {348* => lnk: port@0 { ... }; // to pair349* port@1 { ep { ... = rep0 } }; // Multi Element350* port@2 { ep { ... = rep1 } }; // Multi Element351* ...352* };353* };354*355* xxx {356* port@0 { rep0 };357* port@1 { rep1 };358* };359*/360361/*362* Don't use of_graph_get_next_port() here363*364* In overlay case, "port" are not necessarily in order. So we need to use365* of_graph_get_port_by_id() instead366*/367of_node_put(*port);368369*port = of_graph_get_port_by_id(ports, idx);370if (*port) {371struct device_node *ep __free(device_node) = of_graph_get_next_port_endpoint(*port, NULL);372373rep = of_graph_get_remote_endpoint(ep);374}375376return rep;377}378379static const struct snd_soc_ops graph_ops = {380.startup = simple_util_startup,381.shutdown = simple_util_shutdown,382.hw_params = simple_util_hw_params,383};384385static void graph_parse_convert(struct device_node *ep,386struct simple_dai_props *props)387{388struct device_node *port __free(device_node) = ep_to_port(ep);389struct device_node *ports __free(device_node) = port_to_ports(port);390struct simple_util_data *adata = &props->adata;391392simple_util_parse_convert(ports, NULL, adata);393simple_util_parse_convert(port, NULL, adata);394simple_util_parse_convert(ep, NULL, adata);395}396397static int __graph_parse_node(struct simple_util_priv *priv,398enum graph_type gtype,399struct device_node *ep,400struct link_info *li,401int is_cpu, int idx)402{403struct device *dev = simple_priv_to_dev(priv);404struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link);405struct simple_dai_props *dai_props = simple_priv_to_props(priv, li->link);406struct snd_soc_dai_link_component *dlc;407struct simple_util_dai *dai;408int ret, is_single_links = 0;409410if (is_cpu) {411dlc = snd_soc_link_to_cpu(dai_link, idx);412dai = simple_props_to_dai_cpu(dai_props, idx);413} else {414dlc = snd_soc_link_to_codec(dai_link, idx);415dai = simple_props_to_dai_codec(dai_props, idx);416}417418ret = graph_util_parse_dai(priv, ep, dlc, &is_single_links);419if (ret < 0)420goto end;421422ret = simple_util_parse_tdm(ep, dai);423if (ret < 0)424goto end;425426ret = simple_util_parse_tdm_width_map(priv, ep, dai);427if (ret < 0)428goto end;429430ret = simple_util_parse_clk(dev, ep, dai, dlc);431if (ret < 0)432goto end;433434/*435* set DAI Name436*/437if (!dai_link->name) {438struct snd_soc_dai_link_component *cpus = dlc;439struct snd_soc_dai_link_component *codecs = snd_soc_link_to_codec(dai_link, idx);440char *cpu_multi = "";441char *codec_multi = "";442443if (dai_link->num_cpus > 1)444cpu_multi = "_multi";445if (dai_link->num_codecs > 1)446codec_multi = "_multi";447448switch (gtype) {449case GRAPH_NORMAL:450/* run is_cpu only. see audio_graph2_link_normal() */451if (is_cpu)452simple_util_set_dailink_name(priv, dai_link, "%s%s-%s%s",453cpus->dai_name, cpu_multi,454codecs->dai_name, codec_multi);455break;456case GRAPH_DPCM:457if (is_cpu)458simple_util_set_dailink_name(priv, dai_link, "fe.%pOFP.%s%s",459cpus->of_node, cpus->dai_name, cpu_multi);460else461simple_util_set_dailink_name(priv, dai_link, "be.%pOFP.%s%s",462codecs->of_node, codecs->dai_name, codec_multi);463break;464case GRAPH_C2C:465/* run is_cpu only. see audio_graph2_link_c2c() */466if (is_cpu)467simple_util_set_dailink_name(priv, dai_link, "c2c.%s%s-%s%s",468cpus->dai_name, cpu_multi,469codecs->dai_name, codec_multi);470break;471default:472break;473}474}475476/*477* Check "prefix" from top node478* if DPCM-BE case479*/480if (!is_cpu && gtype == GRAPH_DPCM) {481struct snd_soc_dai_link_component *codecs = snd_soc_link_to_codec(dai_link, idx);482struct snd_soc_codec_conf *cconf = simple_props_to_codec_conf(dai_props, idx);483struct device_node *rport __free(device_node) = ep_to_port(ep);484struct device_node *rports __free(device_node) = port_to_ports(rport);485486snd_soc_of_parse_node_prefix(rports, cconf, codecs->of_node, "prefix");487snd_soc_of_parse_node_prefix(rport, cconf, codecs->of_node, "prefix");488}489490if (is_cpu) {491struct snd_soc_dai_link_component *cpus = dlc;492struct snd_soc_dai_link_component *platforms = snd_soc_link_to_platform(dai_link, idx);493494simple_util_canonicalize_cpu(cpus, is_single_links);495simple_util_canonicalize_platform(platforms, cpus);496}497end:498return graph_ret(priv, ret);499}500501static int graph_parse_node_multi_nm(struct simple_util_priv *priv,502struct snd_soc_dai_link *dai_link,503int *nm_idx, int cpu_idx,504struct device_node *mcpu_port)505{506/*507* +---+ +---+508* | X|<-@------->|x |509* | | | |510* cpu0 <--|A 1|<--------->|4 a|-> codec0511* cpu1 <--|B 2|<-----+--->|5 b|-> codec1512* cpu2 <--|C 3|<----/ +---+513* +---+514*515* multi {516* ports {517* port@0 { mcpu_top_ep {... = mcodec_ep; }; }; // (X) to pair518* <mcpu_port> port@1 { mcpu0_ep { ... = cpu0_ep; }; // (A) Multi Element519* mcpu0_ep_0 { ... = mcodec0_ep_0; }; }; // (1) connected Codec520* port@2 { mcpu1_ep { ... = cpu1_ep; }; // (B) Multi Element521* mcpu1_ep_0 { ... = mcodec1_ep_0; }; }; // (2) connected Codec522* port@3 { mcpu2_ep { ... = cpu2_ep; }; // (C) Multi Element523* mcpu2_ep_0 { ... = mcodec1_ep_1; }; }; // (3) connected Codec524* };525*526* ports {527* port@0 { mcodec_top_ep {... = mcpu_ep; }; }; // (x) to pair528* <mcodec_port>port@1 { mcodec0_ep { ... = codec0_ep; }; // (a) Multi Element529* mcodec0_ep_0 { ... = mcpu0_ep_0; }; }; // (4) connected CPU530* port@2 { mcodec1_ep { ... = codec1_ep; }; // (b) Multi Element531* mcodec1_ep_0 { ... = mcpu1_ep_0; }; // (5) connected CPU532* mcodec1_ep_1 { ... = mcpu2_ep_0; }; }; // (5) connected CPU533* };534* };535*/536struct device_node *mcpu_ep __free(device_node) = of_graph_get_next_port_endpoint(mcpu_port, NULL);537struct device_node *mcpu_ports __free(device_node) = port_to_ports(mcpu_port);538struct device_node *mcpu_port_top __free(device_node) = of_graph_get_next_port(mcpu_ports, NULL);539struct device_node *mcpu_ep_top __free(device_node) = of_graph_get_next_port_endpoint(mcpu_port_top, NULL);540struct device_node *mcodec_ep_top __free(device_node) = of_graph_get_remote_endpoint(mcpu_ep_top);541struct device_node *mcodec_port_top __free(device_node) = ep_to_port(mcodec_ep_top);542struct device_node *mcodec_ports __free(device_node) = port_to_ports(mcodec_port_top);543int nm_max = max(dai_link->num_cpus, dai_link->num_codecs);544int ret = -EINVAL;545546if (cpu_idx > dai_link->num_cpus)547goto end;548549for_each_of_graph_port_endpoint(mcpu_port, mcpu_ep_n) {550int codec_idx = 0;551552/* ignore 1st ep which is for element */553if (mcpu_ep_n == mcpu_ep)554continue;555556if (*nm_idx > nm_max)557break;558559struct device_node *mcodec_ep_n __free(device_node) = of_graph_get_remote_endpoint(mcpu_ep_n);560struct device_node *mcodec_port __free(device_node) = ep_to_port(mcodec_ep_n);561562ret = -EINVAL;563if (mcodec_ports != port_to_ports(mcodec_port))564break;565566for_each_of_graph_port(mcodec_ports, mcodec_port_i) {567568/* ignore 1st port which is for pair connection */569if (mcodec_port_top == mcodec_port_i)570continue;571572if (codec_idx > dai_link->num_codecs)573break;574575if (mcodec_port_i == mcodec_port) {576dai_link->ch_maps[*nm_idx].cpu = cpu_idx;577dai_link->ch_maps[*nm_idx].codec = codec_idx;578579(*nm_idx)++;580ret = 0;581break;582}583codec_idx++;584}585if (ret < 0)586break;587}588end:589return graph_ret(priv, ret);590}591592static int graph_parse_node_multi(struct simple_util_priv *priv,593enum graph_type gtype,594struct device_node *port,595struct link_info *li, int is_cpu)596{597struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link);598struct device *dev = simple_priv_to_dev(priv);599int ret = -ENOMEM;600int nm_idx = 0;601int nm_max = max(dai_link->num_cpus, dai_link->num_codecs);602603/*604* create ch_maps if CPU:Codec = N:M605* DPCM is out of scope606*/607if (gtype != GRAPH_DPCM && !dai_link->ch_maps &&608dai_link->num_cpus > 1 && dai_link->num_codecs > 1 &&609dai_link->num_cpus != dai_link->num_codecs) {610611dai_link->ch_maps = devm_kcalloc(dev, nm_max,612sizeof(struct snd_soc_dai_link_ch_map), GFP_KERNEL);613if (!dai_link->ch_maps)614goto multi_err;615}616617for (int idx = 0;; idx++) {618/*619* multi {620* ports {621* <port> port@0 { ... }; // to pair622* port@1 { mcpu1_ep { ... = cpu1_ep };}; // Multi Element623* port@2 { mcpu2_ep { ... = cpu2_ep };}; // Multi Element624* };625* };626*627* cpu {628* ports {629* <ep> port@0 { cpu1_ep { ... = mcpu1_ep };};630* };631* };632*/633struct device_node *ep __free(device_node) = graph_get_next_multi_ep(&port, idx + 1);634if (!ep)635break;636637ret = __graph_parse_node(priv, gtype, ep, li, is_cpu, idx);638if (ret < 0)639goto multi_err;640641/* CPU:Codec = N:M */642if (is_cpu && dai_link->ch_maps) {643ret = graph_parse_node_multi_nm(priv, dai_link, &nm_idx, idx, port);644if (ret < 0)645goto multi_err;646}647}648649if (is_cpu && dai_link->ch_maps && (nm_idx != nm_max))650ret = -EINVAL;651652multi_err:653return graph_ret(priv, ret);654}655656static int graph_parse_node_single(struct simple_util_priv *priv,657enum graph_type gtype,658struct device_node *ep,659struct link_info *li, int is_cpu)660{661return graph_ret(priv, __graph_parse_node(priv, gtype, ep, li, is_cpu, 0));662}663664static int graph_parse_node(struct simple_util_priv *priv,665enum graph_type gtype,666struct device_node *ep,667struct link_info *li, int is_cpu)668{669struct device_node *port __free(device_node) = ep_to_port(ep);670int ret;671672if (graph_lnk_is_multi(port))673ret = graph_parse_node_multi(priv, gtype, port, li, is_cpu);674else675ret = graph_parse_node_single(priv, gtype, ep, li, is_cpu);676677return graph_ret(priv, ret);678}679680static void graph_parse_daifmt(struct device_node *node, unsigned int *daifmt)681{682unsigned int fmt;683684if (!node)685return;686687/*688* see also above "daifmt" explanation689* and samples.690*/691692/*693* ports {694* (A)695* port {696* (B)697* endpoint {698* (C)699* };700* };701* };702* };703*/704705#define update_daifmt(name) \706if (!(*daifmt & SND_SOC_DAIFMT_##name##_MASK) && \707(fmt & SND_SOC_DAIFMT_##name##_MASK)) \708*daifmt |= fmt & SND_SOC_DAIFMT_##name##_MASK709710/*711* format712*713* This function is called by (C) -> (B) -> (A) order.714* Set if applicable part was not yet set.715*/716fmt = snd_soc_daifmt_parse_format(node, NULL);717update_daifmt(FORMAT);718update_daifmt(CLOCK);719update_daifmt(INV);720}721722static unsigned int graph_parse_bitframe(struct device_node *ep)723{724struct device_node *port __free(device_node) = ep_to_port(ep);725struct device_node *ports __free(device_node) = port_to_ports(port);726727return snd_soc_daifmt_clock_provider_from_bitmap(728snd_soc_daifmt_parse_clock_provider_as_bitmap(ep, NULL) |729snd_soc_daifmt_parse_clock_provider_as_bitmap(port, NULL) |730snd_soc_daifmt_parse_clock_provider_as_bitmap(ports, NULL));731}732733static void graph_link_init(struct simple_util_priv *priv,734struct device_node *lnk,735struct device_node *ep_cpu,736struct device_node *ep_codec,737struct link_info *li,738int is_cpu_node)739{740struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link);741struct simple_dai_props *dai_props = simple_priv_to_props(priv, li->link);742struct device_node *port_cpu = ep_to_port(ep_cpu);743struct device_node *port_codec = ep_to_port(ep_codec);744struct device_node *multi_cpu_port = NULL, *multi_codec_port = NULL;745struct snd_soc_dai_link_component *dlc;746unsigned int daifmt = 0;747bool playback_only = 0, capture_only = 0;748enum snd_soc_trigger_order trigger_start = SND_SOC_TRIGGER_ORDER_DEFAULT;749enum snd_soc_trigger_order trigger_stop = SND_SOC_TRIGGER_ORDER_DEFAULT;750int multi_cpu_port_idx = 1, multi_codec_port_idx = 1;751int i;752753if (graph_lnk_is_multi(port_cpu)) {754multi_cpu_port = port_cpu;755ep_cpu = graph_get_next_multi_ep(&multi_cpu_port, multi_cpu_port_idx++);756of_node_put(port_cpu);757port_cpu = ep_to_port(ep_cpu);758} else {759of_node_get(ep_cpu);760}761struct device_node *ports_cpu __free(device_node) = port_to_ports(port_cpu);762763if (graph_lnk_is_multi(port_codec)) {764multi_codec_port = port_codec;765ep_codec = graph_get_next_multi_ep(&multi_codec_port, multi_codec_port_idx++);766of_node_put(port_codec);767port_codec = ep_to_port(ep_codec);768} else {769of_node_get(ep_codec);770}771struct device_node *ports_codec __free(device_node) = port_to_ports(port_codec);772773graph_parse_daifmt(ep_cpu, &daifmt);774graph_parse_daifmt(ep_codec, &daifmt);775graph_parse_daifmt(port_cpu, &daifmt);776graph_parse_daifmt(port_codec, &daifmt);777graph_parse_daifmt(ports_cpu, &daifmt);778graph_parse_daifmt(ports_codec, &daifmt);779graph_parse_daifmt(lnk, &daifmt);780781graph_util_parse_link_direction(lnk, &playback_only, &capture_only);782graph_util_parse_link_direction(ports_cpu, &playback_only, &capture_only);783graph_util_parse_link_direction(ports_codec, &playback_only, &capture_only);784graph_util_parse_link_direction(port_cpu, &playback_only, &capture_only);785graph_util_parse_link_direction(port_codec, &playback_only, &capture_only);786graph_util_parse_link_direction(ep_cpu, &playback_only, &capture_only);787graph_util_parse_link_direction(ep_codec, &playback_only, &capture_only);788789of_property_read_u32(lnk, "mclk-fs", &dai_props->mclk_fs);790of_property_read_u32(ports_cpu, "mclk-fs", &dai_props->mclk_fs);791of_property_read_u32(ports_codec, "mclk-fs", &dai_props->mclk_fs);792of_property_read_u32(port_cpu, "mclk-fs", &dai_props->mclk_fs);793of_property_read_u32(port_codec, "mclk-fs", &dai_props->mclk_fs);794of_property_read_u32(ep_cpu, "mclk-fs", &dai_props->mclk_fs);795of_property_read_u32(ep_codec, "mclk-fs", &dai_props->mclk_fs);796797graph_util_parse_trigger_order(priv, lnk, &trigger_start, &trigger_stop);798graph_util_parse_trigger_order(priv, ports_cpu, &trigger_start, &trigger_stop);799graph_util_parse_trigger_order(priv, ports_codec, &trigger_start, &trigger_stop);800graph_util_parse_trigger_order(priv, port_cpu, &trigger_start, &trigger_stop);801graph_util_parse_trigger_order(priv, port_cpu, &trigger_start, &trigger_stop);802graph_util_parse_trigger_order(priv, ep_cpu, &trigger_start, &trigger_stop);803graph_util_parse_trigger_order(priv, ep_codec, &trigger_start, &trigger_stop);804805for_each_link_cpus(dai_link, i, dlc) {806dlc->ext_fmt = graph_parse_bitframe(ep_cpu);807808if (multi_cpu_port)809ep_cpu = graph_get_next_multi_ep(&multi_cpu_port, multi_cpu_port_idx++);810}811812for_each_link_codecs(dai_link, i, dlc) {813dlc->ext_fmt = graph_parse_bitframe(ep_codec);814815if (multi_codec_port)816ep_codec = graph_get_next_multi_ep(&multi_codec_port, multi_codec_port_idx++);817}818819/*** Don't use port_cpu / port_codec after here ***/820821dai_link->playback_only = playback_only;822dai_link->capture_only = capture_only;823824dai_link->trigger_start = trigger_start;825dai_link->trigger_stop = trigger_stop;826827dai_link->dai_fmt = daifmt;828dai_link->init = simple_util_dai_init;829dai_link->ops = &graph_ops;830if (priv->ops)831dai_link->ops = priv->ops;832833of_node_put(port_cpu);834of_node_put(port_codec);835of_node_put(ep_cpu);836of_node_put(ep_codec);837}838839int audio_graph2_link_normal(struct simple_util_priv *priv,840struct device_node *lnk,841struct link_info *li)842{843struct device_node *cpu_port = lnk;844struct device_node *cpu_ep __free(device_node) = of_graph_get_next_port_endpoint(cpu_port, NULL);845struct device_node *codec_ep __free(device_node) = of_graph_get_remote_endpoint(cpu_ep);846int ret;847848/*849* call Codec first.850* see851* __graph_parse_node() :: DAI Naming852*/853ret = graph_parse_node(priv, GRAPH_NORMAL, codec_ep, li, 0);854if (ret < 0)855goto end;856857/*858* call CPU, and set DAI Name859*/860ret = graph_parse_node(priv, GRAPH_NORMAL, cpu_ep, li, 1);861if (ret < 0)862goto end;863864graph_link_init(priv, lnk, cpu_ep, codec_ep, li, 1);865866end:867return graph_ret(priv, ret);868}869EXPORT_SYMBOL_GPL(audio_graph2_link_normal);870871int audio_graph2_link_dpcm(struct simple_util_priv *priv,872struct device_node *lnk,873struct link_info *li)874{875struct device_node *ep __free(device_node) = of_graph_get_next_port_endpoint(lnk, NULL);876struct device_node *rep __free(device_node) = of_graph_get_remote_endpoint(ep);877struct device_node *cpu_ep = NULL;878struct device_node *codec_ep = NULL;879struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link);880struct simple_dai_props *dai_props = simple_priv_to_props(priv, li->link);881int is_cpu = graph_util_is_ports0(lnk);882int ret;883884if (is_cpu) {885cpu_ep = rep;886887/*888* dpcm {889* // Front-End890* ports@0 {891* => lnk: port@0 { ep: { ... = rep }; };892* ...893* };894* // Back-End895* ports@0 {896* ...897* };898* };899*900* CPU {901* rports: ports {902* rport: port@0 { rep: { ... = ep } };903* }904* }905*/906/*907* setup CPU here, Codec is already set as dummy.908* see909* simple_util_init_priv()910*/911dai_link->dynamic = 1;912dai_link->dpcm_merged_format = 1;913914ret = graph_parse_node(priv, GRAPH_DPCM, cpu_ep, li, 1);915if (ret)916return ret;917918} else {919codec_ep = rep;920921/*922* dpcm {923* // Front-End924* ports@0 {925* ...926* };927* // Back-End928* ports@0 {929* => lnk: port@0 { ep: { ... = rep; }; };930* ...931* };932* };933*934* Codec {935* rports: ports {936* rport: port@0 { rep: { ... = ep; }; };937* }938* }939*/940/*941* setup Codec here, CPU is already set as dummy.942* see943* simple_util_init_priv()944*/945946/* BE settings */947dai_link->no_pcm = 1;948dai_link->be_hw_params_fixup = simple_util_be_hw_params_fixup;949950ret = graph_parse_node(priv, GRAPH_DPCM, codec_ep, li, 0);951if (ret < 0)952return ret;953}954955graph_parse_convert(ep, dai_props); /* at node of <dpcm> */956graph_parse_convert(rep, dai_props); /* at node of <CPU/Codec> */957958graph_link_init(priv, lnk, cpu_ep, codec_ep, li, is_cpu);959960return graph_ret(priv, ret);961}962EXPORT_SYMBOL_GPL(audio_graph2_link_dpcm);963964int audio_graph2_link_c2c(struct simple_util_priv *priv,965struct device_node *lnk,966struct link_info *li)967{968struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link);969struct device_node *port0 = lnk;970struct device_node *ports __free(device_node) = port_to_ports(port0);971struct device_node *port1 __free(device_node) = of_graph_get_next_port(ports, port0);972u32 val = 0;973int ret = -EINVAL;974975/*976* codec2codec {977* ports {978* rate = <48000>;979* => lnk: port@0 { c2c0_ep: { ... = codec0_ep; }; };980* port@1 { c2c1_ep: { ... = codec1_ep; }; };981* };982* };983*984* Codec {985* ports {986* port@0 { codec0_ep: ... }; };987* port@1 { codec1_ep: ... }; };988* };989* };990*/991992/*993* Card2 can use original Codec2Codec settings if DT has.994* It will use default settings if no settings on DT.995* see996* simple_util_init_for_codec2codec()997*998* Add more settings here if needed999*/1000of_property_read_u32(ports, "rate", &val);1001if (val) {1002struct device *dev = simple_priv_to_dev(priv);1003struct snd_soc_pcm_stream *c2c_conf;10041005c2c_conf = devm_kzalloc(dev, sizeof(*c2c_conf), GFP_KERNEL);1006if (!c2c_conf) {1007/*1008* Clang doesn't allow to use "goto end" before calling __free(),1009* because it bypasses the initialization. Use graph_ret() directly.1010*/1011return graph_ret(priv, -ENOMEM);1012}10131014c2c_conf->formats = SNDRV_PCM_FMTBIT_S32_LE; /* update ME */1015c2c_conf->rates = SNDRV_PCM_RATE_8000_384000;1016c2c_conf->rate_min =1017c2c_conf->rate_max = val;1018c2c_conf->channels_min =1019c2c_conf->channels_max = 2; /* update ME */10201021dai_link->c2c_params = c2c_conf;1022dai_link->num_c2c_params = 1;1023}10241025struct device_node *ep0 __free(device_node) = of_graph_get_next_port_endpoint(port0, NULL);1026struct device_node *ep1 __free(device_node) = of_graph_get_next_port_endpoint(port1, NULL);10271028struct device_node *codec0_ep __free(device_node) = of_graph_get_remote_endpoint(ep0);1029struct device_node *codec1_ep __free(device_node) = of_graph_get_remote_endpoint(ep1);10301031/*1032* call Codec first.1033* see1034* __graph_parse_node() :: DAI Naming1035*/1036ret = graph_parse_node(priv, GRAPH_C2C, codec1_ep, li, 0);1037if (ret < 0)1038goto end;10391040/*1041* call CPU, and set DAI Name1042*/1043ret = graph_parse_node(priv, GRAPH_C2C, codec0_ep, li, 1);1044if (ret < 0)1045goto end;10461047graph_link_init(priv, lnk, codec0_ep, codec1_ep, li, 1);1048end:1049return graph_ret(priv, ret);1050}1051EXPORT_SYMBOL_GPL(audio_graph2_link_c2c);10521053static int graph_link(struct simple_util_priv *priv,1054struct graph2_custom_hooks *hooks,1055enum graph_type gtype,1056struct device_node *lnk,1057struct link_info *li)1058{1059struct device *dev = simple_priv_to_dev(priv);1060GRAPH2_CUSTOM func = NULL;1061int ret = -EINVAL;10621063switch (gtype) {1064case GRAPH_NORMAL:1065if (hooks && hooks->custom_normal)1066func = hooks->custom_normal;1067else1068func = audio_graph2_link_normal;1069break;1070case GRAPH_DPCM:1071if (hooks && hooks->custom_dpcm)1072func = hooks->custom_dpcm;1073else1074func = audio_graph2_link_dpcm;1075break;1076case GRAPH_C2C:1077if (hooks && hooks->custom_c2c)1078func = hooks->custom_c2c;1079else1080func = audio_graph2_link_c2c;1081break;1082default:1083break;1084}10851086if (!func) {1087dev_err(dev, "non supported gtype (%d)\n", gtype);1088goto err;1089}10901091ret = func(priv, lnk, li);1092if (ret < 0)1093goto err;10941095li->link++;1096err:1097return graph_ret(priv, ret);1098}10991100static int graph_counter(struct device_node *lnk)1101{1102/*1103* Multi CPU / Codec1104*1105* multi {1106* ports {1107* => lnk: port@0 { ... }; // to pair1108* port@1 { ... }; // Multi Element1109* port@2 { ... }; // Multi Element1110* ...1111* };1112* };1113*1114* ignore first lnk part1115*/1116if (graph_lnk_is_multi(lnk)) {1117struct device_node *ports = port_to_ports(lnk);11181119/*1120* CPU/Codec = N:M case has many endpoints.1121* We can't use of_graph_get_endpoint_count() here1122*/1123return of_graph_get_port_count(ports) - 1;1124}1125/*1126* Single CPU / Codec1127*/1128else1129return 1;1130}11311132static int graph_count_normal(struct simple_util_priv *priv,1133struct device_node *lnk,1134struct link_info *li)1135{1136struct device_node *cpu_port = lnk;1137struct device_node *cpu_ep __free(device_node) = of_graph_get_next_port_endpoint(cpu_port, NULL);1138struct device_node *codec_port __free(device_node) = of_graph_get_remote_port(cpu_ep);11391140/*1141* CPU {1142* => lnk: port { endpoint { .. }; };1143* };1144*/1145/*1146* DON'T REMOVE platforms1147* see1148* simple-card.c :: simple_count_noml()1149*/1150li->num[li->link].cpus =1151li->num[li->link].platforms = graph_counter(cpu_port);11521153li->num[li->link].codecs = graph_counter(codec_port);11541155return 0;1156}11571158static int graph_count_dpcm(struct simple_util_priv *priv,1159struct device_node *lnk,1160struct link_info *li)1161{1162struct device_node *ep __free(device_node) = of_graph_get_next_port_endpoint(lnk, NULL);1163struct device_node *rport __free(device_node) = of_graph_get_remote_port(ep);11641165/*1166* dpcm {1167* // Front-End1168* ports@0 {1169* => lnk: port@0 { endpoint { ... }; };1170* ...1171* };1172* // Back-End1173* ports@1 {1174* => lnk: port@0 { endpoint { ... }; };1175* ...1176* };1177* };1178*/11791180if (graph_util_is_ports0(lnk)) {1181/*1182* DON'T REMOVE platforms1183* see1184* simple-card.c :: simple_count_noml()1185*/1186li->num[li->link].cpus = graph_counter(rport); /* FE */1187li->num[li->link].platforms = graph_counter(rport);1188} else {1189li->num[li->link].codecs = graph_counter(rport); /* BE */1190}11911192return 0;1193}11941195static int graph_count_c2c(struct simple_util_priv *priv,1196struct device_node *lnk,1197struct link_info *li)1198{1199struct device_node *ports __free(device_node) = port_to_ports(lnk);1200struct device_node *port0 = of_node_get(lnk);1201struct device_node *port1 = of_node_get(of_graph_get_next_port(ports, of_node_get(port0)));1202struct device_node *ep0 __free(device_node) = of_graph_get_next_port_endpoint(port0, NULL);1203struct device_node *ep1 __free(device_node) = of_graph_get_next_port_endpoint(port1, NULL);1204struct device_node *codec0 __free(device_node) = of_graph_get_remote_port(ep0);1205struct device_node *codec1 __free(device_node) = of_graph_get_remote_port(ep1);12061207/*1208* codec2codec {1209* ports {1210* => lnk: port@0 { endpoint { ... }; };1211* port@1 { endpoint { ... }; };1212* };1213* };1214*/1215/*1216* DON'T REMOVE platforms1217* see1218* simple-card.c :: simple_count_noml()1219*/1220li->num[li->link].cpus =1221li->num[li->link].platforms = graph_counter(codec0);12221223li->num[li->link].codecs = graph_counter(codec1);12241225return 0;1226}12271228static int graph_count(struct simple_util_priv *priv,1229struct graph2_custom_hooks *hooks,1230enum graph_type gtype,1231struct device_node *lnk,1232struct link_info *li)1233{1234struct device *dev = simple_priv_to_dev(priv);1235GRAPH2_CUSTOM func = NULL;1236int ret = -EINVAL;12371238if (li->link >= SNDRV_MAX_LINKS) {1239dev_err(dev, "too many links\n");1240return ret;1241}12421243switch (gtype) {1244case GRAPH_NORMAL:1245func = graph_count_normal;1246break;1247case GRAPH_DPCM:1248func = graph_count_dpcm;1249break;1250case GRAPH_C2C:1251func = graph_count_c2c;1252break;1253default:1254break;1255}12561257if (!func) {1258dev_err(dev, "non supported gtype (%d)\n", gtype);1259goto err;1260}12611262ret = func(priv, lnk, li);1263if (ret < 0)1264goto err;12651266li->link++;1267err:1268return graph_ret(priv, ret);1269}12701271static int graph_for_each_link(struct simple_util_priv *priv,1272struct graph2_custom_hooks *hooks,1273struct link_info *li,1274int (*func)(struct simple_util_priv *priv,1275struct graph2_custom_hooks *hooks,1276enum graph_type gtype,1277struct device_node *lnk,1278struct link_info *li))1279{1280struct of_phandle_iterator it;1281struct device *dev = simple_priv_to_dev(priv);1282struct device_node *node = dev->of_node;1283struct device_node *lnk;1284enum graph_type gtype;1285int rc, ret = 0;12861287/* loop for all listed CPU port */1288of_for_each_phandle(&it, rc, node, "links", NULL, 0) {1289lnk = it.node;12901291gtype = graph_get_type(priv, lnk);12921293ret = func(priv, hooks, gtype, lnk, li);1294if (ret < 0)1295break;1296}12971298return graph_ret(priv, ret);1299}13001301int audio_graph2_parse_of(struct simple_util_priv *priv, struct device *dev,1302struct graph2_custom_hooks *hooks)1303{1304struct snd_soc_card *card = simple_priv_to_card(priv);1305int ret;13061307struct link_info *li __free(kfree) = kzalloc(sizeof(*li), GFP_KERNEL);1308if (!li)1309return -ENOMEM;13101311card->probe = graph_util_card_probe;1312card->owner = THIS_MODULE;1313card->dev = dev;13141315if ((hooks) && (hooks)->hook_pre) {1316ret = (hooks)->hook_pre(priv);1317if (ret < 0)1318goto err;1319}13201321ret = graph_for_each_link(priv, hooks, li, graph_count);1322if (!li->link)1323ret = -EINVAL;1324if (ret < 0)1325goto err;13261327ret = simple_util_init_priv(priv, li);1328if (ret < 0)1329goto err;13301331priv->pa_gpio = devm_gpiod_get_optional(dev, "pa", GPIOD_OUT_LOW);1332if (IS_ERR(priv->pa_gpio)) {1333ret = PTR_ERR(priv->pa_gpio);1334dev_err(dev, "failed to get amplifier gpio: %d\n", ret);1335goto err;1336}13371338ret = simple_util_parse_widgets(card, NULL);1339if (ret < 0)1340goto err;13411342ret = simple_util_parse_routing(card, NULL);1343if (ret < 0)1344goto err;13451346memset(li, 0, sizeof(*li));1347ret = graph_for_each_link(priv, hooks, li, graph_link);1348if (ret < 0)1349goto err;13501351ret = simple_util_parse_card_name(priv, NULL);1352if (ret < 0)1353goto err;13541355snd_soc_card_set_drvdata(card, priv);13561357if ((hooks) && (hooks)->hook_post) {1358ret = (hooks)->hook_post(priv);1359if (ret < 0)1360goto err;1361}13621363simple_util_debug_info(priv);13641365ret = snd_soc_of_parse_aux_devs(card, "aux-devs");1366if (ret < 0)1367goto err;13681369ret = devm_snd_soc_register_card(dev, card);1370err:1371if (ret < 0)1372dev_err_probe(dev, ret, "parse error\n");13731374return graph_ret(priv, ret);1375}1376EXPORT_SYMBOL_GPL(audio_graph2_parse_of);13771378static int graph_probe(struct platform_device *pdev)1379{1380struct simple_util_priv *priv;1381struct device *dev = &pdev->dev;13821383/* Allocate the private data and the DAI link array */1384priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);1385if (!priv)1386return -ENOMEM;13871388return audio_graph2_parse_of(priv, dev, NULL);1389}13901391static const struct of_device_id graph_of_match[] = {1392{ .compatible = "audio-graph-card2", },1393{},1394};1395MODULE_DEVICE_TABLE(of, graph_of_match);13961397static struct platform_driver graph_card = {1398.driver = {1399.name = "asoc-audio-graph-card2",1400.pm = &snd_soc_pm_ops,1401.of_match_table = graph_of_match,1402},1403.probe = graph_probe,1404.remove = simple_util_remove,1405};1406module_platform_driver(graph_card);14071408MODULE_ALIAS("platform:asoc-audio-graph-card2");1409MODULE_LICENSE("GPL v2");1410MODULE_DESCRIPTION("ASoC Audio Graph Card2");1411MODULE_AUTHOR("Kuninori Morimoto <[email protected]>");141214131414