NOTICE: The Processors Wiki will End-of-Life in December of 2020. It is recommended to download any files or other content you may need that are hosted on processors.wiki.ti.com. The site is now set to read only.

Sitara Linux Audio DAC Example

From Texas Instruments Wiki
Jump to: navigation, search


Sitara Linux Software Developer's Guide Sitara Linux SDK Audio: DAC Example


Introduction

This page presents a step by step guide for interfacing an audio DAC to the McASP on AM335x devices, but the concepts apply to any Sitara device with a McASP. It is meant to give an overview of the basic tasks required to configure an audio device to work with linux. The following topics will be covered:

  • Creating a very basic ALSA dummy driver for the DAC
  • Setting up the ALSA McASP driver to fit the requirements of the DAC
  • Linking the DAC driver and McASP driver together
  • Basic testing and debugging techniques to ensure proper operation


Required Software and Hardware

Hardware

This project was done with the following hardware:


NOTE

You don't actually need to have the PCM5102a board to follow this guide; the drivers that will be created will work just fine without it connected. You can even use an o-scope to probe the audio data lines and clocks to see that the driver is working.


Software

All software work was done on Ubuntu 12.04

The AM335x EZSDK 7.0 was used for the kernel source:

The linux kernel source in SDK 7.0 is in the following directory:

ti-sdk-am335x-evm-07.00.00.00/board-support/linux-3.12.10-ti2013.12.01


NOTE

The complete patch that encompasses all the changes described in this guide can be found at the very bottom of this page. Feel free to apply it to the SDK 7.0 kernel and follow along with the details below.


Step by Step Instructions

The PCM5102a Driver

The PCM5102a is a stereo audio DAC. In terms of configuration, it does not require a control interface as is the case with many audio codecs; instead, it internally determines the sampling rate and format of the I2S data with which it is fed.

For more information on the PCM5102a, refer to the datasheet:


Creating the driver

There is no existing linux driver written for this device (nor is there really a need for one), but for the purposes of this project, we will create a dummy template driver. This template could easily be repurposed as the basis for a custom driver for another audio device.

To create our customer PCM5102a driver, we will borrow heavily from another simple driver called the ALSA SoC SPDIF DIT driver. Within the ALSA SoC layer, it is grouped within the codec directory:

sound/soc/codecs/spdif_transmitter.c
NOTE

All directories listed in this guide assume the top level linux kernel source in SDK 7.0 as the starting point.


Here are the steps for creating the driver:

  1. Create a new file for the PCM5102a driver in the ASoC folder.
    sound/soc/codecs/pcm5102a.c
  2. Copy the source code from spdif_transmitter.c to pcm5102a.c
  3. Replace all structure names to refer to pcm5102a instead of spdif. It really doesn't matter what you name these structures, just as long as structures and functions that call are updated with the new names.
  4. Update the driver to reflect the capabilities of the PCM5102a.
    1. Start with the STUB_RATES and STUB_FORMATS defines near the top.
    2. Based on the PCM5102a datasheet, change the format and rate parameter limits. The rate define is actually OK as is, but the formats need to be limited to S16LE, S24LE and S32LE.
  5. The snd_soc_dapm_widget and snd_soc_dapm_route structures are not going to be utilized in this case; they there if power management capabilities are taken into account. These structures can be deleted.
  6. The snd_soc_codec_driver needs to remain even though it will not be populated. Delete the data contained with the brackets, as it refers to the two DAPM structures that we just deleted.
  7. The last major change is in the snd_soc_dai_driver structure. We need to alter it to hold the hardware parameters we defined previously for audio play back. It should look something like this:
static struct snd_soc_dai_driver pcm5102a_dai = {
	.name		= "pcm5102a-hifi",
	.playback 	= {
		.stream_name	= "Playback",
		.channels_min	= 2,
		.channels_max	= 2,
		.rates		= RATES,
		.formats	= FORMATS,
	},
};

The remaining changes just entail updating structure names to something PMC5102a related.

One structure of interest is the following:

#ifdef CONFIG_OF
static const struct of_device_id pcm5102a_dt_ids[] = {
	{ .compatible = "ti,pcm5102a", },
	{ }
};
MODULE_DEVICE_TABLE(of, pcm5102a_dt_ids);
#endif

This makes the driver Device Tree compatible, and it will allow us to reference this driver in the DTS file later on.


Building the Driver into the Kernel

Now we can adjust the kernel makefiles so that this new PCM5102a driver can be built into the kernel. To do this, the following files must be edited:

sound/soc/codecs/Kconfig
sound/soc/codecs/Makefile

These are very simple changes that amount to looking at an existing entry, such as the PCM3008, and reproducing them to reflect PCM5102a. Note that in sound/soc/codecs/Makefile, the changes must be reflective of the actual name of the driver we created before. For example:

snd-soc-pcm5102a-objs := pcm5102a.o

There are still some changes that must be made to make this driver selectable in the kernel menuconfig, but these will be addressed later after the McASP driver has been set up.

Setting up the McASP

Now that we have an ALSA driver for the PCM5102a, we have to configure the AM335x McASP so that it will operate with the needed format and rate when audio data is sent to the PCM5102a. Most of this work will be done in the Machine layer of ALSA SoC, but some will be done through device tree entries.

ALSA Machine Layer Configuration

The ALSA Machine layer is where the audio device driver, in our case the PCM5102a, and the McASP driver are glued together. For Sitara McASP, this machine layer driver is found here:

sound/soc/davinci/davinci-evm.c

Here are the steps for configuring davinci-evm:

DAI Link Structure

  • Create a DAI (digital audio interface) Link structure. This will allow a specific configuration for the McASP to be called when linux is directed to play audio to the PCM5102.
    • Use an existing structure as a template: snd_soc_dai_link evm_dai_tda998x_hdmi will work well for this. It is located around line 630.
    • The new structure should look something like this:

<syntaxhighlight lang='c'>static struct snd_soc_dai_link evm_dai_pcm5102a = { .name = "PCM5102A", //This is chosen arbitrarily. Can be anything. .stream_name = "Playback", //This comes from the PCM5102a driver create previously. .codec_dai_name = "pcm5102a-hifi", //This comes from the PCM5102a driver create previously .ops = &pcm5102a_ops, //This is a structure that we will create later. .dai_fmt = (SND_SOC_DAIFMT_CBS_CFS | SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_IB_NF), };</syntaxhighlight>

Let's take a closer look at the .dai_fmt member of this structure. We may have copied it directly from the tda998x, but it turns out it to be exactly the configuration that is needed for the PCM5102a. Note that all of the settings in this are defined in the McASP driver itself:

sound/soc/davinci/davinci-mcasp.c

Now for a closer look at what each configuration does:

  • SND_SOC_DAIFMT_CBS_CFS
    • This indicates to the McASP that the PCM5102a will be a clock slave for both the bit clock and the frame sync. In other words, the McASP will be responsible for generating the bit clock and frame sync.
    • Here is the exact line of code from the davinci-mcasp.c that corresponds to it:

<syntaxhighlight lang='c'>case SND_SOC_DAIFMT_CBS_CFS: /* codec is clock and frame slave */ mcasp_set_bits(mcasp, DAVINCI_MCASP_ACLKXCTL_REG, ACLKXE); mcasp_set_bits(mcasp, DAVINCI_MCASP_TXFMCTL_REG, AFSXE);

mcasp_set_bits(mcasp, DAVINCI_MCASP_ACLKRCTL_REG, ACLKRE); mcasp_set_bits(mcasp, DAVINCI_MCASP_RXFMCTL_REG, AFSRE);

mcasp_set_bits(mcasp, DAVINCI_MCASP_PDIR_REG, ACLKX | ACLKR); mcasp_set_bits(mcasp, DAVINCI_MCASP_PDIR_REG, AFSX | AFSR);

               break;</syntaxhighlight>
  • SND_SOC_DAIFMT_I2S
    • This tells the McASP to configure itself in I2S mode, which is what the PCM5102a expects. Please refer to the AM335x TRM for more details on this mode.
  • SND_SOC_DAIFMT_IB_NF
    • This sets the expected polarity of the bit clock and frame sync based on the requirements of the PCM5102a. Refer to davinci-mcasp.c for more details on what exactly it is setting.

Filling Out pcm5102a_ops Structure

Our next step is to fill out the pcm5102a_ops structure that we mentioned before. Once again, let's use and existing implementation as a basis.

  • Find and make a copy of the following structure:

<syntaxhighlight lang='c'>static struct snd_soc_ops evm_ops = { .startup = evm_startup, .shutdown = evm_shutdown, .hw_params = evm_hw_params, };</syntaxhighlight>

  • When we copy this structure, we will rename it for PCM5102a, but we will also keep the evm_startup and evm_shutdown members the same, as they are generic enough to be useful for this project. evm_hw_params, however, will need to be changed to a custom function.
  • Here is what the overall result should look like:

<syntaxhighlight lang='c'>static struct snd_soc_ops pcm5102a_ops = { .startup = evm_startup, .shutdown = evm_shutdown, .hw_params = pcm5102a_hw_params, };</syntaxhighlight>

Creating pcm5102a_hw_params Function

Now the pcm5102a_hw_params function that we linked to the pcm5102a_ops structure needs to be made.

  • Find and copy the evm_tda998x_hw_params function, giving the new function the name pcm5102a_hw_params
  • In this function, we will accomplish the following:
    • Configure the clock divider for the bit clock based on the sampling rate and format requested.
    • Setting these a bit more complicated than what we have done so far, so here is what the final result should look like. We will go over it in detail below.

<syntaxhighlight lang='c'>static int pcm5102a_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; struct snd_soc_codec *codec = rtd->codec; struct snd_soc_card *soc_card = codec->card; struct platform_device *pdev = to_platform_device(soc_card->dev); unsigned int bclk_freq = evm_get_bclk(params); unsigned sysclk = ((struct snd_soc_card_drvdata_davinci *) snd_soc_card_get_drvdata(soc_card))->sysclk; int ret;

ret = snd_soc_dai_set_clkdiv(cpu_dai, 1, sysclk/bclk_freq); if (ret < 0) { dev_err(&pdev->dev, "can't set CPU DAI clock divider %d\n", ret); return ret; }

printk("PCM5102a hw params\n"); printk("sysclk=%d\n", sysclk); printk("bclk_freq=%d\n", bclk_freq); ret = snd_soc_dai_set_sysclk(cpu_dai, 0, sysclk, SND_SOC_CLOCK_OUT); if (ret < 0) return ret;

return ret; }</syntaxhighlight>

Let's look at the following line: <syntaxhighlight lang='c'>ret = snd_soc_dai_set_clkdiv(cpu_dai, 1, sysclk/bclk_freq);</syntaxhighlight>

  • This line is telling the McASP clock dividers to set the blck to the ratio of sysclk and bclk.
    • sysclock is the clock source that is fed to the McASP internally. It is 24 MHz on all Sitara EVMs. ALSA knows what this value is, which is why we request it as a variable.
    • bclk_freq is calculated by ALSA depending on the format of the data to be played. Here is an example of how the bit clock is calculated for 44.1 kHz stereo 16 bit audio:
      • 44.1 kHz × 16 × 2 = 1.4112 MHz
    • The second parameter passed to this function, "1", specifying which divider we would like to set. In the davinci-mcasp.c file mentioned before, there the following function shows exactly which dividers can be set:

<syntaxhighlight lang='c'>static int davinci_mcasp_set_clkdiv(struct snd_soc_dai *dai, int div_id, int div) { struct davinci_mcasp *mcasp = snd_soc_dai_get_drvdata(dai);

switch (div_id) { case 0: /* MCLK divider */ mcasp_mod_bits(mcasp, DAVINCI_MCASP_AHCLKXCTL_REG, AHCLKXDIV(div - 1), AHCLKXDIV_MASK); mcasp_mod_bits(mcasp, DAVINCI_MCASP_AHCLKRCTL_REG, AHCLKRDIV(div - 1), AHCLKRDIV_MASK); break;

case 1: /* BCLK divider */ mcasp_mod_bits(mcasp, DAVINCI_MCASP_ACLKXCTL_REG, ACLKXDIV(div - 1), ACLKXDIV_MASK); mcasp_mod_bits(mcasp, DAVINCI_MCASP_ACLKRCTL_REG, ACLKRDIV(div - 1), ACLKRDIV_MASK); break;

case 2: /* BCLK/LRCLK ratio */ mcasp->bclk_lrclk_ratio = div; break;

default: return -EINVAL; }

return 0; }</syntaxhighlight> Notice that we are only setting clock divider "1"; this is because the 24 MHz MCLK (which comes directly from the 24 MHz sysclk), doesn't need to be altered for our configured. Clock divider "2" is the bit clock / frame sync ratio, but it will be updated automatically when we set the bit clock based on the format of the audio to be played, so we don't need to set it here.


The next important line is the following: <syntaxhighlight lang='c'>ret = snd_soc_dai_set_sysclk(cpu_dai, 0, sysclk, SND_SOC_CLOCK_OUT);</syntaxhighlight> This line is making sure that ALSA knows that we will be using the internal 24 MHz clock as sysclk for the McASP, rather than using an external oscillator as is the case with many Sitara EVMs.

Device Tree Additions for davinci-evm

We have one last modification to make to the ASoC machine layer(davinci-evm); an entry needs to be added so that the PCM5102a McASP configuration can be called from Device Tree.

  • Find the static const struct of_device_id davinci_evm_dt_ids[] structure and add an entry for pcm5102a based on the changes we have made above.
  • Here is the final result:

<syntaxhighlight lang='c'>static const struct of_device_id davinci_evm_dt_ids[] = { { .compatible = "ti,pcm5102a-evm-audio", .data = &evm_dai_pcm5102a, }, { .compatible = "ti,da830-evm-audio", .data = &evm_dai_tlv320aic3x, }, { .compatible = "ti,am33xx-beaglebone-black", .data = &evm_dai_tda998x_hdmi, }, { .compatible = "ti,dra7xx-evm-audio", .data = (void *) &dra7xx_evm_link, }, { .compatible = "ti,am43xx-epos-evm-audio", .data = &evm_dai_tlv320aic3111, }, { /* sentinel */ } };</syntaxhighlight>

Enable PCM5102a/McASP Selection in kernel menuconfig

The necessary driver modifications are now in place. Now we can add an option to the kernel configuration so we can enable all this work we've completed. To do this, we will modify the following file:

sound/soc/davinci/Kconfig

Find the line that says config SND_AM43XX_SOC_EPOS_EVM and add the following code above it: <syntaxhighlight lang='c'>config SND_AM33XX_SOC_PCM5102A tristate "SoC Audio support for PCM5102A on BeagleBone" depends on SND_DAVINCI_SOC && SOC_AM33XX select SND_SOC_PCM5102A help Say Y or M if you want to add support for SoC audio on AM33XX BeagleBone board using McASP and PCM5102A. </syntaxhighlight>

Now in the kernel menuconfig, you will be able to enable this option and build the kernel.

==== Kernel config ====
 Device Drivers  --->
   Sound card support  --->
     Advanced Linux Sound Architecture  --->
       ALSA for SoC audio support  --->
         <*>   SoC Audio for TI DAVINCI or AM33XX/AM43XX chips
         <*>   SoC Audio for the AM33XX chip based boards
         <*>   SoC Audio support for PCM5102A on BeagleBone
NOTE

By default in the kernel configuration in the Sitara SDK, ALSA audio support is built as a module. Be sure to either set ALSA to be built into kernel, or install kernel modules into your filesystem after building them.

Adding the Device Tree Entries

The very last thing to do is add the proper device tree entries to link of all of these drivers together. This is also where we will set pinmuxing. Since the BeagleBone was used for this project, the following device tree file must be edited:

arch/arm/boot/dts/am335x-bone.dts

There are 4 things that must be done in this device tree file:

  1. Set the pinmuxing. The McASP0 mux mode 0 pins are broken out in the BeagleBone headers, so these were chosen for this project.
  2. Add a McASP0 node. This will allow the pinmuxing and serializer direction to be set up.
  3. Add a node for the PCM5102a driver we created. This will be call the device tree content we added to the PCM5102a driver.
  4. Add a sound node that links the McASP driver to the PCM5102a driver. This will call the device tree content we added to davinci-evm for our custom McASP configuration.

The end result should like this: <syntaxhighlight lang='c'>/*

* Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/

/dts-v1/;

  1. include "am33xx.dtsi"
  2. include "am335x-bone-common.dtsi"

&ldo3_reg { regulator-min-microvolt = <1800000>; regulator-max-microvolt = <3300000>; regulator-always-on; };

&mmc1 { vmmc-supply = <&ldo3_reg>; };

&am33xx_pinmux { mcasp0_pins: mcasp0_pins { pinctrl-single,pins = < 0x190 (PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* mcasp0_aclkx */ 0x194 (PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* mcasp0_fsx*/ 0x198 (PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* mcasp0_axr0 */ >; }; };

&mcasp0 { pinctrl-names = "default"; pinctrl-0 = <&mcasp0_pins>; status = "okay"; op-mode = <0>; /* MCASP_IIS_MODE */ tdm-slots = <2>; /* 16 serializer */ serial-dir = < /* 0: INACTIVE, 1: TX, 2: RX */ 1 0 0 0 >; tx-num-evt = <32>; rx-num-evt = <32>; };

/ {

pcm5102a: pcm5102a { compatible = "ti,pcm5102a"; };

sound { compatible = "ti,pcm5102a-evm-audio"; ti,model = "TI PCM5102A"; ti,audio-codec = <&pcm5102a>; ti,mcasp-controller = <&mcasp0>; ti,codec-clock-rate = <24000000>; };

};</syntaxhighlight>

Now the device tree blobs can be built, and we are ready to boot the device and test out our new audio device.

Testing It Out

When you boot the board, you should notice that linux probes the PCM5102a driver and successfully adds an audio device.

[    5.852989] ALSA device list:
[    5.856126]   #0: TI PCM5102A

With linux booted, we can now play an audio file. We'll use aplay for now and playback one of the sample ALSA wav files located in the filesystem:

root@am335x-evm:~# aplay /usr/share/sounds/alsa/Front_Left.wav
Playing WAVE '/usr/share/sounds/alsa/Front_Left.wav' : Signed 16[20844.209594] PCM5102a hw params
 bit Little Endian, Rate 48000 Hz, Mono
[20844.215907] sysclk=24000000
[20844.222443] bclk_freq=1536000

And that's it--you should hear sound out of your PCM5102a board. Even if you don't have the PCM5102a board, you can still use and o-scope to probe the McASP0 lines and see the bit clock, frame sync, and TX serial line.

The Complete Patch

From 1250f7b96688bdc5cdc4bc9def37ba669efeb9c1 Mon Sep 17 00:00:00 2001
From: Josh Elliott <jelliott@ti.com>
Date: Fri, 16 May 2014 05:12:56 -0500
Subject: [PATCH] pcm5102a complete

---
 arch/arm/boot/dts/am335x-bone.dts |   40 +++++++++++++++++++
 sound/soc/codecs/Kconfig          |    4 ++
 sound/soc/codecs/Makefile         |    2 +
 sound/soc/codecs/pcm5102a.c       |   80 +++++++++++++++++++++++++++++++++++++
 sound/soc/davinci/Kconfig         |    8 ++++
 sound/soc/davinci/davinci-evm.c   |   54 +++++++++++++++++++++++++
 6 files changed, 188 insertions(+)
 create mode 100644 sound/soc/codecs/pcm5102a.c

diff --git a/arch/arm/boot/dts/am335x-bone.dts b/arch/arm/boot/dts/am335x-bone.dts
index 0d63348..6c83e25 100644
--- a/arch/arm/boot/dts/am335x-bone.dts
+++ b/arch/arm/boot/dts/am335x-bone.dts
@@ -19,3 +19,43 @@
 &mmc1 {
 	vmmc-supply = <&ldo3_reg>;
 };
+
+&am33xx_pinmux {
+	mcasp0_pins: mcasp0_pins {
+		pinctrl-single,pins = <
+			0x190 (PIN_OUTPUT_PULLDOWN | MUX_MODE0)	/* mcasp0_aclkx */
+			0x194 (PIN_OUTPUT_PULLDOWN | MUX_MODE0)	/* mcasp0_fsx*/
+			0x198 (PIN_OUTPUT_PULLDOWN | MUX_MODE0)	/* mcasp0_axr0 */
+		>;
+	};
+};
+
+&mcasp0 {
+		pinctrl-names = "default";
+		pinctrl-0 = <&mcasp0_pins>;
+		status = "okay";
+		op-mode = <0>;          /* MCASP_IIS_MODE */
+		tdm-slots = <2>;
+		/* 16 serializer */
+		serial-dir = <  /* 0: INACTIVE, 1: TX, 2: RX */
+			1 0 0 0
+		>;
+		tx-num-evt = <32>;
+		rx-num-evt = <32>;
+};
+
+/ {
+
+	pcm5102a: pcm5102a {
+		compatible = "ti,pcm5102a";
+	};
+
+	sound {
+		compatible = "ti,pcm5102a-evm-audio";
+		ti,model = "TI PCM5102A";
+		ti,audio-codec = <&pcm5102a>;
+		ti,mcasp-controller = <&mcasp0>;
+		ti,codec-clock-rate = <24000000>;
+	};
+
+};
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 590cf10..73cd8d0 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -59,6 +59,7 @@ config SND_SOC_ALL_CODECS
 	select SND_SOC_PCM1681 if I2C
 	select SND_SOC_PCM1792A if SPI_MASTER
 	select SND_SOC_PCM3008
+	select SND_SOC_PCM5102A
 	select SND_SOC_RT5631 if I2C
 	select SND_SOC_RT5640 if I2C
 	select SND_SOC_SGTL5000 if I2C
@@ -312,6 +313,9 @@ config SND_SOC_PCM1792A
 config SND_SOC_PCM3008
        tristate

+config SND_SOC_PCM5102A
+       tristate
+
 config SND_SOC_RT5631
 	tristate

diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 23f8042..461050f 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -46,6 +46,7 @@ snd-soc-hdmi-codec-objs := hdmi.o
 snd-soc-pcm1681-objs := pcm1681.o
 snd-soc-pcm1792a-codec-objs := pcm1792a.o
 snd-soc-pcm3008-objs := pcm3008.o
+snd-soc-pcm5102a-objs := pcm5102a.o
 snd-soc-rt5631-objs := rt5631.o
 snd-soc-rt5640-objs := rt5640.o
 snd-soc-sgtl5000-objs := sgtl5000.o
@@ -180,6 +181,7 @@ obj-$(CONFIG_SND_SOC_HDMI_CODEC) += snd-soc-hdmi-codec.o
 obj-$(CONFIG_SND_SOC_PCM1681)	+= snd-soc-pcm1681.o
 obj-$(CONFIG_SND_SOC_PCM1792A)	+= snd-soc-pcm1792a-codec.o
 obj-$(CONFIG_SND_SOC_PCM3008)	+= snd-soc-pcm3008.o
+obj-$(CONFIG_SND_SOC_PCM5102A)	+= snd-soc-pcm5102a.o
 obj-$(CONFIG_SND_SOC_RT5631)	+= snd-soc-rt5631.o
 obj-$(CONFIG_SND_SOC_RT5640)	+= snd-soc-rt5640.o
 obj-$(CONFIG_SND_SOC_SGTL5000)  += snd-soc-sgtl5000.o
diff --git a/sound/soc/codecs/pcm5102a.c b/sound/soc/codecs/pcm5102a.c
new file mode 100644
index 0000000..9a2059b
--- /dev/null
+++ b/sound/soc/codecs/pcm5102a.c
@@ -0,0 +1,80 @@
+/*
+ * ALSA SoC PCM5102a driver
+ *
+ * Author:      Josh Elliott, <jelliott@ti.com>
+ * Copyright:   Copyright:   (C) 2014  Texas Instruments
+ *
+ * Based on sound/soc/codecs/spdif_transmitter.c by Steve Chen
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/slab.h>
+#include <sound/soc.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+#include <linux/of.h>
+
+#define DRV_NAME "pcm5102a"
+
+#define RATES		SNDRV_PCM_RATE_8000_96000
+#define FORMATS		(SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
+			 SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_codec_driver soc_codec_pcm5102a = {
+
+};
+
+static struct snd_soc_dai_driver pcm5102a_dai = {
+	.name		= "pcm5102a-hifi",
+	.playback 	= {
+		.stream_name	= "Playback",
+		.channels_min	= 1,
+		.channels_max	= 2,
+		.rates		= RATES,
+		.formats	= FORMATS,
+	},
+};
+
+static int pcm5102a_probe(struct platform_device *pdev)
+{
+	printk("PCM5102a probe...\n");
+
+	return snd_soc_register_codec(&pdev->dev, &soc_codec_pcm5102a,
+			&pcm5102a_dai, 1);
+}
+
+static int pcm5102a_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_codec(&pdev->dev);
+	return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id pcm5102a_dt_ids[] = {
+	{ .compatible = "ti,pcm5102a", },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, pcm5102a_dt_ids);
+#endif
+
+static struct platform_driver pcm5102a_driver = {
+	.probe		= pcm5102a_probe,
+	.remove		= pcm5102a_remove,
+	.driver		= {
+		.name	= DRV_NAME,
+		.owner	= THIS_MODULE,
+		.of_match_table = of_match_ptr(pcm5102a_dt_ids),
+	},
+};
+
+module_platform_driver(pcm5102a_driver);
+
+MODULE_AUTHOR("Josh Elliott <jelliott@ti.com>");
+MODULE_DESCRIPTION("PCM5102A dummy codec driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRV_NAME);
diff --git a/sound/soc/davinci/Kconfig b/sound/soc/davinci/Kconfig
index c2153cf..3e11da5 100644
--- a/sound/soc/davinci/Kconfig
+++ b/sound/soc/davinci/Kconfig
@@ -26,6 +26,14 @@ config SND_AM33XX_SOC_EVM
 	  AM335X-EVMSK, AM43XX-GP-EVM, and BeagelBone with AudioCape boards have
 	  this setup.

+config SND_AM33XX_SOC_PCM5102A
+	tristate "SoC Audio support for PCM5102A on BeagleBoneBlack"
+	depends on SND_DAVINCI_SOC && SOC_AM33XX
+	select SND_SOC_PCM5102A
+	help
+	  Say Y or M if you want to add support for SoC audio on AM33XX
+	  BeagleBoneBlack board using McASP and PCM5102A.
+
 config SND_AM43XX_SOC_EPOS_EVM
 	tristate "SoC Audio for the AM43XX and TLV320AIC3111 based board"
 	depends on SND_DAVINCI_SOC && SOC_AM43XX
diff --git a/sound/soc/davinci/davinci-evm.c b/sound/soc/davinci/davinci-evm.c
index 14f4049..2deafac 100644
--- a/sound/soc/davinci/davinci-evm.c
+++ b/sound/soc/davinci/davinci-evm.c
@@ -57,7 +57,9 @@ static int evm_startup(struct snd_pcm_substream *substream)
 	struct snd_soc_card *soc_card = rtd->codec->card;
 	struct clk *mclk = ((struct snd_soc_card_drvdata_davinci *)
 			    snd_soc_card_get_drvdata(soc_card))->mclk;
+
 	if (mclk)
+		printk("MCLK: %d", clk_prepare_enable(mclk));
 		return clk_prepare_enable(mclk);

 	return 0;
@@ -93,6 +95,8 @@ static int evm_hw_params(struct snd_pcm_substream *substream,
 	unsigned sysclk = ((struct snd_soc_card_drvdata_davinci *)
 			   snd_soc_card_get_drvdata(soc_card))->sysclk;

+	printk("EVM HW PARAMS\n");
+
 	/* set the codec system clock */
 	ret = snd_soc_dai_set_sysclk(codec_dai, 0, sysclk, SND_SOC_CLOCK_OUT);
 	if (ret < 0)
@@ -159,6 +163,36 @@ static int evm_tda998x_hw_params(struct snd_pcm_substream *substream,
 	return ret;
 }

+static int pcm5102a_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	struct snd_soc_codec *codec = rtd->codec;
+	struct snd_soc_card *soc_card = codec->card;
+	struct platform_device *pdev = to_platform_device(soc_card->dev);
+	unsigned int bclk_freq = evm_get_bclk(params);
+	unsigned sysclk = ((struct snd_soc_card_drvdata_davinci *)
+			   snd_soc_card_get_drvdata(soc_card))->sysclk;
+	int ret;
+
+	ret = snd_soc_dai_set_clkdiv(cpu_dai, 1, sysclk/bclk_freq);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "can't set CPU DAI clock divider %d\n",
+			ret);
+		return ret;
+	}
+
+	printk("PCM5102a hw params\n");
+	printk("sysclk=%d\n", sysclk);
+	printk("bclk_freq=%d\n", bclk_freq);
+	ret = snd_soc_dai_set_sysclk(cpu_dai, 0, sysclk, SND_SOC_CLOCK_OUT);
+	if (ret < 0)
+		return ret;
+
+	return ret;
+}
+
 static int evm_slave_codec_hw_params(struct snd_pcm_substream *substream,
 				     struct snd_pcm_hw_params *params)
 {
@@ -214,6 +248,13 @@ static struct snd_soc_ops dra7xx_ops = {
 	.hw_params = evm_slave_codec_hw_params,
 };

+static struct snd_soc_ops pcm5102a_ops = {
+	.startup = evm_startup,
+	.shutdown = evm_shutdown,
+	.hw_params = pcm5102a_hw_params,
+};
+
+
 /* davinci-evm machine dapm widgets */
 static const struct snd_soc_dapm_widget aic3x_dapm_widgets[] = {
 	SND_SOC_DAPM_HP("Headphone Jack", NULL),
@@ -584,6 +625,15 @@ static struct snd_soc_card da850_snd_soc_card = {
  * The structs are used as place holders. They will be completely
  * filled with data from dt node.
  */
+static struct snd_soc_dai_link evm_dai_pcm5102a = {
+	.name		= "PCM5102A",
+	.stream_name	= "Playback",
+	.codec_dai_name	= "pcm5102a-hifi",
+	.ops            = &pcm5102a_ops,
+	.dai_fmt 	= (SND_SOC_DAIFMT_CBS_CFS | SND_SOC_DAIFMT_I2S |
+			   SND_SOC_DAIFMT_IB_NF),
+};
+
 static struct snd_soc_dai_link evm_dai_tlv320aic3x = {
 	.name		= "TLV320AIC3X",
 	.stream_name	= "AIC3X",
@@ -627,6 +677,10 @@ static struct snd_soc_dai_link evm_dai_tlv320aic3111 = {

 static const struct of_device_id davinci_evm_dt_ids[] = {
 	{
+		.compatible = "ti,pcm5102a-evm-audio",
+		.data = &evm_dai_pcm5102a,
+	},
+	{
 		.compatible = "ti,da830-evm-audio",
 		.data = &evm_dai_tlv320aic3x,
 	},
--
1.7.9.5

Archived

Sitara Linux SDK 07.0x