2018-06-12 05:58:07 +00:00
// SPDX-License-Identifier: GPL-2.0+
//
// siu_pcm.c - ALSA driver for Renesas SH7343, SH7722 SIU peripheral.
//
// Copyright (C) 2009-2010 Guennadi Liakhovetski <g.liakhovetski@gmx.de>
// Copyright (C) 2006 Carlos Munoz <carlos@kenati.com>
2010-01-22 19:09:03 +01:00
# include <linux/delay.h>
# include <linux/dma-mapping.h>
# include <linux/dmaengine.h>
# include <linux/interrupt.h>
# include <linux/module.h>
# include <linux/platform_device.h>
# include <sound/control.h>
# include <sound/core.h>
# include <sound/pcm.h>
# include <sound/pcm_params.h>
2010-11-21 19:48:46 +02:00
# include <sound/soc.h>
2010-01-22 19:09:03 +01:00
# include <asm/siu.h>
# include "siu.h"
2018-01-29 02:43:45 +00:00
# define DRV_NAME "siu-i2s"
2010-01-22 19:09:03 +01:00
# define GET_MAX_PERIODS(buf_bytes, period_bytes) \
( ( buf_bytes ) / ( period_bytes ) )
# define PERIOD_OFFSET(buf_addr, period_num, period_bytes) \
( ( buf_addr ) + ( ( period_num ) * ( period_bytes ) ) )
# define RWF_STM_RD 0x01 /* Read in progress */
# define RWF_STM_WT 0x02 /* Write in progress */
struct siu_port * siu_ports [ SIU_PORT_NUM ] ;
/* transfersize is number of u32 dma transfers per period */
static int siu_pcm_stmwrite_stop ( struct siu_port * port_info )
{
2010-03-17 20:15:21 +00:00
struct siu_info * info = siu_i2s_data ;
2010-01-22 19:09:03 +01:00
u32 __iomem * base = info - > reg ;
struct siu_stream * siu_stream = & port_info - > playback ;
u32 stfifo ;
if ( ! siu_stream - > rw_flg )
return - EPERM ;
/* output FIFO disable */
stfifo = siu_read32 ( base + SIU_STFIFO ) ;
siu_write32 ( base + SIU_STFIFO , stfifo & ~ 0x0c180c18 ) ;
pr_debug ( " %s: STFIFO %x -> %x \n " , __func__ ,
stfifo , stfifo & ~ 0x0c180c18 ) ;
/* during stmwrite clear */
siu_stream - > rw_flg = 0 ;
return 0 ;
}
static int siu_pcm_stmwrite_start ( struct siu_port * port_info )
{
struct siu_stream * siu_stream = & port_info - > playback ;
if ( siu_stream - > rw_flg )
return - EPERM ;
/* Current period in buffer */
port_info - > playback . cur_period = 0 ;
/* during stmwrite flag set */
siu_stream - > rw_flg = RWF_STM_WT ;
/* DMA transfer start */
2020-09-03 12:47:48 +02:00
queue_work ( system_highpri_wq , & siu_stream - > work ) ;
2010-01-22 19:09:03 +01:00
return 0 ;
}
static void siu_dma_tx_complete ( void * arg )
{
struct siu_stream * siu_stream = arg ;
if ( ! siu_stream - > rw_flg )
return ;
/* Update completed period count */
if ( + + siu_stream - > cur_period > =
GET_MAX_PERIODS ( siu_stream - > buf_bytes ,
siu_stream - > period_bytes ) )
siu_stream - > cur_period = 0 ;
pr_debug ( " %s: done period #%d (%u/%u bytes), cookie %d \n " ,
__func__ , siu_stream - > cur_period ,
siu_stream - > cur_period * siu_stream - > period_bytes ,
siu_stream - > buf_bytes , siu_stream - > cookie ) ;
2020-09-03 12:47:48 +02:00
queue_work ( system_highpri_wq , & siu_stream - > work ) ;
2010-01-22 19:09:03 +01:00
/* Notify alsa: a period is done */
snd_pcm_period_elapsed ( siu_stream - > substream ) ;
}
static int siu_pcm_wr_set ( struct siu_port * port_info ,
dma_addr_t buff , u32 size )
{
2010-03-17 20:15:21 +00:00
struct siu_info * info = siu_i2s_data ;
2010-01-22 19:09:03 +01:00
u32 __iomem * base = info - > reg ;
struct siu_stream * siu_stream = & port_info - > playback ;
struct snd_pcm_substream * substream = siu_stream - > substream ;
struct device * dev = substream - > pcm - > card - > dev ;
struct dma_async_tx_descriptor * desc ;
dma_cookie_t cookie ;
struct scatterlist sg ;
u32 stfifo ;
sg_init_table ( & sg , 1 ) ;
sg_set_page ( & sg , pfn_to_page ( PFN_DOWN ( buff ) ) ,
size , offset_in_page ( buff ) ) ;
2010-08-04 15:59:50 +09:00
sg_dma_len ( & sg ) = size ;
2010-01-22 19:09:03 +01:00
sg_dma_address ( & sg ) = buff ;
2012-03-08 16:11:18 -05:00
desc = dmaengine_prep_slave_sg ( siu_stream - > chan ,
2011-10-14 10:49:30 +05:30
& sg , 1 , DMA_MEM_TO_DEV , DMA_PREP_INTERRUPT | DMA_CTRL_ACK ) ;
2010-01-22 19:09:03 +01:00
if ( ! desc ) {
dev_err ( dev , " Failed to allocate a dma descriptor \n " ) ;
return - ENOMEM ;
}
desc - > callback = siu_dma_tx_complete ;
desc - > callback_param = siu_stream ;
2014-08-19 17:48:06 +02:00
cookie = dmaengine_submit ( desc ) ;
2010-01-22 19:09:03 +01:00
if ( cookie < 0 ) {
dev_err ( dev , " Failed to submit a dma transfer \n " ) ;
return cookie ;
}
siu_stream - > tx_desc = desc ;
siu_stream - > cookie = cookie ;
dma_async_issue_pending ( siu_stream - > chan ) ;
/* only output FIFO enable */
stfifo = siu_read32 ( base + SIU_STFIFO ) ;
siu_write32 ( base + SIU_STFIFO , stfifo | ( port_info - > stfifo & 0x0c180c18 ) ) ;
dev_dbg ( dev , " %s: STFIFO %x -> %x \n " , __func__ ,
stfifo , stfifo | ( port_info - > stfifo & 0x0c180c18 ) ) ;
return 0 ;
}
static int siu_pcm_rd_set ( struct siu_port * port_info ,
dma_addr_t buff , size_t size )
{
2010-03-17 20:15:21 +00:00
struct siu_info * info = siu_i2s_data ;
2010-01-22 19:09:03 +01:00
u32 __iomem * base = info - > reg ;
struct siu_stream * siu_stream = & port_info - > capture ;
struct snd_pcm_substream * substream = siu_stream - > substream ;
struct device * dev = substream - > pcm - > card - > dev ;
struct dma_async_tx_descriptor * desc ;
dma_cookie_t cookie ;
struct scatterlist sg ;
u32 stfifo ;
dev_dbg ( dev , " %s: %u@%llx \n " , __func__ , size , ( unsigned long long ) buff ) ;
sg_init_table ( & sg , 1 ) ;
sg_set_page ( & sg , pfn_to_page ( PFN_DOWN ( buff ) ) ,
size , offset_in_page ( buff ) ) ;
2010-08-04 15:59:50 +09:00
sg_dma_len ( & sg ) = size ;
2010-01-22 19:09:03 +01:00
sg_dma_address ( & sg ) = buff ;
2012-03-08 16:11:18 -05:00
desc = dmaengine_prep_slave_sg ( siu_stream - > chan ,
2011-10-14 10:49:30 +05:30
& sg , 1 , DMA_DEV_TO_MEM , DMA_PREP_INTERRUPT | DMA_CTRL_ACK ) ;
2010-01-22 19:09:03 +01:00
if ( ! desc ) {
dev_err ( dev , " Failed to allocate dma descriptor \n " ) ;
return - ENOMEM ;
}
desc - > callback = siu_dma_tx_complete ;
desc - > callback_param = siu_stream ;
2014-08-19 17:48:06 +02:00
cookie = dmaengine_submit ( desc ) ;
2010-01-22 19:09:03 +01:00
if ( cookie < 0 ) {
dev_err ( dev , " Failed to submit dma descriptor \n " ) ;
return cookie ;
}
siu_stream - > tx_desc = desc ;
siu_stream - > cookie = cookie ;
dma_async_issue_pending ( siu_stream - > chan ) ;
/* only input FIFO enable */
stfifo = siu_read32 ( base + SIU_STFIFO ) ;
siu_write32 ( base + SIU_STFIFO , siu_read32 ( base + SIU_STFIFO ) |
( port_info - > stfifo & 0x13071307 ) ) ;
dev_dbg ( dev , " %s: STFIFO %x -> %x \n " , __func__ ,
stfifo , stfifo | ( port_info - > stfifo & 0x13071307 ) ) ;
return 0 ;
}
2020-09-03 12:47:48 +02:00
static void siu_io_work ( struct work_struct * work )
2010-01-22 19:09:03 +01:00
{
2020-09-03 12:47:48 +02:00
struct siu_stream * siu_stream = container_of ( work , struct siu_stream ,
work ) ;
2010-01-22 19:09:03 +01:00
struct snd_pcm_substream * substream = siu_stream - > substream ;
struct device * dev = substream - > pcm - > card - > dev ;
struct snd_pcm_runtime * rt = substream - > runtime ;
struct siu_port * port_info = siu_port_info ( substream ) ;
dev_dbg ( dev , " %s: flags %x \n " , __func__ , siu_stream - > rw_flg ) ;
if ( ! siu_stream - > rw_flg ) {
dev_dbg ( dev , " %s: stream inactive \n " , __func__ ) ;
return ;
}
if ( substream - > stream = = SNDRV_PCM_STREAM_CAPTURE ) {
dma_addr_t buff ;
size_t count ;
buff = ( dma_addr_t ) PERIOD_OFFSET ( rt - > dma_addr ,
siu_stream - > cur_period ,
siu_stream - > period_bytes ) ;
count = siu_stream - > period_bytes ;
/* DMA transfer start */
siu_pcm_rd_set ( port_info , buff , count ) ;
} else {
siu_pcm_wr_set ( port_info ,
( dma_addr_t ) PERIOD_OFFSET ( rt - > dma_addr ,
siu_stream - > cur_period ,
siu_stream - > period_bytes ) ,
siu_stream - > period_bytes ) ;
}
}
/* Capture */
static int siu_pcm_stmread_start ( struct siu_port * port_info )
{
struct siu_stream * siu_stream = & port_info - > capture ;
if ( siu_stream - > xfer_cnt > 0x1000000 )
return - EINVAL ;
if ( siu_stream - > rw_flg )
return - EPERM ;
/* Current period in buffer */
siu_stream - > cur_period = 0 ;
/* during stmread flag set */
siu_stream - > rw_flg = RWF_STM_RD ;
2020-09-03 12:47:48 +02:00
queue_work ( system_highpri_wq , & siu_stream - > work ) ;
2010-01-22 19:09:03 +01:00
return 0 ;
}
static int siu_pcm_stmread_stop ( struct siu_port * port_info )
{
2010-03-17 20:15:21 +00:00
struct siu_info * info = siu_i2s_data ;
2010-01-22 19:09:03 +01:00
u32 __iomem * base = info - > reg ;
struct siu_stream * siu_stream = & port_info - > capture ;
struct device * dev = siu_stream - > substream - > pcm - > card - > dev ;
u32 stfifo ;
if ( ! siu_stream - > rw_flg )
return - EPERM ;
/* input FIFO disable */
stfifo = siu_read32 ( base + SIU_STFIFO ) ;
siu_write32 ( base + SIU_STFIFO , stfifo & ~ 0x13071307 ) ;
dev_dbg ( dev , " %s: STFIFO %x -> %x \n " , __func__ ,
stfifo , stfifo & ~ 0x13071307 ) ;
/* during stmread flag clear */
siu_stream - > rw_flg = 0 ;
return 0 ;
}
2020-07-14 09:07:01 +09:00
static bool filter ( struct dma_chan * chan , void * secondary )
2010-01-22 19:09:03 +01:00
{
2020-07-14 09:07:01 +09:00
struct sh_dmae_slave * param = secondary ;
2010-01-22 19:09:03 +01:00
2020-07-14 09:07:01 +09:00
pr_debug ( " %s: secondary ID %d \n " , __func__ , param - > shdma_slave . slave_id ) ;
2010-01-22 19:09:03 +01:00
2012-05-09 17:09:18 +02:00
chan - > private = & param - > shdma_slave ;
2010-01-22 19:09:03 +01:00
return true ;
}
2019-10-02 14:33:16 +09:00
static int siu_pcm_open ( struct snd_soc_component * component ,
struct snd_pcm_substream * ss )
2010-01-22 19:09:03 +01:00
{
/* Playback / Capture */
2018-01-29 02:43:45 +00:00
struct siu_platform * pdata = component - > dev - > platform_data ;
2010-03-17 20:15:21 +00:00
struct siu_info * info = siu_i2s_data ;
2010-01-22 19:09:03 +01:00
struct siu_port * port_info = siu_port_info ( ss ) ;
struct siu_stream * siu_stream ;
u32 port = info - > port_id ;
struct device * dev = ss - > pcm - > card - > dev ;
dma_cap_mask_t mask ;
struct sh_dmae_slave * param ;
dma_cap_zero ( mask ) ;
dma_cap_set ( DMA_SLAVE , mask ) ;
dev_dbg ( dev , " %s, port=%d@%p \n " , __func__ , port , port_info ) ;
if ( ss - > stream = = SNDRV_PCM_STREAM_PLAYBACK ) {
siu_stream = & port_info - > playback ;
param = & siu_stream - > param ;
2012-05-09 17:09:18 +02:00
param - > shdma_slave . slave_id = port ? pdata - > dma_slave_tx_b :
2010-05-19 18:33:54 +00:00
pdata - > dma_slave_tx_a ;
2010-01-22 19:09:03 +01:00
} else {
siu_stream = & port_info - > capture ;
param = & siu_stream - > param ;
2012-05-09 17:09:18 +02:00
param - > shdma_slave . slave_id = port ? pdata - > dma_slave_rx_b :
2010-05-19 18:33:54 +00:00
pdata - > dma_slave_rx_a ;
2010-01-22 19:09:03 +01:00
}
/* Get DMA channel */
siu_stream - > chan = dma_request_channel ( mask , filter , param ) ;
if ( ! siu_stream - > chan ) {
dev_err ( dev , " DMA channel allocation failed! \n " ) ;
return - EBUSY ;
}
siu_stream - > substream = ss ;
return 0 ;
}
2019-10-02 14:33:16 +09:00
static int siu_pcm_close ( struct snd_soc_component * component ,
struct snd_pcm_substream * ss )
2010-01-22 19:09:03 +01:00
{
2010-03-17 20:15:21 +00:00
struct siu_info * info = siu_i2s_data ;
2010-01-22 19:09:03 +01:00
struct device * dev = ss - > pcm - > card - > dev ;
struct siu_port * port_info = siu_port_info ( ss ) ;
struct siu_stream * siu_stream ;
dev_dbg ( dev , " %s: port=%d \n " , __func__ , info - > port_id ) ;
if ( ss - > stream = = SNDRV_PCM_STREAM_PLAYBACK )
siu_stream = & port_info - > playback ;
else
siu_stream = & port_info - > capture ;
dma_release_channel ( siu_stream - > chan ) ;
siu_stream - > chan = NULL ;
siu_stream - > substream = NULL ;
return 0 ;
}
2019-10-02 14:33:16 +09:00
static int siu_pcm_prepare ( struct snd_soc_component * component ,
struct snd_pcm_substream * ss )
2010-01-22 19:09:03 +01:00
{
2010-03-17 20:15:21 +00:00
struct siu_info * info = siu_i2s_data ;
2010-01-22 19:09:03 +01:00
struct siu_port * port_info = siu_port_info ( ss ) ;
struct device * dev = ss - > pcm - > card - > dev ;
2021-02-19 17:16:34 -06:00
struct snd_pcm_runtime * rt ;
2010-01-22 19:09:03 +01:00
struct siu_stream * siu_stream ;
snd_pcm_sframes_t xfer_cnt ;
if ( ss - > stream = = SNDRV_PCM_STREAM_PLAYBACK )
siu_stream = & port_info - > playback ;
else
siu_stream = & port_info - > capture ;
rt = siu_stream - > substream - > runtime ;
siu_stream - > buf_bytes = snd_pcm_lib_buffer_bytes ( ss ) ;
siu_stream - > period_bytes = snd_pcm_lib_period_bytes ( ss ) ;
dev_dbg ( dev , " %s: port=%d, %d channels, period=%u bytes \n " , __func__ ,
info - > port_id , rt - > channels , siu_stream - > period_bytes ) ;
/* We only support buffers that are multiples of the period */
if ( siu_stream - > buf_bytes % siu_stream - > period_bytes ) {
dev_err ( dev , " %s() - buffer=%d not multiple of period=%d \n " ,
__func__ , siu_stream - > buf_bytes ,
siu_stream - > period_bytes ) ;
return - EINVAL ;
}
xfer_cnt = bytes_to_frames ( rt , siu_stream - > period_bytes ) ;
if ( ! xfer_cnt | | xfer_cnt > 0x1000000 )
return - EINVAL ;
siu_stream - > format = rt - > format ;
siu_stream - > xfer_cnt = xfer_cnt ;
dev_dbg ( dev , " port=%d buf=%lx buf_bytes=%d period_bytes=%d "
" format=%d channels=%d xfer_cnt=%d \n " , info - > port_id ,
( unsigned long ) rt - > dma_addr , siu_stream - > buf_bytes ,
siu_stream - > period_bytes ,
siu_stream - > format , rt - > channels , ( int ) xfer_cnt ) ;
return 0 ;
}
2019-10-02 14:33:16 +09:00
static int siu_pcm_trigger ( struct snd_soc_component * component ,
struct snd_pcm_substream * ss , int cmd )
2010-01-22 19:09:03 +01:00
{
2010-03-17 20:15:21 +00:00
struct siu_info * info = siu_i2s_data ;
2010-01-22 19:09:03 +01:00
struct device * dev = ss - > pcm - > card - > dev ;
struct siu_port * port_info = siu_port_info ( ss ) ;
int ret ;
dev_dbg ( dev , " %s: port=%d@%p, cmd=%d \n " , __func__ ,
info - > port_id , port_info , cmd ) ;
switch ( cmd ) {
case SNDRV_PCM_TRIGGER_START :
if ( ss - > stream = = SNDRV_PCM_STREAM_PLAYBACK )
ret = siu_pcm_stmwrite_start ( port_info ) ;
else
ret = siu_pcm_stmread_start ( port_info ) ;
if ( ret < 0 )
dev_warn ( dev , " %s: start failed on port=%d \n " ,
__func__ , info - > port_id ) ;
break ;
case SNDRV_PCM_TRIGGER_STOP :
if ( ss - > stream = = SNDRV_PCM_STREAM_PLAYBACK )
siu_pcm_stmwrite_stop ( port_info ) ;
else
siu_pcm_stmread_stop ( port_info ) ;
ret = 0 ;
break ;
default :
dev_err ( dev , " %s() unsupported cmd=%d \n " , __func__ , cmd ) ;
ret = - EINVAL ;
}
return ret ;
}
/*
* So far only resolution of one period is supported , subject to extending the
* dmangine API
*/
2019-10-02 14:33:16 +09:00
static snd_pcm_uframes_t
siu_pcm_pointer_dma ( struct snd_soc_component * component ,
struct snd_pcm_substream * ss )
2010-01-22 19:09:03 +01:00
{
struct device * dev = ss - > pcm - > card - > dev ;
2010-03-17 20:15:21 +00:00
struct siu_info * info = siu_i2s_data ;
2010-01-22 19:09:03 +01:00
u32 __iomem * base = info - > reg ;
struct siu_port * port_info = siu_port_info ( ss ) ;
struct snd_pcm_runtime * rt = ss - > runtime ;
size_t ptr ;
struct siu_stream * siu_stream ;
if ( ss - > stream = = SNDRV_PCM_STREAM_PLAYBACK )
siu_stream = & port_info - > playback ;
else
siu_stream = & port_info - > capture ;
/*
* ptr is the offset into the buffer where the dma is currently at . We
* check if the dma buffer has just wrapped .
*/
ptr = PERIOD_OFFSET ( rt - > dma_addr ,
siu_stream - > cur_period ,
siu_stream - > period_bytes ) - rt - > dma_addr ;
dev_dbg ( dev ,
" %s: port=%d, events %x, FSTS %x, xferred %u/%u, cookie %d \n " ,
__func__ , info - > port_id , siu_read32 ( base + SIU_EVNTC ) ,
siu_read32 ( base + SIU_SBFSTS ) , ptr , siu_stream - > buf_bytes ,
siu_stream - > cookie ) ;
if ( ptr > = siu_stream - > buf_bytes )
ptr = 0 ;
return bytes_to_frames ( ss - > runtime , ptr ) ;
}
2019-10-02 14:33:16 +09:00
static int siu_pcm_new ( struct snd_soc_component * component ,
struct snd_soc_pcm_runtime * rtd )
2010-01-22 19:09:03 +01:00
{
/* card->dev == socdev->dev, see snd_soc_new_pcms() */
2011-06-07 16:08:33 +01:00
struct snd_card * card = rtd - > card - > snd_card ;
struct snd_pcm * pcm = rtd - > pcm ;
2010-03-17 20:15:21 +00:00
struct siu_info * info = siu_i2s_data ;
2010-01-22 19:09:03 +01:00
struct platform_device * pdev = to_platform_device ( card - > dev ) ;
int ret ;
int i ;
/* pdev->id selects between SIUA and SIUB */
if ( pdev - > id < 0 | | pdev - > id > = SIU_PORT_NUM )
return - EINVAL ;
info - > port_id = pdev - > id ;
/*
* While the siu has 2 ports , only one port can be on at a time ( only 1
* SPB ) . So far all the boards using the siu had only one of the ports
* wired to a codec . To simplify things , we only register one port with
* alsa . In case both ports are needed , it should be changed here
*/
for ( i = pdev - > id ; i < pdev - > id + 1 ; i + + ) {
struct siu_port * * port_info = & siu_ports [ i ] ;
ret = siu_init_port ( i , port_info , card ) ;
if ( ret < 0 )
return ret ;
2019-12-10 15:26:00 +01:00
snd_pcm_set_managed_buffer_all ( pcm ,
2019-02-04 14:27:17 +01:00
SNDRV_DMA_TYPE_DEV , card - > dev ,
2010-01-22 19:09:03 +01:00
SIU_BUFFER_BYTES_MAX , SIU_BUFFER_BYTES_MAX ) ;
( * port_info ) - > pcm = pcm ;
2020-09-03 12:47:48 +02:00
/* IO works */
INIT_WORK ( & ( * port_info ) - > playback . work , siu_io_work ) ;
INIT_WORK ( & ( * port_info ) - > capture . work , siu_io_work ) ;
2010-01-22 19:09:03 +01:00
}
dev_info ( card - > dev , " SuperH SIU driver initialized. \n " ) ;
return 0 ;
}
2019-10-02 14:33:16 +09:00
static void siu_pcm_free ( struct snd_soc_component * component ,
struct snd_pcm * pcm )
2010-01-22 19:09:03 +01:00
{
struct platform_device * pdev = to_platform_device ( pcm - > card - > dev ) ;
struct siu_port * port_info = siu_ports [ pdev - > id ] ;
2020-09-03 12:47:48 +02:00
cancel_work_sync ( & port_info - > capture . work ) ;
cancel_work_sync ( & port_info - > playback . work ) ;
2010-01-22 19:09:03 +01:00
siu_free_port ( port_info ) ;
dev_dbg ( pcm - > card - > dev , " %s \n " , __func__ ) ;
}
2021-01-26 16:47:02 +01:00
const struct snd_soc_component_driver siu_component = {
2022-06-23 13:51:24 +01:00
. name = DRV_NAME ,
. open = siu_pcm_open ,
. close = siu_pcm_close ,
. prepare = siu_pcm_prepare ,
. trigger = siu_pcm_trigger ,
. pointer = siu_pcm_pointer_dma ,
. pcm_construct = siu_pcm_new ,
. pcm_destruct = siu_pcm_free ,
. legacy_dai_naming = 1 ,
2010-01-22 19:09:03 +01:00
} ;
2018-01-29 02:43:45 +00:00
EXPORT_SYMBOL_GPL ( siu_component ) ;