2019-05-27 14:55:05 +08:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
2007-11-05 23:06:01 +08:00
|
|
|
/*
|
|
|
|
* ALSA driver for Xilinx ML403 AC97 Controller Reference
|
|
|
|
* IP: opb_ac97_controller_ref_v1_00_a (EDK 8.1i)
|
|
|
|
* IP: opb_ac97_controller_ref_v1_00_a (EDK 9.1i)
|
|
|
|
*
|
|
|
|
* Copyright (c) by 2007 Joachim Foerster <JOFT@gmx.de>
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* Some notes / status of this driver:
|
|
|
|
*
|
|
|
|
* - Don't wonder about some strange implementations of things - especially the
|
|
|
|
* (heavy) shadowing of codec registers, with which I tried to reduce read
|
|
|
|
* accesses to a minimum, because after a variable amount of accesses, the AC97
|
|
|
|
* controller doesn't raise the register access finished bit anymore ...
|
|
|
|
*
|
|
|
|
* - Playback support seems to be pretty stable - no issues here.
|
2007-11-05 22:48:36 +08:00
|
|
|
* - Capture support "works" now, too. Overruns don't happen any longer so often.
|
|
|
|
* But there might still be some ...
|
2007-11-05 23:06:01 +08:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/init.h>
|
2011-07-16 01:13:37 +08:00
|
|
|
#include <linux/module.h>
|
2007-11-05 23:06:01 +08:00
|
|
|
|
|
|
|
#include <linux/platform_device.h>
|
|
|
|
|
|
|
|
#include <linux/ioport.h>
|
include cleanup: Update gfp.h and slab.h includes to prepare for breaking implicit slab.h inclusion from percpu.h
percpu.h is included by sched.h and module.h and thus ends up being
included when building most .c files. percpu.h includes slab.h which
in turn includes gfp.h making everything defined by the two files
universally available and complicating inclusion dependencies.
percpu.h -> slab.h dependency is about to be removed. Prepare for
this change by updating users of gfp and slab facilities include those
headers directly instead of assuming availability. As this conversion
needs to touch large number of source files, the following script is
used as the basis of conversion.
http://userweb.kernel.org/~tj/misc/slabh-sweep.py
The script does the followings.
* Scan files for gfp and slab usages and update includes such that
only the necessary includes are there. ie. if only gfp is used,
gfp.h, if slab is used, slab.h.
* When the script inserts a new include, it looks at the include
blocks and try to put the new include such that its order conforms
to its surrounding. It's put in the include block which contains
core kernel includes, in the same order that the rest are ordered -
alphabetical, Christmas tree, rev-Xmas-tree or at the end if there
doesn't seem to be any matching order.
* If the script can't find a place to put a new include (mostly
because the file doesn't have fitting include block), it prints out
an error message indicating which .h file needs to be added to the
file.
The conversion was done in the following steps.
1. The initial automatic conversion of all .c files updated slightly
over 4000 files, deleting around 700 includes and adding ~480 gfp.h
and ~3000 slab.h inclusions. The script emitted errors for ~400
files.
2. Each error was manually checked. Some didn't need the inclusion,
some needed manual addition while adding it to implementation .h or
embedding .c file was more appropriate for others. This step added
inclusions to around 150 files.
3. The script was run again and the output was compared to the edits
from #2 to make sure no file was left behind.
4. Several build tests were done and a couple of problems were fixed.
e.g. lib/decompress_*.c used malloc/free() wrappers around slab
APIs requiring slab.h to be added manually.
5. The script was run on all .h files but without automatically
editing them as sprinkling gfp.h and slab.h inclusions around .h
files could easily lead to inclusion dependency hell. Most gfp.h
inclusion directives were ignored as stuff from gfp.h was usually
wildly available and often used in preprocessor macros. Each
slab.h inclusion directive was examined and added manually as
necessary.
6. percpu.h was updated not to include slab.h.
7. Build test were done on the following configurations and failures
were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my
distributed build env didn't work with gcov compiles) and a few
more options had to be turned off depending on archs to make things
build (like ipr on powerpc/64 which failed due to missing writeq).
* x86 and x86_64 UP and SMP allmodconfig and a custom test config.
* powerpc and powerpc64 SMP allmodconfig
* sparc and sparc64 SMP allmodconfig
* ia64 SMP allmodconfig
* s390 SMP allmodconfig
* alpha SMP allmodconfig
* um on x86_64 SMP allmodconfig
8. percpu.h modifications were reverted so that it could be applied as
a separate patch and serve as bisection point.
Given the fact that I had only a couple of failures from tests on step
6, I'm fairly confident about the coverage of this conversion patch.
If there is a breakage, it's likely to be something in one of the arch
headers which should be easily discoverable easily on most builds of
the specific arch.
Signed-off-by: Tejun Heo <tj@kernel.org>
Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
2010-03-24 16:04:11 +08:00
|
|
|
#include <linux/slab.h>
|
2007-11-05 23:06:01 +08:00
|
|
|
#include <linux/io.h>
|
|
|
|
#include <linux/interrupt.h>
|
|
|
|
|
|
|
|
/* HZ */
|
|
|
|
#include <linux/param.h>
|
|
|
|
/* jiffies, time_*() */
|
|
|
|
#include <linux/jiffies.h>
|
|
|
|
/* schedule_timeout*() */
|
|
|
|
#include <linux/sched.h>
|
|
|
|
/* spin_lock*() */
|
|
|
|
#include <linux/spinlock.h>
|
|
|
|
/* struct mutex, mutex_init(), mutex_*lock() */
|
|
|
|
#include <linux/mutex.h>
|
|
|
|
|
|
|
|
/* snd_printk(), snd_printd() */
|
|
|
|
#include <sound/core.h>
|
|
|
|
#include <sound/pcm.h>
|
|
|
|
#include <sound/pcm_params.h>
|
|
|
|
#include <sound/initval.h>
|
|
|
|
#include <sound/ac97_codec.h>
|
|
|
|
|
|
|
|
#include "pcm-indirect2.h"
|
|
|
|
|
|
|
|
|
|
|
|
#define SND_ML403_AC97CR_DRIVER "ml403-ac97cr"
|
|
|
|
|
|
|
|
MODULE_AUTHOR("Joachim Foerster <JOFT@gmx.de>");
|
|
|
|
MODULE_DESCRIPTION("Xilinx ML403 AC97 Controller Reference");
|
|
|
|
MODULE_LICENSE("GPL");
|
|
|
|
MODULE_SUPPORTED_DEVICE("{{Xilinx,ML403 AC97 Controller Reference}}");
|
|
|
|
|
|
|
|
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
|
|
|
|
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
|
2011-12-15 11:19:36 +08:00
|
|
|
static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE;
|
2007-11-05 23:06:01 +08:00
|
|
|
|
|
|
|
module_param_array(index, int, NULL, 0444);
|
|
|
|
MODULE_PARM_DESC(index, "Index value for ML403 AC97 Controller Reference.");
|
|
|
|
module_param_array(id, charp, NULL, 0444);
|
|
|
|
MODULE_PARM_DESC(id, "ID string for ML403 AC97 Controller Reference.");
|
|
|
|
module_param_array(enable, bool, NULL, 0444);
|
|
|
|
MODULE_PARM_DESC(enable, "Enable this ML403 AC97 Controller Reference.");
|
|
|
|
|
|
|
|
/* Special feature options */
|
|
|
|
/*#define CODEC_WRITE_CHECK_RAF*/ /* don't return after a write to a codec
|
|
|
|
* register, while RAF bit is not set
|
|
|
|
*/
|
|
|
|
/* Debug options for code which may be removed completely in a final version */
|
|
|
|
#ifdef CONFIG_SND_DEBUG
|
|
|
|
/*#define CODEC_STAT*/ /* turn on some minimal "statistics"
|
|
|
|
* about codec register usage
|
|
|
|
*/
|
|
|
|
#define SND_PCM_INDIRECT2_STAT /* turn on some "statistics" about the
|
|
|
|
* process of copying bytes from the
|
|
|
|
* intermediate buffer to the hardware
|
|
|
|
* fifo and the other way round
|
|
|
|
*/
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* Definition of a "level/facility dependent" printk(); may be removed
|
|
|
|
* completely in a final version
|
|
|
|
*/
|
|
|
|
#undef PDEBUG
|
|
|
|
#ifdef CONFIG_SND_DEBUG
|
|
|
|
/* "facilities" for PDEBUG */
|
|
|
|
#define UNKNOWN (1<<0)
|
|
|
|
#define CODEC_SUCCESS (1<<1)
|
|
|
|
#define CODEC_FAKE (1<<2)
|
|
|
|
#define INIT_INFO (1<<3)
|
|
|
|
#define INIT_FAILURE (1<<4)
|
|
|
|
#define WORK_INFO (1<<5)
|
|
|
|
#define WORK_FAILURE (1<<6)
|
|
|
|
|
|
|
|
#define PDEBUG_FACILITIES (UNKNOWN | INIT_FAILURE | WORK_FAILURE)
|
|
|
|
|
|
|
|
#define PDEBUG(fac, fmt, args...) do { \
|
|
|
|
if (fac & PDEBUG_FACILITIES) \
|
|
|
|
snd_printd(KERN_DEBUG SND_ML403_AC97CR_DRIVER ": " \
|
|
|
|
fmt, ##args); \
|
|
|
|
} while (0)
|
|
|
|
#else
|
|
|
|
#define PDEBUG(fac, fmt, args...) /* nothing */
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Defines for "waits"/timeouts (portions of HZ=250 on arch/ppc by default) */
|
|
|
|
#define CODEC_TIMEOUT_ON_INIT 5 /* timeout for checking for codec
|
|
|
|
* readiness (after insmod)
|
|
|
|
*/
|
|
|
|
#ifndef CODEC_WRITE_CHECK_RAF
|
|
|
|
#define CODEC_WAIT_AFTER_WRITE 100 /* general, static wait after a write
|
|
|
|
* access to a codec register, may be
|
|
|
|
* 0 to completely remove wait
|
|
|
|
*/
|
|
|
|
#else
|
|
|
|
#define CODEC_TIMEOUT_AFTER_WRITE 5 /* timeout after a write access to a
|
|
|
|
* codec register, if RAF bit is used
|
|
|
|
*/
|
|
|
|
#endif
|
|
|
|
#define CODEC_TIMEOUT_AFTER_READ 5 /* timeout after a read access to a
|
|
|
|
* codec register (checking RAF bit)
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* Infrastructure for codec register shadowing */
|
|
|
|
#define LM4550_REG_OK (1<<0) /* register exists */
|
|
|
|
#define LM4550_REG_DONEREAD (1<<1) /* read register once, value should be
|
|
|
|
* the same currently in the register
|
|
|
|
*/
|
|
|
|
#define LM4550_REG_NOSAVE (1<<2) /* values written to this register will
|
|
|
|
* not be saved in the register
|
|
|
|
*/
|
|
|
|
#define LM4550_REG_NOSHADOW (1<<3) /* don't do register shadowing, use plain
|
|
|
|
* hardware access
|
|
|
|
*/
|
|
|
|
#define LM4550_REG_READONLY (1<<4) /* register is read only */
|
|
|
|
#define LM4550_REG_FAKEPROBE (1<<5) /* fake write _and_ read actions during
|
|
|
|
* probe() correctly
|
|
|
|
*/
|
|
|
|
#define LM4550_REG_FAKEREAD (1<<6) /* fake read access, always return
|
|
|
|
* default value
|
|
|
|
*/
|
|
|
|
#define LM4550_REG_ALLFAKE (LM4550_REG_FAKEREAD | LM4550_REG_FAKEPROBE)
|
|
|
|
|
|
|
|
struct lm4550_reg {
|
|
|
|
u16 value;
|
|
|
|
u16 flag;
|
|
|
|
u16 wmask;
|
|
|
|
u16 def;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct lm4550_reg lm4550_regfile[64] = {
|
|
|
|
[AC97_RESET / 2] = {.flag = LM4550_REG_OK \
|
|
|
|
| LM4550_REG_NOSAVE \
|
|
|
|
| LM4550_REG_FAKEREAD,
|
|
|
|
.def = 0x0D50},
|
|
|
|
[AC97_MASTER / 2] = {.flag = LM4550_REG_OK
|
|
|
|
| LM4550_REG_FAKEPROBE,
|
|
|
|
.wmask = 0x9F1F,
|
|
|
|
.def = 0x8000},
|
|
|
|
[AC97_HEADPHONE / 2] = {.flag = LM4550_REG_OK \
|
|
|
|
| LM4550_REG_FAKEPROBE,
|
|
|
|
.wmask = 0x9F1F,
|
|
|
|
.def = 0x8000},
|
|
|
|
[AC97_MASTER_MONO / 2] = {.flag = LM4550_REG_OK \
|
|
|
|
| LM4550_REG_FAKEPROBE,
|
|
|
|
.wmask = 0x801F,
|
|
|
|
.def = 0x8000},
|
|
|
|
[AC97_PC_BEEP / 2] = {.flag = LM4550_REG_OK \
|
|
|
|
| LM4550_REG_FAKEPROBE,
|
|
|
|
.wmask = 0x801E,
|
|
|
|
.def = 0x0},
|
|
|
|
[AC97_PHONE / 2] = {.flag = LM4550_REG_OK \
|
|
|
|
| LM4550_REG_FAKEPROBE,
|
|
|
|
.wmask = 0x801F,
|
|
|
|
.def = 0x8008},
|
|
|
|
[AC97_MIC / 2] = {.flag = LM4550_REG_OK \
|
|
|
|
| LM4550_REG_FAKEPROBE,
|
|
|
|
.wmask = 0x805F,
|
|
|
|
.def = 0x8008},
|
|
|
|
[AC97_LINE / 2] = {.flag = LM4550_REG_OK \
|
|
|
|
| LM4550_REG_FAKEPROBE,
|
|
|
|
.wmask = 0x9F1F,
|
|
|
|
.def = 0x8808},
|
|
|
|
[AC97_CD / 2] = {.flag = LM4550_REG_OK \
|
|
|
|
| LM4550_REG_FAKEPROBE,
|
|
|
|
.wmask = 0x9F1F,
|
|
|
|
.def = 0x8808},
|
|
|
|
[AC97_VIDEO / 2] = {.flag = LM4550_REG_OK \
|
|
|
|
| LM4550_REG_FAKEPROBE,
|
|
|
|
.wmask = 0x9F1F,
|
|
|
|
.def = 0x8808},
|
|
|
|
[AC97_AUX / 2] = {.flag = LM4550_REG_OK \
|
|
|
|
| LM4550_REG_FAKEPROBE,
|
|
|
|
.wmask = 0x9F1F,
|
|
|
|
.def = 0x8808},
|
|
|
|
[AC97_PCM / 2] = {.flag = LM4550_REG_OK \
|
|
|
|
| LM4550_REG_FAKEPROBE,
|
|
|
|
.wmask = 0x9F1F,
|
|
|
|
.def = 0x8008},
|
|
|
|
[AC97_REC_SEL / 2] = {.flag = LM4550_REG_OK \
|
|
|
|
| LM4550_REG_FAKEPROBE,
|
|
|
|
.wmask = 0x707,
|
|
|
|
.def = 0x0},
|
|
|
|
[AC97_REC_GAIN / 2] = {.flag = LM4550_REG_OK \
|
|
|
|
| LM4550_REG_FAKEPROBE,
|
|
|
|
.wmask = 0x8F0F,
|
|
|
|
.def = 0x8000},
|
|
|
|
[AC97_GENERAL_PURPOSE / 2] = {.flag = LM4550_REG_OK \
|
|
|
|
| LM4550_REG_FAKEPROBE,
|
|
|
|
.def = 0x0,
|
|
|
|
.wmask = 0xA380},
|
|
|
|
[AC97_3D_CONTROL / 2] = {.flag = LM4550_REG_OK \
|
|
|
|
| LM4550_REG_FAKEREAD \
|
|
|
|
| LM4550_REG_READONLY,
|
|
|
|
.def = 0x0101},
|
|
|
|
[AC97_POWERDOWN / 2] = {.flag = LM4550_REG_OK \
|
|
|
|
| LM4550_REG_NOSHADOW \
|
|
|
|
| LM4550_REG_NOSAVE,
|
|
|
|
.wmask = 0xFF00},
|
|
|
|
/* may not write ones to
|
|
|
|
* REF/ANL/DAC/ADC bits
|
|
|
|
* FIXME: Is this ok?
|
|
|
|
*/
|
|
|
|
[AC97_EXTENDED_ID / 2] = {.flag = LM4550_REG_OK \
|
|
|
|
| LM4550_REG_FAKEREAD \
|
|
|
|
| LM4550_REG_READONLY,
|
|
|
|
.def = 0x0201}, /* primary codec */
|
|
|
|
[AC97_EXTENDED_STATUS / 2] = {.flag = LM4550_REG_OK \
|
|
|
|
| LM4550_REG_NOSHADOW \
|
|
|
|
| LM4550_REG_NOSAVE,
|
|
|
|
.wmask = 0x1},
|
|
|
|
[AC97_PCM_FRONT_DAC_RATE / 2] = {.flag = LM4550_REG_OK \
|
|
|
|
| LM4550_REG_FAKEPROBE,
|
|
|
|
.def = 0xBB80,
|
|
|
|
.wmask = 0xFFFF},
|
|
|
|
[AC97_PCM_LR_ADC_RATE / 2] = {.flag = LM4550_REG_OK \
|
|
|
|
| LM4550_REG_FAKEPROBE,
|
|
|
|
.def = 0xBB80,
|
|
|
|
.wmask = 0xFFFF},
|
|
|
|
[AC97_VENDOR_ID1 / 2] = {.flag = LM4550_REG_OK \
|
|
|
|
| LM4550_REG_READONLY \
|
|
|
|
| LM4550_REG_FAKEREAD,
|
|
|
|
.def = 0x4E53},
|
|
|
|
[AC97_VENDOR_ID2 / 2] = {.flag = LM4550_REG_OK \
|
|
|
|
| LM4550_REG_READONLY \
|
|
|
|
| LM4550_REG_FAKEREAD,
|
|
|
|
.def = 0x4350}
|
|
|
|
};
|
|
|
|
|
|
|
|
#define LM4550_RF_OK(reg) (lm4550_regfile[reg / 2].flag & LM4550_REG_OK)
|
|
|
|
|
|
|
|
static void lm4550_regfile_init(void)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < 64; i++)
|
|
|
|
if (lm4550_regfile[i].flag & LM4550_REG_FAKEPROBE)
|
|
|
|
lm4550_regfile[i].value = lm4550_regfile[i].def;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void lm4550_regfile_write_values_after_init(struct snd_ac97 *ac97)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < 64; i++)
|
|
|
|
if ((lm4550_regfile[i].flag & LM4550_REG_FAKEPROBE) &&
|
|
|
|
(lm4550_regfile[i].value != lm4550_regfile[i].def)) {
|
|
|
|
PDEBUG(CODEC_FAKE, "lm4550_regfile_write_values_after_"
|
|
|
|
"init(): reg=0x%x value=0x%x / %d is different "
|
|
|
|
"from def=0x%x / %d\n",
|
|
|
|
i, lm4550_regfile[i].value,
|
|
|
|
lm4550_regfile[i].value, lm4550_regfile[i].def,
|
|
|
|
lm4550_regfile[i].def);
|
|
|
|
snd_ac97_write(ac97, i * 2, lm4550_regfile[i].value);
|
|
|
|
lm4550_regfile[i].flag |= LM4550_REG_DONEREAD;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* direct registers */
|
|
|
|
#define CR_REG(ml403_ac97cr, x) ((ml403_ac97cr)->port + CR_REG_##x)
|
|
|
|
|
|
|
|
#define CR_REG_PLAYFIFO 0x00
|
|
|
|
#define CR_PLAYDATA(a) ((a) & 0xFFFF)
|
|
|
|
|
|
|
|
#define CR_REG_RECFIFO 0x04
|
|
|
|
#define CR_RECDATA(a) ((a) & 0xFFFF)
|
|
|
|
|
|
|
|
#define CR_REG_STATUS 0x08
|
|
|
|
#define CR_RECOVER (1<<7)
|
|
|
|
#define CR_PLAYUNDER (1<<6)
|
|
|
|
#define CR_CODECREADY (1<<5)
|
|
|
|
#define CR_RAF (1<<4)
|
|
|
|
#define CR_RECEMPTY (1<<3)
|
|
|
|
#define CR_RECFULL (1<<2)
|
|
|
|
#define CR_PLAYHALF (1<<1)
|
|
|
|
#define CR_PLAYFULL (1<<0)
|
|
|
|
|
|
|
|
#define CR_REG_RESETFIFO 0x0C
|
|
|
|
#define CR_RECRESET (1<<1)
|
|
|
|
#define CR_PLAYRESET (1<<0)
|
|
|
|
|
|
|
|
#define CR_REG_CODEC_ADDR 0x10
|
|
|
|
/* UG082 says:
|
|
|
|
* #define CR_CODEC_ADDR(a) ((a) << 1)
|
|
|
|
* #define CR_CODEC_READ (1<<0)
|
|
|
|
* #define CR_CODEC_WRITE (0<<0)
|
|
|
|
*/
|
|
|
|
/* RefDesign example says: */
|
|
|
|
#define CR_CODEC_ADDR(a) ((a) << 0)
|
|
|
|
#define CR_CODEC_READ (1<<7)
|
|
|
|
#define CR_CODEC_WRITE (0<<7)
|
|
|
|
|
|
|
|
#define CR_REG_CODEC_DATAREAD 0x14
|
|
|
|
#define CR_CODEC_DATAREAD(v) ((v) & 0xFFFF)
|
|
|
|
|
|
|
|
#define CR_REG_CODEC_DATAWRITE 0x18
|
|
|
|
#define CR_CODEC_DATAWRITE(v) ((v) & 0xFFFF)
|
|
|
|
|
|
|
|
#define CR_FIFO_SIZE 32
|
|
|
|
|
|
|
|
struct snd_ml403_ac97cr {
|
|
|
|
/* lock for access to (controller) registers */
|
|
|
|
spinlock_t reg_lock;
|
|
|
|
/* mutex for the whole sequence of accesses to (controller) registers
|
|
|
|
* which affect codec registers
|
|
|
|
*/
|
|
|
|
struct mutex cdc_mutex;
|
|
|
|
|
|
|
|
int irq; /* for playback */
|
|
|
|
int enable_irq; /* for playback */
|
|
|
|
|
|
|
|
int capture_irq;
|
|
|
|
int enable_capture_irq;
|
|
|
|
|
|
|
|
struct resource *res_port;
|
|
|
|
void *port;
|
|
|
|
|
|
|
|
struct snd_ac97 *ac97;
|
|
|
|
int ac97_fake;
|
|
|
|
#ifdef CODEC_STAT
|
|
|
|
int ac97_read;
|
|
|
|
int ac97_write;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
struct platform_device *pfdev;
|
|
|
|
struct snd_card *card;
|
|
|
|
struct snd_pcm *pcm;
|
|
|
|
struct snd_pcm_substream *playback_substream;
|
|
|
|
struct snd_pcm_substream *capture_substream;
|
|
|
|
|
|
|
|
struct snd_pcm_indirect2 ind_rec; /* for playback */
|
|
|
|
struct snd_pcm_indirect2 capture_ind2_rec;
|
|
|
|
};
|
|
|
|
|
2017-08-17 17:15:51 +08:00
|
|
|
static const struct snd_pcm_hardware snd_ml403_ac97cr_playback = {
|
2007-11-05 23:06:01 +08:00
|
|
|
.info = (SNDRV_PCM_INFO_MMAP |
|
|
|
|
SNDRV_PCM_INFO_INTERLEAVED |
|
|
|
|
SNDRV_PCM_INFO_MMAP_VALID),
|
|
|
|
.formats = SNDRV_PCM_FMTBIT_S16_BE,
|
|
|
|
.rates = (SNDRV_PCM_RATE_CONTINUOUS |
|
|
|
|
SNDRV_PCM_RATE_8000_48000),
|
|
|
|
.rate_min = 4000,
|
|
|
|
.rate_max = 48000,
|
|
|
|
.channels_min = 2,
|
|
|
|
.channels_max = 2,
|
|
|
|
.buffer_bytes_max = (128*1024),
|
|
|
|
.period_bytes_min = CR_FIFO_SIZE/2,
|
|
|
|
.period_bytes_max = (64*1024),
|
|
|
|
.periods_min = 2,
|
|
|
|
.periods_max = (128*1024)/(CR_FIFO_SIZE/2),
|
|
|
|
.fifo_size = 0,
|
|
|
|
};
|
|
|
|
|
2017-08-17 17:15:51 +08:00
|
|
|
static const struct snd_pcm_hardware snd_ml403_ac97cr_capture = {
|
2007-11-05 23:06:01 +08:00
|
|
|
.info = (SNDRV_PCM_INFO_MMAP |
|
|
|
|
SNDRV_PCM_INFO_INTERLEAVED |
|
|
|
|
SNDRV_PCM_INFO_MMAP_VALID),
|
|
|
|
.formats = SNDRV_PCM_FMTBIT_S16_BE,
|
|
|
|
.rates = (SNDRV_PCM_RATE_CONTINUOUS |
|
|
|
|
SNDRV_PCM_RATE_8000_48000),
|
|
|
|
.rate_min = 4000,
|
|
|
|
.rate_max = 48000,
|
|
|
|
.channels_min = 2,
|
|
|
|
.channels_max = 2,
|
|
|
|
.buffer_bytes_max = (128*1024),
|
|
|
|
.period_bytes_min = CR_FIFO_SIZE/2,
|
|
|
|
.period_bytes_max = (64*1024),
|
|
|
|
.periods_min = 2,
|
|
|
|
.periods_max = (128*1024)/(CR_FIFO_SIZE/2),
|
|
|
|
.fifo_size = 0,
|
|
|
|
};
|
|
|
|
|
|
|
|
static size_t
|
|
|
|
snd_ml403_ac97cr_playback_ind2_zero(struct snd_pcm_substream *substream,
|
|
|
|
struct snd_pcm_indirect2 *rec)
|
|
|
|
{
|
|
|
|
struct snd_ml403_ac97cr *ml403_ac97cr;
|
|
|
|
int copied_words = 0;
|
|
|
|
u32 full = 0;
|
|
|
|
|
|
|
|
ml403_ac97cr = snd_pcm_substream_chip(substream);
|
|
|
|
|
|
|
|
spin_lock(&ml403_ac97cr->reg_lock);
|
|
|
|
while ((full = (in_be32(CR_REG(ml403_ac97cr, STATUS)) &
|
|
|
|
CR_PLAYFULL)) != CR_PLAYFULL) {
|
|
|
|
out_be32(CR_REG(ml403_ac97cr, PLAYFIFO), 0);
|
|
|
|
copied_words++;
|
|
|
|
}
|
|
|
|
rec->hw_ready = 0;
|
|
|
|
spin_unlock(&ml403_ac97cr->reg_lock);
|
|
|
|
|
|
|
|
return (size_t) (copied_words * 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
static size_t
|
|
|
|
snd_ml403_ac97cr_playback_ind2_copy(struct snd_pcm_substream *substream,
|
|
|
|
struct snd_pcm_indirect2 *rec,
|
|
|
|
size_t bytes)
|
|
|
|
{
|
|
|
|
struct snd_ml403_ac97cr *ml403_ac97cr;
|
|
|
|
u16 *src;
|
|
|
|
int copied_words = 0;
|
|
|
|
u32 full = 0;
|
|
|
|
|
|
|
|
ml403_ac97cr = snd_pcm_substream_chip(substream);
|
|
|
|
src = (u16 *)(substream->runtime->dma_area + rec->sw_data);
|
|
|
|
|
|
|
|
spin_lock(&ml403_ac97cr->reg_lock);
|
|
|
|
while (((full = (in_be32(CR_REG(ml403_ac97cr, STATUS)) &
|
|
|
|
CR_PLAYFULL)) != CR_PLAYFULL) && (bytes > 1)) {
|
|
|
|
out_be32(CR_REG(ml403_ac97cr, PLAYFIFO),
|
|
|
|
CR_PLAYDATA(src[copied_words]));
|
|
|
|
copied_words++;
|
|
|
|
bytes = bytes - 2;
|
|
|
|
}
|
|
|
|
if (full != CR_PLAYFULL)
|
|
|
|
rec->hw_ready = 1;
|
|
|
|
else
|
|
|
|
rec->hw_ready = 0;
|
|
|
|
spin_unlock(&ml403_ac97cr->reg_lock);
|
|
|
|
|
|
|
|
return (size_t) (copied_words * 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
static size_t
|
|
|
|
snd_ml403_ac97cr_capture_ind2_null(struct snd_pcm_substream *substream,
|
|
|
|
struct snd_pcm_indirect2 *rec)
|
|
|
|
{
|
|
|
|
struct snd_ml403_ac97cr *ml403_ac97cr;
|
|
|
|
int copied_words = 0;
|
|
|
|
u32 empty = 0;
|
|
|
|
|
|
|
|
ml403_ac97cr = snd_pcm_substream_chip(substream);
|
|
|
|
|
|
|
|
spin_lock(&ml403_ac97cr->reg_lock);
|
|
|
|
while ((empty = (in_be32(CR_REG(ml403_ac97cr, STATUS)) &
|
|
|
|
CR_RECEMPTY)) != CR_RECEMPTY) {
|
|
|
|
volatile u32 trash;
|
|
|
|
|
|
|
|
trash = CR_RECDATA(in_be32(CR_REG(ml403_ac97cr, RECFIFO)));
|
|
|
|
/* Hmmmm, really necessary? Don't want call to in_be32()
|
|
|
|
* to be optimised away!
|
|
|
|
*/
|
|
|
|
trash++;
|
|
|
|
copied_words++;
|
|
|
|
}
|
|
|
|
rec->hw_ready = 0;
|
|
|
|
spin_unlock(&ml403_ac97cr->reg_lock);
|
|
|
|
|
|
|
|
return (size_t) (copied_words * 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
static size_t
|
|
|
|
snd_ml403_ac97cr_capture_ind2_copy(struct snd_pcm_substream *substream,
|
|
|
|
struct snd_pcm_indirect2 *rec, size_t bytes)
|
|
|
|
{
|
|
|
|
struct snd_ml403_ac97cr *ml403_ac97cr;
|
|
|
|
u16 *dst;
|
|
|
|
int copied_words = 0;
|
|
|
|
u32 empty = 0;
|
|
|
|
|
|
|
|
ml403_ac97cr = snd_pcm_substream_chip(substream);
|
|
|
|
dst = (u16 *)(substream->runtime->dma_area + rec->sw_data);
|
|
|
|
|
|
|
|
spin_lock(&ml403_ac97cr->reg_lock);
|
|
|
|
while (((empty = (in_be32(CR_REG(ml403_ac97cr, STATUS)) &
|
|
|
|
CR_RECEMPTY)) != CR_RECEMPTY) && (bytes > 1)) {
|
|
|
|
dst[copied_words] = CR_RECDATA(in_be32(CR_REG(ml403_ac97cr,
|
|
|
|
RECFIFO)));
|
|
|
|
copied_words++;
|
|
|
|
bytes = bytes - 2;
|
|
|
|
}
|
|
|
|
if (empty != CR_RECEMPTY)
|
|
|
|
rec->hw_ready = 1;
|
|
|
|
else
|
|
|
|
rec->hw_ready = 0;
|
|
|
|
spin_unlock(&ml403_ac97cr->reg_lock);
|
|
|
|
|
|
|
|
return (size_t) (copied_words * 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
static snd_pcm_uframes_t
|
|
|
|
snd_ml403_ac97cr_pcm_pointer(struct snd_pcm_substream *substream)
|
|
|
|
{
|
|
|
|
struct snd_ml403_ac97cr *ml403_ac97cr;
|
|
|
|
struct snd_pcm_indirect2 *ind2_rec = NULL;
|
|
|
|
|
|
|
|
ml403_ac97cr = snd_pcm_substream_chip(substream);
|
|
|
|
|
|
|
|
if (substream == ml403_ac97cr->playback_substream)
|
|
|
|
ind2_rec = &ml403_ac97cr->ind_rec;
|
|
|
|
if (substream == ml403_ac97cr->capture_substream)
|
|
|
|
ind2_rec = &ml403_ac97cr->capture_ind2_rec;
|
|
|
|
|
|
|
|
if (ind2_rec != NULL)
|
|
|
|
return snd_pcm_indirect2_pointer(substream, ind2_rec);
|
|
|
|
return (snd_pcm_uframes_t) 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
snd_ml403_ac97cr_pcm_playback_trigger(struct snd_pcm_substream *substream,
|
|
|
|
int cmd)
|
|
|
|
{
|
|
|
|
struct snd_ml403_ac97cr *ml403_ac97cr;
|
|
|
|
int err = 0;
|
|
|
|
|
|
|
|
ml403_ac97cr = snd_pcm_substream_chip(substream);
|
|
|
|
|
|
|
|
switch (cmd) {
|
|
|
|
case SNDRV_PCM_TRIGGER_START:
|
|
|
|
PDEBUG(WORK_INFO, "trigger(playback): START\n");
|
|
|
|
ml403_ac97cr->ind_rec.hw_ready = 1;
|
|
|
|
|
|
|
|
/* clear play FIFO */
|
|
|
|
out_be32(CR_REG(ml403_ac97cr, RESETFIFO), CR_PLAYRESET);
|
|
|
|
|
|
|
|
/* enable play irq */
|
|
|
|
ml403_ac97cr->enable_irq = 1;
|
|
|
|
enable_irq(ml403_ac97cr->irq);
|
|
|
|
break;
|
|
|
|
case SNDRV_PCM_TRIGGER_STOP:
|
|
|
|
PDEBUG(WORK_INFO, "trigger(playback): STOP\n");
|
|
|
|
ml403_ac97cr->ind_rec.hw_ready = 0;
|
|
|
|
#ifdef SND_PCM_INDIRECT2_STAT
|
|
|
|
snd_pcm_indirect2_stat(substream, &ml403_ac97cr->ind_rec);
|
|
|
|
#endif
|
|
|
|
/* disable play irq */
|
|
|
|
disable_irq_nosync(ml403_ac97cr->irq);
|
|
|
|
ml403_ac97cr->enable_irq = 0;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
err = -EINVAL;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
PDEBUG(WORK_INFO, "trigger(playback): (done)\n");
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
snd_ml403_ac97cr_pcm_capture_trigger(struct snd_pcm_substream *substream,
|
|
|
|
int cmd)
|
|
|
|
{
|
|
|
|
struct snd_ml403_ac97cr *ml403_ac97cr;
|
|
|
|
int err = 0;
|
|
|
|
|
|
|
|
ml403_ac97cr = snd_pcm_substream_chip(substream);
|
|
|
|
|
|
|
|
switch (cmd) {
|
|
|
|
case SNDRV_PCM_TRIGGER_START:
|
|
|
|
PDEBUG(WORK_INFO, "trigger(capture): START\n");
|
|
|
|
ml403_ac97cr->capture_ind2_rec.hw_ready = 0;
|
|
|
|
|
|
|
|
/* clear record FIFO */
|
|
|
|
out_be32(CR_REG(ml403_ac97cr, RESETFIFO), CR_RECRESET);
|
|
|
|
|
|
|
|
/* enable record irq */
|
|
|
|
ml403_ac97cr->enable_capture_irq = 1;
|
|
|
|
enable_irq(ml403_ac97cr->capture_irq);
|
|
|
|
break;
|
|
|
|
case SNDRV_PCM_TRIGGER_STOP:
|
|
|
|
PDEBUG(WORK_INFO, "trigger(capture): STOP\n");
|
|
|
|
ml403_ac97cr->capture_ind2_rec.hw_ready = 0;
|
|
|
|
#ifdef SND_PCM_INDIRECT2_STAT
|
|
|
|
snd_pcm_indirect2_stat(substream,
|
|
|
|
&ml403_ac97cr->capture_ind2_rec);
|
|
|
|
#endif
|
|
|
|
/* disable capture irq */
|
|
|
|
disable_irq_nosync(ml403_ac97cr->capture_irq);
|
|
|
|
ml403_ac97cr->enable_capture_irq = 0;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
err = -EINVAL;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
PDEBUG(WORK_INFO, "trigger(capture): (done)\n");
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
snd_ml403_ac97cr_pcm_playback_prepare(struct snd_pcm_substream *substream)
|
|
|
|
{
|
|
|
|
struct snd_ml403_ac97cr *ml403_ac97cr;
|
|
|
|
struct snd_pcm_runtime *runtime;
|
|
|
|
|
|
|
|
ml403_ac97cr = snd_pcm_substream_chip(substream);
|
|
|
|
runtime = substream->runtime;
|
|
|
|
|
|
|
|
PDEBUG(WORK_INFO,
|
|
|
|
"prepare(): period_bytes=%d, minperiod_bytes=%d\n",
|
|
|
|
snd_pcm_lib_period_bytes(substream), CR_FIFO_SIZE / 2);
|
|
|
|
|
|
|
|
/* set sampling rate */
|
|
|
|
snd_ac97_set_rate(ml403_ac97cr->ac97, AC97_PCM_FRONT_DAC_RATE,
|
|
|
|
runtime->rate);
|
|
|
|
PDEBUG(WORK_INFO, "prepare(): rate=%d\n", runtime->rate);
|
|
|
|
|
|
|
|
/* init struct for intermediate buffer */
|
|
|
|
memset(&ml403_ac97cr->ind_rec, 0,
|
|
|
|
sizeof(struct snd_pcm_indirect2));
|
|
|
|
ml403_ac97cr->ind_rec.hw_buffer_size = CR_FIFO_SIZE;
|
|
|
|
ml403_ac97cr->ind_rec.sw_buffer_size =
|
|
|
|
snd_pcm_lib_buffer_bytes(substream);
|
|
|
|
ml403_ac97cr->ind_rec.min_periods = -1;
|
|
|
|
ml403_ac97cr->ind_rec.min_multiple =
|
|
|
|
snd_pcm_lib_period_bytes(substream) / (CR_FIFO_SIZE / 2);
|
|
|
|
PDEBUG(WORK_INFO, "prepare(): hw_buffer_size=%d, "
|
|
|
|
"sw_buffer_size=%d, min_multiple=%d\n",
|
|
|
|
CR_FIFO_SIZE, ml403_ac97cr->ind_rec.sw_buffer_size,
|
|
|
|
ml403_ac97cr->ind_rec.min_multiple);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
snd_ml403_ac97cr_pcm_capture_prepare(struct snd_pcm_substream *substream)
|
|
|
|
{
|
|
|
|
struct snd_ml403_ac97cr *ml403_ac97cr;
|
|
|
|
struct snd_pcm_runtime *runtime;
|
|
|
|
|
|
|
|
ml403_ac97cr = snd_pcm_substream_chip(substream);
|
|
|
|
runtime = substream->runtime;
|
|
|
|
|
|
|
|
PDEBUG(WORK_INFO,
|
|
|
|
"prepare(capture): period_bytes=%d, minperiod_bytes=%d\n",
|
|
|
|
snd_pcm_lib_period_bytes(substream), CR_FIFO_SIZE / 2);
|
|
|
|
|
|
|
|
/* set sampling rate */
|
|
|
|
snd_ac97_set_rate(ml403_ac97cr->ac97, AC97_PCM_LR_ADC_RATE,
|
|
|
|
runtime->rate);
|
|
|
|
PDEBUG(WORK_INFO, "prepare(capture): rate=%d\n", runtime->rate);
|
|
|
|
|
|
|
|
/* init struct for intermediate buffer */
|
|
|
|
memset(&ml403_ac97cr->capture_ind2_rec, 0,
|
|
|
|
sizeof(struct snd_pcm_indirect2));
|
|
|
|
ml403_ac97cr->capture_ind2_rec.hw_buffer_size = CR_FIFO_SIZE;
|
|
|
|
ml403_ac97cr->capture_ind2_rec.sw_buffer_size =
|
|
|
|
snd_pcm_lib_buffer_bytes(substream);
|
|
|
|
ml403_ac97cr->capture_ind2_rec.min_multiple =
|
|
|
|
snd_pcm_lib_period_bytes(substream) / (CR_FIFO_SIZE / 2);
|
|
|
|
PDEBUG(WORK_INFO, "prepare(capture): hw_buffer_size=%d, "
|
|
|
|
"sw_buffer_size=%d, min_multiple=%d\n", CR_FIFO_SIZE,
|
|
|
|
ml403_ac97cr->capture_ind2_rec.sw_buffer_size,
|
|
|
|
ml403_ac97cr->capture_ind2_rec.min_multiple);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int snd_ml403_ac97cr_hw_free(struct snd_pcm_substream *substream)
|
|
|
|
{
|
|
|
|
PDEBUG(WORK_INFO, "hw_free()\n");
|
|
|
|
return snd_pcm_lib_free_pages(substream);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
snd_ml403_ac97cr_hw_params(struct snd_pcm_substream *substream,
|
|
|
|
struct snd_pcm_hw_params *hw_params)
|
|
|
|
{
|
|
|
|
PDEBUG(WORK_INFO, "hw_params(): desired buffer bytes=%d, desired "
|
|
|
|
"period bytes=%d\n",
|
|
|
|
params_buffer_bytes(hw_params), params_period_bytes(hw_params));
|
|
|
|
return snd_pcm_lib_malloc_pages(substream,
|
|
|
|
params_buffer_bytes(hw_params));
|
|
|
|
}
|
|
|
|
|
|
|
|
static int snd_ml403_ac97cr_playback_open(struct snd_pcm_substream *substream)
|
|
|
|
{
|
|
|
|
struct snd_ml403_ac97cr *ml403_ac97cr;
|
|
|
|
struct snd_pcm_runtime *runtime;
|
|
|
|
|
|
|
|
ml403_ac97cr = snd_pcm_substream_chip(substream);
|
|
|
|
runtime = substream->runtime;
|
|
|
|
|
|
|
|
PDEBUG(WORK_INFO, "open(playback)\n");
|
|
|
|
ml403_ac97cr->playback_substream = substream;
|
|
|
|
runtime->hw = snd_ml403_ac97cr_playback;
|
|
|
|
|
|
|
|
snd_pcm_hw_constraint_step(runtime, 0,
|
|
|
|
SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
|
|
|
|
CR_FIFO_SIZE / 2);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int snd_ml403_ac97cr_capture_open(struct snd_pcm_substream *substream)
|
|
|
|
{
|
|
|
|
struct snd_ml403_ac97cr *ml403_ac97cr;
|
|
|
|
struct snd_pcm_runtime *runtime;
|
|
|
|
|
|
|
|
ml403_ac97cr = snd_pcm_substream_chip(substream);
|
|
|
|
runtime = substream->runtime;
|
|
|
|
|
|
|
|
PDEBUG(WORK_INFO, "open(capture)\n");
|
|
|
|
ml403_ac97cr->capture_substream = substream;
|
|
|
|
runtime->hw = snd_ml403_ac97cr_capture;
|
|
|
|
|
|
|
|
snd_pcm_hw_constraint_step(runtime, 0,
|
|
|
|
SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
|
|
|
|
CR_FIFO_SIZE / 2);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int snd_ml403_ac97cr_playback_close(struct snd_pcm_substream *substream)
|
|
|
|
{
|
|
|
|
struct snd_ml403_ac97cr *ml403_ac97cr;
|
|
|
|
|
|
|
|
ml403_ac97cr = snd_pcm_substream_chip(substream);
|
|
|
|
|
|
|
|
PDEBUG(WORK_INFO, "close(playback)\n");
|
|
|
|
ml403_ac97cr->playback_substream = NULL;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int snd_ml403_ac97cr_capture_close(struct snd_pcm_substream *substream)
|
|
|
|
{
|
|
|
|
struct snd_ml403_ac97cr *ml403_ac97cr;
|
|
|
|
|
|
|
|
ml403_ac97cr = snd_pcm_substream_chip(substream);
|
|
|
|
|
|
|
|
PDEBUG(WORK_INFO, "close(capture)\n");
|
|
|
|
ml403_ac97cr->capture_substream = NULL;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-08-18 15:45:12 +08:00
|
|
|
static const struct snd_pcm_ops snd_ml403_ac97cr_playback_ops = {
|
2007-11-05 23:06:01 +08:00
|
|
|
.open = snd_ml403_ac97cr_playback_open,
|
|
|
|
.close = snd_ml403_ac97cr_playback_close,
|
|
|
|
.ioctl = snd_pcm_lib_ioctl,
|
|
|
|
.hw_params = snd_ml403_ac97cr_hw_params,
|
|
|
|
.hw_free = snd_ml403_ac97cr_hw_free,
|
|
|
|
.prepare = snd_ml403_ac97cr_pcm_playback_prepare,
|
|
|
|
.trigger = snd_ml403_ac97cr_pcm_playback_trigger,
|
|
|
|
.pointer = snd_ml403_ac97cr_pcm_pointer,
|
|
|
|
};
|
|
|
|
|
2017-08-18 15:45:12 +08:00
|
|
|
static const struct snd_pcm_ops snd_ml403_ac97cr_capture_ops = {
|
2007-11-05 23:06:01 +08:00
|
|
|
.open = snd_ml403_ac97cr_capture_open,
|
|
|
|
.close = snd_ml403_ac97cr_capture_close,
|
|
|
|
.ioctl = snd_pcm_lib_ioctl,
|
|
|
|
.hw_params = snd_ml403_ac97cr_hw_params,
|
|
|
|
.hw_free = snd_ml403_ac97cr_hw_free,
|
|
|
|
.prepare = snd_ml403_ac97cr_pcm_capture_prepare,
|
|
|
|
.trigger = snd_ml403_ac97cr_pcm_capture_trigger,
|
|
|
|
.pointer = snd_ml403_ac97cr_pcm_pointer,
|
|
|
|
};
|
|
|
|
|
|
|
|
static irqreturn_t snd_ml403_ac97cr_irq(int irq, void *dev_id)
|
|
|
|
{
|
|
|
|
struct snd_ml403_ac97cr *ml403_ac97cr;
|
|
|
|
struct platform_device *pfdev;
|
|
|
|
int cmp_irq;
|
|
|
|
|
|
|
|
ml403_ac97cr = (struct snd_ml403_ac97cr *)dev_id;
|
|
|
|
if (ml403_ac97cr == NULL)
|
|
|
|
return IRQ_NONE;
|
|
|
|
|
|
|
|
pfdev = ml403_ac97cr->pfdev;
|
|
|
|
|
|
|
|
/* playback interrupt */
|
|
|
|
cmp_irq = platform_get_irq(pfdev, 0);
|
|
|
|
if (irq == cmp_irq) {
|
|
|
|
if (ml403_ac97cr->enable_irq)
|
|
|
|
snd_pcm_indirect2_playback_interrupt(
|
|
|
|
ml403_ac97cr->playback_substream,
|
|
|
|
&ml403_ac97cr->ind_rec,
|
|
|
|
snd_ml403_ac97cr_playback_ind2_copy,
|
|
|
|
snd_ml403_ac97cr_playback_ind2_zero);
|
|
|
|
else
|
|
|
|
goto __disable_irq;
|
|
|
|
} else {
|
|
|
|
/* record interrupt */
|
|
|
|
cmp_irq = platform_get_irq(pfdev, 1);
|
|
|
|
if (irq == cmp_irq) {
|
|
|
|
if (ml403_ac97cr->enable_capture_irq)
|
|
|
|
snd_pcm_indirect2_capture_interrupt(
|
|
|
|
ml403_ac97cr->capture_substream,
|
|
|
|
&ml403_ac97cr->capture_ind2_rec,
|
|
|
|
snd_ml403_ac97cr_capture_ind2_copy,
|
|
|
|
snd_ml403_ac97cr_capture_ind2_null);
|
|
|
|
else
|
|
|
|
goto __disable_irq;
|
|
|
|
} else
|
|
|
|
return IRQ_NONE;
|
|
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
|
|
|
|
|
|
__disable_irq:
|
|
|
|
PDEBUG(INIT_INFO, "irq(): irq %d is meant to be disabled! So, now try "
|
|
|
|
"to disable it _really_!\n", irq);
|
|
|
|
disable_irq_nosync(irq);
|
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
|
|
|
static unsigned short
|
|
|
|
snd_ml403_ac97cr_codec_read(struct snd_ac97 *ac97, unsigned short reg)
|
|
|
|
{
|
|
|
|
struct snd_ml403_ac97cr *ml403_ac97cr = ac97->private_data;
|
|
|
|
#ifdef CODEC_STAT
|
|
|
|
u32 stat;
|
|
|
|
u32 rafaccess = 0;
|
|
|
|
#endif
|
|
|
|
unsigned long end_time;
|
|
|
|
u16 value = 0;
|
|
|
|
|
|
|
|
if (!LM4550_RF_OK(reg)) {
|
|
|
|
snd_printk(KERN_WARNING SND_ML403_AC97CR_DRIVER ": "
|
|
|
|
"access to unknown/unused codec register 0x%x "
|
|
|
|
"ignored!\n", reg);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
/* check if we can fake/answer this access from our shadow register */
|
|
|
|
if ((lm4550_regfile[reg / 2].flag &
|
|
|
|
(LM4550_REG_DONEREAD | LM4550_REG_ALLFAKE)) &&
|
|
|
|
!(lm4550_regfile[reg / 2].flag & LM4550_REG_NOSHADOW)) {
|
|
|
|
if (lm4550_regfile[reg / 2].flag & LM4550_REG_FAKEREAD) {
|
|
|
|
PDEBUG(CODEC_FAKE, "codec_read(): faking read from "
|
|
|
|
"reg=0x%x, val=0x%x / %d\n",
|
|
|
|
reg, lm4550_regfile[reg / 2].def,
|
|
|
|
lm4550_regfile[reg / 2].def);
|
|
|
|
return lm4550_regfile[reg / 2].def;
|
|
|
|
} else if ((lm4550_regfile[reg / 2].flag &
|
|
|
|
LM4550_REG_FAKEPROBE) &&
|
|
|
|
ml403_ac97cr->ac97_fake) {
|
|
|
|
PDEBUG(CODEC_FAKE, "codec_read(): faking read from "
|
|
|
|
"reg=0x%x, val=0x%x / %d (probe)\n",
|
|
|
|
reg, lm4550_regfile[reg / 2].value,
|
|
|
|
lm4550_regfile[reg / 2].value);
|
|
|
|
return lm4550_regfile[reg / 2].value;
|
|
|
|
} else {
|
|
|
|
#ifdef CODEC_STAT
|
|
|
|
PDEBUG(CODEC_FAKE, "codec_read(): read access "
|
|
|
|
"answered by shadow register 0x%x (value=0x%x "
|
|
|
|
"/ %d) (cw=%d cr=%d)\n",
|
|
|
|
reg, lm4550_regfile[reg / 2].value,
|
|
|
|
lm4550_regfile[reg / 2].value,
|
|
|
|
ml403_ac97cr->ac97_write,
|
|
|
|
ml403_ac97cr->ac97_read);
|
|
|
|
#else
|
|
|
|
PDEBUG(CODEC_FAKE, "codec_read(): read access "
|
|
|
|
"answered by shadow register 0x%x (value=0x%x "
|
|
|
|
"/ %d)\n",
|
|
|
|
reg, lm4550_regfile[reg / 2].value,
|
|
|
|
lm4550_regfile[reg / 2].value);
|
|
|
|
#endif
|
|
|
|
return lm4550_regfile[reg / 2].value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* if we are here, we _have_ to access the codec really, no faking */
|
|
|
|
if (mutex_lock_interruptible(&ml403_ac97cr->cdc_mutex) != 0)
|
|
|
|
return 0;
|
|
|
|
#ifdef CODEC_STAT
|
|
|
|
ml403_ac97cr->ac97_read++;
|
|
|
|
#endif
|
|
|
|
spin_lock(&ml403_ac97cr->reg_lock);
|
|
|
|
out_be32(CR_REG(ml403_ac97cr, CODEC_ADDR),
|
|
|
|
CR_CODEC_ADDR(reg) | CR_CODEC_READ);
|
|
|
|
spin_unlock(&ml403_ac97cr->reg_lock);
|
|
|
|
end_time = jiffies + (HZ / CODEC_TIMEOUT_AFTER_READ);
|
|
|
|
do {
|
|
|
|
spin_lock(&ml403_ac97cr->reg_lock);
|
|
|
|
#ifdef CODEC_STAT
|
|
|
|
rafaccess++;
|
|
|
|
stat = in_be32(CR_REG(ml403_ac97cr, STATUS));
|
|
|
|
if ((stat & CR_RAF) == CR_RAF) {
|
|
|
|
value = CR_CODEC_DATAREAD(
|
|
|
|
in_be32(CR_REG(ml403_ac97cr, CODEC_DATAREAD)));
|
|
|
|
PDEBUG(CODEC_SUCCESS, "codec_read(): (done) reg=0x%x, "
|
|
|
|
"value=0x%x / %d (STATUS=0x%x)\n",
|
|
|
|
reg, value, value, stat);
|
|
|
|
#else
|
|
|
|
if ((in_be32(CR_REG(ml403_ac97cr, STATUS)) &
|
|
|
|
CR_RAF) == CR_RAF) {
|
|
|
|
value = CR_CODEC_DATAREAD(
|
|
|
|
in_be32(CR_REG(ml403_ac97cr, CODEC_DATAREAD)));
|
|
|
|
PDEBUG(CODEC_SUCCESS, "codec_read(): (done) "
|
|
|
|
"reg=0x%x, value=0x%x / %d\n",
|
|
|
|
reg, value, value);
|
|
|
|
#endif
|
|
|
|
lm4550_regfile[reg / 2].value = value;
|
|
|
|
lm4550_regfile[reg / 2].flag |= LM4550_REG_DONEREAD;
|
|
|
|
spin_unlock(&ml403_ac97cr->reg_lock);
|
|
|
|
mutex_unlock(&ml403_ac97cr->cdc_mutex);
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
spin_unlock(&ml403_ac97cr->reg_lock);
|
|
|
|
schedule_timeout_uninterruptible(1);
|
|
|
|
} while (time_after(end_time, jiffies));
|
|
|
|
/* read the DATAREAD register anyway, see comment below */
|
|
|
|
spin_lock(&ml403_ac97cr->reg_lock);
|
|
|
|
value =
|
|
|
|
CR_CODEC_DATAREAD(in_be32(CR_REG(ml403_ac97cr, CODEC_DATAREAD)));
|
|
|
|
spin_unlock(&ml403_ac97cr->reg_lock);
|
|
|
|
#ifdef CODEC_STAT
|
|
|
|
snd_printk(KERN_WARNING SND_ML403_AC97CR_DRIVER ": "
|
|
|
|
"timeout while codec read! "
|
|
|
|
"(reg=0x%x, last STATUS=0x%x, DATAREAD=0x%x / %d, %d) "
|
|
|
|
"(cw=%d, cr=%d)\n",
|
|
|
|
reg, stat, value, value, rafaccess,
|
|
|
|
ml403_ac97cr->ac97_write, ml403_ac97cr->ac97_read);
|
|
|
|
#else
|
|
|
|
snd_printk(KERN_WARNING SND_ML403_AC97CR_DRIVER ": "
|
|
|
|
"timeout while codec read! "
|
|
|
|
"(reg=0x%x, DATAREAD=0x%x / %d)\n",
|
|
|
|
reg, value, value);
|
|
|
|
#endif
|
|
|
|
/* BUG: This is PURE speculation! But after _most_ read timeouts the
|
|
|
|
* value in the register is ok!
|
|
|
|
*/
|
|
|
|
lm4550_regfile[reg / 2].value = value;
|
|
|
|
lm4550_regfile[reg / 2].flag |= LM4550_REG_DONEREAD;
|
|
|
|
mutex_unlock(&ml403_ac97cr->cdc_mutex);
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
snd_ml403_ac97cr_codec_write(struct snd_ac97 *ac97, unsigned short reg,
|
|
|
|
unsigned short val)
|
|
|
|
{
|
|
|
|
struct snd_ml403_ac97cr *ml403_ac97cr = ac97->private_data;
|
|
|
|
|
|
|
|
#ifdef CODEC_STAT
|
|
|
|
u32 stat;
|
|
|
|
u32 rafaccess = 0;
|
|
|
|
#endif
|
|
|
|
#ifdef CODEC_WRITE_CHECK_RAF
|
|
|
|
unsigned long end_time;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (!LM4550_RF_OK(reg)) {
|
|
|
|
snd_printk(KERN_WARNING SND_ML403_AC97CR_DRIVER ": "
|
|
|
|
"access to unknown/unused codec register 0x%x "
|
|
|
|
"ignored!\n", reg);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (lm4550_regfile[reg / 2].flag & LM4550_REG_READONLY) {
|
|
|
|
snd_printk(KERN_WARNING SND_ML403_AC97CR_DRIVER ": "
|
|
|
|
"write access to read only codec register 0x%x "
|
|
|
|
"ignored!\n", reg);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if ((val & lm4550_regfile[reg / 2].wmask) != val) {
|
|
|
|
snd_printk(KERN_WARNING SND_ML403_AC97CR_DRIVER ": "
|
|
|
|
"write access to codec register 0x%x "
|
|
|
|
"with bad value 0x%x / %d!\n",
|
|
|
|
reg, val, val);
|
|
|
|
val = val & lm4550_regfile[reg / 2].wmask;
|
|
|
|
}
|
|
|
|
if (((lm4550_regfile[reg / 2].flag & LM4550_REG_FAKEPROBE) &&
|
|
|
|
ml403_ac97cr->ac97_fake) &&
|
|
|
|
!(lm4550_regfile[reg / 2].flag & LM4550_REG_NOSHADOW)) {
|
|
|
|
PDEBUG(CODEC_FAKE, "codec_write(): faking write to reg=0x%x, "
|
|
|
|
"val=0x%x / %d\n", reg, val, val);
|
|
|
|
lm4550_regfile[reg / 2].value = (val &
|
|
|
|
lm4550_regfile[reg / 2].wmask);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (mutex_lock_interruptible(&ml403_ac97cr->cdc_mutex) != 0)
|
|
|
|
return;
|
|
|
|
#ifdef CODEC_STAT
|
|
|
|
ml403_ac97cr->ac97_write++;
|
|
|
|
#endif
|
|
|
|
spin_lock(&ml403_ac97cr->reg_lock);
|
|
|
|
out_be32(CR_REG(ml403_ac97cr, CODEC_DATAWRITE),
|
|
|
|
CR_CODEC_DATAWRITE(val));
|
|
|
|
out_be32(CR_REG(ml403_ac97cr, CODEC_ADDR),
|
|
|
|
CR_CODEC_ADDR(reg) | CR_CODEC_WRITE);
|
|
|
|
spin_unlock(&ml403_ac97cr->reg_lock);
|
|
|
|
#ifdef CODEC_WRITE_CHECK_RAF
|
|
|
|
/* check CR_CODEC_RAF bit to see if write access to register is done;
|
|
|
|
* loop until bit is set or timeout happens
|
|
|
|
*/
|
|
|
|
end_time = jiffies + HZ / CODEC_TIMEOUT_AFTER_WRITE;
|
|
|
|
do {
|
|
|
|
spin_lock(&ml403_ac97cr->reg_lock);
|
|
|
|
#ifdef CODEC_STAT
|
|
|
|
rafaccess++;
|
|
|
|
stat = in_be32(CR_REG(ml403_ac97cr, STATUS))
|
|
|
|
if ((stat & CR_RAF) == CR_RAF) {
|
|
|
|
#else
|
|
|
|
if ((in_be32(CR_REG(ml403_ac97cr, STATUS)) &
|
|
|
|
CR_RAF) == CR_RAF) {
|
|
|
|
#endif
|
|
|
|
PDEBUG(CODEC_SUCCESS, "codec_write(): (done) "
|
|
|
|
"reg=0x%x, value=%d / 0x%x\n",
|
|
|
|
reg, val, val);
|
|
|
|
if (!(lm4550_regfile[reg / 2].flag &
|
|
|
|
LM4550_REG_NOSHADOW) &&
|
|
|
|
!(lm4550_regfile[reg / 2].flag &
|
|
|
|
LM4550_REG_NOSAVE))
|
|
|
|
lm4550_regfile[reg / 2].value = val;
|
|
|
|
lm4550_regfile[reg / 2].flag |= LM4550_REG_DONEREAD;
|
|
|
|
spin_unlock(&ml403_ac97cr->reg_lock);
|
|
|
|
mutex_unlock(&ml403_ac97cr->cdc_mutex);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
spin_unlock(&ml403_ac97cr->reg_lock);
|
|
|
|
schedule_timeout_uninterruptible(1);
|
|
|
|
} while (time_after(end_time, jiffies));
|
|
|
|
#ifdef CODEC_STAT
|
|
|
|
snd_printk(KERN_WARNING SND_ML403_AC97CR_DRIVER ": "
|
|
|
|
"timeout while codec write "
|
|
|
|
"(reg=0x%x, val=0x%x / %d, last STATUS=0x%x, %d) "
|
|
|
|
"(cw=%d, cr=%d)\n",
|
|
|
|
reg, val, val, stat, rafaccess, ml403_ac97cr->ac97_write,
|
|
|
|
ml403_ac97cr->ac97_read);
|
|
|
|
#else
|
|
|
|
snd_printk(KERN_WARNING SND_ML403_AC97CR_DRIVER ": "
|
|
|
|
"timeout while codec write (reg=0x%x, val=0x%x / %d)\n",
|
|
|
|
reg, val, val);
|
|
|
|
#endif
|
|
|
|
#else /* CODEC_WRITE_CHECK_RAF */
|
|
|
|
#if CODEC_WAIT_AFTER_WRITE > 0
|
|
|
|
/* officially, in AC97 spec there is no possibility for a AC97
|
|
|
|
* controller to determine, if write access is done or not - so: How
|
|
|
|
* is Xilinx able to provide a RAF bit for write access?
|
|
|
|
* => very strange, thus just don't check RAF bit (compare with
|
|
|
|
* Xilinx's example app in EDK 8.1i) and wait
|
|
|
|
*/
|
|
|
|
schedule_timeout_uninterruptible(HZ / CODEC_WAIT_AFTER_WRITE);
|
|
|
|
#endif
|
|
|
|
PDEBUG(CODEC_SUCCESS, "codec_write(): (done) "
|
|
|
|
"reg=0x%x, value=%d / 0x%x (no RAF check)\n",
|
|
|
|
reg, val, val);
|
|
|
|
#endif
|
|
|
|
mutex_unlock(&ml403_ac97cr->cdc_mutex);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-12-07 01:35:27 +08:00
|
|
|
static int
|
2007-11-05 23:06:01 +08:00
|
|
|
snd_ml403_ac97cr_chip_init(struct snd_ml403_ac97cr *ml403_ac97cr)
|
|
|
|
{
|
|
|
|
unsigned long end_time;
|
|
|
|
PDEBUG(INIT_INFO, "chip_init():\n");
|
|
|
|
end_time = jiffies + HZ / CODEC_TIMEOUT_ON_INIT;
|
|
|
|
do {
|
|
|
|
if (in_be32(CR_REG(ml403_ac97cr, STATUS)) & CR_CODECREADY) {
|
|
|
|
/* clear both hardware FIFOs */
|
|
|
|
out_be32(CR_REG(ml403_ac97cr, RESETFIFO),
|
|
|
|
CR_RECRESET | CR_PLAYRESET);
|
|
|
|
PDEBUG(INIT_INFO, "chip_init(): (done)\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
schedule_timeout_uninterruptible(1);
|
|
|
|
} while (time_after(end_time, jiffies));
|
|
|
|
snd_printk(KERN_ERR SND_ML403_AC97CR_DRIVER ": "
|
|
|
|
"timeout while waiting for codec, "
|
|
|
|
"not ready!\n");
|
|
|
|
return -EBUSY;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int snd_ml403_ac97cr_free(struct snd_ml403_ac97cr *ml403_ac97cr)
|
|
|
|
{
|
|
|
|
PDEBUG(INIT_INFO, "free():\n");
|
|
|
|
/* irq release */
|
|
|
|
if (ml403_ac97cr->irq >= 0)
|
|
|
|
free_irq(ml403_ac97cr->irq, ml403_ac97cr);
|
|
|
|
if (ml403_ac97cr->capture_irq >= 0)
|
|
|
|
free_irq(ml403_ac97cr->capture_irq, ml403_ac97cr);
|
|
|
|
/* give back "port" */
|
2015-01-04 05:55:54 +08:00
|
|
|
iounmap(ml403_ac97cr->port);
|
2007-11-05 23:06:01 +08:00
|
|
|
kfree(ml403_ac97cr);
|
|
|
|
PDEBUG(INIT_INFO, "free(): (done)\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int snd_ml403_ac97cr_dev_free(struct snd_device *snddev)
|
|
|
|
{
|
|
|
|
struct snd_ml403_ac97cr *ml403_ac97cr = snddev->device_data;
|
|
|
|
PDEBUG(INIT_INFO, "dev_free():\n");
|
|
|
|
return snd_ml403_ac97cr_free(ml403_ac97cr);
|
|
|
|
}
|
|
|
|
|
2012-12-07 01:35:27 +08:00
|
|
|
static int
|
2007-11-05 23:06:01 +08:00
|
|
|
snd_ml403_ac97cr_create(struct snd_card *card, struct platform_device *pfdev,
|
|
|
|
struct snd_ml403_ac97cr **rml403_ac97cr)
|
|
|
|
{
|
|
|
|
struct snd_ml403_ac97cr *ml403_ac97cr;
|
|
|
|
int err;
|
|
|
|
static struct snd_device_ops ops = {
|
|
|
|
.dev_free = snd_ml403_ac97cr_dev_free,
|
|
|
|
};
|
|
|
|
struct resource *resource;
|
|
|
|
int irq;
|
|
|
|
|
|
|
|
*rml403_ac97cr = NULL;
|
|
|
|
ml403_ac97cr = kzalloc(sizeof(*ml403_ac97cr), GFP_KERNEL);
|
|
|
|
if (ml403_ac97cr == NULL)
|
|
|
|
return -ENOMEM;
|
|
|
|
spin_lock_init(&ml403_ac97cr->reg_lock);
|
|
|
|
mutex_init(&ml403_ac97cr->cdc_mutex);
|
|
|
|
ml403_ac97cr->card = card;
|
|
|
|
ml403_ac97cr->pfdev = pfdev;
|
|
|
|
ml403_ac97cr->irq = -1;
|
|
|
|
ml403_ac97cr->enable_irq = 0;
|
|
|
|
ml403_ac97cr->capture_irq = -1;
|
|
|
|
ml403_ac97cr->enable_capture_irq = 0;
|
|
|
|
ml403_ac97cr->port = NULL;
|
|
|
|
ml403_ac97cr->res_port = NULL;
|
|
|
|
|
|
|
|
PDEBUG(INIT_INFO, "Trying to reserve resources now ...\n");
|
|
|
|
resource = platform_get_resource(pfdev, IORESOURCE_MEM, 0);
|
|
|
|
/* get "port" */
|
|
|
|
ml403_ac97cr->port = ioremap_nocache(resource->start,
|
|
|
|
(resource->end) -
|
|
|
|
(resource->start) + 1);
|
|
|
|
if (ml403_ac97cr->port == NULL) {
|
|
|
|
snd_printk(KERN_ERR SND_ML403_AC97CR_DRIVER ": "
|
2010-12-14 05:42:22 +08:00
|
|
|
"unable to remap memory region (%pR)\n",
|
|
|
|
resource);
|
2007-11-05 23:06:01 +08:00
|
|
|
snd_ml403_ac97cr_free(ml403_ac97cr);
|
|
|
|
return -EBUSY;
|
|
|
|
}
|
|
|
|
snd_printk(KERN_INFO SND_ML403_AC97CR_DRIVER ": "
|
|
|
|
"remap controller memory region to "
|
|
|
|
"0x%x done\n", (unsigned int)ml403_ac97cr->port);
|
|
|
|
/* get irq */
|
|
|
|
irq = platform_get_irq(pfdev, 0);
|
2011-09-22 16:59:20 +08:00
|
|
|
if (request_irq(irq, snd_ml403_ac97cr_irq, 0,
|
2008-11-02 10:50:35 +08:00
|
|
|
dev_name(&pfdev->dev), (void *)ml403_ac97cr)) {
|
2007-11-05 23:06:01 +08:00
|
|
|
snd_printk(KERN_ERR SND_ML403_AC97CR_DRIVER ": "
|
|
|
|
"unable to grab IRQ %d\n",
|
|
|
|
irq);
|
|
|
|
snd_ml403_ac97cr_free(ml403_ac97cr);
|
|
|
|
return -EBUSY;
|
|
|
|
}
|
|
|
|
ml403_ac97cr->irq = irq;
|
|
|
|
snd_printk(KERN_INFO SND_ML403_AC97CR_DRIVER ": "
|
|
|
|
"request (playback) irq %d done\n",
|
|
|
|
ml403_ac97cr->irq);
|
|
|
|
irq = platform_get_irq(pfdev, 1);
|
2011-09-22 16:59:20 +08:00
|
|
|
if (request_irq(irq, snd_ml403_ac97cr_irq, 0,
|
2008-11-02 10:50:35 +08:00
|
|
|
dev_name(&pfdev->dev), (void *)ml403_ac97cr)) {
|
2007-11-05 23:06:01 +08:00
|
|
|
snd_printk(KERN_ERR SND_ML403_AC97CR_DRIVER ": "
|
|
|
|
"unable to grab IRQ %d\n",
|
|
|
|
irq);
|
|
|
|
snd_ml403_ac97cr_free(ml403_ac97cr);
|
|
|
|
return -EBUSY;
|
|
|
|
}
|
|
|
|
ml403_ac97cr->capture_irq = irq;
|
|
|
|
snd_printk(KERN_INFO SND_ML403_AC97CR_DRIVER ": "
|
|
|
|
"request (capture) irq %d done\n",
|
|
|
|
ml403_ac97cr->capture_irq);
|
|
|
|
|
|
|
|
err = snd_ml403_ac97cr_chip_init(ml403_ac97cr);
|
|
|
|
if (err < 0) {
|
|
|
|
snd_ml403_ac97cr_free(ml403_ac97cr);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, ml403_ac97cr, &ops);
|
|
|
|
if (err < 0) {
|
|
|
|
PDEBUG(INIT_FAILURE, "probe(): snd_device_new() failed!\n");
|
|
|
|
snd_ml403_ac97cr_free(ml403_ac97cr);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
*rml403_ac97cr = ml403_ac97cr;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void snd_ml403_ac97cr_mixer_free(struct snd_ac97 *ac97)
|
|
|
|
{
|
|
|
|
struct snd_ml403_ac97cr *ml403_ac97cr = ac97->private_data;
|
|
|
|
PDEBUG(INIT_INFO, "mixer_free():\n");
|
|
|
|
ml403_ac97cr->ac97 = NULL;
|
|
|
|
PDEBUG(INIT_INFO, "mixer_free(): (done)\n");
|
|
|
|
}
|
|
|
|
|
2012-12-07 01:35:27 +08:00
|
|
|
static int
|
2007-11-05 23:06:01 +08:00
|
|
|
snd_ml403_ac97cr_mixer(struct snd_ml403_ac97cr *ml403_ac97cr)
|
|
|
|
{
|
|
|
|
struct snd_ac97_bus *bus;
|
|
|
|
struct snd_ac97_template ac97;
|
|
|
|
int err;
|
|
|
|
static struct snd_ac97_bus_ops ops = {
|
|
|
|
.write = snd_ml403_ac97cr_codec_write,
|
|
|
|
.read = snd_ml403_ac97cr_codec_read,
|
|
|
|
};
|
|
|
|
PDEBUG(INIT_INFO, "mixer():\n");
|
|
|
|
err = snd_ac97_bus(ml403_ac97cr->card, 0, &ops, NULL, &bus);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
memset(&ac97, 0, sizeof(ac97));
|
|
|
|
ml403_ac97cr->ac97_fake = 1;
|
|
|
|
lm4550_regfile_init();
|
|
|
|
#ifdef CODEC_STAT
|
|
|
|
ml403_ac97cr->ac97_read = 0;
|
|
|
|
ml403_ac97cr->ac97_write = 0;
|
|
|
|
#endif
|
|
|
|
ac97.private_data = ml403_ac97cr;
|
|
|
|
ac97.private_free = snd_ml403_ac97cr_mixer_free;
|
|
|
|
ac97.scaps = AC97_SCAP_AUDIO | AC97_SCAP_SKIP_MODEM |
|
|
|
|
AC97_SCAP_NO_SPDIF;
|
|
|
|
err = snd_ac97_mixer(bus, &ac97, &ml403_ac97cr->ac97);
|
|
|
|
ml403_ac97cr->ac97_fake = 0;
|
|
|
|
lm4550_regfile_write_values_after_init(ml403_ac97cr->ac97);
|
|
|
|
PDEBUG(INIT_INFO, "mixer(): (done) snd_ac97_mixer()=%d\n", err);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2012-12-07 01:35:27 +08:00
|
|
|
static int
|
2015-01-02 19:24:35 +08:00
|
|
|
snd_ml403_ac97cr_pcm(struct snd_ml403_ac97cr *ml403_ac97cr, int device)
|
2007-11-05 23:06:01 +08:00
|
|
|
{
|
|
|
|
struct snd_pcm *pcm;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
err = snd_pcm_new(ml403_ac97cr->card, "ML403AC97CR/1", device, 1, 1,
|
|
|
|
&pcm);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
|
|
|
|
&snd_ml403_ac97cr_playback_ops);
|
|
|
|
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
|
|
|
|
&snd_ml403_ac97cr_capture_ops);
|
|
|
|
pcm->private_data = ml403_ac97cr;
|
|
|
|
pcm->info_flags = 0;
|
|
|
|
strcpy(pcm->name, "ML403AC97CR DAC/ADC");
|
|
|
|
ml403_ac97cr->pcm = pcm;
|
|
|
|
|
|
|
|
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
|
|
|
|
snd_dma_continuous_data(GFP_KERNEL),
|
|
|
|
64 * 1024,
|
|
|
|
128 * 1024);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-12-07 01:35:27 +08:00
|
|
|
static int snd_ml403_ac97cr_probe(struct platform_device *pfdev)
|
2007-11-05 23:06:01 +08:00
|
|
|
{
|
|
|
|
struct snd_card *card;
|
|
|
|
struct snd_ml403_ac97cr *ml403_ac97cr = NULL;
|
|
|
|
int err;
|
|
|
|
int dev = pfdev->id;
|
|
|
|
|
|
|
|
if (dev >= SNDRV_CARDS)
|
|
|
|
return -ENODEV;
|
|
|
|
if (!enable[dev])
|
|
|
|
return -ENOENT;
|
|
|
|
|
2014-01-29 19:59:08 +08:00
|
|
|
err = snd_card_new(&pfdev->dev, index[dev], id[dev], THIS_MODULE,
|
|
|
|
0, &card);
|
2008-12-28 23:45:02 +08:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
2007-11-05 23:06:01 +08:00
|
|
|
err = snd_ml403_ac97cr_create(card, pfdev, &ml403_ac97cr);
|
|
|
|
if (err < 0) {
|
|
|
|
PDEBUG(INIT_FAILURE, "probe(): create failed!\n");
|
|
|
|
snd_card_free(card);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
PDEBUG(INIT_INFO, "probe(): create done\n");
|
|
|
|
card->private_data = ml403_ac97cr;
|
|
|
|
err = snd_ml403_ac97cr_mixer(ml403_ac97cr);
|
|
|
|
if (err < 0) {
|
|
|
|
snd_card_free(card);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
PDEBUG(INIT_INFO, "probe(): mixer done\n");
|
2015-01-02 19:24:35 +08:00
|
|
|
err = snd_ml403_ac97cr_pcm(ml403_ac97cr, 0);
|
2007-11-05 23:06:01 +08:00
|
|
|
if (err < 0) {
|
|
|
|
snd_card_free(card);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
PDEBUG(INIT_INFO, "probe(): PCM done\n");
|
|
|
|
strcpy(card->driver, SND_ML403_AC97CR_DRIVER);
|
|
|
|
strcpy(card->shortname, "ML403 AC97 Controller Reference");
|
|
|
|
sprintf(card->longname, "%s %s at 0x%lx, irq %i & %i, device %i",
|
|
|
|
card->shortname, card->driver,
|
|
|
|
(unsigned long)ml403_ac97cr->port, ml403_ac97cr->irq,
|
|
|
|
ml403_ac97cr->capture_irq, dev + 1);
|
|
|
|
|
|
|
|
err = snd_card_register(card);
|
|
|
|
if (err < 0) {
|
|
|
|
snd_card_free(card);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
platform_set_drvdata(pfdev, card);
|
|
|
|
PDEBUG(INIT_INFO, "probe(): (done)\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int snd_ml403_ac97cr_remove(struct platform_device *pfdev)
|
|
|
|
{
|
|
|
|
snd_card_free(platform_get_drvdata(pfdev));
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-04-14 19:33:36 +08:00
|
|
|
/* work with hotplug and coldplug */
|
|
|
|
MODULE_ALIAS("platform:" SND_ML403_AC97CR_DRIVER);
|
|
|
|
|
2007-11-05 23:06:01 +08:00
|
|
|
static struct platform_driver snd_ml403_ac97cr_driver = {
|
|
|
|
.probe = snd_ml403_ac97cr_probe,
|
|
|
|
.remove = snd_ml403_ac97cr_remove,
|
|
|
|
.driver = {
|
|
|
|
.name = SND_ML403_AC97CR_DRIVER,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2011-11-27 16:36:04 +08:00
|
|
|
module_platform_driver(snd_ml403_ac97cr_driver);
|