1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2005-2006 Micronas USA Inc. |
4 | */ |
5 | |
6 | #include <linux/kernel.h> |
7 | #include <linux/module.h> |
8 | #include <linux/moduleparam.h> |
9 | #include <linux/spinlock.h> |
10 | #include <linux/delay.h> |
11 | #include <linux/sched.h> |
12 | #include <linux/time.h> |
13 | #include <linux/mm.h> |
14 | #include <linux/i2c.h> |
15 | #include <linux/mutex.h> |
16 | #include <linux/uaccess.h> |
17 | #include <linux/slab.h> |
18 | #include <sound/core.h> |
19 | #include <sound/pcm.h> |
20 | #include <sound/initval.h> |
21 | |
22 | #include "go7007-priv.h" |
23 | |
24 | static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; |
25 | static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; |
26 | static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; |
27 | |
28 | module_param_array(index, int, NULL, 0444); |
29 | module_param_array(id, charp, NULL, 0444); |
30 | module_param_array(enable, bool, NULL, 0444); |
31 | MODULE_PARM_DESC(index, "Index value for the go7007 audio driver" ); |
32 | MODULE_PARM_DESC(id, "ID string for the go7007 audio driver" ); |
33 | MODULE_PARM_DESC(enable, "Enable for the go7007 audio driver" ); |
34 | |
35 | struct go7007_snd { |
36 | struct snd_card *card; |
37 | struct snd_pcm *pcm; |
38 | struct snd_pcm_substream *substream; |
39 | spinlock_t lock; |
40 | int w_idx; |
41 | int hw_ptr; |
42 | int avail; |
43 | int capturing; |
44 | }; |
45 | |
46 | static const struct snd_pcm_hardware go7007_snd_capture_hw = { |
47 | .info = (SNDRV_PCM_INFO_MMAP | |
48 | SNDRV_PCM_INFO_INTERLEAVED | |
49 | SNDRV_PCM_INFO_BLOCK_TRANSFER | |
50 | SNDRV_PCM_INFO_MMAP_VALID), |
51 | .formats = SNDRV_PCM_FMTBIT_S16_LE, |
52 | .rates = SNDRV_PCM_RATE_48000, |
53 | .rate_min = 48000, |
54 | .rate_max = 48000, |
55 | .channels_min = 2, |
56 | .channels_max = 2, |
57 | .buffer_bytes_max = (128*1024), |
58 | .period_bytes_min = 4096, |
59 | .period_bytes_max = (128*1024), |
60 | .periods_min = 1, |
61 | .periods_max = 32, |
62 | }; |
63 | |
64 | static void parse_audio_stream_data(struct go7007 *go, u8 *buf, int length) |
65 | { |
66 | struct go7007_snd *gosnd = go->snd_context; |
67 | struct snd_pcm_runtime *runtime = gosnd->substream->runtime; |
68 | int frames = bytes_to_frames(runtime, size: length); |
69 | unsigned long flags; |
70 | |
71 | spin_lock_irqsave(&gosnd->lock, flags); |
72 | gosnd->hw_ptr += frames; |
73 | if (gosnd->hw_ptr >= runtime->buffer_size) |
74 | gosnd->hw_ptr -= runtime->buffer_size; |
75 | gosnd->avail += frames; |
76 | spin_unlock_irqrestore(lock: &gosnd->lock, flags); |
77 | if (gosnd->w_idx + length > runtime->dma_bytes) { |
78 | int cpy = runtime->dma_bytes - gosnd->w_idx; |
79 | |
80 | memcpy(runtime->dma_area + gosnd->w_idx, buf, cpy); |
81 | length -= cpy; |
82 | buf += cpy; |
83 | gosnd->w_idx = 0; |
84 | } |
85 | memcpy(runtime->dma_area + gosnd->w_idx, buf, length); |
86 | gosnd->w_idx += length; |
87 | spin_lock_irqsave(&gosnd->lock, flags); |
88 | if (gosnd->avail < runtime->period_size) { |
89 | spin_unlock_irqrestore(lock: &gosnd->lock, flags); |
90 | return; |
91 | } |
92 | gosnd->avail -= runtime->period_size; |
93 | spin_unlock_irqrestore(lock: &gosnd->lock, flags); |
94 | if (gosnd->capturing) |
95 | snd_pcm_period_elapsed(substream: gosnd->substream); |
96 | } |
97 | |
98 | static int go7007_snd_hw_params(struct snd_pcm_substream *substream, |
99 | struct snd_pcm_hw_params *hw_params) |
100 | { |
101 | struct go7007 *go = snd_pcm_substream_chip(substream); |
102 | |
103 | go->audio_deliver = parse_audio_stream_data; |
104 | return 0; |
105 | } |
106 | |
107 | static int go7007_snd_hw_free(struct snd_pcm_substream *substream) |
108 | { |
109 | struct go7007 *go = snd_pcm_substream_chip(substream); |
110 | |
111 | go->audio_deliver = NULL; |
112 | return 0; |
113 | } |
114 | |
115 | static int go7007_snd_capture_open(struct snd_pcm_substream *substream) |
116 | { |
117 | struct go7007 *go = snd_pcm_substream_chip(substream); |
118 | struct go7007_snd *gosnd = go->snd_context; |
119 | unsigned long flags; |
120 | int r; |
121 | |
122 | spin_lock_irqsave(&gosnd->lock, flags); |
123 | if (gosnd->substream == NULL) { |
124 | gosnd->substream = substream; |
125 | substream->runtime->hw = go7007_snd_capture_hw; |
126 | r = 0; |
127 | } else |
128 | r = -EBUSY; |
129 | spin_unlock_irqrestore(lock: &gosnd->lock, flags); |
130 | return r; |
131 | } |
132 | |
133 | static int go7007_snd_capture_close(struct snd_pcm_substream *substream) |
134 | { |
135 | struct go7007 *go = snd_pcm_substream_chip(substream); |
136 | struct go7007_snd *gosnd = go->snd_context; |
137 | |
138 | gosnd->substream = NULL; |
139 | return 0; |
140 | } |
141 | |
142 | static int go7007_snd_pcm_prepare(struct snd_pcm_substream *substream) |
143 | { |
144 | return 0; |
145 | } |
146 | |
147 | static int go7007_snd_pcm_trigger(struct snd_pcm_substream *substream, int cmd) |
148 | { |
149 | struct go7007 *go = snd_pcm_substream_chip(substream); |
150 | struct go7007_snd *gosnd = go->snd_context; |
151 | |
152 | switch (cmd) { |
153 | case SNDRV_PCM_TRIGGER_START: |
154 | /* Just set a flag to indicate we should signal ALSA when |
155 | * sound comes in */ |
156 | gosnd->capturing = 1; |
157 | return 0; |
158 | case SNDRV_PCM_TRIGGER_STOP: |
159 | gosnd->hw_ptr = gosnd->w_idx = gosnd->avail = 0; |
160 | gosnd->capturing = 0; |
161 | return 0; |
162 | default: |
163 | return -EINVAL; |
164 | } |
165 | } |
166 | |
167 | static snd_pcm_uframes_t go7007_snd_pcm_pointer(struct snd_pcm_substream *substream) |
168 | { |
169 | struct go7007 *go = snd_pcm_substream_chip(substream); |
170 | struct go7007_snd *gosnd = go->snd_context; |
171 | |
172 | return gosnd->hw_ptr; |
173 | } |
174 | |
175 | static const struct snd_pcm_ops go7007_snd_capture_ops = { |
176 | .open = go7007_snd_capture_open, |
177 | .close = go7007_snd_capture_close, |
178 | .hw_params = go7007_snd_hw_params, |
179 | .hw_free = go7007_snd_hw_free, |
180 | .prepare = go7007_snd_pcm_prepare, |
181 | .trigger = go7007_snd_pcm_trigger, |
182 | .pointer = go7007_snd_pcm_pointer, |
183 | }; |
184 | |
185 | static int go7007_snd_free(struct snd_device *device) |
186 | { |
187 | struct go7007 *go = device->device_data; |
188 | |
189 | kfree(objp: go->snd_context); |
190 | go->snd_context = NULL; |
191 | return 0; |
192 | } |
193 | |
194 | static const struct snd_device_ops go7007_snd_device_ops = { |
195 | .dev_free = go7007_snd_free, |
196 | }; |
197 | |
198 | int go7007_snd_init(struct go7007 *go) |
199 | { |
200 | static int dev; |
201 | struct go7007_snd *gosnd; |
202 | int ret; |
203 | |
204 | if (dev >= SNDRV_CARDS) |
205 | return -ENODEV; |
206 | if (!enable[dev]) { |
207 | dev++; |
208 | return -ENOENT; |
209 | } |
210 | gosnd = kmalloc(size: sizeof(struct go7007_snd), GFP_KERNEL); |
211 | if (gosnd == NULL) |
212 | return -ENOMEM; |
213 | spin_lock_init(&gosnd->lock); |
214 | gosnd->hw_ptr = gosnd->w_idx = gosnd->avail = 0; |
215 | gosnd->capturing = 0; |
216 | ret = snd_card_new(parent: go->dev, idx: index[dev], xid: id[dev], THIS_MODULE, extra_size: 0, |
217 | card_ret: &gosnd->card); |
218 | if (ret < 0) |
219 | goto free_snd; |
220 | |
221 | ret = snd_device_new(card: gosnd->card, type: SNDRV_DEV_LOWLEVEL, device_data: go, |
222 | ops: &go7007_snd_device_ops); |
223 | if (ret < 0) |
224 | goto free_card; |
225 | |
226 | ret = snd_pcm_new(card: gosnd->card, id: "go7007" , device: 0, playback_count: 0, capture_count: 1, rpcm: &gosnd->pcm); |
227 | if (ret < 0) |
228 | goto free_card; |
229 | |
230 | strscpy(gosnd->card->driver, "go7007" , sizeof(gosnd->card->driver)); |
231 | strscpy(gosnd->card->shortname, go->name, sizeof(gosnd->card->shortname)); |
232 | strscpy(gosnd->card->longname, gosnd->card->shortname, |
233 | sizeof(gosnd->card->longname)); |
234 | |
235 | gosnd->pcm->private_data = go; |
236 | snd_pcm_set_ops(pcm: gosnd->pcm, direction: SNDRV_PCM_STREAM_CAPTURE, |
237 | ops: &go7007_snd_capture_ops); |
238 | snd_pcm_set_managed_buffer_all(pcm: gosnd->pcm, SNDRV_DMA_TYPE_VMALLOC, |
239 | NULL, size: 0, max: 0); |
240 | |
241 | ret = snd_card_register(card: gosnd->card); |
242 | if (ret < 0) |
243 | goto free_card; |
244 | |
245 | gosnd->substream = NULL; |
246 | go->snd_context = gosnd; |
247 | v4l2_device_get(v4l2_dev: &go->v4l2_dev); |
248 | ++dev; |
249 | |
250 | return 0; |
251 | |
252 | free_card: |
253 | snd_card_free(card: gosnd->card); |
254 | free_snd: |
255 | kfree(objp: gosnd); |
256 | return ret; |
257 | } |
258 | EXPORT_SYMBOL(go7007_snd_init); |
259 | |
260 | int go7007_snd_remove(struct go7007 *go) |
261 | { |
262 | struct go7007_snd *gosnd = go->snd_context; |
263 | |
264 | snd_card_disconnect(card: gosnd->card); |
265 | snd_card_free_when_closed(card: gosnd->card); |
266 | v4l2_device_put(v4l2_dev: &go->v4l2_dev); |
267 | return 0; |
268 | } |
269 | EXPORT_SYMBOL(go7007_snd_remove); |
270 | |
271 | MODULE_LICENSE("GPL v2" ); |
272 | |