2019-05-19 15:08:55 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2015-03-03 01:22:59 +03:00
/*
* HD - audio core bus driver
*/
# include <linux/init.h>
# include <linux/device.h>
# include <linux/module.h>
# include <linux/export.h>
# include <sound/hdaudio.h>
2015-03-10 17:16:28 +03:00
# include "trace.h"
2015-03-03 01:22:59 +03:00
2015-04-14 13:15:47 +03:00
static const struct hdac_bus_ops default_ops = {
. command = snd_hdac_bus_send_cmd ,
. get_response = snd_hdac_bus_get_response ,
} ;
2015-03-03 01:22:59 +03:00
/**
* snd_hdac_bus_init - initialize a HD - audio bas bus
* @ bus : the pointer to bus object
2015-04-14 13:15:47 +03:00
* @ ops : bus verb operators
* @ io_ops : lowlevel I / O operators
2015-03-03 01:22:59 +03:00
*
* Returns 0 if successful , or a negative error code .
*/
int snd_hdac_bus_init ( struct hdac_bus * bus , struct device * dev ,
2015-04-14 13:15:47 +03:00
const struct hdac_bus_ops * ops ,
const struct hdac_io_ops * io_ops )
2015-03-03 01:22:59 +03:00
{
memset ( bus , 0 , sizeof ( * bus ) ) ;
bus - > dev = dev ;
2015-04-14 13:15:47 +03:00
if ( ops )
bus - > ops = ops ;
else
bus - > ops = & default_ops ;
bus - > io_ops = io_ops ;
INIT_LIST_HEAD ( & bus - > stream_list ) ;
2015-03-03 01:22:59 +03:00
INIT_LIST_HEAD ( & bus - > codec_list ) ;
2018-12-12 00:30:27 +03:00
INIT_WORK ( & bus - > unsol_work , snd_hdac_bus_process_unsol_events ) ;
2015-04-14 13:15:47 +03:00
spin_lock_init ( & bus - > reg_lock ) ;
2015-03-03 01:22:59 +03:00
mutex_init ( & bus - > cmd_mutex ) ;
2019-04-10 13:49:55 +03:00
mutex_init ( & bus - > lock ) ;
2019-04-10 17:00:54 +03:00
INIT_LIST_HEAD ( & bus - > hlink_list ) ;
2015-04-14 13:15:47 +03:00
bus - > irq = - 1 ;
2015-03-03 01:22:59 +03:00
return 0 ;
}
EXPORT_SYMBOL_GPL ( snd_hdac_bus_init ) ;
/**
* snd_hdac_bus_exit - clean up a HD - audio bas bus
* @ bus : the pointer to bus object
*/
void snd_hdac_bus_exit ( struct hdac_bus * bus )
{
2015-04-14 13:15:47 +03:00
WARN_ON ( ! list_empty ( & bus - > stream_list ) ) ;
2015-03-03 01:22:59 +03:00
WARN_ON ( ! list_empty ( & bus - > codec_list ) ) ;
cancel_work_sync ( & bus - > unsol_work ) ;
}
EXPORT_SYMBOL_GPL ( snd_hdac_bus_exit ) ;
/**
* snd_hdac_bus_exec_verb - execute a HD - audio verb on the given bus
* @ bus : bus object
* @ cmd : HD - audio encoded verb
* @ res : pointer to store the response , NULL if performing asynchronously
*
* Returns 0 if successful , or a negative error code .
*/
int snd_hdac_bus_exec_verb ( struct hdac_bus * bus , unsigned int addr ,
unsigned int cmd , unsigned int * res )
{
int err ;
mutex_lock ( & bus - > cmd_mutex ) ;
err = snd_hdac_bus_exec_verb_unlocked ( bus , addr , cmd , res ) ;
mutex_unlock ( & bus - > cmd_mutex ) ;
return err ;
}
EXPORT_SYMBOL_GPL ( snd_hdac_bus_exec_verb ) ;
/**
* snd_hdac_bus_exec_verb_unlocked - unlocked version
* @ bus : bus object
* @ cmd : HD - audio encoded verb
* @ res : pointer to store the response , NULL if performing asynchronously
*
* Returns 0 if successful , or a negative error code .
*/
int snd_hdac_bus_exec_verb_unlocked ( struct hdac_bus * bus , unsigned int addr ,
unsigned int cmd , unsigned int * res )
{
unsigned int tmp ;
int err ;
if ( cmd = = ~ 0 )
return - EINVAL ;
if ( res )
* res = - 1 ;
else if ( bus - > sync_write )
res = & tmp ;
for ( ; ; ) {
2015-03-10 17:16:28 +03:00
trace_hda_send_cmd ( bus , cmd ) ;
2015-03-03 01:22:59 +03:00
err = bus - > ops - > command ( bus , cmd ) ;
if ( err ! = - EAGAIN )
break ;
/* process pending verbs */
err = bus - > ops - > get_response ( bus , addr , & tmp ) ;
if ( err )
break ;
}
2015-03-10 17:16:28 +03:00
if ( ! err & & res ) {
2015-03-03 01:22:59 +03:00
err = bus - > ops - > get_response ( bus , addr , res ) ;
2015-03-10 17:16:28 +03:00
trace_hda_get_response ( bus , addr , * res ) ;
}
2015-03-03 01:22:59 +03:00
return err ;
}
EXPORT_SYMBOL_GPL ( snd_hdac_bus_exec_verb_unlocked ) ;
/**
* snd_hdac_bus_queue_event - add an unsolicited event to queue
* @ bus : the BUS
* @ res : unsolicited event ( lower 32 bit of RIRB entry )
* @ res_ex : codec addr and flags ( upper 32 bit or RIRB entry )
*
* Adds the given event to the queue . The events are processed in
* the workqueue asynchronously . Call this function in the interrupt
* hanlder when RIRB receives an unsolicited event .
*/
void snd_hdac_bus_queue_event ( struct hdac_bus * bus , u32 res , u32 res_ex )
{
unsigned int wp ;
if ( ! bus )
return ;
2015-03-10 17:16:28 +03:00
trace_hda_unsol_event ( bus , res , res_ex ) ;
2015-03-03 01:22:59 +03:00
wp = ( bus - > unsol_wp + 1 ) % HDA_UNSOL_QUEUE_SIZE ;
bus - > unsol_wp = wp ;
wp < < = 1 ;
bus - > unsol_queue [ wp ] = res ;
bus - > unsol_queue [ wp + 1 ] = res_ex ;
schedule_work ( & bus - > unsol_work ) ;
}
EXPORT_SYMBOL_GPL ( snd_hdac_bus_queue_event ) ;
/*
* process queued unsolicited events
*/
2018-12-12 00:30:27 +03:00
void snd_hdac_bus_process_unsol_events ( struct work_struct * work )
2015-03-03 01:22:59 +03:00
{
struct hdac_bus * bus = container_of ( work , struct hdac_bus , unsol_work ) ;
struct hdac_device * codec ;
struct hdac_driver * drv ;
unsigned int rp , caddr , res ;
while ( bus - > unsol_rp ! = bus - > unsol_wp ) {
rp = ( bus - > unsol_rp + 1 ) % HDA_UNSOL_QUEUE_SIZE ;
bus - > unsol_rp = rp ;
rp < < = 1 ;
res = bus - > unsol_queue [ rp ] ;
caddr = bus - > unsol_queue [ rp + 1 ] ;
if ( ! ( caddr & ( 1 < < 4 ) ) ) /* no unsolicited event? */
continue ;
codec = bus - > caddr_tbl [ caddr & 0x0f ] ;
if ( ! codec | | ! codec - > dev . driver )
continue ;
drv = drv_to_hdac_driver ( codec - > dev . driver ) ;
if ( drv - > unsol_event )
drv - > unsol_event ( codec , res ) ;
}
}
2018-12-12 00:30:27 +03:00
EXPORT_SYMBOL_GPL ( snd_hdac_bus_process_unsol_events ) ;
2015-03-03 01:22:59 +03:00
2015-10-28 14:26:48 +03:00
/**
* snd_hdac_bus_add_device - Add a codec to bus
* @ bus : HDA core bus
* @ codec : HDA core device to add
*
* Adds the given codec to the list in the bus . The caddr_tbl array
* and codec_powered bits are updated , as well .
* Returns zero if success , or a negative error code .
*/
2015-03-03 01:22:59 +03:00
int snd_hdac_bus_add_device ( struct hdac_bus * bus , struct hdac_device * codec )
{
if ( bus - > caddr_tbl [ codec - > addr ] ) {
dev_err ( bus - > dev , " address 0x%x is already occupied \n " ,
codec - > addr ) ;
return - EBUSY ;
}
list_add_tail ( & codec - > list , & bus - > codec_list ) ;
bus - > caddr_tbl [ codec - > addr ] = codec ;
set_bit ( codec - > addr , & bus - > codec_powered ) ;
bus - > num_codecs + + ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( snd_hdac_bus_add_device ) ;
2015-10-28 14:26:48 +03:00
/**
* snd_hdac_bus_remove_device - Remove a codec from bus
* @ bus : HDA core bus
* @ codec : HDA core device to remove
*/
2015-03-03 01:22:59 +03:00
void snd_hdac_bus_remove_device ( struct hdac_bus * bus ,
struct hdac_device * codec )
{
WARN_ON ( bus ! = codec - > bus ) ;
if ( list_empty ( & codec - > list ) )
return ;
list_del_init ( & codec - > list ) ;
bus - > caddr_tbl [ codec - > addr ] = NULL ;
clear_bit ( codec - > addr , & bus - > codec_powered ) ;
bus - > num_codecs - - ;
2017-06-19 18:49:48 +03:00
flush_work ( & bus - > unsol_work ) ;
2015-03-03 01:22:59 +03:00
}
EXPORT_SYMBOL_GPL ( snd_hdac_bus_remove_device ) ;