diff options
Diffstat (limited to 'drivers/staging/solo6x10/g723.c')
-rw-r--r-- | drivers/staging/solo6x10/g723.c | 399 |
1 files changed, 399 insertions, 0 deletions
diff --git a/drivers/staging/solo6x10/g723.c b/drivers/staging/solo6x10/g723.c new file mode 100644 index 00000000000..59274bfca95 --- /dev/null +++ b/drivers/staging/solo6x10/g723.c | |||
@@ -0,0 +1,399 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2010 Bluecherry, LLC www.bluecherrydvr.com | ||
3 | * Copyright (C) 2010 Ben Collins <bcollins@bluecherry.net> | ||
4 | * | ||
5 | * This program is free software; you can redistribute it and/or modify | ||
6 | * it under the terms of the GNU General Public License as published by | ||
7 | * the Free Software Foundation; either version 2 of the License, or | ||
8 | * (at your option) any later version. | ||
9 | * | ||
10 | * This program is distributed in the hope that it will be useful, | ||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
13 | * GNU General Public License for more details. | ||
14 | * | ||
15 | * You should have received a copy of the GNU General Public License | ||
16 | * along with this program; if not, write to the Free Software | ||
17 | * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | ||
18 | */ | ||
19 | |||
20 | #include <linux/kernel.h> | ||
21 | #include <linux/mempool.h> | ||
22 | #include <linux/poll.h> | ||
23 | #include <linux/kthread.h> | ||
24 | #include <linux/slab.h> | ||
25 | #include <linux/freezer.h> | ||
26 | #include <sound/core.h> | ||
27 | #include <sound/initval.h> | ||
28 | #include <sound/pcm.h> | ||
29 | #include <sound/control.h> | ||
30 | #include "solo6x10.h" | ||
31 | #include "tw28.h" | ||
32 | |||
33 | #define G723_INTR_ORDER 0 | ||
34 | #define G723_FDMA_PAGES 32 | ||
35 | #define G723_PERIOD_BYTES 48 | ||
36 | #define G723_PERIOD_BLOCK 1024 | ||
37 | #define G723_FRAMES_PER_PAGE 48 | ||
38 | |||
39 | /* Sets up channels 16-19 for decoding and 0-15 for encoding */ | ||
40 | #define OUTMODE_MASK 0x300 | ||
41 | |||
42 | #define SAMPLERATE 8000 | ||
43 | #define BITRATE 25 | ||
44 | |||
45 | /* The solo writes to 1k byte pages, 32 pages, in the dma. Each 1k page | ||
46 | * is broken down to 20 * 48 byte regions (one for each channel possible) | ||
47 | * with the rest of the page being dummy data. */ | ||
48 | #define MAX_BUFFER (G723_PERIOD_BYTES * PERIODS_MAX) | ||
49 | #define IRQ_PAGES 4 /* 0 - 4 */ | ||
50 | #define PERIODS_MIN (1 << IRQ_PAGES) | ||
51 | #define PERIODS_MAX G723_FDMA_PAGES | ||
52 | |||
53 | struct solo_snd_pcm { | ||
54 | int on; | ||
55 | spinlock_t lock; | ||
56 | struct solo_dev *solo_dev; | ||
57 | unsigned char g723_buf[G723_PERIOD_BYTES]; | ||
58 | }; | ||
59 | |||
60 | static void solo_g723_config(struct solo_dev *solo_dev) | ||
61 | { | ||
62 | int clk_div; | ||
63 | |||
64 | clk_div = SOLO_CLOCK_MHZ / (SAMPLERATE * (BITRATE * 2) * 2); | ||
65 | |||
66 | solo_reg_write(solo_dev, SOLO_AUDIO_SAMPLE, | ||
67 | SOLO_AUDIO_BITRATE(BITRATE) | | ||
68 | SOLO_AUDIO_CLK_DIV(clk_div)); | ||
69 | |||
70 | solo_reg_write(solo_dev, SOLO_AUDIO_FDMA_INTR, | ||
71 | SOLO_AUDIO_FDMA_INTERVAL(IRQ_PAGES) | | ||
72 | SOLO_AUDIO_INTR_ORDER(G723_INTR_ORDER) | | ||
73 | SOLO_AUDIO_FDMA_BASE(SOLO_G723_EXT_ADDR(solo_dev) >> 16)); | ||
74 | |||
75 | solo_reg_write(solo_dev, SOLO_AUDIO_CONTROL, | ||
76 | SOLO_AUDIO_ENABLE | SOLO_AUDIO_I2S_MODE | | ||
77 | SOLO_AUDIO_I2S_MULTI(3) | SOLO_AUDIO_MODE(OUTMODE_MASK)); | ||
78 | } | ||
79 | |||
80 | void solo_g723_isr(struct solo_dev *solo_dev) | ||
81 | { | ||
82 | struct snd_pcm_str *pstr = | ||
83 | &solo_dev->snd_pcm->streams[SNDRV_PCM_STREAM_CAPTURE]; | ||
84 | struct snd_pcm_substream *ss; | ||
85 | struct solo_snd_pcm *solo_pcm; | ||
86 | |||
87 | solo_reg_write(solo_dev, SOLO_IRQ_STAT, SOLO_IRQ_G723); | ||
88 | |||
89 | for (ss = pstr->substream; ss != NULL; ss = ss->next) { | ||
90 | if (snd_pcm_substream_chip(ss) == NULL) | ||
91 | continue; | ||
92 | |||
93 | /* This means open() hasn't been called on this one */ | ||
94 | if (snd_pcm_substream_chip(ss) == solo_dev) | ||
95 | continue; | ||
96 | |||
97 | /* Haven't triggered a start yet */ | ||
98 | solo_pcm = snd_pcm_substream_chip(ss); | ||
99 | if (!solo_pcm->on) | ||
100 | continue; | ||
101 | |||
102 | snd_pcm_period_elapsed(ss); | ||
103 | } | ||
104 | } | ||
105 | |||
106 | static int snd_solo_hw_params(struct snd_pcm_substream *ss, | ||
107 | struct snd_pcm_hw_params *hw_params) | ||
108 | { | ||
109 | return snd_pcm_lib_malloc_pages(ss, params_buffer_bytes(hw_params)); | ||
110 | } | ||
111 | |||
112 | static int snd_solo_hw_free(struct snd_pcm_substream *ss) | ||
113 | { | ||
114 | return snd_pcm_lib_free_pages(ss); | ||
115 | } | ||
116 | |||
117 | static struct snd_pcm_hardware snd_solo_pcm_hw = { | ||
118 | .info = (SNDRV_PCM_INFO_MMAP | | ||
119 | SNDRV_PCM_INFO_INTERLEAVED | | ||
120 | SNDRV_PCM_INFO_BLOCK_TRANSFER | | ||
121 | SNDRV_PCM_INFO_MMAP_VALID), | ||
122 | .formats = SNDRV_PCM_FMTBIT_U8, | ||
123 | .rates = SNDRV_PCM_RATE_8000, | ||
124 | .rate_min = 8000, | ||
125 | .rate_max = 8000, | ||
126 | .channels_min = 1, | ||
127 | .channels_max = 1, | ||
128 | .buffer_bytes_max = MAX_BUFFER, | ||
129 | .period_bytes_min = G723_PERIOD_BYTES, | ||
130 | .period_bytes_max = G723_PERIOD_BYTES, | ||
131 | .periods_min = PERIODS_MIN, | ||
132 | .periods_max = PERIODS_MAX, | ||
133 | }; | ||
134 | |||
135 | static int snd_solo_pcm_open(struct snd_pcm_substream *ss) | ||
136 | { | ||
137 | struct solo_dev *solo_dev = snd_pcm_substream_chip(ss); | ||
138 | struct solo_snd_pcm *solo_pcm; | ||
139 | |||
140 | solo_pcm = kzalloc(sizeof(*solo_pcm), GFP_KERNEL); | ||
141 | if (solo_pcm == NULL) | ||
142 | return -ENOMEM; | ||
143 | |||
144 | spin_lock_init(&solo_pcm->lock); | ||
145 | solo_pcm->solo_dev = solo_dev; | ||
146 | ss->runtime->hw = snd_solo_pcm_hw; | ||
147 | |||
148 | snd_pcm_substream_chip(ss) = solo_pcm; | ||
149 | |||
150 | return 0; | ||
151 | } | ||
152 | |||
153 | static int snd_solo_pcm_close(struct snd_pcm_substream *ss) | ||
154 | { | ||
155 | struct solo_snd_pcm *solo_pcm = snd_pcm_substream_chip(ss); | ||
156 | |||
157 | snd_pcm_substream_chip(ss) = solo_pcm->solo_dev; | ||
158 | kfree(solo_pcm); | ||
159 | |||
160 | return 0; | ||
161 | } | ||
162 | |||
163 | static int snd_solo_pcm_trigger(struct snd_pcm_substream *ss, int cmd) | ||
164 | { | ||
165 | struct solo_snd_pcm *solo_pcm = snd_pcm_substream_chip(ss); | ||
166 | struct solo_dev *solo_dev = solo_pcm->solo_dev; | ||
167 | int ret = 0; | ||
168 | |||
169 | spin_lock(&solo_pcm->lock); | ||
170 | |||
171 | switch (cmd) { | ||
172 | case SNDRV_PCM_TRIGGER_START: | ||
173 | if (solo_pcm->on == 0) { | ||
174 | /* If this is the first user, switch on interrupts */ | ||
175 | if (atomic_inc_return(&solo_dev->snd_users) == 1) | ||
176 | solo_irq_on(solo_dev, SOLO_IRQ_G723); | ||
177 | solo_pcm->on = 1; | ||
178 | } | ||
179 | break; | ||
180 | case SNDRV_PCM_TRIGGER_STOP: | ||
181 | if (solo_pcm->on) { | ||
182 | /* If this was our last user, switch them off */ | ||
183 | if (atomic_dec_return(&solo_dev->snd_users) == 0) | ||
184 | solo_irq_off(solo_dev, SOLO_IRQ_G723); | ||
185 | solo_pcm->on = 0; | ||
186 | } | ||
187 | break; | ||
188 | default: | ||
189 | ret = -EINVAL; | ||
190 | } | ||
191 | |||
192 | spin_unlock(&solo_pcm->lock); | ||
193 | |||
194 | return ret; | ||
195 | } | ||
196 | |||
197 | static int snd_solo_pcm_prepare(struct snd_pcm_substream *ss) | ||
198 | { | ||
199 | return 0; | ||
200 | } | ||
201 | |||
202 | static snd_pcm_uframes_t snd_solo_pcm_pointer(struct snd_pcm_substream *ss) | ||
203 | { | ||
204 | struct solo_snd_pcm *solo_pcm = snd_pcm_substream_chip(ss); | ||
205 | struct solo_dev *solo_dev = solo_pcm->solo_dev; | ||
206 | snd_pcm_uframes_t idx = solo_reg_read(solo_dev, SOLO_AUDIO_STA) & 0x1f; | ||
207 | |||
208 | return idx * G723_FRAMES_PER_PAGE; | ||
209 | } | ||
210 | |||
211 | static int snd_solo_pcm_copy(struct snd_pcm_substream *ss, int channel, | ||
212 | snd_pcm_uframes_t pos, void __user *dst, | ||
213 | snd_pcm_uframes_t count) | ||
214 | { | ||
215 | struct solo_snd_pcm *solo_pcm = snd_pcm_substream_chip(ss); | ||
216 | struct solo_dev *solo_dev = solo_pcm->solo_dev; | ||
217 | int err, i; | ||
218 | |||
219 | for (i = 0; i < (count / G723_FRAMES_PER_PAGE); i++) { | ||
220 | int page = (pos / G723_FRAMES_PER_PAGE) + i; | ||
221 | |||
222 | err = solo_p2m_dma(solo_dev, SOLO_P2M_DMA_ID_G723E, 0, | ||
223 | solo_pcm->g723_buf, | ||
224 | SOLO_G723_EXT_ADDR(solo_dev) + | ||
225 | (page * G723_PERIOD_BLOCK) + | ||
226 | (ss->number * G723_PERIOD_BYTES), | ||
227 | G723_PERIOD_BYTES); | ||
228 | if (err) | ||
229 | return err; | ||
230 | |||
231 | err = copy_to_user(dst + (i * G723_PERIOD_BYTES), | ||
232 | solo_pcm->g723_buf, G723_PERIOD_BYTES); | ||
233 | |||
234 | if (err) | ||
235 | return -EFAULT; | ||
236 | } | ||
237 | |||
238 | return 0; | ||
239 | } | ||
240 | |||
241 | static struct snd_pcm_ops snd_solo_pcm_ops = { | ||
242 | .open = snd_solo_pcm_open, | ||
243 | .close = snd_solo_pcm_close, | ||
244 | .ioctl = snd_pcm_lib_ioctl, | ||
245 | .hw_params = snd_solo_hw_params, | ||
246 | .hw_free = snd_solo_hw_free, | ||
247 | .prepare = snd_solo_pcm_prepare, | ||
248 | .trigger = snd_solo_pcm_trigger, | ||
249 | .pointer = snd_solo_pcm_pointer, | ||
250 | .copy = snd_solo_pcm_copy, | ||
251 | }; | ||
252 | |||
253 | static int snd_solo_capture_volume_info(struct snd_kcontrol *kcontrol, | ||
254 | struct snd_ctl_elem_info *info) | ||
255 | { | ||
256 | info->type = SNDRV_CTL_ELEM_TYPE_INTEGER; | ||
257 | info->count = 1; | ||
258 | info->value.integer.min = 0; | ||
259 | info->value.integer.max = 15; | ||
260 | info->value.integer.step = 1; | ||
261 | |||
262 | return 0; | ||
263 | } | ||
264 | |||
265 | static int snd_solo_capture_volume_get(struct snd_kcontrol *kcontrol, | ||
266 | struct snd_ctl_elem_value *value) | ||
267 | { | ||
268 | struct solo_dev *solo_dev = snd_kcontrol_chip(kcontrol); | ||
269 | u8 ch = value->id.numid - 1; | ||
270 | |||
271 | value->value.integer.value[0] = tw28_get_audio_gain(solo_dev, ch); | ||
272 | |||
273 | return 0; | ||
274 | } | ||
275 | |||
276 | static int snd_solo_capture_volume_put(struct snd_kcontrol *kcontrol, | ||
277 | struct snd_ctl_elem_value *value) | ||
278 | { | ||
279 | struct solo_dev *solo_dev = snd_kcontrol_chip(kcontrol); | ||
280 | u8 ch = value->id.numid - 1; | ||
281 | u8 old_val; | ||
282 | |||
283 | old_val = tw28_get_audio_gain(solo_dev, ch); | ||
284 | if (old_val == value->value.integer.value[0]) | ||
285 | return 0; | ||
286 | |||
287 | tw28_set_audio_gain(solo_dev, ch, value->value.integer.value[0]); | ||
288 | |||
289 | return 1; | ||
290 | } | ||
291 | |||
292 | static struct snd_kcontrol_new snd_solo_capture_volume = { | ||
293 | .iface = SNDRV_CTL_ELEM_IFACE_MIXER, | ||
294 | .name = "Capture Volume", | ||
295 | .info = snd_solo_capture_volume_info, | ||
296 | .get = snd_solo_capture_volume_get, | ||
297 | .put = snd_solo_capture_volume_put, | ||
298 | }; | ||
299 | |||
300 | static int solo_snd_pcm_init(struct solo_dev *solo_dev) | ||
301 | { | ||
302 | struct snd_card *card = solo_dev->snd_card; | ||
303 | struct snd_pcm *pcm; | ||
304 | struct snd_pcm_substream *ss; | ||
305 | int ret; | ||
306 | int i; | ||
307 | |||
308 | ret = snd_pcm_new(card, card->driver, 0, 0, solo_dev->nr_chans, | ||
309 | &pcm); | ||
310 | if (ret < 0) | ||
311 | return ret; | ||
312 | |||
313 | snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, | ||
314 | &snd_solo_pcm_ops); | ||
315 | |||
316 | snd_pcm_chip(pcm) = solo_dev; | ||
317 | pcm->info_flags = 0; | ||
318 | strcpy(pcm->name, card->shortname); | ||
319 | |||
320 | for (i = 0, ss = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; | ||
321 | ss; ss = ss->next, i++) | ||
322 | sprintf(ss->name, "Camera #%d Audio", i); | ||
323 | |||
324 | ret = snd_pcm_lib_preallocate_pages_for_all(pcm, | ||
325 | SNDRV_DMA_TYPE_CONTINUOUS, | ||
326 | snd_dma_continuous_data(GFP_KERNEL), | ||
327 | MAX_BUFFER, MAX_BUFFER); | ||
328 | if (ret < 0) | ||
329 | return ret; | ||
330 | |||
331 | solo_dev->snd_pcm = pcm; | ||
332 | |||
333 | return 0; | ||
334 | } | ||
335 | |||
336 | int solo_g723_init(struct solo_dev *solo_dev) | ||
337 | { | ||
338 | static struct snd_device_ops ops = { NULL }; | ||
339 | struct snd_card *card; | ||
340 | struct snd_kcontrol_new kctl; | ||
341 | char name[32]; | ||
342 | int ret; | ||
343 | |||
344 | atomic_set(&solo_dev->snd_users, 0); | ||
345 | |||
346 | /* Allows for easier mapping between video and audio */ | ||
347 | sprintf(name, "Softlogic%d", solo_dev->vfd->num); | ||
348 | |||
349 | ret = snd_card_create(SNDRV_DEFAULT_IDX1, name, THIS_MODULE, 0, | ||
350 | &solo_dev->snd_card); | ||
351 | if (ret < 0) | ||
352 | return ret; | ||
353 | |||
354 | card = solo_dev->snd_card; | ||
355 | |||
356 | strcpy(card->driver, SOLO6X10_NAME); | ||
357 | strcpy(card->shortname, "SOLO-6x10 Audio"); | ||
358 | sprintf(card->longname, "%s on %s IRQ %d", card->shortname, | ||
359 | pci_name(solo_dev->pdev), solo_dev->pdev->irq); | ||
360 | snd_card_set_dev(card, &solo_dev->pdev->dev); | ||
361 | |||
362 | ret = snd_device_new(card, SNDRV_DEV_LOWLEVEL, solo_dev, &ops); | ||
363 | if (ret < 0) | ||
364 | goto snd_error; | ||
365 | |||
366 | /* Mixer controls */ | ||
367 | strcpy(card->mixername, "SOLO-6x10"); | ||
368 | kctl = snd_solo_capture_volume; | ||
369 | kctl.count = solo_dev->nr_chans; | ||
370 | ret = snd_ctl_add(card, snd_ctl_new1(&kctl, solo_dev)); | ||
371 | if (ret < 0) | ||
372 | return ret; | ||
373 | |||
374 | ret = solo_snd_pcm_init(solo_dev); | ||
375 | if (ret < 0) | ||
376 | goto snd_error; | ||
377 | |||
378 | ret = snd_card_register(card); | ||
379 | if (ret < 0) | ||
380 | goto snd_error; | ||
381 | |||
382 | solo_g723_config(solo_dev); | ||
383 | |||
384 | dev_info(&solo_dev->pdev->dev, "Alsa sound card as %s\n", name); | ||
385 | |||
386 | return 0; | ||
387 | |||
388 | snd_error: | ||
389 | snd_card_free(card); | ||
390 | return ret; | ||
391 | } | ||
392 | |||
393 | void solo_g723_exit(struct solo_dev *solo_dev) | ||
394 | { | ||
395 | solo_reg_write(solo_dev, SOLO_AUDIO_CONTROL, 0); | ||
396 | solo_irq_off(solo_dev, SOLO_IRQ_G723); | ||
397 | |||
398 | snd_card_free(solo_dev->snd_card); | ||
399 | } | ||