Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at v3.6 533 lines 14 kB view raw
1/* 2 * omap-mcpdm.c -- OMAP ALSA SoC DAI driver using McPDM port 3 * 4 * Copyright (C) 2009 - 2011 Texas Instruments 5 * 6 * Author: Misael Lopez Cruz <misael.lopez@ti.com> 7 * Contact: Jorge Eduardo Candelaria <x0107209@ti.com> 8 * Margarita Olaya <magi.olaya@ti.com> 9 * Peter Ujfalusi <peter.ujfalusi@ti.com> 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/init.h> 28#include <linux/module.h> 29#include <linux/platform_device.h> 30#include <linux/interrupt.h> 31#include <linux/err.h> 32#include <linux/io.h> 33#include <linux/irq.h> 34#include <linux/slab.h> 35#include <linux/pm_runtime.h> 36#include <linux/of_device.h> 37 38#include <sound/core.h> 39#include <sound/pcm.h> 40#include <sound/pcm_params.h> 41#include <sound/soc.h> 42 43#include <plat/dma.h> 44#include <plat/omap_hwmod.h> 45#include "omap-mcpdm.h" 46#include "omap-pcm.h" 47 48struct omap_mcpdm { 49 struct device *dev; 50 unsigned long phys_base; 51 void __iomem *io_base; 52 int irq; 53 54 struct mutex mutex; 55 56 /* channel data */ 57 u32 dn_channels; 58 u32 up_channels; 59 60 /* McPDM FIFO thresholds */ 61 u32 dn_threshold; 62 u32 up_threshold; 63 64 /* McPDM dn offsets for rx1, and 2 channels */ 65 u32 dn_rx_offset; 66}; 67 68/* 69 * Stream DMA parameters 70 */ 71static struct omap_pcm_dma_data omap_mcpdm_dai_dma_params[] = { 72 { 73 .name = "Audio playback", 74 .dma_req = OMAP44XX_DMA_MCPDM_DL, 75 .data_type = OMAP_DMA_DATA_TYPE_S32, 76 .sync_mode = OMAP_DMA_SYNC_PACKET, 77 .port_addr = OMAP44XX_MCPDM_L3_BASE + MCPDM_REG_DN_DATA, 78 }, 79 { 80 .name = "Audio capture", 81 .dma_req = OMAP44XX_DMA_MCPDM_UP, 82 .data_type = OMAP_DMA_DATA_TYPE_S32, 83 .sync_mode = OMAP_DMA_SYNC_PACKET, 84 .port_addr = OMAP44XX_MCPDM_L3_BASE + MCPDM_REG_UP_DATA, 85 }, 86}; 87 88static inline void omap_mcpdm_write(struct omap_mcpdm *mcpdm, u16 reg, u32 val) 89{ 90 __raw_writel(val, mcpdm->io_base + reg); 91} 92 93static inline int omap_mcpdm_read(struct omap_mcpdm *mcpdm, u16 reg) 94{ 95 return __raw_readl(mcpdm->io_base + reg); 96} 97 98#ifdef DEBUG 99static void omap_mcpdm_reg_dump(struct omap_mcpdm *mcpdm) 100{ 101 dev_dbg(mcpdm->dev, "***********************\n"); 102 dev_dbg(mcpdm->dev, "IRQSTATUS_RAW: 0x%04x\n", 103 omap_mcpdm_read(mcpdm, MCPDM_REG_IRQSTATUS_RAW)); 104 dev_dbg(mcpdm->dev, "IRQSTATUS: 0x%04x\n", 105 omap_mcpdm_read(mcpdm, MCPDM_REG_IRQSTATUS)); 106 dev_dbg(mcpdm->dev, "IRQENABLE_SET: 0x%04x\n", 107 omap_mcpdm_read(mcpdm, MCPDM_REG_IRQENABLE_SET)); 108 dev_dbg(mcpdm->dev, "IRQENABLE_CLR: 0x%04x\n", 109 omap_mcpdm_read(mcpdm, MCPDM_REG_IRQENABLE_CLR)); 110 dev_dbg(mcpdm->dev, "IRQWAKE_EN: 0x%04x\n", 111 omap_mcpdm_read(mcpdm, MCPDM_REG_IRQWAKE_EN)); 112 dev_dbg(mcpdm->dev, "DMAENABLE_SET: 0x%04x\n", 113 omap_mcpdm_read(mcpdm, MCPDM_REG_DMAENABLE_SET)); 114 dev_dbg(mcpdm->dev, "DMAENABLE_CLR: 0x%04x\n", 115 omap_mcpdm_read(mcpdm, MCPDM_REG_DMAENABLE_CLR)); 116 dev_dbg(mcpdm->dev, "DMAWAKEEN: 0x%04x\n", 117 omap_mcpdm_read(mcpdm, MCPDM_REG_DMAWAKEEN)); 118 dev_dbg(mcpdm->dev, "CTRL: 0x%04x\n", 119 omap_mcpdm_read(mcpdm, MCPDM_REG_CTRL)); 120 dev_dbg(mcpdm->dev, "DN_DATA: 0x%04x\n", 121 omap_mcpdm_read(mcpdm, MCPDM_REG_DN_DATA)); 122 dev_dbg(mcpdm->dev, "UP_DATA: 0x%04x\n", 123 omap_mcpdm_read(mcpdm, MCPDM_REG_UP_DATA)); 124 dev_dbg(mcpdm->dev, "FIFO_CTRL_DN: 0x%04x\n", 125 omap_mcpdm_read(mcpdm, MCPDM_REG_FIFO_CTRL_DN)); 126 dev_dbg(mcpdm->dev, "FIFO_CTRL_UP: 0x%04x\n", 127 omap_mcpdm_read(mcpdm, MCPDM_REG_FIFO_CTRL_UP)); 128 dev_dbg(mcpdm->dev, "***********************\n"); 129} 130#else 131static void omap_mcpdm_reg_dump(struct omap_mcpdm *mcpdm) {} 132#endif 133 134/* 135 * Enables the transfer through the PDM interface to/from the Phoenix 136 * codec by enabling the corresponding UP or DN channels. 137 */ 138static void omap_mcpdm_start(struct omap_mcpdm *mcpdm) 139{ 140 u32 ctrl = omap_mcpdm_read(mcpdm, MCPDM_REG_CTRL); 141 142 ctrl |= (MCPDM_SW_DN_RST | MCPDM_SW_UP_RST); 143 omap_mcpdm_write(mcpdm, MCPDM_REG_CTRL, ctrl); 144 145 ctrl |= mcpdm->dn_channels | mcpdm->up_channels; 146 omap_mcpdm_write(mcpdm, MCPDM_REG_CTRL, ctrl); 147 148 ctrl &= ~(MCPDM_SW_DN_RST | MCPDM_SW_UP_RST); 149 omap_mcpdm_write(mcpdm, MCPDM_REG_CTRL, ctrl); 150} 151 152/* 153 * Disables the transfer through the PDM interface to/from the Phoenix 154 * codec by disabling the corresponding UP or DN channels. 155 */ 156static void omap_mcpdm_stop(struct omap_mcpdm *mcpdm) 157{ 158 u32 ctrl = omap_mcpdm_read(mcpdm, MCPDM_REG_CTRL); 159 160 ctrl |= (MCPDM_SW_DN_RST | MCPDM_SW_UP_RST); 161 omap_mcpdm_write(mcpdm, MCPDM_REG_CTRL, ctrl); 162 163 ctrl &= ~(mcpdm->dn_channels | mcpdm->up_channels); 164 omap_mcpdm_write(mcpdm, MCPDM_REG_CTRL, ctrl); 165 166 ctrl &= ~(MCPDM_SW_DN_RST | MCPDM_SW_UP_RST); 167 omap_mcpdm_write(mcpdm, MCPDM_REG_CTRL, ctrl); 168 169} 170 171/* 172 * Is the physical McPDM interface active. 173 */ 174static inline int omap_mcpdm_active(struct omap_mcpdm *mcpdm) 175{ 176 return omap_mcpdm_read(mcpdm, MCPDM_REG_CTRL) & 177 (MCPDM_PDM_DN_MASK | MCPDM_PDM_UP_MASK); 178} 179 180/* 181 * Configures McPDM uplink, and downlink for audio. 182 * This function should be called before omap_mcpdm_start. 183 */ 184static void omap_mcpdm_open_streams(struct omap_mcpdm *mcpdm) 185{ 186 omap_mcpdm_write(mcpdm, MCPDM_REG_IRQENABLE_SET, 187 MCPDM_DN_IRQ_EMPTY | MCPDM_DN_IRQ_FULL | 188 MCPDM_UP_IRQ_EMPTY | MCPDM_UP_IRQ_FULL); 189 190 /* Enable DN RX1/2 offset cancellation feature, if configured */ 191 if (mcpdm->dn_rx_offset) { 192 u32 dn_offset = mcpdm->dn_rx_offset; 193 194 omap_mcpdm_write(mcpdm, MCPDM_REG_DN_OFFSET, dn_offset); 195 dn_offset |= (MCPDM_DN_OFST_RX1_EN | MCPDM_DN_OFST_RX2_EN); 196 omap_mcpdm_write(mcpdm, MCPDM_REG_DN_OFFSET, dn_offset); 197 } 198 199 omap_mcpdm_write(mcpdm, MCPDM_REG_FIFO_CTRL_DN, mcpdm->dn_threshold); 200 omap_mcpdm_write(mcpdm, MCPDM_REG_FIFO_CTRL_UP, mcpdm->up_threshold); 201 202 omap_mcpdm_write(mcpdm, MCPDM_REG_DMAENABLE_SET, 203 MCPDM_DMA_DN_ENABLE | MCPDM_DMA_UP_ENABLE); 204} 205 206/* 207 * Cleans McPDM uplink, and downlink configuration. 208 * This function should be called when the stream is closed. 209 */ 210static void omap_mcpdm_close_streams(struct omap_mcpdm *mcpdm) 211{ 212 /* Disable irq request generation for downlink */ 213 omap_mcpdm_write(mcpdm, MCPDM_REG_IRQENABLE_CLR, 214 MCPDM_DN_IRQ_EMPTY | MCPDM_DN_IRQ_FULL); 215 216 /* Disable DMA request generation for downlink */ 217 omap_mcpdm_write(mcpdm, MCPDM_REG_DMAENABLE_CLR, MCPDM_DMA_DN_ENABLE); 218 219 /* Disable irq request generation for uplink */ 220 omap_mcpdm_write(mcpdm, MCPDM_REG_IRQENABLE_CLR, 221 MCPDM_UP_IRQ_EMPTY | MCPDM_UP_IRQ_FULL); 222 223 /* Disable DMA request generation for uplink */ 224 omap_mcpdm_write(mcpdm, MCPDM_REG_DMAENABLE_CLR, MCPDM_DMA_UP_ENABLE); 225 226 /* Disable RX1/2 offset cancellation */ 227 if (mcpdm->dn_rx_offset) 228 omap_mcpdm_write(mcpdm, MCPDM_REG_DN_OFFSET, 0); 229} 230 231static irqreturn_t omap_mcpdm_irq_handler(int irq, void *dev_id) 232{ 233 struct omap_mcpdm *mcpdm = dev_id; 234 int irq_status; 235 236 irq_status = omap_mcpdm_read(mcpdm, MCPDM_REG_IRQSTATUS); 237 238 /* Acknowledge irq event */ 239 omap_mcpdm_write(mcpdm, MCPDM_REG_IRQSTATUS, irq_status); 240 241 if (irq_status & MCPDM_DN_IRQ_FULL) 242 dev_dbg(mcpdm->dev, "DN (playback) FIFO Full\n"); 243 244 if (irq_status & MCPDM_DN_IRQ_EMPTY) 245 dev_dbg(mcpdm->dev, "DN (playback) FIFO Empty\n"); 246 247 if (irq_status & MCPDM_DN_IRQ) 248 dev_dbg(mcpdm->dev, "DN (playback) write request\n"); 249 250 if (irq_status & MCPDM_UP_IRQ_FULL) 251 dev_dbg(mcpdm->dev, "UP (capture) FIFO Full\n"); 252 253 if (irq_status & MCPDM_UP_IRQ_EMPTY) 254 dev_dbg(mcpdm->dev, "UP (capture) FIFO Empty\n"); 255 256 if (irq_status & MCPDM_UP_IRQ) 257 dev_dbg(mcpdm->dev, "UP (capture) write request\n"); 258 259 return IRQ_HANDLED; 260} 261 262static int omap_mcpdm_dai_startup(struct snd_pcm_substream *substream, 263 struct snd_soc_dai *dai) 264{ 265 struct omap_mcpdm *mcpdm = snd_soc_dai_get_drvdata(dai); 266 267 mutex_lock(&mcpdm->mutex); 268 269 if (!dai->active) { 270 /* Enable watch dog for ES above ES 1.0 to avoid saturation */ 271 if (omap_rev() != OMAP4430_REV_ES1_0) { 272 u32 ctrl = omap_mcpdm_read(mcpdm, MCPDM_REG_CTRL); 273 274 omap_mcpdm_write(mcpdm, MCPDM_REG_CTRL, 275 ctrl | MCPDM_WD_EN); 276 } 277 omap_mcpdm_open_streams(mcpdm); 278 } 279 280 mutex_unlock(&mcpdm->mutex); 281 282 return 0; 283} 284 285static void omap_mcpdm_dai_shutdown(struct snd_pcm_substream *substream, 286 struct snd_soc_dai *dai) 287{ 288 struct omap_mcpdm *mcpdm = snd_soc_dai_get_drvdata(dai); 289 290 mutex_lock(&mcpdm->mutex); 291 292 if (!dai->active) { 293 if (omap_mcpdm_active(mcpdm)) { 294 omap_mcpdm_stop(mcpdm); 295 omap_mcpdm_close_streams(mcpdm); 296 } 297 } 298 299 mutex_unlock(&mcpdm->mutex); 300} 301 302static int omap_mcpdm_dai_hw_params(struct snd_pcm_substream *substream, 303 struct snd_pcm_hw_params *params, 304 struct snd_soc_dai *dai) 305{ 306 struct omap_mcpdm *mcpdm = snd_soc_dai_get_drvdata(dai); 307 int stream = substream->stream; 308 struct omap_pcm_dma_data *dma_data; 309 int channels; 310 int link_mask = 0; 311 312 channels = params_channels(params); 313 switch (channels) { 314 case 5: 315 if (stream == SNDRV_PCM_STREAM_CAPTURE) 316 /* up to 3 channels for capture */ 317 return -EINVAL; 318 link_mask |= 1 << 4; 319 case 4: 320 if (stream == SNDRV_PCM_STREAM_CAPTURE) 321 /* up to 3 channels for capture */ 322 return -EINVAL; 323 link_mask |= 1 << 3; 324 case 3: 325 link_mask |= 1 << 2; 326 case 2: 327 link_mask |= 1 << 1; 328 case 1: 329 link_mask |= 1 << 0; 330 break; 331 default: 332 /* unsupported number of channels */ 333 return -EINVAL; 334 } 335 336 dma_data = &omap_mcpdm_dai_dma_params[stream]; 337 338 /* Configure McPDM channels, and DMA packet size */ 339 if (stream == SNDRV_PCM_STREAM_PLAYBACK) { 340 mcpdm->dn_channels = link_mask << 3; 341 dma_data->packet_size = 342 (MCPDM_DN_THRES_MAX - mcpdm->dn_threshold) * channels; 343 } else { 344 mcpdm->up_channels = link_mask << 0; 345 dma_data->packet_size = mcpdm->up_threshold * channels; 346 } 347 348 snd_soc_dai_set_dma_data(dai, substream, dma_data); 349 350 return 0; 351} 352 353static int omap_mcpdm_prepare(struct snd_pcm_substream *substream, 354 struct snd_soc_dai *dai) 355{ 356 struct omap_mcpdm *mcpdm = snd_soc_dai_get_drvdata(dai); 357 358 if (!omap_mcpdm_active(mcpdm)) { 359 omap_mcpdm_start(mcpdm); 360 omap_mcpdm_reg_dump(mcpdm); 361 } 362 363 return 0; 364} 365 366static const struct snd_soc_dai_ops omap_mcpdm_dai_ops = { 367 .startup = omap_mcpdm_dai_startup, 368 .shutdown = omap_mcpdm_dai_shutdown, 369 .hw_params = omap_mcpdm_dai_hw_params, 370 .prepare = omap_mcpdm_prepare, 371}; 372 373static int omap_mcpdm_probe(struct snd_soc_dai *dai) 374{ 375 struct omap_mcpdm *mcpdm = snd_soc_dai_get_drvdata(dai); 376 int ret; 377 378 pm_runtime_enable(mcpdm->dev); 379 380 /* Disable lines while request is ongoing */ 381 pm_runtime_get_sync(mcpdm->dev); 382 omap_mcpdm_write(mcpdm, MCPDM_REG_CTRL, 0x00); 383 384 ret = request_irq(mcpdm->irq, omap_mcpdm_irq_handler, 385 0, "McPDM", (void *)mcpdm); 386 387 pm_runtime_put_sync(mcpdm->dev); 388 389 if (ret) { 390 dev_err(mcpdm->dev, "Request for IRQ failed\n"); 391 pm_runtime_disable(mcpdm->dev); 392 } 393 394 /* Configure McPDM threshold values */ 395 mcpdm->dn_threshold = 2; 396 mcpdm->up_threshold = MCPDM_UP_THRES_MAX - 3; 397 return ret; 398} 399 400static int omap_mcpdm_remove(struct snd_soc_dai *dai) 401{ 402 struct omap_mcpdm *mcpdm = snd_soc_dai_get_drvdata(dai); 403 404 free_irq(mcpdm->irq, (void *)mcpdm); 405 pm_runtime_disable(mcpdm->dev); 406 407 return 0; 408} 409 410#define OMAP_MCPDM_RATES (SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) 411#define OMAP_MCPDM_FORMATS SNDRV_PCM_FMTBIT_S32_LE 412 413static struct snd_soc_dai_driver omap_mcpdm_dai = { 414 .probe = omap_mcpdm_probe, 415 .remove = omap_mcpdm_remove, 416 .probe_order = SND_SOC_COMP_ORDER_LATE, 417 .remove_order = SND_SOC_COMP_ORDER_EARLY, 418 .playback = { 419 .channels_min = 1, 420 .channels_max = 5, 421 .rates = OMAP_MCPDM_RATES, 422 .formats = OMAP_MCPDM_FORMATS, 423 .sig_bits = 24, 424 }, 425 .capture = { 426 .channels_min = 1, 427 .channels_max = 3, 428 .rates = OMAP_MCPDM_RATES, 429 .formats = OMAP_MCPDM_FORMATS, 430 .sig_bits = 24, 431 }, 432 .ops = &omap_mcpdm_dai_ops, 433}; 434 435void omap_mcpdm_configure_dn_offsets(struct snd_soc_pcm_runtime *rtd, 436 u8 rx1, u8 rx2) 437{ 438 struct omap_mcpdm *mcpdm = snd_soc_dai_get_drvdata(rtd->cpu_dai); 439 440 mcpdm->dn_rx_offset = MCPDM_DNOFST_RX1(rx1) | MCPDM_DNOFST_RX2(rx2); 441} 442EXPORT_SYMBOL_GPL(omap_mcpdm_configure_dn_offsets); 443 444static __devinit int asoc_mcpdm_probe(struct platform_device *pdev) 445{ 446 struct omap_mcpdm *mcpdm; 447 struct resource *res; 448 int ret = 0; 449 450 mcpdm = kzalloc(sizeof(struct omap_mcpdm), GFP_KERNEL); 451 if (!mcpdm) 452 return -ENOMEM; 453 454 platform_set_drvdata(pdev, mcpdm); 455 456 mutex_init(&mcpdm->mutex); 457 458 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 459 if (res == NULL) { 460 dev_err(&pdev->dev, "no resource\n"); 461 goto err_res; 462 } 463 464 if (!request_mem_region(res->start, resource_size(res), "McPDM")) { 465 ret = -EBUSY; 466 goto err_res; 467 } 468 469 mcpdm->io_base = ioremap(res->start, resource_size(res)); 470 if (!mcpdm->io_base) { 471 ret = -ENOMEM; 472 goto err_iomap; 473 } 474 475 mcpdm->irq = platform_get_irq(pdev, 0); 476 if (mcpdm->irq < 0) { 477 ret = mcpdm->irq; 478 goto err_irq; 479 } 480 481 mcpdm->dev = &pdev->dev; 482 483 ret = snd_soc_register_dai(&pdev->dev, &omap_mcpdm_dai); 484 if (!ret) 485 return 0; 486 487err_irq: 488 iounmap(mcpdm->io_base); 489err_iomap: 490 release_mem_region(res->start, resource_size(res)); 491err_res: 492 kfree(mcpdm); 493 return ret; 494} 495 496static int __devexit asoc_mcpdm_remove(struct platform_device *pdev) 497{ 498 struct omap_mcpdm *mcpdm = platform_get_drvdata(pdev); 499 struct resource *res; 500 501 snd_soc_unregister_dai(&pdev->dev); 502 503 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 504 iounmap(mcpdm->io_base); 505 release_mem_region(res->start, resource_size(res)); 506 507 kfree(mcpdm); 508 return 0; 509} 510 511static const struct of_device_id omap_mcpdm_of_match[] = { 512 { .compatible = "ti,omap4-mcpdm", }, 513 { } 514}; 515MODULE_DEVICE_TABLE(of, omap_mcpdm_of_match); 516 517static struct platform_driver asoc_mcpdm_driver = { 518 .driver = { 519 .name = "omap-mcpdm", 520 .owner = THIS_MODULE, 521 .of_match_table = omap_mcpdm_of_match, 522 }, 523 524 .probe = asoc_mcpdm_probe, 525 .remove = __devexit_p(asoc_mcpdm_remove), 526}; 527 528module_platform_driver(asoc_mcpdm_driver); 529 530MODULE_ALIAS("platform:omap-mcpdm"); 531MODULE_AUTHOR("Misael Lopez Cruz <misael.lopez@ti.com>"); 532MODULE_DESCRIPTION("OMAP PDM SoC Interface"); 533MODULE_LICENSE("GPL");