···11+Allwinner Sony/Philips Digital Interface Format (S/PDIF) Controller22+33+The Allwinner S/PDIF audio block is a transceiver that allows the44+processor to receive and transmit digital audio via an coaxial cable or55+a fibre cable.66+For now only playback is supported.77+88+Required properties:99+1010+ - compatible : should be one of the following:1111+ - "allwinner,sun4i-a10-spdif": for the Allwinner A10 SoC1212+1313+ - reg : Offset and length of the register set for the device.1414+1515+ - interrupts : Contains the spdif interrupt.1616+1717+ - dmas : Generic dma devicetree binding as described in1818+ Documentation/devicetree/bindings/dma/dma.txt.1919+2020+ - dma-names : Two dmas have to be defined, "tx" and "rx".2121+2222+ - clocks : Contains an entry for each entry in clock-names.2323+2424+ - clock-names : Includes the following entries:2525+ "apb" clock for the spdif bus.2626+ "spdif" clock for spdif controller.2727+2828+Example:2929+3030+spdif: spdif@01c21000 {3131+ compatible = "allwinner,sun4i-a10-spdif";3232+ reg = <0x01c21000 0x40>;3333+ interrupts = <13>;3434+ clocks = <&apb0_gates 1>, <&spdif_clk>;3535+ clock-names = "apb", "spdif";3636+ dmas = <&dma 0 2>, <&dma 0 2>;3737+ dma-names = "rx", "tx";3838+ status = "okay";3939+};
+10-11
include/sound/soc-topology.h
···5656 unsigned int kcontrol_enum:1; /* this widget is an enum kcontrol */5757};58585959-/* dynamic PCM DAI object */6060-struct snd_soc_dobj_pcm_dai {6161- struct snd_soc_tplg_pcm_dai *pd;6262- unsigned int count;6363-};6464-6559/* generic dynamic object - all dynamic objects belong to this struct */6660struct snd_soc_dobj {6761 enum snd_soc_dobj_type type;···6571 union {6672 struct snd_soc_dobj_control control;6773 struct snd_soc_dobj_widget widget;6868- struct snd_soc_dobj_pcm_dai pcm_dai;6974 };7075 void *private; /* core does not touch this */7176};···119126 int (*widget_unload)(struct snd_soc_component *,120127 struct snd_soc_dobj *);121128122122- /* FE - used for any driver specific init */123123- int (*pcm_dai_load)(struct snd_soc_component *,124124- struct snd_soc_tplg_pcm_dai *pcm_dai, int num_fe);125125- int (*pcm_dai_unload)(struct snd_soc_component *,129129+ /* FE DAI - used for any driver specific init */130130+ int (*dai_load)(struct snd_soc_component *,131131+ struct snd_soc_dai_driver *dai_drv);132132+ int (*dai_unload)(struct snd_soc_component *,133133+ struct snd_soc_dobj *);134134+135135+ /* DAI link - used for any driver specific init */136136+ int (*link_load)(struct snd_soc_component *,137137+ struct snd_soc_dai_link *link);138138+ int (*link_unload)(struct snd_soc_component *,126139 struct snd_soc_dobj *);127140128141 /* callback to handle vendor bespoke data */
···28282929#include "wm8974.h"30303131+struct wm8974_priv {3232+ unsigned int mclk;3333+ unsigned int fs;3434+};3535+3136static const struct reg_default wm8974_reg_defaults[] = {3237 { 0, 0x0000 }, { 1, 0x0000 }, { 2, 0x0000 }, { 3, 0x0000 },3338 { 4, 0x0050 }, { 5, 0x0000 }, { 6, 0x0140 }, { 7, 0x0000 },···384379 return 0;385380}386381382382+static unsigned int wm8974_get_mclkdiv(unsigned int f_in, unsigned int f_out,383383+ int *mclkdiv)384384+{385385+ unsigned int ratio = 2 * f_in / f_out;386386+387387+ if (ratio <= 2) {388388+ *mclkdiv = WM8974_MCLKDIV_1;389389+ ratio = 2;390390+ } else if (ratio == 3) {391391+ *mclkdiv = WM8974_MCLKDIV_1_5;392392+ } else if (ratio == 4) {393393+ *mclkdiv = WM8974_MCLKDIV_2;394394+ } else if (ratio <= 6) {395395+ *mclkdiv = WM8974_MCLKDIV_3;396396+ ratio = 6;397397+ } else if (ratio <= 8) {398398+ *mclkdiv = WM8974_MCLKDIV_4;399399+ ratio = 8;400400+ } else if (ratio <= 12) {401401+ *mclkdiv = WM8974_MCLKDIV_6;402402+ ratio = 12;403403+ } else if (ratio <= 16) {404404+ *mclkdiv = WM8974_MCLKDIV_8;405405+ ratio = 16;406406+ } else {407407+ *mclkdiv = WM8974_MCLKDIV_12;408408+ ratio = 24;409409+ }410410+411411+ return f_out * ratio / 2;412412+}413413+414414+static int wm8974_update_clocks(struct snd_soc_dai *dai)415415+{416416+ struct snd_soc_codec *codec = dai->codec;417417+ struct wm8974_priv *priv = snd_soc_codec_get_drvdata(codec);418418+ unsigned int fs256;419419+ unsigned int fpll = 0;420420+ unsigned int f;421421+ int mclkdiv;422422+423423+ if (!priv->mclk || !priv->fs)424424+ return 0;425425+426426+ fs256 = 256 * priv->fs;427427+428428+ f = wm8974_get_mclkdiv(priv->mclk, fs256, &mclkdiv);429429+430430+ if (f != priv->mclk) {431431+ /* The PLL performs best around 90MHz */432432+ fpll = wm8974_get_mclkdiv(22500000, fs256, &mclkdiv);433433+ }434434+435435+ wm8974_set_dai_pll(dai, 0, 0, priv->mclk, fpll);436436+ wm8974_set_dai_clkdiv(dai, WM8974_MCLKDIV, mclkdiv);437437+438438+ return 0;439439+}440440+441441+static int wm8974_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id,442442+ unsigned int freq, int dir)443443+{444444+ struct snd_soc_codec *codec = dai->codec;445445+ struct wm8974_priv *priv = snd_soc_codec_get_drvdata(codec);446446+447447+ if (dir != SND_SOC_CLOCK_IN)448448+ return -EINVAL;449449+450450+ priv->mclk = freq;451451+452452+ return wm8974_update_clocks(dai);453453+}454454+387455static int wm8974_set_dai_fmt(struct snd_soc_dai *codec_dai,388456 unsigned int fmt)389457{···519441 struct snd_soc_dai *dai)520442{521443 struct snd_soc_codec *codec = dai->codec;444444+ struct wm8974_priv *priv = snd_soc_codec_get_drvdata(codec);522445 u16 iface = snd_soc_read(codec, WM8974_IFACE) & 0x19f;523446 u16 adn = snd_soc_read(codec, WM8974_ADD) & 0x1f1;447447+ int err;448448+449449+ priv->fs = params_rate(params);450450+ err = wm8974_update_clocks(dai);451451+ if (err)452452+ return err;524453525454 /* bit size */526455 switch (params_width(params)) {···632547 .set_fmt = wm8974_set_dai_fmt,633548 .set_clkdiv = wm8974_set_dai_clkdiv,634549 .set_pll = wm8974_set_dai_pll,550550+ .set_sysclk = wm8974_set_dai_sysclk,635551};636552637553static struct snd_soc_dai_driver wm8974_dai = {···692606static int wm8974_i2c_probe(struct i2c_client *i2c,693607 const struct i2c_device_id *id)694608{609609+ struct wm8974_priv *priv;695610 struct regmap *regmap;696611 int ret;612612+613613+ priv = devm_kzalloc(&i2c->dev, sizeof(*priv), GFP_KERNEL);614614+ if (!priv)615615+ return -ENOMEM;616616+617617+ i2c_set_clientdata(i2c, priv);697618698619 regmap = devm_regmap_init_i2c(i2c, &wm8974_regmap);699620 if (IS_ERR(regmap))
+154-86
sound/soc/soc-topology.c
···223223 return -EINVAL;224224}225225226226-static enum snd_soc_dobj_type get_dobj_mixer_type(227227- struct snd_soc_tplg_ctl_hdr *control_hdr)228228-{229229- if (control_hdr == NULL)230230- return SND_SOC_DOBJ_NONE;231231-232232- switch (control_hdr->ops.info) {233233- case SND_SOC_TPLG_CTL_VOLSW:234234- case SND_SOC_TPLG_CTL_VOLSW_SX:235235- case SND_SOC_TPLG_CTL_VOLSW_XR_SX:236236- case SND_SOC_TPLG_CTL_RANGE:237237- case SND_SOC_TPLG_CTL_STROBE:238238- return SND_SOC_DOBJ_MIXER;239239- case SND_SOC_TPLG_CTL_ENUM:240240- case SND_SOC_TPLG_CTL_ENUM_VALUE:241241- return SND_SOC_DOBJ_ENUM;242242- case SND_SOC_TPLG_CTL_BYTES:243243- return SND_SOC_DOBJ_BYTES;244244- default:245245- return SND_SOC_DOBJ_NONE;246246- }247247-}248248-249249-static enum snd_soc_dobj_type get_dobj_type(struct snd_soc_tplg_hdr *hdr,250250- struct snd_soc_tplg_ctl_hdr *control_hdr)251251-{252252- switch (hdr->type) {253253- case SND_SOC_TPLG_TYPE_MIXER:254254- return get_dobj_mixer_type(control_hdr);255255- case SND_SOC_TPLG_TYPE_DAPM_GRAPH:256256- case SND_SOC_TPLG_TYPE_MANIFEST:257257- return SND_SOC_DOBJ_NONE;258258- case SND_SOC_TPLG_TYPE_DAPM_WIDGET:259259- return SND_SOC_DOBJ_WIDGET;260260- case SND_SOC_TPLG_TYPE_DAI_LINK:261261- return SND_SOC_DOBJ_DAI_LINK;262262- case SND_SOC_TPLG_TYPE_PCM:263263- return SND_SOC_DOBJ_PCM;264264- case SND_SOC_TPLG_TYPE_CODEC_LINK:265265- return SND_SOC_DOBJ_CODEC_LINK;266266- default:267267- return SND_SOC_DOBJ_NONE;268268- }269269-}270270-271226static inline void soc_bind_err(struct soc_tplg *tplg,272227 struct snd_soc_tplg_ctl_hdr *hdr, int index)273228{···285330 return 0;286331}287332288288-/* pass dynamic FEs configurations to component driver */289289-static int soc_tplg_pcm_dai_load(struct soc_tplg *tplg,290290- struct snd_soc_tplg_pcm_dai *pcm_dai, int num_pcm_dai)333333+/* pass DAI configurations to component driver for extra intialization */334334+static int soc_tplg_dai_load(struct soc_tplg *tplg,335335+ struct snd_soc_dai_driver *dai_drv)291336{292292- if (tplg->comp && tplg->ops && tplg->ops->pcm_dai_load)293293- return tplg->ops->pcm_dai_load(tplg->comp, pcm_dai, num_pcm_dai);337337+ if (tplg->comp && tplg->ops && tplg->ops->dai_load)338338+ return tplg->ops->dai_load(tplg->comp, dai_drv);339339+340340+ return 0;341341+}342342+343343+/* pass link configurations to component driver for extra intialization */344344+static int soc_tplg_dai_link_load(struct soc_tplg *tplg,345345+ struct snd_soc_dai_link *link)346346+{347347+ if (tplg->comp && tplg->ops && tplg->ops->link_load)348348+ return tplg->ops->link_load(tplg->comp, link);294349295350 return 0;296351}···460495 /* widget w is freed by soc-dapm.c */461496}462497463463-/* remove PCM DAI configurations */464464-static void remove_pcm_dai(struct snd_soc_component *comp,498498+/* remove DAI configurations */499499+static void remove_dai(struct snd_soc_component *comp,465500 struct snd_soc_dobj *dobj, int pass)466501{502502+ struct snd_soc_dai_driver *dai_drv =503503+ container_of(dobj, struct snd_soc_dai_driver, dobj);504504+467505 if (pass != SOC_TPLG_PASS_PCM_DAI)468506 return;469507470470- if (dobj->ops && dobj->ops->pcm_dai_unload)471471- dobj->ops->pcm_dai_unload(comp, dobj);508508+ if (dobj->ops && dobj->ops->dai_unload)509509+ dobj->ops->dai_unload(comp, dobj);472510473511 list_del(&dobj->list);474474- kfree(dobj);512512+ kfree(dai_drv);513513+}514514+515515+/* remove link configurations */516516+static void remove_link(struct snd_soc_component *comp,517517+ struct snd_soc_dobj *dobj, int pass)518518+{519519+ struct snd_soc_dai_link *link =520520+ container_of(dobj, struct snd_soc_dai_link, dobj);521521+522522+ if (pass != SOC_TPLG_PASS_PCM_DAI)523523+ return;524524+525525+ if (dobj->ops && dobj->ops->link_unload)526526+ dobj->ops->link_unload(comp, dobj);527527+528528+ list_del(&dobj->list);529529+ snd_soc_remove_dai_link(comp->card, link);530530+ kfree(link);475531}476532477533/* bind a kcontrol to it's IO handlers */···15301544 return 0;15311545}1532154615331533-static int soc_tplg_pcm_dai_elems_load(struct soc_tplg *tplg,15471547+static void set_stream_info(struct snd_soc_pcm_stream *stream,15481548+ struct snd_soc_tplg_stream_caps *caps)15491549+{15501550+ stream->stream_name = kstrdup(caps->name, GFP_KERNEL);15511551+ stream->channels_min = caps->channels_min;15521552+ stream->channels_max = caps->channels_max;15531553+ stream->rates = caps->rates;15541554+ stream->rate_min = caps->rate_min;15551555+ stream->rate_max = caps->rate_max;15561556+ stream->formats = caps->formats;15571557+}15581558+15591559+static int soc_tplg_dai_create(struct soc_tplg *tplg,15601560+ struct snd_soc_tplg_pcm *pcm)15611561+{15621562+ struct snd_soc_dai_driver *dai_drv;15631563+ struct snd_soc_pcm_stream *stream;15641564+ struct snd_soc_tplg_stream_caps *caps;15651565+ int ret;15661566+15671567+ dai_drv = kzalloc(sizeof(struct snd_soc_dai_driver), GFP_KERNEL);15681568+ if (dai_drv == NULL)15691569+ return -ENOMEM;15701570+15711571+ dai_drv->name = pcm->dai_name;15721572+ dai_drv->id = pcm->dai_id;15731573+15741574+ if (pcm->playback) {15751575+ stream = &dai_drv->playback;15761576+ caps = &pcm->caps[SND_SOC_TPLG_STREAM_PLAYBACK];15771577+ set_stream_info(stream, caps);15781578+ }15791579+15801580+ if (pcm->capture) {15811581+ stream = &dai_drv->capture;15821582+ caps = &pcm->caps[SND_SOC_TPLG_STREAM_CAPTURE];15831583+ set_stream_info(stream, caps);15841584+ }15851585+15861586+ /* pass control to component driver for optional further init */15871587+ ret = soc_tplg_dai_load(tplg, dai_drv);15881588+ if (ret < 0) {15891589+ dev_err(tplg->comp->dev, "ASoC: DAI loading failed\n");15901590+ kfree(dai_drv);15911591+ return ret;15921592+ }15931593+15941594+ dai_drv->dobj.index = tplg->index;15951595+ dai_drv->dobj.ops = tplg->ops;15961596+ dai_drv->dobj.type = SND_SOC_DOBJ_PCM;15971597+ list_add(&dai_drv->dobj.list, &tplg->comp->dobj_list);15981598+15991599+ /* register the DAI to the component */16001600+ return snd_soc_register_dai(tplg->comp, dai_drv);16011601+}16021602+16031603+static int soc_tplg_link_create(struct soc_tplg *tplg,16041604+ struct snd_soc_tplg_pcm *pcm)16051605+{16061606+ struct snd_soc_dai_link *link;16071607+ int ret;16081608+16091609+ link = kzalloc(sizeof(struct snd_soc_dai_link), GFP_KERNEL);16101610+ if (link == NULL)16111611+ return -ENOMEM;16121612+16131613+ link->name = pcm->pcm_name;16141614+ link->stream_name = pcm->pcm_name;16151615+16161616+ /* pass control to component driver for optional further init */16171617+ ret = soc_tplg_dai_link_load(tplg, link);16181618+ if (ret < 0) {16191619+ dev_err(tplg->comp->dev, "ASoC: FE link loading failed\n");16201620+ kfree(link);16211621+ return ret;16221622+ }16231623+16241624+ link->dobj.index = tplg->index;16251625+ link->dobj.ops = tplg->ops;16261626+ link->dobj.type = SND_SOC_DOBJ_DAI_LINK;16271627+ list_add(&link->dobj.list, &tplg->comp->dobj_list);16281628+16291629+ snd_soc_add_dai_link(tplg->comp->card, link);16301630+ return 0;16311631+}16321632+16331633+/* create a FE DAI and DAI link from the PCM object */16341634+static int soc_tplg_pcm_create(struct soc_tplg *tplg,16351635+ struct snd_soc_tplg_pcm *pcm)16361636+{16371637+ int ret;16381638+16391639+ ret = soc_tplg_dai_create(tplg, pcm);16401640+ if (ret < 0)16411641+ return ret;16421642+16431643+ return soc_tplg_link_create(tplg, pcm);16441644+}16451645+16461646+static int soc_tplg_pcm_elems_load(struct soc_tplg *tplg,15341647 struct snd_soc_tplg_hdr *hdr)15351648{15361536- struct snd_soc_tplg_pcm_dai *pcm_dai;15371537- struct snd_soc_dobj *dobj;16491649+ struct snd_soc_tplg_pcm *pcm;15381650 int count = hdr->count;15391539- int ret;16511651+ int i;1540165215411653 if (tplg->pass != SOC_TPLG_PASS_PCM_DAI)15421654 return 0;1543165515441544- pcm_dai = (struct snd_soc_tplg_pcm_dai *)tplg->pos;16561656+ pcm = (struct snd_soc_tplg_pcm *)tplg->pos;1545165715461658 if (soc_tplg_check_elem_count(tplg,15471659 sizeof(struct snd_soc_tplg_pcm), count,···16491565 return -EINVAL;16501566 }1651156715681568+ /* create the FE DAIs and DAI links */15691569+ for (i = 0; i < count; i++) {15701570+ soc_tplg_pcm_create(tplg, pcm);15711571+ pcm++;15721572+ }15731573+16521574 dev_dbg(tplg->dev, "ASoC: adding %d PCM DAIs\n", count);16531575 tplg->pos += sizeof(struct snd_soc_tplg_pcm) * count;1654157616551655- dobj = kzalloc(sizeof(struct snd_soc_dobj), GFP_KERNEL);16561656- if (dobj == NULL)16571657- return -ENOMEM;16581658-16591659- /* Call the platform driver call back to register the dais */16601660- ret = soc_tplg_pcm_dai_load(tplg, pcm_dai, count);16611661- if (ret < 0) {16621662- dev_err(tplg->comp->dev, "ASoC: PCM DAI loading failed\n");16631663- goto err;16641664- }16651665-16661666- dobj->type = get_dobj_type(hdr, NULL);16671667- dobj->pcm_dai.count = count;16681668- dobj->pcm_dai.pd = pcm_dai;16691669- dobj->ops = tplg->ops;16701670- dobj->index = tplg->index;16711671- list_add(&dobj->list, &tplg->comp->dobj_list);16721577 return 0;16731673-16741674-err:16751675- kfree(dobj);16761676- return ret;16771578}1678157916791580static int soc_tplg_manifest_load(struct soc_tplg *tplg,···17501681 case SND_SOC_TPLG_TYPE_DAPM_WIDGET:17511682 return soc_tplg_dapm_widget_elems_load(tplg, hdr);17521683 case SND_SOC_TPLG_TYPE_PCM:17531753- case SND_SOC_TPLG_TYPE_DAI_LINK:17541754- case SND_SOC_TPLG_TYPE_CODEC_LINK:17551755- return soc_tplg_pcm_dai_elems_load(tplg, hdr);16841684+ return soc_tplg_pcm_elems_load(tplg, hdr);17561685 case SND_SOC_TPLG_TYPE_MANIFEST:17571686 return soc_tplg_manifest_load(tplg, hdr);17581687 default:···19081841 remove_widget(comp, dobj, pass);19091842 break;19101843 case SND_SOC_DOBJ_PCM:18441844+ remove_dai(comp, dobj, pass);18451845+ break;19111846 case SND_SOC_DOBJ_DAI_LINK:19121912- case SND_SOC_DOBJ_CODEC_LINK:19131913- remove_pcm_dai(comp, dobj, pass);18471847+ remove_link(comp, dobj, pass);19141848 break;19151849 default:19161850 dev_err(comp->dev, "ASoC: invalid component type %d for removal\n",
+8
sound/soc/sunxi/Kconfig
···88 Select Y or M to add support for the Codec embedded in the Allwinner99 A10 and affiliated SoCs.10101111+config SND_SUN4I_SPDIF1212+ tristate "Allwinner A10 SPDIF Support"1313+ depends on OF1414+ select SND_SOC_GENERIC_DMAENGINE_PCM1515+ select REGMAP_MMIO1616+ help1717+ Say Y or M to add support for the S/PDIF audio block in the Allwinner1818+ A10 and affiliated SoCs.1119endmenu