162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (C) 2010-2013 Bluecherry, LLC <https://www.bluecherrydvr.com> 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Original author: 662306a36Sopenharmony_ci * Ben Collins <bcollins@ubuntu.com> 762306a36Sopenharmony_ci * 862306a36Sopenharmony_ci * Additional work by: 962306a36Sopenharmony_ci * John Brooks <john.brooks@bluecherry.net> 1062306a36Sopenharmony_ci */ 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#include <linux/kernel.h> 1362306a36Sopenharmony_ci#include <linux/mempool.h> 1462306a36Sopenharmony_ci#include <linux/poll.h> 1562306a36Sopenharmony_ci#include <linux/kthread.h> 1662306a36Sopenharmony_ci#include <linux/freezer.h> 1762306a36Sopenharmony_ci#include <linux/module.h> 1862306a36Sopenharmony_ci#include <linux/slab.h> 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci#include <sound/core.h> 2162306a36Sopenharmony_ci#include <sound/initval.h> 2262306a36Sopenharmony_ci#include <sound/pcm.h> 2362306a36Sopenharmony_ci#include <sound/control.h> 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci#include "solo6x10.h" 2662306a36Sopenharmony_ci#include "solo6x10-tw28.h" 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci#define G723_FDMA_PAGES 32 2962306a36Sopenharmony_ci#define G723_PERIOD_BYTES 48 3062306a36Sopenharmony_ci#define G723_PERIOD_BLOCK 1024 3162306a36Sopenharmony_ci#define G723_FRAMES_PER_PAGE 48 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci/* Sets up channels 16-19 for decoding and 0-15 for encoding */ 3462306a36Sopenharmony_ci#define OUTMODE_MASK 0x300 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci#define SAMPLERATE 8000 3762306a36Sopenharmony_ci#define BITRATE 25 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci/* The solo writes to 1k byte pages, 32 pages, in the dma. Each 1k page 4062306a36Sopenharmony_ci * is broken down to 20 * 48 byte regions (one for each channel possible) 4162306a36Sopenharmony_ci * with the rest of the page being dummy data. */ 4262306a36Sopenharmony_ci#define PERIODS G723_FDMA_PAGES 4362306a36Sopenharmony_ci#define G723_INTR_ORDER 4 /* 0 - 4 */ 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_cistruct solo_snd_pcm { 4662306a36Sopenharmony_ci int on; 4762306a36Sopenharmony_ci spinlock_t lock; 4862306a36Sopenharmony_ci struct solo_dev *solo_dev; 4962306a36Sopenharmony_ci u8 *g723_buf; 5062306a36Sopenharmony_ci dma_addr_t g723_dma; 5162306a36Sopenharmony_ci}; 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_cistatic void solo_g723_config(struct solo_dev *solo_dev) 5462306a36Sopenharmony_ci{ 5562306a36Sopenharmony_ci int clk_div; 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci clk_div = (solo_dev->clock_mhz * 1000000) 5862306a36Sopenharmony_ci / (SAMPLERATE * (BITRATE * 2) * 2); 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_ci solo_reg_write(solo_dev, SOLO_AUDIO_SAMPLE, 6162306a36Sopenharmony_ci SOLO_AUDIO_BITRATE(BITRATE) 6262306a36Sopenharmony_ci | SOLO_AUDIO_CLK_DIV(clk_div)); 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci solo_reg_write(solo_dev, SOLO_AUDIO_FDMA_INTR, 6562306a36Sopenharmony_ci SOLO_AUDIO_FDMA_INTERVAL(1) 6662306a36Sopenharmony_ci | SOLO_AUDIO_INTR_ORDER(G723_INTR_ORDER) 6762306a36Sopenharmony_ci | SOLO_AUDIO_FDMA_BASE(SOLO_G723_EXT_ADDR(solo_dev) >> 16)); 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci solo_reg_write(solo_dev, SOLO_AUDIO_CONTROL, 7062306a36Sopenharmony_ci SOLO_AUDIO_ENABLE 7162306a36Sopenharmony_ci | SOLO_AUDIO_I2S_MODE 7262306a36Sopenharmony_ci | SOLO_AUDIO_I2S_MULTI(3) 7362306a36Sopenharmony_ci | SOLO_AUDIO_MODE(OUTMODE_MASK)); 7462306a36Sopenharmony_ci} 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_civoid solo_g723_isr(struct solo_dev *solo_dev) 7762306a36Sopenharmony_ci{ 7862306a36Sopenharmony_ci struct snd_pcm_str *pstr = 7962306a36Sopenharmony_ci &solo_dev->snd_pcm->streams[SNDRV_PCM_STREAM_CAPTURE]; 8062306a36Sopenharmony_ci struct snd_pcm_substream *ss; 8162306a36Sopenharmony_ci struct solo_snd_pcm *solo_pcm; 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci for (ss = pstr->substream; ss != NULL; ss = ss->next) { 8462306a36Sopenharmony_ci if (snd_pcm_substream_chip(ss) == NULL) 8562306a36Sopenharmony_ci continue; 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci /* This means open() hasn't been called on this one */ 8862306a36Sopenharmony_ci if (snd_pcm_substream_chip(ss) == solo_dev) 8962306a36Sopenharmony_ci continue; 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci /* Haven't triggered a start yet */ 9262306a36Sopenharmony_ci solo_pcm = snd_pcm_substream_chip(ss); 9362306a36Sopenharmony_ci if (!solo_pcm->on) 9462306a36Sopenharmony_ci continue; 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci snd_pcm_period_elapsed(ss); 9762306a36Sopenharmony_ci } 9862306a36Sopenharmony_ci} 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_cistatic const struct snd_pcm_hardware snd_solo_pcm_hw = { 10162306a36Sopenharmony_ci .info = (SNDRV_PCM_INFO_MMAP | 10262306a36Sopenharmony_ci SNDRV_PCM_INFO_INTERLEAVED | 10362306a36Sopenharmony_ci SNDRV_PCM_INFO_BLOCK_TRANSFER | 10462306a36Sopenharmony_ci SNDRV_PCM_INFO_MMAP_VALID), 10562306a36Sopenharmony_ci .formats = SNDRV_PCM_FMTBIT_U8, 10662306a36Sopenharmony_ci .rates = SNDRV_PCM_RATE_8000, 10762306a36Sopenharmony_ci .rate_min = SAMPLERATE, 10862306a36Sopenharmony_ci .rate_max = SAMPLERATE, 10962306a36Sopenharmony_ci .channels_min = 1, 11062306a36Sopenharmony_ci .channels_max = 1, 11162306a36Sopenharmony_ci .buffer_bytes_max = G723_PERIOD_BYTES * PERIODS, 11262306a36Sopenharmony_ci .period_bytes_min = G723_PERIOD_BYTES, 11362306a36Sopenharmony_ci .period_bytes_max = G723_PERIOD_BYTES, 11462306a36Sopenharmony_ci .periods_min = PERIODS, 11562306a36Sopenharmony_ci .periods_max = PERIODS, 11662306a36Sopenharmony_ci}; 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_cistatic int snd_solo_pcm_open(struct snd_pcm_substream *ss) 11962306a36Sopenharmony_ci{ 12062306a36Sopenharmony_ci struct solo_dev *solo_dev = snd_pcm_substream_chip(ss); 12162306a36Sopenharmony_ci struct solo_snd_pcm *solo_pcm; 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci solo_pcm = kzalloc(sizeof(*solo_pcm), GFP_KERNEL); 12462306a36Sopenharmony_ci if (solo_pcm == NULL) 12562306a36Sopenharmony_ci goto oom; 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci solo_pcm->g723_buf = dma_alloc_coherent(&solo_dev->pdev->dev, 12862306a36Sopenharmony_ci G723_PERIOD_BYTES, 12962306a36Sopenharmony_ci &solo_pcm->g723_dma, 13062306a36Sopenharmony_ci GFP_KERNEL); 13162306a36Sopenharmony_ci if (solo_pcm->g723_buf == NULL) 13262306a36Sopenharmony_ci goto oom; 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci spin_lock_init(&solo_pcm->lock); 13562306a36Sopenharmony_ci solo_pcm->solo_dev = solo_dev; 13662306a36Sopenharmony_ci ss->runtime->hw = snd_solo_pcm_hw; 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci snd_pcm_substream_chip(ss) = solo_pcm; 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci return 0; 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_cioom: 14362306a36Sopenharmony_ci kfree(solo_pcm); 14462306a36Sopenharmony_ci return -ENOMEM; 14562306a36Sopenharmony_ci} 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_cistatic int snd_solo_pcm_close(struct snd_pcm_substream *ss) 14862306a36Sopenharmony_ci{ 14962306a36Sopenharmony_ci struct solo_snd_pcm *solo_pcm = snd_pcm_substream_chip(ss); 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci snd_pcm_substream_chip(ss) = solo_pcm->solo_dev; 15262306a36Sopenharmony_ci dma_free_coherent(&solo_pcm->solo_dev->pdev->dev, G723_PERIOD_BYTES, 15362306a36Sopenharmony_ci solo_pcm->g723_buf, solo_pcm->g723_dma); 15462306a36Sopenharmony_ci kfree(solo_pcm); 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci return 0; 15762306a36Sopenharmony_ci} 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_cistatic int snd_solo_pcm_trigger(struct snd_pcm_substream *ss, int cmd) 16062306a36Sopenharmony_ci{ 16162306a36Sopenharmony_ci struct solo_snd_pcm *solo_pcm = snd_pcm_substream_chip(ss); 16262306a36Sopenharmony_ci struct solo_dev *solo_dev = solo_pcm->solo_dev; 16362306a36Sopenharmony_ci int ret = 0; 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci spin_lock(&solo_pcm->lock); 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci switch (cmd) { 16862306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_START: 16962306a36Sopenharmony_ci if (solo_pcm->on == 0) { 17062306a36Sopenharmony_ci /* If this is the first user, switch on interrupts */ 17162306a36Sopenharmony_ci if (atomic_inc_return(&solo_dev->snd_users) == 1) 17262306a36Sopenharmony_ci solo_irq_on(solo_dev, SOLO_IRQ_G723); 17362306a36Sopenharmony_ci solo_pcm->on = 1; 17462306a36Sopenharmony_ci } 17562306a36Sopenharmony_ci break; 17662306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_STOP: 17762306a36Sopenharmony_ci if (solo_pcm->on) { 17862306a36Sopenharmony_ci /* If this was our last user, switch them off */ 17962306a36Sopenharmony_ci if (atomic_dec_return(&solo_dev->snd_users) == 0) 18062306a36Sopenharmony_ci solo_irq_off(solo_dev, SOLO_IRQ_G723); 18162306a36Sopenharmony_ci solo_pcm->on = 0; 18262306a36Sopenharmony_ci } 18362306a36Sopenharmony_ci break; 18462306a36Sopenharmony_ci default: 18562306a36Sopenharmony_ci ret = -EINVAL; 18662306a36Sopenharmony_ci } 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci spin_unlock(&solo_pcm->lock); 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci return ret; 19162306a36Sopenharmony_ci} 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_cistatic int snd_solo_pcm_prepare(struct snd_pcm_substream *ss) 19462306a36Sopenharmony_ci{ 19562306a36Sopenharmony_ci return 0; 19662306a36Sopenharmony_ci} 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_cistatic snd_pcm_uframes_t snd_solo_pcm_pointer(struct snd_pcm_substream *ss) 19962306a36Sopenharmony_ci{ 20062306a36Sopenharmony_ci struct solo_snd_pcm *solo_pcm = snd_pcm_substream_chip(ss); 20162306a36Sopenharmony_ci struct solo_dev *solo_dev = solo_pcm->solo_dev; 20262306a36Sopenharmony_ci snd_pcm_uframes_t idx = solo_reg_read(solo_dev, SOLO_AUDIO_STA) & 0x1f; 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci return idx * G723_FRAMES_PER_PAGE; 20562306a36Sopenharmony_ci} 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_cistatic int snd_solo_pcm_copy(struct snd_pcm_substream *ss, int channel, 20862306a36Sopenharmony_ci unsigned long pos, struct iov_iter *dst, 20962306a36Sopenharmony_ci unsigned long count) 21062306a36Sopenharmony_ci{ 21162306a36Sopenharmony_ci struct solo_snd_pcm *solo_pcm = snd_pcm_substream_chip(ss); 21262306a36Sopenharmony_ci struct solo_dev *solo_dev = solo_pcm->solo_dev; 21362306a36Sopenharmony_ci int err, i; 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_ci for (i = 0; i < (count / G723_FRAMES_PER_PAGE); i++) { 21662306a36Sopenharmony_ci int page = (pos / G723_FRAMES_PER_PAGE) + i; 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci err = solo_p2m_dma_t(solo_dev, 0, solo_pcm->g723_dma, 21962306a36Sopenharmony_ci SOLO_G723_EXT_ADDR(solo_dev) + 22062306a36Sopenharmony_ci (page * G723_PERIOD_BLOCK) + 22162306a36Sopenharmony_ci (ss->number * G723_PERIOD_BYTES), 22262306a36Sopenharmony_ci G723_PERIOD_BYTES, 0, 0); 22362306a36Sopenharmony_ci if (err) 22462306a36Sopenharmony_ci return err; 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci if (copy_to_iter(solo_pcm->g723_buf, G723_PERIOD_BYTES, dst) != 22762306a36Sopenharmony_ci G723_PERIOD_BYTES) 22862306a36Sopenharmony_ci return -EFAULT; 22962306a36Sopenharmony_ci } 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ci return 0; 23262306a36Sopenharmony_ci} 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_cistatic const struct snd_pcm_ops snd_solo_pcm_ops = { 23562306a36Sopenharmony_ci .open = snd_solo_pcm_open, 23662306a36Sopenharmony_ci .close = snd_solo_pcm_close, 23762306a36Sopenharmony_ci .prepare = snd_solo_pcm_prepare, 23862306a36Sopenharmony_ci .trigger = snd_solo_pcm_trigger, 23962306a36Sopenharmony_ci .pointer = snd_solo_pcm_pointer, 24062306a36Sopenharmony_ci .copy = snd_solo_pcm_copy, 24162306a36Sopenharmony_ci}; 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_cistatic int snd_solo_capture_volume_info(struct snd_kcontrol *kcontrol, 24462306a36Sopenharmony_ci struct snd_ctl_elem_info *info) 24562306a36Sopenharmony_ci{ 24662306a36Sopenharmony_ci info->type = SNDRV_CTL_ELEM_TYPE_INTEGER; 24762306a36Sopenharmony_ci info->count = 1; 24862306a36Sopenharmony_ci info->value.integer.min = 0; 24962306a36Sopenharmony_ci info->value.integer.max = 15; 25062306a36Sopenharmony_ci info->value.integer.step = 1; 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_ci return 0; 25362306a36Sopenharmony_ci} 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_cistatic int snd_solo_capture_volume_get(struct snd_kcontrol *kcontrol, 25662306a36Sopenharmony_ci struct snd_ctl_elem_value *value) 25762306a36Sopenharmony_ci{ 25862306a36Sopenharmony_ci struct solo_dev *solo_dev = snd_kcontrol_chip(kcontrol); 25962306a36Sopenharmony_ci u8 ch = value->id.numid - 1; 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_ci value->value.integer.value[0] = tw28_get_audio_gain(solo_dev, ch); 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_ci return 0; 26462306a36Sopenharmony_ci} 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_cistatic int snd_solo_capture_volume_put(struct snd_kcontrol *kcontrol, 26762306a36Sopenharmony_ci struct snd_ctl_elem_value *value) 26862306a36Sopenharmony_ci{ 26962306a36Sopenharmony_ci struct solo_dev *solo_dev = snd_kcontrol_chip(kcontrol); 27062306a36Sopenharmony_ci u8 ch = value->id.numid - 1; 27162306a36Sopenharmony_ci u8 old_val; 27262306a36Sopenharmony_ci 27362306a36Sopenharmony_ci old_val = tw28_get_audio_gain(solo_dev, ch); 27462306a36Sopenharmony_ci if (old_val == value->value.integer.value[0]) 27562306a36Sopenharmony_ci return 0; 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_ci tw28_set_audio_gain(solo_dev, ch, value->value.integer.value[0]); 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_ci return 1; 28062306a36Sopenharmony_ci} 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_cistatic const struct snd_kcontrol_new snd_solo_capture_volume = { 28362306a36Sopenharmony_ci .iface = SNDRV_CTL_ELEM_IFACE_MIXER, 28462306a36Sopenharmony_ci .name = "Capture Volume", 28562306a36Sopenharmony_ci .info = snd_solo_capture_volume_info, 28662306a36Sopenharmony_ci .get = snd_solo_capture_volume_get, 28762306a36Sopenharmony_ci .put = snd_solo_capture_volume_put, 28862306a36Sopenharmony_ci}; 28962306a36Sopenharmony_ci 29062306a36Sopenharmony_cistatic int solo_snd_pcm_init(struct solo_dev *solo_dev) 29162306a36Sopenharmony_ci{ 29262306a36Sopenharmony_ci struct snd_card *card = solo_dev->snd_card; 29362306a36Sopenharmony_ci struct snd_pcm *pcm; 29462306a36Sopenharmony_ci struct snd_pcm_substream *ss; 29562306a36Sopenharmony_ci int ret; 29662306a36Sopenharmony_ci int i; 29762306a36Sopenharmony_ci 29862306a36Sopenharmony_ci ret = snd_pcm_new(card, card->driver, 0, 0, solo_dev->nr_chans, 29962306a36Sopenharmony_ci &pcm); 30062306a36Sopenharmony_ci if (ret < 0) 30162306a36Sopenharmony_ci return ret; 30262306a36Sopenharmony_ci 30362306a36Sopenharmony_ci snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, 30462306a36Sopenharmony_ci &snd_solo_pcm_ops); 30562306a36Sopenharmony_ci 30662306a36Sopenharmony_ci snd_pcm_chip(pcm) = solo_dev; 30762306a36Sopenharmony_ci pcm->info_flags = 0; 30862306a36Sopenharmony_ci strscpy(pcm->name, card->shortname, sizeof(pcm->name)); 30962306a36Sopenharmony_ci 31062306a36Sopenharmony_ci for (i = 0, ss = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; 31162306a36Sopenharmony_ci ss; ss = ss->next, i++) 31262306a36Sopenharmony_ci sprintf(ss->name, "Camera #%d Audio", i); 31362306a36Sopenharmony_ci 31462306a36Sopenharmony_ci snd_pcm_set_managed_buffer_all(pcm, 31562306a36Sopenharmony_ci SNDRV_DMA_TYPE_CONTINUOUS, 31662306a36Sopenharmony_ci NULL, 31762306a36Sopenharmony_ci G723_PERIOD_BYTES * PERIODS, 31862306a36Sopenharmony_ci G723_PERIOD_BYTES * PERIODS); 31962306a36Sopenharmony_ci 32062306a36Sopenharmony_ci solo_dev->snd_pcm = pcm; 32162306a36Sopenharmony_ci 32262306a36Sopenharmony_ci return 0; 32362306a36Sopenharmony_ci} 32462306a36Sopenharmony_ci 32562306a36Sopenharmony_ciint solo_g723_init(struct solo_dev *solo_dev) 32662306a36Sopenharmony_ci{ 32762306a36Sopenharmony_ci static struct snd_device_ops ops = { }; 32862306a36Sopenharmony_ci struct snd_card *card; 32962306a36Sopenharmony_ci struct snd_kcontrol_new kctl; 33062306a36Sopenharmony_ci char name[32]; 33162306a36Sopenharmony_ci int ret; 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_ci atomic_set(&solo_dev->snd_users, 0); 33462306a36Sopenharmony_ci 33562306a36Sopenharmony_ci /* Allows for easier mapping between video and audio */ 33662306a36Sopenharmony_ci sprintf(name, "Softlogic%d", solo_dev->vfd->num); 33762306a36Sopenharmony_ci 33862306a36Sopenharmony_ci ret = snd_card_new(&solo_dev->pdev->dev, 33962306a36Sopenharmony_ci SNDRV_DEFAULT_IDX1, name, THIS_MODULE, 0, 34062306a36Sopenharmony_ci &solo_dev->snd_card); 34162306a36Sopenharmony_ci if (ret < 0) 34262306a36Sopenharmony_ci return ret; 34362306a36Sopenharmony_ci 34462306a36Sopenharmony_ci card = solo_dev->snd_card; 34562306a36Sopenharmony_ci 34662306a36Sopenharmony_ci strscpy(card->driver, SOLO6X10_NAME, sizeof(card->driver)); 34762306a36Sopenharmony_ci strscpy(card->shortname, "SOLO-6x10 Audio", sizeof(card->shortname)); 34862306a36Sopenharmony_ci sprintf(card->longname, "%s on %s IRQ %d", card->shortname, 34962306a36Sopenharmony_ci pci_name(solo_dev->pdev), solo_dev->pdev->irq); 35062306a36Sopenharmony_ci 35162306a36Sopenharmony_ci ret = snd_device_new(card, SNDRV_DEV_LOWLEVEL, solo_dev, &ops); 35262306a36Sopenharmony_ci if (ret < 0) 35362306a36Sopenharmony_ci goto snd_error; 35462306a36Sopenharmony_ci 35562306a36Sopenharmony_ci /* Mixer controls */ 35662306a36Sopenharmony_ci strscpy(card->mixername, "SOLO-6x10", sizeof(card->mixername)); 35762306a36Sopenharmony_ci kctl = snd_solo_capture_volume; 35862306a36Sopenharmony_ci kctl.count = solo_dev->nr_chans; 35962306a36Sopenharmony_ci 36062306a36Sopenharmony_ci ret = snd_ctl_add(card, snd_ctl_new1(&kctl, solo_dev)); 36162306a36Sopenharmony_ci if (ret < 0) 36262306a36Sopenharmony_ci goto snd_error; 36362306a36Sopenharmony_ci 36462306a36Sopenharmony_ci ret = solo_snd_pcm_init(solo_dev); 36562306a36Sopenharmony_ci if (ret < 0) 36662306a36Sopenharmony_ci goto snd_error; 36762306a36Sopenharmony_ci 36862306a36Sopenharmony_ci ret = snd_card_register(card); 36962306a36Sopenharmony_ci if (ret < 0) 37062306a36Sopenharmony_ci goto snd_error; 37162306a36Sopenharmony_ci 37262306a36Sopenharmony_ci solo_g723_config(solo_dev); 37362306a36Sopenharmony_ci 37462306a36Sopenharmony_ci dev_info(&solo_dev->pdev->dev, "Alsa sound card as %s\n", name); 37562306a36Sopenharmony_ci 37662306a36Sopenharmony_ci return 0; 37762306a36Sopenharmony_ci 37862306a36Sopenharmony_cisnd_error: 37962306a36Sopenharmony_ci snd_card_free(card); 38062306a36Sopenharmony_ci return ret; 38162306a36Sopenharmony_ci} 38262306a36Sopenharmony_ci 38362306a36Sopenharmony_civoid solo_g723_exit(struct solo_dev *solo_dev) 38462306a36Sopenharmony_ci{ 38562306a36Sopenharmony_ci if (!solo_dev->snd_card) 38662306a36Sopenharmony_ci return; 38762306a36Sopenharmony_ci 38862306a36Sopenharmony_ci solo_reg_write(solo_dev, SOLO_AUDIO_CONTROL, 0); 38962306a36Sopenharmony_ci solo_irq_off(solo_dev, SOLO_IRQ_G723); 39062306a36Sopenharmony_ci 39162306a36Sopenharmony_ci snd_card_free(solo_dev->snd_card); 39262306a36Sopenharmony_ci solo_dev->snd_card = NULL; 39362306a36Sopenharmony_ci} 394