diff options
Diffstat (limited to 'target/linux/goldfish/patches-2.6.30/0120--ARM-goldfish-Add-audio-driver-for-goldfish.patch')
-rw-r--r-- | target/linux/goldfish/patches-2.6.30/0120--ARM-goldfish-Add-audio-driver-for-goldfish.patch | 404 |
1 files changed, 404 insertions, 0 deletions
diff --git a/target/linux/goldfish/patches-2.6.30/0120--ARM-goldfish-Add-audio-driver-for-goldfish.patch b/target/linux/goldfish/patches-2.6.30/0120--ARM-goldfish-Add-audio-driver-for-goldfish.patch new file mode 100644 index 0000000000..23f4b8f659 --- /dev/null +++ b/target/linux/goldfish/patches-2.6.30/0120--ARM-goldfish-Add-audio-driver-for-goldfish.patch @@ -0,0 +1,404 @@ +From aefe5257bf3ed9bf950ad962a63e5315566c2f97 Mon Sep 17 00:00:00 2001 +From: Mike Lockwood <lockwood@google.com> +Date: Mon, 23 Jul 2007 09:31:54 -0400 +Subject: [PATCH 120/134] [ARM] goldfish: Add audio driver for goldfish. + +Signed-off-by: Mike A. Chan <mikechan@google.com> +--- + arch/arm/mach-goldfish/Makefile | 2 +- + arch/arm/mach-goldfish/audio.c | 379 +++++++++++++++++++++++++++++++++++++++ + 2 files changed, 380 insertions(+), 1 deletions(-) + create mode 100644 arch/arm/mach-goldfish/audio.c + +--- a/arch/arm/mach-goldfish/Makefile ++++ b/arch/arm/mach-goldfish/Makefile +@@ -4,6 +4,6 @@ + + # Object file lists. + +-obj-y := pdev_bus.o timer.o ++obj-y := pdev_bus.o timer.o audio.o + obj-$(CONFIG_MACH_GOLDFISH) += board-goldfish.o + +--- /dev/null ++++ b/arch/arm/mach-goldfish/audio.c +@@ -0,0 +1,379 @@ ++/* arch/arm/mach-goldfish/audio.c ++** ++** Copyright (C) 2007 Google, Inc. ++** ++** This software is licensed under the terms of the GNU General Public ++** License version 2, as published by the Free Software Foundation, and ++** may be copied, distributed, and modified under those terms. ++** ++** This program is distributed in the hope that it will be useful, ++** but WITHOUT ANY WARRANTY; without even the implied warranty of ++** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++** GNU General Public License for more details. ++** ++*/ ++ ++#include <linux/module.h> ++#include <linux/miscdevice.h> ++#include <linux/fs.h> ++#include <linux/platform_device.h> ++ ++#include <linux/types.h> ++#include <linux/pci.h> ++#include <linux/interrupt.h> ++ ++#include <asm/types.h> ++#include <asm/io.h> ++#include <asm/uaccess.h> ++ ++ ++MODULE_AUTHOR("Google, Inc."); ++MODULE_DESCRIPTION("Android QEMU Audio Driver"); ++MODULE_LICENSE("GPL"); ++MODULE_VERSION("1.0"); ++ ++struct goldfish_audio { ++ uint32_t reg_base; ++ int irq; ++ spinlock_t lock; ++ wait_queue_head_t wait; ++ ++ char __iomem *buffer_virt; /* combined buffer virtual address */ ++ unsigned long buffer_phys; /* combined buffer physical address */ ++ ++ char __iomem *write_buffer1; /* write buffer 1 virtual address */ ++ char __iomem *write_buffer2; /* write buffer 2 virtual address */ ++ char __iomem *read_buffer; /* read buffer virtual address */ ++ int buffer_status; ++ int read_supported; /* true if we have audio input support */ ++}; ++ ++/* We will allocate two read buffers and two write buffers. ++ Having two read buffers facilitate stereo -> mono conversion. ++ Having two write buffers facilitate interleaved IO. ++*/ ++#define READ_BUFFER_SIZE 16384 ++#define WRITE_BUFFER_SIZE 16384 ++#define COMBINED_BUFFER_SIZE ((2 * READ_BUFFER_SIZE) + (2 * WRITE_BUFFER_SIZE)) ++ ++#define GOLDFISH_AUDIO_READ(data, addr) (readl(data->reg_base + addr)) ++#define GOLDFISH_AUDIO_WRITE(data, addr, x) (writel(x, data->reg_base + addr)) ++ ++/* temporary variable used between goldfish_audio_probe() and goldfish_audio_open() */ ++static struct goldfish_audio* audio_data; ++ ++enum { ++ /* audio status register */ ++ AUDIO_INT_STATUS = 0x00, ++ /* set this to enable IRQ */ ++ AUDIO_INT_ENABLE = 0x04, ++ /* set these to specify buffer addresses */ ++ AUDIO_SET_WRITE_BUFFER_1 = 0x08, ++ AUDIO_SET_WRITE_BUFFER_2 = 0x0C, ++ /* set number of bytes in buffer to write */ ++ AUDIO_WRITE_BUFFER_1 = 0x10, ++ AUDIO_WRITE_BUFFER_2 = 0x14, ++ ++ /* true if audio input is supported */ ++ AUDIO_READ_SUPPORTED = 0x18, ++ /* buffer to use for audio input */ ++ AUDIO_SET_READ_BUFFER = 0x1C, ++ ++ /* driver writes number of bytes to read */ ++ AUDIO_START_READ = 0x20, ++ ++ /* number of bytes available in read buffer */ ++ AUDIO_READ_BUFFER_AVAILABLE = 0x24, ++ ++ /* AUDIO_INT_STATUS bits */ ++ ++ /* this bit set when it is safe to write more bytes to the buffer */ ++ AUDIO_INT_WRITE_BUFFER_1_EMPTY = 1U << 0, ++ AUDIO_INT_WRITE_BUFFER_2_EMPTY = 1U << 1, ++ AUDIO_INT_READ_BUFFER_FULL = 1U << 2, ++ ++ AUDIO_INT_MASK = AUDIO_INT_WRITE_BUFFER_1_EMPTY | ++ AUDIO_INT_WRITE_BUFFER_2_EMPTY | ++ AUDIO_INT_READ_BUFFER_FULL, ++}; ++ ++ ++static atomic_t open_count = ATOMIC_INIT(0); ++ ++ ++static ssize_t goldfish_audio_read(struct file *fp, char __user *buf, ++ size_t count, loff_t *pos) ++{ ++ struct goldfish_audio* data = fp->private_data; ++ int length; ++ int result = 0; ++ ++ if (!data->read_supported) ++ return -ENODEV; ++ ++ while (count > 0) { ++ length = (count > READ_BUFFER_SIZE ? READ_BUFFER_SIZE : count); ++ GOLDFISH_AUDIO_WRITE(data, AUDIO_START_READ, length); ++ ++ wait_event_interruptible(data->wait, (data->buffer_status & AUDIO_INT_READ_BUFFER_FULL)); ++ ++ length = GOLDFISH_AUDIO_READ(data, AUDIO_READ_BUFFER_AVAILABLE); ++ ++ /* copy data to user space */ ++ if (copy_to_user(buf, data->read_buffer, length)) ++ { ++ printk("copy_from_user failed!\n"); ++ return -EFAULT; ++ } ++ ++ result += length; ++ buf += length; ++ count -= length; ++ } ++ ++ return result; ++} ++ ++static ssize_t goldfish_audio_write(struct file *fp, const char __user *buf, ++ size_t count, loff_t *pos) ++{ ++ struct goldfish_audio* data = fp->private_data; ++ unsigned long irq_flags; ++ ssize_t result = 0; ++ char __iomem *kbuf; ++ ++ while (count > 0) ++ { ++ ssize_t copy = count; ++ if (copy > WRITE_BUFFER_SIZE) ++ copy = WRITE_BUFFER_SIZE; ++ wait_event_interruptible(data->wait, ++ (data->buffer_status & (AUDIO_INT_WRITE_BUFFER_1_EMPTY | AUDIO_INT_WRITE_BUFFER_2_EMPTY))); ++ ++ if ((data->buffer_status & AUDIO_INT_WRITE_BUFFER_1_EMPTY) != 0) { ++ kbuf = data->write_buffer1; ++ } else { ++ kbuf = data->write_buffer2; ++ } ++ ++ /* copy from user space to the appropriate buffer */ ++ if (copy_from_user(kbuf, buf, copy)) ++ { ++ printk("copy_from_user failed!\n"); ++ result = -EFAULT; ++ break; ++ } ++ else ++ { ++ spin_lock_irqsave(&data->lock, irq_flags); ++ ++ /* clear the buffer empty flag, and signal the emulator to start writing the buffer */ ++ if (kbuf == data->write_buffer1) { ++ data->buffer_status &= ~AUDIO_INT_WRITE_BUFFER_1_EMPTY; ++ GOLDFISH_AUDIO_WRITE(data, AUDIO_WRITE_BUFFER_1, copy); ++ } else { ++ data->buffer_status &= ~AUDIO_INT_WRITE_BUFFER_2_EMPTY; ++ GOLDFISH_AUDIO_WRITE(data, AUDIO_WRITE_BUFFER_2, copy); ++ } ++ ++ spin_unlock_irqrestore(&data->lock, irq_flags); ++ } ++ ++ buf += copy; ++ result += copy; ++ count -= copy; ++ } ++ ++ return result; ++} ++ ++static int goldfish_audio_open(struct inode *ip, struct file *fp) ++{ ++ if (!audio_data) ++ return -ENODEV; ++ ++ if (atomic_inc_return(&open_count) == 1) ++ { ++ fp->private_data = audio_data; ++ audio_data->buffer_status = (AUDIO_INT_WRITE_BUFFER_1_EMPTY | AUDIO_INT_WRITE_BUFFER_2_EMPTY); ++ GOLDFISH_AUDIO_WRITE(audio_data, AUDIO_INT_ENABLE, AUDIO_INT_MASK); ++ return 0; ++ } ++ else ++ { ++ atomic_dec(&open_count); ++ return -EBUSY; ++ } ++} ++ ++static int goldfish_audio_release(struct inode *ip, struct file* fp) ++{ ++ atomic_dec(&open_count); ++ GOLDFISH_AUDIO_WRITE(audio_data, AUDIO_INT_ENABLE, 0); ++ return 0; ++} ++ ++static int goldfish_audio_ioctl(struct inode* ip, struct file* fp, unsigned int cmd, unsigned long arg) ++{ ++ /* temporary workaround, until we switch to the ALSA API */ ++ if (cmd == 315) ++ return -1; ++ else ++ return 0; ++} ++ ++static irqreturn_t ++goldfish_audio_interrupt(int irq, void *dev_id) ++{ ++ unsigned long irq_flags; ++ struct goldfish_audio *data = dev_id; ++ uint32_t status; ++ ++ spin_lock_irqsave(&data->lock, irq_flags); ++ ++ /* read buffer status flags */ ++ status = GOLDFISH_AUDIO_READ(data, AUDIO_INT_STATUS); ++ status &= AUDIO_INT_MASK; ++ /* if buffers are newly empty, wake up blocked goldfish_audio_write() call */ ++ if(status) { ++ data->buffer_status = status; ++ wake_up(&data->wait); ++ } ++ ++ spin_unlock_irqrestore(&data->lock, irq_flags); ++ return status ? IRQ_HANDLED : IRQ_NONE; ++} ++ ++/* file operations for /dev/eac */ ++static struct file_operations goldfish_audio_fops = { ++ .owner = THIS_MODULE, ++ .read = goldfish_audio_read, ++ .write = goldfish_audio_write, ++ .open = goldfish_audio_open, ++ .release = goldfish_audio_release, ++ .ioctl = goldfish_audio_ioctl, ++ ++}; ++ ++static struct miscdevice goldfish_audio_device = { ++ .minor = MISC_DYNAMIC_MINOR, ++ .name = "eac", ++ .fops = &goldfish_audio_fops, ++}; ++ ++static int goldfish_audio_probe(struct platform_device *pdev) ++{ ++ int ret; ++ struct resource *r; ++ struct goldfish_audio *data; ++ dma_addr_t buf_addr; ++ ++printk("goldfish_audio_probe\n"); ++ data = kzalloc(sizeof(*data), GFP_KERNEL); ++ if(data == NULL) { ++ ret = -ENOMEM; ++ goto err_data_alloc_failed; ++ } ++ spin_lock_init(&data->lock); ++ init_waitqueue_head(&data->wait); ++ platform_set_drvdata(pdev, data); ++ ++ r = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ if(r == NULL) { ++ printk("platform_get_resource failed\n"); ++ ret = -ENODEV; ++ goto err_no_io_base; ++ } ++ data->reg_base = IO_ADDRESS(r->start - IO_START); ++ ++ data->irq = platform_get_irq(pdev, 0); ++ if(data->irq < 0) { ++ printk("platform_get_irq failed\n"); ++ ret = -ENODEV; ++ goto err_no_irq; ++ } ++ ++ data->buffer_virt = dma_alloc_writecombine(&pdev->dev, COMBINED_BUFFER_SIZE, ++ &buf_addr, GFP_KERNEL); ++ if(data->buffer_virt == 0) { ++ ret = -ENOMEM; ++ goto err_alloc_write_buffer_failed; ++ } ++ data->buffer_phys = buf_addr; ++ data->write_buffer1 = data->buffer_virt; ++ data->write_buffer2 = data->buffer_virt + WRITE_BUFFER_SIZE; ++ data->read_buffer = data->buffer_virt + 2 * WRITE_BUFFER_SIZE; ++ ++ ret = request_irq(data->irq, goldfish_audio_interrupt, IRQF_SHARED, pdev->name, data); ++ if(ret) ++ goto err_request_irq_failed; ++ ++ if((ret = misc_register(&goldfish_audio_device))) ++ { ++ printk("misc_register returned %d in goldfish_audio_init\n", ret); ++ goto err_misc_register_failed; ++ } ++ ++ ++ GOLDFISH_AUDIO_WRITE(data, AUDIO_SET_WRITE_BUFFER_1, buf_addr); ++ GOLDFISH_AUDIO_WRITE(data, AUDIO_SET_WRITE_BUFFER_2, buf_addr + WRITE_BUFFER_SIZE); ++ ++ data->read_supported = GOLDFISH_AUDIO_READ(data, AUDIO_READ_SUPPORTED); ++ if (data->read_supported) ++ GOLDFISH_AUDIO_WRITE(data, AUDIO_SET_READ_BUFFER, buf_addr + 2 * WRITE_BUFFER_SIZE); ++ ++ audio_data = data; ++ return 0; ++ ++err_misc_register_failed: ++err_request_irq_failed: ++ dma_free_writecombine(&pdev->dev, COMBINED_BUFFER_SIZE, data->buffer_virt, data->buffer_phys); ++err_alloc_write_buffer_failed: ++err_no_irq: ++err_no_io_base: ++ kfree(data); ++err_data_alloc_failed: ++ return ret; ++} ++ ++static int goldfish_audio_remove(struct platform_device *pdev) ++{ ++ struct goldfish_audio *data = platform_get_drvdata(pdev); ++ ++ misc_deregister(&goldfish_audio_device); ++ free_irq(data->irq, data); ++ dma_free_writecombine(&pdev->dev, COMBINED_BUFFER_SIZE, data->buffer_virt, data->buffer_phys); ++ kfree(data); ++ audio_data = NULL; ++ return 0; ++} ++ ++static struct platform_driver goldfish_audio_driver = { ++ .probe = goldfish_audio_probe, ++ .remove = goldfish_audio_remove, ++ .driver = { ++ .name = "goldfish_audio" ++ } ++}; ++ ++static int __init goldfish_audio_init(void) ++{ ++ int ret; ++ ++ ret = platform_driver_register(&goldfish_audio_driver); ++ if (ret < 0) ++ { ++ printk("platform_driver_register returned %d\n", ret); ++ return ret; ++ } ++ ++ return ret; ++} ++ ++static void __exit goldfish_audio_exit(void) ++{ ++ platform_driver_unregister(&goldfish_audio_driver); ++} ++ ++module_init(goldfish_audio_init); ++module_exit(goldfish_audio_exit); |