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