diff options
Diffstat (limited to 'sound/oss/sh_dac_audio.c')
-rw-r--r-- | sound/oss/sh_dac_audio.c | 325 |
1 files changed, 325 insertions, 0 deletions
diff --git a/sound/oss/sh_dac_audio.c b/sound/oss/sh_dac_audio.c new file mode 100644 index 000000000000..c09cdeedc191 --- /dev/null +++ b/sound/oss/sh_dac_audio.c | |||
@@ -0,0 +1,325 @@ | |||
1 | #include <linux/config.h> | ||
2 | #include <linux/module.h> | ||
3 | #include <linux/init.h> | ||
4 | #include <linux/sched.h> | ||
5 | #include <linux/version.h> | ||
6 | #include <linux/linkage.h> | ||
7 | #include <linux/slab.h> | ||
8 | #include <linux/fs.h> | ||
9 | #include <linux/sound.h> | ||
10 | #include <linux/soundcard.h> | ||
11 | #include <asm/io.h> | ||
12 | #include <asm/uaccess.h> | ||
13 | #include <asm/irq.h> | ||
14 | #include <asm/delay.h> | ||
15 | #include <linux/interrupt.h> | ||
16 | |||
17 | #include <asm/cpu/dac.h> | ||
18 | |||
19 | #ifdef MACH_HP600 | ||
20 | #include <asm/hp6xx/hp6xx.h> | ||
21 | #include <asm/hd64461/hd64461.h> | ||
22 | #endif | ||
23 | |||
24 | #define MODNAME "sh_dac_audio" | ||
25 | |||
26 | #define TMU_TOCR_INIT 0x00 | ||
27 | |||
28 | #define TMU1_TCR_INIT 0x0020 /* Clock/4, rising edge; interrupt on */ | ||
29 | #define TMU1_TSTR_INIT 0x02 /* Bit to turn on TMU1 */ | ||
30 | |||
31 | #define TMU_TSTR 0xfffffe92 | ||
32 | #define TMU1_TCOR 0xfffffea0 | ||
33 | #define TMU1_TCNT 0xfffffea4 | ||
34 | #define TMU1_TCR 0xfffffea8 | ||
35 | |||
36 | #define BUFFER_SIZE 48000 | ||
37 | |||
38 | static int rate; | ||
39 | static int empty; | ||
40 | static char *data_buffer, *buffer_begin, *buffer_end; | ||
41 | static int in_use, device_major; | ||
42 | |||
43 | static void dac_audio_start_timer(void) | ||
44 | { | ||
45 | u8 tstr; | ||
46 | |||
47 | tstr = ctrl_inb(TMU_TSTR); | ||
48 | tstr |= TMU1_TSTR_INIT; | ||
49 | ctrl_outb(tstr, TMU_TSTR); | ||
50 | } | ||
51 | |||
52 | static void dac_audio_stop_timer(void) | ||
53 | { | ||
54 | u8 tstr; | ||
55 | |||
56 | tstr = ctrl_inb(TMU_TSTR); | ||
57 | tstr &= ~TMU1_TSTR_INIT; | ||
58 | ctrl_outb(tstr, TMU_TSTR); | ||
59 | } | ||
60 | |||
61 | static void dac_audio_reset(void) | ||
62 | { | ||
63 | dac_audio_stop_timer(); | ||
64 | buffer_begin = buffer_end = data_buffer; | ||
65 | empty = 1; | ||
66 | } | ||
67 | |||
68 | static void dac_audio_sync(void) | ||
69 | { | ||
70 | while (!empty) | ||
71 | schedule(); | ||
72 | } | ||
73 | |||
74 | static void dac_audio_start(void) | ||
75 | { | ||
76 | #ifdef MACH_HP600 | ||
77 | u16 v; | ||
78 | v = inw(HD64461_GPADR); | ||
79 | v &= ~HD64461_GPADR_SPEAKER; | ||
80 | outw(v, HD64461_GPADR); | ||
81 | #endif | ||
82 | sh_dac_enable(CONFIG_SOUND_SH_DAC_AUDIO_CHANNEL); | ||
83 | ctrl_outw(TMU1_TCR_INIT, TMU1_TCR); | ||
84 | } | ||
85 | static void dac_audio_stop(void) | ||
86 | { | ||
87 | #ifdef MACH_HP600 | ||
88 | u16 v; | ||
89 | #endif | ||
90 | dac_audio_stop_timer(); | ||
91 | #ifdef MACH_HP600 | ||
92 | v = inw(HD64461_GPADR); | ||
93 | v |= HD64461_GPADR_SPEAKER; | ||
94 | outw(v, HD64461_GPADR); | ||
95 | #endif | ||
96 | sh_dac_disable(CONFIG_SOUND_SH_DAC_AUDIO_CHANNEL); | ||
97 | } | ||
98 | |||
99 | static void dac_audio_set_rate(void) | ||
100 | { | ||
101 | unsigned long interval; | ||
102 | |||
103 | interval = (current_cpu_data.module_clock / 4) / rate; | ||
104 | ctrl_outl(interval, TMU1_TCOR); | ||
105 | ctrl_outl(interval, TMU1_TCNT); | ||
106 | } | ||
107 | |||
108 | static int dac_audio_ioctl(struct inode *inode, struct file *file, | ||
109 | unsigned int cmd, unsigned long arg) | ||
110 | { | ||
111 | int val; | ||
112 | |||
113 | switch (cmd) { | ||
114 | case OSS_GETVERSION: | ||
115 | return put_user(SOUND_VERSION, (int *)arg); | ||
116 | |||
117 | case SNDCTL_DSP_SYNC: | ||
118 | dac_audio_sync(); | ||
119 | return 0; | ||
120 | |||
121 | case SNDCTL_DSP_RESET: | ||
122 | dac_audio_reset(); | ||
123 | return 0; | ||
124 | |||
125 | case SNDCTL_DSP_GETFMTS: | ||
126 | return put_user(AFMT_U8, (int *)arg); | ||
127 | |||
128 | case SNDCTL_DSP_SETFMT: | ||
129 | return put_user(AFMT_U8, (int *)arg); | ||
130 | |||
131 | case SNDCTL_DSP_NONBLOCK: | ||
132 | file->f_flags |= O_NONBLOCK; | ||
133 | return 0; | ||
134 | |||
135 | case SNDCTL_DSP_GETCAPS: | ||
136 | return 0; | ||
137 | |||
138 | case SOUND_PCM_WRITE_RATE: | ||
139 | val = *(int *)arg; | ||
140 | if (val > 0) { | ||
141 | rate = val; | ||
142 | dac_audio_set_rate(); | ||
143 | } | ||
144 | return put_user(rate, (int *)arg); | ||
145 | |||
146 | case SNDCTL_DSP_STEREO: | ||
147 | return put_user(0, (int *)arg); | ||
148 | |||
149 | case SOUND_PCM_WRITE_CHANNELS: | ||
150 | return put_user(1, (int *)arg); | ||
151 | |||
152 | case SNDCTL_DSP_SETDUPLEX: | ||
153 | return -EINVAL; | ||
154 | |||
155 | case SNDCTL_DSP_PROFILE: | ||
156 | return -EINVAL; | ||
157 | |||
158 | case SNDCTL_DSP_GETBLKSIZE: | ||
159 | return put_user(BUFFER_SIZE, (int *)arg); | ||
160 | |||
161 | case SNDCTL_DSP_SETFRAGMENT: | ||
162 | return 0; | ||
163 | |||
164 | default: | ||
165 | printk(KERN_ERR "sh_dac_audio: unimplemented ioctl=0x%x\n", | ||
166 | cmd); | ||
167 | return -EINVAL; | ||
168 | } | ||
169 | return -EINVAL; | ||
170 | } | ||
171 | |||
172 | static ssize_t dac_audio_write(struct file *file, const char *buf, size_t count, | ||
173 | loff_t * ppos) | ||
174 | { | ||
175 | int free; | ||
176 | int nbytes; | ||
177 | |||
178 | if (count < 0) | ||
179 | return -EINVAL; | ||
180 | |||
181 | if (!count) { | ||
182 | dac_audio_sync(); | ||
183 | return 0; | ||
184 | } | ||
185 | |||
186 | free = buffer_begin - buffer_end; | ||
187 | |||
188 | if (free < 0) | ||
189 | free += BUFFER_SIZE; | ||
190 | if ((free == 0) && (empty)) | ||
191 | free = BUFFER_SIZE; | ||
192 | if (count > free) | ||
193 | count = free; | ||
194 | if (buffer_begin > buffer_end) { | ||
195 | if (copy_from_user((void *)buffer_end, buf, count)) | ||
196 | return -EFAULT; | ||
197 | |||
198 | buffer_end += count; | ||
199 | } else { | ||
200 | nbytes = data_buffer + BUFFER_SIZE - buffer_end; | ||
201 | if (nbytes > count) { | ||
202 | if (copy_from_user((void *)buffer_end, buf, count)) | ||
203 | return -EFAULT; | ||
204 | buffer_end += count; | ||
205 | } else { | ||
206 | if (copy_from_user((void *)buffer_end, buf, nbytes)) | ||
207 | return -EFAULT; | ||
208 | if (copy_from_user | ||
209 | ((void *)data_buffer, buf + nbytes, count - nbytes)) | ||
210 | return -EFAULT; | ||
211 | buffer_end = data_buffer + count - nbytes; | ||
212 | } | ||
213 | } | ||
214 | |||
215 | if (empty) { | ||
216 | empty = 0; | ||
217 | dac_audio_start_timer(); | ||
218 | } | ||
219 | |||
220 | return count; | ||
221 | } | ||
222 | |||
223 | static ssize_t dac_audio_read(struct file *file, char *buf, size_t count, | ||
224 | loff_t * ppos) | ||
225 | { | ||
226 | return -EINVAL; | ||
227 | } | ||
228 | |||
229 | static int dac_audio_open(struct inode *inode, struct file *file) | ||
230 | { | ||
231 | if (file->f_mode & FMODE_READ) | ||
232 | return -ENODEV; | ||
233 | if (in_use) | ||
234 | return -EBUSY; | ||
235 | |||
236 | in_use = 1; | ||
237 | |||
238 | dac_audio_start(); | ||
239 | |||
240 | return 0; | ||
241 | } | ||
242 | |||
243 | static int dac_audio_release(struct inode *inode, struct file *file) | ||
244 | { | ||
245 | dac_audio_sync(); | ||
246 | dac_audio_stop(); | ||
247 | in_use = 0; | ||
248 | |||
249 | return 0; | ||
250 | } | ||
251 | |||
252 | struct file_operations dac_audio_fops = { | ||
253 | .read = dac_audio_read, | ||
254 | .write = dac_audio_write, | ||
255 | .ioctl = dac_audio_ioctl, | ||
256 | .open = dac_audio_open, | ||
257 | .release = dac_audio_release, | ||
258 | }; | ||
259 | |||
260 | static irqreturn_t timer1_interrupt(int irq, void *dev, struct pt_regs *regs) | ||
261 | { | ||
262 | unsigned long timer_status; | ||
263 | |||
264 | timer_status = ctrl_inw(TMU1_TCR); | ||
265 | timer_status &= ~0x100; | ||
266 | ctrl_outw(timer_status, TMU1_TCR); | ||
267 | |||
268 | if (!empty) { | ||
269 | sh_dac_output(*buffer_begin, CONFIG_SOUND_SH_DAC_AUDIO_CHANNEL); | ||
270 | buffer_begin++; | ||
271 | |||
272 | if (buffer_begin == data_buffer + BUFFER_SIZE) | ||
273 | buffer_begin = data_buffer; | ||
274 | if (buffer_begin == buffer_end) { | ||
275 | empty = 1; | ||
276 | dac_audio_stop_timer(); | ||
277 | } | ||
278 | } | ||
279 | return IRQ_HANDLED; | ||
280 | } | ||
281 | |||
282 | static int __init dac_audio_init(void) | ||
283 | { | ||
284 | int retval; | ||
285 | |||
286 | if ((device_major = register_sound_dsp(&dac_audio_fops, -1)) < 0) { | ||
287 | printk(KERN_ERR "Cannot register dsp device"); | ||
288 | return device_major; | ||
289 | } | ||
290 | |||
291 | in_use = 0; | ||
292 | |||
293 | data_buffer = (char *)kmalloc(BUFFER_SIZE, GFP_KERNEL); | ||
294 | if (data_buffer == NULL) | ||
295 | return -ENOMEM; | ||
296 | |||
297 | dac_audio_reset(); | ||
298 | rate = 8000; | ||
299 | dac_audio_set_rate(); | ||
300 | |||
301 | retval = | ||
302 | request_irq(TIMER1_IRQ, timer1_interrupt, SA_INTERRUPT, MODNAME, 0); | ||
303 | if (retval < 0) { | ||
304 | printk(KERN_ERR "sh_dac_audio: IRQ %d request failed\n", | ||
305 | TIMER1_IRQ); | ||
306 | return retval; | ||
307 | } | ||
308 | |||
309 | return 0; | ||
310 | } | ||
311 | |||
312 | static void __exit dac_audio_exit(void) | ||
313 | { | ||
314 | free_irq(TIMER1_IRQ, 0); | ||
315 | |||
316 | unregister_sound_dsp(device_major); | ||
317 | kfree((void *)data_buffer); | ||
318 | } | ||
319 | |||
320 | module_init(dac_audio_init); | ||
321 | module_exit(dac_audio_exit); | ||
322 | |||
323 | MODULE_AUTHOR("Andriy Skulysh, askulysh@image.kiev.ua"); | ||
324 | MODULE_DESCRIPTION("SH DAC sound driver"); | ||
325 | MODULE_LICENSE("GPL"); | ||