1/* 2 * trimslice.c - TrimSlice machine ASoC driver 3 * 4 * Copyright (C) 2011 - CompuLab, Ltd. 5 * Author: Mike Rapoport <mike@compulab.co.il> 6 * 7 * Based on code copyright/by: 8 * Author: Stephen Warren <swarren@nvidia.com> 9 * Copyright (C) 2010-2011 - NVIDIA, Inc. 10 * 11 * This program is free software; you can redistribute it and/or 12 * modify it under the terms of the GNU General Public License 13 * version 2 as published by the Free Software Foundation. 14 * 15 * This program is distributed in the hope that it will be useful, but 16 * WITHOUT ANY WARRANTY; without even the implied warranty of 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 * General Public License for more details. 19 * 20 * You should have received a copy of the GNU General Public License 21 * along with this program; if not, write to the Free Software 22 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 23 * 02110-1301 USA 24 * 25 */ 26 27#include <linux/module.h> 28#include <linux/of.h> 29#include <linux/platform_device.h> 30#include <linux/slab.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/tlv320aic23.h" 39 40#include "tegra_asoc_utils.h" 41 42#define DRV_NAME "tegra-snd-trimslice" 43 44struct tegra_trimslice { 45 struct tegra_asoc_utils_data util_data; 46}; 47 48static int trimslice_asoc_hw_params(struct snd_pcm_substream *substream, 49 struct snd_pcm_hw_params *params) 50{ 51 struct snd_soc_pcm_runtime *rtd = substream->private_data; 52 struct snd_soc_dai *codec_dai = rtd->codec_dai; 53 struct snd_soc_card *card = rtd->card; 54 struct tegra_trimslice *trimslice = snd_soc_card_get_drvdata(card); 55 int srate, mclk; 56 int err; 57 58 srate = params_rate(params); 59 mclk = 128 * srate; 60 61 err = tegra_asoc_utils_set_rate(&trimslice->util_data, srate, mclk); 62 if (err < 0) { 63 dev_err(card->dev, "Can't configure clocks\n"); 64 return err; 65 } 66 67 err = snd_soc_dai_set_sysclk(codec_dai, 0, mclk, 68 SND_SOC_CLOCK_IN); 69 if (err < 0) { 70 dev_err(card->dev, "codec_dai clock not set\n"); 71 return err; 72 } 73 74 return 0; 75} 76 77static struct snd_soc_ops trimslice_asoc_ops = { 78 .hw_params = trimslice_asoc_hw_params, 79}; 80 81static const struct snd_soc_dapm_widget trimslice_dapm_widgets[] = { 82 SND_SOC_DAPM_HP("Line Out", NULL), 83 SND_SOC_DAPM_LINE("Line In", NULL), 84}; 85 86static const struct snd_soc_dapm_route trimslice_audio_map[] = { 87 {"Line Out", NULL, "LOUT"}, 88 {"Line Out", NULL, "ROUT"}, 89 90 {"LLINEIN", NULL, "Line In"}, 91 {"RLINEIN", NULL, "Line In"}, 92}; 93 94static struct snd_soc_dai_link trimslice_tlv320aic23_dai = { 95 .name = "TLV320AIC23", 96 .stream_name = "AIC23", 97 .codec_dai_name = "tlv320aic23-hifi", 98 .ops = &trimslice_asoc_ops, 99 .dai_fmt = SND_SOC_DAIFMT_I2S | 100 SND_SOC_DAIFMT_NB_NF | 101 SND_SOC_DAIFMT_CBS_CFS, 102}; 103 104static struct snd_soc_card snd_soc_trimslice = { 105 .name = "tegra-trimslice", 106 .owner = THIS_MODULE, 107 .dai_link = &trimslice_tlv320aic23_dai, 108 .num_links = 1, 109 110 .dapm_widgets = trimslice_dapm_widgets, 111 .num_dapm_widgets = ARRAY_SIZE(trimslice_dapm_widgets), 112 .dapm_routes = trimslice_audio_map, 113 .num_dapm_routes = ARRAY_SIZE(trimslice_audio_map), 114 .fully_routed = true, 115}; 116 117static int tegra_snd_trimslice_probe(struct platform_device *pdev) 118{ 119 struct device_node *np = pdev->dev.of_node; 120 struct snd_soc_card *card = &snd_soc_trimslice; 121 struct tegra_trimslice *trimslice; 122 int ret; 123 124 trimslice = devm_kzalloc(&pdev->dev, sizeof(struct tegra_trimslice), 125 GFP_KERNEL); 126 if (!trimslice) { 127 dev_err(&pdev->dev, "Can't allocate tegra_trimslice\n"); 128 return -ENOMEM; 129 } 130 131 card->dev = &pdev->dev; 132 platform_set_drvdata(pdev, card); 133 snd_soc_card_set_drvdata(card, trimslice); 134 135 trimslice_tlv320aic23_dai.codec_of_node = of_parse_phandle(np, 136 "nvidia,audio-codec", 0); 137 if (!trimslice_tlv320aic23_dai.codec_of_node) { 138 dev_err(&pdev->dev, 139 "Property 'nvidia,audio-codec' missing or invalid\n"); 140 ret = -EINVAL; 141 goto err; 142 } 143 144 trimslice_tlv320aic23_dai.cpu_of_node = of_parse_phandle(np, 145 "nvidia,i2s-controller", 0); 146 if (!trimslice_tlv320aic23_dai.cpu_of_node) { 147 dev_err(&pdev->dev, 148 "Property 'nvidia,i2s-controller' missing or invalid\n"); 149 ret = -EINVAL; 150 goto err; 151 } 152 153 trimslice_tlv320aic23_dai.platform_of_node = 154 trimslice_tlv320aic23_dai.cpu_of_node; 155 156 ret = tegra_asoc_utils_init(&trimslice->util_data, &pdev->dev); 157 if (ret) 158 goto err; 159 160 ret = snd_soc_register_card(card); 161 if (ret) { 162 dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", 163 ret); 164 goto err_fini_utils; 165 } 166 167 return 0; 168 169err_fini_utils: 170 tegra_asoc_utils_fini(&trimslice->util_data); 171err: 172 return ret; 173} 174 175static int tegra_snd_trimslice_remove(struct platform_device *pdev) 176{ 177 struct snd_soc_card *card = platform_get_drvdata(pdev); 178 struct tegra_trimslice *trimslice = snd_soc_card_get_drvdata(card); 179 180 snd_soc_unregister_card(card); 181 182 tegra_asoc_utils_fini(&trimslice->util_data); 183 184 return 0; 185} 186 187static const struct of_device_id trimslice_of_match[] = { 188 { .compatible = "nvidia,tegra-audio-trimslice", }, 189 {}, 190}; 191MODULE_DEVICE_TABLE(of, trimslice_of_match); 192 193static struct platform_driver tegra_snd_trimslice_driver = { 194 .driver = { 195 .name = DRV_NAME, 196 .of_match_table = trimslice_of_match, 197 }, 198 .probe = tegra_snd_trimslice_probe, 199 .remove = tegra_snd_trimslice_remove, 200}; 201module_platform_driver(tegra_snd_trimslice_driver); 202 203MODULE_AUTHOR("Mike Rapoport <mike@compulab.co.il>"); 204MODULE_DESCRIPTION("Trimslice machine ASoC driver"); 205MODULE_LICENSE("GPL"); 206MODULE_ALIAS("platform:" DRV_NAME); 207