@ -1,39 +1,482 @@
/*
* VIA south bridges sound support
*
* Copyright ( c ) 2022 - 2023 BALATON Zoltan
*
* This work is licensed under the GNU GPL license version 2 or later .
*/
/*
* TODO : This is entirely boiler plate just registering empty PCI devices
* with the right ID guests expect , functionality should be added here .
* TODO : This is only a basic implementation of one audio playback channel
* more functionality should be added here .
*/
# include "qemu/osdep.h"
# include "qemu/log.h"
# include "hw/isa/vt82c686.h"
# include "hw/pci/pci_device.h"
# include "ac97.h"
# include "trace.h"
# define CLEN_IS_EOL(x) ((x)->clen & BIT(31))
# define CLEN_IS_FLAG(x) ((x)->clen & BIT(30))
# define CLEN_IS_STOP(x) ((x)->clen & BIT(29))
# define CLEN_LEN(x) ((x)->clen & 0xffffff)
# define STAT_ACTIVE BIT(7)
# define STAT_PAUSED BIT(6)
# define STAT_TRIG BIT(3)
# define STAT_STOP BIT(2)
# define STAT_EOL BIT(1)
# define STAT_FLAG BIT(0)
# define CNTL_START BIT(7)
# define CNTL_TERM BIT(6)
# define CNTL_PAUSE BIT(3)
static void open_voice_out ( ViaAC97State * s ) ;
static uint16_t codec_rates [ ] = { 8000 , 11025 , 16000 , 22050 , 32000 , 44100 ,
48000 } ;
# define CODEC_REG(s, o) ((s)->codec_regs[(o) / 2])
# define CODEC_VOL(vol, mask) ((255 * ((vol) & mask)) / mask)
static void codec_volume_set_out ( ViaAC97State * s )
{
int lvol , rvol , mute ;
lvol = 255 - CODEC_VOL ( CODEC_REG ( s , AC97_Master_Volume_Mute ) > > 8 , 0x1f ) ;
lvol * = 255 - CODEC_VOL ( CODEC_REG ( s , AC97_PCM_Out_Volume_Mute ) > > 8 , 0x1f ) ;
lvol / = 255 ;
rvol = 255 - CODEC_VOL ( CODEC_REG ( s , AC97_Master_Volume_Mute ) , 0x1f ) ;
rvol * = 255 - CODEC_VOL ( CODEC_REG ( s , AC97_PCM_Out_Volume_Mute ) , 0x1f ) ;
rvol / = 255 ;
mute = CODEC_REG ( s , AC97_Master_Volume_Mute ) > > MUTE_SHIFT ;
mute | = CODEC_REG ( s , AC97_PCM_Out_Volume_Mute ) > > MUTE_SHIFT ;
AUD_set_volume_out ( s - > vo , mute , lvol , rvol ) ;
}
static void codec_reset ( ViaAC97State * s )
{
memset ( s - > codec_regs , 0 , sizeof ( s - > codec_regs ) ) ;
CODEC_REG ( s , AC97_Reset ) = 0x6a90 ;
CODEC_REG ( s , AC97_Master_Volume_Mute ) = 0x8000 ;
CODEC_REG ( s , AC97_Headphone_Volume_Mute ) = 0x8000 ;
CODEC_REG ( s , AC97_Master_Volume_Mono_Mute ) = 0x8000 ;
CODEC_REG ( s , AC97_Phone_Volume_Mute ) = 0x8008 ;
CODEC_REG ( s , AC97_Mic_Volume_Mute ) = 0x8008 ;
CODEC_REG ( s , AC97_Line_In_Volume_Mute ) = 0x8808 ;
CODEC_REG ( s , AC97_CD_Volume_Mute ) = 0x8808 ;
CODEC_REG ( s , AC97_Video_Volume_Mute ) = 0x8808 ;
CODEC_REG ( s , AC97_Aux_Volume_Mute ) = 0x8808 ;
CODEC_REG ( s , AC97_PCM_Out_Volume_Mute ) = 0x8808 ;
CODEC_REG ( s , AC97_Record_Gain_Mute ) = 0x8000 ;
CODEC_REG ( s , AC97_Powerdown_Ctrl_Stat ) = 0x000f ;
CODEC_REG ( s , AC97_Extended_Audio_ID ) = 0x0a05 ;
CODEC_REG ( s , AC97_Extended_Audio_Ctrl_Stat ) = 0x0400 ;
CODEC_REG ( s , AC97_PCM_Front_DAC_Rate ) = 48000 ;
CODEC_REG ( s , AC97_PCM_LR_ADC_Rate ) = 48000 ;
/* Sigmatel 9766 (STAC9766) */
CODEC_REG ( s , AC97_Vendor_ID1 ) = 0x8384 ;
CODEC_REG ( s , AC97_Vendor_ID2 ) = 0x7666 ;
}
static uint16_t codec_read ( ViaAC97State * s , uint8_t addr )
{
return CODEC_REG ( s , addr ) ;
}
static void codec_write ( ViaAC97State * s , uint8_t addr , uint16_t val )
{
trace_via_ac97_codec_write ( addr , val ) ;
switch ( addr ) {
case AC97_Reset :
codec_reset ( s ) ;
return ;
case AC97_Master_Volume_Mute :
case AC97_PCM_Out_Volume_Mute :
if ( addr = = AC97_Master_Volume_Mute ) {
if ( val & BIT ( 13 ) ) {
val | = 0x1f00 ;
}
if ( val & BIT ( 5 ) ) {
val | = 0x1f ;
}
}
CODEC_REG ( s , addr ) = val & 0x9f1f ;
codec_volume_set_out ( s ) ;
return ;
case AC97_Extended_Audio_Ctrl_Stat :
CODEC_REG ( s , addr ) & = ~ EACS_VRA ;
CODEC_REG ( s , addr ) | = val & EACS_VRA ;
if ( ! ( val & EACS_VRA ) ) {
CODEC_REG ( s , AC97_PCM_Front_DAC_Rate ) = 48000 ;
CODEC_REG ( s , AC97_PCM_LR_ADC_Rate ) = 48000 ;
open_voice_out ( s ) ;
}
return ;
case AC97_PCM_Front_DAC_Rate :
case AC97_PCM_LR_ADC_Rate :
if ( CODEC_REG ( s , AC97_Extended_Audio_Ctrl_Stat ) & EACS_VRA ) {
int i ;
uint16_t rate = val ;
for ( i = 0 ; i < ARRAY_SIZE ( codec_rates ) - 1 ; i + + ) {
if ( rate < codec_rates [ i ] +
( codec_rates [ i + 1 ] - codec_rates [ i ] ) / 2 ) {
rate = codec_rates [ i ] ;
break ;
}
}
if ( rate > 48000 ) {
rate = 48000 ;
}
CODEC_REG ( s , addr ) = rate ;
open_voice_out ( s ) ;
}
return ;
case AC97_Powerdown_Ctrl_Stat :
CODEC_REG ( s , addr ) = ( val & 0xff00 ) | ( CODEC_REG ( s , addr ) & 0xff ) ;
return ;
case AC97_Extended_Audio_ID :
case AC97_Vendor_ID1 :
case AC97_Vendor_ID2 :
/* Read only registers */
return ;
default :
qemu_log_mask ( LOG_UNIMP ,
" via-ac97: Unimplemented codec register 0x%x \n " , addr ) ;
CODEC_REG ( s , addr ) = val ;
}
}
static void fetch_sgd ( ViaAC97SGDChannel * c , PCIDevice * d )
{
uint32_t b [ 2 ] ;
if ( c - > curr < c - > base ) {
c - > curr = c - > base ;
}
if ( unlikely ( pci_dma_read ( d , c - > curr , b , sizeof ( b ) ) ! = MEMTX_OK ) ) {
qemu_log_mask ( LOG_GUEST_ERROR ,
" via-ac97: DMA error reading SGD table \n " ) ;
return ;
}
c - > addr = le32_to_cpu ( b [ 0 ] ) ;
c - > clen = le32_to_cpu ( b [ 1 ] ) ;
trace_via_ac97_sgd_fetch ( c - > curr , c - > addr , CLEN_IS_STOP ( c ) ? ' S ' : ' - ' ,
CLEN_IS_EOL ( c ) ? ' E ' : ' - ' ,
CLEN_IS_FLAG ( c ) ? ' F ' : ' - ' , CLEN_LEN ( c ) ) ;
}
static void out_cb ( void * opaque , int avail )
{
ViaAC97State * s = opaque ;
ViaAC97SGDChannel * c = & s - > aur ;
int temp , to_copy , copied ;
bool stop = false ;
uint8_t tmpbuf [ 4096 ] ;
if ( c - > stat & STAT_PAUSED ) {
return ;
}
c - > stat | = STAT_ACTIVE ;
while ( avail & & ! stop ) {
if ( ! c - > clen ) {
fetch_sgd ( c , & s - > dev ) ;
}
temp = MIN ( CLEN_LEN ( c ) , avail ) ;
while ( temp ) {
to_copy = MIN ( temp , sizeof ( tmpbuf ) ) ;
pci_dma_read ( & s - > dev , c - > addr , tmpbuf , to_copy ) ;
copied = AUD_write ( s - > vo , tmpbuf , to_copy ) ;
if ( ! copied ) {
stop = true ;
break ;
}
temp - = copied ;
avail - = copied ;
c - > addr + = copied ;
c - > clen - = copied ;
}
if ( CLEN_LEN ( c ) = = 0 ) {
c - > curr + = 8 ;
if ( CLEN_IS_EOL ( c ) ) {
c - > stat | = STAT_EOL ;
if ( c - > type & CNTL_START ) {
c - > curr = c - > base ;
c - > stat | = STAT_PAUSED ;
} else {
c - > stat & = ~ STAT_ACTIVE ;
AUD_set_active_out ( s - > vo , 0 ) ;
}
if ( c - > type & STAT_EOL ) {
pci_set_irq ( & s - > dev , 1 ) ;
}
}
if ( CLEN_IS_FLAG ( c ) ) {
c - > stat | = STAT_FLAG ;
c - > stat | = STAT_PAUSED ;
if ( c - > type & STAT_FLAG ) {
pci_set_irq ( & s - > dev , 1 ) ;
}
}
if ( CLEN_IS_STOP ( c ) ) {
c - > stat | = STAT_STOP ;
c - > stat | = STAT_PAUSED ;
}
c - > clen = 0 ;
stop = true ;
}
}
}
static void open_voice_out ( ViaAC97State * s )
{
struct audsettings as = {
. freq = CODEC_REG ( s , AC97_PCM_Front_DAC_Rate ) ,
. nchannels = s - > aur . type & BIT ( 4 ) ? 2 : 1 ,
. fmt = s - > aur . type & BIT ( 5 ) ? AUDIO_FORMAT_S16 : AUDIO_FORMAT_S8 ,
. endianness = 0 ,
} ;
s - > vo = AUD_open_out ( & s - > card , s - > vo , " via-ac97.out " , s , out_cb , & as ) ;
}
static uint64_t sgd_read ( void * opaque , hwaddr addr , unsigned size )
{
ViaAC97State * s = opaque ;
uint64_t val = 0 ;
switch ( addr ) {
case 0 :
val = s - > aur . stat ;
if ( s - > aur . type & CNTL_START ) {
val | = STAT_TRIG ;
}
break ;
case 1 :
val = s - > aur . stat & STAT_PAUSED ? BIT ( 3 ) : 0 ;
break ;
case 2 :
val = s - > aur . type ;
break ;
case 4 :
val = s - > aur . curr ;
break ;
case 0xc :
val = CLEN_LEN ( & s - > aur ) ;
break ;
case 0x10 :
/* silence unimplemented log message that happens at every IRQ */
break ;
case 0x80 :
val = s - > ac97_cmd ;
break ;
case 0x84 :
val = s - > aur . stat & STAT_FLAG ;
if ( s - > aur . stat & STAT_EOL ) {
val | = BIT ( 4 ) ;
}
if ( s - > aur . stat & STAT_STOP ) {
val | = BIT ( 8 ) ;
}
if ( s - > aur . stat & STAT_ACTIVE ) {
val | = BIT ( 12 ) ;
}
break ;
default :
qemu_log_mask ( LOG_UNIMP , " via-ac97: Unimplemented register read 0x% "
HWADDR_PRIx " \n " , addr ) ;
}
trace_via_ac97_sgd_read ( addr , size , val ) ;
return val ;
}
static void sgd_write ( void * opaque , hwaddr addr , uint64_t val , unsigned size )
{
ViaAC97State * s = opaque ;
trace_via_ac97_sgd_write ( addr , size , val ) ;
switch ( addr ) {
case 0 :
if ( val & STAT_STOP ) {
s - > aur . stat & = ~ STAT_PAUSED ;
}
if ( val & STAT_EOL ) {
s - > aur . stat & = ~ ( STAT_EOL | STAT_PAUSED ) ;
if ( s - > aur . type & STAT_EOL ) {
pci_set_irq ( & s - > dev , 0 ) ;
}
}
if ( val & STAT_FLAG ) {
s - > aur . stat & = ~ ( STAT_FLAG | STAT_PAUSED ) ;
if ( s - > aur . type & STAT_FLAG ) {
pci_set_irq ( & s - > dev , 0 ) ;
}
}
break ;
case 1 :
if ( val & CNTL_START ) {
AUD_set_active_out ( s - > vo , 1 ) ;
s - > aur . stat = STAT_ACTIVE ;
}
if ( val & CNTL_TERM ) {
AUD_set_active_out ( s - > vo , 0 ) ;
s - > aur . stat & = ~ ( STAT_ACTIVE | STAT_PAUSED ) ;
s - > aur . clen = 0 ;
}
if ( val & CNTL_PAUSE ) {
AUD_set_active_out ( s - > vo , 0 ) ;
s - > aur . stat & = ~ STAT_ACTIVE ;
s - > aur . stat | = STAT_PAUSED ;
} else if ( ! ( val & CNTL_PAUSE ) & & ( s - > aur . stat & STAT_PAUSED ) ) {
AUD_set_active_out ( s - > vo , 1 ) ;
s - > aur . stat | = STAT_ACTIVE ;
s - > aur . stat & = ~ STAT_PAUSED ;
}
break ;
case 2 :
{
uint32_t oldval = s - > aur . type ;
s - > aur . type = val ;
if ( ( oldval & 0x30 ) ! = ( val & 0x30 ) ) {
open_voice_out ( s ) ;
}
break ;
}
case 4 :
s - > aur . base = val & ~ 1ULL ;
s - > aur . curr = s - > aur . base ;
break ;
case 0x80 :
if ( val > > 30 ) {
/* we only have primary codec */
break ;
}
if ( val & BIT ( 23 ) ) { /* read reg */
s - > ac97_cmd = val & 0xc0ff0000ULL ;
s - > ac97_cmd | = codec_read ( s , ( val > > 16 ) & 0x7f ) ;
s - > ac97_cmd | = BIT ( 25 ) ; /* data valid */
} else {
s - > ac97_cmd = val & 0xc0ffffffULL ;
codec_write ( s , ( val > > 16 ) & 0x7f , val ) ;
}
break ;
case 0xc :
case 0x84 :
/* Read only */
break ;
default :
qemu_log_mask ( LOG_UNIMP , " via-ac97: Unimplemented register write 0x% "
HWADDR_PRIx " \n " , addr ) ;
}
}
static const MemoryRegionOps sgd_ops = {
. read = sgd_read ,
. write = sgd_write ,
. endianness = DEVICE_LITTLE_ENDIAN ,
} ;
static uint64_t fm_read ( void * opaque , hwaddr addr , unsigned size )
{
qemu_log_mask ( LOG_UNIMP , " %s: 0x% " HWADDR_PRIx " %d \n " , __func__ , addr , size ) ;
return 0 ;
}
static void fm_write ( void * opaque , hwaddr addr , uint64_t val , unsigned size )
{
qemu_log_mask ( LOG_UNIMP , " %s: 0x% " HWADDR_PRIx " %d <= 0x% " PRIX64 " \n " ,
__func__ , addr , size , val ) ;
}
static const MemoryRegionOps fm_ops = {
. read = fm_read ,
. write = fm_write ,
. endianness = DEVICE_LITTLE_ENDIAN ,
} ;
static uint64_t midi_read ( void * opaque , hwaddr addr , unsigned size )
{
qemu_log_mask ( LOG_UNIMP , " %s: 0x% " HWADDR_PRIx " %d \n " , __func__ , addr , size ) ;
return 0 ;
}
static void midi_write ( void * opaque , hwaddr addr , uint64_t val , unsigned size )
{
qemu_log_mask ( LOG_UNIMP , " %s: 0x% " HWADDR_PRIx " %d <= 0x% " PRIX64 " \n " ,
__func__ , addr , size , val ) ;
}
static const MemoryRegionOps midi_ops = {
. read = midi_read ,
. write = midi_write ,
. endianness = DEVICE_LITTLE_ENDIAN ,
} ;
static void via_ac97_reset ( DeviceState * dev )
{
ViaAC97State * s = VIA_AC97 ( dev ) ;
codec_reset ( s ) ;
}
static void via_ac97_realize ( PCIDevice * pci_dev , Error * * errp )
{
pci_set_word ( pci_dev - > config + PCI_COMMAND ,
PCI_COMMAND_INVALIDATE | PCI_COMMAND_PARITY ) ;
ViaAC97State * s = VIA_AC97 ( pci_dev ) ;
Object * o = OBJECT ( s ) ;
/*
* Command register Bus Master bit is documented to be fixed at 0 but it ' s
* needed for PCI DMA to work in QEMU . The pegasos2 firmware writes 0 here
* and the AmigaOS driver writes 1 only enabling IO bit which works on
* real hardware . So set it here and fix it to 1 to allow DMA .
*/
pci_set_word ( pci_dev - > config + PCI_COMMAND , PCI_COMMAND_MASTER ) ;
pci_set_word ( pci_dev - > wmask + PCI_COMMAND , PCI_COMMAND_IO ) ;
pci_set_word ( pci_dev - > config + PCI_STATUS ,
PCI_STATUS_CAP_LIST | PCI_STATUS_DEVSEL_MEDIUM ) ;
pci_set_long ( pci_dev - > config + PCI_INTERRUPT_PIN , 0x03 ) ;
pci_set_byte ( pci_dev - > config + 0x40 , 1 ) ; /* codec ready */
memory_region_init_io ( & s - > sgd , o , & sgd_ops , s , " via-ac97.sgd " , 256 ) ;
pci_register_bar ( pci_dev , 0 , PCI_BASE_ADDRESS_SPACE_IO , & s - > sgd ) ;
memory_region_init_io ( & s - > fm , o , & fm_ops , s , " via-ac97.fm " , 4 ) ;
pci_register_bar ( pci_dev , 1 , PCI_BASE_ADDRESS_SPACE_IO , & s - > fm ) ;
memory_region_init_io ( & s - > midi , o , & midi_ops , s , " via-ac97.midi " , 4 ) ;
pci_register_bar ( pci_dev , 2 , PCI_BASE_ADDRESS_SPACE_IO , & s - > midi ) ;
AUD_register_card ( " via-ac97 " , & s - > card ) ;
}
static void via_ac97_exit ( PCIDevice * dev )
{
ViaAC97State * s = VIA_AC97 ( dev ) ;
AUD_close_out ( & s - > card , s - > vo ) ;
AUD_remove_card ( & s - > card ) ;
}
static Property via_ac97_properties [ ] = {
DEFINE_AUDIO_PROPERTIES ( ViaAC97State , card ) ,
DEFINE_PROP_END_OF_LIST ( ) ,
} ;
static void via_ac97_class_init ( ObjectClass * klass , void * data )
{
DeviceClass * dc = DEVICE_CLASS ( klass ) ;
PCIDeviceClass * k = PCI_DEVICE_CLASS ( klass ) ;
k - > realize = via_ac97_realize ;
k - > exit = via_ac97_exit ;
k - > vendor_id = PCI_VENDOR_ID_VIA ;
k - > device_id = PCI_DEVICE_ID_VIA_AC97 ;
k - > revision = 0x50 ;
k - > class_id = PCI_CLASS_MULTIMEDIA_AUDIO ;
device_class_set_props ( dc , via_ac97_properties ) ;
set_bit ( DEVICE_CATEGORY_SOUND , dc - > categories ) ;
dc - > desc = " VIA AC97 " ;
dc - > reset = via_ac97_reset ;
/* Reason: Part of a south bridge chip */
dc - > user_creatable = false ;
}
@ -41,7 +484,7 @@ static void via_ac97_class_init(ObjectClass *klass, void *data)
static const TypeInfo via_ac97_info = {
. name = TYPE_VIA_AC97 ,
. parent = TYPE_PCI_DEVICE ,
. instance_size = sizeof ( PCIDevic e) ,
. instance_size = sizeof ( ViaAC97Stat e) ,
. class_init = via_ac97_class_init ,
. interfaces = ( InterfaceInfo [ ] ) {
{ INTERFACE_CONVENTIONAL_PCI_DEVICE } ,