linux/sound/soc/sh/migor.c
<<
>>
Prefs
   1/*
   2 * ALSA SoC driver for Migo-R
   3 *
   4 * Copyright (C) 2009-2010 Guennadi Liakhovetski <g.liakhovetski@gmx.de>
   5 *
   6 * This program is free software; you can redistribute it and/or modify
   7 * it under the terms of the GNU General Public License version 2 as
   8 * published by the Free Software Foundation.
   9 */
  10
  11#include <linux/clkdev.h>
  12#include <linux/device.h>
  13#include <linux/firmware.h>
  14#include <linux/module.h>
  15
  16#include <asm/clock.h>
  17
  18#include <cpu/sh7722.h>
  19
  20#include <sound/core.h>
  21#include <sound/pcm.h>
  22#include <sound/soc.h>
  23
  24#include "../codecs/wm8978.h"
  25#include "siu.h"
  26
  27/* Default 8000Hz sampling frequency */
  28static unsigned long codec_freq = 8000 * 512;
  29
  30static unsigned int use_count;
  31
  32/* External clock, sourced from the codec at the SIUMCKB pin */
  33static unsigned long siumckb_recalc(struct clk *clk)
  34{
  35        return codec_freq;
  36}
  37
  38static struct sh_clk_ops siumckb_clk_ops = {
  39        .recalc = siumckb_recalc,
  40};
  41
  42static struct clk siumckb_clk = {
  43        .ops            = &siumckb_clk_ops,
  44        .rate           = 0, /* initialised at run-time */
  45};
  46
  47static struct clk_lookup *siumckb_lookup;
  48
  49static int migor_hw_params(struct snd_pcm_substream *substream,
  50                           struct snd_pcm_hw_params *params)
  51{
  52        struct snd_soc_pcm_runtime *rtd = substream->private_data;
  53        struct snd_soc_dai *codec_dai = rtd->codec_dai;
  54        int ret;
  55        unsigned int rate = params_rate(params);
  56
  57        ret = snd_soc_dai_set_sysclk(codec_dai, WM8978_PLL, 13000000,
  58                                     SND_SOC_CLOCK_IN);
  59        if (ret < 0)
  60                return ret;
  61
  62        ret = snd_soc_dai_set_clkdiv(codec_dai, WM8978_OPCLKRATE, rate * 512);
  63        if (ret < 0)
  64                return ret;
  65
  66        ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_NB_IF |
  67                                  SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS);
  68        if (ret < 0)
  69                return ret;
  70
  71        ret = snd_soc_dai_set_fmt(rtd->cpu_dai, SND_SOC_DAIFMT_NB_IF |
  72                                  SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS);
  73        if (ret < 0)
  74                return ret;
  75
  76        codec_freq = rate * 512;
  77        /*
  78         * This propagates the parent frequency change to children and
  79         * recalculates the frequency table
  80         */
  81        clk_set_rate(&siumckb_clk, codec_freq);
  82        dev_dbg(codec_dai->dev, "%s: configure %luHz\n", __func__, codec_freq);
  83
  84        ret = snd_soc_dai_set_sysclk(rtd->cpu_dai, SIU_CLKB_EXT,
  85                                     codec_freq / 2, SND_SOC_CLOCK_IN);
  86
  87        if (!ret)
  88                use_count++;
  89
  90        return ret;
  91}
  92
  93static int migor_hw_free(struct snd_pcm_substream *substream)
  94{
  95        struct snd_soc_pcm_runtime *rtd = substream->private_data;
  96        struct snd_soc_dai *codec_dai = rtd->codec_dai;
  97
  98        if (use_count) {
  99                use_count--;
 100
 101                if (!use_count)
 102                        snd_soc_dai_set_sysclk(codec_dai, WM8978_PLL, 0,
 103                                               SND_SOC_CLOCK_IN);
 104        } else {
 105                dev_dbg(codec_dai->dev, "Unbalanced hw_free!\n");
 106        }
 107
 108        return 0;
 109}
 110
 111static struct snd_soc_ops migor_dai_ops = {
 112        .hw_params = migor_hw_params,
 113        .hw_free = migor_hw_free,
 114};
 115
 116static const struct snd_soc_dapm_widget migor_dapm_widgets[] = {
 117        SND_SOC_DAPM_HP("Headphone", NULL),
 118        SND_SOC_DAPM_MIC("Onboard Microphone", NULL),
 119        SND_SOC_DAPM_MIC("External Microphone", NULL),
 120};
 121
 122static const struct snd_soc_dapm_route audio_map[] = {
 123        /* Headphone output connected to LHP/RHP, enable OUT4 for VMID */
 124        { "Headphone", NULL,  "OUT4 VMID" },
 125        { "OUT4 VMID", NULL,  "LHP" },
 126        { "OUT4 VMID", NULL,  "RHP" },
 127
 128        /* On-board microphone */
 129        { "RMICN", NULL, "Mic Bias" },
 130        { "RMICP", NULL, "Mic Bias" },
 131        { "Mic Bias", NULL, "Onboard Microphone" },
 132
 133        /* External microphone */
 134        { "LMICN", NULL, "Mic Bias" },
 135        { "LMICP", NULL, "Mic Bias" },
 136        { "Mic Bias", NULL, "External Microphone" },
 137};
 138
 139static int migor_dai_init(struct snd_soc_pcm_runtime *rtd)
 140{
 141        struct snd_soc_codec *codec = rtd->codec;
 142        struct snd_soc_dapm_context *dapm = &codec->dapm;
 143
 144        snd_soc_dapm_new_controls(dapm, migor_dapm_widgets,
 145                                  ARRAY_SIZE(migor_dapm_widgets));
 146
 147        snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
 148
 149        return 0;
 150}
 151
 152/* migor digital audio interface glue - connects codec <--> CPU */
 153static struct snd_soc_dai_link migor_dai = {
 154        .name = "wm8978",
 155        .stream_name = "WM8978",
 156        .cpu_dai_name = "siu-pcm-audio",
 157        .codec_dai_name = "wm8978-hifi",
 158        .platform_name = "siu-pcm-audio",
 159        .codec_name = "wm8978.0-001a",
 160        .ops = &migor_dai_ops,
 161        .init = migor_dai_init,
 162};
 163
 164/* migor audio machine driver */
 165static struct snd_soc_card snd_soc_migor = {
 166        .name = "Migo-R",
 167        .owner = THIS_MODULE,
 168        .dai_link = &migor_dai,
 169        .num_links = 1,
 170};
 171
 172static struct platform_device *migor_snd_device;
 173
 174static int __init migor_init(void)
 175{
 176        int ret;
 177
 178        ret = clk_register(&siumckb_clk);
 179        if (ret < 0)
 180                return ret;
 181
 182        siumckb_lookup = clkdev_alloc(&siumckb_clk, "siumckb_clk", NULL);
 183        if (!siumckb_lookup) {
 184                ret = -ENOMEM;
 185                goto eclkdevalloc;
 186        }
 187        clkdev_add(siumckb_lookup);
 188
 189        /* Port number used on this machine: port B */
 190        migor_snd_device = platform_device_alloc("soc-audio", 1);
 191        if (!migor_snd_device) {
 192                ret = -ENOMEM;
 193                goto epdevalloc;
 194        }
 195
 196        platform_set_drvdata(migor_snd_device, &snd_soc_migor);
 197
 198        ret = platform_device_add(migor_snd_device);
 199        if (ret)
 200                goto epdevadd;
 201
 202        return 0;
 203
 204epdevadd:
 205        platform_device_put(migor_snd_device);
 206epdevalloc:
 207        clkdev_drop(siumckb_lookup);
 208eclkdevalloc:
 209        clk_unregister(&siumckb_clk);
 210        return ret;
 211}
 212
 213static void __exit migor_exit(void)
 214{
 215        clkdev_drop(siumckb_lookup);
 216        clk_unregister(&siumckb_clk);
 217        platform_device_unregister(migor_snd_device);
 218}
 219
 220module_init(migor_init);
 221module_exit(migor_exit);
 222
 223MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de>");
 224MODULE_DESCRIPTION("ALSA SoC Migor");
 225MODULE_LICENSE("GPL v2");
 226