|  | // SPDX-License-Identifier: GPL-2.0-only | 
|  | /* | 
|  | * Copyright (C) 2005-2006 Micronas USA Inc. | 
|  | */ | 
|  |  | 
|  | #include <linux/kernel.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/moduleparam.h> | 
|  | #include <linux/spinlock.h> | 
|  | #include <linux/delay.h> | 
|  | #include <linux/sched.h> | 
|  | #include <linux/time.h> | 
|  | #include <linux/mm.h> | 
|  | #include <linux/i2c.h> | 
|  | #include <linux/mutex.h> | 
|  | #include <linux/uaccess.h> | 
|  | #include <linux/slab.h> | 
|  | #include <sound/core.h> | 
|  | #include <sound/pcm.h> | 
|  | #include <sound/initval.h> | 
|  |  | 
|  | #include "go7007-priv.h" | 
|  |  | 
|  | static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; | 
|  | static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; | 
|  | static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; | 
|  |  | 
|  | module_param_array(index, int, NULL, 0444); | 
|  | module_param_array(id, charp, NULL, 0444); | 
|  | module_param_array(enable, bool, NULL, 0444); | 
|  | MODULE_PARM_DESC(index, "Index value for the go7007 audio driver"); | 
|  | MODULE_PARM_DESC(id, "ID string for the go7007 audio driver"); | 
|  | MODULE_PARM_DESC(enable, "Enable for the go7007 audio driver"); | 
|  |  | 
|  | struct go7007_snd { | 
|  | struct snd_card *card; | 
|  | struct snd_pcm *pcm; | 
|  | struct snd_pcm_substream *substream; | 
|  | spinlock_t lock; | 
|  | int w_idx; | 
|  | int hw_ptr; | 
|  | int avail; | 
|  | int capturing; | 
|  | }; | 
|  |  | 
|  | static const struct snd_pcm_hardware go7007_snd_capture_hw = { | 
|  | .info			= (SNDRV_PCM_INFO_MMAP | | 
|  | SNDRV_PCM_INFO_INTERLEAVED | | 
|  | SNDRV_PCM_INFO_BLOCK_TRANSFER | | 
|  | SNDRV_PCM_INFO_MMAP_VALID), | 
|  | .formats		= SNDRV_PCM_FMTBIT_S16_LE, | 
|  | .rates			= SNDRV_PCM_RATE_48000, | 
|  | .rate_min		= 48000, | 
|  | .rate_max		= 48000, | 
|  | .channels_min		= 2, | 
|  | .channels_max		= 2, | 
|  | .buffer_bytes_max	= (128*1024), | 
|  | .period_bytes_min	= 4096, | 
|  | .period_bytes_max	= (128*1024), | 
|  | .periods_min		= 1, | 
|  | .periods_max		= 32, | 
|  | }; | 
|  |  | 
|  | static void parse_audio_stream_data(struct go7007 *go, u8 *buf, int length) | 
|  | { | 
|  | struct go7007_snd *gosnd = go->snd_context; | 
|  | struct snd_pcm_runtime *runtime = gosnd->substream->runtime; | 
|  | int frames = bytes_to_frames(runtime, length); | 
|  | unsigned long flags; | 
|  |  | 
|  | spin_lock_irqsave(&gosnd->lock, flags); | 
|  | gosnd->hw_ptr += frames; | 
|  | if (gosnd->hw_ptr >= runtime->buffer_size) | 
|  | gosnd->hw_ptr -= runtime->buffer_size; | 
|  | gosnd->avail += frames; | 
|  | spin_unlock_irqrestore(&gosnd->lock, flags); | 
|  | if (gosnd->w_idx + length > runtime->dma_bytes) { | 
|  | int cpy = runtime->dma_bytes - gosnd->w_idx; | 
|  |  | 
|  | memcpy(runtime->dma_area + gosnd->w_idx, buf, cpy); | 
|  | length -= cpy; | 
|  | buf += cpy; | 
|  | gosnd->w_idx = 0; | 
|  | } | 
|  | memcpy(runtime->dma_area + gosnd->w_idx, buf, length); | 
|  | gosnd->w_idx += length; | 
|  | spin_lock_irqsave(&gosnd->lock, flags); | 
|  | if (gosnd->avail < runtime->period_size) { | 
|  | spin_unlock_irqrestore(&gosnd->lock, flags); | 
|  | return; | 
|  | } | 
|  | gosnd->avail -= runtime->period_size; | 
|  | spin_unlock_irqrestore(&gosnd->lock, flags); | 
|  | if (gosnd->capturing) | 
|  | snd_pcm_period_elapsed(gosnd->substream); | 
|  | } | 
|  |  | 
|  | static int go7007_snd_hw_params(struct snd_pcm_substream *substream, | 
|  | struct snd_pcm_hw_params *hw_params) | 
|  | { | 
|  | struct go7007 *go = snd_pcm_substream_chip(substream); | 
|  |  | 
|  | go->audio_deliver = parse_audio_stream_data; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int go7007_snd_hw_free(struct snd_pcm_substream *substream) | 
|  | { | 
|  | struct go7007 *go = snd_pcm_substream_chip(substream); | 
|  |  | 
|  | go->audio_deliver = NULL; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int go7007_snd_capture_open(struct snd_pcm_substream *substream) | 
|  | { | 
|  | struct go7007 *go = snd_pcm_substream_chip(substream); | 
|  | struct go7007_snd *gosnd = go->snd_context; | 
|  | unsigned long flags; | 
|  | int r; | 
|  |  | 
|  | spin_lock_irqsave(&gosnd->lock, flags); | 
|  | if (gosnd->substream == NULL) { | 
|  | gosnd->substream = substream; | 
|  | substream->runtime->hw = go7007_snd_capture_hw; | 
|  | r = 0; | 
|  | } else | 
|  | r = -EBUSY; | 
|  | spin_unlock_irqrestore(&gosnd->lock, flags); | 
|  | return r; | 
|  | } | 
|  |  | 
|  | static int go7007_snd_capture_close(struct snd_pcm_substream *substream) | 
|  | { | 
|  | struct go7007 *go = snd_pcm_substream_chip(substream); | 
|  | struct go7007_snd *gosnd = go->snd_context; | 
|  |  | 
|  | gosnd->substream = NULL; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int go7007_snd_pcm_prepare(struct snd_pcm_substream *substream) | 
|  | { | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int go7007_snd_pcm_trigger(struct snd_pcm_substream *substream, int cmd) | 
|  | { | 
|  | struct go7007 *go = snd_pcm_substream_chip(substream); | 
|  | struct go7007_snd *gosnd = go->snd_context; | 
|  |  | 
|  | switch (cmd) { | 
|  | case SNDRV_PCM_TRIGGER_START: | 
|  | /* Just set a flag to indicate we should signal ALSA when | 
|  | * sound comes in */ | 
|  | gosnd->capturing = 1; | 
|  | return 0; | 
|  | case SNDRV_PCM_TRIGGER_STOP: | 
|  | gosnd->hw_ptr = gosnd->w_idx = gosnd->avail = 0; | 
|  | gosnd->capturing = 0; | 
|  | return 0; | 
|  | default: | 
|  | return -EINVAL; | 
|  | } | 
|  | } | 
|  |  | 
|  | static snd_pcm_uframes_t go7007_snd_pcm_pointer(struct snd_pcm_substream *substream) | 
|  | { | 
|  | struct go7007 *go = snd_pcm_substream_chip(substream); | 
|  | struct go7007_snd *gosnd = go->snd_context; | 
|  |  | 
|  | return gosnd->hw_ptr; | 
|  | } | 
|  |  | 
|  | static const struct snd_pcm_ops go7007_snd_capture_ops = { | 
|  | .open		= go7007_snd_capture_open, | 
|  | .close		= go7007_snd_capture_close, | 
|  | .hw_params	= go7007_snd_hw_params, | 
|  | .hw_free	= go7007_snd_hw_free, | 
|  | .prepare	= go7007_snd_pcm_prepare, | 
|  | .trigger	= go7007_snd_pcm_trigger, | 
|  | .pointer	= go7007_snd_pcm_pointer, | 
|  | }; | 
|  |  | 
|  | static int go7007_snd_free(struct snd_device *device) | 
|  | { | 
|  | struct go7007 *go = device->device_data; | 
|  |  | 
|  | kfree(go->snd_context); | 
|  | go->snd_context = NULL; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const struct snd_device_ops go7007_snd_device_ops = { | 
|  | .dev_free	= go7007_snd_free, | 
|  | }; | 
|  |  | 
|  | int go7007_snd_init(struct go7007 *go) | 
|  | { | 
|  | static int dev; | 
|  | struct go7007_snd *gosnd; | 
|  | int ret; | 
|  |  | 
|  | if (dev >= SNDRV_CARDS) | 
|  | return -ENODEV; | 
|  | if (!enable[dev]) { | 
|  | dev++; | 
|  | return -ENOENT; | 
|  | } | 
|  | gosnd = kmalloc(sizeof(struct go7007_snd), GFP_KERNEL); | 
|  | if (gosnd == NULL) | 
|  | return -ENOMEM; | 
|  | spin_lock_init(&gosnd->lock); | 
|  | gosnd->hw_ptr = gosnd->w_idx = gosnd->avail = 0; | 
|  | gosnd->capturing = 0; | 
|  | ret = snd_card_new(go->dev, index[dev], id[dev], THIS_MODULE, 0, | 
|  | &gosnd->card); | 
|  | if (ret < 0) | 
|  | goto free_snd; | 
|  |  | 
|  | ret = snd_device_new(gosnd->card, SNDRV_DEV_LOWLEVEL, go, | 
|  | &go7007_snd_device_ops); | 
|  | if (ret < 0) | 
|  | goto free_card; | 
|  |  | 
|  | ret = snd_pcm_new(gosnd->card, "go7007", 0, 0, 1, &gosnd->pcm); | 
|  | if (ret < 0) | 
|  | goto free_card; | 
|  |  | 
|  | strscpy(gosnd->card->driver, "go7007", sizeof(gosnd->card->driver)); | 
|  | strscpy(gosnd->card->shortname, go->name, sizeof(gosnd->card->shortname)); | 
|  | strscpy(gosnd->card->longname, gosnd->card->shortname, | 
|  | sizeof(gosnd->card->longname)); | 
|  |  | 
|  | gosnd->pcm->private_data = go; | 
|  | snd_pcm_set_ops(gosnd->pcm, SNDRV_PCM_STREAM_CAPTURE, | 
|  | &go7007_snd_capture_ops); | 
|  | snd_pcm_set_managed_buffer_all(gosnd->pcm, SNDRV_DMA_TYPE_VMALLOC, | 
|  | NULL, 0, 0); | 
|  |  | 
|  | ret = snd_card_register(gosnd->card); | 
|  | if (ret < 0) | 
|  | goto free_card; | 
|  |  | 
|  | gosnd->substream = NULL; | 
|  | go->snd_context = gosnd; | 
|  | v4l2_device_get(&go->v4l2_dev); | 
|  | ++dev; | 
|  |  | 
|  | return 0; | 
|  |  | 
|  | free_card: | 
|  | snd_card_free(gosnd->card); | 
|  | free_snd: | 
|  | kfree(gosnd); | 
|  | return ret; | 
|  | } | 
|  | EXPORT_SYMBOL(go7007_snd_init); | 
|  |  | 
|  | int go7007_snd_remove(struct go7007 *go) | 
|  | { | 
|  | struct go7007_snd *gosnd = go->snd_context; | 
|  |  | 
|  | snd_card_disconnect(gosnd->card); | 
|  | snd_card_free_when_closed(gosnd->card); | 
|  | v4l2_device_put(&go->v4l2_dev); | 
|  | return 0; | 
|  | } | 
|  | EXPORT_SYMBOL(go7007_snd_remove); | 
|  |  | 
|  | MODULE_LICENSE("GPL v2"); |