[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[cdi-devel] [RFC] CDI.audio: ac97



From: Max Reitz <max@xxxxxxxxxx>

This is a sample driver for AC97 using the proposed CDI.audio interface
(with some slight changes, e.g., num_buffers instead of buffers in
struct cdi_audio_stream and the usage of cdi_mem_area for
transfer_data).

I don't know if it's the best example because AC97 doesn't support
hardware mixing nor do I know anything about recording, thus this isn't
supported either. However, I hope it makes clear how the interface is
meant to be used.

Max
---
 ac97/device.c |  296 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 ac97/device.h |  116 ++++++++++++++++++++++
 ac97/main.c   |   65 +++++++++++++
 3 files changed, 477 insertions(+), 0 deletions(-)
 create mode 100644 ac97/device.c
 create mode 100644 ac97/device.h
 create mode 100644 ac97/main.c

diff --git a/ac97/device.c b/ac97/device.c
new file mode 100644
index 0000000..4525e8d
--- /dev/null
+++ b/ac97/device.c
@@ -0,0 +1,296 @@
+/******************************************************************************
+ * Copyright (c) 2010 Max Reitz                                               *
+ *                                                                            *
+ * Permission is hereby granted,  free of charge,  to any person  obtaining a *
+ * copy of this software and associated documentation files (the "Software"), *
+ * to deal in the Software without restriction,  including without limitation *
+ * the rights to use, copy,  modify, merge, publish,  distribute, sublicense, *
+ * and/or  sell copies of the  Software,  and to permit  persons to whom  the *
+ * Software is furnished to do so, subject to the following conditions:       *
+ *                                                                            *
+ * The above copyright notice and this permission notice shall be included in *
+ * all copies or substantial portions of the Software.                        *
+ *                                                                            *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
+ * IMPLIED,  INCLUDING BUT NOT LIMITED TO THE WARRANTIES  OF MERCHANTABILITY, *
+ * FITNESS  FOR A PARTICULAR  PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL *
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
+ * LIABILITY,  WHETHER IN AN ACTION OF CONTRACT,  TORT OR OTHERWISE,  ARISING *
+ * FROM,  OUT OF  OR IN CONNECTION  WITH  THE SOFTWARE  OR THE  USE OR  OTHER *
+ * DEALINGS IN THE SOFTWARE.                                                  *
+ ******************************************************************************/
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "cdi.h"
+#include "cdi/audio.h"
+#include "cdi/io.h"
+#include "cdi/lists.h"
+#include "cdi/mem.h"
+#include "cdi/misc.h"
+#include "cdi/pci.h"
+
+#include "device.h"
+
+static void ac97_isr(struct cdi_device* device);
+
+struct cdi_device* ac97_init_device(struct cdi_bus_data* bus_data)
+{
+    struct cdi_pci_device* pci = (struct cdi_pci_device*) bus_data;
+
+    // Search for a usable device
+    if ((pci->vendor_id != 0x8086) || ((pci->device_id != 0x2415) &&
+        (pci->device_id != 0x2425) &&  (pci->device_id != 0x2445)))
+    {
+        return NULL;
+    }
+
+    struct ac97_device* dev = calloc(1, sizeof(*dev));
+
+    cdi_pci_alloc_ioports(pci);
+
+    // Get the I/O resources (NAMBAR and NABMBAR)
+    struct cdi_pci_resource* res;
+    for (int i = 0; (res = cdi_list_get(pci->resources, i)); i++) {
+        if (res->type == CDI_PCI_IOPORTS) {
+            if (!res->index) {
+                dev->nambar = res->start;
+            } else {
+                dev->nabmbar = res->start;
+                break;
+            }
+        }
+    }
+
+    if (!dev->nambar || !dev->nabmbar) {
+        free(dev);
+        cdi_pci_free_ioports(pci);
+        return NULL;
+    }
+
+    #ifdef CDI_PCI_DIRECT_ACCESS
+    // If possible, activate the I/O resources and the busmaster capability
+    cdi_pci_config_writew(pci, 4, cdi_pci_config_readw(pci, 4) | 5);
+    #endif
+
+    struct ac97_card* ac97_card = malloc(sizeof(*ac97_card));
+    struct ac97_stream* ac97_stream = malloc(sizeof(*ac97_stream));
+
+    // Define the CDI.audio sound card
+    ac97_card->card.record = 0;
+    ac97_card->card.streams = cdi_list_create();
+    ac97_card->device = dev;
+
+    // And the only stream
+    ac97_stream->stream.device = &ac97_card->card;
+    ac97_stream->stream.num_buffers = 32;
+    ac97_stream->stream.buffer_size = 0xFFFE;
+    ac97_stream->stream.sample_format = CDI_AUDIO_16SI;
+
+    dev->output = &ac97_stream->stream;
+
+    cdi_list_push(ac97_card->card.streams, ac97_stream);
+
+    // Register the IRQ
+    cdi_register_irq(pci->irq, &ac97_isr, &ac97_card->card.dev);
+
+    // Reset the card
+    cdi_outw(dev->nambar + NAM_RESET, 42);
+
+    cdi_sleep_ms(100);
+
+    // Reset some more...
+    cdi_outb(dev->nabmbar + NABM_GLB_CTRL_STAT, 0x02);
+
+    cdi_sleep_ms(100);
+
+    // And still something...
+    cdi_outw(dev->nabmbar + NABM_PICONTROL, 0x02);
+    cdi_outw(dev->nabmbar + NABM_POCONTROL, 0x02);
+    cdi_outw(dev->nabmbar + NABM_MCCONTROL, 0x02);
+
+    cdi_sleep_ms(100);
+
+    // Set volume to 100 %
+    cdi_outw(dev->nambar + NAM_MASTER_VOLUME, 0x0000);
+    cdi_outw(dev->nambar + NAM_MONO_VOLUME, 0x00);
+    cdi_outw(dev->nambar + NAM_PC_BEEP, 0x00);
+    cdi_outw(dev->nambar + NAM_PCM_VOLUME, 0x0000);
+
+    cdi_sleep_ms(10);
+
+    // Test if sample rate is adjustable
+    if (!(cdi_inw(dev->nambar + NAM_EXT_AUDIO_ID) & 1)) {
+        // No it isn't; it's fixed to 48 kHz
+        ac97_stream->stream.fixed_sample_rate = 48000;
+    } else {
+        // Yes, it is
+        ac97_stream->stream.fixed_sample_rate = 0;
+
+        // Use that capability (Variable Audio Rate)
+        cdi_outw(dev->nambar + NAM_EXT_AUDIO_STC,
+            cdi_inw(dev->nambar + NAM_EXT_AUDIO_STC) | 1);
+
+        cdi_sleep_ms(10);
+
+        // Set the sample rate to 44.1 kHz
+        cdi_outw(dev->nambar + NAM_FRONT_SPLRATE, 44100);
+        cdi_outw(dev->nambar + NAM_LR_SPLRATE, 44100);
+    }
+
+    // TODO: Error handling
+
+    // Allocate the buffer descriptor array
+    struct cdi_mem_area* bd = cdi_mem_alloc(32 * sizeof(dev->bd[0]),
+        3 | CDI_MEM_DMA_4G | CDI_MEM_PHYS_CONTIGUOUS);
+
+    dev->bd  = bd->vaddr;
+    dev->vbd = calloc(32, sizeof(dev->vbd[0]));
+
+    for (int i = 0; i < 32; i++)
+    {
+        // Allocate each buffer (actually, their sizes equals 131068, but who
+        // cares about those four bytes)
+        struct cdi_mem_area* buffer = cdi_mem_alloc(131072,
+            12 | CDI_MEM_DMA_4G | CDI_MEM_PHYS_CONTIGUOUS);
+
+        dev->bd[i].buffer = buffer->paddr.items->start;
+        dev->bd[i].length = 0xFFFE;
+        // Generate an IRQ on completion
+        dev->bd[i].flags = FLAG_IOC;
+        dev->vbd[i] = buffer->vaddr;
+
+        // Zero the buffer
+        memset(buffer->vaddr, 0, 131072);
+    }
+
+    // Set the base address of the descriptor array
+    cdi_outl(dev->nabmbar + NABM_POBDBAR, bd->paddr.items->start);
+
+    // Return the new device
+    return &ac97_card->card.dev;
+}
+
+void ac97_remove_device(struct cdi_device* device)
+{
+    // TODO: Well...
+    (void) device;
+}
+
+int ac97_transfer_data(struct cdi_audio_stream* stream, size_t buffer,
+    struct cdi_mem_area* memory)
+{
+    // Check if buffer index is valid
+    if (buffer >= 32)
+        return -1;
+
+    struct ac97_stream* s = (struct ac97_stream*) stream;
+
+    // Copy the content to the AC97 buffer
+    memcpy(s->device->vbd[buffer], memory, 0xFFFE);
+
+    return 0;
+}
+
+cdi_audio_status_t ac97_change_status(struct cdi_audio_device* device,
+    cdi_audio_status_t status)
+{
+    struct ac97_device* dev = ((struct ac97_card*) device)->device;
+
+    if (status == CDI_AUDIO_PLAY) {
+        // Start playback
+        cdi_outb(dev->nabmbar + NABM_POCONTROL, 0x11);
+        // Set last valid buffer index to 31
+        cdi_outb(dev->nabmbar + NABM_POLVI, 31);
+    } else if (status == CDI_AUDIO_STOP) {
+        // Stop playback
+        cdi_outb(dev->nabmbar + NABM_POCONTROL, 0x00);
+    }
+
+    // Returns whether the card is active or not
+    return cdi_inb(dev->nabmbar + NABM_POCONTROL) & 0x1;
+}
+
+void ac97_set_volume(struct cdi_audio_stream* stream, uint8_t volume)
+{
+    struct ac97_device* dev = ((struct ac97_stream*) stream)->device;
+
+    // Calculate the AC97 volume (which is 63 - (volume * 63 / 255))
+    int rvolume = volume * 63;
+    rvolume /= 255;
+
+    rvolume = 63 - rvolume;
+
+    // And set it
+    cdi_outw(dev->nambar + NAM_MASTER_VOLUME, (rvolume << 8) | rvolume);
+}
+
+int ac97_set_sample_rate(struct cdi_audio_stream* stream, int sample_rate)
+{
+    struct ac97_device* dev = ((struct ac97_stream*) stream)->device;
+
+    if (stream->fixed_sample_rate) {
+        // If the sample rate cannot be adjustet, return the fixed value
+        return stream->fixed_sample_rate;
+    }
+
+    if (sample_rate > 0) {
+        // Try to set the requested sample rate
+        cdi_outw(dev->nambar + NAM_FRONT_SPLRATE, sample_rate);
+        cdi_outw(dev->nambar + NAM_LR_SPLRATE, sample_rate);
+
+        cdi_sleep_ms(10);
+    }
+
+    // And return the actual rate set
+    return cdi_inw(dev->nambar + NAM_FRONT_SPLRATE);
+}
+
+void ac97_get_position(struct cdi_audio_stream* stream,
+    cdi_audio_position_t* position)
+{
+    struct ac97_device* dev = ((struct ac97_stream*) stream)->device;
+
+    position->buffer = cdi_inb(dev->nabmbar + NABM_POCIV);
+    // Yes, really 0x7FFE, not 0x7FFF
+    // (POPICB contains the number of samples not yet read from the buffer,
+    // we are required to return the number of frames played)
+    position->frame = 0x7FFE - cdi_inw(dev->nabmbar + NABM_POPICB) / 2;
+}
+
+int ac97_set_channel_count(struct cdi_audio_device* dev, int channels)
+{
+    (void) dev;
+    (void) channels;
+
+    // TODO: Is that really fixed or is there any way to use e.g. mono?
+    return 2;
+}
+
+static void ac97_isr(struct cdi_device* device)
+{
+    struct ac97_device* dev = ((struct ac97_card*) device)->device;
+
+    int pi = cdi_inb(dev->nabmbar + NABM_PISTATUS) & 0x1C;
+    int po = cdi_inb(dev->nabmbar + NABM_POSTATUS) & 0x1C;
+    int mc = cdi_inb(dev->nabmbar + NABM_MCSTATUS) & 0x1C;
+
+    // Check if just anything happened
+    if (!pi && !po && !mc) {
+        return;
+    }
+
+    // Tell the card we handled everything (no, we don't, but who cares).
+    cdi_outb(dev->nabmbar + NABM_PISTATUS, pi);
+    cdi_outb(dev->nabmbar + NABM_POSTATUS, po);
+    cdi_outb(dev->nabmbar + NABM_MCSTATUS, mc);
+
+    if (po & (1 << 3)) {
+        // If a buffer has been completed, tell that to the CDI library
+        cdi_audio_buffer_completed(dev->output,
+            cdi_inb(dev->nabmbar + NABM_POCIV));
+    }
+}
+
diff --git a/ac97/device.h b/ac97/device.h
new file mode 100644
index 0000000..e096722
--- /dev/null
+++ b/ac97/device.h
@@ -0,0 +1,116 @@
+/******************************************************************************
+ * Copyright (c) 2010 Max Reitz                                               *
+ *                                                                            *
+ * Permission is hereby granted,  free of charge,  to any person  obtaining a *
+ * copy of this software and associated documentation files (the "Software"), *
+ * to deal in the Software without restriction,  including without limitation *
+ * the rights to use, copy,  modify, merge, publish,  distribute, sublicense, *
+ * and/or  sell copies of the  Software,  and to permit  persons to whom  the *
+ * Software is furnished to do so, subject to the following conditions:       *
+ *                                                                            *
+ * The above copyright notice and this permission notice shall be included in *
+ * all copies or substantial portions of the Software.                        *
+ *                                                                            *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
+ * IMPLIED,  INCLUDING BUT NOT LIMITED TO THE WARRANTIES  OF MERCHANTABILITY, *
+ * FITNESS  FOR A PARTICULAR  PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL *
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
+ * LIABILITY,  WHETHER IN AN ACTION OF CONTRACT,  TORT OR OTHERWISE,  ARISING *
+ * FROM,  OUT OF  OR IN CONNECTION  WITH  THE SOFTWARE  OR THE  USE OR  OTHER *
+ * DEALINGS IN THE SOFTWARE.                                                  *
+ ******************************************************************************/
+
+#ifndef DEVICE_H
+#define DEVICE_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "cdi.h"
+#include "cdi/audio.h"
+#include "cdi/mem.h"
+#include "cdi/pci.h"
+
+// Some I/O port offsets
+// NAMBAR
+#define NAM_RESET          0x0000
+#define NAM_MASTER_VOLUME  0x0002
+#define NAM_MONO_VOLUME    0x0006
+#define NAM_PC_BEEP        0x000A
+#define NAM_PCM_VOLUME     0x0018
+#define NAM_EXT_AUDIO_ID   0x0028
+#define NAM_EXT_AUDIO_STC  0x002A
+#define NAM_FRONT_SPLRATE  0x002C
+#define NAM_LR_SPLRATE     0x0032
+// NABMBAR
+#define NABM_POBDBAR       0x0010
+#define NABM_POCIV         0x0014
+#define NABM_POLVI         0x0015
+#define NABM_PISTATUS      0x0006
+#define NABM_POSTATUS      0x0016
+#define NABM_MCSTATUS      0x0026
+#define NABM_POPICB        0x0018
+#define NABM_PICONTROL     0x000B
+#define NABM_POCONTROL     0x001B
+#define NABM_MCCONTROL     0x002B
+#define NABM_GLB_STAT      0x0030
+#define NABM_GLB_CTRL_STAT 0x0060
+
+// Buffer flags which may be set
+// Buffer underrun policy: If set, output 0 after the last sample, if there's no
+// new buffer; else, output the last sample.
+#define FLAG_BUP (1 << 14)
+// Interrupt on completion: Request an interrupt when having completed the
+// buffer
+#define FLAG_IOC (1 << 15)
+
+// A descriptor describing a buffer
+struct buf_desc
+{
+    // Physical address
+    uint32_t buffer;
+    // Number of samples contained
+    uint16_t length;
+    // Flags (BUP and/or IOC)
+    uint16_t flags;
+} __attribute__((packed));
+
+// A structure describing an AC97 card
+struct ac97_device {
+    // The two I/O spaces
+    uint16_t nambar, nabmbar;
+
+    // The buffer descriptor array
+    struct buf_desc* bd;
+    // The buffer's virtual addresses
+    void** vbd;
+
+    // Pointer to the output stream
+    struct cdi_audio_stream* output;
+};
+
+struct ac97_stream {
+    struct cdi_audio_stream stream;
+    struct ac97_device* device;
+};
+
+struct ac97_card {
+    struct cdi_audio_device card;
+    struct ac97_device* device;
+};
+
+struct cdi_device* ac97_init_device(struct cdi_bus_data* bus_data);
+void ac97_remove_device(struct cdi_device* device);
+
+int ac97_transfer_data(struct cdi_audio_stream* stream, size_t buffer,
+    struct cdi_mem_area* memory);
+cdi_audio_status_t ac97_change_status(struct cdi_audio_device* device,
+    cdi_audio_status_t status);
+void ac97_set_volume(struct cdi_audio_stream* stream, uint8_t volume);
+int ac97_set_sample_rate(struct cdi_audio_stream* stream, int sample_rate);
+void ac97_get_position(struct cdi_audio_stream* stream,
+    cdi_audio_position_t* position);
+int ac97_set_channel_count(struct cdi_audio_device* dev, int channels);
+
+#endif
+
diff --git a/ac97/main.c b/ac97/main.c
new file mode 100644
index 0000000..5afa557
--- /dev/null
+++ b/ac97/main.c
@@ -0,0 +1,65 @@
+/******************************************************************************
+ * Copyright (c) 2010 Max Reitz                                               *
+ *                                                                            *
+ * Permission is hereby granted,  free of charge,  to any person  obtaining a *
+ * copy of this software and associated documentation files (the "Software"), *
+ * to deal in the Software without restriction,  including without limitation *
+ * the rights to use, copy,  modify, merge, publish,  distribute, sublicense, *
+ * and/or  sell copies of the  Software,  and to permit  persons to whom  the *
+ * Software is furnished to do so, subject to the following conditions:       *
+ *                                                                            *
+ * The above copyright notice and this permission notice shall be included in *
+ * all copies or substantial portions of the Software.                        *
+ *                                                                            *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
+ * IMPLIED,  INCLUDING BUT NOT LIMITED TO THE WARRANTIES  OF MERCHANTABILITY, *
+ * FITNESS  FOR A PARTICULAR  PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL *
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
+ * LIABILITY,  WHETHER IN AN ACTION OF CONTRACT,  TORT OR OTHERWISE,  ARISING *
+ * FROM,  OUT OF  OR IN CONNECTION  WITH  THE SOFTWARE  OR THE  USE OR  OTHER *
+ * DEALINGS IN THE SOFTWARE.                                                  *
+ ******************************************************************************/
+
+#include "cdi/audio.h"
+
+#include "device.h"
+
+#define DRIVER_NAME "ac97"
+
+static struct cdi_audio_driver driver;
+
+static int ac97_driver_init(void)
+{
+    cdi_driver_init(&driver.drv);
+
+    return 0;
+}
+
+static int ac97_driver_destroy(void)
+{
+    cdi_driver_destroy(&driver.drv);
+
+    return 0;
+}
+
+static struct cdi_audio_driver driver = {
+    .drv = {
+        .name          = DRIVER_NAME,
+        .type          = CDI_AUDIO,
+        .bus           = CDI_PCI,
+        .init          = ac97_driver_init,
+        .destroy       = ac97_driver_destroy,
+        .init_device   = ac97_init_device,
+        .remove_device = ac97_remove_device
+    },
+
+    .transfer_data          = ac97_transfer_data,
+    .change_device_status   = ac97_change_status,
+    .set_volume             = ac97_set_volume,
+    .set_sample_rate        = ac97_set_sample_rate,
+    .get_position           = ac97_get_position,
+    .set_number_of_channels = ac97_set_channel_count
+};
+
+CDI_DRIVER(DRIVER_NAME, driver)
+
-- 
1.7.1