/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Purpose: Driver for the Creative P16X AC97 audio controller */ /* * * Copyright (C) 4Front Technologies 1996-2009. * * This software is released under CDDL 1.0 source license. * See the COPYING file included in the main directory of this source * distribution for the license terms and conditions. */ #include #include #include #include #include #include #include #include #include #include #include "audiop16x.h" /* * These boards use an AC'97 codec, but don't have all of the * various outputs that the AC'97 codec can offer. We just * suppress them for now. */ static char *p16x_remove_ac97[] = { AUDIO_CTRL_ID_BEEP, AUDIO_CTRL_ID_VIDEO, AUDIO_CTRL_ID_MICSRC, AUDIO_CTRL_ID_SPEAKER, AUDIO_CTRL_ID_SPKSRC, NULL }; static struct ddi_device_acc_attr dev_attr = { DDI_DEVICE_ATTR_V0, DDI_STRUCTURE_LE_ACC, DDI_STRICTORDER_ACC }; static struct ddi_device_acc_attr buf_attr = { DDI_DEVICE_ATTR_V0, DDI_NEVERSWAP_ACC, DDI_STRICTORDER_ACC }; static ddi_dma_attr_t dma_attr_buf = { DMA_ATTR_V0, /* version number */ 0x00000000, /* low DMA address range */ 0xffffffff, /* high DMA address range */ 0xfffffffe, /* DMA counter register */ 4, /* DMA address alignment */ 0x3c, /* DMA burstsizes */ 4, /* min effective DMA size */ 0xffffffff, /* max DMA xfer size */ 0xffffffff, /* segment boundary */ 1, /* s/g length */ 4, /* granularity of device */ 0 /* Bus specific DMA flags */ }; static int p16x_attach(dev_info_t *); static int p16x_resume(dev_info_t *); static int p16x_detach(p16x_dev_t *); static int p16x_suspend(p16x_dev_t *); static int p16x_open(void *, int, unsigned *, caddr_t *); static void p16x_close(void *); static int p16x_start(void *); static void p16x_stop(void *); static int p16x_format(void *); static int p16x_channels(void *); static int p16x_rate(void *); static uint64_t p16x_count(void *); static void p16x_sync(void *, unsigned); static void p16x_chinfo(void *, int, unsigned *, unsigned *); static uint16_t p16x_read_ac97(void *, uint8_t); static void p16x_write_ac97(void *, uint8_t, uint16_t); static int p16x_alloc_port(p16x_dev_t *, int); static void p16x_destroy(p16x_dev_t *); static void p16x_hwinit(p16x_dev_t *); static audio_engine_ops_t p16x_engine_ops = { AUDIO_ENGINE_VERSION, p16x_open, p16x_close, p16x_start, p16x_stop, p16x_count, p16x_format, p16x_channels, p16x_rate, p16x_sync, NULL, p16x_chinfo, NULL }; static unsigned int read_reg(p16x_dev_t *dev, int reg, int chn) { unsigned int val; mutex_enter(&dev->mutex); OUTL(dev, (reg << 16) | (chn & 0xffff), PTR); /* Pointer */ val = INL(dev, DR); /* Data */ mutex_exit(&dev->mutex); return (val); } static void write_reg(p16x_dev_t *dev, int reg, int chn, unsigned int value) { mutex_enter(&dev->mutex); OUTL(dev, (reg << 16) | (chn & 0xffff), PTR); /* Pointer */ OUTL(dev, value, DR); /* Data */ mutex_exit(&dev->mutex); } static void set_reg_bits(p16x_dev_t *dev, int reg, int chn, unsigned int mask) { unsigned int val; mutex_enter(&dev->mutex); OUTL(dev, (reg << 16) | (chn & 0xffff), PTR); /* Pointer */ val = INL(dev, DR); /* Data */ val |= mask; OUTL(dev, val, DR); /* Data */ mutex_exit(&dev->mutex); } static void clear_reg_bits(p16x_dev_t *dev, int reg, int chn, unsigned int mask) { unsigned int val; mutex_enter(&dev->mutex); OUTL(dev, (reg << 16) | (chn & 0xffff), PTR); /* Pointer */ val = INL(dev, DR); /* Data */ val &= ~(mask); OUTL(dev, val, DR); /* Data */ mutex_exit(&dev->mutex); } static uint16_t p16x_read_ac97(void *arg, uint8_t index) { p16x_dev_t *dev = arg; uint16_t value; int i; OUTB(dev, index, AC97A); for (i = 0; i < 10000; i++) if (INB(dev, AC97A) & 0x80) break; value = INW(dev, AC97D); return (value); } static void p16x_write_ac97(void *arg, uint8_t index, uint16_t data) { p16x_dev_t *dev = arg; unsigned int i; OUTB(dev, index, AC97A); for (i = 0; i < 10000; i++) if (INB(dev, AC97A) & 0x80) break; OUTW(dev, data, AC97D); } /* * Audio routines */ int p16x_open(void *arg, int flag, uint_t *nframes, caddr_t *bufp) { p16x_port_t *port = arg; _NOTE(ARGUNUSED(flag)); port->count = 0; *nframes = port->buf_frames; *bufp = port->buf_kaddr; return (0); } void p16x_close(void *arg) { _NOTE(ARGUNUSED(arg)); } int p16x_start(void *arg) { p16x_port_t *port = arg; p16x_dev_t *dev = port->dev; port->offset = 0; if (port->port_num == P16X_REC) { write_reg(dev, CRFA, 0, 0); write_reg(dev, CRCAV, 0, 0); /* Enable rec channel */ set_reg_bits(dev, SA, 0, 0x100); } else { for (int i = 0; i < 3; i++) { write_reg(dev, PTBA, i, 0); write_reg(dev, PTBS, i, 0); write_reg(dev, PTCA, i, 0); write_reg(dev, PFEA, i, 0); write_reg(dev, CPFA, i, 0); write_reg(dev, CPCAV, i, 0); } /* Enable play channel */ set_reg_bits(dev, SA, 0, 0x7); } return (0); } void p16x_stop(void *arg) { p16x_port_t *port = arg; p16x_dev_t *dev = port->dev; if (port->port_num == P16X_REC) { /* Disable rec channel */ clear_reg_bits(dev, SA, 0, 0x100); } else { /* Disable Play channel */ clear_reg_bits(dev, SA, 0, 0x7); } } int p16x_format(void *arg) { _NOTE(ARGUNUSED(arg)); return (AUDIO_FORMAT_S16_LE); } int p16x_channels(void *arg) { p16x_port_t *port = arg; return (port->nchan); } int p16x_rate(void *arg) { _NOTE(ARGUNUSED(arg)); return (48000); } void p16x_sync(void *arg, unsigned nframes) { p16x_port_t *port = arg; _NOTE(ARGUNUSED(nframes)); (void) ddi_dma_sync(port->buf_dmah, 0, 0, port->syncdir); } uint64_t p16x_count(void *arg) { p16x_port_t *port = arg; p16x_dev_t *dev = port->dev; uint64_t val; uint32_t offset, n; if (port->port_num == P16X_PLAY) { offset = read_reg(dev, CPFA, 0); } else { offset = read_reg(dev, CRFA, 0); } /* get the offset, and switch to frames */ offset /= (2 * sizeof (uint16_t)); if (offset >= port->offset) { n = offset - port->offset; } else { n = offset + (port->buf_frames - port->offset); } port->offset = offset; port->count += n; val = port->count; return (val); } static void p16x_chinfo(void *arg, int chan, unsigned *offset, unsigned *incr) { p16x_port_t *port = arg; unsigned mult; if (port->port_num == P16X_PLAY) { switch (chan) { case 0: /* left front */ case 1: /* right front */ mult = 0; break; case 2: /* center */ case 3: /* lfe */ mult = 2; break; case 4: /* left surround */ case 5: /* right surround */ mult = 1; break; } *offset = (port->buf_frames * 2 * mult) + (chan % 2); *incr = 2; } else { *offset = chan; *incr = 2; } } /* private implementation bits */ int p16x_alloc_port(p16x_dev_t *dev, int num) { p16x_port_t *port; size_t len; ddi_dma_cookie_t cookie; uint_t count; int dir; unsigned caps; audio_dev_t *adev; adev = dev->adev; port = kmem_zalloc(sizeof (*port), KM_SLEEP); dev->port[num] = port; port->dev = dev; switch (num) { case P16X_REC: port->syncdir = DDI_DMA_SYNC_FORKERNEL; caps = ENGINE_INPUT_CAP; dir = DDI_DMA_READ; port->port_num = P16X_REC; port->nchan = 2; break; case P16X_PLAY: port->syncdir = DDI_DMA_SYNC_FORDEV; caps = ENGINE_OUTPUT_CAP; dir = DDI_DMA_WRITE; port->port_num = P16X_PLAY; port->nchan = 6; break; default: return (DDI_FAILURE); } /* * NB: The device operates in pairs of dwords at a time, for * performance reasons. So make sure that our buffer is * arranged as a whole number of these. The value below gives * a reasonably large buffer so we can support a deep * playahead if we need to (and we should avoid input * overruns.) */ port->buf_frames = 4096; port->buf_size = port->buf_frames * port->nchan * sizeof (uint16_t); /* now allocate buffers */ if (ddi_dma_alloc_handle(dev->dip, &dma_attr_buf, DDI_DMA_SLEEP, NULL, &port->buf_dmah) != DDI_SUCCESS) { audio_dev_warn(adev, "failed to allocate BUF handle"); return (DDI_FAILURE); } if (ddi_dma_mem_alloc(port->buf_dmah, port->buf_size, &buf_attr, DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &port->buf_kaddr, &len, &port->buf_acch) != DDI_SUCCESS) { audio_dev_warn(adev, "failed to allocate BUF memory"); return (DDI_FAILURE); } if (ddi_dma_addr_bind_handle(port->buf_dmah, NULL, port->buf_kaddr, len, DDI_DMA_CONSISTENT | dir, DDI_DMA_SLEEP, NULL, &cookie, &count) != DDI_SUCCESS) { audio_dev_warn(adev, "failed binding BUF DMA handle"); return (DDI_FAILURE); } port->buf_paddr = cookie.dmac_address; port->engine = audio_engine_alloc(&p16x_engine_ops, caps); if (port->engine == NULL) { audio_dev_warn(adev, "audio_engine_alloc failed"); return (DDI_FAILURE); } audio_engine_set_private(port->engine, port); audio_dev_add_engine(adev, port->engine); return (DDI_SUCCESS); } void p16x_destroy(p16x_dev_t *dev) { mutex_destroy(&dev->mutex); for (int i = 0; i < P16X_NUM_PORT; i++) { p16x_port_t *port = dev->port[i]; if (!port) continue; if (port->engine) { audio_dev_remove_engine(dev->adev, port->engine); audio_engine_free(port->engine); } if (port->buf_paddr) { (void) ddi_dma_unbind_handle(port->buf_dmah); } if (port->buf_acch) { ddi_dma_mem_free(&port->buf_acch); } if (port->buf_dmah) { ddi_dma_free_handle(&port->buf_dmah); } kmem_free(port, sizeof (*port)); } if (dev->ac97 != NULL) { ac97_free(dev->ac97); } if (dev->adev != NULL) { audio_dev_free(dev->adev); } if (dev->regsh != NULL) { ddi_regs_map_free(&dev->regsh); } if (dev->pcih != NULL) { pci_config_teardown(&dev->pcih); } kmem_free(dev, sizeof (*dev)); } void p16x_hwinit(p16x_dev_t *dev) { p16x_port_t *port; uint32_t paddr; uint32_t chunksz; int i; for (i = 0; i < 3; i++) { write_reg(dev, PTBA, i, 0); write_reg(dev, PTBS, i, 0); write_reg(dev, PTCA, i, 0); write_reg(dev, PFEA, i, 0); write_reg(dev, CPFA, i, 0); write_reg(dev, CPCAV, i, 0); write_reg(dev, CRFA, i, 0); write_reg(dev, CRCAV, i, 0); } write_reg(dev, SCS0, 0, 0x02108504); write_reg(dev, SCS1, 0, 0x02108504); write_reg(dev, SCS2, 0, 0x02108504); /* set the spdif/analog combo jack to analog out */ write_reg(dev, SPC, 0, 0x00000700); write_reg(dev, EA_aux, 0, 0x0001003f); port = dev->port[P16X_REC]; /* Set physical address of the DMA buffer */ write_reg(dev, RFBA, 0, port->buf_paddr); write_reg(dev, RFBS, 0, (port->buf_size) << 16); /* Set physical address of the DMA buffer */ port = dev->port[P16X_PLAY]; paddr = port->buf_paddr; chunksz = port->buf_frames * 4; write_reg(dev, PFBA, 0, paddr); write_reg(dev, PFBS, 0, chunksz << 16); paddr += chunksz; write_reg(dev, PFBA, 1, paddr); write_reg(dev, PFBS, 1, chunksz << 16); paddr += chunksz; write_reg(dev, PFBA, 2, paddr); write_reg(dev, PFBS, 2, chunksz << 16); OUTL(dev, 0x1080, GPIO); /* GPIO */ /* Clear any pending interrupts */ OUTL(dev, INTR_ALL, IP); OUTL(dev, 0, IE); OUTL(dev, 0x9, HC); /* Enable audio */ } int p16x_attach(dev_info_t *dip) { uint16_t vendor, device; p16x_dev_t *dev; ddi_acc_handle_t pcih; dev = kmem_zalloc(sizeof (*dev), KM_SLEEP); dev->dip = dip; ddi_set_driver_private(dip, dev); mutex_init(&dev->mutex, NULL, MUTEX_DRIVER, NULL); if ((dev->adev = audio_dev_alloc(dip, 0)) == NULL) { cmn_err(CE_WARN, "audio_dev_alloc failed"); goto error; } if (pci_config_setup(dip, &pcih) != DDI_SUCCESS) { audio_dev_warn(dev->adev, "pci_config_setup failed"); goto error; } dev->pcih = pcih; vendor = pci_config_get16(pcih, PCI_CONF_VENID); device = pci_config_get16(pcih, PCI_CONF_DEVID); if (vendor != CREATIVE_VENDOR_ID || device != SB_P16X_ID) { audio_dev_warn(dev->adev, "Hardware not recognized " "(vendor=%x, dev=%x)", vendor, device); goto error; } /* set PCI command register */ pci_config_put16(pcih, PCI_CONF_COMM, pci_config_get16(pcih, PCI_CONF_COMM) | PCI_COMM_MAE | PCI_COMM_IO); if ((ddi_regs_map_setup(dip, 1, &dev->base, 0, 0, &dev_attr, &dev->regsh)) != DDI_SUCCESS) { audio_dev_warn(dev->adev, "failed to map registers"); goto error; } audio_dev_set_description(dev->adev, "Creative Sound Blaster Live!"); audio_dev_set_version(dev->adev, "SBO200"); if ((p16x_alloc_port(dev, P16X_PLAY) != DDI_SUCCESS) || (p16x_alloc_port(dev, P16X_REC) != DDI_SUCCESS)) { goto error; } p16x_hwinit(dev); dev->ac97 = ac97_allocate(dev->adev, dip, p16x_read_ac97, p16x_write_ac97, dev); if (dev->ac97 == NULL) { audio_dev_warn(dev->adev, "failed to allocate ac97 handle"); goto error; } ac97_probe_controls(dev->ac97); /* remove the AC'97 controls we don't want to expose */ for (int i = 0; p16x_remove_ac97[i]; i++) { ac97_ctrl_t *ctrl; ctrl = ac97_control_find(dev->ac97, p16x_remove_ac97[i]); if (ctrl != NULL) { ac97_control_unregister(ctrl); } } ac97_register_controls(dev->ac97); if (audio_dev_register(dev->adev) != DDI_SUCCESS) { audio_dev_warn(dev->adev, "unable to register with framework"); goto error; } ddi_report_dev(dip); return (DDI_SUCCESS); error: p16x_destroy(dev); return (DDI_FAILURE); } int p16x_resume(dev_info_t *dip) { p16x_dev_t *dev; dev = ddi_get_driver_private(dip); p16x_hwinit(dev); ac97_reset(dev->ac97); audio_dev_resume(dev->adev); return (DDI_SUCCESS); } int p16x_detach(p16x_dev_t *dev) { if (audio_dev_unregister(dev->adev) != DDI_SUCCESS) return (DDI_FAILURE); p16x_destroy(dev); return (DDI_SUCCESS); } int p16x_suspend(p16x_dev_t *dev) { audio_dev_suspend(dev->adev); return (DDI_SUCCESS); } static int p16x_ddi_attach(dev_info_t *, ddi_attach_cmd_t); static int p16x_ddi_detach(dev_info_t *, ddi_detach_cmd_t); static int p16x_ddi_quiesce(dev_info_t *); static struct dev_ops p16x_dev_ops = { DEVO_REV, /* rev */ 0, /* refcnt */ NULL, /* getinfo */ nulldev, /* identify */ nulldev, /* probe */ p16x_ddi_attach, /* attach */ p16x_ddi_detach, /* detach */ nodev, /* reset */ NULL, /* cb_ops */ NULL, /* bus_ops */ NULL, /* power */ p16x_ddi_quiesce, /* quiesce */ }; static struct modldrv p16x_modldrv = { &mod_driverops, /* drv_modops */ "Creative P16X Audio", /* linkinfo */ &p16x_dev_ops, /* dev_ops */ }; static struct modlinkage modlinkage = { MODREV_1, { &p16x_modldrv, NULL } }; int _init(void) { int rv; audio_init_ops(&p16x_dev_ops, P16X_NAME); if ((rv = mod_install(&modlinkage)) != 0) { audio_fini_ops(&p16x_dev_ops); } return (rv); } int _fini(void) { int rv; if ((rv = mod_remove(&modlinkage)) == 0) { audio_fini_ops(&p16x_dev_ops); } return (rv); } int _info(struct modinfo *modinfop) { return (mod_info(&modlinkage, modinfop)); } int p16x_ddi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { switch (cmd) { case DDI_ATTACH: return (p16x_attach(dip)); case DDI_RESUME: return (p16x_resume(dip)); default: return (DDI_FAILURE); } } int p16x_ddi_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) { p16x_dev_t *dev; dev = ddi_get_driver_private(dip); switch (cmd) { case DDI_DETACH: return (p16x_detach(dev)); case DDI_SUSPEND: return (p16x_suspend(dev)); default: return (DDI_FAILURE); } } int p16x_ddi_quiesce(dev_info_t *dip) { p16x_dev_t *dev; dev = ddi_get_driver_private(dip); write_reg(dev, SA, 0, 0); OUTL(dev, 0x01, HC); return (DDI_SUCCESS); }