--- /dev/null
+Documentation for the SoundPro CMI8330 extensions in the WSS driver (ad1848.o)
+------------------------------------------------------------------------------
+
+Ion Badulescu, ionut@cs.columbia.edu
+February 24, 1999
+
+(derived from the OPL3-SA2 documentation by Scott Murray)
+
+The SoundPro CMI8330 (ISA) is a chip usually found on some Taiwanese
+motherboards. The official name in the documentation is CMI8330, SoundPro
+is the nickname and the big inscription on the chip itself.
+
+The chip emulates a WSS as well as a SB16, but it has certain differences
+in the mixer section which require separate support. It also emulates an
+MPU401 and an OPL3 synthesizer, so you probably want to enable support
+for these, too.
+
+The chip identifies itself as an AD1848, but its mixer is significantly
+more advanced than the original AD1848 one. If your system works with
+either WSS or SB16 and you are having problems with some mixer controls
+(no CD audio, no line-in, etc), you might want to give this driver a try.
+Detection should work, but it hasn't been widely tested, so it might still
+mis-identify the chip. You can still force soundpro=1 in the modprobe
+parameters for ad1848. Please let me know if it happens to you, so I can
+adjust the detection routine.
+
+The chip is capable of doing full-duplex, but since the driver sees it as an
+AD1848, it cannot take advantage of this. Moreover, the full-duplex mode is
+not achievable through the WSS interface, b/c it needs a dma16 line which is
+assigned only to the SB16 subdevice (with isapnp). Windows documentation
+says the user must use WSS Playback and SB16 Recording for full-duplex, so
+it might be possible to do the same thing under Linux. You can try loading
+up both ad1848 and sb then use one for playback and the other for
+recording. I don't know if this works, b/c I haven't tested it. Anyway, if
+you try it, be very careful: the SB16 mixer *mostly* works, but certain
+settings can have unexpected effects. Use the WSS mixer for best results.
+
+There is also a PCI SoundPro chip. I have not seen this chip, so I have
+no idea if the driver will work with it. I suspect it won't.
+
+As with PnP cards, some configuration is required. There are two ways
+of doing this. The most common is to use the isapnptools package to
+initialize the card, and use the kernel module form of the sound
+subsystem and sound drivers. Alternatively, some BIOS's allow manual
+configuration of installed PnP devices in a BIOS menu, which should
+allow using the non-modular sound drivers, i.e. built into the kernel.
+Since in this latter case you cannot use module parameters, you will
+have to enable support for the SoundPro at compile time.
+
+The IRQ and DMA values can be any that are considered acceptable for a
+WSS. Assuming you've got isapnp all happy, then you should be able to
+do something like the following (which *must* match the isapnp/BIOS
+configuration):
+
+modprobe ad1848 io=0x530 irq=11 dma=0 soundpro=1
+-and maybe-
+modprobe sb io=0x220 irq=5 dma=1 dma16=5
+
+-then-
+modprobe mpu401 io=0x330 irq=9
+modprobe opl3 io=0x388
+
+If all goes well and you see no error messages, you should be able to
+start using the sound capabilities of your system. If you get an
+error message while trying to insert the module(s), then make
+sure that the values of the various arguments match what you specified
+in your isapnp configuration file, and that there is no conflict with
+another device for an I/O port or interrupt. Checking the contents of
+/proc/ioports and /proc/interrupts can be useful to see if you're
+butting heads with another device.
+
+If you do not see the chipset version message, and none of the other
+messages present in the system log are helpful, try adding 'debug=1'
+to the ad1848 parameters, email me the syslog results and I'll do
+my best to help.
+
+Lastly, if you're using modules and want to set up automatic module
+loading with kmod, the kernel module loader, here is the section I
+currently use in my conf.modules file:
+
+# Sound
+post-install sound modprobe -k ad1848; modprobe -k mpu401; modprobe -k opl3
+options ad1848 io=0x530 irq=11 dma=0
+options sb io=0x220 irq=5 dma=1 dma16=5
+options mpu401 io=0x330 irq=9
+options opl3 io=0x388
+
+The above ensures that ad1848 will be loaded whenever the sound system
+is being used.
+
+Good luck.
+
+Ion
+
+NOT REALLY TESTED:
+- recording
+- recording device selection
+- full-duplex
+
+TODO:
+- implement mixer support for surround, loud, digital CD switches.
+- come up with a scheme which allows recording volumes for each subdevice.
+This is a major OSS API change.
0, 0, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL
};
-#define AIC7XXX_C_VERSION "5.1.19"
+#define AIC7XXX_C_VERSION "5.1.20"
#define NUMBER(arr) (sizeof(arr) / sizeof(arr[0]))
#define MIN(a,b) (((a) < (b)) ? (a) : (b))
AHC_SG_PRELOAD = 0x0080,
AHC_SPIOCAP = 0x0100,
AHC_ULTRA3 = 0x0200,
+ AHC_NEW_AUTOTERM = 0x0400,
AHC_AIC7770_FE = AHC_FENONE,
AHC_AIC7850_FE = AHC_SPIOCAP,
AHC_AIC7860_FE = AHC_ULTRA|AHC_SPIOCAP,
AHC_AIC7870_FE = AHC_FENONE,
AHC_AIC7880_FE = AHC_ULTRA,
AHC_AIC7890_FE = AHC_MORE_SRAM|AHC_CMD_CHAN|AHC_ULTRA2|
- AHC_QUEUE_REGS|AHC_SG_PRELOAD,
+ AHC_QUEUE_REGS|AHC_SG_PRELOAD|AHC_NEW_AUTOTERM,
AHC_AIC7895_FE = AHC_MORE_SRAM|AHC_CMD_CHAN|AHC_ULTRA,
AHC_AIC7896_FE = AHC_AIC7890_FE,
AHC_AIC7892_FE = AHC_AIC7890_FE|AHC_ULTRA3,
* would result in never finding any devices :)
*/
static int aic7xxx_no_probe = 0;
-
+/*
+ * On some machines, enabling the external SCB RAM isn't reliable yet. I
+ * haven't had time to make test patches for things like changing the
+ * timing mode on that external RAM either. Some of those changes may
+ * fix the problem. Until then though, we default to external SCB RAM
+ * off and give a command line option to enable it.
+ */
+static int aic7xxx_scbram = 0;
/*
* So that insmod can find the variable and make it point to something
*/
unsigned char x;
if(p->maddr)
{
- x = p->maddr[port];
+ x = readb(p->maddr + port);
}
else
{
x = inb(p->base + port);
}
- mb();
return(x);
#else
return(inb(p->base + port));
#ifdef MMAPIO
if(p->maddr)
{
- p->maddr[port] = val;
+ writeb(val, p->maddr + port);
}
else
{
{ "pci_parity", &aic7xxx_pci_parity },
{ "dump_card", &aic7xxx_dump_card },
{ "dump_sequencer", &aic7xxx_dump_sequencer },
+ { "scbram", &aic7xxx_scbram },
{ "tag_info", NULL }
};
cmd->result = 0;
scb = NULL;
}
- if (scb->cmd == p->dev_dtr_cmnd[TARGET_INDEX(scb->cmd)])
+ else if (scb->cmd == p->dev_dtr_cmnd[TARGET_INDEX(scb->cmd)])
{
/*
* Turn off the needsdtr, needwdtr, and needppr bits since this device
}
#endif
+
+/*+F*************************************************************************
+ * Function:
+ * aic7xxx_handle_command_completion_intr
+ *
+ * Description:
+ * SCSI command completion interrupt handler.
+ *-F*************************************************************************/
+static void
+aic7xxx_handle_command_completion_intr(struct aic7xxx_host *p)
+{
+ struct aic7xxx_scb *scb = NULL;
+ Scsi_Cmnd *cmd;
+ unsigned char scb_index;
+
+#ifdef AIC7XXX_VERBOSE_DEBUGGING
+ if( (p->isr_count < 16) && (aic7xxx_verbose > 0xffff) )
+ printk(INFO_LEAD "Command Complete Int.\n", p->host_no, -1, -1, -1);
+#endif
+
+ /*
+ * Read the INTSTAT location after clearing the CMDINT bit. This forces
+ * any posted PCI writes to flush to memory. Gerard Roudier suggested
+ * this fix to the possible race of clearing the CMDINT bit but not
+ * having all command bytes flushed onto the qoutfifo.
+ */
+ aic_outb(p, CLRCMDINT, CLRINT);
+ aic_inb(p, INTSTAT);
+ /*
+ * The sequencer will continue running when it
+ * issues this interrupt. There may be >1 commands
+ * finished, so loop until we've processed them all.
+ */
+
+ while (p->qoutfifo[p->qoutfifonext] != SCB_LIST_NULL)
+ {
+ scb_index = p->qoutfifo[p->qoutfifonext];
+ p->qoutfifo[p->qoutfifonext++] = SCB_LIST_NULL;
+ if ( scb_index >= p->scb_data->numscbs )
+ scb = NULL;
+ else
+ scb = p->scb_data->scb_array[scb_index];
+ if (scb == NULL)
+ {
+ printk(WARN_LEAD "CMDCMPLT with invalid SCB index %d\n", p->host_no,
+ -1, -1, -1, scb_index);
+ continue;
+ }
+ else if (!(scb->flags & SCB_ACTIVE) || (scb->cmd == NULL))
+ {
+ printk(WARN_LEAD "CMDCMPLT without command for SCB %d, SCB flags "
+ "0x%x, cmd 0x%lx\n", p->host_no, -1, -1, -1, scb_index, scb->flags,
+ (unsigned long) scb->cmd);
+ continue;
+ }
+ else if (scb->flags & SCB_QUEUED_ABORT)
+ {
+ pause_sequencer(p);
+ if ( ((aic_inb(p, LASTPHASE) & PHASE_MASK) != P_BUSFREE) &&
+ (aic_inb(p, SCB_TAG) == scb->hscb->tag) )
+ {
+ unpause_sequencer(p, FALSE);
+ continue;
+ }
+ aic7xxx_reset_device(p, scb->cmd->target, scb->cmd->channel,
+ scb->cmd->lun, scb->hscb->tag);
+ scb->flags &= ~(SCB_QUEUED_FOR_DONE | SCB_RESET | SCB_ABORT |
+ SCB_QUEUED_ABORT);
+ unpause_sequencer(p, FALSE);
+ }
+ else if (scb->flags & SCB_ABORT)
+ {
+ /*
+ * We started to abort this, but it completed on us, let it
+ * through as successful
+ */
+ scb->flags &= ~(SCB_ABORT|SCB_RESET);
+ }
+ switch (status_byte(scb->hscb->target_status))
+ {
+ case QUEUE_FULL:
+ case BUSY:
+ scb->hscb->target_status = 0;
+ scb->cmd->result = 0;
+ aic7xxx_error(scb->cmd) = DID_OK;
+ break;
+ default:
+ cmd = scb->cmd;
+ if (scb->hscb->residual_SG_segment_count != 0)
+ {
+ aic7xxx_calculate_residual(p, scb);
+ }
+ cmd->result |= (aic7xxx_error(cmd) << 16);
+ aic7xxx_done(p, scb);
+ break;
+ }
+ }
+}
+
/*+F*************************************************************************
* Function:
* aic7xxx_isr
*/
if (intstat & CMDCMPLT)
{
- struct aic7xxx_scb *scb = NULL;
- Scsi_Cmnd *cmd;
- unsigned char scb_index;
-
-#ifdef AIC7XXX_VERBOSE_DEBUGGING
- if( (p->isr_count < 16) && (aic7xxx_verbose > 0xffff) )
- printk(INFO_LEAD "Command Complete Int.\n", p->host_no, -1, -1, -1);
-#endif
-
- /*
- * Clear interrupt status before running the completion loop.
- * This eliminates a race condition whereby a command could
- * complete between the last check of qoutfifo and the
- * CLRCMDINT statement. This would result in us thinking the
- * qoutfifo was empty when it wasn't, and in actuality be a lost
- * completion interrupt. With multiple devices or tagged queueing
- * this could be very bad if we caught all but the last completion
- * and no more are imediately sent.
- */
- aic_outb(p, CLRCMDINT, CLRINT);
- /*
- * The sequencer will continue running when it
- * issues this interrupt. There may be >1 commands
- * finished, so loop until we've processed them all.
- */
-
- while (p->qoutfifo[p->qoutfifonext] != SCB_LIST_NULL)
- {
- scb_index = p->qoutfifo[p->qoutfifonext];
- p->qoutfifo[p->qoutfifonext++] = SCB_LIST_NULL;
- if ( scb_index >= p->scb_data->numscbs )
- scb = NULL;
- else
- scb = p->scb_data->scb_array[scb_index];
- if (scb == NULL)
- {
- printk(WARN_LEAD "CMDCMPLT with invalid SCB index %d\n", p->host_no,
- -1, -1, -1, scb_index);
- continue;
- }
- else if (!(scb->flags & SCB_ACTIVE) || (scb->cmd == NULL))
- {
- printk(WARN_LEAD "CMDCMPLT without command for SCB %d, SCB flags "
- "0x%x, cmd 0x%lx\n", p->host_no, -1, -1, -1, scb_index, scb->flags,
- (unsigned long) scb->cmd);
- continue;
- }
- else if (scb->flags & SCB_QUEUED_ABORT)
- {
- pause_sequencer(p);
- if ( ((aic_inb(p, LASTPHASE) & PHASE_MASK) != P_BUSFREE) &&
- (aic_inb(p, SCB_TAG) == scb->hscb->tag) )
- {
- unpause_sequencer(p, FALSE);
- continue;
- }
- aic7xxx_reset_device(p, scb->cmd->target, scb->cmd->channel,
- scb->cmd->lun, scb->hscb->tag);
- scb->flags &= ~(SCB_QUEUED_FOR_DONE | SCB_RESET | SCB_ABORT |
- SCB_QUEUED_ABORT);
- unpause_sequencer(p, FALSE);
- }
- else if (scb->flags & SCB_ABORT)
- {
- /*
- * We started to abort this, but it completed on us, let it
- * through as successful
- */
- scb->flags &= ~(SCB_ABORT|SCB_RESET);
- }
- switch (status_byte(scb->hscb->target_status))
- {
- case QUEUE_FULL:
- case BUSY:
- scb->hscb->target_status = 0;
- scb->cmd->result = 0;
- aic7xxx_error(scb->cmd) = DID_OK;
- break;
- default:
- cmd = scb->cmd;
- if (scb->hscb->residual_SG_segment_count != 0)
- {
- aic7xxx_calculate_residual(p, scb);
- }
- cmd->result |= (aic7xxx_error(cmd) << 16);
- aic7xxx_done(p, scb);
- break;
- }
- }
+ aic7xxx_handle_command_completion_intr(p);
}
if (intstat & BRKADRINT)
aic_outb(p, SEEMS | SEECS, SEECTL);
sxfrctl1 &= ~STPWEN;
if ( (p->adapter_control & CFAUTOTERM) ||
- (p->features & AHC_ULTRA2) )
+ (p->features & AHC_NEW_AUTOTERM) )
{
- if ( (p->adapter_control & CFAUTOTERM) && !(p->features & AHC_ULTRA2) )
+ if ( (p->adapter_control & CFAUTOTERM) &&
+ !(p->features & AHC_NEW_AUTOTERM) )
{
printk(KERN_INFO "(scsi%d) Warning - detected auto-termination\n",
p->host_no);
}
/* Configure auto termination. */
- if (p->features & AHC_ULTRA2)
+ if (p->features & AHC_NEW_AUTOTERM)
{
if (aic7xxx_override_term == -1)
aic7xxx_ultra2_term_detect(p, &enableSE_low, &enableSE_high,
if (max_target <= 8)
internal68_present = 0;
- if ( !(p->features & AHC_ULTRA2) )
+ if ( !(p->features & AHC_NEW_AUTOTERM) )
{
if (max_target > 8)
{
* SE Low Term Enable = BRDDAT5 (7890)
* LVD High Term Enable = BRDDAT4 (7890)
*/
- if ( !(p->features & AHC_ULTRA2) &&
+ if ( !(p->features & AHC_NEW_AUTOTERM) &&
(internal50_present && internal68_present && external_present) )
{
printk(KERN_INFO "(scsi%d) Illegal cable configuration!! Only two\n",
(external_present ? 1 : 0)) <= 1) ||
(enableSE_low != 0) )
{
- if (p->features & AHC_ULTRA2)
+ if (p->features & AHC_NEW_AUTOTERM)
brddat |= BRDDAT5;
else
sxfrctl1 |= STPWEN;
{
if (p->adapter_control & CFSTERM)
{
- if (p->features & AHC_ULTRA2)
+ if (p->features & AHC_NEW_AUTOTERM)
brddat |= BRDDAT5;
else
sxfrctl1 |= STPWEN;
AHC_PAGESCBS | AHC_BIOS_ENABLED, AHC_AIC7880_FE, 18,
32, C46 },
{PCI_VENDOR_ID_ADAPTEC, PCI_DEVICE_ID_ADAPTEC_7887, AHC_AIC7880,
- AHC_PAGESCBS | AHC_BIOS_ENABLED, AHC_AIC7880_FE, 18,
+ AHC_PAGESCBS | AHC_BIOS_ENABLED, AHC_AIC7880_FE | AHC_NEW_AUTOTERM, 18,
32, C46 },
{PCI_VENDOR_ID_ADAPTEC, PCI_DEVICE_ID_ADAPTEC_7888, AHC_AIC7880,
AHC_PAGESCBS | AHC_BIOS_ENABLED, AHC_AIC7880_FE, 18,
temp_p->base &= PCI_BASE_ADDRESS_IO_MASK;
temp_p->mbase &= PCI_BASE_ADDRESS_MEM_MASK;
current_p = list_p;
- while(current_p)
+ while(current_p && temp_p)
{
if ( ((current_p->pci_bus == temp_p->pci_bus) &&
(current_p->pci_device_fn == temp_p->pci_device_fn)) ||
#endif
if (temp_p->features & AHC_ULTRA2)
{
- if (aic_inb(temp_p, DSCOMMAND0) & RAMPSM_ULTRA2)
+ if ( (aic_inb(temp_p, DSCOMMAND0) & RAMPSM_ULTRA2) &&
+ (aic7xxx_scbram) )
{
aic_outb(temp_p,
aic_inb(temp_p, DSCOMMAND0) & ~SCBRAMSEL_ULTRA2,
temp_p->flags |= AHC_EXTERNAL_SRAM;
devconfig |= EXTSCBPEN;
}
+ else if (aic_inb(temp_p, DSCOMMAND0) & RAMPSM_ULTRA2)
+ {
+ printk(KERN_INFO "aic7xxx: <%s> at PCI %d/%d\n",
+ board_names[aic_pdevs[i].board_name_index],
+ PCI_SLOT(temp_p->pci_device_fn),
+ PCI_FUNC(temp_p->pci_device_fn));
+ printk("aic7xxx: external SCB RAM detected, "
+ "but not enabled\n");
+ }
}
- else if (devconfig & RAMPSM)
+ else
{
- devconfig &= ~SCBRAMSEL;
- devconfig |= EXTSCBPEN;
- temp_p->flags |= AHC_EXTERNAL_SRAM;
+ if ((devconfig & RAMPSM) && (aic7xxx_scbram))
+ {
+ devconfig &= ~SCBRAMSEL;
+ devconfig |= EXTSCBPEN;
+ temp_p->flags |= AHC_EXTERNAL_SRAM;
+ }
+ else if (devconfig & RAMPSM)
+ {
+ printk(KERN_INFO "aic7xxx: <%s> at PCI %d/%d\n",
+ board_names[aic_pdevs[i].board_name_index],
+ PCI_SLOT(temp_p->pci_device_fn),
+ PCI_FUNC(temp_p->pci_device_fn));
+ printk("aic7xxx: external SCB RAM detected, "
+ "but not enabled\n");
+ }
}
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,1,92)
pci_write_config_dword(pdev, DEVCONFIG, devconfig);
if ( aic7xxx_scb_on_qoutfifo(p, scb) )
{
if(aic7xxx_verbose & VERBOSE_RESET_RETURN)
- printk(INFO_LEAD "SCB on qoutfifo, returning.\n", p->host_no,
+ printk(INFO_LEAD "SCB on qoutfifo, completing.\n", p->host_no,
CTL_OF_SCB(scb));
- aic7xxx_run_done_queue(p, TRUE);
+ if ((aic_inb(p,INTSTAT) & CMDCMPLT) == 0)
+ printk(INFO_LEAD "missed CMDCMPLT interrupt!\n", p->host_no,
+ CTL_OF_SCB(scb));
+ aic7xxx_handle_command_completion_intr(p);
+ aic7xxx_done_cmds_complete(p);
aic7xxx_run_waiting_queues(p);
unpause_sequencer(p, FALSE);
DRIVER_UNLOCK
- return(SCSI_RESET_NOT_RUNNING);
+ return(SCSI_RESET_SUCCESS);
}
if ( flags & SCSI_RESET_SUGGEST_HOST_RESET )
{