diff options
Diffstat (limited to 'sound/soc/sh/migor.c')
-rw-r--r-- | sound/soc/sh/migor.c | 208 |
1 files changed, 208 insertions, 0 deletions
diff --git a/sound/soc/sh/migor.c b/sound/soc/sh/migor.c new file mode 100644 index 000000000..82f582344 --- /dev/null +++ b/sound/soc/sh/migor.c @@ -0,0 +1,208 @@ +/* + * ALSA SoC driver for Migo-R + * + * Copyright (C) 2009-2010 Guennadi Liakhovetski <g.liakhovetski@gmx.de> + * + * 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/clkdev.h> +#include <linux/device.h> +#include <linux/firmware.h> +#include <linux/module.h> + +#include <asm/clock.h> + +#include <cpu/sh7722.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> + +#include "../codecs/wm8978.h" +#include "siu.h" + +/* Default 8000Hz sampling frequency */ +static unsigned long codec_freq = 8000 * 512; + +static unsigned int use_count; + +/* External clock, sourced from the codec at the SIUMCKB pin */ +static unsigned long siumckb_recalc(struct clk *clk) +{ + return codec_freq; +} + +static struct sh_clk_ops siumckb_clk_ops = { + .recalc = siumckb_recalc, +}; + +static struct clk siumckb_clk = { + .ops = &siumckb_clk_ops, + .rate = 0, /* initialised at run-time */ +}; + +static struct clk_lookup *siumckb_lookup; + +static int migor_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; + int ret; + unsigned int rate = params_rate(params); + + ret = snd_soc_dai_set_sysclk(codec_dai, WM8978_PLL, 13000000, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_clkdiv(codec_dai, WM8978_OPCLKRATE, rate * 512); + if (ret < 0) + return ret; + + codec_freq = rate * 512; + /* + * This propagates the parent frequency change to children and + * recalculates the frequency table + */ + clk_set_rate(&siumckb_clk, codec_freq); + dev_dbg(codec_dai->dev, "%s: configure %luHz\n", __func__, codec_freq); + + ret = snd_soc_dai_set_sysclk(rtd->cpu_dai, SIU_CLKB_EXT, + codec_freq / 2, SND_SOC_CLOCK_IN); + + if (!ret) + use_count++; + + return ret; +} + +static int migor_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + + if (use_count) { + use_count--; + + if (!use_count) + snd_soc_dai_set_sysclk(codec_dai, WM8978_PLL, 0, + SND_SOC_CLOCK_IN); + } else { + dev_dbg(codec_dai->dev, "Unbalanced hw_free!\n"); + } + + return 0; +} + +static struct snd_soc_ops migor_dai_ops = { + .hw_params = migor_hw_params, + .hw_free = migor_hw_free, +}; + +static const struct snd_soc_dapm_widget migor_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Onboard Microphone", NULL), + SND_SOC_DAPM_MIC("External Microphone", NULL), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* Headphone output connected to LHP/RHP, enable OUT4 for VMID */ + { "Headphone", NULL, "OUT4 VMID" }, + { "OUT4 VMID", NULL, "LHP" }, + { "OUT4 VMID", NULL, "RHP" }, + + /* On-board microphone */ + { "RMICN", NULL, "Mic Bias" }, + { "RMICP", NULL, "Mic Bias" }, + { "Mic Bias", NULL, "Onboard Microphone" }, + + /* External microphone */ + { "LMICN", NULL, "Mic Bias" }, + { "LMICP", NULL, "Mic Bias" }, + { "Mic Bias", NULL, "External Microphone" }, +}; + +/* migor digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link migor_dai = { + .name = "wm8978", + .stream_name = "WM8978", + .cpu_dai_name = "siu-pcm-audio", + .codec_dai_name = "wm8978-hifi", + .platform_name = "siu-pcm-audio", + .codec_name = "wm8978.0-001a", + .dai_fmt = SND_SOC_DAIFMT_NB_IF | SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_CBS_CFS, + .ops = &migor_dai_ops, +}; + +/* migor audio machine driver */ +static struct snd_soc_card snd_soc_migor = { + .name = "Migo-R", + .owner = THIS_MODULE, + .dai_link = &migor_dai, + .num_links = 1, + + .dapm_widgets = migor_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(migor_dapm_widgets), + .dapm_routes = audio_map, + .num_dapm_routes = ARRAY_SIZE(audio_map), +}; + +static struct platform_device *migor_snd_device; + +static int __init migor_init(void) +{ + int ret; + + ret = clk_register(&siumckb_clk); + if (ret < 0) + return ret; + + siumckb_lookup = clkdev_alloc(&siumckb_clk, "siumckb_clk", NULL); + if (!siumckb_lookup) { + ret = -ENOMEM; + goto eclkdevalloc; + } + clkdev_add(siumckb_lookup); + + /* Port number used on this machine: port B */ + migor_snd_device = platform_device_alloc("soc-audio", 1); + if (!migor_snd_device) { + ret = -ENOMEM; + goto epdevalloc; + } + + platform_set_drvdata(migor_snd_device, &snd_soc_migor); + + ret = platform_device_add(migor_snd_device); + if (ret) + goto epdevadd; + + return 0; + +epdevadd: + platform_device_put(migor_snd_device); +epdevalloc: + clkdev_drop(siumckb_lookup); +eclkdevalloc: + clk_unregister(&siumckb_clk); + return ret; +} + +static void __exit migor_exit(void) +{ + clkdev_drop(siumckb_lookup); + clk_unregister(&siumckb_clk); + platform_device_unregister(migor_snd_device); +} + +module_init(migor_init); +module_exit(migor_exit); + +MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de>"); +MODULE_DESCRIPTION("ALSA SoC Migor"); +MODULE_LICENSE("GPL v2"); |