Path: blob/master/sound/soc/atmel/playpaq_wm8510.c
10817 views
/* sound/soc/at32/playpaq_wm8510.c1* ASoC machine driver for PlayPaq using WM8510 codec2*3* Copyright (C) 2008 Long Range Systems4* Geoffrey Wossum <[email protected]>5*6* This program is free software; you can redistribute it and/or modify7* it under the terms of the GNU General Public License version 2 as8* published by the Free Software Foundation.9*10* This code is largely inspired by sound/soc/at91/eti_b1_wm8731.c11*12* NOTE: If you don't have the AT32 enhanced portmux configured (which13* isn't currently in the mainline or Atmel patched kernel), you will14* need to set the MCLK pin (PA30) to peripheral A in your board initialization15* code. Something like:16* at32_select_periph(GPIO_PIN_PA(30), GPIO_PERIPH_A, 0);17*18*/1920/* #define DEBUG */2122#include <linux/module.h>23#include <linux/moduleparam.h>24#include <linux/kernel.h>25#include <linux/errno.h>26#include <linux/clk.h>27#include <linux/timer.h>28#include <linux/interrupt.h>29#include <linux/platform_device.h>3031#include <sound/core.h>32#include <sound/pcm.h>33#include <sound/pcm_params.h>34#include <sound/soc.h>3536#include <mach/at32ap700x.h>37#include <mach/portmux.h>3839#include "../codecs/wm8510.h"40#include "atmel-pcm.h"41#include "atmel_ssc_dai.h"424344/*-------------------------------------------------------------------------*\45* constants46\*-------------------------------------------------------------------------*/47#define MCLK_PIN GPIO_PIN_PA(30)48#define MCLK_PERIPH GPIO_PERIPH_A495051/*-------------------------------------------------------------------------*\52* data types53\*-------------------------------------------------------------------------*/54/* SSC clocking data */55struct ssc_clock_data {56/* CMR div */57unsigned int cmr_div;5859/* Frame period (as needed by xCMR.PERIOD) */60unsigned int period;6162/* The SSC clock rate these settings where calculated for */63unsigned long ssc_rate;64};656667/*-------------------------------------------------------------------------*\68* module data69\*-------------------------------------------------------------------------*/70static struct clk *_gclk0;71static struct clk *_pll0;7273#define CODEC_CLK (_gclk0)747576/*-------------------------------------------------------------------------*\77* Sound SOC operations78\*-------------------------------------------------------------------------*/79#if defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE80static struct ssc_clock_data playpaq_wm8510_calc_ssc_clock(81struct snd_pcm_hw_params *params,82struct snd_soc_dai *cpu_dai)83{84struct at32_ssc_info *ssc_p = snd_soc_dai_get_drvdata(cpu_dai);85struct ssc_device *ssc = ssc_p->ssc;86struct ssc_clock_data cd;87unsigned int rate, width_bits, channels;88unsigned int bitrate, ssc_div;89unsigned actual_rate;909192/*93* Figure out required bitrate94*/95rate = params_rate(params);96channels = params_channels(params);97width_bits = snd_pcm_format_physical_width(params_format(params));98bitrate = rate * width_bits * channels;99100101/*102* Figure out required SSC divider and period for required bitrate103*/104cd.ssc_rate = clk_get_rate(ssc->clk);105ssc_div = cd.ssc_rate / bitrate;106cd.cmr_div = ssc_div / 2;107if (ssc_div & 1) {108/* round cmr_div up */109cd.cmr_div++;110}111cd.period = width_bits - 1;112113114/*115* Find actual rate, compare to requested rate116*/117actual_rate = (cd.ssc_rate / (cd.cmr_div * 2)) / (2 * (cd.period + 1));118pr_debug("playpaq_wm8510: Request rate = %u, actual rate = %u\n",119rate, actual_rate);120121122return cd;123}124#endif /* CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE */125126127128static int playpaq_wm8510_hw_params(struct snd_pcm_substream *substream,129struct snd_pcm_hw_params *params)130{131struct snd_soc_pcm_runtime *rtd = substream->private_data;132struct snd_soc_dai *codec_dai = rtd->codec_dai;133struct snd_soc_dai *cpu_dai = rtd->cpu_dai;134struct at32_ssc_info *ssc_p = snd_soc_dai_get_drvdata(cpu_dai);135struct ssc_device *ssc = ssc_p->ssc;136unsigned int pll_out = 0, bclk = 0, mclk_div = 0;137int ret;138139140/* Due to difficulties with getting the correct clocks from the AT32's141* PLL0, we're going to let the CODEC be in charge of all the clocks142*/143#if !defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE144const unsigned int fmt = (SND_SOC_DAIFMT_I2S |145SND_SOC_DAIFMT_NB_NF |146SND_SOC_DAIFMT_CBM_CFM);147#else148struct ssc_clock_data cd;149const unsigned int fmt = (SND_SOC_DAIFMT_I2S |150SND_SOC_DAIFMT_NB_NF |151SND_SOC_DAIFMT_CBS_CFS);152#endif153154if (ssc == NULL) {155pr_warning("playpaq_wm8510_hw_params: ssc is NULL!\n");156return -EINVAL;157}158159160/*161* Figure out PLL and BCLK dividers for WM8510162*/163switch (params_rate(params)) {164case 48000:165pll_out = 24576000;166mclk_div = WM8510_MCLKDIV_2;167bclk = WM8510_BCLKDIV_8;168break;169170case 44100:171pll_out = 22579200;172mclk_div = WM8510_MCLKDIV_2;173bclk = WM8510_BCLKDIV_8;174break;175176case 22050:177pll_out = 22579200;178mclk_div = WM8510_MCLKDIV_4;179bclk = WM8510_BCLKDIV_8;180break;181182case 16000:183pll_out = 24576000;184mclk_div = WM8510_MCLKDIV_6;185bclk = WM8510_BCLKDIV_8;186break;187188case 11025:189pll_out = 22579200;190mclk_div = WM8510_MCLKDIV_8;191bclk = WM8510_BCLKDIV_8;192break;193194case 8000:195pll_out = 24576000;196mclk_div = WM8510_MCLKDIV_12;197bclk = WM8510_BCLKDIV_8;198break;199200default:201pr_warning("playpaq_wm8510: Unsupported sample rate %d\n",202params_rate(params));203return -EINVAL;204}205206207/*208* set CPU and CODEC DAI configuration209*/210ret = snd_soc_dai_set_fmt(codec_dai, fmt);211if (ret < 0) {212pr_warning("playpaq_wm8510: "213"Failed to set CODEC DAI format (%d)\n",214ret);215return ret;216}217ret = snd_soc_dai_set_fmt(cpu_dai, fmt);218if (ret < 0) {219pr_warning("playpaq_wm8510: "220"Failed to set CPU DAI format (%d)\n",221ret);222return ret;223}224225226/*227* Set CPU clock configuration228*/229#if defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE230cd = playpaq_wm8510_calc_ssc_clock(params, cpu_dai);231pr_debug("playpaq_wm8510: cmr_div = %d, period = %d\n",232cd.cmr_div, cd.period);233ret = snd_soc_dai_set_clkdiv(cpu_dai, AT32_SSC_CMR_DIV, cd.cmr_div);234if (ret < 0) {235pr_warning("playpaq_wm8510: Failed to set CPU CMR_DIV (%d)\n",236ret);237return ret;238}239ret = snd_soc_dai_set_clkdiv(cpu_dai, AT32_SSC_TCMR_PERIOD,240cd.period);241if (ret < 0) {242pr_warning("playpaq_wm8510: "243"Failed to set CPU transmit period (%d)\n",244ret);245return ret;246}247#endif /* CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE */248249250/*251* Set CODEC clock configuration252*/253pr_debug("playpaq_wm8510: "254"pll_in = %ld, pll_out = %u, bclk = %x, mclk = %x\n",255clk_get_rate(CODEC_CLK), pll_out, bclk, mclk_div);256257258#if !defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE259ret = snd_soc_dai_set_clkdiv(codec_dai, WM8510_BCLKDIV, bclk);260if (ret < 0) {261pr_warning262("playpaq_wm8510: Failed to set CODEC DAI BCLKDIV (%d)\n",263ret);264return ret;265}266#endif /* CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE */267268269ret = snd_soc_dai_set_pll(codec_dai, 0, 0,270clk_get_rate(CODEC_CLK), pll_out);271if (ret < 0) {272pr_warning("playpaq_wm8510: Failed to set CODEC DAI PLL (%d)\n",273ret);274return ret;275}276277278ret = snd_soc_dai_set_clkdiv(codec_dai, WM8510_MCLKDIV, mclk_div);279if (ret < 0) {280pr_warning("playpaq_wm8510: Failed to set CODEC MCLKDIV (%d)\n",281ret);282return ret;283}284285286return 0;287}288289290291static struct snd_soc_ops playpaq_wm8510_ops = {292.hw_params = playpaq_wm8510_hw_params,293};294295296297static const struct snd_soc_dapm_widget playpaq_dapm_widgets[] = {298SND_SOC_DAPM_MIC("Int Mic", NULL),299SND_SOC_DAPM_SPK("Ext Spk", NULL),300};301302303304static const struct snd_soc_dapm_route intercon[] = {305/* speaker connected to SPKOUT */306{"Ext Spk", NULL, "SPKOUTP"},307{"Ext Spk", NULL, "SPKOUTN"},308309{"Mic Bias", NULL, "Int Mic"},310{"MICN", NULL, "Mic Bias"},311{"MICP", NULL, "Mic Bias"},312};313314315316static int playpaq_wm8510_init(struct snd_soc_pcm_runtime *rtd)317{318struct snd_soc_codec *codec = rtd->codec;319struct snd_soc_dapm_context *dapm = &codec->dapm;320int i;321322/*323* Add DAPM widgets324*/325for (i = 0; i < ARRAY_SIZE(playpaq_dapm_widgets); i++)326snd_soc_dapm_new_control(dapm, &playpaq_dapm_widgets[i]);327328329330/*331* Setup audio path interconnects332*/333snd_soc_dapm_add_routes(dapm, intercon, ARRAY_SIZE(intercon));334335336337/* always connected pins */338snd_soc_dapm_enable_pin(dapm, "Int Mic");339snd_soc_dapm_enable_pin(dapm, "Ext Spk");340snd_soc_dapm_sync(dapm);341342343344/* Make CSB show PLL rate */345snd_soc_dai_set_clkdiv(rtd->codec_dai, WM8510_OPCLKDIV,346WM8510_OPCLKDIV_1 | 4);347348return 0;349}350351352353static struct snd_soc_dai_link playpaq_wm8510_dai = {354.name = "WM8510",355.stream_name = "WM8510 PCM",356.cpu_dai_name= "atmel-ssc-dai.0",357.platform_name = "atmel-pcm-audio",358.codec_name = "wm8510-codec.0-0x1a",359.codec_dai_name = "wm8510-hifi",360.init = playpaq_wm8510_init,361.ops = &playpaq_wm8510_ops,362};363364365366static struct snd_soc_card snd_soc_playpaq = {367.name = "LRS_PlayPaq_WM8510",368.dai_link = &playpaq_wm8510_dai,369.num_links = 1,370};371372static struct platform_device *playpaq_snd_device;373374375static int __init playpaq_asoc_init(void)376{377int ret = 0;378379/*380* Configure MCLK for WM8510381*/382_gclk0 = clk_get(NULL, "gclk0");383if (IS_ERR(_gclk0)) {384_gclk0 = NULL;385goto err_gclk0;386}387_pll0 = clk_get(NULL, "pll0");388if (IS_ERR(_pll0)) {389_pll0 = NULL;390goto err_pll0;391}392if (clk_set_parent(_gclk0, _pll0)) {393pr_warning("snd-soc-playpaq: "394"Failed to set PLL0 as parent for DAC clock\n");395goto err_set_clk;396}397clk_set_rate(CODEC_CLK, 12000000);398clk_enable(CODEC_CLK);399400#if defined CONFIG_AT32_ENHANCED_PORTMUX401at32_select_periph(MCLK_PIN, MCLK_PERIPH, 0);402#endif403404405/*406* Create and register platform device407*/408playpaq_snd_device = platform_device_alloc("soc-audio", 0);409if (playpaq_snd_device == NULL) {410ret = -ENOMEM;411goto err_device_alloc;412}413414platform_set_drvdata(playpaq_snd_device, &snd_soc_playpaq);415416ret = platform_device_add(playpaq_snd_device);417if (ret) {418pr_warning("playpaq_wm8510: platform_device_add failed (%d)\n",419ret);420goto err_device_add;421}422423return 0;424425426err_device_add:427if (playpaq_snd_device != NULL) {428platform_device_put(playpaq_snd_device);429playpaq_snd_device = NULL;430}431err_device_alloc:432err_set_clk:433if (_pll0 != NULL) {434clk_put(_pll0);435_pll0 = NULL;436}437err_pll0:438if (_gclk0 != NULL) {439clk_put(_gclk0);440_gclk0 = NULL;441}442return ret;443}444445446static void __exit playpaq_asoc_exit(void)447{448if (_gclk0 != NULL) {449clk_put(_gclk0);450_gclk0 = NULL;451}452if (_pll0 != NULL) {453clk_put(_pll0);454_pll0 = NULL;455}456457#if defined CONFIG_AT32_ENHANCED_PORTMUX458at32_free_pin(MCLK_PIN);459#endif460461platform_device_unregister(playpaq_snd_device);462playpaq_snd_device = NULL;463}464465module_init(playpaq_asoc_init);466module_exit(playpaq_asoc_exit);467468MODULE_AUTHOR("Geoffrey Wossum <[email protected]>");469MODULE_DESCRIPTION("ASoC machine driver for LRS PlayPaq");470MODULE_LICENSE("GPL");471472473