2019-05-27 09:55:00 +03:00
// SPDX-License-Identifier: GPL-2.0-or-later
2012-02-22 13:49:08 +04:00
/*
* Copyright ( C ) 2012 , Analog Devices Inc .
* Author : Lars - Peter Clausen < lars @ metafoo . de >
*
* Based on :
* imx - pcm - dma - mx2 . c , Copyright 2009 Sascha Hauer < s . hauer @ pengutronix . de >
* mxs - pcm . c , Copyright ( C ) 2011 Freescale Semiconductor , Inc .
* ep93xx - pcm . c , Copyright ( C ) 2006 Lennert Buytenhek < buytenh @ wantstofly . org >
* Copyright ( C ) 2006 Applied Data Systems
*/
# include <linux/module.h>
# include <linux/init.h>
# include <linux/dmaengine.h>
# include <linux/slab.h>
# include <sound/pcm.h>
# include <sound/pcm_params.h>
# include <sound/soc.h>
# include <sound/dmaengine_pcm.h>
struct dmaengine_pcm_runtime_data {
struct dma_chan * dma_chan ;
2012-06-11 22:11:42 +04:00
dma_cookie_t cookie ;
2012-02-22 13:49:08 +04:00
unsigned int pos ;
} ;
static inline struct dmaengine_pcm_runtime_data * substream_to_prtd (
const struct snd_pcm_substream * substream )
{
return substream - > runtime - > private_data ;
}
struct dma_chan * snd_dmaengine_pcm_get_chan ( struct snd_pcm_substream * substream )
{
struct dmaengine_pcm_runtime_data * prtd = substream_to_prtd ( substream ) ;
return prtd - > dma_chan ;
}
EXPORT_SYMBOL_GPL ( snd_dmaengine_pcm_get_chan ) ;
/**
* snd_hwparams_to_dma_slave_config - Convert hw_params to dma_slave_config
* @ substream : PCM substream
* @ params : hw_params
* @ slave_config : DMA slave config
*
* This function can be used to initialize a dma_slave_config from a substream
* and hw_params in a dmaengine based PCM driver implementation .
*/
int snd_hwparams_to_dma_slave_config ( const struct snd_pcm_substream * substream ,
const struct snd_pcm_hw_params * params ,
struct dma_slave_config * slave_config )
{
enum dma_slave_buswidth buswidth ;
2013-10-25 18:33:12 +04:00
int bits ;
2012-02-22 13:49:08 +04:00
2014-07-03 08:51:53 +04:00
bits = params_physical_width ( params ) ;
2013-10-25 18:33:12 +04:00
if ( bits < 8 | | bits > 64 )
return - EINVAL ;
else if ( bits = = 8 )
2012-02-22 13:49:08 +04:00
buswidth = DMA_SLAVE_BUSWIDTH_1_BYTE ;
2013-10-25 18:33:12 +04:00
else if ( bits = = 16 )
2012-02-22 13:49:08 +04:00
buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES ;
2014-07-03 08:51:55 +04:00
else if ( bits = = 24 )
buswidth = DMA_SLAVE_BUSWIDTH_3_BYTES ;
2013-10-25 18:33:12 +04:00
else if ( bits < = 32 )
2012-02-22 13:49:08 +04:00
buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES ;
2013-10-25 18:33:12 +04:00
else
buswidth = DMA_SLAVE_BUSWIDTH_8_BYTES ;
2012-02-22 13:49:08 +04:00
if ( substream - > stream = = SNDRV_PCM_STREAM_PLAYBACK ) {
slave_config - > direction = DMA_MEM_TO_DEV ;
slave_config - > dst_addr_width = buswidth ;
} else {
slave_config - > direction = DMA_DEV_TO_MEM ;
slave_config - > src_addr_width = buswidth ;
}
2013-04-03 13:02:56 +04:00
slave_config - > device_fc = false ;
2012-02-22 13:49:08 +04:00
return 0 ;
}
EXPORT_SYMBOL_GPL ( snd_hwparams_to_dma_slave_config ) ;
2013-04-03 13:06:02 +04:00
/**
* snd_dmaengine_pcm_set_config_from_dai_data ( ) - Initializes a dma slave config
* using DAI DMA data .
* @ substream : PCM substream
* @ dma_data : DAI DMA data
* @ slave_config : DMA slave configuration
*
* Initializes the { dst , src } _addr , { dst , src } _maxburst , { dst , src } _addr_width and
* slave_id fields of the DMA slave config from the same fields of the DAI DMA
* data struct . The src and dst fields will be initialized depending on the
* direction of the substream . If the substream is a playback stream the dst
* fields will be initialized , if it is a capture stream the src fields will be
* initialized . The { dst , src } _addr_width field will only be initialized if the
2016-04-27 16:26:51 +03:00
* SND_DMAENGINE_PCM_DAI_FLAG_PACK flag is set or if the addr_width field of
* the DAI DMA data struct is not equal to DMA_SLAVE_BUSWIDTH_UNDEFINED . If
* both conditions are met the latter takes priority .
2013-04-03 13:06:02 +04:00
*/
void snd_dmaengine_pcm_set_config_from_dai_data (
const struct snd_pcm_substream * substream ,
const struct snd_dmaengine_dai_dma_data * dma_data ,
struct dma_slave_config * slave_config )
{
if ( substream - > stream = = SNDRV_PCM_STREAM_PLAYBACK ) {
slave_config - > dst_addr = dma_data - > addr ;
slave_config - > dst_maxburst = dma_data - > maxburst ;
2016-04-27 16:26:51 +03:00
if ( dma_data - > flags & SND_DMAENGINE_PCM_DAI_FLAG_PACK )
slave_config - > dst_addr_width =
DMA_SLAVE_BUSWIDTH_UNDEFINED ;
2013-04-03 13:06:02 +04:00
if ( dma_data - > addr_width ! = DMA_SLAVE_BUSWIDTH_UNDEFINED )
slave_config - > dst_addr_width = dma_data - > addr_width ;
} else {
slave_config - > src_addr = dma_data - > addr ;
slave_config - > src_maxburst = dma_data - > maxburst ;
2016-04-27 16:26:51 +03:00
if ( dma_data - > flags & SND_DMAENGINE_PCM_DAI_FLAG_PACK )
slave_config - > src_addr_width =
DMA_SLAVE_BUSWIDTH_UNDEFINED ;
2013-04-03 13:06:02 +04:00
if ( dma_data - > addr_width ! = DMA_SLAVE_BUSWIDTH_UNDEFINED )
slave_config - > src_addr_width = dma_data - > addr_width ;
}
slave_config - > slave_id = dma_data - > slave_id ;
2021-02-05 10:26:25 +03:00
slave_config - > peripheral_config = dma_data - > peripheral_config ;
slave_config - > peripheral_size = dma_data - > peripheral_size ;
2013-04-03 13:06:02 +04:00
}
EXPORT_SYMBOL_GPL ( snd_dmaengine_pcm_set_config_from_dai_data ) ;
2012-02-22 13:49:08 +04:00
static void dmaengine_pcm_dma_complete ( void * arg )
{
struct snd_pcm_substream * substream = arg ;
struct dmaengine_pcm_runtime_data * prtd = substream_to_prtd ( substream ) ;
prtd - > pos + = snd_pcm_lib_period_bytes ( substream ) ;
if ( prtd - > pos > = snd_pcm_lib_buffer_bytes ( substream ) )
prtd - > pos = 0 ;
snd_pcm_period_elapsed ( substream ) ;
}
static int dmaengine_pcm_prepare_and_submit ( struct snd_pcm_substream * substream )
{
struct dmaengine_pcm_runtime_data * prtd = substream_to_prtd ( substream ) ;
struct dma_chan * chan = prtd - > dma_chan ;
struct dma_async_tx_descriptor * desc ;
enum dma_transfer_direction direction ;
2012-09-24 11:58:04 +04:00
unsigned long flags = DMA_CTRL_ACK ;
2012-02-22 13:49:08 +04:00
direction = snd_pcm_substream_to_dma_direction ( substream ) ;
2012-09-24 11:58:04 +04:00
if ( ! substream - > runtime - > no_period_wakeup )
flags | = DMA_PREP_INTERRUPT ;
2012-03-05 17:02:14 +04:00
prtd - > pos = 0 ;
2012-03-26 15:58:08 +04:00
desc = dmaengine_prep_dma_cyclic ( chan ,
2012-02-22 13:49:08 +04:00
substream - > runtime - > dma_addr ,
snd_pcm_lib_buffer_bytes ( substream ) ,
2012-09-24 11:58:04 +04:00
snd_pcm_lib_period_bytes ( substream ) , direction , flags ) ;
2012-02-22 13:49:08 +04:00
if ( ! desc )
return - ENOMEM ;
desc - > callback = dmaengine_pcm_dma_complete ;
desc - > callback_param = substream ;
2012-06-11 22:11:42 +04:00
prtd - > cookie = dmaengine_submit ( desc ) ;
2012-02-22 13:49:08 +04:00
return 0 ;
}
/**
* snd_dmaengine_pcm_trigger - dmaengine based PCM trigger implementation
* @ substream : PCM substream
* @ cmd : Trigger command
*
* Returns 0 on success , a negative error code otherwise .
*
* This function can be used as the PCM trigger callback for dmaengine based PCM
* driver implementations .
*/
int snd_dmaengine_pcm_trigger ( struct snd_pcm_substream * substream , int cmd )
{
struct dmaengine_pcm_runtime_data * prtd = substream_to_prtd ( substream ) ;
2014-05-19 13:23:53 +04:00
struct snd_pcm_runtime * runtime = substream - > runtime ;
2012-02-22 13:49:08 +04:00
int ret ;
switch ( cmd ) {
case SNDRV_PCM_TRIGGER_START :
ret = dmaengine_pcm_prepare_and_submit ( substream ) ;
if ( ret )
return ret ;
dma_async_issue_pending ( prtd - > dma_chan ) ;
break ;
case SNDRV_PCM_TRIGGER_RESUME :
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE :
dmaengine_resume ( prtd - > dma_chan ) ;
break ;
case SNDRV_PCM_TRIGGER_SUSPEND :
2014-05-19 13:23:53 +04:00
if ( runtime - > info & SNDRV_PCM_INFO_PAUSE )
dmaengine_pause ( prtd - > dma_chan ) ;
else
2015-10-20 12:46:31 +03:00
dmaengine_terminate_async ( prtd - > dma_chan ) ;
2014-05-19 13:23:53 +04:00
break ;
2012-02-22 13:49:08 +04:00
case SNDRV_PCM_TRIGGER_PAUSE_PUSH :
dmaengine_pause ( prtd - > dma_chan ) ;
break ;
case SNDRV_PCM_TRIGGER_STOP :
2015-10-20 12:46:31 +03:00
dmaengine_terminate_async ( prtd - > dma_chan ) ;
2012-02-22 13:49:08 +04:00
break ;
default :
return - EINVAL ;
}
return 0 ;
}
EXPORT_SYMBOL_GPL ( snd_dmaengine_pcm_trigger ) ;
/**
2012-06-11 22:11:41 +04:00
* snd_dmaengine_pcm_pointer_no_residue - dmaengine based PCM pointer implementation
2012-02-22 13:49:08 +04:00
* @ substream : PCM substream
*
2012-06-11 22:11:41 +04:00
* This function is deprecated and should not be used by new drivers , as its
* results may be unreliable .
2012-02-22 13:49:08 +04:00
*/
2012-06-11 22:11:41 +04:00
snd_pcm_uframes_t snd_dmaengine_pcm_pointer_no_residue ( struct snd_pcm_substream * substream )
2012-02-22 13:49:08 +04:00
{
struct dmaengine_pcm_runtime_data * prtd = substream_to_prtd ( substream ) ;
return bytes_to_frames ( substream - > runtime , prtd - > pos ) ;
}
2012-06-11 22:11:41 +04:00
EXPORT_SYMBOL_GPL ( snd_dmaengine_pcm_pointer_no_residue ) ;
2012-02-22 13:49:08 +04:00
2012-06-11 22:11:42 +04:00
/**
* snd_dmaengine_pcm_pointer - dmaengine based PCM pointer implementation
* @ substream : PCM substream
*
* This function can be used as the PCM pointer callback for dmaengine based PCM
* driver implementations .
*/
snd_pcm_uframes_t snd_dmaengine_pcm_pointer ( struct snd_pcm_substream * substream )
{
struct dmaengine_pcm_runtime_data * prtd = substream_to_prtd ( substream ) ;
2020-02-10 18:33:36 +03:00
struct snd_pcm_runtime * runtime = substream - > runtime ;
2012-06-11 22:11:42 +04:00
struct dma_tx_state state ;
enum dma_status status ;
unsigned int buf_size ;
unsigned int pos = 0 ;
status = dmaengine_tx_status ( prtd - > dma_chan , prtd - > cookie , & state ) ;
if ( status = = DMA_IN_PROGRESS | | status = = DMA_PAUSED ) {
buf_size = snd_pcm_lib_buffer_bytes ( substream ) ;
if ( state . residue > 0 & & state . residue < = buf_size )
pos = buf_size - state . residue ;
2020-02-10 18:14:02 +03:00
runtime - > delay = bytes_to_frames ( runtime ,
state . in_flight_bytes ) ;
2012-06-11 22:11:42 +04:00
}
2020-02-10 18:33:36 +03:00
return bytes_to_frames ( runtime , pos ) ;
2012-06-11 22:11:42 +04:00
}
EXPORT_SYMBOL_GPL ( snd_dmaengine_pcm_pointer ) ;
2013-04-15 21:19:51 +04:00
/**
* snd_dmaengine_pcm_request_channel - Request channel for the dmaengine PCM
* @ filter_fn : Filter function used to request the DMA channel
* @ filter_data : Data passed to the DMA filter function
*
* Returns NULL or the requested DMA channel .
*
* This function request a DMA channel for usage with dmaengine PCM .
*/
struct dma_chan * snd_dmaengine_pcm_request_channel ( dma_filter_fn filter_fn ,
2013-04-15 21:19:48 +04:00
void * filter_data )
2012-02-22 13:49:08 +04:00
{
dma_cap_mask_t mask ;
dma_cap_zero ( mask ) ;
dma_cap_set ( DMA_SLAVE , mask ) ;
dma_cap_set ( DMA_CYCLIC , mask ) ;
2013-04-15 21:19:48 +04:00
return dma_request_channel ( mask , filter_fn , filter_data ) ;
2012-02-22 13:49:08 +04:00
}
2013-04-15 21:19:51 +04:00
EXPORT_SYMBOL_GPL ( snd_dmaengine_pcm_request_channel ) ;
2012-02-22 13:49:08 +04:00
/**
* snd_dmaengine_pcm_open - Open a dmaengine based PCM substream
* @ substream : PCM substream
2013-04-15 21:19:48 +04:00
* @ chan : DMA channel to use for data transfers
2012-02-22 13:49:08 +04:00
*
* Returns 0 on success , a negative error code otherwise .
*
2013-04-15 21:19:48 +04:00
* The function should usually be called from the pcm open callback . Note that
* this function will use private_data field of the substream ' s runtime . So it
2015-03-04 04:56:13 +03:00
* is not available to your pcm driver implementation .
2012-02-22 13:49:08 +04:00
*/
int snd_dmaengine_pcm_open ( struct snd_pcm_substream * substream ,
2013-04-15 21:19:48 +04:00
struct dma_chan * chan )
2012-02-22 13:49:08 +04:00
{
struct dmaengine_pcm_runtime_data * prtd ;
int ret ;
2013-04-15 21:19:48 +04:00
if ( ! chan )
return - ENXIO ;
2012-02-22 13:49:08 +04:00
ret = snd_pcm_hw_constraint_integer ( substream - > runtime ,
SNDRV_PCM_HW_PARAM_PERIODS ) ;
if ( ret < 0 )
return ret ;
prtd = kzalloc ( sizeof ( * prtd ) , GFP_KERNEL ) ;
if ( ! prtd )
return - ENOMEM ;
2013-04-15 21:19:48 +04:00
prtd - > dma_chan = chan ;
2012-02-22 13:49:08 +04:00
substream - > runtime - > private_data = prtd ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( snd_dmaengine_pcm_open ) ;
2013-04-15 21:19:48 +04:00
/**
* snd_dmaengine_pcm_open_request_chan - Open a dmaengine based PCM substream and request channel
* @ substream : PCM substream
* @ filter_fn : Filter function used to request the DMA channel
* @ filter_data : Data passed to the DMA filter function
*
* Returns 0 on success , a negative error code otherwise .
*
* This function will request a DMA channel using the passed filter function and
* data . The function should usually be called from the pcm open callback . Note
* that this function will use private_data field of the substream ' s runtime . So
2015-03-04 04:56:13 +03:00
* it is not available to your pcm driver implementation .
2013-04-15 21:19:48 +04:00
*/
int snd_dmaengine_pcm_open_request_chan ( struct snd_pcm_substream * substream ,
dma_filter_fn filter_fn , void * filter_data )
{
return snd_dmaengine_pcm_open ( substream ,
2013-04-15 21:19:51 +04:00
snd_dmaengine_pcm_request_channel ( filter_fn , filter_data ) ) ;
2013-04-15 21:19:48 +04:00
}
EXPORT_SYMBOL_GPL ( snd_dmaengine_pcm_open_request_chan ) ;
2012-02-22 13:49:08 +04:00
/**
* snd_dmaengine_pcm_close - Close a dmaengine based PCM substream
* @ substream : PCM substream
*/
int snd_dmaengine_pcm_close ( struct snd_pcm_substream * substream )
{
struct dmaengine_pcm_runtime_data * prtd = substream_to_prtd ( substream ) ;
2015-10-20 12:46:31 +03:00
dmaengine_synchronize ( prtd - > dma_chan ) ;
2012-02-22 13:49:08 +04:00
kfree ( prtd ) ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( snd_dmaengine_pcm_close ) ;
2012-11-22 16:31:10 +04:00
2013-04-15 21:19:48 +04:00
/**
2020-10-23 19:33:35 +03:00
* snd_dmaengine_pcm_close_release_chan - Close a dmaengine based PCM
* substream and release channel
2013-04-15 21:19:48 +04:00
* @ substream : PCM substream
*
* Releases the DMA channel associated with the PCM substream .
*/
int snd_dmaengine_pcm_close_release_chan ( struct snd_pcm_substream * substream )
{
struct dmaengine_pcm_runtime_data * prtd = substream_to_prtd ( substream ) ;
2015-10-20 12:46:31 +03:00
dmaengine_synchronize ( prtd - > dma_chan ) ;
2013-04-15 21:19:48 +04:00
dma_release_channel ( prtd - > dma_chan ) ;
2015-10-20 12:46:31 +03:00
kfree ( prtd ) ;
2013-04-15 21:19:48 +04:00
2015-10-20 12:46:31 +03:00
return 0 ;
2013-04-15 21:19:48 +04:00
}
EXPORT_SYMBOL_GPL ( snd_dmaengine_pcm_close_release_chan ) ;
2019-09-27 04:46:11 +03:00
/**
* snd_dmaengine_pcm_refine_runtime_hwparams - Refine runtime hw params
* @ substream : PCM substream
* @ dma_data : DAI DMA data
* @ hw : PCM hw params
* @ chan : DMA channel to use for data transfers
*
* Returns 0 on success , a negative error code otherwise .
*
* This function will query DMA capability , then refine the pcm hardware
* parameters .
*/
int snd_dmaengine_pcm_refine_runtime_hwparams (
struct snd_pcm_substream * substream ,
struct snd_dmaengine_dai_dma_data * dma_data ,
struct snd_pcm_hardware * hw ,
struct dma_chan * chan )
{
struct dma_slave_caps dma_caps ;
u32 addr_widths = BIT ( DMA_SLAVE_BUSWIDTH_1_BYTE ) |
BIT ( DMA_SLAVE_BUSWIDTH_2_BYTES ) |
BIT ( DMA_SLAVE_BUSWIDTH_4_BYTES ) ;
snd_pcm_format_t i ;
int ret = 0 ;
if ( ! hw | | ! chan | | ! dma_data )
return - EINVAL ;
ret = dma_get_slave_caps ( chan , & dma_caps ) ;
if ( ret = = 0 ) {
if ( dma_caps . cmd_pause & & dma_caps . cmd_resume )
hw - > info | = SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME ;
if ( dma_caps . residue_granularity < = DMA_RESIDUE_GRANULARITY_SEGMENT )
hw - > info | = SNDRV_PCM_INFO_BATCH ;
if ( substream - > stream = = SNDRV_PCM_STREAM_PLAYBACK )
addr_widths = dma_caps . dst_addr_widths ;
else
addr_widths = dma_caps . src_addr_widths ;
}
/*
* If SND_DMAENGINE_PCM_DAI_FLAG_PACK is set keep
* hw . formats set to 0 , meaning no restrictions are in place .
* In this case it ' s the responsibility of the DAI driver to
* provide the supported format information .
*/
if ( ! ( dma_data - > flags & SND_DMAENGINE_PCM_DAI_FLAG_PACK ) )
/*
* Prepare formats mask for valid / allowed sample types . If the
* dma does not have support for the given physical word size ,
* it needs to be masked out so user space can not use the
* format which produces corrupted audio .
* In case the dma driver does not implement the slave_caps the
* default assumption is that it supports 1 , 2 and 4 bytes
* widths .
*/
2020-02-06 19:39:44 +03:00
pcm_for_each_format ( i ) {
2019-09-27 04:46:11 +03:00
int bits = snd_pcm_format_physical_width ( i ) ;
/*
* Enable only samples with DMA supported physical
* widths
*/
switch ( bits ) {
case 8 :
case 16 :
case 24 :
case 32 :
case 64 :
if ( addr_widths & ( 1 < < ( bits / 8 ) ) )
hw - > formats | = pcm_format_to_bits ( i ) ;
break ;
default :
/* Unsupported types */
break ;
}
}
return ret ;
}
EXPORT_SYMBOL_GPL ( snd_dmaengine_pcm_refine_runtime_hwparams ) ;
2012-11-22 16:31:10 +04:00
MODULE_LICENSE ( " GPL " ) ;