// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) by Jaroslav Kysela <perex@perex.cz> * Routines for control of CS4231(A)/CS4232/InterWave & compatible chips * * Bugs: * - sometimes record brokes playback with WSS portion of * Yamaha OPL3-SA3 chip * - CS4231 (GUS MAX) - still trouble with occasional noises * - broken initialization?
*/
MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
MODULE_DESCRIPTION("Routines for control of CS4231(A)/CS4232/InterWave & compatible chips");
MODULE_LICENSE("GPL");
staticvoid snd_wss_debug(struct snd_wss *chip)
{
dev_dbg(chip->card->dev, "CS4231 REGS: INDEX = 0x%02x " " STATUS = 0x%02x\n",
wss_inb(chip, CS4231P(REGSEL)),
wss_inb(chip, CS4231P(STATUS)));
dev_dbg(chip->card->dev, " 0x00: left input = 0x%02x " " 0x10: alt 1 (CFIG 2) = 0x%02x\n",
snd_wss_in(chip, 0x00),
snd_wss_in(chip, 0x10));
dev_dbg(chip->card->dev, " 0x01: right input = 0x%02x " " 0x11: alt 2 (CFIG 3) = 0x%02x\n",
snd_wss_in(chip, 0x01),
snd_wss_in(chip, 0x11));
dev_dbg(chip->card->dev, " 0x02: GF1 left input = 0x%02x " " 0x12: left line in = 0x%02x\n",
snd_wss_in(chip, 0x02),
snd_wss_in(chip, 0x12));
dev_dbg(chip->card->dev, " 0x03: GF1 right input = 0x%02x " " 0x13: right line in = 0x%02x\n",
snd_wss_in(chip, 0x03),
snd_wss_in(chip, 0x13));
dev_dbg(chip->card->dev, " 0x04: CD left input = 0x%02x " " 0x14: timer low = 0x%02x\n",
snd_wss_in(chip, 0x04),
snd_wss_in(chip, 0x14));
dev_dbg(chip->card->dev, " 0x05: CD right input = 0x%02x " " 0x15: timer high = 0x%02x\n",
snd_wss_in(chip, 0x05),
snd_wss_in(chip, 0x15));
dev_dbg(chip->card->dev, " 0x06: left output = 0x%02x " " 0x16: left MIC (PnP) = 0x%02x\n",
snd_wss_in(chip, 0x06),
snd_wss_in(chip, 0x16));
dev_dbg(chip->card->dev, " 0x07: right output = 0x%02x " " 0x17: right MIC (PnP) = 0x%02x\n",
snd_wss_in(chip, 0x07),
snd_wss_in(chip, 0x17));
dev_dbg(chip->card->dev, " 0x08: playback format = 0x%02x " " 0x18: IRQ status = 0x%02x\n",
snd_wss_in(chip, 0x08),
snd_wss_in(chip, 0x18));
dev_dbg(chip->card->dev, " 0x09: iface (CFIG 1) = 0x%02x " " 0x19: left line out = 0x%02x\n",
snd_wss_in(chip, 0x09),
snd_wss_in(chip, 0x19));
dev_dbg(chip->card->dev, " 0x0a: pin control = 0x%02x " " 0x1a: mono control = 0x%02x\n",
snd_wss_in(chip, 0x0a),
snd_wss_in(chip, 0x1a));
dev_dbg(chip->card->dev, " 0x0b: init & status = 0x%02x " " 0x1b: right line out = 0x%02x\n",
snd_wss_in(chip, 0x0b),
snd_wss_in(chip, 0x1b));
dev_dbg(chip->card->dev, " 0x0c: revision & mode = 0x%02x " " 0x1c: record format = 0x%02x\n",
snd_wss_in(chip, 0x0c),
snd_wss_in(chip, 0x1c));
dev_dbg(chip->card->dev, " 0x0d: loopback = 0x%02x " " 0x1d: var freq (PnP) = 0x%02x\n",
snd_wss_in(chip, 0x0d),
snd_wss_in(chip, 0x1d));
dev_dbg(chip->card->dev, " 0x0e: ply upr count = 0x%02x " " 0x1e: ply lwr count = 0x%02x\n",
snd_wss_in(chip, 0x0e),
snd_wss_in(chip, 0x1e));
dev_dbg(chip->card->dev, " 0x0f: rec upr count = 0x%02x " " 0x1f: rec lwr count = 0x%02x\n",
snd_wss_in(chip, 0x0f),
snd_wss_in(chip, 0x1f));
}
#endif
/* * CS4231 detection / MCE routines
*/
staticvoid snd_wss_busy_wait(struct snd_wss *chip)
{ int timeout;
/* huh.. looks like this sequence is proper for CS4231A chip (GUS MAX) */ for (timeout = 5; timeout > 0; timeout--)
wss_inb(chip, CS4231P(REGSEL)); /* end of cleanup sequence */ for (timeout = 25000;
timeout > 0 && (wss_inb(chip, CS4231P(REGSEL)) & CS4231_INIT);
timeout--)
udelay(10);
}
void snd_wss_mce_up(struct snd_wss *chip)
{ unsignedlong flags; int timeout;
snd_wss_wait(chip); #ifdef CONFIG_SND_DEBUG if (wss_inb(chip, CS4231P(REGSEL)) & CS4231_INIT)
dev_dbg(chip->card->dev, "mce_up - auto calibration time out (0)\n"); #endif
spin_lock_irqsave(&chip->reg_lock, flags);
chip->mce_bit |= CS4231_MCE;
timeout = wss_inb(chip, CS4231P(REGSEL)); if (timeout == 0x80)
dev_dbg(chip->card->dev, "mce_up [0x%lx]: serious init problem - codec still busy\n",
chip->port); if (!(timeout & CS4231_MCE))
wss_outb(chip, CS4231P(REGSEL),
chip->mce_bit | (timeout & 0x1f));
spin_unlock_irqrestore(&chip->reg_lock, flags);
}
EXPORT_SYMBOL(snd_wss_mce_up);
void snd_wss_mce_down(struct snd_wss *chip)
{ unsignedlong flags; unsignedlong end_time; int timeout; int hw_mask = WSS_HW_CS4231_MASK | WSS_HW_CS4232_MASK | WSS_HW_AD1848;
snd_wss_busy_wait(chip);
#ifdef CONFIG_SND_DEBUG if (wss_inb(chip, CS4231P(REGSEL)) & CS4231_INIT)
dev_dbg(chip->card->dev, "mce_down [0x%lx] - auto calibration time out (0)\n",
(long)CS4231P(REGSEL)); #endif
spin_lock_irqsave(&chip->reg_lock, flags);
chip->mce_bit &= ~CS4231_MCE;
timeout = wss_inb(chip, CS4231P(REGSEL));
wss_outb(chip, CS4231P(REGSEL), chip->mce_bit | (timeout & 0x1f));
spin_unlock_irqrestore(&chip->reg_lock, flags); if (timeout == 0x80)
dev_dbg(chip->card->dev, "mce_down [0x%lx]: serious init problem - codec still busy\n",
chip->port); if ((timeout & CS4231_MCE) == 0 || !(chip->hardware & hw_mask)) return;
/* * Wait for (possible -- during init auto-calibration may not be set) * calibration process to start. Needs up to 5 sample periods on AD1848 * which at the slowest possible rate of 5.5125 kHz means 907 us.
*/
msleep(1);
/* check condition up to 100 ms */
end_time = jiffies + msecs_to_jiffies(100); while (wss_inb(chip, CS4231P(REGSEL)) & CS4231_INIT) { if (time_after(jiffies, end_time)) {
dev_err(chip->card->dev, "mce_down - auto calibration time out (3)\n"); return;
}
msleep(1);
}
mutex_lock(&chip->mce_mutex); if (chip->hardware == WSS_HW_CS4231A ||
(chip->hardware & WSS_HW_CS4232_MASK)) {
spin_lock_irqsave(&chip->reg_lock, flags); if ((chip->image[CS4231_PLAYBK_FORMAT] & 0x0f) == (pdfr & 0x0f)) { /* rate is same? */
snd_wss_out(chip, CS4231_ALT_FEATURE_1,
chip->image[CS4231_ALT_FEATURE_1] | 0x10);
chip->image[CS4231_PLAYBK_FORMAT] = pdfr;
snd_wss_out(chip, CS4231_PLAYBK_FORMAT,
chip->image[CS4231_PLAYBK_FORMAT]);
snd_wss_out(chip, CS4231_ALT_FEATURE_1,
chip->image[CS4231_ALT_FEATURE_1] &= ~0x10);
udelay(100); /* Fixes audible clicks at least on GUS MAX */
full_calib = 0;
}
spin_unlock_irqrestore(&chip->reg_lock, flags);
} elseif (chip->hardware == WSS_HW_AD1845) { unsigned rate = params_rate(params);
/* * Program the AD1845 correctly for the playback stream. * Note that we do NOT need to toggle the MCE bit because * the PLAYBACK_ENABLE bit of the Interface Configuration * register is set. * * NOTE: We seem to need to write to the MSB before the LSB * to get the correct sample frequency.
*/
spin_lock_irqsave(&chip->reg_lock, flags);
snd_wss_out(chip, CS4231_PLAYBK_FORMAT, (pdfr & 0xf0));
snd_wss_out(chip, AD1845_UPR_FREQ_SEL, (rate >> 8) & 0xff);
snd_wss_out(chip, AD1845_LWR_FREQ_SEL, rate & 0xff);
full_calib = 0;
spin_unlock_irqrestore(&chip->reg_lock, flags);
} if (full_calib) {
snd_wss_mce_up(chip);
spin_lock_irqsave(&chip->reg_lock, flags); if (chip->hardware != WSS_HW_INTERWAVE && !chip->single_dma) { if (chip->image[CS4231_IFACE_CTRL] & CS4231_RECORD_ENABLE)
pdfr = (pdfr & 0xf0) |
(chip->image[CS4231_REC_FORMAT] & 0x0f);
} else {
chip->image[CS4231_PLAYBK_FORMAT] = pdfr;
}
snd_wss_out(chip, CS4231_PLAYBK_FORMAT, pdfr);
spin_unlock_irqrestore(&chip->reg_lock, flags); if (chip->hardware == WSS_HW_OPL3SA2)
udelay(100); /* this seems to help */
snd_wss_mce_down(chip);
}
mutex_unlock(&chip->mce_mutex);
}
/* * Program the AD1845 correctly for the capture stream. * Note that we do NOT need to toggle the MCE bit because * the PLAYBACK_ENABLE bit of the Interface Configuration * register is set. * * NOTE: We seem to need to write to the MSB before the LSB * to get the correct sample frequency.
*/
spin_lock_irqsave(&chip->reg_lock, flags);
snd_wss_out(chip, CS4231_REC_FORMAT, (cdfr & 0xf0));
snd_wss_out(chip, AD1845_UPR_FREQ_SEL, (rate >> 8) & 0xff);
snd_wss_out(chip, AD1845_LWR_FREQ_SEL, rate & 0xff);
full_calib = 0;
spin_unlock_irqrestore(&chip->reg_lock, flags);
} if (full_calib) {
snd_wss_mce_up(chip);
spin_lock_irqsave(&chip->reg_lock, flags); if (chip->hardware != WSS_HW_INTERWAVE &&
!(chip->image[CS4231_IFACE_CTRL] & CS4231_PLAYBACK_ENABLE)) { if (chip->single_dma)
snd_wss_out(chip, CS4231_PLAYBK_FORMAT, cdfr); else
snd_wss_out(chip, CS4231_PLAYBK_FORMAT,
(chip->image[CS4231_PLAYBK_FORMAT] & 0xf0) |
(cdfr & 0x0f));
spin_unlock_irqrestore(&chip->reg_lock, flags);
snd_wss_mce_down(chip);
snd_wss_mce_up(chip);
spin_lock_irqsave(&chip->reg_lock, flags);
} if (chip->hardware & WSS_HW_AD1848_MASK)
snd_wss_out(chip, CS4231_PLAYBK_FORMAT, cdfr); else
snd_wss_out(chip, CS4231_REC_FORMAT, cdfr);
spin_unlock_irqrestore(&chip->reg_lock, flags);
snd_wss_mce_down(chip);
}
mutex_unlock(&chip->mce_mutex);
}
spin_lock_irqsave(&chip->reg_lock, flags);
res = snd_wss_in(chip, CS4231_TEST_INIT);
spin_unlock_irqrestore(&chip->reg_lock, flags); if (res & (0x08 | 0x02)) /* detect overrange only above 0dB; may be user selectable? */
chip->capture_substream->runtime->overrange++;
}
EXPORT_SYMBOL(snd_wss_overrange);
if (chip->hardware & WSS_HW_AD1848_MASK) /* pretend it was the only possible irq for AD1848 */
status = CS4231_PLAYBACK_IRQ; else
status = snd_wss_in(chip, CS4231_IRQ_STATUS); if (status & CS4231_TIMER_IRQ) { if (chip->timer)
snd_timer_interrupt(chip->timer, chip->timer->sticks);
} if (chip->single_dma && chip->hardware != WSS_HW_INTERWAVE) { if (status & CS4231_PLAYBACK_IRQ) { if (chip->mode & WSS_MODE_PLAY) { if (chip->playback_substream)
snd_pcm_period_elapsed(chip->playback_substream);
} if (chip->mode & WSS_MODE_RECORD) { if (chip->capture_substream) {
snd_wss_overrange(chip);
snd_pcm_period_elapsed(chip->capture_substream);
}
}
}
} else { if (status & CS4231_PLAYBACK_IRQ) { if (chip->playback_substream)
snd_pcm_period_elapsed(chip->playback_substream);
} if (status & CS4231_RECORD_IRQ) { if (chip->capture_substream) {
snd_wss_overrange(chip);
snd_pcm_period_elapsed(chip->capture_substream);
}
}
}
if ((chip->hardware & WSS_HW_TYPE_MASK) != WSS_HW_DETECT) goto out;
if (hardware) {
chip->hardware = hardware; goto out;
}
r = snd_wss_in(chip, CS4231_MISC_INFO);
/* set CS423x MODE 2 */
snd_wss_dout(chip, CS4231_MISC_INFO, CS4231_MODE2); for (i = 0; i < 16; i++) { if (snd_wss_in(chip, i) != snd_wss_in(chip, 16 + i)) { /* we have more than 16 registers: check ID */ if ((r & 0xf) != 0xa) goto out_mode; /* * on CMI8330, CS4231_VERSION is volume control and * can be set to 0
*/
snd_wss_dout(chip, CS4231_VERSION, 0);
r = snd_wss_in(chip, CS4231_VERSION) & 0xe7; if (!r)
chip->hardware = WSS_HW_CMI8330; goto out_mode;
}
} if (r & 0x80)
chip->hardware = WSS_HW_CS4248; else
chip->hardware = WSS_HW_AD1848;
out_mode:
snd_wss_dout(chip, CS4231_MISC_INFO, 0);
out:
spin_unlock_irqrestore(&chip->reg_lock, flags); return err;
}
staticint snd_wss_probe(struct snd_wss *chip)
{ unsignedlong flags; int i, id, rev, regnum; unsignedchar *ptr; unsignedint hw;
id = snd_ad1848_probe(chip); if (id < 0) return id;
hw = chip->hardware; if ((hw & WSS_HW_TYPE_MASK) == WSS_HW_DETECT) { for (i = 0; i < 50; i++) {
mb(); if (wss_inb(chip, CS4231P(REGSEL)) & CS4231_INIT)
msleep(2); else {
spin_lock_irqsave(&chip->reg_lock, flags);
snd_wss_out(chip, CS4231_MISC_INFO,
CS4231_MODE2);
id = snd_wss_in(chip, CS4231_MISC_INFO) & 0x0f;
spin_unlock_irqrestore(&chip->reg_lock, flags); if (id == 0x0a) break; /* this is valid value */
}
}
dev_dbg(chip->card->dev, "wss: port = 0x%lx, id = 0x%x\n",
chip->port, id); if (id != 0x0a) return -ENODEV; /* no valid device found */
if (!(chip->hardware & WSS_HW_AD1848_MASK))
chip->image[CS4231_MISC_INFO] = CS4231_MODE2; switch (chip->hardware) { case WSS_HW_INTERWAVE:
chip->image[CS4231_MISC_INFO] = CS4231_IW_MODE3; break; case WSS_HW_CS4235: case WSS_HW_CS4236B: case WSS_HW_CS4237B: case WSS_HW_CS4238B: case WSS_HW_CS4239: if (hw == WSS_HW_DETECT3)
chip->image[CS4231_MISC_INFO] = CS4231_4236_MODE3; else
chip->hardware = WSS_HW_CS4236; break;
}
/* lowlevel resume callback for CS4231 */ staticvoid snd_wss_resume(struct snd_wss *chip)
{ int reg; unsignedlong flags; /* int timeout; */
if (chip->thinkpad_flag)
snd_wss_thinkpad_twiddle(chip, 1);
snd_wss_mce_up(chip);
spin_lock_irqsave(&chip->reg_lock, flags); for (reg = 0; reg < 32; reg++) { switch (reg) { case CS4231_VERSION: break; default:
snd_wss_out(chip, reg, chip->image[reg]); break;
}
} /* Yamaha needs this to resume properly */ if (chip->hardware == WSS_HW_OPL3SA2)
snd_wss_out(chip, CS4231_PLAYBK_FORMAT,
chip->image[CS4231_PLAYBK_FORMAT]);
spin_unlock_irqrestore(&chip->reg_lock, flags); #if1
snd_wss_mce_down(chip); #else /* The following is a workaround to avoid freeze after resume on TP600E. This is the first half of copy of snd_wss_mce_down(), but doesn't include rescheduling. -- iwai
*/
snd_wss_busy_wait(chip);
spin_lock_irqsave(&chip->reg_lock, flags);
chip->mce_bit &= ~CS4231_MCE;
timeout = wss_inb(chip, CS4231P(REGSEL));
wss_outb(chip, CS4231P(REGSEL), chip->mce_bit | (timeout & 0x1f));
spin_unlock_irqrestore(&chip->reg_lock, flags); if (timeout == 0x80)
dev_err(chip->card->dev "down [0x%lx]: serious init problem - codec still busy\n",
chip->port); if ((timeout & CS4231_MCE) == 0 ||
!(chip->hardware & (WSS_HW_CS4231_MASK | WSS_HW_CS4232_MASK))) { return;
}
snd_wss_busy_wait(chip); #endif
} #endif/* CONFIG_PM */
constchar *snd_wss_chip_id(struct snd_wss *chip)
{ switch (chip->hardware) { case WSS_HW_CS4231: return"CS4231"; case WSS_HW_CS4231A: return"CS4231A"; case WSS_HW_CS4232: return"CS4232"; case WSS_HW_CS4232A: return"CS4232A"; case WSS_HW_CS4235: return"CS4235"; case WSS_HW_CS4236: return"CS4236"; case WSS_HW_CS4236B: return"CS4236B"; case WSS_HW_CS4237B: return"CS4237B"; case WSS_HW_CS4238B: return"CS4238B"; case WSS_HW_CS4239: return"CS4239"; case WSS_HW_INTERWAVE: return"AMD InterWave"; case WSS_HW_OPL3SA2: return chip->card->shortname; case WSS_HW_AD1845: return"AD1845"; case WSS_HW_OPTI93X: return"OPTi 93x"; case WSS_HW_AD1847: return"AD1847"; case WSS_HW_AD1848: return"AD1848"; case WSS_HW_CS4248: return"CS4248"; case WSS_HW_CMI8330: return"CMI8330/C3D"; default: return"???";
}
}
EXPORT_SYMBOL(snd_wss_chip_id);
/* global setup */ if (snd_wss_probe(chip) < 0) return -ENODEV;
snd_wss_init(chip);
#if0 if (chip->hardware & WSS_HW_CS4232_MASK) { if (chip->res_cport == NULL)
dev_err(chip->card->dev, "CS4232 control port features are not accessible\n");
} #endif
int snd_wss_mixer(struct snd_wss *chip)
{ struct snd_card *card; unsignedint idx; int err; int count = ARRAY_SIZE(snd_wss_controls);
if (snd_BUG_ON(!chip || !chip->pcm)) return -EINVAL;
card = chip->card;
strscpy(card->mixername, chip->pcm->name);
/* Use only the first 11 entries on AD1848 */ if (chip->hardware & WSS_HW_AD1848_MASK)
count = 11; /* There is no loopback on OPTI93X */ elseif (chip->hardware == WSS_HW_OPTI93X)
count = 9;
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.31Bemerkung:
(vorverarbeitet am 2026-06-08)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.