/* * a.lp_mp3 - VS1011B driver for Fonera * Copyright (c) 2007 phrozen.org - John Crispin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * 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. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA02111-1307USA * * Feedback, Bugs... john@phrozen.org * */ #include #include #include #include #include #include #include #include #include #include #include // do we want debuging info ? #if 0 #define DBG(x) x #else #define DBG(x) #endif #define MP3_CHUNK_SIZE 4096 #define MP3_BUFFERING 0 #define MP3_PLAYING 1 #define MP3_BUFFER_FINISHED 2 #define MP3_PLAY_FINISHED 3 typedef struct _MP3_DATA{ unsigned char mp3[MP3_CHUNK_SIZE]; unsigned char state; } MP3_DATA; #define IOCTL_MP3_INIT 0x01 #define IOCTL_MP3_RESET 0x02 #define IOCTL_MP3_SETVOLUME 0x03 #define IOCTL_MP3_GETVOLUME 0x04 typedef struct _AUDIO_DATA{ unsigned int bitrate; unsigned int sample_rate; unsigned char is_stereo; }AUDIO_DATA; #define IOCTL_MP3_GETAUDIODATA 0x05 #define IOCTL_MP3_CLEARBUFFER 0x06 #define IOCTL_MP3_PLAY 0x07 typedef struct _MP3_BEEP{ unsigned char freq; unsigned int ms; } MP3_BEEP; #define IOCTL_MP3_BEEP 0x08 #define IOCTL_MP3_END_REACHED 0x09 #define IOCTL_MP3_BASS 0x10 #define CRYSTAL12288 0x9800 #define CRYSTAL24576 0x0 #define DEV_NAME "mp3" #define DEV_MAJOR 196 #define MAX_MP3_COUNT 1 typedef struct _mp3_inf{ unsigned char is_open; } mp3_inf; static mp3_inf mp3_info[MAX_MP3_COUNT]; #define MP3_BUFFER_SIZE (128 * 1024) unsigned char mp3_buffer[MP3_BUFFER_SIZE]; static unsigned long int mp3_buffer_offset_write = 0; static unsigned long int mp3_buffer_offset_read = 0; static unsigned char mp3_buffering_status = MP3_BUFFERING; static unsigned long int mp3_data_in_buffer = 0; static int mp3_thread = 0; unsigned int crystal_freq; #include "vs10xx.c" static wait_queue_head_t wq; static DECLARE_COMPLETION(mp3_exit); static int mp3_playback_thread(void *data){ int j; unsigned long timeout; unsigned char empty = 0; printk("started kthread\n"); daemonize("kmp3"); while(mp3_buffering_status != MP3_PLAY_FINISHED){ if((mp3_buffering_status == MP3_PLAYING) || (mp3_buffering_status == MP3_BUFFER_FINISHED)){ while((VS1011_NEEDS_DATA) && (!empty)){ if(mp3_buffer_offset_read == MP3_BUFFER_SIZE){ mp3_buffer_offset_read = 0; } if(mp3_data_in_buffer == 0){ if(mp3_buffering_status == MP3_BUFFER_FINISHED){ printk("mp3_drv.ko : finished playing\n"); mp3_buffering_status = MP3_PLAY_FINISHED; } else { empty = 1; printk("mp3_drv.ko : buffer empty ?\n"); if(mp3_buffering_status != MP3_PLAY_FINISHED){ } } } else { for(j = 0; j < 32; j++){ VS1011_send_SDI(mp3_buffer[mp3_buffer_offset_read + j]); } mp3_buffer_offset_read += 32; mp3_data_in_buffer -= 32; } } } empty = 0; timeout = 1; timeout = wait_event_interruptible_timeout(wq, (timeout==0), timeout); } complete_and_exit(&mp3_exit, 0); } static ssize_t module_write(struct file * file, const char * buffer, size_t count, loff_t *offset){ MP3_DATA mp3_data; copy_from_user((char*) &mp3_data, buffer, sizeof(MP3_DATA)); if(mp3_data.state == MP3_BUFFER_FINISHED){ mp3_buffering_status = MP3_BUFFER_FINISHED; DBG(printk("mp3_drv.ko : file end reached\n")); return 1; } if(mp3_data.state == MP3_PLAY_FINISHED){ mp3_buffering_status = MP3_PLAY_FINISHED; mp3_data_in_buffer = 0; DBG(printk("mp3_drv.ko : stop playing\n")); return 1; } if(mp3_data_in_buffer + MP3_CHUNK_SIZE >= MP3_BUFFER_SIZE){ DBG(printk("mp3_drv.ko : buffer is full? %ld\n", mp3_data_in_buffer);) return 0; } if(mp3_buffer_offset_write == MP3_BUFFER_SIZE){ mp3_buffer_offset_write = 0; } memcpy(&mp3_buffer[mp3_buffer_offset_write], mp3_data.mp3, MP3_CHUNK_SIZE); mp3_buffer_offset_write += MP3_CHUNK_SIZE; mp3_buffering_status = mp3_data.state; mp3_data_in_buffer += MP3_CHUNK_SIZE; return 1; } static int module_ioctl(struct inode * inode, struct file * file, unsigned int cmd, unsigned long arg){ unsigned int retval = 0; AUDIO_DATA audio_data; MP3_BEEP mp3_beep; DBG(printk("mp3_drv.ko : Ioctl Called (cmd=%d)\n", cmd );) switch (cmd) { case IOCTL_MP3_INIT: crystal_freq = arg; VS1011_init(crystal_freq, 1); VS1011_print_registers(); break; case IOCTL_MP3_RESET: DBG(printk("mp3_drv.ko : doing a sw reset\n");) VS1011_init(crystal_freq, 0); VS1011_print_registers(); VS1011_send_zeros(0x20); break; case IOCTL_MP3_SETVOLUME: DBG(printk("mp3_drv.ko : setting volume to : %lu\n", arg&0xffff);) VS1011_set_volume(arg); break; case IOCTL_MP3_GETVOLUME: retval = VS1011_get_volume(); DBG(printk("mp3_drv.ko : read volume : %d\n", retval);) break; case IOCTL_MP3_GETAUDIODATA: DBG(printk("mp3_drv.ko : read audio data\n");) VS1011_get_audio_data(&audio_data); copy_to_user((char*)arg, (char*)&audio_data, sizeof(AUDIO_DATA)); break; case IOCTL_MP3_CLEARBUFFER: DBG(printk("mp3_drv.ko : clearing buffer\n");) mp3_buffer_offset_read = 0; mp3_buffer_offset_write = 0; mp3_buffering_status = MP3_PLAY_FINISHED; mp3_data_in_buffer = 0; break; case IOCTL_MP3_PLAY: mp3_thread = kernel_thread(mp3_playback_thread, NULL, CLONE_KERNEL); break; case IOCTL_MP3_BEEP: copy_from_user((char*)&mp3_beep, (char*)arg, sizeof(MP3_BEEP)); VS1011_sine(1,mp3_beep.freq); msDelay(mp3_beep.ms); VS1011_sine(0,0); break; case IOCTL_MP3_END_REACHED: if(mp3_buffering_status == MP3_PLAY_FINISHED){ retval = 1; } break; case IOCTL_MP3_BASS: VS1011_set_bass(arg); break; default: printk("mp3_drv.ko : unknown ioctl\n"); break; } return retval; } static int module_open(struct inode *inode, struct file *file){ unsigned int dev_minor = MINOR(inode->i_rdev); if(dev_minor != 0){ printk("mp3_drv.ko : trying to access unknown minor device -> %d\n", dev_minor); return -ENODEV; } if(mp3_info[dev_minor].is_open) { printk("mp3_drv.ko : Device with minor ID %d already in use\n", dev_minor); return -EBUSY; } mp3_info[dev_minor].is_open = 1; mp3_buffering_status = MP3_PLAY_FINISHED; printk("mp3_drv.ko : Minor %d has been opened\n", dev_minor); return 0; } static int module_close(struct inode * inode, struct file * file){ unsigned int dev_minor = MINOR(inode->i_rdev); mp3_info[dev_minor].is_open = 0; printk("mp3_drv.ko : Minor %d has been closed\n", dev_minor); mp3_buffering_status = MP3_PLAY_FINISHED; return 0; } struct file_operations modulemp3_fops = { write: module_write, ioctl: module_ioctl, open: module_open, release: module_close }; static struct class *mp3_class; static int __init mod_init(void){ printk("mp3_drv.ko : VS1011b Driver\n"); printk("mp3_drv.ko : Made by John '2B|!2B' Crispin (john@phrozen.org)\n"); printk("mp3_drv.ko : Starting ...\n"); printk("disabling atheros reset button irq\n"); ar531x_disable_reset_button(); if(register_chrdev(DEV_MAJOR, DEV_NAME, &modulemp3_fops)) { printk( "mp3_drv.ko : Error whilst opening %s (%d)\n", DEV_NAME, DEV_MAJOR); return( -ENODEV ); } printk("mp3_drv.ko : using sysfs to create device nodes\n"); mp3_class = class_create(THIS_MODULE, DEV_NAME); class_device_create(mp3_class, NULL, MKDEV(DEV_MAJOR, 0), NULL, DEV_NAME); mp3_info[0].is_open = 0; printk("mp3_drv.ko : Device %s registered for major ID %d\n", DEV_NAME, DEV_MAJOR); crystal_freq = CRYSTAL12288; VS1011_init(crystal_freq, 1); VS1011_print_registers(); printk("end of init\n"); init_waitqueue_head(&wq); printk("wait queue started\n"); return 0; } static void __exit mod_exit(void){ printk( "mp3_drv.ko : Cleanup\n" ); unregister_chrdev(DEV_MAJOR, DEV_NAME); } module_init (mod_init); module_exit (mod_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("K. John '2B|!2B' Crispin"); MODULE_DESCRIPTION("vs1011 Driver for Fox Board");