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:
Takashi Iwai 2009-09-10 15:32:40 +02:00
commit e0b3032bcd
138 changed files with 23493 additions and 2818 deletions

View file

@ -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)

View file

@ -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);

View file

@ -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;

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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)

View file

@ -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

View file

@ -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 */

83
include/sound/sh_fsi.h Normal file
View file

@ -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 */

View file

@ -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,

View file

@ -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 */
};
/*

View file

@ -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
};

22
include/sound/uda1380.h Normal file
View file

@ -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 */

44
include/sound/wm8993.h Normal file
View file

@ -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

View file

@ -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;
}

View file

@ -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;

View file

@ -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"

View file

@ -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/

View file

@ -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);

View file

@ -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>");

View file

@ -29,6 +29,7 @@ struct au1xpsc_audio_data {
unsigned long pm[2];
struct resource *ioarea;
struct mutex lock;
};
#define PCM_TX 0

View file

@ -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

View file

@ -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

View file

@ -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;
}

View file

@ -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");

View file

@ -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");

View file

@ -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);

View file

@ -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;
}

View file

@ -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);

View file

@ -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");

View file

@ -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

View file

@ -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");

View file

@ -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

View file

@ -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

View file

@ -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

446
sound/soc/codecs/ad1836.c Normal file
View file

@ -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");

64
sound/soc/codecs/ad1836.h Normal file
View file

@ -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

682
sound/soc/codecs/ad1938.c Normal file
View file

@ -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");

100
sound/soc/codecs/ad1938.h Normal file
View file

@ -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

View file

@ -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

502
sound/soc/codecs/ak4642.c Normal file
View file

@ -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");

20
sound/soc/codecs/ak4642.h Normal file
View file

@ -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

View file

@ -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);

501
sound/soc/codecs/cx20442.c Normal file
View file

@ -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");

View file

@ -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

308
sound/soc/codecs/max9877.c Normal file
View file

@ -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");

View file

@ -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

View file

@ -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)
{

View file

@ -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 ||

View file

@ -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);

View file

@ -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];
};

View file

@ -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[] = {

View file

@ -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__ */

View file

@ -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;
}

View file

@ -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);

View file

@ -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 */

View file

@ -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)

View file

@ -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)

View file

@ -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");

699
sound/soc/codecs/wm8523.c Normal file
View file

@ -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");

160
sound/soc/codecs/wm8523.h Normal file
View file

@ -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

View file

@ -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

View file

@ -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");

View file

@ -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

View file

@ -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");

View file

@ -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

744
sound/soc/codecs/wm8776.c Normal file
View file

@ -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");

51
sound/soc/codecs/wm8776.h Normal file
View file

@ -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

View file

@ -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 = &reg;
/* 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,
};

View file

@ -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 = &reg;
/* 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,
};

View file

@ -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,
};

View file

@ -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,
};

1265
sound/soc/codecs/wm8961.c Normal file

File diff suppressed because it is too large Load diff

866
sound/soc/codecs/wm8961.h Normal file
View file

@ -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

View file

@ -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

808
sound/soc/codecs/wm8974.c Normal file
View file

@ -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");

99
sound/soc/codecs/wm8974.h Normal file
View file

@ -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

View file

@ -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

View file

@ -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));

1675
sound/soc/codecs/wm8993.c Normal file

File diff suppressed because it is too large Load diff

2132
sound/soc/codecs/wm8993.h Normal file

File diff suppressed because it is too large Load diff

View file

@ -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 = &reg;
/* 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,
};

View file

@ -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;

743
sound/soc/codecs/wm_hubs.c Normal file
View file

@ -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");

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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);

View file

@ -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);

View file

@ -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");

View file

@ -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 */

View file

@ -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)

View file

@ -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;

View file

@ -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))

View file

@ -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(&regs->op1, MPC52xx_PSC_OP_RES);
udelay(10);
out_8(&regs->op0, MPC52xx_PSC_OP_RES);
udelay(50);
msleep(1);
psc_ac97_warm_reset(ac97);
}

21
sound/soc/imx/Kconfig Normal file
View file

@ -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.

10
sound/soc/imx/Makefile Normal file
View file

@ -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

488
sound/soc/imx/mx1_mx2-pcm.c Normal file
View file

@ -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");

View file

@ -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