ibook sound, limping mixer

Conrad H Ziesler cziesler at umich.edu
Wed Jan 10 08:29:44 EST 2001


in case this is of any use to anyone:

i've attached /dev/mixer code to talk to the dac3550a chip through some
i2c interface.  it just calls i2c bus write operations.

the lmsensors package provides an apple 'keywest' i2c controller
interface, which is a polling driver which appears to do the
same stuff as the PPCI2CInterface.cpp from IO/Drivers/.. in darwin

it all seems simple enough.  i hacked up a simple i2c driver merged into
the mixer code from above, cut apart dmasound to eliminate the mixer
device and extraneous stuff, and added the appropriate missing
KL1_AUDIO_XXX and KL1_I2S_) bits to arch/ppc/kernel/feature.c (in
feature_bits_keylargo[], under FEATURE_Sound_xxx  so the daca gets powered
up)

then i rebooted and had sound and a mixer device, and was able to
hear some sound, fiddle with the mixer controls, and then hear no sound
once or twice i was able to reenable sound by randomly fiddling with the
mixer.

so apparently SOME data was getting across the i2c bus to the daca chip,
but probably it was reading trash.

since the dac3550a has write-only i2c registers, its pretty hard to debug
the i2c interface.  probably if someone puts an oscilloscope probe on the
i2c bus signals it should all become clear.

--conrad







-------------- next part --------------
#include <stdarg.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/timer.h>
#include <linux/major.h>
#include <linux/config.h>
#include <linux/fcntl.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/malloc.h>
#include <linux/sound.h>
#include <linux/delay.h>
#include <linux/config.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/miscdevice.h>
#include <linux/blkdev.h>
#include <linux/pci.h>
#include <linux/poll.h>
#include <asm/system.h>
#include <asm/irq.h>
#include <asm/pgtable.h>
#include <asm/uaccess.h>
#include <asm/prom.h>
#include <asm/io.h>
#include <asm/pgtable.h>
#include <asm/system.h>
#include <asm/init.h>
#include <asm/irq.h>
#include <asm/feature.h>
#include <asm/uaccess.h>
#include <asm/mmu_context.h>
#include <asm/setup.h>
#include <linux/soundcard.h>

/* requires i2c access functions: 

   struct i2c_t *i2c_find(void);
   i2c_open(i2c_t *dp);
   i2c_close(i2c_t *dp);
   i2c_write(i2c_t *dp, uchar addr, uchar subaddr, uchar *data, int len);
*/


/* dmasound keeps this */
extern int beep_volume;



/* note: the daca i2c registers are write only */

/* i2c address */
#define  DACAI2Caddr 0x4d 

/* i2c control or subaddresses 
#define  DACAI2Csrate 0x01
#define  DACAI2Cavol 0x02
#define  DACAI2Cgcfg 0x03

/* gcfg register */
#define DACAhvolt  0x40
#define DACAlowp   0x20
#define DACAaux2on 0x10
#define DACAaux1on 0x08
#define DACAdacon  0x04
#define DACAmono   0x02
#define DACAinvert 0x01

/* srate register */
#define DACAlrsel_r 0x10
#define DACAlrsel_l 0x00
#define DACAdelay_1 0x08
#define DACAsrc48 0x00
#define DACAsrc32 0x01
#define DACAsrc24 0x02
#define DACAsrc16 0x03
#define DACAsrc12 0x04
#define DACAsrc8 0x05
#define DACAsrca 0x06

typedef struct daca_i2c_regs_st
{
  void *i2c;
  unsigned short int avol_reg;
  unsigned char sr_reg;
  unsigned char gcfg_reg;
}daca_i2c_regs_t;

/* mirror state daca regs (since they are read only) */
daca_i2c_regs_t daca_regs;


void daca_update(daca_i2c_regs_t *dacap)
{
  i2c_open(dacap->i2c);
  i2c_write(dacap->i2c,DACAI2Caddr, DACAI2Cgcfg, &(dacap->gcfg_reg), 1);  
  i2c_write(dacap->i2c,DACAI2Caddr, DACAI2Cavol, &(dacap->avol_reg), 2);
  i2c_write(dacap->i2c,DACAI2Caddr, DACAI2Csrate, &(dacap->sr_reg), 1); 
  i2c_close(dacap->i2c);
} 
 


struct sound_mixer 
{
  int busy;
  int modify_counter;

  /* keep track of status */
  int v_left;
  int v_right;
  int aux1;
  int aux2;
  int headphones_speaker;

};


static void mixer_update(struct sound_mixer *mix)
{
  uchar gcfg;
  uchar vols[2];
  
  if(mix==0)return;
  if(mix->dev==0)return;
  if(!mix->dev->initted)return;
  if(!mix->dev->open)return;
  if(!mix->headphones_speaker) 
    {
      vols[0]=mix->v_left&0x3f;
      vols[1]=mix->v_right&0x3f;
    }
  else
    {
      vols[0]=vols[1]= ((mix->v_left+mix->v_right)/2);
    }
  gcfg= DACAhvolt |  ((mix->aux1)?DACAaux1on:0)| ((mix->aux2)?DACAaux2on:0) | 
    ( (mix->headphones_speaker)?(DACAmono|DACAinvert):0);

  daca.gcfg_reg=gcfg;
  daca.avol_reg= (((unsigned short) vols[1] << 8) | vols[0]);
  daca_update(&daca);
}

 


static struct sound_mixer mixer;
static  int mixer_unit=-1;


static int mixer_open(struct inode *inode, struct file *file)
{
	MOD_INC_USE_COUNT;
	printk("mixer open %p %p\n",inode,file);
	mixer.busy = 1;
	return 0;
}


static int mixer_release(struct inode *inode, struct file *file)
{
  printk("mixer close %p %p\n",inode,file);
  mixer.busy = 0;
  MOD_DEC_USE_COUNT;
  return 0;
}

#define IOCTL_IN(arg, ret) \
	do { int error = get_user(ret, (int *)(arg)); \
		if (error) return error; \
	} while (0)
#define IOCTL_OUT(arg, ret)	ioctl_return((int *)(arg), ret)

static long long sound_lseek(struct file *file, long long offset, int orig)
{
  return -ESPIPE;
}

static inline int ioctl_return(int *addr, int value)
{
	if (value < 0)
	  return(value);

	return put_user(value, addr)? -EFAULT: 0;
}


#define MINL  0
#define MINLR 0
#define MAXL  100
#define MAXLR ((100<<8)|100)

int mixer_ioctl(struct inode *inode, struct file *file, u_int cmd,
		       u_long arg)
{
  unsigned int data;
  if(0)printk("mixer ioctl: %p %p %x %lx\n",inode,file,cmd,arg);
  
  
  if (_SIOC_DIR(cmd) & _SIOC_WRITE)
    mixer.modify_counter++;
  
  switch (cmd) 
    {
    case OSS_GETVERSION:
      return IOCTL_OUT(arg,SOUND_VERSION);
      
    case SOUND_MIXER_INFO: 
      {
	mixer_info info;
	strncpy(info.id, "DACA", sizeof(info.id));
	strncpy(info.name, "DACA-i2c", sizeof(info.name));
	info.name[sizeof(info.name)-1] = 0;
	info.modify_counter = mixer.modify_counter;
	copy_to_user_ret((int *)arg, &info, 
			 sizeof(info), -EFAULT);
	return 0;
      }
    case SOUND_MIXER_READ_DEVMASK:
      data = SOUND_MASK_VOLUME | SOUND_MASK_SPEAKER
	| SOUND_MASK_LINE | SOUND_MASK_ALTPCM 
	| SOUND_MASK_CD  ;
      return IOCTL_OUT(arg, data);
    case SOUND_MIXER_READ_RECMASK:
      data = 0;
      return IOCTL_OUT(arg, data);
    case SOUND_MIXER_READ_RECSRC:
      data = 0;
      return IOCTL_OUT(arg, data);
    case SOUND_MIXER_WRITE_RECSRC:
      IOCTL_IN(arg, data);
      data=0;
      return IOCTL_OUT(arg, data);
    case SOUND_MIXER_READ_STEREODEVS:
      data = SOUND_MASK_VOLUME ;
      return IOCTL_OUT(arg, data);
    case SOUND_MIXER_READ_CAPS:
      return IOCTL_OUT(arg, 0);

    case SOUND_MIXER_WRITE_VOLUME:
      
      IOCTL_IN(arg, data);
      mixer.v_left = (((data&0xff) * 0x30)/100);
      mixer.v_right= ((((data>>8)&0xff)* 0x30)/100);
      if(mixer.v_left>0x30)mixer.v_left=0x30;
      if(mixer.v_right>0x30)mixer.v_right=0x30;
      mixer_update(&mixer);
      /* fall through */
    case SOUND_MIXER_READ_VOLUME:
      data = ((mixer.v_left*100)/0x30) |  (((mixer.v_right*100)/0x30) << 8);
      return IOCTL_OUT(arg, data);

    case SOUND_MIXER_WRITE_SPEAKER:
      
      IOCTL_IN(arg, data);
      if(0)printk("mixer: write speaker %x\n",data);
      if ( !mixer.headphones_speaker && ((data & 0xff) > MINL) )     mixer.headphones_speaker=1;
      else if (  mixer.headphones_speaker && ((data & 0xff) < MAXL) )  mixer.headphones_speaker=0;
      mixer_update(&mixer);
      /* fall through */
    case SOUND_MIXER_READ_SPEAKER:
      data= (mixer.headphones_speaker)?MAXLR:MINLR;
      return IOCTL_OUT(arg, data);

    case SOUND_MIXER_WRITE_ALTPCM:	/* really bell volume */
      IOCTL_IN(arg, data);
      if((data&0xff)> 50)
	{ 
	  printk("hacking... fixing vols to 1,0 \n");
	  mixer.v_left=1;
	  mixer.v_right=0;
	  mixer_update(&mixer);
	}
	beep_volume = ((data & 0xff) + ((data>>8)&0xff))/2;
     
      /* fall through */
    case SOUND_MIXER_READ_ALTPCM:
      data=(beep_volume) | (beep_volume<<8);
      return IOCTL_OUT(arg, data);
      
    case SOUND_MIXER_WRITE_LINE: /* treat line as aux1 (modem?) */
      IOCTL_IN(arg, data);
      if(0)printk("mixer: write line %x\n",data);
      if ( !mixer.aux1 && ((data & 0xff) > MINL) )     mixer.aux1=1;
      else if (  mixer.aux1 && ((data & 0xff) < MAXL) )  mixer.aux1=0;
      mixer_update(&mixer);
      /* fall through */
    case SOUND_MIXER_READ_LINE:
      data = (mixer.aux1)? MAXLR: MINLR;
      return IOCTL_OUT(arg, data);

    case SOUND_MIXER_WRITE_CD:
      IOCTL_IN(arg, data);
      if(0)printk("mixer: write cd %x\n",data);
      if ( !mixer.aux2 && ((data & 0xff) > MINL) )     mixer.aux2=1;
      else if (  mixer.aux2 && ((data & 0xff) < MAXL) )  mixer.aux2=0;
      mixer_update(&mixer);
      /* fall through */
    case SOUND_MIXER_READ_CD:
      data = (mixer.aux2)? MAXLR: MINLR;
      return IOCTL_OUT(arg, data);
    }
  
  return -EINVAL;
}


static struct file_operations mixer_fops =
{
	sound_lseek,
	NULL,			/* mixer_read */
	NULL,			/* mixer_write */
	NULL,			/* mixer_readdir */
	NULL,			/* mixer_poll */
	mixer_ioctl,
	NULL,			/* mixer_mmap */
	mixer_open,
	NULL,			/* flush */
	mixer_release,
};
#endif

static void __init mixer_init(void)
{
  mixer.dev=&audio_i2c_control;
  mixer.headphones_speaker=1;
  mixer.v_left=32; /* 5 bits worth */
  mixer.v_right=32;
  mixer.aux1=0;
  mixer.aux2=0;
  mixer_unit = register_sound_mixer(&mixer_fops, -1);
  if (mixer_unit < 0)
    return;
  printk("daca_i2c_mixer: registered as unit %i\n",mixer_unit);  
  mixer.busy = 0;
}


MODULE_DESCRIPTION("mixer for dac3550a sound chip");

int init_module(void)
{
  daca->i2c=i2c_find("daca");
  mixer_init();
  return 0;
}

void cleanup_module(void)
{
  if (mixer_unit >= 0) 
    unregister_sound_mixer(mixer_unit);
}











More information about the Linuxppc-dev mailing list