2009-08-20 20:01:05 +08:00
|
|
|
/*
|
|
|
|
* Fifo-attached Serial Interface (FSI) support for SH7724
|
|
|
|
*
|
|
|
|
* Copyright (C) 2009 Renesas Solutions Corp.
|
|
|
|
* Kuninori Morimoto <morimoto.kuninori@renesas.com>
|
|
|
|
*
|
|
|
|
* Based on ssi.c
|
|
|
|
* Copyright (c) 2007 Manuel Lauss <mano@roarinelk.homelinux.net>
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
|
|
* published by the Free Software Foundation.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/delay.h>
|
2009-11-30 19:24:48 +08:00
|
|
|
#include <linux/pm_runtime.h>
|
2009-08-20 20:01:05 +08:00
|
|
|
#include <linux/io.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>
|
2011-07-16 00:38:28 +08:00
|
|
|
#include <linux/module.h>
|
2009-08-20 20:01:05 +08:00
|
|
|
#include <sound/soc.h>
|
|
|
|
#include <sound/sh_fsi.h>
|
|
|
|
|
2010-12-03 16:37:55 +08:00
|
|
|
/* PortA/PortB register */
|
|
|
|
#define REG_DO_FMT 0x0000
|
|
|
|
#define REG_DOFF_CTL 0x0004
|
|
|
|
#define REG_DOFF_ST 0x0008
|
|
|
|
#define REG_DI_FMT 0x000C
|
|
|
|
#define REG_DIFF_CTL 0x0010
|
|
|
|
#define REG_DIFF_ST 0x0014
|
|
|
|
#define REG_CKG1 0x0018
|
|
|
|
#define REG_CKG2 0x001C
|
|
|
|
#define REG_DIDT 0x0020
|
|
|
|
#define REG_DODT 0x0024
|
|
|
|
#define REG_MUTE_ST 0x0028
|
2011-11-07 14:05:25 +08:00
|
|
|
#define REG_OUT_DMAC 0x002C
|
2010-12-03 16:37:55 +08:00
|
|
|
#define REG_OUT_SEL 0x0030
|
2011-11-07 14:05:25 +08:00
|
|
|
#define REG_IN_DMAC 0x0038
|
2010-03-25 18:15:53 +08:00
|
|
|
|
2010-12-03 16:38:03 +08:00
|
|
|
/* master register */
|
|
|
|
#define MST_CLK_RST 0x0210
|
|
|
|
#define MST_SOFT_RST 0x0214
|
|
|
|
#define MST_FIFO_SZ 0x0218
|
|
|
|
|
|
|
|
/* core register (depend on FSI version) */
|
2010-07-29 15:48:32 +08:00
|
|
|
#define A_MST_CTLR 0x0180
|
|
|
|
#define B_MST_CTLR 0x01A0
|
2010-03-25 18:15:53 +08:00
|
|
|
#define CPU_INT_ST 0x01F4
|
|
|
|
#define CPU_IEMSK 0x01F8
|
|
|
|
#define CPU_IMSK 0x01FC
|
2009-08-20 20:01:05 +08:00
|
|
|
#define INT_ST 0x0200
|
|
|
|
#define IEMSK 0x0204
|
|
|
|
#define IMSK 0x0208
|
|
|
|
|
|
|
|
/* DO_FMT */
|
|
|
|
/* DI_FMT */
|
2010-12-03 16:36:24 +08:00
|
|
|
#define CR_BWS_24 (0x0 << 20) /* FSI2 */
|
|
|
|
#define CR_BWS_16 (0x1 << 20) /* FSI2 */
|
|
|
|
#define CR_BWS_20 (0x2 << 20) /* FSI2 */
|
|
|
|
|
|
|
|
#define CR_DTMD_PCM (0x0 << 8) /* FSI2 */
|
|
|
|
#define CR_DTMD_SPDIF_PCM (0x1 << 8) /* FSI2 */
|
|
|
|
#define CR_DTMD_SPDIF_STREAM (0x2 << 8) /* FSI2 */
|
|
|
|
|
2010-07-13 11:13:00 +08:00
|
|
|
#define CR_MONO (0x0 << 4)
|
|
|
|
#define CR_MONO_D (0x1 << 4)
|
|
|
|
#define CR_PCM (0x2 << 4)
|
|
|
|
#define CR_I2S (0x3 << 4)
|
|
|
|
#define CR_TDM (0x4 << 4)
|
|
|
|
#define CR_TDM_D (0x5 << 4)
|
2009-08-20 20:01:05 +08:00
|
|
|
|
|
|
|
/* DOFF_CTL */
|
|
|
|
/* DIFF_CTL */
|
|
|
|
#define IRQ_HALF 0x00100000
|
|
|
|
#define FIFO_CLR 0x00000001
|
|
|
|
|
|
|
|
/* DOFF_ST */
|
|
|
|
#define ERR_OVER 0x00000010
|
|
|
|
#define ERR_UNDER 0x00000001
|
2009-12-28 13:09:16 +08:00
|
|
|
#define ST_ERR (ERR_OVER | ERR_UNDER)
|
2009-08-20 20:01:05 +08:00
|
|
|
|
2010-07-13 11:13:14 +08:00
|
|
|
/* CKG1 */
|
|
|
|
#define ACKMD_MASK 0x00007000
|
|
|
|
#define BPFMD_MASK 0x00000700
|
2011-01-20 10:46:02 +08:00
|
|
|
#define DIMD (1 << 4)
|
|
|
|
#define DOMD (1 << 0)
|
2010-07-13 11:13:14 +08:00
|
|
|
|
2010-07-29 15:48:32 +08:00
|
|
|
/* A/B MST_CTLR */
|
|
|
|
#define BP (1 << 4) /* Fix the signal of Biphase output */
|
|
|
|
#define SE (1 << 0) /* Fix the master clock */
|
|
|
|
|
2009-08-20 20:01:05 +08:00
|
|
|
/* CLK_RST */
|
2011-04-21 09:33:52 +08:00
|
|
|
#define CRB (1 << 4)
|
|
|
|
#define CRA (1 << 0)
|
2009-08-20 20:01:05 +08:00
|
|
|
|
2010-10-12 10:40:53 +08:00
|
|
|
/* IO SHIFT / MACRO */
|
|
|
|
#define BI_SHIFT 12
|
|
|
|
#define BO_SHIFT 8
|
|
|
|
#define AI_SHIFT 4
|
|
|
|
#define AO_SHIFT 0
|
|
|
|
#define AB_IO(param, shift) (param << shift)
|
2009-08-20 20:01:05 +08:00
|
|
|
|
2010-03-24 14:27:24 +08:00
|
|
|
/* SOFT_RST */
|
|
|
|
#define PBSR (1 << 12) /* Port B Software Reset */
|
|
|
|
#define PASR (1 << 8) /* Port A Software Reset */
|
|
|
|
#define IR (1 << 4) /* Interrupt Reset */
|
|
|
|
#define FSISR (1 << 0) /* Software Reset */
|
|
|
|
|
2010-12-03 16:36:24 +08:00
|
|
|
/* OUT_SEL (FSI2) */
|
|
|
|
#define DMMD (1 << 4) /* SPDIF output timing 0: Biphase only */
|
|
|
|
/* 1: Biphase and serial */
|
|
|
|
|
2010-03-25 18:15:51 +08:00
|
|
|
/* FIFO_SZ */
|
2010-10-12 10:40:53 +08:00
|
|
|
#define FIFO_SZ_MASK 0x7
|
2010-03-25 18:15:51 +08:00
|
|
|
|
2009-08-20 20:01:05 +08:00
|
|
|
#define FSI_RATES SNDRV_PCM_RATE_8000_96000
|
|
|
|
|
|
|
|
#define FSI_FMTS (SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE)
|
|
|
|
|
2011-01-24 09:42:33 +08:00
|
|
|
typedef int (*set_rate_func)(struct device *dev, int is_porta, int rate, int enable);
|
|
|
|
|
2010-09-17 12:48:45 +08:00
|
|
|
/*
|
|
|
|
* FSI driver use below type name for variable
|
|
|
|
*
|
|
|
|
* xxx_num : number of data
|
2011-05-23 19:46:03 +08:00
|
|
|
* xxx_pos : position of data
|
|
|
|
* xxx_capa : capacity of data
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* period/frame/sample image
|
|
|
|
*
|
|
|
|
* ex) PCM (2ch)
|
|
|
|
*
|
|
|
|
* period pos period pos
|
|
|
|
* [n] [n + 1]
|
|
|
|
* |<-------------------- period--------------------->|
|
|
|
|
* ==|============================================ ... =|==
|
|
|
|
* | |
|
|
|
|
* ||<----- frame ----->|<------ frame ----->| ... |
|
|
|
|
* |+--------------------+--------------------+- ... |
|
|
|
|
* ||[ sample ][ sample ]|[ sample ][ sample ]| ... |
|
|
|
|
* |+--------------------+--------------------+- ... |
|
|
|
|
* ==|============================================ ... =|==
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* FSI FIFO image
|
|
|
|
*
|
|
|
|
* | |
|
|
|
|
* | |
|
|
|
|
* | [ sample ] |
|
|
|
|
* | [ sample ] |
|
|
|
|
* | [ sample ] |
|
|
|
|
* | [ sample ] |
|
|
|
|
* --> go to codecs
|
2010-09-17 12:48:45 +08:00
|
|
|
*/
|
|
|
|
|
2010-09-17 12:48:17 +08:00
|
|
|
/*
|
|
|
|
* struct
|
|
|
|
*/
|
2009-08-20 20:01:05 +08:00
|
|
|
|
2010-10-12 18:19:28 +08:00
|
|
|
struct fsi_stream {
|
2009-08-20 20:01:05 +08:00
|
|
|
struct snd_pcm_substream *substream;
|
|
|
|
|
2011-05-23 19:46:03 +08:00
|
|
|
int fifo_sample_capa; /* sample capacity of FSI FIFO */
|
|
|
|
int buff_sample_capa; /* sample capacity of ALSA buffer */
|
|
|
|
int buff_sample_pos; /* sample position of ALSA buffer */
|
|
|
|
int period_samples; /* sample number / 1 period */
|
|
|
|
int period_pos; /* current period position */
|
2012-02-03 16:50:09 +08:00
|
|
|
int sample_width; /* sample width */
|
2010-12-17 11:55:22 +08:00
|
|
|
|
|
|
|
int uerr_num;
|
|
|
|
int oerr_num;
|
2010-10-12 18:19:28 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
struct fsi_priv {
|
|
|
|
void __iomem *base;
|
|
|
|
struct fsi_master *master;
|
|
|
|
|
|
|
|
struct fsi_stream playback;
|
|
|
|
struct fsi_stream capture;
|
2010-07-29 15:48:32 +08:00
|
|
|
|
2011-05-23 19:46:07 +08:00
|
|
|
u32 do_fmt;
|
|
|
|
u32 di_fmt;
|
|
|
|
|
2011-04-21 09:33:36 +08:00
|
|
|
int chan_num:16;
|
|
|
|
int clk_master:1;
|
2011-05-23 19:46:07 +08:00
|
|
|
int spdif:1;
|
2011-04-21 09:33:36 +08:00
|
|
|
|
2010-11-24 10:44:06 +08:00
|
|
|
long rate;
|
2009-08-20 20:01:05 +08:00
|
|
|
};
|
|
|
|
|
2010-07-13 11:13:04 +08:00
|
|
|
struct fsi_core {
|
|
|
|
int ver;
|
|
|
|
|
2010-03-25 18:15:53 +08:00
|
|
|
u32 int_st;
|
|
|
|
u32 iemsk;
|
|
|
|
u32 imsk;
|
2010-12-03 16:37:44 +08:00
|
|
|
u32 a_mclk;
|
|
|
|
u32 b_mclk;
|
2010-03-25 18:15:53 +08:00
|
|
|
};
|
|
|
|
|
2009-08-20 20:01:05 +08:00
|
|
|
struct fsi_master {
|
|
|
|
void __iomem *base;
|
|
|
|
int irq;
|
|
|
|
struct fsi_priv fsia;
|
|
|
|
struct fsi_priv fsib;
|
2010-07-13 11:13:04 +08:00
|
|
|
struct fsi_core *core;
|
2009-08-20 20:01:05 +08:00
|
|
|
struct sh_fsi_platform_info *info;
|
2010-01-28 12:46:16 +08:00
|
|
|
spinlock_t lock;
|
2009-08-20 20:01:05 +08:00
|
|
|
};
|
|
|
|
|
2010-09-17 12:48:17 +08:00
|
|
|
/*
|
|
|
|
* basic read write function
|
|
|
|
*/
|
2009-08-20 20:01:05 +08:00
|
|
|
|
2011-10-03 04:28:02 +08:00
|
|
|
static void __fsi_reg_write(u32 __iomem *reg, u32 data)
|
2009-08-20 20:01:05 +08:00
|
|
|
{
|
|
|
|
/* valid data area is 24bit */
|
|
|
|
data &= 0x00ffffff;
|
|
|
|
|
2010-02-04 00:37:23 +08:00
|
|
|
__raw_writel(data, reg);
|
2009-08-20 20:01:05 +08:00
|
|
|
}
|
|
|
|
|
2011-10-03 04:28:02 +08:00
|
|
|
static u32 __fsi_reg_read(u32 __iomem *reg)
|
2009-08-20 20:01:05 +08:00
|
|
|
{
|
2010-02-04 00:37:23 +08:00
|
|
|
return __raw_readl(reg);
|
2009-08-20 20:01:05 +08:00
|
|
|
}
|
|
|
|
|
2011-10-03 04:28:02 +08:00
|
|
|
static void __fsi_reg_mask_set(u32 __iomem *reg, u32 mask, u32 data)
|
2009-08-20 20:01:05 +08:00
|
|
|
{
|
|
|
|
u32 val = __fsi_reg_read(reg);
|
|
|
|
|
|
|
|
val &= ~mask;
|
|
|
|
val |= data & mask;
|
|
|
|
|
2010-02-04 00:37:23 +08:00
|
|
|
__fsi_reg_write(reg, val);
|
2009-08-20 20:01:05 +08:00
|
|
|
}
|
|
|
|
|
2010-12-03 16:37:55 +08:00
|
|
|
#define fsi_reg_write(p, r, d)\
|
2011-11-01 13:11:53 +08:00
|
|
|
__fsi_reg_write((p->base + REG_##r), d)
|
2009-08-20 20:01:05 +08:00
|
|
|
|
2010-12-03 16:37:55 +08:00
|
|
|
#define fsi_reg_read(p, r)\
|
2011-11-01 13:11:53 +08:00
|
|
|
__fsi_reg_read((p->base + REG_##r))
|
2009-08-20 20:01:05 +08:00
|
|
|
|
2010-12-03 16:37:55 +08:00
|
|
|
#define fsi_reg_mask_set(p, r, m, d)\
|
2011-11-01 13:11:53 +08:00
|
|
|
__fsi_reg_mask_set((p->base + REG_##r), m, d)
|
2009-08-20 20:01:05 +08:00
|
|
|
|
2010-12-03 16:38:03 +08:00
|
|
|
#define fsi_master_read(p, r) _fsi_master_read(p, MST_##r)
|
|
|
|
#define fsi_core_read(p, r) _fsi_master_read(p, p->core->r)
|
|
|
|
static u32 _fsi_master_read(struct fsi_master *master, u32 reg)
|
2009-08-20 20:01:05 +08:00
|
|
|
{
|
2010-01-28 12:46:16 +08:00
|
|
|
u32 ret;
|
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
spin_lock_irqsave(&master->lock, flags);
|
2011-10-03 04:28:02 +08:00
|
|
|
ret = __fsi_reg_read(master->base + reg);
|
2010-01-28 12:46:16 +08:00
|
|
|
spin_unlock_irqrestore(&master->lock, flags);
|
|
|
|
|
|
|
|
return ret;
|
2009-08-20 20:01:05 +08:00
|
|
|
}
|
|
|
|
|
2010-12-03 16:38:03 +08:00
|
|
|
#define fsi_master_mask_set(p, r, m, d) _fsi_master_mask_set(p, MST_##r, m, d)
|
|
|
|
#define fsi_core_mask_set(p, r, m, d) _fsi_master_mask_set(p, p->core->r, m, d)
|
|
|
|
static void _fsi_master_mask_set(struct fsi_master *master,
|
2009-12-02 14:11:08 +08:00
|
|
|
u32 reg, u32 mask, u32 data)
|
2009-08-20 20:01:05 +08:00
|
|
|
{
|
2010-01-28 12:46:16 +08:00
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
spin_lock_irqsave(&master->lock, flags);
|
2011-10-03 04:28:02 +08:00
|
|
|
__fsi_reg_mask_set(master->base + reg, mask, data);
|
2010-01-28 12:46:16 +08:00
|
|
|
spin_unlock_irqrestore(&master->lock, flags);
|
2009-08-20 20:01:05 +08:00
|
|
|
}
|
|
|
|
|
2010-09-17 12:48:17 +08:00
|
|
|
/*
|
|
|
|
* basic function
|
|
|
|
*/
|
2009-08-20 20:01:05 +08:00
|
|
|
|
2009-12-02 14:11:08 +08:00
|
|
|
static struct fsi_master *fsi_get_master(struct fsi_priv *fsi)
|
2009-08-20 20:01:05 +08:00
|
|
|
{
|
2009-12-02 14:11:08 +08:00
|
|
|
return fsi->master;
|
2009-08-20 20:01:05 +08:00
|
|
|
}
|
|
|
|
|
2011-04-21 09:33:36 +08:00
|
|
|
static int fsi_is_clk_master(struct fsi_priv *fsi)
|
|
|
|
{
|
|
|
|
return fsi->clk_master;
|
|
|
|
}
|
|
|
|
|
2009-08-20 20:01:05 +08:00
|
|
|
static int fsi_is_port_a(struct fsi_priv *fsi)
|
|
|
|
{
|
2009-12-02 14:11:08 +08:00
|
|
|
return fsi->master->base == fsi->base;
|
|
|
|
}
|
2009-08-20 20:01:05 +08:00
|
|
|
|
2011-05-23 19:46:07 +08:00
|
|
|
static int fsi_is_spdif(struct fsi_priv *fsi)
|
|
|
|
{
|
|
|
|
return fsi->spdif;
|
|
|
|
}
|
|
|
|
|
2009-12-28 13:09:11 +08:00
|
|
|
static struct snd_soc_dai *fsi_get_dai(struct snd_pcm_substream *substream)
|
2009-12-02 14:11:08 +08:00
|
|
|
{
|
|
|
|
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
2009-12-28 13:09:11 +08:00
|
|
|
|
2010-03-18 04:15:21 +08:00
|
|
|
return rtd->cpu_dai;
|
2009-12-28 13:09:11 +08:00
|
|
|
}
|
|
|
|
|
2011-01-20 10:45:51 +08:00
|
|
|
static struct fsi_priv *fsi_get_priv_frm_dai(struct snd_soc_dai *dai)
|
2009-12-28 13:09:11 +08:00
|
|
|
{
|
2010-03-18 04:15:21 +08:00
|
|
|
struct fsi_master *master = snd_soc_dai_get_drvdata(dai);
|
2009-08-20 20:01:05 +08:00
|
|
|
|
2010-03-18 04:15:21 +08:00
|
|
|
if (dai->id == 0)
|
|
|
|
return &master->fsia;
|
|
|
|
else
|
|
|
|
return &master->fsib;
|
2009-08-20 20:01:05 +08:00
|
|
|
}
|
|
|
|
|
2011-01-20 10:45:51 +08:00
|
|
|
static struct fsi_priv *fsi_get_priv(struct snd_pcm_substream *substream)
|
|
|
|
{
|
|
|
|
return fsi_get_priv_frm_dai(fsi_get_dai(substream));
|
|
|
|
}
|
|
|
|
|
2011-01-24 09:42:33 +08:00
|
|
|
static set_rate_func fsi_get_info_set_rate(struct fsi_master *master)
|
|
|
|
{
|
|
|
|
if (!master->info)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
return master->info->set_rate;
|
|
|
|
}
|
|
|
|
|
2009-08-20 20:01:05 +08:00
|
|
|
static u32 fsi_get_info_flags(struct fsi_priv *fsi)
|
|
|
|
{
|
|
|
|
int is_porta = fsi_is_port_a(fsi);
|
2009-12-02 14:11:08 +08:00
|
|
|
struct fsi_master *master = fsi_get_master(fsi);
|
2009-08-20 20:01:05 +08:00
|
|
|
|
2011-01-24 09:42:33 +08:00
|
|
|
if (!master->info)
|
|
|
|
return 0;
|
|
|
|
|
2009-08-20 20:01:05 +08:00
|
|
|
return is_porta ? master->info->porta_flags :
|
|
|
|
master->info->portb_flags;
|
|
|
|
}
|
|
|
|
|
2010-10-12 10:40:53 +08:00
|
|
|
static u32 fsi_get_port_shift(struct fsi_priv *fsi, int is_play)
|
2009-08-20 20:01:05 +08:00
|
|
|
{
|
|
|
|
int is_porta = fsi_is_port_a(fsi);
|
2010-10-12 10:40:53 +08:00
|
|
|
u32 shift;
|
2009-08-20 20:01:05 +08:00
|
|
|
|
|
|
|
if (is_porta)
|
2010-10-12 10:40:53 +08:00
|
|
|
shift = is_play ? AO_SHIFT : AI_SHIFT;
|
2009-08-20 20:01:05 +08:00
|
|
|
else
|
2010-10-12 10:40:53 +08:00
|
|
|
shift = is_play ? BO_SHIFT : BI_SHIFT;
|
2009-08-20 20:01:05 +08:00
|
|
|
|
2010-10-12 10:40:53 +08:00
|
|
|
return shift;
|
2009-08-20 20:01:05 +08:00
|
|
|
}
|
|
|
|
|
2011-05-23 19:46:03 +08:00
|
|
|
static int fsi_frame2sample(struct fsi_priv *fsi, int frames)
|
|
|
|
{
|
|
|
|
return frames * fsi->chan_num;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int fsi_sample2frame(struct fsi_priv *fsi, int samples)
|
|
|
|
{
|
|
|
|
return samples / fsi->chan_num;
|
|
|
|
}
|
|
|
|
|
2012-02-03 16:50:35 +08:00
|
|
|
static int fsi_get_current_fifo_samples(struct fsi_priv *fsi, int is_play)
|
|
|
|
{
|
|
|
|
u32 status;
|
|
|
|
int frames;
|
|
|
|
|
|
|
|
status = is_play ?
|
|
|
|
fsi_reg_read(fsi, DOFF_ST) :
|
|
|
|
fsi_reg_read(fsi, DIFF_ST);
|
|
|
|
|
|
|
|
frames = 0x1ff & (status >> 8);
|
|
|
|
|
|
|
|
return fsi_frame2sample(fsi, frames);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void fsi_count_fifo_err(struct fsi_priv *fsi)
|
|
|
|
{
|
|
|
|
u32 ostatus = fsi_reg_read(fsi, DOFF_ST);
|
|
|
|
u32 istatus = fsi_reg_read(fsi, DIFF_ST);
|
|
|
|
|
|
|
|
if (ostatus & ERR_OVER)
|
|
|
|
fsi->playback.oerr_num++;
|
|
|
|
|
|
|
|
if (ostatus & ERR_UNDER)
|
|
|
|
fsi->playback.uerr_num++;
|
|
|
|
|
|
|
|
if (istatus & ERR_OVER)
|
|
|
|
fsi->capture.oerr_num++;
|
|
|
|
|
|
|
|
if (istatus & ERR_UNDER)
|
|
|
|
fsi->capture.uerr_num++;
|
|
|
|
|
|
|
|
fsi_reg_write(fsi, DOFF_ST, 0);
|
|
|
|
fsi_reg_write(fsi, DIFF_ST, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* fsi_stream_xx() function
|
|
|
|
*/
|
|
|
|
#define fsi_is_play(substream) fsi_stream_is_play(substream->stream)
|
|
|
|
static inline int fsi_stream_is_play(int stream)
|
|
|
|
{
|
|
|
|
return stream == SNDRV_PCM_STREAM_PLAYBACK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline struct fsi_stream *fsi_stream_get(struct fsi_priv *fsi,
|
|
|
|
int is_play)
|
|
|
|
{
|
|
|
|
return is_play ? &fsi->playback : &fsi->capture;
|
|
|
|
}
|
|
|
|
|
2011-05-23 19:46:35 +08:00
|
|
|
static int fsi_stream_is_working(struct fsi_priv *fsi,
|
|
|
|
int is_play)
|
|
|
|
{
|
2012-02-03 16:50:35 +08:00
|
|
|
struct fsi_stream *io = fsi_stream_get(fsi, is_play);
|
2011-05-23 19:46:35 +08:00
|
|
|
struct fsi_master *master = fsi_get_master(fsi);
|
|
|
|
unsigned long flags;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
spin_lock_irqsave(&master->lock, flags);
|
|
|
|
ret = !!io->substream;
|
|
|
|
spin_unlock_irqrestore(&master->lock, flags);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2009-08-20 20:01:05 +08:00
|
|
|
static void fsi_stream_push(struct fsi_priv *fsi,
|
2010-10-12 18:19:28 +08:00
|
|
|
int is_play,
|
2011-05-23 19:45:57 +08:00
|
|
|
struct snd_pcm_substream *substream)
|
2009-08-20 20:01:05 +08:00
|
|
|
{
|
2012-02-03 16:50:35 +08:00
|
|
|
struct fsi_stream *io = fsi_stream_get(fsi, is_play);
|
2011-05-23 19:45:57 +08:00
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
2011-05-23 19:46:13 +08:00
|
|
|
struct fsi_master *master = fsi_get_master(fsi);
|
|
|
|
unsigned long flags;
|
2010-10-12 18:19:28 +08:00
|
|
|
|
2011-05-23 19:46:13 +08:00
|
|
|
spin_lock_irqsave(&master->lock, flags);
|
2010-10-12 18:19:28 +08:00
|
|
|
io->substream = substream;
|
2011-05-23 19:46:03 +08:00
|
|
|
io->buff_sample_capa = fsi_frame2sample(fsi, runtime->buffer_size);
|
|
|
|
io->buff_sample_pos = 0;
|
|
|
|
io->period_samples = fsi_frame2sample(fsi, runtime->period_size);
|
|
|
|
io->period_pos = 0;
|
2012-02-03 16:50:09 +08:00
|
|
|
io->sample_width = samples_to_bytes(runtime, 1);
|
2010-12-17 11:55:22 +08:00
|
|
|
io->oerr_num = -1; /* ignore 1st err */
|
|
|
|
io->uerr_num = -1; /* ignore 1st err */
|
2011-05-23 19:46:13 +08:00
|
|
|
spin_unlock_irqrestore(&master->lock, flags);
|
2009-08-20 20:01:05 +08:00
|
|
|
}
|
|
|
|
|
2010-10-12 18:19:28 +08:00
|
|
|
static void fsi_stream_pop(struct fsi_priv *fsi, int is_play)
|
2009-08-20 20:01:05 +08:00
|
|
|
{
|
2012-02-03 16:50:35 +08:00
|
|
|
struct fsi_stream *io = fsi_stream_get(fsi, is_play);
|
2010-12-17 11:55:22 +08:00
|
|
|
struct snd_soc_dai *dai = fsi_get_dai(io->substream);
|
2011-05-23 19:46:13 +08:00
|
|
|
struct fsi_master *master = fsi_get_master(fsi);
|
|
|
|
unsigned long flags;
|
2010-12-17 11:55:22 +08:00
|
|
|
|
2011-05-23 19:46:13 +08:00
|
|
|
spin_lock_irqsave(&master->lock, flags);
|
2010-12-17 11:55:22 +08:00
|
|
|
|
|
|
|
if (io->oerr_num > 0)
|
|
|
|
dev_err(dai->dev, "over_run = %d\n", io->oerr_num);
|
|
|
|
|
|
|
|
if (io->uerr_num > 0)
|
|
|
|
dev_err(dai->dev, "under_run = %d\n", io->uerr_num);
|
2010-10-12 18:19:28 +08:00
|
|
|
|
|
|
|
io->substream = NULL;
|
2011-05-23 19:46:03 +08:00
|
|
|
io->buff_sample_capa = 0;
|
|
|
|
io->buff_sample_pos = 0;
|
|
|
|
io->period_samples = 0;
|
|
|
|
io->period_pos = 0;
|
2012-02-03 16:50:09 +08:00
|
|
|
io->sample_width = 0;
|
2010-12-17 11:55:22 +08:00
|
|
|
io->oerr_num = 0;
|
|
|
|
io->uerr_num = 0;
|
2011-05-23 19:46:13 +08:00
|
|
|
spin_unlock_irqrestore(&master->lock, flags);
|
2009-08-20 20:01:05 +08:00
|
|
|
}
|
|
|
|
|
2010-09-17 12:48:32 +08:00
|
|
|
/*
|
|
|
|
* dma function
|
|
|
|
*/
|
|
|
|
|
2010-10-12 18:19:28 +08:00
|
|
|
static u8 *fsi_dma_get_area(struct fsi_priv *fsi, int stream)
|
2010-09-17 12:48:05 +08:00
|
|
|
{
|
2010-10-12 18:19:28 +08:00
|
|
|
int is_play = fsi_stream_is_play(stream);
|
2012-02-03 16:50:35 +08:00
|
|
|
struct fsi_stream *io = fsi_stream_get(fsi, is_play);
|
2011-05-23 19:46:03 +08:00
|
|
|
struct snd_pcm_runtime *runtime = io->substream->runtime;
|
2010-10-12 18:19:28 +08:00
|
|
|
|
2011-05-23 19:46:03 +08:00
|
|
|
return runtime->dma_area +
|
|
|
|
samples_to_bytes(runtime, io->buff_sample_pos);
|
2010-09-17 12:48:05 +08:00
|
|
|
}
|
|
|
|
|
2010-09-17 12:48:45 +08:00
|
|
|
static void fsi_dma_soft_push16(struct fsi_priv *fsi, int num)
|
2010-09-17 12:48:32 +08:00
|
|
|
{
|
|
|
|
u16 *start;
|
|
|
|
int i;
|
|
|
|
|
2010-10-12 18:19:28 +08:00
|
|
|
start = (u16 *)fsi_dma_get_area(fsi, SNDRV_PCM_STREAM_PLAYBACK);
|
2010-09-17 12:48:32 +08:00
|
|
|
|
2010-09-17 12:48:45 +08:00
|
|
|
for (i = 0; i < num; i++)
|
2010-09-17 12:48:32 +08:00
|
|
|
fsi_reg_write(fsi, DODT, ((u32)*(start + i) << 8));
|
|
|
|
}
|
|
|
|
|
2010-09-17 12:48:45 +08:00
|
|
|
static void fsi_dma_soft_pop16(struct fsi_priv *fsi, int num)
|
2010-09-17 12:48:32 +08:00
|
|
|
{
|
|
|
|
u16 *start;
|
|
|
|
int i;
|
|
|
|
|
2010-10-12 18:19:28 +08:00
|
|
|
start = (u16 *)fsi_dma_get_area(fsi, SNDRV_PCM_STREAM_CAPTURE);
|
|
|
|
|
2010-09-17 12:48:32 +08:00
|
|
|
|
2010-09-17 12:48:45 +08:00
|
|
|
for (i = 0; i < num; i++)
|
2010-09-17 12:48:32 +08:00
|
|
|
*(start + i) = (u16)(fsi_reg_read(fsi, DIDT) >> 8);
|
|
|
|
}
|
|
|
|
|
2010-09-17 12:48:45 +08:00
|
|
|
static void fsi_dma_soft_push32(struct fsi_priv *fsi, int num)
|
2010-09-17 12:48:32 +08:00
|
|
|
{
|
|
|
|
u32 *start;
|
|
|
|
int i;
|
|
|
|
|
2010-10-12 18:19:28 +08:00
|
|
|
start = (u32 *)fsi_dma_get_area(fsi, SNDRV_PCM_STREAM_PLAYBACK);
|
|
|
|
|
2010-09-17 12:48:32 +08:00
|
|
|
|
2010-09-17 12:48:45 +08:00
|
|
|
for (i = 0; i < num; i++)
|
2010-09-17 12:48:32 +08:00
|
|
|
fsi_reg_write(fsi, DODT, *(start + i));
|
|
|
|
}
|
|
|
|
|
2010-09-17 12:48:45 +08:00
|
|
|
static void fsi_dma_soft_pop32(struct fsi_priv *fsi, int num)
|
2010-09-17 12:48:32 +08:00
|
|
|
{
|
|
|
|
u32 *start;
|
|
|
|
int i;
|
|
|
|
|
2010-10-12 18:19:28 +08:00
|
|
|
start = (u32 *)fsi_dma_get_area(fsi, SNDRV_PCM_STREAM_CAPTURE);
|
2010-09-17 12:48:32 +08:00
|
|
|
|
2010-09-17 12:48:45 +08:00
|
|
|
for (i = 0; i < num; i++)
|
2010-09-17 12:48:32 +08:00
|
|
|
*(start + i) = fsi_reg_read(fsi, DIDT);
|
|
|
|
}
|
|
|
|
|
2010-09-17 12:48:17 +08:00
|
|
|
/*
|
|
|
|
* irq function
|
|
|
|
*/
|
2009-08-20 20:01:05 +08:00
|
|
|
|
|
|
|
static void fsi_irq_enable(struct fsi_priv *fsi, int is_play)
|
|
|
|
{
|
2010-10-12 10:40:53 +08:00
|
|
|
u32 data = AB_IO(1, fsi_get_port_shift(fsi, is_play));
|
2009-12-02 14:11:08 +08:00
|
|
|
struct fsi_master *master = fsi_get_master(fsi);
|
2009-08-20 20:01:05 +08:00
|
|
|
|
2010-12-03 16:38:03 +08:00
|
|
|
fsi_core_mask_set(master, imsk, data, data);
|
|
|
|
fsi_core_mask_set(master, iemsk, data, data);
|
2009-08-20 20:01:05 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void fsi_irq_disable(struct fsi_priv *fsi, int is_play)
|
|
|
|
{
|
2010-10-12 10:40:53 +08:00
|
|
|
u32 data = AB_IO(1, fsi_get_port_shift(fsi, is_play));
|
2009-12-02 14:11:08 +08:00
|
|
|
struct fsi_master *master = fsi_get_master(fsi);
|
2009-08-20 20:01:05 +08:00
|
|
|
|
2010-12-03 16:38:03 +08:00
|
|
|
fsi_core_mask_set(master, imsk, data, 0);
|
|
|
|
fsi_core_mask_set(master, iemsk, data, 0);
|
2009-08-20 20:01:05 +08:00
|
|
|
}
|
|
|
|
|
2010-03-23 10:47:54 +08:00
|
|
|
static u32 fsi_irq_get_status(struct fsi_master *master)
|
|
|
|
{
|
2010-12-03 16:38:03 +08:00
|
|
|
return fsi_core_read(master, int_st);
|
2010-03-23 10:47:54 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void fsi_irq_clear_status(struct fsi_priv *fsi)
|
|
|
|
{
|
|
|
|
u32 data = 0;
|
|
|
|
struct fsi_master *master = fsi_get_master(fsi);
|
|
|
|
|
2010-10-12 10:40:53 +08:00
|
|
|
data |= AB_IO(1, fsi_get_port_shift(fsi, 0));
|
|
|
|
data |= AB_IO(1, fsi_get_port_shift(fsi, 1));
|
2010-03-23 10:47:54 +08:00
|
|
|
|
|
|
|
/* clear interrupt factor */
|
2010-12-03 16:38:03 +08:00
|
|
|
fsi_core_mask_set(master, int_st, data, 0);
|
2010-03-23 10:47:54 +08:00
|
|
|
}
|
|
|
|
|
2010-09-17 12:48:17 +08:00
|
|
|
/*
|
|
|
|
* SPDIF master clock function
|
|
|
|
*
|
|
|
|
* These functions are used later FSI2
|
|
|
|
*/
|
2010-07-29 15:48:32 +08:00
|
|
|
static void fsi_spdif_clk_ctrl(struct fsi_priv *fsi, int enable)
|
|
|
|
{
|
|
|
|
struct fsi_master *master = fsi_get_master(fsi);
|
2010-12-03 16:37:44 +08:00
|
|
|
u32 mask, val;
|
2010-07-29 15:48:32 +08:00
|
|
|
|
|
|
|
if (master->core->ver < 2) {
|
|
|
|
pr_err("fsi: register access err (%s)\n", __func__);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2010-12-03 16:37:44 +08:00
|
|
|
mask = BP | SE;
|
|
|
|
val = enable ? mask : 0;
|
|
|
|
|
|
|
|
fsi_is_port_a(fsi) ?
|
2010-12-03 16:38:03 +08:00
|
|
|
fsi_core_mask_set(master, a_mclk, mask, val) :
|
|
|
|
fsi_core_mask_set(master, b_mclk, mask, val);
|
2010-07-29 15:48:32 +08:00
|
|
|
}
|
|
|
|
|
2010-09-17 12:48:17 +08:00
|
|
|
/*
|
2011-04-21 09:33:52 +08:00
|
|
|
* clock function
|
2010-09-17 12:48:17 +08:00
|
|
|
*/
|
2011-05-23 19:46:18 +08:00
|
|
|
static int fsi_set_master_clk(struct device *dev, struct fsi_priv *fsi,
|
|
|
|
long rate, int enable)
|
|
|
|
{
|
|
|
|
struct fsi_master *master = fsi_get_master(fsi);
|
|
|
|
set_rate_func set_rate = fsi_get_info_set_rate(master);
|
|
|
|
int fsi_ver = master->core->ver;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = set_rate(dev, fsi_is_port_a(fsi), rate, enable);
|
|
|
|
if (ret < 0) /* error */
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
if (!enable)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (ret > 0) {
|
|
|
|
u32 data = 0;
|
|
|
|
|
|
|
|
switch (ret & SH_FSI_ACKMD_MASK) {
|
|
|
|
default:
|
|
|
|
/* FALL THROUGH */
|
|
|
|
case SH_FSI_ACKMD_512:
|
|
|
|
data |= (0x0 << 12);
|
|
|
|
break;
|
|
|
|
case SH_FSI_ACKMD_256:
|
|
|
|
data |= (0x1 << 12);
|
|
|
|
break;
|
|
|
|
case SH_FSI_ACKMD_128:
|
|
|
|
data |= (0x2 << 12);
|
|
|
|
break;
|
|
|
|
case SH_FSI_ACKMD_64:
|
|
|
|
data |= (0x3 << 12);
|
|
|
|
break;
|
|
|
|
case SH_FSI_ACKMD_32:
|
|
|
|
if (fsi_ver < 2)
|
|
|
|
dev_err(dev, "unsupported ACKMD\n");
|
|
|
|
else
|
|
|
|
data |= (0x4 << 12);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (ret & SH_FSI_BPFMD_MASK) {
|
|
|
|
default:
|
|
|
|
/* FALL THROUGH */
|
|
|
|
case SH_FSI_BPFMD_32:
|
|
|
|
data |= (0x0 << 8);
|
|
|
|
break;
|
|
|
|
case SH_FSI_BPFMD_64:
|
|
|
|
data |= (0x1 << 8);
|
|
|
|
break;
|
|
|
|
case SH_FSI_BPFMD_128:
|
|
|
|
data |= (0x2 << 8);
|
|
|
|
break;
|
|
|
|
case SH_FSI_BPFMD_256:
|
|
|
|
data |= (0x3 << 8);
|
|
|
|
break;
|
|
|
|
case SH_FSI_BPFMD_512:
|
|
|
|
data |= (0x4 << 8);
|
|
|
|
break;
|
|
|
|
case SH_FSI_BPFMD_16:
|
|
|
|
if (fsi_ver < 2)
|
|
|
|
dev_err(dev, "unsupported ACKMD\n");
|
|
|
|
else
|
|
|
|
data |= (0x7 << 8);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
fsi_reg_mask_set(fsi, CKG1, (ACKMD_MASK | BPFMD_MASK) , data);
|
|
|
|
udelay(10);
|
|
|
|
ret = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2011-05-23 19:46:23 +08:00
|
|
|
#define fsi_port_start(f, i) __fsi_port_clk_ctrl(f, i, 1)
|
|
|
|
#define fsi_port_stop(f, i) __fsi_port_clk_ctrl(f, i, 0)
|
|
|
|
static void __fsi_port_clk_ctrl(struct fsi_priv *fsi, int is_play, int enable)
|
2009-08-20 20:01:05 +08:00
|
|
|
{
|
2009-12-02 14:11:08 +08:00
|
|
|
struct fsi_master *master = fsi_get_master(fsi);
|
2011-04-21 09:33:52 +08:00
|
|
|
u32 clk = fsi_is_port_a(fsi) ? CRA : CRB;
|
2009-08-20 20:01:05 +08:00
|
|
|
|
2011-05-23 19:46:23 +08:00
|
|
|
if (enable)
|
|
|
|
fsi_irq_enable(fsi, is_play);
|
|
|
|
else
|
|
|
|
fsi_irq_disable(fsi, is_play);
|
|
|
|
|
2011-05-23 19:46:35 +08:00
|
|
|
if (fsi_is_clk_master(fsi))
|
2011-04-21 09:33:52 +08:00
|
|
|
fsi_master_mask_set(master, CLK_RST, clk, (enable) ? clk : 0);
|
2009-08-20 20:01:05 +08:00
|
|
|
}
|
|
|
|
|
2011-04-21 09:33:52 +08:00
|
|
|
/*
|
|
|
|
* ctrl function
|
|
|
|
*/
|
2010-03-25 18:15:51 +08:00
|
|
|
static void fsi_fifo_init(struct fsi_priv *fsi,
|
|
|
|
int is_play,
|
2011-05-23 19:46:26 +08:00
|
|
|
struct device *dev)
|
2009-08-20 20:01:05 +08:00
|
|
|
{
|
2010-03-25 18:15:51 +08:00
|
|
|
struct fsi_master *master = fsi_get_master(fsi);
|
2012-02-03 16:50:35 +08:00
|
|
|
struct fsi_stream *io = fsi_stream_get(fsi, is_play);
|
2010-12-03 16:37:55 +08:00
|
|
|
u32 shift, i;
|
2011-05-23 19:46:03 +08:00
|
|
|
int frame_capa;
|
2009-08-20 20:01:05 +08:00
|
|
|
|
2010-03-25 18:15:51 +08:00
|
|
|
/* get on-chip RAM capacity */
|
|
|
|
shift = fsi_master_read(master, FIFO_SZ);
|
2010-10-12 10:40:53 +08:00
|
|
|
shift >>= fsi_get_port_shift(fsi, is_play);
|
|
|
|
shift &= FIFO_SZ_MASK;
|
2011-05-23 19:46:03 +08:00
|
|
|
frame_capa = 256 << shift;
|
2011-05-23 19:46:26 +08:00
|
|
|
dev_dbg(dev, "fifo = %d words\n", frame_capa);
|
2009-08-20 20:01:05 +08:00
|
|
|
|
2010-03-25 18:15:51 +08:00
|
|
|
/*
|
|
|
|
* The maximum number of sample data varies depending
|
|
|
|
* on the number of channels selected for the format.
|
|
|
|
*
|
|
|
|
* FIFOs are used in 4-channel units in 3-channel mode
|
|
|
|
* and in 8-channel units in 5- to 7-channel mode
|
|
|
|
* meaning that more FIFOs than the required size of DPRAM
|
|
|
|
* are used.
|
|
|
|
*
|
|
|
|
* ex) if 256 words of DP-RAM is connected
|
|
|
|
* 1 channel: 256 (256 x 1 = 256)
|
|
|
|
* 2 channels: 128 (128 x 2 = 256)
|
|
|
|
* 3 channels: 64 ( 64 x 3 = 192)
|
|
|
|
* 4 channels: 64 ( 64 x 4 = 256)
|
|
|
|
* 5 channels: 32 ( 32 x 5 = 160)
|
|
|
|
* 6 channels: 32 ( 32 x 6 = 192)
|
|
|
|
* 7 channels: 32 ( 32 x 7 = 224)
|
|
|
|
* 8 channels: 32 ( 32 x 8 = 256)
|
|
|
|
*/
|
2011-01-24 09:42:08 +08:00
|
|
|
for (i = 1; i < fsi->chan_num; i <<= 1)
|
2011-05-23 19:46:03 +08:00
|
|
|
frame_capa >>= 1;
|
2011-05-23 19:46:26 +08:00
|
|
|
dev_dbg(dev, "%d channel %d store\n",
|
2011-05-23 19:46:03 +08:00
|
|
|
fsi->chan_num, frame_capa);
|
|
|
|
|
|
|
|
io->fifo_sample_capa = fsi_frame2sample(fsi, frame_capa);
|
2009-08-20 20:01:05 +08:00
|
|
|
|
2010-12-03 16:37:55 +08:00
|
|
|
/*
|
|
|
|
* set interrupt generation factor
|
|
|
|
* clear FIFO
|
|
|
|
*/
|
|
|
|
if (is_play) {
|
|
|
|
fsi_reg_write(fsi, DOFF_CTL, IRQ_HALF);
|
|
|
|
fsi_reg_mask_set(fsi, DOFF_CTL, FIFO_CLR, FIFO_CLR);
|
|
|
|
} else {
|
|
|
|
fsi_reg_write(fsi, DIFF_CTL, IRQ_HALF);
|
|
|
|
fsi_reg_mask_set(fsi, DIFF_CTL, FIFO_CLR, FIFO_CLR);
|
|
|
|
}
|
2009-08-20 20:01:05 +08:00
|
|
|
}
|
|
|
|
|
2012-02-03 16:50:59 +08:00
|
|
|
static int fsi_fifo_data_ctrl(struct fsi_priv *fsi, struct fsi_stream *io,
|
|
|
|
void (*run16)(struct fsi_priv *fsi, int size),
|
|
|
|
void (*run32)(struct fsi_priv *fsi, int size),
|
|
|
|
int samples)
|
2009-08-20 20:01:05 +08:00
|
|
|
{
|
|
|
|
struct snd_pcm_runtime *runtime;
|
2012-02-03 16:50:59 +08:00
|
|
|
struct snd_pcm_substream *substream;
|
2010-09-17 12:48:32 +08:00
|
|
|
int over_period;
|
2009-08-20 20:01:05 +08:00
|
|
|
|
|
|
|
if (!fsi ||
|
2010-10-12 18:19:28 +08:00
|
|
|
!io->substream ||
|
|
|
|
!io->substream->runtime)
|
2009-08-20 20:01:05 +08:00
|
|
|
return -EINVAL;
|
|
|
|
|
2009-12-28 13:09:05 +08:00
|
|
|
over_period = 0;
|
2010-10-12 18:19:28 +08:00
|
|
|
substream = io->substream;
|
2009-12-28 13:09:05 +08:00
|
|
|
runtime = substream->runtime;
|
2009-08-20 20:01:05 +08:00
|
|
|
|
|
|
|
/* FSI FIFO has limit.
|
|
|
|
* So, this driver can not send periods data at a time
|
|
|
|
*/
|
2011-05-23 19:46:03 +08:00
|
|
|
if (io->buff_sample_pos >=
|
|
|
|
io->period_samples * (io->period_pos + 1)) {
|
2009-08-20 20:01:05 +08:00
|
|
|
|
2009-12-28 13:09:05 +08:00
|
|
|
over_period = 1;
|
2011-05-23 19:46:03 +08:00
|
|
|
io->period_pos = (io->period_pos + 1) % runtime->periods;
|
2009-08-20 20:01:05 +08:00
|
|
|
|
2011-05-23 19:46:03 +08:00
|
|
|
if (0 == io->period_pos)
|
|
|
|
io->buff_sample_pos = 0;
|
2009-08-20 20:01:05 +08:00
|
|
|
}
|
|
|
|
|
2012-02-03 16:50:59 +08:00
|
|
|
switch (io->sample_width) {
|
|
|
|
case 2:
|
|
|
|
run16(fsi, samples);
|
|
|
|
break;
|
|
|
|
case 4:
|
|
|
|
run32(fsi, samples);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return -EINVAL;
|
2010-09-17 12:49:05 +08:00
|
|
|
}
|
2009-08-20 20:01:05 +08:00
|
|
|
|
2011-05-23 19:46:03 +08:00
|
|
|
/* update buff_sample_pos */
|
|
|
|
io->buff_sample_pos += samples;
|
2009-08-20 20:01:05 +08:00
|
|
|
|
2009-12-28 13:09:05 +08:00
|
|
|
if (over_period)
|
2009-08-20 20:01:05 +08:00
|
|
|
snd_pcm_period_elapsed(substream);
|
|
|
|
|
2010-02-22 15:41:57 +08:00
|
|
|
return 0;
|
2009-08-20 20:01:05 +08:00
|
|
|
}
|
|
|
|
|
2010-12-17 11:55:22 +08:00
|
|
|
static int fsi_data_pop(struct fsi_priv *fsi)
|
2009-10-30 11:02:44 +08:00
|
|
|
{
|
2012-02-03 16:50:59 +08:00
|
|
|
int is_play = fsi_stream_is_play(SNDRV_PCM_STREAM_CAPTURE);
|
|
|
|
int sample_residues; /* samples in FSI fifo */
|
|
|
|
int sample_space; /* ALSA free samples space */
|
|
|
|
int samples;
|
|
|
|
struct fsi_stream *io = fsi_stream_get(fsi, is_play);
|
|
|
|
|
|
|
|
sample_residues = fsi_get_current_fifo_samples(fsi, is_play);
|
|
|
|
sample_space = io->buff_sample_capa - io->buff_sample_pos;
|
|
|
|
|
|
|
|
samples = min(sample_residues, sample_space);
|
|
|
|
|
|
|
|
return fsi_fifo_data_ctrl(fsi, io,
|
|
|
|
fsi_dma_soft_pop16,
|
|
|
|
fsi_dma_soft_pop32,
|
|
|
|
samples);
|
2010-09-17 12:49:05 +08:00
|
|
|
}
|
2009-10-30 11:02:44 +08:00
|
|
|
|
2010-12-17 11:55:22 +08:00
|
|
|
static int fsi_data_push(struct fsi_priv *fsi)
|
2010-09-17 12:49:05 +08:00
|
|
|
{
|
2012-02-03 16:50:59 +08:00
|
|
|
int is_play = fsi_stream_is_play(SNDRV_PCM_STREAM_PLAYBACK);
|
|
|
|
int sample_residues; /* ALSA residue samples */
|
|
|
|
int sample_space; /* FSI fifo free samples space */
|
|
|
|
int samples;
|
|
|
|
struct fsi_stream *io = fsi_stream_get(fsi, is_play);
|
|
|
|
|
|
|
|
sample_residues = io->buff_sample_capa - io->buff_sample_pos;
|
|
|
|
sample_space = io->fifo_sample_capa -
|
|
|
|
fsi_get_current_fifo_samples(fsi, is_play);
|
|
|
|
|
|
|
|
samples = min(sample_residues, sample_space);
|
|
|
|
|
|
|
|
return fsi_fifo_data_ctrl(fsi, io,
|
|
|
|
fsi_dma_soft_push16,
|
|
|
|
fsi_dma_soft_push32,
|
|
|
|
samples);
|
2009-10-30 11:02:44 +08:00
|
|
|
}
|
|
|
|
|
2009-08-20 20:01:05 +08:00
|
|
|
static irqreturn_t fsi_interrupt(int irq, void *data)
|
|
|
|
{
|
2009-12-02 14:11:08 +08:00
|
|
|
struct fsi_master *master = data;
|
2010-03-23 10:47:54 +08:00
|
|
|
u32 int_st = fsi_irq_get_status(master);
|
2009-08-20 20:01:05 +08:00
|
|
|
|
|
|
|
/* clear irq status */
|
2010-03-24 14:27:24 +08:00
|
|
|
fsi_master_mask_set(master, SOFT_RST, IR, 0);
|
|
|
|
fsi_master_mask_set(master, SOFT_RST, IR, IR);
|
2009-08-20 20:01:05 +08:00
|
|
|
|
2010-10-12 10:40:53 +08:00
|
|
|
if (int_st & AB_IO(1, AO_SHIFT))
|
2010-12-17 11:55:22 +08:00
|
|
|
fsi_data_push(&master->fsia);
|
2010-10-12 10:40:53 +08:00
|
|
|
if (int_st & AB_IO(1, BO_SHIFT))
|
2010-12-17 11:55:22 +08:00
|
|
|
fsi_data_push(&master->fsib);
|
2010-10-12 10:40:53 +08:00
|
|
|
if (int_st & AB_IO(1, AI_SHIFT))
|
2010-12-17 11:55:22 +08:00
|
|
|
fsi_data_pop(&master->fsia);
|
2010-10-12 10:40:53 +08:00
|
|
|
if (int_st & AB_IO(1, BI_SHIFT))
|
2010-12-17 11:55:22 +08:00
|
|
|
fsi_data_pop(&master->fsib);
|
|
|
|
|
|
|
|
fsi_count_fifo_err(&master->fsia);
|
|
|
|
fsi_count_fifo_err(&master->fsib);
|
2009-08-20 20:01:05 +08:00
|
|
|
|
2010-12-03 16:37:31 +08:00
|
|
|
fsi_irq_clear_status(&master->fsia);
|
|
|
|
fsi_irq_clear_status(&master->fsib);
|
2009-08-20 20:01:05 +08:00
|
|
|
|
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
2010-09-17 12:48:17 +08:00
|
|
|
/*
|
|
|
|
* dai ops
|
|
|
|
*/
|
2009-08-20 20:01:05 +08:00
|
|
|
|
2011-05-23 19:46:26 +08:00
|
|
|
static int fsi_hw_startup(struct fsi_priv *fsi,
|
|
|
|
int is_play,
|
|
|
|
struct device *dev)
|
2009-08-20 20:01:05 +08:00
|
|
|
{
|
2011-11-07 14:05:25 +08:00
|
|
|
struct fsi_master *master = fsi_get_master(fsi);
|
|
|
|
int fsi_ver = master->core->ver;
|
2010-10-12 18:19:28 +08:00
|
|
|
u32 flags = fsi_get_info_flags(fsi);
|
2011-05-23 19:46:07 +08:00
|
|
|
u32 data = 0;
|
2009-08-20 20:01:05 +08:00
|
|
|
|
2011-05-23 19:46:07 +08:00
|
|
|
/* clock setting */
|
|
|
|
if (fsi_is_clk_master(fsi))
|
|
|
|
data = DIMD | DOMD;
|
|
|
|
|
|
|
|
fsi_reg_mask_set(fsi, CKG1, (DIMD | DOMD), data);
|
2009-08-20 20:01:05 +08:00
|
|
|
|
|
|
|
/* clock inversion (CKG2) */
|
|
|
|
data = 0;
|
2010-07-13 11:01:15 +08:00
|
|
|
if (SH_FSI_LRM_INV & flags)
|
|
|
|
data |= 1 << 12;
|
|
|
|
if (SH_FSI_BRM_INV & flags)
|
|
|
|
data |= 1 << 8;
|
|
|
|
if (SH_FSI_LRS_INV & flags)
|
|
|
|
data |= 1 << 4;
|
|
|
|
if (SH_FSI_BRS_INV & flags)
|
|
|
|
data |= 1 << 0;
|
|
|
|
|
2009-08-20 20:01:05 +08:00
|
|
|
fsi_reg_write(fsi, CKG2, data);
|
|
|
|
|
2011-05-23 19:46:07 +08:00
|
|
|
/* set format */
|
|
|
|
fsi_reg_write(fsi, DO_FMT, fsi->do_fmt);
|
|
|
|
fsi_reg_write(fsi, DI_FMT, fsi->di_fmt);
|
|
|
|
|
|
|
|
/* spdif ? */
|
|
|
|
if (fsi_is_spdif(fsi)) {
|
|
|
|
fsi_spdif_clk_ctrl(fsi, 1);
|
|
|
|
fsi_reg_mask_set(fsi, OUT_SEL, DMMD, DMMD);
|
|
|
|
}
|
|
|
|
|
2011-11-07 14:05:25 +08:00
|
|
|
/*
|
|
|
|
* FIXME
|
|
|
|
*
|
|
|
|
* FSI driver assumed that data package is in-back.
|
|
|
|
* FSI2 chip can select it.
|
|
|
|
*/
|
|
|
|
if (fsi_ver >= 2) {
|
|
|
|
fsi_reg_write(fsi, OUT_DMAC, (1 << 4));
|
|
|
|
fsi_reg_write(fsi, IN_DMAC, (1 << 4));
|
|
|
|
}
|
|
|
|
|
2010-03-23 10:47:54 +08:00
|
|
|
/* irq clear */
|
|
|
|
fsi_irq_disable(fsi, is_play);
|
|
|
|
fsi_irq_clear_status(fsi);
|
|
|
|
|
|
|
|
/* fifo init */
|
2011-05-23 19:46:26 +08:00
|
|
|
fsi_fifo_init(fsi, is_play, dev);
|
2009-08-20 20:01:05 +08:00
|
|
|
|
2010-10-12 10:39:50 +08:00
|
|
|
return 0;
|
2009-08-20 20:01:05 +08:00
|
|
|
}
|
|
|
|
|
2011-05-23 19:46:26 +08:00
|
|
|
static void fsi_hw_shutdown(struct fsi_priv *fsi,
|
|
|
|
int is_play,
|
|
|
|
struct device *dev)
|
|
|
|
{
|
|
|
|
if (fsi_is_clk_master(fsi))
|
|
|
|
fsi_set_master_clk(dev, fsi, fsi->rate, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int fsi_dai_startup(struct snd_pcm_substream *substream,
|
|
|
|
struct snd_soc_dai *dai)
|
|
|
|
{
|
|
|
|
struct fsi_priv *fsi = fsi_get_priv(substream);
|
|
|
|
int is_play = fsi_is_play(substream);
|
|
|
|
|
|
|
|
return fsi_hw_startup(fsi, is_play, dai->dev);
|
|
|
|
}
|
|
|
|
|
2009-08-20 20:01:05 +08:00
|
|
|
static void fsi_dai_shutdown(struct snd_pcm_substream *substream,
|
|
|
|
struct snd_soc_dai *dai)
|
|
|
|
{
|
2009-12-02 14:11:08 +08:00
|
|
|
struct fsi_priv *fsi = fsi_get_priv(substream);
|
2011-05-23 19:46:26 +08:00
|
|
|
int is_play = fsi_is_play(substream);
|
2009-08-20 20:01:05 +08:00
|
|
|
|
2011-05-23 19:46:26 +08:00
|
|
|
fsi_hw_shutdown(fsi, is_play, dai->dev);
|
2010-11-24 10:44:06 +08:00
|
|
|
fsi->rate = 0;
|
2009-08-20 20:01:05 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int fsi_dai_trigger(struct snd_pcm_substream *substream, int cmd,
|
|
|
|
struct snd_soc_dai *dai)
|
|
|
|
{
|
2009-12-02 14:11:08 +08:00
|
|
|
struct fsi_priv *fsi = fsi_get_priv(substream);
|
2010-10-12 17:30:14 +08:00
|
|
|
int is_play = fsi_is_play(substream);
|
2009-08-20 20:01:05 +08:00
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
switch (cmd) {
|
|
|
|
case SNDRV_PCM_TRIGGER_START:
|
2011-05-23 19:45:57 +08:00
|
|
|
fsi_stream_push(fsi, is_play, substream);
|
2010-12-17 11:55:22 +08:00
|
|
|
ret = is_play ? fsi_data_push(fsi) : fsi_data_pop(fsi);
|
2011-05-23 19:46:23 +08:00
|
|
|
fsi_port_start(fsi, is_play);
|
2009-08-20 20:01:05 +08:00
|
|
|
break;
|
|
|
|
case SNDRV_PCM_TRIGGER_STOP:
|
2011-05-23 19:46:23 +08:00
|
|
|
fsi_port_stop(fsi, is_play);
|
2010-10-12 18:19:28 +08:00
|
|
|
fsi_stream_pop(fsi, is_play);
|
2009-08-20 20:01:05 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2011-01-24 09:43:19 +08:00
|
|
|
static int fsi_set_fmt_dai(struct fsi_priv *fsi, unsigned int fmt)
|
|
|
|
{
|
|
|
|
u32 data = 0;
|
|
|
|
|
|
|
|
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
|
|
|
|
case SND_SOC_DAIFMT_I2S:
|
|
|
|
data = CR_I2S;
|
|
|
|
fsi->chan_num = 2;
|
|
|
|
break;
|
|
|
|
case SND_SOC_DAIFMT_LEFT_J:
|
|
|
|
data = CR_PCM;
|
|
|
|
fsi->chan_num = 2;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2011-05-23 19:46:07 +08:00
|
|
|
fsi->do_fmt = data;
|
|
|
|
fsi->di_fmt = data;
|
2011-01-24 09:43:19 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int fsi_set_fmt_spdif(struct fsi_priv *fsi)
|
|
|
|
{
|
|
|
|
struct fsi_master *master = fsi_get_master(fsi);
|
|
|
|
u32 data = 0;
|
|
|
|
|
|
|
|
if (master->core->ver < 2)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
data = CR_BWS_16 | CR_DTMD_SPDIF_PCM | CR_PCM;
|
|
|
|
fsi->chan_num = 2;
|
2011-05-23 19:46:07 +08:00
|
|
|
fsi->spdif = 1;
|
2011-01-24 09:43:19 +08:00
|
|
|
|
2011-05-23 19:46:07 +08:00
|
|
|
fsi->do_fmt = data;
|
|
|
|
fsi->di_fmt = data;
|
2011-01-24 09:43:19 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-01-20 10:46:02 +08:00
|
|
|
static int fsi_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
|
|
|
|
{
|
|
|
|
struct fsi_priv *fsi = fsi_get_priv_frm_dai(dai);
|
2011-04-21 09:33:36 +08:00
|
|
|
struct fsi_master *master = fsi_get_master(fsi);
|
|
|
|
set_rate_func set_rate = fsi_get_info_set_rate(master);
|
2011-01-24 09:43:19 +08:00
|
|
|
u32 flags = fsi_get_info_flags(fsi);
|
2011-01-20 10:46:02 +08:00
|
|
|
int ret;
|
|
|
|
|
|
|
|
/* set master/slave audio interface */
|
|
|
|
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
|
|
|
|
case SND_SOC_DAIFMT_CBM_CFM:
|
2011-04-21 09:33:36 +08:00
|
|
|
fsi->clk_master = 1;
|
2011-01-20 10:46:02 +08:00
|
|
|
break;
|
|
|
|
case SND_SOC_DAIFMT_CBS_CFS:
|
|
|
|
break;
|
|
|
|
default:
|
2011-05-23 19:46:07 +08:00
|
|
|
return -EINVAL;
|
2011-01-20 10:46:02 +08:00
|
|
|
}
|
2011-04-21 09:33:36 +08:00
|
|
|
|
|
|
|
if (fsi_is_clk_master(fsi) && !set_rate) {
|
|
|
|
dev_err(dai->dev, "platform doesn't have set_rate\n");
|
2011-05-23 19:46:07 +08:00
|
|
|
return -EINVAL;
|
2011-04-21 09:33:36 +08:00
|
|
|
}
|
|
|
|
|
2011-01-24 09:43:19 +08:00
|
|
|
/* set format */
|
|
|
|
switch (flags & SH_FSI_FMT_MASK) {
|
|
|
|
case SH_FSI_FMT_DAI:
|
|
|
|
ret = fsi_set_fmt_dai(fsi, fmt & SND_SOC_DAIFMT_FORMAT_MASK);
|
|
|
|
break;
|
|
|
|
case SH_FSI_FMT_SPDIF:
|
|
|
|
ret = fsi_set_fmt_spdif(fsi);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
ret = -EINVAL;
|
|
|
|
}
|
2011-01-20 10:46:02 +08:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2010-07-13 11:13:14 +08:00
|
|
|
static int fsi_dai_hw_params(struct snd_pcm_substream *substream,
|
|
|
|
struct snd_pcm_hw_params *params,
|
|
|
|
struct snd_soc_dai *dai)
|
|
|
|
{
|
|
|
|
struct fsi_priv *fsi = fsi_get_priv(substream);
|
2010-11-24 10:44:06 +08:00
|
|
|
long rate = params_rate(params);
|
2010-07-13 11:13:14 +08:00
|
|
|
int ret;
|
|
|
|
|
2011-04-21 09:33:36 +08:00
|
|
|
if (!fsi_is_clk_master(fsi))
|
2010-07-13 11:13:14 +08:00
|
|
|
return 0;
|
|
|
|
|
2011-05-23 19:46:18 +08:00
|
|
|
ret = fsi_set_master_clk(dai->dev, fsi, rate, 1);
|
|
|
|
if (ret < 0)
|
2010-11-24 10:44:06 +08:00
|
|
|
return ret;
|
2010-07-13 11:13:14 +08:00
|
|
|
|
2010-11-24 10:44:06 +08:00
|
|
|
fsi->rate = rate;
|
2010-07-13 11:13:14 +08:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2011-11-23 18:40:40 +08:00
|
|
|
static const struct snd_soc_dai_ops fsi_dai_ops = {
|
2009-08-20 20:01:05 +08:00
|
|
|
.startup = fsi_dai_startup,
|
|
|
|
.shutdown = fsi_dai_shutdown,
|
|
|
|
.trigger = fsi_dai_trigger,
|
2011-01-20 10:46:02 +08:00
|
|
|
.set_fmt = fsi_dai_set_fmt,
|
2010-07-13 11:13:14 +08:00
|
|
|
.hw_params = fsi_dai_hw_params,
|
2009-08-20 20:01:05 +08:00
|
|
|
};
|
|
|
|
|
2010-09-17 12:48:17 +08:00
|
|
|
/*
|
|
|
|
* pcm ops
|
|
|
|
*/
|
2009-08-20 20:01:05 +08:00
|
|
|
|
|
|
|
static struct snd_pcm_hardware fsi_pcm_hardware = {
|
|
|
|
.info = SNDRV_PCM_INFO_INTERLEAVED |
|
|
|
|
SNDRV_PCM_INFO_MMAP |
|
|
|
|
SNDRV_PCM_INFO_MMAP_VALID |
|
|
|
|
SNDRV_PCM_INFO_PAUSE,
|
|
|
|
.formats = FSI_FMTS,
|
|
|
|
.rates = FSI_RATES,
|
|
|
|
.rate_min = 8000,
|
|
|
|
.rate_max = 192000,
|
|
|
|
.channels_min = 1,
|
|
|
|
.channels_max = 2,
|
|
|
|
.buffer_bytes_max = 64 * 1024,
|
|
|
|
.period_bytes_min = 32,
|
|
|
|
.period_bytes_max = 8192,
|
|
|
|
.periods_min = 1,
|
|
|
|
.periods_max = 32,
|
|
|
|
.fifo_size = 256,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int fsi_pcm_open(struct snd_pcm_substream *substream)
|
|
|
|
{
|
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
snd_soc_set_runtime_hwparams(substream, &fsi_pcm_hardware);
|
|
|
|
|
|
|
|
ret = snd_pcm_hw_constraint_integer(runtime,
|
|
|
|
SNDRV_PCM_HW_PARAM_PERIODS);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int fsi_hw_params(struct snd_pcm_substream *substream,
|
|
|
|
struct snd_pcm_hw_params *hw_params)
|
|
|
|
{
|
|
|
|
return snd_pcm_lib_malloc_pages(substream,
|
|
|
|
params_buffer_bytes(hw_params));
|
|
|
|
}
|
|
|
|
|
|
|
|
static int fsi_hw_free(struct snd_pcm_substream *substream)
|
|
|
|
{
|
|
|
|
return snd_pcm_lib_free_pages(substream);
|
|
|
|
}
|
|
|
|
|
|
|
|
static snd_pcm_uframes_t fsi_pointer(struct snd_pcm_substream *substream)
|
|
|
|
{
|
2009-12-02 14:11:08 +08:00
|
|
|
struct fsi_priv *fsi = fsi_get_priv(substream);
|
2012-02-03 16:50:35 +08:00
|
|
|
struct fsi_stream *io = fsi_stream_get(fsi, fsi_is_play(substream));
|
2011-05-23 19:46:03 +08:00
|
|
|
int samples_pos = io->buff_sample_pos - 1;
|
2009-08-20 20:01:05 +08:00
|
|
|
|
2011-05-23 19:46:03 +08:00
|
|
|
if (samples_pos < 0)
|
|
|
|
samples_pos = 0;
|
2009-08-20 20:01:05 +08:00
|
|
|
|
2011-05-23 19:46:03 +08:00
|
|
|
return fsi_sample2frame(fsi, samples_pos);
|
2009-08-20 20:01:05 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static struct snd_pcm_ops fsi_pcm_ops = {
|
|
|
|
.open = fsi_pcm_open,
|
|
|
|
.ioctl = snd_pcm_lib_ioctl,
|
|
|
|
.hw_params = fsi_hw_params,
|
|
|
|
.hw_free = fsi_hw_free,
|
|
|
|
.pointer = fsi_pointer,
|
|
|
|
};
|
|
|
|
|
2010-09-17 12:48:17 +08:00
|
|
|
/*
|
|
|
|
* snd_soc_platform
|
|
|
|
*/
|
2009-08-20 20:01:05 +08:00
|
|
|
|
|
|
|
#define PREALLOC_BUFFER (32 * 1024)
|
|
|
|
#define PREALLOC_BUFFER_MAX (32 * 1024)
|
|
|
|
|
|
|
|
static void fsi_pcm_free(struct snd_pcm *pcm)
|
|
|
|
{
|
|
|
|
snd_pcm_lib_preallocate_free_for_all(pcm);
|
|
|
|
}
|
|
|
|
|
2011-06-07 23:08:33 +08:00
|
|
|
static int fsi_pcm_new(struct snd_soc_pcm_runtime *rtd)
|
2009-08-20 20:01:05 +08:00
|
|
|
{
|
2011-06-07 23:08:33 +08:00
|
|
|
struct snd_pcm *pcm = rtd->pcm;
|
|
|
|
|
2009-08-20 20:01:05 +08:00
|
|
|
/*
|
|
|
|
* dont use SNDRV_DMA_TYPE_DEV, since it will oops the SH kernel
|
|
|
|
* in MMAP mode (i.e. aplay -M)
|
|
|
|
*/
|
|
|
|
return snd_pcm_lib_preallocate_pages_for_all(
|
|
|
|
pcm,
|
|
|
|
SNDRV_DMA_TYPE_CONTINUOUS,
|
|
|
|
snd_dma_continuous_data(GFP_KERNEL),
|
|
|
|
PREALLOC_BUFFER, PREALLOC_BUFFER_MAX);
|
|
|
|
}
|
|
|
|
|
2010-09-17 12:48:17 +08:00
|
|
|
/*
|
|
|
|
* alsa struct
|
|
|
|
*/
|
2009-08-20 20:01:05 +08:00
|
|
|
|
2010-03-18 04:15:21 +08:00
|
|
|
static struct snd_soc_dai_driver fsi_soc_dai[] = {
|
2009-08-20 20:01:05 +08:00
|
|
|
{
|
2010-03-18 04:15:21 +08:00
|
|
|
.name = "fsia-dai",
|
2009-08-20 20:01:05 +08:00
|
|
|
.playback = {
|
|
|
|
.rates = FSI_RATES,
|
|
|
|
.formats = FSI_FMTS,
|
|
|
|
.channels_min = 1,
|
|
|
|
.channels_max = 8,
|
|
|
|
},
|
2009-10-30 11:02:44 +08:00
|
|
|
.capture = {
|
|
|
|
.rates = FSI_RATES,
|
|
|
|
.formats = FSI_FMTS,
|
|
|
|
.channels_min = 1,
|
|
|
|
.channels_max = 8,
|
|
|
|
},
|
2009-08-20 20:01:05 +08:00
|
|
|
.ops = &fsi_dai_ops,
|
|
|
|
},
|
|
|
|
{
|
2010-03-18 04:15:21 +08:00
|
|
|
.name = "fsib-dai",
|
2009-08-20 20:01:05 +08:00
|
|
|
.playback = {
|
|
|
|
.rates = FSI_RATES,
|
|
|
|
.formats = FSI_FMTS,
|
|
|
|
.channels_min = 1,
|
|
|
|
.channels_max = 8,
|
|
|
|
},
|
2009-10-30 11:02:44 +08:00
|
|
|
.capture = {
|
|
|
|
.rates = FSI_RATES,
|
|
|
|
.formats = FSI_FMTS,
|
|
|
|
.channels_min = 1,
|
|
|
|
.channels_max = 8,
|
|
|
|
},
|
2009-08-20 20:01:05 +08:00
|
|
|
.ops = &fsi_dai_ops,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2010-03-18 04:15:21 +08:00
|
|
|
static struct snd_soc_platform_driver fsi_soc_platform = {
|
|
|
|
.ops = &fsi_pcm_ops,
|
2009-08-20 20:01:05 +08:00
|
|
|
.pcm_new = fsi_pcm_new,
|
|
|
|
.pcm_free = fsi_pcm_free,
|
|
|
|
};
|
|
|
|
|
2010-09-17 12:48:17 +08:00
|
|
|
/*
|
|
|
|
* platform function
|
|
|
|
*/
|
2009-08-20 20:01:05 +08:00
|
|
|
|
|
|
|
static int fsi_probe(struct platform_device *pdev)
|
|
|
|
{
|
2009-12-02 14:11:08 +08:00
|
|
|
struct fsi_master *master;
|
2010-03-25 18:15:53 +08:00
|
|
|
const struct platform_device_id *id_entry;
|
2009-08-20 20:01:05 +08:00
|
|
|
struct resource *res;
|
|
|
|
unsigned int irq;
|
|
|
|
int ret;
|
|
|
|
|
2010-03-25 18:15:53 +08:00
|
|
|
id_entry = pdev->id_entry;
|
|
|
|
if (!id_entry) {
|
|
|
|
dev_err(&pdev->dev, "unknown fsi device\n");
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
2009-08-20 20:01:05 +08:00
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
|
|
irq = platform_get_irq(pdev, 0);
|
2009-12-17 00:10:09 +08:00
|
|
|
if (!res || (int)irq <= 0) {
|
2009-08-20 20:01:05 +08:00
|
|
|
dev_err(&pdev->dev, "Not enough FSI platform resources.\n");
|
|
|
|
ret = -ENODEV;
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
master = kzalloc(sizeof(*master), GFP_KERNEL);
|
|
|
|
if (!master) {
|
|
|
|
dev_err(&pdev->dev, "Could not allocate master\n");
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
master->base = ioremap_nocache(res->start, resource_size(res));
|
|
|
|
if (!master->base) {
|
|
|
|
ret = -ENXIO;
|
|
|
|
dev_err(&pdev->dev, "Unable to ioremap FSI registers.\n");
|
|
|
|
goto exit_kfree;
|
|
|
|
}
|
|
|
|
|
2010-07-29 15:48:32 +08:00
|
|
|
/* master setting */
|
2009-08-20 20:01:05 +08:00
|
|
|
master->irq = irq;
|
|
|
|
master->info = pdev->dev.platform_data;
|
2010-07-29 15:48:32 +08:00
|
|
|
master->core = (struct fsi_core *)id_entry->driver_data;
|
|
|
|
spin_lock_init(&master->lock);
|
|
|
|
|
|
|
|
/* FSI A setting */
|
2009-08-20 20:01:05 +08:00
|
|
|
master->fsia.base = master->base;
|
2009-12-02 14:11:08 +08:00
|
|
|
master->fsia.master = master;
|
2010-07-29 15:48:32 +08:00
|
|
|
|
|
|
|
/* FSI B setting */
|
2009-08-20 20:01:05 +08:00
|
|
|
master->fsib.base = master->base + 0x40;
|
2009-12-02 14:11:08 +08:00
|
|
|
master->fsib.master = master;
|
2009-08-20 20:01:05 +08:00
|
|
|
|
2009-11-30 19:24:48 +08:00
|
|
|
pm_runtime_enable(&pdev->dev);
|
2010-03-18 04:15:21 +08:00
|
|
|
dev_set_drvdata(&pdev->dev, master);
|
2009-08-20 20:01:05 +08:00
|
|
|
|
2011-09-22 16:59:20 +08:00
|
|
|
ret = request_irq(irq, &fsi_interrupt, 0,
|
2010-03-25 18:15:53 +08:00
|
|
|
id_entry->name, master);
|
2009-08-20 20:01:05 +08:00
|
|
|
if (ret) {
|
|
|
|
dev_err(&pdev->dev, "irq request err\n");
|
2009-10-30 11:02:39 +08:00
|
|
|
goto exit_iounmap;
|
2009-08-20 20:01:05 +08:00
|
|
|
}
|
|
|
|
|
2010-03-18 04:15:21 +08:00
|
|
|
ret = snd_soc_register_platform(&pdev->dev, &fsi_soc_platform);
|
2009-08-20 20:01:05 +08:00
|
|
|
if (ret < 0) {
|
|
|
|
dev_err(&pdev->dev, "cannot snd soc register\n");
|
|
|
|
goto exit_free_irq;
|
|
|
|
}
|
|
|
|
|
2011-04-08 14:09:02 +08:00
|
|
|
ret = snd_soc_register_dais(&pdev->dev, fsi_soc_dai,
|
|
|
|
ARRAY_SIZE(fsi_soc_dai));
|
|
|
|
if (ret < 0) {
|
|
|
|
dev_err(&pdev->dev, "cannot snd dai register\n");
|
|
|
|
goto exit_snd_soc;
|
|
|
|
}
|
2009-08-20 20:01:05 +08:00
|
|
|
|
2011-04-08 14:09:02 +08:00
|
|
|
return ret;
|
|
|
|
|
|
|
|
exit_snd_soc:
|
|
|
|
snd_soc_unregister_platform(&pdev->dev);
|
2009-08-20 20:01:05 +08:00
|
|
|
exit_free_irq:
|
|
|
|
free_irq(irq, master);
|
|
|
|
exit_iounmap:
|
|
|
|
iounmap(master->base);
|
2009-11-30 19:24:48 +08:00
|
|
|
pm_runtime_disable(&pdev->dev);
|
2009-08-20 20:01:05 +08:00
|
|
|
exit_kfree:
|
|
|
|
kfree(master);
|
|
|
|
master = NULL;
|
|
|
|
exit:
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int fsi_remove(struct platform_device *pdev)
|
|
|
|
{
|
2009-12-02 14:11:08 +08:00
|
|
|
struct fsi_master *master;
|
|
|
|
|
2010-03-18 04:15:21 +08:00
|
|
|
master = dev_get_drvdata(&pdev->dev);
|
2009-12-02 14:11:08 +08:00
|
|
|
|
2011-04-08 14:09:25 +08:00
|
|
|
free_irq(master->irq, master);
|
2009-11-30 19:24:48 +08:00
|
|
|
pm_runtime_disable(&pdev->dev);
|
2009-08-20 20:01:05 +08:00
|
|
|
|
2011-04-08 14:09:25 +08:00
|
|
|
snd_soc_unregister_dais(&pdev->dev, ARRAY_SIZE(fsi_soc_dai));
|
|
|
|
snd_soc_unregister_platform(&pdev->dev);
|
2009-08-20 20:01:05 +08:00
|
|
|
|
|
|
|
iounmap(master->base);
|
|
|
|
kfree(master);
|
2009-12-02 14:11:08 +08:00
|
|
|
|
2009-08-20 20:01:05 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-04-21 09:33:47 +08:00
|
|
|
static void __fsi_suspend(struct fsi_priv *fsi,
|
2011-05-23 19:46:35 +08:00
|
|
|
int is_play,
|
2011-05-23 19:46:18 +08:00
|
|
|
struct device *dev)
|
2011-04-21 09:33:47 +08:00
|
|
|
{
|
2011-05-23 19:46:35 +08:00
|
|
|
if (!fsi_stream_is_working(fsi, is_play))
|
|
|
|
return;
|
2011-04-21 09:33:47 +08:00
|
|
|
|
2011-05-23 19:46:35 +08:00
|
|
|
fsi_port_stop(fsi, is_play);
|
|
|
|
fsi_hw_shutdown(fsi, is_play, dev);
|
2011-04-21 09:33:47 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void __fsi_resume(struct fsi_priv *fsi,
|
2011-05-23 19:46:35 +08:00
|
|
|
int is_play,
|
2011-05-23 19:46:18 +08:00
|
|
|
struct device *dev)
|
2011-04-21 09:33:47 +08:00
|
|
|
{
|
2011-05-23 19:46:35 +08:00
|
|
|
if (!fsi_stream_is_working(fsi, is_play))
|
|
|
|
return;
|
2011-04-21 09:33:47 +08:00
|
|
|
|
2011-05-23 19:46:35 +08:00
|
|
|
fsi_hw_startup(fsi, is_play, dev);
|
|
|
|
|
|
|
|
if (fsi_is_clk_master(fsi) && fsi->rate)
|
2011-05-23 19:46:18 +08:00
|
|
|
fsi_set_master_clk(dev, fsi, fsi->rate, 1);
|
2011-05-23 19:46:35 +08:00
|
|
|
|
|
|
|
fsi_port_start(fsi, is_play);
|
|
|
|
|
2011-04-21 09:33:47 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int fsi_suspend(struct device *dev)
|
|
|
|
{
|
|
|
|
struct fsi_master *master = dev_get_drvdata(dev);
|
2011-05-23 19:46:35 +08:00
|
|
|
struct fsi_priv *fsia = &master->fsia;
|
|
|
|
struct fsi_priv *fsib = &master->fsib;
|
2011-04-21 09:33:47 +08:00
|
|
|
|
2011-05-23 19:46:35 +08:00
|
|
|
__fsi_suspend(fsia, 1, dev);
|
|
|
|
__fsi_suspend(fsia, 0, dev);
|
2011-04-21 09:33:47 +08:00
|
|
|
|
2011-05-23 19:46:35 +08:00
|
|
|
__fsi_suspend(fsib, 1, dev);
|
|
|
|
__fsi_suspend(fsib, 0, dev);
|
2011-04-21 09:33:47 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int fsi_resume(struct device *dev)
|
|
|
|
{
|
|
|
|
struct fsi_master *master = dev_get_drvdata(dev);
|
2011-05-23 19:46:35 +08:00
|
|
|
struct fsi_priv *fsia = &master->fsia;
|
|
|
|
struct fsi_priv *fsib = &master->fsib;
|
2011-04-21 09:33:47 +08:00
|
|
|
|
2011-05-23 19:46:35 +08:00
|
|
|
__fsi_resume(fsia, 1, dev);
|
|
|
|
__fsi_resume(fsia, 0, dev);
|
2011-04-21 09:33:47 +08:00
|
|
|
|
2011-05-23 19:46:35 +08:00
|
|
|
__fsi_resume(fsib, 1, dev);
|
|
|
|
__fsi_resume(fsib, 0, dev);
|
2011-04-21 09:33:47 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-11-30 19:24:48 +08:00
|
|
|
static struct dev_pm_ops fsi_pm_ops = {
|
2011-04-21 09:33:47 +08:00
|
|
|
.suspend = fsi_suspend,
|
|
|
|
.resume = fsi_resume,
|
2009-11-30 19:24:48 +08:00
|
|
|
};
|
|
|
|
|
2010-07-13 11:13:04 +08:00
|
|
|
static struct fsi_core fsi1_core = {
|
|
|
|
.ver = 1,
|
|
|
|
|
|
|
|
/* Interrupt */
|
2010-03-25 18:15:53 +08:00
|
|
|
.int_st = INT_ST,
|
|
|
|
.iemsk = IEMSK,
|
|
|
|
.imsk = IMSK,
|
|
|
|
};
|
|
|
|
|
2010-07-13 11:13:04 +08:00
|
|
|
static struct fsi_core fsi2_core = {
|
|
|
|
.ver = 2,
|
|
|
|
|
|
|
|
/* Interrupt */
|
2010-03-25 18:15:53 +08:00
|
|
|
.int_st = CPU_INT_ST,
|
|
|
|
.iemsk = CPU_IEMSK,
|
|
|
|
.imsk = CPU_IMSK,
|
2010-12-03 16:37:44 +08:00
|
|
|
.a_mclk = A_MST_CTLR,
|
|
|
|
.b_mclk = B_MST_CTLR,
|
2010-03-25 18:15:53 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
static struct platform_device_id fsi_id_table[] = {
|
2010-07-13 11:13:04 +08:00
|
|
|
{ "sh_fsi", (kernel_ulong_t)&fsi1_core },
|
|
|
|
{ "sh_fsi2", (kernel_ulong_t)&fsi2_core },
|
2010-10-05 23:54:28 +08:00
|
|
|
{},
|
2010-03-25 18:15:53 +08:00
|
|
|
};
|
2010-09-17 21:42:05 +08:00
|
|
|
MODULE_DEVICE_TABLE(platform, fsi_id_table);
|
2010-03-25 18:15:53 +08:00
|
|
|
|
2009-08-20 20:01:05 +08:00
|
|
|
static struct platform_driver fsi_driver = {
|
|
|
|
.driver = {
|
2010-03-18 04:15:21 +08:00
|
|
|
.name = "fsi-pcm-audio",
|
2009-11-30 19:24:48 +08:00
|
|
|
.pm = &fsi_pm_ops,
|
2009-08-20 20:01:05 +08:00
|
|
|
},
|
|
|
|
.probe = fsi_probe,
|
|
|
|
.remove = fsi_remove,
|
2010-03-25 18:15:53 +08:00
|
|
|
.id_table = fsi_id_table,
|
2009-08-20 20:01:05 +08:00
|
|
|
};
|
|
|
|
|
2011-11-25 10:15:07 +08:00
|
|
|
module_platform_driver(fsi_driver);
|
2009-08-20 20:01:05 +08:00
|
|
|
|
|
|
|
MODULE_LICENSE("GPL");
|
|
|
|
MODULE_DESCRIPTION("SuperH onchip FSI audio driver");
|
|
|
|
MODULE_AUTHOR("Kuninori Morimoto <morimoto.kuninori@renesas.com>");
|
2011-04-16 02:17:34 +08:00
|
|
|
MODULE_ALIAS("platform:fsi-pcm-audio");
|