1/* 2* tegra_alc5632.c -- Toshiba AC100(PAZ00) machine ASoC driver 3 * 4 * Copyright (C) 2011 The AC100 Kernel Team <ac100@lists.lauchpad.net> 5 * Copyright (C) 2012 - NVIDIA, Inc. 6 * 7 * Authors: Leon Romanovsky <leon@leon.nu> 8 * Andrey Danin <danindrey@mail.ru> 9 * Marc Dietrich <marvin24@gmx.de> 10 * 11 * This program is free software; you can redistribute it and/or modify 12 * it under the terms of the GNU General Public License version 2 as 13 * published by the Free Software Foundation. 14 */ 15 16#include <linux/module.h> 17#include <linux/platform_device.h> 18#include <linux/slab.h> 19#include <linux/gpio.h> 20#include <linux/of_gpio.h> 21 22#include <sound/core.h> 23#include <sound/jack.h> 24#include <sound/pcm.h> 25#include <sound/pcm_params.h> 26#include <sound/soc.h> 27 28#include "../codecs/alc5632.h" 29 30#include "tegra_asoc_utils.h" 31 32#define DRV_NAME "tegra-alc5632" 33 34struct tegra_alc5632 { 35 struct tegra_asoc_utils_data util_data; 36 int gpio_hp_det; 37}; 38 39static int tegra_alc5632_asoc_hw_params(struct snd_pcm_substream *substream, 40 struct snd_pcm_hw_params *params) 41{ 42 struct snd_soc_pcm_runtime *rtd = substream->private_data; 43 struct snd_soc_dai *codec_dai = rtd->codec_dai; 44 struct snd_soc_card *card = rtd->card; 45 struct tegra_alc5632 *alc5632 = snd_soc_card_get_drvdata(card); 46 int srate, mclk; 47 int err; 48 49 srate = params_rate(params); 50 mclk = 512 * srate; 51 52 err = tegra_asoc_utils_set_rate(&alc5632->util_data, srate, mclk); 53 if (err < 0) { 54 dev_err(card->dev, "Can't configure clocks\n"); 55 return err; 56 } 57 58 err = snd_soc_dai_set_sysclk(codec_dai, 0, mclk, 59 SND_SOC_CLOCK_IN); 60 if (err < 0) { 61 dev_err(card->dev, "codec_dai clock not set\n"); 62 return err; 63 } 64 65 return 0; 66} 67 68static struct snd_soc_ops tegra_alc5632_asoc_ops = { 69 .hw_params = tegra_alc5632_asoc_hw_params, 70}; 71 72static struct snd_soc_jack tegra_alc5632_hs_jack; 73 74static struct snd_soc_jack_pin tegra_alc5632_hs_jack_pins[] = { 75 { 76 .pin = "Headset Mic", 77 .mask = SND_JACK_MICROPHONE, 78 }, 79 { 80 .pin = "Headset Stereophone", 81 .mask = SND_JACK_HEADPHONE, 82 }, 83}; 84 85static struct snd_soc_jack_gpio tegra_alc5632_hp_jack_gpio = { 86 .name = "Headset detection", 87 .report = SND_JACK_HEADSET, 88 .debounce_time = 150, 89}; 90 91static const struct snd_soc_dapm_widget tegra_alc5632_dapm_widgets[] = { 92 SND_SOC_DAPM_SPK("Int Spk", NULL), 93 SND_SOC_DAPM_HP("Headset Stereophone", NULL), 94 SND_SOC_DAPM_MIC("Headset Mic", NULL), 95 SND_SOC_DAPM_MIC("Digital Mic", NULL), 96}; 97 98static const struct snd_kcontrol_new tegra_alc5632_controls[] = { 99 SOC_DAPM_PIN_SWITCH("Int Spk"), 100}; 101 102static int tegra_alc5632_asoc_init(struct snd_soc_pcm_runtime *rtd) 103{ 104 struct tegra_alc5632 *machine = snd_soc_card_get_drvdata(rtd->card); 105 106 snd_soc_card_jack_new(rtd->card, "Headset Jack", SND_JACK_HEADSET, 107 &tegra_alc5632_hs_jack, 108 tegra_alc5632_hs_jack_pins, 109 ARRAY_SIZE(tegra_alc5632_hs_jack_pins)); 110 111 if (gpio_is_valid(machine->gpio_hp_det)) { 112 tegra_alc5632_hp_jack_gpio.gpio = machine->gpio_hp_det; 113 snd_soc_jack_add_gpios(&tegra_alc5632_hs_jack, 114 1, 115 &tegra_alc5632_hp_jack_gpio); 116 } 117 118 snd_soc_dapm_force_enable_pin(&rtd->card->dapm, "MICBIAS1"); 119 120 return 0; 121} 122 123static int tegra_alc5632_card_remove(struct snd_soc_card *card) 124{ 125 struct tegra_alc5632 *machine = snd_soc_card_get_drvdata(card); 126 127 if (gpio_is_valid(machine->gpio_hp_det)) { 128 snd_soc_jack_free_gpios(&tegra_alc5632_hs_jack, 1, 129 &tegra_alc5632_hp_jack_gpio); 130 } 131 132 return 0; 133} 134 135static struct snd_soc_dai_link tegra_alc5632_dai = { 136 .name = "ALC5632", 137 .stream_name = "ALC5632 PCM", 138 .codec_dai_name = "alc5632-hifi", 139 .init = tegra_alc5632_asoc_init, 140 .ops = &tegra_alc5632_asoc_ops, 141 .dai_fmt = SND_SOC_DAIFMT_I2S 142 | SND_SOC_DAIFMT_NB_NF 143 | SND_SOC_DAIFMT_CBS_CFS, 144}; 145 146static struct snd_soc_card snd_soc_tegra_alc5632 = { 147 .name = "tegra-alc5632", 148 .owner = THIS_MODULE, 149 .remove = tegra_alc5632_card_remove, 150 .dai_link = &tegra_alc5632_dai, 151 .num_links = 1, 152 .controls = tegra_alc5632_controls, 153 .num_controls = ARRAY_SIZE(tegra_alc5632_controls), 154 .dapm_widgets = tegra_alc5632_dapm_widgets, 155 .num_dapm_widgets = ARRAY_SIZE(tegra_alc5632_dapm_widgets), 156 .fully_routed = true, 157}; 158 159static int tegra_alc5632_probe(struct platform_device *pdev) 160{ 161 struct device_node *np = pdev->dev.of_node; 162 struct snd_soc_card *card = &snd_soc_tegra_alc5632; 163 struct tegra_alc5632 *alc5632; 164 int ret; 165 166 alc5632 = devm_kzalloc(&pdev->dev, 167 sizeof(struct tegra_alc5632), GFP_KERNEL); 168 if (!alc5632) { 169 dev_err(&pdev->dev, "Can't allocate tegra_alc5632\n"); 170 return -ENOMEM; 171 } 172 173 card->dev = &pdev->dev; 174 platform_set_drvdata(pdev, card); 175 snd_soc_card_set_drvdata(card, alc5632); 176 177 alc5632->gpio_hp_det = of_get_named_gpio(np, "nvidia,hp-det-gpios", 0); 178 if (alc5632->gpio_hp_det == -EPROBE_DEFER) 179 return -EPROBE_DEFER; 180 181 ret = snd_soc_of_parse_card_name(card, "nvidia,model"); 182 if (ret) 183 goto err; 184 185 ret = snd_soc_of_parse_audio_routing(card, "nvidia,audio-routing"); 186 if (ret) 187 goto err; 188 189 tegra_alc5632_dai.codec_of_node = of_parse_phandle( 190 pdev->dev.of_node, "nvidia,audio-codec", 0); 191 192 if (!tegra_alc5632_dai.codec_of_node) { 193 dev_err(&pdev->dev, 194 "Property 'nvidia,audio-codec' missing or invalid\n"); 195 ret = -EINVAL; 196 goto err; 197 } 198 199 tegra_alc5632_dai.cpu_of_node = of_parse_phandle(np, 200 "nvidia,i2s-controller", 0); 201 if (!tegra_alc5632_dai.cpu_of_node) { 202 dev_err(&pdev->dev, 203 "Property 'nvidia,i2s-controller' missing or invalid\n"); 204 ret = -EINVAL; 205 goto err; 206 } 207 208 tegra_alc5632_dai.platform_of_node = tegra_alc5632_dai.cpu_of_node; 209 210 ret = tegra_asoc_utils_init(&alc5632->util_data, &pdev->dev); 211 if (ret) 212 goto err; 213 214 ret = snd_soc_register_card(card); 215 if (ret) { 216 dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", 217 ret); 218 goto err_fini_utils; 219 } 220 221 return 0; 222 223err_fini_utils: 224 tegra_asoc_utils_fini(&alc5632->util_data); 225err: 226 return ret; 227} 228 229static int tegra_alc5632_remove(struct platform_device *pdev) 230{ 231 struct snd_soc_card *card = platform_get_drvdata(pdev); 232 struct tegra_alc5632 *machine = snd_soc_card_get_drvdata(card); 233 234 snd_soc_unregister_card(card); 235 236 tegra_asoc_utils_fini(&machine->util_data); 237 238 return 0; 239} 240 241static const struct of_device_id tegra_alc5632_of_match[] = { 242 { .compatible = "nvidia,tegra-audio-alc5632", }, 243 {}, 244}; 245 246static struct platform_driver tegra_alc5632_driver = { 247 .driver = { 248 .name = DRV_NAME, 249 .pm = &snd_soc_pm_ops, 250 .of_match_table = tegra_alc5632_of_match, 251 }, 252 .probe = tegra_alc5632_probe, 253 .remove = tegra_alc5632_remove, 254}; 255module_platform_driver(tegra_alc5632_driver); 256 257MODULE_AUTHOR("Leon Romanovsky <leon@leon.nu>"); 258MODULE_DESCRIPTION("Tegra+ALC5632 machine ASoC driver"); 259MODULE_LICENSE("GPL"); 260MODULE_ALIAS("platform:" DRV_NAME); 261MODULE_DEVICE_TABLE(of, tegra_alc5632_of_match); 262