123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314 |
- // SPDX-License-Identifier: GPL-2.0
- //
- // ASoC Audio Graph Card2 support
- //
- // Copyright (C) 2020 Renesas Electronics Corp.
- // Copyright (C) 2020 Kuninori Morimoto <[email protected]>
- //
- // based on ${LINUX}/sound/soc/generic/audio-graph-card.c
- #include <linux/clk.h>
- #include <linux/device.h>
- #include <linux/gpio.h>
- #include <linux/gpio/consumer.h>
- #include <linux/module.h>
- #include <linux/of.h>
- #include <linux/of_device.h>
- #include <linux/of_gpio.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().
- ************************************
- 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 connection part (= X) and list part (= y).
- links indicates connection part of CPU side (= A).
- +-+ (A) +-+
- CPU1 --(y) | | <-(X)--(X)-> | | (y)-- Codec1
- CPU2 --(y) | | | | (y)-- Codec2
- +-+ +-+
- sound {
- compatible = "audio-graph-card2";
- (A) links = <&mcpu>;
- multi {
- ports@0 {
- (X) (A) mcpu: port@0 { mcpu0_ep: endpoint { remote-endpoint = <&mcodec0_ep>; }; };
- (y) port@1 { mcpu1_ep: endpoint { remote-endpoint = <&cpu1_ep>; }; };
- (y) port@2 { mcpu2_ep: endpoint { remote-endpoint = <&cpu2_ep>; }; };
- };
- ports@1 {
- (X) port@0 { mcodec0_ep: endpoint { remote-endpoint = <&mcpu0_ep>; }; };
- (y) port@1 { mcodec1_ep: endpoint { remote-endpoint = <&codec1_ep>; }; };
- (y) port@2 { mcodec2_ep: endpoint { remote-endpoint = <&codec2_ep>; }; };
- };
- };
- };
- 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 port_to_endpoint(port) of_get_child_by_name(port, "endpoint")
- 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;
- goto out_put;
- }
- if (of_node_name_eq(np, GRAPH_NODENAME_DPCM)) {
- ret = GRAPH_DPCM;
- goto out_put;
- }
- if (of_node_name_eq(np, GRAPH_NODENAME_C2C)) {
- ret = GRAPH_C2C;
- goto out_put;
- }
- ret = GRAPH_NORMAL;
- out_put:
- of_node_put(np);
- return ret;
- }
- static enum graph_type graph_get_type(struct asoc_simple_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 (asoc_graph_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 = of_get_parent(*port);
- struct device_node *ep = NULL;
- struct device_node *rep = NULL;
- /*
- * multi {
- * ports {
- * => lnk: port@0 { ... };
- * port@1 { ep { ... = rep0 } };
- * port@2 { ep { ... = rep1 } };
- * ...
- * };
- * };
- *
- * xxx {
- * port@0 { rep0 };
- * port@1 { rep1 };
- * };
- */
- do {
- *port = of_get_next_child(ports, *port);
- if (!*port)
- break;
- } while (!of_node_name_eq(*port, "port"));
- if (*port) {
- ep = port_to_endpoint(*port);
- 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 = asoc_simple_startup,
- .shutdown = asoc_simple_shutdown,
- .hw_params = asoc_simple_hw_params,
- };
- static int graph_get_dai_id(struct device_node *ep)
- {
- struct device_node *node;
- struct device_node *endpoint;
- struct of_endpoint info;
- int i, id;
- const u32 *reg;
- int ret;
- /* use driver specified DAI ID if exist */
- ret = snd_soc_get_dai_id(ep);
- if (ret != -ENOTSUPP)
- return ret;
- /* use endpoint/port reg if exist */
- ret = of_graph_parse_endpoint(ep, &info);
- if (ret == 0) {
- /*
- * Because it will count port/endpoint if it doesn't have "reg".
- * But, we can't judge whether it has "no reg", or "reg = <0>"
- * only of_graph_parse_endpoint().
- * We need to check "reg" property
- */
- if (of_get_property(ep, "reg", NULL))
- return info.id;
- node = of_get_parent(ep);
- reg = of_get_property(node, "reg", NULL);
- of_node_put(node);
- if (reg)
- return info.port;
- }
- node = of_graph_get_port_parent(ep);
- /*
- * Non HDMI sound case, counting port/endpoint on its DT
- * is enough. Let's count it.
- */
- i = 0;
- id = -1;
- for_each_endpoint_of_node(node, endpoint) {
- if (endpoint == ep)
- id = i;
- i++;
- }
- of_node_put(node);
- if (id < 0)
- return -ENODEV;
- return id;
- }
- static int asoc_simple_parse_dai(struct device_node *ep,
- struct snd_soc_dai_link_component *dlc,
- int *is_single_link)
- {
- struct device_node *node;
- struct of_phandle_args args;
- int ret;
- if (!ep)
- return 0;
- node = of_graph_get_port_parent(ep);
- /* Get dai->name */
- args.np = node;
- args.args[0] = graph_get_dai_id(ep);
- args.args_count = (of_graph_get_endpoint_count(node) > 1);
- /*
- * FIXME
- *
- * Here, dlc->dai_name is pointer to CPU/Codec DAI name.
- * If user unbinded CPU or Codec driver, but not for Sound Card,
- * dlc->dai_name is keeping unbinded CPU or Codec
- * driver's pointer.
- *
- * If user re-bind CPU or Codec driver again, ALSA SoC will try
- * to rebind Card via snd_soc_try_rebind_card(), but because of
- * above reason, it might can't bind Sound Card.
- * Because Sound Card is pointing to released dai_name pointer.
- *
- * To avoid this rebind Card issue,
- * 1) It needs to alloc memory to keep dai_name eventhough
- * CPU or Codec driver was unbinded, or
- * 2) user need to rebind Sound Card everytime
- * if he unbinded CPU or Codec.
- */
- ret = snd_soc_get_dai_name(&args, &dlc->dai_name);
- if (ret < 0) {
- of_node_put(node);
- return ret;
- }
- dlc->of_node = node;
- if (is_single_link)
- *is_single_link = of_graph_get_endpoint_count(node) == 1;
- return 0;
- }
- static void graph_parse_convert(struct device_node *ep,
- struct simple_dai_props *props)
- {
- struct device_node *port = of_get_parent(ep);
- struct device_node *ports = of_get_parent(port);
- struct asoc_simple_data *adata = &props->adata;
- if (of_node_name_eq(ports, "ports"))
- asoc_simple_parse_convert(ports, NULL, adata);
- asoc_simple_parse_convert(port, NULL, adata);
- asoc_simple_parse_convert(ep, NULL, adata);
- of_node_put(port);
- of_node_put(ports);
- }
- static void graph_parse_mclk_fs(struct device_node *ep,
- struct simple_dai_props *props)
- {
- struct device_node *port = of_get_parent(ep);
- struct device_node *ports = of_get_parent(port);
- if (of_node_name_eq(ports, "ports"))
- of_property_read_u32(ports, "mclk-fs", &props->mclk_fs);
- of_property_read_u32(port, "mclk-fs", &props->mclk_fs);
- of_property_read_u32(ep, "mclk-fs", &props->mclk_fs);
- of_node_put(port);
- of_node_put(ports);
- }
- static int __graph_parse_node(struct asoc_simple_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 asoc_simple_dai *dai;
- int ret, is_single_links = 0;
- if (is_cpu) {
- dlc = asoc_link_to_cpu(dai_link, idx);
- dai = simple_props_to_dai_cpu(dai_props, idx);
- } else {
- dlc = asoc_link_to_codec(dai_link, idx);
- dai = simple_props_to_dai_codec(dai_props, idx);
- }
- graph_parse_mclk_fs(ep, dai_props);
- ret = asoc_simple_parse_dai(ep, dlc, &is_single_links);
- if (ret < 0)
- return ret;
- ret = asoc_simple_parse_tdm(ep, dai);
- if (ret < 0)
- return ret;
- ret = asoc_simple_parse_tdm_width_map(dev, ep, dai);
- if (ret < 0)
- return ret;
- ret = asoc_simple_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 = asoc_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)
- asoc_simple_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)
- asoc_simple_set_dailink_name(dev, dai_link, "fe.%pOFP.%s%s",
- cpus->of_node, cpus->dai_name, cpu_multi);
- else
- asoc_simple_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)
- asoc_simple_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 = asoc_link_to_codec(dai_link, idx);
- struct snd_soc_codec_conf *cconf = simple_props_to_codec_conf(dai_props, idx);
- struct device_node *rport = of_get_parent(ep);
- struct device_node *rports = of_get_parent(rport);
- if (of_node_name_eq(rports, "ports"))
- 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 = asoc_link_to_platform(dai_link, idx);
- asoc_simple_canonicalize_cpu(cpus, is_single_links);
- asoc_simple_canonicalize_platform(platforms, cpus);
- }
- return 0;
- }
- static int graph_parse_node(struct asoc_simple_priv *priv,
- enum graph_type gtype,
- struct device_node *port,
- struct link_info *li, int is_cpu)
- {
- struct device_node *ep;
- int ret = 0;
- if (graph_lnk_is_multi(port)) {
- int idx;
- of_node_get(port);
- for (idx = 0;; idx++) {
- 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)
- break;
- }
- } else {
- /* Single CPU / Codec */
- ep = port_to_endpoint(port);
- ret = __graph_parse_node(priv, gtype, ep, li, is_cpu, 0);
- of_node_put(ep);
- }
- return ret;
- }
- static void graph_parse_daifmt(struct device_node *node,
- unsigned int *daifmt, unsigned int *bit_frame)
- {
- unsigned int fmt;
- /*
- * 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 asoc_simple_priv *priv,
- struct device_node *port,
- struct link_info *li,
- int is_cpu_node)
- {
- struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link);
- struct device_node *ep;
- struct device_node *ports;
- unsigned int daifmt = 0, daiclk = 0;
- unsigned int bit_frame = 0;
- if (graph_lnk_is_multi(port)) {
- of_node_get(port);
- ep = graph_get_next_multi_ep(&port);
- port = of_get_parent(ep);
- } else {
- ep = port_to_endpoint(port);
- }
- ports = of_get_parent(port);
- /*
- * ports {
- * (A)
- * port {
- * (B)
- * endpoint {
- * (C)
- * };
- * };
- * };
- * };
- */
- graph_parse_daifmt(ep, &daifmt, &bit_frame); /* (C) */
- graph_parse_daifmt(port, &daifmt, &bit_frame); /* (B) */
- if (of_node_name_eq(ports, "ports"))
- graph_parse_daifmt(ports, &daifmt, &bit_frame); /* (A) */
- /*
- * 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->dai_fmt = daifmt | daiclk;
- dai_link->init = asoc_simple_dai_init;
- dai_link->ops = &graph_ops;
- if (priv->ops)
- dai_link->ops = priv->ops;
- }
- int audio_graph2_link_normal(struct asoc_simple_priv *priv,
- struct device_node *lnk,
- struct link_info *li)
- {
- struct device_node *cpu_port = lnk;
- struct device_node *cpu_ep = port_to_endpoint(cpu_port);
- 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, cpu_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 asoc_simple_priv *priv,
- struct device_node *lnk,
- struct link_info *li)
- {
- struct device_node *ep = port_to_endpoint(lnk);
- struct device_node *rep = of_graph_get_remote_endpoint(ep);
- struct device_node *rport = of_graph_get_remote_port(ep);
- 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 = asoc_graph_is_ports0(lnk);
- int ret;
- if (is_cpu) {
- /*
- * 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
- * asoc_simple_init_priv()
- */
- dai_link->dynamic = 1;
- dai_link->dpcm_merged_format = 1;
- ret = graph_parse_node(priv, GRAPH_DPCM, rport, li, 1);
- if (ret)
- goto err;
- } else {
- /*
- * 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
- * asoc_simple_init_priv()
- */
- /* BE settings */
- dai_link->no_pcm = 1;
- dai_link->be_hw_params_fixup = asoc_simple_be_hw_params_fixup;
- ret = graph_parse_node(priv, GRAPH_DPCM, rport, li, 0);
- if (ret < 0)
- goto err;
- }
- graph_parse_convert(rep, dai_props);
- snd_soc_dai_link_set_capabilities(dai_link);
- graph_link_init(priv, rport, li, is_cpu);
- err:
- of_node_put(ep);
- of_node_put(rep);
- of_node_put(rport);
- return ret;
- }
- EXPORT_SYMBOL_GPL(audio_graph2_link_dpcm);
- int audio_graph2_link_c2c(struct asoc_simple_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 = of_get_parent(port0);
- port1 = of_get_next_child(ports, lnk);
- /*
- * Card2 can use original Codec2Codec settings if DT has.
- * It will use default settings if no settings on DT.
- * see
- * asoc_simple_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->params = c2c_conf;
- dai_link->num_params = 1;
- }
- ep0 = port_to_endpoint(port0);
- ep1 = port_to_endpoint(port1);
- 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, codec0_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 asoc_simple_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 { ... };
- * port@1 { ... };
- * port@2 { ... };
- * ...
- * };
- * };
- *
- * ignore first lnk part
- */
- if (graph_lnk_is_multi(lnk))
- return of_graph_get_endpoint_count(of_get_parent(lnk)) - 1;
- /*
- * Single CPU / Codec
- */
- else
- return 1;
- }
- static int graph_count_normal(struct asoc_simple_priv *priv,
- struct device_node *lnk,
- struct link_info *li)
- {
- struct device_node *cpu_port = lnk;
- struct device_node *cpu_ep = port_to_endpoint(cpu_port);
- struct device_node *codec_port = of_graph_get_remote_port(cpu_ep);
- /*
- * CPU {
- * => lnk: port { endpoint { .. }; };
- * };
- */
- 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 asoc_simple_priv *priv,
- struct device_node *lnk,
- struct link_info *li)
- {
- struct device_node *ep = port_to_endpoint(lnk);
- 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 (asoc_graph_is_ports0(lnk)) {
- 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 asoc_simple_priv *priv,
- struct device_node *lnk,
- struct link_info *li)
- {
- struct device_node *ports = of_get_parent(lnk);
- struct device_node *port0 = lnk;
- struct device_node *port1 = of_get_next_child(ports, lnk);
- struct device_node *ep0 = port_to_endpoint(port0);
- struct device_node *ep1 = port_to_endpoint(port1);
- struct device_node *codec0 = of_graph_get_remote_port(ep0);
- struct device_node *codec1 = of_graph_get_remote_port(ep1);
- of_node_get(lnk);
- /*
- * codec2codec {
- * ports {
- * => lnk: port@0 { endpoint { ... }; };
- * port@1 { endpoint { ... }; };
- * };
- * };
- */
- 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 asoc_simple_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 asoc_simple_priv *priv,
- struct graph2_custom_hooks *hooks,
- struct link_info *li,
- int (*func)(struct asoc_simple_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 asoc_simple_priv *priv, struct device *dev,
- struct graph2_custom_hooks *hooks)
- {
- struct snd_soc_card *card = simple_priv_to_card(priv);
- struct link_info *li;
- int ret;
- li = devm_kzalloc(dev, sizeof(*li), GFP_KERNEL);
- if (!li)
- return -ENOMEM;
- card->probe = asoc_graph_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 = asoc_simple_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 = asoc_simple_parse_widgets(card, NULL);
- if (ret < 0)
- goto err;
- ret = asoc_simple_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 = asoc_simple_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;
- }
- asoc_simple_debug_info(priv);
- ret = devm_snd_soc_register_card(dev, card);
- err:
- devm_kfree(dev, li);
- if (ret < 0)
- dev_err_probe(dev, ret, "parse error\n");
- if (ret == 0)
- dev_warn(dev, "Audio Graph Card2 is still under Experimental stage\n");
- return ret;
- }
- EXPORT_SYMBOL_GPL(audio_graph2_parse_of);
- static int graph_probe(struct platform_device *pdev)
- {
- struct asoc_simple_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 = asoc_simple_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 <[email protected]>");
|