[alsa-devel] ASoC: BeagleBoard driver development (PCM3168)
Hello Peter
I am stuck again in my ASoc driver development and it would be really nice if you could give me an hint how i could solve this.
Acctually i try to connect my PCM3168 Audio Codec to the McBSP on the BeagleBoard. My new driver loads fine and i got 2 soundcards:
*root@beagleboard:/lib/modules/3.7.4+/kernel/sound/soc/omap# aplay -l* **** List of PLAYBACK Hardware Devices **** card 0: omap3beagle [omap3beagle], device 0: TWL4030 twl4030-hifi-0 [] Subdevices: 1/1 Subdevice #0: subdevice #0 card 1: pcm3168 [pcm3168], device 0: PCM3168 pcm3168-hifi-0 [] Subdevices: 1/1 Subdevice #0: subdevice #0
Your TWL4030 driver is still working perfect with:
*root@beagleboard:/lib/modules/3.7.4+/kernel/sound/soc/omap# aplay -Dhw:0,0 /home/root/fifi.wav* [ 1245.302551] omap-dma-engine omap-dma-engine: allocating channel for 33 Playing WAVE '/home/root/fifi.wav' : Signed 16 bit Little Endian, Rate 44100 Hz, Stereo
but when i play the same file with my new driver i get an error (as codec master and also as codec slave):
*root@beagleboard:/lib/modules/3.7.4+/kernel/sound/soc/omap# aplay -Dhw:1,0 /home/root/fifi.wav* [ 1128.677337] omap-dma-engine omap-dma-engine: allocating channel for 17 Playing WAVE '/h[ 1128.689544] can't set codec DAI configuration - pcm3168 ome/root/fifi.wa[ 1128.695281] asoc: machine hw_params failed: -524 v' : Signed 16 b[ 1128.702514] omap-dma-engine omap-dma-engine: freeing channel for 17 it Little Endian, Rate 44100 Hz, Stereo aplay: set_params:1145: Unable to install hw params: ACCESS: RW_INTERLEAVED FORMAT: S16_LE SUBFORMAT: STD SAMPLE_BITS: 16 FRAME_BITS: 32 CHANNELS: 2 RATE: 44100 PERIOD_TIME: (124988 124989) PERIOD_SIZE: 5512 PERIOD_BYTES: 22048 PERIODS: 5 BUFFER_TIME: (624943 624944) BUFFER_SIZE: 27560 BUFFER_BYTES: 110240 TICK_TIME: 0
To check whether my machine driver (/omap-pcm3168.c/) is working i tried to implement the /twl4030.c/ codec driver into it (and deactivated the omap_twl4030_audio_init() function), which worked just fine. With this setup i changed the /.cpu_dai_name = "omap-mcbsp.2"/ to /.cpu_dai_name = "omap-mcbsp.3"/ and i adjusted the codec as slave (/SND_SOC_DAIFMT_CBS_CFS/) to test whether the McBSP3 setup is right (the PCM3168 codec was disconnected). I still got a clock signal on the McBSP2 but the McBSP2_DX had no signal anymore and on the McBSP3 i did not get any signal at all (measured with an osziloscope). I am a little bit confused as i am not shure why there is still the clk signal on the McBSP2 and furthermore i am not shure whether i test the McBSP3 properly or whether there is a proplem in the McBSP3 setup. The PinMUX for the McBSP3 is done in the kernel (/board-omap3beagle.c/) by setting:
static void __init omap3_beagle_config_mcbsp3_mux(void) { omap_mux_init_signal("mcbsp3_fsx.mcbsp3_fsx", OMAP_PIN_INPUT); omap_mux_init_signal("uart2_cts.mcbsp3_dx", OMAP_PIN_OUTPUT); omap_mux_init_signal("uart2_rts.mcbsp3_dr", OMAP_PIN_INPUT); /* NOTE: Clock pins need to be in input mode */ omap_mux_init_signal("uart2_tx.mcbsp3_clkx", OMAP_PIN_INPUT); }
It would be really nice if you could help me.
Thanks Wendelin
****************************************************************************************************** * kernel_3.7.4+/sound/soc/omap/omap-pcm3168.c * ****************************************************************************************************** /* * PCM3168 ASoC driver for BeagleBoard. * * based on: * > omap3beagle.c -- SoC audio for OMAP3 Beagle * > Author: Steve Sakoman steve@sakoman.com * * adapted by Klimann Wendelin wklimann@hotmail.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. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */
#include <linux/clk.h> #include <linux/platform_device.h> #include <linux/module.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/soc.h> #include <sound/pcm_params.h>
#include <asm/mach-types.h> #include <mach/hardware.h> #include <mach/gpio.h> //#include <plat/mcbsp.h>
#include "omap-mcbsp.h" #include "omap-pcm.h"
/* * Uncomment to test codec in slave mode or without actual codec. This makes * possible to test this driver by letting the OMAP to be DAI link master */ #define PCM3168_CODEC_SLAVE 1
static int pcm3168_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 *codec_dai = rtd->codec_dai; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; unsigned int fmt, div; int ret;
#ifdef PCM3168_CODEC_SLAVE fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBS_CFS | SND_SOC_DAIFMT_IB_NF; #else fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBM_CFM | SND_SOC_DAIFMT_IB_NF; #endif
/* Set codec DAI configuration */ ret = snd_soc_dai_set_fmt(codec_dai, fmt); if (ret < 0) { printk(KERN_ERR "can't set codec DAI configuration - pcm3168\n"); return ret; }
/* Set cpu DAI configuration */ ret = snd_soc_dai_set_fmt(cpu_dai, fmt); if (ret < 0) { printk(KERN_ERR "can't set cpu DAI configuration - pcm3168\n"); return ret; }
#ifdef PCM3168_CODEC_SLAVE
ret = snd_soc_dai_set_sysclk(cpu_dai, OMAP_MCBSP_SYSCLK_CLKS_FCLK, 48000000, SND_SOC_CLOCK_IN); if (ret < 0) { pr_err("can't set McBSP sysclk - pcm3168\n"); return ret; }
/* * Calculate McBSP SRG divisor in McBSP master mode */ div = 48000000 / params_rate(params) / params_channels(params);
switch (params_format(params)) { case SNDRV_PCM_FORMAT_S16_LE: div /= 16; break; case SNDRV_PCM_FORMAT_S24_LE: case SNDRV_PCM_FORMAT_S32_LE: div /= 32; break; };
/* * Round to maximum divisor if needed. This means that extra bit-clock * cycles are transmitted when sample rate and number of bits in frame * (channels * sample bits) are low. */ if (div >= 256) div = 256;
ret = snd_soc_dai_set_clkdiv(cpu_dai, OMAP_MCBSP_CLKGDV, div); if (ret < 0) { pr_err("can't set SRG clock divider - pcm3168\n"); return ret; }
#endif
return 0; }
static struct snd_soc_ops pcm3168_ops = { .hw_params = pcm3168_hw_params, };
/* Digital audio interface glue - connects codec <--> CPU */ static struct snd_soc_dai_link pcm3168_dai = { .name = "PCM3168", .stream_name = "PCM3168", .cpu_dai_name = "omap-mcbsp.3", .platform_name = "omap-pcm-audio", .codec_dai_name = "pcm3168-hifi", .codec_name = "pcm3168-codec.0", .ops = &pcm3168_ops, };
/* Audio machine driver */ static struct snd_soc_card snd_soc_pcm3168 = { .name = "pcm3168", .owner = THIS_MODULE, .dai_link = &pcm3168_dai, .num_links = 1, };
struct platform_device pcm3168_codec = { .name = "pcm3168-codec", .id = 0, };
struct platform_device pcm3168_soc_audio = { .name = "pcm3168-soc-audio", .id = 0, };
static int __devinit pcm3168_soc_probe(struct platform_device *pdev) { struct snd_soc_card *card = &snd_soc_pcm3168; int ret;
pr_info("OMAP3 Beagle - PCM3168 ASoC init\n");
card->dev = &pdev->dev;
ret = snd_soc_register_card(card); if (ret) { dev_err(&pdev->dev, "snd_soc_register_card() failed: %d - pcm3168\n", ret); return ret; }
return 0; }
static int __devexit pcm3168_soc_remove(struct platform_device *pdev) { struct snd_soc_card *card = platform_get_drvdata(pdev);
snd_soc_unregister_card(card);
return 0; }
static struct platform_driver pcm3168_driver = { .driver = { .name = "pcm3168-soc-audio", .owner = THIS_MODULE, },
.probe = pcm3168_soc_probe, .remove = __devexit_p(pcm3168_soc_remove), };
static int __init pcm3168_soc_init(void) { platform_device_register(&pcm3168_codec); platform_device_register(&pcm3168_soc_audio); return platform_driver_register(&pcm3168_driver); } module_init(pcm3168_soc_init);
static void __exit pcm3168_soc_exit(void) { platform_driver_unregister(&pcm3168_driver); platform_device_unregister(&pcm3168_soc_audio); platform_device_unregister(&pcm3168_codec); } module_exit(pcm3168_soc_exit);
MODULE_AUTHOR("Klimann Wendelin wklimann@hotmail.com"); MODULE_DESCRIPTION("ALSA SoC PCM3168 add on Soundcard"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:pcm3168-soc-audio");
****************************************************************************************************** * kernel_3.7.4+/sound/soc/codecs/pcm3168.c * ****************************************************************************************************** /* * ALSA Soc PCM3168 codec support * * Author: Klimann Wendelin wklimann@hotmail.com * * based on: * > pcm3008.c * > Author: Hugo Villeneuve * > Copyright (C) 2008 Lyrtech inc * > * > Based on AC97 Soc codec, original copyright follow: * > Copyright 2005 Wolfson Microelectronics PLC. * * 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. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA */
#include <linux/init.h> #include <linux/kernel.h> #include <linux/device.h> #include <linux/gpio.h> #include <linux/slab.h> #include <linux/module.h> #include <linux/clk.h> #include <linux/platform_device.h> #include <linux/module.h> #include <sound/core.h> #include <sound/initval.h> #include <sound/pcm.h> #include <sound/pcm_params.h> //#include <sound/soc-dapm.h> #include <sound/soc.h>
#include <asm/mach-types.h> #include <mach/hardware.h> #include <mach/gpio.h> //#include <plat/mcbsp.h>
#include "../omap/omap-mcbsp.h" #include "../omap/omap-pcm.h"
#include "pcm3168.h"
#define PCM3168_VERSION "0.1"
#define PCM3168_RATES SNDRV_PCM_RATE_8000_96000 /*(SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ SNDRV_PCM_RATE_48000)*/ #define PCM3168_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE)
static struct snd_soc_dai_driver pcm3168_dai = {
.name = "pcm3168-hifi", .playback = { .stream_name = "PCM3168 Playback", .channels_min = 2, .channels_max = 8, .rates = PCM3168_RATES, .formats = PCM3168_FORMATS, //SNDRV_PCM_FMTBIT_S16_LE, //SNDRV_PCM_FMTBIT_S32_LE .sig_bits = 24, }, .capture = { .stream_name = "PCM3168 Capture", .channels_min = 2, .channels_max = 6, .rates = PCM3168_RATES, .formats = PCM3168_FORMATS, //SNDRV_PCM_FMTBIT_S16_LE, //SNDRV_PCM_FMTBIT_S32_LE .sig_bits = 24, }, };
static void pcm3168_gpio_free(struct pcm3168_setup_data *setup) { // gpio_free(setup->dem0_pin); // gpio_free(setup->dem1_pin); // gpio_free(setup->pdad_pin); // gpio_free(setup->pdda_pin); }
static int pcm3168_soc_probe(struct snd_soc_codec *codec) { struct pcm3168_setup_data *setup = codec->dev->platform_data; int ret = 0;
printk(KERN_ALERT "in SOC Probe codec -> kli \n");
printk(KERN_INFO "PCM3168 SoC Audio Codec %s\n", PCM3168_VERSION);
return ret;
gpio_err: // pcm3168_gpio_free(setup);
return ret; }
static int pcm3168_soc_remove(struct snd_soc_codec *codec) { struct pcm3168_setup_data *setup = codec->dev->platform_data;
// pcm3168_gpio_free(setup); return 0; }
#define pcm3168_soc_suspend NULL #define pcm3168_soc_resume NULL
static struct snd_soc_codec_driver soc_codec_dev_pcm3168 = { .probe = pcm3168_soc_probe, .remove = pcm3168_soc_remove, .suspend = pcm3168_soc_suspend, .resume = pcm3168_soc_resume, };
static int __devinit pcm3168_codec_probe(struct platform_device *pdev) { int ret; printk(KERN_ALERT "probe pcm3168 codec -> kli \n");
ret = snd_soc_register_codec(&pdev->dev, &soc_codec_dev_pcm3168, &pcm3168_dai, 1);
printk(KERN_ALERT "probe_after pcm3168 codec -> kli = %d \n", ret);
return ret; }
static int __devexit pcm3168_codec_remove(struct platform_device *pdev) { printk(KERN_ALERT "remove pcm3168 codec -> kli \n");
snd_soc_unregister_codec(&pdev->dev); return 0; }
MODULE_ALIAS("platform:pcm3168-codec");
static struct platform_driver pcm3168_codec_driver = { .probe = pcm3168_codec_probe, .remove = __devexit_p(pcm3168_codec_remove), .driver = { .name = "pcm3168-codec", .owner = THIS_MODULE, }, };
static int __init pcm3168_modinit(void) { printk(KERN_ALERT "in init of 3168 -> kli \n"); return platform_driver_register(&pcm3168_codec_driver); } module_init(pcm3168_modinit);
static void __exit pcm3168_exit(void) { printk(KERN_ALERT "in exit of 3168 -> kli \n"); platform_driver_unregister(&pcm3168_codec_driver); } module_exit(pcm3168_exit);
MODULE_DESCRIPTION("Soc PCM3168 driver"); MODULE_AUTHOR("Klimann Wendelin wklimann@hotmail.com"); MODULE_LICENSE("GPL");
On 23.03.2013 15:37, Wendelin Klimann wrote:
Hello Peter
I am stuck again in my ASoc driver development and it would be really nice if you could give me an hint how i could solve this.
Acctually i try to connect my PCM3168 Audio Codec to the McBSP on the BeagleBoard. My new driver loads fine and i got 2 soundcards:
*root@beagleboard:/lib/modules/3.7.4+/kernel/sound/soc/omap# aplay -l* **** List of PLAYBACK Hardware Devices **** card 0: omap3beagle [omap3beagle], device 0: TWL4030 twl4030-hifi-0 [] Subdevices: 1/1 Subdevice #0: subdevice #0 card 1: pcm3168 [pcm3168], device 0: PCM3168 pcm3168-hifi-0 [] Subdevices: 1/1 Subdevice #0: subdevice #0
Your TWL4030 driver is still working perfect with:
*root@beagleboard:/lib/modules/3.7.4+/kernel/sound/soc/omap# aplay -Dhw:0,0 /home/root/fifi.wav* [ 1245.302551] omap-dma-engine omap-dma-engine: allocating channel for 33 Playing WAVE '/home/root/fifi.wav' : Signed 16 bit Little Endian, Rate 44100 Hz, Stereo
but when i play the same file with my new driver i get an error (as codec master and also as codec slave):
*root@beagleboard:/lib/modules/3.7.4+/kernel/sound/soc/omap# aplay -Dhw:1,0 /home/root/fifi.wav* [ 1128.677337] omap-dma-engine omap-dma-engine: allocating channel for 17 Playing WAVE '/h[ 1128.689544] can't set codec DAI configuration - pcm3168
This is where your trouble starts, and the reason is that when you call snd_soc_dai_set_fmt() on your codec dai, the core will do this:
if (dai->driver->ops->set_fmt == NULL) return -ENOTSUPP;
And because you didn't implement that callback in your codec driver, the setup will fail. You need to implement that callback, and acknowledge that the codec is able to operate under the wanted conditions. See other codec drivers for a reference.
Also, you seem to wildly mix machine and codec code, which is exactly what ASoC tries to prevent. Codec drivers are completely separated from the machine part, because they are supposed to be exchangeable. See what other machine code and codec drivers do. In general, as soon you as you import a machine or platform specific header file from your codec code, you're doing something wrong.
HTH, Daniel
Hello Daniel
*root@beagleboard:/lib/modules/3.7.4+/kernel/sound/soc/omap# aplay
-Dhw:1,0 /home/root/fifi.wav* [ 1128.677337] omap-dma-engine omap-dma-engine: allocating channel for 17 Playing WAVE '/h[ 1128.689544] can't set codec DAI configuration - pcm3168
This is where your trouble starts, and the reason is that when you call snd_soc_dai_set_fmt() on your codec dai, the core will do this:
if (dai->driver->ops->set_fmt == NULL) return -ENOTSUPP;
And because you didn't implement that callback in your codec driver, the setup will fail. You need to implement that callback, and acknowledge that the codec is able to operate under the wanted conditions. See other codec drivers for a reference.
Thanks that helped to understand the problem and i was able to measure first signals on my McBSP :-)
Also, you seem to wildly mix machine and codec code, which is exactly what ASoC tries to prevent. Codec drivers are completely separated from the machine part, because they are supposed to be exchangeable. See what other machine code and codec drivers do. In general, as soon you as you import a machine or platform specific header file from your codec code, you're doing something wrong.
well i had several headers in my codec file which were not used but i think there is no code which is mixed between the codec- and the machine-driver. Next time i will clean up my code before sending it.
Thanks a lot for the fast answer Wendelin
ps.: The cleaned up files are added, if there are still mixes between the codec- and machine-driver i would be glad if you could tell me.
****************************************************************************************************** * kernel_3.7.4+/sound/soc/codecs/pcm3168.c * ****************************************************************************************************** /* * ALSA Soc PCM3168 codec support * * Author: Klimann Wendelin wklimann@hotmail.com * * based on: * > pcm3008.c * > Author: Hugo Villeneuve * > Copyright (C) 2008 Lyrtech inc * > * > Based on AC97 Soc codec, original copyright follow: * > Copyright 2005 Wolfson Microelectronics PLC. * * 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. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA */
#include <linux/init.h> #include <linux/kernel.h> #include <linux/device.h> #include <linux/gpio.h> #include <linux/slab.h> #include <linux/module.h> #include <linux/clk.h> #include <linux/platform_device.h> #include <linux/module.h> #include <sound/core.h> #include <sound/initval.h> #include <sound/pcm.h> #include <sound/pcm_params.h> #include <sound/soc.h>
#include "pcm3168.h"
#define PCM3168_VERSION "0.1"
static int pcm3168_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_codec *codec = rtd->codec;
dev_dbg(codec->dev, "Sample format 0x%X\n", params_format(params)); dev_dbg(codec->dev, "Channels %d\n", params_channels(params)); dev_dbg(codec->dev, "Rate %d\n", params_rate(params));
return 0; }
static int pcm3168_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { struct snd_soc_codec *codec = codec_dai->codec;
/* codec role */ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { case SND_SOC_DAIFMT_CBM_CFM: dev_dbg(codec->dev, "Codec is master\n"); break; case SND_SOC_DAIFMT_CBS_CFS: dev_dbg(codec->dev, "Codec is slave\n"); break; default: return -EINVAL; }
/* DAI format */ dev_dbg(codec->dev, "DAI format 0x%X", fmt & SND_SOC_DAIFMT_FORMAT_MASK);
/* Bit clock and frame sync polarities */ dev_dbg(codec->dev, "Clock polarities 0x%X\n", fmt & SND_SOC_DAIFMT_INV_MASK);
return 0; }
#define PCM3168_RATES SNDRV_PCM_RATE_8000_96000 /*(SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ SNDRV_PCM_RATE_48000)*/ #define PCM3168_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE)
static const struct snd_soc_dai_ops pcm3168_dai_hifi_ops = { .hw_params = pcm3168_hw_params, .set_fmt = pcm3168_set_dai_fmt, };
static struct snd_soc_dai_driver pcm3168_dai = { .name = "pcm3168-hifi", .playback = { .stream_name = "PCM3168 Playback", .channels_min = 2, .channels_max = 8, .rates = PCM3168_RATES, .formats = PCM3168_FORMATS, //SNDRV_PCM_FMTBIT_S16_LE, //SNDRV_PCM_FMTBIT_S32_LE .sig_bits = 24, }, .capture = { .stream_name = "PCM3168 Capture", .channels_min = 2, .channels_max = 6, .rates = PCM3168_RATES, .formats = PCM3168_FORMATS, //SNDRV_PCM_FMTBIT_S16_LE, //SNDRV_PCM_FMTBIT_S32_LE .sig_bits = 24, }, .ops = &pcm3168_dai_hifi_ops, };
static int pcm3168_soc_probe(struct snd_soc_codec *codec) { struct pcm3168_setup_data *setup = codec->dev->platform_data; int ret = 0;
printk(KERN_INFO "PCM3168 SoC Audio Codec %s\n", PCM3168_VERSION);
return ret; }
static int pcm3168_soc_remove(struct snd_soc_codec *codec) { struct pcm3168_setup_data *setup = codec->dev->platform_data;
return 0; }
#define pcm3168_soc_suspend NULL #define pcm3168_soc_resume NULL
static struct snd_soc_codec_driver soc_codec_dev_pcm3168 = { .probe = pcm3168_soc_probe, .remove = pcm3168_soc_remove, .suspend = pcm3168_soc_suspend, .resume = pcm3168_soc_resume, };
static int __devinit pcm3168_codec_probe(struct platform_device *pdev) { int ret; printk(KERN_ALERT "probe pcm3168 codec -> kli \n");
ret = snd_soc_register_codec(&pdev->dev, &soc_codec_dev_pcm3168, &pcm3168_dai, 1);
printk(KERN_ALERT "probe_after pcm3168 codec -> kli = %d \n", ret);
return ret; }
static int __devexit pcm3168_codec_remove(struct platform_device *pdev) { printk(KERN_ALERT "remove pcm3168 codec -> kli \n");
snd_soc_unregister_codec(&pdev->dev); return 0; }
static struct platform_driver pcm3168_codec_driver = { .probe = pcm3168_codec_probe, .remove = __devexit_p(pcm3168_codec_remove), .driver = { .name = "pcm3168-codec", .owner = THIS_MODULE, }, };
static int __init pcm3168_modinit(void) { printk(KERN_ALERT "in init of 3168 -> kli \n"); return platform_driver_register(&pcm3168_codec_driver); } module_init(pcm3168_modinit);
static void __exit pcm3168_exit(void) { printk(KERN_ALERT "in exit of 3168 -> kli \n"); platform_driver_unregister(&pcm3168_codec_driver); } module_exit(pcm3168_exit);
MODULE_DESCRIPTION("Soc PCM3168 driver"); MODULE_AUTHOR("Klimann Wendelin wklimann@hotmail.com"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:pcm3168-codec");
****************************************************************************************************** * kernel_3.7.4+/sound/soc/omap/omap-pcm3168.c * ****************************************************************************************************** /* * PCM3168 ASoC driver for BeagleBoard. * * based on: * > omap3beagle.c -- SoC audio for OMAP3 Beagle * > Author: Steve Sakoman steve@sakoman.com * * adapted by Klimann Wendelin wklimann@hotmail.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. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */
#include <linux/clk.h> #include <linux/platform_device.h> #include <linux/module.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/soc.h> #include <sound/pcm_params.h>
#include <asm/mach-types.h> #include <mach/hardware.h> #include <mach/gpio.h> //#include <plat/mcbsp.h>
#include "omap-mcbsp.h" #include "omap-pcm.h"
/* * Uncomment to test codec in slave mode or without actual codec. This makes * possible to test this driver by letting the OMAP to be DAI link master */ #define PCM3168_CODEC_SLAVE 1
static int pcm3168_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 *codec_dai = rtd->codec_dai; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; unsigned int fmt, div; int ret;
#ifdef PCM3168_CODEC_SLAVE fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBS_CFS | SND_SOC_DAIFMT_IB_NF; #else fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBM_CFM | SND_SOC_DAIFMT_IB_NF; #endif
/* Set codec DAI configuration */ ret = snd_soc_dai_set_fmt(codec_dai, fmt); if (ret < 0) { printk(KERN_ERR "can't set codec DAI configuration - pcm3168\n"); return ret; }
/* Set cpu DAI configuration */ ret = snd_soc_dai_set_fmt(cpu_dai, fmt); if (ret < 0) { printk(KERN_ERR "can't set cpu DAI configuration - pcm3168\n"); return ret; }
#ifdef PCM3168_CODEC_SLAVE
ret = snd_soc_dai_set_sysclk(cpu_dai, OMAP_MCBSP_SYSCLK_CLKS_FCLK, 48000000, SND_SOC_CLOCK_IN); if (ret < 0) { pr_err("can't set McBSP sysclk - pcm3168\n"); return ret; }
/* * Calculate McBSP SRG divisor in McBSP master mode */ div = 48000000 / params_rate(params) / params_channels(params);
switch (params_format(params)) { case SNDRV_PCM_FORMAT_S16_LE: div /= 16; break; case SNDRV_PCM_FORMAT_S24_LE: case SNDRV_PCM_FORMAT_S32_LE: div /= 32; break; };
/* * Round to maximum divisor if needed. This means that extra bit-clock * cycles are transmitted when sample rate and number of bits in frame * (channels * sample bits) are low. */ if (div >= 256) div = 256;
ret = snd_soc_dai_set_clkdiv(cpu_dai, OMAP_MCBSP_CLKGDV, div); if (ret < 0) { pr_err("can't set SRG clock divider - pcm3168\n"); return ret; } #endif
return 0; }
static struct snd_soc_ops pcm3168_ops = { .hw_params = pcm3168_hw_params, };
/* Digital audio interface glue - connects codec <--> CPU */ static struct snd_soc_dai_link pcm3168_dai = { .name = "PCM3168", .stream_name = "PCM3168", .cpu_dai_name = "omap-mcbsp.3", .platform_name = "omap-pcm-audio", .codec_dai_name = "pcm3168-hifi", .codec_name = "pcm3168-codec.0", .ops = &pcm3168_ops, };
/* Audio machine driver */ static struct snd_soc_card snd_soc_pcm3168 = { .name = "pcm3168", .owner = THIS_MODULE, .dai_link = &pcm3168_dai, .num_links = 1, };
struct platform_device pcm3168_codec = { .name = "pcm3168-codec", .id = 0, };
struct platform_device pcm3168_soc_audio = { .name = "pcm3168-soc-audio", .id = 0, };
static int __devinit pcm3168_soc_probe(struct platform_device *pdev) { struct snd_soc_card *card = &snd_soc_pcm3168; int ret;
pr_info("OMAP3 Beagle - PCM3168 ASoC init\n"); card->dev = &pdev->dev;
ret = snd_soc_register_card(card); if (ret) { dev_err(&pdev->dev, "snd_soc_register_card() failed: %d - pcm3168\n", ret); return ret; } return 0; }
static int __devexit pcm3168_soc_remove(struct platform_device *pdev) { struct snd_soc_card *card = platform_get_drvdata(pdev); snd_soc_unregister_card(card);
return 0; }
static struct platform_driver pcm3168_driver = { .driver = { .name = "pcm3168-soc-audio", .owner = THIS_MODULE, }, .probe = pcm3168_soc_probe, .remove = __devexit_p(pcm3168_soc_remove), };
static int __init pcm3168_soc_init(void) { platform_device_register(&pcm3168_codec); platform_device_register(&pcm3168_soc_audio); return platform_driver_register(&pcm3168_driver); } module_init(pcm3168_soc_init);
static void __exit pcm3168_soc_exit(void) { platform_driver_unregister(&pcm3168_driver); platform_device_unregister(&pcm3168_soc_audio); platform_device_unregister(&pcm3168_codec); } module_exit(pcm3168_soc_exit);
MODULE_AUTHOR("Klimann Wendelin wklimann@hotmail.com"); MODULE_DESCRIPTION("ALSA SoC PCM3168 add on Soundcard"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:pcm3168-soc-audio");
On 23.03.2013 22:56, wendelin klimann wrote:
The cleaned up files are added, if there are still mixes between the codec- and machine-driver i would be glad if you could tell me.
It looks much better already, but your codec functions are currently just stubs. So you need to fill them with logic that actually sets up the hardware of course, and you should use regmap for abstracting the registers.
Also, the codec driver has to become a i2c/spi driver rather than a platform device. There are tons of other drivers for reference.
There are many smaller things to comment on, but you can submit the codec driver first once you're finished, so it can be reviewed independently from the rest. Note, however, that all new drivers have to be written in a device-tree aware fashion.
Daniel
kernel_3.7.4+/sound/soc/codecs/pcm3168.c
/*
- ALSA Soc PCM3168 codec support
- Author: Klimann Wendelin <wklimann@hotmail.com
- based on:
pcm3008.c Author: Hugo Villeneuve Copyright (C) 2008 Lyrtech inc Based on AC97 Soc codec, original copyright follow: Copyright 2005 Wolfson Microelectronics PLC.- 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.
- This program is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- 02110-1301 USA
*/
#include <linux/init.h> #include <linux/kernel.h> #include <linux/device.h> #include <linux/gpio.h> #include <linux/slab.h> #include <linux/module.h> #include <linux/clk.h> #include <linux/platform_device.h> #include <linux/module.h> #include <sound/core.h> #include <sound/initval.h> #include <sound/pcm.h> #include <sound/pcm_params.h> #include <sound/soc.h>
#include "pcm3168.h"
#define PCM3168_VERSION "0.1"
static int pcm3168_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_codec *codec = rtd->codec;
dev_dbg(codec->dev, "Sample format 0x%X\n", params_format(params)); dev_dbg(codec->dev, "Channels %d\n", params_channels(params)); dev_dbg(codec->dev, "Rate %d\n", params_rate(params)); return 0;
}
static int pcm3168_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { struct snd_soc_codec *codec = codec_dai->codec;
/* codec role */ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { case SND_SOC_DAIFMT_CBM_CFM: dev_dbg(codec->dev, "Codec is master\n"); break; case SND_SOC_DAIFMT_CBS_CFS: dev_dbg(codec->dev, "Codec is slave\n"); break; default: return -EINVAL; } /* DAI format */ dev_dbg(codec->dev, "DAI format 0x%X", fmt & SND_SOC_DAIFMT_FORMAT_MASK); /* Bit clock and frame sync polarities */ dev_dbg(codec->dev, "Clock polarities 0x%X\n", fmt & SND_SOC_DAIFMT_INV_MASK); return 0;
}
#define PCM3168_RATES SNDRV_PCM_RATE_8000_96000 /*(SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ SNDRV_PCM_RATE_48000)*/ #define PCM3168_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE)
static const struct snd_soc_dai_ops pcm3168_dai_hifi_ops = { .hw_params = pcm3168_hw_params, .set_fmt = pcm3168_set_dai_fmt, };
static struct snd_soc_dai_driver pcm3168_dai = { .name = "pcm3168-hifi", .playback = { .stream_name = "PCM3168 Playback", .channels_min = 2, .channels_max = 8, .rates = PCM3168_RATES, .formats = PCM3168_FORMATS, //SNDRV_PCM_FMTBIT_S16_LE, //SNDRV_PCM_FMTBIT_S32_LE .sig_bits = 24, }, .capture = { .stream_name = "PCM3168 Capture", .channels_min = 2, .channels_max = 6, .rates = PCM3168_RATES, .formats = PCM3168_FORMATS, //SNDRV_PCM_FMTBIT_S16_LE, //SNDRV_PCM_FMTBIT_S32_LE .sig_bits = 24, }, .ops = &pcm3168_dai_hifi_ops, };
static int pcm3168_soc_probe(struct snd_soc_codec *codec) { struct pcm3168_setup_data *setup = codec->dev->platform_data; int ret = 0;
printk(KERN_INFO "PCM3168 SoC Audio Codec %s\n", PCM3168_VERSION); return ret;
}
static int pcm3168_soc_remove(struct snd_soc_codec *codec) { struct pcm3168_setup_data *setup = codec->dev->platform_data;
return 0;
}
#define pcm3168_soc_suspend NULL #define pcm3168_soc_resume NULL
static struct snd_soc_codec_driver soc_codec_dev_pcm3168 = { .probe = pcm3168_soc_probe, .remove = pcm3168_soc_remove, .suspend = pcm3168_soc_suspend, .resume = pcm3168_soc_resume, };
static int __devinit pcm3168_codec_probe(struct platform_device *pdev) { int ret; printk(KERN_ALERT "probe pcm3168 codec -> kli \n");
ret = snd_soc_register_codec(&pdev->dev, &soc_codec_dev_pcm3168, &pcm3168_dai, 1); printk(KERN_ALERT "probe_after pcm3168 codec -> kli = %d \n", ret); return ret;
}
static int __devexit pcm3168_codec_remove(struct platform_device *pdev) { printk(KERN_ALERT "remove pcm3168 codec -> kli \n");
snd_soc_unregister_codec(&pdev->dev); return 0;
}
static struct platform_driver pcm3168_codec_driver = { .probe = pcm3168_codec_probe, .remove = __devexit_p(pcm3168_codec_remove), .driver = { .name = "pcm3168-codec", .owner = THIS_MODULE, }, };
static int __init pcm3168_modinit(void) { printk(KERN_ALERT "in init of 3168 -> kli \n"); return platform_driver_register(&pcm3168_codec_driver); } module_init(pcm3168_modinit);
static void __exit pcm3168_exit(void) { printk(KERN_ALERT "in exit of 3168 -> kli \n"); platform_driver_unregister(&pcm3168_codec_driver); } module_exit(pcm3168_exit);
MODULE_DESCRIPTION("Soc PCM3168 driver"); MODULE_AUTHOR("Klimann Wendelin <wklimann@hotmail.com mailto:wklimann@hotmail.com>"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:pcm3168-codec");
kernel_3.7.4+/sound/soc/omap/omap-pcm3168.c
/*
- PCM3168 ASoC driver for BeagleBoard.
- based on:
omap3beagle.c -- SoC audio for OMAP3 Beagle Author: Steve Sakoman <steve@sakoman.com mailto:steve@sakoman.com>- adapted by Klimann Wendelin <wklimann@hotmail.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.
- This program is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- 02110-1301 USA
*/
#include <linux/clk.h> #include <linux/platform_device.h> #include <linux/module.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/soc.h> #include <sound/pcm_params.h>
#include <asm/mach-types.h> #include <mach/hardware.h> #include <mach/gpio.h> //#include <plat/mcbsp.h>
#include "omap-mcbsp.h" #include "omap-pcm.h"
/*
- Uncomment to test codec in slave mode or without actual codec. This makes
- possible to test this driver by letting the OMAP to be DAI link master
*/ #define PCM3168_CODEC_SLAVE 1
static int pcm3168_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 *codec_dai = rtd->codec_dai; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; unsigned int fmt, div; int ret;
#ifdef PCM3168_CODEC_SLAVE fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBS_CFS | SND_SOC_DAIFMT_IB_NF; #else fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBM_CFM | SND_SOC_DAIFMT_IB_NF; #endif
/* Set codec DAI configuration */ ret = snd_soc_dai_set_fmt(codec_dai, fmt); if (ret < 0) { printk(KERN_ERR "can't set codec DAI configuration - pcm3168\n"); return ret; } /* Set cpu DAI configuration */ ret = snd_soc_dai_set_fmt(cpu_dai, fmt); if (ret < 0) { printk(KERN_ERR "can't set cpu DAI configuration - pcm3168\n"); return ret; }
#ifdef PCM3168_CODEC_SLAVE
ret = snd_soc_dai_set_sysclk(cpu_dai, OMAP_MCBSP_SYSCLK_CLKS_FCLK, 48000000, SND_SOC_CLOCK_IN); if (ret < 0) { pr_err("can't set McBSP sysclk - pcm3168\n"); return ret; } /* * Calculate McBSP SRG divisor in McBSP master mode */ div = 48000000 / params_rate(params) / params_channels(params); switch (params_format(params)) { case SNDRV_PCM_FORMAT_S16_LE: div /= 16; break; case SNDRV_PCM_FORMAT_S24_LE: case SNDRV_PCM_FORMAT_S32_LE: div /= 32; break; }; /* * Round to maximum divisor if needed. This means that extra bit-clock * cycles are transmitted when sample rate and number of bits in frame * (channels * sample bits) are low. */ if (div >= 256) div = 256; ret = snd_soc_dai_set_clkdiv(cpu_dai, OMAP_MCBSP_CLKGDV, div); if (ret < 0) { pr_err("can't set SRG clock divider - pcm3168\n"); return ret; }
#endif
return 0;
}
static struct snd_soc_ops pcm3168_ops = { .hw_params = pcm3168_hw_params, };
/* Digital audio interface glue - connects codec <--> CPU */ static struct snd_soc_dai_link pcm3168_dai = { .name = "PCM3168", .stream_name = "PCM3168", .cpu_dai_name = "omap-mcbsp.3", .platform_name = "omap-pcm-audio", .codec_dai_name = "pcm3168-hifi", .codec_name = "pcm3168-codec.0", .ops = &pcm3168_ops, };
/* Audio machine driver */ static struct snd_soc_card snd_soc_pcm3168 = { .name = "pcm3168", .owner = THIS_MODULE, .dai_link = &pcm3168_dai, .num_links = 1, };
struct platform_device pcm3168_codec = { .name = "pcm3168-codec", .id = 0, };
struct platform_device pcm3168_soc_audio = { .name = "pcm3168-soc-audio", .id = 0, };
static int __devinit pcm3168_soc_probe(struct platform_device *pdev) { struct snd_soc_card *card = &snd_soc_pcm3168; int ret;
pr_info("OMAP3 Beagle - PCM3168 ASoC init\n"); card->dev = &pdev->dev; ret = snd_soc_register_card(card); if (ret) { dev_err(&pdev->dev, "snd_soc_register_card() failed: %d -
pcm3168\n", ret); return ret; } return 0; }
static int __devexit pcm3168_soc_remove(struct platform_device *pdev) { struct snd_soc_card *card = platform_get_drvdata(pdev); snd_soc_unregister_card(card);
return 0;
}
static struct platform_driver pcm3168_driver = { .driver = { .name = "pcm3168-soc-audio", .owner = THIS_MODULE, }, .probe = pcm3168_soc_probe, .remove = __devexit_p(pcm3168_soc_remove), };
static int __init pcm3168_soc_init(void) { platform_device_register(&pcm3168_codec); platform_device_register(&pcm3168_soc_audio); return platform_driver_register(&pcm3168_driver); } module_init(pcm3168_soc_init);
static void __exit pcm3168_soc_exit(void) { platform_driver_unregister(&pcm3168_driver); platform_device_unregister(&pcm3168_soc_audio); platform_device_unregister(&pcm3168_codec); } module_exit(pcm3168_soc_exit);
MODULE_AUTHOR("Klimann Wendelin <wklimann@hotmail.com mailto:wklimann@hotmail.com>"); MODULE_DESCRIPTION("ALSA SoC PCM3168 add on Soundcard"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:pcm3168-soc-audio");
Hello
Well i gess slowly i get some success, as i have some kind of audio output on the audio codec :-)
On 23.03.2013 22:56, wendelin klimann wrote:
The cleaned up files are added, if there are still mixes between the codec- and machine-driver i would be glad if you could tell me.
It looks much better already, but your codec functions are currently just stubs. So you need to fill them with logic that actually sets up the hardware of course, and you should use regmap for abstracting the registers.
Do you mean that i should setup the audiocodec over i2c/spi or miss i something else?
Also, the codec driver has to become a i2c/spi driver rather than a platform device. There are tons of other drivers for reference.
I am trying to add support for i2c but can not find how to setup the correct i2c -> that one which will be used for the communication (i2c-2 in my case) It would be nice if you could give me an hint how to find that.
There are many smaller things to comment on, but you can submit the codec driver first once you're finished, so it can be reviewed independently from the rest. Note, however, that all new drivers have to be written in a device-tree aware fashion.
I give my best to learn from other device drivers :-)
Acctually i am stuck at a point where i get just 4 channels instead of 8 channels audio output on my audio codec (PCM3168 from TI) i get every second channel on the audio codec (alsa channel 1 -> to audio codec channel 1; alsa channel 3 -> to audio codec channel 2; alsa channel 5 -> to audio codec channel 3; alsa channel 7 -> to audio codec channel 4)
The audio codec is setup to use I2S-TDM (24Bit (32Bit frame), 48kHz) mode and i test the system with:
soc/omap# speaker-test -Dhw:1,0 -c8 -twav
speaker-test 1.0.25
Playback device is hw:1,0 Stream parameters are 48000Hz, S16_LE, 8 channels WAV file(s) [ 657.989562] omap-dma-engine omap-dma-engine: allocating channel for 17 Rate set to 48000Hz (requested 48000Hz) Buffer size range from 16 to 8192 Period size range from 2 to 4096 Using max buffer size 8192 Periods = 4 was set period_size = 2048 was set buffer_size = 8192 0 - Front Left 4 - Center 1 - Front Right 7 - Side Right 3 - Rear Right 2 - Rear Left 6 - Side Left 5 - LFE Time per period = 11.223022
When i meassure the digital signal with an osziloscope i see that alsa send all 8 channels (16Bit) on just half of the LRCKL
________________________________ | | | | | | LRCKL - AD/DA | | | | |______________________________|
| all 8 channels | nothing | AudioCodec-IN / Alsa-OUT
I gess that is because it is using S16_LE format instead of a S32_LE format but till now i was not able to find a way to change that neither in the driver by changing the
#define PCM3168_FORMATS SNDRV_PCM_FMTBIT_S32_LE
nor by using
speaker-test -Dhw:1,0 -c8 -twav -FS32_LE
which has the same output as the one without -F32_LE
Please help and thanks in advance
Wendelin
****************************************************************************************************** * kernel_3.7.4+/sound/soc/codecs/pcm3168.c * ******************************************************************************************************
/* * ALSA Soc PCM3168 codec support * * Author: Klimann Wendelin <wklimann@hotmail.com mailto:wklimann@hotmail.com> * * based on: * > pcm3008.c * > Author: Hugo Villeneuve * > Copyright (C) 2008 Lyrtech inc * > * > Based on AC97 Soc codec, original copyright follow: * > Copyright 2005 Wolfson Microelectronics PLC. * * 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. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA */
#include <linux/init.h> #include <linux/kernel.h> #include <linux/device.h> #include <linux/gpio.h> #include <linux/slab.h> #include <linux/module.h> #include <linux/clk.h> #include <linux/platform_device.h> #include <linux/module.h> #include <sound/core.h> #include <sound/initval.h> #include <sound/pcm.h> #include <sound/pcm_params.h> #include <sound/soc.h>
#include "pcm3168.h"
#define PCM3168_VERSION "0.1"
static int pcm3168_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_codec *codec = rtd->codec;
dev_dbg(codec->dev, "Sample format 0x%X\n", params_format(params)); dev_dbg(codec->dev, "Channels %d\n", params_channels(params)); dev_dbg(codec->dev, "Rate %d\n", params_rate(params));
return 0; }
static int pcm3168_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { struct snd_soc_codec *codec = codec_dai->codec;
/* codec role */ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { case SND_SOC_DAIFMT_CBM_CFM: dev_dbg(codec->dev, "Codec is master\n"); break; case SND_SOC_DAIFMT_CBS_CFS: dev_dbg(codec->dev, "Codec is slave\n"); break; default: return -EINVAL; }
/* DAI format */ dev_dbg(codec->dev, "DAI format 0x%X", fmt & SND_SOC_DAIFMT_FORMAT_MASK);
/* Bit clock and frame sync polarities */ dev_dbg(codec->dev, "Clock polarities 0x%X\n", fmt & SND_SOC_DAIFMT_INV_MASK);
return 0; }
#define PCM3168_RATES SNDRV_PCM_RATE_8000_96000 /*(SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ SNDRV_PCM_RATE_48000)*/ #define PCM3168_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE)
static const struct snd_soc_dai_ops pcm3168_dai_hifi_ops = { .hw_params = pcm3168_hw_params, .set_fmt = pcm3168_set_dai_fmt, };
static struct snd_soc_dai_driver pcm3168_dai = { .name = "pcm3168-hifi", .playback = { .stream_name = "PCM3168 Playback", .channels_min = 8, .channels_max = 8, .rates = PCM3168_RATES, .formats = PCM3168_FORMATS, //SNDRV_PCM_FMTBIT_S16_LE, //SNDRV_PCM_FMTBIT_S32_LE .sig_bits = 24, }, .capture = { .stream_name = "PCM3168 Capture", .channels_min = 6, .channels_max = 6, .rates = PCM3168_RATES, .formats = PCM3168_FORMATS, //SNDRV_PCM_FMTBIT_S16_LE, //SNDRV_PCM_FMTBIT_S32_LE .sig_bits = 24, }, .ops = &pcm3168_dai_hifi_ops, };
static int pcm3168_soc_probe(struct snd_soc_codec *codec) { struct pcm3168_setup_data *setup = codec->dev->platform_data; int ret = 0;
printk(KERN_INFO "PCM3168 SoC Audio Codec %s\n", PCM3168_VERSION);
return ret; }
static int pcm3168_soc_remove(struct snd_soc_codec *codec) { struct pcm3168_setup_data *setup = codec->dev->platform_data;
return 0; }
#define pcm3168_soc_suspend NULL #define pcm3168_soc_resume NULL
static struct snd_soc_codec_driver soc_codec_dev_pcm3168 = { .probe = pcm3168_soc_probe, .remove = pcm3168_soc_remove, .suspend = pcm3168_soc_suspend, .resume = pcm3168_soc_resume, };
static int __devinit pcm3168_codec_probe(struct platform_device *pdev) { int ret; printk(KERN_ALERT "probe pcm3168 codec -> kli \n");
ret = snd_soc_register_codec(&pdev->dev, &soc_codec_dev_pcm3168, &pcm3168_dai, 1);
printk(KERN_ALERT "probe_after pcm3168 codec -> kli = %d \n", ret);
return ret; }
static int __devexit pcm3168_codec_remove(struct platform_device *pdev) { printk(KERN_ALERT "remove pcm3168 codec -> kli \n");
snd_soc_unregister_codec(&pdev->dev); return 0; }
static struct platform_driver pcm3168_codec_driver = { .probe = pcm3168_codec_probe, .remove = __devexit_p(pcm3168_codec_remove), .driver = { .name = "pcm3168-codec", .owner = THIS_MODULE, }, };
static int __init pcm3168_modinit(void) { printk(KERN_ALERT "in init of 3168 -> kli \n"); return platform_driver_register(&pcm3168_codec_driver); } module_init(pcm3168_modinit);
static void __exit pcm3168_exit(void) { printk(KERN_ALERT "in exit of 3168 -> kli \n"); platform_driver_unregister(&pcm3168_codec_driver); } module_exit(pcm3168_exit);
MODULE_DESCRIPTION("Soc PCM3168 driver"); MODULE_AUTHOR("Klimann Wendelin <wklimann@hotmail.com mailto:wklimann@hotmail.com>"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:pcm3168-codec");
****************************************************************************************************** * kernel_3.7.4+/sound/soc/omap/omap-pcm3168.c * ******************************************************************************************************
/* * PCM3168 ASoC driver for BeagleBoard. * * based on: * > omap3beagle.c -- SoC audio for OMAP3 Beagle * > Author: Steve Sakoman <steve@sakoman.com mailto:steve@sakoman.com> * * adapted by Klimann Wendelin <wklimann@hotmail.com mailto:wklimann@hotmail.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. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */
#include <linux/clk.h> #include <linux/platform_device.h> #include <linux/module.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/soc.h> #include <sound/pcm_params.h>
#include <asm/mach-types.h> #include <mach/hardware.h> #include <mach/gpio.h> //#include <plat/mcbsp.h>
#include "omap-mcbsp.h" #include "omap-pcm.h"
/* * Uncomment to test codec in slave mode or without actual codec. This makes * possible to test this driver by letting the OMAP to be DAI link master */ //#define PCM3168_CODEC_SLAVE 1
static int pcm3168_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 *codec_dai = rtd->codec_dai; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; unsigned int fmt, div; int ret;
#ifdef PCM3168_CODEC_SLAVE fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS | SND_SOC_DAIFMT_NB_NF; #else fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM | SND_SOC_DAIFMT_NB_NF; #endif
/* Set codec DAI configuration */ ret = snd_soc_dai_set_fmt(codec_dai, fmt); if (ret < 0) { printk(KERN_ERR "can't set codec DAI configuration - pcm3168\n"); return ret; }
/* Set cpu DAI configuration */ ret = snd_soc_dai_set_fmt(cpu_dai, fmt); if (ret < 0) { printk(KERN_ERR "can't set cpu DAI configuration - pcm3168\n"); return ret; }
/* Set the codec system clock for DAC and ADC */ /* ret = snd_soc_dai_set_sysclk(codec_dai, 0, 26000000, SND_SOC_CLOCK_IN); if (ret < 0) { printk(KERN_ERR "can't set codec system clock\n"); return ret; }*/
#ifdef PCM3168_CODEC_SLAVE
ret = snd_soc_dai_set_sysclk(cpu_dai, OMAP_MCBSP_SYSCLK_CLKS_FCLK, 48000000, SND_SOC_CLOCK_IN); if (ret < 0) { pr_err("can't set McBSP sysclk - pcm3168\n"); return ret; }
/* * Calculate McBSP SRG divisor in McBSP master mode */ div = 48000000 / params_rate(params) / params_channels(params);
switch (params_format(params)) { case SNDRV_PCM_FORMAT_S16_LE: div /= 16; break; case SNDRV_PCM_FORMAT_S24_LE: case SNDRV_PCM_FORMAT_S32_LE: div /= 32; break; };
/* * Round to maximum divisor if needed. This means that extra bit-clock * cycles are transmitted when sample rate and number of bits in frame * (channels * sample bits) are low. */ if (div >= 256) div = 256;
ret = snd_soc_dai_set_clkdiv(cpu_dai, OMAP_MCBSP_CLKGDV, div); if (ret < 0) { pr_err("can't set SRG clock divider - pcm3168\n"); return ret; }
#endif
return 0; }
static struct snd_soc_ops pcm3168_ops = { .hw_params = pcm3168_hw_params, };
/* Digital audio interface glue - connects codec <--> CPU */ static struct snd_soc_dai_link pcm3168_dai = { .name = "PCM3168", .stream_name = "PCM3168", .cpu_dai_name = "omap-mcbsp.3", .platform_name = "omap-pcm-audio", .codec_dai_name = "pcm3168-hifi", .codec_name = "pcm3168-codec.0", .ops = &pcm3168_ops, };
/* Audio machine driver */ static struct snd_soc_card snd_soc_pcm3168 = { .name = "pcm3168", .owner = THIS_MODULE, .dai_link = &pcm3168_dai, .num_links = 1, };
struct platform_device pcm3168_codec = { .name = "pcm3168-codec", .id = 0, };
struct platform_device pcm3168_soc_audio = { .name = "pcm3168-soc-audio", .id = 0, };
static int __devinit pcm3168_soc_probe(struct platform_device *pdev) { struct snd_soc_card *card = &snd_soc_pcm3168; int ret;
pr_info("OMAP3 Beagle - PCM3168 ASoC init\n");
card->dev = &pdev->dev;
ret = snd_soc_register_card(card); if (ret) { dev_err(&pdev->dev, "snd_soc_register_card() failed: %d - pcm3168\n", ret); return ret; }
return 0; }
static int __devexit pcm3168_soc_remove(struct platform_device *pdev) { struct snd_soc_card *card = platform_get_drvdata(pdev);
snd_soc_unregister_card(card);
return 0; }
static struct platform_driver pcm3168_driver = { .driver = { .name = "pcm3168-soc-audio", .owner = THIS_MODULE, },
.probe = pcm3168_soc_probe, .remove = __devexit_p(pcm3168_soc_remove), };
static int __init pcm3168_soc_init(void) { platform_device_register(&pcm3168_codec); platform_device_register(&pcm3168_soc_audio); return platform_driver_register(&pcm3168_driver); } module_init(pcm3168_soc_init);
static void __exit pcm3168_soc_exit(void) { platform_driver_unregister(&pcm3168_driver); platform_device_unregister(&pcm3168_soc_audio); platform_device_unregister(&pcm3168_codec); } module_exit(pcm3168_soc_exit);
MODULE_AUTHOR("Klimann Wendelin <wklimann@hotmail.com mailto:wklimann@hotmail.com>"); MODULE_DESCRIPTION("ALSA SoC PCM3168 add on Soundcard"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:pcm3168-soc-audio");
Hi,
there's something badly wrong with your mail client. Please do not send HTML emails and configure your client for proper quoting.
On 04.04.2013 15:15, Wendelin Klimann wrote:
On 23.03.2013 22:56, wendelin klimann wrote:
The cleaned up files are added, if there are still mixes between the codec- and machine-driver i would be glad if you could tell me.
It looks much better already, but your codec functions are currently just stubs. So you need to fill them with logic that actually sets up the hardware of course, and you should use regmap for abstracting the registers.
Do you mean that i should setup the audiocodec over i2c/spi or miss i something else?
Yes, you need to tell the codec which format and frequency it runs on. Also, you might want to control some volume registers, mute flags or other mixer switches. As I said, just use regmap for that, which abstracts the register access for you.
Also, the codec driver has to become a i2c/spi driver rather than a platform device. There are tons of other drivers for reference.
I am trying to add support for i2c but can not find how to setup the correct i2c -> that one which will be used for the communication (i2c-2 in my case) It would be nice if you could give me an hint how to find that.
That is of course not codec driver specific. Why would you want to alter the codec driver if you want to re-use it on a different platform, where the chip is wired to some other controller?
This information is configured on the platform side - in board support files for legacy kernels, in device-tree for newer versions.
Just grep through some board files (arch/arm/mach-omap2/board-omap3beagle.c for instance) and look for "i2c_board_info".
There are many smaller things to comment on, but you can submit the codec driver first once you're finished, so it can be reviewed independently from the rest. Note, however, that all new drivers have to be written in a device-tree aware fashion.
I give my best to learn from other device drivers :-)
Acctually i am stuck at a point where i get just 4 channels instead of 8 channels audio output on my audio codec (PCM3168 from TI) i get every second channel on the audio codec (alsa channel 1 -> to audio codec channel 1; alsa channel 3 -> to audio codec channel 2; alsa channel 5 -> to audio codec channel 3; alsa channel 7 -> to audio codec channel 4)
The audio codec is setup to use I2S-TDM (24Bit (32Bit frame), 48kHz) mode and i test the system with:
soc/omap# speaker-test -Dhw:1,0 -c8 -twav
speaker-test 1.0.25 Playback device is hw:1,0 Stream parameters are 48000Hz, S16_LE, 8 channels WAV file(s) [ 657.989562] omap-dma-engine omap-dma-engine: allocating channel for 17 Rate set to 48000Hz (requested 48000Hz) Buffer size range from 16 to 8192 Period size range from 2 to 4096 Using max buffer size 8192 Periods = 4 was set period_size = 2048 was set buffer_size = 8192 0 - Front Left 4 - Center 1 - Front Right 7 - Side Right 3 - Rear Right 2 - Rear Left 6 - Side Left 5 - LFE Time per period = 11.223022
When i meassure the digital signal with an osziloscope i see that alsa send all 8 channels (16Bit) on just half of the LRCKL
| | | | | | LRCKL
- AD/DA
| | | | |______________________________|
| all 8 channels | nothing | AudioCodec-IN / Alsa-OUT
This has nothing to do with the codec driver of course.
You should trace which audio format is selected on the CPU side.
static int pcm3168_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_codec *codec = rtd->codec;
dev_dbg(codec->dev, "Sample format 0x%X\n", params_format(params)); dev_dbg(codec->dev, "Channels %d\n", params_channels(params)); dev_dbg(codec->dev, "Rate %d\n", params_rate(params)); return 0;
}
Again - you have to tell the chip about your runtime configuration when this function is called.
static int pcm3168_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { struct snd_soc_codec *codec = codec_dai->codec;
/* codec role */ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { case SND_SOC_DAIFMT_CBM_CFM: dev_dbg(codec->dev, "Codec is master\n"); break; case SND_SOC_DAIFMT_CBS_CFS: dev_dbg(codec->dev, "Codec is slave\n"); break; default: return -EINVAL; } /* DAI format */ dev_dbg(codec->dev, "DAI format 0x%X", fmt & SND_SOC_DAIFMT_FORMAT_MASK); /* Bit clock and frame sync polarities */ dev_dbg(codec->dev, "Clock polarities 0x%X\n", fmt & SND_SOC_DAIFMT_INV_MASK); return 0;
}
Dito. Your driver cannot work until you fill these functions with life.
Daniel
participants (2)
-
Daniel Mack
-
Wendelin Klimann