1/* 2 * File: sound/soc/blackfin/bf5xx-i2s.c 3 * Author: Cliff Cai <Cliff.Cai@analog.com> 4 * 5 * Created: Tue June 06 2008 6 * Description: Blackfin I2S CPU DAI driver 7 * 8 * Modified: 9 * Copyright 2008 Analog Devices Inc. 10 * 11 * Bugs: Enter bugs at http://blackfin.uclinux.org/ 12 * 13 * This program is free software; you can redistribute it and/or modify 14 * it under the terms of the GNU General Public License as published by 15 * the Free Software Foundation; either version 2 of the License, or 16 * (at your option) any later version. 17 * 18 * This program is distributed in the hope that it will be useful, 19 * but WITHOUT ANY WARRANTY; without even the implied warranty of 20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 * GNU General Public License for more details. 22 * 23 * You should have received a copy of the GNU General Public License 24 * along with this program; if not, see the file COPYING, or write 25 * to the Free Software Foundation, Inc., 26 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 27 */ 28 29#include <linux/init.h> 30#include <linux/module.h> 31#include <linux/device.h> 32#include <linux/delay.h> 33#include <sound/core.h> 34#include <sound/pcm.h> 35#include <sound/pcm_params.h> 36#include <sound/initval.h> 37#include <sound/soc.h> 38 39#include <asm/irq.h> 40#include <asm/portmux.h> 41#include <linux/mutex.h> 42#include <linux/gpio.h> 43 44#include "bf5xx-sport.h" 45#include "bf5xx-i2s.h" 46 47struct bf5xx_i2s_port { 48 u16 tcr1; 49 u16 rcr1; 50 u16 tcr2; 51 u16 rcr2; 52 int configured; 53}; 54 55static struct bf5xx_i2s_port bf5xx_i2s; 56static int sport_num = CONFIG_SND_BF5XX_SPORT_NUM; 57 58static struct sport_param sport_params[2] = { 59 { 60 .dma_rx_chan = CH_SPORT0_RX, 61 .dma_tx_chan = CH_SPORT0_TX, 62 .err_irq = IRQ_SPORT0_ERROR, 63 .regs = (struct sport_register *)SPORT0_TCR1, 64 }, 65 { 66 .dma_rx_chan = CH_SPORT1_RX, 67 .dma_tx_chan = CH_SPORT1_TX, 68 .err_irq = IRQ_SPORT1_ERROR, 69 .regs = (struct sport_register *)SPORT1_TCR1, 70 } 71}; 72 73/* 74 * Setting the TFS pin selector for SPORT 0 based on whether the selected 75 * port id F or G. If the port is F then no conflict should exist for the 76 * TFS. When Port G is selected and EMAC then there is a conflict between 77 * the PHY interrupt line and TFS. Current settings prevent the conflict 78 * by ignoring the TFS pin when Port G is selected. This allows both 79 * codecs and EMAC using Port G concurrently. 80 */ 81#ifdef CONFIG_BF527_SPORT0_PORTG 82#define LOCAL_SPORT0_TFS (0) 83#else 84#define LOCAL_SPORT0_TFS (P_SPORT0_TFS) 85#endif 86 87static u16 sport_req[][7] = { {P_SPORT0_DTPRI, P_SPORT0_TSCLK, P_SPORT0_RFS, 88 P_SPORT0_DRPRI, P_SPORT0_RSCLK, LOCAL_SPORT0_TFS, 0}, 89 {P_SPORT1_DTPRI, P_SPORT1_TSCLK, P_SPORT1_RFS, P_SPORT1_DRPRI, 90 P_SPORT1_RSCLK, P_SPORT1_TFS, 0} }; 91 92static int bf5xx_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai, 93 unsigned int fmt) 94{ 95 int ret = 0; 96 97 /* interface format:support I2S,slave mode */ 98 switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { 99 case SND_SOC_DAIFMT_I2S: 100 bf5xx_i2s.tcr1 |= TFSR | TCKFE; 101 bf5xx_i2s.rcr1 |= RFSR | RCKFE; 102 bf5xx_i2s.tcr2 |= TSFSE; 103 bf5xx_i2s.rcr2 |= RSFSE; 104 break; 105 case SND_SOC_DAIFMT_DSP_A: 106 bf5xx_i2s.tcr1 |= TFSR; 107 bf5xx_i2s.rcr1 |= RFSR; 108 break; 109 case SND_SOC_DAIFMT_LEFT_J: 110 ret = -EINVAL; 111 break; 112 default: 113 printk(KERN_ERR "%s: Unknown DAI format type\n", __func__); 114 ret = -EINVAL; 115 break; 116 } 117 118 switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { 119 case SND_SOC_DAIFMT_CBM_CFM: 120 break; 121 case SND_SOC_DAIFMT_CBS_CFS: 122 case SND_SOC_DAIFMT_CBM_CFS: 123 case SND_SOC_DAIFMT_CBS_CFM: 124 ret = -EINVAL; 125 break; 126 default: 127 printk(KERN_ERR "%s: Unknown DAI master type\n", __func__); 128 ret = -EINVAL; 129 break; 130 } 131 132 return ret; 133} 134 135static int bf5xx_i2s_hw_params(struct snd_pcm_substream *substream, 136 struct snd_pcm_hw_params *params, 137 struct snd_soc_dai *dai) 138{ 139 int ret = 0; 140 141 bf5xx_i2s.tcr2 &= ~0x1f; 142 bf5xx_i2s.rcr2 &= ~0x1f; 143 switch (params_format(params)) { 144 case SNDRV_PCM_FORMAT_S16_LE: 145 bf5xx_i2s.tcr2 |= 15; 146 bf5xx_i2s.rcr2 |= 15; 147 sport_handle->wdsize = 2; 148 break; 149 case SNDRV_PCM_FORMAT_S24_LE: 150 bf5xx_i2s.tcr2 |= 23; 151 bf5xx_i2s.rcr2 |= 23; 152 sport_handle->wdsize = 3; 153 break; 154 case SNDRV_PCM_FORMAT_S32_LE: 155 bf5xx_i2s.tcr2 |= 31; 156 bf5xx_i2s.rcr2 |= 31; 157 sport_handle->wdsize = 4; 158 break; 159 } 160 161 if (!bf5xx_i2s.configured) { 162 /* 163 * TX and RX are not independent,they are enabled at the 164 * same time, even if only one side is running. So, we 165 * need to configure both of them at the time when the first 166 * stream is opened. 167 * 168 * CPU DAI:slave mode. 169 */ 170 bf5xx_i2s.configured = 1; 171 ret = sport_config_rx(sport_handle, bf5xx_i2s.rcr1, 172 bf5xx_i2s.rcr2, 0, 0); 173 if (ret) { 174 pr_err("SPORT is busy!\n"); 175 return -EBUSY; 176 } 177 178 ret = sport_config_tx(sport_handle, bf5xx_i2s.tcr1, 179 bf5xx_i2s.tcr2, 0, 0); 180 if (ret) { 181 pr_err("SPORT is busy!\n"); 182 return -EBUSY; 183 } 184 } 185 186 return 0; 187} 188 189static void bf5xx_i2s_shutdown(struct snd_pcm_substream *substream, 190 struct snd_soc_dai *dai) 191{ 192 pr_debug("%s enter\n", __func__); 193 /* No active stream, SPORT is allowed to be configured again. */ 194 if (!dai->active) 195 bf5xx_i2s.configured = 0; 196} 197 198static int bf5xx_i2s_probe(struct platform_device *pdev, 199 struct snd_soc_dai *dai) 200{ 201 pr_debug("%s enter\n", __func__); 202 if (peripheral_request_list(&sport_req[sport_num][0], "soc-audio")) { 203 pr_err("Requesting Peripherals failed\n"); 204 return -EFAULT; 205 } 206 207 /* request DMA for SPORT */ 208 sport_handle = sport_init(&sport_params[sport_num], 4, \ 209 2 * sizeof(u32), NULL); 210 if (!sport_handle) { 211 peripheral_free_list(&sport_req[sport_num][0]); 212 return -ENODEV; 213 } 214 215 return 0; 216} 217 218static void bf5xx_i2s_remove(struct platform_device *pdev, 219 struct snd_soc_dai *dai) 220{ 221 pr_debug("%s enter\n", __func__); 222 peripheral_free_list(&sport_req[sport_num][0]); 223} 224 225#ifdef CONFIG_PM 226static int bf5xx_i2s_suspend(struct snd_soc_dai *dai) 227{ 228 229 pr_debug("%s : sport %d\n", __func__, dai->id); 230 231 if (dai->capture.active) 232 sport_rx_stop(sport_handle); 233 if (dai->playback.active) 234 sport_tx_stop(sport_handle); 235 return 0; 236} 237 238static int bf5xx_i2s_resume(struct snd_soc_dai *dai) 239{ 240 int ret; 241 242 pr_debug("%s : sport %d\n", __func__, dai->id); 243 244 ret = sport_config_rx(sport_handle, bf5xx_i2s.rcr1, 245 bf5xx_i2s.rcr2, 0, 0); 246 if (ret) { 247 pr_err("SPORT is busy!\n"); 248 return -EBUSY; 249 } 250 251 ret = sport_config_tx(sport_handle, bf5xx_i2s.tcr1, 252 bf5xx_i2s.tcr2, 0, 0); 253 if (ret) { 254 pr_err("SPORT is busy!\n"); 255 return -EBUSY; 256 } 257 258 return 0; 259} 260 261#else 262#define bf5xx_i2s_suspend NULL 263#define bf5xx_i2s_resume NULL 264#endif 265 266#define BF5XX_I2S_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ 267 SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \ 268 SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | \ 269 SNDRV_PCM_RATE_96000) 270 271#define BF5XX_I2S_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE |\ 272 SNDRV_PCM_FMTBIT_S32_LE) 273 274static struct snd_soc_dai_ops bf5xx_i2s_dai_ops = { 275 .shutdown = bf5xx_i2s_shutdown, 276 .hw_params = bf5xx_i2s_hw_params, 277 .set_fmt = bf5xx_i2s_set_dai_fmt, 278}; 279 280struct snd_soc_dai bf5xx_i2s_dai = { 281 .name = "bf5xx-i2s", 282 .id = 0, 283 .probe = bf5xx_i2s_probe, 284 .remove = bf5xx_i2s_remove, 285 .suspend = bf5xx_i2s_suspend, 286 .resume = bf5xx_i2s_resume, 287 .playback = { 288 .channels_min = 1, 289 .channels_max = 2, 290 .rates = BF5XX_I2S_RATES, 291 .formats = BF5XX_I2S_FORMATS,}, 292 .capture = { 293 .channels_min = 1, 294 .channels_max = 2, 295 .rates = BF5XX_I2S_RATES, 296 .formats = BF5XX_I2S_FORMATS,}, 297 .ops = &bf5xx_i2s_dai_ops, 298}; 299EXPORT_SYMBOL_GPL(bf5xx_i2s_dai); 300 301static int __init bfin_i2s_init(void) 302{ 303 return snd_soc_register_dai(&bf5xx_i2s_dai); 304} 305module_init(bfin_i2s_init); 306 307static void __exit bfin_i2s_exit(void) 308{ 309 snd_soc_unregister_dai(&bf5xx_i2s_dai); 310} 311module_exit(bfin_i2s_exit); 312 313/* Module information */ 314MODULE_AUTHOR("Cliff Cai"); 315MODULE_DESCRIPTION("I2S driver for ADI Blackfin"); 316MODULE_LICENSE("GPL"); 317