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@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 */