1/* 2 * File: sound/soc/blackfin/bf5xx-tdm.c 3 * Author: Barry Song <Barry.Song@analog.com> 4 * 5 * Created: Thurs June 04 2009 6 * Description: Blackfin I2S(TDM) CPU DAI driver 7 * Even though TDM mode can be as part of I2S DAI, but there 8 * are so much difference in configuration and data flow, 9 * it's very ugly to integrate I2S and TDM into a module 10 * 11 * Modified: 12 * Copyright 2009 Analog Devices Inc. 13 * 14 * Bugs: Enter bugs at http://blackfin.uclinux.org/ 15 * 16 * This program is free software; you can redistribute it and/or modify 17 * it under the terms of the GNU General Public License as published by 18 * the Free Software Foundation; either version 2 of the License, or 19 * (at your option) any later version. 20 * 21 * This program is distributed in the hope that it will be useful, 22 * but WITHOUT ANY WARRANTY; without even the implied warranty of 23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 24 * GNU General Public License for more details. 25 * 26 * You should have received a copy of the GNU General Public License 27 * along with this program; if not, see the file COPYING, or write 28 * to the Free Software Foundation, Inc., 29 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 30 */ 31 32#include <linux/init.h> 33#include <linux/module.h> 34#include <linux/device.h> 35#include <sound/core.h> 36#include <sound/pcm.h> 37#include <sound/pcm_params.h> 38#include <sound/initval.h> 39#include <sound/soc.h> 40 41#include <asm/irq.h> 42#include <asm/portmux.h> 43#include <linux/mutex.h> 44#include <linux/gpio.h> 45 46#include "bf5xx-sport.h" 47#include "bf5xx-tdm.h" 48 49static struct bf5xx_tdm_port bf5xx_tdm; 50static int sport_num = CONFIG_SND_BF5XX_SPORT_NUM; 51 52static struct sport_param sport_params[2] = { 53 { 54 .dma_rx_chan = CH_SPORT0_RX, 55 .dma_tx_chan = CH_SPORT0_TX, 56 .err_irq = IRQ_SPORT0_ERROR, 57 .regs = (struct sport_register *)SPORT0_TCR1, 58 }, 59 { 60 .dma_rx_chan = CH_SPORT1_RX, 61 .dma_tx_chan = CH_SPORT1_TX, 62 .err_irq = IRQ_SPORT1_ERROR, 63 .regs = (struct sport_register *)SPORT1_TCR1, 64 } 65}; 66 67/* 68 * Setting the TFS pin selector for SPORT 0 based on whether the selected 69 * port id F or G. If the port is F then no conflict should exist for the 70 * TFS. When Port G is selected and EMAC then there is a conflict between 71 * the PHY interrupt line and TFS. Current settings prevent the conflict 72 * by ignoring the TFS pin when Port G is selected. This allows both 73 * codecs and EMAC using Port G concurrently. 74 */ 75#ifdef CONFIG_BF527_SPORT0_PORTG 76#define LOCAL_SPORT0_TFS (0) 77#else 78#define LOCAL_SPORT0_TFS (P_SPORT0_TFS) 79#endif 80 81static u16 sport_req[][7] = { {P_SPORT0_DTPRI, P_SPORT0_TSCLK, P_SPORT0_RFS, 82 P_SPORT0_DRPRI, P_SPORT0_RSCLK, LOCAL_SPORT0_TFS, 0}, 83 {P_SPORT1_DTPRI, P_SPORT1_TSCLK, P_SPORT1_RFS, P_SPORT1_DRPRI, 84 P_SPORT1_RSCLK, P_SPORT1_TFS, 0} }; 85 86static int bf5xx_tdm_set_dai_fmt(struct snd_soc_dai *cpu_dai, 87 unsigned int fmt) 88{ 89 int ret = 0; 90 91 /* interface format:support TDM,slave mode */ 92 switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { 93 case SND_SOC_DAIFMT_DSP_A: 94 break; 95 default: 96 printk(KERN_ERR "%s: Unknown DAI format type\n", __func__); 97 ret = -EINVAL; 98 break; 99 } 100 101 switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { 102 case SND_SOC_DAIFMT_CBM_CFM: 103 break; 104 case SND_SOC_DAIFMT_CBS_CFS: 105 case SND_SOC_DAIFMT_CBM_CFS: 106 case SND_SOC_DAIFMT_CBS_CFM: 107 ret = -EINVAL; 108 break; 109 default: 110 printk(KERN_ERR "%s: Unknown DAI master type\n", __func__); 111 ret = -EINVAL; 112 break; 113 } 114 115 return ret; 116} 117 118static int bf5xx_tdm_hw_params(struct snd_pcm_substream *substream, 119 struct snd_pcm_hw_params *params, 120 struct snd_soc_dai *dai) 121{ 122 int ret = 0; 123 124 bf5xx_tdm.tcr2 &= ~0x1f; 125 bf5xx_tdm.rcr2 &= ~0x1f; 126 switch (params_format(params)) { 127 case SNDRV_PCM_FORMAT_S32_LE: 128 bf5xx_tdm.tcr2 |= 31; 129 bf5xx_tdm.rcr2 |= 31; 130 sport_handle->wdsize = 4; 131 break; 132 /* at present, we only support 32bit transfer */ 133 default: 134 pr_err("not supported PCM format yet\n"); 135 return -EINVAL; 136 break; 137 } 138 139 if (!bf5xx_tdm.configured) { 140 /* 141 * TX and RX are not independent,they are enabled at the 142 * same time, even if only one side is running. So, we 143 * need to configure both of them at the time when the first 144 * stream is opened. 145 * 146 * CPU DAI:slave mode. 147 */ 148 ret = sport_config_rx(sport_handle, bf5xx_tdm.rcr1, 149 bf5xx_tdm.rcr2, 0, 0); 150 if (ret) { 151 pr_err("SPORT is busy!\n"); 152 return -EBUSY; 153 } 154 155 ret = sport_config_tx(sport_handle, bf5xx_tdm.tcr1, 156 bf5xx_tdm.tcr2, 0, 0); 157 if (ret) { 158 pr_err("SPORT is busy!\n"); 159 return -EBUSY; 160 } 161 162 bf5xx_tdm.configured = 1; 163 } 164 165 return 0; 166} 167 168static void bf5xx_tdm_shutdown(struct snd_pcm_substream *substream, 169 struct snd_soc_dai *dai) 170{ 171 /* No active stream, SPORT is allowed to be configured again. */ 172 if (!dai->active) 173 bf5xx_tdm.configured = 0; 174} 175 176static int bf5xx_tdm_set_channel_map(struct snd_soc_dai *dai, 177 unsigned int tx_num, unsigned int *tx_slot, 178 unsigned int rx_num, unsigned int *rx_slot) 179{ 180 int i; 181 unsigned int slot; 182 unsigned int tx_mapped = 0, rx_mapped = 0; 183 184 if ((tx_num > BFIN_TDM_DAI_MAX_SLOTS) || 185 (rx_num > BFIN_TDM_DAI_MAX_SLOTS)) 186 return -EINVAL; 187 188 for (i = 0; i < tx_num; i++) { 189 slot = tx_slot[i]; 190 if ((slot < BFIN_TDM_DAI_MAX_SLOTS) && 191 (!(tx_mapped & (1 << slot)))) { 192 bf5xx_tdm.tx_map[i] = slot; 193 tx_mapped |= 1 << slot; 194 } else 195 return -EINVAL; 196 } 197 for (i = 0; i < rx_num; i++) { 198 slot = rx_slot[i]; 199 if ((slot < BFIN_TDM_DAI_MAX_SLOTS) && 200 (!(rx_mapped & (1 << slot)))) { 201 bf5xx_tdm.rx_map[i] = slot; 202 rx_mapped |= 1 << slot; 203 } else 204 return -EINVAL; 205 } 206 207 return 0; 208} 209 210#ifdef CONFIG_PM 211static int bf5xx_tdm_suspend(struct snd_soc_dai *dai) 212{ 213 struct sport_device *sport = snd_soc_dai_get_drvdata(dai); 214 215 if (!dai->active) 216 return 0; 217 if (dai->capture.active) 218 sport_rx_stop(sport); 219 if (dai->playback.active) 220 sport_tx_stop(sport); 221 return 0; 222} 223 224static int bf5xx_tdm_resume(struct snd_soc_dai *dai) 225{ 226 int ret; 227 struct sport_device *sport = dai->private_data; 228 229 if (!dai->active) 230 return 0; 231 232 ret = sport_set_multichannel(sport, 8, 0xFF, 1); 233 if (ret) { 234 pr_err("SPORT is busy!\n"); 235 ret = -EBUSY; 236 } 237 238 ret = sport_config_rx(sport, IRFS, 0x1F, 0, 0); 239 if (ret) { 240 pr_err("SPORT is busy!\n"); 241 ret = -EBUSY; 242 } 243 244 ret = sport_config_tx(sport, ITFS, 0x1F, 0, 0); 245 if (ret) { 246 pr_err("SPORT is busy!\n"); 247 ret = -EBUSY; 248 } 249 250 return 0; 251} 252 253#else 254#define bf5xx_tdm_suspend NULL 255#define bf5xx_tdm_resume NULL 256#endif 257 258static struct snd_soc_dai_ops bf5xx_tdm_dai_ops = { 259 .hw_params = bf5xx_tdm_hw_params, 260 .set_fmt = bf5xx_tdm_set_dai_fmt, 261 .shutdown = bf5xx_tdm_shutdown, 262 .set_channel_map = bf5xx_tdm_set_channel_map, 263}; 264 265struct snd_soc_dai bf5xx_tdm_dai = { 266 .name = "bf5xx-tdm", 267 .id = 0, 268 .suspend = bf5xx_tdm_suspend, 269 .resume = bf5xx_tdm_resume, 270 .playback = { 271 .channels_min = 2, 272 .channels_max = 8, 273 .rates = SNDRV_PCM_RATE_48000, 274 .formats = SNDRV_PCM_FMTBIT_S32_LE,}, 275 .capture = { 276 .channels_min = 2, 277 .channels_max = 8, 278 .rates = SNDRV_PCM_RATE_48000, 279 .formats = SNDRV_PCM_FMTBIT_S32_LE,}, 280 .ops = &bf5xx_tdm_dai_ops, 281}; 282EXPORT_SYMBOL_GPL(bf5xx_tdm_dai); 283 284static int __devinit bfin_tdm_probe(struct platform_device *pdev) 285{ 286 int ret = 0; 287 288 if (peripheral_request_list(&sport_req[sport_num][0], "soc-audio")) { 289 pr_err("Requesting Peripherals failed\n"); 290 return -EFAULT; 291 } 292 293 /* request DMA for SPORT */ 294 sport_handle = sport_init(&sport_params[sport_num], 4, \ 295 8 * sizeof(u32), NULL); 296 if (!sport_handle) { 297 peripheral_free_list(&sport_req[sport_num][0]); 298 return -ENODEV; 299 } 300 301 /* SPORT works in TDM mode */ 302 ret = sport_set_multichannel(sport_handle, 8, 0xFF, 1); 303 if (ret) { 304 pr_err("SPORT is busy!\n"); 305 ret = -EBUSY; 306 goto sport_config_err; 307 } 308 309 ret = sport_config_rx(sport_handle, IRFS, 0x1F, 0, 0); 310 if (ret) { 311 pr_err("SPORT is busy!\n"); 312 ret = -EBUSY; 313 goto sport_config_err; 314 } 315 316 ret = sport_config_tx(sport_handle, ITFS, 0x1F, 0, 0); 317 if (ret) { 318 pr_err("SPORT is busy!\n"); 319 ret = -EBUSY; 320 goto sport_config_err; 321 } 322 323 ret = snd_soc_register_dai(&bf5xx_tdm_dai); 324 if (ret) { 325 pr_err("Failed to register DAI: %d\n", ret); 326 goto sport_config_err; 327 } 328 329 sport_handle->private_data = &bf5xx_tdm; 330 return 0; 331 332sport_config_err: 333 peripheral_free_list(&sport_req[sport_num][0]); 334 return ret; 335} 336 337static int __devexit bfin_tdm_remove(struct platform_device *pdev) 338{ 339 peripheral_free_list(&sport_req[sport_num][0]); 340 snd_soc_unregister_dai(&bf5xx_tdm_dai); 341 342 return 0; 343} 344 345static struct platform_driver bfin_tdm_driver = { 346 .probe = bfin_tdm_probe, 347 .remove = __devexit_p(bfin_tdm_remove), 348 .driver = { 349 .name = "bfin-tdm", 350 .owner = THIS_MODULE, 351 }, 352}; 353 354static int __init bfin_tdm_init(void) 355{ 356 return platform_driver_register(&bfin_tdm_driver); 357} 358module_init(bfin_tdm_init); 359 360static void __exit bfin_tdm_exit(void) 361{ 362 platform_driver_unregister(&bfin_tdm_driver); 363} 364module_exit(bfin_tdm_exit); 365 366/* Module information */ 367MODULE_AUTHOR("Barry Song"); 368MODULE_DESCRIPTION("TDM driver for ADI Blackfin"); 369MODULE_LICENSE("GPL"); 370