812 lines
20 KiB
C
812 lines
20 KiB
C
/*
|
|
* Core routines for STMicroelectronics' SoCs audio drivers
|
|
*
|
|
* Copyright (c) 2005-2011 STMicroelectronics Limited
|
|
*
|
|
* Author: Pawel Moll <pawel.moll@st.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
*/
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/io.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/bpa2.h>
|
|
#include <linux/stm/stm-dma.h>
|
|
#include <sound/core.h>
|
|
#include <sound/pcm.h>
|
|
#include <sound/info.h>
|
|
#include <sound/pcm_params.h>
|
|
#include <sound/asoundef.h>
|
|
|
|
#include "common.h"
|
|
|
|
|
|
|
|
int snd_stm_debug_level;
|
|
module_param_named(debug, snd_stm_debug_level, int, S_IRUGO | S_IWUSR);
|
|
|
|
static int snd_stm_card_index = -1; /* First available index */
|
|
module_param_named(index, snd_stm_card_index, int, 0444);
|
|
MODULE_PARM_DESC(index, "Index value for STMicroelectronics audio subsystem "
|
|
"card.");
|
|
|
|
static char *snd_stm_card_id;
|
|
module_param_named(id, snd_stm_card_id, charp, 0444);
|
|
MODULE_PARM_DESC(id, "ID string for STMicroelectronics audio subsystem card.");
|
|
|
|
|
|
|
|
/*
|
|
* ALSA card management
|
|
*/
|
|
|
|
static struct snd_card *snd_stm_card;
|
|
static int snd_stm_card_registered;
|
|
|
|
int snd_stm_card_register(void)
|
|
{
|
|
int result;
|
|
const char *soc_type = get_cpu_subtype(cpu_data);
|
|
|
|
BUG_ON(!soc_type);
|
|
BUG_ON(!snd_stm_card);
|
|
BUG_ON(snd_stm_card_registered);
|
|
|
|
if (!snd_stm_card_id)
|
|
strlcpy(snd_stm_card->id, soc_type, sizeof(snd_stm_card->id));
|
|
|
|
strlcpy(snd_stm_card->driver, soc_type, sizeof(snd_stm_card->driver));
|
|
snprintf(snd_stm_card->shortname, sizeof(snd_stm_card->shortname),
|
|
"%s audio subsystem", soc_type);
|
|
if (cpu_data->cut_minor < 0)
|
|
snprintf(snd_stm_card->longname, sizeof(snd_stm_card->longname),
|
|
"STMicroelectronics %s cut %d.x SOC audio "
|
|
"subsystem", soc_type, cpu_data->cut_major);
|
|
else
|
|
snprintf(snd_stm_card->longname, sizeof(snd_stm_card->longname),
|
|
"STMicroelectronics %s cut %d.%d SOC audio "
|
|
"subsystem", soc_type, cpu_data->cut_major,
|
|
cpu_data->cut_minor);
|
|
|
|
result = snd_card_register(snd_stm_card);
|
|
|
|
if (result == 0)
|
|
snd_stm_card_registered = 1;
|
|
|
|
return result;
|
|
}
|
|
EXPORT_SYMBOL(snd_stm_card_register);
|
|
|
|
int snd_stm_card_is_registered(void)
|
|
{
|
|
BUG_ON(!snd_stm_card);
|
|
|
|
return snd_stm_card_registered;
|
|
}
|
|
EXPORT_SYMBOL(snd_stm_card_is_registered);
|
|
|
|
struct snd_card *snd_stm_card_get(void)
|
|
{
|
|
BUG_ON(!snd_stm_card);
|
|
|
|
return snd_stm_card;
|
|
}
|
|
EXPORT_SYMBOL(snd_stm_card_get);
|
|
|
|
|
|
/*
|
|
* Resources management
|
|
*/
|
|
|
|
int snd_stm_memory_request(struct platform_device *pdev,
|
|
struct resource **mem_region, void **base_address)
|
|
{
|
|
struct resource *resource;
|
|
|
|
resource = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
if (!resource) {
|
|
snd_stm_printe("Failed to"
|
|
" platform_get_resource(IORESOURCE_MEM)!\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
*mem_region = request_mem_region(resource->start,
|
|
resource->end - resource->start + 1, pdev->name);
|
|
if (!*mem_region) {
|
|
snd_stm_printe("Failed request_mem_region(0x%08x,"
|
|
" 0x%08x, '%s')!\n", resource->start,
|
|
resource->end - resource->start + 1,
|
|
pdev->name);
|
|
return -EBUSY;
|
|
}
|
|
snd_stm_printd(0, "Memory region: 0x%08x-0x%08x\n",
|
|
(*mem_region)->start, (*mem_region)->end);
|
|
|
|
*base_address = ioremap(resource->start,
|
|
resource->end - resource->start + 1);
|
|
if (!*base_address) {
|
|
release_resource(*mem_region);
|
|
snd_stm_printe("Failed ioremap!\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
snd_stm_printd(0, "Base address is 0x%p.\n", *base_address);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(snd_stm_memory_request);
|
|
|
|
void snd_stm_memory_release(struct resource *mem_region,
|
|
void *base_address)
|
|
{
|
|
iounmap(base_address);
|
|
release_resource(mem_region);
|
|
}
|
|
EXPORT_SYMBOL(snd_stm_memory_release);
|
|
|
|
int snd_stm_irq_request(struct platform_device *pdev,
|
|
unsigned int *irq, irq_handler_t handler, void *dev_id)
|
|
{
|
|
struct resource *resource;
|
|
int result;
|
|
|
|
resource = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
|
|
if (!resource) {
|
|
snd_stm_printe("Failed to "
|
|
"platform_get_resource(IORESOURCE_IRQ)!\n");
|
|
return -ENODEV;
|
|
}
|
|
snd_stm_printd(0, "IRQ: %u\n", resource->start);
|
|
|
|
*irq = resource->start;
|
|
|
|
result = request_irq(*irq, handler, IRQF_DISABLED, pdev->name, dev_id);
|
|
if (result != 0) {
|
|
snd_stm_printe("Failed request_irq!\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* request_irq() enables the interrupt immediately; as it is
|
|
* lethal in concurrent audio environment, we want to have
|
|
* it disabled for most of the time... */
|
|
disable_irq(*irq);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int snd_stm_fdma_request(struct platform_device *pdev, int *channel)
|
|
{
|
|
static const char *fdmac_id[] = { STM_DMAC_ID, NULL };
|
|
static const char *fdma_cap_lb[] = { STM_DMA_CAP_LOW_BW, NULL };
|
|
static const char *fdma_cap_hb[] = { STM_DMA_CAP_HIGH_BW, NULL };
|
|
|
|
*channel = request_dma_bycap(fdmac_id, fdma_cap_lb, pdev->name);
|
|
if (*channel < 0) {
|
|
*channel = request_dma_bycap(fdmac_id, fdma_cap_hb, pdev->name);
|
|
if (*channel < 0) {
|
|
snd_stm_printe("Failed to request_dma_bycap()==%d!\n",
|
|
*channel);
|
|
return -ENODEV;
|
|
}
|
|
}
|
|
snd_stm_printd(0, "FDMA channel: %d\n", *channel);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* ALSA procfs additional entries
|
|
*/
|
|
|
|
static struct snd_info_entry *snd_stm_info_root;
|
|
|
|
int snd_stm_info_create(void)
|
|
{
|
|
int result = 0;
|
|
|
|
snd_stm_info_root = snd_info_create_module_entry(THIS_MODULE,
|
|
"stm", NULL);
|
|
if (snd_stm_info_root) {
|
|
snd_stm_info_root->mode = S_IFDIR | S_IRUGO | S_IXUGO;
|
|
if (snd_info_register(snd_stm_info_root) < 0) {
|
|
result = -EINVAL;
|
|
snd_info_free_entry(snd_stm_info_root);
|
|
}
|
|
} else {
|
|
result = -ENOMEM;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void snd_stm_info_dispose(void)
|
|
{
|
|
if (snd_stm_info_root)
|
|
snd_info_free_entry(snd_stm_info_root);
|
|
}
|
|
|
|
int snd_stm_info_register(struct snd_info_entry **entry,
|
|
const char *name,
|
|
void (read)(struct snd_info_entry *, struct snd_info_buffer *),
|
|
void *private_data)
|
|
{
|
|
int result = 0;
|
|
|
|
/* Skip the "snd_" prefix, if bus_id has been simply given */
|
|
if (strncmp(name, "snd_", 4) == 0)
|
|
name += 4;
|
|
|
|
*entry = snd_info_create_module_entry(THIS_MODULE, name,
|
|
snd_stm_info_root);
|
|
if (*entry) {
|
|
(*entry)->c.text.read = read;
|
|
(*entry)->private_data = private_data;
|
|
if (snd_info_register(*entry) < 0) {
|
|
result = -EINVAL;
|
|
snd_info_free_entry(*entry);
|
|
}
|
|
} else {
|
|
result = -EINVAL;
|
|
}
|
|
return result;
|
|
}
|
|
EXPORT_SYMBOL(snd_stm_info_register);
|
|
|
|
void snd_stm_info_unregister(struct snd_info_entry *entry)
|
|
{
|
|
if (entry)
|
|
snd_info_free_entry(entry);
|
|
}
|
|
EXPORT_SYMBOL(snd_stm_info_unregister);
|
|
|
|
|
|
|
|
/*
|
|
* PCM buffer memory management
|
|
*/
|
|
|
|
struct snd_stm_buffer {
|
|
struct snd_pcm *pcm;
|
|
|
|
struct bpa2_part *bpa2_part;
|
|
|
|
int allocated;
|
|
struct snd_pcm_substream *substream;
|
|
|
|
snd_stm_magic_field;
|
|
};
|
|
|
|
#if defined(CONFIG_BPA2)
|
|
static char *bpa2_part = CONFIG_SND_STM_BPA2_PARTITION_NAME;
|
|
#else
|
|
static char *bpa2_part = "";
|
|
#endif
|
|
module_param(bpa2_part, charp, S_IRUGO);
|
|
|
|
struct snd_stm_buffer *snd_stm_buffer_create(struct snd_pcm *pcm,
|
|
struct device *device, int prealloc_size)
|
|
{
|
|
struct snd_stm_buffer *buffer;
|
|
|
|
snd_stm_printd(1, "snd_stm_buffer_init(pcm=%p, prealloc_size=%d)\n",
|
|
pcm, prealloc_size);
|
|
|
|
BUG_ON(!pcm);
|
|
|
|
buffer = kzalloc(sizeof(*buffer), GFP_KERNEL);
|
|
if (!buffer) {
|
|
snd_stm_printe("Can't allocate memory for a buffer "
|
|
"description!\n");
|
|
return NULL;
|
|
}
|
|
snd_stm_magic_set(buffer);
|
|
buffer->pcm = pcm;
|
|
|
|
#if defined(CONFIG_BPA2)
|
|
buffer->bpa2_part = bpa2_find_part(bpa2_part);
|
|
if (buffer->bpa2_part) {
|
|
snd_stm_printd(0, "Using BPA2 partition '%s'...\n", bpa2_part);
|
|
return buffer;
|
|
}
|
|
|
|
buffer->bpa2_part = bpa2_find_part("bigphysarea");
|
|
if (buffer->bpa2_part) {
|
|
snd_stm_printd(0, "Using legacy 'bigphysarea' BPA2 "
|
|
"partition...\n");
|
|
return buffer;
|
|
}
|
|
#endif
|
|
|
|
if (snd_pcm_lib_preallocate_pages_for_all(pcm,
|
|
SNDRV_DMA_TYPE_DEV, device,
|
|
prealloc_size, prealloc_size) == 0) {
|
|
snd_stm_printd(0, "Using pcm_lib's preallocated buffer "
|
|
"(%d bytes)...\n", prealloc_size);
|
|
return buffer;
|
|
}
|
|
|
|
snd_stm_printe("Can't provide any memory for buffers!\n");
|
|
kfree(buffer);
|
|
return NULL;
|
|
}
|
|
|
|
void snd_stm_buffer_dispose(struct snd_stm_buffer *buffer)
|
|
{
|
|
snd_stm_printd(1, "snd_stm_buffer_dispose(buffer=%p)\n", buffer);
|
|
|
|
BUG_ON(!buffer);
|
|
BUG_ON(!snd_stm_magic_valid(buffer));
|
|
BUG_ON(buffer->allocated);
|
|
|
|
/* snd_pcm_lib__preallocate*-ed buffer is freed automagically */
|
|
|
|
snd_stm_magic_clear(buffer);
|
|
kfree(buffer);
|
|
}
|
|
|
|
int snd_stm_buffer_is_allocated(struct snd_stm_buffer *buffer)
|
|
{
|
|
snd_stm_printd(1, "snd_stm_buffer_is_allocated(buffer=%p)\n",
|
|
buffer);
|
|
|
|
BUG_ON(!buffer);
|
|
BUG_ON(!snd_stm_magic_valid(buffer));
|
|
|
|
return buffer->allocated;
|
|
}
|
|
|
|
int snd_stm_buffer_alloc(struct snd_stm_buffer *buffer,
|
|
struct snd_pcm_substream *substream, int size)
|
|
{
|
|
snd_stm_printd(1, "snd_stm_buffer_alloc(buffer=%p, substream=%p, "
|
|
"size=%d)\n", buffer, substream, size);
|
|
|
|
BUG_ON(!buffer);
|
|
BUG_ON(!snd_stm_magic_valid(buffer));
|
|
BUG_ON(buffer->allocated);
|
|
BUG_ON(size <= 0);
|
|
|
|
if (buffer->bpa2_part) {
|
|
#if defined(CONFIG_BPA2)
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
int pages = (size + PAGE_SIZE - 1) / PAGE_SIZE;
|
|
|
|
runtime->dma_addr = bpa2_alloc_pages(buffer->bpa2_part, pages,
|
|
0, GFP_KERNEL);
|
|
if (runtime->dma_addr == 0) {
|
|
snd_stm_printe("Can't get %d pages from BPA2!\n",
|
|
pages);
|
|
return -ENOMEM;
|
|
}
|
|
runtime->dma_bytes = size;
|
|
runtime->dma_area = ioremap_nocache(runtime->dma_addr, size);
|
|
#else
|
|
snd_BUG();
|
|
#endif
|
|
} else {
|
|
if (snd_pcm_lib_malloc_pages(substream, size) < 0) {
|
|
snd_stm_printe("Can't allocate pages using pcm_lib!\n");
|
|
return -ENOMEM;
|
|
}
|
|
/* runtime->dma_* are set by snd_pcm_lib_malloc_pages()
|
|
* (by snd_pcm_set_runtime_buffer() to be more specific.) */
|
|
}
|
|
|
|
snd_stm_printd(1, "Allocated memory: dma_addr=0x%08x, dma_area=0x%p, "
|
|
"dma_bytes=%u\n", substream->runtime->dma_addr,
|
|
substream->runtime->dma_area,
|
|
substream->runtime->dma_bytes);
|
|
|
|
buffer->substream = substream;
|
|
buffer->allocated = 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void snd_stm_buffer_free(struct snd_stm_buffer *buffer)
|
|
{
|
|
struct snd_pcm_runtime *runtime;
|
|
|
|
snd_stm_printd(1, "snd_stm_buffer_free(buffer=%p)\n", buffer);
|
|
|
|
BUG_ON(!buffer);
|
|
BUG_ON(!snd_stm_magic_valid(buffer));
|
|
BUG_ON(!buffer->allocated);
|
|
|
|
runtime = buffer->substream->runtime;
|
|
|
|
snd_stm_printd(1, "Freeing dma_addr=0x%08x, dma_area=0x%p, "
|
|
"dma_bytes=%u\n", runtime->dma_addr,
|
|
runtime->dma_area, runtime->dma_bytes);
|
|
|
|
if (buffer->bpa2_part) {
|
|
#if defined(CONFIG_BPA2)
|
|
iounmap(runtime->dma_area);
|
|
|
|
bpa2_free_pages(buffer->bpa2_part, runtime->dma_addr);
|
|
runtime->dma_area = NULL;
|
|
runtime->dma_addr = 0;
|
|
runtime->dma_bytes = 0;
|
|
#else
|
|
snd_BUG();
|
|
#endif
|
|
} else {
|
|
snd_pcm_lib_free_pages(buffer->substream);
|
|
/* runtime->dma_* are cleared by snd_pcm_lib_free_pages()
|
|
* (by snd_pcm_set_runtime_buffer() to be more specific.) */
|
|
}
|
|
|
|
buffer->allocated = 0;
|
|
buffer->substream = NULL;
|
|
}
|
|
|
|
static int snd_stm_buffer_mmap_fault(struct vm_area_struct *area,
|
|
struct vm_fault *vmf)
|
|
{
|
|
/* No VMA expanding here! */
|
|
return VM_FAULT_SIGBUS;
|
|
}
|
|
|
|
static struct vm_operations_struct snd_stm_buffer_mmap_vm_ops = {
|
|
.open = snd_pcm_mmap_data_open,
|
|
.close = snd_pcm_mmap_data_close,
|
|
.fault = snd_stm_buffer_mmap_fault,
|
|
};
|
|
|
|
int snd_stm_buffer_mmap(struct snd_pcm_substream *substream,
|
|
struct vm_area_struct *area)
|
|
{
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
unsigned long map_offset = area->vm_pgoff << PAGE_SHIFT;
|
|
unsigned long phys_addr = runtime->dma_addr + map_offset;
|
|
unsigned long map_size = area->vm_end - area->vm_start;
|
|
unsigned long phys_size = runtime->dma_bytes + PAGE_SIZE -
|
|
runtime->dma_bytes % PAGE_SIZE;
|
|
|
|
snd_stm_printd(1, "snd_stm_buffer_mmap(substream=%p, area=%p)\n",
|
|
substream, area);
|
|
|
|
snd_stm_printd(1, "Mmaping %lu bytes starting from 0x%08lx "
|
|
"(dma_addr=0x%08x, dma_size=%u, vm_pgoff=%lu, "
|
|
"vm_start=0x%lx, vm_end=0x%lx)...\n", map_size,
|
|
phys_addr, runtime->dma_addr, runtime->dma_bytes,
|
|
area->vm_pgoff, area->vm_start, area->vm_end);
|
|
|
|
if (map_size > phys_size) {
|
|
snd_stm_printe("Trying to perform mmap larger than buffer!\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
area->vm_ops = &snd_stm_buffer_mmap_vm_ops;
|
|
area->vm_private_data = substream;
|
|
area->vm_flags |= VM_RESERVED;
|
|
area->vm_page_prot = pgprot_noncached(area->vm_page_prot);
|
|
|
|
if (remap_pfn_range(area, area->vm_start, phys_addr >> PAGE_SHIFT,
|
|
map_size, area->vm_page_prot) != 0) {
|
|
snd_stm_printe("Can't remap buffer!\n");
|
|
return -EAGAIN;
|
|
}
|
|
|
|
/* Must be called implicitly here... */
|
|
snd_pcm_mmap_data_open(area);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Common ALSA parameters constraints
|
|
*/
|
|
|
|
/*
|
|
#define FIXED_TRANSFER_BYTES max_transfer_bytes > 16 ? 16 : max_transfer_bytes
|
|
#define FIXED_TRANSFER_BYTES max_transfer_bytes
|
|
*/
|
|
|
|
#if defined(FIXED_TRANSFER_BYTES)
|
|
|
|
int snd_stm_pcm_transfer_bytes(unsigned int bytes_per_frame,
|
|
unsigned int max_transfer_bytes)
|
|
{
|
|
int transfer_bytes = FIXED_TRANSFER_BYTES;
|
|
|
|
snd_stm_printd(1, "snd_stm_pcm_transfer_bytes(bytes_per_frame=%u, "
|
|
"max_transfer_bytes=%u) = %u (FIXED)\n",
|
|
bytes_per_frame, max_transfer_bytes, transfer_bytes);
|
|
|
|
return transfer_bytes;
|
|
}
|
|
|
|
int snd_stm_pcm_hw_constraint_transfer_bytes(struct snd_pcm_runtime *runtime,
|
|
unsigned int max_transfer_bytes)
|
|
{
|
|
return snd_pcm_hw_constraint_step(runtime, 0,
|
|
SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
|
|
snd_stm_pcm_transfer_bytes(0, max_transfer_bytes));
|
|
}
|
|
|
|
#else
|
|
|
|
int snd_stm_pcm_transfer_bytes(unsigned int bytes_per_frame,
|
|
unsigned int max_transfer_bytes)
|
|
{
|
|
unsigned int transfer_bytes;
|
|
|
|
for (transfer_bytes = bytes_per_frame;
|
|
transfer_bytes * 2 < max_transfer_bytes;
|
|
transfer_bytes *= 2)
|
|
;
|
|
|
|
snd_stm_printd(2, "snd_stm_pcm_transfer_bytes(bytes_per_frame=%u, "
|
|
"max_transfer_bytes=%u) = %u\n", bytes_per_frame,
|
|
max_transfer_bytes, transfer_bytes);
|
|
|
|
return transfer_bytes;
|
|
}
|
|
|
|
static int snd_stm_pcm_hw_rule_transfer_bytes(struct snd_pcm_hw_params *params,
|
|
struct snd_pcm_hw_rule *rule)
|
|
{
|
|
int changed = 0;
|
|
unsigned int max_transfer_bytes = (unsigned int)rule->private;
|
|
struct snd_interval *period_bytes = hw_param_interval(params,
|
|
SNDRV_PCM_HW_PARAM_PERIOD_BYTES);
|
|
struct snd_interval *frame_bits = hw_param_interval(params,
|
|
SNDRV_PCM_HW_PARAM_FRAME_BITS);
|
|
unsigned int transfer_bytes, n;
|
|
|
|
transfer_bytes = snd_stm_pcm_transfer_bytes(frame_bits->min / 8,
|
|
max_transfer_bytes);
|
|
n = period_bytes->min % transfer_bytes;
|
|
if (n != 0 || period_bytes->openmin) {
|
|
period_bytes->min += transfer_bytes - n;
|
|
changed = 1;
|
|
}
|
|
|
|
transfer_bytes = snd_stm_pcm_transfer_bytes(frame_bits->max / 8,
|
|
max_transfer_bytes);
|
|
n = period_bytes->max % transfer_bytes;
|
|
if (n != 0 || period_bytes->openmax) {
|
|
period_bytes->max -= n;
|
|
changed = 1;
|
|
}
|
|
|
|
if (snd_interval_checkempty(period_bytes)) {
|
|
period_bytes->empty = 1;
|
|
return -EINVAL;
|
|
}
|
|
|
|
return changed;
|
|
}
|
|
|
|
int snd_stm_pcm_hw_constraint_transfer_bytes(struct snd_pcm_runtime *runtime,
|
|
unsigned int max_transfer_bytes)
|
|
{
|
|
return snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
|
|
snd_stm_pcm_hw_rule_transfer_bytes,
|
|
(void *)max_transfer_bytes,
|
|
SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
|
|
SNDRV_PCM_HW_PARAM_FRAME_BITS, -1);
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/*
|
|
* Common ALSA controls routines
|
|
*/
|
|
|
|
int snd_stm_ctl_boolean_info(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_info *uinfo)
|
|
{
|
|
uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
|
|
uinfo->count = 1;
|
|
uinfo->value.integer.min = 0;
|
|
uinfo->value.integer.max = 1;
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(snd_stm_ctl_boolean_info);
|
|
|
|
int snd_stm_ctl_iec958_info(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_info *uinfo)
|
|
{
|
|
uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
|
|
uinfo->count = 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int snd_stm_ctl_iec958_mask_get_con(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
ucontrol->value.iec958.status[0] = IEC958_AES0_PROFESSIONAL |
|
|
IEC958_AES0_NONAUDIO |
|
|
IEC958_AES0_CON_NOT_COPYRIGHT |
|
|
IEC958_AES0_CON_EMPHASIS |
|
|
IEC958_AES0_CON_MODE;
|
|
ucontrol->value.iec958.status[1] = IEC958_AES1_CON_CATEGORY |
|
|
IEC958_AES1_CON_ORIGINAL;
|
|
ucontrol->value.iec958.status[2] = IEC958_AES2_CON_SOURCE |
|
|
IEC958_AES2_CON_CHANNEL;
|
|
ucontrol->value.iec958.status[3] = IEC958_AES3_CON_FS |
|
|
IEC958_AES3_CON_CLOCK;
|
|
ucontrol->value.iec958.status[4] = IEC958_AES4_CON_MAX_WORDLEN_24 |
|
|
IEC958_AES4_CON_WORDLEN;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int snd_stm_ctl_iec958_mask_get_pro(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
ucontrol->value.iec958.status[0] = IEC958_AES0_PROFESSIONAL |
|
|
IEC958_AES0_NONAUDIO |
|
|
IEC958_AES0_PRO_EMPHASIS |
|
|
IEC958_AES0_PRO_FREQ_UNLOCKED |
|
|
IEC958_AES0_PRO_FS;
|
|
ucontrol->value.iec958.status[1] = IEC958_AES1_PRO_MODE |
|
|
IEC958_AES1_PRO_USERBITS;
|
|
ucontrol->value.iec958.status[2] = IEC958_AES2_PRO_SBITS |
|
|
IEC958_AES2_PRO_WORDLEN;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int snd_stm_iec958_cmp(const struct snd_aes_iec958 *a,
|
|
const struct snd_aes_iec958 *b)
|
|
{
|
|
int result;
|
|
|
|
BUG_ON(!a);
|
|
BUG_ON(!b);
|
|
|
|
result = memcmp(a->status, b->status, sizeof(a->status));
|
|
if (result == 0)
|
|
result = memcmp(a->subcode, b->subcode, sizeof(a->subcode));
|
|
if (result == 0)
|
|
result = memcmp(a->dig_subframe, b->dig_subframe,
|
|
sizeof(a->dig_subframe));
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/*
|
|
* Debug features
|
|
*/
|
|
|
|
/* Memory dump function */
|
|
|
|
void snd_stm_hex_dump(void *data, int size)
|
|
{
|
|
unsigned char *buffer = data;
|
|
char line[57];
|
|
int i;
|
|
|
|
for (i = 0; i < size; i++) {
|
|
if (i % 16 == 0)
|
|
sprintf(line, "%p", data + i);
|
|
sprintf(line + 8 + ((i % 16) * 3), " %02x", *buffer++);
|
|
if (i % 16 == 15 || i == size - 1)
|
|
printk(KERN_DEBUG "%s\n", line);
|
|
}
|
|
}
|
|
|
|
/* IEC958 structure dump */
|
|
void snd_stm_iec958_dump(const struct snd_aes_iec958 *vuc)
|
|
{
|
|
int i;
|
|
char line[54];
|
|
const unsigned char *data;
|
|
|
|
printk(KERN_DEBUG " "
|
|
"0 1 2 3 4 5 6 7 8 9\n");
|
|
data = vuc->status;
|
|
for (i = 0; i < 24; i++) {
|
|
if (i % 10 == 0)
|
|
sprintf(line, "%p status %02d:",
|
|
(unsigned char *)vuc + i, i);
|
|
sprintf(line + 22 + ((i % 10) * 3), " %02x", *data++);
|
|
if (i % 10 == 9 || i == 23)
|
|
printk(KERN_DEBUG "%s\n", line);
|
|
}
|
|
|
|
data = vuc->subcode;
|
|
for (i = 0; i < 147; i++) {
|
|
if (i % 10 == 0)
|
|
sprintf(line, "%p subcode %03d:",
|
|
(unsigned char *)vuc +
|
|
offsetof(struct snd_aes_iec958,
|
|
dig_subframe) + i, i);
|
|
sprintf(line + 22 + ((i % 10) * 3), " %02x", *data++);
|
|
if (i % 10 == 9 || i == 146)
|
|
printk(KERN_DEBUG "%s\n", line);
|
|
}
|
|
|
|
printk(KERN_DEBUG "%p dig_subframe: %02x %02x %02x %02x\n",
|
|
(unsigned char *)vuc +
|
|
offsetof(struct snd_aes_iec958, dig_subframe),
|
|
vuc->dig_subframe[0], vuc->dig_subframe[1],
|
|
vuc->dig_subframe[2], vuc->dig_subframe[3]);
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Core initialization
|
|
*/
|
|
|
|
static int __init snd_stm_core_init(void)
|
|
{
|
|
int result;
|
|
|
|
snd_stm_printd(0, "%s()\n", __func__);
|
|
|
|
result = snd_card_create(snd_stm_card_index, snd_stm_card_id,
|
|
THIS_MODULE, 0, &snd_stm_card);
|
|
if (result != 0) {
|
|
snd_stm_printe("Failed to create ALSA card!\n");
|
|
goto error_card_create;
|
|
}
|
|
|
|
result = snd_stm_info_create();
|
|
if (result != 0) {
|
|
snd_stm_printe("Procfs info creation failed!\n");
|
|
goto error_info;
|
|
}
|
|
result = snd_stm_conv_init();
|
|
if (result != 0) {
|
|
snd_stm_printe("Converters infrastructure initialization"
|
|
" failed!\n");
|
|
goto error_conv;
|
|
}
|
|
|
|
return result;
|
|
|
|
error_conv:
|
|
snd_stm_info_dispose();
|
|
error_info:
|
|
snd_card_free(snd_stm_card);
|
|
error_card_create:
|
|
return result;
|
|
}
|
|
|
|
static void __exit snd_stm_core_exit(void)
|
|
{
|
|
snd_stm_printd(0, "%s()\n", __func__);
|
|
|
|
snd_stm_conv_exit();
|
|
snd_stm_info_dispose();
|
|
|
|
snd_card_free(snd_stm_card);
|
|
}
|
|
|
|
MODULE_AUTHOR("Pawel Moll <pawel.moll@st.com>");
|
|
MODULE_DESCRIPTION("STMicroelectronics System-on-Chips' audio core driver");
|
|
MODULE_LICENSE("GPL");
|
|
|
|
module_init(snd_stm_core_init);
|
|
module_exit(snd_stm_core_exit);
|