1/* 2* tegra_rt5640.c - Tegra machine ASoC driver for boards using WM8903 codec. 3 * 4 * Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved. 5 * 6 * This program is free software; you can redistribute it and/or modify it 7 * under the terms and conditions of the GNU General Public License, 8 * version 2, as published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope it will be useful, but WITHOUT 11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 13 * more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 * Based on code copyright/by: 19 * 20 * Copyright (C) 2010-2012 - NVIDIA, Inc. 21 * Copyright (C) 2011 The AC100 Kernel Team <ac100@lists.lauchpad.net> 22 * (c) 2009, 2010 Nvidia Graphics Pvt. Ltd. 23 * Copyright 2007 Wolfson Microelectronics PLC. 24 */ 25 26#include <linux/module.h> 27#include <linux/platform_device.h> 28#include <linux/slab.h> 29#include <linux/gpio.h> 30#include <linux/of_gpio.h> 31 32#include <sound/core.h> 33#include <sound/jack.h> 34#include <sound/pcm.h> 35#include <sound/pcm_params.h> 36#include <sound/soc.h> 37 38#include "../codecs/rt5640.h" 39 40#include "tegra_asoc_utils.h" 41 42#define DRV_NAME "tegra-snd-rt5640" 43 44struct tegra_rt5640 { 45 struct tegra_asoc_utils_data util_data; 46 int gpio_hp_det; 47 enum of_gpio_flags gpio_hp_det_flags; 48}; 49 50static int tegra_rt5640_asoc_hw_params(struct snd_pcm_substream *substream, 51 struct snd_pcm_hw_params *params) 52{ 53 struct snd_soc_pcm_runtime *rtd = substream->private_data; 54 struct snd_soc_dai *codec_dai = rtd->codec_dai; 55 struct snd_soc_card *card = rtd->card; 56 struct tegra_rt5640 *machine = snd_soc_card_get_drvdata(card); 57 int srate, mclk; 58 int err; 59 60 srate = params_rate(params); 61 mclk = 256 * srate; 62 63 err = tegra_asoc_utils_set_rate(&machine->util_data, srate, mclk); 64 if (err < 0) { 65 dev_err(card->dev, "Can't configure clocks\n"); 66 return err; 67 } 68 69 err = snd_soc_dai_set_sysclk(codec_dai, RT5640_SCLK_S_MCLK, mclk, 70 SND_SOC_CLOCK_IN); 71 if (err < 0) { 72 dev_err(card->dev, "codec_dai clock not set\n"); 73 return err; 74 } 75 76 return 0; 77} 78 79static struct snd_soc_ops tegra_rt5640_ops = { 80 .hw_params = tegra_rt5640_asoc_hw_params, 81}; 82 83static struct snd_soc_jack tegra_rt5640_hp_jack; 84 85static struct snd_soc_jack_pin tegra_rt5640_hp_jack_pins[] = { 86 { 87 .pin = "Headphones", 88 .mask = SND_JACK_HEADPHONE, 89 }, 90}; 91 92static struct snd_soc_jack_gpio tegra_rt5640_hp_jack_gpio = { 93 .name = "Headphone detection", 94 .report = SND_JACK_HEADPHONE, 95 .debounce_time = 150, 96 .invert = 1, 97}; 98 99static const struct snd_soc_dapm_widget tegra_rt5640_dapm_widgets[] = { 100 SND_SOC_DAPM_HP("Headphones", NULL), 101 SND_SOC_DAPM_SPK("Speakers", NULL), 102 SND_SOC_DAPM_MIC("Mic Jack", NULL), 103}; 104 105static const struct snd_kcontrol_new tegra_rt5640_controls[] = { 106 SOC_DAPM_PIN_SWITCH("Speakers"), 107}; 108 109static int tegra_rt5640_asoc_init(struct snd_soc_pcm_runtime *rtd) 110{ 111 struct tegra_rt5640 *machine = snd_soc_card_get_drvdata(rtd->card); 112 113 snd_soc_card_jack_new(rtd->card, "Headphones", SND_JACK_HEADPHONE, 114 &tegra_rt5640_hp_jack, tegra_rt5640_hp_jack_pins, 115 ARRAY_SIZE(tegra_rt5640_hp_jack_pins)); 116 117 if (gpio_is_valid(machine->gpio_hp_det)) { 118 tegra_rt5640_hp_jack_gpio.gpio = machine->gpio_hp_det; 119 tegra_rt5640_hp_jack_gpio.invert = 120 !!(machine->gpio_hp_det_flags & OF_GPIO_ACTIVE_LOW); 121 snd_soc_jack_add_gpios(&tegra_rt5640_hp_jack, 122 1, 123 &tegra_rt5640_hp_jack_gpio); 124 } 125 126 return 0; 127} 128 129static int tegra_rt5640_card_remove(struct snd_soc_card *card) 130{ 131 struct tegra_rt5640 *machine = snd_soc_card_get_drvdata(card); 132 133 if (gpio_is_valid(machine->gpio_hp_det)) { 134 snd_soc_jack_free_gpios(&tegra_rt5640_hp_jack, 1, 135 &tegra_rt5640_hp_jack_gpio); 136 } 137 138 return 0; 139} 140 141static struct snd_soc_dai_link tegra_rt5640_dai = { 142 .name = "RT5640", 143 .stream_name = "RT5640 PCM", 144 .codec_dai_name = "rt5640-aif1", 145 .init = tegra_rt5640_asoc_init, 146 .ops = &tegra_rt5640_ops, 147 .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | 148 SND_SOC_DAIFMT_CBS_CFS, 149}; 150 151static struct snd_soc_card snd_soc_tegra_rt5640 = { 152 .name = "tegra-rt5640", 153 .owner = THIS_MODULE, 154 .remove = tegra_rt5640_card_remove, 155 .dai_link = &tegra_rt5640_dai, 156 .num_links = 1, 157 .controls = tegra_rt5640_controls, 158 .num_controls = ARRAY_SIZE(tegra_rt5640_controls), 159 .dapm_widgets = tegra_rt5640_dapm_widgets, 160 .num_dapm_widgets = ARRAY_SIZE(tegra_rt5640_dapm_widgets), 161 .fully_routed = true, 162}; 163 164static int tegra_rt5640_probe(struct platform_device *pdev) 165{ 166 struct device_node *np = pdev->dev.of_node; 167 struct snd_soc_card *card = &snd_soc_tegra_rt5640; 168 struct tegra_rt5640 *machine; 169 int ret; 170 171 machine = devm_kzalloc(&pdev->dev, 172 sizeof(struct tegra_rt5640), GFP_KERNEL); 173 if (!machine) { 174 dev_err(&pdev->dev, "Can't allocate tegra_rt5640\n"); 175 return -ENOMEM; 176 } 177 178 card->dev = &pdev->dev; 179 platform_set_drvdata(pdev, card); 180 snd_soc_card_set_drvdata(card, machine); 181 182 machine->gpio_hp_det = of_get_named_gpio_flags( 183 np, "nvidia,hp-det-gpios", 0, &machine->gpio_hp_det_flags); 184 if (machine->gpio_hp_det == -EPROBE_DEFER) 185 return -EPROBE_DEFER; 186 187 ret = snd_soc_of_parse_card_name(card, "nvidia,model"); 188 if (ret) 189 goto err; 190 191 ret = snd_soc_of_parse_audio_routing(card, "nvidia,audio-routing"); 192 if (ret) 193 goto err; 194 195 tegra_rt5640_dai.codec_of_node = of_parse_phandle(np, 196 "nvidia,audio-codec", 0); 197 if (!tegra_rt5640_dai.codec_of_node) { 198 dev_err(&pdev->dev, 199 "Property 'nvidia,audio-codec' missing or invalid\n"); 200 ret = -EINVAL; 201 goto err; 202 } 203 204 tegra_rt5640_dai.cpu_of_node = of_parse_phandle(np, 205 "nvidia,i2s-controller", 0); 206 if (!tegra_rt5640_dai.cpu_of_node) { 207 dev_err(&pdev->dev, 208 "Property 'nvidia,i2s-controller' missing or invalid\n"); 209 ret = -EINVAL; 210 goto err; 211 } 212 213 tegra_rt5640_dai.platform_of_node = tegra_rt5640_dai.cpu_of_node; 214 215 ret = tegra_asoc_utils_init(&machine->util_data, &pdev->dev); 216 if (ret) 217 goto err; 218 219 ret = snd_soc_register_card(card); 220 if (ret) { 221 dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", 222 ret); 223 goto err_fini_utils; 224 } 225 226 return 0; 227 228err_fini_utils: 229 tegra_asoc_utils_fini(&machine->util_data); 230err: 231 return ret; 232} 233 234static int tegra_rt5640_remove(struct platform_device *pdev) 235{ 236 struct snd_soc_card *card = platform_get_drvdata(pdev); 237 struct tegra_rt5640 *machine = snd_soc_card_get_drvdata(card); 238 239 snd_soc_unregister_card(card); 240 241 tegra_asoc_utils_fini(&machine->util_data); 242 243 return 0; 244} 245 246static const struct of_device_id tegra_rt5640_of_match[] = { 247 { .compatible = "nvidia,tegra-audio-rt5640", }, 248 {}, 249}; 250 251static struct platform_driver tegra_rt5640_driver = { 252 .driver = { 253 .name = DRV_NAME, 254 .pm = &snd_soc_pm_ops, 255 .of_match_table = tegra_rt5640_of_match, 256 }, 257 .probe = tegra_rt5640_probe, 258 .remove = tegra_rt5640_remove, 259}; 260module_platform_driver(tegra_rt5640_driver); 261 262MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); 263MODULE_DESCRIPTION("Tegra+RT5640 machine ASoC driver"); 264MODULE_LICENSE("GPL v2"); 265MODULE_ALIAS("platform:" DRV_NAME); 266MODULE_DEVICE_TABLE(of, tegra_rt5640_of_match); 267