audiocmi.c revision af145792def4317aeeb9d20f7772b32a35a0161f
5205N/A/*
5205N/A * CDDL HEADER START
5205N/A *
5205N/A * The contents of this file are subject to the terms of the
5205N/A * Common Development and Distribution License (the "License").
5205N/A * You may not use this file except in compliance with the License.
5205N/A *
5205N/A * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
5205N/A * or http://www.opensolaris.org/os/licensing.
5205N/A * See the License for the specific language governing permissions
5205N/A * and limitations under the License.
5205N/A *
5205N/A * When distributing Covered Code, include this CDDL HEADER in each
5205N/A * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
5205N/A * If applicable, add the following below this CDDL HEADER, with the
5205N/A * fields enclosed by brackets "[]" replaced with your own identifying
5205N/A * information: Portions Copyright [yyyy] [name of copyright owner]
5205N/A *
5205N/A * CDDL HEADER END
5205N/A */
5205N/A/*
5205N/A * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
5205N/A * Use is subject to license terms.
5205N/A */
5205N/A/*
6226N/A * Purpose: Driver for CMEDIA CM8738 PCI audio controller.
5205N/A */
5205N/A/*
5205N/A * This file is part of Open Sound System
5754N/A *
5205N/A * Copyright (C) 4Front Technologies 1996-2008.
5205N/A */
5205N/A
5754N/A#include <sys/audio/audio_driver.h>
6678N/A#include <sys/note.h>
5205N/A#include <sys/pci.h>
5205N/A#include <sys/sysmacros.h>
5205N/A#include "audiocmi.h"
5205N/A
5205N/A/*
5221N/A * Note: The original 4Front driver had support SPDIF and dual dac
5205N/A * options. Dual dac support is probably not terribly useful. SPDIF
6678N/A * on the other hand might be quite useful, we just don't have a card
6678N/A * that supports it at present. Some variants of the chip are also
6678N/A * capable of jack retasking, but we're electing to punt on supporting
6678N/A * that as well, for now (we don't have any cards that would benefit
6678N/A * from this feature.)
5205N/A *
5205N/A * Note that surround support requires the use of the second DMA
5205N/A * engine, and that the same second DMA engine is the only way one can
5205N/A * capture from SPDIF. Rather than support a lot more complexity in
5205N/A * the driver, we we will probably just punt on ever supporting
5205N/A * capture of SPDIF. (SPDIF playback should be doable, however.)
5205N/A *
5205N/A * Adding back support for the advanced features would be an
5205N/A * interesting project for someone with access to suitable hardware.
5205N/A *
5205N/A * Note that each variant (CMI 8338, 8738-033, -037, -055, and 8768)
5205N/A * seems to have significant differences in some of the registers.
5205N/A * While programming these parts for basic stereo is pretty much the
5205N/A * same on all parts, doing anything more than that can be
5205N/A * sigificantly different for each part.
5205N/A */
5205N/A
5205N/Astatic ddi_device_acc_attr_t acc_attr = {
5205N/A DDI_DEVICE_ATTR_V0,
5205N/A DDI_STRUCTURE_LE_ACC,
5205N/A DDI_STRICTORDER_ACC
5753N/A};
5205N/A
5205N/Astatic ddi_device_acc_attr_t buf_attr = {
5205N/A DDI_DEVICE_ATTR_V0,
6415N/A DDI_NEVERSWAP_ACC,
5205N/A DDI_STRICTORDER_ACC
5205N/A};
5205N/A
5205N/Astatic ddi_dma_attr_t dma_attr = {
5205N/A DMA_ATTR_VERSION, /* dma_attr_version */
5205N/A 0x0, /* dma_attr_addr_lo */
5205N/A 0xffffffffU, /* dma_attr_addr_hi */
5753N/A 0x3ffff, /* dma_attr_count_max */
5205N/A 0x8, /* dma_attr_align */
5754N/A 0x7f, /* dma_attr_burstsizes */
5754N/A 0x1, /* dma_attr_minxfer */
5754N/A 0x3ffff, /* dma_attr_maxxfer */
5754N/A 0x3ffff, /* dma_attr_seg */
5754N/A 0x1, /* dma_attr_sgllen */
6415N/A 0x1, /* dma_attr_granular */
6417N/A 0 /* dma_attr_flags */
6678N/A};
6678N/A
6678N/Astatic uint_t
6678N/Acmpci_intr(caddr_t arg1, caddr_t arg2)
6678N/A{
6678N/A cmpci_dev_t *dev = (void *)arg1;
6678N/A
5221N/A uint32_t intstat, intctrl, intclear;
5221N/A void (*cb0)(audio_engine_t *) = NULL;
5221N/A void (*cb1)(audio_engine_t *) = NULL;
6415N/A uint_t rv;
6678N/A
5221N/A _NOTE(ARGUNUSED(arg2));
5221N/A
5221N/A rv = DDI_INTR_UNCLAIMED;
5221N/A
6678N/A mutex_enter(&dev->mutex);
5221N/A if (dev->suspended) {
5221N/A mutex_exit(&dev->mutex);
5221N/A return (rv);
5221N/A }
5221N/A
5221N/A intclear = 0;
5221N/A intstat = GET32(dev, REG_INTSTAT);
5221N/A intctrl = GET32(dev, REG_INTCTRL);
5205N/A if ((intstat & INTSTAT_CH0_INT) && (intctrl & INTCTRL_CH0_EN)) {
5205N/A intclear |= INTCTRL_CH0_EN;
5205N/A cb0 = dev->port[0].callb;
5205N/A }
6678N/A if ((intstat & INTSTAT_CH1_INT) && (intctrl & INTCTRL_CH1_EN)) {
6678N/A intclear |= INTCTRL_CH1_EN;
5205N/A cb1 = dev->port[1].callb;
5205N/A }
5205N/A
5205N/A /* toggle the bits that we are going to handle */
5205N/A if (intclear) {
5205N/A CLR32(dev, REG_INTCTRL, intclear);
5205N/A SET32(dev, REG_INTCTRL, intclear);
5205N/A rv = DDI_INTR_CLAIMED;
5205N/A
5205N/A KSINTR(dev)->intrs[KSTAT_INTR_HARD]++;
5205N/A }
5205N/A
5205N/A mutex_exit(&dev->mutex);
5205N/A
5205N/A if (cb0) {
5205N/A (*cb0)(dev->port[0].engine);
5205N/A }
6678N/A if (cb1) {
5205N/A (*cb1)(dev->port[1].engine);
6678N/A }
5205N/A
5205N/A return (rv);
5205N/A}
6678N/A
6678N/Astatic void
5205N/Acmpci_reset_port(cmpci_port_t *port)
5205N/A{
5205N/A cmpci_dev_t *dev = port->dev;
5205N/A
5205N/A if (dev->suspended)
5205N/A return;
5205N/A
5205N/A port->offset = 0;
6415N/A
6678N/A /* reset channel */
5205N/A SET32(dev, REG_FUNCTRL0, port->fc0_rst_bit);
6415N/A drv_usecwait(10);
6678N/A CLR32(dev, REG_FUNCTRL0, port->fc0_rst_bit);
5205N/A drv_usecwait(10);
6415N/A
6415N/A /* Set 48k 16-bit stereo -- these are just with all bits set. */
6415N/A SET32(dev, REG_FUNCTRL1, port->fc1_rate_mask);
6415N/A SET32(dev, REG_CHFORMAT, port->chformat_mask);
5205N/A
5205N/A if ((port->num == 1) && (dev->maxch > 2)) {
5205N/A CLR32(dev, REG_LEGACY, LEGACY_NXCHG);
5205N/A
5205N/A if (port->nchan > 2) {
5205N/A SET32(dev, REG_MISC, MISC_XCHGDAC);
5205N/A CLR32(dev, REG_MISC, MISC_N4SPK3D);
5205N/A } else {
5205N/A CLR32(dev, REG_MISC, MISC_XCHGDAC);
5205N/A SET32(dev, REG_MISC, MISC_N4SPK3D);
5221N/A }
5205N/A
5205N/A switch (port->nchan) {
5205N/A case 2:
5205N/A if (dev->maxch >= 8) {
5205N/A CLR8(dev, REG_MISC2, MISC2_CHB3D8C);
5205N/A }
5205N/A if (dev->maxch >= 6) {
5205N/A CLR32(dev, REG_CHFORMAT, CHFORMAT_CHB3D5C);
5205N/A CLR32(dev, REG_LEGACY, LEGACY_CHB3D6C);
5205N/A }
5205N/A if (dev->maxch >= 4) {
5205N/A CLR32(dev, REG_CHFORMAT, CHFORMAT_CHB3D);
5205N/A }
5205N/A break;
5205N/A case 4:
5205N/A if (dev->maxch >= 8) {
5205N/A CLR8(dev, REG_MISC2, MISC2_CHB3D8C);
6678N/A }
5205N/A if (dev->maxch >= 6) {
5205N/A CLR32(dev, REG_CHFORMAT, CHFORMAT_CHB3D5C);
5205N/A CLR32(dev, REG_LEGACY, LEGACY_CHB3D6C);
5205N/A CLR32(dev, REG_MISC, MISC_ENCENTER);
5205N/A CLR32(dev, REG_LEGACY, LEGACY_EXBASSEN);
5205N/A }
5205N/A SET32(dev, REG_CHFORMAT, CHFORMAT_CHB3D);
5205N/A break;
5205N/A case 6:
5205N/A if (dev->maxch >= 8) {
5205N/A CLR8(dev, REG_MISC2, MISC2_CHB3D8C);
6678N/A }
5205N/A SET32(dev, REG_CHFORMAT, CHFORMAT_CHB3D5C);
5205N/A SET32(dev, REG_LEGACY, LEGACY_CHB3D6C);
5205N/A CLR32(dev, REG_MISC, MISC_ENCENTER);
5205N/A CLR32(dev, REG_LEGACY, LEGACY_EXBASSEN);
5205N/A CLR32(dev, REG_CHFORMAT, CHFORMAT_CHB3D);
5205N/A break;
5205N/A
5205N/A case 8:
5205N/A SET8(dev, REG_MISC2, MISC2_CHB3D8C);
5205N/A CLR32(dev, REG_MISC, MISC_ENCENTER);
6415N/A CLR32(dev, REG_LEGACY, LEGACY_EXBASSEN);
6415N/A CLR32(dev, REG_CHFORMAT, CHFORMAT_CHB3D5C);
6415N/A CLR32(dev, REG_LEGACY, LEGACY_CHB3D6C);
6415N/A CLR32(dev, REG_CHFORMAT, CHFORMAT_CHB3D);
6415N/A break;
6415N/A }
6415N/A }
6415N/A
6415N/A PUT32(dev, port->reg_paddr, port->paddr);
6415N/A PUT16(dev, port->reg_bufsz, (port->bufsz / 4) - 1);
6415N/A PUT16(dev, port->reg_fragsz, (port->fragfr * port->nchan / 2) - 1);
5205N/A
6415N/A /* Analog output */
6415N/A if (port->capture) {
5205N/A /* Analog capture */
5205N/A SET32(dev, REG_FUNCTRL0, port->fc0_rec_bit);
5205N/A } else {
5205N/A CLR32(dev, REG_FUNCTRL0, port->fc0_rec_bit);
5205N/A }
5205N/A}
6678N/A
5205N/Astatic void
5205N/Acmpci_start_port(cmpci_port_t *port)
5205N/A{
5205N/A cmpci_dev_t *dev = port->dev;
5205N/A
5205N/A if (dev->suspended)
5205N/A return;
5205N/A
5205N/A SET32(dev, REG_FUNCTRL0, port->fc0_en_bit);
5205N/A SET32(dev, REG_INTCTRL, port->int_en_bit);
5205N/A}
5205N/A
5205N/Astatic void
5205N/Acmpci_stop_port(cmpci_port_t *port)
5205N/A{
5205N/A cmpci_dev_t *dev = port->dev;
5205N/A
6678N/A if (dev->suspended)
5205N/A return;
5205N/A
5205N/A CLR32(dev, REG_FUNCTRL0, port->fc0_en_bit);
5205N/A CLR32(dev, REG_INTCTRL, port->int_en_bit);
5205N/A}
5205N/A
5205N/Astatic int
5221N/Acmpci_open(void *arg, int flag, uint_t *fragfrp, uint_t *nfp, caddr_t *bufp)
6415N/A{
5205N/A cmpci_port_t *port = arg;
5205N/A cmpci_dev_t *dev = port->dev;
5205N/A
5205N/A _NOTE(ARGUNUSED(flag));
5205N/A
5205N/A mutex_enter(&dev->mutex);
5205N/A
5205N/A *fragfrp = port->fragfr;
5205N/A *nfp = port->nfrags;
5205N/A *bufp = port->kaddr;
5205N/A
5205N/A port->count = 0;
5205N/A port->open = B_TRUE;
5205N/A
5205N/A cmpci_reset_port(port);
5205N/A cmpci_start_port(port);
6678N/A
6678N/A mutex_exit(&dev->mutex);
5205N/A return (0);
5205N/A}
5205N/A
5205N/Astatic void
5205N/Acmpci_close(void *arg)
5205N/A{
5221N/A cmpci_port_t *port = arg;
5205N/A cmpci_dev_t *dev = port->dev;
5205N/A
5205N/A mutex_enter(&dev->mutex);
5205N/A port->open = B_FALSE;
5205N/A cmpci_stop_port(port);
6415N/A mutex_exit(&dev->mutex);
5205N/A}
5205N/A
5205N/Astatic void
5205N/Acmpci_update_port(cmpci_port_t *port)
5205N/A{
5205N/A cmpci_dev_t *dev = port->dev;
5205N/A uint32_t count;
5205N/A uint32_t offset;
5205N/A
5205N/A if ((dev->suspended) || (!port->open))
6678N/A return;
5205N/A
5205N/A /* this gives us the offset in dwords */
5205N/A offset = (port->bufsz / 4) - (GET16(dev, port->reg_bufsz) + 1);
5205N/A
5205N/A /* check for wrap - note that the count is given in dwords */
5205N/A if (offset < port->offset) {
5205N/A count = ((port->bufsz / 4) - port->offset) + offset;
5205N/A } else {
5205N/A count = offset - port->offset;
5205N/A }
5205N/A port->count += count;
5205N/A port->offset = offset;
5205N/A}
5205N/A
5205N/Astatic uint64_t
5205N/Acmpci_count(void *arg)
5205N/A{
5205N/A cmpci_port_t *port = arg;
5205N/A cmpci_dev_t *dev = port->dev;
5205N/A uint64_t count;
5753N/A
5205N/A mutex_enter(&dev->mutex);
6678N/A cmpci_update_port(port);
5205N/A
6678N/A /* the count is in dwords */
6678N/A count = port->count;
6415N/A
5205N/A mutex_exit(&dev->mutex);
5205N/A
5205N/A /*
5205N/A * convert dwords to frames - unfortunately this requires a
5205N/A * divide
5205N/A */
5205N/A return (count / (port->nchan / 2));
5205N/A}
6415N/A
6415N/A
6415N/Astatic int
5205N/Acmpci_setup_interrupts(cmpci_dev_t *dev)
6678N/A{
6415N/A int actual;
6415N/A uint_t ipri;
6415N/A
6415N/A if ((ddi_intr_alloc(dev->dip, &dev->ihandle, DDI_INTR_TYPE_FIXED,
6415N/A 0, 1, &actual, DDI_INTR_ALLOC_NORMAL) != DDI_SUCCESS) ||
5205N/A (actual != 1)) {
6678N/A audio_dev_warn(dev->adev, "can't alloc intr handle");
5205N/A return (DDI_FAILURE);
5205N/A }
5753N/A
5205N/A if (ddi_intr_get_pri(dev->ihandle, &ipri) != DDI_SUCCESS) {
6678N/A audio_dev_warn(dev->adev, "can't determine intr priority");
6415N/A (void) ddi_intr_free(dev->ihandle);
5205N/A dev->ihandle = NULL;
5205N/A return (DDI_FAILURE);
5205N/A }
5205N/A
5205N/A if (ddi_intr_add_handler(dev->ihandle, cmpci_intr, dev,
5205N/A NULL) != DDI_SUCCESS) {
5205N/A audio_dev_warn(dev->adev, "can't add intr handler");
5205N/A (void) ddi_intr_free(dev->ihandle);
5205N/A dev->ihandle = NULL;
5205N/A return (DDI_FAILURE);
5205N/A }
5205N/A
5205N/A mutex_init(&dev->mutex, NULL, MUTEX_DRIVER, DDI_INTR_PRI(ipri));
5205N/A
5205N/A return (DDI_SUCCESS);
5205N/A}
5205N/A
5205N/A
5205N/A#define MASK(nbits) ((1 << (nbits)) - 1)
5205N/A#define SCALE(val, nbits) \
5205N/A ((uint8_t)((((val) * MASK(nbits)) / 100)) << (8 - (nbits)))
5205N/A
5205N/A#define LEFT(dev, ctl) min(((dev->controls[ctl].value) >> 8), 100)
5205N/A#define RIGHT(dev, ctl) min(((dev->controls[ctl].value) & 0xff), 100)
5205N/A#define MONO(dev, ctl) min(dev->controls[ctl].value, 100)
5205N/A
5205N/Astatic void
5205N/Acmpci_setmixer(cmpci_dev_t *dev, uint8_t idx, uint8_t val)
5205N/A{
5205N/A PUT8(dev, REG_IDXADDR, idx);
5205N/A PUT8(dev, REG_IDXDATA, val);
5205N/A}
5205N/A
5205N/Astatic uint8_t
5205N/Acmpci_getmixer(cmpci_dev_t *dev, uint8_t idx)
5205N/A{
5205N/A PUT8(dev, REG_IDXADDR, idx);
5205N/A return (GET8(dev, REG_IDXDATA));
5205N/A}
5205N/A
5205N/A
5205N/Astatic void
5205N/Acmpci_configure_mixer(cmpci_dev_t *dev)
5205N/A{
5205N/A uint64_t left, right;
5205N/A uint8_t outmix;
5205N/A uint8_t inmix[2];
5205N/A uint64_t recsrcs;
5205N/A uint64_t monsrcs;
5205N/A
5205N/A if (dev->suspended)
5205N/A return;
5221N/A
5221N/A /* reset all mix values */
5221N/A outmix = inmix[0] = inmix[1] = 0;
5221N/A
5221N/A outmix = OUTMIX_MIC |
5221N/A OUTMIX_CD_R | OUTMIX_CD_L | OUTMIX_LINE_R | OUTMIX_LINE_L;
5221N/A
5221N/A inmix[0] = INMIX_LINE_L | INMIX_CD_L | INMIX_MIC;
5221N/A inmix[1] = INMIX_LINE_R | INMIX_CD_R | INMIX_MIC;
5221N/A
5221N/A recsrcs = dev->controls[CTL_RECSRCS].value;
6415N/A monsrcs = dev->controls[CTL_MONSRCS].value;
6415N/A
5205N/A /* program PCM volume */
5205N/A left = MONO(dev, CTL_VOLUME);
6415N/A if (left) {
6415N/A /* left and right are the same */
5205N/A cmpci_setmixer(dev, IDX_VOICE_LEFT, SCALE(left, 5));
5221N/A cmpci_setmixer(dev, IDX_VOICE_RIGHT, SCALE(left, 5));
5205N/A CLR8(dev, REG_MIX2, MIX2_WSMUTE);
5221N/A } else {
5221N/A cmpci_setmixer(dev, IDX_VOICE_LEFT, 0);
5221N/A cmpci_setmixer(dev, IDX_VOICE_RIGHT, 0);
5221N/A SET8(dev, REG_MIX2, MIX2_WSMUTE);
5221N/A }
5221N/A
5221N/A left = LEFT(dev, CTL_LINEOUT);
5221N/A right = RIGHT(dev, CTL_LINEOUT);
5205N/A
5753N/A /* lineout/master volume - no separate mute */
5205N/A cmpci_setmixer(dev, IDX_MASTER_LEFT, SCALE(left, 5));
5205N/A cmpci_setmixer(dev, IDX_MASTER_RIGHT, SCALE(right, 5));
5205N/A
5205N/A /* speaker volume - mute in extension register, but we don't use */
5205N/A left = MONO(dev, CTL_SPEAKER);
5221N/A cmpci_setmixer(dev, IDX_SPEAKER, SCALE(left, 2));
5205N/A
6415N/A /* mic gain */
5205N/A left = MONO(dev, CTL_MIC);
5205N/A if (left) {
6415N/A cmpci_setmixer(dev, IDX_MIC, SCALE(left, 5));
6415N/A /* set record mic gain */
5205N/A uint8_t v = GET8(dev, REG_MIX3);
5221N/A v &= ~(0x7 << 1);
5205N/A v |= ((left * 7) / 100) << 1;
5221N/A PUT8(dev, REG_MIX3, v);
5205N/A cmpci_setmixer(dev, 0x3f, SCALE(100, 2));
6415N/A cmpci_setmixer(dev, 0x40, SCALE(100, 2));
5205N/A } else {
5205N/A cmpci_setmixer(dev, IDX_MIC, 0);
5205N/A outmix &= ~OUTMIX_MIC;
5205N/A inmix[0] &= ~INMIX_MIC;
5205N/A inmix[1] &= ~INMIX_MIC;
6415N/A }
5205N/A
5205N/A /* line in */
5205N/A left = LEFT(dev, CTL_LINEOUT);
5205N/A right = RIGHT(dev, CTL_LINEOUT);
6678N/A if (left) {
5205N/A cmpci_setmixer(dev, IDX_LINEIN_LEFT, SCALE(left, 5));
6415N/A } else {
5205N/A cmpci_setmixer(dev, IDX_LINEIN_LEFT, 0);
6226N/A inmix[0] &= ~INMIX_LINE_L;
6226N/A outmix &= ~OUTMIX_LINE_L;
5205N/A }
6418N/A if (right) {
6415N/A cmpci_setmixer(dev, IDX_LINEIN_RIGHT, SCALE(left, 5));
5221N/A } else {
6415N/A cmpci_setmixer(dev, IDX_LINEIN_RIGHT, 0);
5221N/A inmix[1] &= ~INMIX_LINE_R;
5205N/A outmix &= ~OUTMIX_LINE_R;
6418N/A }
5221N/A
6418N/A /* cd */
5221N/A left = LEFT(dev, CTL_CD);
5221N/A right = RIGHT(dev, CTL_CD);
6418N/A if (left) {
6418N/A cmpci_setmixer(dev, IDX_CDDA_LEFT, SCALE(left, 5));
6418N/A } else {
5205N/A cmpci_setmixer(dev, IDX_CDDA_LEFT, 0);
5221N/A inmix[0] &= ~INMIX_CD_L;
5205N/A outmix &= ~OUTMIX_CD_L;
6418N/A }
5221N/A if (right) {
5221N/A cmpci_setmixer(dev, IDX_CDDA_RIGHT, SCALE(left, 5));
5221N/A } else {
5221N/A cmpci_setmixer(dev, IDX_CDDA_RIGHT, 0);
6418N/A inmix[1] &= ~INMIX_CD_R;
6678N/A outmix &= ~OUTMIX_CD_R;
6418N/A }
6418N/A
6418N/A /* aux - trickier because it doesn't use regular sbpro mixer */
5205N/A left = LEFT(dev, CTL_AUX);
6418N/A right = RIGHT(dev, CTL_AUX);
5205N/A PUT8(dev, REG_VAUX, (((left * 15) / 100) << 4) | ((right * 15) / 100));
5205N/A /* maybe enable recording */
6418N/A if ((left || right) && (recsrcs & (1 << SRC_LINE))) {
6418N/A SET8(dev, REG_MIX3, MIX3_RAUXREN | MIX3_RAUXLEN);
6418N/A } else {
6418N/A CLR8(dev, REG_MIX3, MIX3_RAUXREN | MIX3_RAUXLEN);
5205N/A }
5205N/A /* maybe enable monitoring */
6418N/A if ((left || right) && (monsrcs & (1 << SRC_AUX))) {
6418N/A CLR8(dev, REG_MIX3, MIX3_VAUXRM | MIX3_VAUXLM);
6418N/A } else {
5205N/A SET8(dev, REG_MIX3, MIX3_VAUXRM | MIX3_VAUXLM);
5205N/A }
6415N/A
5205N/A /* now do the recsrcs */
5205N/A if ((recsrcs & (1 << SRC_MIC)) == 0) {
5205N/A inmix[0] &= ~INMIX_MIC;
5205N/A inmix[1] &= ~INMIX_MIC;
5205N/A }
6418N/A if ((recsrcs & (1 << SRC_LINE)) == 0) {
5221N/A inmix[0] &= ~INMIX_LINE_L;
5221N/A inmix[1] &= ~INMIX_LINE_R;
5221N/A }
5221N/A if ((recsrcs & (1 << SRC_CD)) == 0) {
6418N/A inmix[0] &= ~INMIX_CD_L;
6418N/A inmix[1] &= ~INMIX_CD_R;
5205N/A }
5205N/A if (recsrcs & (1 << SRC_MIX)) {
5205N/A SET8(dev, REG_MIX2, MIX2_WAVEIN_L | MIX2_WAVEIN_R);
5205N/A } else {
6418N/A CLR8(dev, REG_MIX2, MIX2_WAVEIN_L | MIX2_WAVEIN_R);
5205N/A }
6418N/A cmpci_setmixer(dev, IDX_INMIX_L, inmix[0]);
5221N/A cmpci_setmixer(dev, IDX_INMIX_R, inmix[1]);
6418N/A
5205N/A /* now the monsrcs */
5205N/A if ((monsrcs & (1 << SRC_MIC)) == 0) {
6418N/A outmix &= ~OUTMIX_MIC;
5205N/A }
6418N/A if ((monsrcs & (1 << SRC_LINE)) == 0) {
5221N/A outmix &= ~(OUTMIX_LINE_L | OUTMIX_LINE_R);
6418N/A }
5205N/A if ((monsrcs & (1 << SRC_CD)) == 0) {
5205N/A outmix &= ~(OUTMIX_CD_L | OUTMIX_CD_R);
5205N/A }
5205N/A cmpci_setmixer(dev, IDX_OUTMIX, outmix);
5205N/A
6678N/A /* micboost */
6418N/A if (dev->controls[CTL_MICBOOST].value != 0) {
6678N/A CLR8(dev, REG_MIX3, MIX3_MICGAINZ);
6418N/A cmpci_setmixer(dev, IDX_EXTENSION,
6418N/A cmpci_getmixer(dev, IDX_EXTENSION) & ~0x1);
6418N/A } else {
6418N/A SET8(dev, REG_MIX3, MIX3_MICGAINZ);
6418N/A cmpci_setmixer(dev, IDX_EXTENSION,
6418N/A cmpci_getmixer(dev, IDX_EXTENSION) | 0x1);
6418N/A }
6418N/A}
6418N/A
6678N/Astatic int
6418N/Acmpci_set_ctrl(void *arg, uint64_t val)
6418N/A{
6418N/A cmpci_ctrl_t *cc = arg;
6418N/A cmpci_dev_t *dev = cc->dev;
6418N/A
6418N/A /*
6678N/A * We don't bother to check for valid values - a bogus value
6418N/A * will give incorrect volumes, but is otherwise harmless.
6418N/A */
6418N/A mutex_enter(&dev->mutex);
6418N/A cc->value = val;
6418N/A cmpci_configure_mixer(dev);
5205N/A mutex_exit(&dev->mutex);
6417N/A
5221N/A return (0);
5221N/A}
5221N/A
5221N/Astatic int
5221N/Acmpci_get_ctrl(void *arg, uint64_t *val)
5221N/A{
5221N/A cmpci_ctrl_t *cc = arg;
5221N/A cmpci_dev_t *dev = cc->dev;
5221N/A
5221N/A mutex_enter(&dev->mutex);
6678N/A *val = cc->value;
6678N/A mutex_exit(&dev->mutex);
5205N/A return (0);
6678N/A}
6678N/A
6678N/A#define PLAYCTL (AUDIO_CTRL_FLAG_RW | AUDIO_CTRL_FLAG_PLAY)
6678N/A#define RECCTL (AUDIO_CTRL_FLAG_RW | AUDIO_CTRL_FLAG_REC)
5205N/A#define MONCTL (AUDIO_CTRL_FLAG_RW | AUDIO_CTRL_FLAG_MONITOR)
6678N/A#define PCMVOL (PLAYCTL | AUDIO_CTRL_FLAG_PCMVOL)
5205N/A#define MAINVOL (PLAYCTL | AUDIO_CTRL_FLAG_MAINVOL)
6678N/A#define RECVOL (RECCTL | AUDIO_CTRL_FLAG_RECVOL)
5205N/A
5205N/Astatic void
5205N/Acmpci_alloc_ctrl(cmpci_dev_t *dev, uint32_t num, uint64_t val)
5205N/A{
6678N/A audio_ctrl_desc_t desc;
5205N/A cmpci_ctrl_t *cc;
6678N/A
5205N/A cc = &dev->controls[num];
6678N/A bzero(&desc, sizeof (desc));
5205N/A cc->dev = dev;
5205N/A
5205N/A switch (num) {
5205N/A case CTL_VOLUME:
5205N/A desc.acd_name = AUDIO_CTRL_ID_VOLUME;
5205N/A desc.acd_type = AUDIO_CTRL_TYPE_MONO;
5205N/A desc.acd_minvalue = 0;
5205N/A desc.acd_maxvalue = 100;
5205N/A desc.acd_flags = PCMVOL;
5205N/A break;
5205N/A
5205N/A case CTL_LINEOUT:
5205N/A desc.acd_name = AUDIO_CTRL_ID_LINEOUT;
5205N/A desc.acd_type = AUDIO_CTRL_TYPE_STEREO;
5205N/A desc.acd_minvalue = 0;
5205N/A desc.acd_maxvalue = 100;
5205N/A desc.acd_flags = MAINVOL;
5205N/A break;
5205N/A
5205N/A case CTL_SPEAKER:
5205N/A desc.acd_name = AUDIO_CTRL_ID_SPEAKER;
5205N/A desc.acd_type = AUDIO_CTRL_TYPE_MONO;
5205N/A desc.acd_minvalue = 0;
5205N/A desc.acd_maxvalue = 100;
5205N/A desc.acd_flags = MAINVOL;
5205N/A break;
5205N/A
5205N/A case CTL_MIC:
5205N/A desc.acd_name = AUDIO_CTRL_ID_MIC;
5205N/A desc.acd_type = AUDIO_CTRL_TYPE_MONO;
5205N/A desc.acd_minvalue = 0;
5205N/A desc.acd_maxvalue = 100;
5205N/A desc.acd_flags = RECVOL;
5205N/A break;
5205N/A
5205N/A case CTL_LINEIN:
5205N/A desc.acd_name = AUDIO_CTRL_ID_LINEIN;
5205N/A desc.acd_type = AUDIO_CTRL_TYPE_STEREO;
5205N/A desc.acd_minvalue = 0;
5205N/A desc.acd_maxvalue = 100;
5205N/A desc.acd_flags = RECVOL;
5205N/A break;
5205N/A
5205N/A case CTL_CD:
5205N/A desc.acd_name = AUDIO_CTRL_ID_CD;
6417N/A desc.acd_type = AUDIO_CTRL_TYPE_STEREO;
5205N/A desc.acd_minvalue = 0;
5205N/A desc.acd_maxvalue = 100;
5205N/A desc.acd_flags = RECVOL;
5205N/A break;
5205N/A
5205N/A case CTL_AUX:
5205N/A desc.acd_name = AUDIO_CTRL_ID_AUX1IN;
5205N/A desc.acd_type = AUDIO_CTRL_TYPE_STEREO;
5205N/A desc.acd_minvalue = 0;
5205N/A desc.acd_maxvalue = 100;
5205N/A desc.acd_flags = RECVOL;
5205N/A break;
5205N/A
5205N/A case CTL_RECSRCS:
6678N/A desc.acd_name = AUDIO_CTRL_ID_RECSRC;
5205N/A desc.acd_type = AUDIO_CTRL_TYPE_ENUM;
5205N/A desc.acd_enum[SRC_MIC] = AUDIO_PORT_MIC;
5205N/A desc.acd_enum[SRC_LINE] = AUDIO_PORT_LINEIN;
6678N/A desc.acd_enum[SRC_CD] = AUDIO_PORT_CD;
6678N/A desc.acd_enum[SRC_AUX] = AUDIO_PORT_AUX1IN;
5221N/A desc.acd_enum[SRC_MIX] = AUDIO_PORT_STEREOMIX;
5205N/A desc.acd_minvalue = (1 << (SRC_MIX + 1)) - 1;
5205N/A desc.acd_maxvalue = desc.acd_minvalue;
6678N/A desc.acd_flags = RECCTL | AUDIO_CTRL_FLAG_MULTI;
5205N/A break;
5205N/A
5205N/A case CTL_MONSRCS:
6678N/A desc.acd_name = AUDIO_CTRL_ID_MONSRC;
5205N/A desc.acd_type = AUDIO_CTRL_TYPE_ENUM;
5205N/A desc.acd_enum[SRC_MIC] = AUDIO_PORT_MIC;
5205N/A desc.acd_enum[SRC_LINE] = AUDIO_PORT_LINEIN;
5205N/A desc.acd_enum[SRC_CD] = AUDIO_PORT_CD;
5205N/A desc.acd_enum[SRC_AUX] = AUDIO_PORT_AUX1IN;
5205N/A desc.acd_minvalue = ((1 << (SRC_AUX + 1)) - 1);
5205N/A desc.acd_maxvalue = desc.acd_minvalue;
5205N/A desc.acd_flags = MONCTL | AUDIO_CTRL_FLAG_MULTI;
5205N/A break;
5205N/A
5205N/A case CTL_MICBOOST:
5205N/A desc.acd_name = AUDIO_CTRL_ID_MICBOOST;
5205N/A desc.acd_type = AUDIO_CTRL_TYPE_BOOLEAN;
5205N/A desc.acd_minvalue = 0;
5205N/A desc.acd_maxvalue = 1;
5205N/A desc.acd_flags = RECCTL;
5205N/A break;
5205N/A }
5205N/A
5205N/A cc->value = val;
5205N/A cc->ctrl = audio_dev_add_control(dev->adev, &desc,
5205N/A cmpci_get_ctrl, cmpci_set_ctrl, cc);
5205N/A}
5205N/A
5205N/Astatic void
5205N/Acmpci_add_controls(cmpci_dev_t *dev)
5205N/A{
5205N/A cmpci_alloc_ctrl(dev, CTL_VOLUME, 75);
5205N/A cmpci_alloc_ctrl(dev, CTL_LINEOUT, 90 | (90 << 8));
5205N/A cmpci_alloc_ctrl(dev, CTL_SPEAKER, 75);
5205N/A cmpci_alloc_ctrl(dev, CTL_MIC, 32);
5205N/A cmpci_alloc_ctrl(dev, CTL_LINEIN, 64 | (64 << 8));
5205N/A cmpci_alloc_ctrl(dev, CTL_CD, 75 | (75 << 8));
5205N/A cmpci_alloc_ctrl(dev, CTL_AUX, 75 | (75 << 8));
5205N/A cmpci_alloc_ctrl(dev, CTL_RECSRCS, (1 << SRC_MIC));
5205N/A cmpci_alloc_ctrl(dev, CTL_MONSRCS, 0);
5205N/A cmpci_alloc_ctrl(dev, CTL_MICBOOST, 0);
5205N/A}
5205N/A
6415N/Astatic void
5205N/Acmpci_del_controls(cmpci_dev_t *dev)
6415N/A{
6415N/A for (int i = 0; i < CTL_NUM; i++) {
6415N/A if (dev->controls[i].ctrl) {
5205N/A audio_dev_del_control(dev->controls[i].ctrl);
5205N/A dev->controls[i].ctrl = NULL;
6678N/A }
5205N/A }
5205N/A}
5205N/A
5205N/Astatic void
5205N/Acmpci_reset(cmpci_dev_t *dev)
5205N/A{
5205N/A /* Full reset */
5205N/A SET32(dev, REG_MISC, MISC_RESET);
5205N/A (void) GET32(dev, REG_MISC);
5205N/A drv_usecwait(100);
5205N/A CLR32(dev, REG_MISC, MISC_RESET);
5205N/A
5205N/A /* reset all channels */
5205N/A PUT32(dev, REG_FUNCTRL0, 0);
5205N/A
5205N/A /* disable interrupts and such */
5205N/A CLR32(dev, REG_FUNCTRL0, FUNCTRL0_CH0_EN | FUNCTRL0_CH1_EN);
5205N/A CLR32(dev, REG_INTCTRL, INTCTRL_CH0_EN | INTCTRL_CH1_EN);
5205N/A
5205N/A /* disable uart, joystick in Function Control Reg1 */
5205N/A CLR32(dev, REG_FUNCTRL1, FUNCTRL1_UART_EN | FUNCTRL1_JYSTK_EN);
5205N/A
5205N/A /*
5205N/A * Set DAC and ADC rates to 48 kHz - note that both rates have
5205N/A * all bits set in them, so we can do this with a simple "set".
5205N/A */
5205N/A SET32(dev, REG_FUNCTRL1,
5205N/A FUNCTRL1_DAC_RATE_48K | FUNCTRL1_ADC_RATE_48K);
5205N/A
6678N/A /* Set 16-bit stereo -- also these are just with all bits set. */
6678N/A SET32(dev, REG_CHFORMAT, CHFORMAT_CH0_16ST | CHFORMAT_CH1_16ST);
5205N/A}
5205N/A
5205N/Astatic int
5205N/Acmpci_format(void *unused)
5205N/A{
5205N/A _NOTE(ARGUNUSED(unused));
5205N/A return (AUDIO_FORMAT_S16_LE);
5205N/A}
5205N/A
5205N/Astatic int
5205N/Acmpci_channels(void *arg)
5205N/A{
5205N/A cmpci_port_t *port = arg;
5205N/A
6678N/A return (port->nchan);
6678N/A}
5205N/A
5205N/Astatic void
5205N/Acmpci_chinfo(void *arg, int chan, unsigned *offset, unsigned *incr)
5205N/A{
5205N/A cmpci_port_t *port = arg;
5205N/A static const int map8ch[] = { 0, 1, 4, 5, 2, 3, 6, 7 };
6678N/A static const int map4ch[] = { 0, 1, 2, 3 };
6678N/A
5205N/A if (port->nchan <= 4) {
5205N/A *offset = map4ch[chan];
5205N/A } else {
5205N/A *offset = map8ch[chan];
5205N/A }
5205N/A *incr = port->nchan;
6678N/A}
5205N/A
6678N/Astatic int
5205N/Acmpci_rate(void *unused)
6415N/A{
5205N/A _NOTE(ARGUNUSED(unused));
6415N/A return (48000);
6415N/A}
5205N/A
5205N/Astatic void
5205N/Acmpci_sync(void *arg, unsigned nframes)
5205N/A{
5753N/A cmpci_port_t *port = arg;
6415N/A
6678N/A _NOTE(ARGUNUSED(nframes));
5754N/A
6678N/A (void) ddi_dma_sync(port->dmah, 0, 0, port->sync_dir);
5205N/A}
5205N/A
5205N/Astatic size_t
5205N/Acmpci_qlen(void *unused)
5205N/A{
5205N/A _NOTE(ARGUNUSED(unused));
5205N/A
5205N/A return (0);
5205N/A}
5205N/A
5205N/Aaudio_engine_ops_t cmpci_engine_ops = {
5205N/A AUDIO_ENGINE_VERSION, /* version number */
5205N/A cmpci_open,
5205N/A cmpci_close,
5205N/A NULL, /* start */
5205N/A NULL, /* stop */
5205N/A cmpci_count,
5205N/A cmpci_format,
5205N/A cmpci_channels,
5205N/A cmpci_rate,
5753N/A cmpci_sync,
5205N/A cmpci_qlen,
5205N/A cmpci_chinfo,
5205N/A};
5205N/A
5205N/Astatic int
5205N/Acmpci_init(cmpci_dev_t *dev)
5205N/A{
5754N/A audio_dev_t *adev = dev->adev;
5205N/A int playch;
5753N/A int intrs;
5205N/A
5205N/A dev->pintrs = ddi_prop_get_int(DDI_DEV_T_ANY, dev->dip,
5753N/A DDI_PROP_DONTPASS, "play-interrupts", DEFINTS);
5205N/A
5205N/A dev->rintrs = ddi_prop_get_int(DDI_DEV_T_ANY, dev->dip,
5754N/A DDI_PROP_DONTPASS, "record-interrupts", DEFINTS);
5205N/A
5205N/A playch = ddi_prop_get_int(DDI_DEV_T_ANY, dev->dip,
5205N/A DDI_PROP_DONTPASS, "channels", dev->maxch);
5205N/A
6417N/A if ((playch % 2) || (playch < 2) || (playch > dev->maxch)) {
5205N/A audio_dev_warn(adev,
5205N/A "Invalid channels property (%d), resetting to %d",
5205N/A playch, dev->maxch);
5205N/A playch = dev->maxch;
5205N/A }
5753N/A
5205N/A for (int i = 0; i < PORT_MAX; i++) {
5205N/A
5205N/A cmpci_port_t *port;
5753N/A unsigned dmaflags;
5205N/A unsigned caps;
5754N/A size_t rlen;
5205N/A ddi_dma_cookie_t c;
5205N/A unsigned ccnt;
5205N/A
5205N/A port = &dev->port[i];
5205N/A port->dev = dev;
5205N/A port->num = i;
5205N/A
5205N/A /*
5205N/A * Channel 0 is recording channel, unless we are in
5205N/A * dual DAC mode. The reason for this is simple --
5205N/A * only channel "B" (which I presume to mean channel
5205N/A * 1) supports multichannel configuration.
5205N/A *
5205N/A * However, if we're going to use SPDIF recording,
5205N/A * then recording *must* occur on channel 1. Yes, the
5753N/A * hardware is "strange".
5205N/A */
5221N/A
5221N/A switch (i) {
6678N/A case 0:
6678N/A caps = ENGINE_INPUT_CAP;
5221N/A dmaflags = DDI_DMA_READ | DDI_DMA_CONSISTENT;
6678N/A port->callb = audio_engine_produce;
5221N/A port->reg_paddr = REG_CH0_PADDR;
6678N/A port->reg_bufsz = REG_CH0_BUFSZ;
5221N/A port->reg_fragsz = REG_CH0_FRAGSZ;
6678N/A port->fc0_rst_bit = FUNCTRL0_CH0_RST;
6678N/A port->fc0_rec_bit = FUNCTRL0_CH0_REC;
6678N/A port->fc0_en_bit = FUNCTRL0_CH0_EN;
5221N/A port->int_en_bit = INTCTRL_CH0_EN;
5221N/A port->sync_dir = DDI_DMA_SYNC_FORKERNEL;
5221N/A port->capture = B_TRUE;
6678N/A port->fc1_rate_mask = FUNCTRL1_ADC_RATE_48K;
5221N/A port->chformat_mask = CHFORMAT_CH0_16ST;
6678N/A port->nchan = 2;
5221N/A intrs = dev->rintrs;
5221N/A break;
5221N/A
5221N/A case 1:
6678N/A caps = ENGINE_OUTPUT_CAP;
5221N/A dmaflags = DDI_DMA_WRITE | DDI_DMA_CONSISTENT;
6678N/A port->callb = audio_engine_consume;
5221N/A port->reg_paddr = REG_CH1_PADDR;
5754N/A port->reg_bufsz = REG_CH1_BUFSZ;
6415N/A port->reg_fragsz = REG_CH1_FRAGSZ;
6415N/A port->fc0_rst_bit = FUNCTRL0_CH1_RST;
6415N/A port->fc0_rec_bit = FUNCTRL0_CH1_REC;
6415N/A port->fc0_en_bit = FUNCTRL0_CH1_EN;
6415N/A port->int_en_bit = INTCTRL_CH1_EN;
5754N/A port->sync_dir = DDI_DMA_SYNC_FORDEV;
5754N/A port->capture = B_FALSE;
5754N/A port->fc1_rate_mask = FUNCTRL1_DAC_RATE_48K;
5754N/A port->chformat_mask = CHFORMAT_CH1_16ST;
5754N/A port->nchan = playch;
5754N/A intrs = dev->pintrs;
5754N/A break;
5754N/A }
5754N/A
5754N/A /*
5754N/A * Calculate fragfr, nfrags, buf.
5754N/A *
5754N/A * 48 as minimum is chosen to ensure that we will have
5754N/A * at least 4 fragments. 512 is just an arbitrary
5754N/A * limit, and at the smallest frame size will result
5754N/A * in no more than 176 fragments.
5754N/A */
5754N/A intrs = min(512, max(48, intrs));
5754N/A
5754N/A /*
5754N/A * Two fragments are enough to get ping-pong buffers.
5754N/A * The hardware could support considerably more than
5754N/A * this, but it just wastes memory.
5754N/A */
5754N/A port->nfrags = 2;
5754N/A
5754N/A /*
5754N/A * For efficiency, we'd like to have the fragments
5754N/A * evenly divisble by 64 bytes. Since frames are
5205N/A * already evenly divisble by 4 (16-bit stereo), this
5205N/A * is adequate. For a typical configuration (175 Hz
* requested) this will translate to 166 Hz.
*/
port->fragfr = P2ROUNDUP((48000 / intrs), 16);
port->nframes = port->nfrags * port->fragfr;
port->bufsz = port->nframes * port->nchan * 2;
if (ddi_dma_alloc_handle(dev->dip, &dma_attr, DDI_DMA_DONTWAIT,
NULL, &port->dmah) != DDI_SUCCESS) {
audio_dev_warn(adev, "ch%d: dma hdl alloc failed", i);
return (DDI_FAILURE);
}
if (ddi_dma_mem_alloc(port->dmah, port->bufsz, &buf_attr,
DDI_DMA_CONSISTENT, DDI_DMA_DONTWAIT, NULL, &port->kaddr,
&rlen, &port->acch) != DDI_SUCCESS) {
audio_dev_warn(adev, "ch%d: dma mem allcoc failed", i);
return (DDI_FAILURE);
}
bzero(port->kaddr, rlen);
if (ddi_dma_addr_bind_handle(port->dmah, NULL, port->kaddr,
rlen, dmaflags, DDI_DMA_DONTWAIT, NULL, &c, &ccnt) !=
DDI_DMA_MAPPED) {
audio_dev_warn(adev, "ch%d: dma bind failed", i);
return (DDI_FAILURE);
}
port->paddr = c.dmac_address;
port->engine = audio_engine_alloc(&cmpci_engine_ops, caps);
if (port->engine == NULL) {
audio_dev_warn(adev, "ch%d: alloc engine failed", i);
return (DDI_FAILURE);
}
audio_engine_set_private(port->engine, port);
audio_dev_add_engine(adev, port->engine);
}
cmpci_add_controls(dev);
dev->ksp = kstat_create(ddi_driver_name(dev->dip),
ddi_get_instance(dev->dip), ddi_driver_name(dev->dip),
"controller", KSTAT_TYPE_INTR, 1, KSTAT_FLAG_PERSISTENT);
if (dev->ksp != NULL) {
kstat_install(dev->ksp);
}
cmpci_reset(dev);
cmpci_configure_mixer(dev);
if (audio_dev_register(adev) != DDI_SUCCESS) {
audio_dev_warn(adev, "audio_dev_register failed");
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
void
cmpci_destroy(cmpci_dev_t *dev)
{
if (dev->ihandle != NULL) {
(void) ddi_intr_disable(dev->ihandle);
(void) ddi_intr_remove_handler(dev->ihandle);
(void) ddi_intr_free(dev->ihandle);
mutex_destroy(&dev->mutex);
}
if (dev->ksp != NULL) {
kstat_delete(dev->ksp);
}
/* free up ports, including DMA resources for ports */
for (int i = 0; i < PORT_MAX; i++) {
cmpci_port_t *port = &dev->port[i];
if (port->paddr != 0)
(void) ddi_dma_unbind_handle(port->dmah);
if (port->acch != NULL)
ddi_dma_mem_free(&port->acch);
if (port->dmah != NULL)
ddi_dma_free_handle(&port->dmah);
if (port->engine != NULL) {
audio_dev_remove_engine(dev->adev, port->engine);
audio_engine_free(port->engine);
}
}
if (dev->acch != NULL) {
ddi_regs_map_free(&dev->acch);
}
cmpci_del_controls(dev);
if (dev->adev != NULL) {
audio_dev_free(dev->adev);
}
kmem_free(dev, sizeof (*dev));
}
int
cmpci_attach(dev_info_t *dip)
{
uint16_t vendor, device;
cmpci_dev_t *dev;
ddi_acc_handle_t pcih;
audio_dev_t *adev;
uint32_t val;
if (pci_config_setup(dip, &pcih) != DDI_SUCCESS) {
audio_dev_warn(NULL, "pci_config_setup failed");
return (DDI_FAILURE);
}
vendor = pci_config_get16(pcih, PCI_CONF_VENID);
device = pci_config_get16(pcih, PCI_CONF_DEVID);
if (vendor != CMEDIA_VENDOR_ID ||
((device != CMEDIA_CM8738) && (device != CMEDIA_CM8338A) &&
(device != CMEDIA_CM8338B))) {
pci_config_teardown(&pcih);
audio_dev_warn(NULL, "device not recognized");
return (DDI_FAILURE);
}
/* enable IO and Master accesses */
pci_config_put16(pcih, PCI_CONF_COMM,
pci_config_get16(pcih, PCI_CONF_COMM) |
PCI_COMM_MAE | PCI_COMM_IO);
pci_config_teardown(&pcih);
dev = kmem_zalloc(sizeof (*dev), KM_SLEEP);
dev->dip = dip;
ddi_set_driver_private(dip, dev);
if ((adev = audio_dev_alloc(dip, 0)) == NULL) {
goto err_exit;
}
dev->adev = adev;
if (ddi_regs_map_setup(dip, 1, &dev->regs, 0, 0, &acc_attr,
&dev->acch) != DDI_SUCCESS) {
audio_dev_warn(adev, "can't map registers");
goto err_exit;
}
/* setup some initial values */
dev->maxch = 2;
audio_dev_set_description(adev, "C-Media PCI Audio");
switch (device) {
case CMEDIA_CM8738:
/*
* Crazy 8738 detection scheme. Reviewing multiple
* different open sources gives multiple different
* answers here. Its unclear how accurate this is.
* The approach taken here is a bit conservative in
* assigning multiple channel support, but for users
* with newer 8768 cards should offer the best
* capability.
*/
val = GET32(dev, REG_INTCTRL) & INTCTRL_MDL_MASK;
if (val == 0) {
if (GET32(dev, REG_CHFORMAT & CHFORMAT_VER_MASK)) {
audio_dev_set_version(adev, "CMI-8738-037");
dev->maxch = 4;
} else {
audio_dev_set_version(adev, "CMI-8738-033");
}
} else if ((val & INTCTRL_MDL_068) == INTCTRL_MDL_068) {
audio_dev_set_version(adev, "CMI-8768");
dev->maxch = 8;
} else if ((val & INTCTRL_MDL_055) == INTCTRL_MDL_055) {
audio_dev_set_version(adev, "CMI-8738-055");
dev->maxch = 6;
} else if ((val & INTCTRL_MDL_039) == INTCTRL_MDL_039) {
audio_dev_set_version(adev, "CMI-8738-039");
dev->maxch = 4;
} else {
audio_dev_set_version(adev, "CMI-8738");
}
break;
case CMEDIA_CM8338A:
audio_dev_set_version(dev->adev, "CMI-8338");
break;
case CMEDIA_CM8338B:
audio_dev_set_version(dev->adev, "CMI-8338B");
break;
}
if (cmpci_setup_interrupts(dev) != DDI_SUCCESS) {
audio_dev_warn(dev->adev, "can't register interrupts");
goto err_exit;
}
if (cmpci_init(dev) != DDI_SUCCESS) {
audio_dev_warn(dev->adev, "can't init device");
goto err_exit;
}
(void) ddi_intr_enable(dev->ihandle);
return (DDI_SUCCESS);
err_exit:
cmpci_destroy(dev);
return (DDI_FAILURE);
}
static int
cmpci_resume(cmpci_dev_t *dev)
{
audio_engine_reset(dev->port[0].engine);
audio_engine_reset(dev->port[1].engine);
mutex_enter(&dev->mutex);
dev->suspended = B_FALSE;
cmpci_reset(dev);
/* wait one millisecond, to give reset a chance to get up */
drv_usecwait(1000);
cmpci_configure_mixer(dev);
for (int i = 0; i < PORT_MAX; i++) {
cmpci_port_t *port = &dev->port[i];
cmpci_reset_port(port);
if (port->open) {
cmpci_start_port(port);
}
}
mutex_exit(&dev->mutex);
return (DDI_SUCCESS);
}
static int
cmpci_detach(cmpci_dev_t *dev)
{
if (audio_dev_unregister(dev->adev) != DDI_SUCCESS)
return (DDI_FAILURE);
mutex_enter(&dev->mutex);
/* disable interrupts */
CLR32(dev, REG_INTCTRL, INTCTRL_CH1_EN | INTCTRL_CH0_EN);
/* disable channels */
PUT32(dev, REG_FUNCTRL0, 0);
mutex_exit(&dev->mutex);
cmpci_destroy(dev);
return (DDI_SUCCESS);
}
static int
cmpci_suspend(cmpci_dev_t *dev)
{
mutex_enter(&dev->mutex);
cmpci_update_port(&dev->port[0]);
cmpci_stop_port(&dev->port[0]);
cmpci_update_port(&dev->port[1]);
cmpci_stop_port(&dev->port[1]);
dev->suspended = B_TRUE;
mutex_exit(&dev->mutex);
return (DDI_SUCCESS);
}
static int
cmpci_quiesce(dev_info_t *dip)
{
cmpci_dev_t *dev;
if ((dev = ddi_get_driver_private(dip)) == NULL) {
return (DDI_FAILURE);
}
/* disable interrupts */
PUT32(dev, REG_INTCTRL, 0);
/* disable channels */
PUT32(dev, REG_FUNCTRL0, 0);
return (DDI_SUCCESS);
}
static int
cmpci_ddi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
cmpci_dev_t *dev;
switch (cmd) {
case DDI_ATTACH:
return (cmpci_attach(dip));
case DDI_RESUME:
if ((dev = ddi_get_driver_private(dip)) == NULL) {
return (DDI_FAILURE);
}
return (cmpci_resume(dev));
default:
return (DDI_FAILURE);
}
}
static int
cmpci_ddi_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
cmpci_dev_t *dev;
if ((dev = ddi_get_driver_private(dip)) == NULL) {
return (DDI_FAILURE);
}
switch (cmd) {
case DDI_DETACH:
return (cmpci_detach(dev));
case DDI_SUSPEND:
return (cmpci_suspend(dev));
default:
return (DDI_FAILURE);
}
}
static struct dev_ops cmpci_dev_ops = {
DEVO_REV, /* rev */
0, /* refcnt */
NULL, /* getinfo */
nulldev, /* identify */
nulldev, /* probe */
cmpci_ddi_attach, /* attach */
cmpci_ddi_detach, /* detach */
nodev, /* reset */
NULL, /* cb_ops */
NULL, /* bus_ops */
NULL, /* power */
cmpci_quiesce, /* quiesce */
};
static struct modldrv cmpci_modldrv = {
&mod_driverops, /* drv_modops */
"C-Media PCI Audio", /* linkinfo */
&cmpci_dev_ops, /* dev_ops */
};
static struct modlinkage modlinkage = {
MODREV_1,
{ &cmpci_modldrv, NULL }
};
int
_init(void)
{
int rv;
audio_init_ops(&cmpci_dev_ops, "audiocmi");
if ((rv = mod_install(&modlinkage)) != 0) {
audio_fini_ops(&cmpci_dev_ops);
}
return (rv);
}
int
_fini(void)
{
int rv;
if ((rv = mod_remove(&modlinkage)) == 0) {
audio_fini_ops(&cmpci_dev_ops);
}
return (rv);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}