Adds the davbus and sound nodes like this: mac-io/davbus/sound
This enables audio playback for a Mac OS 9 and a Mac OS X guest.
To test sound playback go to the sound node: cd mac-io/davbus/sound
Then type: test-sound
This will play some tones.
Signed-off-by: John Arbuckle <programmingkidx(a)gmail.com>
---
v2 changes:
- Added sound test feature.
config/examples/ppc_config.xml | 1 +
drivers/build.xml | 1 +
drivers/macio.c | 2 +
drivers/pci.c | 4 +
drivers/screamer.c | 636 +++++++++++++++++++++++++++++++++++++++++
drivers/screamer.h | 11 +
6 files changed, 655 insertions(+)
create mode 100755 drivers/screamer.c
create mode 100644 drivers/screamer.h
diff --git a/config/examples/ppc_config.xml b/config/examples/ppc_config.xml
index 19dc043..908d497 100644
--- a/config/examples/ppc_config.xml
+++ b/config/examples/ppc_config.xml
@@ -84,5 +84,6 @@
<option name="CONFIG_DRIVER_USB" type="boolean" value="true"/>
<option name="CONFIG_DEBUG_USB" type="boolean" value="false"/>
<option name="CONFIG_USB_HID" type="boolean" value="true"/>
+ <option name="CONFIG_DRIVER_SCREAMER" type="boolean" value="true"/>
<option name="CONFIG_DRIVER_LSI_53C810" type="boolean" value="true"/>
<option name="CONFIG_DRIVER_VIRTIO_BLK" type="boolean" value="true"/>
diff --git a/drivers/build.xml b/drivers/build.xml
index 5a28bc2..575ac6a 100644
--- a/drivers/build.xml
+++ b/drivers/build.xml
@@ -22,6 +22,7 @@
<object source="pc_kbd.c" condition="DRIVER_PC_KBD"/>
<object source="pc_serial.c" condition="DRIVER_PC_SERIAL"/>
<object source="escc.c" condition="DRIVER_ESCC"/>
+ <object source="screamer.c" condition="DRIVER_SCREAMER"/>
<object source="fw_cfg.c" condition="DRIVER_FW_CFG"/>
<object source="usb.c" condition="DRIVER_USB"/>
<object source="usbhid.c" condition="USB_HID"/>
diff --git a/drivers/macio.c b/drivers/macio.c
index 496bab1..0f95705 100644
--- a/drivers/macio.c
+++ b/drivers/macio.c
@@ -21,6 +21,7 @@
#include "pmu.h"
#include "escc.h"
#include "drivers/pci.h"
+#include "screamer.h"
#define OW_IO_NVRAM_SIZE 0x00020000
#define OW_IO_NVRAM_OFFSET 0x00060000
@@ -391,6 +392,7 @@ ob_macio_keylargo_init(const char *path, phys_addr_t addr)
escc_init(path, addr);
macio_ide_init(path, addr, 2);
openpic_init(path, addr);
+ screamer_init(addr);
aliases = find_dev("/aliases");
set_property(aliases, "mac-io", path, strlen(path) + 1);
diff --git a/drivers/pci.c b/drivers/pci.c
index 9d501d3..b607fa5 100644
--- a/drivers/pci.c
+++ b/drivers/pci.c
@@ -1978,6 +1978,10 @@ static phandle_t ob_pci_host_set_interrupt_map(phandle_t host)
target_node = find_dev(path);
set_int_property(target_node, "interrupt-parent", dnode);
+ snprintf(buf, sizeof(buf), "%s/mac-io/davbus", path);
+ target_node = find_dev(buf);
+ set_int_property(target_node, "interrupt-parent", dnode);
+
return dnode;
}
diff --git a/drivers/screamer.c b/drivers/screamer.c
new file mode 100755
index 0000000..b9fced1
--- /dev/null
+++ b/drivers/screamer.c
@@ -0,0 +1,636 @@
+// File: screamer.c
+// Description: Implements the davbus and sound nodes for sound support.
+
+#include "screamer.h"
+#include "asm/types.h"
+#include "kernel/stack.h"
+#include "libopenbios/bindings.h"
+#include <stddef.h>
+#include <stdbool.h>
+#include "libc/vsprintf.h"
+#include "libc/stdlib.h"
+#include "timer.h"
+#include "libopenbios/ofmem.h"
+
+uint32_t screamer_base_address = 0;
+uint32_t dma_base_address = 0;
+uint32_t chip_freq = 8820;
+int *sample_array = NULL;
+int buffer_size = 0;
+int append_index = 0;
+int packet_size = 8000;
+
+#define DEAD 0x0800
+#define RUN 0x8000
+
+#define DEBUG 0
+#define debug_print(fmt, ...) do { \
+if (DEBUG) { \
+ printk(fmt, ## __VA_ARGS__); \
+} \
+} while (0)
+
+/* Prints to the console */
+static int print(const char *fmt, ...)
+{
+ va_list args;
+ int i;
+ char buf[100];
+ va_start(args, fmt);
+ i=vsprintf(buf,fmt,args);
+ va_end(args);
+ push_str(buf);
+ fword("type");
+ return i;
+}
+
+
+/* Makes the screen blank and places the cursor in the upper left corner */
+static void clear_screen(void)
+{
+ int i;
+
+ // clears the screen
+ for (i = 0; i < 100; i++) {
+ print("\n");
+ }
+
+ // makes cursor go back to the top left corner
+ for (i = 0; i < 100; i++) {
+ print("%c", 11);
+ }
+}
+
+
+/* Centers and prints a message on the screen */
+static void print_center(const char *message)
+{
+ int message_length = strlen(message);
+ int screen_width = 96;
+ int padding = (screen_width - message_length) / 2;
+ if (padding < 0) {
+ padding = 0;
+ }
+ char output_string[81];
+ memset(output_string, ' ', padding);
+ sprintf(output_string + padding, "%s", message);
+ print(output_string);
+}
+
+
+/* Writes a 32 bit value to the specified register in Screamer */
+static void write_screamer(int reg, uint32_t value)
+{
+ reg = reg << 4;
+ *(volatile uint32_t *)(screamer_base_address + reg) = value;
+ asm volatile("eieio" : : : "memory");
+}
+
+
+/* Set sampling rate to 8820 Hz */
+static void set_sampling_rate(void)
+{
+ write_screamer(0, 0x60000);
+}
+
+
+/* Sets the volume to the maximum value */
+static void set_volume(void)
+{
+ write_screamer(1, 0x400000);
+}
+
+
+/* Clears the audio buffer */
+static void clear_buffer(void)
+{
+ memset(sample_array, 0, buffer_size);
+ append_index = 0;
+}
+
+
+/* Initializes the tone playing system */
+static void initialize(void)
+{
+ static bool is_initialized = false;
+
+ /* Only need to do this once */
+ if (is_initialized) {
+ return;
+ }
+
+ /* Allocate memory for audio buffer */
+ int i, status, current_size = 2000000; /* Start big and work our way down */
+ for (i = 0; i < current_size; i++) {
+ status = ofmem_posix_memalign((void *)&sample_array, 32, sizeof(int) *
+ current_size);
+ if (status == 0) {
+ break;
+ }
+ current_size = current_size / 2;
+ }
+ if (!sample_array) {
+ print("Failed to allocate memory for samples!\n");
+ return;
+ } else {
+ debug_print("buffer size: %d\n", current_size);
+ }
+ set_sampling_rate();
+ set_volume();
+ is_initialized = true;
+ buffer_size = current_size;
+ clear_buffer();
+}
+
+
+/* Converts a big endian 16 bit value to little endian */
+static uint16_t convert2le16(uint16_t value)
+{
+ uint16_t byte1, byte2;
+ byte1 = (value & 0xff) << 8u;
+ byte2 = (value & 0xff00) >> 8u;
+ value = byte1 | byte2;
+ return value;
+}
+
+/* Converts a big endian 32 bit value to little endian */
+static uint32_t convert2le32(uint32_t value)
+{
+ uint32_t byte1, byte2, byte3, byte4;
+ byte1 = (value & 0xff) << 24u;
+ byte2 = (value & 0xff00) << 8u;
+ byte3 = (value & 0xff0000) >> 8u;
+ byte4 = (value & 0xff000000) >> 24u;
+ value = byte1 | byte2 | byte3 | byte4;
+ return value;
+}
+
+/* Read a register's value for channel 16 */
+static uint32_t read_dma(int reg)
+{
+ int channel = 16; // Screamer's output channel
+ uint32_t addr, return_value;
+ addr = channel << 7; // set channel
+ addr |= (4*reg); // set register
+ asm volatile("eieio" : : : "memory");
+ return_value = *(volatile uint32_t *)(dma_base_address + addr);
+ return_value = return_value >> 8;
+ return return_value;
+}
+
+/* Tell the dbdma the value to set for the register for channel 16 */
+static void write_dma(int reg, uint32_t value)
+{
+ int channel = 16; // Screamer's output channel
+ uint32_t addr;
+ addr = channel << 7; // set channel
+ addr |= (4*reg); // et register
+ value = convert2le32(value); // DMA controller uses little endian
+ *(volatile uint32_t *)(dma_base_address + addr) = value;
+ asm volatile("eieio" : : : "memory");
+}
+
+
+/* Programs the DMA controller to send our samples to Screamer */
+static void send_samples_to_dma(int offset)
+{
+ typedef struct dbdma_cmd {
+ uint16_t req_count; /* length of data */
+ uint16_t command; /* tells the dma what to do */
+ uint32_t phy_addr; /* address of the sample buffer */
+ uint32_t cmd_dep; /* command-dependent field - not used */
+ uint16_t res_count; /* not used */
+ uint16_t xfer_status; /* transfer status - not used */
+ } dbdma_cmd;
+
+ int num_of_descriptors;
+ if (packet_size > append_index) {
+ num_of_descriptors = 1;
+ } else {
+ num_of_descriptors = append_index/packet_size;
+ }
+ dbdma_cmd *descriptor_array;
+
+ /* DMA expects 16 bit alignment */
+ if (ofmem_posix_memalign((void *)&descriptor_array, 16, sizeof(dbdma_cmd) *
+ num_of_descriptors)) {
+ print("Error: failed to allocate memory for dbdma command!\n");
+ return;
+ }
+
+ /* === Initialize the descriptors === */
+ int i;
+ for (i = 0; i < num_of_descriptors; i++) {
+
+ /* Set the length of the data */
+ descriptor_array[i].req_count = convert2le16(packet_size);
+
+ if (i == (num_of_descriptors - 1)) {
+ /*
+ * Don't branch & OUTPUT_LAST & KEY_STREAM0 & interrupt never &
+ * wait never
+ */
+ descriptor_array[i].command = convert2le16(0x1000);
+ } else {
+ /*
+ * Don't branch & OUTPUT_MORE & KEY_STREAM0 & interrupt never &
+ * wait never
+ */
+ descriptor_array[i].command = 0;
+ }
+
+ /* Set the address of the buffer used to feed Screamer samples */
+ descriptor_array[i].phy_addr = convert2le32(sample_array +
+ (packet_size * i));
+ }
+ /* send the above structure to the DMA for parsing */
+ write_dma(3, descriptor_array);
+
+ /*
+ * Disables branch select feature - makes dma load another descriptor
+ * automatically using an array of descriptors. The branch() and next()
+ * functions of the DMA both load the next descriptor. The only difference
+ * is which variable it is loaded from. The dma's next() function seems
+ * easier to use because it simply expects an array of descriptors. The
+ * branch function would want a linked list of descriptors.
+ */
+ write_dma(5, 0);
+
+ /* Set to disable interrupts because we use polling instead */
+ write_dma(4, 0);
+
+ /*
+ * Sets the upper 32 bits of the address to zero since the CPU is only 32
+ * bits.
+ */
+ write_dma(2, 0);
+
+ /* Disable flushing so audio plays back correctly */
+ write_dma(1, 0);
+
+ /* Calls control write function - starts the whole DMA process */
+ write_dma(0, 0x84008400);
+}
+
+
+/* Adds samples to a buffer */
+static int append(int value)
+{
+ if (append_index >= buffer_size) {
+ print("Can't append to buffer - out of space\n");
+ return -1;
+ }
+ sample_array[append_index] = value;
+ append_index++;
+ return 0;
+}
+
+
+/* Creates a sample */
+static int get_sample(bool is_even)
+{
+ if (is_even) {
+ return 1;
+ } else {
+ return -1;
+ }
+}
+
+
+/* Delays execution by specified milliseconds */
+static void sleep(int milliseconds)
+{
+ mdelay(milliseconds);
+}
+
+
+/* Blocks execution while the dma plays the samples */
+static void play_samples(void)
+{
+ int i, iterations, offset;
+ uint32_t status;
+
+ /* Nothing to do */
+ if (append_index == 0) {
+ return;
+ }
+
+ if (packet_size > append_index) {
+ iterations = 1;
+ } else {
+ iterations = append_index/packet_size;
+ }
+
+ send_samples_to_dma(0);
+ for(i = 0; i < iterations; i++)
+ {
+ offset = i * packet_size;
+ status = read_dma(1);
+ debug_print("dma status: 0x%x\n", status);
+ while ((status & RUN) && !(status & DEAD)) {
+ debug_print("Still waiting - dma status: 0x%x\n", status);
+ sleep(10);
+ status = read_dma(1);
+ }
+
+ if (status & DEAD) {
+ debug_print("dma status is dead\n");
+ break;
+ }
+ }
+}
+
+
+/*
+ * Plays a note of a specified frequency and duration.
+ * Returns the number of samples added to the buffer.
+ * note_freq: in Hertz
+ * duration: in milliseconds
+ */
+static void play_note(uint32_t note_freq, int duration)
+{
+ // makes audio play for the correct duration
+ duration = duration * 2;
+
+ if (note_freq <= 0 || duration <= 0) {
+ print("Error: note frequency and duration must be greater than zero\n");
+ return;
+ }
+ int samples_between_changes = chip_freq/note_freq;
+ debug_print("chip_freq: %d\tnote_freq: %d\n", chip_freq, note_freq);
+ debug_print("samples between changes: %d\n", samples_between_changes);
+ int amplitude = 25000;
+ int iterations = (chip_freq * duration) / (samples_between_changes * 1000);
+ debug_print("iterations: %d\n", iterations);
+ int i, sample;
+ for (i = 0; i < iterations; i++) {
+ int s;
+ for (s = 0; s < samples_between_changes; s++) {
+ sample = get_sample(i % 2 == 0);
+ sample = sample * amplitude;
+ if (append(sample) != 0) { // if buffer is full
+ debug_print("Sending samples to screamer...\n");
+
+ /* Try to fix the problem with breaking up audio samples for the DMA */
+ append_index = append_index - s;
+ s = 0;
+
+ play_samples();
+ clear_buffer();
+ if (append(sample) != 0) {
+ print("Error: failure to add sample!\n");
+ }
+ }
+ }
+ }
+ play_samples();
+ clear_buffer();
+}
+
+
+/*
+ * Implements the Forth play-note word.
+ * ( frequency duration(in ms) - )
+ */
+ static void forth_play_note(void)
+ {
+ uint32_t note_freq, duration;
+ duration = POP();
+ note_freq = POP();
+ initialize();
+ clear_buffer();
+ play_note(note_freq, duration);
+ }
+
+
+/*
+ * Adds silence to the output.
+ * Returns the number of samples added to the buffer.
+ * duration: in milliseconds
+ */
+static void silence(int duration)
+{
+ // This make the correct amount of samples
+ duration = duration * 2;
+
+ int number_of_samples = chip_freq * duration / 1000;
+ debug_print("silence samples: %d\n", number_of_samples);
+ int i;
+ for (i = 0; i < number_of_samples; i++) {
+ if (append(0) != 0) {
+ play_samples();
+ clear_buffer();
+ }
+ }
+}
+
+
+/*
+ * Implements the Forth silence word.
+ * ( duration - )
+ */
+static void forth_silence(void)
+{
+ initialize();
+ int duration = POP();
+ silence(duration);
+}
+
+
+/* Creates a little song */
+static void create_samples(void)
+{
+ int i;
+ for (i = 0; i < 3; i++) {
+ play_note(400, 500);
+ play_note(600, 500);
+ play_note(800, 500);
+ play_note(1000, 500);
+ play_note(1200, 500);
+ silence(300);
+ }
+
+ play_note(400, 400);
+ silence(50);
+ play_note(400, 400);
+ silence(50);
+ play_note(800, 400);
+ silence(50);
+ play_note(800, 400);
+ silence(50);
+ play_note(900, 400);
+ silence(50);
+ play_note(900, 400);
+ silence(50);
+ play_note(800, 400);
+}
+
+
+/* Lets the user know the sound test is complete */
+static void end_test(void)
+{
+ clear_screen();
+ print_center("Testing Sound");
+ print("\n\n");
+ print_center("Done\n\n");
+ sleep(700);
+}
+
+
+/* Tells user testing has begun and creates and plays samples */
+static void play_sound(void)
+{
+ clear_screen();
+ print_center("Testing Sound");
+ print("\n\n");
+ print_center("Now playing...");
+ create_samples();
+ play_samples();
+}
+
+
+/* Displays a countdown informing the user testing is about to begin */
+static void display_countdown(void)
+{
+ clear_screen();
+ print_center("Testing Sound");
+ print("\n");
+ sleep(500);
+ print_center("3\n");
+ sleep(500);
+ print_center("2\n");
+ sleep(500);
+ print_center("1\n");
+ sleep(500);
+ print_center("0\n");
+ sleep(500);
+}
+
+
+/* Starts the sound test */
+static void test_sound(void)
+{
+ initialize();
+ display_countdown();
+ play_sound();
+ end_test();
+}
+
+
+/* Makes sound related functions available in Forth */
+static void add_words(phys_addr_t base_address)
+{
+ screamer_base_address = base_address + 0x14000;
+ dma_base_address = 0x80008000;
+
+ bind_func("test-sound", test_sound);
+ bind_func("play-note", forth_play_note);
+ bind_func("silence", forth_silence);
+}
+
+
+/* Add the sound node for the screamer sound chip */
+static void setup_screamer_node(phys_addr_t base_address)
+{
+ phandle_t node;
+
+ fword("new-device");
+ push_str("sound");
+ fword("device-name");
+
+ push_str("screamer");
+ fword("encode-string");
+ push_str("awacs");
+ fword("encode-string");
+ fword("encode+");
+ push_str("compatible");
+ fword("property");
+
+ push_str("343S0184");
+ fword("encode-string");
+ push_str("model");
+ fword("property");
+
+ node = get_cur_dev();
+
+ set_int_property(node, "#-outputs", 1);
+ set_int_property(node, "#-detects", 3);
+ set_int_property(node, "#-features", 3);
+ set_int_property(node, "object-model-version", 1);
+ set_int_property(node, "device-id", 5);
+
+ char *value = "init operation 2 param 00000001 param-size 4\0"
+ "feature index 0 model Proj7PowerControl\0"
+ "feature index 1 model USBSubwoofer\0"
+ "feature index 2 model NotifySSprockets\0"
+ "detect bit-mask 2 bit-match 2 device 2 index 0 model InSenseBitsDetect\0"
+ "detect bit-mask 4 bit-match 4 device 16 index 1 model InSenseBitsDetect\0"
+ "detect bit-mask 1 bit-match 0 device 32 index 2 model InSenseBitsDetect\0"
+ "input icon-id -16526 index 0 name-id -20520 port-connection 2 "
+ "port-type 0x656D6963 zero-gain 0 model ExternalMic\0"
+ "input icon-id -16526 index 1 name-id -20528 port-connection 2 "
+ "port-type 0x73696E6A zero-gain 0 model InputPort\0"
+ "input icon-id -20184 index 2 name-id -20540 port-connection 3 "
+ "port-type 0x6d6f646d zero-gain 0 model InputPort\0"
+ "input index 3 model NoInput\0"
+ "output device-mask 2 device-match 0 icon-id -16563 index 0 name-id -20525"
+ " port-connection 2 port-type 0x6973706B model OutputPort\0"
+ "output device-mask 2 device-match 2 icon-id -16563 index 1 name-id -20524"
+ " port-connection 1 port-type 0x6864706E model OutputPort\0";
+
+ set_property(node, "sound-objects", value, 996);
+ add_words(base_address);
+ fword("finish-device");
+}
+
+
+/* Add the davbus node */
+static void setup_davbus_node(phys_addr_t base_address)
+{
+ phandle_t node;
+ fword("new-device");
+
+ push_str("davbus");
+ fword("device-name");
+
+ push_str("soundbus");
+ fword("encode-string");
+ push_str("device_type");
+ fword("property");
+
+ push_str("davbus");
+ fword("encode-string");
+ push_str("compatible");
+ fword("property");
+
+ node = get_cur_dev();
+ int properties[6];
+
+ /* reg property */
+ properties[0] = 0x14000;
+ properties[1] = 0x1000;
+ properties[2] = 0x8800;
+ properties[3] = 0x100;
+ properties[4] = 0x8900;
+ properties[5] = 0x100;
+ set_property(node, "reg", (char *)properties, sizeof(int) * 6);
+
+ /* interrupt property */
+ properties[0] = 0x18;
+ properties[1] = 1;
+ properties[2] = 9;
+ properties[3] = 0;
+ properties[4] = 0xa;
+ properties[5] = 0;
+ set_property(node, "interrupts", (char *)properties, sizeof(int) * 6);
+
+ setup_screamer_node(base_address);
+
+ fword("finish-device");
+}
+
+
+void screamer_init(phys_addr_t base_address)
+{
+ setup_davbus_node(base_address);
+}
diff --git a/drivers/screamer.h b/drivers/screamer.h
new file mode 100644
index 0000000..64d9443
--- /dev/null
+++ b/drivers/screamer.h
@@ -0,0 +1,11 @@
+// File: screamer.h
+// Description: header file for screamer.c
+
+#ifndef screamer_h
+#define screamer_h
+
+#include "drivers/drivers.h"
+
+void screamer_init(phys_addr_t base_address);
+
+#endif /* screamer_h */
--
2.14.3 (Apple Git-98)