2005-05-01 19:58:43 +04:00
/*
* Mac Mini " toonie " mixer control
*
* Copyright ( c ) 2005 by Benjamin Herrenschmidt < benh @ kernel . crashing . org >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* This program 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 program ; if not , write to the Free Software
* Foundation , Inc . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA
*/
# include <sound/driver.h>
# include <linux/init.h>
# include <linux/delay.h>
# include <linux/i2c.h>
# include <linux/i2c-dev.h>
# include <linux/kmod.h>
# include <linux/slab.h>
# include <linux/interrupt.h>
# include <sound/core.h>
# include <asm/io.h>
# include <asm/irq.h>
# include <asm/machdep.h>
# include <asm/pmac_feature.h>
# include "pmac.h"
# undef DEBUG
# ifdef DEBUG
# define DBG(fmt...) printk(fmt)
# else
# define DBG(fmt...)
# endif
struct pmac_gpio {
unsigned int addr ;
u8 active_val ;
u8 inactive_val ;
u8 active_state ;
} ;
struct pmac_toonie
{
struct pmac_gpio hp_detect_gpio ;
struct pmac_gpio hp_mute_gpio ;
struct pmac_gpio amp_mute_gpio ;
int hp_detect_irq ;
int auto_mute_notify ;
struct work_struct detect_work ;
} ;
/*
* gpio access
*/
# define do_gpio_write(gp, val) \
pmac_call_feature ( PMAC_FTR_WRITE_GPIO , NULL , ( gp ) - > addr , val )
# define do_gpio_read(gp) \
pmac_call_feature ( PMAC_FTR_READ_GPIO , NULL , ( gp ) - > addr , 0 )
# define tumbler_gpio_free(gp) /* NOP */
static void write_audio_gpio ( struct pmac_gpio * gp , int active )
{
if ( ! gp - > addr )
return ;
active = active ? gp - > active_val : gp - > inactive_val ;
do_gpio_write ( gp , active ) ;
DBG ( " (I) gpio %x write %d \n " , gp - > addr , active ) ;
}
static int check_audio_gpio ( struct pmac_gpio * gp )
{
int ret ;
if ( ! gp - > addr )
return 0 ;
ret = do_gpio_read ( gp ) ;
return ( ret & 0xd ) = = ( gp - > active_val & 0xd ) ;
}
static int read_audio_gpio ( struct pmac_gpio * gp )
{
int ret ;
if ( ! gp - > addr )
return 0 ;
ret = ( ( do_gpio_read ( gp ) & 0x02 ) ! = 0 ) ;
return ret = = gp - > active_state ;
}
enum { TOONIE_MUTE_HP , TOONIE_MUTE_AMP } ;
2005-11-17 17:09:46 +03:00
static int toonie_get_mute_switch ( struct snd_kcontrol * kcontrol ,
struct snd_ctl_elem_value * ucontrol )
2005-05-01 19:58:43 +04:00
{
2005-11-17 17:09:46 +03:00
struct snd_pmac * chip = snd_kcontrol_chip ( kcontrol ) ;
2005-05-01 19:58:43 +04:00
struct pmac_toonie * mix = chip - > mixer_data ;
struct pmac_gpio * gp ;
if ( mix = = NULL )
return - ENODEV ;
switch ( kcontrol - > private_value ) {
case TOONIE_MUTE_HP :
gp = & mix - > hp_mute_gpio ;
break ;
case TOONIE_MUTE_AMP :
gp = & mix - > amp_mute_gpio ;
break ;
default :
return - EINVAL ; ;
}
ucontrol - > value . integer . value [ 0 ] = ! check_audio_gpio ( gp ) ;
return 0 ;
}
2005-11-17 17:09:46 +03:00
static int toonie_put_mute_switch ( struct snd_kcontrol * kcontrol ,
struct snd_ctl_elem_value * ucontrol )
2005-05-01 19:58:43 +04:00
{
2005-11-17 17:09:46 +03:00
struct snd_pmac * chip = snd_kcontrol_chip ( kcontrol ) ;
2005-05-01 19:58:43 +04:00
struct pmac_toonie * mix = chip - > mixer_data ;
struct pmac_gpio * gp ;
int val ;
if ( chip - > update_automute & & chip - > auto_mute )
return 0 ; /* don't touch in the auto-mute mode */
if ( mix = = NULL )
return - ENODEV ;
switch ( kcontrol - > private_value ) {
case TOONIE_MUTE_HP :
gp = & mix - > hp_mute_gpio ;
break ;
case TOONIE_MUTE_AMP :
gp = & mix - > amp_mute_gpio ;
break ;
default :
return - EINVAL ; ;
}
val = ! check_audio_gpio ( gp ) ;
if ( val ! = ucontrol - > value . integer . value [ 0 ] ) {
write_audio_gpio ( gp , ! ucontrol - > value . integer . value [ 0 ] ) ;
return 1 ;
}
return 0 ;
}
2005-11-17 17:09:46 +03:00
static struct snd_kcontrol_new toonie_hp_sw __initdata = {
2005-05-01 19:58:43 +04:00
. iface = SNDRV_CTL_ELEM_IFACE_MIXER ,
. name = " Headphone Playback Switch " ,
. info = snd_pmac_boolean_mono_info ,
. get = toonie_get_mute_switch ,
. put = toonie_put_mute_switch ,
. private_value = TOONIE_MUTE_HP ,
} ;
2005-11-17 17:09:46 +03:00
static struct snd_kcontrol_new toonie_speaker_sw __initdata = {
2005-05-01 19:58:43 +04:00
. iface = SNDRV_CTL_ELEM_IFACE_MIXER ,
. name = " PC Speaker Playback Switch " ,
. info = snd_pmac_boolean_mono_info ,
. get = toonie_get_mute_switch ,
. put = toonie_put_mute_switch ,
. private_value = TOONIE_MUTE_AMP ,
} ;
/*
* auto - mute stuffs
*/
2005-11-17 17:09:46 +03:00
static int toonie_detect_headphone ( struct snd_pmac * chip )
2005-05-01 19:58:43 +04:00
{
struct pmac_toonie * mix = chip - > mixer_data ;
int detect = 0 ;
if ( mix - > hp_detect_gpio . addr )
detect | = read_audio_gpio ( & mix - > hp_detect_gpio ) ;
return detect ;
}
2005-11-17 17:09:46 +03:00
static void toonie_check_mute ( struct snd_pmac * chip , struct pmac_gpio * gp , int val ,
int do_notify , struct snd_kcontrol * sw )
2005-05-01 19:58:43 +04:00
{
if ( check_audio_gpio ( gp ) ! = val ) {
write_audio_gpio ( gp , val ) ;
if ( do_notify )
snd_ctl_notify ( chip - > card , SNDRV_CTL_EVENT_MASK_VALUE ,
& sw - > id ) ;
}
}
static void toonie_detect_handler ( void * self )
{
2005-11-17 17:09:46 +03:00
struct snd_pmac * chip = ( struct snd_pmac * ) self ;
2005-05-01 19:58:43 +04:00
struct pmac_toonie * mix ;
int headphone ;
if ( ! chip )
return ;
mix = chip - > mixer_data ;
snd_assert ( mix , return ) ;
headphone = toonie_detect_headphone ( chip ) ;
DBG ( " headphone: %d, lineout: %d \n " , headphone , lineout ) ;
if ( headphone ) {
/* unmute headphone/lineout & mute speaker */
toonie_check_mute ( chip , & mix - > hp_mute_gpio , 0 ,
mix - > auto_mute_notify , chip - > master_sw_ctl ) ;
toonie_check_mute ( chip , & mix - > amp_mute_gpio , 1 ,
mix - > auto_mute_notify , chip - > speaker_sw_ctl ) ;
} else {
/* unmute speaker, mute others */
toonie_check_mute ( chip , & mix - > amp_mute_gpio , 0 ,
mix - > auto_mute_notify , chip - > speaker_sw_ctl ) ;
toonie_check_mute ( chip , & mix - > hp_mute_gpio , 1 ,
mix - > auto_mute_notify , chip - > master_sw_ctl ) ;
}
if ( mix - > auto_mute_notify ) {
snd_ctl_notify ( chip - > card , SNDRV_CTL_EVENT_MASK_VALUE ,
& chip - > hp_detect_ctl - > id ) ;
}
}
2005-11-17 17:09:46 +03:00
static void toonie_update_automute ( struct snd_pmac * chip , int do_notify )
2005-05-01 19:58:43 +04:00
{
if ( chip - > auto_mute ) {
struct pmac_toonie * mix ;
mix = chip - > mixer_data ;
snd_assert ( mix , return ) ;
mix - > auto_mute_notify = do_notify ;
schedule_work ( & mix - > detect_work ) ;
}
}
/* interrupt - headphone plug changed */
static irqreturn_t toonie_hp_intr ( int irq , void * devid , struct pt_regs * regs )
{
2005-11-17 17:09:46 +03:00
struct snd_pmac * chip = devid ;
2005-05-01 19:58:43 +04:00
if ( chip - > update_automute & & chip - > initialized ) {
chip - > update_automute ( chip , 1 ) ;
return IRQ_HANDLED ;
}
return IRQ_NONE ;
}
/* look for audio gpio device */
static int find_audio_gpio ( const char * name , const char * platform ,
struct pmac_gpio * gp )
{
struct device_node * np ;
u32 * base , addr ;
if ( ! ( np = find_devices ( " gpio " ) ) )
return - ENODEV ;
for ( np = np - > child ; np ; np = np - > sibling ) {
char * property = get_property ( np , " audio-gpio " , NULL ) ;
if ( property & & strcmp ( property , name ) = = 0 )
break ;
if ( device_is_compatible ( np , name ) )
break ;
}
if ( np = = NULL )
return - ENODEV ;
base = ( u32 * ) get_property ( np , " AAPL,address " , NULL ) ;
if ( ! base ) {
base = ( u32 * ) get_property ( np , " reg " , NULL ) ;
if ( ! base ) {
2005-05-02 05:54:23 +04:00
DBG ( " (E) cannot find address for device %s ! \n " , name ) ;
2005-05-01 19:58:43 +04:00
return - ENODEV ;
}
addr = * base ;
if ( addr < 0x50 )
addr + = 0x50 ;
} else
addr = * base ;
gp - > addr = addr & 0x0000ffff ;
/* Try to find the active state, default to 0 ! */
base = ( u32 * ) get_property ( np , " audio-gpio-active-state " , NULL ) ;
if ( base ) {
gp - > active_state = * base ;
gp - > active_val = ( * base ) ? 0x5 : 0x4 ;
gp - > inactive_val = ( * base ) ? 0x4 : 0x5 ;
} else {
u32 * prop = NULL ;
gp - > active_state = 0 ;
gp - > active_val = 0x4 ;
gp - > inactive_val = 0x5 ;
/* Here are some crude hacks to extract the GPIO polarity and
* open collector informations out of the do - platform script
* as we don ' t yet have an interpreter for these things
*/
if ( platform )
prop = ( u32 * ) get_property ( np , platform , NULL ) ;
if ( prop ) {
if ( prop [ 3 ] = = 0x9 & & prop [ 4 ] = = 0x9 ) {
gp - > active_val = 0xd ;
gp - > inactive_val = 0xc ;
}
if ( prop [ 3 ] = = 0x1 & & prop [ 4 ] = = 0x1 ) {
gp - > active_val = 0x5 ;
gp - > inactive_val = 0x4 ;
}
}
}
DBG ( " (I) GPIO device %s found, offset: %x, active state: %d ! \n " ,
2005-05-02 08:45:12 +04:00
name , gp - > addr , gp - > active_state ) ;
2005-05-01 19:58:43 +04:00
return ( np - > n_intrs > 0 ) ? np - > intrs [ 0 ] . line : 0 ;
}
2005-11-17 17:09:46 +03:00
static void toonie_cleanup ( struct snd_pmac * chip )
2005-05-01 19:58:43 +04:00
{
struct pmac_toonie * mix = chip - > mixer_data ;
if ( ! mix )
return ;
if ( mix - > hp_detect_irq > = 0 )
free_irq ( mix - > hp_detect_irq , chip ) ;
kfree ( mix ) ;
chip - > mixer_data = NULL ;
}
2005-11-17 17:09:46 +03:00
int snd_pmac_toonie_init ( struct snd_pmac * chip )
2005-05-01 19:58:43 +04:00
{
struct pmac_toonie * mix ;
mix = kmalloc ( sizeof ( * mix ) , GFP_KERNEL ) ;
if ( ! mix )
return - ENOMEM ;
chip - > mixer_data = mix ;
chip - > mixer_free = toonie_cleanup ;
find_audio_gpio ( " headphone-mute " , NULL , & mix - > hp_mute_gpio ) ;
find_audio_gpio ( " amp-mute " , NULL , & mix - > amp_mute_gpio ) ;
mix - > hp_detect_irq = find_audio_gpio ( " headphone-detect " ,
NULL , & mix - > hp_detect_gpio ) ;
strcpy ( chip - > card - > mixername , " PowerMac Toonie " ) ;
chip - > master_sw_ctl = snd_ctl_new1 ( & toonie_hp_sw , chip ) ;
snd_ctl_add ( chip - > card , chip - > master_sw_ctl ) ;
chip - > speaker_sw_ctl = snd_ctl_new1 ( & toonie_speaker_sw , chip ) ;
snd_ctl_add ( chip - > card , chip - > speaker_sw_ctl ) ;
INIT_WORK ( & mix - > detect_work , toonie_detect_handler , ( void * ) chip ) ;
if ( mix - > hp_detect_irq > = 0 ) {
snd_pmac_add_automute ( chip ) ;
chip - > detect_headphone = toonie_detect_headphone ;
chip - > update_automute = toonie_update_automute ;
toonie_update_automute ( chip , 0 ) ;
if ( request_irq ( mix - > hp_detect_irq , toonie_hp_intr , 0 ,
" Sound Headphone Detection " , chip ) < 0 )
mix - > hp_detect_irq = - 1 ;
}
return 0 ;
}