blob: fdb58eb83d4e15daede90704337ab2da5c2297d6 [file] [log] [blame]
Andriy Skulysh7e27b9b2006-09-27 13:48:32 +09001/*
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 */
Linus Torvalds1da177e2005-04-16 15:20:36 -070012#include <linux/module.h>
13#include <linux/init.h>
14#include <linux/sched.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070015#include <linux/linkage.h>
16#include <linux/slab.h>
17#include <linux/fs.h>
Arnd Bergmannd2099742010-07-12 19:53:18 +020018#include <linux/smp_lock.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070019#include <linux/sound.h>
Arnd Bergmann90dc7632010-07-11 12:16:36 +020020#include <linux/smp_lock.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070021#include <linux/soundcard.h>
Andriy Skulysh4bcac202006-09-27 13:07:38 +090022#include <linux/interrupt.h>
Magnus Damm639138a2009-05-15 12:07:17 +090023#include <linux/hrtimer.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070024#include <asm/io.h>
25#include <asm/uaccess.h>
26#include <asm/irq.h>
27#include <asm/delay.h>
Andriy Skulysh7e27b9b2006-09-27 13:48:32 +090028#include <asm/clock.h>
Magnus Damm639138a2009-05-15 12:07:17 +090029#include <cpu/dac.h>
Andriy Skulysh4bcac202006-09-27 13:07:38 +090030#include <asm/machvec.h>
Paul Mundt7639a452008-10-20 13:02:48 +090031#include <mach/hp6xx.h>
Andriy Skulysh7e27b9b2006-09-27 13:48:32 +090032#include <asm/hd64461.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070033
34#define MODNAME "sh_dac_audio"
35
Linus Torvalds1da177e2005-04-16 15:20:36 -070036#define BUFFER_SIZE 48000
37
38static int rate;
39static int empty;
40static char *data_buffer, *buffer_begin, *buffer_end;
41static int in_use, device_major;
Magnus Damm639138a2009-05-15 12:07:17 +090042static struct hrtimer hrtimer;
43static ktime_t wakeups_per_second;
Linus Torvalds1da177e2005-04-16 15:20:36 -070044
45static void dac_audio_start_timer(void)
46{
Magnus Damm639138a2009-05-15 12:07:17 +090047 hrtimer_start(&hrtimer, wakeups_per_second, HRTIMER_MODE_REL);
Linus Torvalds1da177e2005-04-16 15:20:36 -070048}
49
50static void dac_audio_stop_timer(void)
51{
Magnus Damm639138a2009-05-15 12:07:17 +090052 hrtimer_cancel(&hrtimer);
Linus Torvalds1da177e2005-04-16 15:20:36 -070053}
54
55static void dac_audio_reset(void)
56{
57 dac_audio_stop_timer();
58 buffer_begin = buffer_end = data_buffer;
59 empty = 1;
60}
61
62static void dac_audio_sync(void)
63{
64 while (!empty)
65 schedule();
66}
67
68static void dac_audio_start(void)
69{
Andriy Skulysh4bcac202006-09-27 13:07:38 +090070 if (mach_is_hp6xx()) {
Magnus Damm639138a2009-05-15 12:07:17 +090071 u16 v = __raw_readw(HD64461_GPADR);
Andriy Skulysh4bcac202006-09-27 13:07:38 +090072 v &= ~HD64461_GPADR_SPEAKER;
Magnus Damm639138a2009-05-15 12:07:17 +090073 __raw_writew(v, HD64461_GPADR);
Andriy Skulysh4bcac202006-09-27 13:07:38 +090074 }
75
Linus Torvalds1da177e2005-04-16 15:20:36 -070076 sh_dac_enable(CONFIG_SOUND_SH_DAC_AUDIO_CHANNEL);
Linus Torvalds1da177e2005-04-16 15:20:36 -070077}
78static void dac_audio_stop(void)
79{
Linus Torvalds1da177e2005-04-16 15:20:36 -070080 dac_audio_stop_timer();
Andriy Skulysh4bcac202006-09-27 13:07:38 +090081
82 if (mach_is_hp6xx()) {
Magnus Damm639138a2009-05-15 12:07:17 +090083 u16 v = __raw_readw(HD64461_GPADR);
Andriy Skulysh4bcac202006-09-27 13:07:38 +090084 v |= HD64461_GPADR_SPEAKER;
Magnus Damm639138a2009-05-15 12:07:17 +090085 __raw_writew(v, HD64461_GPADR);
Andriy Skulysh4bcac202006-09-27 13:07:38 +090086 }
87
Magnus Damm639138a2009-05-15 12:07:17 +090088 sh_dac_output(0, CONFIG_SOUND_SH_DAC_AUDIO_CHANNEL);
Linus Torvalds1da177e2005-04-16 15:20:36 -070089 sh_dac_disable(CONFIG_SOUND_SH_DAC_AUDIO_CHANNEL);
90}
91
92static void dac_audio_set_rate(void)
93{
Magnus Damm639138a2009-05-15 12:07:17 +090094 wakeups_per_second = ktime_set(0, 1000000000 / rate);
Linus Torvalds1da177e2005-04-16 15:20:36 -070095}
96
Arnd Bergmannd2099742010-07-12 19:53:18 +020097static int dac_audio_ioctl(struct file *file,
Linus Torvalds1da177e2005-04-16 15:20:36 -070098 unsigned int cmd, unsigned long arg)
99{
100 int val;
101
102 switch (cmd) {
103 case OSS_GETVERSION:
104 return put_user(SOUND_VERSION, (int *)arg);
105
106 case SNDCTL_DSP_SYNC:
107 dac_audio_sync();
108 return 0;
109
110 case SNDCTL_DSP_RESET:
111 dac_audio_reset();
112 return 0;
113
114 case SNDCTL_DSP_GETFMTS:
115 return put_user(AFMT_U8, (int *)arg);
116
117 case SNDCTL_DSP_SETFMT:
118 return put_user(AFMT_U8, (int *)arg);
119
120 case SNDCTL_DSP_NONBLOCK:
Jonathan Corbetdb1dd4d2009-02-06 15:25:24 -0700121 spin_lock(&file->f_lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700122 file->f_flags |= O_NONBLOCK;
Jonathan Corbetdb1dd4d2009-02-06 15:25:24 -0700123 spin_unlock(&file->f_lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700124 return 0;
125
126 case SNDCTL_DSP_GETCAPS:
127 return 0;
128
129 case SOUND_PCM_WRITE_RATE:
130 val = *(int *)arg;
131 if (val > 0) {
132 rate = val;
133 dac_audio_set_rate();
134 }
135 return put_user(rate, (int *)arg);
136
137 case SNDCTL_DSP_STEREO:
138 return put_user(0, (int *)arg);
139
140 case SOUND_PCM_WRITE_CHANNELS:
141 return put_user(1, (int *)arg);
142
143 case SNDCTL_DSP_SETDUPLEX:
144 return -EINVAL;
145
146 case SNDCTL_DSP_PROFILE:
147 return -EINVAL;
148
149 case SNDCTL_DSP_GETBLKSIZE:
150 return put_user(BUFFER_SIZE, (int *)arg);
151
152 case SNDCTL_DSP_SETFRAGMENT:
153 return 0;
154
155 default:
156 printk(KERN_ERR "sh_dac_audio: unimplemented ioctl=0x%x\n",
157 cmd);
158 return -EINVAL;
159 }
160 return -EINVAL;
161}
162
Arnd Bergmannd2099742010-07-12 19:53:18 +0200163static long dac_audio_unlocked_ioctl(struct file *file, u_int cmd, u_long arg)
164{
165 int ret;
166
167 lock_kernel();
168 ret = dac_audio_ioctl(file, cmd, arg);
169 unlock_kernel();
170
171 return ret;
172}
173
Linus Torvalds1da177e2005-04-16 15:20:36 -0700174static ssize_t dac_audio_write(struct file *file, const char *buf, size_t count,
175 loff_t * ppos)
176{
177 int free;
178 int nbytes;
179
Linus Torvalds1da177e2005-04-16 15:20:36 -0700180 if (!count) {
181 dac_audio_sync();
182 return 0;
183 }
184
185 free = buffer_begin - buffer_end;
186
187 if (free < 0)
188 free += BUFFER_SIZE;
189 if ((free == 0) && (empty))
190 free = BUFFER_SIZE;
191 if (count > free)
192 count = free;
193 if (buffer_begin > buffer_end) {
194 if (copy_from_user((void *)buffer_end, buf, count))
195 return -EFAULT;
196
197 buffer_end += count;
198 } else {
199 nbytes = data_buffer + BUFFER_SIZE - buffer_end;
200 if (nbytes > count) {
201 if (copy_from_user((void *)buffer_end, buf, count))
202 return -EFAULT;
203 buffer_end += count;
204 } else {
205 if (copy_from_user((void *)buffer_end, buf, nbytes))
206 return -EFAULT;
207 if (copy_from_user
208 ((void *)data_buffer, buf + nbytes, count - nbytes))
209 return -EFAULT;
210 buffer_end = data_buffer + count - nbytes;
211 }
212 }
213
214 if (empty) {
215 empty = 0;
216 dac_audio_start_timer();
217 }
218
219 return count;
220}
221
222static ssize_t dac_audio_read(struct file *file, char *buf, size_t count,
223 loff_t * ppos)
224{
225 return -EINVAL;
226}
227
228static int dac_audio_open(struct inode *inode, struct file *file)
229{
230 if (file->f_mode & FMODE_READ)
231 return -ENODEV;
Arnd Bergmann90dc7632010-07-11 12:16:36 +0200232
233 lock_kernel();
234 if (in_use) {
235 unlock_kernel();
Linus Torvalds1da177e2005-04-16 15:20:36 -0700236 return -EBUSY;
Arnd Bergmann90dc7632010-07-11 12:16:36 +0200237 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700238
239 in_use = 1;
240
241 dac_audio_start();
Arnd Bergmann90dc7632010-07-11 12:16:36 +0200242 unlock_kernel();
Linus Torvalds1da177e2005-04-16 15:20:36 -0700243 return 0;
244}
245
246static int dac_audio_release(struct inode *inode, struct file *file)
247{
248 dac_audio_sync();
249 dac_audio_stop();
250 in_use = 0;
251
252 return 0;
253}
254
Arjan van de Ven9c2e08c2007-02-12 00:55:37 -0800255const struct file_operations dac_audio_fops = {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700256 .read = dac_audio_read,
Arnd Bergmannd2099742010-07-12 19:53:18 +0200257 .write = dac_audio_write,
258 .unlocked_ioctl = dac_audio_unlocked_ioctl,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700259 .open = dac_audio_open,
260 .release = dac_audio_release,
261};
262
Magnus Damm639138a2009-05-15 12:07:17 +0900263static enum hrtimer_restart sh_dac_audio_timer(struct hrtimer *handle)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700264{
Linus Torvalds1da177e2005-04-16 15:20:36 -0700265 if (!empty) {
266 sh_dac_output(*buffer_begin, CONFIG_SOUND_SH_DAC_AUDIO_CHANNEL);
267 buffer_begin++;
268
269 if (buffer_begin == data_buffer + BUFFER_SIZE)
270 buffer_begin = data_buffer;
Magnus Damm639138a2009-05-15 12:07:17 +0900271 if (buffer_begin == buffer_end)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700272 empty = 1;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700273 }
Magnus Damm639138a2009-05-15 12:07:17 +0900274
275 if (!empty)
276 hrtimer_start(&hrtimer, wakeups_per_second, HRTIMER_MODE_REL);
277
278 return HRTIMER_NORESTART;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700279}
280
281static int __init dac_audio_init(void)
282{
Linus Torvalds1da177e2005-04-16 15:20:36 -0700283 if ((device_major = register_sound_dsp(&dac_audio_fops, -1)) < 0) {
284 printk(KERN_ERR "Cannot register dsp device");
285 return device_major;
286 }
287
288 in_use = 0;
289
Jesper Juhl4fa95ef2006-03-28 01:56:54 -0800290 data_buffer = kmalloc(BUFFER_SIZE, GFP_KERNEL);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700291 if (data_buffer == NULL)
292 return -ENOMEM;
293
294 dac_audio_reset();
295 rate = 8000;
296 dac_audio_set_rate();
297
Magnus Damm639138a2009-05-15 12:07:17 +0900298 /* Today: High Resolution Timer driven DAC playback.
299 * The timer callback gets called once per sample. Ouch.
300 *
301 * Future: A much better approach would be to use the
302 * SH7720 CMT+DMAC+DAC hardware combination like this:
303 * - Program sample rate using CMT0 or CMT1
304 * - Program DMAC to use CMT for timing and output to DAC
305 * - Play sound using DMAC, let CPU sleep.
306 * - While at it, rewrite this driver to use ALSA.
307 */
308
309 hrtimer_init(&hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
310 hrtimer.function = sh_dac_audio_timer;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700311
312 return 0;
313}
314
315static void __exit dac_audio_exit(void)
316{
Linus Torvalds1da177e2005-04-16 15:20:36 -0700317 unregister_sound_dsp(device_major);
318 kfree((void *)data_buffer);
319}
320
321module_init(dac_audio_init);
322module_exit(dac_audio_exit);
323
324MODULE_AUTHOR("Andriy Skulysh, askulysh@image.kiev.ua");
325MODULE_DESCRIPTION("SH DAC sound driver");
326MODULE_LICENSE("GPL");