diff options
Diffstat (limited to 'sound/soc/imx/mx27vis_wm8974.c')
-rw-r--r-- | sound/soc/imx/mx27vis_wm8974.c | 317 |
1 files changed, 317 insertions, 0 deletions
diff --git a/sound/soc/imx/mx27vis_wm8974.c b/sound/soc/imx/mx27vis_wm8974.c new file mode 100644 index 000000000000..e4dcb539108a --- /dev/null +++ b/sound/soc/imx/mx27vis_wm8974.c @@ -0,0 +1,317 @@ +/* + * mx27vis_wm8974.c -- SoC audio for mx27vis + * + * Copyright 2009 Vista Silicon S.L. + * Author: Javier Martin + * javier.martin@vista-silicon.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> + + +#include "../codecs/wm8974.h" +#include "mx1_mx2-pcm.h" +#include "mxc-ssi.h" +#include <mach/gpio.h> +#include <mach/iomux.h> + +#define IGNORED_ARG 0 + + +static struct snd_soc_card mx27vis; + +/** + * This function connects SSI1 (HPCR1) as slave to + * SSI1 external signals (PPCR1) + * As slave, HPCR1 must set TFSDIR and TCLKDIR as inputs from + * port 4 + */ +void audmux_connect_1_4(void) +{ + pr_debug("AUDMUX: normal operation mode\n"); + /* Reset HPCR1 and PPCR1 */ + + DAM_HPCR1 = 0x00000000; + DAM_PPCR1 = 0x00000000; + + /* set to synchronous */ + DAM_HPCR1 |= AUDMUX_HPCR_SYN; + DAM_PPCR1 |= AUDMUX_PPCR_SYN; + + + /* set Rx sources 1 <--> 4 */ + DAM_HPCR1 |= AUDMUX_HPCR_RXDSEL(3); /* port 4 */ + DAM_PPCR1 |= AUDMUX_PPCR_RXDSEL(0); /* port 1 */ + + /* set Tx frame and Clock direction and source 4 --> 1 output */ + DAM_HPCR1 |= AUDMUX_HPCR_TFSDIR | AUDMUX_HPCR_TCLKDIR; + DAM_HPCR1 |= AUDMUX_HPCR_TFCSEL(3); /* TxDS and TxCclk from port 4 */ + + return; +} + +static int mx27vis_hifi_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->dai->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + unsigned int pll_out = 0, bclk = 0, fmt = 0, mclk = 0; + int ret = 0; + + /* + * The WM8974 is better at generating accurate audio clocks than the + * MX27 SSI controller, so we will use it as master when we can. + */ + switch (params_rate(params)) { + case 8000: + fmt = SND_SOC_DAIFMT_CBM_CFM; + mclk = WM8974_MCLKDIV_12; + pll_out = 24576000; + break; + case 16000: + fmt = SND_SOC_DAIFMT_CBM_CFM; + pll_out = 12288000; + break; + case 48000: + fmt = SND_SOC_DAIFMT_CBM_CFM; + bclk = WM8974_BCLKDIV_4; + pll_out = 12288000; + break; + case 96000: + fmt = SND_SOC_DAIFMT_CBM_CFM; + bclk = WM8974_BCLKDIV_2; + pll_out = 12288000; + break; + case 11025: + fmt = SND_SOC_DAIFMT_CBM_CFM; + bclk = WM8974_BCLKDIV_16; + pll_out = 11289600; + break; + case 22050: + fmt = SND_SOC_DAIFMT_CBM_CFM; + bclk = WM8974_BCLKDIV_8; + pll_out = 11289600; + break; + case 44100: + fmt = SND_SOC_DAIFMT_CBM_CFM; + bclk = WM8974_BCLKDIV_4; + mclk = WM8974_MCLKDIV_2; + pll_out = 11289600; + break; + case 88200: + fmt = SND_SOC_DAIFMT_CBM_CFM; + bclk = WM8974_BCLKDIV_2; + pll_out = 11289600; + break; + } + + /* set codec DAI configuration */ + ret = codec_dai->ops->set_fmt(codec_dai, + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_IF | + SND_SOC_DAIFMT_SYNC | fmt); + if (ret < 0) { + printk(KERN_ERR "Error from codec DAI configuration\n"); + return ret; + } + + /* set cpu DAI configuration */ + ret = cpu_dai->ops->set_fmt(cpu_dai, + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_SYNC | fmt); + if (ret < 0) { + printk(KERN_ERR "Error from cpu DAI configuration\n"); + return ret; + } + + /* Put DC field of STCCR to 1 (not zero) */ + ret = cpu_dai->ops->set_tdm_slot(cpu_dai, 0, 2); + + /* set the SSI system clock as input */ + ret = cpu_dai->ops->set_sysclk(cpu_dai, IMX_SSP_SYS_CLK, 0, + SND_SOC_CLOCK_IN); + if (ret < 0) { + printk(KERN_ERR "Error when setting system SSI clk\n"); + return ret; + } + + /* set codec BCLK division for sample rate */ + ret = codec_dai->ops->set_clkdiv(codec_dai, WM8974_BCLKDIV, bclk); + if (ret < 0) { + printk(KERN_ERR "Error when setting BCLK division\n"); + return ret; + } + + + /* codec PLL input is 25 MHz */ + ret = codec_dai->ops->set_pll(codec_dai, IGNORED_ARG, + 25000000, pll_out); + if (ret < 0) { + printk(KERN_ERR "Error when setting PLL input\n"); + return ret; + } + + /*set codec MCLK division for sample rate */ + ret = codec_dai->ops->set_clkdiv(codec_dai, WM8974_MCLKDIV, mclk); + if (ret < 0) { + printk(KERN_ERR "Error when setting MCLK division\n"); + return ret; + } + + return 0; +} + +static int mx27vis_hifi_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + + /* disable the PLL */ + return codec_dai->ops->set_pll(codec_dai, IGNORED_ARG, 0, 0); +} + +/* + * mx27vis WM8974 HiFi DAI opserations. + */ +static struct snd_soc_ops mx27vis_hifi_ops = { + .hw_params = mx27vis_hifi_hw_params, + .hw_free = mx27vis_hifi_hw_free, +}; + + +static int mx27vis_suspend(struct platform_device *pdev, pm_message_t state) +{ + return 0; +} + +static int mx27vis_resume(struct platform_device *pdev) +{ + return 0; +} + +static int mx27vis_probe(struct platform_device *pdev) +{ + int ret = 0; + + ret = get_ssi_clk(0, &pdev->dev); + + if (ret < 0) { + printk(KERN_ERR "%s: cant get ssi clock\n", __func__); + return ret; + } + + + return 0; +} + +static int mx27vis_remove(struct platform_device *pdev) +{ + put_ssi_clk(0); + return 0; +} + +static struct snd_soc_dai_link mx27vis_dai[] = { +{ /* Hifi Playback*/ + .name = "WM8974", + .stream_name = "WM8974 HiFi", + .cpu_dai = &imx_ssi_pcm_dai[0], + .codec_dai = &wm8974_dai, + .ops = &mx27vis_hifi_ops, +}, +}; + +static struct snd_soc_card mx27vis = { + .name = "mx27vis", + .platform = &mx1_mx2_soc_platform, + .probe = mx27vis_probe, + .remove = mx27vis_remove, + .suspend_pre = mx27vis_suspend, + .resume_post = mx27vis_resume, + .dai_link = mx27vis_dai, + .num_links = ARRAY_SIZE(mx27vis_dai), +}; + +static struct snd_soc_device mx27vis_snd_devdata = { + .card = &mx27vis, + .codec_dev = &soc_codec_dev_wm8974, +}; + +static struct platform_device *mx27vis_snd_device; + +/* Temporal definition of board specific behaviour */ +void gpio_ssi_active(int ssi_num) +{ + int ret = 0; + + unsigned int ssi1_pins[] = { + PC20_PF_SSI1_FS, + PC21_PF_SSI1_RXD, + PC22_PF_SSI1_TXD, + PC23_PF_SSI1_CLK, + }; + unsigned int ssi2_pins[] = { + PC24_PF_SSI2_FS, + PC25_PF_SSI2_RXD, + PC26_PF_SSI2_TXD, + PC27_PF_SSI2_CLK, + }; + if (ssi_num == 0) + ret = mxc_gpio_setup_multiple_pins(ssi1_pins, + ARRAY_SIZE(ssi1_pins), "USB OTG"); + else + ret = mxc_gpio_setup_multiple_pins(ssi2_pins, + ARRAY_SIZE(ssi2_pins), "USB OTG"); + if (ret) + printk(KERN_ERR "Error requesting ssi %x pins\n", ssi_num); +} + + +static int __init mx27vis_init(void) +{ + int ret; + + mx27vis_snd_device = platform_device_alloc("soc-audio", -1); + if (!mx27vis_snd_device) + return -ENOMEM; + + platform_set_drvdata(mx27vis_snd_device, &mx27vis_snd_devdata); + mx27vis_snd_devdata.dev = &mx27vis_snd_device->dev; + ret = platform_device_add(mx27vis_snd_device); + + if (ret) { + printk(KERN_ERR "ASoC: Platform device allocation failed\n"); + platform_device_put(mx27vis_snd_device); + } + + /* WM8974 uses SSI1 (HPCR1) via AUDMUX port 4 for audio (PPCR1) */ + gpio_ssi_active(0); + audmux_connect_1_4(); + + return ret; +} + +static void __exit mx27vis_exit(void) +{ + /* We should call some "ssi_gpio_inactive()" properly */ +} + +module_init(mx27vis_init); +module_exit(mx27vis_exit); + + +MODULE_AUTHOR("Javier Martin, javier.martin@vista-silicon.com"); +MODULE_DESCRIPTION("ALSA SoC WM8974 mx27vis"); +MODULE_LICENSE("GPL"); |