/* sound/soc/samsung/smartq_wm8987.c * * Copyright 2010 Maurus Cuelenaere * * Based on smdk6410_wm8987.c * Copyright 2007 Wolfson Microelectronics PLC. - linux@wolfsonmicro.com * Graeme Gregory - graeme.gregory@wolfsonmicro.com * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * */ #include #include #include #include #include #include #include "i2s.h" #include "../codecs/wm8750.h" /* * WM8987 is register compatible with WM8750, so using that as base driver. */ static struct snd_soc_card snd_soc_smartq; static int smartq_hifi_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; unsigned int clk = 0; int ret; switch (params_rate(params)) { case 8000: case 16000: case 32000: case 48000: case 96000: clk = 12288000; break; case 11025: case 22050: case 44100: case 88200: clk = 11289600; break; } /* Use PCLK for I2S signal generation */ ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_RCLKSRC_0, 0, SND_SOC_CLOCK_IN); if (ret < 0) return ret; /* Gate the RCLK output on PAD */ ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_CDCLK, 0, SND_SOC_CLOCK_IN); if (ret < 0) return ret; /* set the codec system clock for DAC and ADC */ ret = snd_soc_dai_set_sysclk(codec_dai, WM8750_SYSCLK, clk, SND_SOC_CLOCK_IN); if (ret < 0) return ret; return 0; } /* * SmartQ WM8987 HiFi DAI operations. */ static struct snd_soc_ops smartq_hifi_ops = { .hw_params = smartq_hifi_hw_params, }; static struct snd_soc_jack smartq_jack; static struct snd_soc_jack_pin smartq_jack_pins[] = { /* Disable speaker when headphone is plugged in */ { .pin = "Internal Speaker", .mask = SND_JACK_HEADPHONE, }, }; static struct snd_soc_jack_gpio smartq_jack_gpios[] = { { .gpio = S3C64XX_GPL(12), .name = "headphone detect", .report = SND_JACK_HEADPHONE, .debounce_time = 200, }, }; static const struct snd_kcontrol_new wm8987_smartq_controls[] = { SOC_DAPM_PIN_SWITCH("Internal Speaker"), SOC_DAPM_PIN_SWITCH("Headphone Jack"), SOC_DAPM_PIN_SWITCH("Internal Mic"), }; static int smartq_speaker_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *k, int event) { gpio_set_value(S3C64XX_GPK(12), SND_SOC_DAPM_EVENT_OFF(event)); return 0; } static const struct snd_soc_dapm_widget wm8987_dapm_widgets[] = { SND_SOC_DAPM_SPK("Internal Speaker", smartq_speaker_event), SND_SOC_DAPM_HP("Headphone Jack", NULL), SND_SOC_DAPM_MIC("Internal Mic", NULL), }; static const struct snd_soc_dapm_route audio_map[] = { {"Headphone Jack", NULL, "LOUT2"}, {"Headphone Jack", NULL, "ROUT2"}, {"Internal Speaker", NULL, "LOUT2"}, {"Internal Speaker", NULL, "ROUT2"}, {"Mic Bias", NULL, "Internal Mic"}, {"LINPUT2", NULL, "Mic Bias"}, }; static int smartq_wm8987_init(struct snd_soc_pcm_runtime *rtd) { struct snd_soc_codec *codec = rtd->codec; struct snd_soc_dapm_context *dapm = &codec->dapm; int err = 0; /* set endpoints to not connected */ snd_soc_dapm_nc_pin(dapm, "LINPUT1"); snd_soc_dapm_nc_pin(dapm, "RINPUT1"); snd_soc_dapm_nc_pin(dapm, "OUT3"); snd_soc_dapm_nc_pin(dapm, "ROUT1"); /* set endpoints to default off mode */ snd_soc_dapm_disable_pin(dapm, "Headphone Jack"); /* Headphone jack detection */ err = snd_soc_jack_new(codec, "Headphone Jack", SND_JACK_HEADPHONE, &smartq_jack); if (err) return err; err = snd_soc_jack_add_pins(&smartq_jack, ARRAY_SIZE(smartq_jack_pins), smartq_jack_pins); if (err) return err; err = snd_soc_jack_add_gpios(&smartq_jack, ARRAY_SIZE(smartq_jack_gpios), smartq_jack_gpios); return err; } static int smartq_wm8987_card_remove(struct snd_soc_card *card) { snd_soc_jack_free_gpios(&smartq_jack, ARRAY_SIZE(smartq_jack_gpios), smartq_jack_gpios); return 0; } static struct snd_soc_dai_link smartq_dai[] = { { .name = "wm8987", .stream_name = "SmartQ Hi-Fi", .cpu_dai_name = "samsung-i2s.0", .codec_dai_name = "wm8750-hifi", .platform_name = "samsung-i2s.0", .codec_name = "wm8750.0-0x1a", .init = smartq_wm8987_init, .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS, .ops = &smartq_hifi_ops, }, }; static struct snd_soc_card snd_soc_smartq = { .name = "SmartQ", .owner = THIS_MODULE, .remove = smartq_wm8987_card_remove, .dai_link = smartq_dai, .num_links = ARRAY_SIZE(smartq_dai), .dapm_widgets = wm8987_dapm_widgets, .num_dapm_widgets = ARRAY_SIZE(wm8987_dapm_widgets), .dapm_routes = audio_map, .num_dapm_routes = ARRAY_SIZE(audio_map), .controls = wm8987_smartq_controls, .num_controls = ARRAY_SIZE(wm8987_smartq_controls), }; static struct platform_device *smartq_snd_device; static int __init smartq_init(void) { int ret; if (!machine_is_smartq7() && !machine_is_smartq5()) { pr_info("Only SmartQ is supported by this ASoC driver\n"); return -ENODEV; } smartq_snd_device = platform_device_alloc("soc-audio", -1); if (!smartq_snd_device) return -ENOMEM; platform_set_drvdata(smartq_snd_device, &snd_soc_smartq); ret = platform_device_add(smartq_snd_device); if (ret) { platform_device_put(smartq_snd_device); return ret; } /* Initialise GPIOs used by amplifiers */ ret = gpio_request(S3C64XX_GPK(12), "amplifiers shutdown"); if (ret) { dev_err(&smartq_snd_device->dev, "Failed to register GPK12\n"); goto err_unregister_device; } /* Disable amplifiers */ ret = gpio_direction_output(S3C64XX_GPK(12), 1); if (ret) { dev_err(&smartq_snd_device->dev, "Failed to configure GPK12\n"); goto err_free_gpio_amp_shut; } return 0; err_free_gpio_amp_shut: gpio_free(S3C64XX_GPK(12)); err_unregister_device: platform_device_unregister(smartq_snd_device); return ret; } static void __exit smartq_exit(void) { gpio_free(S3C64XX_GPK(12)); platform_device_unregister(smartq_snd_device); } module_init(smartq_init); module_exit(smartq_exit); /* Module information */ MODULE_AUTHOR("Maurus Cuelenaere "); MODULE_DESCRIPTION("ALSA SoC SmartQ WM8987"); MODULE_LICENSE("GPL");