2007-12-23 19:50:57 +01:00
/*
* C - Media CMI8788 driver for C - Media ' s reference design and for the X - Meridian
*
* Copyright ( c ) Clemens Ladisch < clemens @ ladisch . de >
*
*
* This driver is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License , version 2.
*
* This driver is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this driver ; if not , write to the Free Software
* Foundation , Inc . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA
*/
/*
* SPI 0 - > 1 st AK4396 ( front )
2008-01-14 08:55:03 +01:00
* SPI 1 - > 2 nd AK4396 ( surround )
2007-12-23 19:50:57 +01:00
* SPI 2 - > 3 rd AK4396 ( center / LFE )
* SPI 3 - > WM8785
2008-01-14 08:55:03 +01:00
* SPI 4 - > 4 th AK4396 ( back )
2007-12-23 19:50:57 +01:00
*
* GPIO 0 - > DFS0 of AK5385
* GPIO 1 - > DFS1 of AK5385
*/
2008-08-29 13:08:34 +02:00
# include <linux/delay.h>
2008-02-22 18:40:56 +01:00
# include <linux/mutex.h>
2007-12-23 19:50:57 +01:00
# include <linux/pci.h>
2008-02-22 18:40:56 +01:00
# include <sound/ac97_codec.h>
2008-01-16 08:32:08 +01:00
# include <sound/control.h>
2007-12-23 19:50:57 +01:00
# include <sound/core.h>
# include <sound/initval.h>
# include <sound/pcm.h>
# include <sound/pcm_params.h>
# include <sound/tlv.h>
# include "oxygen.h"
2008-01-25 08:41:52 +01:00
# include "ak4396.h"
2008-03-19 08:14:01 +01:00
# include "wm8785.h"
2007-12-23 19:50:57 +01:00
MODULE_AUTHOR ( " Clemens Ladisch <clemens@ladisch.de> " ) ;
MODULE_DESCRIPTION ( " C-Media CMI8788 driver " ) ;
2008-05-13 09:18:27 +02:00
MODULE_LICENSE ( " GPL v2 " ) ;
2007-12-23 19:50:57 +01:00
MODULE_SUPPORTED_DEVICE ( " {{C-Media,CMI8788}} " ) ;
static int index [ SNDRV_CARDS ] = SNDRV_DEFAULT_IDX ;
static char * id [ SNDRV_CARDS ] = SNDRV_DEFAULT_STR ;
static int enable [ SNDRV_CARDS ] = SNDRV_DEFAULT_ENABLE_PNP ;
module_param_array ( index , int , NULL , 0444 ) ;
MODULE_PARM_DESC ( index , " card index " ) ;
module_param_array ( id , charp , NULL , 0444 ) ;
MODULE_PARM_DESC ( id , " ID string " ) ;
module_param_array ( enable , bool , NULL , 0444 ) ;
MODULE_PARM_DESC ( enable , " enable card " ) ;
2008-09-22 08:57:24 +02:00
enum {
MODEL_CMEDIA_REF , /* C-Media's reference design */
MODEL_MERIDIAN , /* AuzenTech X-Meridian */
} ;
2007-12-23 19:50:57 +01:00
static struct pci_device_id oxygen_ids [ ] __devinitdata = {
2008-09-22 08:57:24 +02:00
{ OXYGEN_PCI_SUBID ( 0x10b0 , 0x0216 ) , . driver_data = MODEL_CMEDIA_REF } ,
{ OXYGEN_PCI_SUBID ( 0x10b0 , 0x0218 ) , . driver_data = MODEL_CMEDIA_REF } ,
{ OXYGEN_PCI_SUBID ( 0x10b0 , 0x0219 ) , . driver_data = MODEL_CMEDIA_REF } ,
{ OXYGEN_PCI_SUBID ( 0x13f6 , 0x0001 ) , . driver_data = MODEL_CMEDIA_REF } ,
{ OXYGEN_PCI_SUBID ( 0x13f6 , 0x0010 ) , . driver_data = MODEL_CMEDIA_REF } ,
{ OXYGEN_PCI_SUBID ( 0x13f6 , 0x8788 ) , . driver_data = MODEL_CMEDIA_REF } ,
{ OXYGEN_PCI_SUBID ( 0x147a , 0xa017 ) , . driver_data = MODEL_CMEDIA_REF } ,
{ OXYGEN_PCI_SUBID ( 0x1a58 , 0x0910 ) , . driver_data = MODEL_CMEDIA_REF } ,
{ OXYGEN_PCI_SUBID ( 0x415a , 0x5431 ) , . driver_data = MODEL_MERIDIAN } ,
{ OXYGEN_PCI_SUBID ( 0x7284 , 0x9761 ) , . driver_data = MODEL_CMEDIA_REF } ,
2007-12-23 19:50:57 +01:00
{ }
} ;
MODULE_DEVICE_TABLE ( pci , oxygen_ids ) ;
2008-01-21 08:50:19 +01:00
# define GPIO_AK5385_DFS_MASK 0x0003
# define GPIO_AK5385_DFS_NORMAL 0x0000
# define GPIO_AK5385_DFS_DOUBLE 0x0001
# define GPIO_AK5385_DFS_QUAD 0x0002
2008-01-21 08:51:55 +01:00
struct generic_data {
u8 ak4396_ctl2 ;
2008-05-13 09:20:51 +02:00
u16 saved_wm8785_registers [ 2 ] ;
2008-01-21 08:51:55 +01:00
} ;
2007-12-23 19:50:57 +01:00
static void ak4396_write ( struct oxygen * chip , unsigned int codec ,
u8 reg , u8 value )
{
/* maps ALSA channel pair number to SPI output */
static const u8 codec_spi_map [ 4 ] = {
2008-01-14 08:55:03 +01:00
0 , 1 , 2 , 4
2007-12-23 19:50:57 +01:00
} ;
2008-01-18 09:17:53 +01:00
oxygen_write_spi ( chip , OXYGEN_SPI_TRIGGER |
2007-12-23 19:50:57 +01:00
OXYGEN_SPI_DATA_LENGTH_2 |
2008-01-30 08:38:30 +01:00
OXYGEN_SPI_CLOCK_160 |
2007-12-23 19:50:57 +01:00
( codec_spi_map [ codec ] < < OXYGEN_SPI_CODEC_SHIFT ) |
2008-01-18 09:17:53 +01:00
OXYGEN_SPI_CEN_LATCH_CLOCK_HI ,
2007-12-23 19:50:57 +01:00
AK4396_WRITE | ( reg < < 8 ) | value ) ;
}
static void wm8785_write ( struct oxygen * chip , u8 reg , unsigned int value )
{
2008-05-13 09:20:51 +02:00
struct generic_data * data = chip - > model_data ;
2008-01-18 09:17:53 +01:00
oxygen_write_spi ( chip , OXYGEN_SPI_TRIGGER |
2007-12-23 19:50:57 +01:00
OXYGEN_SPI_DATA_LENGTH_2 |
2008-01-30 08:38:30 +01:00
OXYGEN_SPI_CLOCK_160 |
2008-01-18 09:17:53 +01:00
( 3 < < OXYGEN_SPI_CODEC_SHIFT ) |
OXYGEN_SPI_CEN_LATCH_CLOCK_LO ,
2007-12-23 19:50:57 +01:00
( reg < < 9 ) | value ) ;
2008-05-13 09:20:51 +02:00
if ( reg < ARRAY_SIZE ( data - > saved_wm8785_registers ) )
data - > saved_wm8785_registers [ reg ] = value ;
2007-12-23 19:50:57 +01:00
}
2008-05-13 09:21:48 +02:00
static void update_ak4396_volume ( struct oxygen * chip )
{
unsigned int i ;
for ( i = 0 ; i < 4 ; + + i ) {
ak4396_write ( chip , i ,
AK4396_LCH_ATT , chip - > dac_volume [ i * 2 ] ) ;
ak4396_write ( chip , i ,
AK4396_RCH_ATT , chip - > dac_volume [ i * 2 + 1 ] ) ;
}
}
2008-05-13 09:22:43 +02:00
static void ak4396_registers_init ( struct oxygen * chip )
2007-12-23 19:50:57 +01:00
{
2008-01-21 08:51:55 +01:00
struct generic_data * data = chip - > model_data ;
2007-12-23 19:50:57 +01:00
unsigned int i ;
for ( i = 0 ; i < 4 ; + + i ) {
2008-01-21 08:50:19 +01:00
ak4396_write ( chip , i ,
AK4396_CONTROL_1 , AK4396_DIF_24_MSB | AK4396_RSTN ) ;
ak4396_write ( chip , i ,
2008-01-21 08:51:55 +01:00
AK4396_CONTROL_2 , data - > ak4396_ctl2 ) ;
2008-01-21 08:50:19 +01:00
ak4396_write ( chip , i ,
AK4396_CONTROL_3 , AK4396_PCM ) ;
2007-12-23 19:50:57 +01:00
}
2008-05-13 09:21:48 +02:00
update_ak4396_volume ( chip ) ;
2008-05-13 09:22:43 +02:00
}
static void ak4396_init ( struct oxygen * chip )
{
struct generic_data * data = chip - > model_data ;
data - > ak4396_ctl2 = AK4396_SMUTE | AK4396_DEM_OFF | AK4396_DFS_NORMAL ;
ak4396_registers_init ( chip ) ;
2007-12-23 19:50:57 +01:00
snd_component_add ( chip - > card , " AK4396 " ) ;
}
static void ak5385_init ( struct oxygen * chip )
{
2008-01-21 08:50:19 +01:00
oxygen_set_bits16 ( chip , OXYGEN_GPIO_CONTROL , GPIO_AK5385_DFS_MASK ) ;
oxygen_clear_bits16 ( chip , OXYGEN_GPIO_DATA , GPIO_AK5385_DFS_MASK ) ;
2007-12-23 19:50:57 +01:00
snd_component_add ( chip - > card , " AK5385 " ) ;
}
2008-05-13 09:22:43 +02:00
static void wm8785_registers_init ( struct oxygen * chip )
2007-12-23 19:50:57 +01:00
{
2008-05-13 09:20:51 +02:00
struct generic_data * data = chip - > model_data ;
2008-01-21 08:50:19 +01:00
wm8785_write ( chip , WM8785_R7 , 0 ) ;
2008-05-13 09:20:51 +02:00
wm8785_write ( chip , WM8785_R0 , data - > saved_wm8785_registers [ 0 ] ) ;
wm8785_write ( chip , WM8785_R1 , data - > saved_wm8785_registers [ 1 ] ) ;
2008-05-13 09:22:43 +02:00
}
2008-05-13 09:20:51 +02:00
2008-05-13 09:22:43 +02:00
static void wm8785_init ( struct oxygen * chip )
{
struct generic_data * data = chip - > model_data ;
data - > saved_wm8785_registers [ 0 ] = WM8785_MCR_SLAVE |
WM8785_OSR_SINGLE | WM8785_FORMAT_LJUST ;
data - > saved_wm8785_registers [ 1 ] = WM8785_WL_24 ;
wm8785_registers_init ( chip ) ;
2007-12-23 19:50:57 +01:00
snd_component_add ( chip - > card , " WM8785 " ) ;
}
static void generic_init ( struct oxygen * chip )
{
ak4396_init ( chip ) ;
wm8785_init ( chip ) ;
}
static void meridian_init ( struct oxygen * chip )
{
ak4396_init ( chip ) ;
ak5385_init ( chip ) ;
}
static void generic_cleanup ( struct oxygen * chip )
{
}
2008-05-13 09:24:39 +02:00
static void generic_resume ( struct oxygen * chip )
{
ak4396_registers_init ( chip ) ;
wm8785_registers_init ( chip ) ;
}
2008-09-22 09:05:29 +02:00
static void meridian_resume ( struct oxygen * chip )
{
ak4396_registers_init ( chip ) ;
}
2007-12-23 19:50:57 +01:00
static void set_ak4396_params ( struct oxygen * chip ,
struct snd_pcm_hw_params * params )
{
2008-01-21 08:51:55 +01:00
struct generic_data * data = chip - > model_data ;
2007-12-23 19:50:57 +01:00
unsigned int i ;
u8 value ;
2008-01-21 08:51:55 +01:00
value = data - > ak4396_ctl2 & ~ AK4396_DFS_MASK ;
2007-12-23 19:50:57 +01:00
if ( params_rate ( params ) < = 54000 )
value | = AK4396_DFS_NORMAL ;
2008-01-28 08:32:58 +01:00
else if ( params_rate ( params ) < = 108000 )
2007-12-23 19:50:57 +01:00
value | = AK4396_DFS_DOUBLE ;
else
value | = AK4396_DFS_QUAD ;
2008-01-21 08:51:55 +01:00
data - > ak4396_ctl2 = value ;
2008-08-29 13:08:34 +02:00
msleep ( 1 ) ; /* wait for the new MCLK to become stable */
2007-12-23 19:50:57 +01:00
for ( i = 0 ; i < 4 ; + + i ) {
2008-01-21 08:50:19 +01:00
ak4396_write ( chip , i ,
AK4396_CONTROL_1 , AK4396_DIF_24_MSB ) ;
ak4396_write ( chip , i ,
AK4396_CONTROL_2 , value ) ;
ak4396_write ( chip , i ,
AK4396_CONTROL_1 , AK4396_DIF_24_MSB | AK4396_RSTN ) ;
2007-12-23 19:50:57 +01:00
}
}
static void update_ak4396_mute ( struct oxygen * chip )
{
2008-01-21 08:51:55 +01:00
struct generic_data * data = chip - > model_data ;
2007-12-23 19:50:57 +01:00
unsigned int i ;
u8 value ;
2008-01-21 08:51:55 +01:00
value = data - > ak4396_ctl2 & ~ AK4396_SMUTE ;
2007-12-23 19:50:57 +01:00
if ( chip - > dac_mute )
value | = AK4396_SMUTE ;
2008-01-21 08:51:55 +01:00
data - > ak4396_ctl2 = value ;
2007-12-23 19:50:57 +01:00
for ( i = 0 ; i < 4 ; + + i )
2008-01-21 08:50:19 +01:00
ak4396_write ( chip , i , AK4396_CONTROL_2 , value ) ;
2007-12-23 19:50:57 +01:00
}
static void set_wm8785_params ( struct oxygen * chip ,
struct snd_pcm_hw_params * params )
{
unsigned int value ;
2008-01-21 08:50:19 +01:00
wm8785_write ( chip , WM8785_R7 , 0 ) ;
2007-12-23 19:50:57 +01:00
2008-01-21 08:50:19 +01:00
value = WM8785_MCR_SLAVE | WM8785_FORMAT_LJUST ;
2008-01-21 08:50:51 +01:00
if ( params_rate ( params ) < = 48000 )
value | = WM8785_OSR_SINGLE ;
else if ( params_rate ( params ) < = 96000 )
2007-12-23 19:50:57 +01:00
value | = WM8785_OSR_DOUBLE ;
else
2008-01-21 08:50:51 +01:00
value | = WM8785_OSR_QUAD ;
2008-01-21 08:50:19 +01:00
wm8785_write ( chip , WM8785_R0 , value ) ;
2007-12-23 19:50:57 +01:00
if ( snd_pcm_format_width ( params_format ( params ) ) < = 16 )
value = WM8785_WL_16 ;
else
value = WM8785_WL_24 ;
2008-01-21 08:50:19 +01:00
wm8785_write ( chip , WM8785_R1 , value ) ;
2007-12-23 19:50:57 +01:00
}
static void set_ak5385_params ( struct oxygen * chip ,
struct snd_pcm_hw_params * params )
{
unsigned int value ;
if ( params_rate ( params ) < = 54000 )
2008-01-21 08:50:19 +01:00
value = GPIO_AK5385_DFS_NORMAL ;
2007-12-23 19:50:57 +01:00
else if ( params_rate ( params ) < = 108000 )
2008-01-21 08:50:19 +01:00
value = GPIO_AK5385_DFS_DOUBLE ;
2007-12-23 19:50:57 +01:00
else
2008-01-21 08:50:19 +01:00
value = GPIO_AK5385_DFS_QUAD ;
oxygen_write16_masked ( chip , OXYGEN_GPIO_DATA ,
value , GPIO_AK5385_DFS_MASK ) ;
2007-12-23 19:50:57 +01:00
}
static const DECLARE_TLV_DB_LINEAR ( ak4396_db_scale , TLV_DB_GAIN_MUTE , 0 ) ;
2008-09-22 08:59:18 +02:00
static int generic_probe ( struct oxygen * chip , unsigned long driver_data )
{
if ( driver_data = = MODEL_MERIDIAN ) {
chip - > model . init = meridian_init ;
2008-09-22 09:05:29 +02:00
chip - > model . resume = meridian_resume ;
2008-09-22 08:59:18 +02:00
chip - > model . set_adc_params = set_ak5385_params ;
2008-09-22 09:02:08 +02:00
chip - > model . device_config = PLAYBACK_0_TO_I2S |
PLAYBACK_1_TO_SPDIF |
CAPTURE_0_FROM_I2S_2 |
CAPTURE_1_FROM_SPDIF ;
2008-09-22 08:59:18 +02:00
chip - > model . misc_flags = OXYGEN_MISC_MIDI ;
2008-09-22 09:03:42 +02:00
chip - > model . device_config | = MIDI_OUTPUT | MIDI_INPUT ;
2008-09-22 08:59:18 +02:00
}
return 0 ;
}
2007-12-23 19:50:57 +01:00
static const struct oxygen_model model_generic = {
. shortname = " C-Media CMI8788 " ,
. longname = " C-Media Oxygen HD Audio " ,
. chip = " CMI8788 " ,
. owner = THIS_MODULE ,
2008-09-22 08:59:18 +02:00
. probe = generic_probe ,
2007-12-23 19:50:57 +01:00
. init = generic_init ,
. cleanup = generic_cleanup ,
2008-05-13 09:24:39 +02:00
. resume = generic_resume ,
2007-12-23 19:50:57 +01:00
. set_dac_params = set_ak4396_params ,
. set_adc_params = set_wm8785_params ,
. update_dac_volume = update_ak4396_volume ,
. update_dac_mute = update_ak4396_mute ,
2008-04-16 09:15:45 +02:00
. dac_tlv = ak4396_db_scale ,
2008-01-21 08:51:55 +01:00
. model_data_size = sizeof ( struct generic_data ) ,
2008-09-22 09:02:08 +02:00
. device_config = PLAYBACK_0_TO_I2S |
PLAYBACK_1_TO_SPDIF |
PLAYBACK_2_TO_AC97_1 |
CAPTURE_0_FROM_I2S_1 |
CAPTURE_1_FROM_SPDIF |
CAPTURE_2_FROM_AC97_1 ,
2008-01-25 08:37:49 +01:00
. dac_channels = 8 ,
2008-04-16 09:13:36 +02:00
. dac_volume_min = 0 ,
. dac_volume_max = 255 ,
2008-03-19 08:20:13 +01:00
. function_flags = OXYGEN_FUNCTION_SPI |
OXYGEN_FUNCTION_ENABLE_SPI_4_5 ,
2008-01-17 09:05:09 +01:00
. dac_i2s_format = OXYGEN_I2S_FORMAT_LJUST ,
. adc_i2s_format = OXYGEN_I2S_FORMAT_LJUST ,
2007-12-23 19:50:57 +01:00
} ;
static int __devinit generic_oxygen_probe ( struct pci_dev * pci ,
const struct pci_device_id * pci_id )
{
static int dev ;
int err ;
if ( dev > = SNDRV_CARDS )
return - ENODEV ;
if ( ! enable [ dev ] ) {
+ + dev ;
return - ENOENT ;
}
2008-03-19 08:20:59 +01:00
err = oxygen_pci_probe ( pci , index [ dev ] , id [ dev ] ,
2008-09-22 08:59:18 +02:00
& model_generic , pci_id - > driver_data ) ;
2007-12-23 19:50:57 +01:00
if ( err > = 0 )
+ + dev ;
return err ;
}
static struct pci_driver oxygen_driver = {
. name = " CMI8788 " ,
. id_table = oxygen_ids ,
. probe = generic_oxygen_probe ,
. remove = __devexit_p ( oxygen_pci_remove ) ,
2008-05-13 09:24:39 +02:00
# ifdef CONFIG_PM
. suspend = oxygen_pci_suspend ,
. resume = oxygen_pci_resume ,
# endif
2007-12-23 19:50:57 +01:00
} ;
static int __init alsa_card_oxygen_init ( void )
{
return pci_register_driver ( & oxygen_driver ) ;
}
static void __exit alsa_card_oxygen_exit ( void )
{
pci_unregister_driver ( & oxygen_driver ) ;
}
module_init ( alsa_card_oxygen_init )
module_exit ( alsa_card_oxygen_exit )