2017-11-07 16:58:50 +03:00
// SPDX-License-Identifier: GPL-2.0
2015-07-24 17:11:51 +03:00
/*
2017-11-21 17:05:06 +03:00
* sound . c - Sound component for Mostcore
2015-07-24 17:11:51 +03:00
*
* Copyright ( C ) 2015 Microchip Technology Germany II GmbH & Co . KG
*/
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
# include <linux/module.h>
# include <linux/printk.h>
# include <linux/kernel.h>
2018-12-17 17:10:12 +03:00
# include <linux/slab.h>
2015-07-24 17:11:51 +03:00
# include <linux/init.h>
# include <sound/core.h>
# include <sound/pcm.h>
2015-09-28 18:18:47 +03:00
# include <sound/pcm_params.h>
2015-07-24 17:11:51 +03:00
# include <linux/sched.h>
# include <linux/kthread.h>
2020-03-10 16:02:40 +03:00
# include <linux/most.h>
2015-07-24 17:11:51 +03:00
# define DRIVER_NAME "sound"
2019-04-03 16:19:52 +03:00
# define STRING_SIZE 80
2015-07-24 17:11:51 +03:00
2019-12-13 15:04:15 +03:00
static struct most_component comp ;
2015-07-24 17:11:51 +03:00
/**
* struct channel - private structure to keep channel specific data
* @ substream : stores the substream structure
* @ iface : interface for which the channel belongs to
* @ cfg : channel configuration
* @ card : registered sound card
* @ list : list for private use
* @ id : channel index
* @ period_pos : current period position ( ring buffer )
* @ buffer_pos : current buffer position ( ring buffer )
* @ is_stream_running : identifies whether a stream is running or not
* @ opened : set when the stream is opened
* @ playback_task : playback thread
* @ playback_waitq : waitq used by playback thread
*/
struct channel {
struct snd_pcm_substream * substream ;
2015-09-28 18:18:49 +03:00
struct snd_pcm_hardware pcm_hardware ;
2015-07-24 17:11:51 +03:00
struct most_interface * iface ;
struct most_channel_config * cfg ;
struct snd_card * card ;
struct list_head list ;
int id ;
unsigned int period_pos ;
unsigned int buffer_pos ;
bool is_stream_running ;
struct task_struct * playback_task ;
wait_queue_head_t playback_waitq ;
void ( * copy_fn ) ( void * alsa , void * most , unsigned int bytes ) ;
} ;
2018-12-17 17:10:12 +03:00
struct sound_adapter {
struct list_head dev_list ;
struct most_interface * iface ;
struct snd_card * card ;
struct list_head list ;
bool registered ;
int pcm_dev_idx ;
} ;
static struct list_head adpt_list ;
2015-07-24 17:11:51 +03:00
# define MOST_PCM_INFO (SNDRV_PCM_INFO_MMAP | \
SNDRV_PCM_INFO_MMAP_VALID | \
SNDRV_PCM_INFO_BATCH | \
SNDRV_PCM_INFO_INTERLEAVED | \
SNDRV_PCM_INFO_BLOCK_TRANSFER )
static void swap_copy16 ( u16 * dest , const u16 * source , unsigned int bytes )
{
unsigned int i = 0 ;
while ( i < ( bytes / 2 ) ) {
2020-11-04 15:50:42 +03:00
dest [ i ] = swab16 ( source [ i ] ) ;
2015-07-24 17:11:51 +03:00
i + + ;
}
}
static void swap_copy24 ( u8 * dest , const u8 * source , unsigned int bytes )
{
unsigned int i = 0 ;
2021-02-02 19:21:05 +03:00
if ( bytes < 2 )
return ;
2015-07-24 17:11:51 +03:00
while ( i < bytes - 2 ) {
dest [ i ] = source [ i + 2 ] ;
dest [ i + 1 ] = source [ i + 1 ] ;
dest [ i + 2 ] = source [ i ] ;
i + = 3 ;
}
}
static void swap_copy32 ( u32 * dest , const u32 * source , unsigned int bytes )
{
unsigned int i = 0 ;
while ( i < bytes / 4 ) {
2020-11-04 15:50:42 +03:00
dest [ i ] = swab32 ( source [ i ] ) ;
2015-07-24 17:11:51 +03:00
i + + ;
}
}
static void alsa_to_most_memcpy ( void * alsa , void * most , unsigned int bytes )
{
memcpy ( most , alsa , bytes ) ;
}
static void alsa_to_most_copy16 ( void * alsa , void * most , unsigned int bytes )
{
swap_copy16 ( most , alsa , bytes ) ;
}
static void alsa_to_most_copy24 ( void * alsa , void * most , unsigned int bytes )
{
swap_copy24 ( most , alsa , bytes ) ;
}
static void alsa_to_most_copy32 ( void * alsa , void * most , unsigned int bytes )
{
swap_copy32 ( most , alsa , bytes ) ;
}
static void most_to_alsa_memcpy ( void * alsa , void * most , unsigned int bytes )
{
memcpy ( alsa , most , bytes ) ;
}
static void most_to_alsa_copy16 ( void * alsa , void * most , unsigned int bytes )
{
swap_copy16 ( alsa , most , bytes ) ;
}
static void most_to_alsa_copy24 ( void * alsa , void * most , unsigned int bytes )
{
swap_copy24 ( alsa , most , bytes ) ;
}
static void most_to_alsa_copy32 ( void * alsa , void * most , unsigned int bytes )
{
swap_copy32 ( alsa , most , bytes ) ;
}
/**
* get_channel - get pointer to channel
* @ iface : interface structure
* @ channel_id : channel ID
*
* This traverses the channel list and returns the channel matching the
* ID and interface .
*
* Returns pointer to channel on success or NULL otherwise .
*/
static struct channel * get_channel ( struct most_interface * iface ,
int channel_id )
{
2018-12-17 17:10:12 +03:00
struct sound_adapter * adpt = iface - > priv ;
2021-02-02 14:38:10 +03:00
struct channel * channel ;
2015-07-24 17:11:51 +03:00
2021-02-02 14:38:10 +03:00
list_for_each_entry ( channel , & adpt - > dev_list , list ) {
2015-07-24 17:11:51 +03:00
if ( ( channel - > iface = = iface ) & & ( channel - > id = = channel_id ) )
return channel ;
}
return NULL ;
}
/**
* copy_data - implements data copying function
* @ channel : channel
* @ mbo : MBO from core
*
* Copy data from / to ring buffer to / from MBO and update the buffer position
*/
static bool copy_data ( struct channel * channel , struct mbo * mbo )
{
struct snd_pcm_runtime * const runtime = channel - > substream - > runtime ;
unsigned int const frame_bytes = channel - > cfg - > subbuffer_size ;
unsigned int const buffer_size = runtime - > buffer_size ;
unsigned int frames ;
unsigned int fr0 ;
if ( channel - > cfg - > direction & MOST_CH_RX )
frames = mbo - > processed_length / frame_bytes ;
else
frames = mbo - > buffer_length / frame_bytes ;
fr0 = min ( buffer_size - channel - > buffer_pos , frames ) ;
channel - > copy_fn ( runtime - > dma_area + channel - > buffer_pos * frame_bytes ,
mbo - > virt_address ,
fr0 * frame_bytes ) ;
if ( frames > fr0 ) {
/* wrap around at end of ring buffer */
channel - > copy_fn ( runtime - > dma_area ,
mbo - > virt_address + fr0 * frame_bytes ,
( frames - fr0 ) * frame_bytes ) ;
}
channel - > buffer_pos + = frames ;
if ( channel - > buffer_pos > = buffer_size )
channel - > buffer_pos - = buffer_size ;
channel - > period_pos + = frames ;
if ( channel - > period_pos > = runtime - > period_size ) {
channel - > period_pos - = runtime - > period_size ;
return true ;
}
return false ;
}
/**
* playback_thread - function implements the playback thread
* @ data : private data
*
* Thread which does the playback functionality in a loop . It waits for a free
* MBO from mostcore for a particular channel and copy the data from ring buffer
* to MBO . Submit the MBO back to mostcore , after copying the data .
*
* Returns 0 on success or error code otherwise .
*/
static int playback_thread ( void * data )
{
struct channel * const channel = data ;
while ( ! kthread_should_stop ( ) ) {
struct mbo * mbo = NULL ;
bool period_elapsed = false ;
wait_event_interruptible (
channel - > playback_waitq ,
kthread_should_stop ( ) | |
2015-09-28 18:18:58 +03:00
( channel - > is_stream_running & &
( mbo = most_get_mbo ( channel - > iface , channel - > id ,
2017-11-21 17:05:06 +03:00
& comp ) ) ) ) ;
2015-07-24 17:11:51 +03:00
if ( ! mbo )
continue ;
if ( channel - > is_stream_running )
period_elapsed = copy_data ( channel , mbo ) ;
else
memset ( mbo - > virt_address , 0 , mbo - > buffer_length ) ;
2016-09-21 15:49:05 +03:00
most_submit_mbo ( mbo ) ;
2015-07-24 17:11:51 +03:00
if ( period_elapsed )
snd_pcm_period_elapsed ( channel - > substream ) ;
}
return 0 ;
}
/**
* pcm_open - implements open callback function for PCM middle layer
* @ substream : pointer to ALSA PCM substream
*
* This is called when a PCM substream is opened . At least , the function should
* initialize the runtime - > hw record .
*
* Returns 0 on success or error code otherwise .
*/
static int pcm_open ( struct snd_pcm_substream * substream )
{
struct channel * channel = substream - > private_data ;
struct snd_pcm_runtime * runtime = substream - > runtime ;
struct most_channel_config * cfg = channel - > cfg ;
2020-06-23 18:07:33 +03:00
int ret ;
2015-07-24 17:11:51 +03:00
channel - > substream = substream ;
if ( cfg - > direction = = MOST_CH_TX ) {
2015-10-13 18:37:49 +03:00
channel - > playback_task = kthread_run ( playback_thread , channel ,
2015-07-24 17:11:51 +03:00
" most_audio_playback " ) ;
2015-09-28 18:18:58 +03:00
if ( IS_ERR ( channel - > playback_task ) ) {
pr_err ( " Couldn't start thread \n " ) ;
2015-07-24 17:11:51 +03:00
return PTR_ERR ( channel - > playback_task ) ;
2015-09-28 18:18:58 +03:00
}
2015-07-24 17:11:51 +03:00
}
2020-06-23 18:07:33 +03:00
ret = most_start_channel ( channel - > iface , channel - > id , & comp ) ;
if ( ret ) {
2015-07-24 17:11:51 +03:00
pr_err ( " most_start_channel() failed! \n " ) ;
if ( cfg - > direction = = MOST_CH_TX )
kthread_stop ( channel - > playback_task ) ;
2020-06-23 18:07:33 +03:00
return ret ;
2015-07-24 17:11:51 +03:00
}
2015-09-28 18:18:49 +03:00
runtime - > hw = channel - > pcm_hardware ;
2015-07-24 17:11:51 +03:00
return 0 ;
}
/**
* pcm_close - implements close callback function for PCM middle layer
* @ substream : sub - stream pointer
*
* Obviously , this is called when a PCM substream is closed . Any private
* instance for a PCM substream allocated in the open callback will be
* released here .
*
* Returns 0 on success or error code otherwise .
*/
static int pcm_close ( struct snd_pcm_substream * substream )
{
struct channel * channel = substream - > private_data ;
if ( channel - > cfg - > direction = = MOST_CH_TX )
kthread_stop ( channel - > playback_task ) ;
2017-11-21 17:05:06 +03:00
most_stop_channel ( channel - > iface , channel - > id , & comp ) ;
2015-07-24 17:11:51 +03:00
return 0 ;
}
/**
* pcm_prepare - implements prepare callback function for PCM middle layer
* @ substream : substream pointer
*
* This callback is called when the PCM is " prepared " . Format rate , sample rate ,
* etc . , can be set here . This callback can be called many times at each setup .
*
* Returns 0 on success or error code otherwise .
*/
static int pcm_prepare ( struct snd_pcm_substream * substream )
{
struct channel * channel = substream - > private_data ;
struct snd_pcm_runtime * runtime = substream - > runtime ;
struct most_channel_config * cfg = channel - > cfg ;
int width = snd_pcm_format_physical_width ( runtime - > format ) ;
channel - > copy_fn = NULL ;
if ( cfg - > direction = = MOST_CH_TX ) {
if ( snd_pcm_format_big_endian ( runtime - > format ) | | width = = 8 )
channel - > copy_fn = alsa_to_most_memcpy ;
else if ( width = = 16 )
channel - > copy_fn = alsa_to_most_copy16 ;
else if ( width = = 24 )
channel - > copy_fn = alsa_to_most_copy24 ;
else if ( width = = 32 )
channel - > copy_fn = alsa_to_most_copy32 ;
} else {
if ( snd_pcm_format_big_endian ( runtime - > format ) | | width = = 8 )
channel - > copy_fn = most_to_alsa_memcpy ;
else if ( width = = 16 )
channel - > copy_fn = most_to_alsa_copy16 ;
else if ( width = = 24 )
channel - > copy_fn = most_to_alsa_copy24 ;
else if ( width = = 32 )
channel - > copy_fn = most_to_alsa_copy32 ;
}
2020-06-23 18:07:31 +03:00
if ( ! channel - > copy_fn )
2015-07-24 17:11:51 +03:00
return - EINVAL ;
channel - > period_pos = 0 ;
channel - > buffer_pos = 0 ;
return 0 ;
}
/**
* pcm_trigger - implements trigger callback function for PCM middle layer
* @ substream : substream pointer
* @ cmd : action to perform
*
* This is called when the PCM is started , stopped or paused . The action will be
* specified in the second argument , SNDRV_PCM_TRIGGER_XXX
*
* Returns 0 on success or error code otherwise .
*/
static int pcm_trigger ( struct snd_pcm_substream * substream , int cmd )
{
struct channel * channel = substream - > private_data ;
switch ( cmd ) {
case SNDRV_PCM_TRIGGER_START :
channel - > is_stream_running = true ;
2015-09-28 18:18:58 +03:00
wake_up_interruptible ( & channel - > playback_waitq ) ;
2015-07-24 17:11:51 +03:00
return 0 ;
case SNDRV_PCM_TRIGGER_STOP :
channel - > is_stream_running = false ;
return 0 ;
default :
return - EINVAL ;
}
return 0 ;
}
/**
* pcm_pointer - implements pointer callback function for PCM middle layer
* @ substream : substream pointer
*
* This callback is called when the PCM middle layer inquires the current
* hardware position on the buffer . The position must be returned in frames ,
* ranging from 0 to buffer_size - 1.
*/
static snd_pcm_uframes_t pcm_pointer ( struct snd_pcm_substream * substream )
{
struct channel * channel = substream - > private_data ;
return channel - > buffer_pos ;
}
/**
* Initialization of struct snd_pcm_ops
*/
2016-09-26 20:39:03 +03:00
static const struct snd_pcm_ops pcm_ops = {
2015-07-24 17:11:51 +03:00
. open = pcm_open ,
. close = pcm_close ,
. prepare = pcm_prepare ,
. trigger = pcm_trigger ,
. pointer = pcm_pointer ,
} ;
2019-04-03 16:19:47 +03:00
static int split_arg_list ( char * buf , u16 * ch_num , char * * sample_res )
2015-07-24 17:11:51 +03:00
{
2018-05-08 12:44:54 +03:00
char * num ;
int ret ;
num = strsep ( & buf , " x " ) ;
if ( ! num )
goto err ;
ret = kstrtou16 ( num , 0 , ch_num ) ;
if ( ret )
goto err ;
* sample_res = strsep ( & buf , " . \n " ) ;
if ( ! * sample_res )
goto err ;
2015-07-24 17:11:51 +03:00
return 0 ;
2018-05-08 12:44:54 +03:00
err :
pr_err ( " Bad PCM format \n " ) ;
2020-06-23 18:07:33 +03:00
return - EINVAL ;
2015-07-24 17:11:51 +03:00
}
2018-05-08 12:44:54 +03:00
static const struct sample_resolution_info {
const char * sample_res ;
int bytes ;
u64 formats ;
} sinfo [ ] = {
{ " 8 " , 1 , SNDRV_PCM_FMTBIT_S8 } ,
{ " 16 " , 2 , SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE } ,
{ " 24 " , 3 , SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_3BE } ,
{ " 32 " , 4 , SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S32_BE } ,
} ;
2015-09-28 18:18:53 +03:00
static int audio_set_hw_params ( struct snd_pcm_hardware * pcm_hw ,
2018-05-08 12:44:54 +03:00
u16 ch_num , char * sample_res ,
2015-09-28 18:18:55 +03:00
struct most_channel_config * cfg )
2015-07-24 17:11:51 +03:00
{
2018-05-08 12:44:54 +03:00
int i ;
for ( i = 0 ; i < ARRAY_SIZE ( sinfo ) ; i + + ) {
if ( ! strcmp ( sample_res , sinfo [ i ] . sample_res ) )
goto found ;
}
pr_err ( " Unsupported PCM format \n " ) ;
2020-06-23 18:07:33 +03:00
return - EINVAL ;
2018-05-08 12:44:54 +03:00
found :
if ( ! ch_num ) {
pr_err ( " Bad number of channels \n " ) ;
return - EINVAL ;
}
if ( cfg - > subbuffer_size ! = ch_num * sinfo [ i ] . bytes ) {
pr_err ( " Audio resolution doesn't fit subbuffer size \n " ) ;
return - EINVAL ;
}
2015-09-28 18:18:54 +03:00
pcm_hw - > info = MOST_PCM_INFO ;
pcm_hw - > rates = SNDRV_PCM_RATE_48000 ;
pcm_hw - > rate_min = 48000 ;
pcm_hw - > rate_max = 48000 ;
pcm_hw - > buffer_bytes_max = cfg - > num_buffers * cfg - > buffer_size ;
pcm_hw - > period_bytes_min = cfg - > buffer_size ;
pcm_hw - > period_bytes_max = cfg - > buffer_size ;
pcm_hw - > periods_min = 1 ;
pcm_hw - > periods_max = cfg - > num_buffers ;
2018-05-08 12:44:54 +03:00
pcm_hw - > channels_min = ch_num ;
pcm_hw - > channels_max = ch_num ;
pcm_hw - > formats = sinfo [ i ] . formats ;
2015-07-24 17:11:51 +03:00
return 0 ;
}
2018-12-17 17:10:12 +03:00
static void release_adapter ( struct sound_adapter * adpt )
{
struct channel * channel , * tmp ;
list_for_each_entry_safe ( channel , tmp , & adpt - > dev_list , list ) {
list_del ( & channel - > list ) ;
kfree ( channel ) ;
}
if ( adpt - > card )
snd_card_free ( adpt - > card ) ;
list_del ( & adpt - > list ) ;
kfree ( adpt ) ;
}
2015-07-24 17:11:51 +03:00
/**
* audio_probe_channel - probe function of the driver module
* @ iface : pointer to interface instance
* @ channel_id : channel index / ID
* @ cfg : pointer to actual channel configuration
* @ arg_list : string that provides the name of the device to be created in / dev
* plus the desired audio resolution
*
* Creates sound card , pcm device , sets pcm ops and registers sound card .
*
* Returns 0 on success or error code otherwise .
*/
static int audio_probe_channel ( struct most_interface * iface , int channel_id ,
struct most_channel_config * cfg ,
2019-04-03 16:19:45 +03:00
char * device_name , char * arg_list )
2015-07-24 17:11:51 +03:00
{
struct channel * channel ;
2018-12-17 17:10:12 +03:00
struct sound_adapter * adpt ;
2015-07-24 17:11:51 +03:00
struct snd_pcm * pcm ;
int playback_count = 0 ;
int capture_count = 0 ;
int ret ;
int direction ;
2018-05-08 12:44:54 +03:00
u16 ch_num ;
char * sample_res ;
2019-04-03 16:19:52 +03:00
char arg_list_cpy [ STRING_SIZE ] ;
2015-07-24 17:11:51 +03:00
if ( cfg - > data_type ! = MOST_CH_SYNC ) {
pr_err ( " Incompatible channel type \n " ) ;
return - EINVAL ;
}
2021-01-31 20:28:25 +03:00
strscpy ( arg_list_cpy , arg_list , STRING_SIZE ) ;
2019-04-03 16:19:52 +03:00
ret = split_arg_list ( arg_list_cpy , & ch_num , & sample_res ) ;
2018-12-17 17:10:12 +03:00
if ( ret < 0 )
return ret ;
list_for_each_entry ( adpt , & adpt_list , list ) {
if ( adpt - > iface ! = iface )
continue ;
if ( adpt - > registered )
return - ENOSPC ;
adpt - > pcm_dev_idx + + ;
goto skip_adpt_alloc ;
}
adpt = kzalloc ( sizeof ( * adpt ) , GFP_KERNEL ) ;
if ( ! adpt )
return - ENOMEM ;
adpt - > iface = iface ;
INIT_LIST_HEAD ( & adpt - > dev_list ) ;
iface - > priv = adpt ;
list_add_tail ( & adpt - > list , & adpt_list ) ;
2019-04-30 15:07:48 +03:00
ret = snd_card_new ( iface - > driver_dev , - 1 , " INIC " , THIS_MODULE ,
2018-12-17 17:10:12 +03:00
sizeof ( * channel ) , & adpt - > card ) ;
if ( ret < 0 )
2018-12-17 17:10:13 +03:00
goto err_free_adpt ;
2018-12-17 17:10:12 +03:00
snprintf ( adpt - > card - > driver , sizeof ( adpt - > card - > driver ) ,
" %s " , DRIVER_NAME ) ;
snprintf ( adpt - > card - > shortname , sizeof ( adpt - > card - > shortname ) ,
2018-12-17 17:10:15 +03:00
" Microchip INIC " ) ;
2018-12-17 17:10:12 +03:00
snprintf ( adpt - > card - > longname , sizeof ( adpt - > card - > longname ) ,
2018-12-17 17:10:16 +03:00
" %s at %s " , adpt - > card - > shortname , iface - > description ) ;
2018-12-17 17:10:12 +03:00
skip_adpt_alloc :
2015-07-24 17:11:51 +03:00
if ( get_channel ( iface , channel_id ) ) {
pr_err ( " channel (%s:%d) is already linked \n " ,
iface - > description , channel_id ) ;
2020-06-23 18:07:33 +03:00
return - EEXIST ;
2015-07-24 17:11:51 +03:00
}
if ( cfg - > direction = = MOST_CH_TX ) {
playback_count = 1 ;
direction = SNDRV_PCM_STREAM_PLAYBACK ;
} else {
capture_count = 1 ;
direction = SNDRV_PCM_STREAM_CAPTURE ;
}
2018-12-17 17:10:12 +03:00
channel = kzalloc ( sizeof ( * channel ) , GFP_KERNEL ) ;
if ( ! channel ) {
ret = - ENOMEM ;
2018-12-17 17:10:13 +03:00
goto err_free_adpt ;
2018-12-17 17:10:12 +03:00
}
channel - > card = adpt - > card ;
2015-07-24 17:11:51 +03:00
channel - > cfg = cfg ;
channel - > iface = iface ;
channel - > id = channel_id ;
2015-09-28 18:18:58 +03:00
init_waitqueue_head ( & channel - > playback_waitq ) ;
2018-12-17 17:10:12 +03:00
list_add_tail ( & channel - > list , & adpt - > dev_list ) ;
2015-09-28 18:18:49 +03:00
2018-05-08 12:44:54 +03:00
ret = audio_set_hw_params ( & channel - > pcm_hardware , ch_num , sample_res ,
cfg ) ;
2016-09-25 18:41:11 +03:00
if ( ret )
2018-12-17 17:10:13 +03:00
goto err_free_adpt ;
2015-07-24 17:11:51 +03:00
2018-12-17 17:10:14 +03:00
ret = snd_pcm_new ( adpt - > card , device_name , adpt - > pcm_dev_idx ,
2018-12-17 17:10:12 +03:00
playback_count , capture_count , & pcm ) ;
2015-07-24 17:11:51 +03:00
if ( ret < 0 )
2018-12-17 17:10:13 +03:00
goto err_free_adpt ;
2015-07-24 17:11:51 +03:00
pcm - > private_data = channel ;
2018-12-18 14:35:52 +03:00
strscpy ( pcm - > name , device_name , sizeof ( pcm - > name ) ) ;
2015-07-24 17:11:51 +03:00
snd_pcm_set_ops ( pcm , direction , & pcm_ops ) ;
2019-12-10 17:13:53 +03:00
snd_pcm_set_managed_buffer_all ( pcm , SNDRV_DMA_TYPE_VMALLOC , NULL , 0 , 0 ) ;
2015-07-24 17:11:51 +03:00
return 0 ;
2018-12-17 17:10:13 +03:00
err_free_adpt :
2018-12-17 17:10:12 +03:00
release_adapter ( adpt ) ;
2015-07-24 17:11:51 +03:00
return ret ;
}
2019-04-03 16:19:47 +03:00
static int audio_create_sound_card ( void )
{
int ret ;
struct sound_adapter * adpt ;
list_for_each_entry ( adpt , & adpt_list , list ) {
if ( ! adpt - > registered )
goto adpt_alloc ;
}
return - ENODEV ;
adpt_alloc :
ret = snd_card_register ( adpt - > card ) ;
if ( ret < 0 ) {
release_adapter ( adpt ) ;
return ret ;
}
adpt - > registered = true ;
return 0 ;
}
2015-07-24 17:11:51 +03:00
/**
* audio_disconnect_channel - function to disconnect a channel
* @ iface : pointer to interface instance
* @ channel_id : channel index
*
* This frees allocated memory and removes the sound card from ALSA
*
* Returns 0 on success or error code otherwise .
*/
static int audio_disconnect_channel ( struct most_interface * iface ,
int channel_id )
{
struct channel * channel ;
2018-12-17 17:10:12 +03:00
struct sound_adapter * adpt = iface - > priv ;
2015-07-24 17:11:51 +03:00
channel = get_channel ( iface , channel_id ) ;
2020-06-23 18:07:31 +03:00
if ( ! channel )
2015-07-24 17:11:51 +03:00
return - EINVAL ;
list_del ( & channel - > list ) ;
2018-12-17 17:10:12 +03:00
kfree ( channel ) ;
if ( list_empty ( & adpt - > dev_list ) )
release_adapter ( adpt ) ;
2015-07-24 17:11:51 +03:00
return 0 ;
}
/**
* audio_rx_completion - completion handler for rx channels
* @ mbo : pointer to buffer object that has completed
*
* This searches for the channel this MBO belongs to and copy the data from MBO
* to ring buffer
*
* Returns 0 on success or error code otherwise .
*/
static int audio_rx_completion ( struct mbo * mbo )
{
struct channel * channel = get_channel ( mbo - > ifp , mbo - > hdm_channel_id ) ;
bool period_elapsed = false ;
2020-06-23 18:07:31 +03:00
if ( ! channel )
2015-07-24 17:11:51 +03:00
return - EINVAL ;
if ( channel - > is_stream_running )
period_elapsed = copy_data ( channel , mbo ) ;
most_put_mbo ( mbo ) ;
if ( period_elapsed )
snd_pcm_period_elapsed ( channel - > substream ) ;
return 0 ;
}
/**
* audio_tx_completion - completion handler for tx channels
* @ iface : pointer to interface instance
* @ channel_id : channel index / ID
*
* This searches the channel that belongs to this combination of interface
* pointer and channel ID and wakes a process sitting in the wait queue of
* this channel .
*
* Returns 0 on success or error code otherwise .
*/
static int audio_tx_completion ( struct most_interface * iface , int channel_id )
{
struct channel * channel = get_channel ( iface , channel_id ) ;
2020-06-23 18:07:31 +03:00
if ( ! channel )
2015-07-24 17:11:51 +03:00
return - EINVAL ;
wake_up_interruptible ( & channel - > playback_waitq ) ;
return 0 ;
}
/**
2019-12-13 15:04:15 +03:00
* Initialization of the struct most_component
2015-07-24 17:11:51 +03:00
*/
2019-12-13 15:04:15 +03:00
static struct most_component comp = {
2019-11-08 19:21:08 +03:00
. mod = THIS_MODULE ,
2015-07-24 17:11:51 +03:00
. name = DRIVER_NAME ,
. probe_channel = audio_probe_channel ,
. disconnect_channel = audio_disconnect_channel ,
. rx_completion = audio_rx_completion ,
. tx_completion = audio_tx_completion ,
2019-04-03 16:19:47 +03:00
. cfg_complete = audio_create_sound_card ,
2015-07-24 17:11:51 +03:00
} ;
static int __init audio_init ( void )
{
2019-04-03 16:19:48 +03:00
int ret ;
2018-12-17 17:10:12 +03:00
INIT_LIST_HEAD ( & adpt_list ) ;
2015-07-24 17:11:51 +03:00
2019-04-03 16:19:48 +03:00
ret = most_register_component ( & comp ) ;
2020-06-23 18:07:32 +03:00
if ( ret ) {
2019-04-03 16:19:48 +03:00
pr_err ( " Failed to register %s \n " , comp . name ) ;
2020-06-23 18:07:32 +03:00
return ret ;
}
2019-04-03 16:19:48 +03:00
ret = most_register_configfs_subsys ( & comp ) ;
2019-08-27 16:13:46 +03:00
if ( ret ) {
2019-04-03 16:19:48 +03:00
pr_err ( " Failed to register %s configfs subsys \n " , comp . name ) ;
2019-08-27 16:13:46 +03:00
most_deregister_component ( & comp ) ;
}
2019-04-03 16:19:48 +03:00
return ret ;
2015-07-24 17:11:51 +03:00
}
static void __exit audio_exit ( void )
{
2019-04-03 16:19:48 +03:00
most_deregister_configfs_subsys ( & comp ) ;
2017-11-21 17:05:06 +03:00
most_deregister_component ( & comp ) ;
2015-07-24 17:11:51 +03:00
}
module_init ( audio_init ) ;
module_exit ( audio_exit ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " Christian Gromm <christian.gromm@microchip.com> " ) ;
2017-11-21 17:05:12 +03:00
MODULE_DESCRIPTION ( " Sound Component Module for Mostcore " ) ;