Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

Merge remote-tracking branches 'asoc/topic/sunxi', 'asoc/topic/topology' and 'asoc/topic/wm8974' into asoc-next

+856 -98
+39
Documentation/devicetree/bindings/sound/sunxi,sun4i-spdif.txt
··· 1 + Allwinner Sony/Philips Digital Interface Format (S/PDIF) Controller 2 + 3 + The Allwinner S/PDIF audio block is a transceiver that allows the 4 + processor to receive and transmit digital audio via an coaxial cable or 5 + a fibre cable. 6 + For now only playback is supported. 7 + 8 + Required properties: 9 + 10 + - compatible : should be one of the following: 11 + - "allwinner,sun4i-a10-spdif": for the Allwinner A10 SoC 12 + 13 + - reg : Offset and length of the register set for the device. 14 + 15 + - interrupts : Contains the spdif interrupt. 16 + 17 + - dmas : Generic dma devicetree binding as described in 18 + Documentation/devicetree/bindings/dma/dma.txt. 19 + 20 + - dma-names : Two dmas have to be defined, "tx" and "rx". 21 + 22 + - clocks : Contains an entry for each entry in clock-names. 23 + 24 + - clock-names : Includes the following entries: 25 + "apb" clock for the spdif bus. 26 + "spdif" clock for spdif controller. 27 + 28 + Example: 29 + 30 + spdif: spdif@01c21000 { 31 + compatible = "allwinner,sun4i-a10-spdif"; 32 + reg = <0x01c21000 0x40>; 33 + interrupts = <13>; 34 + clocks = <&apb0_gates 1>, <&spdif_clk>; 35 + clock-names = "apb", "spdif"; 36 + dmas = <&dma 0 2>, <&dma 0 2>; 37 + dma-names = "rx", "tx"; 38 + status = "okay"; 39 + };
+10 -11
include/sound/soc-topology.h
··· 56 56 unsigned int kcontrol_enum:1; /* this widget is an enum kcontrol */ 57 57 }; 58 58 59 - /* dynamic PCM DAI object */ 60 - struct snd_soc_dobj_pcm_dai { 61 - struct snd_soc_tplg_pcm_dai *pd; 62 - unsigned int count; 63 - }; 64 - 65 59 /* generic dynamic object - all dynamic objects belong to this struct */ 66 60 struct snd_soc_dobj { 67 61 enum snd_soc_dobj_type type; ··· 65 71 union { 66 72 struct snd_soc_dobj_control control; 67 73 struct snd_soc_dobj_widget widget; 68 - struct snd_soc_dobj_pcm_dai pcm_dai; 69 74 }; 70 75 void *private; /* core does not touch this */ 71 76 }; ··· 119 126 int (*widget_unload)(struct snd_soc_component *, 120 127 struct snd_soc_dobj *); 121 128 122 - /* FE - used for any driver specific init */ 123 - int (*pcm_dai_load)(struct snd_soc_component *, 124 - struct snd_soc_tplg_pcm_dai *pcm_dai, int num_fe); 125 - int (*pcm_dai_unload)(struct snd_soc_component *, 129 + /* FE DAI - used for any driver specific init */ 130 + int (*dai_load)(struct snd_soc_component *, 131 + struct snd_soc_dai_driver *dai_drv); 132 + int (*dai_unload)(struct snd_soc_component *, 133 + struct snd_soc_dobj *); 134 + 135 + /* DAI link - used for any driver specific init */ 136 + int (*link_load)(struct snd_soc_component *, 137 + struct snd_soc_dai_link *link); 138 + int (*link_unload)(struct snd_soc_component *, 126 139 struct snd_soc_dobj *); 127 140 128 141 /* callback to handle vendor bespoke data */
+1 -1
include/sound/soc.h
··· 27 27 #include <sound/compress_driver.h> 28 28 #include <sound/control.h> 29 29 #include <sound/ac97_codec.h> 30 - #include <sound/soc-topology.h> 31 30 32 31 /* 33 32 * Convenience kcontrol builders ··· 403 404 struct snd_soc_jack_pin; 404 405 #include <sound/soc-dapm.h> 405 406 #include <sound/soc-dpcm.h> 407 + #include <sound/soc-topology.h> 406 408 407 409 struct snd_soc_jack_gpio; 408 410
+93
sound/soc/codecs/wm8974.c
··· 28 28 29 29 #include "wm8974.h" 30 30 31 + struct wm8974_priv { 32 + unsigned int mclk; 33 + unsigned int fs; 34 + }; 35 + 31 36 static const struct reg_default wm8974_reg_defaults[] = { 32 37 { 0, 0x0000 }, { 1, 0x0000 }, { 2, 0x0000 }, { 3, 0x0000 }, 33 38 { 4, 0x0050 }, { 5, 0x0000 }, { 6, 0x0140 }, { 7, 0x0000 }, ··· 384 379 return 0; 385 380 } 386 381 382 + static unsigned int wm8974_get_mclkdiv(unsigned int f_in, unsigned int f_out, 383 + int *mclkdiv) 384 + { 385 + unsigned int ratio = 2 * f_in / f_out; 386 + 387 + if (ratio <= 2) { 388 + *mclkdiv = WM8974_MCLKDIV_1; 389 + ratio = 2; 390 + } else if (ratio == 3) { 391 + *mclkdiv = WM8974_MCLKDIV_1_5; 392 + } else if (ratio == 4) { 393 + *mclkdiv = WM8974_MCLKDIV_2; 394 + } else if (ratio <= 6) { 395 + *mclkdiv = WM8974_MCLKDIV_3; 396 + ratio = 6; 397 + } else if (ratio <= 8) { 398 + *mclkdiv = WM8974_MCLKDIV_4; 399 + ratio = 8; 400 + } else if (ratio <= 12) { 401 + *mclkdiv = WM8974_MCLKDIV_6; 402 + ratio = 12; 403 + } else if (ratio <= 16) { 404 + *mclkdiv = WM8974_MCLKDIV_8; 405 + ratio = 16; 406 + } else { 407 + *mclkdiv = WM8974_MCLKDIV_12; 408 + ratio = 24; 409 + } 410 + 411 + return f_out * ratio / 2; 412 + } 413 + 414 + static int wm8974_update_clocks(struct snd_soc_dai *dai) 415 + { 416 + struct snd_soc_codec *codec = dai->codec; 417 + struct wm8974_priv *priv = snd_soc_codec_get_drvdata(codec); 418 + unsigned int fs256; 419 + unsigned int fpll = 0; 420 + unsigned int f; 421 + int mclkdiv; 422 + 423 + if (!priv->mclk || !priv->fs) 424 + return 0; 425 + 426 + fs256 = 256 * priv->fs; 427 + 428 + f = wm8974_get_mclkdiv(priv->mclk, fs256, &mclkdiv); 429 + 430 + if (f != priv->mclk) { 431 + /* The PLL performs best around 90MHz */ 432 + fpll = wm8974_get_mclkdiv(22500000, fs256, &mclkdiv); 433 + } 434 + 435 + wm8974_set_dai_pll(dai, 0, 0, priv->mclk, fpll); 436 + wm8974_set_dai_clkdiv(dai, WM8974_MCLKDIV, mclkdiv); 437 + 438 + return 0; 439 + } 440 + 441 + static int wm8974_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, 442 + unsigned int freq, int dir) 443 + { 444 + struct snd_soc_codec *codec = dai->codec; 445 + struct wm8974_priv *priv = snd_soc_codec_get_drvdata(codec); 446 + 447 + if (dir != SND_SOC_CLOCK_IN) 448 + return -EINVAL; 449 + 450 + priv->mclk = freq; 451 + 452 + return wm8974_update_clocks(dai); 453 + } 454 + 387 455 static int wm8974_set_dai_fmt(struct snd_soc_dai *codec_dai, 388 456 unsigned int fmt) 389 457 { ··· 519 441 struct snd_soc_dai *dai) 520 442 { 521 443 struct snd_soc_codec *codec = dai->codec; 444 + struct wm8974_priv *priv = snd_soc_codec_get_drvdata(codec); 522 445 u16 iface = snd_soc_read(codec, WM8974_IFACE) & 0x19f; 523 446 u16 adn = snd_soc_read(codec, WM8974_ADD) & 0x1f1; 447 + int err; 448 + 449 + priv->fs = params_rate(params); 450 + err = wm8974_update_clocks(dai); 451 + if (err) 452 + return err; 524 453 525 454 /* bit size */ 526 455 switch (params_width(params)) { ··· 632 547 .set_fmt = wm8974_set_dai_fmt, 633 548 .set_clkdiv = wm8974_set_dai_clkdiv, 634 549 .set_pll = wm8974_set_dai_pll, 550 + .set_sysclk = wm8974_set_dai_sysclk, 635 551 }; 636 552 637 553 static struct snd_soc_dai_driver wm8974_dai = { ··· 692 606 static int wm8974_i2c_probe(struct i2c_client *i2c, 693 607 const struct i2c_device_id *id) 694 608 { 609 + struct wm8974_priv *priv; 695 610 struct regmap *regmap; 696 611 int ret; 612 + 613 + priv = devm_kzalloc(&i2c->dev, sizeof(*priv), GFP_KERNEL); 614 + if (!priv) 615 + return -ENOMEM; 616 + 617 + i2c_set_clientdata(i2c, priv); 697 618 698 619 regmap = devm_regmap_init_i2c(i2c, &wm8974_regmap); 699 620 if (IS_ERR(regmap))
+154 -86
sound/soc/soc-topology.c
··· 223 223 return -EINVAL; 224 224 } 225 225 226 - static enum snd_soc_dobj_type get_dobj_mixer_type( 227 - struct snd_soc_tplg_ctl_hdr *control_hdr) 228 - { 229 - if (control_hdr == NULL) 230 - return SND_SOC_DOBJ_NONE; 231 - 232 - switch (control_hdr->ops.info) { 233 - case SND_SOC_TPLG_CTL_VOLSW: 234 - case SND_SOC_TPLG_CTL_VOLSW_SX: 235 - case SND_SOC_TPLG_CTL_VOLSW_XR_SX: 236 - case SND_SOC_TPLG_CTL_RANGE: 237 - case SND_SOC_TPLG_CTL_STROBE: 238 - return SND_SOC_DOBJ_MIXER; 239 - case SND_SOC_TPLG_CTL_ENUM: 240 - case SND_SOC_TPLG_CTL_ENUM_VALUE: 241 - return SND_SOC_DOBJ_ENUM; 242 - case SND_SOC_TPLG_CTL_BYTES: 243 - return SND_SOC_DOBJ_BYTES; 244 - default: 245 - return SND_SOC_DOBJ_NONE; 246 - } 247 - } 248 - 249 - static enum snd_soc_dobj_type get_dobj_type(struct snd_soc_tplg_hdr *hdr, 250 - struct snd_soc_tplg_ctl_hdr *control_hdr) 251 - { 252 - switch (hdr->type) { 253 - case SND_SOC_TPLG_TYPE_MIXER: 254 - return get_dobj_mixer_type(control_hdr); 255 - case SND_SOC_TPLG_TYPE_DAPM_GRAPH: 256 - case SND_SOC_TPLG_TYPE_MANIFEST: 257 - return SND_SOC_DOBJ_NONE; 258 - case SND_SOC_TPLG_TYPE_DAPM_WIDGET: 259 - return SND_SOC_DOBJ_WIDGET; 260 - case SND_SOC_TPLG_TYPE_DAI_LINK: 261 - return SND_SOC_DOBJ_DAI_LINK; 262 - case SND_SOC_TPLG_TYPE_PCM: 263 - return SND_SOC_DOBJ_PCM; 264 - case SND_SOC_TPLG_TYPE_CODEC_LINK: 265 - return SND_SOC_DOBJ_CODEC_LINK; 266 - default: 267 - return SND_SOC_DOBJ_NONE; 268 - } 269 - } 270 - 271 226 static inline void soc_bind_err(struct soc_tplg *tplg, 272 227 struct snd_soc_tplg_ctl_hdr *hdr, int index) 273 228 { ··· 285 330 return 0; 286 331 } 287 332 288 - /* pass dynamic FEs configurations to component driver */ 289 - static int soc_tplg_pcm_dai_load(struct soc_tplg *tplg, 290 - struct snd_soc_tplg_pcm_dai *pcm_dai, int num_pcm_dai) 333 + /* pass DAI configurations to component driver for extra intialization */ 334 + static int soc_tplg_dai_load(struct soc_tplg *tplg, 335 + struct snd_soc_dai_driver *dai_drv) 291 336 { 292 - if (tplg->comp && tplg->ops && tplg->ops->pcm_dai_load) 293 - return tplg->ops->pcm_dai_load(tplg->comp, pcm_dai, num_pcm_dai); 337 + if (tplg->comp && tplg->ops && tplg->ops->dai_load) 338 + return tplg->ops->dai_load(tplg->comp, dai_drv); 339 + 340 + return 0; 341 + } 342 + 343 + /* pass link configurations to component driver for extra intialization */ 344 + static int soc_tplg_dai_link_load(struct soc_tplg *tplg, 345 + struct snd_soc_dai_link *link) 346 + { 347 + if (tplg->comp && tplg->ops && tplg->ops->link_load) 348 + return tplg->ops->link_load(tplg->comp, link); 294 349 295 350 return 0; 296 351 } ··· 460 495 /* widget w is freed by soc-dapm.c */ 461 496 } 462 497 463 - /* remove PCM DAI configurations */ 464 - static void remove_pcm_dai(struct snd_soc_component *comp, 498 + /* remove DAI configurations */ 499 + static void remove_dai(struct snd_soc_component *comp, 465 500 struct snd_soc_dobj *dobj, int pass) 466 501 { 502 + struct snd_soc_dai_driver *dai_drv = 503 + container_of(dobj, struct snd_soc_dai_driver, dobj); 504 + 467 505 if (pass != SOC_TPLG_PASS_PCM_DAI) 468 506 return; 469 507 470 - if (dobj->ops && dobj->ops->pcm_dai_unload) 471 - dobj->ops->pcm_dai_unload(comp, dobj); 508 + if (dobj->ops && dobj->ops->dai_unload) 509 + dobj->ops->dai_unload(comp, dobj); 472 510 473 511 list_del(&dobj->list); 474 - kfree(dobj); 512 + kfree(dai_drv); 513 + } 514 + 515 + /* remove link configurations */ 516 + static void remove_link(struct snd_soc_component *comp, 517 + struct snd_soc_dobj *dobj, int pass) 518 + { 519 + struct snd_soc_dai_link *link = 520 + container_of(dobj, struct snd_soc_dai_link, dobj); 521 + 522 + if (pass != SOC_TPLG_PASS_PCM_DAI) 523 + return; 524 + 525 + if (dobj->ops && dobj->ops->link_unload) 526 + dobj->ops->link_unload(comp, dobj); 527 + 528 + list_del(&dobj->list); 529 + snd_soc_remove_dai_link(comp->card, link); 530 + kfree(link); 475 531 } 476 532 477 533 /* bind a kcontrol to it's IO handlers */ ··· 1530 1544 return 0; 1531 1545 } 1532 1546 1533 - static int soc_tplg_pcm_dai_elems_load(struct soc_tplg *tplg, 1547 + static void set_stream_info(struct snd_soc_pcm_stream *stream, 1548 + struct snd_soc_tplg_stream_caps *caps) 1549 + { 1550 + stream->stream_name = kstrdup(caps->name, GFP_KERNEL); 1551 + stream->channels_min = caps->channels_min; 1552 + stream->channels_max = caps->channels_max; 1553 + stream->rates = caps->rates; 1554 + stream->rate_min = caps->rate_min; 1555 + stream->rate_max = caps->rate_max; 1556 + stream->formats = caps->formats; 1557 + } 1558 + 1559 + static int soc_tplg_dai_create(struct soc_tplg *tplg, 1560 + struct snd_soc_tplg_pcm *pcm) 1561 + { 1562 + struct snd_soc_dai_driver *dai_drv; 1563 + struct snd_soc_pcm_stream *stream; 1564 + struct snd_soc_tplg_stream_caps *caps; 1565 + int ret; 1566 + 1567 + dai_drv = kzalloc(sizeof(struct snd_soc_dai_driver), GFP_KERNEL); 1568 + if (dai_drv == NULL) 1569 + return -ENOMEM; 1570 + 1571 + dai_drv->name = pcm->dai_name; 1572 + dai_drv->id = pcm->dai_id; 1573 + 1574 + if (pcm->playback) { 1575 + stream = &dai_drv->playback; 1576 + caps = &pcm->caps[SND_SOC_TPLG_STREAM_PLAYBACK]; 1577 + set_stream_info(stream, caps); 1578 + } 1579 + 1580 + if (pcm->capture) { 1581 + stream = &dai_drv->capture; 1582 + caps = &pcm->caps[SND_SOC_TPLG_STREAM_CAPTURE]; 1583 + set_stream_info(stream, caps); 1584 + } 1585 + 1586 + /* pass control to component driver for optional further init */ 1587 + ret = soc_tplg_dai_load(tplg, dai_drv); 1588 + if (ret < 0) { 1589 + dev_err(tplg->comp->dev, "ASoC: DAI loading failed\n"); 1590 + kfree(dai_drv); 1591 + return ret; 1592 + } 1593 + 1594 + dai_drv->dobj.index = tplg->index; 1595 + dai_drv->dobj.ops = tplg->ops; 1596 + dai_drv->dobj.type = SND_SOC_DOBJ_PCM; 1597 + list_add(&dai_drv->dobj.list, &tplg->comp->dobj_list); 1598 + 1599 + /* register the DAI to the component */ 1600 + return snd_soc_register_dai(tplg->comp, dai_drv); 1601 + } 1602 + 1603 + static int soc_tplg_link_create(struct soc_tplg *tplg, 1604 + struct snd_soc_tplg_pcm *pcm) 1605 + { 1606 + struct snd_soc_dai_link *link; 1607 + int ret; 1608 + 1609 + link = kzalloc(sizeof(struct snd_soc_dai_link), GFP_KERNEL); 1610 + if (link == NULL) 1611 + return -ENOMEM; 1612 + 1613 + link->name = pcm->pcm_name; 1614 + link->stream_name = pcm->pcm_name; 1615 + 1616 + /* pass control to component driver for optional further init */ 1617 + ret = soc_tplg_dai_link_load(tplg, link); 1618 + if (ret < 0) { 1619 + dev_err(tplg->comp->dev, "ASoC: FE link loading failed\n"); 1620 + kfree(link); 1621 + return ret; 1622 + } 1623 + 1624 + link->dobj.index = tplg->index; 1625 + link->dobj.ops = tplg->ops; 1626 + link->dobj.type = SND_SOC_DOBJ_DAI_LINK; 1627 + list_add(&link->dobj.list, &tplg->comp->dobj_list); 1628 + 1629 + snd_soc_add_dai_link(tplg->comp->card, link); 1630 + return 0; 1631 + } 1632 + 1633 + /* create a FE DAI and DAI link from the PCM object */ 1634 + static int soc_tplg_pcm_create(struct soc_tplg *tplg, 1635 + struct snd_soc_tplg_pcm *pcm) 1636 + { 1637 + int ret; 1638 + 1639 + ret = soc_tplg_dai_create(tplg, pcm); 1640 + if (ret < 0) 1641 + return ret; 1642 + 1643 + return soc_tplg_link_create(tplg, pcm); 1644 + } 1645 + 1646 + static int soc_tplg_pcm_elems_load(struct soc_tplg *tplg, 1534 1647 struct snd_soc_tplg_hdr *hdr) 1535 1648 { 1536 - struct snd_soc_tplg_pcm_dai *pcm_dai; 1537 - struct snd_soc_dobj *dobj; 1649 + struct snd_soc_tplg_pcm *pcm; 1538 1650 int count = hdr->count; 1539 - int ret; 1651 + int i; 1540 1652 1541 1653 if (tplg->pass != SOC_TPLG_PASS_PCM_DAI) 1542 1654 return 0; 1543 1655 1544 - pcm_dai = (struct snd_soc_tplg_pcm_dai *)tplg->pos; 1656 + pcm = (struct snd_soc_tplg_pcm *)tplg->pos; 1545 1657 1546 1658 if (soc_tplg_check_elem_count(tplg, 1547 1659 sizeof(struct snd_soc_tplg_pcm), count, ··· 1649 1565 return -EINVAL; 1650 1566 } 1651 1567 1568 + /* create the FE DAIs and DAI links */ 1569 + for (i = 0; i < count; i++) { 1570 + soc_tplg_pcm_create(tplg, pcm); 1571 + pcm++; 1572 + } 1573 + 1652 1574 dev_dbg(tplg->dev, "ASoC: adding %d PCM DAIs\n", count); 1653 1575 tplg->pos += sizeof(struct snd_soc_tplg_pcm) * count; 1654 1576 1655 - dobj = kzalloc(sizeof(struct snd_soc_dobj), GFP_KERNEL); 1656 - if (dobj == NULL) 1657 - return -ENOMEM; 1658 - 1659 - /* Call the platform driver call back to register the dais */ 1660 - ret = soc_tplg_pcm_dai_load(tplg, pcm_dai, count); 1661 - if (ret < 0) { 1662 - dev_err(tplg->comp->dev, "ASoC: PCM DAI loading failed\n"); 1663 - goto err; 1664 - } 1665 - 1666 - dobj->type = get_dobj_type(hdr, NULL); 1667 - dobj->pcm_dai.count = count; 1668 - dobj->pcm_dai.pd = pcm_dai; 1669 - dobj->ops = tplg->ops; 1670 - dobj->index = tplg->index; 1671 - list_add(&dobj->list, &tplg->comp->dobj_list); 1672 1577 return 0; 1673 - 1674 - err: 1675 - kfree(dobj); 1676 - return ret; 1677 1578 } 1678 1579 1679 1580 static int soc_tplg_manifest_load(struct soc_tplg *tplg, ··· 1750 1681 case SND_SOC_TPLG_TYPE_DAPM_WIDGET: 1751 1682 return soc_tplg_dapm_widget_elems_load(tplg, hdr); 1752 1683 case SND_SOC_TPLG_TYPE_PCM: 1753 - case SND_SOC_TPLG_TYPE_DAI_LINK: 1754 - case SND_SOC_TPLG_TYPE_CODEC_LINK: 1755 - return soc_tplg_pcm_dai_elems_load(tplg, hdr); 1684 + return soc_tplg_pcm_elems_load(tplg, hdr); 1756 1685 case SND_SOC_TPLG_TYPE_MANIFEST: 1757 1686 return soc_tplg_manifest_load(tplg, hdr); 1758 1687 default: ··· 1908 1841 remove_widget(comp, dobj, pass); 1909 1842 break; 1910 1843 case SND_SOC_DOBJ_PCM: 1844 + remove_dai(comp, dobj, pass); 1845 + break; 1911 1846 case SND_SOC_DOBJ_DAI_LINK: 1912 - case SND_SOC_DOBJ_CODEC_LINK: 1913 - remove_pcm_dai(comp, dobj, pass); 1847 + remove_link(comp, dobj, pass); 1914 1848 break; 1915 1849 default: 1916 1850 dev_err(comp->dev, "ASoC: invalid component type %d for removal\n",
+8
sound/soc/sunxi/Kconfig
··· 8 8 Select Y or M to add support for the Codec embedded in the Allwinner 9 9 A10 and affiliated SoCs. 10 10 11 + config SND_SUN4I_SPDIF 12 + tristate "Allwinner A10 SPDIF Support" 13 + depends on OF 14 + select SND_SOC_GENERIC_DMAENGINE_PCM 15 + select REGMAP_MMIO 16 + help 17 + Say Y or M to add support for the S/PDIF audio block in the Allwinner 18 + A10 and affiliated SoCs. 11 19 endmenu
+1
sound/soc/sunxi/Makefile
··· 1 1 obj-$(CONFIG_SND_SUN4I_CODEC) += sun4i-codec.o 2 2 3 + obj-$(CONFIG_SND_SUN4I_SPDIF) += sun4i-spdif.o
+550
sound/soc/sunxi/sun4i-spdif.c
··· 1 + /* 2 + * ALSA SoC SPDIF Audio Layer 3 + * 4 + * Copyright 2015 Andrea Venturi <be17068@iperbole.bo.it> 5 + * Copyright 2015 Marcus Cooper <codekipper@gmail.com> 6 + * 7 + * Based on the Allwinner SDK driver, released under the GPL. 8 + * 9 + * This program is free software; you can redistribute it and/or modify 10 + * it under the terms of the GNU General Public License as published by 11 + * the Free Software Foundation; either version 2 of the License, or 12 + * (at your option) any later version. 13 + * 14 + * This program is distributed in the hope that it will be useful, 15 + * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 + * GNU General Public License for more details. 18 + */ 19 + 20 + #include <linux/clk.h> 21 + #include <linux/delay.h> 22 + #include <linux/device.h> 23 + #include <linux/kernel.h> 24 + #include <linux/init.h> 25 + #include <linux/regmap.h> 26 + #include <linux/of_address.h> 27 + #include <linux/of_device.h> 28 + #include <linux/ioport.h> 29 + #include <linux/module.h> 30 + #include <linux/platform_device.h> 31 + #include <linux/pm_runtime.h> 32 + #include <sound/dmaengine_pcm.h> 33 + #include <sound/pcm_params.h> 34 + #include <sound/soc.h> 35 + 36 + #define SUN4I_SPDIF_CTL (0x00) 37 + #define SUN4I_SPDIF_CTL_MCLKDIV(v) ((v) << 4) /* v even */ 38 + #define SUN4I_SPDIF_CTL_MCLKOUTEN BIT(2) 39 + #define SUN4I_SPDIF_CTL_GEN BIT(1) 40 + #define SUN4I_SPDIF_CTL_RESET BIT(0) 41 + 42 + #define SUN4I_SPDIF_TXCFG (0x04) 43 + #define SUN4I_SPDIF_TXCFG_SINGLEMOD BIT(31) 44 + #define SUN4I_SPDIF_TXCFG_ASS BIT(17) 45 + #define SUN4I_SPDIF_TXCFG_NONAUDIO BIT(16) 46 + #define SUN4I_SPDIF_TXCFG_TXRATIO(v) ((v) << 4) 47 + #define SUN4I_SPDIF_TXCFG_TXRATIO_MASK GENMASK(8, 4) 48 + #define SUN4I_SPDIF_TXCFG_FMTRVD GENMASK(3, 2) 49 + #define SUN4I_SPDIF_TXCFG_FMT16BIT (0 << 2) 50 + #define SUN4I_SPDIF_TXCFG_FMT20BIT (1 << 2) 51 + #define SUN4I_SPDIF_TXCFG_FMT24BIT (2 << 2) 52 + #define SUN4I_SPDIF_TXCFG_CHSTMODE BIT(1) 53 + #define SUN4I_SPDIF_TXCFG_TXEN BIT(0) 54 + 55 + #define SUN4I_SPDIF_RXCFG (0x08) 56 + #define SUN4I_SPDIF_RXCFG_LOCKFLAG BIT(4) 57 + #define SUN4I_SPDIF_RXCFG_CHSTSRC BIT(3) 58 + #define SUN4I_SPDIF_RXCFG_CHSTCP BIT(1) 59 + #define SUN4I_SPDIF_RXCFG_RXEN BIT(0) 60 + 61 + #define SUN4I_SPDIF_TXFIFO (0x0C) 62 + 63 + #define SUN4I_SPDIF_RXFIFO (0x10) 64 + 65 + #define SUN4I_SPDIF_FCTL (0x14) 66 + #define SUN4I_SPDIF_FCTL_FIFOSRC BIT(31) 67 + #define SUN4I_SPDIF_FCTL_FTX BIT(17) 68 + #define SUN4I_SPDIF_FCTL_FRX BIT(16) 69 + #define SUN4I_SPDIF_FCTL_TXTL(v) ((v) << 8) 70 + #define SUN4I_SPDIF_FCTL_TXTL_MASK GENMASK(12, 8) 71 + #define SUN4I_SPDIF_FCTL_RXTL(v) ((v) << 3) 72 + #define SUN4I_SPDIF_FCTL_RXTL_MASK GENMASK(7, 3) 73 + #define SUN4I_SPDIF_FCTL_TXIM BIT(2) 74 + #define SUN4I_SPDIF_FCTL_RXOM(v) ((v) << 0) 75 + #define SUN4I_SPDIF_FCTL_RXOM_MASK GENMASK(1, 0) 76 + 77 + #define SUN4I_SPDIF_FSTA (0x18) 78 + #define SUN4I_SPDIF_FSTA_TXE BIT(14) 79 + #define SUN4I_SPDIF_FSTA_TXECNTSHT (8) 80 + #define SUN4I_SPDIF_FSTA_RXA BIT(6) 81 + #define SUN4I_SPDIF_FSTA_RXACNTSHT (0) 82 + 83 + #define SUN4I_SPDIF_INT (0x1C) 84 + #define SUN4I_SPDIF_INT_RXLOCKEN BIT(18) 85 + #define SUN4I_SPDIF_INT_RXUNLOCKEN BIT(17) 86 + #define SUN4I_SPDIF_INT_RXPARERREN BIT(16) 87 + #define SUN4I_SPDIF_INT_TXDRQEN BIT(7) 88 + #define SUN4I_SPDIF_INT_TXUIEN BIT(6) 89 + #define SUN4I_SPDIF_INT_TXOIEN BIT(5) 90 + #define SUN4I_SPDIF_INT_TXEIEN BIT(4) 91 + #define SUN4I_SPDIF_INT_RXDRQEN BIT(2) 92 + #define SUN4I_SPDIF_INT_RXOIEN BIT(1) 93 + #define SUN4I_SPDIF_INT_RXAIEN BIT(0) 94 + 95 + #define SUN4I_SPDIF_ISTA (0x20) 96 + #define SUN4I_SPDIF_ISTA_RXLOCKSTA BIT(18) 97 + #define SUN4I_SPDIF_ISTA_RXUNLOCKSTA BIT(17) 98 + #define SUN4I_SPDIF_ISTA_RXPARERRSTA BIT(16) 99 + #define SUN4I_SPDIF_ISTA_TXUSTA BIT(6) 100 + #define SUN4I_SPDIF_ISTA_TXOSTA BIT(5) 101 + #define SUN4I_SPDIF_ISTA_TXESTA BIT(4) 102 + #define SUN4I_SPDIF_ISTA_RXOSTA BIT(1) 103 + #define SUN4I_SPDIF_ISTA_RXASTA BIT(0) 104 + 105 + #define SUN4I_SPDIF_TXCNT (0x24) 106 + 107 + #define SUN4I_SPDIF_RXCNT (0x28) 108 + 109 + #define SUN4I_SPDIF_TXCHSTA0 (0x2C) 110 + #define SUN4I_SPDIF_TXCHSTA0_CLK(v) ((v) << 28) 111 + #define SUN4I_SPDIF_TXCHSTA0_SAMFREQ(v) ((v) << 24) 112 + #define SUN4I_SPDIF_TXCHSTA0_SAMFREQ_MASK GENMASK(27, 24) 113 + #define SUN4I_SPDIF_TXCHSTA0_CHNUM(v) ((v) << 20) 114 + #define SUN4I_SPDIF_TXCHSTA0_CHNUM_MASK GENMASK(23, 20) 115 + #define SUN4I_SPDIF_TXCHSTA0_SRCNUM(v) ((v) << 16) 116 + #define SUN4I_SPDIF_TXCHSTA0_CATACOD(v) ((v) << 8) 117 + #define SUN4I_SPDIF_TXCHSTA0_MODE(v) ((v) << 6) 118 + #define SUN4I_SPDIF_TXCHSTA0_EMPHASIS(v) ((v) << 3) 119 + #define SUN4I_SPDIF_TXCHSTA0_CP BIT(2) 120 + #define SUN4I_SPDIF_TXCHSTA0_AUDIO BIT(1) 121 + #define SUN4I_SPDIF_TXCHSTA0_PRO BIT(0) 122 + 123 + #define SUN4I_SPDIF_TXCHSTA1 (0x30) 124 + #define SUN4I_SPDIF_TXCHSTA1_CGMSA(v) ((v) << 8) 125 + #define SUN4I_SPDIF_TXCHSTA1_ORISAMFREQ(v) ((v) << 4) 126 + #define SUN4I_SPDIF_TXCHSTA1_ORISAMFREQ_MASK GENMASK(7, 4) 127 + #define SUN4I_SPDIF_TXCHSTA1_SAMWORDLEN(v) ((v) << 1) 128 + #define SUN4I_SPDIF_TXCHSTA1_MAXWORDLEN BIT(0) 129 + 130 + #define SUN4I_SPDIF_RXCHSTA0 (0x34) 131 + #define SUN4I_SPDIF_RXCHSTA0_CLK(v) ((v) << 28) 132 + #define SUN4I_SPDIF_RXCHSTA0_SAMFREQ(v) ((v) << 24) 133 + #define SUN4I_SPDIF_RXCHSTA0_CHNUM(v) ((v) << 20) 134 + #define SUN4I_SPDIF_RXCHSTA0_SRCNUM(v) ((v) << 16) 135 + #define SUN4I_SPDIF_RXCHSTA0_CATACOD(v) ((v) << 8) 136 + #define SUN4I_SPDIF_RXCHSTA0_MODE(v) ((v) << 6) 137 + #define SUN4I_SPDIF_RXCHSTA0_EMPHASIS(v) ((v) << 3) 138 + #define SUN4I_SPDIF_RXCHSTA0_CP BIT(2) 139 + #define SUN4I_SPDIF_RXCHSTA0_AUDIO BIT(1) 140 + #define SUN4I_SPDIF_RXCHSTA0_PRO BIT(0) 141 + 142 + #define SUN4I_SPDIF_RXCHSTA1 (0x38) 143 + #define SUN4I_SPDIF_RXCHSTA1_CGMSA(v) ((v) << 8) 144 + #define SUN4I_SPDIF_RXCHSTA1_ORISAMFREQ(v) ((v) << 4) 145 + #define SUN4I_SPDIF_RXCHSTA1_SAMWORDLEN(v) ((v) << 1) 146 + #define SUN4I_SPDIF_RXCHSTA1_MAXWORDLEN BIT(0) 147 + 148 + /* Defines for Sampling Frequency */ 149 + #define SUN4I_SPDIF_SAMFREQ_44_1KHZ 0x0 150 + #define SUN4I_SPDIF_SAMFREQ_NOT_INDICATED 0x1 151 + #define SUN4I_SPDIF_SAMFREQ_48KHZ 0x2 152 + #define SUN4I_SPDIF_SAMFREQ_32KHZ 0x3 153 + #define SUN4I_SPDIF_SAMFREQ_22_05KHZ 0x4 154 + #define SUN4I_SPDIF_SAMFREQ_24KHZ 0x6 155 + #define SUN4I_SPDIF_SAMFREQ_88_2KHZ 0x8 156 + #define SUN4I_SPDIF_SAMFREQ_76_8KHZ 0x9 157 + #define SUN4I_SPDIF_SAMFREQ_96KHZ 0xa 158 + #define SUN4I_SPDIF_SAMFREQ_176_4KHZ 0xc 159 + #define SUN4I_SPDIF_SAMFREQ_192KHZ 0xe 160 + 161 + struct sun4i_spdif_dev { 162 + struct platform_device *pdev; 163 + struct clk *spdif_clk; 164 + struct clk *apb_clk; 165 + struct snd_soc_dai_driver cpu_dai_drv; 166 + struct regmap *regmap; 167 + struct snd_dmaengine_dai_dma_data dma_params_tx; 168 + }; 169 + 170 + static void sun4i_spdif_configure(struct sun4i_spdif_dev *host) 171 + { 172 + /* soft reset SPDIF */ 173 + regmap_write(host->regmap, SUN4I_SPDIF_CTL, SUN4I_SPDIF_CTL_RESET); 174 + 175 + /* flush TX FIFO */ 176 + regmap_update_bits(host->regmap, SUN4I_SPDIF_FCTL, 177 + SUN4I_SPDIF_FCTL_FTX, SUN4I_SPDIF_FCTL_FTX); 178 + 179 + /* clear TX counter */ 180 + regmap_write(host->regmap, SUN4I_SPDIF_TXCNT, 0); 181 + } 182 + 183 + static void sun4i_snd_txctrl_on(struct snd_pcm_substream *substream, 184 + struct sun4i_spdif_dev *host) 185 + { 186 + if (substream->runtime->channels == 1) 187 + regmap_update_bits(host->regmap, SUN4I_SPDIF_TXCFG, 188 + SUN4I_SPDIF_TXCFG_SINGLEMOD, 189 + SUN4I_SPDIF_TXCFG_SINGLEMOD); 190 + 191 + /* SPDIF TX ENABLE */ 192 + regmap_update_bits(host->regmap, SUN4I_SPDIF_TXCFG, 193 + SUN4I_SPDIF_TXCFG_TXEN, SUN4I_SPDIF_TXCFG_TXEN); 194 + 195 + /* DRQ ENABLE */ 196 + regmap_update_bits(host->regmap, SUN4I_SPDIF_INT, 197 + SUN4I_SPDIF_INT_TXDRQEN, SUN4I_SPDIF_INT_TXDRQEN); 198 + 199 + /* Global enable */ 200 + regmap_update_bits(host->regmap, SUN4I_SPDIF_CTL, 201 + SUN4I_SPDIF_CTL_GEN, SUN4I_SPDIF_CTL_GEN); 202 + } 203 + 204 + static void sun4i_snd_txctrl_off(struct snd_pcm_substream *substream, 205 + struct sun4i_spdif_dev *host) 206 + { 207 + /* SPDIF TX DISABLE */ 208 + regmap_update_bits(host->regmap, SUN4I_SPDIF_TXCFG, 209 + SUN4I_SPDIF_TXCFG_TXEN, 0); 210 + 211 + /* DRQ DISABLE */ 212 + regmap_update_bits(host->regmap, SUN4I_SPDIF_INT, 213 + SUN4I_SPDIF_INT_TXDRQEN, 0); 214 + 215 + /* Global disable */ 216 + regmap_update_bits(host->regmap, SUN4I_SPDIF_CTL, 217 + SUN4I_SPDIF_CTL_GEN, 0); 218 + } 219 + 220 + static int sun4i_spdif_startup(struct snd_pcm_substream *substream, 221 + struct snd_soc_dai *cpu_dai) 222 + { 223 + struct snd_soc_pcm_runtime *rtd = substream->private_data; 224 + struct sun4i_spdif_dev *host = snd_soc_dai_get_drvdata(rtd->cpu_dai); 225 + 226 + if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) 227 + return -EINVAL; 228 + 229 + sun4i_spdif_configure(host); 230 + 231 + return 0; 232 + } 233 + 234 + static int sun4i_spdif_hw_params(struct snd_pcm_substream *substream, 235 + struct snd_pcm_hw_params *params, 236 + struct snd_soc_dai *cpu_dai) 237 + { 238 + int ret = 0; 239 + int fmt; 240 + unsigned long rate = params_rate(params); 241 + u32 mclk_div = 0; 242 + unsigned int mclk = 0; 243 + u32 reg_val; 244 + struct sun4i_spdif_dev *host = snd_soc_dai_get_drvdata(cpu_dai); 245 + struct platform_device *pdev = host->pdev; 246 + 247 + /* Add the PCM and raw data select interface */ 248 + switch (params_channels(params)) { 249 + case 1: /* PCM mode */ 250 + case 2: 251 + fmt = 0; 252 + break; 253 + case 4: /* raw data mode */ 254 + fmt = SUN4I_SPDIF_TXCFG_NONAUDIO; 255 + break; 256 + default: 257 + return -EINVAL; 258 + } 259 + 260 + switch (params_format(params)) { 261 + case SNDRV_PCM_FORMAT_S16_LE: 262 + fmt |= SUN4I_SPDIF_TXCFG_FMT16BIT; 263 + break; 264 + case SNDRV_PCM_FORMAT_S20_3LE: 265 + fmt |= SUN4I_SPDIF_TXCFG_FMT20BIT; 266 + break; 267 + case SNDRV_PCM_FORMAT_S24_LE: 268 + fmt |= SUN4I_SPDIF_TXCFG_FMT24BIT; 269 + break; 270 + default: 271 + return -EINVAL; 272 + } 273 + 274 + switch (rate) { 275 + case 22050: 276 + case 44100: 277 + case 88200: 278 + case 176400: 279 + mclk = 22579200; 280 + break; 281 + case 24000: 282 + case 32000: 283 + case 48000: 284 + case 96000: 285 + case 192000: 286 + mclk = 24576000; 287 + break; 288 + default: 289 + return -EINVAL; 290 + } 291 + 292 + ret = clk_set_rate(host->spdif_clk, mclk); 293 + if (ret < 0) { 294 + dev_err(&pdev->dev, 295 + "Setting SPDIF clock rate for %d Hz failed!\n", mclk); 296 + return ret; 297 + } 298 + 299 + regmap_update_bits(host->regmap, SUN4I_SPDIF_FCTL, 300 + SUN4I_SPDIF_FCTL_TXIM, SUN4I_SPDIF_FCTL_TXIM); 301 + 302 + switch (rate) { 303 + case 22050: 304 + case 24000: 305 + mclk_div = 8; 306 + break; 307 + case 32000: 308 + mclk_div = 6; 309 + break; 310 + case 44100: 311 + case 48000: 312 + mclk_div = 4; 313 + break; 314 + case 88200: 315 + case 96000: 316 + mclk_div = 2; 317 + break; 318 + case 176400: 319 + case 192000: 320 + mclk_div = 1; 321 + break; 322 + default: 323 + return -EINVAL; 324 + } 325 + 326 + reg_val = 0; 327 + reg_val |= SUN4I_SPDIF_TXCFG_ASS; 328 + reg_val |= fmt; /* set non audio and bit depth */ 329 + reg_val |= SUN4I_SPDIF_TXCFG_CHSTMODE; 330 + reg_val |= SUN4I_SPDIF_TXCFG_TXRATIO(mclk_div - 1); 331 + regmap_write(host->regmap, SUN4I_SPDIF_TXCFG, reg_val); 332 + 333 + return 0; 334 + } 335 + 336 + static int sun4i_spdif_trigger(struct snd_pcm_substream *substream, int cmd, 337 + struct snd_soc_dai *dai) 338 + { 339 + int ret = 0; 340 + struct sun4i_spdif_dev *host = snd_soc_dai_get_drvdata(dai); 341 + 342 + if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) 343 + return -EINVAL; 344 + 345 + switch (cmd) { 346 + case SNDRV_PCM_TRIGGER_START: 347 + case SNDRV_PCM_TRIGGER_RESUME: 348 + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: 349 + sun4i_snd_txctrl_on(substream, host); 350 + break; 351 + 352 + case SNDRV_PCM_TRIGGER_STOP: 353 + case SNDRV_PCM_TRIGGER_SUSPEND: 354 + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: 355 + sun4i_snd_txctrl_off(substream, host); 356 + break; 357 + 358 + default: 359 + ret = -EINVAL; 360 + break; 361 + } 362 + return ret; 363 + } 364 + 365 + static int sun4i_spdif_soc_dai_probe(struct snd_soc_dai *dai) 366 + { 367 + struct sun4i_spdif_dev *host = snd_soc_dai_get_drvdata(dai); 368 + 369 + snd_soc_dai_init_dma_data(dai, &host->dma_params_tx, NULL); 370 + return 0; 371 + } 372 + 373 + static const struct snd_soc_dai_ops sun4i_spdif_dai_ops = { 374 + .startup = sun4i_spdif_startup, 375 + .trigger = sun4i_spdif_trigger, 376 + .hw_params = sun4i_spdif_hw_params, 377 + }; 378 + 379 + static const struct regmap_config sun4i_spdif_regmap_config = { 380 + .reg_bits = 32, 381 + .reg_stride = 4, 382 + .val_bits = 32, 383 + .max_register = SUN4I_SPDIF_RXCHSTA1, 384 + }; 385 + 386 + #define SUN4I_RATES SNDRV_PCM_RATE_8000_192000 387 + 388 + #define SUN4I_FORMATS (SNDRV_PCM_FORMAT_S16_LE | \ 389 + SNDRV_PCM_FORMAT_S20_3LE | \ 390 + SNDRV_PCM_FORMAT_S24_LE) 391 + 392 + static struct snd_soc_dai_driver sun4i_spdif_dai = { 393 + .playback = { 394 + .channels_min = 1, 395 + .channels_max = 2, 396 + .rates = SUN4I_RATES, 397 + .formats = SUN4I_FORMATS, 398 + }, 399 + .probe = sun4i_spdif_soc_dai_probe, 400 + .ops = &sun4i_spdif_dai_ops, 401 + .name = "spdif", 402 + }; 403 + 404 + static const struct snd_soc_dapm_widget dit_widgets[] = { 405 + SND_SOC_DAPM_OUTPUT("spdif-out"), 406 + }; 407 + 408 + static const struct snd_soc_dapm_route dit_routes[] = { 409 + { "spdif-out", NULL, "Playback" }, 410 + }; 411 + 412 + static const struct of_device_id sun4i_spdif_of_match[] = { 413 + { .compatible = "allwinner,sun4i-a10-spdif", }, 414 + { /* sentinel */ } 415 + }; 416 + MODULE_DEVICE_TABLE(of, sun4i_spdif_of_match); 417 + 418 + static const struct snd_soc_component_driver sun4i_spdif_component = { 419 + .name = "sun4i-spdif", 420 + }; 421 + 422 + static int sun4i_spdif_runtime_suspend(struct device *dev) 423 + { 424 + struct sun4i_spdif_dev *host = dev_get_drvdata(dev); 425 + 426 + clk_disable_unprepare(host->spdif_clk); 427 + clk_disable_unprepare(host->apb_clk); 428 + 429 + return 0; 430 + } 431 + 432 + static int sun4i_spdif_runtime_resume(struct device *dev) 433 + { 434 + struct sun4i_spdif_dev *host = dev_get_drvdata(dev); 435 + 436 + clk_prepare_enable(host->spdif_clk); 437 + clk_prepare_enable(host->apb_clk); 438 + 439 + return 0; 440 + } 441 + 442 + static int sun4i_spdif_probe(struct platform_device *pdev) 443 + { 444 + struct sun4i_spdif_dev *host; 445 + struct resource *res; 446 + int ret; 447 + void __iomem *base; 448 + 449 + dev_dbg(&pdev->dev, "Entered %s\n", __func__); 450 + 451 + host = devm_kzalloc(&pdev->dev, sizeof(*host), GFP_KERNEL); 452 + if (!host) 453 + return -ENOMEM; 454 + 455 + host->pdev = pdev; 456 + 457 + /* Initialize this copy of the CPU DAI driver structure */ 458 + memcpy(&host->cpu_dai_drv, &sun4i_spdif_dai, sizeof(sun4i_spdif_dai)); 459 + host->cpu_dai_drv.name = dev_name(&pdev->dev); 460 + 461 + /* Get the addresses */ 462 + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 463 + base = devm_ioremap_resource(&pdev->dev, res); 464 + if (IS_ERR(base)) 465 + return PTR_ERR(base); 466 + 467 + host->regmap = devm_regmap_init_mmio(&pdev->dev, base, 468 + &sun4i_spdif_regmap_config); 469 + 470 + /* Clocks */ 471 + host->apb_clk = devm_clk_get(&pdev->dev, "apb"); 472 + if (IS_ERR(host->apb_clk)) { 473 + dev_err(&pdev->dev, "failed to get a apb clock.\n"); 474 + return PTR_ERR(host->apb_clk); 475 + } 476 + 477 + host->spdif_clk = devm_clk_get(&pdev->dev, "spdif"); 478 + if (IS_ERR(host->spdif_clk)) { 479 + dev_err(&pdev->dev, "failed to get a spdif clock.\n"); 480 + ret = PTR_ERR(host->spdif_clk); 481 + goto err_disable_apb_clk; 482 + } 483 + 484 + host->dma_params_tx.addr = res->start + SUN4I_SPDIF_TXFIFO; 485 + host->dma_params_tx.maxburst = 4; 486 + host->dma_params_tx.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; 487 + 488 + platform_set_drvdata(pdev, host); 489 + 490 + ret = devm_snd_soc_register_component(&pdev->dev, 491 + &sun4i_spdif_component, &sun4i_spdif_dai, 1); 492 + if (ret) 493 + goto err_disable_apb_clk; 494 + 495 + pm_runtime_enable(&pdev->dev); 496 + if (!pm_runtime_enabled(&pdev->dev)) { 497 + ret = sun4i_spdif_runtime_resume(&pdev->dev); 498 + if (ret) 499 + goto err_unregister; 500 + } 501 + 502 + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); 503 + if (ret) 504 + goto err_suspend; 505 + return 0; 506 + err_suspend: 507 + if (!pm_runtime_status_suspended(&pdev->dev)) 508 + sun4i_spdif_runtime_suspend(&pdev->dev); 509 + err_unregister: 510 + pm_runtime_disable(&pdev->dev); 511 + snd_soc_unregister_component(&pdev->dev); 512 + err_disable_apb_clk: 513 + clk_disable_unprepare(host->apb_clk); 514 + return ret; 515 + } 516 + 517 + static int sun4i_spdif_remove(struct platform_device *pdev) 518 + { 519 + pm_runtime_disable(&pdev->dev); 520 + if (!pm_runtime_status_suspended(&pdev->dev)) 521 + sun4i_spdif_runtime_suspend(&pdev->dev); 522 + 523 + snd_soc_unregister_platform(&pdev->dev); 524 + snd_soc_unregister_component(&pdev->dev); 525 + 526 + return 0; 527 + } 528 + 529 + static const struct dev_pm_ops sun4i_spdif_pm = { 530 + SET_RUNTIME_PM_OPS(sun4i_spdif_runtime_suspend, 531 + sun4i_spdif_runtime_resume, NULL) 532 + }; 533 + 534 + static struct platform_driver sun4i_spdif_driver = { 535 + .driver = { 536 + .name = "sun4i-spdif", 537 + .of_match_table = of_match_ptr(sun4i_spdif_of_match), 538 + .pm = &sun4i_spdif_pm, 539 + }, 540 + .probe = sun4i_spdif_probe, 541 + .remove = sun4i_spdif_remove, 542 + }; 543 + 544 + module_platform_driver(sun4i_spdif_driver); 545 + 546 + MODULE_AUTHOR("Marcus Cooper <codekipper@gmail.com>"); 547 + MODULE_AUTHOR("Andrea Venturi <be17068@iperbole.bo.it>"); 548 + MODULE_DESCRIPTION("Allwinner sun4i SPDIF SoC Interface"); 549 + MODULE_LICENSE("GPL"); 550 + MODULE_ALIAS("platform:sun4i-spdif");