harmony.c 5.9 KB
Newer Older
1 2 3 4
/*
 * harmony.c - Harmony machine ASoC driver
 *
 * Author: Stephen Warren <swarren@nvidia.com>
5
 * Copyright (C) 2010-2011 - NVIDIA, Inc.
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
 *
 * Based on code copyright/by:
 *
 * (c) 2009, 2010 Nvidia Graphics Pvt. Ltd.
 *
 * Copyright 2007 Wolfson Microelectronics PLC.
 * Author: Graeme Gregory
 *         graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com
 *
 * 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 <asm/mach-types.h>
32

33
#include <linux/module.h>
34 35 36
#include <linux/platform_device.h>
#include <linux/slab.h>

37 38 39 40 41 42 43 44 45 46
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>

#include "tegra_das.h"
#include "tegra_i2s.h"
#include "tegra_pcm.h"
#include "tegra_asoc_utils.h"

47 48
#define DRV_NAME "tegra-snd-harmony"
#define PREFIX DRV_NAME ": "
49

50 51
struct tegra_harmony {
};
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115

static int harmony_asoc_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;
	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
	int srate, mclk, mclk_change;
	int err;

	srate = params_rate(params);
	switch (srate) {
	case 64000:
	case 88200:
	case 96000:
		mclk = 128 * srate;
		break;
	default:
		mclk = 256 * srate;
		break;
	}
	/* FIXME: Codec only requires >= 3MHz if OSR==0 */
	while (mclk < 6000000)
		mclk *= 2;

	err = tegra_asoc_utils_set_rate(srate, mclk, &mclk_change);
	if (err < 0) {
		pr_err(PREFIX "Can't configure clocks\n");
		return err;
	}

	err = snd_soc_dai_set_fmt(codec_dai,
					SND_SOC_DAIFMT_I2S |
					SND_SOC_DAIFMT_NB_NF |
					SND_SOC_DAIFMT_CBS_CFS);
	if (err < 0) {
		pr_err(PREFIX "codec_dai fmt not set\n");
		return err;
	}

	err = snd_soc_dai_set_fmt(cpu_dai,
					SND_SOC_DAIFMT_I2S |
					SND_SOC_DAIFMT_NB_NF |
					SND_SOC_DAIFMT_CBS_CFS);
	if (err < 0) {
		pr_err(PREFIX "cpu_dai fmt not set\n");
		return err;
	}

	if (mclk_change) {
	    err = snd_soc_dai_set_sysclk(codec_dai, 0, mclk, SND_SOC_CLOCK_IN);
	    if (err < 0) {
		    pr_err(PREFIX "codec_dai clock not set\n");
		    return err;
	    }
	}

	return 0;
}

static struct snd_soc_ops harmony_asoc_ops = {
	.hw_params = harmony_asoc_hw_params,
};

116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
static const struct snd_soc_dapm_widget harmony_dapm_widgets[] = {
	SND_SOC_DAPM_HP("Headphone Jack", NULL),
	SND_SOC_DAPM_MIC("Mic Jack", NULL),
};

static const struct snd_soc_dapm_route harmony_audio_map[] = {
	{"Headphone Jack", NULL, "HPOUTR"},
	{"Headphone Jack", NULL, "HPOUTL"},
	{"Mic Bias", NULL, "Mic Jack"},
	{"IN1L", NULL, "Mic Bias"},
};

static int harmony_asoc_init(struct snd_soc_pcm_runtime *rtd)
{
	struct snd_soc_codec *codec = rtd->codec;
	struct snd_soc_dapm_context *dapm = &codec->dapm;

	snd_soc_dapm_new_controls(dapm, harmony_dapm_widgets,
					ARRAY_SIZE(harmony_dapm_widgets));

	snd_soc_dapm_add_routes(dapm, harmony_audio_map,
				ARRAY_SIZE(harmony_audio_map));

	snd_soc_dapm_enable_pin(dapm, "Headphone Jack");
	snd_soc_dapm_enable_pin(dapm, "Mic Jack");
	snd_soc_dapm_sync(dapm);

	return 0;
}

146 147 148 149 150 151 152
static struct snd_soc_dai_link harmony_wm8903_dai = {
	.name = "WM8903",
	.stream_name = "WM8903 PCM",
	.codec_name = "wm8903-codec.0-001a",
	.platform_name = "tegra-pcm-audio",
	.cpu_dai_name = "tegra-i2s.0",
	.codec_dai_name = "wm8903-hifi",
153
	.init = harmony_asoc_init,
154 155 156 157 158 159 160 161 162
	.ops = &harmony_asoc_ops,
};

static struct snd_soc_card snd_soc_harmony = {
	.name = "tegra-harmony",
	.dai_link = &harmony_wm8903_dai,
	.num_links = 1,
};

163
static __devinit int tegra_snd_harmony_probe(struct platform_device *pdev)
164
{
165 166
	struct snd_soc_card *card = &snd_soc_harmony;
	struct tegra_harmony *harmony;
167 168 169
	int ret;

	if (!machine_is_harmony()) {
170
		dev_err(&pdev->dev, "Not running on Tegra Harmony!\n");
171 172 173
		return -ENODEV;
	}

174 175 176 177
	harmony = kzalloc(sizeof(struct tegra_harmony), GFP_KERNEL);
	if (!harmony) {
		dev_err(&pdev->dev, "Can't allocate tegra_harmony\n");
		return -ENOMEM;
178 179
	}

180 181 182
	ret = tegra_asoc_utils_init();
	if (ret)
		goto err_free_harmony;
183

184 185 186
	card->dev = &pdev->dev;
	platform_set_drvdata(pdev, card);
	snd_soc_card_set_drvdata(card, harmony);
187

188
	ret = snd_soc_register_card(card);
189
	if (ret) {
190
		dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n",
191
			ret);
192
		goto err_clear_drvdata;
193 194 195 196
	}

	return 0;

197 198 199 200
err_clear_drvdata:
	snd_soc_card_set_drvdata(card, NULL);
	platform_set_drvdata(pdev, NULL);
	card->dev = NULL;
201
	tegra_asoc_utils_fini();
202 203
err_free_harmony:
	kfree(harmony);
204 205 206
	return ret;
}

207
static int __devexit tegra_snd_harmony_remove(struct platform_device *pdev)
208
{
209 210 211 212 213 214 215 216
	struct snd_soc_card *card = platform_get_drvdata(pdev);
	struct tegra_harmony *harmony = snd_soc_card_get_drvdata(card);

	snd_soc_unregister_card(card);

	snd_soc_card_set_drvdata(card, NULL);
	platform_set_drvdata(pdev, NULL);
	card->dev = NULL;
217 218

	tegra_asoc_utils_fini();
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242

	kfree(harmony);

	return 0;
}

static struct platform_driver tegra_snd_harmony_driver = {
	.driver = {
		.name = DRV_NAME,
		.owner = THIS_MODULE,
	},
	.probe = tegra_snd_harmony_probe,
	.remove = __devexit_p(tegra_snd_harmony_remove),
};

static int __init snd_tegra_harmony_init(void)
{
	return platform_driver_register(&tegra_snd_harmony_driver);
}
module_init(snd_tegra_harmony_init);

static void __exit snd_tegra_harmony_exit(void)
{
	platform_driver_unregister(&tegra_snd_harmony_driver);
243
}
244
module_exit(snd_tegra_harmony_exit);
245 246 247 248

MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>");
MODULE_DESCRIPTION("Harmony machine ASoC driver");
MODULE_LICENSE("GPL");