Logo Search packages:      
Sourcecode: alsa-driver version File versions  Download package

wl1273.c

/*
 * ALSA SoC WL1273 codec driver
 *
 * Author:      Matti Aaltonen, <matti.j.aaltonen@nokia.com>
 *
 * Copyright:   (C) 2010 Nokia Corporation
 *
 * 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/mfd/wl1273-core.h>
#include <linux/slab.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/initval.h>

#include "wl1273.h"

enum wl1273_mode { WL1273_MODE_BT, WL1273_MODE_FM_RX, WL1273_MODE_FM_TX };

/* codec private data */
00036 struct wl1273_priv {
      enum wl1273_mode mode;
      struct wl1273_core *core;
      unsigned int channels;
};

static int snd_wl1273_fm_set_i2s_mode(struct wl1273_core *core,
                              int rate, int width)
{
      struct device *dev = &core->client->dev;
      int r = 0;
      u16 mode;

      dev_dbg(dev, "rate: %d\n", rate);
      dev_dbg(dev, "width: %d\n", width);

      mutex_lock(&core->lock);

      mode = core->i2s_mode & ~WL1273_IS2_WIDTH & ~WL1273_IS2_RATE;

      switch (rate) {
      case 48000:
            mode |= WL1273_IS2_RATE_48K;
            break;
      case 44100:
            mode |= WL1273_IS2_RATE_44_1K;
            break;
      case 32000:
            mode |= WL1273_IS2_RATE_32K;
            break;
      case 22050:
            mode |= WL1273_IS2_RATE_22_05K;
            break;
      case 16000:
            mode |= WL1273_IS2_RATE_16K;
            break;
      case 12000:
            mode |= WL1273_IS2_RATE_12K;
            break;
      case 11025:
            mode |= WL1273_IS2_RATE_11_025;
            break;
      case 8000:
            mode |= WL1273_IS2_RATE_8K;
            break;
      default:
            dev_err(dev, "Sampling rate: %d not supported\n", rate);
            r = -EINVAL;
            goto out;
      }

      switch (width) {
      case 16:
            mode |= WL1273_IS2_WIDTH_32;
            break;
      case 20:
            mode |= WL1273_IS2_WIDTH_40;
            break;
      case 24:
            mode |= WL1273_IS2_WIDTH_48;
            break;
      case 25:
            mode |= WL1273_IS2_WIDTH_50;
            break;
      case 30:
            mode |= WL1273_IS2_WIDTH_60;
            break;
      case 32:
            mode |= WL1273_IS2_WIDTH_64;
            break;
      case 40:
            mode |= WL1273_IS2_WIDTH_80;
            break;
      case 48:
            mode |= WL1273_IS2_WIDTH_96;
            break;
      case 64:
            mode |= WL1273_IS2_WIDTH_128;
            break;
      default:
            dev_err(dev, "Data width: %d not supported\n", width);
            r = -EINVAL;
            goto out;
      }

      dev_dbg(dev, "WL1273_I2S_DEF_MODE: 0x%04x\n",  WL1273_I2S_DEF_MODE);
      dev_dbg(dev, "core->i2s_mode: 0x%04x\n", core->i2s_mode);
      dev_dbg(dev, "mode: 0x%04x\n", mode);

      if (core->i2s_mode != mode) {
            r = core->write(core, WL1273_I2S_MODE_CONFIG_SET, mode);
            if (r)
                  goto out;

            core->i2s_mode = mode;
            r = core->write(core, WL1273_AUDIO_ENABLE,
                        WL1273_AUDIO_ENABLE_I2S);
            if (r)
                  goto out;
      }
out:
      mutex_unlock(&core->lock);

      return r;
}

static int snd_wl1273_fm_set_channel_number(struct wl1273_core *core,
                                  int channel_number)
{
      struct device *dev = &core->client->dev;
      int r = 0;

      dev_dbg(dev, "%s\n", __func__);

      mutex_lock(&core->lock);

      if (core->channel_number == channel_number)
            goto out;

      if (channel_number == 1 && core->mode == WL1273_MODE_RX)
            r = core->write(core, WL1273_MOST_MODE_SET, WL1273_RX_MONO);
      else if (channel_number == 1 && core->mode == WL1273_MODE_TX)
            r = core->write(core, WL1273_MONO_SET, WL1273_TX_MONO);
      else if (channel_number == 2 && core->mode == WL1273_MODE_RX)
            r = core->write(core, WL1273_MOST_MODE_SET, WL1273_RX_STEREO);
      else if (channel_number == 2 && core->mode == WL1273_MODE_TX)
            r = core->write(core, WL1273_MONO_SET, WL1273_TX_STEREO);
      else
            r = -EINVAL;
out:
      mutex_unlock(&core->lock);

      return r;
}

static int snd_wl1273_get_audio_route(struct snd_kcontrol *kcontrol,
                              struct snd_ctl_elem_value *ucontrol)
{
      struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
      struct wl1273_priv *wl1273 = snd_soc_codec_get_drvdata(codec);

      ucontrol->value.integer.value[0] = wl1273->mode;

      return 0;
}

static const char *wl1273_audio_route[] = { "Bt", "FmRx", "FmTx" };

static int snd_wl1273_set_audio_route(struct snd_kcontrol *kcontrol,
                              struct snd_ctl_elem_value *ucontrol)
{
      struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
      struct wl1273_priv *wl1273 = snd_soc_codec_get_drvdata(codec);

      if (wl1273->mode == ucontrol->value.integer.value[0])
            return 0;

      /* Do not allow changes while stream is running */
      if (codec->active)
            return -EPERM;

      if (ucontrol->value.integer.value[0] < 0 ||
          ucontrol->value.integer.value[0] >=  ARRAY_SIZE(wl1273_audio_route))
            return -EINVAL;

      wl1273->mode = ucontrol->value.integer.value[0];

      return 1;
}

static const struct soc_enum wl1273_enum =
      SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(wl1273_audio_route), wl1273_audio_route);

static int snd_wl1273_fm_audio_get(struct snd_kcontrol *kcontrol,
                           struct snd_ctl_elem_value *ucontrol)
{
      struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
      struct wl1273_priv *wl1273 = snd_soc_codec_get_drvdata(codec);

      dev_dbg(codec->dev, "%s: enter.\n", __func__);

      ucontrol->value.integer.value[0] = wl1273->core->audio_mode;

      return 0;
}

static int snd_wl1273_fm_audio_put(struct snd_kcontrol *kcontrol,
                           struct snd_ctl_elem_value *ucontrol)
{
      struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
      struct wl1273_priv *wl1273 = snd_soc_codec_get_drvdata(codec);
      int val, r = 0;

      dev_dbg(codec->dev, "%s: enter.\n", __func__);

      val = ucontrol->value.integer.value[0];
      if (wl1273->core->audio_mode == val)
            return 0;

      r = wl1273->core->set_audio(wl1273->core, val);
      if (r < 0)
            return r;

      return 1;
}

static const char *wl1273_audio_strings[] = { "Digital", "Analog" };

static const struct soc_enum wl1273_audio_enum =
      SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(wl1273_audio_strings),
                      wl1273_audio_strings);

static int snd_wl1273_fm_volume_get(struct snd_kcontrol *kcontrol,
                            struct snd_ctl_elem_value *ucontrol)
{
      struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
      struct wl1273_priv *wl1273 = snd_soc_codec_get_drvdata(codec);

      dev_dbg(codec->dev, "%s: enter.\n", __func__);

      ucontrol->value.integer.value[0] = wl1273->core->volume;

      return 0;
}

static int snd_wl1273_fm_volume_put(struct snd_kcontrol *kcontrol,
                            struct snd_ctl_elem_value *ucontrol)
{
      struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
      struct wl1273_priv *wl1273 = snd_soc_codec_get_drvdata(codec);
      int r;

      dev_dbg(codec->dev, "%s: enter.\n", __func__);

      r = wl1273->core->set_volume(wl1273->core,
                             ucontrol->value.integer.value[0]);
      if (r)
            return r;

      return 1;
}

static const struct snd_kcontrol_new wl1273_controls[] = {
      SOC_ENUM_EXT("Codec Mode", wl1273_enum,
                 snd_wl1273_get_audio_route, snd_wl1273_set_audio_route),
      SOC_ENUM_EXT("Audio Switch", wl1273_audio_enum,
                 snd_wl1273_fm_audio_get,  snd_wl1273_fm_audio_put),
      SOC_SINGLE_EXT("Volume", 0, 0, WL1273_MAX_VOLUME, 0,
                   snd_wl1273_fm_volume_get, snd_wl1273_fm_volume_put),
};

static int wl1273_startup(struct snd_pcm_substream *substream,
                    struct snd_soc_dai *dai)
{
      struct snd_soc_pcm_runtime *rtd = substream->private_data;
      struct snd_soc_codec *codec = rtd->codec;
      struct wl1273_priv *wl1273 = snd_soc_codec_get_drvdata(codec);

      switch (wl1273->mode) {
      case WL1273_MODE_BT:
            snd_pcm_hw_constraint_minmax(substream->runtime,
                                   SNDRV_PCM_HW_PARAM_RATE,
                                   8000, 8000);
            snd_pcm_hw_constraint_minmax(substream->runtime,
                                   SNDRV_PCM_HW_PARAM_CHANNELS, 1, 1);
            break;
      case WL1273_MODE_FM_RX:
            if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
                  pr_err("Cannot play in RX mode.\n");
                  return -EINVAL;
            }
            break;
      case WL1273_MODE_FM_TX:
            if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
                  pr_err("Cannot capture in TX mode.\n");
                  return -EINVAL;
            }
            break;
      default:
            return -EINVAL;
            break;
      }

      return 0;
}

static int wl1273_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 wl1273_priv *wl1273 = snd_soc_codec_get_drvdata(rtd->codec);
      struct wl1273_core *core = wl1273->core;
      unsigned int rate, width, r;

      if (params_format(params) != SNDRV_PCM_FORMAT_S16_LE) {
            pr_err("Only SNDRV_PCM_FORMAT_S16_LE supported.\n");
            return -EINVAL;
      }

      rate = params_rate(params);
      width =  hw_param_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS)->min;

      if (wl1273->mode == WL1273_MODE_BT) {
            if (rate != 8000) {
                  pr_err("Rate %d not supported.\n", params_rate(params));
                  return -EINVAL;
            }

            if (params_channels(params) != 1) {
                  pr_err("Only mono supported.\n");
                  return -EINVAL;
            }

            return 0;
      }

      if (wl1273->mode == WL1273_MODE_FM_TX &&
          substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
            pr_err("Only playback supported with TX.\n");
            return -EINVAL;
      }

      if (wl1273->mode == WL1273_MODE_FM_RX  &&
          substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
            pr_err("Only capture supported with RX.\n");
            return -EINVAL;
      }

      if (wl1273->mode != WL1273_MODE_FM_RX  &&
          wl1273->mode != WL1273_MODE_FM_TX) {
            pr_err("Unexpected mode: %d.\n", wl1273->mode);
            return -EINVAL;
      }

      r = snd_wl1273_fm_set_i2s_mode(core, rate, width);
      if (r)
            return r;

      wl1273->channels = params_channels(params);
      r = snd_wl1273_fm_set_channel_number(core, wl1273->channels);
      if (r)
            return r;

      return 0;
}

static struct snd_soc_dai_ops wl1273_dai_ops = {
      .startup    = wl1273_startup,
      .hw_params  = wl1273_hw_params,
};

static struct snd_soc_dai_driver wl1273_dai = {
      .name = "wl1273-fm",
      .playback = {
            .stream_name = "Playback",
            .channels_min = 1,
            .channels_max = 2,
            .rates = SNDRV_PCM_RATE_8000_48000,
            .formats = SNDRV_PCM_FMTBIT_S16_LE},
      .capture = {
            .stream_name = "Capture",
            .channels_min = 1,
            .channels_max = 2,
            .rates = SNDRV_PCM_RATE_8000_48000,
            .formats = SNDRV_PCM_FMTBIT_S16_LE},
      .ops = &wl1273_dai_ops,
};

/* Audio interface format for the soc_card driver */
int wl1273_get_format(struct snd_soc_codec *codec, unsigned int *fmt)
{
      struct wl1273_priv *wl1273;

      if (codec == NULL || fmt == NULL)
            return -EINVAL;

      wl1273 = snd_soc_codec_get_drvdata(codec);

      switch (wl1273->mode) {
      case WL1273_MODE_FM_RX:
      case WL1273_MODE_FM_TX:
            *fmt =      SND_SOC_DAIFMT_I2S |
                  SND_SOC_DAIFMT_NB_NF |
                  SND_SOC_DAIFMT_CBM_CFM;

            break;
      case WL1273_MODE_BT:
            *fmt =      SND_SOC_DAIFMT_DSP_A |
                  SND_SOC_DAIFMT_IB_NF |
                  SND_SOC_DAIFMT_CBM_CFM;

            break;
      default:
            return -EINVAL;
      }

      return 0;
}
EXPORT_SYMBOL_GPL(wl1273_get_format);

static int wl1273_probe(struct snd_soc_codec *codec)
{
      struct wl1273_core **core = codec->dev->platform_data;
      struct wl1273_priv *wl1273;
      int r;

      dev_dbg(codec->dev, "%s.\n", __func__);

      if (!core) {
            dev_err(codec->dev, "Platform data is missing.\n");
            return -EINVAL;
      }

      wl1273 = kzalloc(sizeof(struct wl1273_priv), GFP_KERNEL);
      if (wl1273 == NULL) {
            dev_err(codec->dev, "Cannot allocate memory.\n");
            return -ENOMEM;
      }

      wl1273->mode = WL1273_MODE_BT;
      wl1273->core = *core;

      snd_soc_codec_set_drvdata(codec, wl1273);
      mutex_init(&codec->mutex);

      r = snd_soc_add_controls(codec, wl1273_controls,
                         ARRAY_SIZE(wl1273_controls));
      if (r)
            kfree(wl1273);

      return r;
}

static int wl1273_remove(struct snd_soc_codec *codec)
{
      struct wl1273_priv *wl1273 = snd_soc_codec_get_drvdata(codec);

      dev_dbg(codec->dev, "%s\n", __func__);
      kfree(wl1273);

      return 0;
}

static struct snd_soc_codec_driver soc_codec_dev_wl1273 = {
      .probe = wl1273_probe,
      .remove = wl1273_remove,
};

static int __devinit wl1273_platform_probe(struct platform_device *pdev)
{
      return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_wl1273,
                              &wl1273_dai, 1);
}

static int __devexit wl1273_platform_remove(struct platform_device *pdev)
{
      snd_soc_unregister_codec(&pdev->dev);
      return 0;
}

MODULE_ALIAS("platform:wl1273-codec");

static struct platform_driver wl1273_platform_driver = {
      .driver           = {
            .name = "wl1273-codec",
            .owner      = THIS_MODULE,
      },
      .probe            = wl1273_platform_probe,
      .remove           = __devexit_p(wl1273_platform_remove),
};

static int __init wl1273_init(void)
{
      return platform_driver_register(&wl1273_platform_driver);
}
module_init(wl1273_init);

static void __exit wl1273_exit(void)
{
      platform_driver_unregister(&wl1273_platform_driver);
}
module_exit(wl1273_exit);

MODULE_AUTHOR("Matti Aaltonen <matti.j.aaltonen@nokia.com>");
MODULE_DESCRIPTION("ASoC WL1273 codec driver");
MODULE_LICENSE("GPL");

Generated by  Doxygen 1.6.0   Back to index