linux/sound/oss/sh_dac_audio.c
<<
>>
Prefs
   1/*
   2 * sound/oss/sh_dac_audio.c
   3 *
   4 * SH DAC based sound :(
   5 *
   6 *  Copyright (C) 2004,2005  Andriy Skulysh
   7 *
   8 * This file is subject to the terms and conditions of the GNU General Public
   9 * License.  See the file "COPYING" in the main directory of this archive
  10 * for more details.
  11 */
  12#include <linux/module.h>
  13#include <linux/init.h>
  14#include <linux/sched.h>
  15#include <linux/linkage.h>
  16#include <linux/slab.h>
  17#include <linux/fs.h>
  18#include <linux/sound.h>
  19#include <linux/soundcard.h>
  20#include <linux/interrupt.h>
  21#include <linux/hrtimer.h>
  22#include <asm/io.h>
  23#include <asm/uaccess.h>
  24#include <asm/irq.h>
  25#include <asm/delay.h>
  26#include <asm/clock.h>
  27#include <cpu/dac.h>
  28#include <asm/machvec.h>
  29#include <mach/hp6xx.h>
  30#include <asm/hd64461.h>
  31
  32#define MODNAME "sh_dac_audio"
  33
  34#define BUFFER_SIZE 48000
  35
  36static int rate;
  37static int empty;
  38static char *data_buffer, *buffer_begin, *buffer_end;
  39static int in_use, device_major;
  40static struct hrtimer hrtimer;
  41static ktime_t wakeups_per_second;
  42
  43static void dac_audio_start_timer(void)
  44{
  45        hrtimer_start(&hrtimer, wakeups_per_second, HRTIMER_MODE_REL);
  46}
  47
  48static void dac_audio_stop_timer(void)
  49{
  50        hrtimer_cancel(&hrtimer);
  51}
  52
  53static void dac_audio_reset(void)
  54{
  55        dac_audio_stop_timer();
  56        buffer_begin = buffer_end = data_buffer;
  57        empty = 1;
  58}
  59
  60static void dac_audio_sync(void)
  61{
  62        while (!empty)
  63                schedule();
  64}
  65
  66static void dac_audio_start(void)
  67{
  68        if (mach_is_hp6xx()) {
  69                u16 v = __raw_readw(HD64461_GPADR);
  70                v &= ~HD64461_GPADR_SPEAKER;
  71                __raw_writew(v, HD64461_GPADR);
  72        }
  73
  74        sh_dac_enable(CONFIG_SOUND_SH_DAC_AUDIO_CHANNEL);
  75}
  76static void dac_audio_stop(void)
  77{
  78        dac_audio_stop_timer();
  79
  80        if (mach_is_hp6xx()) {
  81                u16 v = __raw_readw(HD64461_GPADR);
  82                v |= HD64461_GPADR_SPEAKER;
  83                __raw_writew(v, HD64461_GPADR);
  84        }
  85
  86        sh_dac_output(0, CONFIG_SOUND_SH_DAC_AUDIO_CHANNEL);
  87        sh_dac_disable(CONFIG_SOUND_SH_DAC_AUDIO_CHANNEL);
  88}
  89
  90static void dac_audio_set_rate(void)
  91{
  92        wakeups_per_second = ktime_set(0, 1000000000 / rate);
  93}
  94
  95static int dac_audio_ioctl(struct inode *inode, struct file *file,
  96                           unsigned int cmd, unsigned long arg)
  97{
  98        int val;
  99
 100        switch (cmd) {
 101        case OSS_GETVERSION:
 102                return put_user(SOUND_VERSION, (int *)arg);
 103
 104        case SNDCTL_DSP_SYNC:
 105                dac_audio_sync();
 106                return 0;
 107
 108        case SNDCTL_DSP_RESET:
 109                dac_audio_reset();
 110                return 0;
 111
 112        case SNDCTL_DSP_GETFMTS:
 113                return put_user(AFMT_U8, (int *)arg);
 114
 115        case SNDCTL_DSP_SETFMT:
 116                return put_user(AFMT_U8, (int *)arg);
 117
 118        case SNDCTL_DSP_NONBLOCK:
 119                spin_lock(&file->f_lock);
 120                file->f_flags |= O_NONBLOCK;
 121                spin_unlock(&file->f_lock);
 122                return 0;
 123
 124        case SNDCTL_DSP_GETCAPS:
 125                return 0;
 126
 127        case SOUND_PCM_WRITE_RATE:
 128                val = *(int *)arg;
 129                if (val > 0) {
 130                        rate = val;
 131                        dac_audio_set_rate();
 132                }
 133                return put_user(rate, (int *)arg);
 134
 135        case SNDCTL_DSP_STEREO:
 136                return put_user(0, (int *)arg);
 137
 138        case SOUND_PCM_WRITE_CHANNELS:
 139                return put_user(1, (int *)arg);
 140
 141        case SNDCTL_DSP_SETDUPLEX:
 142                return -EINVAL;
 143
 144        case SNDCTL_DSP_PROFILE:
 145                return -EINVAL;
 146
 147        case SNDCTL_DSP_GETBLKSIZE:
 148                return put_user(BUFFER_SIZE, (int *)arg);
 149
 150        case SNDCTL_DSP_SETFRAGMENT:
 151                return 0;
 152
 153        default:
 154                printk(KERN_ERR "sh_dac_audio: unimplemented ioctl=0x%x\n",
 155                       cmd);
 156                return -EINVAL;
 157        }
 158        return -EINVAL;
 159}
 160
 161static ssize_t dac_audio_write(struct file *file, const char *buf, size_t count,
 162                               loff_t * ppos)
 163{
 164        int free;
 165        int nbytes;
 166
 167        if (count < 0)
 168                return -EINVAL;
 169
 170        if (!count) {
 171                dac_audio_sync();
 172                return 0;
 173        }
 174
 175        free = buffer_begin - buffer_end;
 176
 177        if (free < 0)
 178                free += BUFFER_SIZE;
 179        if ((free == 0) && (empty))
 180                free = BUFFER_SIZE;
 181        if (count > free)
 182                count = free;
 183        if (buffer_begin > buffer_end) {
 184                if (copy_from_user((void *)buffer_end, buf, count))
 185                        return -EFAULT;
 186
 187                buffer_end += count;
 188        } else {
 189                nbytes = data_buffer + BUFFER_SIZE - buffer_end;
 190                if (nbytes > count) {
 191                        if (copy_from_user((void *)buffer_end, buf, count))
 192                                return -EFAULT;
 193                        buffer_end += count;
 194                } else {
 195                        if (copy_from_user((void *)buffer_end, buf, nbytes))
 196                                return -EFAULT;
 197                        if (copy_from_user
 198                            ((void *)data_buffer, buf + nbytes, count - nbytes))
 199                                return -EFAULT;
 200                        buffer_end = data_buffer + count - nbytes;
 201                }
 202        }
 203
 204        if (empty) {
 205                empty = 0;
 206                dac_audio_start_timer();
 207        }
 208
 209        return count;
 210}
 211
 212static ssize_t dac_audio_read(struct file *file, char *buf, size_t count,
 213                              loff_t * ppos)
 214{
 215        return -EINVAL;
 216}
 217
 218static int dac_audio_open(struct inode *inode, struct file *file)
 219{
 220        if (file->f_mode & FMODE_READ)
 221                return -ENODEV;
 222        if (in_use)
 223                return -EBUSY;
 224
 225        in_use = 1;
 226
 227        dac_audio_start();
 228
 229        return 0;
 230}
 231
 232static int dac_audio_release(struct inode *inode, struct file *file)
 233{
 234        dac_audio_sync();
 235        dac_audio_stop();
 236        in_use = 0;
 237
 238        return 0;
 239}
 240
 241const struct file_operations dac_audio_fops = {
 242      .read =           dac_audio_read,
 243      .write =  dac_audio_write,
 244      .ioctl =  dac_audio_ioctl,
 245      .open =           dac_audio_open,
 246      .release =        dac_audio_release,
 247};
 248
 249static enum hrtimer_restart sh_dac_audio_timer(struct hrtimer *handle)
 250{
 251        if (!empty) {
 252                sh_dac_output(*buffer_begin, CONFIG_SOUND_SH_DAC_AUDIO_CHANNEL);
 253                buffer_begin++;
 254
 255                if (buffer_begin == data_buffer + BUFFER_SIZE)
 256                        buffer_begin = data_buffer;
 257                if (buffer_begin == buffer_end)
 258                        empty = 1;
 259        }
 260
 261        if (!empty)
 262                hrtimer_start(&hrtimer, wakeups_per_second, HRTIMER_MODE_REL);
 263
 264        return HRTIMER_NORESTART;
 265}
 266
 267static int __init dac_audio_init(void)
 268{
 269        if ((device_major = register_sound_dsp(&dac_audio_fops, -1)) < 0) {
 270                printk(KERN_ERR "Cannot register dsp device");
 271                return device_major;
 272        }
 273
 274        in_use = 0;
 275
 276        data_buffer = kmalloc(BUFFER_SIZE, GFP_KERNEL);
 277        if (data_buffer == NULL)
 278                return -ENOMEM;
 279
 280        dac_audio_reset();
 281        rate = 8000;
 282        dac_audio_set_rate();
 283
 284        /* Today: High Resolution Timer driven DAC playback.
 285         * The timer callback gets called once per sample. Ouch.
 286         *
 287         * Future: A much better approach would be to use the
 288         * SH7720 CMT+DMAC+DAC hardware combination like this:
 289         * - Program sample rate using CMT0 or CMT1
 290         * - Program DMAC to use CMT for timing and output to DAC
 291         * - Play sound using DMAC, let CPU sleep.
 292         * - While at it, rewrite this driver to use ALSA.
 293         */
 294
 295        hrtimer_init(&hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
 296        hrtimer.function = sh_dac_audio_timer;
 297
 298        return 0;
 299}
 300
 301static void __exit dac_audio_exit(void)
 302{
 303        unregister_sound_dsp(device_major);
 304        kfree((void *)data_buffer);
 305}
 306
 307module_init(dac_audio_init);
 308module_exit(dac_audio_exit);
 309
 310MODULE_AUTHOR("Andriy Skulysh, askulysh@image.kiev.ua");
 311MODULE_DESCRIPTION("SH DAC sound driver");
 312MODULE_LICENSE("GPL");
 313