xref: /linux/drivers/media/usb/go7007/snd-go7007.c (revision 03ab8e6297acd1bc0eedaa050e2a1635c576fd11)
11802d0beSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
2866b8695SGreg Kroah-Hartman /*
3866b8695SGreg Kroah-Hartman  * Copyright (C) 2005-2006 Micronas USA Inc.
4866b8695SGreg Kroah-Hartman  */
5866b8695SGreg Kroah-Hartman 
6866b8695SGreg Kroah-Hartman #include <linux/kernel.h>
7866b8695SGreg Kroah-Hartman #include <linux/module.h>
8866b8695SGreg Kroah-Hartman #include <linux/moduleparam.h>
9866b8695SGreg Kroah-Hartman #include <linux/spinlock.h>
10866b8695SGreg Kroah-Hartman #include <linux/delay.h>
11866b8695SGreg Kroah-Hartman #include <linux/sched.h>
12866b8695SGreg Kroah-Hartman #include <linux/time.h>
13866b8695SGreg Kroah-Hartman #include <linux/mm.h>
14866b8695SGreg Kroah-Hartman #include <linux/i2c.h>
15fd9a40daSMauro Carvalho Chehab #include <linux/mutex.h>
16866b8695SGreg Kroah-Hartman #include <linux/uaccess.h>
175a0e3ad6STejun Heo #include <linux/slab.h>
18866b8695SGreg Kroah-Hartman #include <sound/core.h>
19866b8695SGreg Kroah-Hartman #include <sound/pcm.h>
20866b8695SGreg Kroah-Hartman #include <sound/initval.h>
21866b8695SGreg Kroah-Hartman 
22866b8695SGreg Kroah-Hartman #include "go7007-priv.h"
23866b8695SGreg Kroah-Hartman 
24866b8695SGreg Kroah-Hartman static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
25866b8695SGreg Kroah-Hartman static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
2690ab5ee9SRusty Russell static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
27866b8695SGreg Kroah-Hartman 
28866b8695SGreg Kroah-Hartman module_param_array(index, int, NULL, 0444);
29866b8695SGreg Kroah-Hartman module_param_array(id, charp, NULL, 0444);
30866b8695SGreg Kroah-Hartman module_param_array(enable, bool, NULL, 0444);
31866b8695SGreg Kroah-Hartman MODULE_PARM_DESC(index, "Index value for the go7007 audio driver");
32df20d69eSRoss Cohen MODULE_PARM_DESC(id, "ID string for the go7007 audio driver");
33df20d69eSRoss Cohen MODULE_PARM_DESC(enable, "Enable for the go7007 audio driver");
34866b8695SGreg Kroah-Hartman 
35866b8695SGreg Kroah-Hartman struct go7007_snd {
36866b8695SGreg Kroah-Hartman 	struct snd_card *card;
37866b8695SGreg Kroah-Hartman 	struct snd_pcm *pcm;
38866b8695SGreg Kroah-Hartman 	struct snd_pcm_substream *substream;
39866b8695SGreg Kroah-Hartman 	spinlock_t lock;
40866b8695SGreg Kroah-Hartman 	int w_idx;
41866b8695SGreg Kroah-Hartman 	int hw_ptr;
42866b8695SGreg Kroah-Hartman 	int avail;
43866b8695SGreg Kroah-Hartman 	int capturing;
44866b8695SGreg Kroah-Hartman };
45866b8695SGreg Kroah-Hartman 
4638c4f03aSBhumika Goyal static const struct snd_pcm_hardware go7007_snd_capture_hw = {
47866b8695SGreg Kroah-Hartman 	.info			= (SNDRV_PCM_INFO_MMAP |
48866b8695SGreg Kroah-Hartman 					SNDRV_PCM_INFO_INTERLEAVED |
49866b8695SGreg Kroah-Hartman 					SNDRV_PCM_INFO_BLOCK_TRANSFER |
50866b8695SGreg Kroah-Hartman 					SNDRV_PCM_INFO_MMAP_VALID),
51866b8695SGreg Kroah-Hartman 	.formats		= SNDRV_PCM_FMTBIT_S16_LE,
52866b8695SGreg Kroah-Hartman 	.rates			= SNDRV_PCM_RATE_48000,
53866b8695SGreg Kroah-Hartman 	.rate_min		= 48000,
54866b8695SGreg Kroah-Hartman 	.rate_max		= 48000,
55866b8695SGreg Kroah-Hartman 	.channels_min		= 2,
56866b8695SGreg Kroah-Hartman 	.channels_max		= 2,
57866b8695SGreg Kroah-Hartman 	.buffer_bytes_max	= (128*1024),
58866b8695SGreg Kroah-Hartman 	.period_bytes_min	= 4096,
59866b8695SGreg Kroah-Hartman 	.period_bytes_max	= (128*1024),
60866b8695SGreg Kroah-Hartman 	.periods_min		= 1,
61866b8695SGreg Kroah-Hartman 	.periods_max		= 32,
62866b8695SGreg Kroah-Hartman };
63866b8695SGreg Kroah-Hartman 
64866b8695SGreg Kroah-Hartman static void parse_audio_stream_data(struct go7007 *go, u8 *buf, int length)
65866b8695SGreg Kroah-Hartman {
66866b8695SGreg Kroah-Hartman 	struct go7007_snd *gosnd = go->snd_context;
67866b8695SGreg Kroah-Hartman 	struct snd_pcm_runtime *runtime = gosnd->substream->runtime;
68866b8695SGreg Kroah-Hartman 	int frames = bytes_to_frames(runtime, length);
69a4733b52SSebastian Andrzej Siewior 	unsigned long flags;
70866b8695SGreg Kroah-Hartman 
71a4733b52SSebastian Andrzej Siewior 	spin_lock_irqsave(&gosnd->lock, flags);
72866b8695SGreg Kroah-Hartman 	gosnd->hw_ptr += frames;
73866b8695SGreg Kroah-Hartman 	if (gosnd->hw_ptr >= runtime->buffer_size)
74866b8695SGreg Kroah-Hartman 		gosnd->hw_ptr -= runtime->buffer_size;
75866b8695SGreg Kroah-Hartman 	gosnd->avail += frames;
76a4733b52SSebastian Andrzej Siewior 	spin_unlock_irqrestore(&gosnd->lock, flags);
77866b8695SGreg Kroah-Hartman 	if (gosnd->w_idx + length > runtime->dma_bytes) {
78866b8695SGreg Kroah-Hartman 		int cpy = runtime->dma_bytes - gosnd->w_idx;
79866b8695SGreg Kroah-Hartman 
80866b8695SGreg Kroah-Hartman 		memcpy(runtime->dma_area + gosnd->w_idx, buf, cpy);
81866b8695SGreg Kroah-Hartman 		length -= cpy;
82866b8695SGreg Kroah-Hartman 		buf += cpy;
83866b8695SGreg Kroah-Hartman 		gosnd->w_idx = 0;
84866b8695SGreg Kroah-Hartman 	}
85866b8695SGreg Kroah-Hartman 	memcpy(runtime->dma_area + gosnd->w_idx, buf, length);
86866b8695SGreg Kroah-Hartman 	gosnd->w_idx += length;
87a4733b52SSebastian Andrzej Siewior 	spin_lock_irqsave(&gosnd->lock, flags);
88866b8695SGreg Kroah-Hartman 	if (gosnd->avail < runtime->period_size) {
89a4733b52SSebastian Andrzej Siewior 		spin_unlock_irqrestore(&gosnd->lock, flags);
90866b8695SGreg Kroah-Hartman 		return;
91866b8695SGreg Kroah-Hartman 	}
92866b8695SGreg Kroah-Hartman 	gosnd->avail -= runtime->period_size;
93a4733b52SSebastian Andrzej Siewior 	spin_unlock_irqrestore(&gosnd->lock, flags);
94866b8695SGreg Kroah-Hartman 	if (gosnd->capturing)
95866b8695SGreg Kroah-Hartman 		snd_pcm_period_elapsed(gosnd->substream);
96866b8695SGreg Kroah-Hartman }
97866b8695SGreg Kroah-Hartman 
98866b8695SGreg Kroah-Hartman static int go7007_snd_hw_params(struct snd_pcm_substream *substream,
99866b8695SGreg Kroah-Hartman 				struct snd_pcm_hw_params *hw_params)
100866b8695SGreg Kroah-Hartman {
101866b8695SGreg Kroah-Hartman 	struct go7007 *go = snd_pcm_substream_chip(substream);
102866b8695SGreg Kroah-Hartman 
103866b8695SGreg Kroah-Hartman 	go->audio_deliver = parse_audio_stream_data;
104866b8695SGreg Kroah-Hartman 	return 0;
105866b8695SGreg Kroah-Hartman }
106866b8695SGreg Kroah-Hartman 
107866b8695SGreg Kroah-Hartman static int go7007_snd_hw_free(struct snd_pcm_substream *substream)
108866b8695SGreg Kroah-Hartman {
109866b8695SGreg Kroah-Hartman 	struct go7007 *go = snd_pcm_substream_chip(substream);
110866b8695SGreg Kroah-Hartman 
111866b8695SGreg Kroah-Hartman 	go->audio_deliver = NULL;
112866b8695SGreg Kroah-Hartman 	return 0;
113866b8695SGreg Kroah-Hartman }
114866b8695SGreg Kroah-Hartman 
115866b8695SGreg Kroah-Hartman static int go7007_snd_capture_open(struct snd_pcm_substream *substream)
116866b8695SGreg Kroah-Hartman {
117866b8695SGreg Kroah-Hartman 	struct go7007 *go = snd_pcm_substream_chip(substream);
118866b8695SGreg Kroah-Hartman 	struct go7007_snd *gosnd = go->snd_context;
119866b8695SGreg Kroah-Hartman 	unsigned long flags;
120866b8695SGreg Kroah-Hartman 	int r;
121866b8695SGreg Kroah-Hartman 
122866b8695SGreg Kroah-Hartman 	spin_lock_irqsave(&gosnd->lock, flags);
123866b8695SGreg Kroah-Hartman 	if (gosnd->substream == NULL) {
124866b8695SGreg Kroah-Hartman 		gosnd->substream = substream;
125866b8695SGreg Kroah-Hartman 		substream->runtime->hw = go7007_snd_capture_hw;
126866b8695SGreg Kroah-Hartman 		r = 0;
127866b8695SGreg Kroah-Hartman 	} else
128866b8695SGreg Kroah-Hartman 		r = -EBUSY;
129866b8695SGreg Kroah-Hartman 	spin_unlock_irqrestore(&gosnd->lock, flags);
130866b8695SGreg Kroah-Hartman 	return r;
131866b8695SGreg Kroah-Hartman }
132866b8695SGreg Kroah-Hartman 
133866b8695SGreg Kroah-Hartman static int go7007_snd_capture_close(struct snd_pcm_substream *substream)
134866b8695SGreg Kroah-Hartman {
135866b8695SGreg Kroah-Hartman 	struct go7007 *go = snd_pcm_substream_chip(substream);
136866b8695SGreg Kroah-Hartman 	struct go7007_snd *gosnd = go->snd_context;
137866b8695SGreg Kroah-Hartman 
138866b8695SGreg Kroah-Hartman 	gosnd->substream = NULL;
139866b8695SGreg Kroah-Hartman 	return 0;
140866b8695SGreg Kroah-Hartman }
141866b8695SGreg Kroah-Hartman 
142866b8695SGreg Kroah-Hartman static int go7007_snd_pcm_prepare(struct snd_pcm_substream *substream)
143866b8695SGreg Kroah-Hartman {
144866b8695SGreg Kroah-Hartman 	return 0;
145866b8695SGreg Kroah-Hartman }
146866b8695SGreg Kroah-Hartman 
147866b8695SGreg Kroah-Hartman static int go7007_snd_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
148866b8695SGreg Kroah-Hartman {
149866b8695SGreg Kroah-Hartman 	struct go7007 *go = snd_pcm_substream_chip(substream);
150866b8695SGreg Kroah-Hartman 	struct go7007_snd *gosnd = go->snd_context;
151866b8695SGreg Kroah-Hartman 
152866b8695SGreg Kroah-Hartman 	switch (cmd) {
153866b8695SGreg Kroah-Hartman 	case SNDRV_PCM_TRIGGER_START:
154866b8695SGreg Kroah-Hartman 		/* Just set a flag to indicate we should signal ALSA when
155866b8695SGreg Kroah-Hartman 		 * sound comes in */
156866b8695SGreg Kroah-Hartman 		gosnd->capturing = 1;
157866b8695SGreg Kroah-Hartman 		return 0;
158866b8695SGreg Kroah-Hartman 	case SNDRV_PCM_TRIGGER_STOP:
159866b8695SGreg Kroah-Hartman 		gosnd->hw_ptr = gosnd->w_idx = gosnd->avail = 0;
160866b8695SGreg Kroah-Hartman 		gosnd->capturing = 0;
161866b8695SGreg Kroah-Hartman 		return 0;
162866b8695SGreg Kroah-Hartman 	default:
163866b8695SGreg Kroah-Hartman 		return -EINVAL;
164866b8695SGreg Kroah-Hartman 	}
165866b8695SGreg Kroah-Hartman }
166866b8695SGreg Kroah-Hartman 
167866b8695SGreg Kroah-Hartman static snd_pcm_uframes_t go7007_snd_pcm_pointer(struct snd_pcm_substream *substream)
168866b8695SGreg Kroah-Hartman {
169866b8695SGreg Kroah-Hartman 	struct go7007 *go = snd_pcm_substream_chip(substream);
170866b8695SGreg Kroah-Hartman 	struct go7007_snd *gosnd = go->snd_context;
171866b8695SGreg Kroah-Hartman 
172866b8695SGreg Kroah-Hartman 	return gosnd->hw_ptr;
173866b8695SGreg Kroah-Hartman }
174866b8695SGreg Kroah-Hartman 
17522511cfaSJulia Lawall static const struct snd_pcm_ops go7007_snd_capture_ops = {
176866b8695SGreg Kroah-Hartman 	.open		= go7007_snd_capture_open,
177866b8695SGreg Kroah-Hartman 	.close		= go7007_snd_capture_close,
178866b8695SGreg Kroah-Hartman 	.hw_params	= go7007_snd_hw_params,
179866b8695SGreg Kroah-Hartman 	.hw_free	= go7007_snd_hw_free,
180866b8695SGreg Kroah-Hartman 	.prepare	= go7007_snd_pcm_prepare,
181866b8695SGreg Kroah-Hartman 	.trigger	= go7007_snd_pcm_trigger,
182866b8695SGreg Kroah-Hartman 	.pointer	= go7007_snd_pcm_pointer,
183866b8695SGreg Kroah-Hartman };
184866b8695SGreg Kroah-Hartman 
185866b8695SGreg Kroah-Hartman static int go7007_snd_free(struct snd_device *device)
186866b8695SGreg Kroah-Hartman {
187866b8695SGreg Kroah-Hartman 	struct go7007 *go = device->device_data;
188866b8695SGreg Kroah-Hartman 
189866b8695SGreg Kroah-Hartman 	kfree(go->snd_context);
190866b8695SGreg Kroah-Hartman 	go->snd_context = NULL;
191866b8695SGreg Kroah-Hartman 	return 0;
192866b8695SGreg Kroah-Hartman }
193866b8695SGreg Kroah-Hartman 
194*524e1effSRikard Falkeborn static const struct snd_device_ops go7007_snd_device_ops = {
195866b8695SGreg Kroah-Hartman 	.dev_free	= go7007_snd_free,
196866b8695SGreg Kroah-Hartman };
197866b8695SGreg Kroah-Hartman 
198866b8695SGreg Kroah-Hartman int go7007_snd_init(struct go7007 *go)
199866b8695SGreg Kroah-Hartman {
200866b8695SGreg Kroah-Hartman 	static int dev;
201866b8695SGreg Kroah-Hartman 	struct go7007_snd *gosnd;
2029aa4d4eaSMarkus Elfring 	int ret;
203866b8695SGreg Kroah-Hartman 
204866b8695SGreg Kroah-Hartman 	if (dev >= SNDRV_CARDS)
205866b8695SGreg Kroah-Hartman 		return -ENODEV;
206866b8695SGreg Kroah-Hartman 	if (!enable[dev]) {
207866b8695SGreg Kroah-Hartman 		dev++;
208866b8695SGreg Kroah-Hartman 		return -ENOENT;
209866b8695SGreg Kroah-Hartman 	}
210866b8695SGreg Kroah-Hartman 	gosnd = kmalloc(sizeof(struct go7007_snd), GFP_KERNEL);
211866b8695SGreg Kroah-Hartman 	if (gosnd == NULL)
212866b8695SGreg Kroah-Hartman 		return -ENOMEM;
213866b8695SGreg Kroah-Hartman 	spin_lock_init(&gosnd->lock);
214866b8695SGreg Kroah-Hartman 	gosnd->hw_ptr = gosnd->w_idx = gosnd->avail = 0;
215866b8695SGreg Kroah-Hartman 	gosnd->capturing = 0;
2163a3d2afdSTakashi Iwai 	ret = snd_card_new(go->dev, index[dev], id[dev], THIS_MODULE, 0,
2176ff18716STakashi Iwai 			   &gosnd->card);
2189453264eSChuhong Yuan 	if (ret < 0)
2199453264eSChuhong Yuan 		goto free_snd;
2209453264eSChuhong Yuan 
221866b8695SGreg Kroah-Hartman 	ret = snd_device_new(gosnd->card, SNDRV_DEV_LOWLEVEL, go,
222866b8695SGreg Kroah-Hartman 			&go7007_snd_device_ops);
2239453264eSChuhong Yuan 	if (ret < 0)
2249453264eSChuhong Yuan 		goto free_card;
2259453264eSChuhong Yuan 
226866b8695SGreg Kroah-Hartman 	ret = snd_pcm_new(gosnd->card, "go7007", 0, 0, 1, &gosnd->pcm);
2279453264eSChuhong Yuan 	if (ret < 0)
2289453264eSChuhong Yuan 		goto free_card;
2299453264eSChuhong Yuan 
230c0decac1SMauro Carvalho Chehab 	strscpy(gosnd->card->driver, "go7007", sizeof(gosnd->card->driver));
231ba78170eSJoe Perches 	strscpy(gosnd->card->shortname, go->name, sizeof(gosnd->card->shortname));
232c0decac1SMauro Carvalho Chehab 	strscpy(gosnd->card->longname, gosnd->card->shortname,
233866b8695SGreg Kroah-Hartman 		sizeof(gosnd->card->longname));
234866b8695SGreg Kroah-Hartman 
235866b8695SGreg Kroah-Hartman 	gosnd->pcm->private_data = go;
236866b8695SGreg Kroah-Hartman 	snd_pcm_set_ops(gosnd->pcm, SNDRV_PCM_STREAM_CAPTURE,
237866b8695SGreg Kroah-Hartman 			&go7007_snd_capture_ops);
2382d19a2cdSTakashi Iwai 	snd_pcm_set_managed_buffer_all(gosnd->pcm, SNDRV_DMA_TYPE_VMALLOC,
2392d19a2cdSTakashi Iwai 				       NULL, 0, 0);
240866b8695SGreg Kroah-Hartman 
241866b8695SGreg Kroah-Hartman 	ret = snd_card_register(gosnd->card);
2429453264eSChuhong Yuan 	if (ret < 0)
2439453264eSChuhong Yuan 		goto free_card;
244866b8695SGreg Kroah-Hartman 
245866b8695SGreg Kroah-Hartman 	gosnd->substream = NULL;
246866b8695SGreg Kroah-Hartman 	go->snd_context = gosnd;
247d5d3a7ccSHans Verkuil 	v4l2_device_get(&go->v4l2_dev);
248866b8695SGreg Kroah-Hartman 	++dev;
249866b8695SGreg Kroah-Hartman 
250866b8695SGreg Kroah-Hartman 	return 0;
2519453264eSChuhong Yuan 
2529453264eSChuhong Yuan free_card:
2539453264eSChuhong Yuan 	snd_card_free(gosnd->card);
2549453264eSChuhong Yuan free_snd:
2559453264eSChuhong Yuan 	kfree(gosnd);
2569453264eSChuhong Yuan 	return ret;
257866b8695SGreg Kroah-Hartman }
258866b8695SGreg Kroah-Hartman EXPORT_SYMBOL(go7007_snd_init);
259866b8695SGreg Kroah-Hartman 
260866b8695SGreg Kroah-Hartman int go7007_snd_remove(struct go7007 *go)
261866b8695SGreg Kroah-Hartman {
262866b8695SGreg Kroah-Hartman 	struct go7007_snd *gosnd = go->snd_context;
263866b8695SGreg Kroah-Hartman 
264866b8695SGreg Kroah-Hartman 	snd_card_disconnect(gosnd->card);
265866b8695SGreg Kroah-Hartman 	snd_card_free_when_closed(gosnd->card);
266d5d3a7ccSHans Verkuil 	v4l2_device_put(&go->v4l2_dev);
267866b8695SGreg Kroah-Hartman 	return 0;
268866b8695SGreg Kroah-Hartman }
269866b8695SGreg Kroah-Hartman EXPORT_SYMBOL(go7007_snd_remove);
270866b8695SGreg Kroah-Hartman 
271866b8695SGreg Kroah-Hartman MODULE_LICENSE("GPL v2");
272