1/* 2 * bf6xx-i2s.c - Analog Devices BF6XX i2s interface driver 3 * 4 * Copyright (c) 2012 Analog Devices Inc. 5 * 6 * This program is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 2 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program; if not, write to the Free Software 17 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 18 */ 19 20#include <linux/device.h> 21#include <linux/init.h> 22#include <linux/module.h> 23#include <linux/platform_device.h> 24#include <sound/pcm.h> 25#include <sound/pcm_params.h> 26#include <sound/soc.h> 27#include <sound/soc-dai.h> 28 29#include "bf6xx-sport.h" 30 31struct sport_params param; 32 33static int bfin_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai, 34 unsigned int fmt) 35{ 36 struct sport_device *sport = snd_soc_dai_get_drvdata(cpu_dai); 37 struct device *dev = &sport->pdev->dev; 38 int ret = 0; 39 40 param.spctl &= ~(SPORT_CTL_OPMODE | SPORT_CTL_CKRE | SPORT_CTL_FSR 41 | SPORT_CTL_LFS | SPORT_CTL_LAFS); 42 switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { 43 case SND_SOC_DAIFMT_I2S: 44 param.spctl |= SPORT_CTL_OPMODE | SPORT_CTL_CKRE 45 | SPORT_CTL_LFS; 46 break; 47 case SND_SOC_DAIFMT_DSP_A: 48 param.spctl |= SPORT_CTL_FSR; 49 break; 50 case SND_SOC_DAIFMT_LEFT_J: 51 param.spctl |= SPORT_CTL_OPMODE | SPORT_CTL_LFS 52 | SPORT_CTL_LAFS; 53 break; 54 default: 55 dev_err(dev, "%s: Unknown DAI format type\n", __func__); 56 ret = -EINVAL; 57 break; 58 } 59 60 param.spctl &= ~(SPORT_CTL_ICLK | SPORT_CTL_IFS); 61 switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { 62 case SND_SOC_DAIFMT_CBM_CFM: 63 break; 64 case SND_SOC_DAIFMT_CBS_CFS: 65 case SND_SOC_DAIFMT_CBM_CFS: 66 case SND_SOC_DAIFMT_CBS_CFM: 67 ret = -EINVAL; 68 break; 69 default: 70 dev_err(dev, "%s: Unknown DAI master type\n", __func__); 71 ret = -EINVAL; 72 break; 73 } 74 75 return ret; 76} 77 78static int bfin_i2s_hw_params(struct snd_pcm_substream *substream, 79 struct snd_pcm_hw_params *params, 80 struct snd_soc_dai *dai) 81{ 82 struct sport_device *sport = snd_soc_dai_get_drvdata(dai); 83 struct device *dev = &sport->pdev->dev; 84 int ret = 0; 85 86 param.spctl &= ~SPORT_CTL_SLEN; 87 switch (params_format(params)) { 88 case SNDRV_PCM_FORMAT_S8: 89 param.spctl |= 0x70; 90 sport->wdsize = 1; 91 break; 92 case SNDRV_PCM_FORMAT_S16_LE: 93 param.spctl |= 0xf0; 94 sport->wdsize = 2; 95 break; 96 case SNDRV_PCM_FORMAT_S24_LE: 97 param.spctl |= 0x170; 98 sport->wdsize = 3; 99 break; 100 case SNDRV_PCM_FORMAT_S32_LE: 101 param.spctl |= 0x1f0; 102 sport->wdsize = 4; 103 break; 104 } 105 106 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { 107 ret = sport_set_tx_params(sport, ¶m); 108 if (ret) { 109 dev_err(dev, "SPORT tx is busy!\n"); 110 return ret; 111 } 112 } else { 113 ret = sport_set_rx_params(sport, ¶m); 114 if (ret) { 115 dev_err(dev, "SPORT rx is busy!\n"); 116 return ret; 117 } 118 } 119 return 0; 120} 121 122#ifdef CONFIG_PM 123static int bfin_i2s_suspend(struct snd_soc_dai *dai) 124{ 125 struct sport_device *sport = snd_soc_dai_get_drvdata(dai); 126 127 if (dai->capture_active) 128 sport_rx_stop(sport); 129 if (dai->playback_active) 130 sport_tx_stop(sport); 131 return 0; 132} 133 134static int bfin_i2s_resume(struct snd_soc_dai *dai) 135{ 136 struct sport_device *sport = snd_soc_dai_get_drvdata(dai); 137 struct device *dev = &sport->pdev->dev; 138 int ret; 139 140 ret = sport_set_tx_params(sport, ¶m); 141 if (ret) { 142 dev_err(dev, "SPORT tx is busy!\n"); 143 return ret; 144 } 145 ret = sport_set_rx_params(sport, ¶m); 146 if (ret) { 147 dev_err(dev, "SPORT rx is busy!\n"); 148 return ret; 149 } 150 151 return 0; 152} 153 154#else 155#define bfin_i2s_suspend NULL 156#define bfin_i2s_resume NULL 157#endif 158 159#define BFIN_I2S_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ 160 SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \ 161 SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | \ 162 SNDRV_PCM_RATE_96000) 163 164#define BFIN_I2S_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE | \ 165 SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) 166 167static struct snd_soc_dai_ops bfin_i2s_dai_ops = { 168 .hw_params = bfin_i2s_hw_params, 169 .set_fmt = bfin_i2s_set_dai_fmt, 170}; 171 172static struct snd_soc_dai_driver bfin_i2s_dai = { 173 .suspend = bfin_i2s_suspend, 174 .resume = bfin_i2s_resume, 175 .playback = { 176 .channels_min = 1, 177 .channels_max = 2, 178 .rates = BFIN_I2S_RATES, 179 .formats = BFIN_I2S_FORMATS, 180 }, 181 .capture = { 182 .channels_min = 1, 183 .channels_max = 2, 184 .rates = BFIN_I2S_RATES, 185 .formats = BFIN_I2S_FORMATS, 186 }, 187 .ops = &bfin_i2s_dai_ops, 188}; 189 190static const struct snd_soc_component_driver bfin_i2s_component = { 191 .name = "bfin-i2s", 192}; 193 194static int bfin_i2s_probe(struct platform_device *pdev) 195{ 196 struct sport_device *sport; 197 struct device *dev = &pdev->dev; 198 int ret; 199 200 sport = sport_create(pdev); 201 if (!sport) 202 return -ENODEV; 203 204 /* register with the ASoC layers */ 205 ret = snd_soc_register_component(dev, &bfin_i2s_component, 206 &bfin_i2s_dai, 1); 207 if (ret) { 208 dev_err(dev, "Failed to register DAI: %d\n", ret); 209 sport_delete(sport); 210 return ret; 211 } 212 platform_set_drvdata(pdev, sport); 213 214 return 0; 215} 216 217static int bfin_i2s_remove(struct platform_device *pdev) 218{ 219 struct sport_device *sport = platform_get_drvdata(pdev); 220 221 snd_soc_unregister_component(&pdev->dev); 222 sport_delete(sport); 223 224 return 0; 225} 226 227static struct platform_driver bfin_i2s_driver = { 228 .probe = bfin_i2s_probe, 229 .remove = bfin_i2s_remove, 230 .driver = { 231 .name = "bfin-i2s", 232 }, 233}; 234 235module_platform_driver(bfin_i2s_driver); 236 237MODULE_DESCRIPTION("Analog Devices BF6XX i2s interface driver"); 238MODULE_AUTHOR("Scott Jiang <Scott.Jiang.Linux@gmail.com>"); 239MODULE_LICENSE("GPL v2"); 240