Merge branch 'topic/asoc' into for-linus
* topic/asoc: (226 commits) ASoC: au1x: PSC-AC97 bugfixes ASoC: Fix WM835x Out4 capture enumeration ASoC: Remove unuused hw_read_t ASoC: fix pxa2xx-ac97.c breakage ASoC: Fully specify DC servo bits to update in wm_hubs ASoC: Debugged improper setting of PLL fields in WM8580 driver ASoC: new board driver to connect bfin-5xx with ad1836 codec ASoC: OMAP: Add functionality to set CLKR and FSR sources in McBSP DAI ASoC: davinci: i2c device creation moved into board files ASoC: Don't reconfigure WM8350 FLL if not needed ASoC: Fix s3c-i2s-v2 build ASoC: Make platform data optional for TLV320AIC3x ASoC: Add S3C24xx dependencies for Simtec machines ASoC: SDP3430: Fix TWL GPIO6 pin mux request ASoC: S3C platform: Fix s3c2410_dma_started() called at improper time ARM: OMAP: McBSP: Merge two functions into omap_mcbsp_start/_stop ASoC: OMAP: Fix setup of XCCR and RCCR registers in McBSP DAI OMAP: McBSP: Use textual values in DMA operating mode sysfs files ARM: OMAP: DMA: Add support for DMA channel self linking on OMAP1510 ASoC: Select core DMA when building for S3C64xx ...
This commit is contained in:
commit
e0b3032bcd
|
@ -128,6 +128,7 @@ static struct omap_mcbsp_platform_data omap34xx_mcbsp_pdata[] = {
|
|||
.rx_irq = INT_24XX_MCBSP1_IRQ_RX,
|
||||
.tx_irq = INT_24XX_MCBSP1_IRQ_TX,
|
||||
.ops = &omap2_mcbsp_ops,
|
||||
.buffer_size = 0x6F,
|
||||
},
|
||||
{
|
||||
.phys_base = OMAP34XX_MCBSP2_BASE,
|
||||
|
@ -136,6 +137,7 @@ static struct omap_mcbsp_platform_data omap34xx_mcbsp_pdata[] = {
|
|||
.rx_irq = INT_24XX_MCBSP2_IRQ_RX,
|
||||
.tx_irq = INT_24XX_MCBSP2_IRQ_TX,
|
||||
.ops = &omap2_mcbsp_ops,
|
||||
.buffer_size = 0x3FF,
|
||||
},
|
||||
{
|
||||
.phys_base = OMAP34XX_MCBSP3_BASE,
|
||||
|
@ -144,6 +146,7 @@ static struct omap_mcbsp_platform_data omap34xx_mcbsp_pdata[] = {
|
|||
.rx_irq = INT_24XX_MCBSP3_IRQ_RX,
|
||||
.tx_irq = INT_24XX_MCBSP3_IRQ_TX,
|
||||
.ops = &omap2_mcbsp_ops,
|
||||
.buffer_size = 0x6F,
|
||||
},
|
||||
{
|
||||
.phys_base = OMAP34XX_MCBSP4_BASE,
|
||||
|
@ -152,6 +155,7 @@ static struct omap_mcbsp_platform_data omap34xx_mcbsp_pdata[] = {
|
|||
.rx_irq = INT_24XX_MCBSP4_IRQ_RX,
|
||||
.tx_irq = INT_24XX_MCBSP4_IRQ_TX,
|
||||
.ops = &omap2_mcbsp_ops,
|
||||
.buffer_size = 0x6F,
|
||||
},
|
||||
{
|
||||
.phys_base = OMAP34XX_MCBSP5_BASE,
|
||||
|
@ -160,6 +164,7 @@ static struct omap_mcbsp_platform_data omap34xx_mcbsp_pdata[] = {
|
|||
.rx_irq = INT_24XX_MCBSP5_IRQ_RX,
|
||||
.tx_irq = INT_24XX_MCBSP5_IRQ_TX,
|
||||
.ops = &omap2_mcbsp_ops,
|
||||
.buffer_size = 0x6F,
|
||||
},
|
||||
};
|
||||
#define OMAP34XX_MCBSP_PDATA_SZ ARRAY_SIZE(omap34xx_mcbsp_pdata)
|
||||
|
|
|
@ -3,10 +3,12 @@
|
|||
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/ac97_codec.h>
|
||||
|
||||
/*
|
||||
* @reset_gpio: AC97 reset gpio (normally gpio113 or gpio95)
|
||||
* a -1 value means no gpio will be used for reset
|
||||
* @codec_pdata: AC97 codec platform_data
|
||||
|
||||
* reset_gpio should only be specified for pxa27x CPUs where a silicon
|
||||
* bug prevents correct operation of the reset line. If not specified,
|
||||
|
@ -20,6 +22,7 @@ typedef struct {
|
|||
void (*resume)(void *);
|
||||
void *priv;
|
||||
int reset_gpio;
|
||||
void *codec_pdata[AC97_BUS_MAX_DEVICES];
|
||||
} pxa2xx_audio_ops_t;
|
||||
|
||||
extern void pxa_set_ac97_info(pxa2xx_audio_ops_t *ops);
|
||||
|
|
|
@ -1127,6 +1127,11 @@ int omap_dma_running(void)
|
|||
void omap_dma_link_lch(int lch_head, int lch_queue)
|
||||
{
|
||||
if (omap_dma_in_1510_mode()) {
|
||||
if (lch_head == lch_queue) {
|
||||
dma_write(dma_read(CCR(lch_head)) | (3 << 8),
|
||||
CCR(lch_head));
|
||||
return;
|
||||
}
|
||||
printk(KERN_ERR "DMA linking is not supported in 1510 mode\n");
|
||||
BUG();
|
||||
return;
|
||||
|
@ -1149,6 +1154,11 @@ EXPORT_SYMBOL(omap_dma_link_lch);
|
|||
void omap_dma_unlink_lch(int lch_head, int lch_queue)
|
||||
{
|
||||
if (omap_dma_in_1510_mode()) {
|
||||
if (lch_head == lch_queue) {
|
||||
dma_write(dma_read(CCR(lch_head)) & ~(3 << 8),
|
||||
CCR(lch_head));
|
||||
return;
|
||||
}
|
||||
printk(KERN_ERR "DMA linking is not supported in 1510 mode\n");
|
||||
BUG();
|
||||
return;
|
||||
|
|
|
@ -134,6 +134,11 @@
|
|||
#define OMAP_MCBSP_REG_XCERG 0x74
|
||||
#define OMAP_MCBSP_REG_XCERH 0x78
|
||||
#define OMAP_MCBSP_REG_SYSCON 0x8C
|
||||
#define OMAP_MCBSP_REG_THRSH2 0x90
|
||||
#define OMAP_MCBSP_REG_THRSH1 0x94
|
||||
#define OMAP_MCBSP_REG_IRQST 0xA0
|
||||
#define OMAP_MCBSP_REG_IRQEN 0xA4
|
||||
#define OMAP_MCBSP_REG_WAKEUPEN 0xA8
|
||||
#define OMAP_MCBSP_REG_XCCR 0xAC
|
||||
#define OMAP_MCBSP_REG_RCCR 0xB0
|
||||
|
||||
|
@ -249,8 +254,27 @@
|
|||
#define RDISABLE 0x0001
|
||||
|
||||
/********************** McBSP SYSCONFIG bit definitions ********************/
|
||||
#define CLOCKACTIVITY(value) ((value)<<8)
|
||||
#define SIDLEMODE(value) ((value)<<3)
|
||||
#define ENAWAKEUP 0x0004
|
||||
#define SOFTRST 0x0002
|
||||
|
||||
/********************** McBSP DMA operating modes **************************/
|
||||
#define MCBSP_DMA_MODE_ELEMENT 0
|
||||
#define MCBSP_DMA_MODE_THRESHOLD 1
|
||||
#define MCBSP_DMA_MODE_FRAME 2
|
||||
|
||||
/********************** McBSP WAKEUPEN bit definitions *********************/
|
||||
#define XEMPTYEOFEN 0x4000
|
||||
#define XRDYEN 0x0400
|
||||
#define XEOFEN 0x0200
|
||||
#define XFSXEN 0x0100
|
||||
#define XSYNCERREN 0x0080
|
||||
#define RRDYEN 0x0008
|
||||
#define REOFEN 0x0004
|
||||
#define RFSREN 0x0002
|
||||
#define RSYNCERREN 0x0001
|
||||
|
||||
/* we don't do multichannel for now */
|
||||
struct omap_mcbsp_reg_cfg {
|
||||
u16 spcr2;
|
||||
|
@ -344,6 +368,9 @@ struct omap_mcbsp_platform_data {
|
|||
u8 dma_rx_sync, dma_tx_sync;
|
||||
u16 rx_irq, tx_irq;
|
||||
struct omap_mcbsp_ops *ops;
|
||||
#ifdef CONFIG_ARCH_OMAP34XX
|
||||
u16 buffer_size;
|
||||
#endif
|
||||
};
|
||||
|
||||
struct omap_mcbsp {
|
||||
|
@ -377,6 +404,11 @@ struct omap_mcbsp {
|
|||
struct omap_mcbsp_platform_data *pdata;
|
||||
struct clk *iclk;
|
||||
struct clk *fclk;
|
||||
#ifdef CONFIG_ARCH_OMAP34XX
|
||||
int dma_op_mode;
|
||||
u16 max_tx_thres;
|
||||
u16 max_rx_thres;
|
||||
#endif
|
||||
};
|
||||
extern struct omap_mcbsp **mcbsp_ptr;
|
||||
extern int omap_mcbsp_count;
|
||||
|
@ -385,10 +417,25 @@ int omap_mcbsp_init(void);
|
|||
void omap_mcbsp_register_board_cfg(struct omap_mcbsp_platform_data *config,
|
||||
int size);
|
||||
void omap_mcbsp_config(unsigned int id, const struct omap_mcbsp_reg_cfg * config);
|
||||
#ifdef CONFIG_ARCH_OMAP34XX
|
||||
void omap_mcbsp_set_tx_threshold(unsigned int id, u16 threshold);
|
||||
void omap_mcbsp_set_rx_threshold(unsigned int id, u16 threshold);
|
||||
u16 omap_mcbsp_get_max_tx_threshold(unsigned int id);
|
||||
u16 omap_mcbsp_get_max_rx_threshold(unsigned int id);
|
||||
int omap_mcbsp_get_dma_op_mode(unsigned int id);
|
||||
#else
|
||||
static inline void omap_mcbsp_set_tx_threshold(unsigned int id, u16 threshold)
|
||||
{ }
|
||||
static inline void omap_mcbsp_set_rx_threshold(unsigned int id, u16 threshold)
|
||||
{ }
|
||||
static inline u16 omap_mcbsp_get_max_tx_threshold(unsigned int id) { return 0; }
|
||||
static inline u16 omap_mcbsp_get_max_rx_threshold(unsigned int id) { return 0; }
|
||||
static inline int omap_mcbsp_get_dma_op_mode(unsigned int id) { return 0; }
|
||||
#endif
|
||||
int omap_mcbsp_request(unsigned int id);
|
||||
void omap_mcbsp_free(unsigned int id);
|
||||
void omap_mcbsp_start(unsigned int id);
|
||||
void omap_mcbsp_stop(unsigned int id);
|
||||
void omap_mcbsp_start(unsigned int id, int tx, int rx);
|
||||
void omap_mcbsp_stop(unsigned int id, int tx, int rx);
|
||||
void omap_mcbsp_xmit_word(unsigned int id, u32 word);
|
||||
u32 omap_mcbsp_recv_word(unsigned int id);
|
||||
|
||||
|
|
|
@ -198,6 +198,170 @@ void omap_mcbsp_config(unsigned int id, const struct omap_mcbsp_reg_cfg *config)
|
|||
}
|
||||
EXPORT_SYMBOL(omap_mcbsp_config);
|
||||
|
||||
#ifdef CONFIG_ARCH_OMAP34XX
|
||||
/*
|
||||
* omap_mcbsp_set_tx_threshold configures how to deal
|
||||
* with transmit threshold. the threshold value and handler can be
|
||||
* configure in here.
|
||||
*/
|
||||
void omap_mcbsp_set_tx_threshold(unsigned int id, u16 threshold)
|
||||
{
|
||||
struct omap_mcbsp *mcbsp;
|
||||
void __iomem *io_base;
|
||||
|
||||
if (!cpu_is_omap34xx())
|
||||
return;
|
||||
|
||||
if (!omap_mcbsp_check_valid_id(id)) {
|
||||
printk(KERN_ERR "%s: Invalid id (%d)\n", __func__, id + 1);
|
||||
return;
|
||||
}
|
||||
mcbsp = id_to_mcbsp_ptr(id);
|
||||
io_base = mcbsp->io_base;
|
||||
|
||||
OMAP_MCBSP_WRITE(io_base, THRSH2, threshold);
|
||||
}
|
||||
EXPORT_SYMBOL(omap_mcbsp_set_tx_threshold);
|
||||
|
||||
/*
|
||||
* omap_mcbsp_set_rx_threshold configures how to deal
|
||||
* with receive threshold. the threshold value and handler can be
|
||||
* configure in here.
|
||||
*/
|
||||
void omap_mcbsp_set_rx_threshold(unsigned int id, u16 threshold)
|
||||
{
|
||||
struct omap_mcbsp *mcbsp;
|
||||
void __iomem *io_base;
|
||||
|
||||
if (!cpu_is_omap34xx())
|
||||
return;
|
||||
|
||||
if (!omap_mcbsp_check_valid_id(id)) {
|
||||
printk(KERN_ERR "%s: Invalid id (%d)\n", __func__, id + 1);
|
||||
return;
|
||||
}
|
||||
mcbsp = id_to_mcbsp_ptr(id);
|
||||
io_base = mcbsp->io_base;
|
||||
|
||||
OMAP_MCBSP_WRITE(io_base, THRSH1, threshold);
|
||||
}
|
||||
EXPORT_SYMBOL(omap_mcbsp_set_rx_threshold);
|
||||
|
||||
/*
|
||||
* omap_mcbsp_get_max_tx_thres just return the current configured
|
||||
* maximum threshold for transmission
|
||||
*/
|
||||
u16 omap_mcbsp_get_max_tx_threshold(unsigned int id)
|
||||
{
|
||||
struct omap_mcbsp *mcbsp;
|
||||
|
||||
if (!omap_mcbsp_check_valid_id(id)) {
|
||||
printk(KERN_ERR "%s: Invalid id (%d)\n", __func__, id + 1);
|
||||
return -ENODEV;
|
||||
}
|
||||
mcbsp = id_to_mcbsp_ptr(id);
|
||||
|
||||
return mcbsp->max_tx_thres;
|
||||
}
|
||||
EXPORT_SYMBOL(omap_mcbsp_get_max_tx_threshold);
|
||||
|
||||
/*
|
||||
* omap_mcbsp_get_max_rx_thres just return the current configured
|
||||
* maximum threshold for reception
|
||||
*/
|
||||
u16 omap_mcbsp_get_max_rx_threshold(unsigned int id)
|
||||
{
|
||||
struct omap_mcbsp *mcbsp;
|
||||
|
||||
if (!omap_mcbsp_check_valid_id(id)) {
|
||||
printk(KERN_ERR "%s: Invalid id (%d)\n", __func__, id + 1);
|
||||
return -ENODEV;
|
||||
}
|
||||
mcbsp = id_to_mcbsp_ptr(id);
|
||||
|
||||
return mcbsp->max_rx_thres;
|
||||
}
|
||||
EXPORT_SYMBOL(omap_mcbsp_get_max_rx_threshold);
|
||||
|
||||
/*
|
||||
* omap_mcbsp_get_dma_op_mode just return the current configured
|
||||
* operating mode for the mcbsp channel
|
||||
*/
|
||||
int omap_mcbsp_get_dma_op_mode(unsigned int id)
|
||||
{
|
||||
struct omap_mcbsp *mcbsp;
|
||||
int dma_op_mode;
|
||||
|
||||
if (!omap_mcbsp_check_valid_id(id)) {
|
||||
printk(KERN_ERR "%s: Invalid id (%u)\n", __func__, id + 1);
|
||||
return -ENODEV;
|
||||
}
|
||||
mcbsp = id_to_mcbsp_ptr(id);
|
||||
|
||||
spin_lock_irq(&mcbsp->lock);
|
||||
dma_op_mode = mcbsp->dma_op_mode;
|
||||
spin_unlock_irq(&mcbsp->lock);
|
||||
|
||||
return dma_op_mode;
|
||||
}
|
||||
EXPORT_SYMBOL(omap_mcbsp_get_dma_op_mode);
|
||||
|
||||
static inline void omap34xx_mcbsp_request(struct omap_mcbsp *mcbsp)
|
||||
{
|
||||
/*
|
||||
* Enable wakup behavior, smart idle and all wakeups
|
||||
* REVISIT: some wakeups may be unnecessary
|
||||
*/
|
||||
if (cpu_is_omap34xx()) {
|
||||
u16 syscon;
|
||||
|
||||
syscon = OMAP_MCBSP_READ(mcbsp->io_base, SYSCON);
|
||||
syscon &= ~(ENAWAKEUP | SIDLEMODE(0x03) | CLOCKACTIVITY(0x03));
|
||||
|
||||
spin_lock_irq(&mcbsp->lock);
|
||||
if (mcbsp->dma_op_mode == MCBSP_DMA_MODE_THRESHOLD) {
|
||||
syscon |= (ENAWAKEUP | SIDLEMODE(0x02) |
|
||||
CLOCKACTIVITY(0x02));
|
||||
OMAP_MCBSP_WRITE(mcbsp->io_base, WAKEUPEN,
|
||||
XRDYEN | RRDYEN);
|
||||
} else {
|
||||
syscon |= SIDLEMODE(0x01);
|
||||
}
|
||||
spin_unlock_irq(&mcbsp->lock);
|
||||
|
||||
OMAP_MCBSP_WRITE(mcbsp->io_base, SYSCON, syscon);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void omap34xx_mcbsp_free(struct omap_mcbsp *mcbsp)
|
||||
{
|
||||
/*
|
||||
* Disable wakup behavior, smart idle and all wakeups
|
||||
*/
|
||||
if (cpu_is_omap34xx()) {
|
||||
u16 syscon;
|
||||
|
||||
syscon = OMAP_MCBSP_READ(mcbsp->io_base, SYSCON);
|
||||
syscon &= ~(ENAWAKEUP | SIDLEMODE(0x03) | CLOCKACTIVITY(0x03));
|
||||
/*
|
||||
* HW bug workaround - If no_idle mode is taken, we need to
|
||||
* go to smart_idle before going to always_idle, or the
|
||||
* device will not hit retention anymore.
|
||||
*/
|
||||
syscon |= SIDLEMODE(0x02);
|
||||
OMAP_MCBSP_WRITE(mcbsp->io_base, SYSCON, syscon);
|
||||
|
||||
syscon &= ~(SIDLEMODE(0x03));
|
||||
OMAP_MCBSP_WRITE(mcbsp->io_base, SYSCON, syscon);
|
||||
|
||||
OMAP_MCBSP_WRITE(mcbsp->io_base, WAKEUPEN, 0);
|
||||
}
|
||||
}
|
||||
#else
|
||||
static inline void omap34xx_mcbsp_request(struct omap_mcbsp *mcbsp) {}
|
||||
static inline void omap34xx_mcbsp_free(struct omap_mcbsp *mcbsp) {}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* We can choose between IRQ based or polled IO.
|
||||
* This needs to be called before omap_mcbsp_request().
|
||||
|
@ -257,6 +421,9 @@ int omap_mcbsp_request(unsigned int id)
|
|||
clk_enable(mcbsp->iclk);
|
||||
clk_enable(mcbsp->fclk);
|
||||
|
||||
/* Do procedure specific to omap34xx arch, if applicable */
|
||||
omap34xx_mcbsp_request(mcbsp);
|
||||
|
||||
/*
|
||||
* Make sure that transmitter, receiver and sample-rate generator are
|
||||
* not running before activating IRQs.
|
||||
|
@ -305,6 +472,9 @@ void omap_mcbsp_free(unsigned int id)
|
|||
if (mcbsp->pdata && mcbsp->pdata->ops && mcbsp->pdata->ops->free)
|
||||
mcbsp->pdata->ops->free(id);
|
||||
|
||||
/* Do procedure specific to omap34xx arch, if applicable */
|
||||
omap34xx_mcbsp_free(mcbsp);
|
||||
|
||||
clk_disable(mcbsp->fclk);
|
||||
clk_disable(mcbsp->iclk);
|
||||
|
||||
|
@ -328,14 +498,15 @@ void omap_mcbsp_free(unsigned int id)
|
|||
EXPORT_SYMBOL(omap_mcbsp_free);
|
||||
|
||||
/*
|
||||
* Here we start the McBSP, by enabling the sample
|
||||
* generator, both transmitter and receivers,
|
||||
* and the frame sync.
|
||||
* Here we start the McBSP, by enabling transmitter, receiver or both.
|
||||
* If no transmitter or receiver is active prior calling, then sample-rate
|
||||
* generator and frame sync are started.
|
||||
*/
|
||||
void omap_mcbsp_start(unsigned int id)
|
||||
void omap_mcbsp_start(unsigned int id, int tx, int rx)
|
||||
{
|
||||
struct omap_mcbsp *mcbsp;
|
||||
void __iomem *io_base;
|
||||
int idle;
|
||||
u16 w;
|
||||
|
||||
if (!omap_mcbsp_check_valid_id(id)) {
|
||||
|
@ -348,32 +519,58 @@ void omap_mcbsp_start(unsigned int id)
|
|||
mcbsp->rx_word_length = (OMAP_MCBSP_READ(io_base, RCR1) >> 5) & 0x7;
|
||||
mcbsp->tx_word_length = (OMAP_MCBSP_READ(io_base, XCR1) >> 5) & 0x7;
|
||||
|
||||
/* Start the sample generator */
|
||||
w = OMAP_MCBSP_READ(io_base, SPCR2);
|
||||
OMAP_MCBSP_WRITE(io_base, SPCR2, w | (1 << 6));
|
||||
idle = !((OMAP_MCBSP_READ(io_base, SPCR2) |
|
||||
OMAP_MCBSP_READ(io_base, SPCR1)) & 1);
|
||||
|
||||
if (idle) {
|
||||
/* Start the sample generator */
|
||||
w = OMAP_MCBSP_READ(io_base, SPCR2);
|
||||
OMAP_MCBSP_WRITE(io_base, SPCR2, w | (1 << 6));
|
||||
}
|
||||
|
||||
/* Enable transmitter and receiver */
|
||||
tx &= 1;
|
||||
w = OMAP_MCBSP_READ(io_base, SPCR2);
|
||||
OMAP_MCBSP_WRITE(io_base, SPCR2, w | 1);
|
||||
OMAP_MCBSP_WRITE(io_base, SPCR2, w | tx);
|
||||
|
||||
rx &= 1;
|
||||
w = OMAP_MCBSP_READ(io_base, SPCR1);
|
||||
OMAP_MCBSP_WRITE(io_base, SPCR1, w | 1);
|
||||
OMAP_MCBSP_WRITE(io_base, SPCR1, w | rx);
|
||||
|
||||
udelay(100);
|
||||
/*
|
||||
* Worst case: CLKSRG*2 = 8000khz: (1/8000) * 2 * 2 usec
|
||||
* REVISIT: 100us may give enough time for two CLKSRG, however
|
||||
* due to some unknown PM related, clock gating etc. reason it
|
||||
* is now at 500us.
|
||||
*/
|
||||
udelay(500);
|
||||
|
||||
/* Start frame sync */
|
||||
w = OMAP_MCBSP_READ(io_base, SPCR2);
|
||||
OMAP_MCBSP_WRITE(io_base, SPCR2, w | (1 << 7));
|
||||
if (idle) {
|
||||
/* Start frame sync */
|
||||
w = OMAP_MCBSP_READ(io_base, SPCR2);
|
||||
OMAP_MCBSP_WRITE(io_base, SPCR2, w | (1 << 7));
|
||||
}
|
||||
|
||||
if (cpu_is_omap2430() || cpu_is_omap34xx()) {
|
||||
/* Release the transmitter and receiver */
|
||||
w = OMAP_MCBSP_READ(io_base, XCCR);
|
||||
w &= ~(tx ? XDISABLE : 0);
|
||||
OMAP_MCBSP_WRITE(io_base, XCCR, w);
|
||||
w = OMAP_MCBSP_READ(io_base, RCCR);
|
||||
w &= ~(rx ? RDISABLE : 0);
|
||||
OMAP_MCBSP_WRITE(io_base, RCCR, w);
|
||||
}
|
||||
|
||||
/* Dump McBSP Regs */
|
||||
omap_mcbsp_dump_reg(id);
|
||||
}
|
||||
EXPORT_SYMBOL(omap_mcbsp_start);
|
||||
|
||||
void omap_mcbsp_stop(unsigned int id)
|
||||
void omap_mcbsp_stop(unsigned int id, int tx, int rx)
|
||||
{
|
||||
struct omap_mcbsp *mcbsp;
|
||||
void __iomem *io_base;
|
||||
int idle;
|
||||
u16 w;
|
||||
|
||||
if (!omap_mcbsp_check_valid_id(id)) {
|
||||
|
@ -385,16 +582,33 @@ void omap_mcbsp_stop(unsigned int id)
|
|||
io_base = mcbsp->io_base;
|
||||
|
||||
/* Reset transmitter */
|
||||
tx &= 1;
|
||||
if (cpu_is_omap2430() || cpu_is_omap34xx()) {
|
||||
w = OMAP_MCBSP_READ(io_base, XCCR);
|
||||
w |= (tx ? XDISABLE : 0);
|
||||
OMAP_MCBSP_WRITE(io_base, XCCR, w);
|
||||
}
|
||||
w = OMAP_MCBSP_READ(io_base, SPCR2);
|
||||
OMAP_MCBSP_WRITE(io_base, SPCR2, w & ~(1));
|
||||
OMAP_MCBSP_WRITE(io_base, SPCR2, w & ~tx);
|
||||
|
||||
/* Reset receiver */
|
||||
rx &= 1;
|
||||
if (cpu_is_omap2430() || cpu_is_omap34xx()) {
|
||||
w = OMAP_MCBSP_READ(io_base, RCCR);
|
||||
w |= (tx ? RDISABLE : 0);
|
||||
OMAP_MCBSP_WRITE(io_base, RCCR, w);
|
||||
}
|
||||
w = OMAP_MCBSP_READ(io_base, SPCR1);
|
||||
OMAP_MCBSP_WRITE(io_base, SPCR1, w & ~(1));
|
||||
OMAP_MCBSP_WRITE(io_base, SPCR1, w & ~rx);
|
||||
|
||||
/* Reset the sample rate generator */
|
||||
w = OMAP_MCBSP_READ(io_base, SPCR2);
|
||||
OMAP_MCBSP_WRITE(io_base, SPCR2, w & ~(1 << 6));
|
||||
idle = !((OMAP_MCBSP_READ(io_base, SPCR2) |
|
||||
OMAP_MCBSP_READ(io_base, SPCR1)) & 1);
|
||||
|
||||
if (idle) {
|
||||
/* Reset the sample rate generator */
|
||||
w = OMAP_MCBSP_READ(io_base, SPCR2);
|
||||
OMAP_MCBSP_WRITE(io_base, SPCR2, w & ~(1 << 6));
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(omap_mcbsp_stop);
|
||||
|
||||
|
@ -883,6 +1097,149 @@ void omap_mcbsp_set_spi_mode(unsigned int id,
|
|||
}
|
||||
EXPORT_SYMBOL(omap_mcbsp_set_spi_mode);
|
||||
|
||||
#ifdef CONFIG_ARCH_OMAP34XX
|
||||
#define max_thres(m) (mcbsp->pdata->buffer_size)
|
||||
#define valid_threshold(m, val) ((val) <= max_thres(m))
|
||||
#define THRESHOLD_PROP_BUILDER(prop) \
|
||||
static ssize_t prop##_show(struct device *dev, \
|
||||
struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
struct omap_mcbsp *mcbsp = dev_get_drvdata(dev); \
|
||||
\
|
||||
return sprintf(buf, "%u\n", mcbsp->prop); \
|
||||
} \
|
||||
\
|
||||
static ssize_t prop##_store(struct device *dev, \
|
||||
struct device_attribute *attr, \
|
||||
const char *buf, size_t size) \
|
||||
{ \
|
||||
struct omap_mcbsp *mcbsp = dev_get_drvdata(dev); \
|
||||
unsigned long val; \
|
||||
int status; \
|
||||
\
|
||||
status = strict_strtoul(buf, 0, &val); \
|
||||
if (status) \
|
||||
return status; \
|
||||
\
|
||||
if (!valid_threshold(mcbsp, val)) \
|
||||
return -EDOM; \
|
||||
\
|
||||
mcbsp->prop = val; \
|
||||
return size; \
|
||||
} \
|
||||
\
|
||||
static DEVICE_ATTR(prop, 0644, prop##_show, prop##_store);
|
||||
|
||||
THRESHOLD_PROP_BUILDER(max_tx_thres);
|
||||
THRESHOLD_PROP_BUILDER(max_rx_thres);
|
||||
|
||||
static const char *dma_op_modes[] = {
|
||||
"element", "threshold", "frame",
|
||||
};
|
||||
|
||||
static ssize_t dma_op_mode_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct omap_mcbsp *mcbsp = dev_get_drvdata(dev);
|
||||
int dma_op_mode, i = 0;
|
||||
ssize_t len = 0;
|
||||
const char * const *s;
|
||||
|
||||
spin_lock_irq(&mcbsp->lock);
|
||||
dma_op_mode = mcbsp->dma_op_mode;
|
||||
spin_unlock_irq(&mcbsp->lock);
|
||||
|
||||
for (s = &dma_op_modes[i]; i < ARRAY_SIZE(dma_op_modes); s++, i++) {
|
||||
if (dma_op_mode == i)
|
||||
len += sprintf(buf + len, "[%s] ", *s);
|
||||
else
|
||||
len += sprintf(buf + len, "%s ", *s);
|
||||
}
|
||||
len += sprintf(buf + len, "\n");
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static ssize_t dma_op_mode_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t size)
|
||||
{
|
||||
struct omap_mcbsp *mcbsp = dev_get_drvdata(dev);
|
||||
const char * const *s;
|
||||
int i = 0;
|
||||
|
||||
for (s = &dma_op_modes[i]; i < ARRAY_SIZE(dma_op_modes); s++, i++)
|
||||
if (sysfs_streq(buf, *s))
|
||||
break;
|
||||
|
||||
if (i == ARRAY_SIZE(dma_op_modes))
|
||||
return -EINVAL;
|
||||
|
||||
spin_lock_irq(&mcbsp->lock);
|
||||
if (!mcbsp->free) {
|
||||
size = -EBUSY;
|
||||
goto unlock;
|
||||
}
|
||||
mcbsp->dma_op_mode = i;
|
||||
|
||||
unlock:
|
||||
spin_unlock_irq(&mcbsp->lock);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(dma_op_mode, 0644, dma_op_mode_show, dma_op_mode_store);
|
||||
|
||||
static const struct attribute *additional_attrs[] = {
|
||||
&dev_attr_max_tx_thres.attr,
|
||||
&dev_attr_max_rx_thres.attr,
|
||||
&dev_attr_dma_op_mode.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group additional_attr_group = {
|
||||
.attrs = (struct attribute **)additional_attrs,
|
||||
};
|
||||
|
||||
static inline int __devinit omap_additional_add(struct device *dev)
|
||||
{
|
||||
return sysfs_create_group(&dev->kobj, &additional_attr_group);
|
||||
}
|
||||
|
||||
static inline void __devexit omap_additional_remove(struct device *dev)
|
||||
{
|
||||
sysfs_remove_group(&dev->kobj, &additional_attr_group);
|
||||
}
|
||||
|
||||
static inline void __devinit omap34xx_device_init(struct omap_mcbsp *mcbsp)
|
||||
{
|
||||
mcbsp->dma_op_mode = MCBSP_DMA_MODE_ELEMENT;
|
||||
if (cpu_is_omap34xx()) {
|
||||
mcbsp->max_tx_thres = max_thres(mcbsp);
|
||||
mcbsp->max_rx_thres = max_thres(mcbsp);
|
||||
/*
|
||||
* REVISIT: Set dmap_op_mode to THRESHOLD as default
|
||||
* for mcbsp2 instances.
|
||||
*/
|
||||
if (omap_additional_add(mcbsp->dev))
|
||||
dev_warn(mcbsp->dev,
|
||||
"Unable to create additional controls\n");
|
||||
} else {
|
||||
mcbsp->max_tx_thres = -EINVAL;
|
||||
mcbsp->max_rx_thres = -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void __devexit omap34xx_device_exit(struct omap_mcbsp *mcbsp)
|
||||
{
|
||||
if (cpu_is_omap34xx())
|
||||
omap_additional_remove(mcbsp->dev);
|
||||
}
|
||||
#else
|
||||
static inline void __devinit omap34xx_device_init(struct omap_mcbsp *mcbsp) {}
|
||||
static inline void __devexit omap34xx_device_exit(struct omap_mcbsp *mcbsp) {}
|
||||
#endif /* CONFIG_ARCH_OMAP34XX */
|
||||
|
||||
/*
|
||||
* McBSP1 and McBSP3 are directly mapped on 1610 and 1510.
|
||||
* 730 has only 2 McBSP, and both of them are MPU peripherals.
|
||||
|
@ -953,6 +1310,10 @@ static int __devinit omap_mcbsp_probe(struct platform_device *pdev)
|
|||
mcbsp->dev = &pdev->dev;
|
||||
mcbsp_ptr[id] = mcbsp;
|
||||
platform_set_drvdata(pdev, mcbsp);
|
||||
|
||||
/* Initialize mcbsp properties for OMAP34XX if needed / applicable */
|
||||
omap34xx_device_init(mcbsp);
|
||||
|
||||
return 0;
|
||||
|
||||
err_fclk:
|
||||
|
@ -976,6 +1337,8 @@ static int __devexit omap_mcbsp_remove(struct platform_device *pdev)
|
|||
mcbsp->pdata->ops->free)
|
||||
mcbsp->pdata->ops->free(mcbsp->id);
|
||||
|
||||
omap34xx_device_exit(mcbsp);
|
||||
|
||||
clk_disable(mcbsp->fclk);
|
||||
clk_disable(mcbsp->iclk);
|
||||
clk_put(mcbsp->fclk);
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
/* arch/arm/plat-s3c/include/plat/audio-simtec.h
|
||||
*
|
||||
* Copyright 2008 Simtec Electronics
|
||||
* http://armlinux.simtec.co.uk/
|
||||
* Ben Dooks <ben@simtec.co.uk>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Simtec Audio support.
|
||||
*/
|
||||
|
||||
/**
|
||||
* struct s3c24xx_audio_simtec_pdata - platform data for simtec audio
|
||||
* @use_mpllin: Select codec clock from MPLLin
|
||||
* @output_cdclk: Need to output CDCLK to the codec
|
||||
* @have_mic: Set if we have a MIC socket
|
||||
* @have_lout: Set if we have a LineOut socket
|
||||
* @amp_gpio: GPIO pin to enable the AMP
|
||||
* @amp_gain: Option GPIO to control AMP gain
|
||||
*/
|
||||
struct s3c24xx_audio_simtec_pdata {
|
||||
unsigned int use_mpllin:1;
|
||||
unsigned int output_cdclk:1;
|
||||
|
||||
unsigned int have_mic:1;
|
||||
unsigned int have_lout:1;
|
||||
|
||||
int amp_gpio;
|
||||
int amp_gain[2];
|
||||
|
||||
void (*startup)(void);
|
||||
};
|
||||
|
||||
extern int simtec_audio_add(const char *codec_name,
|
||||
struct s3c24xx_audio_simtec_pdata *pdata);
|
|
@ -33,6 +33,11 @@
|
|||
#define S3C2412_IISCON_RXDMA_ACTIVE (1 << 1)
|
||||
#define S3C2412_IISCON_IIS_ACTIVE (1 << 0)
|
||||
|
||||
#define S3C64XX_IISMOD_BLC_16BIT (0 << 13)
|
||||
#define S3C64XX_IISMOD_BLC_8BIT (1 << 13)
|
||||
#define S3C64XX_IISMOD_BLC_24BIT (2 << 13)
|
||||
#define S3C64XX_IISMOD_BLC_MASK (3 << 13)
|
||||
|
||||
#define S3C64XX_IISMOD_IMS_PCLK (0 << 10)
|
||||
#define S3C64XX_IISMOD_IMS_SYSMUX (1 << 10)
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
*/
|
||||
#define NR_UNIX98_PTY_DEFAULT 4096 /* Default maximum for Unix98 ptys */
|
||||
#define NR_UNIX98_PTY_MAX (1 << MINORBITS) /* Absolute limit */
|
||||
#define NR_LDISCS 19
|
||||
#define NR_LDISCS 20
|
||||
|
||||
/* line disciplines */
|
||||
#define N_TTY 0
|
||||
|
@ -47,6 +47,8 @@
|
|||
#define N_SLCAN 17 /* Serial / USB serial CAN Adaptors */
|
||||
#define N_PPS 18 /* Pulse per Second */
|
||||
|
||||
#define N_V253 19 /* Codec control over voice modem */
|
||||
|
||||
/*
|
||||
* This character is the same as _POSIX_VDISABLE: it cannot be used as
|
||||
* a c_cc[] character, but indicates that a particular special character
|
||||
|
|
|
@ -32,6 +32,9 @@
|
|||
#include "control.h"
|
||||
#include "info.h"
|
||||
|
||||
/* maximum number of devices on the AC97 bus */
|
||||
#define AC97_BUS_MAX_DEVICES 4
|
||||
|
||||
/*
|
||||
* AC'97 codec registers
|
||||
*/
|
||||
|
@ -642,4 +645,10 @@ int snd_ac97_pcm_double_rate_rules(struct snd_pcm_runtime *runtime);
|
|||
/* ad hoc AC97 device driver access */
|
||||
extern struct bus_type ac97_bus_type;
|
||||
|
||||
/* AC97 platform_data adding function */
|
||||
static inline void snd_ac97_dev_add_pdata(struct snd_ac97 *ac97, void *data)
|
||||
{
|
||||
ac97->dev.platform_data = data;
|
||||
}
|
||||
|
||||
#endif /* __SOUND_AC97_CODEC_H */
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
#ifndef __SOUND_FSI_H
|
||||
#define __SOUND_FSI_H
|
||||
|
||||
/*
|
||||
* Fifo-attached Serial Interface (FSI) support for SH7724
|
||||
*
|
||||
* Copyright (C) 2009 Renesas Solutions Corp.
|
||||
* Kuninori Morimoto <morimoto.kuninori@renesas.com>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* flags format
|
||||
|
||||
* 0xABCDEEFF
|
||||
*
|
||||
* A: channel size for TDM (input)
|
||||
* B: channel size for TDM (ooutput)
|
||||
* C: inversion
|
||||
* D: mode
|
||||
* E: input format
|
||||
* F: output format
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
/* TDM channel */
|
||||
#define SH_FSI_SET_CH_I(x) ((x & 0xF) << 28)
|
||||
#define SH_FSI_SET_CH_O(x) ((x & 0xF) << 24)
|
||||
|
||||
#define SH_FSI_CH_IMASK 0xF0000000
|
||||
#define SH_FSI_CH_OMASK 0x0F000000
|
||||
#define SH_FSI_GET_CH_I(x) ((x & SH_FSI_CH_IMASK) >> 28)
|
||||
#define SH_FSI_GET_CH_O(x) ((x & SH_FSI_CH_OMASK) >> 24)
|
||||
|
||||
/* clock inversion */
|
||||
#define SH_FSI_INVERSION_MASK 0x00F00000
|
||||
#define SH_FSI_LRM_INV (1 << 20)
|
||||
#define SH_FSI_BRM_INV (1 << 21)
|
||||
#define SH_FSI_LRS_INV (1 << 22)
|
||||
#define SH_FSI_BRS_INV (1 << 23)
|
||||
|
||||
/* mode */
|
||||
#define SH_FSI_MODE_MASK 0x000F0000
|
||||
#define SH_FSI_IN_SLAVE_MODE (1 << 16) /* default master mode */
|
||||
#define SH_FSI_OUT_SLAVE_MODE (1 << 17) /* default master mode */
|
||||
|
||||
/* DI format */
|
||||
#define SH_FSI_FMT_MASK 0x000000FF
|
||||
#define SH_FSI_IFMT(x) (((SH_FSI_FMT_ ## x) & SH_FSI_FMT_MASK) << 8)
|
||||
#define SH_FSI_OFMT(x) (((SH_FSI_FMT_ ## x) & SH_FSI_FMT_MASK) << 0)
|
||||
#define SH_FSI_GET_IFMT(x) ((x >> 8) & SH_FSI_FMT_MASK)
|
||||
#define SH_FSI_GET_OFMT(x) ((x >> 0) & SH_FSI_FMT_MASK)
|
||||
|
||||
#define SH_FSI_FMT_MONO (1 << 0)
|
||||
#define SH_FSI_FMT_MONO_DELAY (1 << 1)
|
||||
#define SH_FSI_FMT_PCM (1 << 2)
|
||||
#define SH_FSI_FMT_I2S (1 << 3)
|
||||
#define SH_FSI_FMT_TDM (1 << 4)
|
||||
#define SH_FSI_FMT_TDM_DELAY (1 << 5)
|
||||
|
||||
#define SH_FSI_IFMT_TDM_CH(x) \
|
||||
(SH_FSI_IFMT(TDM) | SH_FSI_SET_CH_I(x))
|
||||
#define SH_FSI_IFMT_TDM_DELAY_CH(x) \
|
||||
(SH_FSI_IFMT(TDM_DELAY) | SH_FSI_SET_CH_I(x))
|
||||
|
||||
#define SH_FSI_OFMT_TDM_CH(x) \
|
||||
(SH_FSI_OFMT(TDM) | SH_FSI_SET_CH_O(x))
|
||||
#define SH_FSI_OFMT_TDM_DELAY_CH(x) \
|
||||
(SH_FSI_OFMT(TDM_DELAY) | SH_FSI_SET_CH_O(x))
|
||||
|
||||
struct sh_fsi_platform_info {
|
||||
unsigned long porta_flags;
|
||||
unsigned long portb_flags;
|
||||
};
|
||||
|
||||
extern struct snd_soc_dai fsi_soc_dai[2];
|
||||
extern struct snd_soc_platform fsi_soc_platform;
|
||||
|
||||
#endif /* __SOUND_FSI_H */
|
|
@ -27,8 +27,8 @@ struct snd_pcm_substream;
|
|||
#define SND_SOC_DAIFMT_I2S 0 /* I2S mode */
|
||||
#define SND_SOC_DAIFMT_RIGHT_J 1 /* Right Justified mode */
|
||||
#define SND_SOC_DAIFMT_LEFT_J 2 /* Left Justified mode */
|
||||
#define SND_SOC_DAIFMT_DSP_A 3 /* L data msb after FRM LRC */
|
||||
#define SND_SOC_DAIFMT_DSP_B 4 /* L data msb during FRM LRC */
|
||||
#define SND_SOC_DAIFMT_DSP_A 3 /* L data MSB after FRM LRC */
|
||||
#define SND_SOC_DAIFMT_DSP_B 4 /* L data MSB during FRM LRC */
|
||||
#define SND_SOC_DAIFMT_AC97 5 /* AC97 */
|
||||
|
||||
/* left and right justified also known as MSB and LSB respectively */
|
||||
|
@ -38,7 +38,7 @@ struct snd_pcm_substream;
|
|||
/*
|
||||
* DAI Clock gating.
|
||||
*
|
||||
* DAI bit clocks can be be gated (disabled) when not the DAI is not
|
||||
* DAI bit clocks can be be gated (disabled) when the DAI is not
|
||||
* sending or receiving PCM data in a frame. This can be used to save power.
|
||||
*/
|
||||
#define SND_SOC_DAIFMT_CONT (0 << 4) /* continuous clock */
|
||||
|
@ -51,21 +51,21 @@ struct snd_pcm_substream;
|
|||
* format.
|
||||
*/
|
||||
#define SND_SOC_DAIFMT_NB_NF (0 << 8) /* normal bit clock + frame */
|
||||
#define SND_SOC_DAIFMT_NB_IF (1 << 8) /* normal bclk + inv frm */
|
||||
#define SND_SOC_DAIFMT_IB_NF (2 << 8) /* invert bclk + nor frm */
|
||||
#define SND_SOC_DAIFMT_IB_IF (3 << 8) /* invert bclk + frm */
|
||||
#define SND_SOC_DAIFMT_NB_IF (1 << 8) /* normal BCLK + inv FRM */
|
||||
#define SND_SOC_DAIFMT_IB_NF (2 << 8) /* invert BCLK + nor FRM */
|
||||
#define SND_SOC_DAIFMT_IB_IF (3 << 8) /* invert BCLK + FRM */
|
||||
|
||||
/*
|
||||
* DAI hardware clock masters.
|
||||
*
|
||||
* This is wrt the codec, the inverse is true for the interface
|
||||
* i.e. if the codec is clk and frm master then the interface is
|
||||
* i.e. if the codec is clk and FRM master then the interface is
|
||||
* clk and frame slave.
|
||||
*/
|
||||
#define SND_SOC_DAIFMT_CBM_CFM (0 << 12) /* codec clk & frm master */
|
||||
#define SND_SOC_DAIFMT_CBS_CFM (1 << 12) /* codec clk slave & frm master */
|
||||
#define SND_SOC_DAIFMT_CBM_CFM (0 << 12) /* codec clk & FRM master */
|
||||
#define SND_SOC_DAIFMT_CBS_CFM (1 << 12) /* codec clk slave & FRM master */
|
||||
#define SND_SOC_DAIFMT_CBM_CFS (2 << 12) /* codec clk master & frame slave */
|
||||
#define SND_SOC_DAIFMT_CBS_CFS (3 << 12) /* codec clk & frm slave */
|
||||
#define SND_SOC_DAIFMT_CBS_CFS (3 << 12) /* codec clk & FRM slave */
|
||||
|
||||
#define SND_SOC_DAIFMT_FORMAT_MASK 0x000f
|
||||
#define SND_SOC_DAIFMT_CLOCK_MASK 0x00f0
|
||||
|
@ -78,7 +78,13 @@ struct snd_pcm_substream;
|
|||
#define SND_SOC_CLOCK_IN 0
|
||||
#define SND_SOC_CLOCK_OUT 1
|
||||
|
||||
#define SND_SOC_STD_AC97_FMTS (SNDRV_PCM_FMTBIT_S16_LE |\
|
||||
#define SND_SOC_STD_AC97_FMTS (SNDRV_PCM_FMTBIT_S8 |\
|
||||
SNDRV_PCM_FMTBIT_S16_LE |\
|
||||
SNDRV_PCM_FMTBIT_S16_BE |\
|
||||
SNDRV_PCM_FMTBIT_S20_3LE |\
|
||||
SNDRV_PCM_FMTBIT_S20_3BE |\
|
||||
SNDRV_PCM_FMTBIT_S24_3LE |\
|
||||
SNDRV_PCM_FMTBIT_S24_3BE |\
|
||||
SNDRV_PCM_FMTBIT_S32_LE |\
|
||||
SNDRV_PCM_FMTBIT_S32_BE)
|
||||
|
||||
|
@ -106,7 +112,7 @@ int snd_soc_dai_set_pll(struct snd_soc_dai *dai,
|
|||
int snd_soc_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt);
|
||||
|
||||
int snd_soc_dai_set_tdm_slot(struct snd_soc_dai *dai,
|
||||
unsigned int mask, int slots);
|
||||
unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width);
|
||||
|
||||
int snd_soc_dai_set_tristate(struct snd_soc_dai *dai, int tristate);
|
||||
|
||||
|
@ -116,12 +122,12 @@ int snd_soc_dai_digital_mute(struct snd_soc_dai *dai, int mute);
|
|||
/*
|
||||
* Digital Audio Interface.
|
||||
*
|
||||
* Describes the Digital Audio Interface in terms of it's ALSA, DAI and AC97
|
||||
* operations an capabilities. Codec and platfom drivers will register a this
|
||||
* Describes the Digital Audio Interface in terms of its ALSA, DAI and AC97
|
||||
* operations and capabilities. Codec and platform drivers will register this
|
||||
* structure for every DAI they have.
|
||||
*
|
||||
* This structure covers the clocking, formating and ALSA operations for each
|
||||
* interface a
|
||||
* interface.
|
||||
*/
|
||||
struct snd_soc_dai_ops {
|
||||
/*
|
||||
|
@ -140,7 +146,8 @@ struct snd_soc_dai_ops {
|
|||
*/
|
||||
int (*set_fmt)(struct snd_soc_dai *dai, unsigned int fmt);
|
||||
int (*set_tdm_slot)(struct snd_soc_dai *dai,
|
||||
unsigned int mask, int slots);
|
||||
unsigned int tx_mask, unsigned int rx_mask,
|
||||
int slots, int slot_width);
|
||||
int (*set_tristate)(struct snd_soc_dai *dai, int tristate);
|
||||
|
||||
/*
|
||||
|
@ -179,6 +186,7 @@ struct snd_soc_dai {
|
|||
int ac97_control;
|
||||
|
||||
struct device *dev;
|
||||
void *ac97_pdata; /* platform_data for the ac97 codec */
|
||||
|
||||
/* DAI callbacks */
|
||||
int (*probe)(struct platform_device *pdev,
|
||||
|
|
|
@ -137,6 +137,12 @@
|
|||
.event_flags = SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD}
|
||||
|
||||
/* stream domain */
|
||||
#define SND_SOC_DAPM_AIF_IN(wname, stname, wslot, wreg, wshift, winvert) \
|
||||
{ .id = snd_soc_dapm_aif_in, .name = wname, .sname = stname, \
|
||||
.reg = wreg, .shift = wshift, .invert = winvert }
|
||||
#define SND_SOC_DAPM_AIF_OUT(wname, stname, wslot, wreg, wshift, winvert) \
|
||||
{ .id = snd_soc_dapm_aif_out, .name = wname, .sname = stname, \
|
||||
.reg = wreg, .shift = wshift, .invert = winvert }
|
||||
#define SND_SOC_DAPM_DAC(wname, stname, wreg, wshift, winvert) \
|
||||
{ .id = snd_soc_dapm_dac, .name = wname, .sname = stname, .reg = wreg, \
|
||||
.shift = wshift, .invert = winvert}
|
||||
|
@ -279,9 +285,11 @@ int snd_soc_dapm_add_routes(struct snd_soc_codec *codec,
|
|||
/* dapm events */
|
||||
int snd_soc_dapm_stream_event(struct snd_soc_codec *codec, char *stream,
|
||||
int event);
|
||||
void snd_soc_dapm_shutdown(struct snd_soc_device *socdev);
|
||||
|
||||
/* dapm sys fs - used by the core */
|
||||
int snd_soc_dapm_sys_add(struct device *dev);
|
||||
void snd_soc_dapm_debugfs_init(struct snd_soc_codec *codec);
|
||||
|
||||
/* dapm audio pin control and status */
|
||||
int snd_soc_dapm_enable_pin(struct snd_soc_codec *codec, const char *pin);
|
||||
|
@ -311,6 +319,8 @@ enum snd_soc_dapm_type {
|
|||
snd_soc_dapm_pre, /* machine specific pre widget - exec first */
|
||||
snd_soc_dapm_post, /* machine specific post widget - exec last */
|
||||
snd_soc_dapm_supply, /* power/clock supply */
|
||||
snd_soc_dapm_aif_in, /* audio interface input */
|
||||
snd_soc_dapm_aif_out, /* audio interface output */
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
|
@ -135,6 +135,28 @@
|
|||
.info = snd_soc_info_volsw, \
|
||||
.get = xhandler_get, .put = xhandler_put, \
|
||||
.private_value = SOC_SINGLE_VALUE(xreg, xshift, xmax, xinvert) }
|
||||
#define SOC_DOUBLE_EXT_TLV(xname, xreg, shift_left, shift_right, xmax, xinvert,\
|
||||
xhandler_get, xhandler_put, tlv_array) \
|
||||
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \
|
||||
SNDRV_CTL_ELEM_ACCESS_READWRITE, \
|
||||
.tlv.p = (tlv_array), \
|
||||
.info = snd_soc_info_volsw, \
|
||||
.get = xhandler_get, .put = xhandler_put, \
|
||||
.private_value = (unsigned long)&(struct soc_mixer_control) \
|
||||
{.reg = xreg, .shift = shift_left, .rshift = shift_right, \
|
||||
.max = xmax, .invert = xinvert} }
|
||||
#define SOC_DOUBLE_R_EXT_TLV(xname, reg_left, reg_right, xshift, xmax, xinvert,\
|
||||
xhandler_get, xhandler_put, tlv_array) \
|
||||
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \
|
||||
SNDRV_CTL_ELEM_ACCESS_READWRITE, \
|
||||
.tlv.p = (tlv_array), \
|
||||
.info = snd_soc_info_volsw_2r, \
|
||||
.get = xhandler_get, .put = xhandler_put, \
|
||||
.private_value = (unsigned long)&(struct soc_mixer_control) \
|
||||
{.reg = reg_left, .rreg = reg_right, .shift = xshift, \
|
||||
.max = xmax, .invert = xinvert} }
|
||||
#define SOC_SINGLE_BOOL_EXT(xname, xdata, xhandler_get, xhandler_put) \
|
||||
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
|
||||
.info = snd_soc_info_bool_ext, \
|
||||
|
@ -183,14 +205,28 @@ struct snd_soc_jack_gpio;
|
|||
#endif
|
||||
|
||||
typedef int (*hw_write_t)(void *,const char* ,int);
|
||||
typedef int (*hw_read_t)(void *,char* ,int);
|
||||
|
||||
extern struct snd_ac97_bus_ops soc_ac97_ops;
|
||||
|
||||
enum snd_soc_control_type {
|
||||
SND_SOC_CUSTOM,
|
||||
SND_SOC_I2C,
|
||||
SND_SOC_SPI,
|
||||
};
|
||||
|
||||
int snd_soc_register_platform(struct snd_soc_platform *platform);
|
||||
void snd_soc_unregister_platform(struct snd_soc_platform *platform);
|
||||
int snd_soc_register_codec(struct snd_soc_codec *codec);
|
||||
void snd_soc_unregister_codec(struct snd_soc_codec *codec);
|
||||
int snd_soc_codec_volatile_register(struct snd_soc_codec *codec, int reg);
|
||||
int snd_soc_codec_set_cache_io(struct snd_soc_codec *codec,
|
||||
int addr_bits, int data_bits,
|
||||
enum snd_soc_control_type control);
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
int snd_soc_suspend_device(struct device *dev);
|
||||
int snd_soc_resume_device(struct device *dev);
|
||||
#endif
|
||||
|
||||
/* pcm <-> DAI connect */
|
||||
void snd_soc_free_pcms(struct snd_soc_device *socdev);
|
||||
|
@ -216,9 +252,9 @@ void snd_soc_jack_free_gpios(struct snd_soc_jack *jack, int count,
|
|||
|
||||
/* codec register bit access */
|
||||
int snd_soc_update_bits(struct snd_soc_codec *codec, unsigned short reg,
|
||||
unsigned short mask, unsigned short value);
|
||||
unsigned int mask, unsigned int value);
|
||||
int snd_soc_test_bits(struct snd_soc_codec *codec, unsigned short reg,
|
||||
unsigned short mask, unsigned short value);
|
||||
unsigned int mask, unsigned int value);
|
||||
|
||||
int snd_soc_new_ac97_codec(struct snd_soc_codec *codec,
|
||||
struct snd_ac97_bus_ops *ops, int num);
|
||||
|
@ -356,8 +392,10 @@ struct snd_soc_codec {
|
|||
int (*write)(struct snd_soc_codec *, unsigned int, unsigned int);
|
||||
int (*display_register)(struct snd_soc_codec *, char *,
|
||||
size_t, unsigned int);
|
||||
int (*volatile_register)(unsigned int);
|
||||
int (*readable_register)(unsigned int);
|
||||
hw_write_t hw_write;
|
||||
hw_read_t hw_read;
|
||||
unsigned int (*hw_read)(struct snd_soc_codec *, unsigned int);
|
||||
void *reg_cache;
|
||||
short reg_cache_size;
|
||||
short reg_cache_step;
|
||||
|
@ -369,8 +407,6 @@ struct snd_soc_codec {
|
|||
enum snd_soc_bias_level bias_level;
|
||||
enum snd_soc_bias_level suspend_bias_level;
|
||||
struct delayed_work delayed_work;
|
||||
struct list_head up_list;
|
||||
struct list_head down_list;
|
||||
|
||||
/* codec DAI's */
|
||||
struct snd_soc_dai *dai;
|
||||
|
@ -379,6 +415,7 @@ struct snd_soc_codec {
|
|||
#ifdef CONFIG_DEBUG_FS
|
||||
struct dentry *debugfs_reg;
|
||||
struct dentry *debugfs_pop_time;
|
||||
struct dentry *debugfs_dapm;
|
||||
#endif
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* UDA1380 ALSA SoC Codec driver
|
||||
*
|
||||
* Copyright 2009 Philipp Zabel
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __UDA1380_H
|
||||
#define __UDA1380_H
|
||||
|
||||
struct uda1380_platform_data {
|
||||
int gpio_power;
|
||||
int gpio_reset;
|
||||
int dac_clk;
|
||||
#define UDA1380_DAC_CLK_SYSCLK 0
|
||||
#define UDA1380_DAC_CLK_WSPLL 1
|
||||
};
|
||||
|
||||
#endif /* __UDA1380_H */
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* linux/sound/wm8993.h -- Platform data for WM8993
|
||||
*
|
||||
* Copyright 2009 Wolfson Microelectronics. PLC.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __LINUX_SND_WM8993_H
|
||||
#define __LINUX_SND_WM8993_H
|
||||
|
||||
/* Note that EQ1 only contains the enable/disable bit so will be
|
||||
ignored but is included for simplicity.
|
||||
*/
|
||||
struct wm8993_retune_mobile_setting {
|
||||
const char *name;
|
||||
unsigned int rate;
|
||||
u16 config[24];
|
||||
};
|
||||
|
||||
struct wm8993_platform_data {
|
||||
struct wm8993_retune_mobile_setting *retune_configs;
|
||||
int num_retune_configs;
|
||||
|
||||
/* LINEOUT can be differential or single ended */
|
||||
unsigned int lineout1_diff:1;
|
||||
unsigned int lineout2_diff:1;
|
||||
|
||||
/* Common mode feedback */
|
||||
unsigned int lineout1fb:1;
|
||||
unsigned int lineout2fb:1;
|
||||
|
||||
/* Microphone biases: 0=0.9*AVDD1 1=0.65*AVVD1 */
|
||||
unsigned int micbias1_lvl:1;
|
||||
unsigned int micbias2_lvl:1;
|
||||
|
||||
/* Jack detect threashold levels, see datasheet for values */
|
||||
unsigned int jd_scthr:2;
|
||||
unsigned int jd_thr:2;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -170,6 +170,13 @@ static int __devinit pxa2xx_ac97_probe(struct platform_device *dev)
|
|||
struct snd_ac97_bus *ac97_bus;
|
||||
struct snd_ac97_template ac97_template;
|
||||
int ret;
|
||||
pxa2xx_audio_ops_t *pdata = dev->dev.platform_data;
|
||||
|
||||
if (dev->id >= 0) {
|
||||
dev_err(&dev->dev, "PXA2xx has only one AC97 port.\n");
|
||||
ret = -ENXIO;
|
||||
goto err_dev;
|
||||
}
|
||||
|
||||
ret = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
|
||||
THIS_MODULE, 0, &card);
|
||||
|
@ -200,6 +207,8 @@ static int __devinit pxa2xx_ac97_probe(struct platform_device *dev)
|
|||
snprintf(card->longname, sizeof(card->longname),
|
||||
"%s (%s)", dev->dev.driver->name, card->mixername);
|
||||
|
||||
if (pdata && pdata->codec_pdata[0])
|
||||
snd_ac97_dev_add_pdata(ac97_bus->codec[0], pdata->codec_pdata[0]);
|
||||
snd_card_set_dev(card, &dev->dev);
|
||||
ret = snd_card_register(card);
|
||||
if (ret == 0) {
|
||||
|
@ -212,6 +221,7 @@ err_remove:
|
|||
err:
|
||||
if (card)
|
||||
snd_card_free(card);
|
||||
err_dev:
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
|
@ -136,6 +136,9 @@ int __pxa2xx_pcm_prepare(struct snd_pcm_substream *substream)
|
|||
{
|
||||
struct pxa2xx_runtime_data *prtd = substream->runtime->private_data;
|
||||
|
||||
if (!prtd || !prtd->params)
|
||||
return 0;
|
||||
|
||||
DCSR(prtd->dma_ch) &= ~DCSR_RUN;
|
||||
DCSR(prtd->dma_ch) = 0;
|
||||
DCMD(prtd->dma_ch) = 0;
|
||||
|
|
|
@ -29,6 +29,7 @@ source "sound/soc/au1x/Kconfig"
|
|||
source "sound/soc/blackfin/Kconfig"
|
||||
source "sound/soc/davinci/Kconfig"
|
||||
source "sound/soc/fsl/Kconfig"
|
||||
source "sound/soc/imx/Kconfig"
|
||||
source "sound/soc/omap/Kconfig"
|
||||
source "sound/soc/pxa/Kconfig"
|
||||
source "sound/soc/s3c24xx/Kconfig"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
snd-soc-core-objs := soc-core.o soc-dapm.o soc-jack.o
|
||||
snd-soc-core-objs := soc-core.o soc-dapm.o soc-jack.o soc-cache.o
|
||||
|
||||
obj-$(CONFIG_SND_SOC) += snd-soc-core.o
|
||||
obj-$(CONFIG_SND_SOC) += codecs/
|
||||
|
@ -7,6 +7,7 @@ obj-$(CONFIG_SND_SOC) += au1x/
|
|||
obj-$(CONFIG_SND_SOC) += blackfin/
|
||||
obj-$(CONFIG_SND_SOC) += davinci/
|
||||
obj-$(CONFIG_SND_SOC) += fsl/
|
||||
obj-$(CONFIG_SND_SOC) += imx/
|
||||
obj-$(CONFIG_SND_SOC) += omap/
|
||||
obj-$(CONFIG_SND_SOC) += pxa/
|
||||
obj-$(CONFIG_SND_SOC) += s3c24xx/
|
||||
|
|
|
@ -56,133 +56,32 @@
|
|||
|
||||
#define MCLK_RATE 12000000
|
||||
|
||||
/*
|
||||
* As shipped the board does not have inputs. However, it is relatively
|
||||
* straightforward to modify the board to hook them up so support is left
|
||||
* in the driver.
|
||||
*/
|
||||
#undef ENABLE_MIC_INPUT
|
||||
|
||||
static struct clk *mclk;
|
||||
|
||||
static int at91sam9g20ek_startup(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream);
|
||||
struct snd_soc_dai *codec_dai = rtd->dai->codec_dai;
|
||||
int ret;
|
||||
|
||||
ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK,
|
||||
MCLK_RATE, SND_SOC_CLOCK_IN);
|
||||
if (ret < 0) {
|
||||
clk_disable(mclk);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void at91sam9g20ek_shutdown(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream);
|
||||
|
||||
dev_dbg(rtd->socdev->dev, "shutdown");
|
||||
}
|
||||
|
||||
static int at91sam9g20ek_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *codec_dai = rtd->dai->codec_dai;
|
||||
struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
|
||||
struct atmel_ssc_info *ssc_p = cpu_dai->private_data;
|
||||
struct ssc_device *ssc = ssc_p->ssc;
|
||||
int ret;
|
||||
|
||||
unsigned int rate;
|
||||
int cmr_div, period;
|
||||
|
||||
if (ssc == NULL) {
|
||||
printk(KERN_INFO "at91sam9g20ek_hw_params: ssc is NULL!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* set codec DAI configuration */
|
||||
ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
|
||||
SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
|
||||
SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* set cpu DAI configuration */
|
||||
ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
|
||||
SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* The SSC clock dividers depend on the sample rate. The CMR.DIV
|
||||
* field divides the system master clock MCK to drive the SSC TK
|
||||
* signal which provides the codec BCLK. The TCMR.PERIOD and
|
||||
* RCMR.PERIOD fields further divide the BCLK signal to drive
|
||||
* the SSC TF and RF signals which provide the codec DACLRC and
|
||||
* ADCLRC clocks.
|
||||
*
|
||||
* The dividers were determined through trial and error, where a
|
||||
* CMR.DIV value is chosen such that the resulting BCLK value is
|
||||
* divisible, or almost divisible, by (2 * sample rate), and then
|
||||
* the TCMR.PERIOD or RCMR.PERIOD is BCLK / (2 * sample rate) - 1.
|
||||
*/
|
||||
rate = params_rate(params);
|
||||
|
||||
switch (rate) {
|
||||
case 8000:
|
||||
cmr_div = 55; /* BCLK = 133MHz/(2*55) = 1.209MHz */
|
||||
period = 74; /* LRC = BCLK/(2*(74+1)) ~= 8060,6Hz */
|
||||
break;
|
||||
case 11025:
|
||||
cmr_div = 67; /* BCLK = 133MHz/(2*60) = 1.108MHz */
|
||||
period = 45; /* LRC = BCLK/(2*(49+1)) = 11083,3Hz */
|
||||
break;
|
||||
case 16000:
|
||||
cmr_div = 63; /* BCLK = 133MHz/(2*63) = 1.055MHz */
|
||||
period = 32; /* LRC = BCLK/(2*(32+1)) = 15993,2Hz */
|
||||
break;
|
||||
case 22050:
|
||||
cmr_div = 52; /* BCLK = 133MHz/(2*52) = 1.278MHz */
|
||||
period = 28; /* LRC = BCLK/(2*(28+1)) = 22049Hz */
|
||||
break;
|
||||
case 32000:
|
||||
cmr_div = 66; /* BCLK = 133MHz/(2*66) = 1.007MHz */
|
||||
period = 15; /* LRC = BCLK/(2*(15+1)) = 31486,742Hz */
|
||||
break;
|
||||
case 44100:
|
||||
cmr_div = 29; /* BCLK = 133MHz/(2*29) = 2.293MHz */
|
||||
period = 25; /* LRC = BCLK/(2*(25+1)) = 44098Hz */
|
||||
break;
|
||||
case 48000:
|
||||
cmr_div = 33; /* BCLK = 133MHz/(2*33) = 2.015MHz */
|
||||
period = 20; /* LRC = BCLK/(2*(20+1)) = 47979,79Hz */
|
||||
break;
|
||||
case 88200:
|
||||
cmr_div = 29; /* BCLK = 133MHz/(2*29) = 2.293MHz */
|
||||
period = 12; /* LRC = BCLK/(2*(12+1)) = 88196Hz */
|
||||
break;
|
||||
case 96000:
|
||||
cmr_div = 23; /* BCLK = 133MHz/(2*23) = 2.891MHz */
|
||||
period = 14; /* LRC = BCLK/(2*(14+1)) = 96376Hz */
|
||||
break;
|
||||
default:
|
||||
printk(KERN_WARNING "unsupported rate %d"
|
||||
" on at91sam9g20ek board\n", rate);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* set the MCK divider for BCLK */
|
||||
ret = snd_soc_dai_set_clkdiv(cpu_dai, ATMEL_SSC_CMR_DIV, cmr_div);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
/* set the BCLK divider for DACLRC */
|
||||
ret = snd_soc_dai_set_clkdiv(cpu_dai,
|
||||
ATMEL_SSC_TCMR_PERIOD, period);
|
||||
} else {
|
||||
/* set the BCLK divider for ADCLRC */
|
||||
ret = snd_soc_dai_set_clkdiv(cpu_dai,
|
||||
ATMEL_SSC_RCMR_PERIOD, period);
|
||||
}
|
||||
SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
|
@ -190,9 +89,7 @@ static int at91sam9g20ek_hw_params(struct snd_pcm_substream *substream,
|
|||
}
|
||||
|
||||
static struct snd_soc_ops at91sam9g20ek_ops = {
|
||||
.startup = at91sam9g20ek_startup,
|
||||
.hw_params = at91sam9g20ek_hw_params,
|
||||
.shutdown = at91sam9g20ek_shutdown,
|
||||
};
|
||||
|
||||
static int at91sam9g20ek_set_bias_level(struct snd_soc_card *card,
|
||||
|
@ -241,10 +138,20 @@ static const struct snd_soc_dapm_route intercon[] = {
|
|||
*/
|
||||
static int at91sam9g20ek_wm8731_init(struct snd_soc_codec *codec)
|
||||
{
|
||||
struct snd_soc_dai *codec_dai = &codec->dai[0];
|
||||
int ret;
|
||||
|
||||
printk(KERN_DEBUG
|
||||
"at91sam9g20ek_wm8731 "
|
||||
": at91sam9g20ek_wm8731_init() called\n");
|
||||
|
||||
ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK,
|
||||
MCLK_RATE, SND_SOC_CLOCK_IN);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "Failed to set WM8731 SYSCLK: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Add specific widgets */
|
||||
snd_soc_dapm_new_controls(codec, at91sam9g20ek_dapm_widgets,
|
||||
ARRAY_SIZE(at91sam9g20ek_dapm_widgets));
|
||||
|
@ -255,8 +162,13 @@ static int at91sam9g20ek_wm8731_init(struct snd_soc_codec *codec)
|
|||
snd_soc_dapm_nc_pin(codec, "RLINEIN");
|
||||
snd_soc_dapm_nc_pin(codec, "LLINEIN");
|
||||
|
||||
/* always connected */
|
||||
#ifdef ENABLE_MIC_INPUT
|
||||
snd_soc_dapm_enable_pin(codec, "Int Mic");
|
||||
#else
|
||||
snd_soc_dapm_nc_pin(codec, "Int Mic");
|
||||
#endif
|
||||
|
||||
/* always connected */
|
||||
snd_soc_dapm_enable_pin(codec, "Ext Spk");
|
||||
|
||||
snd_soc_dapm_sync(codec);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
/*
|
||||
* Au12x0/Au1550 PSC ALSA ASoC audio support.
|
||||
*
|
||||
* (c) 2007-2008 MSC Vertriebsges.m.b.H.,
|
||||
* Manuel Lauss <mano@roarinelk.homelinux.net>
|
||||
* (c) 2007-2009 MSC Vertriebsges.m.b.H.,
|
||||
* Manuel Lauss <manuel.lauss@gmail.com>
|
||||
*
|
||||
* 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
|
||||
|
@ -19,6 +19,7 @@
|
|||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/suspend.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
|
@ -29,6 +30,9 @@
|
|||
|
||||
#include "psc.h"
|
||||
|
||||
/* how often to retry failed codec register reads/writes */
|
||||
#define AC97_RW_RETRIES 5
|
||||
|
||||
#define AC97_DIR \
|
||||
(SND_SOC_DAIDIR_PLAYBACK | SND_SOC_DAIDIR_CAPTURE)
|
||||
|
||||
|
@ -45,6 +49,9 @@
|
|||
#define AC97PCR_CLRFIFO(stype) \
|
||||
((stype) == PCM_TX ? PSC_AC97PCR_TC : PSC_AC97PCR_RC)
|
||||
|
||||
#define AC97STAT_BUSY(stype) \
|
||||
((stype) == PCM_TX ? PSC_AC97STAT_TB : PSC_AC97STAT_RB)
|
||||
|
||||
/* instance data. There can be only one, MacLeod!!!! */
|
||||
static struct au1xpsc_audio_data *au1xpsc_ac97_workdata;
|
||||
|
||||
|
@ -54,24 +61,33 @@ static unsigned short au1xpsc_ac97_read(struct snd_ac97 *ac97,
|
|||
{
|
||||
/* FIXME */
|
||||
struct au1xpsc_audio_data *pscdata = au1xpsc_ac97_workdata;
|
||||
unsigned short data, tmo;
|
||||
|
||||
au_writel(PSC_AC97CDC_RD | PSC_AC97CDC_INDX(reg), AC97_CDC(pscdata));
|
||||
au_sync();
|
||||
|
||||
tmo = 1000;
|
||||
while ((!(au_readl(AC97_EVNT(pscdata)) & PSC_AC97EVNT_CD)) && --tmo)
|
||||
udelay(2);
|
||||
|
||||
if (!tmo)
|
||||
data = 0xffff;
|
||||
else
|
||||
data = au_readl(AC97_CDC(pscdata)) & 0xffff;
|
||||
unsigned short data, retry, tmo;
|
||||
|
||||
au_writel(PSC_AC97EVNT_CD, AC97_EVNT(pscdata));
|
||||
au_sync();
|
||||
|
||||
return data;
|
||||
retry = AC97_RW_RETRIES;
|
||||
do {
|
||||
mutex_lock(&pscdata->lock);
|
||||
|
||||
au_writel(PSC_AC97CDC_RD | PSC_AC97CDC_INDX(reg),
|
||||
AC97_CDC(pscdata));
|
||||
au_sync();
|
||||
|
||||
tmo = 2000;
|
||||
while ((!(au_readl(AC97_EVNT(pscdata)) & PSC_AC97EVNT_CD))
|
||||
&& --tmo)
|
||||
udelay(2);
|
||||
|
||||
data = au_readl(AC97_CDC(pscdata)) & 0xffff;
|
||||
|
||||
au_writel(PSC_AC97EVNT_CD, AC97_EVNT(pscdata));
|
||||
au_sync();
|
||||
|
||||
mutex_unlock(&pscdata->lock);
|
||||
} while (--retry && !tmo);
|
||||
|
||||
return retry ? data : 0xffff;
|
||||
}
|
||||
|
||||
/* AC97 controller writes to codec register */
|
||||
|
@ -80,16 +96,29 @@ static void au1xpsc_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
|
|||
{
|
||||
/* FIXME */
|
||||
struct au1xpsc_audio_data *pscdata = au1xpsc_ac97_workdata;
|
||||
unsigned int tmo;
|
||||
|
||||
au_writel(PSC_AC97CDC_INDX(reg) | (val & 0xffff), AC97_CDC(pscdata));
|
||||
au_sync();
|
||||
tmo = 1000;
|
||||
while ((!(au_readl(AC97_EVNT(pscdata)) & PSC_AC97EVNT_CD)) && --tmo)
|
||||
au_sync();
|
||||
unsigned int tmo, retry;
|
||||
|
||||
au_writel(PSC_AC97EVNT_CD, AC97_EVNT(pscdata));
|
||||
au_sync();
|
||||
|
||||
retry = AC97_RW_RETRIES;
|
||||
do {
|
||||
mutex_lock(&pscdata->lock);
|
||||
|
||||
au_writel(PSC_AC97CDC_INDX(reg) | (val & 0xffff),
|
||||
AC97_CDC(pscdata));
|
||||
au_sync();
|
||||
|
||||
tmo = 2000;
|
||||
while ((!(au_readl(AC97_EVNT(pscdata)) & PSC_AC97EVNT_CD))
|
||||
&& --tmo)
|
||||
udelay(2);
|
||||
|
||||
au_writel(PSC_AC97EVNT_CD, AC97_EVNT(pscdata));
|
||||
au_sync();
|
||||
|
||||
mutex_unlock(&pscdata->lock);
|
||||
} while (--retry && !tmo);
|
||||
}
|
||||
|
||||
/* AC97 controller asserts a warm reset */
|
||||
|
@ -129,9 +158,9 @@ static void au1xpsc_ac97_cold_reset(struct snd_ac97 *ac97)
|
|||
au_sync();
|
||||
|
||||
/* wait for PSC to indicate it's ready */
|
||||
i = 100000;
|
||||
i = 1000;
|
||||
while (!((au_readl(AC97_STAT(pscdata)) & PSC_AC97STAT_SR)) && (--i))
|
||||
au_sync();
|
||||
msleep(1);
|
||||
|
||||
if (i == 0) {
|
||||
printk(KERN_ERR "au1xpsc-ac97: PSC not ready!\n");
|
||||
|
@ -143,9 +172,9 @@ static void au1xpsc_ac97_cold_reset(struct snd_ac97 *ac97)
|
|||
au_sync();
|
||||
|
||||
/* wait for AC97 core to become ready */
|
||||
i = 100000;
|
||||
i = 1000;
|
||||
while (!((au_readl(AC97_STAT(pscdata)) & PSC_AC97STAT_DR)) && (--i))
|
||||
au_sync();
|
||||
msleep(1);
|
||||
if (i == 0)
|
||||
printk(KERN_ERR "au1xpsc-ac97: AC97 ctrl not ready\n");
|
||||
}
|
||||
|
@ -165,12 +194,12 @@ static int au1xpsc_ac97_hw_params(struct snd_pcm_substream *substream,
|
|||
{
|
||||
/* FIXME */
|
||||
struct au1xpsc_audio_data *pscdata = au1xpsc_ac97_workdata;
|
||||
unsigned long r, stat;
|
||||
unsigned long r, ro, stat;
|
||||
int chans, stype = SUBSTREAM_TYPE(substream);
|
||||
|
||||
chans = params_channels(params);
|
||||
|
||||
r = au_readl(AC97_CFG(pscdata));
|
||||
r = ro = au_readl(AC97_CFG(pscdata));
|
||||
stat = au_readl(AC97_STAT(pscdata));
|
||||
|
||||
/* already active? */
|
||||
|
@ -180,9 +209,6 @@ static int au1xpsc_ac97_hw_params(struct snd_pcm_substream *substream,
|
|||
(pscdata->rate != params_rate(params)))
|
||||
return -EINVAL;
|
||||
} else {
|
||||
/* disable AC97 device controller first */
|
||||
au_writel(r & ~PSC_AC97CFG_DE_ENABLE, AC97_CFG(pscdata));
|
||||
au_sync();
|
||||
|
||||
/* set sample bitdepth: REG[24:21]=(BITS-2)/2 */
|
||||
r &= ~PSC_AC97CFG_LEN_MASK;
|
||||
|
@ -199,14 +225,40 @@ static int au1xpsc_ac97_hw_params(struct snd_pcm_substream *substream,
|
|||
r |= PSC_AC97CFG_RXSLOT_ENA(4);
|
||||
}
|
||||
|
||||
/* finally enable the AC97 controller again */
|
||||
/* do we need to poke the hardware? */
|
||||
if (!(r ^ ro))
|
||||
goto out;
|
||||
|
||||
/* ac97 engine is about to be disabled */
|
||||
mutex_lock(&pscdata->lock);
|
||||
|
||||
/* disable AC97 device controller first... */
|
||||
au_writel(r & ~PSC_AC97CFG_DE_ENABLE, AC97_CFG(pscdata));
|
||||
au_sync();
|
||||
|
||||
/* ...wait for it... */
|
||||
while (au_readl(AC97_STAT(pscdata)) & PSC_AC97STAT_DR)
|
||||
asm volatile ("nop");
|
||||
|
||||
/* ...write config... */
|
||||
au_writel(r, AC97_CFG(pscdata));
|
||||
au_sync();
|
||||
|
||||
/* ...enable the AC97 controller again... */
|
||||
au_writel(r | PSC_AC97CFG_DE_ENABLE, AC97_CFG(pscdata));
|
||||
au_sync();
|
||||
|
||||
/* ...and wait for ready bit */
|
||||
while (!(au_readl(AC97_STAT(pscdata)) & PSC_AC97STAT_DR))
|
||||
asm volatile ("nop");
|
||||
|
||||
mutex_unlock(&pscdata->lock);
|
||||
|
||||
pscdata->cfg = r;
|
||||
pscdata->rate = params_rate(params);
|
||||
}
|
||||
|
||||
out:
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -222,6 +274,8 @@ static int au1xpsc_ac97_trigger(struct snd_pcm_substream *substream,
|
|||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
case SNDRV_PCM_TRIGGER_RESUME:
|
||||
au_writel(AC97PCR_CLRFIFO(stype), AC97_PCR(pscdata));
|
||||
au_sync();
|
||||
au_writel(AC97PCR_START(stype), AC97_PCR(pscdata));
|
||||
au_sync();
|
||||
break;
|
||||
|
@ -229,6 +283,13 @@ static int au1xpsc_ac97_trigger(struct snd_pcm_substream *substream,
|
|||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
au_writel(AC97PCR_STOP(stype), AC97_PCR(pscdata));
|
||||
au_sync();
|
||||
|
||||
while (au_readl(AC97_STAT(pscdata)) & AC97STAT_BUSY(stype))
|
||||
asm volatile ("nop");
|
||||
|
||||
au_writel(AC97PCR_CLRFIFO(stype), AC97_PCR(pscdata));
|
||||
au_sync();
|
||||
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
|
@ -251,6 +312,8 @@ static int au1xpsc_ac97_probe(struct platform_device *pdev,
|
|||
if (!au1xpsc_ac97_workdata)
|
||||
return -ENOMEM;
|
||||
|
||||
mutex_init(&au1xpsc_ac97_workdata->lock);
|
||||
|
||||
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!r) {
|
||||
ret = -ENODEV;
|
||||
|
@ -269,9 +332,9 @@ static int au1xpsc_ac97_probe(struct platform_device *pdev,
|
|||
goto out1;
|
||||
|
||||
/* configuration: max dma trigger threshold, enable ac97 */
|
||||
au1xpsc_ac97_workdata->cfg = PSC_AC97CFG_RT_FIFO8 |
|
||||
PSC_AC97CFG_TT_FIFO8 |
|
||||
PSC_AC97CFG_DE_ENABLE;
|
||||
au1xpsc_ac97_workdata->cfg = PSC_AC97CFG_RT_FIFO8 |
|
||||
PSC_AC97CFG_TT_FIFO8 |
|
||||
PSC_AC97CFG_DE_ENABLE;
|
||||
|
||||
/* preserve PSC clock source set up by platform (dev.platform_data
|
||||
* is already occupied by soc layer)
|
||||
|
@ -386,4 +449,4 @@ module_exit(au1xpsc_ac97_exit);
|
|||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("Au12x0/Au1550 PSC AC97 ALSA ASoC audio driver");
|
||||
MODULE_AUTHOR("Manuel Lauss <mano@roarinelk.homelinux.net>");
|
||||
MODULE_AUTHOR("Manuel Lauss <manuel.lauss@gmail.com>");
|
||||
|
|
|
@ -29,6 +29,7 @@ struct au1xpsc_audio_data {
|
|||
|
||||
unsigned long pm[2];
|
||||
struct resource *ioarea;
|
||||
struct mutex lock;
|
||||
};
|
||||
|
||||
#define PCM_TX 0
|
||||
|
|
|
@ -7,6 +7,15 @@ config SND_BF5XX_I2S
|
|||
mode (supports single stereo In/Out).
|
||||
You will also need to select the audio interfaces to support below.
|
||||
|
||||
config SND_BF5XX_TDM
|
||||
tristate "SoC I2S(TDM mode) Audio for the ADI BF5xx chip"
|
||||
depends on (BLACKFIN && SND_SOC)
|
||||
help
|
||||
Say Y or M if you want to add support for codecs attached to
|
||||
the Blackfin SPORT (synchronous serial ports) interface in TDM
|
||||
mode.
|
||||
You will also need to select the audio interfaces to support below.
|
||||
|
||||
config SND_BF5XX_SOC_SSM2602
|
||||
tristate "SoC SSM2602 Audio support for BF52x ezkit"
|
||||
depends on SND_BF5XX_I2S
|
||||
|
@ -69,12 +78,24 @@ config SND_BF5XX_SOC_I2S
|
|||
tristate
|
||||
select SND_BF5XX_SOC_SPORT
|
||||
|
||||
config SND_BF5XX_SOC_TDM
|
||||
tristate
|
||||
select SND_BF5XX_SOC_SPORT
|
||||
|
||||
config SND_BF5XX_SOC_AC97
|
||||
tristate
|
||||
select AC97_BUS
|
||||
select SND_SOC_AC97_BUS
|
||||
select SND_BF5XX_SOC_SPORT
|
||||
|
||||
config SND_BF5XX_SOC_AD1836
|
||||
tristate "SoC AD1836 Audio support for BF5xx"
|
||||
depends on SND_BF5XX_TDM
|
||||
select SND_BF5XX_SOC_TDM
|
||||
select SND_SOC_AD1836
|
||||
help
|
||||
Say Y if you want to add support for SoC audio on BF5xx STAMP/EZKIT.
|
||||
|
||||
config SND_BF5XX_SOC_AD1980
|
||||
tristate "SoC AD1980/1 Audio support for BF5xx"
|
||||
depends on SND_BF5XX_AC97
|
||||
|
@ -83,9 +104,17 @@ config SND_BF5XX_SOC_AD1980
|
|||
help
|
||||
Say Y if you want to add support for SoC audio on BF5xx STAMP/EZKIT.
|
||||
|
||||
config SND_BF5XX_SOC_AD1938
|
||||
tristate "SoC AD1938 Audio support for Blackfin"
|
||||
depends on SND_BF5XX_TDM
|
||||
select SND_BF5XX_SOC_TDM
|
||||
select SND_SOC_AD1938
|
||||
help
|
||||
Say Y if you want to add support for AD1938 codec on Blackfin.
|
||||
|
||||
config SND_BF5XX_SPORT_NUM
|
||||
int "Set a SPORT for Sound chip"
|
||||
depends on (SND_BF5XX_I2S || SND_BF5XX_AC97)
|
||||
depends on (SND_BF5XX_I2S || SND_BF5XX_AC97 || SND_BF5XX_TDM)
|
||||
range 0 3 if BF54x
|
||||
range 0 1 if !BF54x
|
||||
default 0
|
||||
|
|
|
@ -1,21 +1,29 @@
|
|||
# Blackfin Platform Support
|
||||
snd-bf5xx-ac97-objs := bf5xx-ac97-pcm.o
|
||||
snd-bf5xx-i2s-objs := bf5xx-i2s-pcm.o
|
||||
snd-bf5xx-tdm-objs := bf5xx-tdm-pcm.o
|
||||
snd-soc-bf5xx-sport-objs := bf5xx-sport.o
|
||||
snd-soc-bf5xx-ac97-objs := bf5xx-ac97.o
|
||||
snd-soc-bf5xx-i2s-objs := bf5xx-i2s.o
|
||||
snd-soc-bf5xx-tdm-objs := bf5xx-tdm.o
|
||||
|
||||
obj-$(CONFIG_SND_BF5XX_AC97) += snd-bf5xx-ac97.o
|
||||
obj-$(CONFIG_SND_BF5XX_I2S) += snd-bf5xx-i2s.o
|
||||
obj-$(CONFIG_SND_BF5XX_TDM) += snd-bf5xx-tdm.o
|
||||
obj-$(CONFIG_SND_BF5XX_SOC_SPORT) += snd-soc-bf5xx-sport.o
|
||||
obj-$(CONFIG_SND_BF5XX_SOC_AC97) += snd-soc-bf5xx-ac97.o
|
||||
obj-$(CONFIG_SND_BF5XX_SOC_I2S) += snd-soc-bf5xx-i2s.o
|
||||
obj-$(CONFIG_SND_BF5XX_SOC_TDM) += snd-soc-bf5xx-tdm.o
|
||||
|
||||
# Blackfin Machine Support
|
||||
snd-ad1836-objs := bf5xx-ad1836.o
|
||||
snd-ad1980-objs := bf5xx-ad1980.o
|
||||
snd-ssm2602-objs := bf5xx-ssm2602.o
|
||||
snd-ad73311-objs := bf5xx-ad73311.o
|
||||
snd-ad1938-objs := bf5xx-ad1938.o
|
||||
|
||||
obj-$(CONFIG_SND_BF5XX_SOC_AD1836) += snd-ad1836.o
|
||||
obj-$(CONFIG_SND_BF5XX_SOC_AD1980) += snd-ad1980.o
|
||||
obj-$(CONFIG_SND_BF5XX_SOC_SSM2602) += snd-ssm2602.o
|
||||
obj-$(CONFIG_SND_BF5XX_SOC_AD73311) += snd-ad73311.o
|
||||
obj-$(CONFIG_SND_BF5XX_SOC_AD1938) += snd-ad1938.o
|
||||
|
|
|
@ -277,28 +277,24 @@ static int bf5xx_ac97_resume(struct snd_soc_dai *dai)
|
|||
if (!dai->active)
|
||||
return 0;
|
||||
|
||||
ret = sport_set_multichannel(sport_handle, 16, 0x1F, 1);
|
||||
ret = sport_set_multichannel(sport, 16, 0x1F, 1);
|
||||
if (ret) {
|
||||
pr_err("SPORT is busy!\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
ret = sport_config_rx(sport_handle, IRFS, 0xF, 0, (16*16-1));
|
||||
ret = sport_config_rx(sport, IRFS, 0xF, 0, (16*16-1));
|
||||
if (ret) {
|
||||
pr_err("SPORT is busy!\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
ret = sport_config_tx(sport_handle, ITFS, 0xF, 0, (16*16-1));
|
||||
ret = sport_config_tx(sport, ITFS, 0xF, 0, (16*16-1));
|
||||
if (ret) {
|
||||
pr_err("SPORT is busy!\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
if (dai->capture.active)
|
||||
sport_rx_start(sport);
|
||||
if (dai->playback.active)
|
||||
sport_tx_start(sport);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* File: sound/soc/blackfin/bf5xx-ad1836.c
|
||||
* Author: Barry Song <Barry.Song@analog.com>
|
||||
*
|
||||
* Created: Aug 4 2009
|
||||
* Description: Board driver for ad1836 sound chip
|
||||
*
|
||||
* Bugs: Enter bugs at http://blackfin.uclinux.org/
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/device.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/soc-dapm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
|
||||
#include <asm/blackfin.h>
|
||||
#include <asm/cacheflush.h>
|
||||
#include <asm/irq.h>
|
||||
#include <asm/dma.h>
|
||||
#include <asm/portmux.h>
|
||||
|
||||
#include "../codecs/ad1836.h"
|
||||
#include "bf5xx-sport.h"
|
||||
|
||||
#include "bf5xx-tdm-pcm.h"
|
||||
#include "bf5xx-tdm.h"
|
||||
|
||||
static struct snd_soc_card bf5xx_ad1836;
|
||||
|
||||
static int bf5xx_ad1836_startup(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
|
||||
|
||||
cpu_dai->private_data = sport_handle;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bf5xx_ad1836_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
|
||||
struct snd_soc_dai *codec_dai = rtd->dai->codec_dai;
|
||||
int ret = 0;
|
||||
/* set cpu DAI configuration */
|
||||
ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_DSP_A |
|
||||
SND_SOC_DAIFMT_IB_IF | SND_SOC_DAIFMT_CBM_CFM);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* set codec DAI configuration */
|
||||
ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_A |
|
||||
SND_SOC_DAIFMT_IB_IF | SND_SOC_DAIFMT_CBM_CFM);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_ops bf5xx_ad1836_ops = {
|
||||
.startup = bf5xx_ad1836_startup,
|
||||
.hw_params = bf5xx_ad1836_hw_params,
|
||||
};
|
||||
|
||||
static struct snd_soc_dai_link bf5xx_ad1836_dai = {
|
||||
.name = "ad1836",
|
||||
.stream_name = "AD1836",
|
||||
.cpu_dai = &bf5xx_tdm_dai,
|
||||
.codec_dai = &ad1836_dai,
|
||||
.ops = &bf5xx_ad1836_ops,
|
||||
};
|
||||
|
||||
static struct snd_soc_card bf5xx_ad1836 = {
|
||||
.name = "bf5xx_ad1836",
|
||||
.platform = &bf5xx_tdm_soc_platform,
|
||||
.dai_link = &bf5xx_ad1836_dai,
|
||||
.num_links = 1,
|
||||
};
|
||||
|
||||
static struct snd_soc_device bf5xx_ad1836_snd_devdata = {
|
||||
.card = &bf5xx_ad1836,
|
||||
.codec_dev = &soc_codec_dev_ad1836,
|
||||
};
|
||||
|
||||
static struct platform_device *bfxx_ad1836_snd_device;
|
||||
|
||||
static int __init bf5xx_ad1836_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
bfxx_ad1836_snd_device = platform_device_alloc("soc-audio", -1);
|
||||
if (!bfxx_ad1836_snd_device)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(bfxx_ad1836_snd_device, &bf5xx_ad1836_snd_devdata);
|
||||
bf5xx_ad1836_snd_devdata.dev = &bfxx_ad1836_snd_device->dev;
|
||||
ret = platform_device_add(bfxx_ad1836_snd_device);
|
||||
|
||||
if (ret)
|
||||
platform_device_put(bfxx_ad1836_snd_device);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit bf5xx_ad1836_exit(void)
|
||||
{
|
||||
platform_device_unregister(bfxx_ad1836_snd_device);
|
||||
}
|
||||
|
||||
module_init(bf5xx_ad1836_init);
|
||||
module_exit(bf5xx_ad1836_exit);
|
||||
|
||||
/* Module information */
|
||||
MODULE_AUTHOR("Barry Song");
|
||||
MODULE_DESCRIPTION("ALSA SoC AD1836 board driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
* File: sound/soc/blackfin/bf5xx-ad1938.c
|
||||
* Author: Barry Song <Barry.Song@analog.com>
|
||||
*
|
||||
* Created: Thur June 4 2009
|
||||
* Description: Board driver for ad1938 sound chip
|
||||
*
|
||||
* Bugs: Enter bugs at http://blackfin.uclinux.org/
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see the file COPYING, or write
|
||||
* to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/device.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/soc-dapm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
|
||||
#include <asm/blackfin.h>
|
||||
#include <asm/cacheflush.h>
|
||||
#include <asm/irq.h>
|
||||
#include <asm/dma.h>
|
||||
#include <asm/portmux.h>
|
||||
|
||||
#include "../codecs/ad1938.h"
|
||||
#include "bf5xx-sport.h"
|
||||
|
||||
#include "bf5xx-tdm-pcm.h"
|
||||
#include "bf5xx-tdm.h"
|
||||
|
||||
static struct snd_soc_card bf5xx_ad1938;
|
||||
|
||||
static int bf5xx_ad1938_startup(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
|
||||
|
||||
cpu_dai->private_data = sport_handle;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bf5xx_ad1938_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
|
||||
struct snd_soc_dai *codec_dai = rtd->dai->codec_dai;
|
||||
int ret = 0;
|
||||
/* set cpu DAI configuration */
|
||||
ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_DSP_A |
|
||||
SND_SOC_DAIFMT_IB_IF | SND_SOC_DAIFMT_CBM_CFM);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* set codec DAI configuration */
|
||||
ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_A |
|
||||
SND_SOC_DAIFMT_IB_IF | SND_SOC_DAIFMT_CBM_CFM);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* set codec DAI slots, 8 channels, all channels are enabled */
|
||||
ret = snd_soc_dai_set_tdm_slot(codec_dai, 0xFF, 8);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_ops bf5xx_ad1938_ops = {
|
||||
.startup = bf5xx_ad1938_startup,
|
||||
.hw_params = bf5xx_ad1938_hw_params,
|
||||
};
|
||||
|
||||
static struct snd_soc_dai_link bf5xx_ad1938_dai = {
|
||||
.name = "ad1938",
|
||||
.stream_name = "AD1938",
|
||||
.cpu_dai = &bf5xx_tdm_dai,
|
||||
.codec_dai = &ad1938_dai,
|
||||
.ops = &bf5xx_ad1938_ops,
|
||||
};
|
||||
|
||||
static struct snd_soc_card bf5xx_ad1938 = {
|
||||
.name = "bf5xx_ad1938",
|
||||
.platform = &bf5xx_tdm_soc_platform,
|
||||
.dai_link = &bf5xx_ad1938_dai,
|
||||
.num_links = 1,
|
||||
};
|
||||
|
||||
static struct snd_soc_device bf5xx_ad1938_snd_devdata = {
|
||||
.card = &bf5xx_ad1938,
|
||||
.codec_dev = &soc_codec_dev_ad1938,
|
||||
};
|
||||
|
||||
static struct platform_device *bfxx_ad1938_snd_device;
|
||||
|
||||
static int __init bf5xx_ad1938_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
bfxx_ad1938_snd_device = platform_device_alloc("soc-audio", -1);
|
||||
if (!bfxx_ad1938_snd_device)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(bfxx_ad1938_snd_device, &bf5xx_ad1938_snd_devdata);
|
||||
bf5xx_ad1938_snd_devdata.dev = &bfxx_ad1938_snd_device->dev;
|
||||
ret = platform_device_add(bfxx_ad1938_snd_device);
|
||||
|
||||
if (ret)
|
||||
platform_device_put(bfxx_ad1938_snd_device);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit bf5xx_ad1938_exit(void)
|
||||
{
|
||||
platform_device_unregister(bfxx_ad1938_snd_device);
|
||||
}
|
||||
|
||||
module_init(bf5xx_ad1938_init);
|
||||
module_exit(bf5xx_ad1938_exit);
|
||||
|
||||
/* Module information */
|
||||
MODULE_AUTHOR("Barry Song");
|
||||
MODULE_DESCRIPTION("ALSA SoC AD1938 board driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
@ -203,23 +203,23 @@ static struct snd_soc_device bf5xx_ad73311_snd_devdata = {
|
|||
.codec_dev = &soc_codec_dev_ad73311,
|
||||
};
|
||||
|
||||
static struct platform_device *bf52x_ad73311_snd_device;
|
||||
static struct platform_device *bf5xx_ad73311_snd_device;
|
||||
|
||||
static int __init bf5xx_ad73311_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
pr_debug("%s enter\n", __func__);
|
||||
bf52x_ad73311_snd_device = platform_device_alloc("soc-audio", -1);
|
||||
if (!bf52x_ad73311_snd_device)
|
||||
bf5xx_ad73311_snd_device = platform_device_alloc("soc-audio", -1);
|
||||
if (!bf5xx_ad73311_snd_device)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(bf52x_ad73311_snd_device, &bf5xx_ad73311_snd_devdata);
|
||||
bf5xx_ad73311_snd_devdata.dev = &bf52x_ad73311_snd_device->dev;
|
||||
ret = platform_device_add(bf52x_ad73311_snd_device);
|
||||
platform_set_drvdata(bf5xx_ad73311_snd_device, &bf5xx_ad73311_snd_devdata);
|
||||
bf5xx_ad73311_snd_devdata.dev = &bf5xx_ad73311_snd_device->dev;
|
||||
ret = platform_device_add(bf5xx_ad73311_snd_device);
|
||||
|
||||
if (ret)
|
||||
platform_device_put(bf52x_ad73311_snd_device);
|
||||
platform_device_put(bf5xx_ad73311_snd_device);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -227,7 +227,7 @@ static int __init bf5xx_ad73311_init(void)
|
|||
static void __exit bf5xx_ad73311_exit(void)
|
||||
{
|
||||
pr_debug("%s enter\n", __func__);
|
||||
platform_device_unregister(bf52x_ad73311_snd_device);
|
||||
platform_device_unregister(bf5xx_ad73311_snd_device);
|
||||
}
|
||||
|
||||
module_init(bf5xx_ad73311_init);
|
||||
|
|
|
@ -259,22 +259,18 @@ static int bf5xx_i2s_resume(struct snd_soc_dai *dai)
|
|||
if (!dai->active)
|
||||
return 0;
|
||||
|
||||
ret = sport_config_rx(sport_handle, RFSR | RCKFE, RSFSE|0x1f, 0, 0);
|
||||
ret = sport_config_rx(sport, RFSR | RCKFE, RSFSE|0x1f, 0, 0);
|
||||
if (ret) {
|
||||
pr_err("SPORT is busy!\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
ret = sport_config_tx(sport_handle, TFSR | TCKFE, TSFSE|0x1f, 0, 0);
|
||||
ret = sport_config_tx(sport, TFSR | TCKFE, TSFSE|0x1f, 0, 0);
|
||||
if (ret) {
|
||||
pr_err("SPORT is busy!\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
if (dai->capture.active)
|
||||
sport_rx_start(sport);
|
||||
if (dai->playback.active)
|
||||
sport_tx_start(sport);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -148,24 +148,24 @@ static struct snd_soc_device bf5xx_ssm2602_snd_devdata = {
|
|||
.codec_data = &bf5xx_ssm2602_setup,
|
||||
};
|
||||
|
||||
static struct platform_device *bf52x_ssm2602_snd_device;
|
||||
static struct platform_device *bf5xx_ssm2602_snd_device;
|
||||
|
||||
static int __init bf5xx_ssm2602_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
pr_debug("%s enter\n", __func__);
|
||||
bf52x_ssm2602_snd_device = platform_device_alloc("soc-audio", -1);
|
||||
if (!bf52x_ssm2602_snd_device)
|
||||
bf5xx_ssm2602_snd_device = platform_device_alloc("soc-audio", -1);
|
||||
if (!bf5xx_ssm2602_snd_device)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(bf52x_ssm2602_snd_device,
|
||||
platform_set_drvdata(bf5xx_ssm2602_snd_device,
|
||||
&bf5xx_ssm2602_snd_devdata);
|
||||
bf5xx_ssm2602_snd_devdata.dev = &bf52x_ssm2602_snd_device->dev;
|
||||
ret = platform_device_add(bf52x_ssm2602_snd_device);
|
||||
bf5xx_ssm2602_snd_devdata.dev = &bf5xx_ssm2602_snd_device->dev;
|
||||
ret = platform_device_add(bf5xx_ssm2602_snd_device);
|
||||
|
||||
if (ret)
|
||||
platform_device_put(bf52x_ssm2602_snd_device);
|
||||
platform_device_put(bf5xx_ssm2602_snd_device);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -173,7 +173,7 @@ static int __init bf5xx_ssm2602_init(void)
|
|||
static void __exit bf5xx_ssm2602_exit(void)
|
||||
{
|
||||
pr_debug("%s enter\n", __func__);
|
||||
platform_device_unregister(bf52x_ssm2602_snd_device);
|
||||
platform_device_unregister(bf5xx_ssm2602_snd_device);
|
||||
}
|
||||
|
||||
module_init(bf5xx_ssm2602_init);
|
||||
|
|
|
@ -0,0 +1,330 @@
|
|||
/*
|
||||
* File: sound/soc/blackfin/bf5xx-tdm-pcm.c
|
||||
* Author: Barry Song <Barry.Song@analog.com>
|
||||
*
|
||||
* Created: Tue June 06 2009
|
||||
* Description: DMA driver for tdm codec
|
||||
*
|
||||
* Modified:
|
||||
* Copyright 2009 Analog Devices Inc.
|
||||
*
|
||||
* Bugs: Enter bugs at http://blackfin.uclinux.org/
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see the file COPYING, or write
|
||||
* to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
#include <asm/dma.h>
|
||||
|
||||
#include "bf5xx-tdm-pcm.h"
|
||||
#include "bf5xx-tdm.h"
|
||||
#include "bf5xx-sport.h"
|
||||
|
||||
#define PCM_BUFFER_MAX 0x10000
|
||||
#define FRAGMENT_SIZE_MIN (4*1024)
|
||||
#define FRAGMENTS_MIN 2
|
||||
#define FRAGMENTS_MAX 32
|
||||
|
||||
static void bf5xx_dma_irq(void *data)
|
||||
{
|
||||
struct snd_pcm_substream *pcm = data;
|
||||
snd_pcm_period_elapsed(pcm);
|
||||
}
|
||||
|
||||
static const struct snd_pcm_hardware bf5xx_pcm_hardware = {
|
||||
.info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
|
||||
SNDRV_PCM_INFO_RESUME),
|
||||
.formats = SNDRV_PCM_FMTBIT_S32_LE,
|
||||
.rates = SNDRV_PCM_RATE_48000,
|
||||
.channels_min = 2,
|
||||
.channels_max = 8,
|
||||
.buffer_bytes_max = PCM_BUFFER_MAX,
|
||||
.period_bytes_min = FRAGMENT_SIZE_MIN,
|
||||
.period_bytes_max = PCM_BUFFER_MAX/2,
|
||||
.periods_min = FRAGMENTS_MIN,
|
||||
.periods_max = FRAGMENTS_MAX,
|
||||
};
|
||||
|
||||
static int bf5xx_pcm_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
size_t size = bf5xx_pcm_hardware.buffer_bytes_max;
|
||||
snd_pcm_lib_malloc_pages(substream, size * 4);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bf5xx_pcm_hw_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
snd_pcm_lib_free_pages(substream);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bf5xx_pcm_prepare(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct sport_device *sport = runtime->private_data;
|
||||
int fragsize_bytes = frames_to_bytes(runtime, runtime->period_size);
|
||||
|
||||
fragsize_bytes /= runtime->channels;
|
||||
/* inflate the fragsize to match the dma width of SPORT */
|
||||
fragsize_bytes *= 8;
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
sport_set_tx_callback(sport, bf5xx_dma_irq, substream);
|
||||
sport_config_tx_dma(sport, runtime->dma_area,
|
||||
runtime->periods, fragsize_bytes);
|
||||
} else {
|
||||
sport_set_rx_callback(sport, bf5xx_dma_irq, substream);
|
||||
sport_config_rx_dma(sport, runtime->dma_area,
|
||||
runtime->periods, fragsize_bytes);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bf5xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct sport_device *sport = runtime->private_data;
|
||||
int ret = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
sport_tx_start(sport);
|
||||
else
|
||||
sport_rx_start(sport);
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
sport_tx_stop(sport);
|
||||
else
|
||||
sport_rx_stop(sport);
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static snd_pcm_uframes_t bf5xx_pcm_pointer(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct sport_device *sport = runtime->private_data;
|
||||
unsigned int diff;
|
||||
snd_pcm_uframes_t frames;
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
diff = sport_curr_offset_tx(sport);
|
||||
frames = diff / (8*4); /* 32 bytes per frame */
|
||||
} else {
|
||||
diff = sport_curr_offset_rx(sport);
|
||||
frames = diff / (8*4);
|
||||
}
|
||||
return frames;
|
||||
}
|
||||
|
||||
static int bf5xx_pcm_open(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
int ret = 0;
|
||||
|
||||
snd_soc_set_runtime_hwparams(substream, &bf5xx_pcm_hardware);
|
||||
|
||||
ret = snd_pcm_hw_constraint_integer(runtime,
|
||||
SNDRV_PCM_HW_PARAM_PERIODS);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
if (sport_handle != NULL)
|
||||
runtime->private_data = sport_handle;
|
||||
else {
|
||||
pr_err("sport_handle is NULL\n");
|
||||
ret = -ENODEV;
|
||||
}
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int bf5xx_pcm_copy(struct snd_pcm_substream *substream, int channel,
|
||||
snd_pcm_uframes_t pos, void *buf, snd_pcm_uframes_t count)
|
||||
{
|
||||
unsigned int *src;
|
||||
unsigned int *dst;
|
||||
int i;
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
src = buf;
|
||||
dst = (unsigned int *)substream->runtime->dma_area;
|
||||
|
||||
dst += pos * 8;
|
||||
while (count--) {
|
||||
for (i = 0; i < substream->runtime->channels; i++)
|
||||
*(dst + i) = *src++;
|
||||
dst += 8;
|
||||
}
|
||||
} else {
|
||||
src = (unsigned int *)substream->runtime->dma_area;
|
||||
dst = buf;
|
||||
|
||||
src += pos * 8;
|
||||
while (count--) {
|
||||
for (i = 0; i < substream->runtime->channels; i++)
|
||||
*dst++ = *(src+i);
|
||||
src += 8;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bf5xx_pcm_silence(struct snd_pcm_substream *substream,
|
||||
int channel, snd_pcm_uframes_t pos, snd_pcm_uframes_t count)
|
||||
{
|
||||
unsigned char *buf = substream->runtime->dma_area;
|
||||
buf += pos * 8 * 4;
|
||||
memset(buf, '\0', count * 8 * 4);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
struct snd_pcm_ops bf5xx_pcm_tdm_ops = {
|
||||
.open = bf5xx_pcm_open,
|
||||
.ioctl = snd_pcm_lib_ioctl,
|
||||
.hw_params = bf5xx_pcm_hw_params,
|
||||
.hw_free = bf5xx_pcm_hw_free,
|
||||
.prepare = bf5xx_pcm_prepare,
|
||||
.trigger = bf5xx_pcm_trigger,
|
||||
.pointer = bf5xx_pcm_pointer,
|
||||
.copy = bf5xx_pcm_copy,
|
||||
.silence = bf5xx_pcm_silence,
|
||||
};
|
||||
|
||||
static int bf5xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
|
||||
{
|
||||
struct snd_pcm_substream *substream = pcm->streams[stream].substream;
|
||||
struct snd_dma_buffer *buf = &substream->dma_buffer;
|
||||
size_t size = bf5xx_pcm_hardware.buffer_bytes_max;
|
||||
|
||||
buf->dev.type = SNDRV_DMA_TYPE_DEV;
|
||||
buf->dev.dev = pcm->card->dev;
|
||||
buf->private_data = NULL;
|
||||
buf->area = dma_alloc_coherent(pcm->card->dev, size * 4,
|
||||
&buf->addr, GFP_KERNEL);
|
||||
if (!buf->area) {
|
||||
pr_err("Failed to allocate dma memory \
|
||||
Please increase uncached DMA memory region\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
buf->bytes = size;
|
||||
|
||||
if (stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
sport_handle->tx_buf = buf->area;
|
||||
else
|
||||
sport_handle->rx_buf = buf->area;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void bf5xx_pcm_free_dma_buffers(struct snd_pcm *pcm)
|
||||
{
|
||||
struct snd_pcm_substream *substream;
|
||||
struct snd_dma_buffer *buf;
|
||||
int stream;
|
||||
|
||||
for (stream = 0; stream < 2; stream++) {
|
||||
substream = pcm->streams[stream].substream;
|
||||
if (!substream)
|
||||
continue;
|
||||
|
||||
buf = &substream->dma_buffer;
|
||||
if (!buf->area)
|
||||
continue;
|
||||
dma_free_coherent(NULL, buf->bytes, buf->area, 0);
|
||||
buf->area = NULL;
|
||||
}
|
||||
if (sport_handle)
|
||||
sport_done(sport_handle);
|
||||
}
|
||||
|
||||
static u64 bf5xx_pcm_dmamask = DMA_BIT_MASK(32);
|
||||
|
||||
static int bf5xx_pcm_tdm_new(struct snd_card *card, struct snd_soc_dai *dai,
|
||||
struct snd_pcm *pcm)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
if (!card->dev->dma_mask)
|
||||
card->dev->dma_mask = &bf5xx_pcm_dmamask;
|
||||
if (!card->dev->coherent_dma_mask)
|
||||
card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
|
||||
|
||||
if (dai->playback.channels_min) {
|
||||
ret = bf5xx_pcm_preallocate_dma_buffer(pcm,
|
||||
SNDRV_PCM_STREAM_PLAYBACK);
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (dai->capture.channels_min) {
|
||||
ret = bf5xx_pcm_preallocate_dma_buffer(pcm,
|
||||
SNDRV_PCM_STREAM_CAPTURE);
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct snd_soc_platform bf5xx_tdm_soc_platform = {
|
||||
.name = "bf5xx-audio",
|
||||
.pcm_ops = &bf5xx_pcm_tdm_ops,
|
||||
.pcm_new = bf5xx_pcm_tdm_new,
|
||||
.pcm_free = bf5xx_pcm_free_dma_buffers,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(bf5xx_tdm_soc_platform);
|
||||
|
||||
static int __init bfin_pcm_tdm_init(void)
|
||||
{
|
||||
return snd_soc_register_platform(&bf5xx_tdm_soc_platform);
|
||||
}
|
||||
module_init(bfin_pcm_tdm_init);
|
||||
|
||||
static void __exit bfin_pcm_tdm_exit(void)
|
||||
{
|
||||
snd_soc_unregister_platform(&bf5xx_tdm_soc_platform);
|
||||
}
|
||||
module_exit(bfin_pcm_tdm_exit);
|
||||
|
||||
MODULE_AUTHOR("Barry Song");
|
||||
MODULE_DESCRIPTION("ADI Blackfin TDM PCM DMA module");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* sound/soc/blackfin/bf5xx-tdm-pcm.h -- ALSA PCM interface for the Blackfin
|
||||
*
|
||||
* Copyright 2009 Analog Device Inc.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef _BF5XX_TDM_PCM_H
|
||||
#define _BF5XX_TDM_PCM_H
|
||||
|
||||
struct bf5xx_pcm_dma_params {
|
||||
char *name; /* stream identifier */
|
||||
};
|
||||
|
||||
/* platform data */
|
||||
extern struct snd_soc_platform bf5xx_tdm_soc_platform;
|
||||
|
||||
#endif
|
|
@ -0,0 +1,343 @@
|
|||
/*
|
||||
* File: sound/soc/blackfin/bf5xx-tdm.c
|
||||
* Author: Barry Song <Barry.Song@analog.com>
|
||||
*
|
||||
* Created: Thurs June 04 2009
|
||||
* Description: Blackfin I2S(TDM) CPU DAI driver
|
||||
* Even though TDM mode can be as part of I2S DAI, but there
|
||||
* are so much difference in configuration and data flow,
|
||||
* it's very ugly to integrate I2S and TDM into a module
|
||||
*
|
||||
* Modified:
|
||||
* Copyright 2009 Analog Devices Inc.
|
||||
*
|
||||
* Bugs: Enter bugs at http://blackfin.uclinux.org/
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see the file COPYING, or write
|
||||
* to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/initval.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
#include <asm/irq.h>
|
||||
#include <asm/portmux.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/gpio.h>
|
||||
|
||||
#include "bf5xx-sport.h"
|
||||
#include "bf5xx-tdm.h"
|
||||
|
||||
struct bf5xx_tdm_port {
|
||||
u16 tcr1;
|
||||
u16 rcr1;
|
||||
u16 tcr2;
|
||||
u16 rcr2;
|
||||
int configured;
|
||||
};
|
||||
|
||||
static struct bf5xx_tdm_port bf5xx_tdm;
|
||||
static int sport_num = CONFIG_SND_BF5XX_SPORT_NUM;
|
||||
|
||||
static struct sport_param sport_params[2] = {
|
||||
{
|
||||
.dma_rx_chan = CH_SPORT0_RX,
|
||||
.dma_tx_chan = CH_SPORT0_TX,
|
||||
.err_irq = IRQ_SPORT0_ERROR,
|
||||
.regs = (struct sport_register *)SPORT0_TCR1,
|
||||
},
|
||||
{
|
||||
.dma_rx_chan = CH_SPORT1_RX,
|
||||
.dma_tx_chan = CH_SPORT1_TX,
|
||||
.err_irq = IRQ_SPORT1_ERROR,
|
||||
.regs = (struct sport_register *)SPORT1_TCR1,
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Setting the TFS pin selector for SPORT 0 based on whether the selected
|
||||
* port id F or G. If the port is F then no conflict should exist for the
|
||||
* TFS. When Port G is selected and EMAC then there is a conflict between
|
||||
* the PHY interrupt line and TFS. Current settings prevent the conflict
|
||||
* by ignoring the TFS pin when Port G is selected. This allows both
|
||||
* ssm2602 using Port G and EMAC concurrently.
|
||||
*/
|
||||
#ifdef CONFIG_BF527_SPORT0_PORTF
|
||||
#define LOCAL_SPORT0_TFS (P_SPORT0_TFS)
|
||||
#else
|
||||
#define LOCAL_SPORT0_TFS (0)
|
||||
#endif
|
||||
|
||||
static u16 sport_req[][7] = { {P_SPORT0_DTPRI, P_SPORT0_TSCLK, P_SPORT0_RFS,
|
||||
P_SPORT0_DRPRI, P_SPORT0_RSCLK, LOCAL_SPORT0_TFS, 0},
|
||||
{P_SPORT1_DTPRI, P_SPORT1_TSCLK, P_SPORT1_RFS, P_SPORT1_DRPRI,
|
||||
P_SPORT1_RSCLK, P_SPORT1_TFS, 0} };
|
||||
|
||||
static int bf5xx_tdm_set_dai_fmt(struct snd_soc_dai *cpu_dai,
|
||||
unsigned int fmt)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
/* interface format:support TDM,slave mode */
|
||||
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
|
||||
case SND_SOC_DAIFMT_DSP_A:
|
||||
break;
|
||||
default:
|
||||
printk(KERN_ERR "%s: Unknown DAI format type\n", __func__);
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
|
||||
case SND_SOC_DAIFMT_CBM_CFM:
|
||||
break;
|
||||
case SND_SOC_DAIFMT_CBS_CFS:
|
||||
case SND_SOC_DAIFMT_CBM_CFS:
|
||||
case SND_SOC_DAIFMT_CBS_CFM:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
default:
|
||||
printk(KERN_ERR "%s: Unknown DAI master type\n", __func__);
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int bf5xx_tdm_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
bf5xx_tdm.tcr2 &= ~0x1f;
|
||||
bf5xx_tdm.rcr2 &= ~0x1f;
|
||||
switch (params_format(params)) {
|
||||
case SNDRV_PCM_FORMAT_S32_LE:
|
||||
bf5xx_tdm.tcr2 |= 31;
|
||||
bf5xx_tdm.rcr2 |= 31;
|
||||
sport_handle->wdsize = 4;
|
||||
break;
|
||||
/* at present, we only support 32bit transfer */
|
||||
default:
|
||||
pr_err("not supported PCM format yet\n");
|
||||
return -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!bf5xx_tdm.configured) {
|
||||
/*
|
||||
* TX and RX are not independent,they are enabled at the
|
||||
* same time, even if only one side is running. So, we
|
||||
* need to configure both of them at the time when the first
|
||||
* stream is opened.
|
||||
*
|
||||
* CPU DAI:slave mode.
|
||||
*/
|
||||
ret = sport_config_rx(sport_handle, bf5xx_tdm.rcr1,
|
||||
bf5xx_tdm.rcr2, 0, 0);
|
||||
if (ret) {
|
||||
pr_err("SPORT is busy!\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
ret = sport_config_tx(sport_handle, bf5xx_tdm.tcr1,
|
||||
bf5xx_tdm.tcr2, 0, 0);
|
||||
if (ret) {
|
||||
pr_err("SPORT is busy!\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
bf5xx_tdm.configured = 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void bf5xx_tdm_shutdown(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
/* No active stream, SPORT is allowed to be configured again. */
|
||||
if (!dai->active)
|
||||
bf5xx_tdm.configured = 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int bf5xx_tdm_suspend(struct snd_soc_dai *dai)
|
||||
{
|
||||
struct sport_device *sport =
|
||||
(struct sport_device *)dai->private_data;
|
||||
|
||||
if (!dai->active)
|
||||
return 0;
|
||||
if (dai->capture.active)
|
||||
sport_rx_stop(sport);
|
||||
if (dai->playback.active)
|
||||
sport_tx_stop(sport);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bf5xx_tdm_resume(struct snd_soc_dai *dai)
|
||||
{
|
||||
int ret;
|
||||
struct sport_device *sport =
|
||||
(struct sport_device *)dai->private_data;
|
||||
|
||||
if (!dai->active)
|
||||
return 0;
|
||||
|
||||
ret = sport_set_multichannel(sport, 8, 0xFF, 1);
|
||||
if (ret) {
|
||||
pr_err("SPORT is busy!\n");
|
||||
ret = -EBUSY;
|
||||
}
|
||||
|
||||
ret = sport_config_rx(sport, IRFS, 0x1F, 0, 0);
|
||||
if (ret) {
|
||||
pr_err("SPORT is busy!\n");
|
||||
ret = -EBUSY;
|
||||
}
|
||||
|
||||
ret = sport_config_tx(sport, ITFS, 0x1F, 0, 0);
|
||||
if (ret) {
|
||||
pr_err("SPORT is busy!\n");
|
||||
ret = -EBUSY;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
#define bf5xx_tdm_suspend NULL
|
||||
#define bf5xx_tdm_resume NULL
|
||||
#endif
|
||||
|
||||
static struct snd_soc_dai_ops bf5xx_tdm_dai_ops = {
|
||||
.hw_params = bf5xx_tdm_hw_params,
|
||||
.set_fmt = bf5xx_tdm_set_dai_fmt,
|
||||
.shutdown = bf5xx_tdm_shutdown,
|
||||
};
|
||||
|
||||
struct snd_soc_dai bf5xx_tdm_dai = {
|
||||
.name = "bf5xx-tdm",
|
||||
.id = 0,
|
||||
.suspend = bf5xx_tdm_suspend,
|
||||
.resume = bf5xx_tdm_resume,
|
||||
.playback = {
|
||||
.channels_min = 2,
|
||||
.channels_max = 8,
|
||||
.rates = SNDRV_PCM_RATE_48000,
|
||||
.formats = SNDRV_PCM_FMTBIT_S32_LE,},
|
||||
.capture = {
|
||||
.channels_min = 2,
|
||||
.channels_max = 8,
|
||||
.rates = SNDRV_PCM_RATE_48000,
|
||||
.formats = SNDRV_PCM_FMTBIT_S32_LE,},
|
||||
.ops = &bf5xx_tdm_dai_ops,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(bf5xx_tdm_dai);
|
||||
|
||||
static int __devinit bfin_tdm_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
if (peripheral_request_list(&sport_req[sport_num][0], "soc-audio")) {
|
||||
pr_err("Requesting Peripherals failed\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
/* request DMA for SPORT */
|
||||
sport_handle = sport_init(&sport_params[sport_num], 4, \
|
||||
8 * sizeof(u32), NULL);
|
||||
if (!sport_handle) {
|
||||
peripheral_free_list(&sport_req[sport_num][0]);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* SPORT works in TDM mode */
|
||||
ret = sport_set_multichannel(sport_handle, 8, 0xFF, 1);
|
||||
if (ret) {
|
||||
pr_err("SPORT is busy!\n");
|
||||
ret = -EBUSY;
|
||||
goto sport_config_err;
|
||||
}
|
||||
|
||||
ret = sport_config_rx(sport_handle, IRFS, 0x1F, 0, 0);
|
||||
if (ret) {
|
||||
pr_err("SPORT is busy!\n");
|
||||
ret = -EBUSY;
|
||||
goto sport_config_err;
|
||||
}
|
||||
|
||||
ret = sport_config_tx(sport_handle, ITFS, 0x1F, 0, 0);
|
||||
if (ret) {
|
||||
pr_err("SPORT is busy!\n");
|
||||
ret = -EBUSY;
|
||||
goto sport_config_err;
|
||||
}
|
||||
|
||||
ret = snd_soc_register_dai(&bf5xx_tdm_dai);
|
||||
if (ret) {
|
||||
pr_err("Failed to register DAI: %d\n", ret);
|
||||
goto sport_config_err;
|
||||
}
|
||||
return 0;
|
||||
|
||||
sport_config_err:
|
||||
peripheral_free_list(&sport_req[sport_num][0]);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __devexit bfin_tdm_remove(struct platform_device *pdev)
|
||||
{
|
||||
peripheral_free_list(&sport_req[sport_num][0]);
|
||||
snd_soc_unregister_dai(&bf5xx_tdm_dai);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver bfin_tdm_driver = {
|
||||
.probe = bfin_tdm_probe,
|
||||
.remove = __devexit_p(bfin_tdm_remove),
|
||||
.driver = {
|
||||
.name = "bfin-tdm",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init bfin_tdm_init(void)
|
||||
{
|
||||
return platform_driver_register(&bfin_tdm_driver);
|
||||
}
|
||||
module_init(bfin_tdm_init);
|
||||
|
||||
static void __exit bfin_tdm_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&bfin_tdm_driver);
|
||||
}
|
||||
module_exit(bfin_tdm_exit);
|
||||
|
||||
/* Module information */
|
||||
MODULE_AUTHOR("Barry Song");
|
||||
MODULE_DESCRIPTION("TDM driver for ADI Blackfin");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* sound/soc/blackfin/bf5xx-tdm.h
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef _BF5XX_TDM_H
|
||||
#define _BF5XX_TDM_H
|
||||
|
||||
extern struct snd_soc_dai bf5xx_tdm_dai;
|
||||
|
||||
#endif
|
|
@ -12,11 +12,15 @@ config SND_SOC_ALL_CODECS
|
|||
tristate "Build all ASoC CODEC drivers"
|
||||
select SND_SOC_L3
|
||||
select SND_SOC_AC97_CODEC if SND_SOC_AC97_BUS
|
||||
select SND_SOC_AD1836 if SPI_MASTER
|
||||
select SND_SOC_AD1938 if SPI_MASTER
|
||||
select SND_SOC_AD1980 if SND_SOC_AC97_BUS
|
||||
select SND_SOC_AD73311 if I2C
|
||||
select SND_SOC_AK4104 if SPI_MASTER
|
||||
select SND_SOC_AK4535 if I2C
|
||||
select SND_SOC_AK4642 if I2C
|
||||
select SND_SOC_CS4270 if I2C
|
||||
select SND_SOC_MAX9877 if I2C
|
||||
select SND_SOC_PCM3008
|
||||
select SND_SOC_SPDIF
|
||||
select SND_SOC_SSM2602 if I2C
|
||||
|
@ -30,18 +34,23 @@ config SND_SOC_ALL_CODECS
|
|||
select SND_SOC_WM8350 if MFD_WM8350
|
||||
select SND_SOC_WM8400 if MFD_WM8400
|
||||
select SND_SOC_WM8510 if SND_SOC_I2C_AND_SPI
|
||||
select SND_SOC_WM8523 if I2C
|
||||
select SND_SOC_WM8580 if I2C
|
||||
select SND_SOC_WM8728 if SND_SOC_I2C_AND_SPI
|
||||
select SND_SOC_WM8731 if SND_SOC_I2C_AND_SPI
|
||||
select SND_SOC_WM8750 if SND_SOC_I2C_AND_SPI
|
||||
select SND_SOC_WM8753 if SND_SOC_I2C_AND_SPI
|
||||
select SND_SOC_WM8776 if SND_SOC_I2C_AND_SPI
|
||||
select SND_SOC_WM8900 if I2C
|
||||
select SND_SOC_WM8903 if I2C
|
||||
select SND_SOC_WM8940 if I2C
|
||||
select SND_SOC_WM8960 if I2C
|
||||
select SND_SOC_WM8961 if I2C
|
||||
select SND_SOC_WM8971 if I2C
|
||||
select SND_SOC_WM8974 if I2C
|
||||
select SND_SOC_WM8988 if SND_SOC_I2C_AND_SPI
|
||||
select SND_SOC_WM8990 if I2C
|
||||
select SND_SOC_WM8993 if I2C
|
||||
select SND_SOC_WM9081 if I2C
|
||||
select SND_SOC_WM9705 if SND_SOC_AC97_BUS
|
||||
select SND_SOC_WM9712 if SND_SOC_AC97_BUS
|
||||
|
@ -57,11 +66,21 @@ config SND_SOC_ALL_CODECS
|
|||
|
||||
If unsure select "N".
|
||||
|
||||
config SND_SOC_WM_HUBS
|
||||
tristate
|
||||
default y if SND_SOC_WM8993=y
|
||||
default m if SND_SOC_WM8993=m
|
||||
|
||||
config SND_SOC_AC97_CODEC
|
||||
tristate
|
||||
select SND_AC97_CODEC
|
||||
|
||||
config SND_SOC_AD1836
|
||||
tristate
|
||||
|
||||
config SND_SOC_AD1938
|
||||
tristate
|
||||
|
||||
config SND_SOC_AD1980
|
||||
tristate
|
||||
|
||||
|
@ -74,6 +93,9 @@ config SND_SOC_AK4104
|
|||
config SND_SOC_AK4535
|
||||
tristate
|
||||
|
||||
config SND_SOC_AK4642
|
||||
tristate
|
||||
|
||||
# Cirrus Logic CS4270 Codec
|
||||
config SND_SOC_CS4270
|
||||
tristate
|
||||
|
@ -86,6 +108,9 @@ config SND_SOC_CS4270_VD33_ERRATA
|
|||
bool
|
||||
depends on SND_SOC_CS4270
|
||||
|
||||
config SND_SOC_CX20442
|
||||
tristate
|
||||
|
||||
config SND_SOC_L3
|
||||
tristate
|
||||
|
||||
|
@ -129,6 +154,9 @@ config SND_SOC_WM8400
|
|||
config SND_SOC_WM8510
|
||||
tristate
|
||||
|
||||
config SND_SOC_WM8523
|
||||
tristate
|
||||
|
||||
config SND_SOC_WM8580
|
||||
tristate
|
||||
|
||||
|
@ -144,6 +172,9 @@ config SND_SOC_WM8750
|
|||
config SND_SOC_WM8753
|
||||
tristate
|
||||
|
||||
config SND_SOC_WM8776
|
||||
tristate
|
||||
|
||||
config SND_SOC_WM8900
|
||||
tristate
|
||||
|
||||
|
@ -156,15 +187,24 @@ config SND_SOC_WM8940
|
|||
config SND_SOC_WM8960
|
||||
tristate
|
||||
|
||||
config SND_SOC_WM8961
|
||||
tristate
|
||||
|
||||
config SND_SOC_WM8971
|
||||
tristate
|
||||
|
||||
config SND_SOC_WM8974
|
||||
tristate
|
||||
|
||||
config SND_SOC_WM8988
|
||||
tristate
|
||||
|
||||
config SND_SOC_WM8990
|
||||
tristate
|
||||
|
||||
config SND_SOC_WM8993
|
||||
tristate
|
||||
|
||||
config SND_SOC_WM9081
|
||||
tristate
|
||||
|
||||
|
@ -176,3 +216,7 @@ config SND_SOC_WM9712
|
|||
|
||||
config SND_SOC_WM9713
|
||||
tristate
|
||||
|
||||
# Amp
|
||||
config SND_SOC_MAX9877
|
||||
tristate
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
snd-soc-ac97-objs := ac97.o
|
||||
snd-soc-ad1836-objs := ad1836.o
|
||||
snd-soc-ad1938-objs := ad1938.o
|
||||
snd-soc-ad1980-objs := ad1980.o
|
||||
snd-soc-ad73311-objs := ad73311.o
|
||||
snd-soc-ak4104-objs := ak4104.o
|
||||
snd-soc-ak4535-objs := ak4535.o
|
||||
snd-soc-ak4642-objs := ak4642.o
|
||||
snd-soc-cs4270-objs := cs4270.o
|
||||
snd-soc-cx20442-objs := cx20442.o
|
||||
snd-soc-l3-objs := l3.o
|
||||
snd-soc-pcm3008-objs := pcm3008.o
|
||||
snd-soc-spdif-objs := spdif_transciever.o
|
||||
|
@ -18,29 +22,42 @@ snd-soc-uda1380-objs := uda1380.o
|
|||
snd-soc-wm8350-objs := wm8350.o
|
||||
snd-soc-wm8400-objs := wm8400.o
|
||||
snd-soc-wm8510-objs := wm8510.o
|
||||
snd-soc-wm8523-objs := wm8523.o
|
||||
snd-soc-wm8580-objs := wm8580.o
|
||||
snd-soc-wm8728-objs := wm8728.o
|
||||
snd-soc-wm8731-objs := wm8731.o
|
||||
snd-soc-wm8750-objs := wm8750.o
|
||||
snd-soc-wm8753-objs := wm8753.o
|
||||
snd-soc-wm8776-objs := wm8776.o
|
||||
snd-soc-wm8900-objs := wm8900.o
|
||||
snd-soc-wm8903-objs := wm8903.o
|
||||
snd-soc-wm8940-objs := wm8940.o
|
||||
snd-soc-wm8960-objs := wm8960.o
|
||||
snd-soc-wm8961-objs := wm8961.o
|
||||
snd-soc-wm8971-objs := wm8971.o
|
||||
snd-soc-wm8974-objs := wm8974.o
|
||||
snd-soc-wm8988-objs := wm8988.o
|
||||
snd-soc-wm8990-objs := wm8990.o
|
||||
snd-soc-wm8993-objs := wm8993.o
|
||||
snd-soc-wm9081-objs := wm9081.o
|
||||
snd-soc-wm9705-objs := wm9705.o
|
||||
snd-soc-wm9712-objs := wm9712.o
|
||||
snd-soc-wm9713-objs := wm9713.o
|
||||
snd-soc-wm-hubs-objs := wm_hubs.o
|
||||
|
||||
# Amp
|
||||
snd-soc-max9877-objs := max9877.o
|
||||
|
||||
obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o
|
||||
obj-$(CONFIG_SND_SOC_AD1836) += snd-soc-ad1836.o
|
||||
obj-$(CONFIG_SND_SOC_AD1938) += snd-soc-ad1938.o
|
||||
obj-$(CONFIG_SND_SOC_AD1980) += snd-soc-ad1980.o
|
||||
obj-$(CONFIG_SND_SOC_AD73311) += snd-soc-ad73311.o
|
||||
obj-$(CONFIG_SND_SOC_AK4104) += snd-soc-ak4104.o
|
||||
obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o
|
||||
obj-$(CONFIG_SND_SOC_AK4642) += snd-soc-ak4642.o
|
||||
obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o
|
||||
obj-$(CONFIG_SND_SOC_CX20442) += snd-soc-cx20442.o
|
||||
obj-$(CONFIG_SND_SOC_L3) += snd-soc-l3.o
|
||||
obj-$(CONFIG_SND_SOC_PCM3008) += snd-soc-pcm3008.o
|
||||
obj-$(CONFIG_SND_SOC_SPDIF) += snd-soc-spdif.o
|
||||
|
@ -55,19 +72,28 @@ obj-$(CONFIG_SND_SOC_UDA1380) += snd-soc-uda1380.o
|
|||
obj-$(CONFIG_SND_SOC_WM8350) += snd-soc-wm8350.o
|
||||
obj-$(CONFIG_SND_SOC_WM8400) += snd-soc-wm8400.o
|
||||
obj-$(CONFIG_SND_SOC_WM8510) += snd-soc-wm8510.o
|
||||
obj-$(CONFIG_SND_SOC_WM8523) += snd-soc-wm8523.o
|
||||
obj-$(CONFIG_SND_SOC_WM8580) += snd-soc-wm8580.o
|
||||
obj-$(CONFIG_SND_SOC_WM8728) += snd-soc-wm8728.o
|
||||
obj-$(CONFIG_SND_SOC_WM8731) += snd-soc-wm8731.o
|
||||
obj-$(CONFIG_SND_SOC_WM8750) += snd-soc-wm8750.o
|
||||
obj-$(CONFIG_SND_SOC_WM8753) += snd-soc-wm8753.o
|
||||
obj-$(CONFIG_SND_SOC_WM8776) += snd-soc-wm8776.o
|
||||
obj-$(CONFIG_SND_SOC_WM8900) += snd-soc-wm8900.o
|
||||
obj-$(CONFIG_SND_SOC_WM8903) += snd-soc-wm8903.o
|
||||
obj-$(CONFIG_SND_SOC_WM8971) += snd-soc-wm8971.o
|
||||
obj-$(CONFIG_SND_SOC_WM8974) += snd-soc-wm8974.o
|
||||
obj-$(CONFIG_SND_SOC_WM8940) += snd-soc-wm8940.o
|
||||
obj-$(CONFIG_SND_SOC_WM8960) += snd-soc-wm8960.o
|
||||
obj-$(CONFIG_SND_SOC_WM8961) += snd-soc-wm8961.o
|
||||
obj-$(CONFIG_SND_SOC_WM8988) += snd-soc-wm8988.o
|
||||
obj-$(CONFIG_SND_SOC_WM8990) += snd-soc-wm8990.o
|
||||
obj-$(CONFIG_SND_SOC_WM8993) += snd-soc-wm8993.o
|
||||
obj-$(CONFIG_SND_SOC_WM9081) += snd-soc-wm9081.o
|
||||
obj-$(CONFIG_SND_SOC_WM9705) += snd-soc-wm9705.o
|
||||
obj-$(CONFIG_SND_SOC_WM9712) += snd-soc-wm9712.o
|
||||
obj-$(CONFIG_SND_SOC_WM9713) += snd-soc-wm9713.o
|
||||
obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o
|
||||
|
||||
# Amp
|
||||
obj-$(CONFIG_SND_SOC_MAX9877) += snd-soc-max9877.o
|
||||
|
|
|
@ -0,0 +1,446 @@
|
|||
/*
|
||||
* File: sound/soc/codecs/ad1836.c
|
||||
* Author: Barry Song <Barry.Song@analog.com>
|
||||
*
|
||||
* Created: Aug 04 2009
|
||||
* Description: Driver for AD1836 sound chip
|
||||
*
|
||||
* Modified:
|
||||
* Copyright 2009 Analog Devices Inc.
|
||||
*
|
||||
* Bugs: Enter bugs at http://blackfin.uclinux.org/
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/version.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/device.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/initval.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/tlv.h>
|
||||
#include <sound/soc-dapm.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include "ad1836.h"
|
||||
|
||||
/* codec private data */
|
||||
struct ad1836_priv {
|
||||
struct snd_soc_codec codec;
|
||||
u16 reg_cache[AD1836_NUM_REGS];
|
||||
};
|
||||
|
||||
static struct snd_soc_codec *ad1836_codec;
|
||||
struct snd_soc_codec_device soc_codec_dev_ad1836;
|
||||
static int ad1836_register(struct ad1836_priv *ad1836);
|
||||
static void ad1836_unregister(struct ad1836_priv *ad1836);
|
||||
|
||||
/*
|
||||
* AD1836 volume/mute/de-emphasis etc. controls
|
||||
*/
|
||||
static const char *ad1836_deemp[] = {"None", "44.1kHz", "32kHz", "48kHz"};
|
||||
|
||||
static const struct soc_enum ad1836_deemp_enum =
|
||||
SOC_ENUM_SINGLE(AD1836_DAC_CTRL1, 8, 4, ad1836_deemp);
|
||||
|
||||
static const struct snd_kcontrol_new ad1836_snd_controls[] = {
|
||||
/* DAC volume control */
|
||||
SOC_DOUBLE_R("DAC1 Volume", AD1836_DAC_L1_VOL,
|
||||
AD1836_DAC_R1_VOL, 0, 0x3FF, 0),
|
||||
SOC_DOUBLE_R("DAC2 Volume", AD1836_DAC_L2_VOL,
|
||||
AD1836_DAC_R2_VOL, 0, 0x3FF, 0),
|
||||
SOC_DOUBLE_R("DAC3 Volume", AD1836_DAC_L3_VOL,
|
||||
AD1836_DAC_R3_VOL, 0, 0x3FF, 0),
|
||||
|
||||
/* ADC switch control */
|
||||
SOC_DOUBLE("ADC1 Switch", AD1836_ADC_CTRL2, AD1836_ADCL1_MUTE,
|
||||
AD1836_ADCR1_MUTE, 1, 1),
|
||||
SOC_DOUBLE("ADC2 Switch", AD1836_ADC_CTRL2, AD1836_ADCL2_MUTE,
|
||||
AD1836_ADCR2_MUTE, 1, 1),
|
||||
|
||||
/* DAC switch control */
|
||||
SOC_DOUBLE("DAC1 Switch", AD1836_DAC_CTRL2, AD1836_DACL1_MUTE,
|
||||
AD1836_DACR1_MUTE, 1, 1),
|
||||
SOC_DOUBLE("DAC2 Switch", AD1836_DAC_CTRL2, AD1836_DACL2_MUTE,
|
||||
AD1836_DACR2_MUTE, 1, 1),
|
||||
SOC_DOUBLE("DAC3 Switch", AD1836_DAC_CTRL2, AD1836_DACL3_MUTE,
|
||||
AD1836_DACR3_MUTE, 1, 1),
|
||||
|
||||
/* ADC high-pass filter */
|
||||
SOC_SINGLE("ADC High Pass Filter Switch", AD1836_ADC_CTRL1,
|
||||
AD1836_ADC_HIGHPASS_FILTER, 1, 0),
|
||||
|
||||
/* DAC de-emphasis */
|
||||
SOC_ENUM("Playback Deemphasis", ad1836_deemp_enum),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_widget ad1836_dapm_widgets[] = {
|
||||
SND_SOC_DAPM_DAC("DAC", "Playback", AD1836_DAC_CTRL1,
|
||||
AD1836_DAC_POWERDOWN, 1),
|
||||
SND_SOC_DAPM_ADC("ADC", "Capture", SND_SOC_NOPM, 0, 0),
|
||||
SND_SOC_DAPM_SUPPLY("ADC_PWR", AD1836_ADC_CTRL1,
|
||||
AD1836_ADC_POWERDOWN, 1, NULL, 0),
|
||||
SND_SOC_DAPM_OUTPUT("DAC1OUT"),
|
||||
SND_SOC_DAPM_OUTPUT("DAC2OUT"),
|
||||
SND_SOC_DAPM_OUTPUT("DAC3OUT"),
|
||||
SND_SOC_DAPM_INPUT("ADC1IN"),
|
||||
SND_SOC_DAPM_INPUT("ADC2IN"),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route audio_paths[] = {
|
||||
{ "DAC", NULL, "ADC_PWR" },
|
||||
{ "ADC", NULL, "ADC_PWR" },
|
||||
{ "DAC1OUT", "DAC1 Switch", "DAC" },
|
||||
{ "DAC2OUT", "DAC2 Switch", "DAC" },
|
||||
{ "DAC3OUT", "DAC3 Switch", "DAC" },
|
||||
{ "ADC", "ADC1 Switch", "ADC1IN" },
|
||||
{ "ADC", "ADC2 Switch", "ADC2IN" },
|
||||
};
|
||||
|
||||
/*
|
||||
* DAI ops entries
|
||||
*/
|
||||
|
||||
static int ad1836_set_dai_fmt(struct snd_soc_dai *codec_dai,
|
||||
unsigned int fmt)
|
||||
{
|
||||
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
|
||||
/* at present, we support adc aux mode to interface with
|
||||
* blackfin sport tdm mode
|
||||
*/
|
||||
case SND_SOC_DAIFMT_DSP_A:
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
|
||||
case SND_SOC_DAIFMT_IB_IF:
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
|
||||
/* ALCLK,ABCLK are both output, AD1836 can only be master */
|
||||
case SND_SOC_DAIFMT_CBM_CFM:
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ad1836_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
int word_len = 0;
|
||||
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_device *socdev = rtd->socdev;
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
|
||||
/* bit size */
|
||||
switch (params_format(params)) {
|
||||
case SNDRV_PCM_FORMAT_S16_LE:
|
||||
word_len = 3;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S20_3LE:
|
||||
word_len = 1;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S24_LE:
|
||||
case SNDRV_PCM_FORMAT_S32_LE:
|
||||
word_len = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
snd_soc_update_bits(codec, AD1836_DAC_CTRL1,
|
||||
AD1836_DAC_WORD_LEN_MASK, word_len);
|
||||
|
||||
snd_soc_update_bits(codec, AD1836_ADC_CTRL2,
|
||||
AD1836_ADC_WORD_LEN_MASK, word_len);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* interface to read/write ad1836 register
|
||||
*/
|
||||
#define AD1836_SPI_REG_SHFT 12
|
||||
#define AD1836_SPI_READ (1 << 11)
|
||||
#define AD1836_SPI_VAL_MSK 0x3FF
|
||||
|
||||
/*
|
||||
* write to the ad1836 register space
|
||||
*/
|
||||
|
||||
static int ad1836_write_reg(struct snd_soc_codec *codec, unsigned int reg,
|
||||
unsigned int value)
|
||||
{
|
||||
u16 *reg_cache = codec->reg_cache;
|
||||
int ret = 0;
|
||||
|
||||
if (value != reg_cache[reg]) {
|
||||
unsigned short buf;
|
||||
struct spi_transfer t = {
|
||||
.tx_buf = &buf,
|
||||
.len = 2,
|
||||
};
|
||||
struct spi_message m;
|
||||
|
||||
buf = (reg << AD1836_SPI_REG_SHFT) |
|
||||
(value & AD1836_SPI_VAL_MSK);
|
||||
spi_message_init(&m);
|
||||
spi_message_add_tail(&t, &m);
|
||||
ret = spi_sync(codec->control_data, &m);
|
||||
if (ret == 0)
|
||||
reg_cache[reg] = value;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* read from the ad1836 register space cache
|
||||
*/
|
||||
static unsigned int ad1836_read_reg_cache(struct snd_soc_codec *codec,
|
||||
unsigned int reg)
|
||||
{
|
||||
u16 *reg_cache = codec->reg_cache;
|
||||
|
||||
if (reg >= codec->reg_cache_size)
|
||||
return -EINVAL;
|
||||
|
||||
return reg_cache[reg];
|
||||
}
|
||||
|
||||
static int __devinit ad1836_spi_probe(struct spi_device *spi)
|
||||
{
|
||||
struct snd_soc_codec *codec;
|
||||
struct ad1836_priv *ad1836;
|
||||
|
||||
ad1836 = kzalloc(sizeof(struct ad1836_priv), GFP_KERNEL);
|
||||
if (ad1836 == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
codec = &ad1836->codec;
|
||||
codec->control_data = spi;
|
||||
codec->dev = &spi->dev;
|
||||
|
||||
dev_set_drvdata(&spi->dev, ad1836);
|
||||
|
||||
return ad1836_register(ad1836);
|
||||
}
|
||||
|
||||
static int __devexit ad1836_spi_remove(struct spi_device *spi)
|
||||
{
|
||||
struct ad1836_priv *ad1836 = dev_get_drvdata(&spi->dev);
|
||||
|
||||
ad1836_unregister(ad1836);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct spi_driver ad1836_spi_driver = {
|
||||
.driver = {
|
||||
.name = "ad1836-spi",
|
||||
.bus = &spi_bus_type,
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = ad1836_spi_probe,
|
||||
.remove = __devexit_p(ad1836_spi_remove),
|
||||
};
|
||||
|
||||
static struct snd_soc_dai_ops ad1836_dai_ops = {
|
||||
.hw_params = ad1836_hw_params,
|
||||
.set_fmt = ad1836_set_dai_fmt,
|
||||
};
|
||||
|
||||
/* codec DAI instance */
|
||||
struct snd_soc_dai ad1836_dai = {
|
||||
.name = "AD1836",
|
||||
.playback = {
|
||||
.stream_name = "Playback",
|
||||
.channels_min = 2,
|
||||
.channels_max = 6,
|
||||
.rates = SNDRV_PCM_RATE_48000,
|
||||
.formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE |
|
||||
SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE,
|
||||
},
|
||||
.capture = {
|
||||
.stream_name = "Capture",
|
||||
.channels_min = 2,
|
||||
.channels_max = 4,
|
||||
.rates = SNDRV_PCM_RATE_48000,
|
||||
.formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE |
|
||||
SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE,
|
||||
},
|
||||
.ops = &ad1836_dai_ops,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(ad1836_dai);
|
||||
|
||||
static int ad1836_register(struct ad1836_priv *ad1836)
|
||||
{
|
||||
int ret;
|
||||
struct snd_soc_codec *codec = &ad1836->codec;
|
||||
|
||||
if (ad1836_codec) {
|
||||
dev_err(codec->dev, "Another ad1836 is registered\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_init(&codec->mutex);
|
||||
INIT_LIST_HEAD(&codec->dapm_widgets);
|
||||
INIT_LIST_HEAD(&codec->dapm_paths);
|
||||
codec->private_data = ad1836;
|
||||
codec->reg_cache = ad1836->reg_cache;
|
||||
codec->reg_cache_size = AD1836_NUM_REGS;
|
||||
codec->name = "AD1836";
|
||||
codec->owner = THIS_MODULE;
|
||||
codec->dai = &ad1836_dai;
|
||||
codec->num_dai = 1;
|
||||
codec->write = ad1836_write_reg;
|
||||
codec->read = ad1836_read_reg_cache;
|
||||
INIT_LIST_HEAD(&codec->dapm_widgets);
|
||||
INIT_LIST_HEAD(&codec->dapm_paths);
|
||||
|
||||
ad1836_dai.dev = codec->dev;
|
||||
ad1836_codec = codec;
|
||||
|
||||
/* default setting for ad1836 */
|
||||
/* de-emphasis: 48kHz, power-on dac */
|
||||
codec->write(codec, AD1836_DAC_CTRL1, 0x300);
|
||||
/* unmute dac channels */
|
||||
codec->write(codec, AD1836_DAC_CTRL2, 0x0);
|
||||
/* high-pass filter enable, power-on adc */
|
||||
codec->write(codec, AD1836_ADC_CTRL1, 0x100);
|
||||
/* unmute adc channles, adc aux mode */
|
||||
codec->write(codec, AD1836_ADC_CTRL2, 0x180);
|
||||
/* left/right diff:PGA/MUX */
|
||||
codec->write(codec, AD1836_ADC_CTRL3, 0x3A);
|
||||
/* volume */
|
||||
codec->write(codec, AD1836_DAC_L1_VOL, 0x3FF);
|
||||
codec->write(codec, AD1836_DAC_R1_VOL, 0x3FF);
|
||||
codec->write(codec, AD1836_DAC_L2_VOL, 0x3FF);
|
||||
codec->write(codec, AD1836_DAC_R2_VOL, 0x3FF);
|
||||
codec->write(codec, AD1836_DAC_L3_VOL, 0x3FF);
|
||||
codec->write(codec, AD1836_DAC_R3_VOL, 0x3FF);
|
||||
|
||||
ret = snd_soc_register_codec(codec);
|
||||
if (ret != 0) {
|
||||
dev_err(codec->dev, "Failed to register codec: %d\n", ret);
|
||||
kfree(ad1836);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = snd_soc_register_dai(&ad1836_dai);
|
||||
if (ret != 0) {
|
||||
dev_err(codec->dev, "Failed to register DAI: %d\n", ret);
|
||||
snd_soc_unregister_codec(codec);
|
||||
kfree(ad1836);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ad1836_unregister(struct ad1836_priv *ad1836)
|
||||
{
|
||||
snd_soc_unregister_dai(&ad1836_dai);
|
||||
snd_soc_unregister_codec(&ad1836->codec);
|
||||
kfree(ad1836);
|
||||
ad1836_codec = NULL;
|
||||
}
|
||||
|
||||
static int ad1836_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
struct snd_soc_codec *codec;
|
||||
int ret = 0;
|
||||
|
||||
if (ad1836_codec == NULL) {
|
||||
dev_err(&pdev->dev, "Codec device not registered\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
socdev->card->codec = ad1836_codec;
|
||||
codec = ad1836_codec;
|
||||
|
||||
/* register pcms */
|
||||
ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
|
||||
if (ret < 0) {
|
||||
dev_err(codec->dev, "failed to create pcms: %d\n", ret);
|
||||
goto pcm_err;
|
||||
}
|
||||
|
||||
snd_soc_add_controls(codec, ad1836_snd_controls,
|
||||
ARRAY_SIZE(ad1836_snd_controls));
|
||||
snd_soc_dapm_new_controls(codec, ad1836_dapm_widgets,
|
||||
ARRAY_SIZE(ad1836_dapm_widgets));
|
||||
snd_soc_dapm_add_routes(codec, audio_paths, ARRAY_SIZE(audio_paths));
|
||||
snd_soc_dapm_new_widgets(codec);
|
||||
|
||||
ret = snd_soc_init_card(socdev);
|
||||
if (ret < 0) {
|
||||
dev_err(codec->dev, "failed to register card: %d\n", ret);
|
||||
goto card_err;
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
card_err:
|
||||
snd_soc_free_pcms(socdev);
|
||||
snd_soc_dapm_free(socdev);
|
||||
pcm_err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* power down chip */
|
||||
static int ad1836_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
|
||||
snd_soc_free_pcms(socdev);
|
||||
snd_soc_dapm_free(socdev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct snd_soc_codec_device soc_codec_dev_ad1836 = {
|
||||
.probe = ad1836_probe,
|
||||
.remove = ad1836_remove,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(soc_codec_dev_ad1836);
|
||||
|
||||
static int __init ad1836_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = spi_register_driver(&ad1836_spi_driver);
|
||||
if (ret != 0) {
|
||||
printk(KERN_ERR "Failed to register ad1836 SPI driver: %d\n",
|
||||
ret);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
module_init(ad1836_init);
|
||||
|
||||
static void __exit ad1836_exit(void)
|
||||
{
|
||||
spi_unregister_driver(&ad1836_spi_driver);
|
||||
}
|
||||
module_exit(ad1836_exit);
|
||||
|
||||
MODULE_DESCRIPTION("ASoC ad1836 driver");
|
||||
MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* File: sound/soc/codecs/ad1836.h
|
||||
* Based on:
|
||||
* Author: Barry Song <Barry.Song@analog.com>
|
||||
*
|
||||
* Created: Aug 04, 2009
|
||||
* Description: definitions for AD1836 registers
|
||||
*
|
||||
* Modified:
|
||||
*
|
||||
* Bugs: Enter bugs at http://blackfin.uclinux.org/
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
#ifndef __AD1836_H__
|
||||
#define __AD1836_H__
|
||||
|
||||
#define AD1836_DAC_CTRL1 0
|
||||
#define AD1836_DAC_POWERDOWN 2
|
||||
#define AD1836_DAC_SERFMT_MASK 0xE0
|
||||
#define AD1836_DAC_SERFMT_PCK256 (0x4 << 5)
|
||||
#define AD1836_DAC_SERFMT_PCK128 (0x5 << 5)
|
||||
#define AD1836_DAC_WORD_LEN_MASK 0x18
|
||||
|
||||
#define AD1836_DAC_CTRL2 1
|
||||
#define AD1836_DACL1_MUTE 0
|
||||
#define AD1836_DACR1_MUTE 1
|
||||
#define AD1836_DACL2_MUTE 2
|
||||
#define AD1836_DACR2_MUTE 3
|
||||
#define AD1836_DACL3_MUTE 4
|
||||
#define AD1836_DACR3_MUTE 5
|
||||
|
||||
#define AD1836_DAC_L1_VOL 2
|
||||
#define AD1836_DAC_R1_VOL 3
|
||||
#define AD1836_DAC_L2_VOL 4
|
||||
#define AD1836_DAC_R2_VOL 5
|
||||
#define AD1836_DAC_L3_VOL 6
|
||||
#define AD1836_DAC_R3_VOL 7
|
||||
|
||||
#define AD1836_ADC_CTRL1 12
|
||||
#define AD1836_ADC_POWERDOWN 7
|
||||
#define AD1836_ADC_HIGHPASS_FILTER 8
|
||||
|
||||
#define AD1836_ADC_CTRL2 13
|
||||
#define AD1836_ADCL1_MUTE 0
|
||||
#define AD1836_ADCR1_MUTE 1
|
||||
#define AD1836_ADCL2_MUTE 2
|
||||
#define AD1836_ADCR2_MUTE 3
|
||||
#define AD1836_ADC_WORD_LEN_MASK 0x30
|
||||
#define AD1836_ADC_SERFMT_MASK (7 << 6)
|
||||
#define AD1836_ADC_SERFMT_PCK256 (0x4 << 6)
|
||||
#define AD1836_ADC_SERFMT_PCK128 (0x5 << 6)
|
||||
|
||||
#define AD1836_ADC_CTRL3 14
|
||||
|
||||
#define AD1836_NUM_REGS 16
|
||||
|
||||
extern struct snd_soc_dai ad1836_dai;
|
||||
extern struct snd_soc_codec_device soc_codec_dev_ad1836;
|
||||
#endif
|
|
@ -0,0 +1,682 @@
|
|||
/*
|
||||
* File: sound/soc/codecs/ad1938.c
|
||||
* Author: Barry Song <Barry.Song@analog.com>
|
||||
*
|
||||
* Created: June 04 2009
|
||||
* Description: Driver for AD1938 sound chip
|
||||
*
|
||||
* Modified:
|
||||
* Copyright 2009 Analog Devices Inc.
|
||||
*
|
||||
* Bugs: Enter bugs at http://blackfin.uclinux.org/
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see the file COPYING, or write
|
||||
* to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/version.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/device.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/initval.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/tlv.h>
|
||||
#include <sound/soc-dapm.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include "ad1938.h"
|
||||
|
||||
/* codec private data */
|
||||
struct ad1938_priv {
|
||||
struct snd_soc_codec codec;
|
||||
u8 reg_cache[AD1938_NUM_REGS];
|
||||
};
|
||||
|
||||
static struct snd_soc_codec *ad1938_codec;
|
||||
struct snd_soc_codec_device soc_codec_dev_ad1938;
|
||||
static int ad1938_register(struct ad1938_priv *ad1938);
|
||||
static void ad1938_unregister(struct ad1938_priv *ad1938);
|
||||
|
||||
/*
|
||||
* AD1938 volume/mute/de-emphasis etc. controls
|
||||
*/
|
||||
static const char *ad1938_deemp[] = {"None", "48kHz", "44.1kHz", "32kHz"};
|
||||
|
||||
static const struct soc_enum ad1938_deemp_enum =
|
||||
SOC_ENUM_SINGLE(AD1938_DAC_CTRL2, 1, 4, ad1938_deemp);
|
||||
|
||||
static const struct snd_kcontrol_new ad1938_snd_controls[] = {
|
||||
/* DAC volume control */
|
||||
SOC_DOUBLE_R("DAC1 Volume", AD1938_DAC_L1_VOL,
|
||||
AD1938_DAC_R1_VOL, 0, 0xFF, 1),
|
||||
SOC_DOUBLE_R("DAC2 Volume", AD1938_DAC_L2_VOL,
|
||||
AD1938_DAC_R2_VOL, 0, 0xFF, 1),
|
||||
SOC_DOUBLE_R("DAC3 Volume", AD1938_DAC_L3_VOL,
|
||||
AD1938_DAC_R3_VOL, 0, 0xFF, 1),
|
||||
SOC_DOUBLE_R("DAC4 Volume", AD1938_DAC_L4_VOL,
|
||||
AD1938_DAC_R4_VOL, 0, 0xFF, 1),
|
||||
|
||||
/* ADC switch control */
|
||||
SOC_DOUBLE("ADC1 Switch", AD1938_ADC_CTRL0, AD1938_ADCL1_MUTE,
|
||||
AD1938_ADCR1_MUTE, 1, 1),
|
||||
SOC_DOUBLE("ADC2 Switch", AD1938_ADC_CTRL0, AD1938_ADCL2_MUTE,
|
||||
AD1938_ADCR2_MUTE, 1, 1),
|
||||
|
||||
/* DAC switch control */
|
||||
SOC_DOUBLE("DAC1 Switch", AD1938_DAC_CHNL_MUTE, AD1938_DACL1_MUTE,
|
||||
AD1938_DACR1_MUTE, 1, 1),
|
||||
SOC_DOUBLE("DAC2 Switch", AD1938_DAC_CHNL_MUTE, AD1938_DACL2_MUTE,
|
||||
AD1938_DACR2_MUTE, 1, 1),
|
||||
SOC_DOUBLE("DAC3 Switch", AD1938_DAC_CHNL_MUTE, AD1938_DACL3_MUTE,
|
||||
AD1938_DACR3_MUTE, 1, 1),
|
||||
SOC_DOUBLE("DAC4 Switch", AD1938_DAC_CHNL_MUTE, AD1938_DACL4_MUTE,
|
||||
AD1938_DACR4_MUTE, 1, 1),
|
||||
|
||||
/* ADC high-pass filter */
|
||||
SOC_SINGLE("ADC High Pass Filter Switch", AD1938_ADC_CTRL0,
|
||||
AD1938_ADC_HIGHPASS_FILTER, 1, 0),
|
||||
|
||||
/* DAC de-emphasis */
|
||||
SOC_ENUM("Playback Deemphasis", ad1938_deemp_enum),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_widget ad1938_dapm_widgets[] = {
|
||||
SND_SOC_DAPM_DAC("DAC", "Playback", AD1938_DAC_CTRL0, 0, 1),
|
||||
SND_SOC_DAPM_ADC("ADC", "Capture", SND_SOC_NOPM, 0, 0),
|
||||
SND_SOC_DAPM_SUPPLY("ADC_PWR", AD1938_ADC_CTRL0, 0, 1, NULL, 0),
|
||||
SND_SOC_DAPM_OUTPUT("DAC1OUT"),
|
||||
SND_SOC_DAPM_OUTPUT("DAC2OUT"),
|
||||
SND_SOC_DAPM_OUTPUT("DAC3OUT"),
|
||||
SND_SOC_DAPM_OUTPUT("DAC4OUT"),
|
||||
SND_SOC_DAPM_INPUT("ADC1IN"),
|
||||
SND_SOC_DAPM_INPUT("ADC2IN"),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route audio_paths[] = {
|
||||
{ "DAC", NULL, "ADC_PWR" },
|
||||
{ "ADC", NULL, "ADC_PWR" },
|
||||
{ "DAC1OUT", "DAC1 Switch", "DAC" },
|
||||
{ "DAC2OUT", "DAC2 Switch", "DAC" },
|
||||
{ "DAC3OUT", "DAC3 Switch", "DAC" },
|
||||
{ "DAC4OUT", "DAC4 Switch", "DAC" },
|
||||
{ "ADC", "ADC1 Switch", "ADC1IN" },
|
||||
{ "ADC", "ADC2 Switch", "ADC2IN" },
|
||||
};
|
||||
|
||||
/*
|
||||
* DAI ops entries
|
||||
*/
|
||||
|
||||
static int ad1938_mute(struct snd_soc_dai *dai, int mute)
|
||||
{
|
||||
struct snd_soc_codec *codec = dai->codec;
|
||||
int reg;
|
||||
|
||||
reg = codec->read(codec, AD1938_DAC_CTRL2);
|
||||
reg = (mute > 0) ? reg | AD1938_DAC_MASTER_MUTE : reg &
|
||||
(~AD1938_DAC_MASTER_MUTE);
|
||||
codec->write(codec, AD1938_DAC_CTRL2, reg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int ad1938_pll_powerctrl(struct snd_soc_codec *codec, int cmd)
|
||||
{
|
||||
int reg = codec->read(codec, AD1938_PLL_CLK_CTRL0);
|
||||
reg = (cmd > 0) ? reg & (~AD1938_PLL_POWERDOWN) : reg |
|
||||
AD1938_PLL_POWERDOWN;
|
||||
codec->write(codec, AD1938_PLL_CLK_CTRL0, reg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ad1938_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
|
||||
unsigned int mask, int slots, int width)
|
||||
{
|
||||
struct snd_soc_codec *codec = dai->codec;
|
||||
int dac_reg = codec->read(codec, AD1938_DAC_CTRL1);
|
||||
int adc_reg = codec->read(codec, AD1938_ADC_CTRL2);
|
||||
|
||||
dac_reg &= ~AD1938_DAC_CHAN_MASK;
|
||||
adc_reg &= ~AD1938_ADC_CHAN_MASK;
|
||||
|
||||
switch (slots) {
|
||||
case 2:
|
||||
dac_reg |= AD1938_DAC_2_CHANNELS << AD1938_DAC_CHAN_SHFT;
|
||||
adc_reg |= AD1938_ADC_2_CHANNELS << AD1938_ADC_CHAN_SHFT;
|
||||
break;
|
||||
case 4:
|
||||
dac_reg |= AD1938_DAC_4_CHANNELS << AD1938_DAC_CHAN_SHFT;
|
||||
adc_reg |= AD1938_ADC_4_CHANNELS << AD1938_ADC_CHAN_SHFT;
|
||||
break;
|
||||
case 8:
|
||||
dac_reg |= AD1938_DAC_8_CHANNELS << AD1938_DAC_CHAN_SHFT;
|
||||
adc_reg |= AD1938_ADC_8_CHANNELS << AD1938_ADC_CHAN_SHFT;
|
||||
break;
|
||||
case 16:
|
||||
dac_reg |= AD1938_DAC_16_CHANNELS << AD1938_DAC_CHAN_SHFT;
|
||||
adc_reg |= AD1938_ADC_16_CHANNELS << AD1938_ADC_CHAN_SHFT;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
codec->write(codec, AD1938_DAC_CTRL1, dac_reg);
|
||||
codec->write(codec, AD1938_ADC_CTRL2, adc_reg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ad1938_set_dai_fmt(struct snd_soc_dai *codec_dai,
|
||||
unsigned int fmt)
|
||||
{
|
||||
struct snd_soc_codec *codec = codec_dai->codec;
|
||||
int adc_reg, dac_reg;
|
||||
|
||||
adc_reg = codec->read(codec, AD1938_ADC_CTRL2);
|
||||
dac_reg = codec->read(codec, AD1938_DAC_CTRL1);
|
||||
|
||||
/* At present, the driver only support AUX ADC mode(SND_SOC_DAIFMT_I2S
|
||||
* with TDM) and ADC&DAC TDM mode(SND_SOC_DAIFMT_DSP_A)
|
||||
*/
|
||||
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
|
||||
case SND_SOC_DAIFMT_I2S:
|
||||
adc_reg &= ~AD1938_ADC_SERFMT_MASK;
|
||||
adc_reg |= AD1938_ADC_SERFMT_TDM;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_DSP_A:
|
||||
adc_reg &= ~AD1938_ADC_SERFMT_MASK;
|
||||
adc_reg |= AD1938_ADC_SERFMT_AUX;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
|
||||
case SND_SOC_DAIFMT_NB_NF: /* normal bit clock + frame */
|
||||
adc_reg &= ~AD1938_ADC_LEFT_HIGH;
|
||||
adc_reg &= ~AD1938_ADC_BCLK_INV;
|
||||
dac_reg &= ~AD1938_DAC_LEFT_HIGH;
|
||||
dac_reg &= ~AD1938_DAC_BCLK_INV;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_NB_IF: /* normal bclk + invert frm */
|
||||
adc_reg |= AD1938_ADC_LEFT_HIGH;
|
||||
adc_reg &= ~AD1938_ADC_BCLK_INV;
|
||||
dac_reg |= AD1938_DAC_LEFT_HIGH;
|
||||
dac_reg &= ~AD1938_DAC_BCLK_INV;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_IB_NF: /* invert bclk + normal frm */
|
||||
adc_reg &= ~AD1938_ADC_LEFT_HIGH;
|
||||
adc_reg |= AD1938_ADC_BCLK_INV;
|
||||
dac_reg &= ~AD1938_DAC_LEFT_HIGH;
|
||||
dac_reg |= AD1938_DAC_BCLK_INV;
|
||||
break;
|
||||
|
||||
case SND_SOC_DAIFMT_IB_IF: /* invert bclk + frm */
|
||||
adc_reg |= AD1938_ADC_LEFT_HIGH;
|
||||
adc_reg |= AD1938_ADC_BCLK_INV;
|
||||
dac_reg |= AD1938_DAC_LEFT_HIGH;
|
||||
dac_reg |= AD1938_DAC_BCLK_INV;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
|
||||
case SND_SOC_DAIFMT_CBM_CFM: /* codec clk & frm master */
|
||||
adc_reg |= AD1938_ADC_LCR_MASTER;
|
||||
adc_reg |= AD1938_ADC_BCLK_MASTER;
|
||||
dac_reg |= AD1938_DAC_LCR_MASTER;
|
||||
dac_reg |= AD1938_DAC_BCLK_MASTER;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_CBS_CFM: /* codec clk slave & frm master */
|
||||
adc_reg |= AD1938_ADC_LCR_MASTER;
|
||||
adc_reg &= ~AD1938_ADC_BCLK_MASTER;
|
||||
dac_reg |= AD1938_DAC_LCR_MASTER;
|
||||
dac_reg &= ~AD1938_DAC_BCLK_MASTER;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_CBM_CFS: /* codec clk master & frame slave */
|
||||
adc_reg &= ~AD1938_ADC_LCR_MASTER;
|
||||
adc_reg |= AD1938_ADC_BCLK_MASTER;
|
||||
dac_reg &= ~AD1938_DAC_LCR_MASTER;
|
||||
dac_reg |= AD1938_DAC_BCLK_MASTER;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_CBS_CFS: /* codec clk & frm slave */
|
||||
adc_reg &= ~AD1938_ADC_LCR_MASTER;
|
||||
adc_reg &= ~AD1938_ADC_BCLK_MASTER;
|
||||
dac_reg &= ~AD1938_DAC_LCR_MASTER;
|
||||
dac_reg &= ~AD1938_DAC_BCLK_MASTER;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
codec->write(codec, AD1938_ADC_CTRL2, adc_reg);
|
||||
codec->write(codec, AD1938_DAC_CTRL1, dac_reg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ad1938_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
int word_len = 0, reg = 0;
|
||||
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_device *socdev = rtd->socdev;
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
|
||||
/* bit size */
|
||||
switch (params_format(params)) {
|
||||
case SNDRV_PCM_FORMAT_S16_LE:
|
||||
word_len = 3;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S20_3LE:
|
||||
word_len = 1;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S24_LE:
|
||||
case SNDRV_PCM_FORMAT_S32_LE:
|
||||
word_len = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
reg = codec->read(codec, AD1938_DAC_CTRL2);
|
||||
reg = (reg & (~AD1938_DAC_WORD_LEN_MASK)) | word_len;
|
||||
codec->write(codec, AD1938_DAC_CTRL2, reg);
|
||||
|
||||
reg = codec->read(codec, AD1938_ADC_CTRL1);
|
||||
reg = (reg & (~AD1938_ADC_WORD_LEN_MASK)) | word_len;
|
||||
codec->write(codec, AD1938_ADC_CTRL1, reg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ad1938_set_bias_level(struct snd_soc_codec *codec,
|
||||
enum snd_soc_bias_level level)
|
||||
{
|
||||
switch (level) {
|
||||
case SND_SOC_BIAS_ON:
|
||||
ad1938_pll_powerctrl(codec, 1);
|
||||
break;
|
||||
case SND_SOC_BIAS_PREPARE:
|
||||
break;
|
||||
case SND_SOC_BIAS_STANDBY:
|
||||
case SND_SOC_BIAS_OFF:
|
||||
ad1938_pll_powerctrl(codec, 0);
|
||||
break;
|
||||
}
|
||||
codec->bias_level = level;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* interface to read/write ad1938 register
|
||||
*/
|
||||
|
||||
#define AD1938_SPI_ADDR 0x4
|
||||
#define AD1938_SPI_READ 0x1
|
||||
#define AD1938_SPI_BUFLEN 3
|
||||
|
||||
/*
|
||||
* write to the ad1938 register space
|
||||
*/
|
||||
|
||||
static int ad1938_write_reg(struct snd_soc_codec *codec, unsigned int reg,
|
||||
unsigned int value)
|
||||
{
|
||||
u8 *reg_cache = codec->reg_cache;
|
||||
int ret = 0;
|
||||
|
||||
if (value != reg_cache[reg]) {
|
||||
uint8_t buf[AD1938_SPI_BUFLEN];
|
||||
struct spi_transfer t = {
|
||||
.tx_buf = buf,
|
||||
.len = AD1938_SPI_BUFLEN,
|
||||
};
|
||||
struct spi_message m;
|
||||
|
||||
buf[0] = AD1938_SPI_ADDR << 1;
|
||||
buf[1] = reg;
|
||||
buf[2] = value;
|
||||
spi_message_init(&m);
|
||||
spi_message_add_tail(&t, &m);
|
||||
ret = spi_sync(codec->control_data, &m);
|
||||
if (ret == 0)
|
||||
reg_cache[reg] = value;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* read from the ad1938 register space cache
|
||||
*/
|
||||
|
||||
static unsigned int ad1938_read_reg_cache(struct snd_soc_codec *codec,
|
||||
unsigned int reg)
|
||||
{
|
||||
u8 *reg_cache = codec->reg_cache;
|
||||
|
||||
if (reg >= codec->reg_cache_size)
|
||||
return -EINVAL;
|
||||
|
||||
return reg_cache[reg];
|
||||
}
|
||||
|
||||
/*
|
||||
* read from the ad1938 register space
|
||||
*/
|
||||
|
||||
static unsigned int ad1938_read_reg(struct snd_soc_codec *codec,
|
||||
unsigned int reg)
|
||||
{
|
||||
char w_buf[AD1938_SPI_BUFLEN];
|
||||
char r_buf[AD1938_SPI_BUFLEN];
|
||||
int ret;
|
||||
|
||||
struct spi_transfer t = {
|
||||
.tx_buf = w_buf,
|
||||
.rx_buf = r_buf,
|
||||
.len = AD1938_SPI_BUFLEN,
|
||||
};
|
||||
struct spi_message m;
|
||||
|
||||
w_buf[0] = (AD1938_SPI_ADDR << 1) | AD1938_SPI_READ;
|
||||
w_buf[1] = reg;
|
||||
w_buf[2] = 0;
|
||||
|
||||
spi_message_init(&m);
|
||||
spi_message_add_tail(&t, &m);
|
||||
ret = spi_sync(codec->control_data, &m);
|
||||
if (ret == 0)
|
||||
return r_buf[2];
|
||||
else
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
static int ad1938_fill_cache(struct snd_soc_codec *codec)
|
||||
{
|
||||
int i;
|
||||
u8 *reg_cache = codec->reg_cache;
|
||||
struct spi_device *spi = codec->control_data;
|
||||
|
||||
for (i = 0; i < codec->reg_cache_size; i++) {
|
||||
int ret = ad1938_read_reg(codec, i);
|
||||
if (ret == -EIO) {
|
||||
dev_err(&spi->dev, "AD1938 SPI read failure\n");
|
||||
return ret;
|
||||
}
|
||||
reg_cache[i] = ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __devinit ad1938_spi_probe(struct spi_device *spi)
|
||||
{
|
||||
struct snd_soc_codec *codec;
|
||||
struct ad1938_priv *ad1938;
|
||||
|
||||
ad1938 = kzalloc(sizeof(struct ad1938_priv), GFP_KERNEL);
|
||||
if (ad1938 == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
codec = &ad1938->codec;
|
||||
codec->control_data = spi;
|
||||
codec->dev = &spi->dev;
|
||||
|
||||
dev_set_drvdata(&spi->dev, ad1938);
|
||||
|
||||
return ad1938_register(ad1938);
|
||||
}
|
||||
|
||||
static int __devexit ad1938_spi_remove(struct spi_device *spi)
|
||||
{
|
||||
struct ad1938_priv *ad1938 = dev_get_drvdata(&spi->dev);
|
||||
|
||||
ad1938_unregister(ad1938);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct spi_driver ad1938_spi_driver = {
|
||||
.driver = {
|
||||
.name = "ad1938",
|
||||
.bus = &spi_bus_type,
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = ad1938_spi_probe,
|
||||
.remove = __devexit_p(ad1938_spi_remove),
|
||||
};
|
||||
|
||||
static struct snd_soc_dai_ops ad1938_dai_ops = {
|
||||
.hw_params = ad1938_hw_params,
|
||||
.digital_mute = ad1938_mute,
|
||||
.set_tdm_slot = ad1938_set_tdm_slot,
|
||||
.set_fmt = ad1938_set_dai_fmt,
|
||||
};
|
||||
|
||||
/* codec DAI instance */
|
||||
struct snd_soc_dai ad1938_dai = {
|
||||
.name = "AD1938",
|
||||
.playback = {
|
||||
.stream_name = "Playback",
|
||||
.channels_min = 2,
|
||||
.channels_max = 8,
|
||||
.rates = SNDRV_PCM_RATE_48000,
|
||||
.formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE |
|
||||
SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE,
|
||||
},
|
||||
.capture = {
|
||||
.stream_name = "Capture",
|
||||
.channels_min = 2,
|
||||
.channels_max = 4,
|
||||
.rates = SNDRV_PCM_RATE_48000,
|
||||
.formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE |
|
||||
SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE,
|
||||
},
|
||||
.ops = &ad1938_dai_ops,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(ad1938_dai);
|
||||
|
||||
static int ad1938_register(struct ad1938_priv *ad1938)
|
||||
{
|
||||
int ret;
|
||||
struct snd_soc_codec *codec = &ad1938->codec;
|
||||
|
||||
if (ad1938_codec) {
|
||||
dev_err(codec->dev, "Another ad1938 is registered\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_init(&codec->mutex);
|
||||
INIT_LIST_HEAD(&codec->dapm_widgets);
|
||||
INIT_LIST_HEAD(&codec->dapm_paths);
|
||||
codec->private_data = ad1938;
|
||||
codec->reg_cache = ad1938->reg_cache;
|
||||
codec->reg_cache_size = AD1938_NUM_REGS;
|
||||
codec->name = "AD1938";
|
||||
codec->owner = THIS_MODULE;
|
||||
codec->dai = &ad1938_dai;
|
||||
codec->num_dai = 1;
|
||||
codec->write = ad1938_write_reg;
|
||||
codec->read = ad1938_read_reg_cache;
|
||||
INIT_LIST_HEAD(&codec->dapm_widgets);
|
||||
INIT_LIST_HEAD(&codec->dapm_paths);
|
||||
|
||||
ad1938_dai.dev = codec->dev;
|
||||
ad1938_codec = codec;
|
||||
|
||||
/* default setting for ad1938 */
|
||||
|
||||
/* unmute dac channels */
|
||||
codec->write(codec, AD1938_DAC_CHNL_MUTE, 0x0);
|
||||
/* de-emphasis: 48kHz, powedown dac */
|
||||
codec->write(codec, AD1938_DAC_CTRL2, 0x1A);
|
||||
/* powerdown dac, dac in tdm mode */
|
||||
codec->write(codec, AD1938_DAC_CTRL0, 0x41);
|
||||
/* high-pass filter enable */
|
||||
codec->write(codec, AD1938_ADC_CTRL0, 0x3);
|
||||
/* sata delay=1, adc aux mode */
|
||||
codec->write(codec, AD1938_ADC_CTRL1, 0x43);
|
||||
/* pll input: mclki/xi */
|
||||
codec->write(codec, AD1938_PLL_CLK_CTRL0, 0x9D);
|
||||
codec->write(codec, AD1938_PLL_CLK_CTRL1, 0x04);
|
||||
|
||||
ad1938_fill_cache(codec);
|
||||
|
||||
ret = snd_soc_register_codec(codec);
|
||||
if (ret != 0) {
|
||||
dev_err(codec->dev, "Failed to register codec: %d\n", ret);
|
||||
kfree(ad1938);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = snd_soc_register_dai(&ad1938_dai);
|
||||
if (ret != 0) {
|
||||
dev_err(codec->dev, "Failed to register DAI: %d\n", ret);
|
||||
snd_soc_unregister_codec(codec);
|
||||
kfree(ad1938);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ad1938_unregister(struct ad1938_priv *ad1938)
|
||||
{
|
||||
ad1938_set_bias_level(&ad1938->codec, SND_SOC_BIAS_OFF);
|
||||
snd_soc_unregister_dai(&ad1938_dai);
|
||||
snd_soc_unregister_codec(&ad1938->codec);
|
||||
kfree(ad1938);
|
||||
ad1938_codec = NULL;
|
||||
}
|
||||
|
||||
static int ad1938_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
struct snd_soc_codec *codec;
|
||||
int ret = 0;
|
||||
|
||||
if (ad1938_codec == NULL) {
|
||||
dev_err(&pdev->dev, "Codec device not registered\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
socdev->card->codec = ad1938_codec;
|
||||
codec = ad1938_codec;
|
||||
|
||||
/* register pcms */
|
||||
ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
|
||||
if (ret < 0) {
|
||||
dev_err(codec->dev, "failed to create pcms: %d\n", ret);
|
||||
goto pcm_err;
|
||||
}
|
||||
|
||||
snd_soc_add_controls(codec, ad1938_snd_controls,
|
||||
ARRAY_SIZE(ad1938_snd_controls));
|
||||
snd_soc_dapm_new_controls(codec, ad1938_dapm_widgets,
|
||||
ARRAY_SIZE(ad1938_dapm_widgets));
|
||||
snd_soc_dapm_add_routes(codec, audio_paths, ARRAY_SIZE(audio_paths));
|
||||
snd_soc_dapm_new_widgets(codec);
|
||||
|
||||
ad1938_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
|
||||
|
||||
ret = snd_soc_init_card(socdev);
|
||||
if (ret < 0) {
|
||||
dev_err(codec->dev, "failed to register card: %d\n", ret);
|
||||
goto card_err;
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
card_err:
|
||||
snd_soc_free_pcms(socdev);
|
||||
snd_soc_dapm_free(socdev);
|
||||
pcm_err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* power down chip */
|
||||
static int ad1938_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
|
||||
snd_soc_free_pcms(socdev);
|
||||
snd_soc_dapm_free(socdev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int ad1938_suspend(struct platform_device *pdev,
|
||||
pm_message_t state)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
|
||||
ad1938_set_bias_level(codec, SND_SOC_BIAS_OFF);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ad1938_resume(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
|
||||
if (codec->suspend_bias_level == SND_SOC_BIAS_ON)
|
||||
ad1938_set_bias_level(codec, SND_SOC_BIAS_ON);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
#define ad1938_suspend NULL
|
||||
#define ad1938_resume NULL
|
||||
#endif
|
||||
|
||||
struct snd_soc_codec_device soc_codec_dev_ad1938 = {
|
||||
.probe = ad1938_probe,
|
||||
.remove = ad1938_remove,
|
||||
.suspend = ad1938_suspend,
|
||||
.resume = ad1938_resume,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(soc_codec_dev_ad1938);
|
||||
|
||||
static int __init ad1938_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = spi_register_driver(&ad1938_spi_driver);
|
||||
if (ret != 0) {
|
||||
printk(KERN_ERR "Failed to register ad1938 SPI driver: %d\n",
|
||||
ret);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
module_init(ad1938_init);
|
||||
|
||||
static void __exit ad1938_exit(void)
|
||||
{
|
||||
spi_unregister_driver(&ad1938_spi_driver);
|
||||
}
|
||||
module_exit(ad1938_exit);
|
||||
|
||||
MODULE_DESCRIPTION("ASoC ad1938 driver");
|
||||
MODULE_AUTHOR("Barry Song ");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* File: sound/soc/codecs/ad1836.h
|
||||
* Based on:
|
||||
* Author: Barry Song <Barry.Song@analog.com>
|
||||
*
|
||||
* Created: May 25, 2009
|
||||
* Description: definitions for AD1938 registers
|
||||
*
|
||||
* Modified:
|
||||
*
|
||||
* Bugs: Enter bugs at http://blackfin.uclinux.org/
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see the file COPYING, or write
|
||||
* to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef __AD1938_H__
|
||||
#define __AD1938_H__
|
||||
|
||||
#define AD1938_PLL_CLK_CTRL0 0
|
||||
#define AD1938_PLL_POWERDOWN 0x01
|
||||
#define AD1938_PLL_CLK_CTRL1 1
|
||||
#define AD1938_DAC_CTRL0 2
|
||||
#define AD1938_DAC_POWERDOWN 0x01
|
||||
#define AD1938_DAC_SERFMT_MASK 0xC0
|
||||
#define AD1938_DAC_SERFMT_STEREO (0 << 6)
|
||||
#define AD1938_DAC_SERFMT_TDM (1 << 6)
|
||||
#define AD1938_DAC_CTRL1 3
|
||||
#define AD1938_DAC_2_CHANNELS 0
|
||||
#define AD1938_DAC_4_CHANNELS 1
|
||||
#define AD1938_DAC_8_CHANNELS 2
|
||||
#define AD1938_DAC_16_CHANNELS 3
|
||||
#define AD1938_DAC_CHAN_SHFT 1
|
||||
#define AD1938_DAC_CHAN_MASK (3 << AD1938_DAC_CHAN_SHFT)
|
||||
#define AD1938_DAC_LCR_MASTER (1 << 4)
|
||||
#define AD1938_DAC_BCLK_MASTER (1 << 5)
|
||||
#define AD1938_DAC_LEFT_HIGH (1 << 3)
|
||||
#define AD1938_DAC_BCLK_INV (1 << 7)
|
||||
#define AD1938_DAC_CTRL2 4
|
||||
#define AD1938_DAC_WORD_LEN_MASK 0xC
|
||||
#define AD1938_DAC_MASTER_MUTE 1
|
||||
#define AD1938_DAC_CHNL_MUTE 5
|
||||
#define AD1938_DACL1_MUTE 0
|
||||
#define AD1938_DACR1_MUTE 1
|
||||
#define AD1938_DACL2_MUTE 2
|
||||
#define AD1938_DACR2_MUTE 3
|
||||
#define AD1938_DACL3_MUTE 4
|
||||
#define AD1938_DACR3_MUTE 5
|
||||
#define AD1938_DACL4_MUTE 6
|
||||
#define AD1938_DACR4_MUTE 7
|
||||
#define AD1938_DAC_L1_VOL 6
|
||||
#define AD1938_DAC_R1_VOL 7
|
||||
#define AD1938_DAC_L2_VOL 8
|
||||
#define AD1938_DAC_R2_VOL 9
|
||||
#define AD1938_DAC_L3_VOL 10
|
||||
#define AD1938_DAC_R3_VOL 11
|
||||
#define AD1938_DAC_L4_VOL 12
|
||||
#define AD1938_DAC_R4_VOL 13
|
||||
#define AD1938_ADC_CTRL0 14
|
||||
#define AD1938_ADC_POWERDOWN 0x01
|
||||
#define AD1938_ADC_HIGHPASS_FILTER 1
|
||||
#define AD1938_ADCL1_MUTE 2
|
||||
#define AD1938_ADCR1_MUTE 3
|
||||
#define AD1938_ADCL2_MUTE 4
|
||||
#define AD1938_ADCR2_MUTE 5
|
||||
#define AD1938_ADC_CTRL1 15
|
||||
#define AD1938_ADC_SERFMT_MASK 0x60
|
||||
#define AD1938_ADC_SERFMT_STEREO (0 << 5)
|
||||
#define AD1938_ADC_SERFMT_TDM (1 << 2)
|
||||
#define AD1938_ADC_SERFMT_AUX (2 << 5)
|
||||
#define AD1938_ADC_WORD_LEN_MASK 0x3
|
||||
#define AD1938_ADC_CTRL2 16
|
||||
#define AD1938_ADC_2_CHANNELS 0
|
||||
#define AD1938_ADC_4_CHANNELS 1
|
||||
#define AD1938_ADC_8_CHANNELS 2
|
||||
#define AD1938_ADC_16_CHANNELS 3
|
||||
#define AD1938_ADC_CHAN_SHFT 4
|
||||
#define AD1938_ADC_CHAN_MASK (3 << AD1938_ADC_CHAN_SHFT)
|
||||
#define AD1938_ADC_LCR_MASTER (1 << 3)
|
||||
#define AD1938_ADC_BCLK_MASTER (1 << 6)
|
||||
#define AD1938_ADC_LEFT_HIGH (1 << 2)
|
||||
#define AD1938_ADC_BCLK_INV (1 << 1)
|
||||
|
||||
#define AD1938_NUM_REGS 17
|
||||
|
||||
extern struct snd_soc_dai ad1938_dai;
|
||||
extern struct snd_soc_codec_device soc_codec_dev_ad1938;
|
||||
#endif
|
|
@ -59,21 +59,6 @@ static inline unsigned int ak4535_read_reg_cache(struct snd_soc_codec *codec,
|
|||
return cache[reg];
|
||||
}
|
||||
|
||||
static inline unsigned int ak4535_read(struct snd_soc_codec *codec,
|
||||
unsigned int reg)
|
||||
{
|
||||
u8 data;
|
||||
data = reg;
|
||||
|
||||
if (codec->hw_write(codec->control_data, &data, 1) != 1)
|
||||
return -EIO;
|
||||
|
||||
if (codec->hw_read(codec->control_data, &data, 1) != 1)
|
||||
return -EIO;
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
/*
|
||||
* write ak4535 register cache
|
||||
*/
|
||||
|
@ -635,7 +620,6 @@ static int ak4535_probe(struct platform_device *pdev)
|
|||
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
|
||||
if (setup->i2c_address) {
|
||||
codec->hw_write = (hw_write_t)i2c_master_send;
|
||||
codec->hw_read = (hw_read_t)i2c_master_recv;
|
||||
ret = ak4535_add_i2c_device(pdev, setup);
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,502 @@
|
|||
/*
|
||||
* ak4642.c -- AK4642/AK4643 ALSA Soc Audio driver
|
||||
*
|
||||
* Copyright (C) 2009 Renesas Solutions Corp.
|
||||
* Kuninori Morimoto <morimoto.kuninori@renesas.com>
|
||||
*
|
||||
* Based on wm8731.c by Richard Purdie
|
||||
* Based on ak4535.c by Richard Purdie
|
||||
* Based on wm8753.c by Liam Girdwood
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* ** CAUTION **
|
||||
*
|
||||
* This is very simple driver.
|
||||
* It can use headphone output / stereo input only
|
||||
*
|
||||
* AK4642 is not tested.
|
||||
* AK4643 is tested.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/soc-dapm.h>
|
||||
#include <sound/initval.h>
|
||||
|
||||
#include "ak4642.h"
|
||||
|
||||
#define AK4642_VERSION "0.0.1"
|
||||
|
||||
#define PW_MGMT1 0x00
|
||||
#define PW_MGMT2 0x01
|
||||
#define SG_SL1 0x02
|
||||
#define SG_SL2 0x03
|
||||
#define MD_CTL1 0x04
|
||||
#define MD_CTL2 0x05
|
||||
#define TIMER 0x06
|
||||
#define ALC_CTL1 0x07
|
||||
#define ALC_CTL2 0x08
|
||||
#define L_IVC 0x09
|
||||
#define L_DVC 0x0a
|
||||
#define ALC_CTL3 0x0b
|
||||
#define R_IVC 0x0c
|
||||
#define R_DVC 0x0d
|
||||
#define MD_CTL3 0x0e
|
||||
#define MD_CTL4 0x0f
|
||||
#define PW_MGMT3 0x10
|
||||
#define DF_S 0x11
|
||||
#define FIL3_0 0x12
|
||||
#define FIL3_1 0x13
|
||||
#define FIL3_2 0x14
|
||||
#define FIL3_3 0x15
|
||||
#define EQ_0 0x16
|
||||
#define EQ_1 0x17
|
||||
#define EQ_2 0x18
|
||||
#define EQ_3 0x19
|
||||
#define EQ_4 0x1a
|
||||
#define EQ_5 0x1b
|
||||
#define FIL1_0 0x1c
|
||||
#define FIL1_1 0x1d
|
||||
#define FIL1_2 0x1e
|
||||
#define FIL1_3 0x1f
|
||||
#define PW_MGMT4 0x20
|
||||
#define MD_CTL5 0x21
|
||||
#define LO_MS 0x22
|
||||
#define HP_MS 0x23
|
||||
#define SPK_MS 0x24
|
||||
|
||||
#define AK4642_CACHEREGNUM 0x25
|
||||
|
||||
struct snd_soc_codec_device soc_codec_dev_ak4642;
|
||||
|
||||
/* codec private data */
|
||||
struct ak4642_priv {
|
||||
struct snd_soc_codec codec;
|
||||
unsigned int sysclk;
|
||||
};
|
||||
|
||||
static struct snd_soc_codec *ak4642_codec;
|
||||
|
||||
/*
|
||||
* ak4642 register cache
|
||||
*/
|
||||
static const u16 ak4642_reg[AK4642_CACHEREGNUM] = {
|
||||
0x0000, 0x0000, 0x0001, 0x0000,
|
||||
0x0002, 0x0000, 0x0000, 0x0000,
|
||||
0x00e1, 0x00e1, 0x0018, 0x0000,
|
||||
0x00e1, 0x0018, 0x0011, 0x0008,
|
||||
0x0000, 0x0000, 0x0000, 0x0000,
|
||||
0x0000, 0x0000, 0x0000, 0x0000,
|
||||
0x0000, 0x0000, 0x0000, 0x0000,
|
||||
0x0000, 0x0000, 0x0000, 0x0000,
|
||||
0x0000, 0x0000, 0x0000, 0x0000,
|
||||
0x0000,
|
||||
};
|
||||
|
||||
/*
|
||||
* read ak4642 register cache
|
||||
*/
|
||||
static inline unsigned int ak4642_read_reg_cache(struct snd_soc_codec *codec,
|
||||
unsigned int reg)
|
||||
{
|
||||
u16 *cache = codec->reg_cache;
|
||||
if (reg >= AK4642_CACHEREGNUM)
|
||||
return -1;
|
||||
return cache[reg];
|
||||
}
|
||||
|
||||
/*
|
||||
* write ak4642 register cache
|
||||
*/
|
||||
static inline void ak4642_write_reg_cache(struct snd_soc_codec *codec,
|
||||
u16 reg, unsigned int value)
|
||||
{
|
||||
u16 *cache = codec->reg_cache;
|
||||
if (reg >= AK4642_CACHEREGNUM)
|
||||
return;
|
||||
|
||||
cache[reg] = value;
|
||||
}
|
||||
|
||||
/*
|
||||
* write to the AK4642 register space
|
||||
*/
|
||||
static int ak4642_write(struct snd_soc_codec *codec, unsigned int reg,
|
||||
unsigned int value)
|
||||
{
|
||||
u8 data[2];
|
||||
|
||||
/* data is
|
||||
* D15..D8 AK4642 register offset
|
||||
* D7...D0 register data
|
||||
*/
|
||||
data[0] = reg & 0xff;
|
||||
data[1] = value & 0xff;
|
||||
|
||||
if (codec->hw_write(codec->control_data, data, 2) == 2) {
|
||||
ak4642_write_reg_cache(codec, reg, value);
|
||||
return 0;
|
||||
} else
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
static int ak4642_sync(struct snd_soc_codec *codec)
|
||||
{
|
||||
u16 *cache = codec->reg_cache;
|
||||
int i, r = 0;
|
||||
|
||||
for (i = 0; i < AK4642_CACHEREGNUM; i++)
|
||||
r |= ak4642_write(codec, i, cache[i]);
|
||||
|
||||
return r;
|
||||
};
|
||||
|
||||
static int ak4642_dai_startup(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
int is_play = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
|
||||
struct snd_soc_codec *codec = dai->codec;
|
||||
|
||||
if (is_play) {
|
||||
/*
|
||||
* start headphone output
|
||||
*
|
||||
* PLL, Master Mode
|
||||
* Audio I/F Format :MSB justified (ADC & DAC)
|
||||
* Sampling Frequency: 44.1kHz
|
||||
* Digital Volume: −8dB
|
||||
* Bass Boost Level : Middle
|
||||
*
|
||||
* This operation came from example code of
|
||||
* "ASAHI KASEI AK4642" (japanese) manual p97.
|
||||
*
|
||||
* Example code use 0x39, 0x79 value for 0x01 address,
|
||||
* But we need MCKO (0x02) bit now
|
||||
*/
|
||||
ak4642_write(codec, 0x05, 0x27);
|
||||
ak4642_write(codec, 0x0f, 0x09);
|
||||
ak4642_write(codec, 0x0e, 0x19);
|
||||
ak4642_write(codec, 0x09, 0x91);
|
||||
ak4642_write(codec, 0x0c, 0x91);
|
||||
ak4642_write(codec, 0x0a, 0x28);
|
||||
ak4642_write(codec, 0x0d, 0x28);
|
||||
ak4642_write(codec, 0x00, 0x64);
|
||||
ak4642_write(codec, 0x01, 0x3b); /* + MCKO bit */
|
||||
ak4642_write(codec, 0x01, 0x7b); /* + MCKO bit */
|
||||
} else {
|
||||
/*
|
||||
* start stereo input
|
||||
*
|
||||
* PLL Master Mode
|
||||
* Audio I/F Format:MSB justified (ADC & DAC)
|
||||
* Sampling Frequency:44.1kHz
|
||||
* Pre MIC AMP:+20dB
|
||||
* MIC Power On
|
||||
* ALC setting:Refer to Table 35
|
||||
* ALC bit=“1”
|
||||
*
|
||||
* This operation came from example code of
|
||||
* "ASAHI KASEI AK4642" (japanese) manual p94.
|
||||
*/
|
||||
ak4642_write(codec, 0x05, 0x27);
|
||||
ak4642_write(codec, 0x02, 0x05);
|
||||
ak4642_write(codec, 0x06, 0x3c);
|
||||
ak4642_write(codec, 0x08, 0xe1);
|
||||
ak4642_write(codec, 0x0b, 0x00);
|
||||
ak4642_write(codec, 0x07, 0x21);
|
||||
ak4642_write(codec, 0x00, 0x41);
|
||||
ak4642_write(codec, 0x10, 0x01);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ak4642_dai_shutdown(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
int is_play = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
|
||||
struct snd_soc_codec *codec = dai->codec;
|
||||
|
||||
if (is_play) {
|
||||
/* stop headphone output */
|
||||
ak4642_write(codec, 0x01, 0x3b);
|
||||
ak4642_write(codec, 0x01, 0x0b);
|
||||
ak4642_write(codec, 0x00, 0x40);
|
||||
ak4642_write(codec, 0x0e, 0x11);
|
||||
ak4642_write(codec, 0x0f, 0x08);
|
||||
} else {
|
||||
/* stop stereo input */
|
||||
ak4642_write(codec, 0x00, 0x40);
|
||||
ak4642_write(codec, 0x10, 0x00);
|
||||
ak4642_write(codec, 0x07, 0x01);
|
||||
}
|
||||
}
|
||||
|
||||
static int ak4642_dai_set_sysclk(struct snd_soc_dai *codec_dai,
|
||||
int clk_id, unsigned int freq, int dir)
|
||||
{
|
||||
struct snd_soc_codec *codec = codec_dai->codec;
|
||||
struct ak4642_priv *ak4642 = codec->private_data;
|
||||
|
||||
ak4642->sysclk = freq;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_dai_ops ak4642_dai_ops = {
|
||||
.startup = ak4642_dai_startup,
|
||||
.shutdown = ak4642_dai_shutdown,
|
||||
.set_sysclk = ak4642_dai_set_sysclk,
|
||||
};
|
||||
|
||||
struct snd_soc_dai ak4642_dai = {
|
||||
.name = "AK4642",
|
||||
.playback = {
|
||||
.stream_name = "Playback",
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
.rates = SNDRV_PCM_RATE_8000_48000,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE },
|
||||
.capture = {
|
||||
.stream_name = "Capture",
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
.rates = SNDRV_PCM_RATE_8000_48000,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE },
|
||||
.ops = &ak4642_dai_ops,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(ak4642_dai);
|
||||
|
||||
static int ak4642_resume(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
|
||||
ak4642_sync(codec);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* initialise the AK4642 driver
|
||||
* register the mixer and dsp interfaces with the kernel
|
||||
*/
|
||||
static int ak4642_init(struct ak4642_priv *ak4642)
|
||||
{
|
||||
struct snd_soc_codec *codec = &ak4642->codec;
|
||||
int ret = 0;
|
||||
|
||||
if (ak4642_codec) {
|
||||
dev_err(codec->dev, "Another ak4642 is registered\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_init(&codec->mutex);
|
||||
INIT_LIST_HEAD(&codec->dapm_widgets);
|
||||
INIT_LIST_HEAD(&codec->dapm_paths);
|
||||
|
||||
codec->private_data = ak4642;
|
||||
codec->name = "AK4642";
|
||||
codec->owner = THIS_MODULE;
|
||||
codec->read = ak4642_read_reg_cache;
|
||||
codec->write = ak4642_write;
|
||||
codec->dai = &ak4642_dai;
|
||||
codec->num_dai = 1;
|
||||
codec->hw_write = (hw_write_t)i2c_master_send;
|
||||
codec->reg_cache_size = ARRAY_SIZE(ak4642_reg);
|
||||
codec->reg_cache = kmemdup(ak4642_reg,
|
||||
sizeof(ak4642_reg), GFP_KERNEL);
|
||||
|
||||
if (!codec->reg_cache)
|
||||
return -ENOMEM;
|
||||
|
||||
ak4642_dai.dev = codec->dev;
|
||||
ak4642_codec = codec;
|
||||
|
||||
ret = snd_soc_register_codec(codec);
|
||||
if (ret) {
|
||||
dev_err(codec->dev, "Failed to register codec: %d\n", ret);
|
||||
goto reg_cache_err;
|
||||
}
|
||||
|
||||
ret = snd_soc_register_dai(&ak4642_dai);
|
||||
if (ret) {
|
||||
dev_err(codec->dev, "Failed to register DAI: %d\n", ret);
|
||||
snd_soc_unregister_codec(codec);
|
||||
goto reg_cache_err;
|
||||
}
|
||||
|
||||
/*
|
||||
* clock setting
|
||||
*
|
||||
* Audio I/F Format: MSB justified (ADC & DAC)
|
||||
* BICK frequency at Master Mode: 64fs
|
||||
* Input Master Clock Select at PLL Mode: 11.2896MHz
|
||||
* MCKO: Enable
|
||||
* Sampling Frequency: 44.1kHz
|
||||
*
|
||||
* This operation came from example code of
|
||||
* "ASAHI KASEI AK4642" (japanese) manual p89.
|
||||
*
|
||||
* please fix-me
|
||||
*/
|
||||
ak4642_write(codec, 0x01, 0x08);
|
||||
ak4642_write(codec, 0x04, 0x4a);
|
||||
ak4642_write(codec, 0x05, 0x27);
|
||||
ak4642_write(codec, 0x00, 0x40);
|
||||
ak4642_write(codec, 0x01, 0x0b);
|
||||
|
||||
return ret;
|
||||
|
||||
reg_cache_err:
|
||||
kfree(codec->reg_cache);
|
||||
codec->reg_cache = NULL;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
|
||||
static int ak4642_i2c_probe(struct i2c_client *i2c,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct ak4642_priv *ak4642;
|
||||
struct snd_soc_codec *codec;
|
||||
int ret;
|
||||
|
||||
ak4642 = kzalloc(sizeof(struct ak4642_priv), GFP_KERNEL);
|
||||
if (!ak4642)
|
||||
return -ENOMEM;
|
||||
|
||||
codec = &ak4642->codec;
|
||||
codec->dev = &i2c->dev;
|
||||
|
||||
i2c_set_clientdata(i2c, ak4642);
|
||||
codec->control_data = i2c;
|
||||
|
||||
ret = ak4642_init(ak4642);
|
||||
if (ret < 0)
|
||||
printk(KERN_ERR "failed to initialise AK4642\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ak4642_i2c_remove(struct i2c_client *client)
|
||||
{
|
||||
struct ak4642_priv *ak4642 = i2c_get_clientdata(client);
|
||||
|
||||
snd_soc_unregister_dai(&ak4642_dai);
|
||||
snd_soc_unregister_codec(&ak4642->codec);
|
||||
kfree(ak4642->codec.reg_cache);
|
||||
kfree(ak4642);
|
||||
ak4642_codec = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id ak4642_i2c_id[] = {
|
||||
{ "ak4642", 0 },
|
||||
{ "ak4643", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, ak4642_i2c_id);
|
||||
|
||||
static struct i2c_driver ak4642_i2c_driver = {
|
||||
.driver = {
|
||||
.name = "AK4642 I2C Codec",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = ak4642_i2c_probe,
|
||||
.remove = ak4642_i2c_remove,
|
||||
.id_table = ak4642_i2c_id,
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
static int ak4642_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
int ret;
|
||||
|
||||
if (!ak4642_codec) {
|
||||
dev_err(&pdev->dev, "Codec device not registered\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
socdev->card->codec = ak4642_codec;
|
||||
|
||||
/* register pcms */
|
||||
ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "ak4642: failed to create pcms\n");
|
||||
goto pcm_err;
|
||||
}
|
||||
|
||||
ret = snd_soc_init_card(socdev);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "ak4642: failed to register card\n");
|
||||
goto card_err;
|
||||
}
|
||||
|
||||
dev_info(&pdev->dev, "AK4642 Audio Codec %s", AK4642_VERSION);
|
||||
return ret;
|
||||
|
||||
card_err:
|
||||
snd_soc_free_pcms(socdev);
|
||||
snd_soc_dapm_free(socdev);
|
||||
pcm_err:
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
/* power down chip */
|
||||
static int ak4642_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
|
||||
snd_soc_free_pcms(socdev);
|
||||
snd_soc_dapm_free(socdev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct snd_soc_codec_device soc_codec_dev_ak4642 = {
|
||||
.probe = ak4642_probe,
|
||||
.remove = ak4642_remove,
|
||||
.resume = ak4642_resume,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(soc_codec_dev_ak4642);
|
||||
|
||||
static int __init ak4642_modinit(void)
|
||||
{
|
||||
int ret;
|
||||
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
|
||||
ret = i2c_add_driver(&ak4642_i2c_driver);
|
||||
#endif
|
||||
return ret;
|
||||
|
||||
}
|
||||
module_init(ak4642_modinit);
|
||||
|
||||
static void __exit ak4642_exit(void)
|
||||
{
|
||||
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
|
||||
i2c_del_driver(&ak4642_i2c_driver);
|
||||
#endif
|
||||
|
||||
}
|
||||
module_exit(ak4642_exit);
|
||||
|
||||
MODULE_DESCRIPTION("Soc AK4642 driver");
|
||||
MODULE_AUTHOR("Kuninori Morimoto <morimoto.kuninori@renesas.com>");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* ak4642.h -- AK4642 Soc Audio driver
|
||||
*
|
||||
* Copyright (C) 2009 Renesas Solutions Corp.
|
||||
* Kuninori Morimoto <morimoto.kuninori@renesas.com>
|
||||
*
|
||||
* Based on ak4535.c
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef _AK4642_H
|
||||
#define _AK4642_H
|
||||
|
||||
extern struct snd_soc_dai ak4642_dai;
|
||||
extern struct snd_soc_codec_device soc_codec_dev_ak4642;
|
||||
|
||||
#endif
|
|
@ -806,15 +806,30 @@ static int cs4270_i2c_suspend(struct i2c_client *client, pm_message_t mesg)
|
|||
{
|
||||
struct cs4270_private *cs4270 = i2c_get_clientdata(client);
|
||||
struct snd_soc_codec *codec = &cs4270->codec;
|
||||
int reg = snd_soc_read(codec, CS4270_PWRCTL) | CS4270_PWRCTL_PDN_ALL;
|
||||
|
||||
return snd_soc_write(codec, CS4270_PWRCTL, reg);
|
||||
return snd_soc_suspend_device(codec->dev);
|
||||
}
|
||||
|
||||
static int cs4270_i2c_resume(struct i2c_client *client)
|
||||
{
|
||||
struct cs4270_private *cs4270 = i2c_get_clientdata(client);
|
||||
struct snd_soc_codec *codec = &cs4270->codec;
|
||||
|
||||
return snd_soc_resume_device(codec->dev);
|
||||
}
|
||||
|
||||
static int cs4270_soc_suspend(struct platform_device *pdev, pm_message_t mesg)
|
||||
{
|
||||
struct snd_soc_codec *codec = cs4270_codec;
|
||||
int reg = snd_soc_read(codec, CS4270_PWRCTL) | CS4270_PWRCTL_PDN_ALL;
|
||||
|
||||
return snd_soc_write(codec, CS4270_PWRCTL, reg);
|
||||
}
|
||||
|
||||
static int cs4270_soc_resume(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_codec *codec = cs4270_codec;
|
||||
struct i2c_client *i2c_client = codec->control_data;
|
||||
int reg;
|
||||
|
||||
/* In case the device was put to hard reset during sleep, we need to
|
||||
|
@ -825,7 +840,7 @@ static int cs4270_i2c_resume(struct i2c_client *client)
|
|||
for (reg = CS4270_FIRSTREG; reg <= CS4270_LASTREG; reg++) {
|
||||
u8 val = snd_soc_read(codec, reg);
|
||||
|
||||
if (i2c_smbus_write_byte_data(client, reg, val)) {
|
||||
if (i2c_smbus_write_byte_data(i2c_client, reg, val)) {
|
||||
dev_err(codec->dev, "i2c write failed\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
@ -840,6 +855,8 @@ static int cs4270_i2c_resume(struct i2c_client *client)
|
|||
#else
|
||||
#define cs4270_i2c_suspend NULL
|
||||
#define cs4270_i2c_resume NULL
|
||||
#define cs4270_soc_suspend NULL
|
||||
#define cs4270_soc_resume NULL
|
||||
#endif /* CONFIG_PM */
|
||||
|
||||
/*
|
||||
|
@ -868,7 +885,9 @@ static struct i2c_driver cs4270_i2c_driver = {
|
|||
*/
|
||||
struct snd_soc_codec_device soc_codec_device_cs4270 = {
|
||||
.probe = cs4270_probe,
|
||||
.remove = cs4270_remove
|
||||
.remove = cs4270_remove,
|
||||
.suspend = cs4270_soc_suspend,
|
||||
.resume = cs4270_soc_resume,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(soc_codec_device_cs4270);
|
||||
|
||||
|
|
|
@ -0,0 +1,501 @@
|
|||
/*
|
||||
* cx20442.c -- CX20442 ALSA Soc Audio driver
|
||||
*
|
||||
* Copyright 2009 Janusz Krzysztofik <jkrzyszt@tis.icnet.pl>
|
||||
*
|
||||
* Initially based on sound/soc/codecs/wm8400.c
|
||||
* Copyright 2008, 2009 Wolfson Microelectronics PLC.
|
||||
* Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*/
|
||||
|
||||
#include <linux/tty.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/initval.h>
|
||||
#include <sound/soc-dapm.h>
|
||||
|
||||
#include "cx20442.h"
|
||||
|
||||
|
||||
struct cx20442_priv {
|
||||
struct snd_soc_codec codec;
|
||||
u8 reg_cache[1];
|
||||
};
|
||||
|
||||
#define CX20442_PM 0x0
|
||||
|
||||
#define CX20442_TELIN 0
|
||||
#define CX20442_TELOUT 1
|
||||
#define CX20442_MIC 2
|
||||
#define CX20442_SPKOUT 3
|
||||
#define CX20442_AGC 4
|
||||
|
||||
static const struct snd_soc_dapm_widget cx20442_dapm_widgets[] = {
|
||||
SND_SOC_DAPM_OUTPUT("TELOUT"),
|
||||
SND_SOC_DAPM_OUTPUT("SPKOUT"),
|
||||
SND_SOC_DAPM_OUTPUT("AGCOUT"),
|
||||
|
||||
SND_SOC_DAPM_MIXER("SPKOUT Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
|
||||
|
||||
SND_SOC_DAPM_PGA("TELOUT Amp", CX20442_PM, CX20442_TELOUT, 0, NULL, 0),
|
||||
SND_SOC_DAPM_PGA("SPKOUT Amp", CX20442_PM, CX20442_SPKOUT, 0, NULL, 0),
|
||||
SND_SOC_DAPM_PGA("SPKOUT AGC", CX20442_PM, CX20442_AGC, 0, NULL, 0),
|
||||
|
||||
SND_SOC_DAPM_DAC("DAC", "Playback", SND_SOC_NOPM, 0, 0),
|
||||
SND_SOC_DAPM_ADC("ADC", "Capture", SND_SOC_NOPM, 0, 0),
|
||||
|
||||
SND_SOC_DAPM_MIXER("Input Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
|
||||
|
||||
SND_SOC_DAPM_MICBIAS("TELIN Bias", CX20442_PM, CX20442_TELIN, 0),
|
||||
SND_SOC_DAPM_MICBIAS("MIC Bias", CX20442_PM, CX20442_MIC, 0),
|
||||
|
||||
SND_SOC_DAPM_PGA("MIC AGC", CX20442_PM, CX20442_AGC, 0, NULL, 0),
|
||||
|
||||
SND_SOC_DAPM_INPUT("TELIN"),
|
||||
SND_SOC_DAPM_INPUT("MIC"),
|
||||
SND_SOC_DAPM_INPUT("AGCIN"),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route cx20442_audio_map[] = {
|
||||
{"TELOUT", NULL, "TELOUT Amp"},
|
||||
|
||||
{"SPKOUT", NULL, "SPKOUT Mixer"},
|
||||
{"SPKOUT Mixer", NULL, "SPKOUT Amp"},
|
||||
|
||||
{"TELOUT Amp", NULL, "DAC"},
|
||||
{"SPKOUT Amp", NULL, "DAC"},
|
||||
|
||||
{"SPKOUT Mixer", NULL, "SPKOUT AGC"},
|
||||
{"SPKOUT AGC", NULL, "AGCIN"},
|
||||
|
||||
{"AGCOUT", NULL, "MIC AGC"},
|
||||
{"MIC AGC", NULL, "MIC"},
|
||||
|
||||
{"MIC Bias", NULL, "MIC"},
|
||||
{"Input Mixer", NULL, "MIC Bias"},
|
||||
|
||||
{"TELIN Bias", NULL, "TELIN"},
|
||||
{"Input Mixer", NULL, "TELIN Bias"},
|
||||
|
||||
{"ADC", NULL, "Input Mixer"},
|
||||
};
|
||||
|
||||
static int cx20442_add_widgets(struct snd_soc_codec *codec)
|
||||
{
|
||||
snd_soc_dapm_new_controls(codec, cx20442_dapm_widgets,
|
||||
ARRAY_SIZE(cx20442_dapm_widgets));
|
||||
|
||||
snd_soc_dapm_add_routes(codec, cx20442_audio_map,
|
||||
ARRAY_SIZE(cx20442_audio_map));
|
||||
|
||||
snd_soc_dapm_new_widgets(codec);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned int cx20442_read_reg_cache(struct snd_soc_codec *codec,
|
||||
unsigned int reg)
|
||||
{
|
||||
u8 *reg_cache = codec->reg_cache;
|
||||
|
||||
if (reg >= codec->reg_cache_size)
|
||||
return -EINVAL;
|
||||
|
||||
return reg_cache[reg];
|
||||
}
|
||||
|
||||
enum v253_vls {
|
||||
V253_VLS_NONE = 0,
|
||||
V253_VLS_T,
|
||||
V253_VLS_L,
|
||||
V253_VLS_LT,
|
||||
V253_VLS_S,
|
||||
V253_VLS_ST,
|
||||
V253_VLS_M,
|
||||
V253_VLS_MST,
|
||||
V253_VLS_S1,
|
||||
V253_VLS_S1T,
|
||||
V253_VLS_MS1T,
|
||||
V253_VLS_M1,
|
||||
V253_VLS_M1ST,
|
||||
V253_VLS_M1S1T,
|
||||
V253_VLS_H,
|
||||
V253_VLS_HT,
|
||||
V253_VLS_MS,
|
||||
V253_VLS_MS1,
|
||||
V253_VLS_M1S,
|
||||
V253_VLS_M1S1,
|
||||
V253_VLS_TEST,
|
||||
};
|
||||
|
||||
static int cx20442_pm_to_v253_vls(u8 value)
|
||||
{
|
||||
switch (value & ~(1 << CX20442_AGC)) {
|
||||
case 0:
|
||||
return V253_VLS_T;
|
||||
case (1 << CX20442_SPKOUT):
|
||||
case (1 << CX20442_MIC):
|
||||
case (1 << CX20442_SPKOUT) | (1 << CX20442_MIC):
|
||||
return V253_VLS_M1S1;
|
||||
case (1 << CX20442_TELOUT):
|
||||
case (1 << CX20442_TELIN):
|
||||
case (1 << CX20442_TELOUT) | (1 << CX20442_TELIN):
|
||||
return V253_VLS_L;
|
||||
case (1 << CX20442_TELOUT) | (1 << CX20442_MIC):
|
||||
return V253_VLS_NONE;
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
static int cx20442_pm_to_v253_vsp(u8 value)
|
||||
{
|
||||
switch (value & ~(1 << CX20442_AGC)) {
|
||||
case (1 << CX20442_SPKOUT):
|
||||
case (1 << CX20442_MIC):
|
||||
case (1 << CX20442_SPKOUT) | (1 << CX20442_MIC):
|
||||
return (bool)(value & (1 << CX20442_AGC));
|
||||
}
|
||||
return (value & (1 << CX20442_AGC)) ? -EINVAL : 0;
|
||||
}
|
||||
|
||||
static int cx20442_write(struct snd_soc_codec *codec, unsigned int reg,
|
||||
unsigned int value)
|
||||
{
|
||||
u8 *reg_cache = codec->reg_cache;
|
||||
int vls, vsp, old, len;
|
||||
char buf[18];
|
||||
|
||||
if (reg >= codec->reg_cache_size)
|
||||
return -EINVAL;
|
||||
|
||||
/* hw_write and control_data pointers required for talking to the modem
|
||||
* are expected to be set by the line discipline initialization code */
|
||||
if (!codec->hw_write || !codec->control_data)
|
||||
return -EIO;
|
||||
|
||||
old = reg_cache[reg];
|
||||
reg_cache[reg] = value;
|
||||
|
||||
vls = cx20442_pm_to_v253_vls(value);
|
||||
if (vls < 0)
|
||||
return vls;
|
||||
|
||||
vsp = cx20442_pm_to_v253_vsp(value);
|
||||
if (vsp < 0)
|
||||
return vsp;
|
||||
|
||||
if ((vls == V253_VLS_T) ||
|
||||
(vls == cx20442_pm_to_v253_vls(old))) {
|
||||
if (vsp == cx20442_pm_to_v253_vsp(old))
|
||||
return 0;
|
||||
len = snprintf(buf, ARRAY_SIZE(buf), "at+vsp=%d\r", vsp);
|
||||
} else if (vsp == cx20442_pm_to_v253_vsp(old))
|
||||
len = snprintf(buf, ARRAY_SIZE(buf), "at+vls=%d\r", vls);
|
||||
else
|
||||
len = snprintf(buf, ARRAY_SIZE(buf),
|
||||
"at+vls=%d;+vsp=%d\r", vls, vsp);
|
||||
|
||||
if (unlikely(len > (ARRAY_SIZE(buf) - 1)))
|
||||
return -ENOMEM;
|
||||
|
||||
dev_dbg(codec->dev, "%s: %s\n", __func__, buf);
|
||||
if (codec->hw_write(codec->control_data, buf, len) != len)
|
||||
return -EIO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Moved up here as line discipline referres it during initialization */
|
||||
static struct snd_soc_codec *cx20442_codec;
|
||||
|
||||
|
||||
/*
|
||||
* Line discpline related code
|
||||
*
|
||||
* Any of the callback functions below can be used in two ways:
|
||||
* 1) registerd by a machine driver as one of line discipline operations,
|
||||
* 2) called from a machine's provided line discipline callback function
|
||||
* in case when extra machine specific code must be run as well.
|
||||
*/
|
||||
|
||||
/* Modem init: echo off, digital speaker off, quiet off, voice mode */
|
||||
static const char *v253_init = "ate0m0q0+fclass=8\r";
|
||||
|
||||
/* Line discipline .open() */
|
||||
static int v253_open(struct tty_struct *tty)
|
||||
{
|
||||
struct snd_soc_codec *codec = cx20442_codec;
|
||||
int ret, len = strlen(v253_init);
|
||||
|
||||
/* Doesn't make sense without write callback */
|
||||
if (!tty->ops->write)
|
||||
return -EINVAL;
|
||||
|
||||
/* Pass the codec structure address for use by other ldisc callbacks */
|
||||
tty->disc_data = codec;
|
||||
|
||||
if (tty->ops->write(tty, v253_init, len) != len) {
|
||||
ret = -EIO;
|
||||
goto err;
|
||||
}
|
||||
/* Actual setup will be performed after the modem responds. */
|
||||
return 0;
|
||||
err:
|
||||
tty->disc_data = NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Line discipline .close() */
|
||||
static void v253_close(struct tty_struct *tty)
|
||||
{
|
||||
struct snd_soc_codec *codec = tty->disc_data;
|
||||
|
||||
tty->disc_data = NULL;
|
||||
|
||||
if (!codec)
|
||||
return;
|
||||
|
||||
/* Prevent the codec driver from further accessing the modem */
|
||||
codec->hw_write = NULL;
|
||||
codec->control_data = NULL;
|
||||
codec->pop_time = 0;
|
||||
}
|
||||
|
||||
/* Line discipline .hangup() */
|
||||
static int v253_hangup(struct tty_struct *tty)
|
||||
{
|
||||
v253_close(tty);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Line discipline .receive_buf() */
|
||||
static void v253_receive(struct tty_struct *tty,
|
||||
const unsigned char *cp, char *fp, int count)
|
||||
{
|
||||
struct snd_soc_codec *codec = tty->disc_data;
|
||||
|
||||
if (!codec)
|
||||
return;
|
||||
|
||||
if (!codec->control_data) {
|
||||
/* First modem response, complete setup procedure */
|
||||
|
||||
/* Set up codec driver access to modem controls */
|
||||
codec->control_data = tty;
|
||||
codec->hw_write = (hw_write_t)tty->ops->write;
|
||||
codec->pop_time = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Line discipline .write_wakeup() */
|
||||
static void v253_wakeup(struct tty_struct *tty)
|
||||
{
|
||||
}
|
||||
|
||||
struct tty_ldisc_ops v253_ops = {
|
||||
.magic = TTY_LDISC_MAGIC,
|
||||
.name = "cx20442",
|
||||
.owner = THIS_MODULE,
|
||||
.open = v253_open,
|
||||
.close = v253_close,
|
||||
.hangup = v253_hangup,
|
||||
.receive_buf = v253_receive,
|
||||
.write_wakeup = v253_wakeup,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(v253_ops);
|
||||
|
||||
|
||||
/*
|
||||
* Codec DAI
|
||||
*/
|
||||
|
||||
struct snd_soc_dai cx20442_dai = {
|
||||
.name = "CX20442",
|
||||
.playback = {
|
||||
.stream_name = "Playback",
|
||||
.channels_min = 1,
|
||||
.channels_max = 1,
|
||||
.rates = SNDRV_PCM_RATE_8000,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,
|
||||
},
|
||||
.capture = {
|
||||
.stream_name = "Capture",
|
||||
.channels_min = 1,
|
||||
.channels_max = 1,
|
||||
.rates = SNDRV_PCM_RATE_8000,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,
|
||||
},
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(cx20442_dai);
|
||||
|
||||
static int cx20442_codec_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
struct snd_soc_codec *codec;
|
||||
int ret;
|
||||
|
||||
if (!cx20442_codec) {
|
||||
dev_err(&pdev->dev, "cx20442 not yet discovered\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
codec = cx20442_codec;
|
||||
|
||||
socdev->card->codec = codec;
|
||||
|
||||
/* register pcms */
|
||||
ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "failed to create pcms\n");
|
||||
goto pcm_err;
|
||||
}
|
||||
|
||||
cx20442_add_widgets(codec);
|
||||
|
||||
ret = snd_soc_init_card(socdev);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "failed to register card\n");
|
||||
goto card_err;
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
card_err:
|
||||
snd_soc_free_pcms(socdev);
|
||||
snd_soc_dapm_free(socdev);
|
||||
pcm_err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* power down chip */
|
||||
static int cx20442_codec_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
|
||||
snd_soc_free_pcms(socdev);
|
||||
snd_soc_dapm_free(socdev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct snd_soc_codec_device cx20442_codec_dev = {
|
||||
.probe = cx20442_codec_probe,
|
||||
.remove = cx20442_codec_remove,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(cx20442_codec_dev);
|
||||
|
||||
static int cx20442_register(struct cx20442_priv *cx20442)
|
||||
{
|
||||
struct snd_soc_codec *codec = &cx20442->codec;
|
||||
int ret;
|
||||
|
||||
mutex_init(&codec->mutex);
|
||||
INIT_LIST_HEAD(&codec->dapm_widgets);
|
||||
INIT_LIST_HEAD(&codec->dapm_paths);
|
||||
|
||||
codec->name = "CX20442";
|
||||
codec->owner = THIS_MODULE;
|
||||
codec->private_data = cx20442;
|
||||
|
||||
codec->dai = &cx20442_dai;
|
||||
codec->num_dai = 1;
|
||||
|
||||
codec->reg_cache = &cx20442->reg_cache;
|
||||
codec->reg_cache_size = ARRAY_SIZE(cx20442->reg_cache);
|
||||
codec->read = cx20442_read_reg_cache;
|
||||
codec->write = cx20442_write;
|
||||
|
||||
codec->bias_level = SND_SOC_BIAS_OFF;
|
||||
|
||||
cx20442_dai.dev = codec->dev;
|
||||
|
||||
cx20442_codec = codec;
|
||||
|
||||
ret = snd_soc_register_codec(codec);
|
||||
if (ret != 0) {
|
||||
dev_err(codec->dev, "Failed to register codec: %d\n", ret);
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = snd_soc_register_dai(&cx20442_dai);
|
||||
if (ret != 0) {
|
||||
dev_err(codec->dev, "Failed to register DAI: %d\n", ret);
|
||||
goto err_codec;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_codec:
|
||||
snd_soc_unregister_codec(codec);
|
||||
err:
|
||||
cx20442_codec = NULL;
|
||||
kfree(cx20442);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void cx20442_unregister(struct cx20442_priv *cx20442)
|
||||
{
|
||||
snd_soc_unregister_dai(&cx20442_dai);
|
||||
snd_soc_unregister_codec(&cx20442->codec);
|
||||
|
||||
cx20442_codec = NULL;
|
||||
kfree(cx20442);
|
||||
}
|
||||
|
||||
static int cx20442_platform_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct cx20442_priv *cx20442;
|
||||
struct snd_soc_codec *codec;
|
||||
|
||||
cx20442 = kzalloc(sizeof(struct cx20442_priv), GFP_KERNEL);
|
||||
if (cx20442 == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
codec = &cx20442->codec;
|
||||
|
||||
codec->control_data = NULL;
|
||||
codec->hw_write = NULL;
|
||||
codec->pop_time = 0;
|
||||
|
||||
codec->dev = &pdev->dev;
|
||||
platform_set_drvdata(pdev, cx20442);
|
||||
|
||||
return cx20442_register(cx20442);
|
||||
}
|
||||
|
||||
static int __exit cx20442_platform_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct cx20442_priv *cx20442 = platform_get_drvdata(pdev);
|
||||
|
||||
cx20442_unregister(cx20442);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver cx20442_platform_driver = {
|
||||
.driver = {
|
||||
.name = "cx20442",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = cx20442_platform_probe,
|
||||
.remove = __exit_p(cx20442_platform_remove),
|
||||
};
|
||||
|
||||
static int __init cx20442_init(void)
|
||||
{
|
||||
return platform_driver_register(&cx20442_platform_driver);
|
||||
}
|
||||
module_init(cx20442_init);
|
||||
|
||||
static void __exit cx20442_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&cx20442_platform_driver);
|
||||
}
|
||||
module_exit(cx20442_exit);
|
||||
|
||||
MODULE_DESCRIPTION("ASoC CX20442-11 voice modem codec driver");
|
||||
MODULE_AUTHOR("Janusz Krzysztofik");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:cx20442");
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* cx20442.h -- audio driver for CX20442
|
||||
*
|
||||
* Copyright 2009 Janusz Krzysztofik <jkrzyszt@tis.icnet.pl>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _CX20442_CODEC_H
|
||||
#define _CX20442_CODEC_H
|
||||
|
||||
extern struct snd_soc_dai cx20442_dai;
|
||||
extern struct snd_soc_codec_device cx20442_codec_dev;
|
||||
extern struct tty_ldisc_ops v253_ops;
|
||||
|
||||
#endif
|
|
@ -0,0 +1,308 @@
|
|||
/*
|
||||
* max9877.c -- amp driver for max9877
|
||||
*
|
||||
* Copyright (C) 2009 Samsung Electronics Co.Ltd
|
||||
* Author: Joonyoung Shim <jy0922.shim@samsung.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/tlv.h>
|
||||
|
||||
#include "max9877.h"
|
||||
|
||||
static struct i2c_client *i2c;
|
||||
|
||||
static u8 max9877_regs[5] = { 0x40, 0x00, 0x00, 0x00, 0x49 };
|
||||
|
||||
static void max9877_write_regs(void)
|
||||
{
|
||||
unsigned int i;
|
||||
u8 data[6];
|
||||
|
||||
data[0] = MAX9877_INPUT_MODE;
|
||||
for (i = 0; i < ARRAY_SIZE(max9877_regs); i++)
|
||||
data[i + 1] = max9877_regs[i];
|
||||
|
||||
if (i2c_master_send(i2c, data, 6) != 6)
|
||||
dev_err(&i2c->dev, "i2c write failed\n");
|
||||
}
|
||||
|
||||
static int max9877_get_reg(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct soc_mixer_control *mc =
|
||||
(struct soc_mixer_control *)kcontrol->private_value;
|
||||
unsigned int reg = mc->reg;
|
||||
unsigned int shift = mc->shift;
|
||||
unsigned int mask = mc->max;
|
||||
unsigned int invert = mc->invert;
|
||||
|
||||
ucontrol->value.integer.value[0] = (max9877_regs[reg] >> shift) & mask;
|
||||
|
||||
if (invert)
|
||||
ucontrol->value.integer.value[0] =
|
||||
mask - ucontrol->value.integer.value[0];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int max9877_set_reg(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct soc_mixer_control *mc =
|
||||
(struct soc_mixer_control *)kcontrol->private_value;
|
||||
unsigned int reg = mc->reg;
|
||||
unsigned int shift = mc->shift;
|
||||
unsigned int mask = mc->max;
|
||||
unsigned int invert = mc->invert;
|
||||
unsigned int val = (ucontrol->value.integer.value[0] & mask);
|
||||
|
||||
if (invert)
|
||||
val = mask - val;
|
||||
|
||||
if (((max9877_regs[reg] >> shift) & mask) == val)
|
||||
return 0;
|
||||
|
||||
max9877_regs[reg] &= ~(mask << shift);
|
||||
max9877_regs[reg] |= val << shift;
|
||||
max9877_write_regs();
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int max9877_get_2reg(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct soc_mixer_control *mc =
|
||||
(struct soc_mixer_control *)kcontrol->private_value;
|
||||
unsigned int reg = mc->reg;
|
||||
unsigned int reg2 = mc->rreg;
|
||||
unsigned int shift = mc->shift;
|
||||
unsigned int mask = mc->max;
|
||||
|
||||
ucontrol->value.integer.value[0] = (max9877_regs[reg] >> shift) & mask;
|
||||
ucontrol->value.integer.value[1] = (max9877_regs[reg2] >> shift) & mask;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int max9877_set_2reg(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct soc_mixer_control *mc =
|
||||
(struct soc_mixer_control *)kcontrol->private_value;
|
||||
unsigned int reg = mc->reg;
|
||||
unsigned int reg2 = mc->rreg;
|
||||
unsigned int shift = mc->shift;
|
||||
unsigned int mask = mc->max;
|
||||
unsigned int val = (ucontrol->value.integer.value[0] & mask);
|
||||
unsigned int val2 = (ucontrol->value.integer.value[1] & mask);
|
||||
unsigned int change = 1;
|
||||
|
||||
if (((max9877_regs[reg] >> shift) & mask) == val)
|
||||
change = 0;
|
||||
|
||||
if (((max9877_regs[reg2] >> shift) & mask) == val2)
|
||||
change = 0;
|
||||
|
||||
if (change) {
|
||||
max9877_regs[reg] &= ~(mask << shift);
|
||||
max9877_regs[reg] |= val << shift;
|
||||
max9877_regs[reg2] &= ~(mask << shift);
|
||||
max9877_regs[reg2] |= val2 << shift;
|
||||
max9877_write_regs();
|
||||
}
|
||||
|
||||
return change;
|
||||
}
|
||||
|
||||
static int max9877_get_out_mode(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
u8 value = max9877_regs[MAX9877_OUTPUT_MODE] & MAX9877_OUTMODE_MASK;
|
||||
|
||||
if (value)
|
||||
value -= 1;
|
||||
|
||||
ucontrol->value.integer.value[0] = value;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int max9877_set_out_mode(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
u8 value = ucontrol->value.integer.value[0];
|
||||
|
||||
value += 1;
|
||||
|
||||
if ((max9877_regs[MAX9877_OUTPUT_MODE] & MAX9877_OUTMODE_MASK) == value)
|
||||
return 0;
|
||||
|
||||
max9877_regs[MAX9877_OUTPUT_MODE] &= ~MAX9877_OUTMODE_MASK;
|
||||
max9877_regs[MAX9877_OUTPUT_MODE] |= value;
|
||||
max9877_write_regs();
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int max9877_get_osc_mode(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
u8 value = (max9877_regs[MAX9877_OUTPUT_MODE] & MAX9877_OSC_MASK);
|
||||
|
||||
value = value >> MAX9877_OSC_OFFSET;
|
||||
|
||||
ucontrol->value.integer.value[0] = value;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int max9877_set_osc_mode(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
u8 value = ucontrol->value.integer.value[0];
|
||||
|
||||
value = value << MAX9877_OSC_OFFSET;
|
||||
if ((max9877_regs[MAX9877_OUTPUT_MODE] & MAX9877_OSC_MASK) == value)
|
||||
return 0;
|
||||
|
||||
max9877_regs[MAX9877_OUTPUT_MODE] &= ~MAX9877_OSC_MASK;
|
||||
max9877_regs[MAX9877_OUTPUT_MODE] |= value;
|
||||
max9877_write_regs();
|
||||
return 1;
|
||||
}
|
||||
|
||||
static const unsigned int max9877_pgain_tlv[] = {
|
||||
TLV_DB_RANGE_HEAD(2),
|
||||
0, 1, TLV_DB_SCALE_ITEM(0, 900, 0),
|
||||
2, 2, TLV_DB_SCALE_ITEM(2000, 0, 0),
|
||||
};
|
||||
|
||||
static const unsigned int max9877_output_tlv[] = {
|
||||
TLV_DB_RANGE_HEAD(4),
|
||||
0, 7, TLV_DB_SCALE_ITEM(-7900, 400, 1),
|
||||
8, 15, TLV_DB_SCALE_ITEM(-4700, 300, 0),
|
||||
16, 23, TLV_DB_SCALE_ITEM(-2300, 200, 0),
|
||||
24, 31, TLV_DB_SCALE_ITEM(-700, 100, 0),
|
||||
};
|
||||
|
||||
static const char *max9877_out_mode[] = {
|
||||
"INA -> SPK",
|
||||
"INA -> HP",
|
||||
"INA -> SPK and HP",
|
||||
"INB -> SPK",
|
||||
"INB -> HP",
|
||||
"INB -> SPK and HP",
|
||||
"INA + INB -> SPK",
|
||||
"INA + INB -> HP",
|
||||
"INA + INB -> SPK and HP",
|
||||
};
|
||||
|
||||
static const char *max9877_osc_mode[] = {
|
||||
"1176KHz",
|
||||
"1100KHz",
|
||||
"700KHz",
|
||||
};
|
||||
|
||||
static const struct soc_enum max9877_enum[] = {
|
||||
SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(max9877_out_mode), max9877_out_mode),
|
||||
SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(max9877_osc_mode), max9877_osc_mode),
|
||||
};
|
||||
|
||||
static const struct snd_kcontrol_new max9877_controls[] = {
|
||||
SOC_SINGLE_EXT_TLV("MAX9877 PGAINA Playback Volume",
|
||||
MAX9877_INPUT_MODE, 0, 2, 0,
|
||||
max9877_get_reg, max9877_set_reg, max9877_pgain_tlv),
|
||||
SOC_SINGLE_EXT_TLV("MAX9877 PGAINB Playback Volume",
|
||||
MAX9877_INPUT_MODE, 2, 2, 0,
|
||||
max9877_get_reg, max9877_set_reg, max9877_pgain_tlv),
|
||||
SOC_SINGLE_EXT_TLV("MAX9877 Amp Speaker Playback Volume",
|
||||
MAX9877_SPK_VOLUME, 0, 31, 0,
|
||||
max9877_get_reg, max9877_set_reg, max9877_output_tlv),
|
||||
SOC_DOUBLE_R_EXT_TLV("MAX9877 Amp HP Playback Volume",
|
||||
MAX9877_HPL_VOLUME, MAX9877_HPR_VOLUME, 0, 31, 0,
|
||||
max9877_get_2reg, max9877_set_2reg, max9877_output_tlv),
|
||||
SOC_SINGLE_EXT("MAX9877 INB Stereo Switch",
|
||||
MAX9877_INPUT_MODE, 4, 1, 1,
|
||||
max9877_get_reg, max9877_set_reg),
|
||||
SOC_SINGLE_EXT("MAX9877 INA Stereo Switch",
|
||||
MAX9877_INPUT_MODE, 5, 1, 1,
|
||||
max9877_get_reg, max9877_set_reg),
|
||||
SOC_SINGLE_EXT("MAX9877 Zero-crossing detection Switch",
|
||||
MAX9877_INPUT_MODE, 6, 1, 0,
|
||||
max9877_get_reg, max9877_set_reg),
|
||||
SOC_SINGLE_EXT("MAX9877 Bypass Mode Switch",
|
||||
MAX9877_OUTPUT_MODE, 6, 1, 0,
|
||||
max9877_get_reg, max9877_set_reg),
|
||||
SOC_SINGLE_EXT("MAX9877 Shutdown Mode Switch",
|
||||
MAX9877_OUTPUT_MODE, 7, 1, 1,
|
||||
max9877_get_reg, max9877_set_reg),
|
||||
SOC_ENUM_EXT("MAX9877 Output Mode", max9877_enum[0],
|
||||
max9877_get_out_mode, max9877_set_out_mode),
|
||||
SOC_ENUM_EXT("MAX9877 Oscillator Mode", max9877_enum[1],
|
||||
max9877_get_osc_mode, max9877_set_osc_mode),
|
||||
};
|
||||
|
||||
/* This function is called from ASoC machine driver */
|
||||
int max9877_add_controls(struct snd_soc_codec *codec)
|
||||
{
|
||||
return snd_soc_add_controls(codec, max9877_controls,
|
||||
ARRAY_SIZE(max9877_controls));
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(max9877_add_controls);
|
||||
|
||||
static int __devinit max9877_i2c_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
i2c = client;
|
||||
|
||||
max9877_write_regs();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static __devexit int max9877_i2c_remove(struct i2c_client *client)
|
||||
{
|
||||
i2c = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id max9877_i2c_id[] = {
|
||||
{ "max9877", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, max9877_i2c_id);
|
||||
|
||||
static struct i2c_driver max9877_i2c_driver = {
|
||||
.driver = {
|
||||
.name = "max9877",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = max9877_i2c_probe,
|
||||
.remove = __devexit_p(max9877_i2c_remove),
|
||||
.id_table = max9877_i2c_id,
|
||||
};
|
||||
|
||||
static int __init max9877_init(void)
|
||||
{
|
||||
return i2c_add_driver(&max9877_i2c_driver);
|
||||
}
|
||||
module_init(max9877_init);
|
||||
|
||||
static void __exit max9877_exit(void)
|
||||
{
|
||||
i2c_del_driver(&max9877_i2c_driver);
|
||||
}
|
||||
module_exit(max9877_exit);
|
||||
|
||||
MODULE_DESCRIPTION("ASoC MAX9877 amp driver");
|
||||
MODULE_AUTHOR("Joonyoung Shim <jy0922.shim@samsung.com>");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* max9877.h -- amp driver for max9877
|
||||
*
|
||||
* Copyright (C) 2009 Samsung Electronics Co.Ltd
|
||||
* Author: Joonyoung Shim <jy0922.shim@samsung.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _MAX9877_H
|
||||
#define _MAX9877_H
|
||||
|
||||
#define MAX9877_INPUT_MODE 0x00
|
||||
#define MAX9877_SPK_VOLUME 0x01
|
||||
#define MAX9877_HPL_VOLUME 0x02
|
||||
#define MAX9877_HPR_VOLUME 0x03
|
||||
#define MAX9877_OUTPUT_MODE 0x04
|
||||
|
||||
/* MAX9877_INPUT_MODE */
|
||||
#define MAX9877_INB (1 << 4)
|
||||
#define MAX9877_INA (1 << 5)
|
||||
#define MAX9877_ZCD (1 << 6)
|
||||
|
||||
/* MAX9877_OUTPUT_MODE */
|
||||
#define MAX9877_OUTMODE_MASK (15 << 0)
|
||||
#define MAX9877_OSC_MASK (3 << 4)
|
||||
#define MAX9877_OSC_OFFSET 4
|
||||
#define MAX9877_BYPASS (1 << 6)
|
||||
#define MAX9877_SHDN (1 << 7)
|
||||
|
||||
extern int max9877_add_controls(struct snd_soc_codec *codec);
|
||||
|
||||
#endif
|
|
@ -21,6 +21,8 @@
|
|||
|
||||
#include "spdif_transciever.h"
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
#define STUB_RATES SNDRV_PCM_RATE_8000_96000
|
||||
#define STUB_FORMATS SNDRV_PCM_FMTBIT_S16_LE
|
||||
|
||||
|
@ -34,6 +36,7 @@ struct snd_soc_dai dit_stub_dai = {
|
|||
.formats = STUB_FORMATS,
|
||||
},
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(dit_stub_dai);
|
||||
|
||||
static int spdif_dit_probe(struct platform_device *pdev)
|
||||
{
|
||||
|
|
|
@ -149,7 +149,7 @@ static int stac9766_ac97_write(struct snd_soc_codec *codec, unsigned int reg,
|
|||
stac9766_ac97_write(codec, AC97_INT_PAGING, 1);
|
||||
return 0;
|
||||
}
|
||||
if (reg / 2 > ARRAY_SIZE(stac9766_reg))
|
||||
if (reg / 2 >= ARRAY_SIZE(stac9766_reg))
|
||||
return -EIO;
|
||||
|
||||
soc_ac97_ops.write(codec->ac97, reg, val);
|
||||
|
@ -168,7 +168,7 @@ static unsigned int stac9766_ac97_read(struct snd_soc_codec *codec,
|
|||
stac9766_ac97_write(codec, AC97_INT_PAGING, 1);
|
||||
return val;
|
||||
}
|
||||
if (reg / 2 > ARRAY_SIZE(stac9766_reg))
|
||||
if (reg / 2 >= ARRAY_SIZE(stac9766_reg))
|
||||
return -EIO;
|
||||
|
||||
if (reg == AC97_RESET || reg == AC97_GPIO_STATUS ||
|
||||
|
|
|
@ -53,6 +53,7 @@
|
|||
|
||||
/* codec private data */
|
||||
struct aic3x_priv {
|
||||
struct snd_soc_codec codec;
|
||||
unsigned int sysclk;
|
||||
int master;
|
||||
};
|
||||
|
@ -145,8 +146,8 @@ static int aic3x_read(struct snd_soc_codec *codec, unsigned int reg,
|
|||
u8 *value)
|
||||
{
|
||||
*value = reg & 0xff;
|
||||
if (codec->hw_read(codec->control_data, value, 1) != 1)
|
||||
return -EIO;
|
||||
|
||||
value[0] = i2c_smbus_read_byte_data(codec->control_data, value[0]);
|
||||
|
||||
aic3x_write_reg_cache(codec, reg, *value);
|
||||
return 0;
|
||||
|
@ -1156,11 +1157,13 @@ static int aic3x_resume(struct platform_device *pdev)
|
|||
* initialise the AIC3X driver
|
||||
* register the mixer and dsp interfaces with the kernel
|
||||
*/
|
||||
static int aic3x_init(struct snd_soc_device *socdev)
|
||||
static int aic3x_init(struct snd_soc_codec *codec)
|
||||
{
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
struct aic3x_setup_data *setup = socdev->codec_data;
|
||||
int reg, ret = 0;
|
||||
int reg;
|
||||
|
||||
mutex_init(&codec->mutex);
|
||||
INIT_LIST_HEAD(&codec->dapm_widgets);
|
||||
INIT_LIST_HEAD(&codec->dapm_paths);
|
||||
|
||||
codec->name = "tlv320aic3x";
|
||||
codec->owner = THIS_MODULE;
|
||||
|
@ -1177,13 +1180,6 @@ static int aic3x_init(struct snd_soc_device *socdev)
|
|||
aic3x_write(codec, AIC3X_PAGE_SELECT, PAGE0_SELECT);
|
||||
aic3x_write(codec, AIC3X_RESET, SOFT_RESET);
|
||||
|
||||
/* register pcms */
|
||||
ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "aic3x: failed to create pcms\n");
|
||||
goto pcm_err;
|
||||
}
|
||||
|
||||
/* DAC default volume and mute */
|
||||
aic3x_write(codec, LDAC_VOL, DEFAULT_VOL | MUTE_ON);
|
||||
aic3x_write(codec, RDAC_VOL, DEFAULT_VOL | MUTE_ON);
|
||||
|
@ -1250,30 +1246,51 @@ static int aic3x_init(struct snd_soc_device *socdev)
|
|||
/* off, with power on */
|
||||
aic3x_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
|
||||
|
||||
/* setup GPIO functions */
|
||||
aic3x_write(codec, AIC3X_GPIO1_REG, (setup->gpio_func[0] & 0xf) << 4);
|
||||
aic3x_write(codec, AIC3X_GPIO2_REG, (setup->gpio_func[1] & 0xf) << 4);
|
||||
|
||||
snd_soc_add_controls(codec, aic3x_snd_controls,
|
||||
ARRAY_SIZE(aic3x_snd_controls));
|
||||
aic3x_add_widgets(codec);
|
||||
ret = snd_soc_init_card(socdev);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "aic3x: failed to register card\n");
|
||||
goto card_err;
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
card_err:
|
||||
snd_soc_free_pcms(socdev);
|
||||
snd_soc_dapm_free(socdev);
|
||||
pcm_err:
|
||||
kfree(codec->reg_cache);
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_device *aic3x_socdev;
|
||||
static struct snd_soc_codec *aic3x_codec;
|
||||
|
||||
static int aic3x_register(struct snd_soc_codec *codec)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = aic3x_init(codec);
|
||||
if (ret < 0) {
|
||||
dev_err(codec->dev, "Failed to initialise device\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
aic3x_codec = codec;
|
||||
|
||||
ret = snd_soc_register_codec(codec);
|
||||
if (ret) {
|
||||
dev_err(codec->dev, "Failed to register codec\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = snd_soc_register_dai(&aic3x_dai);
|
||||
if (ret) {
|
||||
dev_err(codec->dev, "Failed to register dai\n");
|
||||
snd_soc_unregister_codec(codec);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int aic3x_unregister(struct aic3x_priv *aic3x)
|
||||
{
|
||||
aic3x_set_bias_level(&aic3x->codec, SND_SOC_BIAS_OFF);
|
||||
|
||||
snd_soc_unregister_dai(&aic3x_dai);
|
||||
snd_soc_unregister_codec(&aic3x->codec);
|
||||
|
||||
kfree(aic3x);
|
||||
aic3x_codec = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
|
||||
/*
|
||||
|
@ -1288,28 +1305,36 @@ static struct snd_soc_device *aic3x_socdev;
|
|||
static int aic3x_i2c_probe(struct i2c_client *i2c,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct snd_soc_device *socdev = aic3x_socdev;
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
int ret;
|
||||
struct snd_soc_codec *codec;
|
||||
struct aic3x_priv *aic3x;
|
||||
|
||||
i2c_set_clientdata(i2c, codec);
|
||||
aic3x = kzalloc(sizeof(struct aic3x_priv), GFP_KERNEL);
|
||||
if (aic3x == NULL) {
|
||||
dev_err(&i2c->dev, "failed to create private data\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
codec = &aic3x->codec;
|
||||
codec->dev = &i2c->dev;
|
||||
codec->private_data = aic3x;
|
||||
codec->control_data = i2c;
|
||||
codec->hw_write = (hw_write_t) i2c_master_send;
|
||||
|
||||
ret = aic3x_init(socdev);
|
||||
if (ret < 0)
|
||||
printk(KERN_ERR "aic3x: failed to initialise AIC3X\n");
|
||||
return ret;
|
||||
i2c_set_clientdata(i2c, aic3x);
|
||||
|
||||
return aic3x_register(codec);
|
||||
}
|
||||
|
||||
static int aic3x_i2c_remove(struct i2c_client *client)
|
||||
{
|
||||
struct snd_soc_codec *codec = i2c_get_clientdata(client);
|
||||
kfree(codec->reg_cache);
|
||||
return 0;
|
||||
struct aic3x_priv *aic3x = i2c_get_clientdata(client);
|
||||
|
||||
return aic3x_unregister(aic3x);
|
||||
}
|
||||
|
||||
static const struct i2c_device_id aic3x_i2c_id[] = {
|
||||
{ "tlv320aic3x", 0 },
|
||||
{ "tlv320aic33", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, aic3x_i2c_id);
|
||||
|
@ -1320,56 +1345,28 @@ static struct i2c_driver aic3x_i2c_driver = {
|
|||
.name = "aic3x I2C Codec",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = aic3x_i2c_probe,
|
||||
.probe = aic3x_i2c_probe,
|
||||
.remove = aic3x_i2c_remove,
|
||||
.id_table = aic3x_i2c_id,
|
||||
};
|
||||
|
||||
static int aic3x_i2c_read(struct i2c_client *client, u8 *value, int len)
|
||||
static inline void aic3x_i2c_init(void)
|
||||
{
|
||||
value[0] = i2c_smbus_read_byte_data(client, value[0]);
|
||||
return (len == 1);
|
||||
}
|
||||
|
||||
static int aic3x_add_i2c_device(struct platform_device *pdev,
|
||||
const struct aic3x_setup_data *setup)
|
||||
{
|
||||
struct i2c_board_info info;
|
||||
struct i2c_adapter *adapter;
|
||||
struct i2c_client *client;
|
||||
int ret;
|
||||
|
||||
ret = i2c_add_driver(&aic3x_i2c_driver);
|
||||
if (ret != 0) {
|
||||
dev_err(&pdev->dev, "can't add i2c driver\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
memset(&info, 0, sizeof(struct i2c_board_info));
|
||||
info.addr = setup->i2c_address;
|
||||
strlcpy(info.type, "tlv320aic3x", I2C_NAME_SIZE);
|
||||
|
||||
adapter = i2c_get_adapter(setup->i2c_bus);
|
||||
if (!adapter) {
|
||||
dev_err(&pdev->dev, "can't get i2c adapter %d\n",
|
||||
setup->i2c_bus);
|
||||
goto err_driver;
|
||||
}
|
||||
|
||||
client = i2c_new_device(adapter, &info);
|
||||
i2c_put_adapter(adapter);
|
||||
if (!client) {
|
||||
dev_err(&pdev->dev, "can't add i2c device at 0x%x\n",
|
||||
(unsigned int)info.addr);
|
||||
goto err_driver;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_driver:
|
||||
i2c_del_driver(&aic3x_i2c_driver);
|
||||
return -ENODEV;
|
||||
if (ret)
|
||||
printk(KERN_ERR "%s: error regsitering i2c driver, %d\n",
|
||||
__func__, ret);
|
||||
}
|
||||
|
||||
static inline void aic3x_i2c_exit(void)
|
||||
{
|
||||
i2c_del_driver(&aic3x_i2c_driver);
|
||||
}
|
||||
#else
|
||||
static inline void aic3x_i2c_init(void) { }
|
||||
static inline void aic3x_i2c_exit(void) { }
|
||||
#endif
|
||||
|
||||
static int aic3x_probe(struct platform_device *pdev)
|
||||
|
@ -1377,43 +1374,51 @@ static int aic3x_probe(struct platform_device *pdev)
|
|||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
struct aic3x_setup_data *setup;
|
||||
struct snd_soc_codec *codec;
|
||||
struct aic3x_priv *aic3x;
|
||||
int ret = 0;
|
||||
|
||||
printk(KERN_INFO "AIC3X Audio Codec %s\n", AIC3X_VERSION);
|
||||
|
||||
setup = socdev->codec_data;
|
||||
codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
|
||||
if (codec == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
aic3x = kzalloc(sizeof(struct aic3x_priv), GFP_KERNEL);
|
||||
if (aic3x == NULL) {
|
||||
kfree(codec);
|
||||
return -ENOMEM;
|
||||
codec = aic3x_codec;
|
||||
if (!codec) {
|
||||
dev_err(&pdev->dev, "Codec not registered\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
codec->private_data = aic3x;
|
||||
socdev->card->codec = codec;
|
||||
mutex_init(&codec->mutex);
|
||||
INIT_LIST_HEAD(&codec->dapm_widgets);
|
||||
INIT_LIST_HEAD(&codec->dapm_paths);
|
||||
setup = socdev->codec_data;
|
||||
|
||||
aic3x_socdev = socdev;
|
||||
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
|
||||
if (setup->i2c_address) {
|
||||
codec->hw_write = (hw_write_t) i2c_master_send;
|
||||
codec->hw_read = (hw_read_t) aic3x_i2c_read;
|
||||
ret = aic3x_add_i2c_device(pdev, setup);
|
||||
if (setup) {
|
||||
/* setup GPIO functions */
|
||||
aic3x_write(codec, AIC3X_GPIO1_REG,
|
||||
(setup->gpio_func[0] & 0xf) << 4);
|
||||
aic3x_write(codec, AIC3X_GPIO2_REG,
|
||||
(setup->gpio_func[1] & 0xf) << 4);
|
||||
}
|
||||
#else
|
||||
/* Add other interfaces here */
|
||||
#endif
|
||||
|
||||
if (ret != 0) {
|
||||
kfree(codec->private_data);
|
||||
kfree(codec);
|
||||
/* register pcms */
|
||||
ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "aic3x: failed to create pcms\n");
|
||||
goto pcm_err;
|
||||
}
|
||||
|
||||
snd_soc_add_controls(codec, aic3x_snd_controls,
|
||||
ARRAY_SIZE(aic3x_snd_controls));
|
||||
|
||||
aic3x_add_widgets(codec);
|
||||
|
||||
ret = snd_soc_init_card(socdev);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "aic3x: failed to register card\n");
|
||||
goto card_err;
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
card_err:
|
||||
snd_soc_free_pcms(socdev);
|
||||
snd_soc_dapm_free(socdev);
|
||||
|
||||
pcm_err:
|
||||
kfree(codec->reg_cache);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -1428,12 +1433,8 @@ static int aic3x_remove(struct platform_device *pdev)
|
|||
|
||||
snd_soc_free_pcms(socdev);
|
||||
snd_soc_dapm_free(socdev);
|
||||
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
|
||||
i2c_unregister_device(codec->control_data);
|
||||
i2c_del_driver(&aic3x_i2c_driver);
|
||||
#endif
|
||||
kfree(codec->private_data);
|
||||
kfree(codec);
|
||||
|
||||
kfree(codec->reg_cache);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -1448,13 +1449,15 @@ EXPORT_SYMBOL_GPL(soc_codec_dev_aic3x);
|
|||
|
||||
static int __init aic3x_modinit(void)
|
||||
{
|
||||
return snd_soc_register_dai(&aic3x_dai);
|
||||
aic3x_i2c_init();
|
||||
|
||||
return 0;
|
||||
}
|
||||
module_init(aic3x_modinit);
|
||||
|
||||
static void __exit aic3x_exit(void)
|
||||
{
|
||||
snd_soc_unregister_dai(&aic3x_dai);
|
||||
aic3x_i2c_exit();
|
||||
}
|
||||
module_exit(aic3x_exit);
|
||||
|
||||
|
|
|
@ -282,8 +282,6 @@ int aic3x_headset_detected(struct snd_soc_codec *codec);
|
|||
int aic3x_button_pressed(struct snd_soc_codec *codec);
|
||||
|
||||
struct aic3x_setup_data {
|
||||
int i2c_bus;
|
||||
unsigned short i2c_address;
|
||||
unsigned int gpio_func[2];
|
||||
};
|
||||
|
||||
|
|
|
@ -225,55 +225,11 @@ static void twl4030_codec_mute(struct snd_soc_codec *codec, int mute)
|
|||
return;
|
||||
|
||||
if (mute) {
|
||||
/* Bypass the reg_cache and mute the volumes
|
||||
* Headset mute is done in it's own event handler
|
||||
* Things to mute: Earpiece, PreDrivL/R, CarkitL/R
|
||||
*/
|
||||
reg_val = twl4030_read_reg_cache(codec, TWL4030_REG_EAR_CTL);
|
||||
twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
|
||||
reg_val & (~TWL4030_EAR_GAIN),
|
||||
TWL4030_REG_EAR_CTL);
|
||||
|
||||
reg_val = twl4030_read_reg_cache(codec, TWL4030_REG_PREDL_CTL);
|
||||
twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
|
||||
reg_val & (~TWL4030_PREDL_GAIN),
|
||||
TWL4030_REG_PREDL_CTL);
|
||||
reg_val = twl4030_read_reg_cache(codec, TWL4030_REG_PREDR_CTL);
|
||||
twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
|
||||
reg_val & (~TWL4030_PREDR_GAIN),
|
||||
TWL4030_REG_PREDL_CTL);
|
||||
|
||||
reg_val = twl4030_read_reg_cache(codec, TWL4030_REG_PRECKL_CTL);
|
||||
twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
|
||||
reg_val & (~TWL4030_PRECKL_GAIN),
|
||||
TWL4030_REG_PRECKL_CTL);
|
||||
reg_val = twl4030_read_reg_cache(codec, TWL4030_REG_PRECKR_CTL);
|
||||
twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
|
||||
reg_val & (~TWL4030_PRECKR_GAIN),
|
||||
TWL4030_REG_PRECKR_CTL);
|
||||
|
||||
/* Disable PLL */
|
||||
reg_val = twl4030_read_reg_cache(codec, TWL4030_REG_APLL_CTL);
|
||||
reg_val &= ~TWL4030_APLL_EN;
|
||||
twl4030_write(codec, TWL4030_REG_APLL_CTL, reg_val);
|
||||
} else {
|
||||
/* Restore the volumes
|
||||
* Headset mute is done in it's own event handler
|
||||
* Things to restore: Earpiece, PreDrivL/R, CarkitL/R
|
||||
*/
|
||||
twl4030_write(codec, TWL4030_REG_EAR_CTL,
|
||||
twl4030_read_reg_cache(codec, TWL4030_REG_EAR_CTL));
|
||||
|
||||
twl4030_write(codec, TWL4030_REG_PREDL_CTL,
|
||||
twl4030_read_reg_cache(codec, TWL4030_REG_PREDL_CTL));
|
||||
twl4030_write(codec, TWL4030_REG_PREDR_CTL,
|
||||
twl4030_read_reg_cache(codec, TWL4030_REG_PREDR_CTL));
|
||||
|
||||
twl4030_write(codec, TWL4030_REG_PRECKL_CTL,
|
||||
twl4030_read_reg_cache(codec, TWL4030_REG_PRECKL_CTL));
|
||||
twl4030_write(codec, TWL4030_REG_PRECKR_CTL,
|
||||
twl4030_read_reg_cache(codec, TWL4030_REG_PRECKR_CTL));
|
||||
|
||||
/* Enable PLL */
|
||||
reg_val = twl4030_read_reg_cache(codec, TWL4030_REG_APLL_CTL);
|
||||
reg_val |= TWL4030_APLL_EN;
|
||||
|
@ -443,16 +399,20 @@ SOC_DAPM_ENUM("Route", twl4030_vibrapath_enum);
|
|||
|
||||
/* Left analog microphone selection */
|
||||
static const struct snd_kcontrol_new twl4030_dapm_analoglmic_controls[] = {
|
||||
SOC_DAPM_SINGLE("Main mic", TWL4030_REG_ANAMICL, 0, 1, 0),
|
||||
SOC_DAPM_SINGLE("Headset mic", TWL4030_REG_ANAMICL, 1, 1, 0),
|
||||
SOC_DAPM_SINGLE("AUXL", TWL4030_REG_ANAMICL, 2, 1, 0),
|
||||
SOC_DAPM_SINGLE("Carkit mic", TWL4030_REG_ANAMICL, 3, 1, 0),
|
||||
SOC_DAPM_SINGLE("Main Mic Capture Switch",
|
||||
TWL4030_REG_ANAMICL, 0, 1, 0),
|
||||
SOC_DAPM_SINGLE("Headset Mic Capture Switch",
|
||||
TWL4030_REG_ANAMICL, 1, 1, 0),
|
||||
SOC_DAPM_SINGLE("AUXL Capture Switch",
|
||||
TWL4030_REG_ANAMICL, 2, 1, 0),
|
||||
SOC_DAPM_SINGLE("Carkit Mic Capture Switch",
|
||||
TWL4030_REG_ANAMICL, 3, 1, 0),
|
||||
};
|
||||
|
||||
/* Right analog microphone selection */
|
||||
static const struct snd_kcontrol_new twl4030_dapm_analogrmic_controls[] = {
|
||||
SOC_DAPM_SINGLE("Sub mic", TWL4030_REG_ANAMICR, 0, 1, 0),
|
||||
SOC_DAPM_SINGLE("AUXR", TWL4030_REG_ANAMICR, 2, 1, 0),
|
||||
SOC_DAPM_SINGLE("Sub Mic Capture Switch", TWL4030_REG_ANAMICR, 0, 1, 0),
|
||||
SOC_DAPM_SINGLE("AUXR Capture Switch", TWL4030_REG_ANAMICR, 2, 1, 0),
|
||||
};
|
||||
|
||||
/* TX1 L/R Analog/Digital microphone selection */
|
||||
|
@ -560,6 +520,41 @@ static int micpath_event(struct snd_soc_dapm_widget *w,
|
|||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Output PGA builder:
|
||||
* Handle the muting and unmuting of the given output (turning off the
|
||||
* amplifier associated with the output pin)
|
||||
* On mute bypass the reg_cache and mute the volume
|
||||
* On unmute: restore the register content
|
||||
* Outputs handled in this way: Earpiece, PreDrivL/R, CarkitL/R
|
||||
*/
|
||||
#define TWL4030_OUTPUT_PGA(pin_name, reg, mask) \
|
||||
static int pin_name##pga_event(struct snd_soc_dapm_widget *w, \
|
||||
struct snd_kcontrol *kcontrol, int event) \
|
||||
{ \
|
||||
u8 reg_val; \
|
||||
\
|
||||
switch (event) { \
|
||||
case SND_SOC_DAPM_POST_PMU: \
|
||||
twl4030_write(w->codec, reg, \
|
||||
twl4030_read_reg_cache(w->codec, reg)); \
|
||||
break; \
|
||||
case SND_SOC_DAPM_POST_PMD: \
|
||||
reg_val = twl4030_read_reg_cache(w->codec, reg); \
|
||||
twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, \
|
||||
reg_val & (~mask), \
|
||||
reg); \
|
||||
break; \
|
||||
} \
|
||||
return 0; \
|
||||
}
|
||||
|
||||
TWL4030_OUTPUT_PGA(earpiece, TWL4030_REG_EAR_CTL, TWL4030_EAR_GAIN);
|
||||
TWL4030_OUTPUT_PGA(predrivel, TWL4030_REG_PREDL_CTL, TWL4030_PREDL_GAIN);
|
||||
TWL4030_OUTPUT_PGA(predriver, TWL4030_REG_PREDR_CTL, TWL4030_PREDR_GAIN);
|
||||
TWL4030_OUTPUT_PGA(carkitl, TWL4030_REG_PRECKL_CTL, TWL4030_PRECKL_GAIN);
|
||||
TWL4030_OUTPUT_PGA(carkitr, TWL4030_REG_PRECKR_CTL, TWL4030_PRECKR_GAIN);
|
||||
|
||||
static void handsfree_ramp(struct snd_soc_codec *codec, int reg, int ramp)
|
||||
{
|
||||
unsigned char hs_ctl;
|
||||
|
@ -620,6 +615,9 @@ static int handsfreerpga_event(struct snd_soc_dapm_widget *w,
|
|||
|
||||
static void headset_ramp(struct snd_soc_codec *codec, int ramp)
|
||||
{
|
||||
struct snd_soc_device *socdev = codec->socdev;
|
||||
struct twl4030_setup_data *setup = socdev->codec_data;
|
||||
|
||||
unsigned char hs_gain, hs_pop;
|
||||
struct twl4030_priv *twl4030 = codec->private_data;
|
||||
/* Base values for ramp delay calculation: 2^19 - 2^26 */
|
||||
|
@ -629,6 +627,17 @@ static void headset_ramp(struct snd_soc_codec *codec, int ramp)
|
|||
hs_gain = twl4030_read_reg_cache(codec, TWL4030_REG_HS_GAIN_SET);
|
||||
hs_pop = twl4030_read_reg_cache(codec, TWL4030_REG_HS_POPN_SET);
|
||||
|
||||
/* Enable external mute control, this dramatically reduces
|
||||
* the pop-noise */
|
||||
if (setup && setup->hs_extmute) {
|
||||
if (setup->set_hs_extmute) {
|
||||
setup->set_hs_extmute(1);
|
||||
} else {
|
||||
hs_pop |= TWL4030_EXTMUTE;
|
||||
twl4030_write(codec, TWL4030_REG_HS_POPN_SET, hs_pop);
|
||||
}
|
||||
}
|
||||
|
||||
if (ramp) {
|
||||
/* Headset ramp-up according to the TRM */
|
||||
hs_pop |= TWL4030_VMID_EN;
|
||||
|
@ -636,6 +645,9 @@ static void headset_ramp(struct snd_soc_codec *codec, int ramp)
|
|||
twl4030_write(codec, TWL4030_REG_HS_GAIN_SET, hs_gain);
|
||||
hs_pop |= TWL4030_RAMP_EN;
|
||||
twl4030_write(codec, TWL4030_REG_HS_POPN_SET, hs_pop);
|
||||
/* Wait ramp delay time + 1, so the VMID can settle */
|
||||
mdelay((ramp_base[(hs_pop & TWL4030_RAMP_DELAY) >> 2] /
|
||||
twl4030->sysclk) + 1);
|
||||
} else {
|
||||
/* Headset ramp-down _not_ according to
|
||||
* the TRM, but in a way that it is working */
|
||||
|
@ -652,6 +664,16 @@ static void headset_ramp(struct snd_soc_codec *codec, int ramp)
|
|||
hs_pop &= ~TWL4030_VMID_EN;
|
||||
twl4030_write(codec, TWL4030_REG_HS_POPN_SET, hs_pop);
|
||||
}
|
||||
|
||||
/* Disable external mute */
|
||||
if (setup && setup->hs_extmute) {
|
||||
if (setup->set_hs_extmute) {
|
||||
setup->set_hs_extmute(0);
|
||||
} else {
|
||||
hs_pop &= ~TWL4030_EXTMUTE;
|
||||
twl4030_write(codec, TWL4030_REG_HS_POPN_SET, hs_pop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int headsetlpga_event(struct snd_soc_dapm_widget *w,
|
||||
|
@ -712,7 +734,19 @@ static int bypass_event(struct snd_soc_dapm_widget *w,
|
|||
|
||||
reg = twl4030_read_reg_cache(w->codec, m->reg);
|
||||
|
||||
if (m->reg <= TWL4030_REG_ARXR2_APGA_CTL) {
|
||||
/*
|
||||
* bypass_state[0:3] - analog HiFi bypass
|
||||
* bypass_state[4] - analog voice bypass
|
||||
* bypass_state[5] - digital voice bypass
|
||||
* bypass_state[6:7] - digital HiFi bypass
|
||||
*/
|
||||
if (m->reg == TWL4030_REG_VSTPGA) {
|
||||
/* Voice digital bypass */
|
||||
if (reg)
|
||||
twl4030->bypass_state |= (1 << 5);
|
||||
else
|
||||
twl4030->bypass_state &= ~(1 << 5);
|
||||
} else if (m->reg <= TWL4030_REG_ARXR2_APGA_CTL) {
|
||||
/* Analog bypass */
|
||||
if (reg & (1 << m->shift))
|
||||
twl4030->bypass_state |=
|
||||
|
@ -726,12 +760,6 @@ static int bypass_event(struct snd_soc_dapm_widget *w,
|
|||
twl4030->bypass_state |= (1 << 4);
|
||||
else
|
||||
twl4030->bypass_state &= ~(1 << 4);
|
||||
} else if (m->reg == TWL4030_REG_VSTPGA) {
|
||||
/* Voice digital bypass */
|
||||
if (reg)
|
||||
twl4030->bypass_state |= (1 << 5);
|
||||
else
|
||||
twl4030->bypass_state &= ~(1 << 5);
|
||||
} else {
|
||||
/* Digital bypass */
|
||||
if (reg & (0x7 << m->shift))
|
||||
|
@ -924,7 +952,7 @@ static const struct soc_enum twl4030_op_modes_enum =
|
|||
ARRAY_SIZE(twl4030_op_modes_texts),
|
||||
twl4030_op_modes_texts);
|
||||
|
||||
int snd_soc_put_twl4030_opmode_enum_double(struct snd_kcontrol *kcontrol,
|
||||
static int snd_soc_put_twl4030_opmode_enum_double(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
|
||||
|
@ -1005,6 +1033,16 @@ static DECLARE_TLV_DB_SCALE(digital_capture_tlv, 0, 100, 0);
|
|||
*/
|
||||
static DECLARE_TLV_DB_SCALE(input_gain_tlv, 0, 600, 0);
|
||||
|
||||
/* AVADC clock priority */
|
||||
static const char *twl4030_avadc_clk_priority_texts[] = {
|
||||
"Voice high priority", "HiFi high priority"
|
||||
};
|
||||
|
||||
static const struct soc_enum twl4030_avadc_clk_priority_enum =
|
||||
SOC_ENUM_SINGLE(TWL4030_REG_AVADC_CTL, 2,
|
||||
ARRAY_SIZE(twl4030_avadc_clk_priority_texts),
|
||||
twl4030_avadc_clk_priority_texts);
|
||||
|
||||
static const char *twl4030_rampdelay_texts[] = {
|
||||
"27/20/14 ms", "55/40/27 ms", "109/81/55 ms", "218/161/109 ms",
|
||||
"437/323/218 ms", "874/645/437 ms", "1748/1291/874 ms",
|
||||
|
@ -1106,6 +1144,8 @@ static const struct snd_kcontrol_new twl4030_snd_controls[] = {
|
|||
SOC_DOUBLE_TLV("Analog Capture Volume", TWL4030_REG_ANAMIC_GAIN,
|
||||
0, 3, 5, 0, input_gain_tlv),
|
||||
|
||||
SOC_ENUM("AVADC Clock Priority", twl4030_avadc_clk_priority_enum),
|
||||
|
||||
SOC_ENUM("HS ramp delay", twl4030_rampdelay_enum),
|
||||
|
||||
SOC_ENUM("Vibra H-bridge mode", twl4030_vibradirmode_enum),
|
||||
|
@ -1208,13 +1248,22 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = {
|
|||
SND_SOC_DAPM_MIXER("Earpiece Mixer", SND_SOC_NOPM, 0, 0,
|
||||
&twl4030_dapm_earpiece_controls[0],
|
||||
ARRAY_SIZE(twl4030_dapm_earpiece_controls)),
|
||||
SND_SOC_DAPM_PGA_E("Earpiece PGA", SND_SOC_NOPM,
|
||||
0, 0, NULL, 0, earpiecepga_event,
|
||||
SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD),
|
||||
/* PreDrivL/R */
|
||||
SND_SOC_DAPM_MIXER("PredriveL Mixer", SND_SOC_NOPM, 0, 0,
|
||||
&twl4030_dapm_predrivel_controls[0],
|
||||
ARRAY_SIZE(twl4030_dapm_predrivel_controls)),
|
||||
SND_SOC_DAPM_PGA_E("PredriveL PGA", SND_SOC_NOPM,
|
||||
0, 0, NULL, 0, predrivelpga_event,
|
||||
SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD),
|
||||
SND_SOC_DAPM_MIXER("PredriveR Mixer", SND_SOC_NOPM, 0, 0,
|
||||
&twl4030_dapm_predriver_controls[0],
|
||||
ARRAY_SIZE(twl4030_dapm_predriver_controls)),
|
||||
SND_SOC_DAPM_PGA_E("PredriveR PGA", SND_SOC_NOPM,
|
||||
0, 0, NULL, 0, predriverpga_event,
|
||||
SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD),
|
||||
/* HeadsetL/R */
|
||||
SND_SOC_DAPM_MIXER("HeadsetL Mixer", SND_SOC_NOPM, 0, 0,
|
||||
&twl4030_dapm_hsol_controls[0],
|
||||
|
@ -1232,22 +1281,28 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = {
|
|||
SND_SOC_DAPM_MIXER("CarkitL Mixer", SND_SOC_NOPM, 0, 0,
|
||||
&twl4030_dapm_carkitl_controls[0],
|
||||
ARRAY_SIZE(twl4030_dapm_carkitl_controls)),
|
||||
SND_SOC_DAPM_PGA_E("CarkitL PGA", SND_SOC_NOPM,
|
||||
0, 0, NULL, 0, carkitlpga_event,
|
||||
SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD),
|
||||
SND_SOC_DAPM_MIXER("CarkitR Mixer", SND_SOC_NOPM, 0, 0,
|
||||
&twl4030_dapm_carkitr_controls[0],
|
||||
ARRAY_SIZE(twl4030_dapm_carkitr_controls)),
|
||||
SND_SOC_DAPM_PGA_E("CarkitR PGA", SND_SOC_NOPM,
|
||||
0, 0, NULL, 0, carkitrpga_event,
|
||||
SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD),
|
||||
|
||||
/* Output MUX controls */
|
||||
/* HandsfreeL/R */
|
||||
SND_SOC_DAPM_MUX("HandsfreeL Mux", SND_SOC_NOPM, 0, 0,
|
||||
&twl4030_dapm_handsfreel_control),
|
||||
SND_SOC_DAPM_SWITCH("HandsfreeL Switch", SND_SOC_NOPM, 0, 0,
|
||||
SND_SOC_DAPM_SWITCH("HandsfreeL", SND_SOC_NOPM, 0, 0,
|
||||
&twl4030_dapm_handsfreelmute_control),
|
||||
SND_SOC_DAPM_PGA_E("HandsfreeL PGA", SND_SOC_NOPM,
|
||||
0, 0, NULL, 0, handsfreelpga_event,
|
||||
SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD),
|
||||
SND_SOC_DAPM_MUX("HandsfreeR Mux", SND_SOC_NOPM, 5, 0,
|
||||
&twl4030_dapm_handsfreer_control),
|
||||
SND_SOC_DAPM_SWITCH("HandsfreeR Switch", SND_SOC_NOPM, 0, 0,
|
||||
SND_SOC_DAPM_SWITCH("HandsfreeR", SND_SOC_NOPM, 0, 0,
|
||||
&twl4030_dapm_handsfreermute_control),
|
||||
SND_SOC_DAPM_PGA_E("HandsfreeR PGA", SND_SOC_NOPM,
|
||||
0, 0, NULL, 0, handsfreerpga_event,
|
||||
|
@ -1282,11 +1337,11 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = {
|
|||
SND_SOC_DAPM_POST_REG),
|
||||
|
||||
/* Analog input mixers for the capture amplifiers */
|
||||
SND_SOC_DAPM_MIXER("Analog Left Capture Route",
|
||||
SND_SOC_DAPM_MIXER("Analog Left",
|
||||
TWL4030_REG_ANAMICL, 4, 0,
|
||||
&twl4030_dapm_analoglmic_controls[0],
|
||||
ARRAY_SIZE(twl4030_dapm_analoglmic_controls)),
|
||||
SND_SOC_DAPM_MIXER("Analog Right Capture Route",
|
||||
SND_SOC_DAPM_MIXER("Analog Right",
|
||||
TWL4030_REG_ANAMICR, 4, 0,
|
||||
&twl4030_dapm_analogrmic_controls[0],
|
||||
ARRAY_SIZE(twl4030_dapm_analogrmic_controls)),
|
||||
|
@ -1326,16 +1381,19 @@ static const struct snd_soc_dapm_route intercon[] = {
|
|||
{"Earpiece Mixer", "AudioL1", "Analog L1 Playback Mixer"},
|
||||
{"Earpiece Mixer", "AudioL2", "Analog L2 Playback Mixer"},
|
||||
{"Earpiece Mixer", "AudioR1", "Analog R1 Playback Mixer"},
|
||||
{"Earpiece PGA", NULL, "Earpiece Mixer"},
|
||||
/* PreDrivL */
|
||||
{"PredriveL Mixer", "Voice", "Analog Voice Playback Mixer"},
|
||||
{"PredriveL Mixer", "AudioL1", "Analog L1 Playback Mixer"},
|
||||
{"PredriveL Mixer", "AudioL2", "Analog L2 Playback Mixer"},
|
||||
{"PredriveL Mixer", "AudioR2", "Analog R2 Playback Mixer"},
|
||||
{"PredriveL PGA", NULL, "PredriveL Mixer"},
|
||||
/* PreDrivR */
|
||||
{"PredriveR Mixer", "Voice", "Analog Voice Playback Mixer"},
|
||||
{"PredriveR Mixer", "AudioR1", "Analog R1 Playback Mixer"},
|
||||
{"PredriveR Mixer", "AudioR2", "Analog R2 Playback Mixer"},
|
||||
{"PredriveR Mixer", "AudioL2", "Analog L2 Playback Mixer"},
|
||||
{"PredriveR PGA", NULL, "PredriveR Mixer"},
|
||||
/* HeadsetL */
|
||||
{"HeadsetL Mixer", "Voice", "Analog Voice Playback Mixer"},
|
||||
{"HeadsetL Mixer", "AudioL1", "Analog L1 Playback Mixer"},
|
||||
|
@ -1350,24 +1408,26 @@ static const struct snd_soc_dapm_route intercon[] = {
|
|||
{"CarkitL Mixer", "Voice", "Analog Voice Playback Mixer"},
|
||||
{"CarkitL Mixer", "AudioL1", "Analog L1 Playback Mixer"},
|
||||
{"CarkitL Mixer", "AudioL2", "Analog L2 Playback Mixer"},
|
||||
{"CarkitL PGA", NULL, "CarkitL Mixer"},
|
||||
/* CarkitR */
|
||||
{"CarkitR Mixer", "Voice", "Analog Voice Playback Mixer"},
|
||||
{"CarkitR Mixer", "AudioR1", "Analog R1 Playback Mixer"},
|
||||
{"CarkitR Mixer", "AudioR2", "Analog R2 Playback Mixer"},
|
||||
{"CarkitR PGA", NULL, "CarkitR Mixer"},
|
||||
/* HandsfreeL */
|
||||
{"HandsfreeL Mux", "Voice", "Analog Voice Playback Mixer"},
|
||||
{"HandsfreeL Mux", "AudioL1", "Analog L1 Playback Mixer"},
|
||||
{"HandsfreeL Mux", "AudioL2", "Analog L2 Playback Mixer"},
|
||||
{"HandsfreeL Mux", "AudioR2", "Analog R2 Playback Mixer"},
|
||||
{"HandsfreeL Switch", "Switch", "HandsfreeL Mux"},
|
||||
{"HandsfreeL PGA", NULL, "HandsfreeL Switch"},
|
||||
{"HandsfreeL", "Switch", "HandsfreeL Mux"},
|
||||
{"HandsfreeL PGA", NULL, "HandsfreeL"},
|
||||
/* HandsfreeR */
|
||||
{"HandsfreeR Mux", "Voice", "Analog Voice Playback Mixer"},
|
||||
{"HandsfreeR Mux", "AudioR1", "Analog R1 Playback Mixer"},
|
||||
{"HandsfreeR Mux", "AudioR2", "Analog R2 Playback Mixer"},
|
||||
{"HandsfreeR Mux", "AudioL2", "Analog L2 Playback Mixer"},
|
||||
{"HandsfreeR Switch", "Switch", "HandsfreeR Mux"},
|
||||
{"HandsfreeR PGA", NULL, "HandsfreeR Switch"},
|
||||
{"HandsfreeR", "Switch", "HandsfreeR Mux"},
|
||||
{"HandsfreeR PGA", NULL, "HandsfreeR"},
|
||||
/* Vibra */
|
||||
{"Vibra Mux", "AudioL1", "DAC Left1"},
|
||||
{"Vibra Mux", "AudioR1", "DAC Right1"},
|
||||
|
@ -1377,29 +1437,29 @@ static const struct snd_soc_dapm_route intercon[] = {
|
|||
/* outputs */
|
||||
{"OUTL", NULL, "Analog L2 Playback Mixer"},
|
||||
{"OUTR", NULL, "Analog R2 Playback Mixer"},
|
||||
{"EARPIECE", NULL, "Earpiece Mixer"},
|
||||
{"PREDRIVEL", NULL, "PredriveL Mixer"},
|
||||
{"PREDRIVER", NULL, "PredriveR Mixer"},
|
||||
{"EARPIECE", NULL, "Earpiece PGA"},
|
||||
{"PREDRIVEL", NULL, "PredriveL PGA"},
|
||||
{"PREDRIVER", NULL, "PredriveR PGA"},
|
||||
{"HSOL", NULL, "HeadsetL PGA"},
|
||||
{"HSOR", NULL, "HeadsetR PGA"},
|
||||
{"CARKITL", NULL, "CarkitL Mixer"},
|
||||
{"CARKITR", NULL, "CarkitR Mixer"},
|
||||
{"CARKITL", NULL, "CarkitL PGA"},
|
||||
{"CARKITR", NULL, "CarkitR PGA"},
|
||||
{"HFL", NULL, "HandsfreeL PGA"},
|
||||
{"HFR", NULL, "HandsfreeR PGA"},
|
||||
{"Vibra Route", "Audio", "Vibra Mux"},
|
||||
{"VIBRA", NULL, "Vibra Route"},
|
||||
|
||||
/* Capture path */
|
||||
{"Analog Left Capture Route", "Main mic", "MAINMIC"},
|
||||
{"Analog Left Capture Route", "Headset mic", "HSMIC"},
|
||||
{"Analog Left Capture Route", "AUXL", "AUXL"},
|
||||
{"Analog Left Capture Route", "Carkit mic", "CARKITMIC"},
|
||||
{"Analog Left", "Main Mic Capture Switch", "MAINMIC"},
|
||||
{"Analog Left", "Headset Mic Capture Switch", "HSMIC"},
|
||||
{"Analog Left", "AUXL Capture Switch", "AUXL"},
|
||||
{"Analog Left", "Carkit Mic Capture Switch", "CARKITMIC"},
|
||||
|
||||
{"Analog Right Capture Route", "Sub mic", "SUBMIC"},
|
||||
{"Analog Right Capture Route", "AUXR", "AUXR"},
|
||||
{"Analog Right", "Sub Mic Capture Switch", "SUBMIC"},
|
||||
{"Analog Right", "AUXR Capture Switch", "AUXR"},
|
||||
|
||||
{"ADC Physical Left", NULL, "Analog Left Capture Route"},
|
||||
{"ADC Physical Right", NULL, "Analog Right Capture Route"},
|
||||
{"ADC Physical Left", NULL, "Analog Left"},
|
||||
{"ADC Physical Right", NULL, "Analog Right"},
|
||||
|
||||
{"Digimic0 Enable", NULL, "DIGIMIC0"},
|
||||
{"Digimic1 Enable", NULL, "DIGIMIC1"},
|
||||
|
@ -1423,11 +1483,11 @@ static const struct snd_soc_dapm_route intercon[] = {
|
|||
{"ADC Virtual Right2", NULL, "TX2 Capture Route"},
|
||||
|
||||
/* Analog bypass routes */
|
||||
{"Right1 Analog Loopback", "Switch", "Analog Right Capture Route"},
|
||||
{"Left1 Analog Loopback", "Switch", "Analog Left Capture Route"},
|
||||
{"Right2 Analog Loopback", "Switch", "Analog Right Capture Route"},
|
||||
{"Left2 Analog Loopback", "Switch", "Analog Left Capture Route"},
|
||||
{"Voice Analog Loopback", "Switch", "Analog Left Capture Route"},
|
||||
{"Right1 Analog Loopback", "Switch", "Analog Right"},
|
||||
{"Left1 Analog Loopback", "Switch", "Analog Left"},
|
||||
{"Right2 Analog Loopback", "Switch", "Analog Right"},
|
||||
{"Left2 Analog Loopback", "Switch", "Analog Left"},
|
||||
{"Voice Analog Loopback", "Switch", "Analog Left"},
|
||||
|
||||
{"Analog R1 Playback Mixer", NULL, "Right1 Analog Loopback"},
|
||||
{"Analog L1 Playback Mixer", NULL, "Left1 Analog Loopback"},
|
||||
|
@ -1609,8 +1669,6 @@ static int twl4030_hw_params(struct snd_pcm_substream *substream,
|
|||
|
||||
/* If the substream has 4 channel, do the necessary setup */
|
||||
if (params_channels(params) == 4) {
|
||||
u8 format, mode;
|
||||
|
||||
format = twl4030_read_reg_cache(codec, TWL4030_REG_AUDIO_IF);
|
||||
mode = twl4030_read_reg_cache(codec, TWL4030_REG_CODEC_MODE);
|
||||
|
||||
|
@ -1806,6 +1864,19 @@ static int twl4030_set_dai_fmt(struct snd_soc_dai *codec_dai,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int twl4030_set_tristate(struct snd_soc_dai *dai, int tristate)
|
||||
{
|
||||
struct snd_soc_codec *codec = dai->codec;
|
||||
u8 reg = twl4030_read_reg_cache(codec, TWL4030_REG_AUDIO_IF);
|
||||
|
||||
if (tristate)
|
||||
reg |= TWL4030_AIF_TRI_EN;
|
||||
else
|
||||
reg &= ~TWL4030_AIF_TRI_EN;
|
||||
|
||||
return twl4030_write(codec, TWL4030_REG_AUDIO_IF, reg);
|
||||
}
|
||||
|
||||
/* In case of voice mode, the RX1 L(VRX) for downlink and the TX2 L/R
|
||||
* (VTXL, VTXR) for uplink has to be enabled/disabled. */
|
||||
static void twl4030_voice_enable(struct snd_soc_codec *codec, int direction,
|
||||
|
@ -1948,7 +2019,7 @@ static int twl4030_voice_set_dai_fmt(struct snd_soc_dai *codec_dai,
|
|||
|
||||
/* set master/slave audio interface */
|
||||
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
|
||||
case SND_SOC_DAIFMT_CBS_CFM:
|
||||
case SND_SOC_DAIFMT_CBM_CFM:
|
||||
format &= ~(TWL4030_VIF_SLAVE_EN);
|
||||
break;
|
||||
case SND_SOC_DAIFMT_CBS_CFS:
|
||||
|
@ -1980,6 +2051,19 @@ static int twl4030_voice_set_dai_fmt(struct snd_soc_dai *codec_dai,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int twl4030_voice_set_tristate(struct snd_soc_dai *dai, int tristate)
|
||||
{
|
||||
struct snd_soc_codec *codec = dai->codec;
|
||||
u8 reg = twl4030_read_reg_cache(codec, TWL4030_REG_VOICE_IF);
|
||||
|
||||
if (tristate)
|
||||
reg |= TWL4030_VIF_TRI_EN;
|
||||
else
|
||||
reg &= ~TWL4030_VIF_TRI_EN;
|
||||
|
||||
return twl4030_write(codec, TWL4030_REG_VOICE_IF, reg);
|
||||
}
|
||||
|
||||
#define TWL4030_RATES (SNDRV_PCM_RATE_8000_48000)
|
||||
#define TWL4030_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FORMAT_S24_LE)
|
||||
|
||||
|
@ -1989,6 +2073,7 @@ static struct snd_soc_dai_ops twl4030_dai_ops = {
|
|||
.hw_params = twl4030_hw_params,
|
||||
.set_sysclk = twl4030_set_dai_sysclk,
|
||||
.set_fmt = twl4030_set_dai_fmt,
|
||||
.set_tristate = twl4030_set_tristate,
|
||||
};
|
||||
|
||||
static struct snd_soc_dai_ops twl4030_dai_voice_ops = {
|
||||
|
@ -1997,6 +2082,7 @@ static struct snd_soc_dai_ops twl4030_dai_voice_ops = {
|
|||
.hw_params = twl4030_voice_hw_params,
|
||||
.set_sysclk = twl4030_voice_set_dai_sysclk,
|
||||
.set_fmt = twl4030_voice_set_dai_fmt,
|
||||
.set_tristate = twl4030_voice_set_tristate,
|
||||
};
|
||||
|
||||
struct snd_soc_dai twl4030_dai[] = {
|
||||
|
|
|
@ -274,6 +274,8 @@ extern struct snd_soc_codec_device soc_codec_dev_twl4030;
|
|||
struct twl4030_setup_data {
|
||||
unsigned int ramp_delay_value;
|
||||
unsigned int sysclk;
|
||||
unsigned int hs_extmute:1;
|
||||
void (*set_hs_extmute)(int mute);
|
||||
};
|
||||
|
||||
#endif /* End of __TWL4030_AUDIO_H__ */
|
||||
|
|
|
@ -163,7 +163,7 @@ static int uda134x_mute(struct snd_soc_dai *dai, int mute)
|
|||
else
|
||||
mute_reg &= ~(1<<2);
|
||||
|
||||
uda134x_write(codec, UDA134X_DATA010, mute_reg & ~(1<<2));
|
||||
uda134x_write(codec, UDA134X_DATA010, mute_reg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -5,9 +5,7 @@
|
|||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* Copyright (c) 2007 Philipp Zabel <philipp.zabel@gmail.com>
|
||||
* Improved support for DAPM and audio routing/mixing capabilities,
|
||||
* added TLV support.
|
||||
* Copyright (c) 2007-2009 Philipp Zabel <philipp.zabel@gmail.com>
|
||||
*
|
||||
* Modified by Richard Purdie <richard@openedhand.com> to fit into SoC
|
||||
* codec model.
|
||||
|
@ -19,26 +17,32 @@
|
|||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/ioctl.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/control.h>
|
||||
#include <sound/initval.h>
|
||||
#include <sound/info.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/soc-dapm.h>
|
||||
#include <sound/tlv.h>
|
||||
#include <sound/uda1380.h>
|
||||
|
||||
#include "uda1380.h"
|
||||
|
||||
static struct work_struct uda1380_work;
|
||||
static struct snd_soc_codec *uda1380_codec;
|
||||
|
||||
/* codec private data */
|
||||
struct uda1380_priv {
|
||||
struct snd_soc_codec codec;
|
||||
u16 reg_cache[UDA1380_CACHEREGNUM];
|
||||
unsigned int dac_clk;
|
||||
struct work_struct work;
|
||||
};
|
||||
|
||||
/*
|
||||
* uda1380 register cache
|
||||
*/
|
||||
|
@ -473,6 +477,7 @@ static int uda1380_trigger(struct snd_pcm_substream *substream, int cmd,
|
|||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_device *socdev = rtd->socdev;
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
struct uda1380_priv *uda1380 = codec->private_data;
|
||||
int mixer = uda1380_read_reg_cache(codec, UDA1380_MIXER);
|
||||
|
||||
switch (cmd) {
|
||||
|
@ -480,13 +485,13 @@ static int uda1380_trigger(struct snd_pcm_substream *substream, int cmd,
|
|||
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
||||
uda1380_write_reg_cache(codec, UDA1380_MIXER,
|
||||
mixer & ~R14_SILENCE);
|
||||
schedule_work(&uda1380_work);
|
||||
schedule_work(&uda1380->work);
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||
uda1380_write_reg_cache(codec, UDA1380_MIXER,
|
||||
mixer | R14_SILENCE);
|
||||
schedule_work(&uda1380_work);
|
||||
schedule_work(&uda1380->work);
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
|
@ -670,44 +675,33 @@ static int uda1380_resume(struct platform_device *pdev)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* initialise the UDA1380 driver
|
||||
* register mixer and dsp interfaces with the kernel
|
||||
*/
|
||||
static int uda1380_init(struct snd_soc_device *socdev, int dac_clk)
|
||||
static int uda1380_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
struct snd_soc_codec *codec;
|
||||
struct uda1380_platform_data *pdata;
|
||||
int ret = 0;
|
||||
|
||||
codec->name = "UDA1380";
|
||||
codec->owner = THIS_MODULE;
|
||||
codec->read = uda1380_read_reg_cache;
|
||||
codec->write = uda1380_write;
|
||||
codec->set_bias_level = uda1380_set_bias_level;
|
||||
codec->dai = uda1380_dai;
|
||||
codec->num_dai = ARRAY_SIZE(uda1380_dai);
|
||||
codec->reg_cache = kmemdup(uda1380_reg, sizeof(uda1380_reg),
|
||||
GFP_KERNEL);
|
||||
if (codec->reg_cache == NULL)
|
||||
return -ENOMEM;
|
||||
codec->reg_cache_size = ARRAY_SIZE(uda1380_reg);
|
||||
codec->reg_cache_step = 1;
|
||||
uda1380_reset(codec);
|
||||
if (uda1380_codec == NULL) {
|
||||
dev_err(&pdev->dev, "Codec device not registered\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
uda1380_codec = codec;
|
||||
INIT_WORK(&uda1380_work, uda1380_flush_work);
|
||||
socdev->card->codec = uda1380_codec;
|
||||
codec = uda1380_codec;
|
||||
pdata = codec->dev->platform_data;
|
||||
|
||||
/* register pcms */
|
||||
ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
|
||||
if (ret < 0) {
|
||||
pr_err("uda1380: failed to create pcms\n");
|
||||
dev_err(codec->dev, "failed to create pcms: %d\n", ret);
|
||||
goto pcm_err;
|
||||
}
|
||||
|
||||
/* power on device */
|
||||
uda1380_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
|
||||
/* set clock input */
|
||||
switch (dac_clk) {
|
||||
switch (pdata->dac_clk) {
|
||||
case UDA1380_DAC_CLK_SYSCLK:
|
||||
uda1380_write(codec, UDA1380_CLK, 0);
|
||||
break;
|
||||
|
@ -716,13 +710,12 @@ static int uda1380_init(struct snd_soc_device *socdev, int dac_clk)
|
|||
break;
|
||||
}
|
||||
|
||||
/* uda1380 init */
|
||||
snd_soc_add_controls(codec, uda1380_snd_controls,
|
||||
ARRAY_SIZE(uda1380_snd_controls));
|
||||
uda1380_add_widgets(codec);
|
||||
ret = snd_soc_init_card(socdev);
|
||||
if (ret < 0) {
|
||||
pr_err("uda1380: failed to register card\n");
|
||||
dev_err(codec->dev, "failed to register card: %d\n", ret);
|
||||
goto card_err;
|
||||
}
|
||||
|
||||
|
@ -732,36 +725,164 @@ card_err:
|
|||
snd_soc_free_pcms(socdev);
|
||||
snd_soc_dapm_free(socdev);
|
||||
pcm_err:
|
||||
kfree(codec->reg_cache);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct snd_soc_device *uda1380_socdev;
|
||||
/* power down chip */
|
||||
static int uda1380_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
|
||||
if (codec->control_data)
|
||||
uda1380_set_bias_level(codec, SND_SOC_BIAS_OFF);
|
||||
|
||||
snd_soc_free_pcms(socdev);
|
||||
snd_soc_dapm_free(socdev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct snd_soc_codec_device soc_codec_dev_uda1380 = {
|
||||
.probe = uda1380_probe,
|
||||
.remove = uda1380_remove,
|
||||
.suspend = uda1380_suspend,
|
||||
.resume = uda1380_resume,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(soc_codec_dev_uda1380);
|
||||
|
||||
static int uda1380_register(struct uda1380_priv *uda1380)
|
||||
{
|
||||
int ret, i;
|
||||
struct snd_soc_codec *codec = &uda1380->codec;
|
||||
struct uda1380_platform_data *pdata = codec->dev->platform_data;
|
||||
|
||||
if (uda1380_codec) {
|
||||
dev_err(codec->dev, "Another UDA1380 is registered\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!pdata || !pdata->gpio_power || !pdata->gpio_reset)
|
||||
return -EINVAL;
|
||||
|
||||
ret = gpio_request(pdata->gpio_power, "uda1380 power");
|
||||
if (ret)
|
||||
goto err_out;
|
||||
ret = gpio_request(pdata->gpio_reset, "uda1380 reset");
|
||||
if (ret)
|
||||
goto err_gpio;
|
||||
|
||||
gpio_direction_output(pdata->gpio_power, 1);
|
||||
|
||||
/* we may need to have the clock running here - pH5 */
|
||||
gpio_direction_output(pdata->gpio_reset, 1);
|
||||
udelay(5);
|
||||
gpio_set_value(pdata->gpio_reset, 0);
|
||||
|
||||
mutex_init(&codec->mutex);
|
||||
INIT_LIST_HEAD(&codec->dapm_widgets);
|
||||
INIT_LIST_HEAD(&codec->dapm_paths);
|
||||
|
||||
codec->private_data = uda1380;
|
||||
codec->name = "UDA1380";
|
||||
codec->owner = THIS_MODULE;
|
||||
codec->read = uda1380_read_reg_cache;
|
||||
codec->write = uda1380_write;
|
||||
codec->bias_level = SND_SOC_BIAS_OFF;
|
||||
codec->set_bias_level = uda1380_set_bias_level;
|
||||
codec->dai = uda1380_dai;
|
||||
codec->num_dai = ARRAY_SIZE(uda1380_dai);
|
||||
codec->reg_cache_size = ARRAY_SIZE(uda1380_reg);
|
||||
codec->reg_cache = &uda1380->reg_cache;
|
||||
codec->reg_cache_step = 1;
|
||||
|
||||
memcpy(codec->reg_cache, uda1380_reg, sizeof(uda1380_reg));
|
||||
|
||||
ret = uda1380_reset(codec);
|
||||
if (ret < 0) {
|
||||
dev_err(codec->dev, "Failed to issue reset\n");
|
||||
goto err_reset;
|
||||
}
|
||||
|
||||
INIT_WORK(&uda1380->work, uda1380_flush_work);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(uda1380_dai); i++)
|
||||
uda1380_dai[i].dev = codec->dev;
|
||||
|
||||
uda1380_codec = codec;
|
||||
|
||||
ret = snd_soc_register_codec(codec);
|
||||
if (ret != 0) {
|
||||
dev_err(codec->dev, "Failed to register codec: %d\n", ret);
|
||||
goto err_reset;
|
||||
}
|
||||
|
||||
ret = snd_soc_register_dais(uda1380_dai, ARRAY_SIZE(uda1380_dai));
|
||||
if (ret != 0) {
|
||||
dev_err(codec->dev, "Failed to register DAIs: %d\n", ret);
|
||||
goto err_dai;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_dai:
|
||||
snd_soc_unregister_codec(codec);
|
||||
err_reset:
|
||||
gpio_set_value(pdata->gpio_power, 0);
|
||||
gpio_free(pdata->gpio_reset);
|
||||
err_gpio:
|
||||
gpio_free(pdata->gpio_power);
|
||||
err_out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void uda1380_unregister(struct uda1380_priv *uda1380)
|
||||
{
|
||||
struct snd_soc_codec *codec = &uda1380->codec;
|
||||
struct uda1380_platform_data *pdata = codec->dev->platform_data;
|
||||
|
||||
snd_soc_unregister_dais(uda1380_dai, ARRAY_SIZE(uda1380_dai));
|
||||
snd_soc_unregister_codec(&uda1380->codec);
|
||||
|
||||
gpio_set_value(pdata->gpio_power, 0);
|
||||
gpio_free(pdata->gpio_reset);
|
||||
gpio_free(pdata->gpio_power);
|
||||
|
||||
kfree(uda1380);
|
||||
uda1380_codec = NULL;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
|
||||
|
||||
static int uda1380_i2c_probe(struct i2c_client *i2c,
|
||||
const struct i2c_device_id *id)
|
||||
static __devinit int uda1380_i2c_probe(struct i2c_client *i2c,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct snd_soc_device *socdev = uda1380_socdev;
|
||||
struct uda1380_setup_data *setup = socdev->codec_data;
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
struct uda1380_priv *uda1380;
|
||||
struct snd_soc_codec *codec;
|
||||
int ret;
|
||||
|
||||
i2c_set_clientdata(i2c, codec);
|
||||
uda1380 = kzalloc(sizeof(struct uda1380_priv), GFP_KERNEL);
|
||||
if (uda1380 == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
codec = &uda1380->codec;
|
||||
codec->hw_write = (hw_write_t)i2c_master_send;
|
||||
|
||||
i2c_set_clientdata(i2c, uda1380);
|
||||
codec->control_data = i2c;
|
||||
|
||||
ret = uda1380_init(socdev, setup->dac_clk);
|
||||
if (ret < 0)
|
||||
pr_err("uda1380: failed to initialise UDA1380\n");
|
||||
codec->dev = &i2c->dev;
|
||||
|
||||
ret = uda1380_register(uda1380);
|
||||
if (ret != 0)
|
||||
kfree(uda1380);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int uda1380_i2c_remove(struct i2c_client *client)
|
||||
static int __devexit uda1380_i2c_remove(struct i2c_client *i2c)
|
||||
{
|
||||
struct snd_soc_codec *codec = i2c_get_clientdata(client);
|
||||
kfree(codec->reg_cache);
|
||||
struct uda1380_priv *uda1380 = i2c_get_clientdata(i2c);
|
||||
uda1380_unregister(uda1380);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -777,120 +898,28 @@ static struct i2c_driver uda1380_i2c_driver = {
|
|||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = uda1380_i2c_probe,
|
||||
.remove = uda1380_i2c_remove,
|
||||
.remove = __devexit_p(uda1380_i2c_remove),
|
||||
.id_table = uda1380_i2c_id,
|
||||
};
|
||||
|
||||
static int uda1380_add_i2c_device(struct platform_device *pdev,
|
||||
const struct uda1380_setup_data *setup)
|
||||
{
|
||||
struct i2c_board_info info;
|
||||
struct i2c_adapter *adapter;
|
||||
struct i2c_client *client;
|
||||
int ret;
|
||||
|
||||
ret = i2c_add_driver(&uda1380_i2c_driver);
|
||||
if (ret != 0) {
|
||||
dev_err(&pdev->dev, "can't add i2c driver\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
memset(&info, 0, sizeof(struct i2c_board_info));
|
||||
info.addr = setup->i2c_address;
|
||||
strlcpy(info.type, "uda1380", I2C_NAME_SIZE);
|
||||
|
||||
adapter = i2c_get_adapter(setup->i2c_bus);
|
||||
if (!adapter) {
|
||||
dev_err(&pdev->dev, "can't get i2c adapter %d\n",
|
||||
setup->i2c_bus);
|
||||
goto err_driver;
|
||||
}
|
||||
|
||||
client = i2c_new_device(adapter, &info);
|
||||
i2c_put_adapter(adapter);
|
||||
if (!client) {
|
||||
dev_err(&pdev->dev, "can't add i2c device at 0x%x\n",
|
||||
(unsigned int)info.addr);
|
||||
goto err_driver;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_driver:
|
||||
i2c_del_driver(&uda1380_i2c_driver);
|
||||
return -ENODEV;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int uda1380_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
struct uda1380_setup_data *setup;
|
||||
struct snd_soc_codec *codec;
|
||||
int ret;
|
||||
|
||||
setup = socdev->codec_data;
|
||||
codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
|
||||
if (codec == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
socdev->card->codec = codec;
|
||||
mutex_init(&codec->mutex);
|
||||
INIT_LIST_HEAD(&codec->dapm_widgets);
|
||||
INIT_LIST_HEAD(&codec->dapm_paths);
|
||||
|
||||
uda1380_socdev = socdev;
|
||||
ret = -ENODEV;
|
||||
|
||||
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
|
||||
if (setup->i2c_address) {
|
||||
codec->hw_write = (hw_write_t)i2c_master_send;
|
||||
ret = uda1380_add_i2c_device(pdev, setup);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (ret != 0)
|
||||
kfree(codec);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* power down chip */
|
||||
static int uda1380_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
|
||||
if (codec->control_data)
|
||||
uda1380_set_bias_level(codec, SND_SOC_BIAS_OFF);
|
||||
|
||||
snd_soc_free_pcms(socdev);
|
||||
snd_soc_dapm_free(socdev);
|
||||
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
|
||||
i2c_unregister_device(codec->control_data);
|
||||
i2c_del_driver(&uda1380_i2c_driver);
|
||||
#endif
|
||||
kfree(codec);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct snd_soc_codec_device soc_codec_dev_uda1380 = {
|
||||
.probe = uda1380_probe,
|
||||
.remove = uda1380_remove,
|
||||
.suspend = uda1380_suspend,
|
||||
.resume = uda1380_resume,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(soc_codec_dev_uda1380);
|
||||
|
||||
static int __init uda1380_modinit(void)
|
||||
{
|
||||
return snd_soc_register_dais(uda1380_dai, ARRAY_SIZE(uda1380_dai));
|
||||
int ret;
|
||||
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
|
||||
ret = i2c_add_driver(&uda1380_i2c_driver);
|
||||
if (ret != 0)
|
||||
pr_err("Failed to register UDA1380 I2C driver: %d\n", ret);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
module_init(uda1380_modinit);
|
||||
|
||||
static void __exit uda1380_exit(void)
|
||||
{
|
||||
snd_soc_unregister_dais(uda1380_dai, ARRAY_SIZE(uda1380_dai));
|
||||
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
|
||||
i2c_del_driver(&uda1380_i2c_driver);
|
||||
#endif
|
||||
}
|
||||
module_exit(uda1380_exit);
|
||||
|
||||
|
|
|
@ -72,14 +72,6 @@
|
|||
#define R22_SKIP_DCFIL 0x0002
|
||||
#define R23_AGC_EN 0x0001
|
||||
|
||||
struct uda1380_setup_data {
|
||||
int i2c_bus;
|
||||
unsigned short i2c_address;
|
||||
int dac_clk;
|
||||
#define UDA1380_DAC_CLK_SYSCLK 0
|
||||
#define UDA1380_DAC_CLK_WSPLL 1
|
||||
};
|
||||
|
||||
#define UDA1380_DAI_DUPLEX 0 /* playback and capture on single DAI */
|
||||
#define UDA1380_DAI_PLAYBACK 1 /* playback DAI */
|
||||
#define UDA1380_DAI_CAPTURE 2 /* capture DAI */
|
||||
|
|
|
@ -63,6 +63,8 @@ struct wm8350_data {
|
|||
struct wm8350_jack_data hpl;
|
||||
struct wm8350_jack_data hpr;
|
||||
struct regulator_bulk_data supplies[ARRAY_SIZE(supply_names)];
|
||||
int fll_freq_out;
|
||||
int fll_freq_in;
|
||||
};
|
||||
|
||||
static unsigned int wm8350_codec_cache_read(struct snd_soc_codec *codec,
|
||||
|
@ -406,7 +408,6 @@ static const char *wm8350_deemp[] = { "None", "32kHz", "44.1kHz", "48kHz" };
|
|||
static const char *wm8350_pol[] = { "Normal", "Inv R", "Inv L", "Inv L & R" };
|
||||
static const char *wm8350_dacmutem[] = { "Normal", "Soft" };
|
||||
static const char *wm8350_dacmutes[] = { "Fast", "Slow" };
|
||||
static const char *wm8350_dacfilter[] = { "Normal", "Sloping" };
|
||||
static const char *wm8350_adcfilter[] = { "None", "High Pass" };
|
||||
static const char *wm8350_adchp[] = { "44.1kHz", "8kHz", "16kHz", "32kHz" };
|
||||
static const char *wm8350_lr[] = { "Left", "Right" };
|
||||
|
@ -416,7 +417,6 @@ static const struct soc_enum wm8350_enum[] = {
|
|||
SOC_ENUM_SINGLE(WM8350_DAC_CONTROL, 0, 4, wm8350_pol),
|
||||
SOC_ENUM_SINGLE(WM8350_DAC_MUTE_VOLUME, 14, 2, wm8350_dacmutem),
|
||||
SOC_ENUM_SINGLE(WM8350_DAC_MUTE_VOLUME, 13, 2, wm8350_dacmutes),
|
||||
SOC_ENUM_SINGLE(WM8350_DAC_MUTE_VOLUME, 12, 2, wm8350_dacfilter),
|
||||
SOC_ENUM_SINGLE(WM8350_ADC_CONTROL, 15, 2, wm8350_adcfilter),
|
||||
SOC_ENUM_SINGLE(WM8350_ADC_CONTROL, 8, 4, wm8350_adchp),
|
||||
SOC_ENUM_SINGLE(WM8350_ADC_CONTROL, 0, 4, wm8350_pol),
|
||||
|
@ -444,10 +444,9 @@ static const struct snd_kcontrol_new wm8350_snd_controls[] = {
|
|||
0, 255, 0, dac_pcm_tlv),
|
||||
SOC_ENUM("Playback PCM Mute Function", wm8350_enum[2]),
|
||||
SOC_ENUM("Playback PCM Mute Speed", wm8350_enum[3]),
|
||||
SOC_ENUM("Playback PCM Filter", wm8350_enum[4]),
|
||||
SOC_ENUM("Capture PCM Filter", wm8350_enum[5]),
|
||||
SOC_ENUM("Capture PCM HP Filter", wm8350_enum[6]),
|
||||
SOC_ENUM("Capture ADC Inversion", wm8350_enum[7]),
|
||||
SOC_ENUM("Capture PCM Filter", wm8350_enum[4]),
|
||||
SOC_ENUM("Capture PCM HP Filter", wm8350_enum[5]),
|
||||
SOC_ENUM("Capture ADC Inversion", wm8350_enum[6]),
|
||||
SOC_WM8350_DOUBLE_R_TLV("Capture PCM Volume",
|
||||
WM8350_ADC_DIGITAL_VOLUME_L,
|
||||
WM8350_ADC_DIGITAL_VOLUME_R,
|
||||
|
@ -613,7 +612,7 @@ SOC_DAPM_SINGLE("Switch", WM8350_BEEP_VOLUME, 15, 1, 1);
|
|||
|
||||
/* Out4 Capture Mux */
|
||||
static const struct snd_kcontrol_new wm8350_out4_capture_controls =
|
||||
SOC_DAPM_ENUM("Route", wm8350_enum[8]);
|
||||
SOC_DAPM_ENUM("Route", wm8350_enum[7]);
|
||||
|
||||
static const struct snd_soc_dapm_widget wm8350_dapm_widgets[] = {
|
||||
|
||||
|
@ -993,6 +992,7 @@ static int wm8350_pcm_hw_params(struct snd_pcm_substream *substream,
|
|||
struct snd_soc_dai *codec_dai)
|
||||
{
|
||||
struct snd_soc_codec *codec = codec_dai->codec;
|
||||
struct wm8350 *wm8350 = codec->control_data;
|
||||
u16 iface = wm8350_codec_read(codec, WM8350_AI_FORMATING) &
|
||||
~WM8350_AIF_WL_MASK;
|
||||
|
||||
|
@ -1012,6 +1012,19 @@ static int wm8350_pcm_hw_params(struct snd_pcm_substream *substream,
|
|||
}
|
||||
|
||||
wm8350_codec_write(codec, WM8350_AI_FORMATING, iface);
|
||||
|
||||
/* The sloping stopband filter is recommended for use with
|
||||
* lower sample rates to improve performance.
|
||||
*/
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
if (params_rate(params) < 24000)
|
||||
wm8350_set_bits(wm8350, WM8350_DAC_MUTE_VOLUME,
|
||||
WM8350_DAC_SB_FILT);
|
||||
else
|
||||
wm8350_clear_bits(wm8350, WM8350_DAC_MUTE_VOLUME,
|
||||
WM8350_DAC_SB_FILT);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -1093,10 +1106,14 @@ static int wm8350_set_fll(struct snd_soc_dai *codec_dai,
|
|||
{
|
||||
struct snd_soc_codec *codec = codec_dai->codec;
|
||||
struct wm8350 *wm8350 = codec->control_data;
|
||||
struct wm8350_data *priv = codec->private_data;
|
||||
struct _fll_div fll_div;
|
||||
int ret = 0;
|
||||
u16 fll_1, fll_4;
|
||||
|
||||
if (freq_in == priv->fll_freq_in && freq_out == priv->fll_freq_out)
|
||||
return 0;
|
||||
|
||||
/* power down FLL - we need to do this for reconfiguration */
|
||||
wm8350_clear_bits(wm8350, WM8350_POWER_MGMT_4,
|
||||
WM8350_FLL_ENA | WM8350_FLL_OSC_ENA);
|
||||
|
@ -1131,6 +1148,9 @@ static int wm8350_set_fll(struct snd_soc_dai *codec_dai,
|
|||
wm8350_set_bits(wm8350, WM8350_POWER_MGMT_4, WM8350_FLL_OSC_ENA);
|
||||
wm8350_set_bits(wm8350, WM8350_POWER_MGMT_4, WM8350_FLL_ENA);
|
||||
|
||||
priv->fll_freq_out = freq_out;
|
||||
priv->fll_freq_in = freq_in;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -1660,6 +1680,21 @@ static int __devexit wm8350_codec_remove(struct platform_device *pdev)
|
|||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int wm8350_codec_suspend(struct platform_device *pdev, pm_message_t m)
|
||||
{
|
||||
return snd_soc_suspend_device(&pdev->dev);
|
||||
}
|
||||
|
||||
static int wm8350_codec_resume(struct platform_device *pdev)
|
||||
{
|
||||
return snd_soc_resume_device(&pdev->dev);
|
||||
}
|
||||
#else
|
||||
#define wm8350_codec_suspend NULL
|
||||
#define wm8350_codec_resume NULL
|
||||
#endif
|
||||
|
||||
static struct platform_driver wm8350_codec_driver = {
|
||||
.driver = {
|
||||
.name = "wm8350-codec",
|
||||
|
@ -1667,6 +1702,8 @@ static struct platform_driver wm8350_codec_driver = {
|
|||
},
|
||||
.probe = wm8350_codec_probe,
|
||||
.remove = __devexit_p(wm8350_codec_remove),
|
||||
.suspend = wm8350_codec_suspend,
|
||||
.resume = wm8350_codec_resume,
|
||||
};
|
||||
|
||||
static __init int wm8350_init(void)
|
||||
|
|
|
@ -1022,10 +1022,15 @@ static int wm8400_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
|
|||
if (freq_in == wm8400->fll_in && freq_out == wm8400->fll_out)
|
||||
return 0;
|
||||
|
||||
if (freq_out != 0) {
|
||||
if (freq_out) {
|
||||
ret = fll_factors(wm8400, &factors, freq_in, freq_out);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
} else {
|
||||
/* Bodge GCC 4.4.0 uninitialised variable warning - it
|
||||
* doesn't seem capable of working out that we exit if
|
||||
* freq_out is 0 before any of the uses. */
|
||||
memset(&factors, 0, sizeof(factors));
|
||||
}
|
||||
|
||||
wm8400->fll_out = freq_out;
|
||||
|
@ -1040,7 +1045,7 @@ static int wm8400_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
|
|||
reg &= ~WM8400_FLL_OSC_ENA;
|
||||
wm8400_write(codec, WM8400_FLL_CONTROL_1, reg);
|
||||
|
||||
if (freq_out == 0)
|
||||
if (!freq_out)
|
||||
return 0;
|
||||
|
||||
reg &= ~(WM8400_FLL_REF_FREQ | WM8400_FLL_FRATIO_MASK);
|
||||
|
@ -1553,6 +1558,21 @@ static int __exit wm8400_codec_remove(struct platform_device *dev)
|
|||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int wm8400_pdev_suspend(struct platform_device *pdev, pm_message_t msg)
|
||||
{
|
||||
return snd_soc_suspend_device(&pdev->dev);
|
||||
}
|
||||
|
||||
static int wm8400_pdev_resume(struct platform_device *pdev)
|
||||
{
|
||||
return snd_soc_resume_device(&pdev->dev);
|
||||
}
|
||||
#else
|
||||
#define wm8400_pdev_suspend NULL
|
||||
#define wm8400_pdev_resume NULL
|
||||
#endif
|
||||
|
||||
static struct platform_driver wm8400_codec_driver = {
|
||||
.driver = {
|
||||
.name = "wm8400-codec",
|
||||
|
@ -1560,6 +1580,8 @@ static struct platform_driver wm8400_codec_driver = {
|
|||
},
|
||||
.probe = wm8400_codec_probe,
|
||||
.remove = __exit_p(wm8400_codec_remove),
|
||||
.suspend = wm8400_pdev_suspend,
|
||||
.resume = wm8400_pdev_resume,
|
||||
};
|
||||
|
||||
static int __init wm8400_codec_init(void)
|
||||
|
|
|
@ -58,55 +58,7 @@ static const u16 wm8510_reg[WM8510_CACHEREGNUM] = {
|
|||
#define WM8510_POWER1_BIASEN 0x08
|
||||
#define WM8510_POWER1_BUFIOEN 0x10
|
||||
|
||||
/*
|
||||
* read wm8510 register cache
|
||||
*/
|
||||
static inline unsigned int wm8510_read_reg_cache(struct snd_soc_codec *codec,
|
||||
unsigned int reg)
|
||||
{
|
||||
u16 *cache = codec->reg_cache;
|
||||
if (reg == WM8510_RESET)
|
||||
return 0;
|
||||
if (reg >= WM8510_CACHEREGNUM)
|
||||
return -1;
|
||||
return cache[reg];
|
||||
}
|
||||
|
||||
/*
|
||||
* write wm8510 register cache
|
||||
*/
|
||||
static inline void wm8510_write_reg_cache(struct snd_soc_codec *codec,
|
||||
u16 reg, unsigned int value)
|
||||
{
|
||||
u16 *cache = codec->reg_cache;
|
||||
if (reg >= WM8510_CACHEREGNUM)
|
||||
return;
|
||||
cache[reg] = value;
|
||||
}
|
||||
|
||||
/*
|
||||
* write to the WM8510 register space
|
||||
*/
|
||||
static int wm8510_write(struct snd_soc_codec *codec, unsigned int reg,
|
||||
unsigned int value)
|
||||
{
|
||||
u8 data[2];
|
||||
|
||||
/* data is
|
||||
* D15..D9 WM8510 register offset
|
||||
* D8...D0 register data
|
||||
*/
|
||||
data[0] = (reg << 1) | ((value >> 8) & 0x0001);
|
||||
data[1] = value & 0x00ff;
|
||||
|
||||
wm8510_write_reg_cache(codec, reg, value);
|
||||
if (codec->hw_write(codec->control_data, data, 2) == 2)
|
||||
return 0;
|
||||
else
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
#define wm8510_reset(c) wm8510_write(c, WM8510_RESET, 0)
|
||||
#define wm8510_reset(c) snd_soc_write(c, WM8510_RESET, 0)
|
||||
|
||||
static const char *wm8510_companding[] = { "Off", "NC", "u-law", "A-law" };
|
||||
static const char *wm8510_deemp[] = { "None", "32kHz", "44.1kHz", "48kHz" };
|
||||
|
@ -327,27 +279,27 @@ static int wm8510_set_dai_pll(struct snd_soc_dai *codec_dai,
|
|||
|
||||
if (freq_in == 0 || freq_out == 0) {
|
||||
/* Clock CODEC directly from MCLK */
|
||||
reg = wm8510_read_reg_cache(codec, WM8510_CLOCK);
|
||||
wm8510_write(codec, WM8510_CLOCK, reg & 0x0ff);
|
||||
reg = snd_soc_read(codec, WM8510_CLOCK);
|
||||
snd_soc_write(codec, WM8510_CLOCK, reg & 0x0ff);
|
||||
|
||||
/* Turn off PLL */
|
||||
reg = wm8510_read_reg_cache(codec, WM8510_POWER1);
|
||||
wm8510_write(codec, WM8510_POWER1, reg & 0x1df);
|
||||
reg = snd_soc_read(codec, WM8510_POWER1);
|
||||
snd_soc_write(codec, WM8510_POWER1, reg & 0x1df);
|
||||
return 0;
|
||||
}
|
||||
|
||||
pll_factors(freq_out*4, freq_in);
|
||||
|
||||
wm8510_write(codec, WM8510_PLLN, (pll_div.pre_div << 4) | pll_div.n);
|
||||
wm8510_write(codec, WM8510_PLLK1, pll_div.k >> 18);
|
||||
wm8510_write(codec, WM8510_PLLK2, (pll_div.k >> 9) & 0x1ff);
|
||||
wm8510_write(codec, WM8510_PLLK3, pll_div.k & 0x1ff);
|
||||
reg = wm8510_read_reg_cache(codec, WM8510_POWER1);
|
||||
wm8510_write(codec, WM8510_POWER1, reg | 0x020);
|
||||
snd_soc_write(codec, WM8510_PLLN, (pll_div.pre_div << 4) | pll_div.n);
|
||||
snd_soc_write(codec, WM8510_PLLK1, pll_div.k >> 18);
|
||||
snd_soc_write(codec, WM8510_PLLK2, (pll_div.k >> 9) & 0x1ff);
|
||||
snd_soc_write(codec, WM8510_PLLK3, pll_div.k & 0x1ff);
|
||||
reg = snd_soc_read(codec, WM8510_POWER1);
|
||||
snd_soc_write(codec, WM8510_POWER1, reg | 0x020);
|
||||
|
||||
/* Run CODEC from PLL instead of MCLK */
|
||||
reg = wm8510_read_reg_cache(codec, WM8510_CLOCK);
|
||||
wm8510_write(codec, WM8510_CLOCK, reg | 0x100);
|
||||
reg = snd_soc_read(codec, WM8510_CLOCK);
|
||||
snd_soc_write(codec, WM8510_CLOCK, reg | 0x100);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -363,24 +315,24 @@ static int wm8510_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
|
|||
|
||||
switch (div_id) {
|
||||
case WM8510_OPCLKDIV:
|
||||
reg = wm8510_read_reg_cache(codec, WM8510_GPIO) & 0x1cf;
|
||||
wm8510_write(codec, WM8510_GPIO, reg | div);
|
||||
reg = snd_soc_read(codec, WM8510_GPIO) & 0x1cf;
|
||||
snd_soc_write(codec, WM8510_GPIO, reg | div);
|
||||
break;
|
||||
case WM8510_MCLKDIV:
|
||||
reg = wm8510_read_reg_cache(codec, WM8510_CLOCK) & 0x11f;
|
||||
wm8510_write(codec, WM8510_CLOCK, reg | div);
|
||||
reg = snd_soc_read(codec, WM8510_CLOCK) & 0x11f;
|
||||
snd_soc_write(codec, WM8510_CLOCK, reg | div);
|
||||
break;
|
||||
case WM8510_ADCCLK:
|
||||
reg = wm8510_read_reg_cache(codec, WM8510_ADC) & 0x1f7;
|
||||
wm8510_write(codec, WM8510_ADC, reg | div);
|
||||
reg = snd_soc_read(codec, WM8510_ADC) & 0x1f7;
|
||||
snd_soc_write(codec, WM8510_ADC, reg | div);
|
||||
break;
|
||||
case WM8510_DACCLK:
|
||||
reg = wm8510_read_reg_cache(codec, WM8510_DAC) & 0x1f7;
|
||||
wm8510_write(codec, WM8510_DAC, reg | div);
|
||||
reg = snd_soc_read(codec, WM8510_DAC) & 0x1f7;
|
||||
snd_soc_write(codec, WM8510_DAC, reg | div);
|
||||
break;
|
||||
case WM8510_BCLKDIV:
|
||||
reg = wm8510_read_reg_cache(codec, WM8510_CLOCK) & 0x1e3;
|
||||
wm8510_write(codec, WM8510_CLOCK, reg | div);
|
||||
reg = snd_soc_read(codec, WM8510_CLOCK) & 0x1e3;
|
||||
snd_soc_write(codec, WM8510_CLOCK, reg | div);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
|
@ -394,7 +346,7 @@ static int wm8510_set_dai_fmt(struct snd_soc_dai *codec_dai,
|
|||
{
|
||||
struct snd_soc_codec *codec = codec_dai->codec;
|
||||
u16 iface = 0;
|
||||
u16 clk = wm8510_read_reg_cache(codec, WM8510_CLOCK) & 0x1fe;
|
||||
u16 clk = snd_soc_read(codec, WM8510_CLOCK) & 0x1fe;
|
||||
|
||||
/* set master/slave audio interface */
|
||||
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
|
||||
|
@ -441,8 +393,8 @@ static int wm8510_set_dai_fmt(struct snd_soc_dai *codec_dai,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
wm8510_write(codec, WM8510_IFACE, iface);
|
||||
wm8510_write(codec, WM8510_CLOCK, clk);
|
||||
snd_soc_write(codec, WM8510_IFACE, iface);
|
||||
snd_soc_write(codec, WM8510_CLOCK, clk);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -453,8 +405,8 @@ static int wm8510_pcm_hw_params(struct snd_pcm_substream *substream,
|
|||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_device *socdev = rtd->socdev;
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
u16 iface = wm8510_read_reg_cache(codec, WM8510_IFACE) & 0x19f;
|
||||
u16 adn = wm8510_read_reg_cache(codec, WM8510_ADD) & 0x1f1;
|
||||
u16 iface = snd_soc_read(codec, WM8510_IFACE) & 0x19f;
|
||||
u16 adn = snd_soc_read(codec, WM8510_ADD) & 0x1f1;
|
||||
|
||||
/* bit size */
|
||||
switch (params_format(params)) {
|
||||
|
@ -493,20 +445,20 @@ static int wm8510_pcm_hw_params(struct snd_pcm_substream *substream,
|
|||
break;
|
||||
}
|
||||
|
||||
wm8510_write(codec, WM8510_IFACE, iface);
|
||||
wm8510_write(codec, WM8510_ADD, adn);
|
||||
snd_soc_write(codec, WM8510_IFACE, iface);
|
||||
snd_soc_write(codec, WM8510_ADD, adn);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wm8510_mute(struct snd_soc_dai *dai, int mute)
|
||||
{
|
||||
struct snd_soc_codec *codec = dai->codec;
|
||||
u16 mute_reg = wm8510_read_reg_cache(codec, WM8510_DAC) & 0xffbf;
|
||||
u16 mute_reg = snd_soc_read(codec, WM8510_DAC) & 0xffbf;
|
||||
|
||||
if (mute)
|
||||
wm8510_write(codec, WM8510_DAC, mute_reg | 0x40);
|
||||
snd_soc_write(codec, WM8510_DAC, mute_reg | 0x40);
|
||||
else
|
||||
wm8510_write(codec, WM8510_DAC, mute_reg);
|
||||
snd_soc_write(codec, WM8510_DAC, mute_reg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -514,13 +466,13 @@ static int wm8510_mute(struct snd_soc_dai *dai, int mute)
|
|||
static int wm8510_set_bias_level(struct snd_soc_codec *codec,
|
||||
enum snd_soc_bias_level level)
|
||||
{
|
||||
u16 power1 = wm8510_read_reg_cache(codec, WM8510_POWER1) & ~0x3;
|
||||
u16 power1 = snd_soc_read(codec, WM8510_POWER1) & ~0x3;
|
||||
|
||||
switch (level) {
|
||||
case SND_SOC_BIAS_ON:
|
||||
case SND_SOC_BIAS_PREPARE:
|
||||
power1 |= 0x1; /* VMID 50k */
|
||||
wm8510_write(codec, WM8510_POWER1, power1);
|
||||
snd_soc_write(codec, WM8510_POWER1, power1);
|
||||
break;
|
||||
|
||||
case SND_SOC_BIAS_STANDBY:
|
||||
|
@ -528,18 +480,18 @@ static int wm8510_set_bias_level(struct snd_soc_codec *codec,
|
|||
|
||||
if (codec->bias_level == SND_SOC_BIAS_OFF) {
|
||||
/* Initial cap charge at VMID 5k */
|
||||
wm8510_write(codec, WM8510_POWER1, power1 | 0x3);
|
||||
snd_soc_write(codec, WM8510_POWER1, power1 | 0x3);
|
||||
mdelay(100);
|
||||
}
|
||||
|
||||
power1 |= 0x2; /* VMID 500k */
|
||||
wm8510_write(codec, WM8510_POWER1, power1);
|
||||
snd_soc_write(codec, WM8510_POWER1, power1);
|
||||
break;
|
||||
|
||||
case SND_SOC_BIAS_OFF:
|
||||
wm8510_write(codec, WM8510_POWER1, 0);
|
||||
wm8510_write(codec, WM8510_POWER2, 0);
|
||||
wm8510_write(codec, WM8510_POWER3, 0);
|
||||
snd_soc_write(codec, WM8510_POWER1, 0);
|
||||
snd_soc_write(codec, WM8510_POWER2, 0);
|
||||
snd_soc_write(codec, WM8510_POWER3, 0);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -577,6 +529,7 @@ struct snd_soc_dai wm8510_dai = {
|
|||
.rates = WM8510_RATES,
|
||||
.formats = WM8510_FORMATS,},
|
||||
.ops = &wm8510_dai_ops,
|
||||
.symmetric_rates = 1,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(wm8510_dai);
|
||||
|
||||
|
@ -612,15 +565,14 @@ static int wm8510_resume(struct platform_device *pdev)
|
|||
* initialise the WM8510 driver
|
||||
* register the mixer and dsp interfaces with the kernel
|
||||
*/
|
||||
static int wm8510_init(struct snd_soc_device *socdev)
|
||||
static int wm8510_init(struct snd_soc_device *socdev,
|
||||
enum snd_soc_control_type control)
|
||||
{
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
int ret = 0;
|
||||
|
||||
codec->name = "WM8510";
|
||||
codec->owner = THIS_MODULE;
|
||||
codec->read = wm8510_read_reg_cache;
|
||||
codec->write = wm8510_write;
|
||||
codec->set_bias_level = wm8510_set_bias_level;
|
||||
codec->dai = &wm8510_dai;
|
||||
codec->num_dai = 1;
|
||||
|
@ -630,13 +582,20 @@ static int wm8510_init(struct snd_soc_device *socdev)
|
|||
if (codec->reg_cache == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = snd_soc_codec_set_cache_io(codec, 7, 9, control);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "wm8510: failed to set cache I/O: %d\n",
|
||||
ret);
|
||||
goto err;
|
||||
}
|
||||
|
||||
wm8510_reset(codec);
|
||||
|
||||
/* register pcms */
|
||||
ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "wm8510: failed to create pcms\n");
|
||||
goto pcm_err;
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* power on device */
|
||||
|
@ -655,7 +614,7 @@ static int wm8510_init(struct snd_soc_device *socdev)
|
|||
card_err:
|
||||
snd_soc_free_pcms(socdev);
|
||||
snd_soc_dapm_free(socdev);
|
||||
pcm_err:
|
||||
err:
|
||||
kfree(codec->reg_cache);
|
||||
return ret;
|
||||
}
|
||||
|
@ -678,7 +637,7 @@ static int wm8510_i2c_probe(struct i2c_client *i2c,
|
|||
i2c_set_clientdata(i2c, codec);
|
||||
codec->control_data = i2c;
|
||||
|
||||
ret = wm8510_init(socdev);
|
||||
ret = wm8510_init(socdev, SND_SOC_I2C);
|
||||
if (ret < 0)
|
||||
pr_err("failed to initialise WM8510\n");
|
||||
|
||||
|
@ -758,7 +717,7 @@ static int __devinit wm8510_spi_probe(struct spi_device *spi)
|
|||
|
||||
codec->control_data = spi;
|
||||
|
||||
ret = wm8510_init(socdev);
|
||||
ret = wm8510_init(socdev, SND_SOC_SPI);
|
||||
if (ret < 0)
|
||||
dev_err(&spi->dev, "failed to initialise WM8510\n");
|
||||
|
||||
|
@ -779,30 +738,6 @@ static struct spi_driver wm8510_spi_driver = {
|
|||
.probe = wm8510_spi_probe,
|
||||
.remove = __devexit_p(wm8510_spi_remove),
|
||||
};
|
||||
|
||||
static int wm8510_spi_write(struct spi_device *spi, const char *data, int len)
|
||||
{
|
||||
struct spi_transfer t;
|
||||
struct spi_message m;
|
||||
u8 msg[2];
|
||||
|
||||
if (len <= 0)
|
||||
return 0;
|
||||
|
||||
msg[0] = data[0];
|
||||
msg[1] = data[1];
|
||||
|
||||
spi_message_init(&m);
|
||||
memset(&t, 0, (sizeof t));
|
||||
|
||||
t.tx_buf = &msg[0];
|
||||
t.len = len;
|
||||
|
||||
spi_message_add_tail(&t, &m);
|
||||
spi_sync(spi, &m);
|
||||
|
||||
return len;
|
||||
}
|
||||
#endif /* CONFIG_SPI_MASTER */
|
||||
|
||||
static int wm8510_probe(struct platform_device *pdev)
|
||||
|
@ -827,13 +762,11 @@ static int wm8510_probe(struct platform_device *pdev)
|
|||
wm8510_socdev = socdev;
|
||||
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
|
||||
if (setup->i2c_address) {
|
||||
codec->hw_write = (hw_write_t)i2c_master_send;
|
||||
ret = wm8510_add_i2c_device(pdev, setup);
|
||||
}
|
||||
#endif
|
||||
#if defined(CONFIG_SPI_MASTER)
|
||||
if (setup->spi) {
|
||||
codec->hw_write = (hw_write_t)wm8510_spi_write;
|
||||
ret = spi_register_driver(&wm8510_spi_driver);
|
||||
if (ret != 0)
|
||||
printk(KERN_ERR "can't add spi driver");
|
||||
|
|
|
@ -0,0 +1,699 @@
|
|||
/*
|
||||
* wm8523.c -- WM8523 ALSA SoC Audio driver
|
||||
*
|
||||
* Copyright 2009 Wolfson Microelectronics plc
|
||||
*
|
||||
* Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
|
||||
*
|
||||
*
|
||||
* 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/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/soc-dapm.h>
|
||||
#include <sound/initval.h>
|
||||
#include <sound/tlv.h>
|
||||
|
||||
#include "wm8523.h"
|
||||
|
||||
static struct snd_soc_codec *wm8523_codec;
|
||||
struct snd_soc_codec_device soc_codec_dev_wm8523;
|
||||
|
||||
#define WM8523_NUM_SUPPLIES 2
|
||||
static const char *wm8523_supply_names[WM8523_NUM_SUPPLIES] = {
|
||||
"AVDD",
|
||||
"LINEVDD",
|
||||
};
|
||||
|
||||
#define WM8523_NUM_RATES 7
|
||||
|
||||
/* codec private data */
|
||||
struct wm8523_priv {
|
||||
struct snd_soc_codec codec;
|
||||
u16 reg_cache[WM8523_REGISTER_COUNT];
|
||||
struct regulator_bulk_data supplies[WM8523_NUM_SUPPLIES];
|
||||
unsigned int sysclk;
|
||||
unsigned int rate_constraint_list[WM8523_NUM_RATES];
|
||||
struct snd_pcm_hw_constraint_list rate_constraint;
|
||||
};
|
||||
|
||||
static const u16 wm8523_reg[WM8523_REGISTER_COUNT] = {
|
||||
0x8523, /* R0 - DEVICE_ID */
|
||||
0x0001, /* R1 - REVISION */
|
||||
0x0000, /* R2 - PSCTRL1 */
|
||||
0x1812, /* R3 - AIF_CTRL1 */
|
||||
0x0000, /* R4 - AIF_CTRL2 */
|
||||
0x0001, /* R5 - DAC_CTRL3 */
|
||||
0x0190, /* R6 - DAC_GAINL */
|
||||
0x0190, /* R7 - DAC_GAINR */
|
||||
0x0000, /* R8 - ZERO_DETECT */
|
||||
};
|
||||
|
||||
static int wm8523_volatile_register(unsigned int reg)
|
||||
{
|
||||
switch (reg) {
|
||||
case WM8523_DEVICE_ID:
|
||||
case WM8523_REVISION:
|
||||
return 1;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int wm8523_reset(struct snd_soc_codec *codec)
|
||||
{
|
||||
return snd_soc_write(codec, WM8523_DEVICE_ID, 0);
|
||||
}
|
||||
|
||||
static const DECLARE_TLV_DB_SCALE(dac_tlv, -10000, 25, 0);
|
||||
|
||||
static const char *wm8523_zd_count_text[] = {
|
||||
"1024",
|
||||
"2048",
|
||||
};
|
||||
|
||||
static const struct soc_enum wm8523_zc_count =
|
||||
SOC_ENUM_SINGLE(WM8523_ZERO_DETECT, 0, 2, wm8523_zd_count_text);
|
||||
|
||||
static const struct snd_kcontrol_new wm8523_snd_controls[] = {
|
||||
SOC_DOUBLE_R_TLV("Playback Volume", WM8523_DAC_GAINL, WM8523_DAC_GAINR,
|
||||
0, 448, 0, dac_tlv),
|
||||
SOC_SINGLE("ZC Switch", WM8523_DAC_CTRL3, 4, 1, 0),
|
||||
SOC_SINGLE("Playback Deemphasis Switch", WM8523_AIF_CTRL1, 8, 1, 0),
|
||||
SOC_DOUBLE("Playback Switch", WM8523_DAC_CTRL3, 2, 3, 1, 1),
|
||||
SOC_SINGLE("Volume Ramp Up Switch", WM8523_DAC_CTRL3, 1, 1, 0),
|
||||
SOC_SINGLE("Volume Ramp Down Switch", WM8523_DAC_CTRL3, 0, 1, 0),
|
||||
SOC_ENUM("Zero Detect Count", wm8523_zc_count),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_widget wm8523_dapm_widgets[] = {
|
||||
SND_SOC_DAPM_DAC("DAC", "Playback", SND_SOC_NOPM, 0, 0),
|
||||
SND_SOC_DAPM_OUTPUT("LINEVOUTL"),
|
||||
SND_SOC_DAPM_OUTPUT("LINEVOUTR"),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route intercon[] = {
|
||||
{ "LINEVOUTL", NULL, "DAC" },
|
||||
{ "LINEVOUTR", NULL, "DAC" },
|
||||
};
|
||||
|
||||
static int wm8523_add_widgets(struct snd_soc_codec *codec)
|
||||
{
|
||||
snd_soc_dapm_new_controls(codec, wm8523_dapm_widgets,
|
||||
ARRAY_SIZE(wm8523_dapm_widgets));
|
||||
|
||||
snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon));
|
||||
|
||||
snd_soc_dapm_new_widgets(codec);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct {
|
||||
int value;
|
||||
int ratio;
|
||||
} lrclk_ratios[WM8523_NUM_RATES] = {
|
||||
{ 1, 128 },
|
||||
{ 2, 192 },
|
||||
{ 3, 256 },
|
||||
{ 4, 384 },
|
||||
{ 5, 512 },
|
||||
{ 6, 768 },
|
||||
{ 7, 1152 },
|
||||
};
|
||||
|
||||
static int wm8523_startup(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct snd_soc_codec *codec = dai->codec;
|
||||
struct wm8523_priv *wm8523 = codec->private_data;
|
||||
|
||||
/* The set of sample rates that can be supported depends on the
|
||||
* MCLK supplied to the CODEC - enforce this.
|
||||
*/
|
||||
if (!wm8523->sysclk) {
|
||||
dev_err(codec->dev,
|
||||
"No MCLK configured, call set_sysclk() on init\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
snd_pcm_hw_constraint_list(substream->runtime, 0,
|
||||
SNDRV_PCM_HW_PARAM_RATE,
|
||||
&wm8523->rate_constraint);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wm8523_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_device *socdev = rtd->socdev;
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
struct wm8523_priv *wm8523 = codec->private_data;
|
||||
int i;
|
||||
u16 aifctrl1 = snd_soc_read(codec, WM8523_AIF_CTRL1);
|
||||
u16 aifctrl2 = snd_soc_read(codec, WM8523_AIF_CTRL2);
|
||||
|
||||
/* Find a supported LRCLK ratio */
|
||||
for (i = 0; i < ARRAY_SIZE(lrclk_ratios); i++) {
|
||||
if (wm8523->sysclk / params_rate(params) ==
|
||||
lrclk_ratios[i].ratio)
|
||||
break;
|
||||
}
|
||||
|
||||
/* Should never happen, should be handled by constraints */
|
||||
if (i == ARRAY_SIZE(lrclk_ratios)) {
|
||||
dev_err(codec->dev, "MCLK/fs ratio %d unsupported\n",
|
||||
wm8523->sysclk / params_rate(params));
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
aifctrl2 &= ~WM8523_SR_MASK;
|
||||
aifctrl2 |= lrclk_ratios[i].value;
|
||||
|
||||
aifctrl1 &= ~WM8523_WL_MASK;
|
||||
switch (params_format(params)) {
|
||||
case SNDRV_PCM_FORMAT_S16_LE:
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S20_3LE:
|
||||
aifctrl1 |= 0x8;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S24_LE:
|
||||
aifctrl1 |= 0x10;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S32_LE:
|
||||
aifctrl1 |= 0x18;
|
||||
break;
|
||||
}
|
||||
|
||||
snd_soc_write(codec, WM8523_AIF_CTRL1, aifctrl1);
|
||||
snd_soc_write(codec, WM8523_AIF_CTRL2, aifctrl2);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wm8523_set_dai_sysclk(struct snd_soc_dai *codec_dai,
|
||||
int clk_id, unsigned int freq, int dir)
|
||||
{
|
||||
struct snd_soc_codec *codec = codec_dai->codec;
|
||||
struct wm8523_priv *wm8523 = codec->private_data;
|
||||
unsigned int val;
|
||||
int i;
|
||||
|
||||
wm8523->sysclk = freq;
|
||||
|
||||
wm8523->rate_constraint.count = 0;
|
||||
for (i = 0; i < ARRAY_SIZE(lrclk_ratios); i++) {
|
||||
val = freq / lrclk_ratios[i].ratio;
|
||||
/* Check that it's a standard rate since core can't
|
||||
* cope with others and having the odd rates confuses
|
||||
* constraint matching.
|
||||
*/
|
||||
switch (val) {
|
||||
case 8000:
|
||||
case 11025:
|
||||
case 16000:
|
||||
case 22050:
|
||||
case 32000:
|
||||
case 44100:
|
||||
case 48000:
|
||||
case 64000:
|
||||
case 88200:
|
||||
case 96000:
|
||||
case 176400:
|
||||
case 192000:
|
||||
dev_dbg(codec->dev, "Supported sample rate: %dHz\n",
|
||||
val);
|
||||
wm8523->rate_constraint_list[i] = val;
|
||||
wm8523->rate_constraint.count++;
|
||||
break;
|
||||
default:
|
||||
dev_dbg(codec->dev, "Skipping sample rate: %dHz\n",
|
||||
val);
|
||||
}
|
||||
}
|
||||
|
||||
/* Need at least one supported rate... */
|
||||
if (wm8523->rate_constraint.count == 0)
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int wm8523_set_dai_fmt(struct snd_soc_dai *codec_dai,
|
||||
unsigned int fmt)
|
||||
{
|
||||
struct snd_soc_codec *codec = codec_dai->codec;
|
||||
u16 aifctrl1 = snd_soc_read(codec, WM8523_AIF_CTRL1);
|
||||
|
||||
aifctrl1 &= ~(WM8523_BCLK_INV_MASK | WM8523_LRCLK_INV_MASK |
|
||||
WM8523_FMT_MASK | WM8523_AIF_MSTR_MASK);
|
||||
|
||||
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
|
||||
case SND_SOC_DAIFMT_CBM_CFM:
|
||||
aifctrl1 |= WM8523_AIF_MSTR;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_CBS_CFS:
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
|
||||
case SND_SOC_DAIFMT_I2S:
|
||||
aifctrl1 |= 0x0002;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_RIGHT_J:
|
||||
break;
|
||||
case SND_SOC_DAIFMT_LEFT_J:
|
||||
aifctrl1 |= 0x0001;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_DSP_A:
|
||||
aifctrl1 |= 0x0003;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_DSP_B:
|
||||
aifctrl1 |= 0x0023;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
|
||||
case SND_SOC_DAIFMT_NB_NF:
|
||||
break;
|
||||
case SND_SOC_DAIFMT_IB_IF:
|
||||
aifctrl1 |= WM8523_BCLK_INV | WM8523_LRCLK_INV;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_IB_NF:
|
||||
aifctrl1 |= WM8523_BCLK_INV;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_NB_IF:
|
||||
aifctrl1 |= WM8523_LRCLK_INV;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
snd_soc_write(codec, WM8523_AIF_CTRL1, aifctrl1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wm8523_set_bias_level(struct snd_soc_codec *codec,
|
||||
enum snd_soc_bias_level level)
|
||||
{
|
||||
struct wm8523_priv *wm8523 = codec->private_data;
|
||||
int ret, i;
|
||||
|
||||
switch (level) {
|
||||
case SND_SOC_BIAS_ON:
|
||||
break;
|
||||
|
||||
case SND_SOC_BIAS_PREPARE:
|
||||
/* Full power on */
|
||||
snd_soc_update_bits(codec, WM8523_PSCTRL1,
|
||||
WM8523_SYS_ENA_MASK, 3);
|
||||
break;
|
||||
|
||||
case SND_SOC_BIAS_STANDBY:
|
||||
if (codec->bias_level == SND_SOC_BIAS_OFF) {
|
||||
ret = regulator_bulk_enable(ARRAY_SIZE(wm8523->supplies),
|
||||
wm8523->supplies);
|
||||
if (ret != 0) {
|
||||
dev_err(codec->dev,
|
||||
"Failed to enable supplies: %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Initial power up */
|
||||
snd_soc_update_bits(codec, WM8523_PSCTRL1,
|
||||
WM8523_SYS_ENA_MASK, 1);
|
||||
|
||||
/* Sync back default/cached values */
|
||||
for (i = WM8523_AIF_CTRL1;
|
||||
i < WM8523_MAX_REGISTER; i++)
|
||||
snd_soc_write(codec, i, wm8523->reg_cache[i]);
|
||||
|
||||
|
||||
msleep(100);
|
||||
}
|
||||
|
||||
/* Power up to mute */
|
||||
snd_soc_update_bits(codec, WM8523_PSCTRL1,
|
||||
WM8523_SYS_ENA_MASK, 2);
|
||||
|
||||
break;
|
||||
|
||||
case SND_SOC_BIAS_OFF:
|
||||
/* The chip runs through the power down sequence for us. */
|
||||
snd_soc_update_bits(codec, WM8523_PSCTRL1,
|
||||
WM8523_SYS_ENA_MASK, 0);
|
||||
msleep(100);
|
||||
|
||||
regulator_bulk_disable(ARRAY_SIZE(wm8523->supplies),
|
||||
wm8523->supplies);
|
||||
break;
|
||||
}
|
||||
codec->bias_level = level;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define WM8523_RATES SNDRV_PCM_RATE_8000_192000
|
||||
|
||||
#define WM8523_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
|
||||
SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
|
||||
|
||||
static struct snd_soc_dai_ops wm8523_dai_ops = {
|
||||
.startup = wm8523_startup,
|
||||
.hw_params = wm8523_hw_params,
|
||||
.set_sysclk = wm8523_set_dai_sysclk,
|
||||
.set_fmt = wm8523_set_dai_fmt,
|
||||
};
|
||||
|
||||
struct snd_soc_dai wm8523_dai = {
|
||||
.name = "WM8523",
|
||||
.playback = {
|
||||
.stream_name = "Playback",
|
||||
.channels_min = 2, /* Mono modes not yet supported */
|
||||
.channels_max = 2,
|
||||
.rates = WM8523_RATES,
|
||||
.formats = WM8523_FORMATS,
|
||||
},
|
||||
.ops = &wm8523_dai_ops,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(wm8523_dai);
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int wm8523_suspend(struct platform_device *pdev, pm_message_t state)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
|
||||
wm8523_set_bias_level(codec, SND_SOC_BIAS_OFF);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wm8523_resume(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
|
||||
wm8523_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
#define wm8523_suspend NULL
|
||||
#define wm8523_resume NULL
|
||||
#endif
|
||||
|
||||
static int wm8523_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
struct snd_soc_codec *codec;
|
||||
int ret = 0;
|
||||
|
||||
if (wm8523_codec == NULL) {
|
||||
dev_err(&pdev->dev, "Codec device not registered\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
socdev->card->codec = wm8523_codec;
|
||||
codec = wm8523_codec;
|
||||
|
||||
/* register pcms */
|
||||
ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
|
||||
if (ret < 0) {
|
||||
dev_err(codec->dev, "failed to create pcms: %d\n", ret);
|
||||
goto pcm_err;
|
||||
}
|
||||
|
||||
snd_soc_add_controls(codec, wm8523_snd_controls,
|
||||
ARRAY_SIZE(wm8523_snd_controls));
|
||||
wm8523_add_widgets(codec);
|
||||
ret = snd_soc_init_card(socdev);
|
||||
if (ret < 0) {
|
||||
dev_err(codec->dev, "failed to register card: %d\n", ret);
|
||||
goto card_err;
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
card_err:
|
||||
snd_soc_free_pcms(socdev);
|
||||
snd_soc_dapm_free(socdev);
|
||||
pcm_err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int wm8523_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
|
||||
snd_soc_free_pcms(socdev);
|
||||
snd_soc_dapm_free(socdev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct snd_soc_codec_device soc_codec_dev_wm8523 = {
|
||||
.probe = wm8523_probe,
|
||||
.remove = wm8523_remove,
|
||||
.suspend = wm8523_suspend,
|
||||
.resume = wm8523_resume,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(soc_codec_dev_wm8523);
|
||||
|
||||
static int wm8523_register(struct wm8523_priv *wm8523,
|
||||
enum snd_soc_control_type control)
|
||||
{
|
||||
int ret;
|
||||
struct snd_soc_codec *codec = &wm8523->codec;
|
||||
int i;
|
||||
|
||||
if (wm8523_codec) {
|
||||
dev_err(codec->dev, "Another WM8523 is registered\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_init(&codec->mutex);
|
||||
INIT_LIST_HEAD(&codec->dapm_widgets);
|
||||
INIT_LIST_HEAD(&codec->dapm_paths);
|
||||
|
||||
codec->private_data = wm8523;
|
||||
codec->name = "WM8523";
|
||||
codec->owner = THIS_MODULE;
|
||||
codec->bias_level = SND_SOC_BIAS_OFF;
|
||||
codec->set_bias_level = wm8523_set_bias_level;
|
||||
codec->dai = &wm8523_dai;
|
||||
codec->num_dai = 1;
|
||||
codec->reg_cache_size = WM8523_REGISTER_COUNT;
|
||||
codec->reg_cache = &wm8523->reg_cache;
|
||||
codec->volatile_register = wm8523_volatile_register;
|
||||
|
||||
wm8523->rate_constraint.list = &wm8523->rate_constraint_list[0];
|
||||
wm8523->rate_constraint.count =
|
||||
ARRAY_SIZE(wm8523->rate_constraint_list);
|
||||
|
||||
memcpy(codec->reg_cache, wm8523_reg, sizeof(wm8523_reg));
|
||||
|
||||
ret = snd_soc_codec_set_cache_io(codec, 8, 16, control);
|
||||
if (ret != 0) {
|
||||
dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
|
||||
goto err;
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(wm8523->supplies); i++)
|
||||
wm8523->supplies[i].supply = wm8523_supply_names[i];
|
||||
|
||||
ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(wm8523->supplies),
|
||||
wm8523->supplies);
|
||||
if (ret != 0) {
|
||||
dev_err(codec->dev, "Failed to request supplies: %d\n", ret);
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = regulator_bulk_enable(ARRAY_SIZE(wm8523->supplies),
|
||||
wm8523->supplies);
|
||||
if (ret != 0) {
|
||||
dev_err(codec->dev, "Failed to enable supplies: %d\n", ret);
|
||||
goto err_get;
|
||||
}
|
||||
|
||||
ret = snd_soc_read(codec, WM8523_DEVICE_ID);
|
||||
if (ret < 0) {
|
||||
dev_err(codec->dev, "Failed to read ID register\n");
|
||||
goto err_enable;
|
||||
}
|
||||
if (ret != wm8523_reg[WM8523_DEVICE_ID]) {
|
||||
dev_err(codec->dev, "Device is not a WM8523, ID is %x\n", ret);
|
||||
ret = -EINVAL;
|
||||
goto err_enable;
|
||||
}
|
||||
|
||||
ret = snd_soc_read(codec, WM8523_REVISION);
|
||||
if (ret < 0) {
|
||||
dev_err(codec->dev, "Failed to read revision register\n");
|
||||
goto err_enable;
|
||||
}
|
||||
dev_info(codec->dev, "revision %c\n",
|
||||
(ret & WM8523_CHIP_REV_MASK) + 'A');
|
||||
|
||||
ret = wm8523_reset(codec);
|
||||
if (ret < 0) {
|
||||
dev_err(codec->dev, "Failed to issue reset\n");
|
||||
goto err_enable;
|
||||
}
|
||||
|
||||
wm8523_dai.dev = codec->dev;
|
||||
|
||||
/* Change some default settings - latch VU and enable ZC */
|
||||
wm8523->reg_cache[WM8523_DAC_GAINR] |= WM8523_DACR_VU;
|
||||
wm8523->reg_cache[WM8523_DAC_CTRL3] |= WM8523_ZC;
|
||||
|
||||
wm8523_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
|
||||
|
||||
/* Bias level configuration will have done an extra enable */
|
||||
regulator_bulk_disable(ARRAY_SIZE(wm8523->supplies), wm8523->supplies);
|
||||
|
||||
wm8523_codec = codec;
|
||||
|
||||
ret = snd_soc_register_codec(codec);
|
||||
if (ret != 0) {
|
||||
dev_err(codec->dev, "Failed to register codec: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = snd_soc_register_dai(&wm8523_dai);
|
||||
if (ret != 0) {
|
||||
dev_err(codec->dev, "Failed to register DAI: %d\n", ret);
|
||||
snd_soc_unregister_codec(codec);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_enable:
|
||||
regulator_bulk_disable(ARRAY_SIZE(wm8523->supplies), wm8523->supplies);
|
||||
err_get:
|
||||
regulator_bulk_free(ARRAY_SIZE(wm8523->supplies), wm8523->supplies);
|
||||
err:
|
||||
kfree(wm8523);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void wm8523_unregister(struct wm8523_priv *wm8523)
|
||||
{
|
||||
wm8523_set_bias_level(&wm8523->codec, SND_SOC_BIAS_OFF);
|
||||
regulator_bulk_free(ARRAY_SIZE(wm8523->supplies), wm8523->supplies);
|
||||
snd_soc_unregister_dai(&wm8523_dai);
|
||||
snd_soc_unregister_codec(&wm8523->codec);
|
||||
kfree(wm8523);
|
||||
wm8523_codec = NULL;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
|
||||
static __devinit int wm8523_i2c_probe(struct i2c_client *i2c,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct wm8523_priv *wm8523;
|
||||
struct snd_soc_codec *codec;
|
||||
|
||||
wm8523 = kzalloc(sizeof(struct wm8523_priv), GFP_KERNEL);
|
||||
if (wm8523 == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
codec = &wm8523->codec;
|
||||
codec->hw_write = (hw_write_t)i2c_master_send;
|
||||
|
||||
i2c_set_clientdata(i2c, wm8523);
|
||||
codec->control_data = i2c;
|
||||
|
||||
codec->dev = &i2c->dev;
|
||||
|
||||
return wm8523_register(wm8523, SND_SOC_I2C);
|
||||
}
|
||||
|
||||
static __devexit int wm8523_i2c_remove(struct i2c_client *client)
|
||||
{
|
||||
struct wm8523_priv *wm8523 = i2c_get_clientdata(client);
|
||||
wm8523_unregister(wm8523);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int wm8523_i2c_suspend(struct i2c_client *i2c, pm_message_t msg)
|
||||
{
|
||||
return snd_soc_suspend_device(&i2c->dev);
|
||||
}
|
||||
|
||||
static int wm8523_i2c_resume(struct i2c_client *i2c)
|
||||
{
|
||||
return snd_soc_resume_device(&i2c->dev);
|
||||
}
|
||||
#else
|
||||
#define wm8523_i2c_suspend NULL
|
||||
#define wm8523_i2c_resume NULL
|
||||
#endif
|
||||
|
||||
static const struct i2c_device_id wm8523_i2c_id[] = {
|
||||
{ "wm8523", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, wm8523_i2c_id);
|
||||
|
||||
static struct i2c_driver wm8523_i2c_driver = {
|
||||
.driver = {
|
||||
.name = "WM8523",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = wm8523_i2c_probe,
|
||||
.remove = __devexit_p(wm8523_i2c_remove),
|
||||
.suspend = wm8523_i2c_suspend,
|
||||
.resume = wm8523_i2c_resume,
|
||||
.id_table = wm8523_i2c_id,
|
||||
};
|
||||
#endif
|
||||
|
||||
static int __init wm8523_modinit(void)
|
||||
{
|
||||
int ret;
|
||||
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
|
||||
ret = i2c_add_driver(&wm8523_i2c_driver);
|
||||
if (ret != 0) {
|
||||
printk(KERN_ERR "Failed to register WM8523 I2C driver: %d\n",
|
||||
ret);
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
module_init(wm8523_modinit);
|
||||
|
||||
static void __exit wm8523_exit(void)
|
||||
{
|
||||
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
|
||||
i2c_del_driver(&wm8523_i2c_driver);
|
||||
#endif
|
||||
}
|
||||
module_exit(wm8523_exit);
|
||||
|
||||
MODULE_DESCRIPTION("ASoC WM8523 driver");
|
||||
MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,160 @@
|
|||
/*
|
||||
* wm8523.h -- WM8423 ASoC driver
|
||||
*
|
||||
* Copyright 2009 Wolfson Microelectronics, plc
|
||||
*
|
||||
* Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
|
||||
*
|
||||
* Based on wm8753.h
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef _WM8523_H
|
||||
#define _WM8523_H
|
||||
|
||||
/*
|
||||
* Register values.
|
||||
*/
|
||||
#define WM8523_DEVICE_ID 0x00
|
||||
#define WM8523_REVISION 0x01
|
||||
#define WM8523_PSCTRL1 0x02
|
||||
#define WM8523_AIF_CTRL1 0x03
|
||||
#define WM8523_AIF_CTRL2 0x04
|
||||
#define WM8523_DAC_CTRL3 0x05
|
||||
#define WM8523_DAC_GAINL 0x06
|
||||
#define WM8523_DAC_GAINR 0x07
|
||||
#define WM8523_ZERO_DETECT 0x08
|
||||
|
||||
#define WM8523_REGISTER_COUNT 9
|
||||
#define WM8523_MAX_REGISTER 0x08
|
||||
|
||||
/*
|
||||
* Field Definitions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* R0 (0x00) - DEVICE_ID
|
||||
*/
|
||||
#define WM8523_CHIP_ID_MASK 0xFFFF /* CHIP_ID - [15:0] */
|
||||
#define WM8523_CHIP_ID_SHIFT 0 /* CHIP_ID - [15:0] */
|
||||
#define WM8523_CHIP_ID_WIDTH 16 /* CHIP_ID - [15:0] */
|
||||
|
||||
/*
|
||||
* R1 (0x01) - REVISION
|
||||
*/
|
||||
#define WM8523_CHIP_REV_MASK 0x0007 /* CHIP_REV - [2:0] */
|
||||
#define WM8523_CHIP_REV_SHIFT 0 /* CHIP_REV - [2:0] */
|
||||
#define WM8523_CHIP_REV_WIDTH 3 /* CHIP_REV - [2:0] */
|
||||
|
||||
/*
|
||||
* R2 (0x02) - PSCTRL1
|
||||
*/
|
||||
#define WM8523_SYS_ENA_MASK 0x0003 /* SYS_ENA - [1:0] */
|
||||
#define WM8523_SYS_ENA_SHIFT 0 /* SYS_ENA - [1:0] */
|
||||
#define WM8523_SYS_ENA_WIDTH 2 /* SYS_ENA - [1:0] */
|
||||
|
||||
/*
|
||||
* R3 (0x03) - AIF_CTRL1
|
||||
*/
|
||||
#define WM8523_TDM_MODE_MASK 0x1800 /* TDM_MODE - [12:11] */
|
||||
#define WM8523_TDM_MODE_SHIFT 11 /* TDM_MODE - [12:11] */
|
||||
#define WM8523_TDM_MODE_WIDTH 2 /* TDM_MODE - [12:11] */
|
||||
#define WM8523_TDM_SLOT_MASK 0x0600 /* TDM_SLOT - [10:9] */
|
||||
#define WM8523_TDM_SLOT_SHIFT 9 /* TDM_SLOT - [10:9] */
|
||||
#define WM8523_TDM_SLOT_WIDTH 2 /* TDM_SLOT - [10:9] */
|
||||
#define WM8523_DEEMPH 0x0100 /* DEEMPH */
|
||||
#define WM8523_DEEMPH_MASK 0x0100 /* DEEMPH */
|
||||
#define WM8523_DEEMPH_SHIFT 8 /* DEEMPH */
|
||||
#define WM8523_DEEMPH_WIDTH 1 /* DEEMPH */
|
||||
#define WM8523_AIF_MSTR 0x0080 /* AIF_MSTR */
|
||||
#define WM8523_AIF_MSTR_MASK 0x0080 /* AIF_MSTR */
|
||||
#define WM8523_AIF_MSTR_SHIFT 7 /* AIF_MSTR */
|
||||
#define WM8523_AIF_MSTR_WIDTH 1 /* AIF_MSTR */
|
||||
#define WM8523_LRCLK_INV 0x0040 /* LRCLK_INV */
|
||||
#define WM8523_LRCLK_INV_MASK 0x0040 /* LRCLK_INV */
|
||||
#define WM8523_LRCLK_INV_SHIFT 6 /* LRCLK_INV */
|
||||
#define WM8523_LRCLK_INV_WIDTH 1 /* LRCLK_INV */
|
||||
#define WM8523_BCLK_INV 0x0020 /* BCLK_INV */
|
||||
#define WM8523_BCLK_INV_MASK 0x0020 /* BCLK_INV */
|
||||
#define WM8523_BCLK_INV_SHIFT 5 /* BCLK_INV */
|
||||
#define WM8523_BCLK_INV_WIDTH 1 /* BCLK_INV */
|
||||
#define WM8523_WL_MASK 0x0018 /* WL - [4:3] */
|
||||
#define WM8523_WL_SHIFT 3 /* WL - [4:3] */
|
||||
#define WM8523_WL_WIDTH 2 /* WL - [4:3] */
|
||||
#define WM8523_FMT_MASK 0x0007 /* FMT - [2:0] */
|
||||
#define WM8523_FMT_SHIFT 0 /* FMT - [2:0] */
|
||||
#define WM8523_FMT_WIDTH 3 /* FMT - [2:0] */
|
||||
|
||||
/*
|
||||
* R4 (0x04) - AIF_CTRL2
|
||||
*/
|
||||
#define WM8523_DAC_OP_MUX_MASK 0x00C0 /* DAC_OP_MUX - [7:6] */
|
||||
#define WM8523_DAC_OP_MUX_SHIFT 6 /* DAC_OP_MUX - [7:6] */
|
||||
#define WM8523_DAC_OP_MUX_WIDTH 2 /* DAC_OP_MUX - [7:6] */
|
||||
#define WM8523_BCLKDIV_MASK 0x0038 /* BCLKDIV - [5:3] */
|
||||
#define WM8523_BCLKDIV_SHIFT 3 /* BCLKDIV - [5:3] */
|
||||
#define WM8523_BCLKDIV_WIDTH 3 /* BCLKDIV - [5:3] */
|
||||
#define WM8523_SR_MASK 0x0007 /* SR - [2:0] */
|
||||
#define WM8523_SR_SHIFT 0 /* SR - [2:0] */
|
||||
#define WM8523_SR_WIDTH 3 /* SR - [2:0] */
|
||||
|
||||
/*
|
||||
* R5 (0x05) - DAC_CTRL3
|
||||
*/
|
||||
#define WM8523_ZC 0x0010 /* ZC */
|
||||
#define WM8523_ZC_MASK 0x0010 /* ZC */
|
||||
#define WM8523_ZC_SHIFT 4 /* ZC */
|
||||
#define WM8523_ZC_WIDTH 1 /* ZC */
|
||||
#define WM8523_DACR 0x0008 /* DACR */
|
||||
#define WM8523_DACR_MASK 0x0008 /* DACR */
|
||||
#define WM8523_DACR_SHIFT 3 /* DACR */
|
||||
#define WM8523_DACR_WIDTH 1 /* DACR */
|
||||
#define WM8523_DACL 0x0004 /* DACL */
|
||||
#define WM8523_DACL_MASK 0x0004 /* DACL */
|
||||
#define WM8523_DACL_SHIFT 2 /* DACL */
|
||||
#define WM8523_DACL_WIDTH 1 /* DACL */
|
||||
#define WM8523_VOL_UP_RAMP 0x0002 /* VOL_UP_RAMP */
|
||||
#define WM8523_VOL_UP_RAMP_MASK 0x0002 /* VOL_UP_RAMP */
|
||||
#define WM8523_VOL_UP_RAMP_SHIFT 1 /* VOL_UP_RAMP */
|
||||
#define WM8523_VOL_UP_RAMP_WIDTH 1 /* VOL_UP_RAMP */
|
||||
#define WM8523_VOL_DOWN_RAMP 0x0001 /* VOL_DOWN_RAMP */
|
||||
#define WM8523_VOL_DOWN_RAMP_MASK 0x0001 /* VOL_DOWN_RAMP */
|
||||
#define WM8523_VOL_DOWN_RAMP_SHIFT 0 /* VOL_DOWN_RAMP */
|
||||
#define WM8523_VOL_DOWN_RAMP_WIDTH 1 /* VOL_DOWN_RAMP */
|
||||
|
||||
/*
|
||||
* R6 (0x06) - DAC_GAINL
|
||||
*/
|
||||
#define WM8523_DACL_VU 0x0200 /* DACL_VU */
|
||||
#define WM8523_DACL_VU_MASK 0x0200 /* DACL_VU */
|
||||
#define WM8523_DACL_VU_SHIFT 9 /* DACL_VU */
|
||||
#define WM8523_DACL_VU_WIDTH 1 /* DACL_VU */
|
||||
#define WM8523_DACL_VOL_MASK 0x01FF /* DACL_VOL - [8:0] */
|
||||
#define WM8523_DACL_VOL_SHIFT 0 /* DACL_VOL - [8:0] */
|
||||
#define WM8523_DACL_VOL_WIDTH 9 /* DACL_VOL - [8:0] */
|
||||
|
||||
/*
|
||||
* R7 (0x07) - DAC_GAINR
|
||||
*/
|
||||
#define WM8523_DACR_VU 0x0200 /* DACR_VU */
|
||||
#define WM8523_DACR_VU_MASK 0x0200 /* DACR_VU */
|
||||
#define WM8523_DACR_VU_SHIFT 9 /* DACR_VU */
|
||||
#define WM8523_DACR_VU_WIDTH 1 /* DACR_VU */
|
||||
#define WM8523_DACR_VOL_MASK 0x01FF /* DACR_VOL - [8:0] */
|
||||
#define WM8523_DACR_VOL_SHIFT 0 /* DACR_VOL - [8:0] */
|
||||
#define WM8523_DACR_VOL_WIDTH 9 /* DACR_VOL - [8:0] */
|
||||
|
||||
/*
|
||||
* R8 (0x08) - ZERO_DETECT
|
||||
*/
|
||||
#define WM8523_ZD_COUNT_MASK 0x0003 /* ZD_COUNT - [1:0] */
|
||||
#define WM8523_ZD_COUNT_SHIFT 0 /* ZD_COUNT - [1:0] */
|
||||
#define WM8523_ZD_COUNT_WIDTH 2 /* ZD_COUNT - [1:0] */
|
||||
|
||||
extern struct snd_soc_dai wm8523_dai;
|
||||
extern struct snd_soc_codec_device soc_codec_dev_wm8523;
|
||||
|
||||
#endif
|
|
@ -24,6 +24,8 @@
|
|||
#include <linux/pm.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
|
@ -187,82 +189,22 @@ struct pll_state {
|
|||
unsigned int out;
|
||||
};
|
||||
|
||||
#define WM8580_NUM_SUPPLIES 3
|
||||
static const char *wm8580_supply_names[WM8580_NUM_SUPPLIES] = {
|
||||
"AVDD",
|
||||
"DVDD",
|
||||
"PVDD",
|
||||
};
|
||||
|
||||
/* codec private data */
|
||||
struct wm8580_priv {
|
||||
struct snd_soc_codec codec;
|
||||
struct regulator_bulk_data supplies[WM8580_NUM_SUPPLIES];
|
||||
u16 reg_cache[WM8580_MAX_REGISTER + 1];
|
||||
struct pll_state a;
|
||||
struct pll_state b;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* read wm8580 register cache
|
||||
*/
|
||||
static inline unsigned int wm8580_read_reg_cache(struct snd_soc_codec *codec,
|
||||
unsigned int reg)
|
||||
{
|
||||
u16 *cache = codec->reg_cache;
|
||||
BUG_ON(reg >= ARRAY_SIZE(wm8580_reg));
|
||||
return cache[reg];
|
||||
}
|
||||
|
||||
/*
|
||||
* write wm8580 register cache
|
||||
*/
|
||||
static inline void wm8580_write_reg_cache(struct snd_soc_codec *codec,
|
||||
unsigned int reg, unsigned int value)
|
||||
{
|
||||
u16 *cache = codec->reg_cache;
|
||||
|
||||
cache[reg] = value;
|
||||
}
|
||||
|
||||
/*
|
||||
* write to the WM8580 register space
|
||||
*/
|
||||
static int wm8580_write(struct snd_soc_codec *codec, unsigned int reg,
|
||||
unsigned int value)
|
||||
{
|
||||
u8 data[2];
|
||||
|
||||
BUG_ON(reg >= ARRAY_SIZE(wm8580_reg));
|
||||
|
||||
/* Registers are 9 bits wide */
|
||||
value &= 0x1ff;
|
||||
|
||||
switch (reg) {
|
||||
case WM8580_RESET:
|
||||
/* Uncached */
|
||||
break;
|
||||
default:
|
||||
if (value == wm8580_read_reg_cache(codec, reg))
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* data is
|
||||
* D15..D9 WM8580 register offset
|
||||
* D8...D0 register data
|
||||
*/
|
||||
data[0] = (reg << 1) | ((value >> 8) & 0x0001);
|
||||
data[1] = value & 0x00ff;
|
||||
|
||||
wm8580_write_reg_cache(codec, reg, value);
|
||||
if (codec->hw_write(codec->control_data, data, 2) == 2)
|
||||
return 0;
|
||||
else
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
static inline unsigned int wm8580_read(struct snd_soc_codec *codec,
|
||||
unsigned int reg)
|
||||
{
|
||||
switch (reg) {
|
||||
default:
|
||||
return wm8580_read_reg_cache(codec, reg);
|
||||
}
|
||||
}
|
||||
|
||||
static const DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 1);
|
||||
|
||||
static int wm8580_out_vu(struct snd_kcontrol *kcontrol,
|
||||
|
@ -271,25 +213,22 @@ static int wm8580_out_vu(struct snd_kcontrol *kcontrol,
|
|||
struct soc_mixer_control *mc =
|
||||
(struct soc_mixer_control *)kcontrol->private_value;
|
||||
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
|
||||
u16 *reg_cache = codec->reg_cache;
|
||||
unsigned int reg = mc->reg;
|
||||
unsigned int reg2 = mc->rreg;
|
||||
int ret;
|
||||
u16 val;
|
||||
|
||||
/* Clear the register cache so we write without VU set */
|
||||
wm8580_write_reg_cache(codec, reg, 0);
|
||||
wm8580_write_reg_cache(codec, reg2, 0);
|
||||
reg_cache[reg] = 0;
|
||||
reg_cache[reg2] = 0;
|
||||
|
||||
ret = snd_soc_put_volsw_2r(kcontrol, ucontrol);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* Now write again with the volume update bit set */
|
||||
val = wm8580_read_reg_cache(codec, reg);
|
||||
wm8580_write(codec, reg, val | 0x0100);
|
||||
|
||||
val = wm8580_read_reg_cache(codec, reg2);
|
||||
wm8580_write(codec, reg2, val | 0x0100);
|
||||
snd_soc_update_bits(codec, reg, 0x100, 0x100);
|
||||
snd_soc_update_bits(codec, reg2, 0x100, 0x100);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -512,27 +451,27 @@ static int wm8580_set_dai_pll(struct snd_soc_dai *codec_dai,
|
|||
/* Always disable the PLL - it is not safe to leave it running
|
||||
* while reprogramming it.
|
||||
*/
|
||||
reg = wm8580_read(codec, WM8580_PWRDN2);
|
||||
wm8580_write(codec, WM8580_PWRDN2, reg | pwr_mask);
|
||||
reg = snd_soc_read(codec, WM8580_PWRDN2);
|
||||
snd_soc_write(codec, WM8580_PWRDN2, reg | pwr_mask);
|
||||
|
||||
if (!freq_in || !freq_out)
|
||||
return 0;
|
||||
|
||||
wm8580_write(codec, WM8580_PLLA1 + offset, pll_div.k & 0x1ff);
|
||||
wm8580_write(codec, WM8580_PLLA2 + offset, (pll_div.k >> 9) & 0xff);
|
||||
wm8580_write(codec, WM8580_PLLA3 + offset,
|
||||
snd_soc_write(codec, WM8580_PLLA1 + offset, pll_div.k & 0x1ff);
|
||||
snd_soc_write(codec, WM8580_PLLA2 + offset, (pll_div.k >> 9) & 0x1ff);
|
||||
snd_soc_write(codec, WM8580_PLLA3 + offset,
|
||||
(pll_div.k >> 18 & 0xf) | (pll_div.n << 4));
|
||||
|
||||
reg = wm8580_read(codec, WM8580_PLLA4 + offset);
|
||||
reg &= ~0x3f;
|
||||
reg = snd_soc_read(codec, WM8580_PLLA4 + offset);
|
||||
reg &= ~0x1b;
|
||||
reg |= pll_div.prescale | pll_div.postscale << 1 |
|
||||
pll_div.freqmode << 3;
|
||||
|
||||
wm8580_write(codec, WM8580_PLLA4 + offset, reg);
|
||||
snd_soc_write(codec, WM8580_PLLA4 + offset, reg);
|
||||
|
||||
/* All done, turn it on */
|
||||
reg = wm8580_read(codec, WM8580_PWRDN2);
|
||||
wm8580_write(codec, WM8580_PWRDN2, reg & ~pwr_mask);
|
||||
reg = snd_soc_read(codec, WM8580_PWRDN2);
|
||||
snd_soc_write(codec, WM8580_PWRDN2, reg & ~pwr_mask);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -547,7 +486,7 @@ static int wm8580_paif_hw_params(struct snd_pcm_substream *substream,
|
|||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_device *socdev = rtd->socdev;
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
u16 paifb = wm8580_read(codec, WM8580_PAIF3 + dai->id);
|
||||
u16 paifb = snd_soc_read(codec, WM8580_PAIF3 + dai->id);
|
||||
|
||||
paifb &= ~WM8580_AIF_LENGTH_MASK;
|
||||
/* bit size */
|
||||
|
@ -567,7 +506,7 @@ static int wm8580_paif_hw_params(struct snd_pcm_substream *substream,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
wm8580_write(codec, WM8580_PAIF3 + dai->id, paifb);
|
||||
snd_soc_write(codec, WM8580_PAIF3 + dai->id, paifb);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -579,8 +518,8 @@ static int wm8580_set_paif_dai_fmt(struct snd_soc_dai *codec_dai,
|
|||
unsigned int aifb;
|
||||
int can_invert_lrclk;
|
||||
|
||||
aifa = wm8580_read(codec, WM8580_PAIF1 + codec_dai->id);
|
||||
aifb = wm8580_read(codec, WM8580_PAIF3 + codec_dai->id);
|
||||
aifa = snd_soc_read(codec, WM8580_PAIF1 + codec_dai->id);
|
||||
aifb = snd_soc_read(codec, WM8580_PAIF3 + codec_dai->id);
|
||||
|
||||
aifb &= ~(WM8580_AIF_FMT_MASK | WM8580_AIF_LRP | WM8580_AIF_BCP);
|
||||
|
||||
|
@ -646,8 +585,8 @@ static int wm8580_set_paif_dai_fmt(struct snd_soc_dai *codec_dai,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
wm8580_write(codec, WM8580_PAIF1 + codec_dai->id, aifa);
|
||||
wm8580_write(codec, WM8580_PAIF3 + codec_dai->id, aifb);
|
||||
snd_soc_write(codec, WM8580_PAIF1 + codec_dai->id, aifa);
|
||||
snd_soc_write(codec, WM8580_PAIF3 + codec_dai->id, aifb);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -660,7 +599,7 @@ static int wm8580_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
|
|||
|
||||
switch (div_id) {
|
||||
case WM8580_MCLK:
|
||||
reg = wm8580_read(codec, WM8580_PLLB4);
|
||||
reg = snd_soc_read(codec, WM8580_PLLB4);
|
||||
reg &= ~WM8580_PLLB4_MCLKOUTSRC_MASK;
|
||||
|
||||
switch (div) {
|
||||
|
@ -682,11 +621,11 @@ static int wm8580_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
|
|||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
wm8580_write(codec, WM8580_PLLB4, reg);
|
||||
snd_soc_write(codec, WM8580_PLLB4, reg);
|
||||
break;
|
||||
|
||||
case WM8580_DAC_CLKSEL:
|
||||
reg = wm8580_read(codec, WM8580_CLKSEL);
|
||||
reg = snd_soc_read(codec, WM8580_CLKSEL);
|
||||
reg &= ~WM8580_CLKSEL_DAC_CLKSEL_MASK;
|
||||
|
||||
switch (div) {
|
||||
|
@ -704,11 +643,11 @@ static int wm8580_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
|
|||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
wm8580_write(codec, WM8580_CLKSEL, reg);
|
||||
snd_soc_write(codec, WM8580_CLKSEL, reg);
|
||||
break;
|
||||
|
||||
case WM8580_CLKOUTSRC:
|
||||
reg = wm8580_read(codec, WM8580_PLLB4);
|
||||
reg = snd_soc_read(codec, WM8580_PLLB4);
|
||||
reg &= ~WM8580_PLLB4_CLKOUTSRC_MASK;
|
||||
|
||||
switch (div) {
|
||||
|
@ -730,7 +669,7 @@ static int wm8580_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
|
|||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
wm8580_write(codec, WM8580_PLLB4, reg);
|
||||
snd_soc_write(codec, WM8580_PLLB4, reg);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -745,14 +684,14 @@ static int wm8580_digital_mute(struct snd_soc_dai *codec_dai, int mute)
|
|||
struct snd_soc_codec *codec = codec_dai->codec;
|
||||
unsigned int reg;
|
||||
|
||||
reg = wm8580_read(codec, WM8580_DAC_CONTROL5);
|
||||
reg = snd_soc_read(codec, WM8580_DAC_CONTROL5);
|
||||
|
||||
if (mute)
|
||||
reg |= WM8580_DAC_CONTROL5_MUTEALL;
|
||||
else
|
||||
reg &= ~WM8580_DAC_CONTROL5_MUTEALL;
|
||||
|
||||
wm8580_write(codec, WM8580_DAC_CONTROL5, reg);
|
||||
snd_soc_write(codec, WM8580_DAC_CONTROL5, reg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -769,20 +708,20 @@ static int wm8580_set_bias_level(struct snd_soc_codec *codec,
|
|||
case SND_SOC_BIAS_STANDBY:
|
||||
if (codec->bias_level == SND_SOC_BIAS_OFF) {
|
||||
/* Power up and get individual control of the DACs */
|
||||
reg = wm8580_read(codec, WM8580_PWRDN1);
|
||||
reg = snd_soc_read(codec, WM8580_PWRDN1);
|
||||
reg &= ~(WM8580_PWRDN1_PWDN | WM8580_PWRDN1_ALLDACPD);
|
||||
wm8580_write(codec, WM8580_PWRDN1, reg);
|
||||
snd_soc_write(codec, WM8580_PWRDN1, reg);
|
||||
|
||||
/* Make VMID high impedence */
|
||||
reg = wm8580_read(codec, WM8580_ADC_CONTROL1);
|
||||
reg = snd_soc_read(codec, WM8580_ADC_CONTROL1);
|
||||
reg &= ~0x100;
|
||||
wm8580_write(codec, WM8580_ADC_CONTROL1, reg);
|
||||
snd_soc_write(codec, WM8580_ADC_CONTROL1, reg);
|
||||
}
|
||||
break;
|
||||
|
||||
case SND_SOC_BIAS_OFF:
|
||||
reg = wm8580_read(codec, WM8580_PWRDN1);
|
||||
wm8580_write(codec, WM8580_PWRDN1, reg | WM8580_PWRDN1_PWDN);
|
||||
reg = snd_soc_read(codec, WM8580_PWRDN1);
|
||||
snd_soc_write(codec, WM8580_PWRDN1, reg | WM8580_PWRDN1_PWDN);
|
||||
break;
|
||||
}
|
||||
codec->bias_level = level;
|
||||
|
@ -893,7 +832,8 @@ struct snd_soc_codec_device soc_codec_dev_wm8580 = {
|
|||
};
|
||||
EXPORT_SYMBOL_GPL(soc_codec_dev_wm8580);
|
||||
|
||||
static int wm8580_register(struct wm8580_priv *wm8580)
|
||||
static int wm8580_register(struct wm8580_priv *wm8580,
|
||||
enum snd_soc_control_type control)
|
||||
{
|
||||
int ret, i;
|
||||
struct snd_soc_codec *codec = &wm8580->codec;
|
||||
|
@ -911,8 +851,6 @@ static int wm8580_register(struct wm8580_priv *wm8580)
|
|||
codec->private_data = wm8580;
|
||||
codec->name = "WM8580";
|
||||
codec->owner = THIS_MODULE;
|
||||
codec->read = wm8580_read_reg_cache;
|
||||
codec->write = wm8580_write;
|
||||
codec->bias_level = SND_SOC_BIAS_OFF;
|
||||
codec->set_bias_level = wm8580_set_bias_level;
|
||||
codec->dai = wm8580_dai;
|
||||
|
@ -922,11 +860,34 @@ static int wm8580_register(struct wm8580_priv *wm8580)
|
|||
|
||||
memcpy(codec->reg_cache, wm8580_reg, sizeof(wm8580_reg));
|
||||
|
||||
ret = snd_soc_codec_set_cache_io(codec, 7, 9, control);
|
||||
if (ret < 0) {
|
||||
dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
|
||||
goto err;
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(wm8580->supplies); i++)
|
||||
wm8580->supplies[i].supply = wm8580_supply_names[i];
|
||||
|
||||
ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(wm8580->supplies),
|
||||
wm8580->supplies);
|
||||
if (ret != 0) {
|
||||
dev_err(codec->dev, "Failed to request supplies: %d\n", ret);
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = regulator_bulk_enable(ARRAY_SIZE(wm8580->supplies),
|
||||
wm8580->supplies);
|
||||
if (ret != 0) {
|
||||
dev_err(codec->dev, "Failed to enable supplies: %d\n", ret);
|
||||
goto err_regulator_get;
|
||||
}
|
||||
|
||||
/* Get the codec into a known state */
|
||||
ret = wm8580_write(codec, WM8580_RESET, 0);
|
||||
ret = snd_soc_write(codec, WM8580_RESET, 0);
|
||||
if (ret != 0) {
|
||||
dev_err(codec->dev, "Failed to reset codec: %d\n", ret);
|
||||
goto err;
|
||||
goto err_regulator_enable;
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(wm8580_dai); i++)
|
||||
|
@ -939,7 +900,7 @@ static int wm8580_register(struct wm8580_priv *wm8580)
|
|||
ret = snd_soc_register_codec(codec);
|
||||
if (ret != 0) {
|
||||
dev_err(codec->dev, "Failed to register codec: %d\n", ret);
|
||||
goto err;
|
||||
goto err_regulator_enable;
|
||||
}
|
||||
|
||||
ret = snd_soc_register_dais(wm8580_dai, ARRAY_SIZE(wm8580_dai));
|
||||
|
@ -952,6 +913,10 @@ static int wm8580_register(struct wm8580_priv *wm8580)
|
|||
|
||||
err_codec:
|
||||
snd_soc_unregister_codec(codec);
|
||||
err_regulator_enable:
|
||||
regulator_bulk_disable(ARRAY_SIZE(wm8580->supplies), wm8580->supplies);
|
||||
err_regulator_get:
|
||||
regulator_bulk_free(ARRAY_SIZE(wm8580->supplies), wm8580->supplies);
|
||||
err:
|
||||
kfree(wm8580);
|
||||
return ret;
|
||||
|
@ -962,6 +927,8 @@ static void wm8580_unregister(struct wm8580_priv *wm8580)
|
|||
wm8580_set_bias_level(&wm8580->codec, SND_SOC_BIAS_OFF);
|
||||
snd_soc_unregister_dais(wm8580_dai, ARRAY_SIZE(wm8580_dai));
|
||||
snd_soc_unregister_codec(&wm8580->codec);
|
||||
regulator_bulk_disable(ARRAY_SIZE(wm8580->supplies), wm8580->supplies);
|
||||
regulator_bulk_free(ARRAY_SIZE(wm8580->supplies), wm8580->supplies);
|
||||
kfree(wm8580);
|
||||
wm8580_codec = NULL;
|
||||
}
|
||||
|
@ -978,14 +945,13 @@ static int wm8580_i2c_probe(struct i2c_client *i2c,
|
|||
return -ENOMEM;
|
||||
|
||||
codec = &wm8580->codec;
|
||||
codec->hw_write = (hw_write_t)i2c_master_send;
|
||||
|
||||
i2c_set_clientdata(i2c, wm8580);
|
||||
codec->control_data = i2c;
|
||||
|
||||
codec->dev = &i2c->dev;
|
||||
|
||||
return wm8580_register(wm8580);
|
||||
return wm8580_register(wm8580, SND_SOC_I2C);
|
||||
}
|
||||
|
||||
static int wm8580_i2c_remove(struct i2c_client *client)
|
||||
|
@ -995,6 +961,21 @@ static int wm8580_i2c_remove(struct i2c_client *client)
|
|||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int wm8580_i2c_suspend(struct i2c_client *client, pm_message_t msg)
|
||||
{
|
||||
return snd_soc_suspend_device(&client->dev);
|
||||
}
|
||||
|
||||
static int wm8580_i2c_resume(struct i2c_client *client)
|
||||
{
|
||||
return snd_soc_resume_device(&client->dev);
|
||||
}
|
||||
#else
|
||||
#define wm8580_i2c_suspend NULL
|
||||
#define wm8580_i2c_resume NULL
|
||||
#endif
|
||||
|
||||
static const struct i2c_device_id wm8580_i2c_id[] = {
|
||||
{ "wm8580", 0 },
|
||||
{ }
|
||||
|
@ -1008,6 +989,8 @@ static struct i2c_driver wm8580_i2c_driver = {
|
|||
},
|
||||
.probe = wm8580_i2c_probe,
|
||||
.remove = wm8580_i2c_remove,
|
||||
.suspend = wm8580_i2c_suspend,
|
||||
.resume = wm8580_i2c_resume,
|
||||
.id_table = wm8580_i2c_id,
|
||||
};
|
||||
#endif
|
||||
|
|
|
@ -43,45 +43,6 @@ static const u16 wm8728_reg_defaults[] = {
|
|||
0x100,
|
||||
};
|
||||
|
||||
static inline unsigned int wm8728_read_reg_cache(struct snd_soc_codec *codec,
|
||||
unsigned int reg)
|
||||
{
|
||||
u16 *cache = codec->reg_cache;
|
||||
BUG_ON(reg >= ARRAY_SIZE(wm8728_reg_defaults));
|
||||
return cache[reg];
|
||||
}
|
||||
|
||||
static inline void wm8728_write_reg_cache(struct snd_soc_codec *codec,
|
||||
u16 reg, unsigned int value)
|
||||
{
|
||||
u16 *cache = codec->reg_cache;
|
||||
BUG_ON(reg >= ARRAY_SIZE(wm8728_reg_defaults));
|
||||
cache[reg] = value;
|
||||
}
|
||||
|
||||
/*
|
||||
* write to the WM8728 register space
|
||||
*/
|
||||
static int wm8728_write(struct snd_soc_codec *codec, unsigned int reg,
|
||||
unsigned int value)
|
||||
{
|
||||
u8 data[2];
|
||||
|
||||
/* data is
|
||||
* D15..D9 WM8728 register offset
|
||||
* D8...D0 register data
|
||||
*/
|
||||
data[0] = (reg << 1) | ((value >> 8) & 0x0001);
|
||||
data[1] = value & 0x00ff;
|
||||
|
||||
wm8728_write_reg_cache(codec, reg, value);
|
||||
|
||||
if (codec->hw_write(codec->control_data, data, 2) == 2)
|
||||
return 0;
|
||||
else
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
static const DECLARE_TLV_DB_SCALE(wm8728_tlv, -12750, 50, 1);
|
||||
|
||||
static const struct snd_kcontrol_new wm8728_snd_controls[] = {
|
||||
|
@ -121,12 +82,12 @@ static int wm8728_add_widgets(struct snd_soc_codec *codec)
|
|||
static int wm8728_mute(struct snd_soc_dai *dai, int mute)
|
||||
{
|
||||
struct snd_soc_codec *codec = dai->codec;
|
||||
u16 mute_reg = wm8728_read_reg_cache(codec, WM8728_DACCTL);
|
||||
u16 mute_reg = snd_soc_read(codec, WM8728_DACCTL);
|
||||
|
||||
if (mute)
|
||||
wm8728_write(codec, WM8728_DACCTL, mute_reg | 1);
|
||||
snd_soc_write(codec, WM8728_DACCTL, mute_reg | 1);
|
||||
else
|
||||
wm8728_write(codec, WM8728_DACCTL, mute_reg & ~1);
|
||||
snd_soc_write(codec, WM8728_DACCTL, mute_reg & ~1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -138,7 +99,7 @@ static int wm8728_hw_params(struct snd_pcm_substream *substream,
|
|||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_device *socdev = rtd->socdev;
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
u16 dac = wm8728_read_reg_cache(codec, WM8728_DACCTL);
|
||||
u16 dac = snd_soc_read(codec, WM8728_DACCTL);
|
||||
|
||||
dac &= ~0x18;
|
||||
|
||||
|
@ -155,7 +116,7 @@ static int wm8728_hw_params(struct snd_pcm_substream *substream,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
wm8728_write(codec, WM8728_DACCTL, dac);
|
||||
snd_soc_write(codec, WM8728_DACCTL, dac);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -164,7 +125,7 @@ static int wm8728_set_dai_fmt(struct snd_soc_dai *codec_dai,
|
|||
unsigned int fmt)
|
||||
{
|
||||
struct snd_soc_codec *codec = codec_dai->codec;
|
||||
u16 iface = wm8728_read_reg_cache(codec, WM8728_IFCTL);
|
||||
u16 iface = snd_soc_read(codec, WM8728_IFCTL);
|
||||
|
||||
/* Currently only I2S is supported by the driver, though the
|
||||
* hardware is more flexible.
|
||||
|
@ -204,7 +165,7 @@ static int wm8728_set_dai_fmt(struct snd_soc_dai *codec_dai,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
wm8728_write(codec, WM8728_IFCTL, iface);
|
||||
snd_soc_write(codec, WM8728_IFCTL, iface);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -220,19 +181,19 @@ static int wm8728_set_bias_level(struct snd_soc_codec *codec,
|
|||
case SND_SOC_BIAS_STANDBY:
|
||||
if (codec->bias_level == SND_SOC_BIAS_OFF) {
|
||||
/* Power everything up... */
|
||||
reg = wm8728_read_reg_cache(codec, WM8728_DACCTL);
|
||||
wm8728_write(codec, WM8728_DACCTL, reg & ~0x4);
|
||||
reg = snd_soc_read(codec, WM8728_DACCTL);
|
||||
snd_soc_write(codec, WM8728_DACCTL, reg & ~0x4);
|
||||
|
||||
/* ..then sync in the register cache. */
|
||||
for (i = 0; i < ARRAY_SIZE(wm8728_reg_defaults); i++)
|
||||
wm8728_write(codec, i,
|
||||
wm8728_read_reg_cache(codec, i));
|
||||
snd_soc_write(codec, i,
|
||||
snd_soc_read(codec, i));
|
||||
}
|
||||
break;
|
||||
|
||||
case SND_SOC_BIAS_OFF:
|
||||
reg = wm8728_read_reg_cache(codec, WM8728_DACCTL);
|
||||
wm8728_write(codec, WM8728_DACCTL, reg | 0x4);
|
||||
reg = snd_soc_read(codec, WM8728_DACCTL);
|
||||
snd_soc_write(codec, WM8728_DACCTL, reg | 0x4);
|
||||
break;
|
||||
}
|
||||
codec->bias_level = level;
|
||||
|
@ -287,15 +248,14 @@ static int wm8728_resume(struct platform_device *pdev)
|
|||
* initialise the WM8728 driver
|
||||
* register the mixer and dsp interfaces with the kernel
|
||||
*/
|
||||
static int wm8728_init(struct snd_soc_device *socdev)
|
||||
static int wm8728_init(struct snd_soc_device *socdev,
|
||||
enum snd_soc_control_type control)
|
||||
{
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
int ret = 0;
|
||||
|
||||
codec->name = "WM8728";
|
||||
codec->owner = THIS_MODULE;
|
||||
codec->read = wm8728_read_reg_cache;
|
||||
codec->write = wm8728_write;
|
||||
codec->set_bias_level = wm8728_set_bias_level;
|
||||
codec->dai = &wm8728_dai;
|
||||
codec->num_dai = 1;
|
||||
|
@ -307,11 +267,18 @@ static int wm8728_init(struct snd_soc_device *socdev)
|
|||
if (codec->reg_cache == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = snd_soc_codec_set_cache_io(codec, 7, 9, control);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "wm8728: failed to configure cache I/O: %d\n",
|
||||
ret);
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* register pcms */
|
||||
ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "wm8728: failed to create pcms\n");
|
||||
goto pcm_err;
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* power on device */
|
||||
|
@ -331,7 +298,7 @@ static int wm8728_init(struct snd_soc_device *socdev)
|
|||
card_err:
|
||||
snd_soc_free_pcms(socdev);
|
||||
snd_soc_dapm_free(socdev);
|
||||
pcm_err:
|
||||
err:
|
||||
kfree(codec->reg_cache);
|
||||
return ret;
|
||||
}
|
||||
|
@ -357,7 +324,7 @@ static int wm8728_i2c_probe(struct i2c_client *i2c,
|
|||
i2c_set_clientdata(i2c, codec);
|
||||
codec->control_data = i2c;
|
||||
|
||||
ret = wm8728_init(socdev);
|
||||
ret = wm8728_init(socdev, SND_SOC_I2C);
|
||||
if (ret < 0)
|
||||
pr_err("failed to initialise WM8728\n");
|
||||
|
||||
|
@ -437,7 +404,7 @@ static int __devinit wm8728_spi_probe(struct spi_device *spi)
|
|||
|
||||
codec->control_data = spi;
|
||||
|
||||
ret = wm8728_init(socdev);
|
||||
ret = wm8728_init(socdev, SND_SOC_SPI);
|
||||
if (ret < 0)
|
||||
dev_err(&spi->dev, "failed to initialise WM8728\n");
|
||||
|
||||
|
@ -458,30 +425,6 @@ static struct spi_driver wm8728_spi_driver = {
|
|||
.probe = wm8728_spi_probe,
|
||||
.remove = __devexit_p(wm8728_spi_remove),
|
||||
};
|
||||
|
||||
static int wm8728_spi_write(struct spi_device *spi, const char *data, int len)
|
||||
{
|
||||
struct spi_transfer t;
|
||||
struct spi_message m;
|
||||
u8 msg[2];
|
||||
|
||||
if (len <= 0)
|
||||
return 0;
|
||||
|
||||
msg[0] = data[0];
|
||||
msg[1] = data[1];
|
||||
|
||||
spi_message_init(&m);
|
||||
memset(&t, 0, (sizeof t));
|
||||
|
||||
t.tx_buf = &msg[0];
|
||||
t.len = len;
|
||||
|
||||
spi_message_add_tail(&t, &m);
|
||||
spi_sync(spi, &m);
|
||||
|
||||
return len;
|
||||
}
|
||||
#endif /* CONFIG_SPI_MASTER */
|
||||
|
||||
static int wm8728_probe(struct platform_device *pdev)
|
||||
|
@ -506,13 +449,11 @@ static int wm8728_probe(struct platform_device *pdev)
|
|||
|
||||
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
|
||||
if (setup->i2c_address) {
|
||||
codec->hw_write = (hw_write_t)i2c_master_send;
|
||||
ret = wm8728_add_i2c_device(pdev, setup);
|
||||
}
|
||||
#endif
|
||||
#if defined(CONFIG_SPI_MASTER)
|
||||
if (setup->spi) {
|
||||
codec->hw_write = (hw_write_t)wm8728_spi_write;
|
||||
ret = spi_register_driver(&wm8728_spi_driver);
|
||||
if (ret != 0)
|
||||
printk(KERN_ERR "can't add spi driver");
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
#include <sound/soc.h>
|
||||
#include <sound/soc-dapm.h>
|
||||
#include <sound/initval.h>
|
||||
#include <sound/tlv.h>
|
||||
|
||||
#include "wm8731.h"
|
||||
|
||||
|
@ -39,9 +40,6 @@ struct wm8731_priv {
|
|||
unsigned int sysclk;
|
||||
};
|
||||
|
||||
#ifdef CONFIG_SPI_MASTER
|
||||
static int wm8731_spi_write(struct spi_device *spi, const char *data, int len);
|
||||
#endif
|
||||
|
||||
/*
|
||||
* wm8731 register cache
|
||||
|
@ -50,60 +48,12 @@ static int wm8731_spi_write(struct spi_device *spi, const char *data, int len);
|
|||
* There is no point in caching the reset register
|
||||
*/
|
||||
static const u16 wm8731_reg[WM8731_CACHEREGNUM] = {
|
||||
0x0097, 0x0097, 0x0079, 0x0079,
|
||||
0x000a, 0x0008, 0x009f, 0x000a,
|
||||
0x0000, 0x0000
|
||||
0x0097, 0x0097, 0x0079, 0x0079,
|
||||
0x000a, 0x0008, 0x009f, 0x000a,
|
||||
0x0000, 0x0000
|
||||
};
|
||||
|
||||
/*
|
||||
* read wm8731 register cache
|
||||
*/
|
||||
static inline unsigned int wm8731_read_reg_cache(struct snd_soc_codec *codec,
|
||||
unsigned int reg)
|
||||
{
|
||||
u16 *cache = codec->reg_cache;
|
||||
if (reg == WM8731_RESET)
|
||||
return 0;
|
||||
if (reg >= WM8731_CACHEREGNUM)
|
||||
return -1;
|
||||
return cache[reg];
|
||||
}
|
||||
|
||||
/*
|
||||
* write wm8731 register cache
|
||||
*/
|
||||
static inline void wm8731_write_reg_cache(struct snd_soc_codec *codec,
|
||||
u16 reg, unsigned int value)
|
||||
{
|
||||
u16 *cache = codec->reg_cache;
|
||||
if (reg >= WM8731_CACHEREGNUM)
|
||||
return;
|
||||
cache[reg] = value;
|
||||
}
|
||||
|
||||
/*
|
||||
* write to the WM8731 register space
|
||||
*/
|
||||
static int wm8731_write(struct snd_soc_codec *codec, unsigned int reg,
|
||||
unsigned int value)
|
||||
{
|
||||
u8 data[2];
|
||||
|
||||
/* data is
|
||||
* D15..D9 WM8731 register offset
|
||||
* D8...D0 register data
|
||||
*/
|
||||
data[0] = (reg << 1) | ((value >> 8) & 0x0001);
|
||||
data[1] = value & 0x00ff;
|
||||
|
||||
wm8731_write_reg_cache(codec, reg, value);
|
||||
if (codec->hw_write(codec->control_data, data, 2) == 2)
|
||||
return 0;
|
||||
else
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
#define wm8731_reset(c) wm8731_write(c, WM8731_RESET, 0)
|
||||
#define wm8731_reset(c) snd_soc_write(c, WM8731_RESET, 0)
|
||||
|
||||
static const char *wm8731_input_select[] = {"Line In", "Mic"};
|
||||
static const char *wm8731_deemph[] = {"None", "32Khz", "44.1Khz", "48Khz"};
|
||||
|
@ -113,20 +63,26 @@ static const struct soc_enum wm8731_enum[] = {
|
|||
SOC_ENUM_SINGLE(WM8731_APDIGI, 1, 4, wm8731_deemph),
|
||||
};
|
||||
|
||||
static const DECLARE_TLV_DB_SCALE(in_tlv, -3450, 150, 0);
|
||||
static const DECLARE_TLV_DB_SCALE(sidetone_tlv, -1500, 300, 0);
|
||||
static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1);
|
||||
|
||||
static const struct snd_kcontrol_new wm8731_snd_controls[] = {
|
||||
|
||||
SOC_DOUBLE_R("Master Playback Volume", WM8731_LOUT1V, WM8731_ROUT1V,
|
||||
0, 127, 0),
|
||||
SOC_DOUBLE_R_TLV("Master Playback Volume", WM8731_LOUT1V, WM8731_ROUT1V,
|
||||
0, 127, 0, out_tlv),
|
||||
SOC_DOUBLE_R("Master Playback ZC Switch", WM8731_LOUT1V, WM8731_ROUT1V,
|
||||
7, 1, 0),
|
||||
|
||||
SOC_DOUBLE_R("Capture Volume", WM8731_LINVOL, WM8731_RINVOL, 0, 31, 0),
|
||||
SOC_DOUBLE_R_TLV("Capture Volume", WM8731_LINVOL, WM8731_RINVOL, 0, 31, 0,
|
||||
in_tlv),
|
||||
SOC_DOUBLE_R("Line Capture Switch", WM8731_LINVOL, WM8731_RINVOL, 7, 1, 1),
|
||||
|
||||
SOC_SINGLE("Mic Boost (+20dB)", WM8731_APANA, 0, 1, 0),
|
||||
SOC_SINGLE("Capture Mic Switch", WM8731_APANA, 1, 1, 1),
|
||||
SOC_SINGLE("Mic Capture Switch", WM8731_APANA, 1, 1, 1),
|
||||
|
||||
SOC_SINGLE("Sidetone Playback Volume", WM8731_APANA, 6, 3, 1),
|
||||
SOC_SINGLE_TLV("Sidetone Playback Volume", WM8731_APANA, 6, 3, 1,
|
||||
sidetone_tlv),
|
||||
|
||||
SOC_SINGLE("ADC High Pass Filter Switch", WM8731_APDIGI, 0, 1, 1),
|
||||
SOC_SINGLE("Store DC Offset Switch", WM8731_APDIGI, 4, 1, 0),
|
||||
|
@ -260,12 +216,12 @@ static int wm8731_hw_params(struct snd_pcm_substream *substream,
|
|||
struct snd_soc_device *socdev = rtd->socdev;
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
struct wm8731_priv *wm8731 = codec->private_data;
|
||||
u16 iface = wm8731_read_reg_cache(codec, WM8731_IFACE) & 0xfff3;
|
||||
u16 iface = snd_soc_read(codec, WM8731_IFACE) & 0xfff3;
|
||||
int i = get_coeff(wm8731->sysclk, params_rate(params));
|
||||
u16 srate = (coeff_div[i].sr << 2) |
|
||||
(coeff_div[i].bosr << 1) | coeff_div[i].usb;
|
||||
|
||||
wm8731_write(codec, WM8731_SRATE, srate);
|
||||
snd_soc_write(codec, WM8731_SRATE, srate);
|
||||
|
||||
/* bit size */
|
||||
switch (params_format(params)) {
|
||||
|
@ -279,7 +235,7 @@ static int wm8731_hw_params(struct snd_pcm_substream *substream,
|
|||
break;
|
||||
}
|
||||
|
||||
wm8731_write(codec, WM8731_IFACE, iface);
|
||||
snd_soc_write(codec, WM8731_IFACE, iface);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -291,7 +247,7 @@ static int wm8731_pcm_prepare(struct snd_pcm_substream *substream,
|
|||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
|
||||
/* set active */
|
||||
wm8731_write(codec, WM8731_ACTIVE, 0x0001);
|
||||
snd_soc_write(codec, WM8731_ACTIVE, 0x0001);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -306,19 +262,19 @@ static void wm8731_shutdown(struct snd_pcm_substream *substream,
|
|||
/* deactivate */
|
||||
if (!codec->active) {
|
||||
udelay(50);
|
||||
wm8731_write(codec, WM8731_ACTIVE, 0x0);
|
||||
snd_soc_write(codec, WM8731_ACTIVE, 0x0);
|
||||
}
|
||||
}
|
||||
|
||||
static int wm8731_mute(struct snd_soc_dai *dai, int mute)
|
||||
{
|
||||
struct snd_soc_codec *codec = dai->codec;
|
||||
u16 mute_reg = wm8731_read_reg_cache(codec, WM8731_APDIGI) & 0xfff7;
|
||||
u16 mute_reg = snd_soc_read(codec, WM8731_APDIGI) & 0xfff7;
|
||||
|
||||
if (mute)
|
||||
wm8731_write(codec, WM8731_APDIGI, mute_reg | 0x8);
|
||||
snd_soc_write(codec, WM8731_APDIGI, mute_reg | 0x8);
|
||||
else
|
||||
wm8731_write(codec, WM8731_APDIGI, mute_reg);
|
||||
snd_soc_write(codec, WM8731_APDIGI, mute_reg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -396,7 +352,7 @@ static int wm8731_set_dai_fmt(struct snd_soc_dai *codec_dai,
|
|||
}
|
||||
|
||||
/* set iface */
|
||||
wm8731_write(codec, WM8731_IFACE, iface);
|
||||
snd_soc_write(codec, WM8731_IFACE, iface);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -412,12 +368,12 @@ static int wm8731_set_bias_level(struct snd_soc_codec *codec,
|
|||
break;
|
||||
case SND_SOC_BIAS_STANDBY:
|
||||
/* Clear PWROFF, gate CLKOUT, everything else as-is */
|
||||
reg = wm8731_read_reg_cache(codec, WM8731_PWR) & 0xff7f;
|
||||
wm8731_write(codec, WM8731_PWR, reg | 0x0040);
|
||||
reg = snd_soc_read(codec, WM8731_PWR) & 0xff7f;
|
||||
snd_soc_write(codec, WM8731_PWR, reg | 0x0040);
|
||||
break;
|
||||
case SND_SOC_BIAS_OFF:
|
||||
wm8731_write(codec, WM8731_ACTIVE, 0x0);
|
||||
wm8731_write(codec, WM8731_PWR, 0xffff);
|
||||
snd_soc_write(codec, WM8731_ACTIVE, 0x0);
|
||||
snd_soc_write(codec, WM8731_PWR, 0xffff);
|
||||
break;
|
||||
}
|
||||
codec->bias_level = level;
|
||||
|
@ -457,15 +413,17 @@ struct snd_soc_dai wm8731_dai = {
|
|||
.rates = WM8731_RATES,
|
||||
.formats = WM8731_FORMATS,},
|
||||
.ops = &wm8731_dai_ops,
|
||||
.symmetric_rates = 1,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(wm8731_dai);
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int wm8731_suspend(struct platform_device *pdev, pm_message_t state)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
|
||||
wm8731_write(codec, WM8731_ACTIVE, 0x0);
|
||||
snd_soc_write(codec, WM8731_ACTIVE, 0x0);
|
||||
wm8731_set_bias_level(codec, SND_SOC_BIAS_OFF);
|
||||
return 0;
|
||||
}
|
||||
|
@ -488,6 +446,10 @@ static int wm8731_resume(struct platform_device *pdev)
|
|||
wm8731_set_bias_level(codec, codec->suspend_bias_level);
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
#define wm8731_suspend NULL
|
||||
#define wm8731_resume NULL
|
||||
#endif
|
||||
|
||||
static int wm8731_probe(struct platform_device *pdev)
|
||||
{
|
||||
|
@ -547,15 +509,16 @@ struct snd_soc_codec_device soc_codec_dev_wm8731 = {
|
|||
};
|
||||
EXPORT_SYMBOL_GPL(soc_codec_dev_wm8731);
|
||||
|
||||
static int wm8731_register(struct wm8731_priv *wm8731)
|
||||
static int wm8731_register(struct wm8731_priv *wm8731,
|
||||
enum snd_soc_control_type control)
|
||||
{
|
||||
int ret;
|
||||
struct snd_soc_codec *codec = &wm8731->codec;
|
||||
u16 reg;
|
||||
|
||||
if (wm8731_codec) {
|
||||
dev_err(codec->dev, "Another WM8731 is registered\n");
|
||||
return -EINVAL;
|
||||
ret = -EINVAL;
|
||||
goto err;
|
||||
}
|
||||
|
||||
mutex_init(&codec->mutex);
|
||||
|
@ -565,8 +528,6 @@ static int wm8731_register(struct wm8731_priv *wm8731)
|
|||
codec->private_data = wm8731;
|
||||
codec->name = "WM8731";
|
||||
codec->owner = THIS_MODULE;
|
||||
codec->read = wm8731_read_reg_cache;
|
||||
codec->write = wm8731_write;
|
||||
codec->bias_level = SND_SOC_BIAS_OFF;
|
||||
codec->set_bias_level = wm8731_set_bias_level;
|
||||
codec->dai = &wm8731_dai;
|
||||
|
@ -576,10 +537,16 @@ static int wm8731_register(struct wm8731_priv *wm8731)
|
|||
|
||||
memcpy(codec->reg_cache, wm8731_reg, sizeof(wm8731_reg));
|
||||
|
||||
ret = snd_soc_codec_set_cache_io(codec, 7, 9, control);
|
||||
if (ret < 0) {
|
||||
dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = wm8731_reset(codec);
|
||||
if (ret < 0) {
|
||||
dev_err(codec->dev, "Failed to issue reset\n");
|
||||
return ret;
|
||||
dev_err(codec->dev, "Failed to issue reset: %d\n", ret);
|
||||
goto err;
|
||||
}
|
||||
|
||||
wm8731_dai.dev = codec->dev;
|
||||
|
@ -587,35 +554,36 @@ static int wm8731_register(struct wm8731_priv *wm8731)
|
|||
wm8731_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
|
||||
|
||||
/* Latch the update bits */
|
||||
reg = wm8731_read_reg_cache(codec, WM8731_LOUT1V);
|
||||
wm8731_write(codec, WM8731_LOUT1V, reg & ~0x0100);
|
||||
reg = wm8731_read_reg_cache(codec, WM8731_ROUT1V);
|
||||
wm8731_write(codec, WM8731_ROUT1V, reg & ~0x0100);
|
||||
reg = wm8731_read_reg_cache(codec, WM8731_LINVOL);
|
||||
wm8731_write(codec, WM8731_LINVOL, reg & ~0x0100);
|
||||
reg = wm8731_read_reg_cache(codec, WM8731_RINVOL);
|
||||
wm8731_write(codec, WM8731_RINVOL, reg & ~0x0100);
|
||||
snd_soc_update_bits(codec, WM8731_LOUT1V, 0x100, 0);
|
||||
snd_soc_update_bits(codec, WM8731_ROUT1V, 0x100, 0);
|
||||
snd_soc_update_bits(codec, WM8731_LINVOL, 0x100, 0);
|
||||
snd_soc_update_bits(codec, WM8731_RINVOL, 0x100, 0);
|
||||
|
||||
/* Disable bypass path by default */
|
||||
reg = wm8731_read_reg_cache(codec, WM8731_APANA);
|
||||
wm8731_write(codec, WM8731_APANA, reg & ~0x4);
|
||||
snd_soc_update_bits(codec, WM8731_APANA, 0x4, 0);
|
||||
|
||||
wm8731_codec = codec;
|
||||
|
||||
ret = snd_soc_register_codec(codec);
|
||||
if (ret != 0) {
|
||||
dev_err(codec->dev, "Failed to register codec: %d\n", ret);
|
||||
return ret;
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = snd_soc_register_dai(&wm8731_dai);
|
||||
if (ret != 0) {
|
||||
dev_err(codec->dev, "Failed to register DAI: %d\n", ret);
|
||||
snd_soc_unregister_codec(codec);
|
||||
return ret;
|
||||
goto err_codec;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_codec:
|
||||
snd_soc_unregister_codec(codec);
|
||||
err:
|
||||
kfree(wm8731);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void wm8731_unregister(struct wm8731_priv *wm8731)
|
||||
|
@ -628,30 +596,6 @@ static void wm8731_unregister(struct wm8731_priv *wm8731)
|
|||
}
|
||||
|
||||
#if defined(CONFIG_SPI_MASTER)
|
||||
static int wm8731_spi_write(struct spi_device *spi, const char *data, int len)
|
||||
{
|
||||
struct spi_transfer t;
|
||||
struct spi_message m;
|
||||
u8 msg[2];
|
||||
|
||||
if (len <= 0)
|
||||
return 0;
|
||||
|
||||
msg[0] = data[0];
|
||||
msg[1] = data[1];
|
||||
|
||||
spi_message_init(&m);
|
||||
memset(&t, 0, (sizeof t));
|
||||
|
||||
t.tx_buf = &msg[0];
|
||||
t.len = len;
|
||||
|
||||
spi_message_add_tail(&t, &m);
|
||||
spi_sync(spi, &m);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static int __devinit wm8731_spi_probe(struct spi_device *spi)
|
||||
{
|
||||
struct snd_soc_codec *codec;
|
||||
|
@ -663,12 +607,11 @@ static int __devinit wm8731_spi_probe(struct spi_device *spi)
|
|||
|
||||
codec = &wm8731->codec;
|
||||
codec->control_data = spi;
|
||||
codec->hw_write = (hw_write_t)wm8731_spi_write;
|
||||
codec->dev = &spi->dev;
|
||||
|
||||
dev_set_drvdata(&spi->dev, wm8731);
|
||||
|
||||
return wm8731_register(wm8731);
|
||||
return wm8731_register(wm8731, SND_SOC_SPI);
|
||||
}
|
||||
|
||||
static int __devexit wm8731_spi_remove(struct spi_device *spi)
|
||||
|
@ -680,6 +623,21 @@ static int __devexit wm8731_spi_remove(struct spi_device *spi)
|
|||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int wm8731_spi_suspend(struct spi_device *spi, pm_message_t msg)
|
||||
{
|
||||
return snd_soc_suspend_device(&spi->dev);
|
||||
}
|
||||
|
||||
static int wm8731_spi_resume(struct spi_device *spi)
|
||||
{
|
||||
return snd_soc_resume_device(&spi->dev);
|
||||
}
|
||||
#else
|
||||
#define wm8731_spi_suspend NULL
|
||||
#define wm8731_spi_resume NULL
|
||||
#endif
|
||||
|
||||
static struct spi_driver wm8731_spi_driver = {
|
||||
.driver = {
|
||||
.name = "wm8731",
|
||||
|
@ -687,6 +645,8 @@ static struct spi_driver wm8731_spi_driver = {
|
|||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = wm8731_spi_probe,
|
||||
.suspend = wm8731_spi_suspend,
|
||||
.resume = wm8731_spi_resume,
|
||||
.remove = __devexit_p(wm8731_spi_remove),
|
||||
};
|
||||
#endif /* CONFIG_SPI_MASTER */
|
||||
|
@ -703,14 +663,13 @@ static __devinit int wm8731_i2c_probe(struct i2c_client *i2c,
|
|||
return -ENOMEM;
|
||||
|
||||
codec = &wm8731->codec;
|
||||
codec->hw_write = (hw_write_t)i2c_master_send;
|
||||
|
||||
i2c_set_clientdata(i2c, wm8731);
|
||||
codec->control_data = i2c;
|
||||
|
||||
codec->dev = &i2c->dev;
|
||||
|
||||
return wm8731_register(wm8731);
|
||||
return wm8731_register(wm8731, SND_SOC_I2C);
|
||||
}
|
||||
|
||||
static __devexit int wm8731_i2c_remove(struct i2c_client *client)
|
||||
|
@ -720,6 +679,21 @@ static __devexit int wm8731_i2c_remove(struct i2c_client *client)
|
|||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int wm8731_i2c_suspend(struct i2c_client *i2c, pm_message_t msg)
|
||||
{
|
||||
return snd_soc_suspend_device(&i2c->dev);
|
||||
}
|
||||
|
||||
static int wm8731_i2c_resume(struct i2c_client *i2c)
|
||||
{
|
||||
return snd_soc_resume_device(&i2c->dev);
|
||||
}
|
||||
#else
|
||||
#define wm8731_i2c_suspend NULL
|
||||
#define wm8731_i2c_resume NULL
|
||||
#endif
|
||||
|
||||
static const struct i2c_device_id wm8731_i2c_id[] = {
|
||||
{ "wm8731", 0 },
|
||||
{ }
|
||||
|
@ -733,6 +707,8 @@ static struct i2c_driver wm8731_i2c_driver = {
|
|||
},
|
||||
.probe = wm8731_i2c_probe,
|
||||
.remove = __devexit_p(wm8731_i2c_remove),
|
||||
.suspend = wm8731_i2c_suspend,
|
||||
.resume = wm8731_i2c_resume,
|
||||
.id_table = wm8731_i2c_id,
|
||||
};
|
||||
#endif
|
||||
|
|
|
@ -55,50 +55,7 @@ static const u16 wm8750_reg[] = {
|
|||
0x0079, 0x0079, 0x0079, /* 40 */
|
||||
};
|
||||
|
||||
/*
|
||||
* read wm8750 register cache
|
||||
*/
|
||||
static inline unsigned int wm8750_read_reg_cache(struct snd_soc_codec *codec,
|
||||
unsigned int reg)
|
||||
{
|
||||
u16 *cache = codec->reg_cache;
|
||||
if (reg > WM8750_CACHE_REGNUM)
|
||||
return -1;
|
||||
return cache[reg];
|
||||
}
|
||||
|
||||
/*
|
||||
* write wm8750 register cache
|
||||
*/
|
||||
static inline void wm8750_write_reg_cache(struct snd_soc_codec *codec,
|
||||
unsigned int reg, unsigned int value)
|
||||
{
|
||||
u16 *cache = codec->reg_cache;
|
||||
if (reg > WM8750_CACHE_REGNUM)
|
||||
return;
|
||||
cache[reg] = value;
|
||||
}
|
||||
|
||||
static int wm8750_write(struct snd_soc_codec *codec, unsigned int reg,
|
||||
unsigned int value)
|
||||
{
|
||||
u8 data[2];
|
||||
|
||||
/* data is
|
||||
* D15..D9 WM8753 register offset
|
||||
* D8...D0 register data
|
||||
*/
|
||||
data[0] = (reg << 1) | ((value >> 8) & 0x0001);
|
||||
data[1] = value & 0x00ff;
|
||||
|
||||
wm8750_write_reg_cache(codec, reg, value);
|
||||
if (codec->hw_write(codec->control_data, data, 2) == 2)
|
||||
return 0;
|
||||
else
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
#define wm8750_reset(c) wm8750_write(c, WM8750_RESET, 0)
|
||||
#define wm8750_reset(c) snd_soc_write(c, WM8750_RESET, 0)
|
||||
|
||||
/*
|
||||
* WM8750 Controls
|
||||
|
@ -594,7 +551,7 @@ static int wm8750_set_dai_fmt(struct snd_soc_dai *codec_dai,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
wm8750_write(codec, WM8750_IFACE, iface);
|
||||
snd_soc_write(codec, WM8750_IFACE, iface);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -606,8 +563,8 @@ static int wm8750_pcm_hw_params(struct snd_pcm_substream *substream,
|
|||
struct snd_soc_device *socdev = rtd->socdev;
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
struct wm8750_priv *wm8750 = codec->private_data;
|
||||
u16 iface = wm8750_read_reg_cache(codec, WM8750_IFACE) & 0x1f3;
|
||||
u16 srate = wm8750_read_reg_cache(codec, WM8750_SRATE) & 0x1c0;
|
||||
u16 iface = snd_soc_read(codec, WM8750_IFACE) & 0x1f3;
|
||||
u16 srate = snd_soc_read(codec, WM8750_SRATE) & 0x1c0;
|
||||
int coeff = get_coeff(wm8750->sysclk, params_rate(params));
|
||||
|
||||
/* bit size */
|
||||
|
@ -626,9 +583,9 @@ static int wm8750_pcm_hw_params(struct snd_pcm_substream *substream,
|
|||
}
|
||||
|
||||
/* set iface & srate */
|
||||
wm8750_write(codec, WM8750_IFACE, iface);
|
||||
snd_soc_write(codec, WM8750_IFACE, iface);
|
||||
if (coeff >= 0)
|
||||
wm8750_write(codec, WM8750_SRATE, srate |
|
||||
snd_soc_write(codec, WM8750_SRATE, srate |
|
||||
(coeff_div[coeff].sr << 1) | coeff_div[coeff].usb);
|
||||
|
||||
return 0;
|
||||
|
@ -637,35 +594,35 @@ static int wm8750_pcm_hw_params(struct snd_pcm_substream *substream,
|
|||
static int wm8750_mute(struct snd_soc_dai *dai, int mute)
|
||||
{
|
||||
struct snd_soc_codec *codec = dai->codec;
|
||||
u16 mute_reg = wm8750_read_reg_cache(codec, WM8750_ADCDAC) & 0xfff7;
|
||||
u16 mute_reg = snd_soc_read(codec, WM8750_ADCDAC) & 0xfff7;
|
||||
|
||||
if (mute)
|
||||
wm8750_write(codec, WM8750_ADCDAC, mute_reg | 0x8);
|
||||
snd_soc_write(codec, WM8750_ADCDAC, mute_reg | 0x8);
|
||||
else
|
||||
wm8750_write(codec, WM8750_ADCDAC, mute_reg);
|
||||
snd_soc_write(codec, WM8750_ADCDAC, mute_reg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wm8750_set_bias_level(struct snd_soc_codec *codec,
|
||||
enum snd_soc_bias_level level)
|
||||
{
|
||||
u16 pwr_reg = wm8750_read_reg_cache(codec, WM8750_PWR1) & 0xfe3e;
|
||||
u16 pwr_reg = snd_soc_read(codec, WM8750_PWR1) & 0xfe3e;
|
||||
|
||||
switch (level) {
|
||||
case SND_SOC_BIAS_ON:
|
||||
/* set vmid to 50k and unmute dac */
|
||||
wm8750_write(codec, WM8750_PWR1, pwr_reg | 0x00c0);
|
||||
snd_soc_write(codec, WM8750_PWR1, pwr_reg | 0x00c0);
|
||||
break;
|
||||
case SND_SOC_BIAS_PREPARE:
|
||||
/* set vmid to 5k for quick power up */
|
||||
wm8750_write(codec, WM8750_PWR1, pwr_reg | 0x01c1);
|
||||
snd_soc_write(codec, WM8750_PWR1, pwr_reg | 0x01c1);
|
||||
break;
|
||||
case SND_SOC_BIAS_STANDBY:
|
||||
/* mute dac and set vmid to 500k, enable VREF */
|
||||
wm8750_write(codec, WM8750_PWR1, pwr_reg | 0x0141);
|
||||
snd_soc_write(codec, WM8750_PWR1, pwr_reg | 0x0141);
|
||||
break;
|
||||
case SND_SOC_BIAS_OFF:
|
||||
wm8750_write(codec, WM8750_PWR1, 0x0001);
|
||||
snd_soc_write(codec, WM8750_PWR1, 0x0001);
|
||||
break;
|
||||
}
|
||||
codec->bias_level = level;
|
||||
|
@ -754,15 +711,14 @@ static int wm8750_resume(struct platform_device *pdev)
|
|||
* initialise the WM8750 driver
|
||||
* register the mixer and dsp interfaces with the kernel
|
||||
*/
|
||||
static int wm8750_init(struct snd_soc_device *socdev)
|
||||
static int wm8750_init(struct snd_soc_device *socdev,
|
||||
enum snd_soc_control_type control)
|
||||
{
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
int reg, ret = 0;
|
||||
|
||||
codec->name = "WM8750";
|
||||
codec->owner = THIS_MODULE;
|
||||
codec->read = wm8750_read_reg_cache;
|
||||
codec->write = wm8750_write;
|
||||
codec->set_bias_level = wm8750_set_bias_level;
|
||||
codec->dai = &wm8750_dai;
|
||||
codec->num_dai = 1;
|
||||
|
@ -771,13 +727,23 @@ static int wm8750_init(struct snd_soc_device *socdev)
|
|||
if (codec->reg_cache == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
wm8750_reset(codec);
|
||||
ret = snd_soc_codec_set_cache_io(codec, 7, 9, control);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "wm8750: failed to set cache I/O: %d\n", ret);
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = wm8750_reset(codec);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "wm8750: failed to reset: %d\n", ret);
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* register pcms */
|
||||
ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "wm8750: failed to create pcms\n");
|
||||
goto pcm_err;
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* charge output caps */
|
||||
|
@ -786,22 +752,22 @@ static int wm8750_init(struct snd_soc_device *socdev)
|
|||
schedule_delayed_work(&codec->delayed_work, msecs_to_jiffies(1000));
|
||||
|
||||
/* set the update bits */
|
||||
reg = wm8750_read_reg_cache(codec, WM8750_LDAC);
|
||||
wm8750_write(codec, WM8750_LDAC, reg | 0x0100);
|
||||
reg = wm8750_read_reg_cache(codec, WM8750_RDAC);
|
||||
wm8750_write(codec, WM8750_RDAC, reg | 0x0100);
|
||||
reg = wm8750_read_reg_cache(codec, WM8750_LOUT1V);
|
||||
wm8750_write(codec, WM8750_LOUT1V, reg | 0x0100);
|
||||
reg = wm8750_read_reg_cache(codec, WM8750_ROUT1V);
|
||||
wm8750_write(codec, WM8750_ROUT1V, reg | 0x0100);
|
||||
reg = wm8750_read_reg_cache(codec, WM8750_LOUT2V);
|
||||
wm8750_write(codec, WM8750_LOUT2V, reg | 0x0100);
|
||||
reg = wm8750_read_reg_cache(codec, WM8750_ROUT2V);
|
||||
wm8750_write(codec, WM8750_ROUT2V, reg | 0x0100);
|
||||
reg = wm8750_read_reg_cache(codec, WM8750_LINVOL);
|
||||
wm8750_write(codec, WM8750_LINVOL, reg | 0x0100);
|
||||
reg = wm8750_read_reg_cache(codec, WM8750_RINVOL);
|
||||
wm8750_write(codec, WM8750_RINVOL, reg | 0x0100);
|
||||
reg = snd_soc_read(codec, WM8750_LDAC);
|
||||
snd_soc_write(codec, WM8750_LDAC, reg | 0x0100);
|
||||
reg = snd_soc_read(codec, WM8750_RDAC);
|
||||
snd_soc_write(codec, WM8750_RDAC, reg | 0x0100);
|
||||
reg = snd_soc_read(codec, WM8750_LOUT1V);
|
||||
snd_soc_write(codec, WM8750_LOUT1V, reg | 0x0100);
|
||||
reg = snd_soc_read(codec, WM8750_ROUT1V);
|
||||
snd_soc_write(codec, WM8750_ROUT1V, reg | 0x0100);
|
||||
reg = snd_soc_read(codec, WM8750_LOUT2V);
|
||||
snd_soc_write(codec, WM8750_LOUT2V, reg | 0x0100);
|
||||
reg = snd_soc_read(codec, WM8750_ROUT2V);
|
||||
snd_soc_write(codec, WM8750_ROUT2V, reg | 0x0100);
|
||||
reg = snd_soc_read(codec, WM8750_LINVOL);
|
||||
snd_soc_write(codec, WM8750_LINVOL, reg | 0x0100);
|
||||
reg = snd_soc_read(codec, WM8750_RINVOL);
|
||||
snd_soc_write(codec, WM8750_RINVOL, reg | 0x0100);
|
||||
|
||||
snd_soc_add_controls(codec, wm8750_snd_controls,
|
||||
ARRAY_SIZE(wm8750_snd_controls));
|
||||
|
@ -816,7 +782,7 @@ static int wm8750_init(struct snd_soc_device *socdev)
|
|||
card_err:
|
||||
snd_soc_free_pcms(socdev);
|
||||
snd_soc_dapm_free(socdev);
|
||||
pcm_err:
|
||||
err:
|
||||
kfree(codec->reg_cache);
|
||||
return ret;
|
||||
}
|
||||
|
@ -844,7 +810,7 @@ static int wm8750_i2c_probe(struct i2c_client *i2c,
|
|||
i2c_set_clientdata(i2c, codec);
|
||||
codec->control_data = i2c;
|
||||
|
||||
ret = wm8750_init(socdev);
|
||||
ret = wm8750_init(socdev, SND_SOC_I2C);
|
||||
if (ret < 0)
|
||||
pr_err("failed to initialise WM8750\n");
|
||||
|
||||
|
@ -924,7 +890,7 @@ static int __devinit wm8750_spi_probe(struct spi_device *spi)
|
|||
|
||||
codec->control_data = spi;
|
||||
|
||||
ret = wm8750_init(socdev);
|
||||
ret = wm8750_init(socdev, SND_SOC_SPI);
|
||||
if (ret < 0)
|
||||
dev_err(&spi->dev, "failed to initialise WM8750\n");
|
||||
|
||||
|
@ -945,30 +911,6 @@ static struct spi_driver wm8750_spi_driver = {
|
|||
.probe = wm8750_spi_probe,
|
||||
.remove = __devexit_p(wm8750_spi_remove),
|
||||
};
|
||||
|
||||
static int wm8750_spi_write(struct spi_device *spi, const char *data, int len)
|
||||
{
|
||||
struct spi_transfer t;
|
||||
struct spi_message m;
|
||||
u8 msg[2];
|
||||
|
||||
if (len <= 0)
|
||||
return 0;
|
||||
|
||||
msg[0] = data[0];
|
||||
msg[1] = data[1];
|
||||
|
||||
spi_message_init(&m);
|
||||
memset(&t, 0, (sizeof t));
|
||||
|
||||
t.tx_buf = &msg[0];
|
||||
t.len = len;
|
||||
|
||||
spi_message_add_tail(&t, &m);
|
||||
spi_sync(spi, &m);
|
||||
|
||||
return len;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int wm8750_probe(struct platform_device *pdev)
|
||||
|
@ -1002,13 +944,11 @@ static int wm8750_probe(struct platform_device *pdev)
|
|||
|
||||
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
|
||||
if (setup->i2c_address) {
|
||||
codec->hw_write = (hw_write_t)i2c_master_send;
|
||||
ret = wm8750_add_i2c_device(pdev, setup);
|
||||
}
|
||||
#endif
|
||||
#if defined(CONFIG_SPI_MASTER)
|
||||
if (setup->spi) {
|
||||
codec->hw_write = (hw_write_t)wm8750_spi_write;
|
||||
ret = spi_register_driver(&wm8750_spi_driver);
|
||||
if (ret != 0)
|
||||
printk(KERN_ERR "can't add spi driver");
|
||||
|
|
|
@ -1766,6 +1766,21 @@ static int wm8753_i2c_remove(struct i2c_client *client)
|
|||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int wm8753_i2c_suspend(struct i2c_client *client, pm_message_t msg)
|
||||
{
|
||||
return snd_soc_suspend_device(&client->dev);
|
||||
}
|
||||
|
||||
static int wm8753_i2c_resume(struct i2c_client *client)
|
||||
{
|
||||
return snd_soc_resume_device(&client->dev);
|
||||
}
|
||||
#else
|
||||
#define wm8753_i2c_suspend NULL
|
||||
#define wm8753_i2c_resume NULL
|
||||
#endif
|
||||
|
||||
static const struct i2c_device_id wm8753_i2c_id[] = {
|
||||
{ "wm8753", 0 },
|
||||
{ }
|
||||
|
@ -1779,6 +1794,8 @@ static struct i2c_driver wm8753_i2c_driver = {
|
|||
},
|
||||
.probe = wm8753_i2c_probe,
|
||||
.remove = wm8753_i2c_remove,
|
||||
.suspend = wm8753_i2c_suspend,
|
||||
.resume = wm8753_i2c_resume,
|
||||
.id_table = wm8753_i2c_id,
|
||||
};
|
||||
#endif
|
||||
|
@ -1834,6 +1851,22 @@ static int __devexit wm8753_spi_remove(struct spi_device *spi)
|
|||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int wm8753_spi_suspend(struct spi_device *spi, pm_message_t msg)
|
||||
{
|
||||
return snd_soc_suspend_device(&spi->dev);
|
||||
}
|
||||
|
||||
static int wm8753_spi_resume(struct spi_device *spi)
|
||||
{
|
||||
return snd_soc_resume_device(&spi->dev);
|
||||
}
|
||||
|
||||
#else
|
||||
#define wm8753_spi_suspend NULL
|
||||
#define wm8753_spi_resume NULL
|
||||
#endif
|
||||
|
||||
static struct spi_driver wm8753_spi_driver = {
|
||||
.driver = {
|
||||
.name = "wm8753",
|
||||
|
@ -1842,6 +1875,8 @@ static struct spi_driver wm8753_spi_driver = {
|
|||
},
|
||||
.probe = wm8753_spi_probe,
|
||||
.remove = __devexit_p(wm8753_spi_remove),
|
||||
.suspend = wm8753_spi_suspend,
|
||||
.resume = wm8753_spi_resume,
|
||||
};
|
||||
#endif
|
||||
|
||||
|
|
|
@ -0,0 +1,744 @@
|
|||
/*
|
||||
* wm8776.c -- WM8776 ALSA SoC Audio driver
|
||||
*
|
||||
* Copyright 2009 Wolfson Microelectronics plc
|
||||
*
|
||||
* Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* TODO: Input ALC/limiter support
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/soc-dapm.h>
|
||||
#include <sound/initval.h>
|
||||
#include <sound/tlv.h>
|
||||
|
||||
#include "wm8776.h"
|
||||
|
||||
static struct snd_soc_codec *wm8776_codec;
|
||||
struct snd_soc_codec_device soc_codec_dev_wm8776;
|
||||
|
||||
/* codec private data */
|
||||
struct wm8776_priv {
|
||||
struct snd_soc_codec codec;
|
||||
u16 reg_cache[WM8776_CACHEREGNUM];
|
||||
int sysclk[2];
|
||||
};
|
||||
|
||||
#ifdef CONFIG_SPI_MASTER
|
||||
static int wm8776_spi_write(struct spi_device *spi, const char *data, int len);
|
||||
#endif
|
||||
|
||||
static const u16 wm8776_reg[WM8776_CACHEREGNUM] = {
|
||||
0x79, 0x79, 0x79, 0xff, 0xff, /* 4 */
|
||||
0xff, 0x00, 0x90, 0x00, 0x00, /* 9 */
|
||||
0x22, 0x22, 0x22, 0x08, 0xcf, /* 14 */
|
||||
0xcf, 0x7b, 0x00, 0x32, 0x00, /* 19 */
|
||||
0xa6, 0x01, 0x01
|
||||
};
|
||||
|
||||
static int wm8776_reset(struct snd_soc_codec *codec)
|
||||
{
|
||||
return snd_soc_write(codec, WM8776_RESET, 0);
|
||||
}
|
||||
|
||||
static const DECLARE_TLV_DB_SCALE(hp_tlv, -12100, 100, 1);
|
||||
static const DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 1);
|
||||
static const DECLARE_TLV_DB_SCALE(adc_tlv, -10350, 50, 1);
|
||||
|
||||
static const struct snd_kcontrol_new wm8776_snd_controls[] = {
|
||||
SOC_DOUBLE_R_TLV("Headphone Playback Volume", WM8776_HPLVOL, WM8776_HPRVOL,
|
||||
0, 127, 0, hp_tlv),
|
||||
SOC_DOUBLE_R_TLV("Digital Playback Volume", WM8776_DACLVOL, WM8776_DACRVOL,
|
||||
0, 255, 0, dac_tlv),
|
||||
SOC_SINGLE("Digital Playback ZC Switch", WM8776_DACCTRL1, 0, 1, 0),
|
||||
|
||||
SOC_SINGLE("Deemphasis Switch", WM8776_DACCTRL2, 0, 1, 0),
|
||||
|
||||
SOC_DOUBLE_R_TLV("Capture Volume", WM8776_ADCLVOL, WM8776_ADCRVOL,
|
||||
0, 255, 0, adc_tlv),
|
||||
SOC_DOUBLE("Capture Switch", WM8776_ADCMUX, 7, 6, 1, 1),
|
||||
SOC_DOUBLE_R("Capture ZC Switch", WM8776_ADCLVOL, WM8776_ADCRVOL, 8, 1, 0),
|
||||
SOC_SINGLE("Capture HPF Switch", WM8776_ADCIFCTRL, 8, 1, 1),
|
||||
};
|
||||
|
||||
static const struct snd_kcontrol_new inmix_controls[] = {
|
||||
SOC_DAPM_SINGLE("AIN1 Switch", WM8776_ADCMUX, 0, 1, 0),
|
||||
SOC_DAPM_SINGLE("AIN2 Switch", WM8776_ADCMUX, 1, 1, 0),
|
||||
SOC_DAPM_SINGLE("AIN3 Switch", WM8776_ADCMUX, 2, 1, 0),
|
||||
SOC_DAPM_SINGLE("AIN4 Switch", WM8776_ADCMUX, 3, 1, 0),
|
||||
SOC_DAPM_SINGLE("AIN5 Switch", WM8776_ADCMUX, 4, 1, 0),
|
||||
};
|
||||
|
||||
static const struct snd_kcontrol_new outmix_controls[] = {
|
||||
SOC_DAPM_SINGLE("DAC Switch", WM8776_OUTMUX, 0, 1, 0),
|
||||
SOC_DAPM_SINGLE("AUX Switch", WM8776_OUTMUX, 1, 1, 0),
|
||||
SOC_DAPM_SINGLE("Bypass Switch", WM8776_OUTMUX, 2, 1, 0),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_widget wm8776_dapm_widgets[] = {
|
||||
SND_SOC_DAPM_INPUT("AUX"),
|
||||
SND_SOC_DAPM_INPUT("AUX"),
|
||||
|
||||
SND_SOC_DAPM_INPUT("AIN1"),
|
||||
SND_SOC_DAPM_INPUT("AIN2"),
|
||||
SND_SOC_DAPM_INPUT("AIN3"),
|
||||
SND_SOC_DAPM_INPUT("AIN4"),
|
||||
SND_SOC_DAPM_INPUT("AIN5"),
|
||||
|
||||
SND_SOC_DAPM_MIXER("Input Mixer", WM8776_PWRDOWN, 6, 1,
|
||||
inmix_controls, ARRAY_SIZE(inmix_controls)),
|
||||
|
||||
SND_SOC_DAPM_ADC("ADC", "Capture", WM8776_PWRDOWN, 1, 1),
|
||||
SND_SOC_DAPM_DAC("DAC", "Playback", WM8776_PWRDOWN, 2, 1),
|
||||
|
||||
SND_SOC_DAPM_MIXER("Output Mixer", SND_SOC_NOPM, 0, 0,
|
||||
outmix_controls, ARRAY_SIZE(outmix_controls)),
|
||||
|
||||
SND_SOC_DAPM_PGA("Headphone PGA", WM8776_PWRDOWN, 3, 1, NULL, 0),
|
||||
|
||||
SND_SOC_DAPM_OUTPUT("VOUT"),
|
||||
|
||||
SND_SOC_DAPM_OUTPUT("HPOUTL"),
|
||||
SND_SOC_DAPM_OUTPUT("HPOUTR"),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route routes[] = {
|
||||
{ "Input Mixer", "AIN1 Switch", "AIN1" },
|
||||
{ "Input Mixer", "AIN2 Switch", "AIN2" },
|
||||
{ "Input Mixer", "AIN3 Switch", "AIN3" },
|
||||
{ "Input Mixer", "AIN4 Switch", "AIN4" },
|
||||
{ "Input Mixer", "AIN5 Switch", "AIN5" },
|
||||
|
||||
{ "ADC", NULL, "Input Mixer" },
|
||||
|
||||
{ "Output Mixer", "DAC Switch", "DAC" },
|
||||
{ "Output Mixer", "AUX Switch", "AUX" },
|
||||
{ "Output Mixer", "Bypass Switch", "Input Mixer" },
|
||||
|
||||
{ "VOUT", NULL, "Output Mixer" },
|
||||
|
||||
{ "Headphone PGA", NULL, "Output Mixer" },
|
||||
|
||||
{ "HPOUTL", NULL, "Headphone PGA" },
|
||||
{ "HPOUTR", NULL, "Headphone PGA" },
|
||||
};
|
||||
|
||||
static int wm8776_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
|
||||
{
|
||||
struct snd_soc_codec *codec = dai->codec;
|
||||
int reg, iface, master;
|
||||
|
||||
switch (dai->id) {
|
||||
case WM8776_DAI_DAC:
|
||||
reg = WM8776_DACIFCTRL;
|
||||
master = 0x80;
|
||||
break;
|
||||
case WM8776_DAI_ADC:
|
||||
reg = WM8776_ADCIFCTRL;
|
||||
master = 0x100;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
iface = 0;
|
||||
|
||||
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
|
||||
case SND_SOC_DAIFMT_CBM_CFM:
|
||||
break;
|
||||
case SND_SOC_DAIFMT_CBS_CFS:
|
||||
master = 0;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
|
||||
case SND_SOC_DAIFMT_I2S:
|
||||
iface |= 0x0002;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_RIGHT_J:
|
||||
break;
|
||||
case SND_SOC_DAIFMT_LEFT_J:
|
||||
iface |= 0x0001;
|
||||
break;
|
||||
/* FIXME: CHECK A/B */
|
||||
case SND_SOC_DAIFMT_DSP_A:
|
||||
iface |= 0x0003;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_DSP_B:
|
||||
iface |= 0x0007;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
|
||||
case SND_SOC_DAIFMT_NB_NF:
|
||||
break;
|
||||
case SND_SOC_DAIFMT_IB_IF:
|
||||
iface |= 0x00c;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_IB_NF:
|
||||
iface |= 0x008;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_NB_IF:
|
||||
iface |= 0x004;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Finally, write out the values */
|
||||
snd_soc_update_bits(codec, reg, 0xf, iface);
|
||||
snd_soc_update_bits(codec, WM8776_MSTRCTRL, 0x180, master);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mclk_ratios[] = {
|
||||
128,
|
||||
192,
|
||||
256,
|
||||
384,
|
||||
512,
|
||||
768,
|
||||
};
|
||||
|
||||
static int wm8776_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct snd_soc_codec *codec = dai->codec;
|
||||
struct wm8776_priv *wm8776 = codec->private_data;
|
||||
int iface_reg, iface;
|
||||
int ratio_shift, master;
|
||||
int i;
|
||||
|
||||
iface = 0;
|
||||
|
||||
switch (dai->id) {
|
||||
case WM8776_DAI_DAC:
|
||||
iface_reg = WM8776_DACIFCTRL;
|
||||
master = 0x80;
|
||||
ratio_shift = 4;
|
||||
break;
|
||||
case WM8776_DAI_ADC:
|
||||
iface_reg = WM8776_ADCIFCTRL;
|
||||
master = 0x100;
|
||||
ratio_shift = 0;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
|
||||
/* Set word length */
|
||||
switch (params_format(params)) {
|
||||
case SNDRV_PCM_FORMAT_S16_LE:
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S20_3LE:
|
||||
iface |= 0x10;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S24_LE:
|
||||
iface |= 0x20;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S32_LE:
|
||||
iface |= 0x30;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Only need to set MCLK/LRCLK ratio if we're master */
|
||||
if (snd_soc_read(codec, WM8776_MSTRCTRL) & master) {
|
||||
for (i = 0; i < ARRAY_SIZE(mclk_ratios); i++) {
|
||||
if (wm8776->sysclk[dai->id] / params_rate(params)
|
||||
== mclk_ratios[i])
|
||||
break;
|
||||
}
|
||||
|
||||
if (i == ARRAY_SIZE(mclk_ratios)) {
|
||||
dev_err(codec->dev,
|
||||
"Unable to configure MCLK ratio %d/%d\n",
|
||||
wm8776->sysclk[dai->id], params_rate(params));
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
dev_dbg(codec->dev, "MCLK is %dfs\n", mclk_ratios[i]);
|
||||
|
||||
snd_soc_update_bits(codec, WM8776_MSTRCTRL,
|
||||
0x7 << ratio_shift, i << ratio_shift);
|
||||
} else {
|
||||
dev_dbg(codec->dev, "DAI in slave mode\n");
|
||||
}
|
||||
|
||||
snd_soc_update_bits(codec, iface_reg, 0x30, iface);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wm8776_mute(struct snd_soc_dai *dai, int mute)
|
||||
{
|
||||
struct snd_soc_codec *codec = dai->codec;
|
||||
|
||||
return snd_soc_write(codec, WM8776_DACMUTE, !!mute);
|
||||
}
|
||||
|
||||
static int wm8776_set_sysclk(struct snd_soc_dai *dai,
|
||||
int clk_id, unsigned int freq, int dir)
|
||||
{
|
||||
struct snd_soc_codec *codec = dai->codec;
|
||||
struct wm8776_priv *wm8776 = codec->private_data;
|
||||
|
||||
BUG_ON(dai->id >= ARRAY_SIZE(wm8776->sysclk));
|
||||
|
||||
wm8776->sysclk[dai->id] = freq;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wm8776_set_bias_level(struct snd_soc_codec *codec,
|
||||
enum snd_soc_bias_level level)
|
||||
{
|
||||
switch (level) {
|
||||
case SND_SOC_BIAS_ON:
|
||||
break;
|
||||
case SND_SOC_BIAS_PREPARE:
|
||||
break;
|
||||
case SND_SOC_BIAS_STANDBY:
|
||||
if (codec->bias_level == SND_SOC_BIAS_OFF) {
|
||||
/* Disable the global powerdown; DAPM does the rest */
|
||||
snd_soc_update_bits(codec, WM8776_PWRDOWN, 1, 0);
|
||||
}
|
||||
|
||||
break;
|
||||
case SND_SOC_BIAS_OFF:
|
||||
snd_soc_update_bits(codec, WM8776_PWRDOWN, 1, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
codec->bias_level = level;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define WM8776_RATES (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\
|
||||
SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 |\
|
||||
SNDRV_PCM_RATE_96000)
|
||||
|
||||
|
||||
#define WM8776_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
|
||||
SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
|
||||
|
||||
static struct snd_soc_dai_ops wm8776_dac_ops = {
|
||||
.digital_mute = wm8776_mute,
|
||||
.hw_params = wm8776_hw_params,
|
||||
.set_fmt = wm8776_set_fmt,
|
||||
.set_sysclk = wm8776_set_sysclk,
|
||||
};
|
||||
|
||||
static struct snd_soc_dai_ops wm8776_adc_ops = {
|
||||
.hw_params = wm8776_hw_params,
|
||||
.set_fmt = wm8776_set_fmt,
|
||||
.set_sysclk = wm8776_set_sysclk,
|
||||
};
|
||||
|
||||
struct snd_soc_dai wm8776_dai[] = {
|
||||
{
|
||||
.name = "WM8776 Playback",
|
||||
.id = WM8776_DAI_DAC,
|
||||
.playback = {
|
||||
.stream_name = "Playback",
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.rates = WM8776_RATES,
|
||||
.formats = WM8776_FORMATS,
|
||||
},
|
||||
.ops = &wm8776_dac_ops,
|
||||
},
|
||||
{
|
||||
.name = "WM8776 Capture",
|
||||
.id = WM8776_DAI_ADC,
|
||||
.capture = {
|
||||
.stream_name = "Capture",
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.rates = WM8776_RATES,
|
||||
.formats = WM8776_FORMATS,
|
||||
},
|
||||
.ops = &wm8776_adc_ops,
|
||||
},
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(wm8776_dai);
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int wm8776_suspend(struct platform_device *pdev, pm_message_t state)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
|
||||
wm8776_set_bias_level(codec, SND_SOC_BIAS_OFF);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wm8776_resume(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
int i;
|
||||
u8 data[2];
|
||||
u16 *cache = codec->reg_cache;
|
||||
|
||||
/* Sync reg_cache with the hardware */
|
||||
for (i = 0; i < ARRAY_SIZE(wm8776_reg); i++) {
|
||||
data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001);
|
||||
data[1] = cache[i] & 0x00ff;
|
||||
codec->hw_write(codec->control_data, data, 2);
|
||||
}
|
||||
|
||||
wm8776_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
#define wm8776_suspend NULL
|
||||
#define wm8776_resume NULL
|
||||
#endif
|
||||
|
||||
static int wm8776_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
struct snd_soc_codec *codec;
|
||||
int ret = 0;
|
||||
|
||||
if (wm8776_codec == NULL) {
|
||||
dev_err(&pdev->dev, "Codec device not registered\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
socdev->card->codec = wm8776_codec;
|
||||
codec = wm8776_codec;
|
||||
|
||||
/* register pcms */
|
||||
ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
|
||||
if (ret < 0) {
|
||||
dev_err(codec->dev, "failed to create pcms: %d\n", ret);
|
||||
goto pcm_err;
|
||||
}
|
||||
|
||||
snd_soc_add_controls(codec, wm8776_snd_controls,
|
||||
ARRAY_SIZE(wm8776_snd_controls));
|
||||
snd_soc_dapm_new_controls(codec, wm8776_dapm_widgets,
|
||||
ARRAY_SIZE(wm8776_dapm_widgets));
|
||||
snd_soc_dapm_add_routes(codec, routes, ARRAY_SIZE(routes));
|
||||
|
||||
ret = snd_soc_init_card(socdev);
|
||||
if (ret < 0) {
|
||||
dev_err(codec->dev, "failed to register card: %d\n", ret);
|
||||
goto card_err;
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
card_err:
|
||||
snd_soc_free_pcms(socdev);
|
||||
snd_soc_dapm_free(socdev);
|
||||
pcm_err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* power down chip */
|
||||
static int wm8776_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
|
||||
snd_soc_free_pcms(socdev);
|
||||
snd_soc_dapm_free(socdev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct snd_soc_codec_device soc_codec_dev_wm8776 = {
|
||||
.probe = wm8776_probe,
|
||||
.remove = wm8776_remove,
|
||||
.suspend = wm8776_suspend,
|
||||
.resume = wm8776_resume,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(soc_codec_dev_wm8776);
|
||||
|
||||
static int wm8776_register(struct wm8776_priv *wm8776,
|
||||
enum snd_soc_control_type control)
|
||||
{
|
||||
int ret, i;
|
||||
struct snd_soc_codec *codec = &wm8776->codec;
|
||||
|
||||
if (wm8776_codec) {
|
||||
dev_err(codec->dev, "Another WM8776 is registered\n");
|
||||
ret = -EINVAL;
|
||||
goto err;
|
||||
}
|
||||
|
||||
mutex_init(&codec->mutex);
|
||||
INIT_LIST_HEAD(&codec->dapm_widgets);
|
||||
INIT_LIST_HEAD(&codec->dapm_paths);
|
||||
|
||||
codec->private_data = wm8776;
|
||||
codec->name = "WM8776";
|
||||
codec->owner = THIS_MODULE;
|
||||
codec->bias_level = SND_SOC_BIAS_OFF;
|
||||
codec->set_bias_level = wm8776_set_bias_level;
|
||||
codec->dai = wm8776_dai;
|
||||
codec->num_dai = ARRAY_SIZE(wm8776_dai);
|
||||
codec->reg_cache_size = WM8776_CACHEREGNUM;
|
||||
codec->reg_cache = &wm8776->reg_cache;
|
||||
|
||||
memcpy(codec->reg_cache, wm8776_reg, sizeof(wm8776_reg));
|
||||
|
||||
ret = snd_soc_codec_set_cache_io(codec, 7, 9, control);
|
||||
if (ret < 0) {
|
||||
dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
|
||||
goto err;
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(wm8776_dai); i++)
|
||||
wm8776_dai[i].dev = codec->dev;
|
||||
|
||||
ret = wm8776_reset(codec);
|
||||
if (ret < 0) {
|
||||
dev_err(codec->dev, "Failed to issue reset: %d\n", ret);
|
||||
goto err;
|
||||
}
|
||||
|
||||
wm8776_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
|
||||
|
||||
/* Latch the update bits; right channel only since we always
|
||||
* update both. */
|
||||
snd_soc_update_bits(codec, WM8776_HPRVOL, 0x100, 0x100);
|
||||
snd_soc_update_bits(codec, WM8776_DACRVOL, 0x100, 0x100);
|
||||
|
||||
wm8776_codec = codec;
|
||||
|
||||
ret = snd_soc_register_codec(codec);
|
||||
if (ret != 0) {
|
||||
dev_err(codec->dev, "Failed to register codec: %d\n", ret);
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = snd_soc_register_dais(wm8776_dai, ARRAY_SIZE(wm8776_dai));
|
||||
if (ret != 0) {
|
||||
dev_err(codec->dev, "Failed to register DAIs: %d\n", ret);
|
||||
goto err_codec;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_codec:
|
||||
snd_soc_unregister_codec(codec);
|
||||
err:
|
||||
kfree(wm8776);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void wm8776_unregister(struct wm8776_priv *wm8776)
|
||||
{
|
||||
wm8776_set_bias_level(&wm8776->codec, SND_SOC_BIAS_OFF);
|
||||
snd_soc_unregister_dais(wm8776_dai, ARRAY_SIZE(wm8776_dai));
|
||||
snd_soc_unregister_codec(&wm8776->codec);
|
||||
kfree(wm8776);
|
||||
wm8776_codec = NULL;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_SPI_MASTER)
|
||||
static int wm8776_spi_write(struct spi_device *spi, const char *data, int len)
|
||||
{
|
||||
struct spi_transfer t;
|
||||
struct spi_message m;
|
||||
u8 msg[2];
|
||||
|
||||
if (len <= 0)
|
||||
return 0;
|
||||
|
||||
msg[0] = data[0];
|
||||
msg[1] = data[1];
|
||||
|
||||
spi_message_init(&m);
|
||||
memset(&t, 0, (sizeof t));
|
||||
|
||||
t.tx_buf = &msg[0];
|
||||
t.len = len;
|
||||
|
||||
spi_message_add_tail(&t, &m);
|
||||
spi_sync(spi, &m);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static int __devinit wm8776_spi_probe(struct spi_device *spi)
|
||||
{
|
||||
struct snd_soc_codec *codec;
|
||||
struct wm8776_priv *wm8776;
|
||||
|
||||
wm8776 = kzalloc(sizeof(struct wm8776_priv), GFP_KERNEL);
|
||||
if (wm8776 == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
codec = &wm8776->codec;
|
||||
codec->control_data = spi;
|
||||
codec->hw_write = (hw_write_t)wm8776_spi_write;
|
||||
codec->dev = &spi->dev;
|
||||
|
||||
dev_set_drvdata(&spi->dev, wm8776);
|
||||
|
||||
return wm8776_register(wm8776, SND_SOC_SPI);
|
||||
}
|
||||
|
||||
static int __devexit wm8776_spi_remove(struct spi_device *spi)
|
||||
{
|
||||
struct wm8776_priv *wm8776 = dev_get_drvdata(&spi->dev);
|
||||
|
||||
wm8776_unregister(wm8776);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int wm8776_spi_suspend(struct spi_device *spi, pm_message_t msg)
|
||||
{
|
||||
return snd_soc_suspend_device(&spi->dev);
|
||||
}
|
||||
|
||||
static int wm8776_spi_resume(struct spi_device *spi)
|
||||
{
|
||||
return snd_soc_resume_device(&spi->dev);
|
||||
}
|
||||
#else
|
||||
#define wm8776_spi_suspend NULL
|
||||
#define wm8776_spi_resume NULL
|
||||
#endif
|
||||
|
||||
static struct spi_driver wm8776_spi_driver = {
|
||||
.driver = {
|
||||
.name = "wm8776",
|
||||
.bus = &spi_bus_type,
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = wm8776_spi_probe,
|
||||
.suspend = wm8776_spi_suspend,
|
||||
.resume = wm8776_spi_resume,
|
||||
.remove = __devexit_p(wm8776_spi_remove),
|
||||
};
|
||||
#endif /* CONFIG_SPI_MASTER */
|
||||
|
||||
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
|
||||
static __devinit int wm8776_i2c_probe(struct i2c_client *i2c,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct wm8776_priv *wm8776;
|
||||
struct snd_soc_codec *codec;
|
||||
|
||||
wm8776 = kzalloc(sizeof(struct wm8776_priv), GFP_KERNEL);
|
||||
if (wm8776 == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
codec = &wm8776->codec;
|
||||
codec->hw_write = (hw_write_t)i2c_master_send;
|
||||
|
||||
i2c_set_clientdata(i2c, wm8776);
|
||||
codec->control_data = i2c;
|
||||
|
||||
codec->dev = &i2c->dev;
|
||||
|
||||
return wm8776_register(wm8776, SND_SOC_I2C);
|
||||
}
|
||||
|
||||
static __devexit int wm8776_i2c_remove(struct i2c_client *client)
|
||||
{
|
||||
struct wm8776_priv *wm8776 = i2c_get_clientdata(client);
|
||||
wm8776_unregister(wm8776);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int wm8776_i2c_suspend(struct i2c_client *i2c, pm_message_t msg)
|
||||
{
|
||||
return snd_soc_suspend_device(&i2c->dev);
|
||||
}
|
||||
|
||||
static int wm8776_i2c_resume(struct i2c_client *i2c)
|
||||
{
|
||||
return snd_soc_resume_device(&i2c->dev);
|
||||
}
|
||||
#else
|
||||
#define wm8776_i2c_suspend NULL
|
||||
#define wm8776_i2c_resume NULL
|
||||
#endif
|
||||
|
||||
static const struct i2c_device_id wm8776_i2c_id[] = {
|
||||
{ "wm8776", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, wm8776_i2c_id);
|
||||
|
||||
static struct i2c_driver wm8776_i2c_driver = {
|
||||
.driver = {
|
||||
.name = "wm8776",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = wm8776_i2c_probe,
|
||||
.remove = __devexit_p(wm8776_i2c_remove),
|
||||
.suspend = wm8776_i2c_suspend,
|
||||
.resume = wm8776_i2c_resume,
|
||||
.id_table = wm8776_i2c_id,
|
||||
};
|
||||
#endif
|
||||
|
||||
static int __init wm8776_modinit(void)
|
||||
{
|
||||
int ret;
|
||||
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
|
||||
ret = i2c_add_driver(&wm8776_i2c_driver);
|
||||
if (ret != 0) {
|
||||
printk(KERN_ERR "Failed to register WM8776 I2C driver: %d\n",
|
||||
ret);
|
||||
}
|
||||
#endif
|
||||
#if defined(CONFIG_SPI_MASTER)
|
||||
ret = spi_register_driver(&wm8776_spi_driver);
|
||||
if (ret != 0) {
|
||||
printk(KERN_ERR "Failed to register WM8776 SPI driver: %d\n",
|
||||
ret);
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
module_init(wm8776_modinit);
|
||||
|
||||
static void __exit wm8776_exit(void)
|
||||
{
|
||||
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
|
||||
i2c_del_driver(&wm8776_i2c_driver);
|
||||
#endif
|
||||
#if defined(CONFIG_SPI_MASTER)
|
||||
spi_unregister_driver(&wm8776_spi_driver);
|
||||
#endif
|
||||
}
|
||||
module_exit(wm8776_exit);
|
||||
|
||||
MODULE_DESCRIPTION("ASoC WM8776 driver");
|
||||
MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* wm8776.h -- WM8776 ASoC driver
|
||||
*
|
||||
* Copyright 2009 Wolfson Microelectronics plc
|
||||
*
|
||||
* Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef _WM8776_H
|
||||
#define _WM8776_H
|
||||
|
||||
/* Registers */
|
||||
|
||||
#define WM8776_HPLVOL 0x00
|
||||
#define WM8776_HPRVOL 0x01
|
||||
#define WM8776_HPMASTER 0x02
|
||||
#define WM8776_DACLVOL 0x03
|
||||
#define WM8776_DACRVOL 0x04
|
||||
#define WM8776_DACMASTER 0x05
|
||||
#define WM8776_PHASESWAP 0x06
|
||||
#define WM8776_DACCTRL1 0x07
|
||||
#define WM8776_DACMUTE 0x08
|
||||
#define WM8776_DACCTRL2 0x09
|
||||
#define WM8776_DACIFCTRL 0x0a
|
||||
#define WM8776_ADCIFCTRL 0x0b
|
||||
#define WM8776_MSTRCTRL 0x0c
|
||||
#define WM8776_PWRDOWN 0x0d
|
||||
#define WM8776_ADCLVOL 0x0e
|
||||
#define WM8776_ADCRVOL 0x0f
|
||||
#define WM8776_ALCCTRL1 0x10
|
||||
#define WM8776_ALCCTRL2 0x11
|
||||
#define WM8776_ALCCTRL3 0x12
|
||||
#define WM8776_NOISEGATE 0x13
|
||||
#define WM8776_LIMITER 0x14
|
||||
#define WM8776_ADCMUX 0x15
|
||||
#define WM8776_OUTMUX 0x16
|
||||
#define WM8776_RESET 0x17
|
||||
|
||||
#define WM8776_CACHEREGNUM 0x17
|
||||
|
||||
#define WM8776_DAI_DAC 0
|
||||
#define WM8776_DAI_ADC 1
|
||||
|
||||
extern struct snd_soc_dai wm8776_dai[];
|
||||
extern struct snd_soc_codec_device soc_codec_dev_wm8776;
|
||||
|
||||
#endif
|
|
@ -116,6 +116,7 @@
|
|||
#define WM8900_REG_CLOCKING2_DAC_CLKDIV 0x1c
|
||||
|
||||
#define WM8900_REG_DACCTRL_MUTE 0x004
|
||||
#define WM8900_REG_DACCTRL_DAC_SB_FILT 0x100
|
||||
#define WM8900_REG_DACCTRL_AIF_LRCLKRATE 0x400
|
||||
|
||||
#define WM8900_REG_AUDIO3_ADCLRC_DIR 0x0800
|
||||
|
@ -182,111 +183,20 @@ static const u16 wm8900_reg_defaults[WM8900_MAXREG] = {
|
|||
/* Remaining registers all zero */
|
||||
};
|
||||
|
||||
/*
|
||||
* read wm8900 register cache
|
||||
*/
|
||||
static inline unsigned int wm8900_read_reg_cache(struct snd_soc_codec *codec,
|
||||
unsigned int reg)
|
||||
{
|
||||
u16 *cache = codec->reg_cache;
|
||||
|
||||
BUG_ON(reg >= WM8900_MAXREG);
|
||||
|
||||
if (reg == WM8900_REG_ID)
|
||||
return 0;
|
||||
|
||||
return cache[reg];
|
||||
}
|
||||
|
||||
/*
|
||||
* write wm8900 register cache
|
||||
*/
|
||||
static inline void wm8900_write_reg_cache(struct snd_soc_codec *codec,
|
||||
u16 reg, unsigned int value)
|
||||
{
|
||||
u16 *cache = codec->reg_cache;
|
||||
|
||||
BUG_ON(reg >= WM8900_MAXREG);
|
||||
|
||||
cache[reg] = value;
|
||||
}
|
||||
|
||||
/*
|
||||
* write to the WM8900 register space
|
||||
*/
|
||||
static int wm8900_write(struct snd_soc_codec *codec, unsigned int reg,
|
||||
unsigned int value)
|
||||
{
|
||||
u8 data[3];
|
||||
|
||||
if (value == wm8900_read_reg_cache(codec, reg))
|
||||
return 0;
|
||||
|
||||
/* data is
|
||||
* D15..D9 WM8900 register offset
|
||||
* D8...D0 register data
|
||||
*/
|
||||
data[0] = reg;
|
||||
data[1] = value >> 8;
|
||||
data[2] = value & 0x00ff;
|
||||
|
||||
wm8900_write_reg_cache(codec, reg, value);
|
||||
if (codec->hw_write(codec->control_data, data, 3) == 3)
|
||||
return 0;
|
||||
else
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read from the wm8900.
|
||||
*/
|
||||
static unsigned int wm8900_chip_read(struct snd_soc_codec *codec, u8 reg)
|
||||
{
|
||||
struct i2c_msg xfer[2];
|
||||
u16 data;
|
||||
int ret;
|
||||
struct i2c_client *client = codec->control_data;
|
||||
|
||||
BUG_ON(reg != WM8900_REG_ID && reg != WM8900_REG_POWER1);
|
||||
|
||||
/* Write register */
|
||||
xfer[0].addr = client->addr;
|
||||
xfer[0].flags = 0;
|
||||
xfer[0].len = 1;
|
||||
xfer[0].buf = ®
|
||||
|
||||
/* Read data */
|
||||
xfer[1].addr = client->addr;
|
||||
xfer[1].flags = I2C_M_RD;
|
||||
xfer[1].len = 2;
|
||||
xfer[1].buf = (u8 *)&data;
|
||||
|
||||
ret = i2c_transfer(client->adapter, xfer, 2);
|
||||
if (ret != 2) {
|
||||
printk(KERN_CRIT "i2c_transfer returned %d\n", ret);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (data >> 8) | ((data & 0xff) << 8);
|
||||
}
|
||||
|
||||
/*
|
||||
* Read from the WM8900 register space. Most registers can't be read
|
||||
* and are therefore supplied from cache.
|
||||
*/
|
||||
static unsigned int wm8900_read(struct snd_soc_codec *codec, unsigned int reg)
|
||||
static int wm8900_volatile_register(unsigned int reg)
|
||||
{
|
||||
switch (reg) {
|
||||
case WM8900_REG_ID:
|
||||
return wm8900_chip_read(codec, reg);
|
||||
case WM8900_REG_POWER1:
|
||||
return 1;
|
||||
default:
|
||||
return wm8900_read_reg_cache(codec, reg);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void wm8900_reset(struct snd_soc_codec *codec)
|
||||
{
|
||||
wm8900_write(codec, WM8900_REG_RESET, 0);
|
||||
snd_soc_write(codec, WM8900_REG_RESET, 0);
|
||||
|
||||
memcpy(codec->reg_cache, wm8900_reg_defaults,
|
||||
sizeof(codec->reg_cache));
|
||||
|
@ -296,14 +206,14 @@ static int wm8900_hp_event(struct snd_soc_dapm_widget *w,
|
|||
struct snd_kcontrol *kcontrol, int event)
|
||||
{
|
||||
struct snd_soc_codec *codec = w->codec;
|
||||
u16 hpctl1 = wm8900_read(codec, WM8900_REG_HPCTL1);
|
||||
u16 hpctl1 = snd_soc_read(codec, WM8900_REG_HPCTL1);
|
||||
|
||||
switch (event) {
|
||||
case SND_SOC_DAPM_PRE_PMU:
|
||||
/* Clamp headphone outputs */
|
||||
hpctl1 = WM8900_REG_HPCTL1_HP_CLAMP_IP |
|
||||
WM8900_REG_HPCTL1_HP_CLAMP_OP;
|
||||
wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1);
|
||||
snd_soc_write(codec, WM8900_REG_HPCTL1, hpctl1);
|
||||
break;
|
||||
|
||||
case SND_SOC_DAPM_POST_PMU:
|
||||
|
@ -312,41 +222,41 @@ static int wm8900_hp_event(struct snd_soc_dapm_widget *w,
|
|||
hpctl1 |= WM8900_REG_HPCTL1_HP_SHORT |
|
||||
WM8900_REG_HPCTL1_HP_SHORT2 |
|
||||
WM8900_REG_HPCTL1_HP_IPSTAGE_ENA;
|
||||
wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1);
|
||||
snd_soc_write(codec, WM8900_REG_HPCTL1, hpctl1);
|
||||
|
||||
msleep(400);
|
||||
|
||||
/* Enable the output stage */
|
||||
hpctl1 &= ~WM8900_REG_HPCTL1_HP_CLAMP_OP;
|
||||
hpctl1 |= WM8900_REG_HPCTL1_HP_OPSTAGE_ENA;
|
||||
wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1);
|
||||
snd_soc_write(codec, WM8900_REG_HPCTL1, hpctl1);
|
||||
|
||||
/* Remove the shorts */
|
||||
hpctl1 &= ~WM8900_REG_HPCTL1_HP_SHORT2;
|
||||
wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1);
|
||||
snd_soc_write(codec, WM8900_REG_HPCTL1, hpctl1);
|
||||
hpctl1 &= ~WM8900_REG_HPCTL1_HP_SHORT;
|
||||
wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1);
|
||||
snd_soc_write(codec, WM8900_REG_HPCTL1, hpctl1);
|
||||
break;
|
||||
|
||||
case SND_SOC_DAPM_PRE_PMD:
|
||||
/* Short the output */
|
||||
hpctl1 |= WM8900_REG_HPCTL1_HP_SHORT;
|
||||
wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1);
|
||||
snd_soc_write(codec, WM8900_REG_HPCTL1, hpctl1);
|
||||
|
||||
/* Disable the output stage */
|
||||
hpctl1 &= ~WM8900_REG_HPCTL1_HP_OPSTAGE_ENA;
|
||||
wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1);
|
||||
snd_soc_write(codec, WM8900_REG_HPCTL1, hpctl1);
|
||||
|
||||
/* Clamp the outputs and power down input */
|
||||
hpctl1 |= WM8900_REG_HPCTL1_HP_CLAMP_IP |
|
||||
WM8900_REG_HPCTL1_HP_CLAMP_OP;
|
||||
hpctl1 &= ~WM8900_REG_HPCTL1_HP_IPSTAGE_ENA;
|
||||
wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1);
|
||||
snd_soc_write(codec, WM8900_REG_HPCTL1, hpctl1);
|
||||
break;
|
||||
|
||||
case SND_SOC_DAPM_POST_PMD:
|
||||
/* Disable everything */
|
||||
wm8900_write(codec, WM8900_REG_HPCTL1, 0);
|
||||
snd_soc_write(codec, WM8900_REG_HPCTL1, 0);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -439,7 +349,6 @@ SOC_SINGLE("DAC Soft Mute Switch", WM8900_REG_DACCTRL, 6, 1, 1),
|
|||
SOC_ENUM("DAC Mute Rate", dac_mute_rate),
|
||||
SOC_SINGLE("DAC Mono Switch", WM8900_REG_DACCTRL, 9, 1, 0),
|
||||
SOC_ENUM("DAC Deemphasis", dac_deemphasis),
|
||||
SOC_SINGLE("DAC Sloping Stopband Filter Switch", WM8900_REG_DACCTRL, 8, 1, 0),
|
||||
SOC_SINGLE("DAC Sigma-Delta Modulator Clock Switch", WM8900_REG_DACCTRL,
|
||||
12, 1, 0),
|
||||
|
||||
|
@ -723,7 +632,7 @@ static int wm8900_hw_params(struct snd_pcm_substream *substream,
|
|||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
u16 reg;
|
||||
|
||||
reg = wm8900_read(codec, WM8900_REG_AUDIO1) & ~0x60;
|
||||
reg = snd_soc_read(codec, WM8900_REG_AUDIO1) & ~0x60;
|
||||
|
||||
switch (params_format(params)) {
|
||||
case SNDRV_PCM_FORMAT_S16_LE:
|
||||
|
@ -741,7 +650,18 @@ static int wm8900_hw_params(struct snd_pcm_substream *substream,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
wm8900_write(codec, WM8900_REG_AUDIO1, reg);
|
||||
snd_soc_write(codec, WM8900_REG_AUDIO1, reg);
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
reg = snd_soc_read(codec, WM8900_REG_DACCTRL);
|
||||
|
||||
if (params_rate(params) <= 24000)
|
||||
reg |= WM8900_REG_DACCTRL_DAC_SB_FILT;
|
||||
else
|
||||
reg &= ~WM8900_REG_DACCTRL_DAC_SB_FILT;
|
||||
|
||||
snd_soc_write(codec, WM8900_REG_DACCTRL, reg);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -834,18 +754,18 @@ static int wm8900_set_fll(struct snd_soc_codec *codec,
|
|||
return 0;
|
||||
|
||||
/* The digital side should be disabled during any change. */
|
||||
reg = wm8900_read(codec, WM8900_REG_POWER1);
|
||||
wm8900_write(codec, WM8900_REG_POWER1,
|
||||
reg = snd_soc_read(codec, WM8900_REG_POWER1);
|
||||
snd_soc_write(codec, WM8900_REG_POWER1,
|
||||
reg & (~WM8900_REG_POWER1_FLL_ENA));
|
||||
|
||||
/* Disable the FLL? */
|
||||
if (!freq_in || !freq_out) {
|
||||
reg = wm8900_read(codec, WM8900_REG_CLOCKING1);
|
||||
wm8900_write(codec, WM8900_REG_CLOCKING1,
|
||||
reg = snd_soc_read(codec, WM8900_REG_CLOCKING1);
|
||||
snd_soc_write(codec, WM8900_REG_CLOCKING1,
|
||||
reg & (~WM8900_REG_CLOCKING1_MCLK_SRC));
|
||||
|
||||
reg = wm8900_read(codec, WM8900_REG_FLLCTL1);
|
||||
wm8900_write(codec, WM8900_REG_FLLCTL1,
|
||||
reg = snd_soc_read(codec, WM8900_REG_FLLCTL1);
|
||||
snd_soc_write(codec, WM8900_REG_FLLCTL1,
|
||||
reg & (~WM8900_REG_FLLCTL1_OSC_ENA));
|
||||
|
||||
wm8900->fll_in = freq_in;
|
||||
|
@ -862,33 +782,33 @@ static int wm8900_set_fll(struct snd_soc_codec *codec,
|
|||
|
||||
/* The osclilator *MUST* be enabled before we enable the
|
||||
* digital circuit. */
|
||||
wm8900_write(codec, WM8900_REG_FLLCTL1,
|
||||
snd_soc_write(codec, WM8900_REG_FLLCTL1,
|
||||
fll_div.fll_ratio | WM8900_REG_FLLCTL1_OSC_ENA);
|
||||
|
||||
wm8900_write(codec, WM8900_REG_FLLCTL4, fll_div.n >> 5);
|
||||
wm8900_write(codec, WM8900_REG_FLLCTL5,
|
||||
snd_soc_write(codec, WM8900_REG_FLLCTL4, fll_div.n >> 5);
|
||||
snd_soc_write(codec, WM8900_REG_FLLCTL5,
|
||||
(fll_div.fllclk_div << 6) | (fll_div.n & 0x1f));
|
||||
|
||||
if (fll_div.k) {
|
||||
wm8900_write(codec, WM8900_REG_FLLCTL2,
|
||||
snd_soc_write(codec, WM8900_REG_FLLCTL2,
|
||||
(fll_div.k >> 8) | 0x100);
|
||||
wm8900_write(codec, WM8900_REG_FLLCTL3, fll_div.k & 0xff);
|
||||
snd_soc_write(codec, WM8900_REG_FLLCTL3, fll_div.k & 0xff);
|
||||
} else
|
||||
wm8900_write(codec, WM8900_REG_FLLCTL2, 0);
|
||||
snd_soc_write(codec, WM8900_REG_FLLCTL2, 0);
|
||||
|
||||
if (fll_div.fll_slow_lock_ref)
|
||||
wm8900_write(codec, WM8900_REG_FLLCTL6,
|
||||
snd_soc_write(codec, WM8900_REG_FLLCTL6,
|
||||
WM8900_REG_FLLCTL6_FLL_SLOW_LOCK_REF);
|
||||
else
|
||||
wm8900_write(codec, WM8900_REG_FLLCTL6, 0);
|
||||
snd_soc_write(codec, WM8900_REG_FLLCTL6, 0);
|
||||
|
||||
reg = wm8900_read(codec, WM8900_REG_POWER1);
|
||||
wm8900_write(codec, WM8900_REG_POWER1,
|
||||
reg = snd_soc_read(codec, WM8900_REG_POWER1);
|
||||
snd_soc_write(codec, WM8900_REG_POWER1,
|
||||
reg | WM8900_REG_POWER1_FLL_ENA);
|
||||
|
||||
reenable:
|
||||
reg = wm8900_read(codec, WM8900_REG_CLOCKING1);
|
||||
wm8900_write(codec, WM8900_REG_CLOCKING1,
|
||||
reg = snd_soc_read(codec, WM8900_REG_CLOCKING1);
|
||||
snd_soc_write(codec, WM8900_REG_CLOCKING1,
|
||||
reg | WM8900_REG_CLOCKING1_MCLK_SRC);
|
||||
|
||||
return 0;
|
||||
|
@ -908,38 +828,38 @@ static int wm8900_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
|
|||
|
||||
switch (div_id) {
|
||||
case WM8900_BCLK_DIV:
|
||||
reg = wm8900_read(codec, WM8900_REG_CLOCKING1);
|
||||
wm8900_write(codec, WM8900_REG_CLOCKING1,
|
||||
reg = snd_soc_read(codec, WM8900_REG_CLOCKING1);
|
||||
snd_soc_write(codec, WM8900_REG_CLOCKING1,
|
||||
div | (reg & WM8900_REG_CLOCKING1_BCLK_MASK));
|
||||
break;
|
||||
case WM8900_OPCLK_DIV:
|
||||
reg = wm8900_read(codec, WM8900_REG_CLOCKING1);
|
||||
wm8900_write(codec, WM8900_REG_CLOCKING1,
|
||||
reg = snd_soc_read(codec, WM8900_REG_CLOCKING1);
|
||||
snd_soc_write(codec, WM8900_REG_CLOCKING1,
|
||||
div | (reg & WM8900_REG_CLOCKING1_OPCLK_MASK));
|
||||
break;
|
||||
case WM8900_DAC_LRCLK:
|
||||
reg = wm8900_read(codec, WM8900_REG_AUDIO4);
|
||||
wm8900_write(codec, WM8900_REG_AUDIO4,
|
||||
reg = snd_soc_read(codec, WM8900_REG_AUDIO4);
|
||||
snd_soc_write(codec, WM8900_REG_AUDIO4,
|
||||
div | (reg & WM8900_LRC_MASK));
|
||||
break;
|
||||
case WM8900_ADC_LRCLK:
|
||||
reg = wm8900_read(codec, WM8900_REG_AUDIO3);
|
||||
wm8900_write(codec, WM8900_REG_AUDIO3,
|
||||
reg = snd_soc_read(codec, WM8900_REG_AUDIO3);
|
||||
snd_soc_write(codec, WM8900_REG_AUDIO3,
|
||||
div | (reg & WM8900_LRC_MASK));
|
||||
break;
|
||||
case WM8900_DAC_CLKDIV:
|
||||
reg = wm8900_read(codec, WM8900_REG_CLOCKING2);
|
||||
wm8900_write(codec, WM8900_REG_CLOCKING2,
|
||||
reg = snd_soc_read(codec, WM8900_REG_CLOCKING2);
|
||||
snd_soc_write(codec, WM8900_REG_CLOCKING2,
|
||||
div | (reg & WM8900_REG_CLOCKING2_DAC_CLKDIV));
|
||||
break;
|
||||
case WM8900_ADC_CLKDIV:
|
||||
reg = wm8900_read(codec, WM8900_REG_CLOCKING2);
|
||||
wm8900_write(codec, WM8900_REG_CLOCKING2,
|
||||
reg = snd_soc_read(codec, WM8900_REG_CLOCKING2);
|
||||
snd_soc_write(codec, WM8900_REG_CLOCKING2,
|
||||
div | (reg & WM8900_REG_CLOCKING2_ADC_CLKDIV));
|
||||
break;
|
||||
case WM8900_LRCLK_MODE:
|
||||
reg = wm8900_read(codec, WM8900_REG_DACCTRL);
|
||||
wm8900_write(codec, WM8900_REG_DACCTRL,
|
||||
reg = snd_soc_read(codec, WM8900_REG_DACCTRL);
|
||||
snd_soc_write(codec, WM8900_REG_DACCTRL,
|
||||
div | (reg & WM8900_REG_DACCTRL_AIF_LRCLKRATE));
|
||||
break;
|
||||
default:
|
||||
|
@ -956,10 +876,10 @@ static int wm8900_set_dai_fmt(struct snd_soc_dai *codec_dai,
|
|||
struct snd_soc_codec *codec = codec_dai->codec;
|
||||
unsigned int clocking1, aif1, aif3, aif4;
|
||||
|
||||
clocking1 = wm8900_read(codec, WM8900_REG_CLOCKING1);
|
||||
aif1 = wm8900_read(codec, WM8900_REG_AUDIO1);
|
||||
aif3 = wm8900_read(codec, WM8900_REG_AUDIO3);
|
||||
aif4 = wm8900_read(codec, WM8900_REG_AUDIO4);
|
||||
clocking1 = snd_soc_read(codec, WM8900_REG_CLOCKING1);
|
||||
aif1 = snd_soc_read(codec, WM8900_REG_AUDIO1);
|
||||
aif3 = snd_soc_read(codec, WM8900_REG_AUDIO3);
|
||||
aif4 = snd_soc_read(codec, WM8900_REG_AUDIO4);
|
||||
|
||||
/* set master/slave audio interface */
|
||||
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
|
||||
|
@ -1055,10 +975,10 @@ static int wm8900_set_dai_fmt(struct snd_soc_dai *codec_dai,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
wm8900_write(codec, WM8900_REG_CLOCKING1, clocking1);
|
||||
wm8900_write(codec, WM8900_REG_AUDIO1, aif1);
|
||||
wm8900_write(codec, WM8900_REG_AUDIO3, aif3);
|
||||
wm8900_write(codec, WM8900_REG_AUDIO4, aif4);
|
||||
snd_soc_write(codec, WM8900_REG_CLOCKING1, clocking1);
|
||||
snd_soc_write(codec, WM8900_REG_AUDIO1, aif1);
|
||||
snd_soc_write(codec, WM8900_REG_AUDIO3, aif3);
|
||||
snd_soc_write(codec, WM8900_REG_AUDIO4, aif4);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -1068,14 +988,14 @@ static int wm8900_digital_mute(struct snd_soc_dai *codec_dai, int mute)
|
|||
struct snd_soc_codec *codec = codec_dai->codec;
|
||||
u16 reg;
|
||||
|
||||
reg = wm8900_read(codec, WM8900_REG_DACCTRL);
|
||||
reg = snd_soc_read(codec, WM8900_REG_DACCTRL);
|
||||
|
||||
if (mute)
|
||||
reg |= WM8900_REG_DACCTRL_MUTE;
|
||||
else
|
||||
reg &= ~WM8900_REG_DACCTRL_MUTE;
|
||||
|
||||
wm8900_write(codec, WM8900_REG_DACCTRL, reg);
|
||||
snd_soc_write(codec, WM8900_REG_DACCTRL, reg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -1124,11 +1044,11 @@ static int wm8900_set_bias_level(struct snd_soc_codec *codec,
|
|||
switch (level) {
|
||||
case SND_SOC_BIAS_ON:
|
||||
/* Enable thermal shutdown */
|
||||
reg = wm8900_read(codec, WM8900_REG_GPIO);
|
||||
wm8900_write(codec, WM8900_REG_GPIO,
|
||||
reg = snd_soc_read(codec, WM8900_REG_GPIO);
|
||||
snd_soc_write(codec, WM8900_REG_GPIO,
|
||||
reg | WM8900_REG_GPIO_TEMP_ENA);
|
||||
reg = wm8900_read(codec, WM8900_REG_ADDCTL);
|
||||
wm8900_write(codec, WM8900_REG_ADDCTL,
|
||||
reg = snd_soc_read(codec, WM8900_REG_ADDCTL);
|
||||
snd_soc_write(codec, WM8900_REG_ADDCTL,
|
||||
reg | WM8900_REG_ADDCTL_TEMP_SD);
|
||||
break;
|
||||
|
||||
|
@ -1139,69 +1059,69 @@ static int wm8900_set_bias_level(struct snd_soc_codec *codec,
|
|||
/* Charge capacitors if initial power up */
|
||||
if (codec->bias_level == SND_SOC_BIAS_OFF) {
|
||||
/* STARTUP_BIAS_ENA on */
|
||||
wm8900_write(codec, WM8900_REG_POWER1,
|
||||
snd_soc_write(codec, WM8900_REG_POWER1,
|
||||
WM8900_REG_POWER1_STARTUP_BIAS_ENA);
|
||||
|
||||
/* Startup bias mode */
|
||||
wm8900_write(codec, WM8900_REG_ADDCTL,
|
||||
snd_soc_write(codec, WM8900_REG_ADDCTL,
|
||||
WM8900_REG_ADDCTL_BIAS_SRC |
|
||||
WM8900_REG_ADDCTL_VMID_SOFTST);
|
||||
|
||||
/* VMID 2x50k */
|
||||
wm8900_write(codec, WM8900_REG_POWER1,
|
||||
snd_soc_write(codec, WM8900_REG_POWER1,
|
||||
WM8900_REG_POWER1_STARTUP_BIAS_ENA | 0x1);
|
||||
|
||||
/* Allow capacitors to charge */
|
||||
schedule_timeout_interruptible(msecs_to_jiffies(400));
|
||||
|
||||
/* Enable bias */
|
||||
wm8900_write(codec, WM8900_REG_POWER1,
|
||||
snd_soc_write(codec, WM8900_REG_POWER1,
|
||||
WM8900_REG_POWER1_STARTUP_BIAS_ENA |
|
||||
WM8900_REG_POWER1_BIAS_ENA | 0x1);
|
||||
|
||||
wm8900_write(codec, WM8900_REG_ADDCTL, 0);
|
||||
snd_soc_write(codec, WM8900_REG_ADDCTL, 0);
|
||||
|
||||
wm8900_write(codec, WM8900_REG_POWER1,
|
||||
snd_soc_write(codec, WM8900_REG_POWER1,
|
||||
WM8900_REG_POWER1_BIAS_ENA | 0x1);
|
||||
}
|
||||
|
||||
reg = wm8900_read(codec, WM8900_REG_POWER1);
|
||||
wm8900_write(codec, WM8900_REG_POWER1,
|
||||
reg = snd_soc_read(codec, WM8900_REG_POWER1);
|
||||
snd_soc_write(codec, WM8900_REG_POWER1,
|
||||
(reg & WM8900_REG_POWER1_FLL_ENA) |
|
||||
WM8900_REG_POWER1_BIAS_ENA | 0x1);
|
||||
wm8900_write(codec, WM8900_REG_POWER2,
|
||||
snd_soc_write(codec, WM8900_REG_POWER2,
|
||||
WM8900_REG_POWER2_SYSCLK_ENA);
|
||||
wm8900_write(codec, WM8900_REG_POWER3, 0);
|
||||
snd_soc_write(codec, WM8900_REG_POWER3, 0);
|
||||
break;
|
||||
|
||||
case SND_SOC_BIAS_OFF:
|
||||
/* Startup bias enable */
|
||||
reg = wm8900_read(codec, WM8900_REG_POWER1);
|
||||
wm8900_write(codec, WM8900_REG_POWER1,
|
||||
reg = snd_soc_read(codec, WM8900_REG_POWER1);
|
||||
snd_soc_write(codec, WM8900_REG_POWER1,
|
||||
reg & WM8900_REG_POWER1_STARTUP_BIAS_ENA);
|
||||
wm8900_write(codec, WM8900_REG_ADDCTL,
|
||||
snd_soc_write(codec, WM8900_REG_ADDCTL,
|
||||
WM8900_REG_ADDCTL_BIAS_SRC |
|
||||
WM8900_REG_ADDCTL_VMID_SOFTST);
|
||||
|
||||
/* Discharge caps */
|
||||
wm8900_write(codec, WM8900_REG_POWER1,
|
||||
snd_soc_write(codec, WM8900_REG_POWER1,
|
||||
WM8900_REG_POWER1_STARTUP_BIAS_ENA);
|
||||
schedule_timeout_interruptible(msecs_to_jiffies(500));
|
||||
|
||||
/* Remove clamp */
|
||||
wm8900_write(codec, WM8900_REG_HPCTL1, 0);
|
||||
snd_soc_write(codec, WM8900_REG_HPCTL1, 0);
|
||||
|
||||
/* Power down */
|
||||
wm8900_write(codec, WM8900_REG_ADDCTL, 0);
|
||||
wm8900_write(codec, WM8900_REG_POWER1, 0);
|
||||
wm8900_write(codec, WM8900_REG_POWER2, 0);
|
||||
wm8900_write(codec, WM8900_REG_POWER3, 0);
|
||||
snd_soc_write(codec, WM8900_REG_ADDCTL, 0);
|
||||
snd_soc_write(codec, WM8900_REG_POWER1, 0);
|
||||
snd_soc_write(codec, WM8900_REG_POWER2, 0);
|
||||
snd_soc_write(codec, WM8900_REG_POWER3, 0);
|
||||
|
||||
/* Need to let things settle before stopping the clock
|
||||
* to ensure that restart works, see "Stopping the
|
||||
* master clock" in the datasheet. */
|
||||
schedule_timeout_interruptible(msecs_to_jiffies(1));
|
||||
wm8900_write(codec, WM8900_REG_POWER2,
|
||||
snd_soc_write(codec, WM8900_REG_POWER2,
|
||||
WM8900_REG_POWER2_SYSCLK_ENA);
|
||||
break;
|
||||
}
|
||||
|
@ -1264,7 +1184,7 @@ static int wm8900_resume(struct platform_device *pdev)
|
|||
|
||||
if (cache) {
|
||||
for (i = 0; i < WM8900_MAXREG; i++)
|
||||
wm8900_write(codec, i, cache[i]);
|
||||
snd_soc_write(codec, i, cache[i]);
|
||||
kfree(cache);
|
||||
} else
|
||||
dev_err(&pdev->dev, "Unable to allocate register cache\n");
|
||||
|
@ -1297,16 +1217,20 @@ static __devinit int wm8900_i2c_probe(struct i2c_client *i2c,
|
|||
|
||||
codec->name = "WM8900";
|
||||
codec->owner = THIS_MODULE;
|
||||
codec->read = wm8900_read;
|
||||
codec->write = wm8900_write;
|
||||
codec->dai = &wm8900_dai;
|
||||
codec->num_dai = 1;
|
||||
codec->hw_write = (hw_write_t)i2c_master_send;
|
||||
codec->control_data = i2c;
|
||||
codec->set_bias_level = wm8900_set_bias_level;
|
||||
codec->volatile_register = wm8900_volatile_register;
|
||||
codec->dev = &i2c->dev;
|
||||
|
||||
reg = wm8900_read(codec, WM8900_REG_ID);
|
||||
ret = snd_soc_codec_set_cache_io(codec, 8, 16, SND_SOC_I2C);
|
||||
if (ret != 0) {
|
||||
dev_err(&i2c->dev, "Failed to set cache I/O: %d\n", ret);
|
||||
goto err;
|
||||
}
|
||||
|
||||
reg = snd_soc_read(codec, WM8900_REG_ID);
|
||||
if (reg != 0x8900) {
|
||||
dev_err(&i2c->dev, "Device is not a WM8900 - ID %x\n", reg);
|
||||
ret = -ENODEV;
|
||||
|
@ -1314,7 +1238,7 @@ static __devinit int wm8900_i2c_probe(struct i2c_client *i2c,
|
|||
}
|
||||
|
||||
/* Read back from the chip */
|
||||
reg = wm8900_chip_read(codec, WM8900_REG_POWER1);
|
||||
reg = snd_soc_read(codec, WM8900_REG_POWER1);
|
||||
reg = (reg >> 12) & 0xf;
|
||||
dev_info(&i2c->dev, "WM8900 revision %d\n", reg);
|
||||
|
||||
|
@ -1324,29 +1248,29 @@ static __devinit int wm8900_i2c_probe(struct i2c_client *i2c,
|
|||
wm8900_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
|
||||
|
||||
/* Latch the volume update bits */
|
||||
wm8900_write(codec, WM8900_REG_LINVOL,
|
||||
wm8900_read(codec, WM8900_REG_LINVOL) | 0x100);
|
||||
wm8900_write(codec, WM8900_REG_RINVOL,
|
||||
wm8900_read(codec, WM8900_REG_RINVOL) | 0x100);
|
||||
wm8900_write(codec, WM8900_REG_LOUT1CTL,
|
||||
wm8900_read(codec, WM8900_REG_LOUT1CTL) | 0x100);
|
||||
wm8900_write(codec, WM8900_REG_ROUT1CTL,
|
||||
wm8900_read(codec, WM8900_REG_ROUT1CTL) | 0x100);
|
||||
wm8900_write(codec, WM8900_REG_LOUT2CTL,
|
||||
wm8900_read(codec, WM8900_REG_LOUT2CTL) | 0x100);
|
||||
wm8900_write(codec, WM8900_REG_ROUT2CTL,
|
||||
wm8900_read(codec, WM8900_REG_ROUT2CTL) | 0x100);
|
||||
wm8900_write(codec, WM8900_REG_LDAC_DV,
|
||||
wm8900_read(codec, WM8900_REG_LDAC_DV) | 0x100);
|
||||
wm8900_write(codec, WM8900_REG_RDAC_DV,
|
||||
wm8900_read(codec, WM8900_REG_RDAC_DV) | 0x100);
|
||||
wm8900_write(codec, WM8900_REG_LADC_DV,
|
||||
wm8900_read(codec, WM8900_REG_LADC_DV) | 0x100);
|
||||
wm8900_write(codec, WM8900_REG_RADC_DV,
|
||||
wm8900_read(codec, WM8900_REG_RADC_DV) | 0x100);
|
||||
snd_soc_write(codec, WM8900_REG_LINVOL,
|
||||
snd_soc_read(codec, WM8900_REG_LINVOL) | 0x100);
|
||||
snd_soc_write(codec, WM8900_REG_RINVOL,
|
||||
snd_soc_read(codec, WM8900_REG_RINVOL) | 0x100);
|
||||
snd_soc_write(codec, WM8900_REG_LOUT1CTL,
|
||||
snd_soc_read(codec, WM8900_REG_LOUT1CTL) | 0x100);
|
||||
snd_soc_write(codec, WM8900_REG_ROUT1CTL,
|
||||
snd_soc_read(codec, WM8900_REG_ROUT1CTL) | 0x100);
|
||||
snd_soc_write(codec, WM8900_REG_LOUT2CTL,
|
||||
snd_soc_read(codec, WM8900_REG_LOUT2CTL) | 0x100);
|
||||
snd_soc_write(codec, WM8900_REG_ROUT2CTL,
|
||||
snd_soc_read(codec, WM8900_REG_ROUT2CTL) | 0x100);
|
||||
snd_soc_write(codec, WM8900_REG_LDAC_DV,
|
||||
snd_soc_read(codec, WM8900_REG_LDAC_DV) | 0x100);
|
||||
snd_soc_write(codec, WM8900_REG_RDAC_DV,
|
||||
snd_soc_read(codec, WM8900_REG_RDAC_DV) | 0x100);
|
||||
snd_soc_write(codec, WM8900_REG_LADC_DV,
|
||||
snd_soc_read(codec, WM8900_REG_LADC_DV) | 0x100);
|
||||
snd_soc_write(codec, WM8900_REG_RADC_DV,
|
||||
snd_soc_read(codec, WM8900_REG_RADC_DV) | 0x100);
|
||||
|
||||
/* Set the DAC and mixer output bias */
|
||||
wm8900_write(codec, WM8900_REG_OUTBIASCTL, 0x81);
|
||||
snd_soc_write(codec, WM8900_REG_OUTBIASCTL, 0x81);
|
||||
|
||||
wm8900_dai.dev = &i2c->dev;
|
||||
|
||||
|
@ -1388,6 +1312,21 @@ static __devexit int wm8900_i2c_remove(struct i2c_client *client)
|
|||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int wm8900_i2c_suspend(struct i2c_client *client, pm_message_t msg)
|
||||
{
|
||||
return snd_soc_suspend_device(&client->dev);
|
||||
}
|
||||
|
||||
static int wm8900_i2c_resume(struct i2c_client *client)
|
||||
{
|
||||
return snd_soc_resume_device(&client->dev);
|
||||
}
|
||||
#else
|
||||
#define wm8900_i2c_suspend NULL
|
||||
#define wm8900_i2c_resume NULL
|
||||
#endif
|
||||
|
||||
static const struct i2c_device_id wm8900_i2c_id[] = {
|
||||
{ "wm8900", 0 },
|
||||
{ }
|
||||
|
@ -1401,6 +1340,8 @@ static struct i2c_driver wm8900_i2c_driver = {
|
|||
},
|
||||
.probe = wm8900_i2c_probe,
|
||||
.remove = __devexit_p(wm8900_i2c_remove),
|
||||
.suspend = wm8900_i2c_suspend,
|
||||
.resume = wm8900_i2c_resume,
|
||||
.id_table = wm8900_i2c_id,
|
||||
};
|
||||
|
||||
|
|
|
@ -225,94 +225,18 @@ struct wm8903_priv {
|
|||
struct snd_pcm_substream *slave_substream;
|
||||
};
|
||||
|
||||
|
||||
static unsigned int wm8903_read_reg_cache(struct snd_soc_codec *codec,
|
||||
unsigned int reg)
|
||||
{
|
||||
u16 *cache = codec->reg_cache;
|
||||
|
||||
BUG_ON(reg >= ARRAY_SIZE(wm8903_reg_defaults));
|
||||
|
||||
return cache[reg];
|
||||
}
|
||||
|
||||
static unsigned int wm8903_hw_read(struct snd_soc_codec *codec, u8 reg)
|
||||
{
|
||||
struct i2c_msg xfer[2];
|
||||
u16 data;
|
||||
int ret;
|
||||
struct i2c_client *client = codec->control_data;
|
||||
|
||||
/* Write register */
|
||||
xfer[0].addr = client->addr;
|
||||
xfer[0].flags = 0;
|
||||
xfer[0].len = 1;
|
||||
xfer[0].buf = ®
|
||||
|
||||
/* Read data */
|
||||
xfer[1].addr = client->addr;
|
||||
xfer[1].flags = I2C_M_RD;
|
||||
xfer[1].len = 2;
|
||||
xfer[1].buf = (u8 *)&data;
|
||||
|
||||
ret = i2c_transfer(client->adapter, xfer, 2);
|
||||
if (ret != 2) {
|
||||
pr_err("i2c_transfer returned %d\n", ret);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (data >> 8) | ((data & 0xff) << 8);
|
||||
}
|
||||
|
||||
static unsigned int wm8903_read(struct snd_soc_codec *codec,
|
||||
unsigned int reg)
|
||||
static int wm8903_volatile_register(unsigned int reg)
|
||||
{
|
||||
switch (reg) {
|
||||
case WM8903_SW_RESET_AND_ID:
|
||||
case WM8903_REVISION_NUMBER:
|
||||
case WM8903_INTERRUPT_STATUS_1:
|
||||
case WM8903_WRITE_SEQUENCER_4:
|
||||
return wm8903_hw_read(codec, reg);
|
||||
return 1;
|
||||
|
||||
default:
|
||||
return wm8903_read_reg_cache(codec, reg);
|
||||
}
|
||||
}
|
||||
|
||||
static void wm8903_write_reg_cache(struct snd_soc_codec *codec,
|
||||
u16 reg, unsigned int value)
|
||||
{
|
||||
u16 *cache = codec->reg_cache;
|
||||
|
||||
BUG_ON(reg >= ARRAY_SIZE(wm8903_reg_defaults));
|
||||
|
||||
switch (reg) {
|
||||
case WM8903_SW_RESET_AND_ID:
|
||||
case WM8903_REVISION_NUMBER:
|
||||
break;
|
||||
|
||||
default:
|
||||
cache[reg] = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int wm8903_write(struct snd_soc_codec *codec, unsigned int reg,
|
||||
unsigned int value)
|
||||
{
|
||||
u8 data[3];
|
||||
|
||||
wm8903_write_reg_cache(codec, reg, value);
|
||||
|
||||
/* Data format is 1 byte of address followed by 2 bytes of data */
|
||||
data[0] = reg;
|
||||
data[1] = (value >> 8) & 0xff;
|
||||
data[2] = value & 0xff;
|
||||
|
||||
if (codec->hw_write(codec->control_data, data, 3) == 2)
|
||||
return 0;
|
||||
else
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
|
||||
static int wm8903_run_sequence(struct snd_soc_codec *codec, unsigned int start)
|
||||
|
@ -323,13 +247,13 @@ static int wm8903_run_sequence(struct snd_soc_codec *codec, unsigned int start)
|
|||
BUG_ON(start > 48);
|
||||
|
||||
/* Enable the sequencer */
|
||||
reg[0] = wm8903_read(codec, WM8903_WRITE_SEQUENCER_0);
|
||||
reg[0] = snd_soc_read(codec, WM8903_WRITE_SEQUENCER_0);
|
||||
reg[0] |= WM8903_WSEQ_ENA;
|
||||
wm8903_write(codec, WM8903_WRITE_SEQUENCER_0, reg[0]);
|
||||
snd_soc_write(codec, WM8903_WRITE_SEQUENCER_0, reg[0]);
|
||||
|
||||
dev_dbg(&i2c->dev, "Starting sequence at %d\n", start);
|
||||
|
||||
wm8903_write(codec, WM8903_WRITE_SEQUENCER_3,
|
||||
snd_soc_write(codec, WM8903_WRITE_SEQUENCER_3,
|
||||
start | WM8903_WSEQ_START);
|
||||
|
||||
/* Wait for it to complete. If we have the interrupt wired up then
|
||||
|
@ -339,13 +263,13 @@ static int wm8903_run_sequence(struct snd_soc_codec *codec, unsigned int start)
|
|||
do {
|
||||
msleep(10);
|
||||
|
||||
reg[4] = wm8903_read(codec, WM8903_WRITE_SEQUENCER_4);
|
||||
reg[4] = snd_soc_read(codec, WM8903_WRITE_SEQUENCER_4);
|
||||
} while (reg[4] & WM8903_WSEQ_BUSY);
|
||||
|
||||
dev_dbg(&i2c->dev, "Sequence complete\n");
|
||||
|
||||
/* Disable the sequencer again */
|
||||
wm8903_write(codec, WM8903_WRITE_SEQUENCER_0,
|
||||
snd_soc_write(codec, WM8903_WRITE_SEQUENCER_0,
|
||||
reg[0] & ~WM8903_WSEQ_ENA);
|
||||
|
||||
return 0;
|
||||
|
@ -357,12 +281,12 @@ static void wm8903_sync_reg_cache(struct snd_soc_codec *codec, u16 *cache)
|
|||
|
||||
/* There really ought to be something better we can do here :/ */
|
||||
for (i = 0; i < ARRAY_SIZE(wm8903_reg_defaults); i++)
|
||||
cache[i] = wm8903_hw_read(codec, i);
|
||||
cache[i] = codec->hw_read(codec, i);
|
||||
}
|
||||
|
||||
static void wm8903_reset(struct snd_soc_codec *codec)
|
||||
{
|
||||
wm8903_write(codec, WM8903_SW_RESET_AND_ID, 0);
|
||||
snd_soc_write(codec, WM8903_SW_RESET_AND_ID, 0);
|
||||
memcpy(codec->reg_cache, wm8903_reg_defaults,
|
||||
sizeof(wm8903_reg_defaults));
|
||||
}
|
||||
|
@ -423,52 +347,52 @@ static int wm8903_output_event(struct snd_soc_dapm_widget *w,
|
|||
}
|
||||
|
||||
if (event & SND_SOC_DAPM_PRE_PMU) {
|
||||
val = wm8903_read(codec, reg);
|
||||
val = snd_soc_read(codec, reg);
|
||||
|
||||
/* Short the output */
|
||||
val &= ~(WM8903_OUTPUT_SHORT << shift);
|
||||
wm8903_write(codec, reg, val);
|
||||
snd_soc_write(codec, reg, val);
|
||||
}
|
||||
|
||||
if (event & SND_SOC_DAPM_POST_PMU) {
|
||||
val = wm8903_read(codec, reg);
|
||||
val = snd_soc_read(codec, reg);
|
||||
|
||||
val |= (WM8903_OUTPUT_IN << shift);
|
||||
wm8903_write(codec, reg, val);
|
||||
snd_soc_write(codec, reg, val);
|
||||
|
||||
val |= (WM8903_OUTPUT_INT << shift);
|
||||
wm8903_write(codec, reg, val);
|
||||
snd_soc_write(codec, reg, val);
|
||||
|
||||
/* Turn on the output ENA_OUTP */
|
||||
val |= (WM8903_OUTPUT_OUT << shift);
|
||||
wm8903_write(codec, reg, val);
|
||||
snd_soc_write(codec, reg, val);
|
||||
|
||||
/* Enable the DC servo */
|
||||
dcs_reg = wm8903_read(codec, WM8903_DC_SERVO_0);
|
||||
dcs_reg = snd_soc_read(codec, WM8903_DC_SERVO_0);
|
||||
dcs_reg |= dcs_bit;
|
||||
wm8903_write(codec, WM8903_DC_SERVO_0, dcs_reg);
|
||||
snd_soc_write(codec, WM8903_DC_SERVO_0, dcs_reg);
|
||||
|
||||
/* Remove the short */
|
||||
val |= (WM8903_OUTPUT_SHORT << shift);
|
||||
wm8903_write(codec, reg, val);
|
||||
snd_soc_write(codec, reg, val);
|
||||
}
|
||||
|
||||
if (event & SND_SOC_DAPM_PRE_PMD) {
|
||||
val = wm8903_read(codec, reg);
|
||||
val = snd_soc_read(codec, reg);
|
||||
|
||||
/* Short the output */
|
||||
val &= ~(WM8903_OUTPUT_SHORT << shift);
|
||||
wm8903_write(codec, reg, val);
|
||||
snd_soc_write(codec, reg, val);
|
||||
|
||||
/* Disable the DC servo */
|
||||
dcs_reg = wm8903_read(codec, WM8903_DC_SERVO_0);
|
||||
dcs_reg = snd_soc_read(codec, WM8903_DC_SERVO_0);
|
||||
dcs_reg &= ~dcs_bit;
|
||||
wm8903_write(codec, WM8903_DC_SERVO_0, dcs_reg);
|
||||
snd_soc_write(codec, WM8903_DC_SERVO_0, dcs_reg);
|
||||
|
||||
/* Then disable the intermediate and output stages */
|
||||
val &= ~((WM8903_OUTPUT_OUT | WM8903_OUTPUT_INT |
|
||||
WM8903_OUTPUT_IN) << shift);
|
||||
wm8903_write(codec, reg, val);
|
||||
snd_soc_write(codec, reg, val);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -492,13 +416,13 @@ static int wm8903_class_w_put(struct snd_kcontrol *kcontrol,
|
|||
u16 reg;
|
||||
int ret;
|
||||
|
||||
reg = wm8903_read(codec, WM8903_CLASS_W_0);
|
||||
reg = snd_soc_read(codec, WM8903_CLASS_W_0);
|
||||
|
||||
/* Turn it off if we're about to enable bypass */
|
||||
if (ucontrol->value.integer.value[0]) {
|
||||
if (wm8903->class_w_users == 0) {
|
||||
dev_dbg(&i2c->dev, "Disabling Class W\n");
|
||||
wm8903_write(codec, WM8903_CLASS_W_0, reg &
|
||||
snd_soc_write(codec, WM8903_CLASS_W_0, reg &
|
||||
~(WM8903_CP_DYN_FREQ | WM8903_CP_DYN_V));
|
||||
}
|
||||
wm8903->class_w_users++;
|
||||
|
@ -511,7 +435,7 @@ static int wm8903_class_w_put(struct snd_kcontrol *kcontrol,
|
|||
if (!ucontrol->value.integer.value[0]) {
|
||||
if (wm8903->class_w_users == 1) {
|
||||
dev_dbg(&i2c->dev, "Enabling Class W\n");
|
||||
wm8903_write(codec, WM8903_CLASS_W_0, reg |
|
||||
snd_soc_write(codec, WM8903_CLASS_W_0, reg |
|
||||
WM8903_CP_DYN_FREQ | WM8903_CP_DYN_V);
|
||||
}
|
||||
wm8903->class_w_users--;
|
||||
|
@ -715,8 +639,6 @@ SOC_ENUM("DAC Soft Mute Rate", soft_mute),
|
|||
SOC_ENUM("DAC Mute Mode", mute_mode),
|
||||
SOC_SINGLE("DAC Mono Switch", WM8903_DAC_DIGITAL_1, 12, 1, 0),
|
||||
SOC_ENUM("DAC De-emphasis", dac_deemphasis),
|
||||
SOC_SINGLE("DAC Sloping Stopband Filter Switch",
|
||||
WM8903_DAC_DIGITAL_1, 11, 1, 0),
|
||||
SOC_ENUM("DAC Companding Mode", dac_companding),
|
||||
SOC_SINGLE("DAC Companding Switch", WM8903_AUDIO_INTERFACE_0, 1, 1, 0),
|
||||
|
||||
|
@ -1011,55 +933,55 @@ static int wm8903_set_bias_level(struct snd_soc_codec *codec,
|
|||
switch (level) {
|
||||
case SND_SOC_BIAS_ON:
|
||||
case SND_SOC_BIAS_PREPARE:
|
||||
reg = wm8903_read(codec, WM8903_VMID_CONTROL_0);
|
||||
reg = snd_soc_read(codec, WM8903_VMID_CONTROL_0);
|
||||
reg &= ~(WM8903_VMID_RES_MASK);
|
||||
reg |= WM8903_VMID_RES_50K;
|
||||
wm8903_write(codec, WM8903_VMID_CONTROL_0, reg);
|
||||
snd_soc_write(codec, WM8903_VMID_CONTROL_0, reg);
|
||||
break;
|
||||
|
||||
case SND_SOC_BIAS_STANDBY:
|
||||
if (codec->bias_level == SND_SOC_BIAS_OFF) {
|
||||
wm8903_write(codec, WM8903_CLOCK_RATES_2,
|
||||
snd_soc_write(codec, WM8903_CLOCK_RATES_2,
|
||||
WM8903_CLK_SYS_ENA);
|
||||
|
||||
/* Change DC servo dither level in startup sequence */
|
||||
wm8903_write(codec, WM8903_WRITE_SEQUENCER_0, 0x11);
|
||||
wm8903_write(codec, WM8903_WRITE_SEQUENCER_1, 0x1257);
|
||||
wm8903_write(codec, WM8903_WRITE_SEQUENCER_2, 0x2);
|
||||
snd_soc_write(codec, WM8903_WRITE_SEQUENCER_0, 0x11);
|
||||
snd_soc_write(codec, WM8903_WRITE_SEQUENCER_1, 0x1257);
|
||||
snd_soc_write(codec, WM8903_WRITE_SEQUENCER_2, 0x2);
|
||||
|
||||
wm8903_run_sequence(codec, 0);
|
||||
wm8903_sync_reg_cache(codec, codec->reg_cache);
|
||||
|
||||
/* Enable low impedence charge pump output */
|
||||
reg = wm8903_read(codec,
|
||||
reg = snd_soc_read(codec,
|
||||
WM8903_CONTROL_INTERFACE_TEST_1);
|
||||
wm8903_write(codec, WM8903_CONTROL_INTERFACE_TEST_1,
|
||||
snd_soc_write(codec, WM8903_CONTROL_INTERFACE_TEST_1,
|
||||
reg | WM8903_TEST_KEY);
|
||||
reg2 = wm8903_read(codec, WM8903_CHARGE_PUMP_TEST_1);
|
||||
wm8903_write(codec, WM8903_CHARGE_PUMP_TEST_1,
|
||||
reg2 = snd_soc_read(codec, WM8903_CHARGE_PUMP_TEST_1);
|
||||
snd_soc_write(codec, WM8903_CHARGE_PUMP_TEST_1,
|
||||
reg2 | WM8903_CP_SW_KELVIN_MODE_MASK);
|
||||
wm8903_write(codec, WM8903_CONTROL_INTERFACE_TEST_1,
|
||||
snd_soc_write(codec, WM8903_CONTROL_INTERFACE_TEST_1,
|
||||
reg);
|
||||
|
||||
/* By default no bypass paths are enabled so
|
||||
* enable Class W support.
|
||||
*/
|
||||
dev_dbg(&i2c->dev, "Enabling Class W\n");
|
||||
wm8903_write(codec, WM8903_CLASS_W_0, reg |
|
||||
snd_soc_write(codec, WM8903_CLASS_W_0, reg |
|
||||
WM8903_CP_DYN_FREQ | WM8903_CP_DYN_V);
|
||||
}
|
||||
|
||||
reg = wm8903_read(codec, WM8903_VMID_CONTROL_0);
|
||||
reg = snd_soc_read(codec, WM8903_VMID_CONTROL_0);
|
||||
reg &= ~(WM8903_VMID_RES_MASK);
|
||||
reg |= WM8903_VMID_RES_250K;
|
||||
wm8903_write(codec, WM8903_VMID_CONTROL_0, reg);
|
||||
snd_soc_write(codec, WM8903_VMID_CONTROL_0, reg);
|
||||
break;
|
||||
|
||||
case SND_SOC_BIAS_OFF:
|
||||
wm8903_run_sequence(codec, 32);
|
||||
reg = wm8903_read(codec, WM8903_CLOCK_RATES_2);
|
||||
reg = snd_soc_read(codec, WM8903_CLOCK_RATES_2);
|
||||
reg &= ~WM8903_CLK_SYS_ENA;
|
||||
wm8903_write(codec, WM8903_CLOCK_RATES_2, reg);
|
||||
snd_soc_write(codec, WM8903_CLOCK_RATES_2, reg);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -1083,7 +1005,7 @@ static int wm8903_set_dai_fmt(struct snd_soc_dai *codec_dai,
|
|||
unsigned int fmt)
|
||||
{
|
||||
struct snd_soc_codec *codec = codec_dai->codec;
|
||||
u16 aif1 = wm8903_read(codec, WM8903_AUDIO_INTERFACE_1);
|
||||
u16 aif1 = snd_soc_read(codec, WM8903_AUDIO_INTERFACE_1);
|
||||
|
||||
aif1 &= ~(WM8903_LRCLK_DIR | WM8903_BCLK_DIR | WM8903_AIF_FMT_MASK |
|
||||
WM8903_AIF_LRCLK_INV | WM8903_AIF_BCLK_INV);
|
||||
|
@ -1161,7 +1083,7 @@ static int wm8903_set_dai_fmt(struct snd_soc_dai *codec_dai,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
wm8903_write(codec, WM8903_AUDIO_INTERFACE_1, aif1);
|
||||
snd_soc_write(codec, WM8903_AUDIO_INTERFACE_1, aif1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -1171,14 +1093,14 @@ static int wm8903_digital_mute(struct snd_soc_dai *codec_dai, int mute)
|
|||
struct snd_soc_codec *codec = codec_dai->codec;
|
||||
u16 reg;
|
||||
|
||||
reg = wm8903_read(codec, WM8903_DAC_DIGITAL_1);
|
||||
reg = snd_soc_read(codec, WM8903_DAC_DIGITAL_1);
|
||||
|
||||
if (mute)
|
||||
reg |= WM8903_DAC_MUTE;
|
||||
else
|
||||
reg &= ~WM8903_DAC_MUTE;
|
||||
|
||||
wm8903_write(codec, WM8903_DAC_DIGITAL_1, reg);
|
||||
snd_soc_write(codec, WM8903_DAC_DIGITAL_1, reg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -1368,17 +1290,24 @@ static int wm8903_hw_params(struct snd_pcm_substream *substream,
|
|||
int cur_val;
|
||||
int clk_sys;
|
||||
|
||||
u16 aif1 = wm8903_read(codec, WM8903_AUDIO_INTERFACE_1);
|
||||
u16 aif2 = wm8903_read(codec, WM8903_AUDIO_INTERFACE_2);
|
||||
u16 aif3 = wm8903_read(codec, WM8903_AUDIO_INTERFACE_3);
|
||||
u16 clock0 = wm8903_read(codec, WM8903_CLOCK_RATES_0);
|
||||
u16 clock1 = wm8903_read(codec, WM8903_CLOCK_RATES_1);
|
||||
u16 aif1 = snd_soc_read(codec, WM8903_AUDIO_INTERFACE_1);
|
||||
u16 aif2 = snd_soc_read(codec, WM8903_AUDIO_INTERFACE_2);
|
||||
u16 aif3 = snd_soc_read(codec, WM8903_AUDIO_INTERFACE_3);
|
||||
u16 clock0 = snd_soc_read(codec, WM8903_CLOCK_RATES_0);
|
||||
u16 clock1 = snd_soc_read(codec, WM8903_CLOCK_RATES_1);
|
||||
u16 dac_digital1 = snd_soc_read(codec, WM8903_DAC_DIGITAL_1);
|
||||
|
||||
if (substream == wm8903->slave_substream) {
|
||||
dev_dbg(&i2c->dev, "Ignoring hw_params for slave substream\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Enable sloping stopband filter for low sample rates */
|
||||
if (fs <= 24000)
|
||||
dac_digital1 |= WM8903_DAC_SB_FILT;
|
||||
else
|
||||
dac_digital1 &= ~WM8903_DAC_SB_FILT;
|
||||
|
||||
/* Configure sample rate logic for DSP - choose nearest rate */
|
||||
dsp_config = 0;
|
||||
best_val = abs(sample_rates[dsp_config].rate - fs);
|
||||
|
@ -1498,11 +1427,12 @@ static int wm8903_hw_params(struct snd_pcm_substream *substream,
|
|||
aif2 |= bclk_divs[bclk_div].div;
|
||||
aif3 |= bclk / fs;
|
||||
|
||||
wm8903_write(codec, WM8903_CLOCK_RATES_0, clock0);
|
||||
wm8903_write(codec, WM8903_CLOCK_RATES_1, clock1);
|
||||
wm8903_write(codec, WM8903_AUDIO_INTERFACE_1, aif1);
|
||||
wm8903_write(codec, WM8903_AUDIO_INTERFACE_2, aif2);
|
||||
wm8903_write(codec, WM8903_AUDIO_INTERFACE_3, aif3);
|
||||
snd_soc_write(codec, WM8903_CLOCK_RATES_0, clock0);
|
||||
snd_soc_write(codec, WM8903_CLOCK_RATES_1, clock1);
|
||||
snd_soc_write(codec, WM8903_AUDIO_INTERFACE_1, aif1);
|
||||
snd_soc_write(codec, WM8903_AUDIO_INTERFACE_2, aif2);
|
||||
snd_soc_write(codec, WM8903_AUDIO_INTERFACE_3, aif3);
|
||||
snd_soc_write(codec, WM8903_DAC_DIGITAL_1, dac_digital1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -1587,7 +1517,7 @@ static int wm8903_resume(struct platform_device *pdev)
|
|||
if (tmp_cache) {
|
||||
for (i = 2; i < ARRAY_SIZE(wm8903_reg_defaults); i++)
|
||||
if (tmp_cache[i] != reg_cache[i])
|
||||
wm8903_write(codec, i, tmp_cache[i]);
|
||||
snd_soc_write(codec, i, tmp_cache[i]);
|
||||
} else {
|
||||
dev_err(&i2c->dev, "Failed to allocate temporary cache\n");
|
||||
}
|
||||
|
@ -1618,9 +1548,6 @@ static __devinit int wm8903_i2c_probe(struct i2c_client *i2c,
|
|||
codec->dev = &i2c->dev;
|
||||
codec->name = "WM8903";
|
||||
codec->owner = THIS_MODULE;
|
||||
codec->read = wm8903_read;
|
||||
codec->write = wm8903_write;
|
||||
codec->hw_write = (hw_write_t)i2c_master_send;
|
||||
codec->bias_level = SND_SOC_BIAS_OFF;
|
||||
codec->set_bias_level = wm8903_set_bias_level;
|
||||
codec->dai = &wm8903_dai;
|
||||
|
@ -1628,18 +1555,25 @@ static __devinit int wm8903_i2c_probe(struct i2c_client *i2c,
|
|||
codec->reg_cache_size = ARRAY_SIZE(wm8903->reg_cache);
|
||||
codec->reg_cache = &wm8903->reg_cache[0];
|
||||
codec->private_data = wm8903;
|
||||
codec->volatile_register = wm8903_volatile_register;
|
||||
|
||||
i2c_set_clientdata(i2c, codec);
|
||||
codec->control_data = i2c;
|
||||
|
||||
val = wm8903_hw_read(codec, WM8903_SW_RESET_AND_ID);
|
||||
ret = snd_soc_codec_set_cache_io(codec, 8, 16, SND_SOC_I2C);
|
||||
if (ret != 0) {
|
||||
dev_err(&i2c->dev, "Failed to set cache I/O: %d\n", ret);
|
||||
goto err;
|
||||
}
|
||||
|
||||
val = snd_soc_read(codec, WM8903_SW_RESET_AND_ID);
|
||||
if (val != wm8903_reg_defaults[WM8903_SW_RESET_AND_ID]) {
|
||||
dev_err(&i2c->dev,
|
||||
"Device with ID register %x is not a WM8903\n", val);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
val = wm8903_read(codec, WM8903_REVISION_NUMBER);
|
||||
val = snd_soc_read(codec, WM8903_REVISION_NUMBER);
|
||||
dev_info(&i2c->dev, "WM8903 revision %d\n",
|
||||
val & WM8903_CHIP_REV_MASK);
|
||||
|
||||
|
@ -1649,35 +1583,35 @@ static __devinit int wm8903_i2c_probe(struct i2c_client *i2c,
|
|||
wm8903_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
|
||||
|
||||
/* Latch volume update bits */
|
||||
val = wm8903_read(codec, WM8903_ADC_DIGITAL_VOLUME_LEFT);
|
||||
val = snd_soc_read(codec, WM8903_ADC_DIGITAL_VOLUME_LEFT);
|
||||
val |= WM8903_ADCVU;
|
||||
wm8903_write(codec, WM8903_ADC_DIGITAL_VOLUME_LEFT, val);
|
||||
wm8903_write(codec, WM8903_ADC_DIGITAL_VOLUME_RIGHT, val);
|
||||
snd_soc_write(codec, WM8903_ADC_DIGITAL_VOLUME_LEFT, val);
|
||||
snd_soc_write(codec, WM8903_ADC_DIGITAL_VOLUME_RIGHT, val);
|
||||
|
||||
val = wm8903_read(codec, WM8903_DAC_DIGITAL_VOLUME_LEFT);
|
||||
val = snd_soc_read(codec, WM8903_DAC_DIGITAL_VOLUME_LEFT);
|
||||
val |= WM8903_DACVU;
|
||||
wm8903_write(codec, WM8903_DAC_DIGITAL_VOLUME_LEFT, val);
|
||||
wm8903_write(codec, WM8903_DAC_DIGITAL_VOLUME_RIGHT, val);
|
||||
snd_soc_write(codec, WM8903_DAC_DIGITAL_VOLUME_LEFT, val);
|
||||
snd_soc_write(codec, WM8903_DAC_DIGITAL_VOLUME_RIGHT, val);
|
||||
|
||||
val = wm8903_read(codec, WM8903_ANALOGUE_OUT1_LEFT);
|
||||
val = snd_soc_read(codec, WM8903_ANALOGUE_OUT1_LEFT);
|
||||
val |= WM8903_HPOUTVU;
|
||||
wm8903_write(codec, WM8903_ANALOGUE_OUT1_LEFT, val);
|
||||
wm8903_write(codec, WM8903_ANALOGUE_OUT1_RIGHT, val);
|
||||
snd_soc_write(codec, WM8903_ANALOGUE_OUT1_LEFT, val);
|
||||
snd_soc_write(codec, WM8903_ANALOGUE_OUT1_RIGHT, val);
|
||||
|
||||
val = wm8903_read(codec, WM8903_ANALOGUE_OUT2_LEFT);
|
||||
val = snd_soc_read(codec, WM8903_ANALOGUE_OUT2_LEFT);
|
||||
val |= WM8903_LINEOUTVU;
|
||||
wm8903_write(codec, WM8903_ANALOGUE_OUT2_LEFT, val);
|
||||
wm8903_write(codec, WM8903_ANALOGUE_OUT2_RIGHT, val);
|
||||
snd_soc_write(codec, WM8903_ANALOGUE_OUT2_LEFT, val);
|
||||
snd_soc_write(codec, WM8903_ANALOGUE_OUT2_RIGHT, val);
|
||||
|
||||
val = wm8903_read(codec, WM8903_ANALOGUE_OUT3_LEFT);
|
||||
val = snd_soc_read(codec, WM8903_ANALOGUE_OUT3_LEFT);
|
||||
val |= WM8903_SPKVU;
|
||||
wm8903_write(codec, WM8903_ANALOGUE_OUT3_LEFT, val);
|
||||
wm8903_write(codec, WM8903_ANALOGUE_OUT3_RIGHT, val);
|
||||
snd_soc_write(codec, WM8903_ANALOGUE_OUT3_LEFT, val);
|
||||
snd_soc_write(codec, WM8903_ANALOGUE_OUT3_RIGHT, val);
|
||||
|
||||
/* Enable DAC soft mute by default */
|
||||
val = wm8903_read(codec, WM8903_DAC_DIGITAL_1);
|
||||
val = snd_soc_read(codec, WM8903_DAC_DIGITAL_1);
|
||||
val |= WM8903_DAC_MUTEMODE;
|
||||
wm8903_write(codec, WM8903_DAC_DIGITAL_1, val);
|
||||
snd_soc_write(codec, WM8903_DAC_DIGITAL_1, val);
|
||||
|
||||
wm8903_dai.dev = &i2c->dev;
|
||||
wm8903_codec = codec;
|
||||
|
@ -1721,6 +1655,21 @@ static __devexit int wm8903_i2c_remove(struct i2c_client *client)
|
|||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int wm8903_i2c_suspend(struct i2c_client *client, pm_message_t msg)
|
||||
{
|
||||
return snd_soc_suspend_device(&client->dev);
|
||||
}
|
||||
|
||||
static int wm8903_i2c_resume(struct i2c_client *client)
|
||||
{
|
||||
return snd_soc_resume_device(&client->dev);
|
||||
}
|
||||
#else
|
||||
#define wm8903_i2c_suspend NULL
|
||||
#define wm8903_i2c_resume NULL
|
||||
#endif
|
||||
|
||||
/* i2c codec control layer */
|
||||
static const struct i2c_device_id wm8903_i2c_id[] = {
|
||||
{ "wm8903", 0 },
|
||||
|
@ -1735,6 +1684,8 @@ static struct i2c_driver wm8903_i2c_driver = {
|
|||
},
|
||||
.probe = wm8903_i2c_probe,
|
||||
.remove = __devexit_p(wm8903_i2c_remove),
|
||||
.suspend = wm8903_i2c_suspend,
|
||||
.resume = wm8903_i2c_resume,
|
||||
.id_table = wm8903_i2c_id,
|
||||
};
|
||||
|
||||
|
|
|
@ -106,50 +106,6 @@ static u16 wm8940_reg_defaults[] = {
|
|||
0x0000, /* Mono Mixer Control */
|
||||
};
|
||||
|
||||
static inline unsigned int wm8940_read_reg_cache(struct snd_soc_codec *codec,
|
||||
unsigned int reg)
|
||||
{
|
||||
u16 *cache = codec->reg_cache;
|
||||
|
||||
if (reg >= ARRAY_SIZE(wm8940_reg_defaults))
|
||||
return -1;
|
||||
|
||||
return cache[reg];
|
||||
}
|
||||
|
||||
static inline int wm8940_write_reg_cache(struct snd_soc_codec *codec,
|
||||
u16 reg, unsigned int value)
|
||||
{
|
||||
u16 *cache = codec->reg_cache;
|
||||
|
||||
if (reg >= ARRAY_SIZE(wm8940_reg_defaults))
|
||||
return -1;
|
||||
|
||||
cache[reg] = value;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wm8940_write(struct snd_soc_codec *codec, unsigned int reg,
|
||||
unsigned int value)
|
||||
{
|
||||
int ret;
|
||||
u8 data[3] = { reg,
|
||||
(value & 0xff00) >> 8,
|
||||
(value & 0x00ff)
|
||||
};
|
||||
|
||||
wm8940_write_reg_cache(codec, reg, value);
|
||||
|
||||
ret = codec->hw_write(codec->control_data, data, 3);
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
else if (ret != 3)
|
||||
return -EIO;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char *wm8940_companding[] = { "Off", "NC", "u-law", "A-law" };
|
||||
static const struct soc_enum wm8940_adc_companding_enum
|
||||
= SOC_ENUM_SINGLE(WM8940_COMPANDINGCTL, 1, 4, wm8940_companding);
|
||||
|
@ -348,14 +304,14 @@ error_ret:
|
|||
return ret;
|
||||
}
|
||||
|
||||
#define wm8940_reset(c) wm8940_write(c, WM8940_SOFTRESET, 0);
|
||||
#define wm8940_reset(c) snd_soc_write(c, WM8940_SOFTRESET, 0);
|
||||
|
||||
static int wm8940_set_dai_fmt(struct snd_soc_dai *codec_dai,
|
||||
unsigned int fmt)
|
||||
{
|
||||
struct snd_soc_codec *codec = codec_dai->codec;
|
||||
u16 iface = wm8940_read_reg_cache(codec, WM8940_IFACE) & 0xFE67;
|
||||
u16 clk = wm8940_read_reg_cache(codec, WM8940_CLOCK) & 0x1fe;
|
||||
u16 iface = snd_soc_read(codec, WM8940_IFACE) & 0xFE67;
|
||||
u16 clk = snd_soc_read(codec, WM8940_CLOCK) & 0x1fe;
|
||||
|
||||
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
|
||||
case SND_SOC_DAIFMT_CBM_CFM:
|
||||
|
@ -366,7 +322,7 @@ static int wm8940_set_dai_fmt(struct snd_soc_dai *codec_dai,
|
|||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
wm8940_write(codec, WM8940_CLOCK, clk);
|
||||
snd_soc_write(codec, WM8940_CLOCK, clk);
|
||||
|
||||
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
|
||||
case SND_SOC_DAIFMT_I2S:
|
||||
|
@ -399,7 +355,7 @@ static int wm8940_set_dai_fmt(struct snd_soc_dai *codec_dai,
|
|||
break;
|
||||
}
|
||||
|
||||
wm8940_write(codec, WM8940_IFACE, iface);
|
||||
snd_soc_write(codec, WM8940_IFACE, iface);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -411,9 +367,9 @@ static int wm8940_i2s_hw_params(struct snd_pcm_substream *substream,
|
|||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_device *socdev = rtd->socdev;
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
u16 iface = wm8940_read_reg_cache(codec, WM8940_IFACE) & 0xFD9F;
|
||||
u16 addcntrl = wm8940_read_reg_cache(codec, WM8940_ADDCNTRL) & 0xFFF1;
|
||||
u16 companding = wm8940_read_reg_cache(codec,
|
||||
u16 iface = snd_soc_read(codec, WM8940_IFACE) & 0xFD9F;
|
||||
u16 addcntrl = snd_soc_read(codec, WM8940_ADDCNTRL) & 0xFFF1;
|
||||
u16 companding = snd_soc_read(codec,
|
||||
WM8940_COMPANDINGCTL) & 0xFFDF;
|
||||
int ret;
|
||||
|
||||
|
@ -442,7 +398,7 @@ static int wm8940_i2s_hw_params(struct snd_pcm_substream *substream,
|
|||
case SNDRV_PCM_RATE_48000:
|
||||
break;
|
||||
}
|
||||
ret = wm8940_write(codec, WM8940_ADDCNTRL, addcntrl);
|
||||
ret = snd_soc_write(codec, WM8940_ADDCNTRL, addcntrl);
|
||||
if (ret)
|
||||
goto error_ret;
|
||||
|
||||
|
@ -462,10 +418,10 @@ static int wm8940_i2s_hw_params(struct snd_pcm_substream *substream,
|
|||
iface |= (3 << 5);
|
||||
break;
|
||||
}
|
||||
ret = wm8940_write(codec, WM8940_COMPANDINGCTL, companding);
|
||||
ret = snd_soc_write(codec, WM8940_COMPANDINGCTL, companding);
|
||||
if (ret)
|
||||
goto error_ret;
|
||||
ret = wm8940_write(codec, WM8940_IFACE, iface);
|
||||
ret = snd_soc_write(codec, WM8940_IFACE, iface);
|
||||
|
||||
error_ret:
|
||||
return ret;
|
||||
|
@ -474,19 +430,19 @@ error_ret:
|
|||
static int wm8940_mute(struct snd_soc_dai *dai, int mute)
|
||||
{
|
||||
struct snd_soc_codec *codec = dai->codec;
|
||||
u16 mute_reg = wm8940_read_reg_cache(codec, WM8940_DAC) & 0xffbf;
|
||||
u16 mute_reg = snd_soc_read(codec, WM8940_DAC) & 0xffbf;
|
||||
|
||||
if (mute)
|
||||
mute_reg |= 0x40;
|
||||
|
||||
return wm8940_write(codec, WM8940_DAC, mute_reg);
|
||||
return snd_soc_write(codec, WM8940_DAC, mute_reg);
|
||||
}
|
||||
|
||||
static int wm8940_set_bias_level(struct snd_soc_codec *codec,
|
||||
enum snd_soc_bias_level level)
|
||||
{
|
||||
u16 val;
|
||||
u16 pwr_reg = wm8940_read_reg_cache(codec, WM8940_POWER1) & 0x1F0;
|
||||
u16 pwr_reg = snd_soc_read(codec, WM8940_POWER1) & 0x1F0;
|
||||
int ret = 0;
|
||||
|
||||
switch (level) {
|
||||
|
@ -494,26 +450,26 @@ static int wm8940_set_bias_level(struct snd_soc_codec *codec,
|
|||
/* ensure bufioen and biasen */
|
||||
pwr_reg |= (1 << 2) | (1 << 3);
|
||||
/* Enable thermal shutdown */
|
||||
val = wm8940_read_reg_cache(codec, WM8940_OUTPUTCTL);
|
||||
ret = wm8940_write(codec, WM8940_OUTPUTCTL, val | 0x2);
|
||||
val = snd_soc_read(codec, WM8940_OUTPUTCTL);
|
||||
ret = snd_soc_write(codec, WM8940_OUTPUTCTL, val | 0x2);
|
||||
if (ret)
|
||||
break;
|
||||
/* set vmid to 75k */
|
||||
ret = wm8940_write(codec, WM8940_POWER1, pwr_reg | 0x1);
|
||||
ret = snd_soc_write(codec, WM8940_POWER1, pwr_reg | 0x1);
|
||||
break;
|
||||
case SND_SOC_BIAS_PREPARE:
|
||||
/* ensure bufioen and biasen */
|
||||
pwr_reg |= (1 << 2) | (1 << 3);
|
||||
ret = wm8940_write(codec, WM8940_POWER1, pwr_reg | 0x1);
|
||||
ret = snd_soc_write(codec, WM8940_POWER1, pwr_reg | 0x1);
|
||||
break;
|
||||
case SND_SOC_BIAS_STANDBY:
|
||||
/* ensure bufioen and biasen */
|
||||
pwr_reg |= (1 << 2) | (1 << 3);
|
||||
/* set vmid to 300k for standby */
|
||||
ret = wm8940_write(codec, WM8940_POWER1, pwr_reg | 0x2);
|
||||
ret = snd_soc_write(codec, WM8940_POWER1, pwr_reg | 0x2);
|
||||
break;
|
||||
case SND_SOC_BIAS_OFF:
|
||||
ret = wm8940_write(codec, WM8940_POWER1, pwr_reg);
|
||||
ret = snd_soc_write(codec, WM8940_POWER1, pwr_reg);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -587,36 +543,36 @@ static int wm8940_set_dai_pll(struct snd_soc_dai *codec_dai,
|
|||
u16 reg;
|
||||
|
||||
/* Turn off PLL */
|
||||
reg = wm8940_read_reg_cache(codec, WM8940_POWER1);
|
||||
wm8940_write(codec, WM8940_POWER1, reg & 0x1df);
|
||||
reg = snd_soc_read(codec, WM8940_POWER1);
|
||||
snd_soc_write(codec, WM8940_POWER1, reg & 0x1df);
|
||||
|
||||
if (freq_in == 0 || freq_out == 0) {
|
||||
/* Clock CODEC directly from MCLK */
|
||||
reg = wm8940_read_reg_cache(codec, WM8940_CLOCK);
|
||||
wm8940_write(codec, WM8940_CLOCK, reg & 0x0ff);
|
||||
reg = snd_soc_read(codec, WM8940_CLOCK);
|
||||
snd_soc_write(codec, WM8940_CLOCK, reg & 0x0ff);
|
||||
/* Pll power down */
|
||||
wm8940_write(codec, WM8940_PLLN, (1 << 7));
|
||||
snd_soc_write(codec, WM8940_PLLN, (1 << 7));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Pll is followed by a frequency divide by 4 */
|
||||
pll_factors(freq_out*4, freq_in);
|
||||
if (pll_div.k)
|
||||
wm8940_write(codec, WM8940_PLLN,
|
||||
snd_soc_write(codec, WM8940_PLLN,
|
||||
(pll_div.pre_scale << 4) | pll_div.n | (1 << 6));
|
||||
else /* No factional component */
|
||||
wm8940_write(codec, WM8940_PLLN,
|
||||
snd_soc_write(codec, WM8940_PLLN,
|
||||
(pll_div.pre_scale << 4) | pll_div.n);
|
||||
wm8940_write(codec, WM8940_PLLK1, pll_div.k >> 18);
|
||||
wm8940_write(codec, WM8940_PLLK2, (pll_div.k >> 9) & 0x1ff);
|
||||
wm8940_write(codec, WM8940_PLLK3, pll_div.k & 0x1ff);
|
||||
snd_soc_write(codec, WM8940_PLLK1, pll_div.k >> 18);
|
||||
snd_soc_write(codec, WM8940_PLLK2, (pll_div.k >> 9) & 0x1ff);
|
||||
snd_soc_write(codec, WM8940_PLLK3, pll_div.k & 0x1ff);
|
||||
/* Enable the PLL */
|
||||
reg = wm8940_read_reg_cache(codec, WM8940_POWER1);
|
||||
wm8940_write(codec, WM8940_POWER1, reg | 0x020);
|
||||
reg = snd_soc_read(codec, WM8940_POWER1);
|
||||
snd_soc_write(codec, WM8940_POWER1, reg | 0x020);
|
||||
|
||||
/* Run CODEC from PLL instead of MCLK */
|
||||
reg = wm8940_read_reg_cache(codec, WM8940_CLOCK);
|
||||
wm8940_write(codec, WM8940_CLOCK, reg | 0x100);
|
||||
reg = snd_soc_read(codec, WM8940_CLOCK);
|
||||
snd_soc_write(codec, WM8940_CLOCK, reg | 0x100);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -648,16 +604,16 @@ static int wm8940_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
|
|||
|
||||
switch (div_id) {
|
||||
case WM8940_BCLKDIV:
|
||||
reg = wm8940_read_reg_cache(codec, WM8940_CLOCK) & 0xFFEF3;
|
||||
ret = wm8940_write(codec, WM8940_CLOCK, reg | (div << 2));
|
||||
reg = snd_soc_read(codec, WM8940_CLOCK) & 0xFFEF3;
|
||||
ret = snd_soc_write(codec, WM8940_CLOCK, reg | (div << 2));
|
||||
break;
|
||||
case WM8940_MCLKDIV:
|
||||
reg = wm8940_read_reg_cache(codec, WM8940_CLOCK) & 0xFF1F;
|
||||
ret = wm8940_write(codec, WM8940_CLOCK, reg | (div << 5));
|
||||
reg = snd_soc_read(codec, WM8940_CLOCK) & 0xFF1F;
|
||||
ret = snd_soc_write(codec, WM8940_CLOCK, reg | (div << 5));
|
||||
break;
|
||||
case WM8940_OPCLKDIV:
|
||||
reg = wm8940_read_reg_cache(codec, WM8940_ADDCNTRL) & 0xFFCF;
|
||||
ret = wm8940_write(codec, WM8940_ADDCNTRL, reg | (div << 4));
|
||||
reg = snd_soc_read(codec, WM8940_ADDCNTRL) & 0xFFCF;
|
||||
ret = snd_soc_write(codec, WM8940_ADDCNTRL, reg | (div << 4));
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
|
@ -808,7 +764,8 @@ struct snd_soc_codec_device soc_codec_dev_wm8940 = {
|
|||
};
|
||||
EXPORT_SYMBOL_GPL(soc_codec_dev_wm8940);
|
||||
|
||||
static int wm8940_register(struct wm8940_priv *wm8940)
|
||||
static int wm8940_register(struct wm8940_priv *wm8940,
|
||||
enum snd_soc_control_type control)
|
||||
{
|
||||
struct wm8940_setup_data *pdata = wm8940->codec.dev->platform_data;
|
||||
struct snd_soc_codec *codec = &wm8940->codec;
|
||||
|
@ -825,8 +782,6 @@ static int wm8940_register(struct wm8940_priv *wm8940)
|
|||
codec->private_data = wm8940;
|
||||
codec->name = "WM8940";
|
||||
codec->owner = THIS_MODULE;
|
||||
codec->read = wm8940_read_reg_cache;
|
||||
codec->write = wm8940_write;
|
||||
codec->bias_level = SND_SOC_BIAS_OFF;
|
||||
codec->set_bias_level = wm8940_set_bias_level;
|
||||
codec->dai = &wm8940_dai;
|
||||
|
@ -834,6 +789,12 @@ static int wm8940_register(struct wm8940_priv *wm8940)
|
|||
codec->reg_cache_size = ARRAY_SIZE(wm8940_reg_defaults);
|
||||
codec->reg_cache = &wm8940->reg_cache;
|
||||
|
||||
ret = snd_soc_codec_set_cache_io(codec, 8, 16, control);
|
||||
if (ret == 0) {
|
||||
dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
memcpy(codec->reg_cache, wm8940_reg_defaults,
|
||||
sizeof(wm8940_reg_defaults));
|
||||
|
||||
|
@ -847,15 +808,15 @@ static int wm8940_register(struct wm8940_priv *wm8940)
|
|||
|
||||
wm8940_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
|
||||
|
||||
ret = wm8940_write(codec, WM8940_POWER1, 0x180);
|
||||
ret = snd_soc_write(codec, WM8940_POWER1, 0x180);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (!pdata)
|
||||
dev_warn(codec->dev, "No platform data supplied\n");
|
||||
else {
|
||||
reg = wm8940_read_reg_cache(codec, WM8940_OUTPUTCTL);
|
||||
ret = wm8940_write(codec, WM8940_OUTPUTCTL, reg | pdata->vroi);
|
||||
reg = snd_soc_read(codec, WM8940_OUTPUTCTL);
|
||||
ret = snd_soc_write(codec, WM8940_OUTPUTCTL, reg | pdata->vroi);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
@ -904,7 +865,7 @@ static int wm8940_i2c_probe(struct i2c_client *i2c,
|
|||
codec->control_data = i2c;
|
||||
codec->dev = &i2c->dev;
|
||||
|
||||
return wm8940_register(wm8940);
|
||||
return wm8940_register(wm8940, SND_SOC_I2C);
|
||||
}
|
||||
|
||||
static int __devexit wm8940_i2c_remove(struct i2c_client *client)
|
||||
|
@ -916,6 +877,21 @@ static int __devexit wm8940_i2c_remove(struct i2c_client *client)
|
|||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int wm8940_i2c_suspend(struct i2c_client *client, pm_message_t msg)
|
||||
{
|
||||
return snd_soc_suspend_device(&client->dev);
|
||||
}
|
||||
|
||||
static int wm8940_i2c_resume(struct i2c_client *client)
|
||||
{
|
||||
return snd_soc_resume_device(&client->dev);
|
||||
}
|
||||
#else
|
||||
#define wm8940_i2c_suspend NULL
|
||||
#define wm8940_i2c_resume NULL
|
||||
#endif
|
||||
|
||||
static const struct i2c_device_id wm8940_i2c_id[] = {
|
||||
{ "wm8940", 0 },
|
||||
{ }
|
||||
|
@ -929,6 +905,8 @@ static struct i2c_driver wm8940_i2c_driver = {
|
|||
},
|
||||
.probe = wm8940_i2c_probe,
|
||||
.remove = __devexit_p(wm8940_i2c_remove),
|
||||
.suspend = wm8940_i2c_suspend,
|
||||
.resume = wm8940_i2c_resume,
|
||||
.id_table = wm8940_i2c_id,
|
||||
};
|
||||
|
||||
|
|
|
@ -69,61 +69,7 @@ struct wm8960_priv {
|
|||
struct snd_soc_codec codec;
|
||||
};
|
||||
|
||||
/*
|
||||
* read wm8960 register cache
|
||||
*/
|
||||
static inline unsigned int wm8960_read_reg_cache(struct snd_soc_codec *codec,
|
||||
unsigned int reg)
|
||||
{
|
||||
u16 *cache = codec->reg_cache;
|
||||
if (reg == WM8960_RESET)
|
||||
return 0;
|
||||
if (reg >= WM8960_CACHEREGNUM)
|
||||
return -1;
|
||||
return cache[reg];
|
||||
}
|
||||
|
||||
/*
|
||||
* write wm8960 register cache
|
||||
*/
|
||||
static inline void wm8960_write_reg_cache(struct snd_soc_codec *codec,
|
||||
u16 reg, unsigned int value)
|
||||
{
|
||||
u16 *cache = codec->reg_cache;
|
||||
if (reg >= WM8960_CACHEREGNUM)
|
||||
return;
|
||||
cache[reg] = value;
|
||||
}
|
||||
|
||||
static inline unsigned int wm8960_read(struct snd_soc_codec *codec,
|
||||
unsigned int reg)
|
||||
{
|
||||
return wm8960_read_reg_cache(codec, reg);
|
||||
}
|
||||
|
||||
/*
|
||||
* write to the WM8960 register space
|
||||
*/
|
||||
static int wm8960_write(struct snd_soc_codec *codec, unsigned int reg,
|
||||
unsigned int value)
|
||||
{
|
||||
u8 data[2];
|
||||
|
||||
/* data is
|
||||
* D15..D9 WM8960 register offset
|
||||
* D8...D0 register data
|
||||
*/
|
||||
data[0] = (reg << 1) | ((value >> 8) & 0x0001);
|
||||
data[1] = value & 0x00ff;
|
||||
|
||||
wm8960_write_reg_cache(codec, reg, value);
|
||||
if (codec->hw_write(codec->control_data, data, 2) == 2)
|
||||
return 0;
|
||||
else
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
#define wm8960_reset(c) wm8960_write(c, WM8960_RESET, 0)
|
||||
#define wm8960_reset(c) snd_soc_write(c, WM8960_RESET, 0)
|
||||
|
||||
/* enumerated controls */
|
||||
static const char *wm8960_deemph[] = {"None", "32Khz", "44.1Khz", "48Khz"};
|
||||
|
@ -420,7 +366,7 @@ static int wm8960_set_dai_fmt(struct snd_soc_dai *codec_dai,
|
|||
}
|
||||
|
||||
/* set iface */
|
||||
wm8960_write(codec, WM8960_IFACE1, iface);
|
||||
snd_soc_write(codec, WM8960_IFACE1, iface);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -431,7 +377,7 @@ static int wm8960_hw_params(struct snd_pcm_substream *substream,
|
|||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_device *socdev = rtd->socdev;
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
u16 iface = wm8960_read(codec, WM8960_IFACE1) & 0xfff3;
|
||||
u16 iface = snd_soc_read(codec, WM8960_IFACE1) & 0xfff3;
|
||||
|
||||
/* bit size */
|
||||
switch (params_format(params)) {
|
||||
|
@ -446,19 +392,19 @@ static int wm8960_hw_params(struct snd_pcm_substream *substream,
|
|||
}
|
||||
|
||||
/* set iface */
|
||||
wm8960_write(codec, WM8960_IFACE1, iface);
|
||||
snd_soc_write(codec, WM8960_IFACE1, iface);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wm8960_mute(struct snd_soc_dai *dai, int mute)
|
||||
{
|
||||
struct snd_soc_codec *codec = dai->codec;
|
||||
u16 mute_reg = wm8960_read(codec, WM8960_DACCTL1) & 0xfff7;
|
||||
u16 mute_reg = snd_soc_read(codec, WM8960_DACCTL1) & 0xfff7;
|
||||
|
||||
if (mute)
|
||||
wm8960_write(codec, WM8960_DACCTL1, mute_reg | 0x8);
|
||||
snd_soc_write(codec, WM8960_DACCTL1, mute_reg | 0x8);
|
||||
else
|
||||
wm8960_write(codec, WM8960_DACCTL1, mute_reg);
|
||||
snd_soc_write(codec, WM8960_DACCTL1, mute_reg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -474,16 +420,16 @@ static int wm8960_set_bias_level(struct snd_soc_codec *codec,
|
|||
|
||||
case SND_SOC_BIAS_PREPARE:
|
||||
/* Set VMID to 2x50k */
|
||||
reg = wm8960_read(codec, WM8960_POWER1);
|
||||
reg = snd_soc_read(codec, WM8960_POWER1);
|
||||
reg &= ~0x180;
|
||||
reg |= 0x80;
|
||||
wm8960_write(codec, WM8960_POWER1, reg);
|
||||
snd_soc_write(codec, WM8960_POWER1, reg);
|
||||
break;
|
||||
|
||||
case SND_SOC_BIAS_STANDBY:
|
||||
if (codec->bias_level == SND_SOC_BIAS_OFF) {
|
||||
/* Enable anti-pop features */
|
||||
wm8960_write(codec, WM8960_APOP1,
|
||||
snd_soc_write(codec, WM8960_APOP1,
|
||||
WM8960_POBCTRL | WM8960_SOFT_ST |
|
||||
WM8960_BUFDCOPEN | WM8960_BUFIOEN);
|
||||
|
||||
|
@ -491,43 +437,43 @@ static int wm8960_set_bias_level(struct snd_soc_codec *codec,
|
|||
reg = WM8960_DISOP;
|
||||
if (pdata)
|
||||
reg |= pdata->dres << 4;
|
||||
wm8960_write(codec, WM8960_APOP2, reg);
|
||||
snd_soc_write(codec, WM8960_APOP2, reg);
|
||||
|
||||
msleep(400);
|
||||
|
||||
wm8960_write(codec, WM8960_APOP2, 0);
|
||||
snd_soc_write(codec, WM8960_APOP2, 0);
|
||||
|
||||
/* Enable & ramp VMID at 2x50k */
|
||||
reg = wm8960_read(codec, WM8960_POWER1);
|
||||
reg = snd_soc_read(codec, WM8960_POWER1);
|
||||
reg |= 0x80;
|
||||
wm8960_write(codec, WM8960_POWER1, reg);
|
||||
snd_soc_write(codec, WM8960_POWER1, reg);
|
||||
msleep(100);
|
||||
|
||||
/* Enable VREF */
|
||||
wm8960_write(codec, WM8960_POWER1, reg | WM8960_VREF);
|
||||
snd_soc_write(codec, WM8960_POWER1, reg | WM8960_VREF);
|
||||
|
||||
/* Disable anti-pop features */
|
||||
wm8960_write(codec, WM8960_APOP1, WM8960_BUFIOEN);
|
||||
snd_soc_write(codec, WM8960_APOP1, WM8960_BUFIOEN);
|
||||
}
|
||||
|
||||
/* Set VMID to 2x250k */
|
||||
reg = wm8960_read(codec, WM8960_POWER1);
|
||||
reg = snd_soc_read(codec, WM8960_POWER1);
|
||||
reg &= ~0x180;
|
||||
reg |= 0x100;
|
||||
wm8960_write(codec, WM8960_POWER1, reg);
|
||||
snd_soc_write(codec, WM8960_POWER1, reg);
|
||||
break;
|
||||
|
||||
case SND_SOC_BIAS_OFF:
|
||||
/* Enable anti-pop features */
|
||||
wm8960_write(codec, WM8960_APOP1,
|
||||
snd_soc_write(codec, WM8960_APOP1,
|
||||
WM8960_POBCTRL | WM8960_SOFT_ST |
|
||||
WM8960_BUFDCOPEN | WM8960_BUFIOEN);
|
||||
|
||||
/* Disable VMID and VREF, let them discharge */
|
||||
wm8960_write(codec, WM8960_POWER1, 0);
|
||||
snd_soc_write(codec, WM8960_POWER1, 0);
|
||||
msleep(600);
|
||||
|
||||
wm8960_write(codec, WM8960_APOP1, 0);
|
||||
snd_soc_write(codec, WM8960_APOP1, 0);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -610,33 +556,33 @@ static int wm8960_set_dai_pll(struct snd_soc_dai *codec_dai,
|
|||
|
||||
/* Disable the PLL: even if we are changing the frequency the
|
||||
* PLL needs to be disabled while we do so. */
|
||||
wm8960_write(codec, WM8960_CLOCK1,
|
||||
wm8960_read(codec, WM8960_CLOCK1) & ~1);
|
||||
wm8960_write(codec, WM8960_POWER2,
|
||||
wm8960_read(codec, WM8960_POWER2) & ~1);
|
||||
snd_soc_write(codec, WM8960_CLOCK1,
|
||||
snd_soc_read(codec, WM8960_CLOCK1) & ~1);
|
||||
snd_soc_write(codec, WM8960_POWER2,
|
||||
snd_soc_read(codec, WM8960_POWER2) & ~1);
|
||||
|
||||
if (!freq_in || !freq_out)
|
||||
return 0;
|
||||
|
||||
reg = wm8960_read(codec, WM8960_PLL1) & ~0x3f;
|
||||
reg = snd_soc_read(codec, WM8960_PLL1) & ~0x3f;
|
||||
reg |= pll_div.pre_div << 4;
|
||||
reg |= pll_div.n;
|
||||
|
||||
if (pll_div.k) {
|
||||
reg |= 0x20;
|
||||
|
||||
wm8960_write(codec, WM8960_PLL2, (pll_div.k >> 18) & 0x3f);
|
||||
wm8960_write(codec, WM8960_PLL3, (pll_div.k >> 9) & 0x1ff);
|
||||
wm8960_write(codec, WM8960_PLL4, pll_div.k & 0x1ff);
|
||||
snd_soc_write(codec, WM8960_PLL2, (pll_div.k >> 18) & 0x3f);
|
||||
snd_soc_write(codec, WM8960_PLL3, (pll_div.k >> 9) & 0x1ff);
|
||||
snd_soc_write(codec, WM8960_PLL4, pll_div.k & 0x1ff);
|
||||
}
|
||||
wm8960_write(codec, WM8960_PLL1, reg);
|
||||
snd_soc_write(codec, WM8960_PLL1, reg);
|
||||
|
||||
/* Turn it on */
|
||||
wm8960_write(codec, WM8960_POWER2,
|
||||
wm8960_read(codec, WM8960_POWER2) | 1);
|
||||
snd_soc_write(codec, WM8960_POWER2,
|
||||
snd_soc_read(codec, WM8960_POWER2) | 1);
|
||||
msleep(250);
|
||||
wm8960_write(codec, WM8960_CLOCK1,
|
||||
wm8960_read(codec, WM8960_CLOCK1) | 1);
|
||||
snd_soc_write(codec, WM8960_CLOCK1,
|
||||
snd_soc_read(codec, WM8960_CLOCK1) | 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -649,28 +595,28 @@ static int wm8960_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
|
|||
|
||||
switch (div_id) {
|
||||
case WM8960_SYSCLKSEL:
|
||||
reg = wm8960_read(codec, WM8960_CLOCK1) & 0x1fe;
|
||||
wm8960_write(codec, WM8960_CLOCK1, reg | div);
|
||||
reg = snd_soc_read(codec, WM8960_CLOCK1) & 0x1fe;
|
||||
snd_soc_write(codec, WM8960_CLOCK1, reg | div);
|
||||
break;
|
||||
case WM8960_SYSCLKDIV:
|
||||
reg = wm8960_read(codec, WM8960_CLOCK1) & 0x1f9;
|
||||
wm8960_write(codec, WM8960_CLOCK1, reg | div);
|
||||
reg = snd_soc_read(codec, WM8960_CLOCK1) & 0x1f9;
|
||||
snd_soc_write(codec, WM8960_CLOCK1, reg | div);
|
||||
break;
|
||||
case WM8960_DACDIV:
|
||||
reg = wm8960_read(codec, WM8960_CLOCK1) & 0x1c7;
|
||||
wm8960_write(codec, WM8960_CLOCK1, reg | div);
|
||||
reg = snd_soc_read(codec, WM8960_CLOCK1) & 0x1c7;
|
||||
snd_soc_write(codec, WM8960_CLOCK1, reg | div);
|
||||
break;
|
||||
case WM8960_OPCLKDIV:
|
||||
reg = wm8960_read(codec, WM8960_PLL1) & 0x03f;
|
||||
wm8960_write(codec, WM8960_PLL1, reg | div);
|
||||
reg = snd_soc_read(codec, WM8960_PLL1) & 0x03f;
|
||||
snd_soc_write(codec, WM8960_PLL1, reg | div);
|
||||
break;
|
||||
case WM8960_DCLKDIV:
|
||||
reg = wm8960_read(codec, WM8960_CLOCK2) & 0x03f;
|
||||
wm8960_write(codec, WM8960_CLOCK2, reg | div);
|
||||
reg = snd_soc_read(codec, WM8960_CLOCK2) & 0x03f;
|
||||
snd_soc_write(codec, WM8960_CLOCK2, reg | div);
|
||||
break;
|
||||
case WM8960_TOCLKSEL:
|
||||
reg = wm8960_read(codec, WM8960_ADDCTL1) & 0x1fd;
|
||||
wm8960_write(codec, WM8960_ADDCTL1, reg | div);
|
||||
reg = snd_soc_read(codec, WM8960_ADDCTL1) & 0x1fd;
|
||||
snd_soc_write(codec, WM8960_ADDCTL1, reg | div);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
|
@ -801,7 +747,8 @@ struct snd_soc_codec_device soc_codec_dev_wm8960 = {
|
|||
};
|
||||
EXPORT_SYMBOL_GPL(soc_codec_dev_wm8960);
|
||||
|
||||
static int wm8960_register(struct wm8960_priv *wm8960)
|
||||
static int wm8960_register(struct wm8960_priv *wm8960,
|
||||
enum snd_soc_control_type control)
|
||||
{
|
||||
struct wm8960_data *pdata = wm8960->codec.dev->platform_data;
|
||||
struct snd_soc_codec *codec = &wm8960->codec;
|
||||
|
@ -810,7 +757,8 @@ static int wm8960_register(struct wm8960_priv *wm8960)
|
|||
|
||||
if (wm8960_codec) {
|
||||
dev_err(codec->dev, "Another WM8960 is registered\n");
|
||||
return -EINVAL;
|
||||
ret = -EINVAL;
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (!pdata) {
|
||||
|
@ -829,8 +777,6 @@ static int wm8960_register(struct wm8960_priv *wm8960)
|
|||
codec->private_data = wm8960;
|
||||
codec->name = "WM8960";
|
||||
codec->owner = THIS_MODULE;
|
||||
codec->read = wm8960_read_reg_cache;
|
||||
codec->write = wm8960_write;
|
||||
codec->bias_level = SND_SOC_BIAS_OFF;
|
||||
codec->set_bias_level = wm8960_set_bias_level;
|
||||
codec->dai = &wm8960_dai;
|
||||
|
@ -840,10 +786,16 @@ static int wm8960_register(struct wm8960_priv *wm8960)
|
|||
|
||||
memcpy(codec->reg_cache, wm8960_reg, sizeof(wm8960_reg));
|
||||
|
||||
ret = snd_soc_codec_set_cache_io(codec, 7, 9, control);
|
||||
if (ret < 0) {
|
||||
dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = wm8960_reset(codec);
|
||||
if (ret < 0) {
|
||||
dev_err(codec->dev, "Failed to issue reset\n");
|
||||
return ret;
|
||||
goto err;
|
||||
}
|
||||
|
||||
wm8960_dai.dev = codec->dev;
|
||||
|
@ -851,43 +803,48 @@ static int wm8960_register(struct wm8960_priv *wm8960)
|
|||
wm8960_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
|
||||
|
||||
/* Latch the update bits */
|
||||
reg = wm8960_read(codec, WM8960_LINVOL);
|
||||
wm8960_write(codec, WM8960_LINVOL, reg | 0x100);
|
||||
reg = wm8960_read(codec, WM8960_RINVOL);
|
||||
wm8960_write(codec, WM8960_RINVOL, reg | 0x100);
|
||||
reg = wm8960_read(codec, WM8960_LADC);
|
||||
wm8960_write(codec, WM8960_LADC, reg | 0x100);
|
||||
reg = wm8960_read(codec, WM8960_RADC);
|
||||
wm8960_write(codec, WM8960_RADC, reg | 0x100);
|
||||
reg = wm8960_read(codec, WM8960_LDAC);
|
||||
wm8960_write(codec, WM8960_LDAC, reg | 0x100);
|
||||
reg = wm8960_read(codec, WM8960_RDAC);
|
||||
wm8960_write(codec, WM8960_RDAC, reg | 0x100);
|
||||
reg = wm8960_read(codec, WM8960_LOUT1);
|
||||
wm8960_write(codec, WM8960_LOUT1, reg | 0x100);
|
||||
reg = wm8960_read(codec, WM8960_ROUT1);
|
||||
wm8960_write(codec, WM8960_ROUT1, reg | 0x100);
|
||||
reg = wm8960_read(codec, WM8960_LOUT2);
|
||||
wm8960_write(codec, WM8960_LOUT2, reg | 0x100);
|
||||
reg = wm8960_read(codec, WM8960_ROUT2);
|
||||
wm8960_write(codec, WM8960_ROUT2, reg | 0x100);
|
||||
reg = snd_soc_read(codec, WM8960_LINVOL);
|
||||
snd_soc_write(codec, WM8960_LINVOL, reg | 0x100);
|
||||
reg = snd_soc_read(codec, WM8960_RINVOL);
|
||||
snd_soc_write(codec, WM8960_RINVOL, reg | 0x100);
|
||||
reg = snd_soc_read(codec, WM8960_LADC);
|
||||
snd_soc_write(codec, WM8960_LADC, reg | 0x100);
|
||||
reg = snd_soc_read(codec, WM8960_RADC);
|
||||
snd_soc_write(codec, WM8960_RADC, reg | 0x100);
|
||||
reg = snd_soc_read(codec, WM8960_LDAC);
|
||||
snd_soc_write(codec, WM8960_LDAC, reg | 0x100);
|
||||
reg = snd_soc_read(codec, WM8960_RDAC);
|
||||
snd_soc_write(codec, WM8960_RDAC, reg | 0x100);
|
||||
reg = snd_soc_read(codec, WM8960_LOUT1);
|
||||
snd_soc_write(codec, WM8960_LOUT1, reg | 0x100);
|
||||
reg = snd_soc_read(codec, WM8960_ROUT1);
|
||||
snd_soc_write(codec, WM8960_ROUT1, reg | 0x100);
|
||||
reg = snd_soc_read(codec, WM8960_LOUT2);
|
||||
snd_soc_write(codec, WM8960_LOUT2, reg | 0x100);
|
||||
reg = snd_soc_read(codec, WM8960_ROUT2);
|
||||
snd_soc_write(codec, WM8960_ROUT2, reg | 0x100);
|
||||
|
||||
wm8960_codec = codec;
|
||||
|
||||
ret = snd_soc_register_codec(codec);
|
||||
if (ret != 0) {
|
||||
dev_err(codec->dev, "Failed to register codec: %d\n", ret);
|
||||
return ret;
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = snd_soc_register_dai(&wm8960_dai);
|
||||
if (ret != 0) {
|
||||
dev_err(codec->dev, "Failed to register DAI: %d\n", ret);
|
||||
snd_soc_unregister_codec(codec);
|
||||
return ret;
|
||||
goto err_codec;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_codec:
|
||||
snd_soc_unregister_codec(codec);
|
||||
err:
|
||||
kfree(wm8960);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void wm8960_unregister(struct wm8960_priv *wm8960)
|
||||
|
@ -910,14 +867,13 @@ static __devinit int wm8960_i2c_probe(struct i2c_client *i2c,
|
|||
return -ENOMEM;
|
||||
|
||||
codec = &wm8960->codec;
|
||||
codec->hw_write = (hw_write_t)i2c_master_send;
|
||||
|
||||
i2c_set_clientdata(i2c, wm8960);
|
||||
codec->control_data = i2c;
|
||||
|
||||
codec->dev = &i2c->dev;
|
||||
|
||||
return wm8960_register(wm8960);
|
||||
return wm8960_register(wm8960, SND_SOC_I2C);
|
||||
}
|
||||
|
||||
static __devexit int wm8960_i2c_remove(struct i2c_client *client)
|
||||
|
@ -927,6 +883,21 @@ static __devexit int wm8960_i2c_remove(struct i2c_client *client)
|
|||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int wm8960_i2c_suspend(struct i2c_client *client, pm_message_t msg)
|
||||
{
|
||||
return snd_soc_suspend_device(&client->dev);
|
||||
}
|
||||
|
||||
static int wm8960_i2c_resume(struct i2c_client *client)
|
||||
{
|
||||
return snd_soc_resume_device(&client->dev);
|
||||
}
|
||||
#else
|
||||
#define wm8960_i2c_suspend NULL
|
||||
#define wm8960_i2c_resume NULL
|
||||
#endif
|
||||
|
||||
static const struct i2c_device_id wm8960_i2c_id[] = {
|
||||
{ "wm8960", 0 },
|
||||
{ }
|
||||
|
@ -940,6 +911,8 @@ static struct i2c_driver wm8960_i2c_driver = {
|
|||
},
|
||||
.probe = wm8960_i2c_probe,
|
||||
.remove = __devexit_p(wm8960_i2c_remove),
|
||||
.suspend = wm8960_i2c_suspend,
|
||||
.resume = wm8960_i2c_resume,
|
||||
.id_table = wm8960_i2c_id,
|
||||
};
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,866 @@
|
|||
/*
|
||||
* wm8961.h -- WM8961 Soc Audio driver
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef _WM8961_H
|
||||
#define _WM8961_H
|
||||
|
||||
#include <sound/soc.h>
|
||||
|
||||
extern struct snd_soc_codec_device soc_codec_dev_wm8961;
|
||||
extern struct snd_soc_dai wm8961_dai;
|
||||
|
||||
#define WM8961_BCLK 1
|
||||
#define WM8961_LRCLK 2
|
||||
|
||||
#define WM8961_BCLK_DIV_1 0
|
||||
#define WM8961_BCLK_DIV_1_5 1
|
||||
#define WM8961_BCLK_DIV_2 2
|
||||
#define WM8961_BCLK_DIV_3 3
|
||||
#define WM8961_BCLK_DIV_4 4
|
||||
#define WM8961_BCLK_DIV_5_5 5
|
||||
#define WM8961_BCLK_DIV_6 6
|
||||
#define WM8961_BCLK_DIV_8 7
|
||||
#define WM8961_BCLK_DIV_11 8
|
||||
#define WM8961_BCLK_DIV_12 9
|
||||
#define WM8961_BCLK_DIV_16 10
|
||||
#define WM8961_BCLK_DIV_24 11
|
||||
#define WM8961_BCLK_DIV_32 13
|
||||
|
||||
|
||||
/*
|
||||
* Register values.
|
||||
*/
|
||||
#define WM8961_LEFT_INPUT_VOLUME 0x00
|
||||
#define WM8961_RIGHT_INPUT_VOLUME 0x01
|
||||
#define WM8961_LOUT1_VOLUME 0x02
|
||||
#define WM8961_ROUT1_VOLUME 0x03
|
||||
#define WM8961_CLOCKING1 0x04
|
||||
#define WM8961_ADC_DAC_CONTROL_1 0x05
|
||||
#define WM8961_ADC_DAC_CONTROL_2 0x06
|
||||
#define WM8961_AUDIO_INTERFACE_0 0x07
|
||||
#define WM8961_CLOCKING2 0x08
|
||||
#define WM8961_AUDIO_INTERFACE_1 0x09
|
||||
#define WM8961_LEFT_DAC_VOLUME 0x0A
|
||||
#define WM8961_RIGHT_DAC_VOLUME 0x0B
|
||||
#define WM8961_AUDIO_INTERFACE_2 0x0E
|
||||
#define WM8961_SOFTWARE_RESET 0x0F
|
||||
#define WM8961_ALC1 0x11
|
||||
#define WM8961_ALC2 0x12
|
||||
#define WM8961_ALC3 0x13
|
||||
#define WM8961_NOISE_GATE 0x14
|
||||
#define WM8961_LEFT_ADC_VOLUME 0x15
|
||||
#define WM8961_RIGHT_ADC_VOLUME 0x16
|
||||
#define WM8961_ADDITIONAL_CONTROL_1 0x17
|
||||
#define WM8961_ADDITIONAL_CONTROL_2 0x18
|
||||
#define WM8961_PWR_MGMT_1 0x19
|
||||
#define WM8961_PWR_MGMT_2 0x1A
|
||||
#define WM8961_ADDITIONAL_CONTROL_3 0x1B
|
||||
#define WM8961_ANTI_POP 0x1C
|
||||
#define WM8961_CLOCKING_3 0x1E
|
||||
#define WM8961_ADCL_SIGNAL_PATH 0x20
|
||||
#define WM8961_ADCR_SIGNAL_PATH 0x21
|
||||
#define WM8961_LOUT2_VOLUME 0x28
|
||||
#define WM8961_ROUT2_VOLUME 0x29
|
||||
#define WM8961_PWR_MGMT_3 0x2F
|
||||
#define WM8961_ADDITIONAL_CONTROL_4 0x30
|
||||
#define WM8961_CLASS_D_CONTROL_1 0x31
|
||||
#define WM8961_CLASS_D_CONTROL_2 0x33
|
||||
#define WM8961_CLOCKING_4 0x38
|
||||
#define WM8961_DSP_SIDETONE_0 0x39
|
||||
#define WM8961_DSP_SIDETONE_1 0x3A
|
||||
#define WM8961_DC_SERVO_0 0x3C
|
||||
#define WM8961_DC_SERVO_1 0x3D
|
||||
#define WM8961_DC_SERVO_3 0x3F
|
||||
#define WM8961_DC_SERVO_5 0x41
|
||||
#define WM8961_ANALOGUE_PGA_BIAS 0x44
|
||||
#define WM8961_ANALOGUE_HP_0 0x45
|
||||
#define WM8961_ANALOGUE_HP_2 0x47
|
||||
#define WM8961_CHARGE_PUMP_1 0x48
|
||||
#define WM8961_CHARGE_PUMP_B 0x52
|
||||
#define WM8961_WRITE_SEQUENCER_1 0x57
|
||||
#define WM8961_WRITE_SEQUENCER_2 0x58
|
||||
#define WM8961_WRITE_SEQUENCER_3 0x59
|
||||
#define WM8961_WRITE_SEQUENCER_4 0x5A
|
||||
#define WM8961_WRITE_SEQUENCER_5 0x5B
|
||||
#define WM8961_WRITE_SEQUENCER_6 0x5C
|
||||
#define WM8961_WRITE_SEQUENCER_7 0x5D
|
||||
#define WM8961_GENERAL_TEST_1 0xFC
|
||||
|
||||
|
||||
/*
|
||||
* Field Definitions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* R0 (0x00) - Left Input volume
|
||||
*/
|
||||
#define WM8961_IPVU 0x0100 /* IPVU */
|
||||
#define WM8961_IPVU_MASK 0x0100 /* IPVU */
|
||||
#define WM8961_IPVU_SHIFT 8 /* IPVU */
|
||||
#define WM8961_IPVU_WIDTH 1 /* IPVU */
|
||||
#define WM8961_LINMUTE 0x0080 /* LINMUTE */
|
||||
#define WM8961_LINMUTE_MASK 0x0080 /* LINMUTE */
|
||||
#define WM8961_LINMUTE_SHIFT 7 /* LINMUTE */
|
||||
#define WM8961_LINMUTE_WIDTH 1 /* LINMUTE */
|
||||
#define WM8961_LIZC 0x0040 /* LIZC */
|
||||
#define WM8961_LIZC_MASK 0x0040 /* LIZC */
|
||||
#define WM8961_LIZC_SHIFT 6 /* LIZC */
|
||||
#define WM8961_LIZC_WIDTH 1 /* LIZC */
|
||||
#define WM8961_LINVOL_MASK 0x003F /* LINVOL - [5:0] */
|
||||
#define WM8961_LINVOL_SHIFT 0 /* LINVOL - [5:0] */
|
||||
#define WM8961_LINVOL_WIDTH 6 /* LINVOL - [5:0] */
|
||||
|
||||
/*
|
||||
* R1 (0x01) - Right Input volume
|
||||
*/
|
||||
#define WM8961_DEVICE_ID_MASK 0xF000 /* DEVICE_ID - [15:12] */
|
||||
#define WM8961_DEVICE_ID_SHIFT 12 /* DEVICE_ID - [15:12] */
|
||||
#define WM8961_DEVICE_ID_WIDTH 4 /* DEVICE_ID - [15:12] */
|
||||
#define WM8961_CHIP_REV_MASK 0x0E00 /* CHIP_REV - [11:9] */
|
||||
#define WM8961_CHIP_REV_SHIFT 9 /* CHIP_REV - [11:9] */
|
||||
#define WM8961_CHIP_REV_WIDTH 3 /* CHIP_REV - [11:9] */
|
||||
#define WM8961_IPVU 0x0100 /* IPVU */
|
||||
#define WM8961_IPVU_MASK 0x0100 /* IPVU */
|
||||
#define WM8961_IPVU_SHIFT 8 /* IPVU */
|
||||
#define WM8961_IPVU_WIDTH 1 /* IPVU */
|
||||
#define WM8961_RINMUTE 0x0080 /* RINMUTE */
|
||||
#define WM8961_RINMUTE_MASK 0x0080 /* RINMUTE */
|
||||
#define WM8961_RINMUTE_SHIFT 7 /* RINMUTE */
|
||||
#define WM8961_RINMUTE_WIDTH 1 /* RINMUTE */
|
||||
#define WM8961_RIZC 0x0040 /* RIZC */
|
||||
#define WM8961_RIZC_MASK 0x0040 /* RIZC */
|
||||
#define WM8961_RIZC_SHIFT 6 /* RIZC */
|
||||
#define WM8961_RIZC_WIDTH 1 /* RIZC */
|
||||
#define WM8961_RINVOL_MASK 0x003F /* RINVOL - [5:0] */
|
||||
#define WM8961_RINVOL_SHIFT 0 /* RINVOL - [5:0] */
|
||||
#define WM8961_RINVOL_WIDTH 6 /* RINVOL - [5:0] */
|
||||
|
||||
/*
|
||||
* R2 (0x02) - LOUT1 volume
|
||||
*/
|
||||
#define WM8961_OUT1VU 0x0100 /* OUT1VU */
|
||||
#define WM8961_OUT1VU_MASK 0x0100 /* OUT1VU */
|
||||
#define WM8961_OUT1VU_SHIFT 8 /* OUT1VU */
|
||||
#define WM8961_OUT1VU_WIDTH 1 /* OUT1VU */
|
||||
#define WM8961_LO1ZC 0x0080 /* LO1ZC */
|
||||
#define WM8961_LO1ZC_MASK 0x0080 /* LO1ZC */
|
||||
#define WM8961_LO1ZC_SHIFT 7 /* LO1ZC */
|
||||
#define WM8961_LO1ZC_WIDTH 1 /* LO1ZC */
|
||||
#define WM8961_LOUT1VOL_MASK 0x007F /* LOUT1VOL - [6:0] */
|
||||
#define WM8961_LOUT1VOL_SHIFT 0 /* LOUT1VOL - [6:0] */
|
||||
#define WM8961_LOUT1VOL_WIDTH 7 /* LOUT1VOL - [6:0] */
|
||||
|
||||
/*
|
||||
* R3 (0x03) - ROUT1 volume
|
||||
*/
|
||||
#define WM8961_OUT1VU 0x0100 /* OUT1VU */
|
||||
#define WM8961_OUT1VU_MASK 0x0100 /* OUT1VU */
|
||||
#define WM8961_OUT1VU_SHIFT 8 /* OUT1VU */
|
||||
#define WM8961_OUT1VU_WIDTH 1 /* OUT1VU */
|
||||
#define WM8961_RO1ZC 0x0080 /* RO1ZC */
|
||||
#define WM8961_RO1ZC_MASK 0x0080 /* RO1ZC */
|
||||
#define WM8961_RO1ZC_SHIFT 7 /* RO1ZC */
|
||||
#define WM8961_RO1ZC_WIDTH 1 /* RO1ZC */
|
||||
#define WM8961_ROUT1VOL_MASK 0x007F /* ROUT1VOL - [6:0] */
|
||||
#define WM8961_ROUT1VOL_SHIFT 0 /* ROUT1VOL - [6:0] */
|
||||
#define WM8961_ROUT1VOL_WIDTH 7 /* ROUT1VOL - [6:0] */
|
||||
|
||||
/*
|
||||
* R4 (0x04) - Clocking1
|
||||
*/
|
||||
#define WM8961_ADCDIV_MASK 0x01C0 /* ADCDIV - [8:6] */
|
||||
#define WM8961_ADCDIV_SHIFT 6 /* ADCDIV - [8:6] */
|
||||
#define WM8961_ADCDIV_WIDTH 3 /* ADCDIV - [8:6] */
|
||||
#define WM8961_DACDIV_MASK 0x0038 /* DACDIV - [5:3] */
|
||||
#define WM8961_DACDIV_SHIFT 3 /* DACDIV - [5:3] */
|
||||
#define WM8961_DACDIV_WIDTH 3 /* DACDIV - [5:3] */
|
||||
#define WM8961_MCLKDIV 0x0004 /* MCLKDIV */
|
||||
#define WM8961_MCLKDIV_MASK 0x0004 /* MCLKDIV */
|
||||
#define WM8961_MCLKDIV_SHIFT 2 /* MCLKDIV */
|
||||
#define WM8961_MCLKDIV_WIDTH 1 /* MCLKDIV */
|
||||
|
||||
/*
|
||||
* R5 (0x05) - ADC & DAC Control 1
|
||||
*/
|
||||
#define WM8961_ADCPOL_MASK 0x0060 /* ADCPOL - [6:5] */
|
||||
#define WM8961_ADCPOL_SHIFT 5 /* ADCPOL - [6:5] */
|
||||
#define WM8961_ADCPOL_WIDTH 2 /* ADCPOL - [6:5] */
|
||||
#define WM8961_DACMU 0x0008 /* DACMU */
|
||||
#define WM8961_DACMU_MASK 0x0008 /* DACMU */
|
||||
#define WM8961_DACMU_SHIFT 3 /* DACMU */
|
||||
#define WM8961_DACMU_WIDTH 1 /* DACMU */
|
||||
#define WM8961_DEEMPH_MASK 0x0006 /* DEEMPH - [2:1] */
|
||||
#define WM8961_DEEMPH_SHIFT 1 /* DEEMPH - [2:1] */
|
||||
#define WM8961_DEEMPH_WIDTH 2 /* DEEMPH - [2:1] */
|
||||
#define WM8961_ADCHPD 0x0001 /* ADCHPD */
|
||||
#define WM8961_ADCHPD_MASK 0x0001 /* ADCHPD */
|
||||
#define WM8961_ADCHPD_SHIFT 0 /* ADCHPD */
|
||||
#define WM8961_ADCHPD_WIDTH 1 /* ADCHPD */
|
||||
|
||||
/*
|
||||
* R6 (0x06) - ADC & DAC Control 2
|
||||
*/
|
||||
#define WM8961_ADC_HPF_CUT_MASK 0x0180 /* ADC_HPF_CUT - [8:7] */
|
||||
#define WM8961_ADC_HPF_CUT_SHIFT 7 /* ADC_HPF_CUT - [8:7] */
|
||||
#define WM8961_ADC_HPF_CUT_WIDTH 2 /* ADC_HPF_CUT - [8:7] */
|
||||
#define WM8961_DACPOL_MASK 0x0060 /* DACPOL - [6:5] */
|
||||
#define WM8961_DACPOL_SHIFT 5 /* DACPOL - [6:5] */
|
||||
#define WM8961_DACPOL_WIDTH 2 /* DACPOL - [6:5] */
|
||||
#define WM8961_DACSMM 0x0008 /* DACSMM */
|
||||
#define WM8961_DACSMM_MASK 0x0008 /* DACSMM */
|
||||
#define WM8961_DACSMM_SHIFT 3 /* DACSMM */
|
||||
#define WM8961_DACSMM_WIDTH 1 /* DACSMM */
|
||||
#define WM8961_DACMR 0x0004 /* DACMR */
|
||||
#define WM8961_DACMR_MASK 0x0004 /* DACMR */
|
||||
#define WM8961_DACMR_SHIFT 2 /* DACMR */
|
||||
#define WM8961_DACMR_WIDTH 1 /* DACMR */
|
||||
#define WM8961_DACSLOPE 0x0002 /* DACSLOPE */
|
||||
#define WM8961_DACSLOPE_MASK 0x0002 /* DACSLOPE */
|
||||
#define WM8961_DACSLOPE_SHIFT 1 /* DACSLOPE */
|
||||
#define WM8961_DACSLOPE_WIDTH 1 /* DACSLOPE */
|
||||
#define WM8961_DAC_OSR128 0x0001 /* DAC_OSR128 */
|
||||
#define WM8961_DAC_OSR128_MASK 0x0001 /* DAC_OSR128 */
|
||||
#define WM8961_DAC_OSR128_SHIFT 0 /* DAC_OSR128 */
|
||||
#define WM8961_DAC_OSR128_WIDTH 1 /* DAC_OSR128 */
|
||||
|
||||
/*
|
||||
* R7 (0x07) - Audio Interface 0
|
||||
*/
|
||||
#define WM8961_ALRSWAP 0x0100 /* ALRSWAP */
|
||||
#define WM8961_ALRSWAP_MASK 0x0100 /* ALRSWAP */
|
||||
#define WM8961_ALRSWAP_SHIFT 8 /* ALRSWAP */
|
||||
#define WM8961_ALRSWAP_WIDTH 1 /* ALRSWAP */
|
||||
#define WM8961_BCLKINV 0x0080 /* BCLKINV */
|
||||
#define WM8961_BCLKINV_MASK 0x0080 /* BCLKINV */
|
||||
#define WM8961_BCLKINV_SHIFT 7 /* BCLKINV */
|
||||
#define WM8961_BCLKINV_WIDTH 1 /* BCLKINV */
|
||||
#define WM8961_MS 0x0040 /* MS */
|
||||
#define WM8961_MS_MASK 0x0040 /* MS */
|
||||
#define WM8961_MS_SHIFT 6 /* MS */
|
||||
#define WM8961_MS_WIDTH 1 /* MS */
|
||||
#define WM8961_DLRSWAP 0x0020 /* DLRSWAP */
|
||||
#define WM8961_DLRSWAP_MASK 0x0020 /* DLRSWAP */
|
||||
#define WM8961_DLRSWAP_SHIFT 5 /* DLRSWAP */
|
||||
#define WM8961_DLRSWAP_WIDTH 1 /* DLRSWAP */
|
||||
#define WM8961_LRP 0x0010 /* LRP */
|
||||
#define WM8961_LRP_MASK 0x0010 /* LRP */
|
||||
#define WM8961_LRP_SHIFT 4 /* LRP */
|
||||
#define WM8961_LRP_WIDTH 1 /* LRP */
|
||||
#define WM8961_WL_MASK 0x000C /* WL - [3:2] */
|
||||
#define WM8961_WL_SHIFT 2 /* WL - [3:2] */
|
||||
#define WM8961_WL_WIDTH 2 /* WL - [3:2] */
|
||||
#define WM8961_FORMAT_MASK 0x0003 /* FORMAT - [1:0] */
|
||||
#define WM8961_FORMAT_SHIFT 0 /* FORMAT - [1:0] */
|
||||
#define WM8961_FORMAT_WIDTH 2 /* FORMAT - [1:0] */
|
||||
|
||||
/*
|
||||
* R8 (0x08) - Clocking2
|
||||
*/
|
||||
#define WM8961_DCLKDIV_MASK 0x01C0 /* DCLKDIV - [8:6] */
|
||||
#define WM8961_DCLKDIV_SHIFT 6 /* DCLKDIV - [8:6] */
|
||||
#define WM8961_DCLKDIV_WIDTH 3 /* DCLKDIV - [8:6] */
|
||||
#define WM8961_CLK_SYS_ENA 0x0020 /* CLK_SYS_ENA */
|
||||
#define WM8961_CLK_SYS_ENA_MASK 0x0020 /* CLK_SYS_ENA */
|
||||
#define WM8961_CLK_SYS_ENA_SHIFT 5 /* CLK_SYS_ENA */
|
||||
#define WM8961_CLK_SYS_ENA_WIDTH 1 /* CLK_SYS_ENA */
|
||||
#define WM8961_CLK_DSP_ENA 0x0010 /* CLK_DSP_ENA */
|
||||
#define WM8961_CLK_DSP_ENA_MASK 0x0010 /* CLK_DSP_ENA */
|
||||
#define WM8961_CLK_DSP_ENA_SHIFT 4 /* CLK_DSP_ENA */
|
||||
#define WM8961_CLK_DSP_ENA_WIDTH 1 /* CLK_DSP_ENA */
|
||||
#define WM8961_BCLKDIV_MASK 0x000F /* BCLKDIV - [3:0] */
|
||||
#define WM8961_BCLKDIV_SHIFT 0 /* BCLKDIV - [3:0] */
|
||||
#define WM8961_BCLKDIV_WIDTH 4 /* BCLKDIV - [3:0] */
|
||||
|
||||
/*
|
||||
* R9 (0x09) - Audio Interface 1
|
||||
*/
|
||||
#define WM8961_DACCOMP_MASK 0x0018 /* DACCOMP - [4:3] */
|
||||
#define WM8961_DACCOMP_SHIFT 3 /* DACCOMP - [4:3] */
|
||||
#define WM8961_DACCOMP_WIDTH 2 /* DACCOMP - [4:3] */
|
||||
#define WM8961_ADCCOMP_MASK 0x0006 /* ADCCOMP - [2:1] */
|
||||
#define WM8961_ADCCOMP_SHIFT 1 /* ADCCOMP - [2:1] */
|
||||
#define WM8961_ADCCOMP_WIDTH 2 /* ADCCOMP - [2:1] */
|
||||
#define WM8961_LOOPBACK 0x0001 /* LOOPBACK */
|
||||
#define WM8961_LOOPBACK_MASK 0x0001 /* LOOPBACK */
|
||||
#define WM8961_LOOPBACK_SHIFT 0 /* LOOPBACK */
|
||||
#define WM8961_LOOPBACK_WIDTH 1 /* LOOPBACK */
|
||||
|
||||
/*
|
||||
* R10 (0x0A) - Left DAC volume
|
||||
*/
|
||||
#define WM8961_DACVU 0x0100 /* DACVU */
|
||||
#define WM8961_DACVU_MASK 0x0100 /* DACVU */
|
||||
#define WM8961_DACVU_SHIFT 8 /* DACVU */
|
||||
#define WM8961_DACVU_WIDTH 1 /* DACVU */
|
||||
#define WM8961_LDACVOL_MASK 0x00FF /* LDACVOL - [7:0] */
|
||||
#define WM8961_LDACVOL_SHIFT 0 /* LDACVOL - [7:0] */
|
||||
#define WM8961_LDACVOL_WIDTH 8 /* LDACVOL - [7:0] */
|
||||
|
||||
/*
|
||||
* R11 (0x0B) - Right DAC volume
|
||||
*/
|
||||
#define WM8961_DACVU 0x0100 /* DACVU */
|
||||
#define WM8961_DACVU_MASK 0x0100 /* DACVU */
|
||||
#define WM8961_DACVU_SHIFT 8 /* DACVU */
|
||||
#define WM8961_DACVU_WIDTH 1 /* DACVU */
|
||||
#define WM8961_RDACVOL_MASK 0x00FF /* RDACVOL - [7:0] */
|
||||
#define WM8961_RDACVOL_SHIFT 0 /* RDACVOL - [7:0] */
|
||||
#define WM8961_RDACVOL_WIDTH 8 /* RDACVOL - [7:0] */
|
||||
|
||||
/*
|
||||
* R14 (0x0E) - Audio Interface 2
|
||||
*/
|
||||
#define WM8961_LRCLK_RATE_MASK 0x01FF /* LRCLK_RATE - [8:0] */
|
||||
#define WM8961_LRCLK_RATE_SHIFT 0 /* LRCLK_RATE - [8:0] */
|
||||
#define WM8961_LRCLK_RATE_WIDTH 9 /* LRCLK_RATE - [8:0] */
|
||||
|
||||
/*
|
||||
* R15 (0x0F) - Software Reset
|
||||
*/
|
||||
#define WM8961_SW_RST_DEV_ID1_MASK 0xFFFF /* SW_RST_DEV_ID1 - [15:0] */
|
||||
#define WM8961_SW_RST_DEV_ID1_SHIFT 0 /* SW_RST_DEV_ID1 - [15:0] */
|
||||
#define WM8961_SW_RST_DEV_ID1_WIDTH 16 /* SW_RST_DEV_ID1 - [15:0] */
|
||||
|
||||
/*
|
||||
* R17 (0x11) - ALC1
|
||||
*/
|
||||
#define WM8961_ALCSEL_MASK 0x0180 /* ALCSEL - [8:7] */
|
||||
#define WM8961_ALCSEL_SHIFT 7 /* ALCSEL - [8:7] */
|
||||
#define WM8961_ALCSEL_WIDTH 2 /* ALCSEL - [8:7] */
|
||||
#define WM8961_MAXGAIN_MASK 0x0070 /* MAXGAIN - [6:4] */
|
||||
#define WM8961_MAXGAIN_SHIFT 4 /* MAXGAIN - [6:4] */
|
||||
#define WM8961_MAXGAIN_WIDTH 3 /* MAXGAIN - [6:4] */
|
||||
#define WM8961_ALCL_MASK 0x000F /* ALCL - [3:0] */
|
||||
#define WM8961_ALCL_SHIFT 0 /* ALCL - [3:0] */
|
||||
#define WM8961_ALCL_WIDTH 4 /* ALCL - [3:0] */
|
||||
|
||||
/*
|
||||
* R18 (0x12) - ALC2
|
||||
*/
|
||||
#define WM8961_ALCZC 0x0080 /* ALCZC */
|
||||
#define WM8961_ALCZC_MASK 0x0080 /* ALCZC */
|
||||
#define WM8961_ALCZC_SHIFT 7 /* ALCZC */
|
||||
#define WM8961_ALCZC_WIDTH 1 /* ALCZC */
|
||||
#define WM8961_MINGAIN_MASK 0x0070 /* MINGAIN - [6:4] */
|
||||
#define WM8961_MINGAIN_SHIFT 4 /* MINGAIN - [6:4] */
|
||||
#define WM8961_MINGAIN_WIDTH 3 /* MINGAIN - [6:4] */
|
||||
#define WM8961_HLD_MASK 0x000F /* HLD - [3:0] */
|
||||
#define WM8961_HLD_SHIFT 0 /* HLD - [3:0] */
|
||||
#define WM8961_HLD_WIDTH 4 /* HLD - [3:0] */
|
||||
|
||||
/*
|
||||
* R19 (0x13) - ALC3
|
||||
*/
|
||||
#define WM8961_ALCMODE 0x0100 /* ALCMODE */
|
||||
#define WM8961_ALCMODE_MASK 0x0100 /* ALCMODE */
|
||||
#define WM8961_ALCMODE_SHIFT 8 /* ALCMODE */
|
||||
#define WM8961_ALCMODE_WIDTH 1 /* ALCMODE */
|
||||
#define WM8961_DCY_MASK 0x00F0 /* DCY - [7:4] */
|
||||
#define WM8961_DCY_SHIFT 4 /* DCY - [7:4] */
|
||||
#define WM8961_DCY_WIDTH 4 /* DCY - [7:4] */
|
||||
#define WM8961_ATK_MASK 0x000F /* ATK - [3:0] */
|
||||
#define WM8961_ATK_SHIFT 0 /* ATK - [3:0] */
|
||||
#define WM8961_ATK_WIDTH 4 /* ATK - [3:0] */
|
||||
|
||||
/*
|
||||
* R20 (0x14) - Noise Gate
|
||||
*/
|
||||
#define WM8961_NGTH_MASK 0x00F8 /* NGTH - [7:3] */
|
||||
#define WM8961_NGTH_SHIFT 3 /* NGTH - [7:3] */
|
||||
#define WM8961_NGTH_WIDTH 5 /* NGTH - [7:3] */
|
||||
#define WM8961_NGG 0x0002 /* NGG */
|
||||
#define WM8961_NGG_MASK 0x0002 /* NGG */
|
||||
#define WM8961_NGG_SHIFT 1 /* NGG */
|
||||
#define WM8961_NGG_WIDTH 1 /* NGG */
|
||||
#define WM8961_NGAT 0x0001 /* NGAT */
|
||||
#define WM8961_NGAT_MASK 0x0001 /* NGAT */
|
||||
#define WM8961_NGAT_SHIFT 0 /* NGAT */
|
||||
#define WM8961_NGAT_WIDTH 1 /* NGAT */
|
||||
|
||||
/*
|
||||
* R21 (0x15) - Left ADC volume
|
||||
*/
|
||||
#define WM8961_ADCVU 0x0100 /* ADCVU */
|
||||
#define WM8961_ADCVU_MASK 0x0100 /* ADCVU */
|
||||
#define WM8961_ADCVU_SHIFT 8 /* ADCVU */
|
||||
#define WM8961_ADCVU_WIDTH 1 /* ADCVU */
|
||||
#define WM8961_LADCVOL_MASK 0x00FF /* LADCVOL - [7:0] */
|
||||
#define WM8961_LADCVOL_SHIFT 0 /* LADCVOL - [7:0] */
|
||||
#define WM8961_LADCVOL_WIDTH 8 /* LADCVOL - [7:0] */
|
||||
|
||||
/*
|
||||
* R22 (0x16) - Right ADC volume
|
||||
*/
|
||||
#define WM8961_ADCVU 0x0100 /* ADCVU */
|
||||
#define WM8961_ADCVU_MASK 0x0100 /* ADCVU */
|
||||
#define WM8961_ADCVU_SHIFT 8 /* ADCVU */
|
||||
#define WM8961_ADCVU_WIDTH 1 /* ADCVU */
|
||||
#define WM8961_RADCVOL_MASK 0x00FF /* RADCVOL - [7:0] */
|
||||
#define WM8961_RADCVOL_SHIFT 0 /* RADCVOL - [7:0] */
|
||||
#define WM8961_RADCVOL_WIDTH 8 /* RADCVOL - [7:0] */
|
||||
|
||||
/*
|
||||
* R23 (0x17) - Additional control(1)
|
||||
*/
|
||||
#define WM8961_TSDEN 0x0100 /* TSDEN */
|
||||
#define WM8961_TSDEN_MASK 0x0100 /* TSDEN */
|
||||
#define WM8961_TSDEN_SHIFT 8 /* TSDEN */
|
||||
#define WM8961_TSDEN_WIDTH 1 /* TSDEN */
|
||||
#define WM8961_DMONOMIX 0x0010 /* DMONOMIX */
|
||||
#define WM8961_DMONOMIX_MASK 0x0010 /* DMONOMIX */
|
||||
#define WM8961_DMONOMIX_SHIFT 4 /* DMONOMIX */
|
||||
#define WM8961_DMONOMIX_WIDTH 1 /* DMONOMIX */
|
||||
#define WM8961_TOEN 0x0001 /* TOEN */
|
||||
#define WM8961_TOEN_MASK 0x0001 /* TOEN */
|
||||
#define WM8961_TOEN_SHIFT 0 /* TOEN */
|
||||
#define WM8961_TOEN_WIDTH 1 /* TOEN */
|
||||
|
||||
/*
|
||||
* R24 (0x18) - Additional control(2)
|
||||
*/
|
||||
#define WM8961_TRIS 0x0008 /* TRIS */
|
||||
#define WM8961_TRIS_MASK 0x0008 /* TRIS */
|
||||
#define WM8961_TRIS_SHIFT 3 /* TRIS */
|
||||
#define WM8961_TRIS_WIDTH 1 /* TRIS */
|
||||
|
||||
/*
|
||||
* R25 (0x19) - Pwr Mgmt (1)
|
||||
*/
|
||||
#define WM8961_VMIDSEL_MASK 0x0180 /* VMIDSEL - [8:7] */
|
||||
#define WM8961_VMIDSEL_SHIFT 7 /* VMIDSEL - [8:7] */
|
||||
#define WM8961_VMIDSEL_WIDTH 2 /* VMIDSEL - [8:7] */
|
||||
#define WM8961_VREF 0x0040 /* VREF */
|
||||
#define WM8961_VREF_MASK 0x0040 /* VREF */
|
||||
#define WM8961_VREF_SHIFT 6 /* VREF */
|
||||
#define WM8961_VREF_WIDTH 1 /* VREF */
|
||||
#define WM8961_AINL 0x0020 /* AINL */
|
||||
#define WM8961_AINL_MASK 0x0020 /* AINL */
|
||||
#define WM8961_AINL_SHIFT 5 /* AINL */
|
||||
#define WM8961_AINL_WIDTH 1 /* AINL */
|
||||
#define WM8961_AINR 0x0010 /* AINR */
|
||||
#define WM8961_AINR_MASK 0x0010 /* AINR */
|
||||
#define WM8961_AINR_SHIFT 4 /* AINR */
|
||||
#define WM8961_AINR_WIDTH 1 /* AINR */
|
||||
#define WM8961_ADCL 0x0008 /* ADCL */
|
||||
#define WM8961_ADCL_MASK 0x0008 /* ADCL */
|
||||
#define WM8961_ADCL_SHIFT 3 /* ADCL */
|
||||
#define WM8961_ADCL_WIDTH 1 /* ADCL */
|
||||
#define WM8961_ADCR 0x0004 /* ADCR */
|
||||
#define WM8961_ADCR_MASK 0x0004 /* ADCR */
|
||||
#define WM8961_ADCR_SHIFT 2 /* ADCR */
|
||||
#define WM8961_ADCR_WIDTH 1 /* ADCR */
|
||||
#define WM8961_MICB 0x0002 /* MICB */
|
||||
#define WM8961_MICB_MASK 0x0002 /* MICB */
|
||||
#define WM8961_MICB_SHIFT 1 /* MICB */
|
||||
#define WM8961_MICB_WIDTH 1 /* MICB */
|
||||
|
||||
/*
|
||||
* R26 (0x1A) - Pwr Mgmt (2)
|
||||
*/
|
||||
#define WM8961_DACL 0x0100 /* DACL */
|
||||
#define WM8961_DACL_MASK 0x0100 /* DACL */
|
||||
#define WM8961_DACL_SHIFT 8 /* DACL */
|
||||
#define WM8961_DACL_WIDTH 1 /* DACL */
|
||||
#define WM8961_DACR 0x0080 /* DACR */
|
||||
#define WM8961_DACR_MASK 0x0080 /* DACR */
|
||||
#define WM8961_DACR_SHIFT 7 /* DACR */
|
||||
#define WM8961_DACR_WIDTH 1 /* DACR */
|
||||
#define WM8961_LOUT1_PGA 0x0040 /* LOUT1_PGA */
|
||||
#define WM8961_LOUT1_PGA_MASK 0x0040 /* LOUT1_PGA */
|
||||
#define WM8961_LOUT1_PGA_SHIFT 6 /* LOUT1_PGA */
|
||||
#define WM8961_LOUT1_PGA_WIDTH 1 /* LOUT1_PGA */
|
||||
#define WM8961_ROUT1_PGA 0x0020 /* ROUT1_PGA */
|
||||
#define WM8961_ROUT1_PGA_MASK 0x0020 /* ROUT1_PGA */
|
||||
#define WM8961_ROUT1_PGA_SHIFT 5 /* ROUT1_PGA */
|
||||
#define WM8961_ROUT1_PGA_WIDTH 1 /* ROUT1_PGA */
|
||||
#define WM8961_SPKL_PGA 0x0010 /* SPKL_PGA */
|
||||
#define WM8961_SPKL_PGA_MASK 0x0010 /* SPKL_PGA */
|
||||
#define WM8961_SPKL_PGA_SHIFT 4 /* SPKL_PGA */
|
||||
#define WM8961_SPKL_PGA_WIDTH 1 /* SPKL_PGA */
|
||||
#define WM8961_SPKR_PGA 0x0008 /* SPKR_PGA */
|
||||
#define WM8961_SPKR_PGA_MASK 0x0008 /* SPKR_PGA */
|
||||
#define WM8961_SPKR_PGA_SHIFT 3 /* SPKR_PGA */
|
||||
#define WM8961_SPKR_PGA_WIDTH 1 /* SPKR_PGA */
|
||||
|
||||
/*
|
||||
* R27 (0x1B) - Additional Control (3)
|
||||
*/
|
||||
#define WM8961_SAMPLE_RATE_MASK 0x0007 /* SAMPLE_RATE - [2:0] */
|
||||
#define WM8961_SAMPLE_RATE_SHIFT 0 /* SAMPLE_RATE - [2:0] */
|
||||
#define WM8961_SAMPLE_RATE_WIDTH 3 /* SAMPLE_RATE - [2:0] */
|
||||
|
||||
/*
|
||||
* R28 (0x1C) - Anti-pop
|
||||
*/
|
||||
#define WM8961_BUFDCOPEN 0x0010 /* BUFDCOPEN */
|
||||
#define WM8961_BUFDCOPEN_MASK 0x0010 /* BUFDCOPEN */
|
||||
#define WM8961_BUFDCOPEN_SHIFT 4 /* BUFDCOPEN */
|
||||
#define WM8961_BUFDCOPEN_WIDTH 1 /* BUFDCOPEN */
|
||||
#define WM8961_BUFIOEN 0x0008 /* BUFIOEN */
|
||||
#define WM8961_BUFIOEN_MASK 0x0008 /* BUFIOEN */
|
||||
#define WM8961_BUFIOEN_SHIFT 3 /* BUFIOEN */
|
||||
#define WM8961_BUFIOEN_WIDTH 1 /* BUFIOEN */
|
||||
#define WM8961_SOFT_ST 0x0004 /* SOFT_ST */
|
||||
#define WM8961_SOFT_ST_MASK 0x0004 /* SOFT_ST */
|
||||
#define WM8961_SOFT_ST_SHIFT 2 /* SOFT_ST */
|
||||
#define WM8961_SOFT_ST_WIDTH 1 /* SOFT_ST */
|
||||
|
||||
/*
|
||||
* R30 (0x1E) - Clocking 3
|
||||
*/
|
||||
#define WM8961_CLK_TO_DIV_MASK 0x0180 /* CLK_TO_DIV - [8:7] */
|
||||
#define WM8961_CLK_TO_DIV_SHIFT 7 /* CLK_TO_DIV - [8:7] */
|
||||
#define WM8961_CLK_TO_DIV_WIDTH 2 /* CLK_TO_DIV - [8:7] */
|
||||
#define WM8961_CLK_256K_DIV_MASK 0x007E /* CLK_256K_DIV - [6:1] */
|
||||
#define WM8961_CLK_256K_DIV_SHIFT 1 /* CLK_256K_DIV - [6:1] */
|
||||
#define WM8961_CLK_256K_DIV_WIDTH 6 /* CLK_256K_DIV - [6:1] */
|
||||
#define WM8961_MANUAL_MODE 0x0001 /* MANUAL_MODE */
|
||||
#define WM8961_MANUAL_MODE_MASK 0x0001 /* MANUAL_MODE */
|
||||
#define WM8961_MANUAL_MODE_SHIFT 0 /* MANUAL_MODE */
|
||||
#define WM8961_MANUAL_MODE_WIDTH 1 /* MANUAL_MODE */
|
||||
|
||||
/*
|
||||
* R32 (0x20) - ADCL signal path
|
||||
*/
|
||||
#define WM8961_LMICBOOST_MASK 0x0030 /* LMICBOOST - [5:4] */
|
||||
#define WM8961_LMICBOOST_SHIFT 4 /* LMICBOOST - [5:4] */
|
||||
#define WM8961_LMICBOOST_WIDTH 2 /* LMICBOOST - [5:4] */
|
||||
|
||||
/*
|
||||
* R33 (0x21) - ADCR signal path
|
||||
*/
|
||||
#define WM8961_RMICBOOST_MASK 0x0030 /* RMICBOOST - [5:4] */
|
||||
#define WM8961_RMICBOOST_SHIFT 4 /* RMICBOOST - [5:4] */
|
||||
#define WM8961_RMICBOOST_WIDTH 2 /* RMICBOOST - [5:4] */
|
||||
|
||||
/*
|
||||
* R40 (0x28) - LOUT2 volume
|
||||
*/
|
||||
#define WM8961_SPKVU 0x0100 /* SPKVU */
|
||||
#define WM8961_SPKVU_MASK 0x0100 /* SPKVU */
|
||||
#define WM8961_SPKVU_SHIFT 8 /* SPKVU */
|
||||
#define WM8961_SPKVU_WIDTH 1 /* SPKVU */
|
||||
#define WM8961_SPKLZC 0x0080 /* SPKLZC */
|
||||
#define WM8961_SPKLZC_MASK 0x0080 /* SPKLZC */
|
||||
#define WM8961_SPKLZC_SHIFT 7 /* SPKLZC */
|
||||
#define WM8961_SPKLZC_WIDTH 1 /* SPKLZC */
|
||||
#define WM8961_SPKLVOL_MASK 0x007F /* SPKLVOL - [6:0] */
|
||||
#define WM8961_SPKLVOL_SHIFT 0 /* SPKLVOL - [6:0] */
|
||||
#define WM8961_SPKLVOL_WIDTH 7 /* SPKLVOL - [6:0] */
|
||||
|
||||
/*
|
||||
* R41 (0x29) - ROUT2 volume
|
||||
*/
|
||||
#define WM8961_SPKVU 0x0100 /* SPKVU */
|
||||
#define WM8961_SPKVU_MASK 0x0100 /* SPKVU */
|
||||
#define WM8961_SPKVU_SHIFT 8 /* SPKVU */
|
||||
#define WM8961_SPKVU_WIDTH 1 /* SPKVU */
|
||||
#define WM8961_SPKRZC 0x0080 /* SPKRZC */
|
||||
#define WM8961_SPKRZC_MASK 0x0080 /* SPKRZC */
|
||||
#define WM8961_SPKRZC_SHIFT 7 /* SPKRZC */
|
||||
#define WM8961_SPKRZC_WIDTH 1 /* SPKRZC */
|
||||
#define WM8961_SPKRVOL_MASK 0x007F /* SPKRVOL - [6:0] */
|
||||
#define WM8961_SPKRVOL_SHIFT 0 /* SPKRVOL - [6:0] */
|
||||
#define WM8961_SPKRVOL_WIDTH 7 /* SPKRVOL - [6:0] */
|
||||
|
||||
/*
|
||||
* R47 (0x2F) - Pwr Mgmt (3)
|
||||
*/
|
||||
#define WM8961_TEMP_SHUT 0x0002 /* TEMP_SHUT */
|
||||
#define WM8961_TEMP_SHUT_MASK 0x0002 /* TEMP_SHUT */
|
||||
#define WM8961_TEMP_SHUT_SHIFT 1 /* TEMP_SHUT */
|
||||
#define WM8961_TEMP_SHUT_WIDTH 1 /* TEMP_SHUT */
|
||||
#define WM8961_TEMP_WARN 0x0001 /* TEMP_WARN */
|
||||
#define WM8961_TEMP_WARN_MASK 0x0001 /* TEMP_WARN */
|
||||
#define WM8961_TEMP_WARN_SHIFT 0 /* TEMP_WARN */
|
||||
#define WM8961_TEMP_WARN_WIDTH 1 /* TEMP_WARN */
|
||||
|
||||
/*
|
||||
* R48 (0x30) - Additional Control (4)
|
||||
*/
|
||||
#define WM8961_TSENSEN 0x0002 /* TSENSEN */
|
||||
#define WM8961_TSENSEN_MASK 0x0002 /* TSENSEN */
|
||||
#define WM8961_TSENSEN_SHIFT 1 /* TSENSEN */
|
||||
#define WM8961_TSENSEN_WIDTH 1 /* TSENSEN */
|
||||
#define WM8961_MBSEL 0x0001 /* MBSEL */
|
||||
#define WM8961_MBSEL_MASK 0x0001 /* MBSEL */
|
||||
#define WM8961_MBSEL_SHIFT 0 /* MBSEL */
|
||||
#define WM8961_MBSEL_WIDTH 1 /* MBSEL */
|
||||
|
||||
/*
|
||||
* R49 (0x31) - Class D Control 1
|
||||
*/
|
||||
#define WM8961_SPKR_ENA 0x0080 /* SPKR_ENA */
|
||||
#define WM8961_SPKR_ENA_MASK 0x0080 /* SPKR_ENA */
|
||||
#define WM8961_SPKR_ENA_SHIFT 7 /* SPKR_ENA */
|
||||
#define WM8961_SPKR_ENA_WIDTH 1 /* SPKR_ENA */
|
||||
#define WM8961_SPKL_ENA 0x0040 /* SPKL_ENA */
|
||||
#define WM8961_SPKL_ENA_MASK 0x0040 /* SPKL_ENA */
|
||||
#define WM8961_SPKL_ENA_SHIFT 6 /* SPKL_ENA */
|
||||
#define WM8961_SPKL_ENA_WIDTH 1 /* SPKL_ENA */
|
||||
|
||||
/*
|
||||
* R51 (0x33) - Class D Control 2
|
||||
*/
|
||||
#define WM8961_CLASSD_ACGAIN_MASK 0x0007 /* CLASSD_ACGAIN - [2:0] */
|
||||
#define WM8961_CLASSD_ACGAIN_SHIFT 0 /* CLASSD_ACGAIN - [2:0] */
|
||||
#define WM8961_CLASSD_ACGAIN_WIDTH 3 /* CLASSD_ACGAIN - [2:0] */
|
||||
|
||||
/*
|
||||
* R56 (0x38) - Clocking 4
|
||||
*/
|
||||
#define WM8961_CLK_DCS_DIV_MASK 0x01E0 /* CLK_DCS_DIV - [8:5] */
|
||||
#define WM8961_CLK_DCS_DIV_SHIFT 5 /* CLK_DCS_DIV - [8:5] */
|
||||
#define WM8961_CLK_DCS_DIV_WIDTH 4 /* CLK_DCS_DIV - [8:5] */
|
||||
#define WM8961_CLK_SYS_RATE_MASK 0x001E /* CLK_SYS_RATE - [4:1] */
|
||||
#define WM8961_CLK_SYS_RATE_SHIFT 1 /* CLK_SYS_RATE - [4:1] */
|
||||
#define WM8961_CLK_SYS_RATE_WIDTH 4 /* CLK_SYS_RATE - [4:1] */
|
||||
|
||||
/*
|
||||
* R57 (0x39) - DSP Sidetone 0
|
||||
*/
|
||||
#define WM8961_ADCR_DAC_SVOL_MASK 0x00F0 /* ADCR_DAC_SVOL - [7:4] */
|
||||
#define WM8961_ADCR_DAC_SVOL_SHIFT 4 /* ADCR_DAC_SVOL - [7:4] */
|
||||
#define WM8961_ADCR_DAC_SVOL_WIDTH 4 /* ADCR_DAC_SVOL - [7:4] */
|
||||
#define WM8961_ADC_TO_DACR_MASK 0x000C /* ADC_TO_DACR - [3:2] */
|
||||
#define WM8961_ADC_TO_DACR_SHIFT 2 /* ADC_TO_DACR - [3:2] */
|
||||
#define WM8961_ADC_TO_DACR_WIDTH 2 /* ADC_TO_DACR - [3:2] */
|
||||
|
||||
/*
|
||||
* R58 (0x3A) - DSP Sidetone 1
|
||||
*/
|
||||
#define WM8961_ADCL_DAC_SVOL_MASK 0x00F0 /* ADCL_DAC_SVOL - [7:4] */
|
||||
#define WM8961_ADCL_DAC_SVOL_SHIFT 4 /* ADCL_DAC_SVOL - [7:4] */
|
||||
#define WM8961_ADCL_DAC_SVOL_WIDTH 4 /* ADCL_DAC_SVOL - [7:4] */
|
||||
#define WM8961_ADC_TO_DACL_MASK 0x000C /* ADC_TO_DACL - [3:2] */
|
||||
#define WM8961_ADC_TO_DACL_SHIFT 2 /* ADC_TO_DACL - [3:2] */
|
||||
#define WM8961_ADC_TO_DACL_WIDTH 2 /* ADC_TO_DACL - [3:2] */
|
||||
|
||||
/*
|
||||
* R60 (0x3C) - DC Servo 0
|
||||
*/
|
||||
#define WM8961_DCS_ENA_CHAN_INL 0x0080 /* DCS_ENA_CHAN_INL */
|
||||
#define WM8961_DCS_ENA_CHAN_INL_MASK 0x0080 /* DCS_ENA_CHAN_INL */
|
||||
#define WM8961_DCS_ENA_CHAN_INL_SHIFT 7 /* DCS_ENA_CHAN_INL */
|
||||
#define WM8961_DCS_ENA_CHAN_INL_WIDTH 1 /* DCS_ENA_CHAN_INL */
|
||||
#define WM8961_DCS_TRIG_STARTUP_INL 0x0040 /* DCS_TRIG_STARTUP_INL */
|
||||
#define WM8961_DCS_TRIG_STARTUP_INL_MASK 0x0040 /* DCS_TRIG_STARTUP_INL */
|
||||
#define WM8961_DCS_TRIG_STARTUP_INL_SHIFT 6 /* DCS_TRIG_STARTUP_INL */
|
||||
#define WM8961_DCS_TRIG_STARTUP_INL_WIDTH 1 /* DCS_TRIG_STARTUP_INL */
|
||||
#define WM8961_DCS_TRIG_SERIES_INL 0x0010 /* DCS_TRIG_SERIES_INL */
|
||||
#define WM8961_DCS_TRIG_SERIES_INL_MASK 0x0010 /* DCS_TRIG_SERIES_INL */
|
||||
#define WM8961_DCS_TRIG_SERIES_INL_SHIFT 4 /* DCS_TRIG_SERIES_INL */
|
||||
#define WM8961_DCS_TRIG_SERIES_INL_WIDTH 1 /* DCS_TRIG_SERIES_INL */
|
||||
#define WM8961_DCS_ENA_CHAN_INR 0x0008 /* DCS_ENA_CHAN_INR */
|
||||
#define WM8961_DCS_ENA_CHAN_INR_MASK 0x0008 /* DCS_ENA_CHAN_INR */
|
||||
#define WM8961_DCS_ENA_CHAN_INR_SHIFT 3 /* DCS_ENA_CHAN_INR */
|
||||
#define WM8961_DCS_ENA_CHAN_INR_WIDTH 1 /* DCS_ENA_CHAN_INR */
|
||||
#define WM8961_DCS_TRIG_STARTUP_INR 0x0004 /* DCS_TRIG_STARTUP_INR */
|
||||
#define WM8961_DCS_TRIG_STARTUP_INR_MASK 0x0004 /* DCS_TRIG_STARTUP_INR */
|
||||
#define WM8961_DCS_TRIG_STARTUP_INR_SHIFT 2 /* DCS_TRIG_STARTUP_INR */
|
||||
#define WM8961_DCS_TRIG_STARTUP_INR_WIDTH 1 /* DCS_TRIG_STARTUP_INR */
|
||||
#define WM8961_DCS_TRIG_SERIES_INR 0x0001 /* DCS_TRIG_SERIES_INR */
|
||||
#define WM8961_DCS_TRIG_SERIES_INR_MASK 0x0001 /* DCS_TRIG_SERIES_INR */
|
||||
#define WM8961_DCS_TRIG_SERIES_INR_SHIFT 0 /* DCS_TRIG_SERIES_INR */
|
||||
#define WM8961_DCS_TRIG_SERIES_INR_WIDTH 1 /* DCS_TRIG_SERIES_INR */
|
||||
|
||||
/*
|
||||
* R61 (0x3D) - DC Servo 1
|
||||
*/
|
||||
#define WM8961_DCS_ENA_CHAN_HPL 0x0080 /* DCS_ENA_CHAN_HPL */
|
||||
#define WM8961_DCS_ENA_CHAN_HPL_MASK 0x0080 /* DCS_ENA_CHAN_HPL */
|
||||
#define WM8961_DCS_ENA_CHAN_HPL_SHIFT 7 /* DCS_ENA_CHAN_HPL */
|
||||
#define WM8961_DCS_ENA_CHAN_HPL_WIDTH 1 /* DCS_ENA_CHAN_HPL */
|
||||
#define WM8961_DCS_TRIG_STARTUP_HPL 0x0040 /* DCS_TRIG_STARTUP_HPL */
|
||||
#define WM8961_DCS_TRIG_STARTUP_HPL_MASK 0x0040 /* DCS_TRIG_STARTUP_HPL */
|
||||
#define WM8961_DCS_TRIG_STARTUP_HPL_SHIFT 6 /* DCS_TRIG_STARTUP_HPL */
|
||||
#define WM8961_DCS_TRIG_STARTUP_HPL_WIDTH 1 /* DCS_TRIG_STARTUP_HPL */
|
||||
#define WM8961_DCS_TRIG_SERIES_HPL 0x0010 /* DCS_TRIG_SERIES_HPL */
|
||||
#define WM8961_DCS_TRIG_SERIES_HPL_MASK 0x0010 /* DCS_TRIG_SERIES_HPL */
|
||||
#define WM8961_DCS_TRIG_SERIES_HPL_SHIFT 4 /* DCS_TRIG_SERIES_HPL */
|
||||
#define WM8961_DCS_TRIG_SERIES_HPL_WIDTH 1 /* DCS_TRIG_SERIES_HPL */
|
||||
#define WM8961_DCS_ENA_CHAN_HPR 0x0008 /* DCS_ENA_CHAN_HPR */
|
||||
#define WM8961_DCS_ENA_CHAN_HPR_MASK 0x0008 /* DCS_ENA_CHAN_HPR */
|
||||
#define WM8961_DCS_ENA_CHAN_HPR_SHIFT 3 /* DCS_ENA_CHAN_HPR */
|
||||
#define WM8961_DCS_ENA_CHAN_HPR_WIDTH 1 /* DCS_ENA_CHAN_HPR */
|
||||
#define WM8961_DCS_TRIG_STARTUP_HPR 0x0004 /* DCS_TRIG_STARTUP_HPR */
|
||||
#define WM8961_DCS_TRIG_STARTUP_HPR_MASK 0x0004 /* DCS_TRIG_STARTUP_HPR */
|
||||
#define WM8961_DCS_TRIG_STARTUP_HPR_SHIFT 2 /* DCS_TRIG_STARTUP_HPR */
|
||||
#define WM8961_DCS_TRIG_STARTUP_HPR_WIDTH 1 /* DCS_TRIG_STARTUP_HPR */
|
||||
#define WM8961_DCS_TRIG_SERIES_HPR 0x0001 /* DCS_TRIG_SERIES_HPR */
|
||||
#define WM8961_DCS_TRIG_SERIES_HPR_MASK 0x0001 /* DCS_TRIG_SERIES_HPR */
|
||||
#define WM8961_DCS_TRIG_SERIES_HPR_SHIFT 0 /* DCS_TRIG_SERIES_HPR */
|
||||
#define WM8961_DCS_TRIG_SERIES_HPR_WIDTH 1 /* DCS_TRIG_SERIES_HPR */
|
||||
|
||||
/*
|
||||
* R63 (0x3F) - DC Servo 3
|
||||
*/
|
||||
#define WM8961_DCS_FILT_BW_SERIES_MASK 0x0030 /* DCS_FILT_BW_SERIES - [5:4] */
|
||||
#define WM8961_DCS_FILT_BW_SERIES_SHIFT 4 /* DCS_FILT_BW_SERIES - [5:4] */
|
||||
#define WM8961_DCS_FILT_BW_SERIES_WIDTH 2 /* DCS_FILT_BW_SERIES - [5:4] */
|
||||
|
||||
/*
|
||||
* R65 (0x41) - DC Servo 5
|
||||
*/
|
||||
#define WM8961_DCS_SERIES_NO_HP_MASK 0x007F /* DCS_SERIES_NO_HP - [6:0] */
|
||||
#define WM8961_DCS_SERIES_NO_HP_SHIFT 0 /* DCS_SERIES_NO_HP - [6:0] */
|
||||
#define WM8961_DCS_SERIES_NO_HP_WIDTH 7 /* DCS_SERIES_NO_HP - [6:0] */
|
||||
|
||||
/*
|
||||
* R68 (0x44) - Analogue PGA Bias
|
||||
*/
|
||||
#define WM8961_HP_PGAS_BIAS_MASK 0x0007 /* HP_PGAS_BIAS - [2:0] */
|
||||
#define WM8961_HP_PGAS_BIAS_SHIFT 0 /* HP_PGAS_BIAS - [2:0] */
|
||||
#define WM8961_HP_PGAS_BIAS_WIDTH 3 /* HP_PGAS_BIAS - [2:0] */
|
||||
|
||||
/*
|
||||
* R69 (0x45) - Analogue HP 0
|
||||
*/
|
||||
#define WM8961_HPL_RMV_SHORT 0x0080 /* HPL_RMV_SHORT */
|
||||
#define WM8961_HPL_RMV_SHORT_MASK 0x0080 /* HPL_RMV_SHORT */
|
||||
#define WM8961_HPL_RMV_SHORT_SHIFT 7 /* HPL_RMV_SHORT */
|
||||
#define WM8961_HPL_RMV_SHORT_WIDTH 1 /* HPL_RMV_SHORT */
|
||||
#define WM8961_HPL_ENA_OUTP 0x0040 /* HPL_ENA_OUTP */
|
||||
#define WM8961_HPL_ENA_OUTP_MASK 0x0040 /* HPL_ENA_OUTP */
|
||||
#define WM8961_HPL_ENA_OUTP_SHIFT 6 /* HPL_ENA_OUTP */
|
||||
#define WM8961_HPL_ENA_OUTP_WIDTH 1 /* HPL_ENA_OUTP */
|
||||
#define WM8961_HPL_ENA_DLY 0x0020 /* HPL_ENA_DLY */
|
||||
#define WM8961_HPL_ENA_DLY_MASK 0x0020 /* HPL_ENA_DLY */
|
||||
#define WM8961_HPL_ENA_DLY_SHIFT 5 /* HPL_ENA_DLY */
|
||||
#define WM8961_HPL_ENA_DLY_WIDTH 1 /* HPL_ENA_DLY */
|
||||
#define WM8961_HPL_ENA 0x0010 /* HPL_ENA */
|
||||
#define WM8961_HPL_ENA_MASK 0x0010 /* HPL_ENA */
|
||||
#define WM8961_HPL_ENA_SHIFT 4 /* HPL_ENA */
|
||||
#define WM8961_HPL_ENA_WIDTH 1 /* HPL_ENA */
|
||||
#define WM8961_HPR_RMV_SHORT 0x0008 /* HPR_RMV_SHORT */
|
||||
#define WM8961_HPR_RMV_SHORT_MASK 0x0008 /* HPR_RMV_SHORT */
|
||||
#define WM8961_HPR_RMV_SHORT_SHIFT 3 /* HPR_RMV_SHORT */
|
||||
#define WM8961_HPR_RMV_SHORT_WIDTH 1 /* HPR_RMV_SHORT */
|
||||
#define WM8961_HPR_ENA_OUTP 0x0004 /* HPR_ENA_OUTP */
|
||||
#define WM8961_HPR_ENA_OUTP_MASK 0x0004 /* HPR_ENA_OUTP */
|
||||
#define WM8961_HPR_ENA_OUTP_SHIFT 2 /* HPR_ENA_OUTP */
|
||||
#define WM8961_HPR_ENA_OUTP_WIDTH 1 /* HPR_ENA_OUTP */
|
||||
#define WM8961_HPR_ENA_DLY 0x0002 /* HPR_ENA_DLY */
|
||||
#define WM8961_HPR_ENA_DLY_MASK 0x0002 /* HPR_ENA_DLY */
|
||||
#define WM8961_HPR_ENA_DLY_SHIFT 1 /* HPR_ENA_DLY */
|
||||
#define WM8961_HPR_ENA_DLY_WIDTH 1 /* HPR_ENA_DLY */
|
||||
#define WM8961_HPR_ENA 0x0001 /* HPR_ENA */
|
||||
#define WM8961_HPR_ENA_MASK 0x0001 /* HPR_ENA */
|
||||
#define WM8961_HPR_ENA_SHIFT 0 /* HPR_ENA */
|
||||
#define WM8961_HPR_ENA_WIDTH 1 /* HPR_ENA */
|
||||
|
||||
/*
|
||||
* R71 (0x47) - Analogue HP 2
|
||||
*/
|
||||
#define WM8961_HPL_VOL_MASK 0x01C0 /* HPL_VOL - [8:6] */
|
||||
#define WM8961_HPL_VOL_SHIFT 6 /* HPL_VOL - [8:6] */
|
||||
#define WM8961_HPL_VOL_WIDTH 3 /* HPL_VOL - [8:6] */
|
||||
#define WM8961_HPR_VOL_MASK 0x0038 /* HPR_VOL - [5:3] */
|
||||
#define WM8961_HPR_VOL_SHIFT 3 /* HPR_VOL - [5:3] */
|
||||
#define WM8961_HPR_VOL_WIDTH 3 /* HPR_VOL - [5:3] */
|
||||
#define WM8961_HP_BIAS_BOOST_MASK 0x0007 /* HP_BIAS_BOOST - [2:0] */
|
||||
#define WM8961_HP_BIAS_BOOST_SHIFT 0 /* HP_BIAS_BOOST - [2:0] */
|
||||
#define WM8961_HP_BIAS_BOOST_WIDTH 3 /* HP_BIAS_BOOST - [2:0] */
|
||||
|
||||
/*
|
||||
* R72 (0x48) - Charge Pump 1
|
||||
*/
|
||||
#define WM8961_CP_ENA 0x0001 /* CP_ENA */
|
||||
#define WM8961_CP_ENA_MASK 0x0001 /* CP_ENA */
|
||||
#define WM8961_CP_ENA_SHIFT 0 /* CP_ENA */
|
||||
#define WM8961_CP_ENA_WIDTH 1 /* CP_ENA */
|
||||
|
||||
/*
|
||||
* R82 (0x52) - Charge Pump B
|
||||
*/
|
||||
#define WM8961_CP_DYN_PWR_MASK 0x0003 /* CP_DYN_PWR - [1:0] */
|
||||
#define WM8961_CP_DYN_PWR_SHIFT 0 /* CP_DYN_PWR - [1:0] */
|
||||
#define WM8961_CP_DYN_PWR_WIDTH 2 /* CP_DYN_PWR - [1:0] */
|
||||
|
||||
/*
|
||||
* R87 (0x57) - Write Sequencer 1
|
||||
*/
|
||||
#define WM8961_WSEQ_ENA 0x0020 /* WSEQ_ENA */
|
||||
#define WM8961_WSEQ_ENA_MASK 0x0020 /* WSEQ_ENA */
|
||||
#define WM8961_WSEQ_ENA_SHIFT 5 /* WSEQ_ENA */
|
||||
#define WM8961_WSEQ_ENA_WIDTH 1 /* WSEQ_ENA */
|
||||
#define WM8961_WSEQ_WRITE_INDEX_MASK 0x001F /* WSEQ_WRITE_INDEX - [4:0] */
|
||||
#define WM8961_WSEQ_WRITE_INDEX_SHIFT 0 /* WSEQ_WRITE_INDEX - [4:0] */
|
||||
#define WM8961_WSEQ_WRITE_INDEX_WIDTH 5 /* WSEQ_WRITE_INDEX - [4:0] */
|
||||
|
||||
/*
|
||||
* R88 (0x58) - Write Sequencer 2
|
||||
*/
|
||||
#define WM8961_WSEQ_EOS 0x0100 /* WSEQ_EOS */
|
||||
#define WM8961_WSEQ_EOS_MASK 0x0100 /* WSEQ_EOS */
|
||||
#define WM8961_WSEQ_EOS_SHIFT 8 /* WSEQ_EOS */
|
||||
#define WM8961_WSEQ_EOS_WIDTH 1 /* WSEQ_EOS */
|
||||
#define WM8961_WSEQ_ADDR_MASK 0x00FF /* WSEQ_ADDR - [7:0] */
|
||||
#define WM8961_WSEQ_ADDR_SHIFT 0 /* WSEQ_ADDR - [7:0] */
|
||||
#define WM8961_WSEQ_ADDR_WIDTH 8 /* WSEQ_ADDR - [7:0] */
|
||||
|
||||
/*
|
||||
* R89 (0x59) - Write Sequencer 3
|
||||
*/
|
||||
#define WM8961_WSEQ_DATA_MASK 0x00FF /* WSEQ_DATA - [7:0] */
|
||||
#define WM8961_WSEQ_DATA_SHIFT 0 /* WSEQ_DATA - [7:0] */
|
||||
#define WM8961_WSEQ_DATA_WIDTH 8 /* WSEQ_DATA - [7:0] */
|
||||
|
||||
/*
|
||||
* R90 (0x5A) - Write Sequencer 4
|
||||
*/
|
||||
#define WM8961_WSEQ_ABORT 0x0100 /* WSEQ_ABORT */
|
||||
#define WM8961_WSEQ_ABORT_MASK 0x0100 /* WSEQ_ABORT */
|
||||
#define WM8961_WSEQ_ABORT_SHIFT 8 /* WSEQ_ABORT */
|
||||
#define WM8961_WSEQ_ABORT_WIDTH 1 /* WSEQ_ABORT */
|
||||
#define WM8961_WSEQ_START 0x0080 /* WSEQ_START */
|
||||
#define WM8961_WSEQ_START_MASK 0x0080 /* WSEQ_START */
|
||||
#define WM8961_WSEQ_START_SHIFT 7 /* WSEQ_START */
|
||||
#define WM8961_WSEQ_START_WIDTH 1 /* WSEQ_START */
|
||||
#define WM8961_WSEQ_START_INDEX_MASK 0x003F /* WSEQ_START_INDEX - [5:0] */
|
||||
#define WM8961_WSEQ_START_INDEX_SHIFT 0 /* WSEQ_START_INDEX - [5:0] */
|
||||
#define WM8961_WSEQ_START_INDEX_WIDTH 6 /* WSEQ_START_INDEX - [5:0] */
|
||||
|
||||
/*
|
||||
* R91 (0x5B) - Write Sequencer 5
|
||||
*/
|
||||
#define WM8961_WSEQ_DATA_WIDTH_MASK 0x0070 /* WSEQ_DATA_WIDTH - [6:4] */
|
||||
#define WM8961_WSEQ_DATA_WIDTH_SHIFT 4 /* WSEQ_DATA_WIDTH - [6:4] */
|
||||
#define WM8961_WSEQ_DATA_WIDTH_WIDTH 3 /* WSEQ_DATA_WIDTH - [6:4] */
|
||||
#define WM8961_WSEQ_DATA_START_MASK 0x000F /* WSEQ_DATA_START - [3:0] */
|
||||
#define WM8961_WSEQ_DATA_START_SHIFT 0 /* WSEQ_DATA_START - [3:0] */
|
||||
#define WM8961_WSEQ_DATA_START_WIDTH 4 /* WSEQ_DATA_START - [3:0] */
|
||||
|
||||
/*
|
||||
* R92 (0x5C) - Write Sequencer 6
|
||||
*/
|
||||
#define WM8961_WSEQ_DELAY_MASK 0x000F /* WSEQ_DELAY - [3:0] */
|
||||
#define WM8961_WSEQ_DELAY_SHIFT 0 /* WSEQ_DELAY - [3:0] */
|
||||
#define WM8961_WSEQ_DELAY_WIDTH 4 /* WSEQ_DELAY - [3:0] */
|
||||
|
||||
/*
|
||||
* R93 (0x5D) - Write Sequencer 7
|
||||
*/
|
||||
#define WM8961_WSEQ_BUSY 0x0001 /* WSEQ_BUSY */
|
||||
#define WM8961_WSEQ_BUSY_MASK 0x0001 /* WSEQ_BUSY */
|
||||
#define WM8961_WSEQ_BUSY_SHIFT 0 /* WSEQ_BUSY */
|
||||
#define WM8961_WSEQ_BUSY_WIDTH 1 /* WSEQ_BUSY */
|
||||
|
||||
/*
|
||||
* R252 (0xFC) - General test 1
|
||||
*/
|
||||
#define WM8961_ARA_ENA 0x0002 /* ARA_ENA */
|
||||
#define WM8961_ARA_ENA_MASK 0x0002 /* ARA_ENA */
|
||||
#define WM8961_ARA_ENA_SHIFT 1 /* ARA_ENA */
|
||||
#define WM8961_ARA_ENA_WIDTH 1 /* ARA_ENA */
|
||||
#define WM8961_AUTO_INC 0x0001 /* AUTO_INC */
|
||||
#define WM8961_AUTO_INC_MASK 0x0001 /* AUTO_INC */
|
||||
#define WM8961_AUTO_INC_SHIFT 0 /* AUTO_INC */
|
||||
#define WM8961_AUTO_INC_WIDTH 1 /* AUTO_INC */
|
||||
|
||||
#endif
|
|
@ -59,44 +59,7 @@ static const u16 wm8971_reg[] = {
|
|||
0x0079, 0x0079, 0x0079, /* 40 */
|
||||
};
|
||||
|
||||
static inline unsigned int wm8971_read_reg_cache(struct snd_soc_codec *codec,
|
||||
unsigned int reg)
|
||||
{
|
||||
u16 *cache = codec->reg_cache;
|
||||
if (reg < WM8971_REG_COUNT)
|
||||
return cache[reg];
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static inline void wm8971_write_reg_cache(struct snd_soc_codec *codec,
|
||||
unsigned int reg, unsigned int value)
|
||||
{
|
||||
u16 *cache = codec->reg_cache;
|
||||
if (reg < WM8971_REG_COUNT)
|
||||
cache[reg] = value;
|
||||
}
|
||||
|
||||
static int wm8971_write(struct snd_soc_codec *codec, unsigned int reg,
|
||||
unsigned int value)
|
||||
{
|
||||
u8 data[2];
|
||||
|
||||
/* data is
|
||||
* D15..D9 WM8753 register offset
|
||||
* D8...D0 register data
|
||||
*/
|
||||
data[0] = (reg << 1) | ((value >> 8) & 0x0001);
|
||||
data[1] = value & 0x00ff;
|
||||
|
||||
wm8971_write_reg_cache (codec, reg, value);
|
||||
if (codec->hw_write(codec->control_data, data, 2) == 2)
|
||||
return 0;
|
||||
else
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
#define wm8971_reset(c) wm8971_write(c, WM8971_RESET, 0)
|
||||
#define wm8971_reset(c) snd_soc_write(c, WM8971_RESET, 0)
|
||||
|
||||
/* WM8971 Controls */
|
||||
static const char *wm8971_bass[] = { "Linear Control", "Adaptive Boost" };
|
||||
|
@ -521,7 +484,7 @@ static int wm8971_set_dai_fmt(struct snd_soc_dai *codec_dai,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
wm8971_write(codec, WM8971_IFACE, iface);
|
||||
snd_soc_write(codec, WM8971_IFACE, iface);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -533,8 +496,8 @@ static int wm8971_pcm_hw_params(struct snd_pcm_substream *substream,
|
|||
struct snd_soc_device *socdev = rtd->socdev;
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
struct wm8971_priv *wm8971 = codec->private_data;
|
||||
u16 iface = wm8971_read_reg_cache(codec, WM8971_IFACE) & 0x1f3;
|
||||
u16 srate = wm8971_read_reg_cache(codec, WM8971_SRATE) & 0x1c0;
|
||||
u16 iface = snd_soc_read(codec, WM8971_IFACE) & 0x1f3;
|
||||
u16 srate = snd_soc_read(codec, WM8971_SRATE) & 0x1c0;
|
||||
int coeff = get_coeff(wm8971->sysclk, params_rate(params));
|
||||
|
||||
/* bit size */
|
||||
|
@ -553,9 +516,9 @@ static int wm8971_pcm_hw_params(struct snd_pcm_substream *substream,
|
|||
}
|
||||
|
||||
/* set iface & srate */
|
||||
wm8971_write(codec, WM8971_IFACE, iface);
|
||||
snd_soc_write(codec, WM8971_IFACE, iface);
|
||||
if (coeff >= 0)
|
||||
wm8971_write(codec, WM8971_SRATE, srate |
|
||||
snd_soc_write(codec, WM8971_SRATE, srate |
|
||||
(coeff_div[coeff].sr << 1) | coeff_div[coeff].usb);
|
||||
|
||||
return 0;
|
||||
|
@ -564,33 +527,33 @@ static int wm8971_pcm_hw_params(struct snd_pcm_substream *substream,
|
|||
static int wm8971_mute(struct snd_soc_dai *dai, int mute)
|
||||
{
|
||||
struct snd_soc_codec *codec = dai->codec;
|
||||
u16 mute_reg = wm8971_read_reg_cache(codec, WM8971_ADCDAC) & 0xfff7;
|
||||
u16 mute_reg = snd_soc_read(codec, WM8971_ADCDAC) & 0xfff7;
|
||||
|
||||
if (mute)
|
||||
wm8971_write(codec, WM8971_ADCDAC, mute_reg | 0x8);
|
||||
snd_soc_write(codec, WM8971_ADCDAC, mute_reg | 0x8);
|
||||
else
|
||||
wm8971_write(codec, WM8971_ADCDAC, mute_reg);
|
||||
snd_soc_write(codec, WM8971_ADCDAC, mute_reg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wm8971_set_bias_level(struct snd_soc_codec *codec,
|
||||
enum snd_soc_bias_level level)
|
||||
{
|
||||
u16 pwr_reg = wm8971_read_reg_cache(codec, WM8971_PWR1) & 0xfe3e;
|
||||
u16 pwr_reg = snd_soc_read(codec, WM8971_PWR1) & 0xfe3e;
|
||||
|
||||
switch (level) {
|
||||
case SND_SOC_BIAS_ON:
|
||||
/* set vmid to 50k and unmute dac */
|
||||
wm8971_write(codec, WM8971_PWR1, pwr_reg | 0x00c1);
|
||||
snd_soc_write(codec, WM8971_PWR1, pwr_reg | 0x00c1);
|
||||
break;
|
||||
case SND_SOC_BIAS_PREPARE:
|
||||
break;
|
||||
case SND_SOC_BIAS_STANDBY:
|
||||
/* mute dac and set vmid to 500k, enable VREF */
|
||||
wm8971_write(codec, WM8971_PWR1, pwr_reg | 0x0140);
|
||||
snd_soc_write(codec, WM8971_PWR1, pwr_reg | 0x0140);
|
||||
break;
|
||||
case SND_SOC_BIAS_OFF:
|
||||
wm8971_write(codec, WM8971_PWR1, 0x0001);
|
||||
snd_soc_write(codec, WM8971_PWR1, 0x0001);
|
||||
break;
|
||||
}
|
||||
codec->bias_level = level;
|
||||
|
@ -667,8 +630,8 @@ static int wm8971_resume(struct platform_device *pdev)
|
|||
|
||||
/* charge wm8971 caps */
|
||||
if (codec->suspend_bias_level == SND_SOC_BIAS_ON) {
|
||||
reg = wm8971_read_reg_cache(codec, WM8971_PWR1) & 0xfe3e;
|
||||
wm8971_write(codec, WM8971_PWR1, reg | 0x01c0);
|
||||
reg = snd_soc_read(codec, WM8971_PWR1) & 0xfe3e;
|
||||
snd_soc_write(codec, WM8971_PWR1, reg | 0x01c0);
|
||||
codec->bias_level = SND_SOC_BIAS_ON;
|
||||
queue_delayed_work(wm8971_workq, &codec->delayed_work,
|
||||
msecs_to_jiffies(1000));
|
||||
|
@ -677,15 +640,14 @@ static int wm8971_resume(struct platform_device *pdev)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int wm8971_init(struct snd_soc_device *socdev)
|
||||
static int wm8971_init(struct snd_soc_device *socdev,
|
||||
enum snd_soc_control_type control)
|
||||
{
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
int reg, ret = 0;
|
||||
|
||||
codec->name = "WM8971";
|
||||
codec->owner = THIS_MODULE;
|
||||
codec->read = wm8971_read_reg_cache;
|
||||
codec->write = wm8971_write;
|
||||
codec->set_bias_level = wm8971_set_bias_level;
|
||||
codec->dai = &wm8971_dai;
|
||||
codec->reg_cache_size = ARRAY_SIZE(wm8971_reg);
|
||||
|
@ -695,42 +657,48 @@ static int wm8971_init(struct snd_soc_device *socdev)
|
|||
if (codec->reg_cache == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = snd_soc_codec_set_cache_io(codec, 7, 9, control);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "wm8971: failed to set cache I/O: %d\n", ret);
|
||||
goto err;
|
||||
}
|
||||
|
||||
wm8971_reset(codec);
|
||||
|
||||
/* register pcms */
|
||||
ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "wm8971: failed to create pcms\n");
|
||||
goto pcm_err;
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* charge output caps - set vmid to 5k for quick power up */
|
||||
reg = wm8971_read_reg_cache(codec, WM8971_PWR1) & 0xfe3e;
|
||||
wm8971_write(codec, WM8971_PWR1, reg | 0x01c0);
|
||||
reg = snd_soc_read(codec, WM8971_PWR1) & 0xfe3e;
|
||||
snd_soc_write(codec, WM8971_PWR1, reg | 0x01c0);
|
||||
codec->bias_level = SND_SOC_BIAS_STANDBY;
|
||||
queue_delayed_work(wm8971_workq, &codec->delayed_work,
|
||||
msecs_to_jiffies(1000));
|
||||
|
||||
/* set the update bits */
|
||||
reg = wm8971_read_reg_cache(codec, WM8971_LDAC);
|
||||
wm8971_write(codec, WM8971_LDAC, reg | 0x0100);
|
||||
reg = wm8971_read_reg_cache(codec, WM8971_RDAC);
|
||||
wm8971_write(codec, WM8971_RDAC, reg | 0x0100);
|
||||
reg = snd_soc_read(codec, WM8971_LDAC);
|
||||
snd_soc_write(codec, WM8971_LDAC, reg | 0x0100);
|
||||
reg = snd_soc_read(codec, WM8971_RDAC);
|
||||
snd_soc_write(codec, WM8971_RDAC, reg | 0x0100);
|
||||
|
||||
reg = wm8971_read_reg_cache(codec, WM8971_LOUT1V);
|
||||
wm8971_write(codec, WM8971_LOUT1V, reg | 0x0100);
|
||||
reg = wm8971_read_reg_cache(codec, WM8971_ROUT1V);
|
||||
wm8971_write(codec, WM8971_ROUT1V, reg | 0x0100);
|
||||
reg = snd_soc_read(codec, WM8971_LOUT1V);
|
||||
snd_soc_write(codec, WM8971_LOUT1V, reg | 0x0100);
|
||||
reg = snd_soc_read(codec, WM8971_ROUT1V);
|
||||
snd_soc_write(codec, WM8971_ROUT1V, reg | 0x0100);
|
||||
|
||||
reg = wm8971_read_reg_cache(codec, WM8971_LOUT2V);
|
||||
wm8971_write(codec, WM8971_LOUT2V, reg | 0x0100);
|
||||
reg = wm8971_read_reg_cache(codec, WM8971_ROUT2V);
|
||||
wm8971_write(codec, WM8971_ROUT2V, reg | 0x0100);
|
||||
reg = snd_soc_read(codec, WM8971_LOUT2V);
|
||||
snd_soc_write(codec, WM8971_LOUT2V, reg | 0x0100);
|
||||
reg = snd_soc_read(codec, WM8971_ROUT2V);
|
||||
snd_soc_write(codec, WM8971_ROUT2V, reg | 0x0100);
|
||||
|
||||
reg = wm8971_read_reg_cache(codec, WM8971_LINVOL);
|
||||
wm8971_write(codec, WM8971_LINVOL, reg | 0x0100);
|
||||
reg = wm8971_read_reg_cache(codec, WM8971_RINVOL);
|
||||
wm8971_write(codec, WM8971_RINVOL, reg | 0x0100);
|
||||
reg = snd_soc_read(codec, WM8971_LINVOL);
|
||||
snd_soc_write(codec, WM8971_LINVOL, reg | 0x0100);
|
||||
reg = snd_soc_read(codec, WM8971_RINVOL);
|
||||
snd_soc_write(codec, WM8971_RINVOL, reg | 0x0100);
|
||||
|
||||
snd_soc_add_controls(codec, wm8971_snd_controls,
|
||||
ARRAY_SIZE(wm8971_snd_controls));
|
||||
|
@ -745,7 +713,7 @@ static int wm8971_init(struct snd_soc_device *socdev)
|
|||
card_err:
|
||||
snd_soc_free_pcms(socdev);
|
||||
snd_soc_dapm_free(socdev);
|
||||
pcm_err:
|
||||
err:
|
||||
kfree(codec->reg_cache);
|
||||
return ret;
|
||||
}
|
||||
|
@ -767,7 +735,7 @@ static int wm8971_i2c_probe(struct i2c_client *i2c,
|
|||
|
||||
codec->control_data = i2c;
|
||||
|
||||
ret = wm8971_init(socdev);
|
||||
ret = wm8971_init(socdev, SND_SOC_I2C);
|
||||
if (ret < 0)
|
||||
pr_err("failed to initialise WM8971\n");
|
||||
|
||||
|
@ -877,7 +845,6 @@ static int wm8971_probe(struct platform_device *pdev)
|
|||
|
||||
#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE)
|
||||
if (setup->i2c_address) {
|
||||
codec->hw_write = (hw_write_t)i2c_master_send;
|
||||
ret = wm8971_add_i2c_device(pdev, setup);
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,808 @@
|
|||
/*
|
||||
* wm8974.c -- WM8974 ALSA Soc Audio driver
|
||||
*
|
||||
* Copyright 2006-2009 Wolfson Microelectronics PLC.
|
||||
*
|
||||
* Author: Liam Girdwood <linux@wolfsonmicro.com>
|
||||
*
|
||||
* 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/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/version.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/soc-dapm.h>
|
||||
#include <sound/initval.h>
|
||||
#include <sound/tlv.h>
|
||||
|
||||
#include "wm8974.h"
|
||||
|
||||
static const u16 wm8974_reg[WM8974_CACHEREGNUM] = {
|
||||
0x0000, 0x0000, 0x0000, 0x0000,
|
||||
0x0050, 0x0000, 0x0140, 0x0000,
|
||||
0x0000, 0x0000, 0x0000, 0x00ff,
|
||||
0x0000, 0x0000, 0x0100, 0x00ff,
|
||||
0x0000, 0x0000, 0x012c, 0x002c,
|
||||
0x002c, 0x002c, 0x002c, 0x0000,
|
||||
0x0032, 0x0000, 0x0000, 0x0000,
|
||||
0x0000, 0x0000, 0x0000, 0x0000,
|
||||
0x0038, 0x000b, 0x0032, 0x0000,
|
||||
0x0008, 0x000c, 0x0093, 0x00e9,
|
||||
0x0000, 0x0000, 0x0000, 0x0000,
|
||||
0x0003, 0x0010, 0x0000, 0x0000,
|
||||
0x0000, 0x0002, 0x0000, 0x0000,
|
||||
0x0000, 0x0000, 0x0039, 0x0000,
|
||||
0x0000,
|
||||
};
|
||||
|
||||
#define WM8974_POWER1_BIASEN 0x08
|
||||
#define WM8974_POWER1_BUFIOEN 0x10
|
||||
|
||||
struct wm8974_priv {
|
||||
struct snd_soc_codec codec;
|
||||
u16 reg_cache[WM8974_CACHEREGNUM];
|
||||
};
|
||||
|
||||
static struct snd_soc_codec *wm8974_codec;
|
||||
|
||||
#define wm8974_reset(c) snd_soc_write(c, WM8974_RESET, 0)
|
||||
|
||||
static const char *wm8974_companding[] = {"Off", "NC", "u-law", "A-law" };
|
||||
static const char *wm8974_deemp[] = {"None", "32kHz", "44.1kHz", "48kHz" };
|
||||
static const char *wm8974_eqmode[] = {"Capture", "Playback" };
|
||||
static const char *wm8974_bw[] = {"Narrow", "Wide" };
|
||||
static const char *wm8974_eq1[] = {"80Hz", "105Hz", "135Hz", "175Hz" };
|
||||
static const char *wm8974_eq2[] = {"230Hz", "300Hz", "385Hz", "500Hz" };
|
||||
static const char *wm8974_eq3[] = {"650Hz", "850Hz", "1.1kHz", "1.4kHz" };
|
||||
static const char *wm8974_eq4[] = {"1.8kHz", "2.4kHz", "3.2kHz", "4.1kHz" };
|
||||
static const char *wm8974_eq5[] = {"5.3kHz", "6.9kHz", "9kHz", "11.7kHz" };
|
||||
static const char *wm8974_alc[] = {"ALC", "Limiter" };
|
||||
|
||||
static const struct soc_enum wm8974_enum[] = {
|
||||
SOC_ENUM_SINGLE(WM8974_COMP, 1, 4, wm8974_companding), /* adc */
|
||||
SOC_ENUM_SINGLE(WM8974_COMP, 3, 4, wm8974_companding), /* dac */
|
||||
SOC_ENUM_SINGLE(WM8974_DAC, 4, 4, wm8974_deemp),
|
||||
SOC_ENUM_SINGLE(WM8974_EQ1, 8, 2, wm8974_eqmode),
|
||||
|
||||
SOC_ENUM_SINGLE(WM8974_EQ1, 5, 4, wm8974_eq1),
|
||||
SOC_ENUM_SINGLE(WM8974_EQ2, 8, 2, wm8974_bw),
|
||||
SOC_ENUM_SINGLE(WM8974_EQ2, 5, 4, wm8974_eq2),
|
||||
SOC_ENUM_SINGLE(WM8974_EQ3, 8, 2, wm8974_bw),
|
||||
|
||||
SOC_ENUM_SINGLE(WM8974_EQ3, 5, 4, wm8974_eq3),
|
||||
SOC_ENUM_SINGLE(WM8974_EQ4, 8, 2, wm8974_bw),
|
||||
SOC_ENUM_SINGLE(WM8974_EQ4, 5, 4, wm8974_eq4),
|
||||
SOC_ENUM_SINGLE(WM8974_EQ5, 8, 2, wm8974_bw),
|
||||
|
||||
SOC_ENUM_SINGLE(WM8974_EQ5, 5, 4, wm8974_eq5),
|
||||
SOC_ENUM_SINGLE(WM8974_ALC3, 8, 2, wm8974_alc),
|
||||
};
|
||||
|
||||
static const char *wm8974_auxmode_text[] = { "Buffer", "Mixer" };
|
||||
|
||||
static const struct soc_enum wm8974_auxmode =
|
||||
SOC_ENUM_SINGLE(WM8974_INPUT, 3, 2, wm8974_auxmode_text);
|
||||
|
||||
static const DECLARE_TLV_DB_SCALE(digital_tlv, -12750, 50, 1);
|
||||
static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0);
|
||||
static const DECLARE_TLV_DB_SCALE(inpga_tlv, -1200, 75, 0);
|
||||
static const DECLARE_TLV_DB_SCALE(spk_tlv, -5700, 100, 0);
|
||||
|
||||
static const struct snd_kcontrol_new wm8974_snd_controls[] = {
|
||||
|
||||
SOC_SINGLE("Digital Loopback Switch", WM8974_COMP, 0, 1, 0),
|
||||
|
||||
SOC_ENUM("DAC Companding", wm8974_enum[1]),
|
||||
SOC_ENUM("ADC Companding", wm8974_enum[0]),
|
||||
|
||||
SOC_ENUM("Playback De-emphasis", wm8974_enum[2]),
|
||||
SOC_SINGLE("DAC Inversion Switch", WM8974_DAC, 0, 1, 0),
|
||||
|
||||
SOC_SINGLE_TLV("PCM Volume", WM8974_DACVOL, 0, 255, 0, digital_tlv),
|
||||
|
||||
SOC_SINGLE("High Pass Filter Switch", WM8974_ADC, 8, 1, 0),
|
||||
SOC_SINGLE("High Pass Cut Off", WM8974_ADC, 4, 7, 0),
|
||||
SOC_SINGLE("ADC Inversion Switch", WM8974_ADC, 0, 1, 0),
|
||||
|
||||
SOC_SINGLE_TLV("Capture Volume", WM8974_ADCVOL, 0, 255, 0, digital_tlv),
|
||||
|
||||
SOC_ENUM("Equaliser Function", wm8974_enum[3]),
|
||||
SOC_ENUM("EQ1 Cut Off", wm8974_enum[4]),
|
||||
SOC_SINGLE_TLV("EQ1 Volume", WM8974_EQ1, 0, 24, 1, eq_tlv),
|
||||
|
||||
SOC_ENUM("Equaliser EQ2 Bandwith", wm8974_enum[5]),
|
||||
SOC_ENUM("EQ2 Cut Off", wm8974_enum[6]),
|
||||
SOC_SINGLE_TLV("EQ2 Volume", WM8974_EQ2, 0, 24, 1, eq_tlv),
|
||||
|
||||
SOC_ENUM("Equaliser EQ3 Bandwith", wm8974_enum[7]),
|
||||
SOC_ENUM("EQ3 Cut Off", wm8974_enum[8]),
|
||||
SOC_SINGLE_TLV("EQ3 Volume", WM8974_EQ3, 0, 24, 1, eq_tlv),
|
||||
|
||||
SOC_ENUM("Equaliser EQ4 Bandwith", wm8974_enum[9]),
|
||||
SOC_ENUM("EQ4 Cut Off", wm8974_enum[10]),
|
||||
SOC_SINGLE_TLV("EQ4 Volume", WM8974_EQ4, 0, 24, 1, eq_tlv),
|
||||
|
||||
SOC_ENUM("Equaliser EQ5 Bandwith", wm8974_enum[11]),
|
||||
SOC_ENUM("EQ5 Cut Off", wm8974_enum[12]),
|
||||
SOC_SINGLE_TLV("EQ5 Volume", WM8974_EQ5, 0, 24, 1, eq_tlv),
|
||||
|
||||
SOC_SINGLE("DAC Playback Limiter Switch", WM8974_DACLIM1, 8, 1, 0),
|
||||
SOC_SINGLE("DAC Playback Limiter Decay", WM8974_DACLIM1, 4, 15, 0),
|
||||
SOC_SINGLE("DAC Playback Limiter Attack", WM8974_DACLIM1, 0, 15, 0),
|
||||
|
||||
SOC_SINGLE("DAC Playback Limiter Threshold", WM8974_DACLIM2, 4, 7, 0),
|
||||
SOC_SINGLE("DAC Playback Limiter Boost", WM8974_DACLIM2, 0, 15, 0),
|
||||
|
||||
SOC_SINGLE("ALC Enable Switch", WM8974_ALC1, 8, 1, 0),
|
||||
SOC_SINGLE("ALC Capture Max Gain", WM8974_ALC1, 3, 7, 0),
|
||||
SOC_SINGLE("ALC Capture Min Gain", WM8974_ALC1, 0, 7, 0),
|
||||
|
||||
SOC_SINGLE("ALC Capture ZC Switch", WM8974_ALC2, 8, 1, 0),
|
||||
SOC_SINGLE("ALC Capture Hold", WM8974_ALC2, 4, 7, 0),
|
||||
SOC_SINGLE("ALC Capture Target", WM8974_ALC2, 0, 15, 0),
|
||||
|
||||
SOC_ENUM("ALC Capture Mode", wm8974_enum[13]),
|
||||
SOC_SINGLE("ALC Capture Decay", WM8974_ALC3, 4, 15, 0),
|
||||
SOC_SINGLE("ALC Capture Attack", WM8974_ALC3, 0, 15, 0),
|
||||
|
||||
SOC_SINGLE("ALC Capture Noise Gate Switch", WM8974_NGATE, 3, 1, 0),
|
||||
SOC_SINGLE("ALC Capture Noise Gate Threshold", WM8974_NGATE, 0, 7, 0),
|
||||
|
||||
SOC_SINGLE("Capture PGA ZC Switch", WM8974_INPPGA, 7, 1, 0),
|
||||
SOC_SINGLE_TLV("Capture PGA Volume", WM8974_INPPGA, 0, 63, 0, inpga_tlv),
|
||||
|
||||
SOC_SINGLE("Speaker Playback ZC Switch", WM8974_SPKVOL, 7, 1, 0),
|
||||
SOC_SINGLE("Speaker Playback Switch", WM8974_SPKVOL, 6, 1, 1),
|
||||
SOC_SINGLE_TLV("Speaker Playback Volume", WM8974_SPKVOL, 0, 63, 0, spk_tlv),
|
||||
|
||||
SOC_ENUM("Aux Mode", wm8974_auxmode),
|
||||
|
||||
SOC_SINGLE("Capture Boost(+20dB)", WM8974_ADCBOOST, 8, 1, 0),
|
||||
SOC_SINGLE("Mono Playback Switch", WM8974_MONOMIX, 6, 1, 1),
|
||||
};
|
||||
|
||||
/* Speaker Output Mixer */
|
||||
static const struct snd_kcontrol_new wm8974_speaker_mixer_controls[] = {
|
||||
SOC_DAPM_SINGLE("Line Bypass Switch", WM8974_SPKMIX, 1, 1, 0),
|
||||
SOC_DAPM_SINGLE("Aux Playback Switch", WM8974_SPKMIX, 5, 1, 0),
|
||||
SOC_DAPM_SINGLE("PCM Playback Switch", WM8974_SPKMIX, 0, 1, 1),
|
||||
};
|
||||
|
||||
/* Mono Output Mixer */
|
||||
static const struct snd_kcontrol_new wm8974_mono_mixer_controls[] = {
|
||||
SOC_DAPM_SINGLE("Line Bypass Switch", WM8974_MONOMIX, 1, 1, 0),
|
||||
SOC_DAPM_SINGLE("Aux Playback Switch", WM8974_MONOMIX, 2, 1, 0),
|
||||
SOC_DAPM_SINGLE("PCM Playback Switch", WM8974_MONOMIX, 0, 1, 0),
|
||||
};
|
||||
|
||||
/* Boost mixer */
|
||||
static const struct snd_kcontrol_new wm8974_boost_mixer[] = {
|
||||
SOC_DAPM_SINGLE("Aux Switch", WM8974_INPPGA, 6, 1, 0),
|
||||
};
|
||||
|
||||
/* Input PGA */
|
||||
static const struct snd_kcontrol_new wm8974_inpga[] = {
|
||||
SOC_DAPM_SINGLE("Aux Switch", WM8974_INPUT, 2, 1, 0),
|
||||
SOC_DAPM_SINGLE("MicN Switch", WM8974_INPUT, 1, 1, 0),
|
||||
SOC_DAPM_SINGLE("MicP Switch", WM8974_INPUT, 0, 1, 0),
|
||||
};
|
||||
|
||||
/* AUX Input boost vol */
|
||||
static const struct snd_kcontrol_new wm8974_aux_boost_controls =
|
||||
SOC_DAPM_SINGLE("Aux Volume", WM8974_ADCBOOST, 0, 7, 0);
|
||||
|
||||
/* Mic Input boost vol */
|
||||
static const struct snd_kcontrol_new wm8974_mic_boost_controls =
|
||||
SOC_DAPM_SINGLE("Mic Volume", WM8974_ADCBOOST, 4, 7, 0);
|
||||
|
||||
static const struct snd_soc_dapm_widget wm8974_dapm_widgets[] = {
|
||||
SND_SOC_DAPM_MIXER("Speaker Mixer", WM8974_POWER3, 2, 0,
|
||||
&wm8974_speaker_mixer_controls[0],
|
||||
ARRAY_SIZE(wm8974_speaker_mixer_controls)),
|
||||
SND_SOC_DAPM_MIXER("Mono Mixer", WM8974_POWER3, 3, 0,
|
||||
&wm8974_mono_mixer_controls[0],
|
||||
ARRAY_SIZE(wm8974_mono_mixer_controls)),
|
||||
SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM8974_POWER3, 0, 0),
|
||||
SND_SOC_DAPM_ADC("ADC", "HiFi Capture", WM8974_POWER2, 0, 0),
|
||||
SND_SOC_DAPM_PGA("Aux Input", WM8974_POWER1, 6, 0, NULL, 0),
|
||||
SND_SOC_DAPM_PGA("SpkN Out", WM8974_POWER3, 5, 0, NULL, 0),
|
||||
SND_SOC_DAPM_PGA("SpkP Out", WM8974_POWER3, 6, 0, NULL, 0),
|
||||
SND_SOC_DAPM_PGA("Mono Out", WM8974_POWER3, 7, 0, NULL, 0),
|
||||
|
||||
SND_SOC_DAPM_MIXER("Input PGA", WM8974_POWER2, 2, 0, wm8974_inpga,
|
||||
ARRAY_SIZE(wm8974_inpga)),
|
||||
SND_SOC_DAPM_MIXER("Boost Mixer", WM8974_POWER2, 4, 0,
|
||||
wm8974_boost_mixer, ARRAY_SIZE(wm8974_boost_mixer)),
|
||||
|
||||
SND_SOC_DAPM_MICBIAS("Mic Bias", WM8974_POWER1, 4, 0),
|
||||
|
||||
SND_SOC_DAPM_INPUT("MICN"),
|
||||
SND_SOC_DAPM_INPUT("MICP"),
|
||||
SND_SOC_DAPM_INPUT("AUX"),
|
||||
SND_SOC_DAPM_OUTPUT("MONOOUT"),
|
||||
SND_SOC_DAPM_OUTPUT("SPKOUTP"),
|
||||
SND_SOC_DAPM_OUTPUT("SPKOUTN"),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route audio_map[] = {
|
||||
/* Mono output mixer */
|
||||
{"Mono Mixer", "PCM Playback Switch", "DAC"},
|
||||
{"Mono Mixer", "Aux Playback Switch", "Aux Input"},
|
||||
{"Mono Mixer", "Line Bypass Switch", "Boost Mixer"},
|
||||
|
||||
/* Speaker output mixer */
|
||||
{"Speaker Mixer", "PCM Playback Switch", "DAC"},
|
||||
{"Speaker Mixer", "Aux Playback Switch", "Aux Input"},
|
||||
{"Speaker Mixer", "Line Bypass Switch", "Boost Mixer"},
|
||||
|
||||
/* Outputs */
|
||||
{"Mono Out", NULL, "Mono Mixer"},
|
||||
{"MONOOUT", NULL, "Mono Out"},
|
||||
{"SpkN Out", NULL, "Speaker Mixer"},
|
||||
{"SpkP Out", NULL, "Speaker Mixer"},
|
||||
{"SPKOUTN", NULL, "SpkN Out"},
|
||||
{"SPKOUTP", NULL, "SpkP Out"},
|
||||
|
||||
/* Boost Mixer */
|
||||
{"ADC", NULL, "Boost Mixer"},
|
||||
{"Boost Mixer", "Aux Switch", "Aux Input"},
|
||||
{"Boost Mixer", NULL, "Input PGA"},
|
||||
{"Boost Mixer", NULL, "MICP"},
|
||||
|
||||
/* Input PGA */
|
||||
{"Input PGA", "Aux Switch", "Aux Input"},
|
||||
{"Input PGA", "MicN Switch", "MICN"},
|
||||
{"Input PGA", "MicP Switch", "MICP"},
|
||||
|
||||
/* Inputs */
|
||||
{"Aux Input", NULL, "AUX"},
|
||||
};
|
||||
|
||||
static int wm8974_add_widgets(struct snd_soc_codec *codec)
|
||||
{
|
||||
snd_soc_dapm_new_controls(codec, wm8974_dapm_widgets,
|
||||
ARRAY_SIZE(wm8974_dapm_widgets));
|
||||
|
||||
snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
|
||||
|
||||
snd_soc_dapm_new_widgets(codec);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct pll_ {
|
||||
unsigned int pre_div:4; /* prescale - 1 */
|
||||
unsigned int n:4;
|
||||
unsigned int k;
|
||||
};
|
||||
|
||||
static struct pll_ pll_div;
|
||||
|
||||
/* The size in bits of the pll divide multiplied by 10
|
||||
* to allow rounding later */
|
||||
#define FIXED_PLL_SIZE ((1 << 24) * 10)
|
||||
|
||||
static void pll_factors(unsigned int target, unsigned int source)
|
||||
{
|
||||
unsigned long long Kpart;
|
||||
unsigned int K, Ndiv, Nmod;
|
||||
|
||||
Ndiv = target / source;
|
||||
if (Ndiv < 6) {
|
||||
source >>= 1;
|
||||
pll_div.pre_div = 1;
|
||||
Ndiv = target / source;
|
||||
} else
|
||||
pll_div.pre_div = 0;
|
||||
|
||||
if ((Ndiv < 6) || (Ndiv > 12))
|
||||
printk(KERN_WARNING
|
||||
"WM8974 N value %u outwith recommended range!\n",
|
||||
Ndiv);
|
||||
|
||||
pll_div.n = Ndiv;
|
||||
Nmod = target % source;
|
||||
Kpart = FIXED_PLL_SIZE * (long long)Nmod;
|
||||
|
||||
do_div(Kpart, source);
|
||||
|
||||
K = Kpart & 0xFFFFFFFF;
|
||||
|
||||
/* Check if we need to round */
|
||||
if ((K % 10) >= 5)
|
||||
K += 5;
|
||||
|
||||
/* Move down to proper range now rounding is done */
|
||||
K /= 10;
|
||||
|
||||
pll_div.k = K;
|
||||
}
|
||||
|
||||
static int wm8974_set_dai_pll(struct snd_soc_dai *codec_dai,
|
||||
int pll_id, unsigned int freq_in, unsigned int freq_out)
|
||||
{
|
||||
struct snd_soc_codec *codec = codec_dai->codec;
|
||||
u16 reg;
|
||||
|
||||
if (freq_in == 0 || freq_out == 0) {
|
||||
/* Clock CODEC directly from MCLK */
|
||||
reg = snd_soc_read(codec, WM8974_CLOCK);
|
||||
snd_soc_write(codec, WM8974_CLOCK, reg & 0x0ff);
|
||||
|
||||
/* Turn off PLL */
|
||||
reg = snd_soc_read(codec, WM8974_POWER1);
|
||||
snd_soc_write(codec, WM8974_POWER1, reg & 0x1df);
|
||||
return 0;
|
||||
}
|
||||
|
||||
pll_factors(freq_out*4, freq_in);
|
||||
|
||||
snd_soc_write(codec, WM8974_PLLN, (pll_div.pre_div << 4) | pll_div.n);
|
||||
snd_soc_write(codec, WM8974_PLLK1, pll_div.k >> 18);
|
||||
snd_soc_write(codec, WM8974_PLLK2, (pll_div.k >> 9) & 0x1ff);
|
||||
snd_soc_write(codec, WM8974_PLLK3, pll_div.k & 0x1ff);
|
||||
reg = snd_soc_read(codec, WM8974_POWER1);
|
||||
snd_soc_write(codec, WM8974_POWER1, reg | 0x020);
|
||||
|
||||
/* Run CODEC from PLL instead of MCLK */
|
||||
reg = snd_soc_read(codec, WM8974_CLOCK);
|
||||
snd_soc_write(codec, WM8974_CLOCK, reg | 0x100);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Configure WM8974 clock dividers.
|
||||
*/
|
||||
static int wm8974_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
|
||||
int div_id, int div)
|
||||
{
|
||||
struct snd_soc_codec *codec = codec_dai->codec;
|
||||
u16 reg;
|
||||
|
||||
switch (div_id) {
|
||||
case WM8974_OPCLKDIV:
|
||||
reg = snd_soc_read(codec, WM8974_GPIO) & 0x1cf;
|
||||
snd_soc_write(codec, WM8974_GPIO, reg | div);
|
||||
break;
|
||||
case WM8974_MCLKDIV:
|
||||
reg = snd_soc_read(codec, WM8974_CLOCK) & 0x11f;
|
||||
snd_soc_write(codec, WM8974_CLOCK, reg | div);
|
||||
break;
|
||||
case WM8974_ADCCLK:
|
||||
reg = snd_soc_read(codec, WM8974_ADC) & 0x1f7;
|
||||
snd_soc_write(codec, WM8974_ADC, reg | div);
|
||||
break;
|
||||
case WM8974_DACCLK:
|
||||
reg = snd_soc_read(codec, WM8974_DAC) & 0x1f7;
|
||||
snd_soc_write(codec, WM8974_DAC, reg | div);
|
||||
break;
|
||||
case WM8974_BCLKDIV:
|
||||
reg = snd_soc_read(codec, WM8974_CLOCK) & 0x1e3;
|
||||
snd_soc_write(codec, WM8974_CLOCK, reg | div);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wm8974_set_dai_fmt(struct snd_soc_dai *codec_dai,
|
||||
unsigned int fmt)
|
||||
{
|
||||
struct snd_soc_codec *codec = codec_dai->codec;
|
||||
u16 iface = 0;
|
||||
u16 clk = snd_soc_read(codec, WM8974_CLOCK) & 0x1fe;
|
||||
|
||||
/* set master/slave audio interface */
|
||||
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
|
||||
case SND_SOC_DAIFMT_CBM_CFM:
|
||||
clk |= 0x0001;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_CBS_CFS:
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* interface format */
|
||||
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
|
||||
case SND_SOC_DAIFMT_I2S:
|
||||
iface |= 0x0010;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_RIGHT_J:
|
||||
break;
|
||||
case SND_SOC_DAIFMT_LEFT_J:
|
||||
iface |= 0x0008;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_DSP_A:
|
||||
iface |= 0x00018;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* clock inversion */
|
||||
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
|
||||
case SND_SOC_DAIFMT_NB_NF:
|
||||
break;
|
||||
case SND_SOC_DAIFMT_IB_IF:
|
||||
iface |= 0x0180;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_IB_NF:
|
||||
iface |= 0x0100;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_NB_IF:
|
||||
iface |= 0x0080;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
snd_soc_write(codec, WM8974_IFACE, iface);
|
||||
snd_soc_write(codec, WM8974_CLOCK, clk);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wm8974_pcm_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct snd_soc_codec *codec = dai->codec;
|
||||
u16 iface = snd_soc_read(codec, WM8974_IFACE) & 0x19f;
|
||||
u16 adn = snd_soc_read(codec, WM8974_ADD) & 0x1f1;
|
||||
|
||||
/* bit size */
|
||||
switch (params_format(params)) {
|
||||
case SNDRV_PCM_FORMAT_S16_LE:
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S20_3LE:
|
||||
iface |= 0x0020;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S24_LE:
|
||||
iface |= 0x0040;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S32_LE:
|
||||
iface |= 0x0060;
|
||||
break;
|
||||
}
|
||||
|
||||
/* filter coefficient */
|
||||
switch (params_rate(params)) {
|
||||
case SNDRV_PCM_RATE_8000:
|
||||
adn |= 0x5 << 1;
|
||||
break;
|
||||
case SNDRV_PCM_RATE_11025:
|
||||
adn |= 0x4 << 1;
|
||||
break;
|
||||
case SNDRV_PCM_RATE_16000:
|
||||
adn |= 0x3 << 1;
|
||||
break;
|
||||
case SNDRV_PCM_RATE_22050:
|
||||
adn |= 0x2 << 1;
|
||||
break;
|
||||
case SNDRV_PCM_RATE_32000:
|
||||
adn |= 0x1 << 1;
|
||||
break;
|
||||
case SNDRV_PCM_RATE_44100:
|
||||
case SNDRV_PCM_RATE_48000:
|
||||
break;
|
||||
}
|
||||
|
||||
snd_soc_write(codec, WM8974_IFACE, iface);
|
||||
snd_soc_write(codec, WM8974_ADD, adn);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wm8974_mute(struct snd_soc_dai *dai, int mute)
|
||||
{
|
||||
struct snd_soc_codec *codec = dai->codec;
|
||||
u16 mute_reg = snd_soc_read(codec, WM8974_DAC) & 0xffbf;
|
||||
|
||||
if (mute)
|
||||
snd_soc_write(codec, WM8974_DAC, mute_reg | 0x40);
|
||||
else
|
||||
snd_soc_write(codec, WM8974_DAC, mute_reg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* liam need to make this lower power with dapm */
|
||||
static int wm8974_set_bias_level(struct snd_soc_codec *codec,
|
||||
enum snd_soc_bias_level level)
|
||||
{
|
||||
u16 power1 = snd_soc_read(codec, WM8974_POWER1) & ~0x3;
|
||||
|
||||
switch (level) {
|
||||
case SND_SOC_BIAS_ON:
|
||||
case SND_SOC_BIAS_PREPARE:
|
||||
power1 |= 0x1; /* VMID 50k */
|
||||
snd_soc_write(codec, WM8974_POWER1, power1);
|
||||
break;
|
||||
|
||||
case SND_SOC_BIAS_STANDBY:
|
||||
power1 |= WM8974_POWER1_BIASEN | WM8974_POWER1_BUFIOEN;
|
||||
|
||||
if (codec->bias_level == SND_SOC_BIAS_OFF) {
|
||||
/* Initial cap charge at VMID 5k */
|
||||
snd_soc_write(codec, WM8974_POWER1, power1 | 0x3);
|
||||
mdelay(100);
|
||||
}
|
||||
|
||||
power1 |= 0x2; /* VMID 500k */
|
||||
snd_soc_write(codec, WM8974_POWER1, power1);
|
||||
break;
|
||||
|
||||
case SND_SOC_BIAS_OFF:
|
||||
snd_soc_write(codec, WM8974_POWER1, 0);
|
||||
snd_soc_write(codec, WM8974_POWER2, 0);
|
||||
snd_soc_write(codec, WM8974_POWER3, 0);
|
||||
break;
|
||||
}
|
||||
|
||||
codec->bias_level = level;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define WM8974_RATES (SNDRV_PCM_RATE_8000_48000)
|
||||
|
||||
#define WM8974_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
|
||||
SNDRV_PCM_FMTBIT_S24_LE)
|
||||
|
||||
static struct snd_soc_dai_ops wm8974_ops = {
|
||||
.hw_params = wm8974_pcm_hw_params,
|
||||
.digital_mute = wm8974_mute,
|
||||
.set_fmt = wm8974_set_dai_fmt,
|
||||
.set_clkdiv = wm8974_set_dai_clkdiv,
|
||||
.set_pll = wm8974_set_dai_pll,
|
||||
};
|
||||
|
||||
struct snd_soc_dai wm8974_dai = {
|
||||
.name = "WM8974 HiFi",
|
||||
.playback = {
|
||||
.stream_name = "Playback",
|
||||
.channels_min = 1,
|
||||
.channels_max = 2, /* Only 1 channel of data */
|
||||
.rates = WM8974_RATES,
|
||||
.formats = WM8974_FORMATS,},
|
||||
.capture = {
|
||||
.stream_name = "Capture",
|
||||
.channels_min = 1,
|
||||
.channels_max = 2, /* Only 1 channel of data */
|
||||
.rates = WM8974_RATES,
|
||||
.formats = WM8974_FORMATS,},
|
||||
.ops = &wm8974_ops,
|
||||
.symmetric_rates = 1,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(wm8974_dai);
|
||||
|
||||
static int wm8974_suspend(struct platform_device *pdev, pm_message_t state)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
|
||||
wm8974_set_bias_level(codec, SND_SOC_BIAS_OFF);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wm8974_resume(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
int i;
|
||||
u8 data[2];
|
||||
u16 *cache = codec->reg_cache;
|
||||
|
||||
/* Sync reg_cache with the hardware */
|
||||
for (i = 0; i < ARRAY_SIZE(wm8974_reg); i++) {
|
||||
data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001);
|
||||
data[1] = cache[i] & 0x00ff;
|
||||
codec->hw_write(codec->control_data, data, 2);
|
||||
}
|
||||
wm8974_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
|
||||
wm8974_set_bias_level(codec, codec->suspend_bias_level);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wm8974_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
struct snd_soc_codec *codec;
|
||||
int ret = 0;
|
||||
|
||||
if (wm8974_codec == NULL) {
|
||||
dev_err(&pdev->dev, "Codec device not registered\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
socdev->card->codec = wm8974_codec;
|
||||
codec = wm8974_codec;
|
||||
|
||||
/* register pcms */
|
||||
ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
|
||||
if (ret < 0) {
|
||||
dev_err(codec->dev, "failed to create pcms: %d\n", ret);
|
||||
goto pcm_err;
|
||||
}
|
||||
|
||||
snd_soc_add_controls(codec, wm8974_snd_controls,
|
||||
ARRAY_SIZE(wm8974_snd_controls));
|
||||
wm8974_add_widgets(codec);
|
||||
ret = snd_soc_init_card(socdev);
|
||||
if (ret < 0) {
|
||||
dev_err(codec->dev, "failed to register card: %d\n", ret);
|
||||
goto card_err;
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
card_err:
|
||||
snd_soc_free_pcms(socdev);
|
||||
snd_soc_dapm_free(socdev);
|
||||
pcm_err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* power down chip */
|
||||
static int wm8974_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
|
||||
snd_soc_free_pcms(socdev);
|
||||
snd_soc_dapm_free(socdev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct snd_soc_codec_device soc_codec_dev_wm8974 = {
|
||||
.probe = wm8974_probe,
|
||||
.remove = wm8974_remove,
|
||||
.suspend = wm8974_suspend,
|
||||
.resume = wm8974_resume,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(soc_codec_dev_wm8974);
|
||||
|
||||
static __devinit int wm8974_register(struct wm8974_priv *wm8974)
|
||||
{
|
||||
int ret;
|
||||
struct snd_soc_codec *codec = &wm8974->codec;
|
||||
|
||||
if (wm8974_codec) {
|
||||
dev_err(codec->dev, "Another WM8974 is registered\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_init(&codec->mutex);
|
||||
INIT_LIST_HEAD(&codec->dapm_widgets);
|
||||
INIT_LIST_HEAD(&codec->dapm_paths);
|
||||
|
||||
codec->private_data = wm8974;
|
||||
codec->name = "WM8974";
|
||||
codec->owner = THIS_MODULE;
|
||||
codec->bias_level = SND_SOC_BIAS_OFF;
|
||||
codec->set_bias_level = wm8974_set_bias_level;
|
||||
codec->dai = &wm8974_dai;
|
||||
codec->num_dai = 1;
|
||||
codec->reg_cache_size = WM8974_CACHEREGNUM;
|
||||
codec->reg_cache = &wm8974->reg_cache;
|
||||
|
||||
ret = snd_soc_codec_set_cache_io(codec, 7, 9, SND_SOC_I2C);
|
||||
if (ret < 0) {
|
||||
dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
|
||||
goto err;
|
||||
}
|
||||
|
||||
memcpy(codec->reg_cache, wm8974_reg, sizeof(wm8974_reg));
|
||||
|
||||
ret = wm8974_reset(codec);
|
||||
if (ret < 0) {
|
||||
dev_err(codec->dev, "Failed to issue reset\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
wm8974_dai.dev = codec->dev;
|
||||
|
||||
wm8974_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
|
||||
|
||||
wm8974_codec = codec;
|
||||
|
||||
ret = snd_soc_register_codec(codec);
|
||||
if (ret != 0) {
|
||||
dev_err(codec->dev, "Failed to register codec: %d\n", ret);
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = snd_soc_register_dai(&wm8974_dai);
|
||||
if (ret != 0) {
|
||||
dev_err(codec->dev, "Failed to register DAI: %d\n", ret);
|
||||
goto err_codec;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_codec:
|
||||
snd_soc_unregister_codec(codec);
|
||||
err:
|
||||
kfree(wm8974);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static __devexit void wm8974_unregister(struct wm8974_priv *wm8974)
|
||||
{
|
||||
wm8974_set_bias_level(&wm8974->codec, SND_SOC_BIAS_OFF);
|
||||
snd_soc_unregister_dai(&wm8974_dai);
|
||||
snd_soc_unregister_codec(&wm8974->codec);
|
||||
kfree(wm8974);
|
||||
wm8974_codec = NULL;
|
||||
}
|
||||
|
||||
static __devinit int wm8974_i2c_probe(struct i2c_client *i2c,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct wm8974_priv *wm8974;
|
||||
struct snd_soc_codec *codec;
|
||||
|
||||
wm8974 = kzalloc(sizeof(struct wm8974_priv), GFP_KERNEL);
|
||||
if (wm8974 == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
codec = &wm8974->codec;
|
||||
codec->hw_write = (hw_write_t)i2c_master_send;
|
||||
|
||||
i2c_set_clientdata(i2c, wm8974);
|
||||
codec->control_data = i2c;
|
||||
|
||||
codec->dev = &i2c->dev;
|
||||
|
||||
return wm8974_register(wm8974);
|
||||
}
|
||||
|
||||
static __devexit int wm8974_i2c_remove(struct i2c_client *client)
|
||||
{
|
||||
struct wm8974_priv *wm8974 = i2c_get_clientdata(client);
|
||||
wm8974_unregister(wm8974);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id wm8974_i2c_id[] = {
|
||||
{ "wm8974", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, wm8974_i2c_id);
|
||||
|
||||
static struct i2c_driver wm8974_i2c_driver = {
|
||||
.driver = {
|
||||
.name = "WM8974",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = wm8974_i2c_probe,
|
||||
.remove = __devexit_p(wm8974_i2c_remove),
|
||||
.id_table = wm8974_i2c_id,
|
||||
};
|
||||
|
||||
static int __init wm8974_modinit(void)
|
||||
{
|
||||
return i2c_add_driver(&wm8974_i2c_driver);
|
||||
}
|
||||
module_init(wm8974_modinit);
|
||||
|
||||
static void __exit wm8974_exit(void)
|
||||
{
|
||||
i2c_del_driver(&wm8974_i2c_driver);
|
||||
}
|
||||
module_exit(wm8974_exit);
|
||||
|
||||
MODULE_DESCRIPTION("ASoC WM8974 driver");
|
||||
MODULE_AUTHOR("Liam Girdwood");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* wm8974.h -- WM8974 Soc Audio driver
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef _WM8974_H
|
||||
#define _WM8974_H
|
||||
|
||||
/* WM8974 register space */
|
||||
|
||||
#define WM8974_RESET 0x0
|
||||
#define WM8974_POWER1 0x1
|
||||
#define WM8974_POWER2 0x2
|
||||
#define WM8974_POWER3 0x3
|
||||
#define WM8974_IFACE 0x4
|
||||
#define WM8974_COMP 0x5
|
||||
#define WM8974_CLOCK 0x6
|
||||
#define WM8974_ADD 0x7
|
||||
#define WM8974_GPIO 0x8
|
||||
#define WM8974_DAC 0xa
|
||||
#define WM8974_DACVOL 0xb
|
||||
#define WM8974_ADC 0xe
|
||||
#define WM8974_ADCVOL 0xf
|
||||
#define WM8974_EQ1 0x12
|
||||
#define WM8974_EQ2 0x13
|
||||
#define WM8974_EQ3 0x14
|
||||
#define WM8974_EQ4 0x15
|
||||
#define WM8974_EQ5 0x16
|
||||
#define WM8974_DACLIM1 0x18
|
||||
#define WM8974_DACLIM2 0x19
|
||||
#define WM8974_NOTCH1 0x1b
|
||||
#define WM8974_NOTCH2 0x1c
|
||||
#define WM8974_NOTCH3 0x1d
|
||||
#define WM8974_NOTCH4 0x1e
|
||||
#define WM8974_ALC1 0x20
|
||||
#define WM8974_ALC2 0x21
|
||||
#define WM8974_ALC3 0x22
|
||||
#define WM8974_NGATE 0x23
|
||||
#define WM8974_PLLN 0x24
|
||||
#define WM8974_PLLK1 0x25
|
||||
#define WM8974_PLLK2 0x26
|
||||
#define WM8974_PLLK3 0x27
|
||||
#define WM8974_ATTEN 0x28
|
||||
#define WM8974_INPUT 0x2c
|
||||
#define WM8974_INPPGA 0x2d
|
||||
#define WM8974_ADCBOOST 0x2f
|
||||
#define WM8974_OUTPUT 0x31
|
||||
#define WM8974_SPKMIX 0x32
|
||||
#define WM8974_SPKVOL 0x36
|
||||
#define WM8974_MONOMIX 0x38
|
||||
|
||||
#define WM8974_CACHEREGNUM 57
|
||||
|
||||
/* Clock divider Id's */
|
||||
#define WM8974_OPCLKDIV 0
|
||||
#define WM8974_MCLKDIV 1
|
||||
#define WM8974_ADCCLK 2
|
||||
#define WM8974_DACCLK 3
|
||||
#define WM8974_BCLKDIV 4
|
||||
|
||||
/* DAC clock dividers */
|
||||
#define WM8974_DACCLK_F2 (1 << 3)
|
||||
#define WM8974_DACCLK_F4 (0 << 3)
|
||||
|
||||
/* ADC clock dividers */
|
||||
#define WM8974_ADCCLK_F2 (1 << 3)
|
||||
#define WM8974_ADCCLK_F4 (0 << 3)
|
||||
|
||||
/* PLL Out dividers */
|
||||
#define WM8974_OPCLKDIV_1 (0 << 4)
|
||||
#define WM8974_OPCLKDIV_2 (1 << 4)
|
||||
#define WM8974_OPCLKDIV_3 (2 << 4)
|
||||
#define WM8974_OPCLKDIV_4 (3 << 4)
|
||||
|
||||
/* BCLK clock dividers */
|
||||
#define WM8974_BCLKDIV_1 (0 << 2)
|
||||
#define WM8974_BCLKDIV_2 (1 << 2)
|
||||
#define WM8974_BCLKDIV_4 (2 << 2)
|
||||
#define WM8974_BCLKDIV_8 (3 << 2)
|
||||
#define WM8974_BCLKDIV_16 (4 << 2)
|
||||
#define WM8974_BCLKDIV_32 (5 << 2)
|
||||
|
||||
/* MCLK clock dividers */
|
||||
#define WM8974_MCLKDIV_1 (0 << 5)
|
||||
#define WM8974_MCLKDIV_1_5 (1 << 5)
|
||||
#define WM8974_MCLKDIV_2 (2 << 5)
|
||||
#define WM8974_MCLKDIV_3 (3 << 5)
|
||||
#define WM8974_MCLKDIV_4 (4 << 5)
|
||||
#define WM8974_MCLKDIV_6 (5 << 5)
|
||||
#define WM8974_MCLKDIV_8 (6 << 5)
|
||||
#define WM8974_MCLKDIV_12 (7 << 5)
|
||||
|
||||
extern struct snd_soc_dai wm8974_dai;
|
||||
extern struct snd_soc_codec_device soc_codec_dev_wm8974;
|
||||
|
||||
#endif
|
|
@ -57,50 +57,7 @@ struct wm8988_priv {
|
|||
};
|
||||
|
||||
|
||||
/*
|
||||
* read wm8988 register cache
|
||||
*/
|
||||
static inline unsigned int wm8988_read_reg_cache(struct snd_soc_codec *codec,
|
||||
unsigned int reg)
|
||||
{
|
||||
u16 *cache = codec->reg_cache;
|
||||
if (reg > WM8988_NUM_REG)
|
||||
return -1;
|
||||
return cache[reg];
|
||||
}
|
||||
|
||||
/*
|
||||
* write wm8988 register cache
|
||||
*/
|
||||
static inline void wm8988_write_reg_cache(struct snd_soc_codec *codec,
|
||||
unsigned int reg, unsigned int value)
|
||||
{
|
||||
u16 *cache = codec->reg_cache;
|
||||
if (reg > WM8988_NUM_REG)
|
||||
return;
|
||||
cache[reg] = value;
|
||||
}
|
||||
|
||||
static int wm8988_write(struct snd_soc_codec *codec, unsigned int reg,
|
||||
unsigned int value)
|
||||
{
|
||||
u8 data[2];
|
||||
|
||||
/* data is
|
||||
* D15..D9 WM8753 register offset
|
||||
* D8...D0 register data
|
||||
*/
|
||||
data[0] = (reg << 1) | ((value >> 8) & 0x0001);
|
||||
data[1] = value & 0x00ff;
|
||||
|
||||
wm8988_write_reg_cache(codec, reg, value);
|
||||
if (codec->hw_write(codec->control_data, data, 2) == 2)
|
||||
return 0;
|
||||
else
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
#define wm8988_reset(c) wm8988_write(c, WM8988_RESET, 0)
|
||||
#define wm8988_reset(c) snd_soc_write(c, WM8988_RESET, 0)
|
||||
|
||||
/*
|
||||
* WM8988 Controls
|
||||
|
@ -226,15 +183,15 @@ static int wm8988_lrc_control(struct snd_soc_dapm_widget *w,
|
|||
struct snd_kcontrol *kcontrol, int event)
|
||||
{
|
||||
struct snd_soc_codec *codec = w->codec;
|
||||
u16 adctl2 = wm8988_read_reg_cache(codec, WM8988_ADCTL2);
|
||||
u16 adctl2 = snd_soc_read(codec, WM8988_ADCTL2);
|
||||
|
||||
/* Use the DAC to gate LRC if active, otherwise use ADC */
|
||||
if (wm8988_read_reg_cache(codec, WM8988_PWR2) & 0x180)
|
||||
if (snd_soc_read(codec, WM8988_PWR2) & 0x180)
|
||||
adctl2 &= ~0x4;
|
||||
else
|
||||
adctl2 |= 0x4;
|
||||
|
||||
return wm8988_write(codec, WM8988_ADCTL2, adctl2);
|
||||
return snd_soc_write(codec, WM8988_ADCTL2, adctl2);
|
||||
}
|
||||
|
||||
static const char *wm8988_line_texts[] = {
|
||||
|
@ -619,7 +576,7 @@ static int wm8988_set_dai_fmt(struct snd_soc_dai *codec_dai,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
wm8988_write(codec, WM8988_IFACE, iface);
|
||||
snd_soc_write(codec, WM8988_IFACE, iface);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -653,8 +610,8 @@ static int wm8988_pcm_hw_params(struct snd_pcm_substream *substream,
|
|||
struct snd_soc_device *socdev = rtd->socdev;
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
struct wm8988_priv *wm8988 = codec->private_data;
|
||||
u16 iface = wm8988_read_reg_cache(codec, WM8988_IFACE) & 0x1f3;
|
||||
u16 srate = wm8988_read_reg_cache(codec, WM8988_SRATE) & 0x180;
|
||||
u16 iface = snd_soc_read(codec, WM8988_IFACE) & 0x1f3;
|
||||
u16 srate = snd_soc_read(codec, WM8988_SRATE) & 0x180;
|
||||
int coeff;
|
||||
|
||||
coeff = get_coeff(wm8988->sysclk, params_rate(params));
|
||||
|
@ -685,9 +642,9 @@ static int wm8988_pcm_hw_params(struct snd_pcm_substream *substream,
|
|||
}
|
||||
|
||||
/* set iface & srate */
|
||||
wm8988_write(codec, WM8988_IFACE, iface);
|
||||
snd_soc_write(codec, WM8988_IFACE, iface);
|
||||
if (coeff >= 0)
|
||||
wm8988_write(codec, WM8988_SRATE, srate |
|
||||
snd_soc_write(codec, WM8988_SRATE, srate |
|
||||
(coeff_div[coeff].sr << 1) | coeff_div[coeff].usb);
|
||||
|
||||
return 0;
|
||||
|
@ -696,19 +653,19 @@ static int wm8988_pcm_hw_params(struct snd_pcm_substream *substream,
|
|||
static int wm8988_mute(struct snd_soc_dai *dai, int mute)
|
||||
{
|
||||
struct snd_soc_codec *codec = dai->codec;
|
||||
u16 mute_reg = wm8988_read_reg_cache(codec, WM8988_ADCDAC) & 0xfff7;
|
||||
u16 mute_reg = snd_soc_read(codec, WM8988_ADCDAC) & 0xfff7;
|
||||
|
||||
if (mute)
|
||||
wm8988_write(codec, WM8988_ADCDAC, mute_reg | 0x8);
|
||||
snd_soc_write(codec, WM8988_ADCDAC, mute_reg | 0x8);
|
||||
else
|
||||
wm8988_write(codec, WM8988_ADCDAC, mute_reg);
|
||||
snd_soc_write(codec, WM8988_ADCDAC, mute_reg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wm8988_set_bias_level(struct snd_soc_codec *codec,
|
||||
enum snd_soc_bias_level level)
|
||||
{
|
||||
u16 pwr_reg = wm8988_read_reg_cache(codec, WM8988_PWR1) & ~0x1c1;
|
||||
u16 pwr_reg = snd_soc_read(codec, WM8988_PWR1) & ~0x1c1;
|
||||
|
||||
switch (level) {
|
||||
case SND_SOC_BIAS_ON:
|
||||
|
@ -716,24 +673,24 @@ static int wm8988_set_bias_level(struct snd_soc_codec *codec,
|
|||
|
||||
case SND_SOC_BIAS_PREPARE:
|
||||
/* VREF, VMID=2x50k, digital enabled */
|
||||
wm8988_write(codec, WM8988_PWR1, pwr_reg | 0x00c0);
|
||||
snd_soc_write(codec, WM8988_PWR1, pwr_reg | 0x00c0);
|
||||
break;
|
||||
|
||||
case SND_SOC_BIAS_STANDBY:
|
||||
if (codec->bias_level == SND_SOC_BIAS_OFF) {
|
||||
/* VREF, VMID=2x5k */
|
||||
wm8988_write(codec, WM8988_PWR1, pwr_reg | 0x1c1);
|
||||
snd_soc_write(codec, WM8988_PWR1, pwr_reg | 0x1c1);
|
||||
|
||||
/* Charge caps */
|
||||
msleep(100);
|
||||
}
|
||||
|
||||
/* VREF, VMID=2*500k, digital stopped */
|
||||
wm8988_write(codec, WM8988_PWR1, pwr_reg | 0x0141);
|
||||
snd_soc_write(codec, WM8988_PWR1, pwr_reg | 0x0141);
|
||||
break;
|
||||
|
||||
case SND_SOC_BIAS_OFF:
|
||||
wm8988_write(codec, WM8988_PWR1, 0x0000);
|
||||
snd_soc_write(codec, WM8988_PWR1, 0x0000);
|
||||
break;
|
||||
}
|
||||
codec->bias_level = level;
|
||||
|
@ -868,7 +825,8 @@ struct snd_soc_codec_device soc_codec_dev_wm8988 = {
|
|||
};
|
||||
EXPORT_SYMBOL_GPL(soc_codec_dev_wm8988);
|
||||
|
||||
static int wm8988_register(struct wm8988_priv *wm8988)
|
||||
static int wm8988_register(struct wm8988_priv *wm8988,
|
||||
enum snd_soc_control_type control)
|
||||
{
|
||||
struct snd_soc_codec *codec = &wm8988->codec;
|
||||
int ret;
|
||||
|
@ -887,8 +845,6 @@ static int wm8988_register(struct wm8988_priv *wm8988)
|
|||
codec->private_data = wm8988;
|
||||
codec->name = "WM8988";
|
||||
codec->owner = THIS_MODULE;
|
||||
codec->read = wm8988_read_reg_cache;
|
||||
codec->write = wm8988_write;
|
||||
codec->dai = &wm8988_dai;
|
||||
codec->num_dai = 1;
|
||||
codec->reg_cache_size = ARRAY_SIZE(wm8988->reg_cache);
|
||||
|
@ -899,23 +855,29 @@ static int wm8988_register(struct wm8988_priv *wm8988)
|
|||
memcpy(codec->reg_cache, wm8988_reg,
|
||||
sizeof(wm8988_reg));
|
||||
|
||||
ret = snd_soc_codec_set_cache_io(codec, 7, 9, control);
|
||||
if (ret < 0) {
|
||||
dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = wm8988_reset(codec);
|
||||
if (ret < 0) {
|
||||
dev_err(codec->dev, "Failed to issue reset\n");
|
||||
return ret;
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* set the update bits (we always update left then right) */
|
||||
reg = wm8988_read_reg_cache(codec, WM8988_RADC);
|
||||
wm8988_write(codec, WM8988_RADC, reg | 0x100);
|
||||
reg = wm8988_read_reg_cache(codec, WM8988_RDAC);
|
||||
wm8988_write(codec, WM8988_RDAC, reg | 0x0100);
|
||||
reg = wm8988_read_reg_cache(codec, WM8988_ROUT1V);
|
||||
wm8988_write(codec, WM8988_ROUT1V, reg | 0x0100);
|
||||
reg = wm8988_read_reg_cache(codec, WM8988_ROUT2V);
|
||||
wm8988_write(codec, WM8988_ROUT2V, reg | 0x0100);
|
||||
reg = wm8988_read_reg_cache(codec, WM8988_RINVOL);
|
||||
wm8988_write(codec, WM8988_RINVOL, reg | 0x0100);
|
||||
reg = snd_soc_read(codec, WM8988_RADC);
|
||||
snd_soc_write(codec, WM8988_RADC, reg | 0x100);
|
||||
reg = snd_soc_read(codec, WM8988_RDAC);
|
||||
snd_soc_write(codec, WM8988_RDAC, reg | 0x0100);
|
||||
reg = snd_soc_read(codec, WM8988_ROUT1V);
|
||||
snd_soc_write(codec, WM8988_ROUT1V, reg | 0x0100);
|
||||
reg = snd_soc_read(codec, WM8988_ROUT2V);
|
||||
snd_soc_write(codec, WM8988_ROUT2V, reg | 0x0100);
|
||||
reg = snd_soc_read(codec, WM8988_RINVOL);
|
||||
snd_soc_write(codec, WM8988_RINVOL, reg | 0x0100);
|
||||
|
||||
wm8988_set_bias_level(&wm8988->codec, SND_SOC_BIAS_STANDBY);
|
||||
|
||||
|
@ -926,18 +888,20 @@ static int wm8988_register(struct wm8988_priv *wm8988)
|
|||
ret = snd_soc_register_codec(codec);
|
||||
if (ret != 0) {
|
||||
dev_err(codec->dev, "Failed to register codec: %d\n", ret);
|
||||
return ret;
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = snd_soc_register_dai(&wm8988_dai);
|
||||
if (ret != 0) {
|
||||
dev_err(codec->dev, "Failed to register DAI: %d\n", ret);
|
||||
snd_soc_unregister_codec(codec);
|
||||
return ret;
|
||||
goto err_codec;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_codec:
|
||||
snd_soc_unregister_codec(codec);
|
||||
err:
|
||||
kfree(wm8988);
|
||||
return ret;
|
||||
|
@ -964,14 +928,13 @@ static int wm8988_i2c_probe(struct i2c_client *i2c,
|
|||
return -ENOMEM;
|
||||
|
||||
codec = &wm8988->codec;
|
||||
codec->hw_write = (hw_write_t)i2c_master_send;
|
||||
|
||||
i2c_set_clientdata(i2c, wm8988);
|
||||
codec->control_data = i2c;
|
||||
|
||||
codec->dev = &i2c->dev;
|
||||
|
||||
return wm8988_register(wm8988);
|
||||
return wm8988_register(wm8988, SND_SOC_I2C);
|
||||
}
|
||||
|
||||
static int wm8988_i2c_remove(struct i2c_client *client)
|
||||
|
@ -981,6 +944,21 @@ static int wm8988_i2c_remove(struct i2c_client *client)
|
|||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int wm8988_i2c_suspend(struct i2c_client *client, pm_message_t msg)
|
||||
{
|
||||
return snd_soc_suspend_device(&client->dev);
|
||||
}
|
||||
|
||||
static int wm8988_i2c_resume(struct i2c_client *client)
|
||||
{
|
||||
return snd_soc_resume_device(&client->dev);
|
||||
}
|
||||
#else
|
||||
#define wm8988_i2c_suspend NULL
|
||||
#define wm8988_i2c_resume NULL
|
||||
#endif
|
||||
|
||||
static const struct i2c_device_id wm8988_i2c_id[] = {
|
||||
{ "wm8988", 0 },
|
||||
{ }
|
||||
|
@ -994,35 +972,13 @@ static struct i2c_driver wm8988_i2c_driver = {
|
|||
},
|
||||
.probe = wm8988_i2c_probe,
|
||||
.remove = wm8988_i2c_remove,
|
||||
.suspend = wm8988_i2c_suspend,
|
||||
.resume = wm8988_i2c_resume,
|
||||
.id_table = wm8988_i2c_id,
|
||||
};
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_SPI_MASTER)
|
||||
static int wm8988_spi_write(struct spi_device *spi, const char *data, int len)
|
||||
{
|
||||
struct spi_transfer t;
|
||||
struct spi_message m;
|
||||
u8 msg[2];
|
||||
|
||||
if (len <= 0)
|
||||
return 0;
|
||||
|
||||
msg[0] = data[0];
|
||||
msg[1] = data[1];
|
||||
|
||||
spi_message_init(&m);
|
||||
memset(&t, 0, (sizeof t));
|
||||
|
||||
t.tx_buf = &msg[0];
|
||||
t.len = len;
|
||||
|
||||
spi_message_add_tail(&t, &m);
|
||||
spi_sync(spi, &m);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static int __devinit wm8988_spi_probe(struct spi_device *spi)
|
||||
{
|
||||
struct wm8988_priv *wm8988;
|
||||
|
@ -1033,13 +989,12 @@ static int __devinit wm8988_spi_probe(struct spi_device *spi)
|
|||
return -ENOMEM;
|
||||
|
||||
codec = &wm8988->codec;
|
||||
codec->hw_write = (hw_write_t)wm8988_spi_write;
|
||||
codec->control_data = spi;
|
||||
codec->dev = &spi->dev;
|
||||
|
||||
dev_set_drvdata(&spi->dev, wm8988);
|
||||
|
||||
return wm8988_register(wm8988);
|
||||
return wm8988_register(wm8988, SND_SOC_SPI);
|
||||
}
|
||||
|
||||
static int __devexit wm8988_spi_remove(struct spi_device *spi)
|
||||
|
@ -1051,6 +1006,21 @@ static int __devexit wm8988_spi_remove(struct spi_device *spi)
|
|||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int wm8988_spi_suspend(struct spi_device *spi, pm_message_t msg)
|
||||
{
|
||||
return snd_soc_suspend_device(&spi->dev);
|
||||
}
|
||||
|
||||
static int wm8988_spi_resume(struct spi_device *spi)
|
||||
{
|
||||
return snd_soc_resume_device(&spi->dev);
|
||||
}
|
||||
#else
|
||||
#define wm8988_spi_suspend NULL
|
||||
#define wm8988_spi_resume NULL
|
||||
#endif
|
||||
|
||||
static struct spi_driver wm8988_spi_driver = {
|
||||
.driver = {
|
||||
.name = "wm8988",
|
||||
|
@ -1059,6 +1029,8 @@ static struct spi_driver wm8988_spi_driver = {
|
|||
},
|
||||
.probe = wm8988_spi_probe,
|
||||
.remove = __devexit_p(wm8988_spi_remove),
|
||||
.suspend = wm8988_spi_suspend,
|
||||
.resume = wm8988_spi_resume,
|
||||
};
|
||||
#endif
|
||||
|
||||
|
|
|
@ -108,53 +108,7 @@ static const u16 wm8990_reg[] = {
|
|||
0x0000, /* R63 - Driver internal */
|
||||
};
|
||||
|
||||
/*
|
||||
* read wm8990 register cache
|
||||
*/
|
||||
static inline unsigned int wm8990_read_reg_cache(struct snd_soc_codec *codec,
|
||||
unsigned int reg)
|
||||
{
|
||||
u16 *cache = codec->reg_cache;
|
||||
BUG_ON(reg >= ARRAY_SIZE(wm8990_reg));
|
||||
return cache[reg];
|
||||
}
|
||||
|
||||
/*
|
||||
* write wm8990 register cache
|
||||
*/
|
||||
static inline void wm8990_write_reg_cache(struct snd_soc_codec *codec,
|
||||
unsigned int reg, unsigned int value)
|
||||
{
|
||||
u16 *cache = codec->reg_cache;
|
||||
|
||||
/* Reset register and reserved registers are uncached */
|
||||
if (reg == 0 || reg >= ARRAY_SIZE(wm8990_reg))
|
||||
return;
|
||||
|
||||
cache[reg] = value;
|
||||
}
|
||||
|
||||
/*
|
||||
* write to the wm8990 register space
|
||||
*/
|
||||
static int wm8990_write(struct snd_soc_codec *codec, unsigned int reg,
|
||||
unsigned int value)
|
||||
{
|
||||
u8 data[3];
|
||||
|
||||
data[0] = reg & 0xFF;
|
||||
data[1] = (value >> 8) & 0xFF;
|
||||
data[2] = value & 0xFF;
|
||||
|
||||
wm8990_write_reg_cache(codec, reg, value);
|
||||
|
||||
if (codec->hw_write(codec->control_data, data, 3) == 2)
|
||||
return 0;
|
||||
else
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
#define wm8990_reset(c) wm8990_write(c, WM8990_RESET, 0)
|
||||
#define wm8990_reset(c) snd_soc_write(c, WM8990_RESET, 0)
|
||||
|
||||
static const DECLARE_TLV_DB_LINEAR(rec_mix_tlv, -1500, 600);
|
||||
|
||||
|
@ -187,8 +141,8 @@ static int wm899x_outpga_put_volsw_vu(struct snd_kcontrol *kcontrol,
|
|||
return ret;
|
||||
|
||||
/* now hit the volume update bits (always bit 8) */
|
||||
val = wm8990_read_reg_cache(codec, reg);
|
||||
return wm8990_write(codec, reg, val | 0x0100);
|
||||
val = snd_soc_read(codec, reg);
|
||||
return snd_soc_write(codec, reg, val | 0x0100);
|
||||
}
|
||||
|
||||
#define SOC_WM899X_OUTPGA_SINGLE_R_TLV(xname, reg, shift, max, invert,\
|
||||
|
@ -427,8 +381,8 @@ static int inmixer_event(struct snd_soc_dapm_widget *w,
|
|||
{
|
||||
u16 reg, fakepower;
|
||||
|
||||
reg = wm8990_read_reg_cache(w->codec, WM8990_POWER_MANAGEMENT_2);
|
||||
fakepower = wm8990_read_reg_cache(w->codec, WM8990_INTDRIVBITS);
|
||||
reg = snd_soc_read(w->codec, WM8990_POWER_MANAGEMENT_2);
|
||||
fakepower = snd_soc_read(w->codec, WM8990_INTDRIVBITS);
|
||||
|
||||
if (fakepower & ((1 << WM8990_INMIXL_PWR_BIT) |
|
||||
(1 << WM8990_AINLMUX_PWR_BIT))) {
|
||||
|
@ -443,7 +397,7 @@ static int inmixer_event(struct snd_soc_dapm_widget *w,
|
|||
} else {
|
||||
reg &= ~WM8990_AINL_ENA;
|
||||
}
|
||||
wm8990_write(w->codec, WM8990_POWER_MANAGEMENT_2, reg);
|
||||
snd_soc_write(w->codec, WM8990_POWER_MANAGEMENT_2, reg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -457,7 +411,7 @@ static int outmixer_event(struct snd_soc_dapm_widget *w,
|
|||
|
||||
switch (reg_shift) {
|
||||
case WM8990_SPEAKER_MIXER | (WM8990_LDSPK_BIT << 8) :
|
||||
reg = wm8990_read_reg_cache(w->codec, WM8990_OUTPUT_MIXER1);
|
||||
reg = snd_soc_read(w->codec, WM8990_OUTPUT_MIXER1);
|
||||
if (reg & WM8990_LDLO) {
|
||||
printk(KERN_WARNING
|
||||
"Cannot set as Output Mixer 1 LDLO Set\n");
|
||||
|
@ -465,7 +419,7 @@ static int outmixer_event(struct snd_soc_dapm_widget *w,
|
|||
}
|
||||
break;
|
||||
case WM8990_SPEAKER_MIXER | (WM8990_RDSPK_BIT << 8):
|
||||
reg = wm8990_read_reg_cache(w->codec, WM8990_OUTPUT_MIXER2);
|
||||
reg = snd_soc_read(w->codec, WM8990_OUTPUT_MIXER2);
|
||||
if (reg & WM8990_RDRO) {
|
||||
printk(KERN_WARNING
|
||||
"Cannot set as Output Mixer 2 RDRO Set\n");
|
||||
|
@ -473,7 +427,7 @@ static int outmixer_event(struct snd_soc_dapm_widget *w,
|
|||
}
|
||||
break;
|
||||
case WM8990_OUTPUT_MIXER1 | (WM8990_LDLO_BIT << 8):
|
||||
reg = wm8990_read_reg_cache(w->codec, WM8990_SPEAKER_MIXER);
|
||||
reg = snd_soc_read(w->codec, WM8990_SPEAKER_MIXER);
|
||||
if (reg & WM8990_LDSPK) {
|
||||
printk(KERN_WARNING
|
||||
"Cannot set as Speaker Mixer LDSPK Set\n");
|
||||
|
@ -481,7 +435,7 @@ static int outmixer_event(struct snd_soc_dapm_widget *w,
|
|||
}
|
||||
break;
|
||||
case WM8990_OUTPUT_MIXER2 | (WM8990_RDRO_BIT << 8):
|
||||
reg = wm8990_read_reg_cache(w->codec, WM8990_SPEAKER_MIXER);
|
||||
reg = snd_soc_read(w->codec, WM8990_SPEAKER_MIXER);
|
||||
if (reg & WM8990_RDSPK) {
|
||||
printk(KERN_WARNING
|
||||
"Cannot set as Speaker Mixer RDSPK Set\n");
|
||||
|
@ -1029,24 +983,24 @@ static int wm8990_set_dai_pll(struct snd_soc_dai *codec_dai,
|
|||
pll_factors(&pll_div, freq_out * 4, freq_in);
|
||||
|
||||
/* Turn on PLL */
|
||||
reg = wm8990_read_reg_cache(codec, WM8990_POWER_MANAGEMENT_2);
|
||||
reg = snd_soc_read(codec, WM8990_POWER_MANAGEMENT_2);
|
||||
reg |= WM8990_PLL_ENA;
|
||||
wm8990_write(codec, WM8990_POWER_MANAGEMENT_2, reg);
|
||||
snd_soc_write(codec, WM8990_POWER_MANAGEMENT_2, reg);
|
||||
|
||||
/* sysclk comes from PLL */
|
||||
reg = wm8990_read_reg_cache(codec, WM8990_CLOCKING_2);
|
||||
wm8990_write(codec, WM8990_CLOCKING_2, reg | WM8990_SYSCLK_SRC);
|
||||
reg = snd_soc_read(codec, WM8990_CLOCKING_2);
|
||||
snd_soc_write(codec, WM8990_CLOCKING_2, reg | WM8990_SYSCLK_SRC);
|
||||
|
||||
/* set up N , fractional mode and pre-divisor if neccessary */
|
||||
wm8990_write(codec, WM8990_PLL1, pll_div.n | WM8990_SDM |
|
||||
snd_soc_write(codec, WM8990_PLL1, pll_div.n | WM8990_SDM |
|
||||
(pll_div.div2?WM8990_PRESCALE:0));
|
||||
wm8990_write(codec, WM8990_PLL2, (u8)(pll_div.k>>8));
|
||||
wm8990_write(codec, WM8990_PLL3, (u8)(pll_div.k & 0xFF));
|
||||
snd_soc_write(codec, WM8990_PLL2, (u8)(pll_div.k>>8));
|
||||
snd_soc_write(codec, WM8990_PLL3, (u8)(pll_div.k & 0xFF));
|
||||
} else {
|
||||
/* Turn on PLL */
|
||||
reg = wm8990_read_reg_cache(codec, WM8990_POWER_MANAGEMENT_2);
|
||||
reg = snd_soc_read(codec, WM8990_POWER_MANAGEMENT_2);
|
||||
reg &= ~WM8990_PLL_ENA;
|
||||
wm8990_write(codec, WM8990_POWER_MANAGEMENT_2, reg);
|
||||
snd_soc_write(codec, WM8990_POWER_MANAGEMENT_2, reg);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
@ -1073,8 +1027,8 @@ static int wm8990_set_dai_fmt(struct snd_soc_dai *codec_dai,
|
|||
struct snd_soc_codec *codec = codec_dai->codec;
|
||||
u16 audio1, audio3;
|
||||
|
||||
audio1 = wm8990_read_reg_cache(codec, WM8990_AUDIO_INTERFACE_1);
|
||||
audio3 = wm8990_read_reg_cache(codec, WM8990_AUDIO_INTERFACE_3);
|
||||
audio1 = snd_soc_read(codec, WM8990_AUDIO_INTERFACE_1);
|
||||
audio3 = snd_soc_read(codec, WM8990_AUDIO_INTERFACE_3);
|
||||
|
||||
/* set master/slave audio interface */
|
||||
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
|
||||
|
@ -1115,8 +1069,8 @@ static int wm8990_set_dai_fmt(struct snd_soc_dai *codec_dai,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
wm8990_write(codec, WM8990_AUDIO_INTERFACE_1, audio1);
|
||||
wm8990_write(codec, WM8990_AUDIO_INTERFACE_3, audio3);
|
||||
snd_soc_write(codec, WM8990_AUDIO_INTERFACE_1, audio1);
|
||||
snd_soc_write(codec, WM8990_AUDIO_INTERFACE_3, audio3);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -1128,24 +1082,24 @@ static int wm8990_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
|
|||
|
||||
switch (div_id) {
|
||||
case WM8990_MCLK_DIV:
|
||||
reg = wm8990_read_reg_cache(codec, WM8990_CLOCKING_2) &
|
||||
reg = snd_soc_read(codec, WM8990_CLOCKING_2) &
|
||||
~WM8990_MCLK_DIV_MASK;
|
||||
wm8990_write(codec, WM8990_CLOCKING_2, reg | div);
|
||||
snd_soc_write(codec, WM8990_CLOCKING_2, reg | div);
|
||||
break;
|
||||
case WM8990_DACCLK_DIV:
|
||||
reg = wm8990_read_reg_cache(codec, WM8990_CLOCKING_2) &
|
||||
reg = snd_soc_read(codec, WM8990_CLOCKING_2) &
|
||||
~WM8990_DAC_CLKDIV_MASK;
|
||||
wm8990_write(codec, WM8990_CLOCKING_2, reg | div);
|
||||
snd_soc_write(codec, WM8990_CLOCKING_2, reg | div);
|
||||
break;
|
||||
case WM8990_ADCCLK_DIV:
|
||||
reg = wm8990_read_reg_cache(codec, WM8990_CLOCKING_2) &
|
||||
reg = snd_soc_read(codec, WM8990_CLOCKING_2) &
|
||||
~WM8990_ADC_CLKDIV_MASK;
|
||||
wm8990_write(codec, WM8990_CLOCKING_2, reg | div);
|
||||
snd_soc_write(codec, WM8990_CLOCKING_2, reg | div);
|
||||
break;
|
||||
case WM8990_BCLK_DIV:
|
||||
reg = wm8990_read_reg_cache(codec, WM8990_CLOCKING_1) &
|
||||
reg = snd_soc_read(codec, WM8990_CLOCKING_1) &
|
||||
~WM8990_BCLK_DIV_MASK;
|
||||
wm8990_write(codec, WM8990_CLOCKING_1, reg | div);
|
||||
snd_soc_write(codec, WM8990_CLOCKING_1, reg | div);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
|
@ -1164,7 +1118,7 @@ static int wm8990_hw_params(struct snd_pcm_substream *substream,
|
|||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_device *socdev = rtd->socdev;
|
||||
struct snd_soc_codec *codec = socdev->card->codec;
|
||||
u16 audio1 = wm8990_read_reg_cache(codec, WM8990_AUDIO_INTERFACE_1);
|
||||
u16 audio1 = snd_soc_read(codec, WM8990_AUDIO_INTERFACE_1);
|
||||
|
||||
audio1 &= ~WM8990_AIF_WL_MASK;
|
||||
/* bit size */
|
||||
|
@ -1182,7 +1136,7 @@ static int wm8990_hw_params(struct snd_pcm_substream *substream,
|
|||
break;
|
||||
}
|
||||
|
||||
wm8990_write(codec, WM8990_AUDIO_INTERFACE_1, audio1);
|
||||
snd_soc_write(codec, WM8990_AUDIO_INTERFACE_1, audio1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -1191,12 +1145,12 @@ static int wm8990_mute(struct snd_soc_dai *dai, int mute)
|
|||
struct snd_soc_codec *codec = dai->codec;
|
||||
u16 val;
|
||||
|
||||
val = wm8990_read_reg_cache(codec, WM8990_DAC_CTRL) & ~WM8990_DAC_MUTE;
|
||||
val = snd_soc_read(codec, WM8990_DAC_CTRL) & ~WM8990_DAC_MUTE;
|
||||
|
||||
if (mute)
|
||||
wm8990_write(codec, WM8990_DAC_CTRL, val | WM8990_DAC_MUTE);
|
||||
snd_soc_write(codec, WM8990_DAC_CTRL, val | WM8990_DAC_MUTE);
|
||||
else
|
||||
wm8990_write(codec, WM8990_DAC_CTRL, val);
|
||||
snd_soc_write(codec, WM8990_DAC_CTRL, val);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -1212,21 +1166,21 @@ static int wm8990_set_bias_level(struct snd_soc_codec *codec,
|
|||
|
||||
case SND_SOC_BIAS_PREPARE:
|
||||
/* VMID=2*50k */
|
||||
val = wm8990_read_reg_cache(codec, WM8990_POWER_MANAGEMENT_1) &
|
||||
val = snd_soc_read(codec, WM8990_POWER_MANAGEMENT_1) &
|
||||
~WM8990_VMID_MODE_MASK;
|
||||
wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, val | 0x2);
|
||||
snd_soc_write(codec, WM8990_POWER_MANAGEMENT_1, val | 0x2);
|
||||
break;
|
||||
|
||||
case SND_SOC_BIAS_STANDBY:
|
||||
if (codec->bias_level == SND_SOC_BIAS_OFF) {
|
||||
/* Enable all output discharge bits */
|
||||
wm8990_write(codec, WM8990_ANTIPOP1, WM8990_DIS_LLINE |
|
||||
snd_soc_write(codec, WM8990_ANTIPOP1, WM8990_DIS_LLINE |
|
||||
WM8990_DIS_RLINE | WM8990_DIS_OUT3 |
|
||||
WM8990_DIS_OUT4 | WM8990_DIS_LOUT |
|
||||
WM8990_DIS_ROUT);
|
||||
|
||||
/* Enable POBCTRL, SOFT_ST, VMIDTOG and BUFDCOPEN */
|
||||
wm8990_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST |
|
||||
snd_soc_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST |
|
||||
WM8990_BUFDCOPEN | WM8990_POBCTRL |
|
||||
WM8990_VMIDTOG);
|
||||
|
||||
|
@ -1234,83 +1188,83 @@ static int wm8990_set_bias_level(struct snd_soc_codec *codec,
|
|||
msleep(msecs_to_jiffies(300));
|
||||
|
||||
/* Disable VMIDTOG */
|
||||
wm8990_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST |
|
||||
snd_soc_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST |
|
||||
WM8990_BUFDCOPEN | WM8990_POBCTRL);
|
||||
|
||||
/* disable all output discharge bits */
|
||||
wm8990_write(codec, WM8990_ANTIPOP1, 0);
|
||||
snd_soc_write(codec, WM8990_ANTIPOP1, 0);
|
||||
|
||||
/* Enable outputs */
|
||||
wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1b00);
|
||||
snd_soc_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1b00);
|
||||
|
||||
msleep(msecs_to_jiffies(50));
|
||||
|
||||
/* Enable VMID at 2x50k */
|
||||
wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1f02);
|
||||
snd_soc_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1f02);
|
||||
|
||||
msleep(msecs_to_jiffies(100));
|
||||
|
||||
/* Enable VREF */
|
||||
wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1f03);
|
||||
snd_soc_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1f03);
|
||||
|
||||
msleep(msecs_to_jiffies(600));
|
||||
|
||||
/* Enable BUFIOEN */
|
||||
wm8990_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST |
|
||||
snd_soc_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST |
|
||||
WM8990_BUFDCOPEN | WM8990_POBCTRL |
|
||||
WM8990_BUFIOEN);
|
||||
|
||||
/* Disable outputs */
|
||||
wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, 0x3);
|
||||
snd_soc_write(codec, WM8990_POWER_MANAGEMENT_1, 0x3);
|
||||
|
||||
/* disable POBCTRL, SOFT_ST and BUFDCOPEN */
|
||||
wm8990_write(codec, WM8990_ANTIPOP2, WM8990_BUFIOEN);
|
||||
snd_soc_write(codec, WM8990_ANTIPOP2, WM8990_BUFIOEN);
|
||||
|
||||
/* Enable workaround for ADC clocking issue. */
|
||||
wm8990_write(codec, WM8990_EXT_ACCESS_ENA, 0x2);
|
||||
wm8990_write(codec, WM8990_EXT_CTL1, 0xa003);
|
||||
wm8990_write(codec, WM8990_EXT_ACCESS_ENA, 0);
|
||||
snd_soc_write(codec, WM8990_EXT_ACCESS_ENA, 0x2);
|
||||
snd_soc_write(codec, WM8990_EXT_CTL1, 0xa003);
|
||||
snd_soc_write(codec, WM8990_EXT_ACCESS_ENA, 0);
|
||||
}
|
||||
|
||||
/* VMID=2*250k */
|
||||
val = wm8990_read_reg_cache(codec, WM8990_POWER_MANAGEMENT_1) &
|
||||
val = snd_soc_read(codec, WM8990_POWER_MANAGEMENT_1) &
|
||||
~WM8990_VMID_MODE_MASK;
|
||||
wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, val | 0x4);
|
||||
snd_soc_write(codec, WM8990_POWER_MANAGEMENT_1, val | 0x4);
|
||||
break;
|
||||
|
||||
case SND_SOC_BIAS_OFF:
|
||||
/* Enable POBCTRL and SOFT_ST */
|
||||
wm8990_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST |
|
||||
snd_soc_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST |
|
||||
WM8990_POBCTRL | WM8990_BUFIOEN);
|
||||
|
||||
/* Enable POBCTRL, SOFT_ST and BUFDCOPEN */
|
||||
wm8990_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST |
|
||||
snd_soc_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST |
|
||||
WM8990_BUFDCOPEN | WM8990_POBCTRL |
|
||||
WM8990_BUFIOEN);
|
||||
|
||||
/* mute DAC */
|
||||
val = wm8990_read_reg_cache(codec, WM8990_DAC_CTRL);
|
||||
wm8990_write(codec, WM8990_DAC_CTRL, val | WM8990_DAC_MUTE);
|
||||
val = snd_soc_read(codec, WM8990_DAC_CTRL);
|
||||
snd_soc_write(codec, WM8990_DAC_CTRL, val | WM8990_DAC_MUTE);
|
||||
|
||||
/* Enable any disabled outputs */
|
||||
wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1f03);
|
||||
snd_soc_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1f03);
|
||||
|
||||
/* Disable VMID */
|
||||
wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1f01);
|
||||
snd_soc_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1f01);
|
||||
|
||||
msleep(msecs_to_jiffies(300));
|
||||
|
||||
/* Enable all output discharge bits */
|
||||
wm8990_write(codec, WM8990_ANTIPOP1, WM8990_DIS_LLINE |
|
||||
snd_soc_write(codec, WM8990_ANTIPOP1, WM8990_DIS_LLINE |
|
||||
WM8990_DIS_RLINE | WM8990_DIS_OUT3 |
|
||||
WM8990_DIS_OUT4 | WM8990_DIS_LOUT |
|
||||
WM8990_DIS_ROUT);
|
||||
|
||||
/* Disable VREF */
|
||||
wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, 0x0);
|
||||
snd_soc_write(codec, WM8990_POWER_MANAGEMENT_1, 0x0);
|
||||
|
||||
/* disable POBCTRL, SOFT_ST and BUFDCOPEN */
|
||||
wm8990_write(codec, WM8990_ANTIPOP2, 0x0);
|
||||
snd_soc_write(codec, WM8990_ANTIPOP2, 0x0);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -1411,8 +1365,6 @@ static int wm8990_init(struct snd_soc_device *socdev)
|
|||
|
||||
codec->name = "WM8990";
|
||||
codec->owner = THIS_MODULE;
|
||||
codec->read = wm8990_read_reg_cache;
|
||||
codec->write = wm8990_write;
|
||||
codec->set_bias_level = wm8990_set_bias_level;
|
||||
codec->dai = &wm8990_dai;
|
||||
codec->num_dai = 2;
|
||||
|
@ -1422,6 +1374,12 @@ static int wm8990_init(struct snd_soc_device *socdev)
|
|||
if (codec->reg_cache == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = snd_soc_codec_set_cache_io(codec, 8, 16, SND_SOC_I2C);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "wm8990: failed to set cache I/O: %d\n", ret);
|
||||
goto pcm_err;
|
||||
}
|
||||
|
||||
wm8990_reset(codec);
|
||||
|
||||
/* register pcms */
|
||||
|
@ -1435,18 +1393,18 @@ static int wm8990_init(struct snd_soc_device *socdev)
|
|||
codec->bias_level = SND_SOC_BIAS_OFF;
|
||||
wm8990_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
|
||||
|
||||
reg = wm8990_read_reg_cache(codec, WM8990_AUDIO_INTERFACE_4);
|
||||
wm8990_write(codec, WM8990_AUDIO_INTERFACE_4, reg | WM8990_ALRCGPIO1);
|
||||
reg = snd_soc_read(codec, WM8990_AUDIO_INTERFACE_4);
|
||||
snd_soc_write(codec, WM8990_AUDIO_INTERFACE_4, reg | WM8990_ALRCGPIO1);
|
||||
|
||||
reg = wm8990_read_reg_cache(codec, WM8990_GPIO1_GPIO2) &
|
||||
reg = snd_soc_read(codec, WM8990_GPIO1_GPIO2) &
|
||||
~WM8990_GPIO1_SEL_MASK;
|
||||
wm8990_write(codec, WM8990_GPIO1_GPIO2, reg | 1);
|
||||
snd_soc_write(codec, WM8990_GPIO1_GPIO2, reg | 1);
|
||||
|
||||
reg = wm8990_read_reg_cache(codec, WM8990_POWER_MANAGEMENT_2);
|
||||
wm8990_write(codec, WM8990_POWER_MANAGEMENT_2, reg | WM8990_OPCLK_ENA);
|
||||
reg = snd_soc_read(codec, WM8990_POWER_MANAGEMENT_2);
|
||||
snd_soc_write(codec, WM8990_POWER_MANAGEMENT_2, reg | WM8990_OPCLK_ENA);
|
||||
|
||||
wm8990_write(codec, WM8990_LEFT_OUTPUT_VOLUME, 0x50 | (1<<8));
|
||||
wm8990_write(codec, WM8990_RIGHT_OUTPUT_VOLUME, 0x50 | (1<<8));
|
||||
snd_soc_write(codec, WM8990_LEFT_OUTPUT_VOLUME, 0x50 | (1<<8));
|
||||
snd_soc_write(codec, WM8990_RIGHT_OUTPUT_VOLUME, 0x50 | (1<<8));
|
||||
|
||||
snd_soc_add_controls(codec, wm8990_snd_controls,
|
||||
ARRAY_SIZE(wm8990_snd_controls));
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -165,87 +165,23 @@ struct wm9081_priv {
|
|||
int master;
|
||||
int fll_fref;
|
||||
int fll_fout;
|
||||
int tdm_width;
|
||||
struct wm9081_retune_mobile_config *retune;
|
||||
};
|
||||
|
||||
static int wm9081_reg_is_volatile(int reg)
|
||||
static int wm9081_volatile_register(unsigned int reg)
|
||||
{
|
||||
switch (reg) {
|
||||
case WM9081_SOFTWARE_RESET:
|
||||
return 1;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned int wm9081_read_reg_cache(struct snd_soc_codec *codec,
|
||||
unsigned int reg)
|
||||
{
|
||||
u16 *cache = codec->reg_cache;
|
||||
BUG_ON(reg > WM9081_MAX_REGISTER);
|
||||
return cache[reg];
|
||||
}
|
||||
|
||||
static unsigned int wm9081_read_hw(struct snd_soc_codec *codec, u8 reg)
|
||||
{
|
||||
struct i2c_msg xfer[2];
|
||||
u16 data;
|
||||
int ret;
|
||||
struct i2c_client *client = codec->control_data;
|
||||
|
||||
BUG_ON(reg > WM9081_MAX_REGISTER);
|
||||
|
||||
/* Write register */
|
||||
xfer[0].addr = client->addr;
|
||||
xfer[0].flags = 0;
|
||||
xfer[0].len = 1;
|
||||
xfer[0].buf = ®
|
||||
|
||||
/* Read data */
|
||||
xfer[1].addr = client->addr;
|
||||
xfer[1].flags = I2C_M_RD;
|
||||
xfer[1].len = 2;
|
||||
xfer[1].buf = (u8 *)&data;
|
||||
|
||||
ret = i2c_transfer(client->adapter, xfer, 2);
|
||||
if (ret != 2) {
|
||||
dev_err(&client->dev, "i2c_transfer() returned %d\n", ret);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (data >> 8) | ((data & 0xff) << 8);
|
||||
}
|
||||
|
||||
static unsigned int wm9081_read(struct snd_soc_codec *codec, unsigned int reg)
|
||||
{
|
||||
if (wm9081_reg_is_volatile(reg))
|
||||
return wm9081_read_hw(codec, reg);
|
||||
else
|
||||
return wm9081_read_reg_cache(codec, reg);
|
||||
}
|
||||
|
||||
static int wm9081_write(struct snd_soc_codec *codec, unsigned int reg,
|
||||
unsigned int value)
|
||||
{
|
||||
u16 *cache = codec->reg_cache;
|
||||
u8 data[3];
|
||||
|
||||
BUG_ON(reg > WM9081_MAX_REGISTER);
|
||||
|
||||
if (!wm9081_reg_is_volatile(reg))
|
||||
cache[reg] = value;
|
||||
|
||||
data[0] = reg;
|
||||
data[1] = value >> 8;
|
||||
data[2] = value & 0x00ff;
|
||||
|
||||
if (codec->hw_write(codec->control_data, data, 3) == 3)
|
||||
return 0;
|
||||
else
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
static int wm9081_reset(struct snd_soc_codec *codec)
|
||||
{
|
||||
return wm9081_write(codec, WM9081_SOFTWARE_RESET, 0);
|
||||
return snd_soc_write(codec, WM9081_SOFTWARE_RESET, 0);
|
||||
}
|
||||
|
||||
static const DECLARE_TLV_DB_SCALE(drc_in_tlv, -4500, 75, 0);
|
||||
|
@ -356,7 +292,7 @@ static int speaker_mode_get(struct snd_kcontrol *kcontrol,
|
|||
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
|
||||
unsigned int reg;
|
||||
|
||||
reg = wm9081_read(codec, WM9081_ANALOGUE_SPEAKER_2);
|
||||
reg = snd_soc_read(codec, WM9081_ANALOGUE_SPEAKER_2);
|
||||
if (reg & WM9081_SPK_MODE)
|
||||
ucontrol->value.integer.value[0] = 1;
|
||||
else
|
||||
|
@ -375,8 +311,8 @@ static int speaker_mode_put(struct snd_kcontrol *kcontrol,
|
|||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
|
||||
unsigned int reg_pwr = wm9081_read(codec, WM9081_POWER_MANAGEMENT);
|
||||
unsigned int reg2 = wm9081_read(codec, WM9081_ANALOGUE_SPEAKER_2);
|
||||
unsigned int reg_pwr = snd_soc_read(codec, WM9081_POWER_MANAGEMENT);
|
||||
unsigned int reg2 = snd_soc_read(codec, WM9081_ANALOGUE_SPEAKER_2);
|
||||
|
||||
/* Are we changing anything? */
|
||||
if (ucontrol->value.integer.value[0] ==
|
||||
|
@ -397,7 +333,7 @@ static int speaker_mode_put(struct snd_kcontrol *kcontrol,
|
|||
reg2 &= ~WM9081_SPK_MODE;
|
||||
}
|
||||
|
||||
wm9081_write(codec, WM9081_ANALOGUE_SPEAKER_2, reg2);
|
||||
snd_soc_write(codec, WM9081_ANALOGUE_SPEAKER_2, reg2);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -456,7 +392,7 @@ static int speaker_event(struct snd_soc_dapm_widget *w,
|
|||
struct snd_kcontrol *kcontrol, int event)
|
||||
{
|
||||
struct snd_soc_codec *codec = w->codec;
|
||||
unsigned int reg = wm9081_read(codec, WM9081_POWER_MANAGEMENT);
|
||||
unsigned int reg = snd_soc_read(codec, WM9081_POWER_MANAGEMENT);
|
||||
|
||||
switch (event) {
|
||||
case SND_SOC_DAPM_POST_PMU:
|
||||
|
@ -468,7 +404,7 @@ static int speaker_event(struct snd_soc_dapm_widget *w,
|
|||
break;
|
||||
}
|
||||
|
||||
wm9081_write(codec, WM9081_POWER_MANAGEMENT, reg);
|
||||
snd_soc_write(codec, WM9081_POWER_MANAGEMENT, reg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -607,7 +543,7 @@ static int wm9081_set_fll(struct snd_soc_codec *codec, int fll_id,
|
|||
if (ret != 0)
|
||||
return ret;
|
||||
|
||||
reg5 = wm9081_read(codec, WM9081_FLL_CONTROL_5);
|
||||
reg5 = snd_soc_read(codec, WM9081_FLL_CONTROL_5);
|
||||
reg5 &= ~WM9081_FLL_CLK_SRC_MASK;
|
||||
|
||||
switch (fll_id) {
|
||||
|
@ -621,44 +557,44 @@ static int wm9081_set_fll(struct snd_soc_codec *codec, int fll_id,
|
|||
}
|
||||
|
||||
/* Disable CLK_SYS while we reconfigure */
|
||||
clk_sys_reg = wm9081_read(codec, WM9081_CLOCK_CONTROL_3);
|
||||
clk_sys_reg = snd_soc_read(codec, WM9081_CLOCK_CONTROL_3);
|
||||
if (clk_sys_reg & WM9081_CLK_SYS_ENA)
|
||||
wm9081_write(codec, WM9081_CLOCK_CONTROL_3,
|
||||
snd_soc_write(codec, WM9081_CLOCK_CONTROL_3,
|
||||
clk_sys_reg & ~WM9081_CLK_SYS_ENA);
|
||||
|
||||
/* Any FLL configuration change requires that the FLL be
|
||||
* disabled first. */
|
||||
reg1 = wm9081_read(codec, WM9081_FLL_CONTROL_1);
|
||||
reg1 = snd_soc_read(codec, WM9081_FLL_CONTROL_1);
|
||||
reg1 &= ~WM9081_FLL_ENA;
|
||||
wm9081_write(codec, WM9081_FLL_CONTROL_1, reg1);
|
||||
snd_soc_write(codec, WM9081_FLL_CONTROL_1, reg1);
|
||||
|
||||
/* Apply the configuration */
|
||||
if (fll_div.k)
|
||||
reg1 |= WM9081_FLL_FRAC_MASK;
|
||||
else
|
||||
reg1 &= ~WM9081_FLL_FRAC_MASK;
|
||||
wm9081_write(codec, WM9081_FLL_CONTROL_1, reg1);
|
||||
snd_soc_write(codec, WM9081_FLL_CONTROL_1, reg1);
|
||||
|
||||
wm9081_write(codec, WM9081_FLL_CONTROL_2,
|
||||
snd_soc_write(codec, WM9081_FLL_CONTROL_2,
|
||||
(fll_div.fll_outdiv << WM9081_FLL_OUTDIV_SHIFT) |
|
||||
(fll_div.fll_fratio << WM9081_FLL_FRATIO_SHIFT));
|
||||
wm9081_write(codec, WM9081_FLL_CONTROL_3, fll_div.k);
|
||||
snd_soc_write(codec, WM9081_FLL_CONTROL_3, fll_div.k);
|
||||
|
||||
reg4 = wm9081_read(codec, WM9081_FLL_CONTROL_4);
|
||||
reg4 = snd_soc_read(codec, WM9081_FLL_CONTROL_4);
|
||||
reg4 &= ~WM9081_FLL_N_MASK;
|
||||
reg4 |= fll_div.n << WM9081_FLL_N_SHIFT;
|
||||
wm9081_write(codec, WM9081_FLL_CONTROL_4, reg4);
|
||||
snd_soc_write(codec, WM9081_FLL_CONTROL_4, reg4);
|
||||
|
||||
reg5 &= ~WM9081_FLL_CLK_REF_DIV_MASK;
|
||||
reg5 |= fll_div.fll_clk_ref_div << WM9081_FLL_CLK_REF_DIV_SHIFT;
|
||||
wm9081_write(codec, WM9081_FLL_CONTROL_5, reg5);
|
||||
snd_soc_write(codec, WM9081_FLL_CONTROL_5, reg5);
|
||||
|
||||
/* Enable the FLL */
|
||||
wm9081_write(codec, WM9081_FLL_CONTROL_1, reg1 | WM9081_FLL_ENA);
|
||||
snd_soc_write(codec, WM9081_FLL_CONTROL_1, reg1 | WM9081_FLL_ENA);
|
||||
|
||||
/* Then bring CLK_SYS up again if it was disabled */
|
||||
if (clk_sys_reg & WM9081_CLK_SYS_ENA)
|
||||
wm9081_write(codec, WM9081_CLOCK_CONTROL_3, clk_sys_reg);
|
||||
snd_soc_write(codec, WM9081_CLOCK_CONTROL_3, clk_sys_reg);
|
||||
|
||||
dev_dbg(codec->dev, "FLL enabled at %dHz->%dHz\n", Fref, Fout);
|
||||
|
||||
|
@ -707,6 +643,10 @@ static int configure_clock(struct snd_soc_codec *codec)
|
|||
target > 3000000)
|
||||
break;
|
||||
}
|
||||
|
||||
if (i == ARRAY_SIZE(clk_sys_rates))
|
||||
return -EINVAL;
|
||||
|
||||
} else if (wm9081->fs) {
|
||||
for (i = 0; i < ARRAY_SIZE(clk_sys_rates); i++) {
|
||||
new_sysclk = clk_sys_rates[i].ratio
|
||||
|
@ -714,6 +654,10 @@ static int configure_clock(struct snd_soc_codec *codec)
|
|||
if (new_sysclk > 3000000)
|
||||
break;
|
||||
}
|
||||
|
||||
if (i == ARRAY_SIZE(clk_sys_rates))
|
||||
return -EINVAL;
|
||||
|
||||
} else {
|
||||
new_sysclk = 12288000;
|
||||
}
|
||||
|
@ -734,19 +678,19 @@ static int configure_clock(struct snd_soc_codec *codec)
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
reg = wm9081_read(codec, WM9081_CLOCK_CONTROL_1);
|
||||
reg = snd_soc_read(codec, WM9081_CLOCK_CONTROL_1);
|
||||
if (mclkdiv)
|
||||
reg |= WM9081_MCLKDIV2;
|
||||
else
|
||||
reg &= ~WM9081_MCLKDIV2;
|
||||
wm9081_write(codec, WM9081_CLOCK_CONTROL_1, reg);
|
||||
snd_soc_write(codec, WM9081_CLOCK_CONTROL_1, reg);
|
||||
|
||||
reg = wm9081_read(codec, WM9081_CLOCK_CONTROL_3);
|
||||
reg = snd_soc_read(codec, WM9081_CLOCK_CONTROL_3);
|
||||
if (fll)
|
||||
reg |= WM9081_CLK_SRC_SEL;
|
||||
else
|
||||
reg &= ~WM9081_CLK_SRC_SEL;
|
||||
wm9081_write(codec, WM9081_CLOCK_CONTROL_3, reg);
|
||||
snd_soc_write(codec, WM9081_CLOCK_CONTROL_3, reg);
|
||||
|
||||
dev_dbg(codec->dev, "CLK_SYS is %dHz\n", wm9081->sysclk_rate);
|
||||
|
||||
|
@ -846,76 +790,76 @@ static int wm9081_set_bias_level(struct snd_soc_codec *codec,
|
|||
|
||||
case SND_SOC_BIAS_PREPARE:
|
||||
/* VMID=2*40k */
|
||||
reg = wm9081_read(codec, WM9081_VMID_CONTROL);
|
||||
reg = snd_soc_read(codec, WM9081_VMID_CONTROL);
|
||||
reg &= ~WM9081_VMID_SEL_MASK;
|
||||
reg |= 0x2;
|
||||
wm9081_write(codec, WM9081_VMID_CONTROL, reg);
|
||||
snd_soc_write(codec, WM9081_VMID_CONTROL, reg);
|
||||
|
||||
/* Normal bias current */
|
||||
reg = wm9081_read(codec, WM9081_BIAS_CONTROL_1);
|
||||
reg = snd_soc_read(codec, WM9081_BIAS_CONTROL_1);
|
||||
reg &= ~WM9081_STBY_BIAS_ENA;
|
||||
wm9081_write(codec, WM9081_BIAS_CONTROL_1, reg);
|
||||
snd_soc_write(codec, WM9081_BIAS_CONTROL_1, reg);
|
||||
break;
|
||||
|
||||
case SND_SOC_BIAS_STANDBY:
|
||||
/* Initial cold start */
|
||||
if (codec->bias_level == SND_SOC_BIAS_OFF) {
|
||||
/* Disable LINEOUT discharge */
|
||||
reg = wm9081_read(codec, WM9081_ANTI_POP_CONTROL);
|
||||
reg = snd_soc_read(codec, WM9081_ANTI_POP_CONTROL);
|
||||
reg &= ~WM9081_LINEOUT_DISCH;
|
||||
wm9081_write(codec, WM9081_ANTI_POP_CONTROL, reg);
|
||||
snd_soc_write(codec, WM9081_ANTI_POP_CONTROL, reg);
|
||||
|
||||
/* Select startup bias source */
|
||||
reg = wm9081_read(codec, WM9081_BIAS_CONTROL_1);
|
||||
reg = snd_soc_read(codec, WM9081_BIAS_CONTROL_1);
|
||||
reg |= WM9081_BIAS_SRC | WM9081_BIAS_ENA;
|
||||
wm9081_write(codec, WM9081_BIAS_CONTROL_1, reg);
|
||||
snd_soc_write(codec, WM9081_BIAS_CONTROL_1, reg);
|
||||
|
||||
/* VMID 2*4k; Soft VMID ramp enable */
|
||||
reg = wm9081_read(codec, WM9081_VMID_CONTROL);
|
||||
reg = snd_soc_read(codec, WM9081_VMID_CONTROL);
|
||||
reg |= WM9081_VMID_RAMP | 0x6;
|
||||
wm9081_write(codec, WM9081_VMID_CONTROL, reg);
|
||||
snd_soc_write(codec, WM9081_VMID_CONTROL, reg);
|
||||
|
||||
mdelay(100);
|
||||
|
||||
/* Normal bias enable & soft start off */
|
||||
reg |= WM9081_BIAS_ENA;
|
||||
reg &= ~WM9081_VMID_RAMP;
|
||||
wm9081_write(codec, WM9081_VMID_CONTROL, reg);
|
||||
snd_soc_write(codec, WM9081_VMID_CONTROL, reg);
|
||||
|
||||
/* Standard bias source */
|
||||
reg = wm9081_read(codec, WM9081_BIAS_CONTROL_1);
|
||||
reg = snd_soc_read(codec, WM9081_BIAS_CONTROL_1);
|
||||
reg &= ~WM9081_BIAS_SRC;
|
||||
wm9081_write(codec, WM9081_BIAS_CONTROL_1, reg);
|
||||
snd_soc_write(codec, WM9081_BIAS_CONTROL_1, reg);
|
||||
}
|
||||
|
||||
/* VMID 2*240k */
|
||||
reg = wm9081_read(codec, WM9081_BIAS_CONTROL_1);
|
||||
reg = snd_soc_read(codec, WM9081_BIAS_CONTROL_1);
|
||||
reg &= ~WM9081_VMID_SEL_MASK;
|
||||
reg |= 0x40;
|
||||
wm9081_write(codec, WM9081_VMID_CONTROL, reg);
|
||||
snd_soc_write(codec, WM9081_VMID_CONTROL, reg);
|
||||
|
||||
/* Standby bias current on */
|
||||
reg = wm9081_read(codec, WM9081_BIAS_CONTROL_1);
|
||||
reg = snd_soc_read(codec, WM9081_BIAS_CONTROL_1);
|
||||
reg |= WM9081_STBY_BIAS_ENA;
|
||||
wm9081_write(codec, WM9081_BIAS_CONTROL_1, reg);
|
||||
snd_soc_write(codec, WM9081_BIAS_CONTROL_1, reg);
|
||||
break;
|
||||
|
||||
case SND_SOC_BIAS_OFF:
|
||||
/* Startup bias source */
|
||||
reg = wm9081_read(codec, WM9081_BIAS_CONTROL_1);
|
||||
reg = snd_soc_read(codec, WM9081_BIAS_CONTROL_1);
|
||||
reg |= WM9081_BIAS_SRC;
|
||||
wm9081_write(codec, WM9081_BIAS_CONTROL_1, reg);
|
||||
snd_soc_write(codec, WM9081_BIAS_CONTROL_1, reg);
|
||||
|
||||
/* Disable VMID and biases with soft ramping */
|
||||
reg = wm9081_read(codec, WM9081_VMID_CONTROL);
|
||||
reg = snd_soc_read(codec, WM9081_VMID_CONTROL);
|
||||
reg &= ~(WM9081_VMID_SEL_MASK | WM9081_BIAS_ENA);
|
||||
reg |= WM9081_VMID_RAMP;
|
||||
wm9081_write(codec, WM9081_VMID_CONTROL, reg);
|
||||
snd_soc_write(codec, WM9081_VMID_CONTROL, reg);
|
||||
|
||||
/* Actively discharge LINEOUT */
|
||||
reg = wm9081_read(codec, WM9081_ANTI_POP_CONTROL);
|
||||
reg = snd_soc_read(codec, WM9081_ANTI_POP_CONTROL);
|
||||
reg |= WM9081_LINEOUT_DISCH;
|
||||
wm9081_write(codec, WM9081_ANTI_POP_CONTROL, reg);
|
||||
snd_soc_write(codec, WM9081_ANTI_POP_CONTROL, reg);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -929,7 +873,7 @@ static int wm9081_set_dai_fmt(struct snd_soc_dai *dai,
|
|||
{
|
||||
struct snd_soc_codec *codec = dai->codec;
|
||||
struct wm9081_priv *wm9081 = codec->private_data;
|
||||
unsigned int aif2 = wm9081_read(codec, WM9081_AUDIO_INTERFACE_2);
|
||||
unsigned int aif2 = snd_soc_read(codec, WM9081_AUDIO_INTERFACE_2);
|
||||
|
||||
aif2 &= ~(WM9081_AIF_BCLK_INV | WM9081_AIF_LRCLK_INV |
|
||||
WM9081_BCLK_DIR | WM9081_LRCLK_DIR | WM9081_AIF_FMT_MASK);
|
||||
|
@ -1010,7 +954,7 @@ static int wm9081_set_dai_fmt(struct snd_soc_dai *dai,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
wm9081_write(codec, WM9081_AUDIO_INTERFACE_2, aif2);
|
||||
snd_soc_write(codec, WM9081_AUDIO_INTERFACE_2, aif2);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -1024,47 +968,51 @@ static int wm9081_hw_params(struct snd_pcm_substream *substream,
|
|||
int ret, i, best, best_val, cur_val;
|
||||
unsigned int clk_ctrl2, aif1, aif2, aif3, aif4;
|
||||
|
||||
clk_ctrl2 = wm9081_read(codec, WM9081_CLOCK_CONTROL_2);
|
||||
clk_ctrl2 = snd_soc_read(codec, WM9081_CLOCK_CONTROL_2);
|
||||
clk_ctrl2 &= ~(WM9081_CLK_SYS_RATE_MASK | WM9081_SAMPLE_RATE_MASK);
|
||||
|
||||
aif1 = wm9081_read(codec, WM9081_AUDIO_INTERFACE_1);
|
||||
aif1 = snd_soc_read(codec, WM9081_AUDIO_INTERFACE_1);
|
||||
|
||||
aif2 = wm9081_read(codec, WM9081_AUDIO_INTERFACE_2);
|
||||
aif2 = snd_soc_read(codec, WM9081_AUDIO_INTERFACE_2);
|
||||
aif2 &= ~WM9081_AIF_WL_MASK;
|
||||
|
||||
aif3 = wm9081_read(codec, WM9081_AUDIO_INTERFACE_3);
|
||||
aif3 = snd_soc_read(codec, WM9081_AUDIO_INTERFACE_3);
|
||||
aif3 &= ~WM9081_BCLK_DIV_MASK;
|
||||
|
||||
aif4 = wm9081_read(codec, WM9081_AUDIO_INTERFACE_4);
|
||||
aif4 = snd_soc_read(codec, WM9081_AUDIO_INTERFACE_4);
|
||||
aif4 &= ~WM9081_LRCLK_RATE_MASK;
|
||||
|
||||
/* What BCLK do we need? */
|
||||
wm9081->fs = params_rate(params);
|
||||
wm9081->bclk = 2 * wm9081->fs;
|
||||
switch (params_format(params)) {
|
||||
case SNDRV_PCM_FORMAT_S16_LE:
|
||||
wm9081->bclk *= 16;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S20_3LE:
|
||||
wm9081->bclk *= 20;
|
||||
aif2 |= 0x4;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S24_LE:
|
||||
wm9081->bclk *= 24;
|
||||
aif2 |= 0x8;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S32_LE:
|
||||
wm9081->bclk *= 32;
|
||||
aif2 |= 0xc;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (aif1 & WM9081_AIFDAC_TDM_MODE_MASK) {
|
||||
if (wm9081->tdm_width) {
|
||||
/* If TDM is set up then that fixes our BCLK. */
|
||||
int slots = ((aif1 & WM9081_AIFDAC_TDM_MODE_MASK) >>
|
||||
WM9081_AIFDAC_TDM_MODE_SHIFT) + 1;
|
||||
wm9081->bclk *= slots;
|
||||
|
||||
wm9081->bclk = wm9081->fs * wm9081->tdm_width * slots;
|
||||
} else {
|
||||
/* Otherwise work out a BCLK from the sample size */
|
||||
wm9081->bclk = 2 * wm9081->fs;
|
||||
|
||||
switch (params_format(params)) {
|
||||
case SNDRV_PCM_FORMAT_S16_LE:
|
||||
wm9081->bclk *= 16;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S20_3LE:
|
||||
wm9081->bclk *= 20;
|
||||
aif2 |= 0x4;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S24_LE:
|
||||
wm9081->bclk *= 24;
|
||||
aif2 |= 0x8;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S32_LE:
|
||||
wm9081->bclk *= 32;
|
||||
aif2 |= 0xc;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
dev_dbg(codec->dev, "Target BCLK is %dHz\n", wm9081->bclk);
|
||||
|
@ -1149,22 +1097,22 @@ static int wm9081_hw_params(struct snd_pcm_substream *substream,
|
|||
s->name, s->rate);
|
||||
|
||||
/* If the EQ is enabled then disable it while we write out */
|
||||
eq1 = wm9081_read(codec, WM9081_EQ_1) & WM9081_EQ_ENA;
|
||||
eq1 = snd_soc_read(codec, WM9081_EQ_1) & WM9081_EQ_ENA;
|
||||
if (eq1 & WM9081_EQ_ENA)
|
||||
wm9081_write(codec, WM9081_EQ_1, 0);
|
||||
snd_soc_write(codec, WM9081_EQ_1, 0);
|
||||
|
||||
/* Write out the other values */
|
||||
for (i = 1; i < ARRAY_SIZE(s->config); i++)
|
||||
wm9081_write(codec, WM9081_EQ_1 + i, s->config[i]);
|
||||
snd_soc_write(codec, WM9081_EQ_1 + i, s->config[i]);
|
||||
|
||||
eq1 |= (s->config[0] & ~WM9081_EQ_ENA);
|
||||
wm9081_write(codec, WM9081_EQ_1, eq1);
|
||||
snd_soc_write(codec, WM9081_EQ_1, eq1);
|
||||
}
|
||||
|
||||
wm9081_write(codec, WM9081_CLOCK_CONTROL_2, clk_ctrl2);
|
||||
wm9081_write(codec, WM9081_AUDIO_INTERFACE_2, aif2);
|
||||
wm9081_write(codec, WM9081_AUDIO_INTERFACE_3, aif3);
|
||||
wm9081_write(codec, WM9081_AUDIO_INTERFACE_4, aif4);
|
||||
snd_soc_write(codec, WM9081_CLOCK_CONTROL_2, clk_ctrl2);
|
||||
snd_soc_write(codec, WM9081_AUDIO_INTERFACE_2, aif2);
|
||||
snd_soc_write(codec, WM9081_AUDIO_INTERFACE_3, aif3);
|
||||
snd_soc_write(codec, WM9081_AUDIO_INTERFACE_4, aif4);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -1174,14 +1122,14 @@ static int wm9081_digital_mute(struct snd_soc_dai *codec_dai, int mute)
|
|||
struct snd_soc_codec *codec = codec_dai->codec;
|
||||
unsigned int reg;
|
||||
|
||||
reg = wm9081_read(codec, WM9081_DAC_DIGITAL_2);
|
||||
reg = snd_soc_read(codec, WM9081_DAC_DIGITAL_2);
|
||||
|
||||
if (mute)
|
||||
reg |= WM9081_DAC_MUTE;
|
||||
else
|
||||
reg &= ~WM9081_DAC_MUTE;
|
||||
|
||||
wm9081_write(codec, WM9081_DAC_DIGITAL_2, reg);
|
||||
snd_soc_write(codec, WM9081_DAC_DIGITAL_2, reg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -1207,19 +1155,25 @@ static int wm9081_set_sysclk(struct snd_soc_dai *codec_dai,
|
|||
}
|
||||
|
||||
static int wm9081_set_tdm_slot(struct snd_soc_dai *dai,
|
||||
unsigned int mask, int slots)
|
||||
unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width)
|
||||
{
|
||||
struct snd_soc_codec *codec = dai->codec;
|
||||
unsigned int aif1 = wm9081_read(codec, WM9081_AUDIO_INTERFACE_1);
|
||||
struct wm9081_priv *wm9081 = codec->private_data;
|
||||
unsigned int aif1 = snd_soc_read(codec, WM9081_AUDIO_INTERFACE_1);
|
||||
|
||||
aif1 &= ~(WM9081_AIFDAC_TDM_SLOT_MASK | WM9081_AIFDAC_TDM_MODE_MASK);
|
||||
|
||||
if (slots < 1 || slots > 4)
|
||||
if (slots < 0 || slots > 4)
|
||||
return -EINVAL;
|
||||
|
||||
wm9081->tdm_width = slot_width;
|
||||
|
||||
if (slots == 0)
|
||||
slots = 1;
|
||||
|
||||
aif1 |= (slots - 1) << WM9081_AIFDAC_TDM_MODE_SHIFT;
|
||||
|
||||
switch (mask) {
|
||||
switch (rx_mask) {
|
||||
case 1:
|
||||
break;
|
||||
case 2:
|
||||
|
@ -1235,7 +1189,7 @@ static int wm9081_set_tdm_slot(struct snd_soc_dai *dai,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
wm9081_write(codec, WM9081_AUDIO_INTERFACE_1, aif1);
|
||||
snd_soc_write(codec, WM9081_AUDIO_INTERFACE_1, aif1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -1357,7 +1311,7 @@ static int wm9081_resume(struct platform_device *pdev)
|
|||
if (i == WM9081_SOFTWARE_RESET)
|
||||
continue;
|
||||
|
||||
wm9081_write(codec, i, reg_cache[i]);
|
||||
snd_soc_write(codec, i, reg_cache[i]);
|
||||
}
|
||||
|
||||
wm9081_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
|
||||
|
@ -1377,7 +1331,8 @@ struct snd_soc_codec_device soc_codec_dev_wm9081 = {
|
|||
};
|
||||
EXPORT_SYMBOL_GPL(soc_codec_dev_wm9081);
|
||||
|
||||
static int wm9081_register(struct wm9081_priv *wm9081)
|
||||
static int wm9081_register(struct wm9081_priv *wm9081,
|
||||
enum snd_soc_control_type control)
|
||||
{
|
||||
struct snd_soc_codec *codec = &wm9081->codec;
|
||||
int ret;
|
||||
|
@ -1396,19 +1351,24 @@ static int wm9081_register(struct wm9081_priv *wm9081)
|
|||
codec->private_data = wm9081;
|
||||
codec->name = "WM9081";
|
||||
codec->owner = THIS_MODULE;
|
||||
codec->read = wm9081_read;
|
||||
codec->write = wm9081_write;
|
||||
codec->dai = &wm9081_dai;
|
||||
codec->num_dai = 1;
|
||||
codec->reg_cache_size = ARRAY_SIZE(wm9081->reg_cache);
|
||||
codec->reg_cache = &wm9081->reg_cache;
|
||||
codec->bias_level = SND_SOC_BIAS_OFF;
|
||||
codec->set_bias_level = wm9081_set_bias_level;
|
||||
codec->volatile_register = wm9081_volatile_register;
|
||||
|
||||
memcpy(codec->reg_cache, wm9081_reg_defaults,
|
||||
sizeof(wm9081_reg_defaults));
|
||||
|
||||
reg = wm9081_read_hw(codec, WM9081_SOFTWARE_RESET);
|
||||
ret = snd_soc_codec_set_cache_io(codec, 8, 16, control);
|
||||
if (ret != 0) {
|
||||
dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
reg = snd_soc_read(codec, WM9081_SOFTWARE_RESET);
|
||||
if (reg != 0x9081) {
|
||||
dev_err(codec->dev, "Device is not a WM9081: ID=0x%x\n", reg);
|
||||
ret = -EINVAL;
|
||||
|
@ -1424,10 +1384,10 @@ static int wm9081_register(struct wm9081_priv *wm9081)
|
|||
wm9081_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
|
||||
|
||||
/* Enable zero cross by default */
|
||||
reg = wm9081_read(codec, WM9081_ANALOGUE_LINEOUT);
|
||||
wm9081_write(codec, WM9081_ANALOGUE_LINEOUT, reg | WM9081_LINEOUTZC);
|
||||
reg = wm9081_read(codec, WM9081_ANALOGUE_SPEAKER_PGA);
|
||||
wm9081_write(codec, WM9081_ANALOGUE_SPEAKER_PGA,
|
||||
reg = snd_soc_read(codec, WM9081_ANALOGUE_LINEOUT);
|
||||
snd_soc_write(codec, WM9081_ANALOGUE_LINEOUT, reg | WM9081_LINEOUTZC);
|
||||
reg = snd_soc_read(codec, WM9081_ANALOGUE_SPEAKER_PGA);
|
||||
snd_soc_write(codec, WM9081_ANALOGUE_SPEAKER_PGA,
|
||||
reg | WM9081_SPKPGAZC);
|
||||
|
||||
wm9081_dai.dev = codec->dev;
|
||||
|
@ -1482,7 +1442,7 @@ static __devinit int wm9081_i2c_probe(struct i2c_client *i2c,
|
|||
|
||||
codec->dev = &i2c->dev;
|
||||
|
||||
return wm9081_register(wm9081);
|
||||
return wm9081_register(wm9081, SND_SOC_I2C);
|
||||
}
|
||||
|
||||
static __devexit int wm9081_i2c_remove(struct i2c_client *client)
|
||||
|
@ -1492,6 +1452,21 @@ static __devexit int wm9081_i2c_remove(struct i2c_client *client)
|
|||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int wm9081_i2c_suspend(struct i2c_client *client, pm_message_t msg)
|
||||
{
|
||||
return snd_soc_suspend_device(&client->dev);
|
||||
}
|
||||
|
||||
static int wm9081_i2c_resume(struct i2c_client *client)
|
||||
{
|
||||
return snd_soc_resume_device(&client->dev);
|
||||
}
|
||||
#else
|
||||
#define wm9081_i2c_suspend NULL
|
||||
#define wm9081_i2c_resume NULL
|
||||
#endif
|
||||
|
||||
static const struct i2c_device_id wm9081_i2c_id[] = {
|
||||
{ "wm9081", 0 },
|
||||
{ }
|
||||
|
@ -1505,6 +1480,8 @@ static struct i2c_driver wm9081_i2c_driver = {
|
|||
},
|
||||
.probe = wm9081_i2c_probe,
|
||||
.remove = __devexit_p(wm9081_i2c_remove),
|
||||
.suspend = wm9081_i2c_suspend,
|
||||
.resume = wm9081_i2c_resume,
|
||||
.id_table = wm9081_i2c_id,
|
||||
};
|
||||
|
||||
|
|
|
@ -406,7 +406,7 @@ static int wm9705_soc_probe(struct platform_device *pdev)
|
|||
ret = snd_soc_init_card(socdev);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "wm9705: failed to register card\n");
|
||||
goto pcm_err;
|
||||
goto reset_err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
|
@ -0,0 +1,743 @@
|
|||
/*
|
||||
* wm_hubs.c -- WM8993/4 common code
|
||||
*
|
||||
* Copyright 2009 Wolfson Microelectronics plc
|
||||
*
|
||||
* Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
|
||||
*
|
||||
*
|
||||
* 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/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/soc-dapm.h>
|
||||
#include <sound/initval.h>
|
||||
#include <sound/tlv.h>
|
||||
|
||||
#include "wm8993.h"
|
||||
#include "wm_hubs.h"
|
||||
|
||||
const DECLARE_TLV_DB_SCALE(wm_hubs_spkmix_tlv, -300, 300, 0);
|
||||
EXPORT_SYMBOL_GPL(wm_hubs_spkmix_tlv);
|
||||
|
||||
static const DECLARE_TLV_DB_SCALE(inpga_tlv, -1650, 150, 0);
|
||||
static const DECLARE_TLV_DB_SCALE(inmix_sw_tlv, 0, 3000, 0);
|
||||
static const DECLARE_TLV_DB_SCALE(inmix_tlv, -1500, 300, 1);
|
||||
static const DECLARE_TLV_DB_SCALE(earpiece_tlv, -600, 600, 0);
|
||||
static const DECLARE_TLV_DB_SCALE(outmix_tlv, -2100, 300, 0);
|
||||
static const DECLARE_TLV_DB_SCALE(spkmixout_tlv, -1800, 600, 1);
|
||||
static const DECLARE_TLV_DB_SCALE(outpga_tlv, -5700, 100, 0);
|
||||
static const unsigned int spkboost_tlv[] = {
|
||||
TLV_DB_RANGE_HEAD(7),
|
||||
0, 6, TLV_DB_SCALE_ITEM(0, 150, 0),
|
||||
7, 7, TLV_DB_SCALE_ITEM(1200, 0, 0),
|
||||
};
|
||||
static const DECLARE_TLV_DB_SCALE(line_tlv, -600, 600, 0);
|
||||
|
||||
static const char *speaker_ref_text[] = {
|
||||
"SPKVDD/2",
|
||||
"VMID",
|
||||
};
|
||||
|
||||
static const struct soc_enum speaker_ref =
|
||||
SOC_ENUM_SINGLE(WM8993_SPEAKER_MIXER, 8, 2, speaker_ref_text);
|
||||
|
||||
static const char *speaker_mode_text[] = {
|
||||
"Class D",
|
||||
"Class AB",
|
||||
};
|
||||
|
||||
static const struct soc_enum speaker_mode =
|
||||
SOC_ENUM_SINGLE(WM8993_SPKMIXR_ATTENUATION, 8, 2, speaker_mode_text);
|
||||
|
||||
static void wait_for_dc_servo(struct snd_soc_codec *codec)
|
||||
{
|
||||
unsigned int reg;
|
||||
int count = 0;
|
||||
|
||||
dev_dbg(codec->dev, "Waiting for DC servo...\n");
|
||||
do {
|
||||
count++;
|
||||
msleep(1);
|
||||
reg = snd_soc_read(codec, WM8993_DC_SERVO_READBACK_0);
|
||||
dev_dbg(codec->dev, "DC servo status: %x\n", reg);
|
||||
} while ((reg & WM8993_DCS_CAL_COMPLETE_MASK)
|
||||
!= WM8993_DCS_CAL_COMPLETE_MASK && count < 1000);
|
||||
|
||||
if ((reg & WM8993_DCS_CAL_COMPLETE_MASK)
|
||||
!= WM8993_DCS_CAL_COMPLETE_MASK)
|
||||
dev_err(codec->dev, "Timed out waiting for DC Servo\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* Update the DC servo calibration on gain changes
|
||||
*/
|
||||
static int wm8993_put_dc_servo(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
|
||||
int ret;
|
||||
|
||||
ret = snd_soc_put_volsw_2r(kcontrol, ucontrol);
|
||||
|
||||
/* Only need to do this if the outputs are active */
|
||||
if (snd_soc_read(codec, WM8993_POWER_MANAGEMENT_1)
|
||||
& (WM8993_HPOUT1L_ENA | WM8993_HPOUT1R_ENA))
|
||||
snd_soc_update_bits(codec,
|
||||
WM8993_DC_SERVO_0,
|
||||
WM8993_DCS_TRIG_SINGLE_0 |
|
||||
WM8993_DCS_TRIG_SINGLE_1,
|
||||
WM8993_DCS_TRIG_SINGLE_0 |
|
||||
WM8993_DCS_TRIG_SINGLE_1);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct snd_kcontrol_new analogue_snd_controls[] = {
|
||||
SOC_SINGLE_TLV("IN1L Volume", WM8993_LEFT_LINE_INPUT_1_2_VOLUME, 0, 31, 0,
|
||||
inpga_tlv),
|
||||
SOC_SINGLE("IN1L Switch", WM8993_LEFT_LINE_INPUT_1_2_VOLUME, 7, 1, 1),
|
||||
SOC_SINGLE("IN1L ZC Switch", WM8993_LEFT_LINE_INPUT_1_2_VOLUME, 7, 1, 0),
|
||||
|
||||
SOC_SINGLE_TLV("IN1R Volume", WM8993_RIGHT_LINE_INPUT_1_2_VOLUME, 0, 31, 0,
|
||||
inpga_tlv),
|
||||
SOC_SINGLE("IN1R Switch", WM8993_RIGHT_LINE_INPUT_1_2_VOLUME, 7, 1, 1),
|
||||
SOC_SINGLE("IN1R ZC Switch", WM8993_RIGHT_LINE_INPUT_1_2_VOLUME, 7, 1, 0),
|
||||
|
||||
|
||||
SOC_SINGLE_TLV("IN2L Volume", WM8993_LEFT_LINE_INPUT_3_4_VOLUME, 0, 31, 0,
|
||||
inpga_tlv),
|
||||
SOC_SINGLE("IN2L Switch", WM8993_LEFT_LINE_INPUT_3_4_VOLUME, 7, 1, 1),
|
||||
SOC_SINGLE("IN2L ZC Switch", WM8993_LEFT_LINE_INPUT_3_4_VOLUME, 7, 1, 0),
|
||||
|
||||
SOC_SINGLE_TLV("IN2R Volume", WM8993_RIGHT_LINE_INPUT_3_4_VOLUME, 0, 31, 0,
|
||||
inpga_tlv),
|
||||
SOC_SINGLE("IN2R Switch", WM8993_RIGHT_LINE_INPUT_3_4_VOLUME, 7, 1, 1),
|
||||
SOC_SINGLE("IN2R ZC Switch", WM8993_RIGHT_LINE_INPUT_3_4_VOLUME, 7, 1, 0),
|
||||
|
||||
SOC_SINGLE_TLV("MIXINL IN2L Volume", WM8993_INPUT_MIXER3, 7, 1, 0,
|
||||
inmix_sw_tlv),
|
||||
SOC_SINGLE_TLV("MIXINL IN1L Volume", WM8993_INPUT_MIXER3, 4, 1, 0,
|
||||
inmix_sw_tlv),
|
||||
SOC_SINGLE_TLV("MIXINL Output Record Volume", WM8993_INPUT_MIXER3, 0, 7, 0,
|
||||
inmix_tlv),
|
||||
SOC_SINGLE_TLV("MIXINL IN1LP Volume", WM8993_INPUT_MIXER5, 6, 7, 0, inmix_tlv),
|
||||
SOC_SINGLE_TLV("MIXINL Direct Voice Volume", WM8993_INPUT_MIXER5, 0, 6, 0,
|
||||
inmix_tlv),
|
||||
|
||||
SOC_SINGLE_TLV("MIXINR IN2R Volume", WM8993_INPUT_MIXER4, 7, 1, 0,
|
||||
inmix_sw_tlv),
|
||||
SOC_SINGLE_TLV("MIXINR IN1R Volume", WM8993_INPUT_MIXER4, 4, 1, 0,
|
||||
inmix_sw_tlv),
|
||||
SOC_SINGLE_TLV("MIXINR Output Record Volume", WM8993_INPUT_MIXER4, 0, 7, 0,
|
||||
inmix_tlv),
|
||||
SOC_SINGLE_TLV("MIXINR IN1RP Volume", WM8993_INPUT_MIXER6, 6, 7, 0, inmix_tlv),
|
||||
SOC_SINGLE_TLV("MIXINR Direct Voice Volume", WM8993_INPUT_MIXER6, 0, 6, 0,
|
||||
inmix_tlv),
|
||||
|
||||
SOC_SINGLE_TLV("Left Output Mixer IN2RN Volume", WM8993_OUTPUT_MIXER5, 6, 7, 1,
|
||||
outmix_tlv),
|
||||
SOC_SINGLE_TLV("Left Output Mixer IN2LN Volume", WM8993_OUTPUT_MIXER3, 6, 7, 1,
|
||||
outmix_tlv),
|
||||
SOC_SINGLE_TLV("Left Output Mixer IN2LP Volume", WM8993_OUTPUT_MIXER3, 9, 7, 1,
|
||||
outmix_tlv),
|
||||
SOC_SINGLE_TLV("Left Output Mixer IN1L Volume", WM8993_OUTPUT_MIXER3, 0, 7, 1,
|
||||
outmix_tlv),
|
||||
SOC_SINGLE_TLV("Left Output Mixer IN1R Volume", WM8993_OUTPUT_MIXER3, 3, 7, 1,
|
||||
outmix_tlv),
|
||||
SOC_SINGLE_TLV("Left Output Mixer Right Input Volume",
|
||||
WM8993_OUTPUT_MIXER5, 3, 7, 1, outmix_tlv),
|
||||
SOC_SINGLE_TLV("Left Output Mixer Left Input Volume",
|
||||
WM8993_OUTPUT_MIXER5, 0, 7, 1, outmix_tlv),
|
||||
SOC_SINGLE_TLV("Left Output Mixer DAC Volume", WM8993_OUTPUT_MIXER5, 9, 7, 1,
|
||||
outmix_tlv),
|
||||
|
||||
SOC_SINGLE_TLV("Right Output Mixer IN2LN Volume",
|
||||
WM8993_OUTPUT_MIXER6, 6, 7, 1, outmix_tlv),
|
||||
SOC_SINGLE_TLV("Right Output Mixer IN2RN Volume",
|
||||
WM8993_OUTPUT_MIXER4, 6, 7, 1, outmix_tlv),
|
||||
SOC_SINGLE_TLV("Right Output Mixer IN1L Volume",
|
||||
WM8993_OUTPUT_MIXER4, 3, 7, 1, outmix_tlv),
|
||||
SOC_SINGLE_TLV("Right Output Mixer IN1R Volume",
|
||||
WM8993_OUTPUT_MIXER4, 0, 7, 1, outmix_tlv),
|
||||
SOC_SINGLE_TLV("Right Output Mixer IN2RP Volume",
|
||||
WM8993_OUTPUT_MIXER4, 9, 7, 1, outmix_tlv),
|
||||
SOC_SINGLE_TLV("Right Output Mixer Left Input Volume",
|
||||
WM8993_OUTPUT_MIXER6, 3, 7, 1, outmix_tlv),
|
||||
SOC_SINGLE_TLV("Right Output Mixer Right Input Volume",
|
||||
WM8993_OUTPUT_MIXER6, 6, 7, 1, outmix_tlv),
|
||||
SOC_SINGLE_TLV("Right Output Mixer DAC Volume",
|
||||
WM8993_OUTPUT_MIXER6, 9, 7, 1, outmix_tlv),
|
||||
|
||||
SOC_DOUBLE_R_TLV("Output Volume", WM8993_LEFT_OPGA_VOLUME,
|
||||
WM8993_RIGHT_OPGA_VOLUME, 0, 63, 0, outpga_tlv),
|
||||
SOC_DOUBLE_R("Output Switch", WM8993_LEFT_OPGA_VOLUME,
|
||||
WM8993_RIGHT_OPGA_VOLUME, 6, 1, 0),
|
||||
SOC_DOUBLE_R("Output ZC Switch", WM8993_LEFT_OPGA_VOLUME,
|
||||
WM8993_RIGHT_OPGA_VOLUME, 7, 1, 0),
|
||||
|
||||
SOC_SINGLE("Earpiece Switch", WM8993_HPOUT2_VOLUME, 5, 1, 1),
|
||||
SOC_SINGLE_TLV("Earpiece Volume", WM8993_HPOUT2_VOLUME, 4, 1, 1, earpiece_tlv),
|
||||
|
||||
SOC_SINGLE_TLV("SPKL Input Volume", WM8993_SPKMIXL_ATTENUATION,
|
||||
5, 1, 1, wm_hubs_spkmix_tlv),
|
||||
SOC_SINGLE_TLV("SPKL IN1LP Volume", WM8993_SPKMIXL_ATTENUATION,
|
||||
4, 1, 1, wm_hubs_spkmix_tlv),
|
||||
SOC_SINGLE_TLV("SPKL Output Volume", WM8993_SPKMIXL_ATTENUATION,
|
||||
3, 1, 1, wm_hubs_spkmix_tlv),
|
||||
|
||||
SOC_SINGLE_TLV("SPKR Input Volume", WM8993_SPKMIXR_ATTENUATION,
|
||||
5, 1, 1, wm_hubs_spkmix_tlv),
|
||||
SOC_SINGLE_TLV("SPKR IN1RP Volume", WM8993_SPKMIXR_ATTENUATION,
|
||||
4, 1, 1, wm_hubs_spkmix_tlv),
|
||||
SOC_SINGLE_TLV("SPKR Output Volume", WM8993_SPKMIXR_ATTENUATION,
|
||||
3, 1, 1, wm_hubs_spkmix_tlv),
|
||||
|
||||
SOC_DOUBLE_R_TLV("Speaker Mixer Volume",
|
||||
WM8993_SPKMIXL_ATTENUATION, WM8993_SPKMIXR_ATTENUATION,
|
||||
0, 3, 1, spkmixout_tlv),
|
||||
SOC_DOUBLE_R_TLV("Speaker Volume",
|
||||
WM8993_SPEAKER_VOLUME_LEFT, WM8993_SPEAKER_VOLUME_RIGHT,
|
||||
0, 63, 0, outpga_tlv),
|
||||
SOC_DOUBLE_R("Speaker Switch",
|
||||
WM8993_SPEAKER_VOLUME_LEFT, WM8993_SPEAKER_VOLUME_RIGHT,
|
||||
6, 1, 0),
|
||||
SOC_DOUBLE_R("Speaker ZC Switch",
|
||||
WM8993_SPEAKER_VOLUME_LEFT, WM8993_SPEAKER_VOLUME_RIGHT,
|
||||
7, 1, 0),
|
||||
SOC_DOUBLE_TLV("Speaker Boost Volume", WM8993_SPKOUT_BOOST, 0, 3, 7, 0,
|
||||
spkboost_tlv),
|
||||
SOC_ENUM("Speaker Reference", speaker_ref),
|
||||
SOC_ENUM("Speaker Mode", speaker_mode),
|
||||
|
||||
{
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Headphone Volume",
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |
|
||||
SNDRV_CTL_ELEM_ACCESS_READWRITE,
|
||||
.tlv.p = outpga_tlv,
|
||||
.info = snd_soc_info_volsw_2r,
|
||||
.get = snd_soc_get_volsw_2r, .put = wm8993_put_dc_servo,
|
||||
.private_value = (unsigned long)&(struct soc_mixer_control) {
|
||||
.reg = WM8993_LEFT_OUTPUT_VOLUME,
|
||||
.rreg = WM8993_RIGHT_OUTPUT_VOLUME,
|
||||
.shift = 0, .max = 63
|
||||
},
|
||||
},
|
||||
SOC_DOUBLE_R("Headphone Switch", WM8993_LEFT_OUTPUT_VOLUME,
|
||||
WM8993_RIGHT_OUTPUT_VOLUME, 6, 1, 0),
|
||||
SOC_DOUBLE_R("Headphone ZC Switch", WM8993_LEFT_OUTPUT_VOLUME,
|
||||
WM8993_RIGHT_OUTPUT_VOLUME, 7, 1, 0),
|
||||
|
||||
SOC_SINGLE("LINEOUT1N Switch", WM8993_LINE_OUTPUTS_VOLUME, 6, 1, 1),
|
||||
SOC_SINGLE("LINEOUT1P Switch", WM8993_LINE_OUTPUTS_VOLUME, 5, 1, 1),
|
||||
SOC_SINGLE_TLV("LINEOUT1 Volume", WM8993_LINE_OUTPUTS_VOLUME, 4, 1, 1,
|
||||
line_tlv),
|
||||
|
||||
SOC_SINGLE("LINEOUT2N Switch", WM8993_LINE_OUTPUTS_VOLUME, 2, 1, 1),
|
||||
SOC_SINGLE("LINEOUT2P Switch", WM8993_LINE_OUTPUTS_VOLUME, 1, 1, 1),
|
||||
SOC_SINGLE_TLV("LINEOUT2 Volume", WM8993_LINE_OUTPUTS_VOLUME, 0, 1, 1,
|
||||
line_tlv),
|
||||
};
|
||||
|
||||
static int hp_event(struct snd_soc_dapm_widget *w,
|
||||
struct snd_kcontrol *kcontrol, int event)
|
||||
{
|
||||
struct snd_soc_codec *codec = w->codec;
|
||||
unsigned int reg = snd_soc_read(codec, WM8993_ANALOGUE_HP_0);
|
||||
|
||||
switch (event) {
|
||||
case SND_SOC_DAPM_POST_PMU:
|
||||
snd_soc_update_bits(codec, WM8993_CHARGE_PUMP_1,
|
||||
WM8993_CP_ENA, WM8993_CP_ENA);
|
||||
|
||||
msleep(5);
|
||||
|
||||
snd_soc_update_bits(codec, WM8993_POWER_MANAGEMENT_1,
|
||||
WM8993_HPOUT1L_ENA | WM8993_HPOUT1R_ENA,
|
||||
WM8993_HPOUT1L_ENA | WM8993_HPOUT1R_ENA);
|
||||
|
||||
reg |= WM8993_HPOUT1L_DLY | WM8993_HPOUT1R_DLY;
|
||||
snd_soc_write(codec, WM8993_ANALOGUE_HP_0, reg);
|
||||
|
||||
/* Start the DC servo */
|
||||
snd_soc_update_bits(codec, WM8993_DC_SERVO_0,
|
||||
0xFFFF,
|
||||
WM8993_DCS_ENA_CHAN_0 |
|
||||
WM8993_DCS_ENA_CHAN_1 |
|
||||
WM8993_DCS_TRIG_STARTUP_1 |
|
||||
WM8993_DCS_TRIG_STARTUP_0);
|
||||
wait_for_dc_servo(codec);
|
||||
|
||||
reg |= WM8993_HPOUT1R_OUTP | WM8993_HPOUT1R_RMV_SHORT |
|
||||
WM8993_HPOUT1L_OUTP | WM8993_HPOUT1L_RMV_SHORT;
|
||||
snd_soc_write(codec, WM8993_ANALOGUE_HP_0, reg);
|
||||
break;
|
||||
|
||||
case SND_SOC_DAPM_PRE_PMD:
|
||||
reg &= ~(WM8993_HPOUT1L_RMV_SHORT |
|
||||
WM8993_HPOUT1L_DLY |
|
||||
WM8993_HPOUT1L_OUTP |
|
||||
WM8993_HPOUT1R_RMV_SHORT |
|
||||
WM8993_HPOUT1R_DLY |
|
||||
WM8993_HPOUT1R_OUTP);
|
||||
|
||||
snd_soc_update_bits(codec, WM8993_DC_SERVO_0,
|
||||
0xffff, 0);
|
||||
|
||||
snd_soc_write(codec, WM8993_ANALOGUE_HP_0, reg);
|
||||
snd_soc_update_bits(codec, WM8993_POWER_MANAGEMENT_1,
|
||||
WM8993_HPOUT1L_ENA | WM8993_HPOUT1R_ENA,
|
||||
0);
|
||||
|
||||
snd_soc_update_bits(codec, WM8993_CHARGE_PUMP_1,
|
||||
WM8993_CP_ENA, 0);
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int earpiece_event(struct snd_soc_dapm_widget *w,
|
||||
struct snd_kcontrol *control, int event)
|
||||
{
|
||||
struct snd_soc_codec *codec = w->codec;
|
||||
u16 reg = snd_soc_read(codec, WM8993_ANTIPOP1) & ~WM8993_HPOUT2_IN_ENA;
|
||||
|
||||
switch (event) {
|
||||
case SND_SOC_DAPM_PRE_PMU:
|
||||
reg |= WM8993_HPOUT2_IN_ENA;
|
||||
snd_soc_write(codec, WM8993_ANTIPOP1, reg);
|
||||
udelay(50);
|
||||
break;
|
||||
|
||||
case SND_SOC_DAPM_POST_PMD:
|
||||
snd_soc_write(codec, WM8993_ANTIPOP1, reg);
|
||||
break;
|
||||
|
||||
default:
|
||||
BUG();
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct snd_kcontrol_new in1l_pga[] = {
|
||||
SOC_DAPM_SINGLE("IN1LP Switch", WM8993_INPUT_MIXER2, 5, 1, 0),
|
||||
SOC_DAPM_SINGLE("IN1LN Switch", WM8993_INPUT_MIXER2, 4, 1, 0),
|
||||
};
|
||||
|
||||
static const struct snd_kcontrol_new in1r_pga[] = {
|
||||
SOC_DAPM_SINGLE("IN1RP Switch", WM8993_INPUT_MIXER2, 1, 1, 0),
|
||||
SOC_DAPM_SINGLE("IN1RN Switch", WM8993_INPUT_MIXER2, 0, 1, 0),
|
||||
};
|
||||
|
||||
static const struct snd_kcontrol_new in2l_pga[] = {
|
||||
SOC_DAPM_SINGLE("IN2LP Switch", WM8993_INPUT_MIXER2, 7, 1, 0),
|
||||
SOC_DAPM_SINGLE("IN2LN Switch", WM8993_INPUT_MIXER2, 6, 1, 0),
|
||||
};
|
||||
|
||||
static const struct snd_kcontrol_new in2r_pga[] = {
|
||||
SOC_DAPM_SINGLE("IN2RP Switch", WM8993_INPUT_MIXER2, 3, 1, 0),
|
||||
SOC_DAPM_SINGLE("IN2RN Switch", WM8993_INPUT_MIXER2, 2, 1, 0),
|
||||
};
|
||||
|
||||
static const struct snd_kcontrol_new mixinl[] = {
|
||||
SOC_DAPM_SINGLE("IN2L Switch", WM8993_INPUT_MIXER3, 8, 1, 0),
|
||||
SOC_DAPM_SINGLE("IN1L Switch", WM8993_INPUT_MIXER3, 5, 1, 0),
|
||||
};
|
||||
|
||||
static const struct snd_kcontrol_new mixinr[] = {
|
||||
SOC_DAPM_SINGLE("IN2R Switch", WM8993_INPUT_MIXER4, 8, 1, 0),
|
||||
SOC_DAPM_SINGLE("IN1R Switch", WM8993_INPUT_MIXER4, 5, 1, 0),
|
||||
};
|
||||
|
||||
static const struct snd_kcontrol_new left_output_mixer[] = {
|
||||
SOC_DAPM_SINGLE("Right Input Switch", WM8993_OUTPUT_MIXER1, 7, 1, 0),
|
||||
SOC_DAPM_SINGLE("Left Input Switch", WM8993_OUTPUT_MIXER1, 6, 1, 0),
|
||||
SOC_DAPM_SINGLE("IN2RN Switch", WM8993_OUTPUT_MIXER1, 5, 1, 0),
|
||||
SOC_DAPM_SINGLE("IN2LN Switch", WM8993_OUTPUT_MIXER1, 4, 1, 0),
|
||||
SOC_DAPM_SINGLE("IN2LP Switch", WM8993_OUTPUT_MIXER1, 1, 1, 0),
|
||||
SOC_DAPM_SINGLE("IN1R Switch", WM8993_OUTPUT_MIXER1, 3, 1, 0),
|
||||
SOC_DAPM_SINGLE("IN1L Switch", WM8993_OUTPUT_MIXER1, 2, 1, 0),
|
||||
SOC_DAPM_SINGLE("DAC Switch", WM8993_OUTPUT_MIXER1, 0, 1, 0),
|
||||
};
|
||||
|
||||
static const struct snd_kcontrol_new right_output_mixer[] = {
|
||||
SOC_DAPM_SINGLE("Left Input Switch", WM8993_OUTPUT_MIXER2, 7, 1, 0),
|
||||
SOC_DAPM_SINGLE("Right Input Switch", WM8993_OUTPUT_MIXER2, 6, 1, 0),
|
||||
SOC_DAPM_SINGLE("IN2LN Switch", WM8993_OUTPUT_MIXER2, 5, 1, 0),
|
||||
SOC_DAPM_SINGLE("IN2RN Switch", WM8993_OUTPUT_MIXER2, 4, 1, 0),
|
||||
SOC_DAPM_SINGLE("IN1L Switch", WM8993_OUTPUT_MIXER2, 3, 1, 0),
|
||||
SOC_DAPM_SINGLE("IN1R Switch", WM8993_OUTPUT_MIXER2, 2, 1, 0),
|
||||
SOC_DAPM_SINGLE("IN2RP Switch", WM8993_OUTPUT_MIXER2, 1, 1, 0),
|
||||
SOC_DAPM_SINGLE("DAC Switch", WM8993_OUTPUT_MIXER2, 0, 1, 0),
|
||||
};
|
||||
|
||||
static const struct snd_kcontrol_new earpiece_mixer[] = {
|
||||
SOC_DAPM_SINGLE("Direct Voice Switch", WM8993_HPOUT2_MIXER, 5, 1, 0),
|
||||
SOC_DAPM_SINGLE("Left Output Switch", WM8993_HPOUT2_MIXER, 4, 1, 0),
|
||||
SOC_DAPM_SINGLE("Right Output Switch", WM8993_HPOUT2_MIXER, 3, 1, 0),
|
||||
};
|
||||
|
||||
static const struct snd_kcontrol_new left_speaker_boost[] = {
|
||||
SOC_DAPM_SINGLE("Direct Voice Switch", WM8993_SPKOUT_MIXERS, 5, 1, 0),
|
||||
SOC_DAPM_SINGLE("SPKL Switch", WM8993_SPKOUT_MIXERS, 4, 1, 0),
|
||||
SOC_DAPM_SINGLE("SPKR Switch", WM8993_SPKOUT_MIXERS, 3, 1, 0),
|
||||
};
|
||||
|
||||
static const struct snd_kcontrol_new right_speaker_boost[] = {
|
||||
SOC_DAPM_SINGLE("Direct Voice Switch", WM8993_SPKOUT_MIXERS, 2, 1, 0),
|
||||
SOC_DAPM_SINGLE("SPKL Switch", WM8993_SPKOUT_MIXERS, 1, 1, 0),
|
||||
SOC_DAPM_SINGLE("SPKR Switch", WM8993_SPKOUT_MIXERS, 0, 1, 0),
|
||||
};
|
||||
|
||||
static const struct snd_kcontrol_new line1_mix[] = {
|
||||
SOC_DAPM_SINGLE("IN1R Switch", WM8993_LINE_MIXER1, 2, 1, 0),
|
||||
SOC_DAPM_SINGLE("IN1L Switch", WM8993_LINE_MIXER1, 1, 1, 0),
|
||||
SOC_DAPM_SINGLE("Output Switch", WM8993_LINE_MIXER1, 0, 1, 0),
|
||||
};
|
||||
|
||||
static const struct snd_kcontrol_new line1n_mix[] = {
|
||||
SOC_DAPM_SINGLE("Left Output Switch", WM8993_LINE_MIXER1, 6, 1, 0),
|
||||
SOC_DAPM_SINGLE("Right Output Switch", WM8993_LINE_MIXER1, 5, 1, 0),
|
||||
};
|
||||
|
||||
static const struct snd_kcontrol_new line1p_mix[] = {
|
||||
SOC_DAPM_SINGLE("Left Output Switch", WM8993_LINE_MIXER1, 0, 1, 0),
|
||||
};
|
||||
|
||||
static const struct snd_kcontrol_new line2_mix[] = {
|
||||
SOC_DAPM_SINGLE("IN2R Switch", WM8993_LINE_MIXER2, 2, 1, 0),
|
||||
SOC_DAPM_SINGLE("IN2L Switch", WM8993_LINE_MIXER2, 1, 1, 0),
|
||||
SOC_DAPM_SINGLE("Output Switch", WM8993_LINE_MIXER2, 0, 1, 0),
|
||||
};
|
||||
|
||||
static const struct snd_kcontrol_new line2n_mix[] = {
|
||||
SOC_DAPM_SINGLE("Left Output Switch", WM8993_LINE_MIXER2, 6, 1, 0),
|
||||
SOC_DAPM_SINGLE("Right Output Switch", WM8993_LINE_MIXER2, 5, 1, 0),
|
||||
};
|
||||
|
||||
static const struct snd_kcontrol_new line2p_mix[] = {
|
||||
SOC_DAPM_SINGLE("Right Output Switch", WM8993_LINE_MIXER2, 0, 1, 0),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_widget analogue_dapm_widgets[] = {
|
||||
SND_SOC_DAPM_INPUT("IN1LN"),
|
||||
SND_SOC_DAPM_INPUT("IN1LP"),
|
||||
SND_SOC_DAPM_INPUT("IN2LN"),
|
||||
SND_SOC_DAPM_INPUT("IN2LP/VXRN"),
|
||||
SND_SOC_DAPM_INPUT("IN1RN"),
|
||||
SND_SOC_DAPM_INPUT("IN1RP"),
|
||||
SND_SOC_DAPM_INPUT("IN2RN"),
|
||||
SND_SOC_DAPM_INPUT("IN2RP/VXRP"),
|
||||
|
||||
SND_SOC_DAPM_MICBIAS("MICBIAS2", WM8993_POWER_MANAGEMENT_1, 5, 0),
|
||||
SND_SOC_DAPM_MICBIAS("MICBIAS1", WM8993_POWER_MANAGEMENT_1, 4, 0),
|
||||
|
||||
SND_SOC_DAPM_MIXER("IN1L PGA", WM8993_POWER_MANAGEMENT_2, 6, 0,
|
||||
in1l_pga, ARRAY_SIZE(in1l_pga)),
|
||||
SND_SOC_DAPM_MIXER("IN1R PGA", WM8993_POWER_MANAGEMENT_2, 4, 0,
|
||||
in1r_pga, ARRAY_SIZE(in1r_pga)),
|
||||
|
||||
SND_SOC_DAPM_MIXER("IN2L PGA", WM8993_POWER_MANAGEMENT_2, 7, 0,
|
||||
in2l_pga, ARRAY_SIZE(in2l_pga)),
|
||||
SND_SOC_DAPM_MIXER("IN2R PGA", WM8993_POWER_MANAGEMENT_2, 5, 0,
|
||||
in2r_pga, ARRAY_SIZE(in2r_pga)),
|
||||
|
||||
/* Dummy widgets to represent differential paths */
|
||||
SND_SOC_DAPM_PGA("Direct Voice", SND_SOC_NOPM, 0, 0, NULL, 0),
|
||||
|
||||
SND_SOC_DAPM_MIXER("MIXINL", WM8993_POWER_MANAGEMENT_2, 9, 0,
|
||||
mixinl, ARRAY_SIZE(mixinl)),
|
||||
SND_SOC_DAPM_MIXER("MIXINR", WM8993_POWER_MANAGEMENT_2, 8, 0,
|
||||
mixinr, ARRAY_SIZE(mixinr)),
|
||||
|
||||
SND_SOC_DAPM_MIXER("Left Output Mixer", WM8993_POWER_MANAGEMENT_3, 5, 0,
|
||||
left_output_mixer, ARRAY_SIZE(left_output_mixer)),
|
||||
SND_SOC_DAPM_MIXER("Right Output Mixer", WM8993_POWER_MANAGEMENT_3, 4, 0,
|
||||
right_output_mixer, ARRAY_SIZE(right_output_mixer)),
|
||||
|
||||
SND_SOC_DAPM_PGA("Left Output PGA", WM8993_POWER_MANAGEMENT_3, 7, 0, NULL, 0),
|
||||
SND_SOC_DAPM_PGA("Right Output PGA", WM8993_POWER_MANAGEMENT_3, 6, 0, NULL, 0),
|
||||
|
||||
SND_SOC_DAPM_PGA_E("Headphone PGA", SND_SOC_NOPM, 0, 0,
|
||||
NULL, 0,
|
||||
hp_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
|
||||
|
||||
SND_SOC_DAPM_MIXER("Earpiece Mixer", SND_SOC_NOPM, 0, 0,
|
||||
earpiece_mixer, ARRAY_SIZE(earpiece_mixer)),
|
||||
SND_SOC_DAPM_PGA_E("Earpiece Driver", WM8993_POWER_MANAGEMENT_1, 11, 0,
|
||||
NULL, 0, earpiece_event,
|
||||
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
|
||||
|
||||
SND_SOC_DAPM_MIXER("SPKL Boost", SND_SOC_NOPM, 0, 0,
|
||||
left_speaker_boost, ARRAY_SIZE(left_speaker_boost)),
|
||||
SND_SOC_DAPM_MIXER("SPKR Boost", SND_SOC_NOPM, 0, 0,
|
||||
right_speaker_boost, ARRAY_SIZE(right_speaker_boost)),
|
||||
|
||||
SND_SOC_DAPM_PGA("SPKL Driver", WM8993_POWER_MANAGEMENT_1, 12, 0,
|
||||
NULL, 0),
|
||||
SND_SOC_DAPM_PGA("SPKR Driver", WM8993_POWER_MANAGEMENT_1, 13, 0,
|
||||
NULL, 0),
|
||||
|
||||
SND_SOC_DAPM_MIXER("LINEOUT1 Mixer", SND_SOC_NOPM, 0, 0,
|
||||
line1_mix, ARRAY_SIZE(line1_mix)),
|
||||
SND_SOC_DAPM_MIXER("LINEOUT2 Mixer", SND_SOC_NOPM, 0, 0,
|
||||
line2_mix, ARRAY_SIZE(line2_mix)),
|
||||
|
||||
SND_SOC_DAPM_MIXER("LINEOUT1N Mixer", SND_SOC_NOPM, 0, 0,
|
||||
line1n_mix, ARRAY_SIZE(line1n_mix)),
|
||||
SND_SOC_DAPM_MIXER("LINEOUT1P Mixer", SND_SOC_NOPM, 0, 0,
|
||||
line1p_mix, ARRAY_SIZE(line1p_mix)),
|
||||
SND_SOC_DAPM_MIXER("LINEOUT2N Mixer", SND_SOC_NOPM, 0, 0,
|
||||
line2n_mix, ARRAY_SIZE(line2n_mix)),
|
||||
SND_SOC_DAPM_MIXER("LINEOUT2P Mixer", SND_SOC_NOPM, 0, 0,
|
||||
line2p_mix, ARRAY_SIZE(line2p_mix)),
|
||||
|
||||
SND_SOC_DAPM_PGA("LINEOUT1N Driver", WM8993_POWER_MANAGEMENT_3, 13, 0,
|
||||
NULL, 0),
|
||||
SND_SOC_DAPM_PGA("LINEOUT1P Driver", WM8993_POWER_MANAGEMENT_3, 12, 0,
|
||||
NULL, 0),
|
||||
SND_SOC_DAPM_PGA("LINEOUT2N Driver", WM8993_POWER_MANAGEMENT_3, 11, 0,
|
||||
NULL, 0),
|
||||
SND_SOC_DAPM_PGA("LINEOUT2P Driver", WM8993_POWER_MANAGEMENT_3, 10, 0,
|
||||
NULL, 0),
|
||||
|
||||
SND_SOC_DAPM_OUTPUT("SPKOUTLP"),
|
||||
SND_SOC_DAPM_OUTPUT("SPKOUTLN"),
|
||||
SND_SOC_DAPM_OUTPUT("SPKOUTRP"),
|
||||
SND_SOC_DAPM_OUTPUT("SPKOUTRN"),
|
||||
SND_SOC_DAPM_OUTPUT("HPOUT1L"),
|
||||
SND_SOC_DAPM_OUTPUT("HPOUT1R"),
|
||||
SND_SOC_DAPM_OUTPUT("HPOUT2P"),
|
||||
SND_SOC_DAPM_OUTPUT("HPOUT2N"),
|
||||
SND_SOC_DAPM_OUTPUT("LINEOUT1P"),
|
||||
SND_SOC_DAPM_OUTPUT("LINEOUT1N"),
|
||||
SND_SOC_DAPM_OUTPUT("LINEOUT2P"),
|
||||
SND_SOC_DAPM_OUTPUT("LINEOUT2N"),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route analogue_routes[] = {
|
||||
{ "IN1L PGA", "IN1LP Switch", "IN1LP" },
|
||||
{ "IN1L PGA", "IN1LN Switch", "IN1LN" },
|
||||
|
||||
{ "IN1R PGA", "IN1RP Switch", "IN1RP" },
|
||||
{ "IN1R PGA", "IN1RN Switch", "IN1RN" },
|
||||
|
||||
{ "IN2L PGA", "IN2LP Switch", "IN2LP/VXRN" },
|
||||
{ "IN2L PGA", "IN2LN Switch", "IN2LN" },
|
||||
|
||||
{ "IN2R PGA", "IN2RP Switch", "IN2RP/VXRP" },
|
||||
{ "IN2R PGA", "IN2RN Switch", "IN2RN" },
|
||||
|
||||
{ "Direct Voice", NULL, "IN2LP/VXRN" },
|
||||
{ "Direct Voice", NULL, "IN2RP/VXRP" },
|
||||
|
||||
{ "MIXINL", "IN1L Switch", "IN1L PGA" },
|
||||
{ "MIXINL", "IN2L Switch", "IN2L PGA" },
|
||||
{ "MIXINL", NULL, "Direct Voice" },
|
||||
{ "MIXINL", NULL, "IN1LP" },
|
||||
{ "MIXINL", NULL, "Left Output Mixer" },
|
||||
|
||||
{ "MIXINR", "IN1R Switch", "IN1R PGA" },
|
||||
{ "MIXINR", "IN2R Switch", "IN2R PGA" },
|
||||
{ "MIXINR", NULL, "Direct Voice" },
|
||||
{ "MIXINR", NULL, "IN1RP" },
|
||||
{ "MIXINR", NULL, "Right Output Mixer" },
|
||||
|
||||
{ "ADCL", NULL, "MIXINL" },
|
||||
{ "ADCR", NULL, "MIXINR" },
|
||||
|
||||
{ "Left Output Mixer", "Left Input Switch", "MIXINL" },
|
||||
{ "Left Output Mixer", "Right Input Switch", "MIXINR" },
|
||||
{ "Left Output Mixer", "IN2RN Switch", "IN2RN" },
|
||||
{ "Left Output Mixer", "IN2LN Switch", "IN2LN" },
|
||||
{ "Left Output Mixer", "IN2LP Switch", "IN2LP/VXRN" },
|
||||
{ "Left Output Mixer", "IN1L Switch", "IN1L PGA" },
|
||||
{ "Left Output Mixer", "IN1R Switch", "IN1R PGA" },
|
||||
|
||||
{ "Right Output Mixer", "Left Input Switch", "MIXINL" },
|
||||
{ "Right Output Mixer", "Right Input Switch", "MIXINR" },
|
||||
{ "Right Output Mixer", "IN2LN Switch", "IN2LN" },
|
||||
{ "Right Output Mixer", "IN2RN Switch", "IN2RN" },
|
||||
{ "Right Output Mixer", "IN2RP Switch", "IN2RP/VXRP" },
|
||||
{ "Right Output Mixer", "IN1L Switch", "IN1L PGA" },
|
||||
{ "Right Output Mixer", "IN1R Switch", "IN1R PGA" },
|
||||
|
||||
{ "Left Output PGA", NULL, "Left Output Mixer" },
|
||||
{ "Left Output PGA", NULL, "TOCLK" },
|
||||
|
||||
{ "Right Output PGA", NULL, "Right Output Mixer" },
|
||||
{ "Right Output PGA", NULL, "TOCLK" },
|
||||
|
||||
{ "Earpiece Mixer", "Direct Voice Switch", "Direct Voice" },
|
||||
{ "Earpiece Mixer", "Left Output Switch", "Left Output PGA" },
|
||||
{ "Earpiece Mixer", "Right Output Switch", "Right Output PGA" },
|
||||
|
||||
{ "Earpiece Driver", NULL, "Earpiece Mixer" },
|
||||
{ "HPOUT2N", NULL, "Earpiece Driver" },
|
||||
{ "HPOUT2P", NULL, "Earpiece Driver" },
|
||||
|
||||
{ "SPKL", "Input Switch", "MIXINL" },
|
||||
{ "SPKL", "IN1LP Switch", "IN1LP" },
|
||||
{ "SPKL", "Output Switch", "Left Output Mixer" },
|
||||
{ "SPKL", NULL, "TOCLK" },
|
||||
|
||||
{ "SPKR", "Input Switch", "MIXINR" },
|
||||
{ "SPKR", "IN1RP Switch", "IN1RP" },
|
||||
{ "SPKR", "Output Switch", "Right Output Mixer" },
|
||||
{ "SPKR", NULL, "TOCLK" },
|
||||
|
||||
{ "SPKL Boost", "Direct Voice Switch", "Direct Voice" },
|
||||
{ "SPKL Boost", "SPKL Switch", "SPKL" },
|
||||
{ "SPKL Boost", "SPKR Switch", "SPKR" },
|
||||
|
||||
{ "SPKR Boost", "Direct Voice Switch", "Direct Voice" },
|
||||
{ "SPKR Boost", "SPKR Switch", "SPKR" },
|
||||
{ "SPKR Boost", "SPKL Switch", "SPKL" },
|
||||
|
||||
{ "SPKL Driver", NULL, "SPKL Boost" },
|
||||
{ "SPKL Driver", NULL, "CLK_SYS" },
|
||||
|
||||
{ "SPKR Driver", NULL, "SPKR Boost" },
|
||||
{ "SPKR Driver", NULL, "CLK_SYS" },
|
||||
|
||||
{ "SPKOUTLP", NULL, "SPKL Driver" },
|
||||
{ "SPKOUTLN", NULL, "SPKL Driver" },
|
||||
{ "SPKOUTRP", NULL, "SPKR Driver" },
|
||||
{ "SPKOUTRN", NULL, "SPKR Driver" },
|
||||
|
||||
{ "Left Headphone Mux", "Mixer", "Left Output Mixer" },
|
||||
{ "Right Headphone Mux", "Mixer", "Right Output Mixer" },
|
||||
|
||||
{ "Headphone PGA", NULL, "Left Headphone Mux" },
|
||||
{ "Headphone PGA", NULL, "Right Headphone Mux" },
|
||||
{ "Headphone PGA", NULL, "CLK_SYS" },
|
||||
|
||||
{ "HPOUT1L", NULL, "Headphone PGA" },
|
||||
{ "HPOUT1R", NULL, "Headphone PGA" },
|
||||
|
||||
{ "LINEOUT1N", NULL, "LINEOUT1N Driver" },
|
||||
{ "LINEOUT1P", NULL, "LINEOUT1P Driver" },
|
||||
{ "LINEOUT2N", NULL, "LINEOUT2N Driver" },
|
||||
{ "LINEOUT2P", NULL, "LINEOUT2P Driver" },
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route lineout1_diff_routes[] = {
|
||||
{ "LINEOUT1 Mixer", "IN1L Switch", "IN1L PGA" },
|
||||
{ "LINEOUT1 Mixer", "IN1R Switch", "IN1R PGA" },
|
||||
{ "LINEOUT1 Mixer", "Output Switch", "Left Output Mixer" },
|
||||
|
||||
{ "LINEOUT1N Driver", NULL, "LINEOUT1 Mixer" },
|
||||
{ "LINEOUT1P Driver", NULL, "LINEOUT1 Mixer" },
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route lineout1_se_routes[] = {
|
||||
{ "LINEOUT1N Mixer", "Left Output Switch", "Left Output Mixer" },
|
||||
{ "LINEOUT1N Mixer", "Right Output Switch", "Left Output Mixer" },
|
||||
|
||||
{ "LINEOUT1P Mixer", "Left Output Switch", "Left Output Mixer" },
|
||||
|
||||
{ "LINEOUT1N Driver", NULL, "LINEOUT1N Mixer" },
|
||||
{ "LINEOUT1P Driver", NULL, "LINEOUT1P Mixer" },
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route lineout2_diff_routes[] = {
|
||||
{ "LINEOUT2 Mixer", "IN2L Switch", "IN2L PGA" },
|
||||
{ "LINEOUT2 Mixer", "IN2R Switch", "IN2R PGA" },
|
||||
{ "LINEOUT2 Mixer", "Output Switch", "Right Output Mixer" },
|
||||
|
||||
{ "LINEOUT2N Driver", NULL, "LINEOUT2 Mixer" },
|
||||
{ "LINEOUT2P Driver", NULL, "LINEOUT2 Mixer" },
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route lineout2_se_routes[] = {
|
||||
{ "LINEOUT2N Mixer", "Left Output Switch", "Left Output Mixer" },
|
||||
{ "LINEOUT2N Mixer", "Right Output Switch", "Left Output Mixer" },
|
||||
|
||||
{ "LINEOUT2P Mixer", "Right Output Switch", "Right Output Mixer" },
|
||||
|
||||
{ "LINEOUT2N Driver", NULL, "LINEOUT2N Mixer" },
|
||||
{ "LINEOUT2P Driver", NULL, "LINEOUT2P Mixer" },
|
||||
};
|
||||
|
||||
int wm_hubs_add_analogue_controls(struct snd_soc_codec *codec)
|
||||
{
|
||||
/* Latch volume update bits & default ZC on */
|
||||
snd_soc_update_bits(codec, WM8993_LEFT_LINE_INPUT_1_2_VOLUME,
|
||||
WM8993_IN1_VU, WM8993_IN1_VU);
|
||||
snd_soc_update_bits(codec, WM8993_RIGHT_LINE_INPUT_1_2_VOLUME,
|
||||
WM8993_IN1_VU, WM8993_IN1_VU);
|
||||
snd_soc_update_bits(codec, WM8993_LEFT_LINE_INPUT_3_4_VOLUME,
|
||||
WM8993_IN2_VU, WM8993_IN2_VU);
|
||||
snd_soc_update_bits(codec, WM8993_RIGHT_LINE_INPUT_3_4_VOLUME,
|
||||
WM8993_IN2_VU, WM8993_IN2_VU);
|
||||
|
||||
snd_soc_update_bits(codec, WM8993_SPEAKER_VOLUME_RIGHT,
|
||||
WM8993_SPKOUT_VU, WM8993_SPKOUT_VU);
|
||||
|
||||
snd_soc_update_bits(codec, WM8993_LEFT_OUTPUT_VOLUME,
|
||||
WM8993_HPOUT1L_ZC, WM8993_HPOUT1L_ZC);
|
||||
snd_soc_update_bits(codec, WM8993_RIGHT_OUTPUT_VOLUME,
|
||||
WM8993_HPOUT1_VU | WM8993_HPOUT1R_ZC,
|
||||
WM8993_HPOUT1_VU | WM8993_HPOUT1R_ZC);
|
||||
|
||||
snd_soc_update_bits(codec, WM8993_LEFT_OPGA_VOLUME,
|
||||
WM8993_MIXOUTL_ZC, WM8993_MIXOUTL_ZC);
|
||||
snd_soc_update_bits(codec, WM8993_RIGHT_OPGA_VOLUME,
|
||||
WM8993_MIXOUTR_ZC | WM8993_MIXOUT_VU,
|
||||
WM8993_MIXOUTR_ZC | WM8993_MIXOUT_VU);
|
||||
|
||||
snd_soc_add_controls(codec, analogue_snd_controls,
|
||||
ARRAY_SIZE(analogue_snd_controls));
|
||||
|
||||
snd_soc_dapm_new_controls(codec, analogue_dapm_widgets,
|
||||
ARRAY_SIZE(analogue_dapm_widgets));
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wm_hubs_add_analogue_controls);
|
||||
|
||||
int wm_hubs_add_analogue_routes(struct snd_soc_codec *codec,
|
||||
int lineout1_diff, int lineout2_diff)
|
||||
{
|
||||
snd_soc_dapm_add_routes(codec, analogue_routes,
|
||||
ARRAY_SIZE(analogue_routes));
|
||||
|
||||
if (lineout1_diff)
|
||||
snd_soc_dapm_add_routes(codec,
|
||||
lineout1_diff_routes,
|
||||
ARRAY_SIZE(lineout1_diff_routes));
|
||||
else
|
||||
snd_soc_dapm_add_routes(codec,
|
||||
lineout1_se_routes,
|
||||
ARRAY_SIZE(lineout1_se_routes));
|
||||
|
||||
if (lineout2_diff)
|
||||
snd_soc_dapm_add_routes(codec,
|
||||
lineout2_diff_routes,
|
||||
ARRAY_SIZE(lineout2_diff_routes));
|
||||
else
|
||||
snd_soc_dapm_add_routes(codec,
|
||||
lineout2_se_routes,
|
||||
ARRAY_SIZE(lineout2_se_routes));
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wm_hubs_add_analogue_routes);
|
||||
|
||||
MODULE_DESCRIPTION("Shared support for Wolfson hubs products");
|
||||
MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* wm_hubs.h -- WM899x common code
|
||||
*
|
||||
* Copyright 2009 Wolfson Microelectronics plc
|
||||
*
|
||||
* Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
|
||||
*
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef _WM_HUBS_H
|
||||
#define _WM_HUBS_H
|
||||
|
||||
struct snd_soc_codec;
|
||||
|
||||
extern const unsigned int wm_hubs_spkmix_tlv[];
|
||||
|
||||
extern int wm_hubs_add_analogue_controls(struct snd_soc_codec *);
|
||||
extern int wm_hubs_add_analogue_routes(struct snd_soc_codec *, int, int);
|
||||
|
||||
#endif
|
|
@ -9,6 +9,9 @@ config SND_DAVINCI_SOC
|
|||
config SND_DAVINCI_SOC_I2S
|
||||
tristate
|
||||
|
||||
config SND_DAVINCI_SOC_MCASP
|
||||
tristate
|
||||
|
||||
config SND_DAVINCI_SOC_EVM
|
||||
tristate "SoC Audio support for DaVinci DM6446 or DM355 EVM"
|
||||
depends on SND_DAVINCI_SOC
|
||||
|
@ -19,6 +22,16 @@ config SND_DAVINCI_SOC_EVM
|
|||
Say Y if you want to add support for SoC audio on TI
|
||||
DaVinci DM6446 or DM355 EVM platforms.
|
||||
|
||||
config SND_DM6467_SOC_EVM
|
||||
tristate "SoC Audio support for DaVinci DM6467 EVM"
|
||||
depends on SND_DAVINCI_SOC && MACH_DAVINCI_DM6467_EVM
|
||||
select SND_DAVINCI_SOC_MCASP
|
||||
select SND_SOC_TLV320AIC3X
|
||||
select SND_SOC_SPDIF
|
||||
|
||||
help
|
||||
Say Y if you want to add support for SoC audio on TI
|
||||
|
||||
config SND_DAVINCI_SOC_SFFSDR
|
||||
tristate "SoC Audio support for SFFSDR"
|
||||
depends on SND_DAVINCI_SOC && MACH_SFFSDR
|
||||
|
@ -28,3 +41,23 @@ config SND_DAVINCI_SOC_SFFSDR
|
|||
help
|
||||
Say Y if you want to add support for SoC audio on
|
||||
Lyrtech SFFSDR board.
|
||||
|
||||
config SND_DA830_SOC_EVM
|
||||
tristate "SoC Audio support for DA830/OMAP-L137 EVM"
|
||||
depends on SND_DAVINCI_SOC && MACH_DAVINCI_DA830_EVM
|
||||
select SND_DAVINCI_SOC_MCASP
|
||||
select SND_SOC_TLV320AIC3X
|
||||
|
||||
help
|
||||
Say Y if you want to add support for SoC audio on TI
|
||||
DA830/OMAP-L137 EVM
|
||||
|
||||
config SND_DA850_SOC_EVM
|
||||
tristate "SoC Audio support for DA850/OMAP-L138 EVM"
|
||||
depends on SND_DAVINCI_SOC && MACH_DAVINCI_DA850_EVM
|
||||
select SND_DAVINCI_SOC_MCASP
|
||||
select SND_SOC_TLV320AIC3X
|
||||
help
|
||||
Say Y if you want to add support for SoC audio on TI
|
||||
DA850/OMAP-L138 EVM
|
||||
|
||||
|
|
|
@ -1,13 +1,18 @@
|
|||
# DAVINCI Platform Support
|
||||
snd-soc-davinci-objs := davinci-pcm.o
|
||||
snd-soc-davinci-i2s-objs := davinci-i2s.o
|
||||
snd-soc-davinci-mcasp-objs:= davinci-mcasp.o
|
||||
|
||||
obj-$(CONFIG_SND_DAVINCI_SOC) += snd-soc-davinci.o
|
||||
obj-$(CONFIG_SND_DAVINCI_SOC_I2S) += snd-soc-davinci-i2s.o
|
||||
obj-$(CONFIG_SND_DAVINCI_SOC_MCASP) += snd-soc-davinci-mcasp.o
|
||||
|
||||
# DAVINCI Machine Support
|
||||
snd-soc-evm-objs := davinci-evm.o
|
||||
snd-soc-sffsdr-objs := davinci-sffsdr.o
|
||||
|
||||
obj-$(CONFIG_SND_DAVINCI_SOC_EVM) += snd-soc-evm.o
|
||||
obj-$(CONFIG_SND_DM6467_SOC_EVM) += snd-soc-evm.o
|
||||
obj-$(CONFIG_SND_DA830_SOC_EVM) += snd-soc-evm.o
|
||||
obj-$(CONFIG_SND_DA850_SOC_EVM) += snd-soc-evm.o
|
||||
obj-$(CONFIG_SND_DAVINCI_SOC_SFFSDR) += snd-soc-sffsdr.o
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include <linux/timer.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/soc.h>
|
||||
|
@ -27,9 +28,10 @@
|
|||
#include <mach/mux.h>
|
||||
|
||||
#include "../codecs/tlv320aic3x.h"
|
||||
#include "../codecs/spdif_transciever.h"
|
||||
#include "davinci-pcm.h"
|
||||
#include "davinci-i2s.h"
|
||||
|
||||
#include "davinci-mcasp.h"
|
||||
|
||||
#define AUDIO_FORMAT (SND_SOC_DAIFMT_DSP_B | \
|
||||
SND_SOC_DAIFMT_CBM_CFM | SND_SOC_DAIFMT_IB_NF)
|
||||
|
@ -43,7 +45,7 @@ static int evm_hw_params(struct snd_pcm_substream *substream,
|
|||
unsigned sysclk;
|
||||
|
||||
/* ASP1 on DM355 EVM is clocked by an external oscillator */
|
||||
if (machine_is_davinci_dm355_evm())
|
||||
if (machine_is_davinci_dm355_evm() || machine_is_davinci_dm6467_evm())
|
||||
sysclk = 27000000;
|
||||
|
||||
/* ASP0 in DM6446 EVM is clocked by U55, as configured by
|
||||
|
@ -53,6 +55,10 @@ static int evm_hw_params(struct snd_pcm_substream *substream,
|
|||
else if (machine_is_davinci_evm())
|
||||
sysclk = 12288000;
|
||||
|
||||
else if (machine_is_davinci_da830_evm() ||
|
||||
machine_is_davinci_da850_evm())
|
||||
sysclk = 24576000;
|
||||
|
||||
else
|
||||
return -EINVAL;
|
||||
|
||||
|
@ -144,6 +150,32 @@ static struct snd_soc_dai_link evm_dai = {
|
|||
.ops = &evm_ops,
|
||||
};
|
||||
|
||||
static struct snd_soc_dai_link dm6467_evm_dai[] = {
|
||||
{
|
||||
.name = "TLV320AIC3X",
|
||||
.stream_name = "AIC3X",
|
||||
.cpu_dai = &davinci_mcasp_dai[DAVINCI_MCASP_I2S_DAI],
|
||||
.codec_dai = &aic3x_dai,
|
||||
.init = evm_aic3x_init,
|
||||
.ops = &evm_ops,
|
||||
},
|
||||
{
|
||||
.name = "McASP",
|
||||
.stream_name = "spdif",
|
||||
.cpu_dai = &davinci_mcasp_dai[DAVINCI_MCASP_DIT_DAI],
|
||||
.codec_dai = &dit_stub_dai,
|
||||
.ops = &evm_ops,
|
||||
},
|
||||
};
|
||||
static struct snd_soc_dai_link da8xx_evm_dai = {
|
||||
.name = "TLV320AIC3X",
|
||||
.stream_name = "AIC3X",
|
||||
.cpu_dai = &davinci_mcasp_dai[DAVINCI_MCASP_I2S_DAI],
|
||||
.codec_dai = &aic3x_dai,
|
||||
.init = evm_aic3x_init,
|
||||
.ops = &evm_ops,
|
||||
};
|
||||
|
||||
/* davinci-evm audio machine driver */
|
||||
static struct snd_soc_card snd_soc_card_evm = {
|
||||
.name = "DaVinci EVM",
|
||||
|
@ -152,73 +184,80 @@ static struct snd_soc_card snd_soc_card_evm = {
|
|||
.num_links = 1,
|
||||
};
|
||||
|
||||
/* evm audio private data */
|
||||
static struct aic3x_setup_data evm_aic3x_setup = {
|
||||
.i2c_bus = 1,
|
||||
.i2c_address = 0x1b,
|
||||
/* davinci dm6467 evm audio machine driver */
|
||||
static struct snd_soc_card dm6467_snd_soc_card_evm = {
|
||||
.name = "DaVinci DM6467 EVM",
|
||||
.platform = &davinci_soc_platform,
|
||||
.dai_link = dm6467_evm_dai,
|
||||
.num_links = ARRAY_SIZE(dm6467_evm_dai),
|
||||
};
|
||||
|
||||
static struct snd_soc_card da830_snd_soc_card = {
|
||||
.name = "DA830/OMAP-L137 EVM",
|
||||
.dai_link = &da8xx_evm_dai,
|
||||
.platform = &davinci_soc_platform,
|
||||
.num_links = 1,
|
||||
};
|
||||
|
||||
static struct snd_soc_card da850_snd_soc_card = {
|
||||
.name = "DA850/OMAP-L138 EVM",
|
||||
.dai_link = &da8xx_evm_dai,
|
||||
.platform = &davinci_soc_platform,
|
||||
.num_links = 1,
|
||||
};
|
||||
|
||||
static struct aic3x_setup_data aic3x_setup;
|
||||
|
||||
/* evm audio subsystem */
|
||||
static struct snd_soc_device evm_snd_devdata = {
|
||||
.card = &snd_soc_card_evm,
|
||||
.codec_dev = &soc_codec_dev_aic3x,
|
||||
.codec_data = &evm_aic3x_setup,
|
||||
.codec_data = &aic3x_setup,
|
||||
};
|
||||
|
||||
/* DM6446 EVM uses ASP0; line-out is a pair of RCA jacks */
|
||||
static struct resource evm_snd_resources[] = {
|
||||
{
|
||||
.start = DAVINCI_ASP0_BASE,
|
||||
.end = DAVINCI_ASP0_BASE + SZ_8K - 1,
|
||||
.flags = IORESOURCE_MEM,
|
||||
},
|
||||
/* evm audio subsystem */
|
||||
static struct snd_soc_device dm6467_evm_snd_devdata = {
|
||||
.card = &dm6467_snd_soc_card_evm,
|
||||
.codec_dev = &soc_codec_dev_aic3x,
|
||||
.codec_data = &aic3x_setup,
|
||||
};
|
||||
|
||||
static struct evm_snd_platform_data evm_snd_data = {
|
||||
.tx_dma_ch = DAVINCI_DMA_ASP0_TX,
|
||||
.rx_dma_ch = DAVINCI_DMA_ASP0_RX,
|
||||
/* evm audio subsystem */
|
||||
static struct snd_soc_device da830_evm_snd_devdata = {
|
||||
.card = &da830_snd_soc_card,
|
||||
.codec_dev = &soc_codec_dev_aic3x,
|
||||
.codec_data = &aic3x_setup,
|
||||
};
|
||||
|
||||
/* DM335 EVM uses ASP1; line-out is a stereo mini-jack */
|
||||
static struct resource dm335evm_snd_resources[] = {
|
||||
{
|
||||
.start = DAVINCI_ASP1_BASE,
|
||||
.end = DAVINCI_ASP1_BASE + SZ_8K - 1,
|
||||
.flags = IORESOURCE_MEM,
|
||||
},
|
||||
};
|
||||
|
||||
static struct evm_snd_platform_data dm335evm_snd_data = {
|
||||
.tx_dma_ch = DAVINCI_DMA_ASP1_TX,
|
||||
.rx_dma_ch = DAVINCI_DMA_ASP1_RX,
|
||||
static struct snd_soc_device da850_evm_snd_devdata = {
|
||||
.card = &da850_snd_soc_card,
|
||||
.codec_dev = &soc_codec_dev_aic3x,
|
||||
.codec_data = &aic3x_setup,
|
||||
};
|
||||
|
||||
static struct platform_device *evm_snd_device;
|
||||
|
||||
static int __init evm_init(void)
|
||||
{
|
||||
struct resource *resources;
|
||||
unsigned num_resources;
|
||||
struct evm_snd_platform_data *data;
|
||||
struct snd_soc_device *evm_snd_dev_data;
|
||||
int index;
|
||||
int ret;
|
||||
|
||||
if (machine_is_davinci_evm()) {
|
||||
davinci_cfg_reg(DM644X_MCBSP);
|
||||
|
||||
resources = evm_snd_resources;
|
||||
num_resources = ARRAY_SIZE(evm_snd_resources);
|
||||
data = &evm_snd_data;
|
||||
evm_snd_dev_data = &evm_snd_devdata;
|
||||
index = 0;
|
||||
} else if (machine_is_davinci_dm355_evm()) {
|
||||
/* we don't use ASP1 IRQs, or we'd need to mux them ... */
|
||||
davinci_cfg_reg(DM355_EVT8_ASP1_TX);
|
||||
davinci_cfg_reg(DM355_EVT9_ASP1_RX);
|
||||
|
||||
resources = dm335evm_snd_resources;
|
||||
num_resources = ARRAY_SIZE(dm335evm_snd_resources);
|
||||
data = &dm335evm_snd_data;
|
||||
evm_snd_dev_data = &evm_snd_devdata;
|
||||
index = 1;
|
||||
} else if (machine_is_davinci_dm6467_evm()) {
|
||||
evm_snd_dev_data = &dm6467_evm_snd_devdata;
|
||||
index = 0;
|
||||
} else if (machine_is_davinci_da830_evm()) {
|
||||
evm_snd_dev_data = &da830_evm_snd_devdata;
|
||||
index = 1;
|
||||
} else if (machine_is_davinci_da850_evm()) {
|
||||
evm_snd_dev_data = &da850_evm_snd_devdata;
|
||||
index = 0;
|
||||
} else
|
||||
return -EINVAL;
|
||||
|
||||
|
@ -226,17 +265,8 @@ static int __init evm_init(void)
|
|||
if (!evm_snd_device)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(evm_snd_device, &evm_snd_devdata);
|
||||
evm_snd_devdata.dev = &evm_snd_device->dev;
|
||||
platform_device_add_data(evm_snd_device, data, sizeof(*data));
|
||||
|
||||
ret = platform_device_add_resources(evm_snd_device, resources,
|
||||
num_resources);
|
||||
if (ret) {
|
||||
platform_device_put(evm_snd_device);
|
||||
return ret;
|
||||
}
|
||||
|
||||
platform_set_drvdata(evm_snd_device, evm_snd_dev_data);
|
||||
evm_snd_dev_data->dev = &evm_snd_device->dev;
|
||||
ret = platform_device_add(evm_snd_device);
|
||||
if (ret)
|
||||
platform_device_put(evm_snd_device);
|
||||
|
|
|
@ -22,6 +22,8 @@
|
|||
#include <sound/initval.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
#include <mach/asp.h>
|
||||
|
||||
#include "davinci-pcm.h"
|
||||
|
||||
|
||||
|
@ -63,6 +65,7 @@
|
|||
#define DAVINCI_MCBSP_RCR_RWDLEN1(v) ((v) << 5)
|
||||
#define DAVINCI_MCBSP_RCR_RFRLEN1(v) ((v) << 8)
|
||||
#define DAVINCI_MCBSP_RCR_RDATDLY(v) ((v) << 16)
|
||||
#define DAVINCI_MCBSP_RCR_RFIG (1 << 18)
|
||||
#define DAVINCI_MCBSP_RCR_RWDLEN2(v) ((v) << 21)
|
||||
|
||||
#define DAVINCI_MCBSP_XCR_XWDLEN1(v) ((v) << 5)
|
||||
|
@ -85,14 +88,6 @@
|
|||
#define DAVINCI_MCBSP_PCR_FSRM (1 << 10)
|
||||
#define DAVINCI_MCBSP_PCR_FSXM (1 << 11)
|
||||
|
||||
#define MOD_REG_BIT(val, mask, set) do { \
|
||||
if (set) { \
|
||||
val |= mask; \
|
||||
} else { \
|
||||
val &= ~mask; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
enum {
|
||||
DAVINCI_MCBSP_WORD_8 = 0,
|
||||
DAVINCI_MCBSP_WORD_12,
|
||||
|
@ -112,6 +107,10 @@ static struct davinci_pcm_dma_params davinci_i2s_pcm_in = {
|
|||
|
||||
struct davinci_mcbsp_dev {
|
||||
void __iomem *base;
|
||||
#define MOD_DSP_A 0
|
||||
#define MOD_DSP_B 1
|
||||
int mode;
|
||||
u32 pcr;
|
||||
struct clk *clk;
|
||||
struct davinci_pcm_dma_params *dma_params[2];
|
||||
};
|
||||
|
@ -127,96 +126,100 @@ static inline u32 davinci_mcbsp_read_reg(struct davinci_mcbsp_dev *dev, int reg)
|
|||
return __raw_readl(dev->base + reg);
|
||||
}
|
||||
|
||||
static void davinci_mcbsp_start(struct snd_pcm_substream *substream)
|
||||
static void toggle_clock(struct davinci_mcbsp_dev *dev, int playback)
|
||||
{
|
||||
u32 m = playback ? DAVINCI_MCBSP_PCR_CLKXP : DAVINCI_MCBSP_PCR_CLKRP;
|
||||
/* The clock needs to toggle to complete reset.
|
||||
* So, fake it by toggling the clk polarity.
|
||||
*/
|
||||
davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_PCR_REG, dev->pcr ^ m);
|
||||
davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_PCR_REG, dev->pcr);
|
||||
}
|
||||
|
||||
static void davinci_mcbsp_start(struct davinci_mcbsp_dev *dev,
|
||||
struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct davinci_mcbsp_dev *dev = rtd->dai->cpu_dai->private_data;
|
||||
struct snd_soc_device *socdev = rtd->socdev;
|
||||
struct snd_soc_platform *platform = socdev->card->platform;
|
||||
u32 w;
|
||||
int ret;
|
||||
int playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK);
|
||||
u32 spcr;
|
||||
u32 mask = playback ? DAVINCI_MCBSP_SPCR_XRST : DAVINCI_MCBSP_SPCR_RRST;
|
||||
spcr = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG);
|
||||
if (spcr & mask) {
|
||||
/* start off disabled */
|
||||
davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG,
|
||||
spcr & ~mask);
|
||||
toggle_clock(dev, playback);
|
||||
}
|
||||
if (dev->pcr & (DAVINCI_MCBSP_PCR_FSXM | DAVINCI_MCBSP_PCR_FSRM |
|
||||
DAVINCI_MCBSP_PCR_CLKXM | DAVINCI_MCBSP_PCR_CLKRM)) {
|
||||
/* Start the sample generator */
|
||||
spcr |= DAVINCI_MCBSP_SPCR_GRST;
|
||||
davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr);
|
||||
}
|
||||
|
||||
/* Start the sample generator and enable transmitter/receiver */
|
||||
w = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG);
|
||||
MOD_REG_BIT(w, DAVINCI_MCBSP_SPCR_GRST, 1);
|
||||
davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, w);
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
if (playback) {
|
||||
/* Stop the DMA to avoid data loss */
|
||||
/* while the transmitter is out of reset to handle XSYNCERR */
|
||||
if (platform->pcm_ops->trigger) {
|
||||
ret = platform->pcm_ops->trigger(substream,
|
||||
int ret = platform->pcm_ops->trigger(substream,
|
||||
SNDRV_PCM_TRIGGER_STOP);
|
||||
if (ret < 0)
|
||||
printk(KERN_DEBUG "Playback DMA stop failed\n");
|
||||
}
|
||||
|
||||
/* Enable the transmitter */
|
||||
w = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG);
|
||||
MOD_REG_BIT(w, DAVINCI_MCBSP_SPCR_XRST, 1);
|
||||
davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, w);
|
||||
spcr = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG);
|
||||
spcr |= DAVINCI_MCBSP_SPCR_XRST;
|
||||
davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr);
|
||||
|
||||
/* wait for any unexpected frame sync error to occur */
|
||||
udelay(100);
|
||||
|
||||
/* Disable the transmitter to clear any outstanding XSYNCERR */
|
||||
w = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG);
|
||||
MOD_REG_BIT(w, DAVINCI_MCBSP_SPCR_XRST, 0);
|
||||
davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, w);
|
||||
spcr = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG);
|
||||
spcr &= ~DAVINCI_MCBSP_SPCR_XRST;
|
||||
davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr);
|
||||
toggle_clock(dev, playback);
|
||||
|
||||
/* Restart the DMA */
|
||||
if (platform->pcm_ops->trigger) {
|
||||
ret = platform->pcm_ops->trigger(substream,
|
||||
int ret = platform->pcm_ops->trigger(substream,
|
||||
SNDRV_PCM_TRIGGER_START);
|
||||
if (ret < 0)
|
||||
printk(KERN_DEBUG "Playback DMA start failed\n");
|
||||
}
|
||||
/* Enable the transmitter */
|
||||
w = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG);
|
||||
MOD_REG_BIT(w, DAVINCI_MCBSP_SPCR_XRST, 1);
|
||||
davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, w);
|
||||
|
||||
} else {
|
||||
|
||||
/* Enable the reciever */
|
||||
w = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG);
|
||||
MOD_REG_BIT(w, DAVINCI_MCBSP_SPCR_RRST, 1);
|
||||
davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, w);
|
||||
}
|
||||
|
||||
/* Enable transmitter or receiver */
|
||||
spcr = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG);
|
||||
spcr |= mask;
|
||||
|
||||
/* Start frame sync */
|
||||
w = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG);
|
||||
MOD_REG_BIT(w, DAVINCI_MCBSP_SPCR_FRST, 1);
|
||||
davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, w);
|
||||
if (dev->pcr & (DAVINCI_MCBSP_PCR_FSXM | DAVINCI_MCBSP_PCR_FSRM)) {
|
||||
/* Start frame sync */
|
||||
spcr |= DAVINCI_MCBSP_SPCR_FRST;
|
||||
}
|
||||
davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr);
|
||||
}
|
||||
|
||||
static void davinci_mcbsp_stop(struct snd_pcm_substream *substream)
|
||||
static void davinci_mcbsp_stop(struct davinci_mcbsp_dev *dev, int playback)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct davinci_mcbsp_dev *dev = rtd->dai->cpu_dai->private_data;
|
||||
u32 w;
|
||||
u32 spcr;
|
||||
|
||||
/* Reset transmitter/receiver and sample rate/frame sync generators */
|
||||
w = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG);
|
||||
MOD_REG_BIT(w, DAVINCI_MCBSP_SPCR_GRST |
|
||||
DAVINCI_MCBSP_SPCR_FRST, 0);
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
MOD_REG_BIT(w, DAVINCI_MCBSP_SPCR_XRST, 0);
|
||||
else
|
||||
MOD_REG_BIT(w, DAVINCI_MCBSP_SPCR_RRST, 0);
|
||||
davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, w);
|
||||
spcr = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG);
|
||||
spcr &= ~(DAVINCI_MCBSP_SPCR_GRST | DAVINCI_MCBSP_SPCR_FRST);
|
||||
spcr &= playback ? ~DAVINCI_MCBSP_SPCR_XRST : ~DAVINCI_MCBSP_SPCR_RRST;
|
||||
davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr);
|
||||
toggle_clock(dev, playback);
|
||||
}
|
||||
|
||||
static int davinci_i2s_startup(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
struct snd_soc_dai *cpu_dai)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
|
||||
struct davinci_mcbsp_dev *dev = rtd->dai->cpu_dai->private_data;
|
||||
|
||||
struct davinci_mcbsp_dev *dev = cpu_dai->private_data;
|
||||
cpu_dai->dma_data = dev->dma_params[substream->stream];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -228,12 +231,11 @@ static int davinci_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai,
|
|||
struct davinci_mcbsp_dev *dev = cpu_dai->private_data;
|
||||
unsigned int pcr;
|
||||
unsigned int srgr;
|
||||
unsigned int rcr;
|
||||
unsigned int xcr;
|
||||
srgr = DAVINCI_MCBSP_SRGR_FSGM |
|
||||
DAVINCI_MCBSP_SRGR_FPER(DEFAULT_BITPERSAMPLE * 2 - 1) |
|
||||
DAVINCI_MCBSP_SRGR_FWID(DEFAULT_BITPERSAMPLE - 1);
|
||||
|
||||
/* set master/slave audio interface */
|
||||
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
|
||||
case SND_SOC_DAIFMT_CBS_CFS:
|
||||
/* cpu is master */
|
||||
|
@ -258,11 +260,8 @@ static int davinci_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
rcr = DAVINCI_MCBSP_RCR_RFRLEN1(1);
|
||||
xcr = DAVINCI_MCBSP_XCR_XFIG | DAVINCI_MCBSP_XCR_XFRLEN1(1);
|
||||
/* interface format */
|
||||
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
|
||||
case SND_SOC_DAIFMT_DSP_B:
|
||||
break;
|
||||
case SND_SOC_DAIFMT_I2S:
|
||||
/* Davinci doesn't support TRUE I2S, but some codecs will have
|
||||
* the left and right channels contiguous. This allows
|
||||
|
@ -282,8 +281,10 @@ static int davinci_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai,
|
|||
*/
|
||||
fmt ^= SND_SOC_DAIFMT_NB_IF;
|
||||
case SND_SOC_DAIFMT_DSP_A:
|
||||
rcr |= DAVINCI_MCBSP_RCR_RDATDLY(1);
|
||||
xcr |= DAVINCI_MCBSP_XCR_XDATDLY(1);
|
||||
dev->mode = MOD_DSP_A;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_DSP_B:
|
||||
dev->mode = MOD_DSP_B;
|
||||
break;
|
||||
default:
|
||||
printk(KERN_ERR "%s:bad format\n", __func__);
|
||||
|
@ -343,9 +344,8 @@ static int davinci_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai,
|
|||
return -EINVAL;
|
||||
}
|
||||
davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SRGR_REG, srgr);
|
||||
dev->pcr = pcr;
|
||||
davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_PCR_REG, pcr);
|
||||
davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_RCR_REG, rcr);
|
||||
davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_XCR_REG, xcr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -353,31 +353,40 @@ static int davinci_i2s_hw_params(struct snd_pcm_substream *substream,
|
|||
struct snd_pcm_hw_params *params,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct davinci_pcm_dma_params *dma_params = rtd->dai->cpu_dai->dma_data;
|
||||
struct davinci_mcbsp_dev *dev = rtd->dai->cpu_dai->private_data;
|
||||
struct davinci_pcm_dma_params *dma_params = dai->dma_data;
|
||||
struct davinci_mcbsp_dev *dev = dai->private_data;
|
||||
struct snd_interval *i = NULL;
|
||||
int mcbsp_word_length;
|
||||
u32 w;
|
||||
unsigned int rcr, xcr, srgr;
|
||||
u32 spcr;
|
||||
|
||||
/* general line settings */
|
||||
w = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG);
|
||||
spcr = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG);
|
||||
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
|
||||
w |= DAVINCI_MCBSP_SPCR_RINTM(3) | DAVINCI_MCBSP_SPCR_FREE;
|
||||
davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, w);
|
||||
spcr |= DAVINCI_MCBSP_SPCR_RINTM(3) | DAVINCI_MCBSP_SPCR_FREE;
|
||||
davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr);
|
||||
} else {
|
||||
w |= DAVINCI_MCBSP_SPCR_XINTM(3) | DAVINCI_MCBSP_SPCR_FREE;
|
||||
davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, w);
|
||||
spcr |= DAVINCI_MCBSP_SPCR_XINTM(3) | DAVINCI_MCBSP_SPCR_FREE;
|
||||
davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr);
|
||||
}
|
||||
|
||||
i = hw_param_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS);
|
||||
w = DAVINCI_MCBSP_SRGR_FSGM;
|
||||
MOD_REG_BIT(w, DAVINCI_MCBSP_SRGR_FWID(snd_interval_value(i) - 1), 1);
|
||||
srgr = DAVINCI_MCBSP_SRGR_FSGM;
|
||||
srgr |= DAVINCI_MCBSP_SRGR_FWID(snd_interval_value(i) - 1);
|
||||
|
||||
i = hw_param_interval(params, SNDRV_PCM_HW_PARAM_FRAME_BITS);
|
||||
MOD_REG_BIT(w, DAVINCI_MCBSP_SRGR_FPER(snd_interval_value(i) - 1), 1);
|
||||
davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SRGR_REG, w);
|
||||
srgr |= DAVINCI_MCBSP_SRGR_FPER(snd_interval_value(i) - 1);
|
||||
davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SRGR_REG, srgr);
|
||||
|
||||
rcr = DAVINCI_MCBSP_RCR_RFIG;
|
||||
xcr = DAVINCI_MCBSP_XCR_XFIG;
|
||||
if (dev->mode == MOD_DSP_B) {
|
||||
rcr |= DAVINCI_MCBSP_RCR_RDATDLY(0);
|
||||
xcr |= DAVINCI_MCBSP_XCR_XDATDLY(0);
|
||||
} else {
|
||||
rcr |= DAVINCI_MCBSP_RCR_RDATDLY(1);
|
||||
xcr |= DAVINCI_MCBSP_XCR_XDATDLY(1);
|
||||
}
|
||||
/* Determine xfer data type */
|
||||
switch (params_format(params)) {
|
||||
case SNDRV_PCM_FORMAT_S8:
|
||||
|
@ -397,18 +406,31 @@ static int davinci_i2s_hw_params(struct snd_pcm_substream *substream,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
|
||||
w = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_RCR_REG);
|
||||
MOD_REG_BIT(w, DAVINCI_MCBSP_RCR_RWDLEN1(mcbsp_word_length) |
|
||||
DAVINCI_MCBSP_RCR_RWDLEN2(mcbsp_word_length), 1);
|
||||
davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_RCR_REG, w);
|
||||
dma_params->acnt = dma_params->data_type;
|
||||
rcr |= DAVINCI_MCBSP_RCR_RFRLEN1(1);
|
||||
xcr |= DAVINCI_MCBSP_XCR_XFRLEN1(1);
|
||||
|
||||
} else {
|
||||
w = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_XCR_REG);
|
||||
MOD_REG_BIT(w, DAVINCI_MCBSP_XCR_XWDLEN1(mcbsp_word_length) |
|
||||
DAVINCI_MCBSP_XCR_XWDLEN2(mcbsp_word_length), 1);
|
||||
davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_XCR_REG, w);
|
||||
rcr |= DAVINCI_MCBSP_RCR_RWDLEN1(mcbsp_word_length) |
|
||||
DAVINCI_MCBSP_RCR_RWDLEN2(mcbsp_word_length);
|
||||
xcr |= DAVINCI_MCBSP_XCR_XWDLEN1(mcbsp_word_length) |
|
||||
DAVINCI_MCBSP_XCR_XWDLEN2(mcbsp_word_length);
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_XCR_REG, xcr);
|
||||
else
|
||||
davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_RCR_REG, rcr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int davinci_i2s_prepare(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct davinci_mcbsp_dev *dev = dai->private_data;
|
||||
int playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK);
|
||||
davinci_mcbsp_stop(dev, playback);
|
||||
if ((dev->pcr & DAVINCI_MCBSP_PCR_FSXM) == 0) {
|
||||
/* codec is master */
|
||||
davinci_mcbsp_start(dev, substream);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
@ -416,35 +438,72 @@ static int davinci_i2s_hw_params(struct snd_pcm_substream *substream,
|
|||
static int davinci_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct davinci_mcbsp_dev *dev = dai->private_data;
|
||||
int ret = 0;
|
||||
int playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK);
|
||||
if ((dev->pcr & DAVINCI_MCBSP_PCR_FSXM) == 0)
|
||||
return 0; /* return if codec is master */
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
case SNDRV_PCM_TRIGGER_RESUME:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
||||
davinci_mcbsp_start(substream);
|
||||
davinci_mcbsp_start(dev, substream);
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||
davinci_mcbsp_stop(substream);
|
||||
davinci_mcbsp_stop(dev, playback);
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int davinci_i2s_probe(struct platform_device *pdev,
|
||||
struct snd_soc_dai *dai)
|
||||
static void davinci_i2s_shutdown(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
struct snd_soc_card *card = socdev->card;
|
||||
struct snd_soc_dai *cpu_dai = card->dai_link->cpu_dai;
|
||||
struct davinci_mcbsp_dev *dev = dai->private_data;
|
||||
int playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK);
|
||||
davinci_mcbsp_stop(dev, playback);
|
||||
}
|
||||
|
||||
#define DAVINCI_I2S_RATES SNDRV_PCM_RATE_8000_96000
|
||||
|
||||
static struct snd_soc_dai_ops davinci_i2s_dai_ops = {
|
||||
.startup = davinci_i2s_startup,
|
||||
.shutdown = davinci_i2s_shutdown,
|
||||
.prepare = davinci_i2s_prepare,
|
||||
.trigger = davinci_i2s_trigger,
|
||||
.hw_params = davinci_i2s_hw_params,
|
||||
.set_fmt = davinci_i2s_set_dai_fmt,
|
||||
|
||||
};
|
||||
|
||||
struct snd_soc_dai davinci_i2s_dai = {
|
||||
.name = "davinci-i2s",
|
||||
.id = 0,
|
||||
.playback = {
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.rates = DAVINCI_I2S_RATES,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,},
|
||||
.capture = {
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.rates = DAVINCI_I2S_RATES,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,},
|
||||
.ops = &davinci_i2s_dai_ops,
|
||||
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(davinci_i2s_dai);
|
||||
|
||||
static int davinci_i2s_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_platform_data *pdata = pdev->dev.platform_data;
|
||||
struct davinci_mcbsp_dev *dev;
|
||||
struct resource *mem, *ioarea;
|
||||
struct evm_snd_platform_data *pdata;
|
||||
struct resource *mem, *ioarea, *res;
|
||||
int ret;
|
||||
|
||||
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
|
@ -466,8 +525,6 @@ static int davinci_i2s_probe(struct platform_device *pdev,
|
|||
goto err_release_region;
|
||||
}
|
||||
|
||||
cpu_dai->private_data = dev;
|
||||
|
||||
dev->clk = clk_get(&pdev->dev, NULL);
|
||||
if (IS_ERR(dev->clk)) {
|
||||
ret = -ENODEV;
|
||||
|
@ -476,18 +533,37 @@ static int davinci_i2s_probe(struct platform_device *pdev,
|
|||
clk_enable(dev->clk);
|
||||
|
||||
dev->base = (void __iomem *)IO_ADDRESS(mem->start);
|
||||
pdata = pdev->dev.platform_data;
|
||||
|
||||
dev->dma_params[SNDRV_PCM_STREAM_PLAYBACK] = &davinci_i2s_pcm_out;
|
||||
dev->dma_params[SNDRV_PCM_STREAM_PLAYBACK]->channel = pdata->tx_dma_ch;
|
||||
dev->dma_params[SNDRV_PCM_STREAM_PLAYBACK]->dma_addr =
|
||||
(dma_addr_t)(io_v2p(dev->base) + DAVINCI_MCBSP_DXR_REG);
|
||||
|
||||
dev->dma_params[SNDRV_PCM_STREAM_CAPTURE] = &davinci_i2s_pcm_in;
|
||||
dev->dma_params[SNDRV_PCM_STREAM_CAPTURE]->channel = pdata->rx_dma_ch;
|
||||
dev->dma_params[SNDRV_PCM_STREAM_CAPTURE]->dma_addr =
|
||||
(dma_addr_t)(io_v2p(dev->base) + DAVINCI_MCBSP_DRR_REG);
|
||||
|
||||
/* first TX, then RX */
|
||||
res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
|
||||
if (!res) {
|
||||
dev_err(&pdev->dev, "no DMA resource\n");
|
||||
ret = -ENXIO;
|
||||
goto err_free_mem;
|
||||
}
|
||||
dev->dma_params[SNDRV_PCM_STREAM_PLAYBACK]->channel = res->start;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_DMA, 1);
|
||||
if (!res) {
|
||||
dev_err(&pdev->dev, "no DMA resource\n");
|
||||
ret = -ENXIO;
|
||||
goto err_free_mem;
|
||||
}
|
||||
dev->dma_params[SNDRV_PCM_STREAM_CAPTURE]->channel = res->start;
|
||||
|
||||
davinci_i2s_dai.private_data = dev;
|
||||
ret = snd_soc_register_dai(&davinci_i2s_dai);
|
||||
if (ret != 0)
|
||||
goto err_free_mem;
|
||||
|
||||
return 0;
|
||||
|
||||
err_free_mem:
|
||||
|
@ -498,62 +574,40 @@ err_release_region:
|
|||
return ret;
|
||||
}
|
||||
|
||||
static void davinci_i2s_remove(struct platform_device *pdev,
|
||||
struct snd_soc_dai *dai)
|
||||
static int davinci_i2s_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
||||
struct snd_soc_card *card = socdev->card;
|
||||
struct snd_soc_dai *cpu_dai = card->dai_link->cpu_dai;
|
||||
struct davinci_mcbsp_dev *dev = cpu_dai->private_data;
|
||||
struct davinci_mcbsp_dev *dev = davinci_i2s_dai.private_data;
|
||||
struct resource *mem;
|
||||
|
||||
snd_soc_unregister_dai(&davinci_i2s_dai);
|
||||
clk_disable(dev->clk);
|
||||
clk_put(dev->clk);
|
||||
dev->clk = NULL;
|
||||
|
||||
kfree(dev);
|
||||
|
||||
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
release_mem_region(mem->start, (mem->end - mem->start) + 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define DAVINCI_I2S_RATES SNDRV_PCM_RATE_8000_96000
|
||||
|
||||
static struct snd_soc_dai_ops davinci_i2s_dai_ops = {
|
||||
.startup = davinci_i2s_startup,
|
||||
.trigger = davinci_i2s_trigger,
|
||||
.hw_params = davinci_i2s_hw_params,
|
||||
.set_fmt = davinci_i2s_set_dai_fmt,
|
||||
static struct platform_driver davinci_mcbsp_driver = {
|
||||
.probe = davinci_i2s_probe,
|
||||
.remove = davinci_i2s_remove,
|
||||
.driver = {
|
||||
.name = "davinci-asp",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
struct snd_soc_dai davinci_i2s_dai = {
|
||||
.name = "davinci-i2s",
|
||||
.id = 0,
|
||||
.probe = davinci_i2s_probe,
|
||||
.remove = davinci_i2s_remove,
|
||||
.playback = {
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.rates = DAVINCI_I2S_RATES,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,},
|
||||
.capture = {
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.rates = DAVINCI_I2S_RATES,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,},
|
||||
.ops = &davinci_i2s_dai_ops,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(davinci_i2s_dai);
|
||||
|
||||
static int __init davinci_i2s_init(void)
|
||||
{
|
||||
return snd_soc_register_dai(&davinci_i2s_dai);
|
||||
return platform_driver_register(&davinci_mcbsp_driver);
|
||||
}
|
||||
module_init(davinci_i2s_init);
|
||||
|
||||
static void __exit davinci_i2s_exit(void)
|
||||
{
|
||||
snd_soc_unregister_dai(&davinci_i2s_dai);
|
||||
platform_driver_unregister(&davinci_mcbsp_driver);
|
||||
}
|
||||
module_exit(davinci_i2s_exit);
|
||||
|
||||
|
|
|
@ -0,0 +1,973 @@
|
|||
/*
|
||||
* ALSA SoC McASP Audio Layer for TI DAVINCI processor
|
||||
*
|
||||
* Multi-channel Audio Serial Port Driver
|
||||
*
|
||||
* Author: Nirmal Pandey <n-pandey@ti.com>,
|
||||
* Suresh Rajashekara <suresh.r@ti.com>
|
||||
* Steve Chen <schen@.mvista.com>
|
||||
*
|
||||
* Copyright: (C) 2009 MontaVista Software, Inc., <source@mvista.com>
|
||||
* Copyright: (C) 2009 Texas Instruments, India
|
||||
*
|
||||
* 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/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/clk.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/initval.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
#include "davinci-pcm.h"
|
||||
#include "davinci-mcasp.h"
|
||||
|
||||
/*
|
||||
* McASP register definitions
|
||||
*/
|
||||
#define DAVINCI_MCASP_PID_REG 0x00
|
||||
#define DAVINCI_MCASP_PWREMUMGT_REG 0x04
|
||||
|
||||
#define DAVINCI_MCASP_PFUNC_REG 0x10
|
||||
#define DAVINCI_MCASP_PDIR_REG 0x14
|
||||
#define DAVINCI_MCASP_PDOUT_REG 0x18
|
||||
#define DAVINCI_MCASP_PDSET_REG 0x1c
|
||||
|
||||
#define DAVINCI_MCASP_PDCLR_REG 0x20
|
||||
|
||||
#define DAVINCI_MCASP_TLGC_REG 0x30
|
||||
#define DAVINCI_MCASP_TLMR_REG 0x34
|
||||
|
||||
#define DAVINCI_MCASP_GBLCTL_REG 0x44
|
||||
#define DAVINCI_MCASP_AMUTE_REG 0x48
|
||||
#define DAVINCI_MCASP_LBCTL_REG 0x4c
|
||||
|
||||
#define DAVINCI_MCASP_TXDITCTL_REG 0x50
|
||||
|
||||
#define DAVINCI_MCASP_GBLCTLR_REG 0x60
|
||||
#define DAVINCI_MCASP_RXMASK_REG 0x64
|
||||
#define DAVINCI_MCASP_RXFMT_REG 0x68
|
||||
#define DAVINCI_MCASP_RXFMCTL_REG 0x6c
|
||||
|
||||
#define DAVINCI_MCASP_ACLKRCTL_REG 0x70
|
||||
#define DAVINCI_MCASP_AHCLKRCTL_REG 0x74
|
||||
#define DAVINCI_MCASP_RXTDM_REG 0x78
|
||||
#define DAVINCI_MCASP_EVTCTLR_REG 0x7c
|
||||
|
||||
#define DAVINCI_MCASP_RXSTAT_REG 0x80
|
||||
#define DAVINCI_MCASP_RXTDMSLOT_REG 0x84
|
||||
#define DAVINCI_MCASP_RXCLKCHK_REG 0x88
|
||||
#define DAVINCI_MCASP_REVTCTL_REG 0x8c
|
||||
|
||||
#define DAVINCI_MCASP_GBLCTLX_REG 0xa0
|
||||
#define DAVINCI_MCASP_TXMASK_REG 0xa4
|
||||
#define DAVINCI_MCASP_TXFMT_REG 0xa8
|
||||
#define DAVINCI_MCASP_TXFMCTL_REG 0xac
|
||||
|
||||
#define DAVINCI_MCASP_ACLKXCTL_REG 0xb0
|
||||
#define DAVINCI_MCASP_AHCLKXCTL_REG 0xb4
|
||||
#define DAVINCI_MCASP_TXTDM_REG 0xb8
|
||||
#define DAVINCI_MCASP_EVTCTLX_REG 0xbc
|
||||
|
||||
#define DAVINCI_MCASP_TXSTAT_REG 0xc0
|
||||
#define DAVINCI_MCASP_TXTDMSLOT_REG 0xc4
|
||||
#define DAVINCI_MCASP_TXCLKCHK_REG 0xc8
|
||||
#define DAVINCI_MCASP_XEVTCTL_REG 0xcc
|
||||
|
||||
/* Left(even TDM Slot) Channel Status Register File */
|
||||
#define DAVINCI_MCASP_DITCSRA_REG 0x100
|
||||
/* Right(odd TDM slot) Channel Status Register File */
|
||||
#define DAVINCI_MCASP_DITCSRB_REG 0x118
|
||||
/* Left(even TDM slot) User Data Register File */
|
||||
#define DAVINCI_MCASP_DITUDRA_REG 0x130
|
||||
/* Right(odd TDM Slot) User Data Register File */
|
||||
#define DAVINCI_MCASP_DITUDRB_REG 0x148
|
||||
|
||||
/* Serializer n Control Register */
|
||||
#define DAVINCI_MCASP_XRSRCTL_BASE_REG 0x180
|
||||
#define DAVINCI_MCASP_XRSRCTL_REG(n) (DAVINCI_MCASP_XRSRCTL_BASE_REG + \
|
||||
(n << 2))
|
||||
|
||||
/* Transmit Buffer for Serializer n */
|
||||
#define DAVINCI_MCASP_TXBUF_REG 0x200
|
||||
/* Receive Buffer for Serializer n */
|
||||
#define DAVINCI_MCASP_RXBUF_REG 0x280
|
||||
|
||||
/* McASP FIFO Registers */
|
||||
#define DAVINCI_MCASP_WFIFOCTL (0x1010)
|
||||
#define DAVINCI_MCASP_WFIFOSTS (0x1014)
|
||||
#define DAVINCI_MCASP_RFIFOCTL (0x1018)
|
||||
#define DAVINCI_MCASP_RFIFOSTS (0x101C)
|
||||
|
||||
/*
|
||||
* DAVINCI_MCASP_PWREMUMGT_REG - Power Down and Emulation Management
|
||||
* Register Bits
|
||||
*/
|
||||
#define MCASP_FREE BIT(0)
|
||||
#define MCASP_SOFT BIT(1)
|
||||
|
||||
/*
|
||||
* DAVINCI_MCASP_PFUNC_REG - Pin Function / GPIO Enable Register Bits
|
||||
*/
|
||||
#define AXR(n) (1<<n)
|
||||
#define PFUNC_AMUTE BIT(25)
|
||||
#define ACLKX BIT(26)
|
||||
#define AHCLKX BIT(27)
|
||||
#define AFSX BIT(28)
|
||||
#define ACLKR BIT(29)
|
||||
#define AHCLKR BIT(30)
|
||||
#define AFSR BIT(31)
|
||||
|
||||
/*
|
||||
* DAVINCI_MCASP_PDIR_REG - Pin Direction Register Bits
|
||||
*/
|
||||
#define AXR(n) (1<<n)
|
||||
#define PDIR_AMUTE BIT(25)
|
||||
#define ACLKX BIT(26)
|
||||
#define AHCLKX BIT(27)
|
||||
#define AFSX BIT(28)
|
||||
#define ACLKR BIT(29)
|
||||
#define AHCLKR BIT(30)
|
||||
#define AFSR BIT(31)
|
||||
|
||||
/*
|
||||
* DAVINCI_MCASP_TXDITCTL_REG - Transmit DIT Control Register Bits
|
||||
*/
|
||||
#define DITEN BIT(0) /* Transmit DIT mode enable/disable */
|
||||
#define VA BIT(2)
|
||||
#define VB BIT(3)
|
||||
|
||||
/*
|
||||
* DAVINCI_MCASP_TXFMT_REG - Transmit Bitstream Format Register Bits
|
||||
*/
|
||||
#define TXROT(val) (val)
|
||||
#define TXSEL BIT(3)
|
||||
#define TXSSZ(val) (val<<4)
|
||||
#define TXPBIT(val) (val<<8)
|
||||
#define TXPAD(val) (val<<13)
|
||||
#define TXORD BIT(15)
|
||||
#define FSXDLY(val) (val<<16)
|
||||
|
||||
/*
|
||||
* DAVINCI_MCASP_RXFMT_REG - Receive Bitstream Format Register Bits
|
||||
*/
|
||||
#define RXROT(val) (val)
|
||||
#define RXSEL BIT(3)
|
||||
#define RXSSZ(val) (val<<4)
|
||||
#define RXPBIT(val) (val<<8)
|
||||
#define RXPAD(val) (val<<13)
|
||||
#define RXORD BIT(15)
|
||||
#define FSRDLY(val) (val<<16)
|
||||
|
||||
/*
|
||||
* DAVINCI_MCASP_TXFMCTL_REG - Transmit Frame Control Register Bits
|
||||
*/
|
||||
#define FSXPOL BIT(0)
|
||||
#define AFSXE BIT(1)
|
||||
#define FSXDUR BIT(4)
|
||||
#define FSXMOD(val) (val<<7)
|
||||
|
||||
/*
|
||||
* DAVINCI_MCASP_RXFMCTL_REG - Receive Frame Control Register Bits
|
||||
*/
|
||||
#define FSRPOL BIT(0)
|
||||
#define AFSRE BIT(1)
|
||||
#define FSRDUR BIT(4)
|
||||
#define FSRMOD(val) (val<<7)
|
||||
|
||||
/*
|
||||
* DAVINCI_MCASP_ACLKXCTL_REG - Transmit Clock Control Register Bits
|
||||
*/
|
||||
#define ACLKXDIV(val) (val)
|
||||
#define ACLKXE BIT(5)
|
||||
#define TX_ASYNC BIT(6)
|
||||
#define ACLKXPOL BIT(7)
|
||||
|
||||
/*
|
||||
* DAVINCI_MCASP_ACLKRCTL_REG Receive Clock Control Register Bits
|
||||
*/
|
||||
#define ACLKRDIV(val) (val)
|
||||
#define ACLKRE BIT(5)
|
||||
#define RX_ASYNC BIT(6)
|
||||
#define ACLKRPOL BIT(7)
|
||||
|
||||
/*
|
||||
* DAVINCI_MCASP_AHCLKXCTL_REG - High Frequency Transmit Clock Control
|
||||
* Register Bits
|
||||
*/
|
||||
#define AHCLKXDIV(val) (val)
|
||||
#define AHCLKXPOL BIT(14)
|
||||
#define AHCLKXE BIT(15)
|
||||
|
||||
/*
|
||||
* DAVINCI_MCASP_AHCLKRCTL_REG - High Frequency Receive Clock Control
|
||||
* Register Bits
|
||||
*/
|
||||
#define AHCLKRDIV(val) (val)
|
||||
#define AHCLKRPOL BIT(14)
|
||||
#define AHCLKRE BIT(15)
|
||||
|
||||
/*
|
||||
* DAVINCI_MCASP_XRSRCTL_BASE_REG - Serializer Control Register Bits
|
||||
*/
|
||||
#define MODE(val) (val)
|
||||
#define DISMOD (val)(val<<2)
|
||||
#define TXSTATE BIT(4)
|
||||
#define RXSTATE BIT(5)
|
||||
|
||||
/*
|
||||
* DAVINCI_MCASP_LBCTL_REG - Loop Back Control Register Bits
|
||||
*/
|
||||
#define LBEN BIT(0)
|
||||
#define LBORD BIT(1)
|
||||
#define LBGENMODE(val) (val<<2)
|
||||
|
||||
/*
|
||||
* DAVINCI_MCASP_TXTDMSLOT_REG - Transmit TDM Slot Register configuration
|
||||
*/
|
||||
#define TXTDMS(n) (1<<n)
|
||||
|
||||
/*
|
||||
* DAVINCI_MCASP_RXTDMSLOT_REG - Receive TDM Slot Register configuration
|
||||
*/
|
||||
#define RXTDMS(n) (1<<n)
|
||||
|
||||
/*
|
||||
* DAVINCI_MCASP_GBLCTL_REG - Global Control Register Bits
|
||||
*/
|
||||
#define RXCLKRST BIT(0) /* Receiver Clock Divider Reset */
|
||||
#define RXHCLKRST BIT(1) /* Receiver High Frequency Clock Divider */
|
||||
#define RXSERCLR BIT(2) /* Receiver Serializer Clear */
|
||||
#define RXSMRST BIT(3) /* Receiver State Machine Reset */
|
||||
#define RXFSRST BIT(4) /* Frame Sync Generator Reset */
|
||||
#define TXCLKRST BIT(8) /* Transmitter Clock Divider Reset */
|
||||
#define TXHCLKRST BIT(9) /* Transmitter High Frequency Clock Divider*/
|
||||
#define TXSERCLR BIT(10) /* Transmit Serializer Clear */
|
||||
#define TXSMRST BIT(11) /* Transmitter State Machine Reset */
|
||||
#define TXFSRST BIT(12) /* Frame Sync Generator Reset */
|
||||
|
||||
/*
|
||||
* DAVINCI_MCASP_AMUTE_REG - Mute Control Register Bits
|
||||
*/
|
||||
#define MUTENA(val) (val)
|
||||
#define MUTEINPOL BIT(2)
|
||||
#define MUTEINENA BIT(3)
|
||||
#define MUTEIN BIT(4)
|
||||
#define MUTER BIT(5)
|
||||
#define MUTEX BIT(6)
|
||||
#define MUTEFSR BIT(7)
|
||||
#define MUTEFSX BIT(8)
|
||||
#define MUTEBADCLKR BIT(9)
|
||||
#define MUTEBADCLKX BIT(10)
|
||||
#define MUTERXDMAERR BIT(11)
|
||||
#define MUTETXDMAERR BIT(12)
|
||||
|
||||
/*
|
||||
* DAVINCI_MCASP_REVTCTL_REG - Receiver DMA Event Control Register bits
|
||||
*/
|
||||
#define RXDATADMADIS BIT(0)
|
||||
|
||||
/*
|
||||
* DAVINCI_MCASP_XEVTCTL_REG - Transmitter DMA Event Control Register bits
|
||||
*/
|
||||
#define TXDATADMADIS BIT(0)
|
||||
|
||||
/*
|
||||
* DAVINCI_MCASP_W[R]FIFOCTL - Write/Read FIFO Control Register bits
|
||||
*/
|
||||
#define FIFO_ENABLE BIT(16)
|
||||
#define NUMEVT_MASK (0xFF << 8)
|
||||
#define NUMDMA_MASK (0xFF)
|
||||
|
||||
#define DAVINCI_MCASP_NUM_SERIALIZER 16
|
||||
|
||||
static inline void mcasp_set_bits(void __iomem *reg, u32 val)
|
||||
{
|
||||
__raw_writel(__raw_readl(reg) | val, reg);
|
||||
}
|
||||
|
||||
static inline void mcasp_clr_bits(void __iomem *reg, u32 val)
|
||||
{
|
||||
__raw_writel((__raw_readl(reg) & ~(val)), reg);
|
||||
}
|
||||
|
||||
static inline void mcasp_mod_bits(void __iomem *reg, u32 val, u32 mask)
|
||||
{
|
||||
__raw_writel((__raw_readl(reg) & ~mask) | val, reg);
|
||||
}
|
||||
|
||||
static inline void mcasp_set_reg(void __iomem *reg, u32 val)
|
||||
{
|
||||
__raw_writel(val, reg);
|
||||
}
|
||||
|
||||
static inline u32 mcasp_get_reg(void __iomem *reg)
|
||||
{
|
||||
return (unsigned int)__raw_readl(reg);
|
||||
}
|
||||
|
||||
static inline void mcasp_set_ctl_reg(void __iomem *regs, u32 val)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
mcasp_set_bits(regs, val);
|
||||
|
||||
/* programming GBLCTL needs to read back from GBLCTL and verfiy */
|
||||
/* loop count is to avoid the lock-up */
|
||||
for (i = 0; i < 1000; i++) {
|
||||
if ((mcasp_get_reg(regs) & val) == val)
|
||||
break;
|
||||
}
|
||||
|
||||
if (i == 1000 && ((mcasp_get_reg(regs) & val) != val))
|
||||
printk(KERN_ERR "GBLCTL write error\n");
|
||||
}
|
||||
|
||||
static int davinci_mcasp_startup(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *cpu_dai)
|
||||
{
|
||||
struct davinci_audio_dev *dev = cpu_dai->private_data;
|
||||
cpu_dai->dma_data = dev->dma_params[substream->stream];
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mcasp_start_rx(struct davinci_audio_dev *dev)
|
||||
{
|
||||
mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLR_REG, RXHCLKRST);
|
||||
mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLR_REG, RXCLKRST);
|
||||
mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLR_REG, RXSERCLR);
|
||||
mcasp_set_reg(dev->base + DAVINCI_MCASP_RXBUF_REG, 0);
|
||||
|
||||
mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLR_REG, RXSMRST);
|
||||
mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLR_REG, RXFSRST);
|
||||
mcasp_set_reg(dev->base + DAVINCI_MCASP_RXBUF_REG, 0);
|
||||
|
||||
mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLR_REG, RXSMRST);
|
||||
mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLR_REG, RXFSRST);
|
||||
}
|
||||
|
||||
static void mcasp_start_tx(struct davinci_audio_dev *dev)
|
||||
{
|
||||
u8 offset = 0, i;
|
||||
u32 cnt;
|
||||
|
||||
mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLX_REG, TXHCLKRST);
|
||||
mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLX_REG, TXCLKRST);
|
||||
mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLX_REG, TXSERCLR);
|
||||
mcasp_set_reg(dev->base + DAVINCI_MCASP_TXBUF_REG, 0);
|
||||
|
||||
mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLX_REG, TXSMRST);
|
||||
mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLX_REG, TXFSRST);
|
||||
mcasp_set_reg(dev->base + DAVINCI_MCASP_TXBUF_REG, 0);
|
||||
for (i = 0; i < dev->num_serializer; i++) {
|
||||
if (dev->serial_dir[i] == TX_MODE) {
|
||||
offset = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* wait for TX ready */
|
||||
cnt = 0;
|
||||
while (!(mcasp_get_reg(dev->base + DAVINCI_MCASP_XRSRCTL_REG(offset)) &
|
||||
TXSTATE) && (cnt < 100000))
|
||||
cnt++;
|
||||
|
||||
mcasp_set_reg(dev->base + DAVINCI_MCASP_TXBUF_REG, 0);
|
||||
}
|
||||
|
||||
static void davinci_mcasp_start(struct davinci_audio_dev *dev, int stream)
|
||||
{
|
||||
if (stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
mcasp_start_tx(dev);
|
||||
else
|
||||
mcasp_start_rx(dev);
|
||||
|
||||
/* enable FIFO */
|
||||
if (dev->txnumevt)
|
||||
mcasp_set_bits(dev->base + DAVINCI_MCASP_WFIFOCTL, FIFO_ENABLE);
|
||||
|
||||
if (dev->rxnumevt)
|
||||
mcasp_set_bits(dev->base + DAVINCI_MCASP_RFIFOCTL, FIFO_ENABLE);
|
||||
}
|
||||
|
||||
static void mcasp_stop_rx(struct davinci_audio_dev *dev)
|
||||
{
|
||||
mcasp_set_reg(dev->base + DAVINCI_MCASP_GBLCTLR_REG, 0);
|
||||
mcasp_set_reg(dev->base + DAVINCI_MCASP_RXSTAT_REG, 0xFFFFFFFF);
|
||||
}
|
||||
|
||||
static void mcasp_stop_tx(struct davinci_audio_dev *dev)
|
||||
{
|
||||
mcasp_set_reg(dev->base + DAVINCI_MCASP_GBLCTLX_REG, 0);
|
||||
mcasp_set_reg(dev->base + DAVINCI_MCASP_TXSTAT_REG, 0xFFFFFFFF);
|
||||
}
|
||||
|
||||
static void davinci_mcasp_stop(struct davinci_audio_dev *dev, int stream)
|
||||
{
|
||||
if (stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
mcasp_stop_tx(dev);
|
||||
else
|
||||
mcasp_stop_rx(dev);
|
||||
|
||||
/* disable FIFO */
|
||||
if (dev->txnumevt)
|
||||
mcasp_clr_bits(dev->base + DAVINCI_MCASP_WFIFOCTL, FIFO_ENABLE);
|
||||
|
||||
if (dev->rxnumevt)
|
||||
mcasp_clr_bits(dev->base + DAVINCI_MCASP_RFIFOCTL, FIFO_ENABLE);
|
||||
}
|
||||
|
||||
static int davinci_mcasp_set_dai_fmt(struct snd_soc_dai *cpu_dai,
|
||||
unsigned int fmt)
|
||||
{
|
||||
struct davinci_audio_dev *dev = cpu_dai->private_data;
|
||||
void __iomem *base = dev->base;
|
||||
|
||||
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
|
||||
case SND_SOC_DAIFMT_CBS_CFS:
|
||||
/* codec is clock and frame slave */
|
||||
mcasp_set_bits(base + DAVINCI_MCASP_ACLKXCTL_REG, ACLKXE);
|
||||
mcasp_set_bits(base + DAVINCI_MCASP_TXFMCTL_REG, AFSXE);
|
||||
|
||||
mcasp_set_bits(base + DAVINCI_MCASP_ACLKRCTL_REG, ACLKRE);
|
||||
mcasp_set_bits(base + DAVINCI_MCASP_RXFMCTL_REG, AFSRE);
|
||||
|
||||
mcasp_set_bits(base + DAVINCI_MCASP_PDIR_REG, (0x7 << 26));
|
||||
break;
|
||||
case SND_SOC_DAIFMT_CBM_CFS:
|
||||
/* codec is clock master and frame slave */
|
||||
mcasp_set_bits(base + DAVINCI_MCASP_ACLKXCTL_REG, ACLKXE);
|
||||
mcasp_set_bits(base + DAVINCI_MCASP_TXFMCTL_REG, AFSXE);
|
||||
|
||||
mcasp_set_bits(base + DAVINCI_MCASP_ACLKRCTL_REG, ACLKRE);
|
||||
mcasp_set_bits(base + DAVINCI_MCASP_RXFMCTL_REG, AFSRE);
|
||||
|
||||
mcasp_set_bits(base + DAVINCI_MCASP_PDIR_REG, (0x2d << 26));
|
||||
break;
|
||||
case SND_SOC_DAIFMT_CBM_CFM:
|
||||
/* codec is clock and frame master */
|
||||
mcasp_clr_bits(base + DAVINCI_MCASP_ACLKXCTL_REG, ACLKXE);
|
||||
mcasp_clr_bits(base + DAVINCI_MCASP_TXFMCTL_REG, AFSXE);
|
||||
|
||||
mcasp_clr_bits(base + DAVINCI_MCASP_ACLKRCTL_REG, ACLKRE);
|
||||
mcasp_clr_bits(base + DAVINCI_MCASP_RXFMCTL_REG, AFSRE);
|
||||
|
||||
mcasp_clr_bits(base + DAVINCI_MCASP_PDIR_REG, (0x3f << 26));
|
||||
break;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
|
||||
case SND_SOC_DAIFMT_IB_NF:
|
||||
mcasp_clr_bits(base + DAVINCI_MCASP_ACLKXCTL_REG, ACLKXPOL);
|
||||
mcasp_clr_bits(base + DAVINCI_MCASP_TXFMCTL_REG, FSXPOL);
|
||||
|
||||
mcasp_set_bits(base + DAVINCI_MCASP_ACLKRCTL_REG, ACLKRPOL);
|
||||
mcasp_clr_bits(base + DAVINCI_MCASP_RXFMCTL_REG, FSRPOL);
|
||||
break;
|
||||
|
||||
case SND_SOC_DAIFMT_NB_IF:
|
||||
mcasp_set_bits(base + DAVINCI_MCASP_ACLKXCTL_REG, ACLKXPOL);
|
||||
mcasp_set_bits(base + DAVINCI_MCASP_TXFMCTL_REG, FSXPOL);
|
||||
|
||||
mcasp_clr_bits(base + DAVINCI_MCASP_ACLKRCTL_REG, ACLKRPOL);
|
||||
mcasp_set_bits(base + DAVINCI_MCASP_RXFMCTL_REG, FSRPOL);
|
||||
break;
|
||||
|
||||
case SND_SOC_DAIFMT_IB_IF:
|
||||
mcasp_clr_bits(base + DAVINCI_MCASP_ACLKXCTL_REG, ACLKXPOL);
|
||||
mcasp_set_bits(base + DAVINCI_MCASP_TXFMCTL_REG, FSXPOL);
|
||||
|
||||
mcasp_set_bits(base + DAVINCI_MCASP_ACLKRCTL_REG, ACLKRPOL);
|
||||
mcasp_set_bits(base + DAVINCI_MCASP_RXFMCTL_REG, FSRPOL);
|
||||
break;
|
||||
|
||||
case SND_SOC_DAIFMT_NB_NF:
|
||||
mcasp_set_bits(base + DAVINCI_MCASP_ACLKXCTL_REG, ACLKXPOL);
|
||||
mcasp_clr_bits(base + DAVINCI_MCASP_TXFMCTL_REG, FSXPOL);
|
||||
|
||||
mcasp_clr_bits(base + DAVINCI_MCASP_ACLKRCTL_REG, ACLKRPOL);
|
||||
mcasp_clr_bits(base + DAVINCI_MCASP_RXFMCTL_REG, FSRPOL);
|
||||
break;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int davinci_config_channel_size(struct davinci_audio_dev *dev,
|
||||
int channel_size)
|
||||
{
|
||||
u32 fmt = 0;
|
||||
|
||||
switch (channel_size) {
|
||||
case DAVINCI_AUDIO_WORD_8:
|
||||
fmt = 0x03;
|
||||
break;
|
||||
|
||||
case DAVINCI_AUDIO_WORD_12:
|
||||
fmt = 0x05;
|
||||
break;
|
||||
|
||||
case DAVINCI_AUDIO_WORD_16:
|
||||
fmt = 0x07;
|
||||
break;
|
||||
|
||||
case DAVINCI_AUDIO_WORD_20:
|
||||
fmt = 0x09;
|
||||
break;
|
||||
|
||||
case DAVINCI_AUDIO_WORD_24:
|
||||
fmt = 0x0B;
|
||||
break;
|
||||
|
||||
case DAVINCI_AUDIO_WORD_28:
|
||||
fmt = 0x0D;
|
||||
break;
|
||||
|
||||
case DAVINCI_AUDIO_WORD_32:
|
||||
fmt = 0x0F;
|
||||
break;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mcasp_mod_bits(dev->base + DAVINCI_MCASP_RXFMT_REG,
|
||||
RXSSZ(fmt), RXSSZ(0x0F));
|
||||
mcasp_mod_bits(dev->base + DAVINCI_MCASP_TXFMT_REG,
|
||||
TXSSZ(fmt), TXSSZ(0x0F));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void davinci_hw_common_param(struct davinci_audio_dev *dev, int stream)
|
||||
{
|
||||
int i;
|
||||
u8 tx_ser = 0;
|
||||
u8 rx_ser = 0;
|
||||
|
||||
/* Default configuration */
|
||||
mcasp_set_bits(dev->base + DAVINCI_MCASP_PWREMUMGT_REG, MCASP_SOFT);
|
||||
|
||||
/* All PINS as McASP */
|
||||
mcasp_set_reg(dev->base + DAVINCI_MCASP_PFUNC_REG, 0x00000000);
|
||||
|
||||
if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
mcasp_set_reg(dev->base + DAVINCI_MCASP_TXSTAT_REG, 0xFFFFFFFF);
|
||||
mcasp_clr_bits(dev->base + DAVINCI_MCASP_XEVTCTL_REG,
|
||||
TXDATADMADIS);
|
||||
} else {
|
||||
mcasp_set_reg(dev->base + DAVINCI_MCASP_RXSTAT_REG, 0xFFFFFFFF);
|
||||
mcasp_clr_bits(dev->base + DAVINCI_MCASP_REVTCTL_REG,
|
||||
RXDATADMADIS);
|
||||
}
|
||||
|
||||
for (i = 0; i < dev->num_serializer; i++) {
|
||||
mcasp_set_bits(dev->base + DAVINCI_MCASP_XRSRCTL_REG(i),
|
||||
dev->serial_dir[i]);
|
||||
if (dev->serial_dir[i] == TX_MODE) {
|
||||
mcasp_set_bits(dev->base + DAVINCI_MCASP_PDIR_REG,
|
||||
AXR(i));
|
||||
tx_ser++;
|
||||
} else if (dev->serial_dir[i] == RX_MODE) {
|
||||
mcasp_clr_bits(dev->base + DAVINCI_MCASP_PDIR_REG,
|
||||
AXR(i));
|
||||
rx_ser++;
|
||||
}
|
||||
}
|
||||
|
||||
if (dev->txnumevt && stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
if (dev->txnumevt * tx_ser > 64)
|
||||
dev->txnumevt = 1;
|
||||
|
||||
mcasp_mod_bits(dev->base + DAVINCI_MCASP_WFIFOCTL, tx_ser,
|
||||
NUMDMA_MASK);
|
||||
mcasp_mod_bits(dev->base + DAVINCI_MCASP_WFIFOCTL,
|
||||
((dev->txnumevt * tx_ser) << 8), NUMEVT_MASK);
|
||||
mcasp_set_bits(dev->base + DAVINCI_MCASP_WFIFOCTL, FIFO_ENABLE);
|
||||
}
|
||||
|
||||
if (dev->rxnumevt && stream == SNDRV_PCM_STREAM_CAPTURE) {
|
||||
if (dev->rxnumevt * rx_ser > 64)
|
||||
dev->rxnumevt = 1;
|
||||
|
||||
mcasp_mod_bits(dev->base + DAVINCI_MCASP_RFIFOCTL, rx_ser,
|
||||
NUMDMA_MASK);
|
||||
mcasp_mod_bits(dev->base + DAVINCI_MCASP_RFIFOCTL,
|
||||
((dev->rxnumevt * rx_ser) << 8), NUMEVT_MASK);
|
||||
mcasp_set_bits(dev->base + DAVINCI_MCASP_RFIFOCTL, FIFO_ENABLE);
|
||||
}
|
||||
}
|
||||
|
||||
static void davinci_hw_param(struct davinci_audio_dev *dev, int stream)
|
||||
{
|
||||
int i, active_slots;
|
||||
u32 mask = 0;
|
||||
|
||||
active_slots = (dev->tdm_slots > 31) ? 32 : dev->tdm_slots;
|
||||
for (i = 0; i < active_slots; i++)
|
||||
mask |= (1 << i);
|
||||
|
||||
mcasp_clr_bits(dev->base + DAVINCI_MCASP_ACLKXCTL_REG, TX_ASYNC);
|
||||
|
||||
if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
/* bit stream is MSB first with no delay */
|
||||
/* DSP_B mode */
|
||||
mcasp_set_bits(dev->base + DAVINCI_MCASP_AHCLKXCTL_REG,
|
||||
AHCLKXE);
|
||||
mcasp_set_reg(dev->base + DAVINCI_MCASP_TXTDM_REG, mask);
|
||||
mcasp_set_bits(dev->base + DAVINCI_MCASP_TXFMT_REG, TXORD);
|
||||
|
||||
if ((dev->tdm_slots >= 2) || (dev->tdm_slots <= 32))
|
||||
mcasp_mod_bits(dev->base + DAVINCI_MCASP_TXFMCTL_REG,
|
||||
FSXMOD(dev->tdm_slots), FSXMOD(0x1FF));
|
||||
else
|
||||
printk(KERN_ERR "playback tdm slot %d not supported\n",
|
||||
dev->tdm_slots);
|
||||
|
||||
mcasp_set_reg(dev->base + DAVINCI_MCASP_TXMASK_REG, 0xFFFFFFFF);
|
||||
mcasp_clr_bits(dev->base + DAVINCI_MCASP_TXFMCTL_REG, FSXDUR);
|
||||
} else {
|
||||
/* bit stream is MSB first with no delay */
|
||||
/* DSP_B mode */
|
||||
mcasp_set_bits(dev->base + DAVINCI_MCASP_RXFMT_REG, RXORD);
|
||||
mcasp_set_bits(dev->base + DAVINCI_MCASP_AHCLKRCTL_REG,
|
||||
AHCLKRE);
|
||||
mcasp_set_reg(dev->base + DAVINCI_MCASP_RXTDM_REG, mask);
|
||||
|
||||
if ((dev->tdm_slots >= 2) || (dev->tdm_slots <= 32))
|
||||
mcasp_mod_bits(dev->base + DAVINCI_MCASP_RXFMCTL_REG,
|
||||
FSRMOD(dev->tdm_slots), FSRMOD(0x1FF));
|
||||
else
|
||||
printk(KERN_ERR "capture tdm slot %d not supported\n",
|
||||
dev->tdm_slots);
|
||||
|
||||
mcasp_set_reg(dev->base + DAVINCI_MCASP_RXMASK_REG, 0xFFFFFFFF);
|
||||
mcasp_clr_bits(dev->base + DAVINCI_MCASP_RXFMCTL_REG, FSRDUR);
|
||||
}
|
||||
}
|
||||
|
||||
/* S/PDIF */
|
||||
static void davinci_hw_dit_param(struct davinci_audio_dev *dev)
|
||||
{
|
||||
/* Set the PDIR for Serialiser as output */
|
||||
mcasp_set_bits(dev->base + DAVINCI_MCASP_PDIR_REG, AFSX);
|
||||
|
||||
/* TXMASK for 24 bits */
|
||||
mcasp_set_reg(dev->base + DAVINCI_MCASP_TXMASK_REG, 0x00FFFFFF);
|
||||
|
||||
/* Set the TX format : 24 bit right rotation, 32 bit slot, Pad 0
|
||||
and LSB first */
|
||||
mcasp_set_bits(dev->base + DAVINCI_MCASP_TXFMT_REG,
|
||||
TXROT(6) | TXSSZ(15));
|
||||
|
||||
/* Set TX frame synch : DIT Mode, 1 bit width, internal, rising edge */
|
||||
mcasp_set_reg(dev->base + DAVINCI_MCASP_TXFMCTL_REG,
|
||||
AFSXE | FSXMOD(0x180));
|
||||
|
||||
/* Set the TX tdm : for all the slots */
|
||||
mcasp_set_reg(dev->base + DAVINCI_MCASP_TXTDM_REG, 0xFFFFFFFF);
|
||||
|
||||
/* Set the TX clock controls : div = 1 and internal */
|
||||
mcasp_set_bits(dev->base + DAVINCI_MCASP_ACLKXCTL_REG,
|
||||
ACLKXE | TX_ASYNC);
|
||||
|
||||
mcasp_clr_bits(dev->base + DAVINCI_MCASP_XEVTCTL_REG, TXDATADMADIS);
|
||||
|
||||
/* Only 44100 and 48000 are valid, both have the same setting */
|
||||
mcasp_set_bits(dev->base + DAVINCI_MCASP_AHCLKXCTL_REG, AHCLKXDIV(3));
|
||||
|
||||
/* Enable the DIT */
|
||||
mcasp_set_bits(dev->base + DAVINCI_MCASP_TXDITCTL_REG, DITEN);
|
||||
}
|
||||
|
||||
static int davinci_mcasp_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params,
|
||||
struct snd_soc_dai *cpu_dai)
|
||||
{
|
||||
struct davinci_audio_dev *dev = cpu_dai->private_data;
|
||||
struct davinci_pcm_dma_params *dma_params =
|
||||
dev->dma_params[substream->stream];
|
||||
int word_length;
|
||||
u8 numevt;
|
||||
|
||||
davinci_hw_common_param(dev, substream->stream);
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
numevt = dev->txnumevt;
|
||||
else
|
||||
numevt = dev->rxnumevt;
|
||||
|
||||
if (!numevt)
|
||||
numevt = 1;
|
||||
|
||||
if (dev->op_mode == DAVINCI_MCASP_DIT_MODE)
|
||||
davinci_hw_dit_param(dev);
|
||||
else
|
||||
davinci_hw_param(dev, substream->stream);
|
||||
|
||||
switch (params_format(params)) {
|
||||
case SNDRV_PCM_FORMAT_S8:
|
||||
dma_params->data_type = 1;
|
||||
word_length = DAVINCI_AUDIO_WORD_8;
|
||||
break;
|
||||
|
||||
case SNDRV_PCM_FORMAT_S16_LE:
|
||||
dma_params->data_type = 2;
|
||||
word_length = DAVINCI_AUDIO_WORD_16;
|
||||
break;
|
||||
|
||||
case SNDRV_PCM_FORMAT_S32_LE:
|
||||
dma_params->data_type = 4;
|
||||
word_length = DAVINCI_AUDIO_WORD_32;
|
||||
break;
|
||||
|
||||
default:
|
||||
printk(KERN_WARNING "davinci-mcasp: unsupported PCM format");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (dev->version == MCASP_VERSION_2) {
|
||||
dma_params->data_type *= numevt;
|
||||
dma_params->acnt = 4 * numevt;
|
||||
} else
|
||||
dma_params->acnt = dma_params->data_type;
|
||||
|
||||
davinci_config_channel_size(dev, word_length);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int davinci_mcasp_trigger(struct snd_pcm_substream *substream,
|
||||
int cmd, struct snd_soc_dai *cpu_dai)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct davinci_audio_dev *dev = rtd->dai->cpu_dai->private_data;
|
||||
int ret = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
case SNDRV_PCM_TRIGGER_RESUME:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
||||
davinci_mcasp_start(dev, substream->stream);
|
||||
break;
|
||||
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||
davinci_mcasp_stop(dev, substream->stream);
|
||||
break;
|
||||
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct snd_soc_dai_ops davinci_mcasp_dai_ops = {
|
||||
.startup = davinci_mcasp_startup,
|
||||
.trigger = davinci_mcasp_trigger,
|
||||
.hw_params = davinci_mcasp_hw_params,
|
||||
.set_fmt = davinci_mcasp_set_dai_fmt,
|
||||
|
||||
};
|
||||
|
||||
struct snd_soc_dai davinci_mcasp_dai[] = {
|
||||
{
|
||||
.name = "davinci-i2s",
|
||||
.id = 0,
|
||||
.playback = {
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.rates = DAVINCI_MCASP_RATES,
|
||||
.formats = SNDRV_PCM_FMTBIT_S8 |
|
||||
SNDRV_PCM_FMTBIT_S16_LE |
|
||||
SNDRV_PCM_FMTBIT_S32_LE,
|
||||
},
|
||||
.capture = {
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.rates = DAVINCI_MCASP_RATES,
|
||||
.formats = SNDRV_PCM_FMTBIT_S8 |
|
||||
SNDRV_PCM_FMTBIT_S16_LE |
|
||||
SNDRV_PCM_FMTBIT_S32_LE,
|
||||
},
|
||||
.ops = &davinci_mcasp_dai_ops,
|
||||
|
||||
},
|
||||
{
|
||||
.name = "davinci-dit",
|
||||
.id = 1,
|
||||
.playback = {
|
||||
.channels_min = 1,
|
||||
.channels_max = 384,
|
||||
.rates = DAVINCI_MCASP_RATES,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,
|
||||
},
|
||||
.ops = &davinci_mcasp_dai_ops,
|
||||
},
|
||||
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(davinci_mcasp_dai);
|
||||
|
||||
static int davinci_mcasp_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct davinci_pcm_dma_params *dma_data;
|
||||
struct resource *mem, *ioarea, *res;
|
||||
struct snd_platform_data *pdata;
|
||||
struct davinci_audio_dev *dev;
|
||||
int count = 0;
|
||||
int ret = 0;
|
||||
|
||||
dev = kzalloc(sizeof(struct davinci_audio_dev), GFP_KERNEL);
|
||||
if (!dev)
|
||||
return -ENOMEM;
|
||||
|
||||
dma_data = kzalloc(sizeof(struct davinci_pcm_dma_params) * 2,
|
||||
GFP_KERNEL);
|
||||
if (!dma_data) {
|
||||
ret = -ENOMEM;
|
||||
goto err_release_dev;
|
||||
}
|
||||
|
||||
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!mem) {
|
||||
dev_err(&pdev->dev, "no mem resource?\n");
|
||||
ret = -ENODEV;
|
||||
goto err_release_data;
|
||||
}
|
||||
|
||||
ioarea = request_mem_region(mem->start,
|
||||
(mem->end - mem->start) + 1, pdev->name);
|
||||
if (!ioarea) {
|
||||
dev_err(&pdev->dev, "Audio region already claimed\n");
|
||||
ret = -EBUSY;
|
||||
goto err_release_data;
|
||||
}
|
||||
|
||||
pdata = pdev->dev.platform_data;
|
||||
dev->clk = clk_get(&pdev->dev, NULL);
|
||||
if (IS_ERR(dev->clk)) {
|
||||
ret = -ENODEV;
|
||||
goto err_release_region;
|
||||
}
|
||||
|
||||
clk_enable(dev->clk);
|
||||
|
||||
dev->base = (void __iomem *)IO_ADDRESS(mem->start);
|
||||
dev->op_mode = pdata->op_mode;
|
||||
dev->tdm_slots = pdata->tdm_slots;
|
||||
dev->num_serializer = pdata->num_serializer;
|
||||
dev->serial_dir = pdata->serial_dir;
|
||||
dev->codec_fmt = pdata->codec_fmt;
|
||||
dev->version = pdata->version;
|
||||
dev->txnumevt = pdata->txnumevt;
|
||||
dev->rxnumevt = pdata->rxnumevt;
|
||||
|
||||
dma_data[count].name = "I2S PCM Stereo out";
|
||||
dma_data[count].eventq_no = pdata->eventq_no;
|
||||
dma_data[count].dma_addr = (dma_addr_t) (pdata->tx_dma_offset +
|
||||
io_v2p(dev->base));
|
||||
dev->dma_params[SNDRV_PCM_STREAM_PLAYBACK] = &dma_data[count];
|
||||
|
||||
/* first TX, then RX */
|
||||
res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
|
||||
if (!res) {
|
||||
dev_err(&pdev->dev, "no DMA resource\n");
|
||||
goto err_release_region;
|
||||
}
|
||||
|
||||
dma_data[count].channel = res->start;
|
||||
count++;
|
||||
dma_data[count].name = "I2S PCM Stereo in";
|
||||
dma_data[count].eventq_no = pdata->eventq_no;
|
||||
dma_data[count].dma_addr = (dma_addr_t)(pdata->rx_dma_offset +
|
||||
io_v2p(dev->base));
|
||||
dev->dma_params[SNDRV_PCM_STREAM_CAPTURE] = &dma_data[count];
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_DMA, 1);
|
||||
if (!res) {
|
||||
dev_err(&pdev->dev, "no DMA resource\n");
|
||||
goto err_release_region;
|
||||
}
|
||||
|
||||
dma_data[count].channel = res->start;
|
||||
davinci_mcasp_dai[pdata->op_mode].private_data = dev;
|
||||
davinci_mcasp_dai[pdata->op_mode].dev = &pdev->dev;
|
||||
ret = snd_soc_register_dai(&davinci_mcasp_dai[pdata->op_mode]);
|
||||
|
||||
if (ret != 0)
|
||||
goto err_release_region;
|
||||
return 0;
|
||||
|
||||
err_release_region:
|
||||
release_mem_region(mem->start, (mem->end - mem->start) + 1);
|
||||
err_release_data:
|
||||
kfree(dma_data);
|
||||
err_release_dev:
|
||||
kfree(dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int davinci_mcasp_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_platform_data *pdata = pdev->dev.platform_data;
|
||||
struct davinci_pcm_dma_params *dma_data;
|
||||
struct davinci_audio_dev *dev;
|
||||
struct resource *mem;
|
||||
|
||||
snd_soc_unregister_dai(&davinci_mcasp_dai[pdata->op_mode]);
|
||||
dev = davinci_mcasp_dai[pdata->op_mode].private_data;
|
||||
clk_disable(dev->clk);
|
||||
clk_put(dev->clk);
|
||||
dev->clk = NULL;
|
||||
|
||||
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
release_mem_region(mem->start, (mem->end - mem->start) + 1);
|
||||
|
||||
dma_data = dev->dma_params[SNDRV_PCM_STREAM_PLAYBACK];
|
||||
kfree(dma_data);
|
||||
kfree(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver davinci_mcasp_driver = {
|
||||
.probe = davinci_mcasp_probe,
|
||||
.remove = davinci_mcasp_remove,
|
||||
.driver = {
|
||||
.name = "davinci-mcasp",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init davinci_mcasp_init(void)
|
||||
{
|
||||
return platform_driver_register(&davinci_mcasp_driver);
|
||||
}
|
||||
module_init(davinci_mcasp_init);
|
||||
|
||||
static void __exit davinci_mcasp_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&davinci_mcasp_driver);
|
||||
}
|
||||
module_exit(davinci_mcasp_exit);
|
||||
|
||||
MODULE_AUTHOR("Steve Chen");
|
||||
MODULE_DESCRIPTION("TI DAVINCI McASP SoC Interface");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* ALSA SoC McASP Audio Layer for TI DAVINCI processor
|
||||
*
|
||||
* MCASP related definitions
|
||||
*
|
||||
* Author: Nirmal Pandey <n-pandey@ti.com>,
|
||||
* Suresh Rajashekara <suresh.r@ti.com>
|
||||
* Steve Chen <schen@.mvista.com>
|
||||
*
|
||||
* Copyright: (C) 2009 MontaVista Software, Inc., <source@mvista.com>
|
||||
* Copyright: (C) 2009 Texas Instruments, India
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef DAVINCI_MCASP_H
|
||||
#define DAVINCI_MCASP_H
|
||||
|
||||
#include <linux/io.h>
|
||||
#include <mach/asp.h>
|
||||
#include "davinci-pcm.h"
|
||||
|
||||
extern struct snd_soc_dai davinci_mcasp_dai[];
|
||||
|
||||
#define DAVINCI_MCASP_RATES SNDRV_PCM_RATE_8000_96000
|
||||
#define DAVINCI_MCASP_I2S_DAI 0
|
||||
#define DAVINCI_MCASP_DIT_DAI 1
|
||||
|
||||
enum {
|
||||
DAVINCI_AUDIO_WORD_8 = 0,
|
||||
DAVINCI_AUDIO_WORD_12,
|
||||
DAVINCI_AUDIO_WORD_16,
|
||||
DAVINCI_AUDIO_WORD_20,
|
||||
DAVINCI_AUDIO_WORD_24,
|
||||
DAVINCI_AUDIO_WORD_32,
|
||||
DAVINCI_AUDIO_WORD_28, /* This is only valid for McASP */
|
||||
};
|
||||
|
||||
struct davinci_audio_dev {
|
||||
void __iomem *base;
|
||||
int sample_rate;
|
||||
struct clk *clk;
|
||||
struct davinci_pcm_dma_params *dma_params[2];
|
||||
unsigned int codec_fmt;
|
||||
|
||||
/* McASP specific data */
|
||||
int tdm_slots;
|
||||
u8 op_mode;
|
||||
u8 num_serializer;
|
||||
u8 *serial_dir;
|
||||
u8 version;
|
||||
|
||||
/* McASP FIFO related */
|
||||
u8 txnumevt;
|
||||
u8 rxnumevt;
|
||||
};
|
||||
|
||||
#endif /* DAVINCI_MCASP_H */
|
|
@ -67,6 +67,7 @@ static void davinci_pcm_enqueue_dma(struct snd_pcm_substream *substream)
|
|||
dma_addr_t src, dst;
|
||||
unsigned short src_bidx, dst_bidx;
|
||||
unsigned int data_type;
|
||||
unsigned short acnt;
|
||||
unsigned int count;
|
||||
|
||||
period_size = snd_pcm_lib_period_bytes(substream);
|
||||
|
@ -91,11 +92,12 @@ static void davinci_pcm_enqueue_dma(struct snd_pcm_substream *substream)
|
|||
dst_bidx = data_type;
|
||||
}
|
||||
|
||||
acnt = prtd->params->acnt;
|
||||
edma_set_src(lch, src, INCR, W8BIT);
|
||||
edma_set_dest(lch, dst, INCR, W8BIT);
|
||||
edma_set_src_index(lch, src_bidx, 0);
|
||||
edma_set_dest_index(lch, dst_bidx, 0);
|
||||
edma_set_transfer_params(lch, data_type, count, 1, 0, ASYNC);
|
||||
edma_set_transfer_params(lch, acnt, count, 1, 0, ASYNC);
|
||||
|
||||
prtd->period++;
|
||||
if (unlikely(prtd->period >= runtime->periods))
|
||||
|
@ -206,6 +208,7 @@ static int davinci_pcm_prepare(struct snd_pcm_substream *substream)
|
|||
/* Copy self-linked parameter RAM entry into master channel */
|
||||
edma_read_slot(prtd->slave_lch, &temp);
|
||||
edma_write_slot(prtd->master_lch, &temp);
|
||||
davinci_pcm_enqueue_dma(substream);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -243,6 +246,11 @@ static int davinci_pcm_open(struct snd_pcm_substream *substream)
|
|||
int ret = 0;
|
||||
|
||||
snd_soc_set_runtime_hwparams(substream, &davinci_pcm_hardware);
|
||||
/* ensure that buffer size is a multiple of period size */
|
||||
ret = snd_pcm_hw_constraint_integer(runtime,
|
||||
SNDRV_PCM_HW_PARAM_PERIODS);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
prtd = kzalloc(sizeof(struct davinci_runtime_data), GFP_KERNEL);
|
||||
if (prtd == NULL)
|
||||
|
|
|
@ -12,17 +12,20 @@
|
|||
#ifndef _DAVINCI_PCM_H
|
||||
#define _DAVINCI_PCM_H
|
||||
|
||||
#include <mach/edma.h>
|
||||
#include <mach/asp.h>
|
||||
|
||||
|
||||
struct davinci_pcm_dma_params {
|
||||
char *name; /* stream identifier */
|
||||
int channel; /* sync dma channel ID */
|
||||
dma_addr_t dma_addr; /* device physical address for DMA */
|
||||
unsigned int data_type; /* xfer data type */
|
||||
char *name; /* stream identifier */
|
||||
int channel; /* sync dma channel ID */
|
||||
unsigned short acnt;
|
||||
dma_addr_t dma_addr; /* device physical address for DMA */
|
||||
enum dma_event_q eventq_no; /* event queue number */
|
||||
unsigned char data_type; /* xfer data type */
|
||||
unsigned char convert_mono_stereo;
|
||||
};
|
||||
|
||||
struct evm_snd_platform_data {
|
||||
int tx_dma_ch;
|
||||
int rx_dma_ch;
|
||||
};
|
||||
|
||||
extern struct snd_soc_platform davinci_soc_platform;
|
||||
|
||||
|
|
|
@ -69,6 +69,23 @@ static void psc_dma_bcom_enqueue_next_buffer(struct psc_dma_stream *s)
|
|||
|
||||
static void psc_dma_bcom_enqueue_tx(struct psc_dma_stream *s)
|
||||
{
|
||||
if (s->appl_ptr > s->runtime->control->appl_ptr) {
|
||||
/*
|
||||
* In this case s->runtime->control->appl_ptr has wrapped around.
|
||||
* Play the data to the end of the boundary, then wrap our own
|
||||
* appl_ptr back around.
|
||||
*/
|
||||
while (s->appl_ptr < s->runtime->boundary) {
|
||||
if (bcom_queue_full(s->bcom_task))
|
||||
return;
|
||||
|
||||
s->appl_ptr += s->period_size;
|
||||
|
||||
psc_dma_bcom_enqueue_next_buffer(s);
|
||||
}
|
||||
s->appl_ptr -= s->runtime->boundary;
|
||||
}
|
||||
|
||||
while (s->appl_ptr < s->runtime->control->appl_ptr) {
|
||||
|
||||
if (bcom_queue_full(s->bcom_task))
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include <linux/module.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/delay.h>
|
||||
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
|
@ -112,7 +113,7 @@ static void psc_ac97_cold_reset(struct snd_ac97 *ac97)
|
|||
out_8(®s->op1, MPC52xx_PSC_OP_RES);
|
||||
udelay(10);
|
||||
out_8(®s->op0, MPC52xx_PSC_OP_RES);
|
||||
udelay(50);
|
||||
msleep(1);
|
||||
psc_ac97_warm_reset(ac97);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
config SND_MX1_MX2_SOC
|
||||
tristate "SoC Audio for Freecale i.MX1x i.MX2x CPUs"
|
||||
depends on ARCH_MX2 || ARCH_MX1
|
||||
select SND_PCM
|
||||
help
|
||||
Say Y or M if you want to add support for codecs attached to
|
||||
the MX1 or MX2 SSI interface.
|
||||
|
||||
config SND_MXC_SOC_SSI
|
||||
tristate
|
||||
|
||||
config SND_SOC_MX27VIS_WM8974
|
||||
tristate "SoC Audio support for MX27 - WM8974 Visstrim_sm10 board"
|
||||
depends on SND_MX1_MX2_SOC && MACH_MX27 && MACH_IMX27_VISSTRIM_M10
|
||||
select SND_MXC_SOC_SSI
|
||||
select SND_SOC_WM8974
|
||||
help
|
||||
Say Y if you want to add support for SoC audio on Visstrim SM10
|
||||
board with WM8974.
|
||||
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
# i.MX Platform Support
|
||||
snd-soc-mx1_mx2-objs := mx1_mx2-pcm.o
|
||||
snd-soc-mxc-ssi-objs := mxc-ssi.o
|
||||
|
||||
obj-$(CONFIG_SND_MX1_MX2_SOC) += snd-soc-mx1_mx2.o
|
||||
obj-$(CONFIG_SND_MXC_SOC_SSI) += snd-soc-mxc-ssi.o
|
||||
|
||||
# i.MX Machine Support
|
||||
snd-soc-mx27vis-wm8974-objs := mx27vis_wm8974.o
|
||||
obj-$(CONFIG_SND_SOC_MX27VIS_WM8974) += snd-soc-mx27vis-wm8974.o
|
|
@ -0,0 +1,488 @@
|
|||
/*
|
||||
* mx1_mx2-pcm.c -- ALSA SoC interface for Freescale i.MX1x, i.MX2x CPUs
|
||||
*
|
||||
* Copyright 2009 Vista Silicon S.L.
|
||||
* Author: Javier Martin
|
||||
* javier.martin@vista-silicon.com
|
||||
*
|
||||
* Based on mxc-pcm.c by Liam Girdwood.
|
||||
*
|
||||
* 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/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
#include <asm/dma.h>
|
||||
#include <mach/hardware.h>
|
||||
#include <mach/dma-mx1-mx2.h>
|
||||
|
||||
#include "mx1_mx2-pcm.h"
|
||||
|
||||
|
||||
static const struct snd_pcm_hardware mx1_mx2_pcm_hardware = {
|
||||
.info = (SNDRV_PCM_INFO_INTERLEAVED |
|
||||
SNDRV_PCM_INFO_BLOCK_TRANSFER |
|
||||
SNDRV_PCM_INFO_MMAP |
|
||||
SNDRV_PCM_INFO_MMAP_VALID),
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,
|
||||
.buffer_bytes_max = 32 * 1024,
|
||||
.period_bytes_min = 64,
|
||||
.period_bytes_max = 8 * 1024,
|
||||
.periods_min = 2,
|
||||
.periods_max = 255,
|
||||
.fifo_size = 0,
|
||||
};
|
||||
|
||||
struct mx1_mx2_runtime_data {
|
||||
int dma_ch;
|
||||
int active;
|
||||
unsigned int period;
|
||||
unsigned int periods;
|
||||
int tx_spin;
|
||||
spinlock_t dma_lock;
|
||||
struct mx1_mx2_pcm_dma_params *dma_params;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* This function stops the current dma transfer for playback
|
||||
* and clears the dma pointers.
|
||||
*
|
||||
* @param substream pointer to the structure of the current stream.
|
||||
*
|
||||
*/
|
||||
static int audio_stop_dma(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct mx1_mx2_runtime_data *prtd = runtime->private_data;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&prtd->dma_lock, flags);
|
||||
|
||||
pr_debug("%s\n", __func__);
|
||||
|
||||
prtd->active = 0;
|
||||
prtd->period = 0;
|
||||
prtd->periods = 0;
|
||||
|
||||
/* this stops the dma channel and clears the buffer ptrs */
|
||||
|
||||
imx_dma_disable(prtd->dma_ch);
|
||||
|
||||
spin_unlock_irqrestore(&prtd->dma_lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is called whenever a new audio block needs to be
|
||||
* transferred to the codec. The function receives the address and the size
|
||||
* of the new block and start a new DMA transfer.
|
||||
*
|
||||
* @param substream pointer to the structure of the current stream.
|
||||
*
|
||||
*/
|
||||
static int dma_new_period(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct mx1_mx2_runtime_data *prtd = runtime->private_data;
|
||||
unsigned int dma_size;
|
||||
unsigned int offset;
|
||||
int ret = 0;
|
||||
dma_addr_t mem_addr;
|
||||
unsigned int dev_addr;
|
||||
|
||||
if (prtd->active) {
|
||||
dma_size = frames_to_bytes(runtime, runtime->period_size);
|
||||
offset = dma_size * prtd->period;
|
||||
|
||||
pr_debug("%s: period (%d) out of (%d)\n", __func__,
|
||||
prtd->period,
|
||||
runtime->periods);
|
||||
pr_debug("period_size %d frames\n offset %d bytes\n",
|
||||
(unsigned int)runtime->period_size,
|
||||
offset);
|
||||
pr_debug("dma_size %d bytes\n", dma_size);
|
||||
|
||||
snd_BUG_ON(dma_size > mx1_mx2_pcm_hardware.period_bytes_max);
|
||||
|
||||
mem_addr = (dma_addr_t)(runtime->dma_addr + offset);
|
||||
dev_addr = prtd->dma_params->per_address;
|
||||
pr_debug("%s: mem_addr is %x\n dev_addr is %x\n",
|
||||
__func__, mem_addr, dev_addr);
|
||||
|
||||
ret = imx_dma_setup_single(prtd->dma_ch, mem_addr,
|
||||
dma_size, dev_addr,
|
||||
prtd->dma_params->transfer_type);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "Error %d configuring DMA\n", ret);
|
||||
return ret;
|
||||
}
|
||||
imx_dma_enable(prtd->dma_ch);
|
||||
|
||||
pr_debug("%s: transfer enabled\nmem_addr = %x\n",
|
||||
__func__, (unsigned int) mem_addr);
|
||||
pr_debug("dev_addr = %x\ndma_size = %d\n",
|
||||
(unsigned int) dev_addr, dma_size);
|
||||
|
||||
prtd->tx_spin = 1; /* FGA little trick to retrieve DMA pos */
|
||||
prtd->period++;
|
||||
prtd->period %= runtime->periods;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This is a callback which will be called
|
||||
* when a TX transfer finishes. The call occurs
|
||||
* in interrupt context.
|
||||
*
|
||||
* @param dat pointer to the structure of the current stream.
|
||||
*
|
||||
*/
|
||||
static void audio_dma_irq(int channel, void *data)
|
||||
{
|
||||
struct snd_pcm_substream *substream;
|
||||
struct snd_pcm_runtime *runtime;
|
||||
struct mx1_mx2_runtime_data *prtd;
|
||||
unsigned int dma_size;
|
||||
unsigned int previous_period;
|
||||
unsigned int offset;
|
||||
|
||||
substream = data;
|
||||
runtime = substream->runtime;
|
||||
prtd = runtime->private_data;
|
||||
previous_period = prtd->periods;
|
||||
dma_size = frames_to_bytes(runtime, runtime->period_size);
|
||||
offset = dma_size * previous_period;
|
||||
|
||||
prtd->tx_spin = 0;
|
||||
prtd->periods++;
|
||||
prtd->periods %= runtime->periods;
|
||||
|
||||
pr_debug("%s: irq per %d offset %x\n", __func__, prtd->periods, offset);
|
||||
|
||||
/*
|
||||
* If we are getting a callback for an active stream then we inform
|
||||
* the PCM middle layer we've finished a period
|
||||
*/
|
||||
if (prtd->active)
|
||||
snd_pcm_period_elapsed(substream);
|
||||
|
||||
/*
|
||||
* Trig next DMA transfer
|
||||
*/
|
||||
dma_new_period(substream);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function configures the hardware to allow audio
|
||||
* playback operations. It is called by ALSA framework.
|
||||
*
|
||||
* @param substream pointer to the structure of the current stream.
|
||||
*
|
||||
* @return 0 on success, -1 otherwise.
|
||||
*/
|
||||
static int
|
||||
snd_mx1_mx2_prepare(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct mx1_mx2_runtime_data *prtd = runtime->private_data;
|
||||
|
||||
prtd->period = 0;
|
||||
prtd->periods = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mx1_mx2_pcm_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *hw_params)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
int ret;
|
||||
|
||||
ret = snd_pcm_lib_malloc_pages(substream,
|
||||
params_buffer_bytes(hw_params));
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "%s: Error %d failed to malloc pcm pages \n",
|
||||
__func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
pr_debug("%s: snd_imx1_mx2_audio_hw_params runtime->dma_addr 0x(%x)\n",
|
||||
__func__, (unsigned int)runtime->dma_addr);
|
||||
pr_debug("%s: snd_imx1_mx2_audio_hw_params runtime->dma_area 0x(%x)\n",
|
||||
__func__, (unsigned int)runtime->dma_area);
|
||||
pr_debug("%s: snd_imx1_mx2_audio_hw_params runtime->dma_bytes 0x(%x)\n",
|
||||
__func__, (unsigned int)runtime->dma_bytes);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mx1_mx2_pcm_hw_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct mx1_mx2_runtime_data *prtd = runtime->private_data;
|
||||
|
||||
imx_dma_free(prtd->dma_ch);
|
||||
|
||||
snd_pcm_lib_free_pages(substream);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mx1_mx2_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
|
||||
{
|
||||
struct mx1_mx2_runtime_data *prtd = substream->runtime->private_data;
|
||||
int ret = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
prtd->tx_spin = 0;
|
||||
/* requested stream startup */
|
||||
prtd->active = 1;
|
||||
pr_debug("%s: starting dma_new_period\n", __func__);
|
||||
ret = dma_new_period(substream);
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
/* requested stream shutdown */
|
||||
pr_debug("%s: stopping dma transfer\n", __func__);
|
||||
ret = audio_stop_dma(substream);
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static snd_pcm_uframes_t
|
||||
mx1_mx2_pcm_pointer(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct mx1_mx2_runtime_data *prtd = runtime->private_data;
|
||||
unsigned int offset = 0;
|
||||
|
||||
/* tx_spin value is used here to check if a transfer is active */
|
||||
if (prtd->tx_spin) {
|
||||
offset = (runtime->period_size * (prtd->periods)) +
|
||||
(runtime->period_size >> 1);
|
||||
if (offset >= runtime->buffer_size)
|
||||
offset = runtime->period_size >> 1;
|
||||
} else {
|
||||
offset = (runtime->period_size * (prtd->periods));
|
||||
if (offset >= runtime->buffer_size)
|
||||
offset = 0;
|
||||
}
|
||||
pr_debug("%s: pointer offset %x\n", __func__, offset);
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
static int mx1_mx2_pcm_open(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct mx1_mx2_runtime_data *prtd;
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct mx1_mx2_pcm_dma_params *dma_data = rtd->dai->cpu_dai->dma_data;
|
||||
int ret;
|
||||
|
||||
snd_soc_set_runtime_hwparams(substream, &mx1_mx2_pcm_hardware);
|
||||
|
||||
ret = snd_pcm_hw_constraint_integer(runtime,
|
||||
SNDRV_PCM_HW_PARAM_PERIODS);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
prtd = kzalloc(sizeof(struct mx1_mx2_runtime_data), GFP_KERNEL);
|
||||
if (prtd == NULL) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
runtime->private_data = prtd;
|
||||
|
||||
if (!dma_data)
|
||||
return -ENODEV;
|
||||
|
||||
prtd->dma_params = dma_data;
|
||||
|
||||
pr_debug("%s: Requesting dma channel (%s)\n", __func__,
|
||||
prtd->dma_params->name);
|
||||
prtd->dma_ch = imx_dma_request_by_prio(prtd->dma_params->name,
|
||||
DMA_PRIO_HIGH);
|
||||
if (prtd->dma_ch < 0) {
|
||||
printk(KERN_ERR "Error %d requesting dma channel\n", ret);
|
||||
return ret;
|
||||
}
|
||||
imx_dma_config_burstlen(prtd->dma_ch,
|
||||
prtd->dma_params->watermark_level);
|
||||
|
||||
ret = imx_dma_config_channel(prtd->dma_ch,
|
||||
prtd->dma_params->per_config,
|
||||
prtd->dma_params->mem_config,
|
||||
prtd->dma_params->event_id, 0);
|
||||
|
||||
if (ret) {
|
||||
pr_debug(KERN_ERR "Error %d configuring dma channel %d\n",
|
||||
ret, prtd->dma_ch);
|
||||
return ret;
|
||||
}
|
||||
|
||||
pr_debug("%s: Setting tx dma callback function\n", __func__);
|
||||
ret = imx_dma_setup_handlers(prtd->dma_ch,
|
||||
audio_dma_irq, NULL,
|
||||
(void *)substream);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "Error %d setting dma callback function\n", ret);
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mx1_mx2_pcm_close(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct mx1_mx2_runtime_data *prtd = runtime->private_data;
|
||||
|
||||
kfree(prtd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mx1_mx2_pcm_mmap(struct snd_pcm_substream *substream,
|
||||
struct vm_area_struct *vma)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
return dma_mmap_writecombine(substream->pcm->card->dev, vma,
|
||||
runtime->dma_area,
|
||||
runtime->dma_addr,
|
||||
runtime->dma_bytes);
|
||||
}
|
||||
|
||||
static struct snd_pcm_ops mx1_mx2_pcm_ops = {
|
||||
.open = mx1_mx2_pcm_open,
|
||||
.close = mx1_mx2_pcm_close,
|
||||
.ioctl = snd_pcm_lib_ioctl,
|
||||
.hw_params = mx1_mx2_pcm_hw_params,
|
||||
.hw_free = mx1_mx2_pcm_hw_free,
|
||||
.prepare = snd_mx1_mx2_prepare,
|
||||
.trigger = mx1_mx2_pcm_trigger,
|
||||
.pointer = mx1_mx2_pcm_pointer,
|
||||
.mmap = mx1_mx2_pcm_mmap,
|
||||
};
|
||||
|
||||
static u64 mx1_mx2_pcm_dmamask = 0xffffffff;
|
||||
|
||||
static int mx1_mx2_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
|
||||
{
|
||||
struct snd_pcm_substream *substream = pcm->streams[stream].substream;
|
||||
struct snd_dma_buffer *buf = &substream->dma_buffer;
|
||||
size_t size = mx1_mx2_pcm_hardware.buffer_bytes_max;
|
||||
buf->dev.type = SNDRV_DMA_TYPE_DEV;
|
||||
buf->dev.dev = pcm->card->dev;
|
||||
buf->private_data = NULL;
|
||||
|
||||
/* Reserve uncached-buffered memory area for DMA */
|
||||
buf->area = dma_alloc_writecombine(pcm->card->dev, size,
|
||||
&buf->addr, GFP_KERNEL);
|
||||
|
||||
pr_debug("%s: preallocate_dma_buffer: area=%p, addr=%p, size=%d\n",
|
||||
__func__, (void *) buf->area, (void *) buf->addr, size);
|
||||
|
||||
if (!buf->area)
|
||||
return -ENOMEM;
|
||||
|
||||
buf->bytes = size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mx1_mx2_pcm_free_dma_buffers(struct snd_pcm *pcm)
|
||||
{
|
||||
struct snd_pcm_substream *substream;
|
||||
struct snd_dma_buffer *buf;
|
||||
int stream;
|
||||
|
||||
for (stream = 0; stream < 2; stream++) {
|
||||
substream = pcm->streams[stream].substream;
|
||||
if (!substream)
|
||||
continue;
|
||||
|
||||
buf = &substream->dma_buffer;
|
||||
if (!buf->area)
|
||||
continue;
|
||||
|
||||
dma_free_writecombine(pcm->card->dev, buf->bytes,
|
||||
buf->area, buf->addr);
|
||||
buf->area = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int mx1_mx2_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
|
||||
struct snd_pcm *pcm)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
if (!card->dev->dma_mask)
|
||||
card->dev->dma_mask = &mx1_mx2_pcm_dmamask;
|
||||
if (!card->dev->coherent_dma_mask)
|
||||
card->dev->coherent_dma_mask = 0xffffffff;
|
||||
|
||||
if (dai->playback.channels_min) {
|
||||
ret = mx1_mx2_pcm_preallocate_dma_buffer(pcm,
|
||||
SNDRV_PCM_STREAM_PLAYBACK);
|
||||
pr_debug("%s: preallocate playback buffer\n", __func__);
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (dai->capture.channels_min) {
|
||||
ret = mx1_mx2_pcm_preallocate_dma_buffer(pcm,
|
||||
SNDRV_PCM_STREAM_CAPTURE);
|
||||
pr_debug("%s: preallocate capture buffer\n", __func__);
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct snd_soc_platform mx1_mx2_soc_platform = {
|
||||
.name = "mx1_mx2-audio",
|
||||
.pcm_ops = &mx1_mx2_pcm_ops,
|
||||
.pcm_new = mx1_mx2_pcm_new,
|
||||
.pcm_free = mx1_mx2_pcm_free_dma_buffers,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(mx1_mx2_soc_platform);
|
||||
|
||||
static int __init mx1_mx2_soc_platform_init(void)
|
||||
{
|
||||
return snd_soc_register_platform(&mx1_mx2_soc_platform);
|
||||
}
|
||||
module_init(mx1_mx2_soc_platform_init);
|
||||
|
||||
static void __exit mx1_mx2_soc_platform_exit(void)
|
||||
{
|
||||
snd_soc_unregister_platform(&mx1_mx2_soc_platform);
|
||||
}
|
||||
module_exit(mx1_mx2_soc_platform_exit);
|
||||
|
||||
MODULE_AUTHOR("Javier Martin, javier.martin@vista-silicon.com");
|
||||
MODULE_DESCRIPTION("Freescale i.MX2x, i.MX1x PCM DMA module");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* mx1_mx2-pcm.h :- ASoC platform header for Freescale i.MX1x, i.MX2x
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef _MX1_MX2_PCM_H
|
||||
#define _MX1_MX2_PCM_H
|
||||
|
||||
/* DMA information for mx1_mx2 platforms */
|
||||
struct mx1_mx2_pcm_dma_params {
|
||||
char *name; /* stream identifier */
|
||||
unsigned int transfer_type; /* READ or WRITE DMA transfer */
|
||||
dma_addr_t per_address; /* physical address of SSI fifo */
|
||||
int event_id; /* fixed DMA number for SSI fifo */
|
||||
int watermark_level; /* SSI fifo watermark level */
|
||||
int per_config; /* DMA Config flags for peripheral */
|
||||
int mem_config; /* DMA Config flags for RAM */
|
||||
};
|
||||
|
||||
/* platform data */
|
||||
extern struct snd_soc_platform mx1_mx2_soc_platform;
|
||||
|
||||
#endif
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue