2010-05-18 13:41:46 +08:00
/*
* Copyright ( c ) 2010 Nuvoton technology corporation .
*
* Wan ZongShun < mcuos . com @ gmail . com >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; version 2 of the License .
*
*/
# include <linux/module.h>
# include <linux/init.h>
# include <linux/io.h>
# include <linux/platform_device.h>
# include <linux/slab.h>
# include <linux/dma-mapping.h>
# include <sound/core.h>
# include <sound/pcm.h>
# include <sound/pcm_params.h>
# include <sound/soc.h>
# include <mach/hardware.h>
2010-06-10 10:40:40 +08:00
# include "nuc900-audio.h"
2010-05-18 13:41:46 +08:00
static const struct snd_pcm_hardware nuc900_pcm_hardware = {
. info = SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_RESUME ,
. buffer_bytes_max = 4 * 1024 ,
. period_bytes_min = 1 * 1024 ,
. period_bytes_max = 4 * 1024 ,
. periods_min = 1 ,
. periods_max = 1024 ,
} ;
static int nuc900_dma_hw_params ( struct snd_pcm_substream * substream ,
struct snd_pcm_hw_params * params )
{
2015-03-15 12:15:24 +01:00
return snd_pcm_lib_malloc_pages ( substream , params_buffer_bytes ( params ) ) ;
2010-05-18 13:41:46 +08:00
}
2015-03-15 12:15:24 +01:00
static void nuc900_update_dma_register ( struct snd_pcm_substream * substream )
2010-05-18 13:41:46 +08:00
{
struct snd_pcm_runtime * runtime = substream - > runtime ;
struct nuc900_audio * nuc900_audio = runtime - > private_data ;
void __iomem * mmio_addr , * mmio_len ;
2010-06-02 13:54:25 +08:00
if ( substream - > stream = = SNDRV_PCM_STREAM_PLAYBACK ) {
2010-05-18 13:41:46 +08:00
mmio_addr = nuc900_audio - > mmio + ACTL_PDSTB ;
mmio_len = nuc900_audio - > mmio + ACTL_PDST_LENGTH ;
} else {
mmio_addr = nuc900_audio - > mmio + ACTL_RDSTB ;
mmio_len = nuc900_audio - > mmio + ACTL_RDST_LENGTH ;
}
2015-03-15 12:15:24 +01:00
AUDIO_WRITE ( mmio_addr , runtime - > dma_addr ) ;
AUDIO_WRITE ( mmio_len , runtime - > dma_bytes ) ;
2010-05-18 13:41:46 +08:00
}
static void nuc900_dma_start ( struct snd_pcm_substream * substream )
{
struct snd_pcm_runtime * runtime = substream - > runtime ;
struct nuc900_audio * nuc900_audio = runtime - > private_data ;
unsigned long val ;
val = AUDIO_READ ( nuc900_audio - > mmio + ACTL_CON ) ;
val | = ( T_DMA_IRQ | R_DMA_IRQ ) ;
AUDIO_WRITE ( nuc900_audio - > mmio + ACTL_CON , val ) ;
}
static void nuc900_dma_stop ( struct snd_pcm_substream * substream )
{
struct snd_pcm_runtime * runtime = substream - > runtime ;
struct nuc900_audio * nuc900_audio = runtime - > private_data ;
unsigned long val ;
val = AUDIO_READ ( nuc900_audio - > mmio + ACTL_CON ) ;
val & = ~ ( T_DMA_IRQ | R_DMA_IRQ ) ;
AUDIO_WRITE ( nuc900_audio - > mmio + ACTL_CON , val ) ;
}
static irqreturn_t nuc900_dma_interrupt ( int irq , void * dev_id )
{
struct snd_pcm_substream * substream = dev_id ;
struct nuc900_audio * nuc900_audio = substream - > runtime - > private_data ;
unsigned long val ;
spin_lock ( & nuc900_audio - > lock ) ;
val = AUDIO_READ ( nuc900_audio - > mmio + ACTL_CON ) ;
if ( val & R_DMA_IRQ ) {
AUDIO_WRITE ( nuc900_audio - > mmio + ACTL_CON , val | R_DMA_IRQ ) ;
val = AUDIO_READ ( nuc900_audio - > mmio + ACTL_RSR ) ;
if ( val & R_DMA_MIDDLE_IRQ ) {
val | = R_DMA_MIDDLE_IRQ ;
AUDIO_WRITE ( nuc900_audio - > mmio + ACTL_RSR , val ) ;
}
if ( val & R_DMA_END_IRQ ) {
val | = R_DMA_END_IRQ ;
AUDIO_WRITE ( nuc900_audio - > mmio + ACTL_RSR , val ) ;
}
} else if ( val & T_DMA_IRQ ) {
AUDIO_WRITE ( nuc900_audio - > mmio + ACTL_CON , val | T_DMA_IRQ ) ;
val = AUDIO_READ ( nuc900_audio - > mmio + ACTL_PSR ) ;
if ( val & P_DMA_MIDDLE_IRQ ) {
val | = P_DMA_MIDDLE_IRQ ;
AUDIO_WRITE ( nuc900_audio - > mmio + ACTL_PSR , val ) ;
}
if ( val & P_DMA_END_IRQ ) {
val | = P_DMA_END_IRQ ;
AUDIO_WRITE ( nuc900_audio - > mmio + ACTL_PSR , val ) ;
}
} else {
dev_err ( nuc900_audio - > dev , " Wrong DMA interrupt status! \n " ) ;
spin_unlock ( & nuc900_audio - > lock ) ;
return IRQ_HANDLED ;
}
spin_unlock ( & nuc900_audio - > lock ) ;
snd_pcm_period_elapsed ( substream ) ;
return IRQ_HANDLED ;
}
static int nuc900_dma_hw_free ( struct snd_pcm_substream * substream )
{
snd_pcm_lib_free_pages ( substream ) ;
return 0 ;
}
static int nuc900_dma_prepare ( struct snd_pcm_substream * substream )
{
struct snd_pcm_runtime * runtime = substream - > runtime ;
struct nuc900_audio * nuc900_audio = runtime - > private_data ;
2010-06-02 13:54:25 +08:00
unsigned long flags , val ;
2010-11-29 17:42:47 +08:00
int ret = 0 ;
2010-05-18 13:41:46 +08:00
spin_lock_irqsave ( & nuc900_audio - > lock , flags ) ;
2015-03-15 12:15:24 +01:00
nuc900_update_dma_register ( substream ) ;
2010-05-18 13:41:46 +08:00
val = AUDIO_READ ( nuc900_audio - > mmio + ACTL_RESET ) ;
switch ( runtime - > channels ) {
case 1 :
2010-06-02 13:54:25 +08:00
if ( substream - > stream = = SNDRV_PCM_STREAM_PLAYBACK ) {
2010-05-18 13:41:46 +08:00
val & = ~ ( PLAY_LEFT_CHNNEL | PLAY_RIGHT_CHNNEL ) ;
val | = PLAY_RIGHT_CHNNEL ;
} else {
val & = ~ ( RECORD_LEFT_CHNNEL | RECORD_RIGHT_CHNNEL ) ;
val | = RECORD_RIGHT_CHNNEL ;
}
AUDIO_WRITE ( nuc900_audio - > mmio + ACTL_RESET , val ) ;
break ;
case 2 :
2010-06-02 13:54:25 +08:00
if ( substream - > stream = = SNDRV_PCM_STREAM_PLAYBACK )
2010-05-18 13:41:46 +08:00
val | = ( PLAY_LEFT_CHNNEL | PLAY_RIGHT_CHNNEL ) ;
else
val | = ( RECORD_LEFT_CHNNEL | RECORD_RIGHT_CHNNEL ) ;
AUDIO_WRITE ( nuc900_audio - > mmio + ACTL_RESET , val ) ;
break ;
default :
2010-11-29 17:42:47 +08:00
ret = - EINVAL ;
2010-05-18 13:41:46 +08:00
}
spin_unlock_irqrestore ( & nuc900_audio - > lock , flags ) ;
2010-11-29 17:42:47 +08:00
return ret ;
2010-05-18 13:41:46 +08:00
}
static int nuc900_dma_trigger ( struct snd_pcm_substream * substream , int cmd )
{
int ret = 0 ;
switch ( cmd ) {
case SNDRV_PCM_TRIGGER_START :
case SNDRV_PCM_TRIGGER_RESUME :
nuc900_dma_start ( substream ) ;
break ;
case SNDRV_PCM_TRIGGER_STOP :
case SNDRV_PCM_TRIGGER_SUSPEND :
nuc900_dma_stop ( substream ) ;
break ;
default :
ret = - EINVAL ;
break ;
}
return ret ;
}
2011-09-21 14:42:49 +08:00
static int nuc900_dma_getposition ( struct snd_pcm_substream * substream ,
2010-05-18 13:41:46 +08:00
dma_addr_t * src , dma_addr_t * dst )
{
struct snd_pcm_runtime * runtime = substream - > runtime ;
struct nuc900_audio * nuc900_audio = runtime - > private_data ;
if ( src ! = NULL )
* src = AUDIO_READ ( nuc900_audio - > mmio + ACTL_PDSTC ) ;
if ( dst ! = NULL )
* dst = AUDIO_READ ( nuc900_audio - > mmio + ACTL_RDSTC ) ;
return 0 ;
}
static snd_pcm_uframes_t nuc900_dma_pointer ( struct snd_pcm_substream * substream )
{
struct snd_pcm_runtime * runtime = substream - > runtime ;
dma_addr_t src , dst ;
unsigned long res ;
nuc900_dma_getposition ( substream , & src , & dst ) ;
if ( substream - > stream = = SNDRV_PCM_STREAM_CAPTURE )
res = dst - runtime - > dma_addr ;
else
res = src - runtime - > dma_addr ;
return bytes_to_frames ( substream - > runtime , res ) ;
}
static int nuc900_dma_open ( struct snd_pcm_substream * substream )
{
struct snd_pcm_runtime * runtime = substream - > runtime ;
struct nuc900_audio * nuc900_audio ;
snd_soc_set_runtime_hwparams ( substream , & nuc900_pcm_hardware ) ;
nuc900_audio = nuc900_ac97_data ;
if ( request_irq ( nuc900_audio - > irq_num , nuc900_dma_interrupt ,
2011-09-22 16:59:20 +08:00
0 , " nuc900-dma " , substream ) )
2010-05-18 13:41:46 +08:00
return - EBUSY ;
runtime - > private_data = nuc900_audio ;
return 0 ;
}
static int nuc900_dma_close ( struct snd_pcm_substream * substream )
{
struct snd_pcm_runtime * runtime = substream - > runtime ;
struct nuc900_audio * nuc900_audio = runtime - > private_data ;
free_irq ( nuc900_audio - > irq_num , substream ) ;
return 0 ;
}
static int nuc900_dma_mmap ( struct snd_pcm_substream * substream ,
struct vm_area_struct * vma )
{
struct snd_pcm_runtime * runtime = substream - > runtime ;
return dma_mmap_writecombine ( substream - > pcm - > card - > dev , vma ,
runtime - > dma_area ,
runtime - > dma_addr ,
runtime - > dma_bytes ) ;
}
static struct snd_pcm_ops nuc900_dma_ops = {
. open = nuc900_dma_open ,
. close = nuc900_dma_close ,
. ioctl = snd_pcm_lib_ioctl ,
. hw_params = nuc900_dma_hw_params ,
. hw_free = nuc900_dma_hw_free ,
. prepare = nuc900_dma_prepare ,
. trigger = nuc900_dma_trigger ,
. pointer = nuc900_dma_pointer ,
. mmap = nuc900_dma_mmap ,
} ;
2011-06-07 16:08:33 +01:00
static int nuc900_dma_new ( struct snd_soc_pcm_runtime * rtd )
2010-05-18 13:41:46 +08:00
{
2011-06-07 16:08:33 +01:00
struct snd_card * card = rtd - > card - > snd_card ;
struct snd_pcm * pcm = rtd - > pcm ;
2013-06-27 12:53:37 +01:00
int ret ;
2011-06-07 16:08:33 +01:00
2013-06-27 12:53:37 +01:00
ret = dma_coerce_mask_and_coherent ( card - > dev , DMA_BIT_MASK ( 32 ) ) ;
if ( ret )
return ret ;
2010-05-18 13:41:46 +08:00
snd_pcm_lib_preallocate_pages_for_all ( pcm , SNDRV_DMA_TYPE_DEV ,
card - > dev , 4 * 1024 , ( 4 * 1024 ) - 1 ) ;
return 0 ;
}
2010-03-17 20:15:21 +00:00
static struct snd_soc_platform_driver nuc900_soc_platform = {
. ops = & nuc900_dma_ops ,
2010-05-18 13:41:46 +08:00
. pcm_new = nuc900_dma_new ,
2010-11-29 17:40:53 +08:00
} ;
2010-05-18 13:41:46 +08:00
2012-12-07 09:26:28 -05:00
static int nuc900_soc_platform_probe ( struct platform_device * pdev )
2010-05-18 13:41:46 +08:00
{
2015-08-27 09:12:17 +08:00
return devm_snd_soc_register_platform ( & pdev - > dev , & nuc900_soc_platform ) ;
2010-05-18 13:41:46 +08:00
}
2010-03-17 20:15:21 +00:00
static struct platform_driver nuc900_pcm_driver = {
. driver = {
. name = " nuc900-pcm-audio " ,
} ,
. probe = nuc900_soc_platform_probe ,
} ;
2011-11-24 10:45:32 +08:00
module_platform_driver ( nuc900_pcm_driver ) ;
2010-05-18 13:41:46 +08:00
MODULE_AUTHOR ( " Wan ZongShun, <mcuos.com@gmail.com> " ) ;
MODULE_DESCRIPTION ( " nuc900 Audio DMA module " ) ;
MODULE_LICENSE ( " GPL " ) ;