Hi Takashi, On Thu, Jul 03, 2003 at 02:29:01PM +0200, Takashi Iwai wrote: > At Wed, 2 Jul 2003 15:29:55 -0500, > Ryan Underwood wrote: > > > > 1) What is ALSA's policy on module options? For example, would it be > > okay to add an option like "thinkpad" to the ad1848 module, so that > > inserting it with "thinkpad=1" would cause the necessary ports to be > > prodded? > > this looks like a feasible solution. > > > 2) Since the chip's power mode can be controlled through a write to that > > port (bit 2 on, cs4248 power on; bit 2 off, cs4248 power off), would it > > be a good idea to add the _suspend, _resume, and _set_power_state > > functions within a #ifdef CONFIG_PM block, like in other drivers? > > yep. we haven't done that just becase of lack of hardware. > if you can write and test it, it would be greatly appreciated.
I have attached a patch against the ALSA 0.94. ad1848.c, ad1848_lib.c, and ad1848.h have changed. I tested the module against 2.4.21 on a Debian system, gcc 3.3 and it works fine; simply set the proper resources and thinkpad=1 and the thinkpad works. One issue is to note, I don't know if this is a problem with the ad1848 driver in general or what; if I cat a 11025hz mono 8-bit file to /dev/dsp, the sound comes out verrrrry slllloooowwwllly. But using aplay, it works fine. I am not sure the best way to test the power managmeent code -- it doesn't crash the machine at least. ;) I added a note to the ALSA wiki about this option. I tried to fix the OSS driver too but it does some other weird things like not allowing a IRQ > 7 to be used. (It thinks the CS4248 is an 8bit card or something). So this will have to be an alsa-only fix for now at least. Let me know if the patch is ok, -- Ryan Underwood, <nemesis at icequake.net>, icq=10317253
diff -ur alsa-driver/alsa-kernel/include/ad1848.h alsanew/alsa-kernel/include/ad1848.h --- alsa-driver/alsa-kernel/include/ad1848.h 2003-04-23 05:01:29.000000000 -0500 +++ alsanew/alsa-kernel/include/ad1848.h 2003-07-03 20:29:59.000000000 -0500 @@ -121,6 +121,11 @@ #define AD1848_HW_CS4248 0x0003 /* CS4248 chip */ #define AD1848_HW_CMI8330 0x0004 /* CMI8330 chip */ +/* IBM Thinkpad specific stuff */ +#define AD1848_THINKPAD_CTL_PORT1 0x15e8 +#define AD1848_THINKPAD_CTL_PORT2 0x15e9 +#define AD1848_THINKPAD_CS4248_ENABLE_BIT 0x02 + struct _snd_ad1848 { unsigned long port; /* i/o port */ struct resource *res_port; @@ -140,6 +145,10 @@ int mce_bit; int calibrate_mute; int dma_size; + int thinkpad_flag; /* Thinkpad CS4248 needs some extra help */ +#ifdef CONFIG_PM + struct pm_dev *thinkpad_pmstate; +#endif spinlock_t reg_lock; struct semaphore open_mutex; @@ -157,7 +166,7 @@ int snd_ad1848_create(snd_card_t * card, unsigned long port, - int irq, int dma, + int irq, int dma, int thinkpad, unsigned short hardware, ad1848_t ** chip); diff -ur alsa-driver/alsa-kernel/isa/ad1848/ad1848.c alsanew/alsa-kernel/isa/ad1848/ad1848.c --- alsa-driver/alsa-kernel/isa/ad1848/ad1848.c 2002-10-21 13:28:21.000000000 -0500 +++ alsanew/alsa-kernel/isa/ad1848/ad1848.c 2003-07-07 13:11:55.000000000 -0500 @@ -46,6 +46,7 @@ static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */ static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5,7,9,11,12,15 */ static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0,1,3,5,6,7 */ +static int thinkpad[SNDRV_CARDS]; /* Thinkpad special case */ MODULE_PARM(index, "1-" __MODULE_STRING(SNDRV_CARDS) "i"); MODULE_PARM_DESC(index, "Index value for AD1848 soundcard."); @@ -65,6 +66,9 @@ MODULE_PARM(dma1, "1-" __MODULE_STRING(SNDRV_CARDS) "i"); MODULE_PARM_DESC(dma1, "DMA1 # for AD1848 driver."); MODULE_PARM_SYNTAX(dma1, SNDRV_DMA_DESC); +MODULE_PARM(thinkpad, "1-" __MODULE_STRING(SNDRV_CARDS) "i"); +MODULE_PARM_DESC(thinkpad, "Enable only for the onboard CS4248 of IBM Thinkpad 360/750/755 series."); +MODULE_PARM_SYNTAX(thinkpad, SNDRV_ENABLED "," SNDRV_BOOLEAN_FALSE_DESC); static snd_card_t *snd_ad1848_cards[SNDRV_CARDS] = SNDRV_DEFAULT_PTR; @@ -96,6 +100,7 @@ if ((err = snd_ad1848_create(card, port[dev], irq[dev], dma1[dev], + thinkpad[dev], AD1848_HW_DETECT, &chip)) < 0) { snd_card_free(card); @@ -116,6 +121,10 @@ sprintf(card->longname, "%s at 0x%lx, irq %d, dma %d", pcm->name, chip->port, irq[dev], dma1[dev]); + if (thinkpad[dev]) { + strcat(card->longname, " [Thinkpad]"); + } + if ((err = snd_card_register(card)) < 0) { snd_card_free(card); return err; @@ -168,7 +177,8 @@ get_id(&str,&id[nr_dev]) == 2 && get_option(&str,(int *)&port[nr_dev]) == 2 && get_option(&str,&irq[nr_dev]) == 2 && - get_option(&str,&dma1[nr_dev]) == 2); + get_option(&str,&dma1[nr_dev]) == 2 && + get_option(&str,&thinkpad[nr_dev]) == 2); nr_dev++; return 1; } diff -ur alsa-driver/alsa-kernel/isa/ad1848/ad1848_lib.c alsanew/alsa-kernel/isa/ad1848/ad1848_lib.c --- alsa-driver/alsa-kernel/isa/ad1848/ad1848_lib.c 2003-05-30 07:28:34.000000000 -0500 +++ alsanew/alsa-kernel/isa/ad1848/ad1848_lib.c 2003-07-03 20:25:21.000000000 -0500 @@ -625,6 +625,92 @@ */ +static void snd_ad1848_thinkpad_twiddle(ad1848_t *chip, int on) { + + int tmp; + + if (!chip->thinkpad_flag) return; + + outb(0x1c, AD1848_THINKPAD_CTL_PORT1); + tmp = inb(AD1848_THINKPAD_CTL_PORT2); + + switch (on) { + case 0: /* turn it off */ + tmp &= ~AD1848_THINKPAD_CS4248_ENABLE_BIT; + default: /* turn it on */ + tmp |= AD1848_THINKPAD_CS4248_ENABLE_BIT; + } + + outb(tmp, AD1848_THINKPAD_CTL_PORT2); + +} + +#ifdef CONFIG_PM +static void snd_ad1848_suspend(ad1848_t *chip) { + + snd_card_t *card = chip->card; + + if (card->power_state == SNDRV_CTL_POWER_D3hot) + return; + + if (chip->thinkpad_flag) { + snd_pcm_suspend_all(chip->pcm); + snd_ad1848_thinkpad_twiddle(chip, 0); + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + } + +} + +static void snd_ad1848_resume(ad1848_t *chip) { + + snd_card_t *card = chip->card; + + if (card->power_state == SNDRV_CTL_POWER_D0) + return; + + if (chip->thinkpad_flag) { + snd_ad1848_thinkpad_twiddle(chip, 1); + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + } +} + +/* callback for control API */ +static int snd_ad1848_set_power_state(snd_card_t *card, unsigned int power_state) +{ + ad1848_t *chip = (ad1848_t *) card->power_state_private_data; + switch (power_state) { + case SNDRV_CTL_POWER_D0: + case SNDRV_CTL_POWER_D1: + case SNDRV_CTL_POWER_D2: + snd_ad1848_resume(chip); + break; + case SNDRV_CTL_POWER_D3hot: + case SNDRV_CTL_POWER_D3cold: + snd_ad1848_suspend(chip); + break; + default: + return -EINVAL; + } + return 0; +} + +static int snd_ad1848_pm_callback(struct pm_dev *dev, pm_request_t rqst, void *data) +{ + ad1848_t *chip = snd_magic_cast(ad1848_t, dev->data, return 0); + + switch (rqst) { + case PM_SUSPEND: + snd_ad1848_suspend(chip); + break; + case PM_RESUME: + snd_ad1848_resume(chip); + break; + } + return 0; +} + +#endif /* CONFIG_PM */ + static int snd_ad1848_probe(ad1848_t * chip) { unsigned long flags; @@ -799,6 +885,10 @@ static int snd_ad1848_free(ad1848_t *chip) { +#ifdef CONFIG_PM + if (chip->thinkpad_flag && chip->thinkpad_pmstate) + pm_unregister(chip->thinkpad_pmstate); +#endif if (chip->res_port) { release_resource(chip->res_port); kfree_nocheck(chip->res_port); @@ -832,7 +922,7 @@ int snd_ad1848_create(snd_card_t * card, unsigned long port, - int irq, int dma, + int irq, int dma, int thinkpad, unsigned short hardware, ad1848_t ** rchip) { @@ -870,6 +960,22 @@ } chip->dma = dma; + if (thinkpad) { + chip->thinkpad_flag = 1; + snd_ad1848_thinkpad_twiddle(chip, 1); +#ifdef CONFIG_PM + chip->thinkpad_pmstate = pm_register(PM_ISA_DEV, 0, snd_ad1848_pm_callback); + if (chip->thinkpad_pmstate) { + chip->thinkpad_pmstate->data = chip; + card->set_power_state = snd_ad1848_set_power_state; /* callback */ + card->power_state_private_data = chip; + } +#endif + } + else { + chip->thinkpad_flag = 0; + } + if (snd_ad1848_probe(chip) < 0) { snd_ad1848_free(chip); return -ENODEV;