2006-09-27 13:48:32 +09:00
/*
* sound / oss / sh_dac_audio . c
*
* SH DAC based sound : (
*
* Copyright ( C ) 2004 , 2005 Andriy Skulysh
*
* This file is subject to the terms and conditions of the GNU General Public
* License . See the file " COPYING " in the main directory of this archive
* for more details .
*/
2005-04-16 15:20:36 -07:00
# include <linux/module.h>
# include <linux/init.h>
# include <linux/sched.h>
# include <linux/linkage.h>
# include <linux/slab.h>
# include <linux/fs.h>
# include <linux/sound.h>
2010-07-11 12:16:36 +02:00
# include <linux/smp_lock.h>
2005-04-16 15:20:36 -07:00
# include <linux/soundcard.h>
2006-09-27 13:07:38 +09:00
# include <linux/interrupt.h>
2009-05-15 12:07:17 +09:00
# include <linux/hrtimer.h>
2005-04-16 15:20:36 -07:00
# include <asm/io.h>
# include <asm/uaccess.h>
# include <asm/irq.h>
# include <asm/delay.h>
2006-09-27 13:48:32 +09:00
# include <asm/clock.h>
2009-05-15 12:07:17 +09:00
# include <cpu/dac.h>
2006-09-27 13:07:38 +09:00
# include <asm/machvec.h>
2008-10-20 13:02:48 +09:00
# include <mach/hp6xx.h>
2006-09-27 13:48:32 +09:00
# include <asm/hd64461.h>
2005-04-16 15:20:36 -07:00
# define MODNAME "sh_dac_audio"
# define BUFFER_SIZE 48000
static int rate ;
static int empty ;
static char * data_buffer , * buffer_begin , * buffer_end ;
static int in_use , device_major ;
2009-05-15 12:07:17 +09:00
static struct hrtimer hrtimer ;
static ktime_t wakeups_per_second ;
2005-04-16 15:20:36 -07:00
static void dac_audio_start_timer ( void )
{
2009-05-15 12:07:17 +09:00
hrtimer_start ( & hrtimer , wakeups_per_second , HRTIMER_MODE_REL ) ;
2005-04-16 15:20:36 -07:00
}
static void dac_audio_stop_timer ( void )
{
2009-05-15 12:07:17 +09:00
hrtimer_cancel ( & hrtimer ) ;
2005-04-16 15:20:36 -07:00
}
static void dac_audio_reset ( void )
{
dac_audio_stop_timer ( ) ;
buffer_begin = buffer_end = data_buffer ;
empty = 1 ;
}
static void dac_audio_sync ( void )
{
while ( ! empty )
schedule ( ) ;
}
static void dac_audio_start ( void )
{
2006-09-27 13:07:38 +09:00
if ( mach_is_hp6xx ( ) ) {
2009-05-15 12:07:17 +09:00
u16 v = __raw_readw ( HD64461_GPADR ) ;
2006-09-27 13:07:38 +09:00
v & = ~ HD64461_GPADR_SPEAKER ;
2009-05-15 12:07:17 +09:00
__raw_writew ( v , HD64461_GPADR ) ;
2006-09-27 13:07:38 +09:00
}
2005-04-16 15:20:36 -07:00
sh_dac_enable ( CONFIG_SOUND_SH_DAC_AUDIO_CHANNEL ) ;
}
static void dac_audio_stop ( void )
{
dac_audio_stop_timer ( ) ;
2006-09-27 13:07:38 +09:00
if ( mach_is_hp6xx ( ) ) {
2009-05-15 12:07:17 +09:00
u16 v = __raw_readw ( HD64461_GPADR ) ;
2006-09-27 13:07:38 +09:00
v | = HD64461_GPADR_SPEAKER ;
2009-05-15 12:07:17 +09:00
__raw_writew ( v , HD64461_GPADR ) ;
2006-09-27 13:07:38 +09:00
}
2009-05-15 12:07:17 +09:00
sh_dac_output ( 0 , CONFIG_SOUND_SH_DAC_AUDIO_CHANNEL ) ;
2005-04-16 15:20:36 -07:00
sh_dac_disable ( CONFIG_SOUND_SH_DAC_AUDIO_CHANNEL ) ;
}
static void dac_audio_set_rate ( void )
{
2009-05-15 12:07:17 +09:00
wakeups_per_second = ktime_set ( 0 , 1000000000 / rate ) ;
2005-04-16 15:20:36 -07:00
}
2010-07-12 19:53:18 +02:00
static int dac_audio_ioctl ( struct file * file ,
2005-04-16 15:20:36 -07:00
unsigned int cmd , unsigned long arg )
{
int val ;
switch ( cmd ) {
case OSS_GETVERSION :
return put_user ( SOUND_VERSION , ( int * ) arg ) ;
case SNDCTL_DSP_SYNC :
dac_audio_sync ( ) ;
return 0 ;
case SNDCTL_DSP_RESET :
dac_audio_reset ( ) ;
return 0 ;
case SNDCTL_DSP_GETFMTS :
return put_user ( AFMT_U8 , ( int * ) arg ) ;
case SNDCTL_DSP_SETFMT :
return put_user ( AFMT_U8 , ( int * ) arg ) ;
case SNDCTL_DSP_NONBLOCK :
2009-02-06 15:25:24 -07:00
spin_lock ( & file - > f_lock ) ;
2005-04-16 15:20:36 -07:00
file - > f_flags | = O_NONBLOCK ;
2009-02-06 15:25:24 -07:00
spin_unlock ( & file - > f_lock ) ;
2005-04-16 15:20:36 -07:00
return 0 ;
case SNDCTL_DSP_GETCAPS :
return 0 ;
case SOUND_PCM_WRITE_RATE :
val = * ( int * ) arg ;
if ( val > 0 ) {
rate = val ;
dac_audio_set_rate ( ) ;
}
return put_user ( rate , ( int * ) arg ) ;
case SNDCTL_DSP_STEREO :
return put_user ( 0 , ( int * ) arg ) ;
case SOUND_PCM_WRITE_CHANNELS :
return put_user ( 1 , ( int * ) arg ) ;
case SNDCTL_DSP_SETDUPLEX :
return - EINVAL ;
case SNDCTL_DSP_PROFILE :
return - EINVAL ;
case SNDCTL_DSP_GETBLKSIZE :
return put_user ( BUFFER_SIZE , ( int * ) arg ) ;
case SNDCTL_DSP_SETFRAGMENT :
return 0 ;
default :
printk ( KERN_ERR " sh_dac_audio: unimplemented ioctl=0x%x \n " ,
cmd ) ;
return - EINVAL ;
}
return - EINVAL ;
}
2010-07-12 19:53:18 +02:00
static long dac_audio_unlocked_ioctl ( struct file * file , u_int cmd , u_long arg )
{
int ret ;
lock_kernel ( ) ;
ret = dac_audio_ioctl ( file , cmd , arg ) ;
unlock_kernel ( ) ;
return ret ;
}
2005-04-16 15:20:36 -07:00
static ssize_t dac_audio_write ( struct file * file , const char * buf , size_t count ,
loff_t * ppos )
{
int free ;
int nbytes ;
if ( ! count ) {
dac_audio_sync ( ) ;
return 0 ;
}
free = buffer_begin - buffer_end ;
if ( free < 0 )
free + = BUFFER_SIZE ;
if ( ( free = = 0 ) & & ( empty ) )
free = BUFFER_SIZE ;
if ( count > free )
count = free ;
if ( buffer_begin > buffer_end ) {
if ( copy_from_user ( ( void * ) buffer_end , buf , count ) )
return - EFAULT ;
buffer_end + = count ;
} else {
nbytes = data_buffer + BUFFER_SIZE - buffer_end ;
if ( nbytes > count ) {
if ( copy_from_user ( ( void * ) buffer_end , buf , count ) )
return - EFAULT ;
buffer_end + = count ;
} else {
if ( copy_from_user ( ( void * ) buffer_end , buf , nbytes ) )
return - EFAULT ;
if ( copy_from_user
( ( void * ) data_buffer , buf + nbytes , count - nbytes ) )
return - EFAULT ;
buffer_end = data_buffer + count - nbytes ;
}
}
if ( empty ) {
empty = 0 ;
dac_audio_start_timer ( ) ;
}
return count ;
}
static ssize_t dac_audio_read ( struct file * file , char * buf , size_t count ,
loff_t * ppos )
{
return - EINVAL ;
}
static int dac_audio_open ( struct inode * inode , struct file * file )
{
if ( file - > f_mode & FMODE_READ )
return - ENODEV ;
2010-07-11 12:16:36 +02:00
lock_kernel ( ) ;
if ( in_use ) {
unlock_kernel ( ) ;
2005-04-16 15:20:36 -07:00
return - EBUSY ;
2010-07-11 12:16:36 +02:00
}
2005-04-16 15:20:36 -07:00
in_use = 1 ;
dac_audio_start ( ) ;
2010-07-11 12:16:36 +02:00
unlock_kernel ( ) ;
2005-04-16 15:20:36 -07:00
return 0 ;
}
static int dac_audio_release ( struct inode * inode , struct file * file )
{
dac_audio_sync ( ) ;
dac_audio_stop ( ) ;
in_use = 0 ;
return 0 ;
}
2007-02-12 00:55:37 -08:00
const struct file_operations dac_audio_fops = {
2005-04-16 15:20:36 -07:00
. read = dac_audio_read ,
2010-07-12 19:53:18 +02:00
. write = dac_audio_write ,
. unlocked_ioctl = dac_audio_unlocked_ioctl ,
2005-04-16 15:20:36 -07:00
. open = dac_audio_open ,
. release = dac_audio_release ,
} ;
2009-05-15 12:07:17 +09:00
static enum hrtimer_restart sh_dac_audio_timer ( struct hrtimer * handle )
2005-04-16 15:20:36 -07:00
{
if ( ! empty ) {
sh_dac_output ( * buffer_begin , CONFIG_SOUND_SH_DAC_AUDIO_CHANNEL ) ;
buffer_begin + + ;
if ( buffer_begin = = data_buffer + BUFFER_SIZE )
buffer_begin = data_buffer ;
2009-05-15 12:07:17 +09:00
if ( buffer_begin = = buffer_end )
2005-04-16 15:20:36 -07:00
empty = 1 ;
}
2009-05-15 12:07:17 +09:00
if ( ! empty )
hrtimer_start ( & hrtimer , wakeups_per_second , HRTIMER_MODE_REL ) ;
return HRTIMER_NORESTART ;
2005-04-16 15:20:36 -07:00
}
static int __init dac_audio_init ( void )
{
if ( ( device_major = register_sound_dsp ( & dac_audio_fops , - 1 ) ) < 0 ) {
printk ( KERN_ERR " Cannot register dsp device " ) ;
return device_major ;
}
in_use = 0 ;
2006-03-28 01:56:54 -08:00
data_buffer = kmalloc ( BUFFER_SIZE , GFP_KERNEL ) ;
2005-04-16 15:20:36 -07:00
if ( data_buffer = = NULL )
return - ENOMEM ;
dac_audio_reset ( ) ;
rate = 8000 ;
dac_audio_set_rate ( ) ;
2009-05-15 12:07:17 +09:00
/* Today: High Resolution Timer driven DAC playback.
* The timer callback gets called once per sample . Ouch .
*
* Future : A much better approach would be to use the
* SH7720 CMT + DMAC + DAC hardware combination like this :
* - Program sample rate using CMT0 or CMT1
* - Program DMAC to use CMT for timing and output to DAC
* - Play sound using DMAC , let CPU sleep .
* - While at it , rewrite this driver to use ALSA .
*/
hrtimer_init ( & hrtimer , CLOCK_MONOTONIC , HRTIMER_MODE_REL ) ;
hrtimer . function = sh_dac_audio_timer ;
2005-04-16 15:20:36 -07:00
return 0 ;
}
static void __exit dac_audio_exit ( void )
{
unregister_sound_dsp ( device_major ) ;
kfree ( ( void * ) data_buffer ) ;
}
module_init ( dac_audio_init ) ;
module_exit ( dac_audio_exit ) ;
MODULE_AUTHOR ( " Andriy Skulysh, askulysh@image.kiev.ua " ) ;
MODULE_DESCRIPTION ( " SH DAC sound driver " ) ;
MODULE_LICENSE ( " GPL " ) ;