]> git.karo-electronics.de Git - karo-tx-linux.git/commitdiff
Merge branch 'topic/asoc' into for-linus
authorTakashi Iwai <tiwai@suse.de>
Thu, 5 Aug 2010 09:17:01 +0000 (11:17 +0200)
committerTakashi Iwai <tiwai@suse.de>
Thu, 5 Aug 2010 09:17:01 +0000 (11:17 +0200)
127 files changed:
arch/arm/mach-davinci/board-da830-evm.c
arch/arm/mach-davinci/board-da850-evm.c
arch/arm/mach-davinci/board-dm646x-evm.c
arch/arm/mach-davinci/include/mach/asp.h
arch/arm/mach-ep93xx/clock.c
arch/arm/mach-ep93xx/core.c
arch/arm/mach-ep93xx/include/mach/ep93xx-regs.h
arch/arm/mach-ep93xx/include/mach/platform.h
arch/arm/mach-ep93xx/snappercl15.c
arch/arm/mach-kirkwood/common.c
arch/arm/mach-kirkwood/common.h
arch/arm/mach-kirkwood/include/mach/kirkwood.h
arch/arm/mach-kirkwood/openrd-setup.c
arch/arm/mach-omap2/mcbsp.c
arch/arm/plat-mxc/include/mach/ssi.h
arch/arm/plat-omap/include/plat/mcbsp.h
arch/arm/plat-omap/mcbsp.c
arch/arm/plat-orion/include/plat/audio.h [new file with mode: 0644]
include/sound/sh_fsi.h
include/sound/soc-dapm.h
include/sound/soc.h
include/sound/tlv320dac33-plat.h
include/sound/uda134x.h
sound/soc/Kconfig
sound/soc/Makefile
sound/soc/atmel/atmel-pcm.c
sound/soc/atmel/atmel_ssc_dai.c
sound/soc/au1x/psc-ac97.c
sound/soc/au1x/psc-i2s.c
sound/soc/au1x/psc.h
sound/soc/blackfin/bf5xx-ac97.c
sound/soc/blackfin/bf5xx-tdm.c
sound/soc/codecs/Kconfig
sound/soc/codecs/Makefile
sound/soc/codecs/ad1836.c
sound/soc/codecs/ad193x.c
sound/soc/codecs/ad193x.h
sound/soc/codecs/ak4642.c
sound/soc/codecs/cs42l51.c [new file with mode: 0644]
sound/soc/codecs/cs42l51.h [new file with mode: 0644]
sound/soc/codecs/da7210.c
sound/soc/codecs/jz4740.c [new file with mode: 0644]
sound/soc/codecs/jz4740.h [new file with mode: 0644]
sound/soc/codecs/spdif_transciever.c
sound/soc/codecs/spdif_transciever.h
sound/soc/codecs/tlv320aic23.c
sound/soc/codecs/tlv320dac33.c
sound/soc/codecs/twl4030.c
sound/soc/codecs/twl4030.h
sound/soc/codecs/twl6040.c
sound/soc/codecs/uda134x.c
sound/soc/codecs/uda134x.h
sound/soc/codecs/wm2000.c
sound/soc/codecs/wm8523.c
sound/soc/codecs/wm8711.c
sound/soc/codecs/wm8741.c [new file with mode: 0644]
sound/soc/codecs/wm8741.h [new file with mode: 0644]
sound/soc/codecs/wm8750.c
sound/soc/codecs/wm8904.c
sound/soc/codecs/wm8940.c
sound/soc/codecs/wm8955.c
sound/soc/codecs/wm8960.c
sound/soc/codecs/wm8961.c
sound/soc/codecs/wm8974.c
sound/soc/codecs/wm8978.c
sound/soc/codecs/wm8990.c
sound/soc/codecs/wm8994.c
sound/soc/codecs/wm8994.h
sound/soc/codecs/wm9081.c
sound/soc/codecs/wm_hubs.c
sound/soc/davinci/davinci-i2s.c
sound/soc/davinci/davinci-i2s.h
sound/soc/davinci/davinci-mcasp.c
sound/soc/davinci/davinci-pcm.c
sound/soc/davinci/davinci-pcm.h
sound/soc/davinci/davinci-vcif.c
sound/soc/ep93xx/Kconfig [new file with mode: 0644]
sound/soc/ep93xx/Makefile [new file with mode: 0644]
sound/soc/ep93xx/ep93xx-i2s.c [new file with mode: 0644]
sound/soc/ep93xx/ep93xx-i2s.h [new file with mode: 0644]
sound/soc/ep93xx/ep93xx-pcm.c [new file with mode: 0644]
sound/soc/ep93xx/ep93xx-pcm.h [new file with mode: 0644]
sound/soc/ep93xx/snappercl15.c [new file with mode: 0644]
sound/soc/fsl/mpc5200_psc_i2s.c
sound/soc/fsl/mpc5200_psc_i2s.h [deleted file]
sound/soc/imx/Kconfig
sound/soc/imx/Makefile
sound/soc/imx/eukrea-tlv320.c [new file with mode: 0644]
sound/soc/imx/imx-pcm-dma-mx2.c
sound/soc/imx/imx-pcm-fiq.c
sound/soc/imx/imx-ssi.c
sound/soc/jz4740/Kconfig [new file with mode: 0644]
sound/soc/jz4740/Makefile [new file with mode: 0644]
sound/soc/jz4740/jz4740-i2s.c [new file with mode: 0644]
sound/soc/jz4740/jz4740-i2s.h [new file with mode: 0644]
sound/soc/jz4740/jz4740-pcm.c [new file with mode: 0644]
sound/soc/jz4740/jz4740-pcm.h [new file with mode: 0644]
sound/soc/jz4740/qi_lb60.c [new file with mode: 0644]
sound/soc/kirkwood/Kconfig [new file with mode: 0644]
sound/soc/kirkwood/Makefile [new file with mode: 0644]
sound/soc/kirkwood/kirkwood-dma.c [new file with mode: 0644]
sound/soc/kirkwood/kirkwood-dma.h [new file with mode: 0644]
sound/soc/kirkwood/kirkwood-i2s.c [new file with mode: 0644]
sound/soc/kirkwood/kirkwood-i2s.h [new file with mode: 0644]
sound/soc/kirkwood/kirkwood-openrd.c [new file with mode: 0644]
sound/soc/kirkwood/kirkwood.h [new file with mode: 0644]
sound/soc/nuc900/Kconfig [new file with mode: 0644]
sound/soc/nuc900/Makefile [new file with mode: 0644]
sound/soc/nuc900/nuc900-ac97.c [new file with mode: 0644]
sound/soc/nuc900/nuc900-audio.c [new file with mode: 0644]
sound/soc/nuc900/nuc900-audio.h [new file with mode: 0644]
sound/soc/nuc900/nuc900-pcm.c [new file with mode: 0644]
sound/soc/omap/omap-mcbsp.c
sound/soc/omap/omap3pandora.c
sound/soc/omap/rx51.c
sound/soc/s3c24xx/Kconfig
sound/soc/s3c24xx/Makefile
sound/soc/s3c24xx/s3c-ac97.c
sound/soc/s3c24xx/s3c-i2s-v2.c
sound/soc/s3c24xx/smartq_wm8987.c [new file with mode: 0644]
sound/soc/s3c24xx/smdk_wm9713.c
sound/soc/s6000/s6000-i2s.c
sound/soc/sh/Kconfig
sound/soc/sh/fsi-ak4642.c
sound/soc/sh/fsi-da7210.c
sound/soc/sh/fsi.c
sound/soc/soc-core.c

index 212d97084bd71dc11bbda9308574f1c3cbc4ea3e..bc384d3561da8162a150a16adf9223c97e896d61 100644 (file)
@@ -208,7 +208,7 @@ static struct snd_platform_data da830_evm_snd_data = {
        .num_serializer = ARRAY_SIZE(da830_iis_serializer_direction),
        .tdm_slots      = 2,
        .serial_dir     = da830_iis_serializer_direction,
-       .eventq_no      = EVENTQ_0,
+       .asp_chan_q     = EVENTQ_0,
        .version        = MCASP_VERSION_2,
        .txnumevt       = 1,
        .rxnumevt       = 1,
index b280efb1fa120806b1cfe85bdea1f90d1bc0caaf..e8c8190902681f01341c6d3d7cd8f3551cd072f3 100644 (file)
@@ -343,7 +343,7 @@ static struct snd_platform_data da850_evm_snd_data = {
        .num_serializer = ARRAY_SIZE(da850_iis_serializer_direction),
        .tdm_slots      = 2,
        .serial_dir     = da850_iis_serializer_direction,
-       .eventq_no      = EVENTQ_1,
+       .asp_chan_q     = EVENTQ_1,
        .version        = MCASP_VERSION_2,
        .txnumevt       = 1,
        .rxnumevt       = 1,
index 6d8889342c9f8b978886bff64f5848b5f5fa7ce0..87521f2d69c740d632606e31b8bc4868c89f4e56 100644 (file)
@@ -323,7 +323,7 @@ static struct snd_platform_data dm646x_evm_snd_data[] = {
                .num_serializer = ARRAY_SIZE(dm646x_iis_serializer_direction),
                .tdm_slots      = 2,
                .serial_dir     = dm646x_iis_serializer_direction,
-               .eventq_no      = EVENTQ_0,
+               .asp_chan_q     = EVENTQ_0,
        },
        {
                .tx_dma_offset  = 0x400,
@@ -332,7 +332,7 @@ static struct snd_platform_data dm646x_evm_snd_data[] = {
                .num_serializer = ARRAY_SIZE(dm646x_dit_serializer_direction),
                .tdm_slots      = 32,
                .serial_dir     = dm646x_dit_serializer_direction,
-               .eventq_no      = EVENTQ_0,
+               .asp_chan_q     = EVENTQ_0,
        },
 };
 
index 834725f1e81d93504936b32abf0f88180c101d32..9aa240909a2cf4f9dac7bc1afc560712d9020ba2 100644 (file)
@@ -52,7 +52,8 @@
 struct snd_platform_data {
        u32 tx_dma_offset;
        u32 rx_dma_offset;
-       enum dma_event_q eventq_no;     /* event queue number */
+       enum dma_event_q asp_chan_q;    /* event queue number for ASP channel */
+       enum dma_event_q ram_chan_q;    /* event queue number for RAM channel */
        unsigned int codec_fmt;
        /*
         * Allowing this is more efficient and eliminates left and right swaps
@@ -63,6 +64,49 @@ struct snd_platform_data {
        unsigned sram_size_playback;
        unsigned sram_size_capture;
 
+       /*
+        * If McBSP peripheral gets the clock from an external pin,
+        * there are three chooses, that are MCBSP_CLKX, MCBSP_CLKR
+        * and MCBSP_CLKS.
+        * Depending on different hardware connections it is possible
+        * to use this setting to change the behaviour of McBSP
+        * driver. The dm365_clk_input_pin enum is available for dm365
+        */
+       int clk_input_pin;
+
+       /*
+        * This flag works when both clock and FS are outputs for the cpu
+        * and makes clock more accurate (FS is not symmetrical and the
+        * clock is very fast.
+        * The clock becoming faster is named
+        * i2s continuous serial clock (I2S_SCK) and it is an externally
+        * visible bit clock.
+        *
+        * first line : WordSelect
+        * second line : ContinuousSerialClock
+        * third line: SerialData
+        *
+        * SYMMETRICAL APPROACH:
+        *   _______________________          LEFT
+        * _|         RIGHT         |______________________|
+        *     _   _         _   _   _   _         _   _
+        *   _| |_| |_ x16 _| |_| |_| |_| |_ x16 _| |_| |_
+        *     _   _         _   _   _   _         _   _
+        *   _/ \_/ \_ ... _/ \_/ \_/ \_/ \_ ... _/ \_/ \_
+        *    \_/ \_/       \_/ \_/ \_/ \_/       \_/ \_/
+        *
+        * ACCURATE CLOCK APPROACH:
+        *   ______________          LEFT
+        * _|     RIGHT    |_______________________________|
+        *     _         _   _         _   _   _   _   _   _
+        *   _| |_ x16 _| |_| |_ x16 _| |_| |_| |_| |_| |_| |
+        *     _         _   _          _      dummy cycles
+        *   _/ \_ ... _/ \_/ \_  ... _/ \__________________
+        *    \_/       \_/ \_/        \_/
+        *
+        */
+       bool i2s_accurate_sck;
+
        /* McASP specific fields */
        int tdm_slots;
        u8 op_mode;
@@ -78,6 +122,11 @@ enum {
        MCASP_VERSION_2,        /* DA8xx/OMAPL1x */
 };
 
+enum dm365_clk_input_pin {
+       MCBSP_CLKR = 0,         /* DM365 */
+       MCBSP_CLKS,
+};
+
 #define INACTIVE_MODE  0
 #define TX_MODE                1
 #define RX_MODE                2
index e29bdef9b2e201ea345c4139487e4895dffad94d..803162492c4a2c923e6d2b5b2f6216985e18b324 100644 (file)
@@ -43,7 +43,8 @@ static unsigned long get_uart_rate(struct clk *clk);
 
 static int set_keytchclk_rate(struct clk *clk, unsigned long rate);
 static int set_div_rate(struct clk *clk, unsigned long rate);
-
+static int set_i2s_sclk_rate(struct clk *clk, unsigned long rate);
+static int set_i2s_lrclk_rate(struct clk *clk, unsigned long rate);
 
 static struct clk clk_xtali = {
        .rate           = EP93XX_EXT_CLK_RATE,
@@ -112,6 +113,29 @@ static struct clk clk_video = {
        .set_rate       = set_div_rate,
 };
 
+static struct clk clk_i2s_mclk = {
+       .sw_locked      = 1,
+       .enable_reg     = EP93XX_SYSCON_I2SCLKDIV,
+       .enable_mask    = EP93XX_SYSCON_CLKDIV_ENABLE,
+       .set_rate       = set_div_rate,
+};
+
+static struct clk clk_i2s_sclk = {
+       .sw_locked      = 1,
+       .parent         = &clk_i2s_mclk,
+       .enable_reg     = EP93XX_SYSCON_I2SCLKDIV,
+       .enable_mask    = EP93XX_SYSCON_I2SCLKDIV_SENA,
+       .set_rate       = set_i2s_sclk_rate,
+};
+
+static struct clk clk_i2s_lrclk = {
+       .sw_locked      = 1,
+       .parent         = &clk_i2s_sclk,
+       .enable_reg     = EP93XX_SYSCON_I2SCLKDIV,
+       .enable_mask    = EP93XX_SYSCON_I2SCLKDIV_SENA,
+       .set_rate       = set_i2s_lrclk_rate,
+};
+
 /* DMA Clocks */
 static struct clk clk_m2p0 = {
        .parent         = &clk_h,
@@ -191,6 +215,9 @@ static struct clk_lookup clocks[] = {
        INIT_CK("ep93xx-keypad",        NULL,           &clk_keypad),
        INIT_CK("ep93xx-fb",            NULL,           &clk_video),
        INIT_CK("ep93xx-spi.0",         NULL,           &clk_spi),
+       INIT_CK("ep93xx-i2s",           "mclk",         &clk_i2s_mclk),
+       INIT_CK("ep93xx-i2s",           "sclk",         &clk_i2s_sclk),
+       INIT_CK("ep93xx-i2s",           "lrclk",        &clk_i2s_lrclk),
        INIT_CK(NULL,                   "pwm_clk",      &clk_pwm),
        INIT_CK(NULL,                   "m2p0",         &clk_m2p0),
        INIT_CK(NULL,                   "m2p1",         &clk_m2p1),
@@ -401,6 +428,44 @@ static int set_div_rate(struct clk *clk, unsigned long rate)
        return 0;
 }
 
+static int set_i2s_sclk_rate(struct clk *clk, unsigned long rate)
+{
+       unsigned val = __raw_readl(clk->enable_reg);
+
+       if (rate == clk_i2s_mclk.rate / 2)
+               ep93xx_syscon_swlocked_write(val & ~EP93XX_I2SCLKDIV_SDIV, 
+                                            clk->enable_reg);
+       else if (rate == clk_i2s_mclk.rate / 4)
+               ep93xx_syscon_swlocked_write(val | EP93XX_I2SCLKDIV_SDIV, 
+                                            clk->enable_reg);
+       else
+               return -EINVAL;
+
+       clk_i2s_sclk.rate = rate;
+       return 0;
+}
+
+static int set_i2s_lrclk_rate(struct clk *clk, unsigned long rate)
+{
+       unsigned val = __raw_readl(clk->enable_reg) & 
+               ~EP93XX_I2SCLKDIV_LRDIV_MASK;
+       
+       if (rate == clk_i2s_sclk.rate / 32)
+               ep93xx_syscon_swlocked_write(val | EP93XX_I2SCLKDIV_LRDIV32,
+                                            clk->enable_reg);
+       else if (rate == clk_i2s_sclk.rate / 64)
+               ep93xx_syscon_swlocked_write(val | EP93XX_I2SCLKDIV_LRDIV64,
+                                            clk->enable_reg);
+       else if (rate == clk_i2s_sclk.rate / 128)
+               ep93xx_syscon_swlocked_write(val | EP93XX_I2SCLKDIV_LRDIV128,
+                                            clk->enable_reg);
+       else
+               return -EINVAL;
+
+       clk_i2s_lrclk.rate = rate;
+       return 0;
+}
+
 int clk_set_rate(struct clk *clk, unsigned long rate)
 {
        if (clk->set_rate)
index 9092677f63eb739a529d137b5b1be891b11f53da..b4ee5409eb7229ff2a8965c7de7ec5c8d47f80ef 100644 (file)
@@ -714,6 +714,73 @@ void ep93xx_keypad_release_gpio(struct platform_device *pdev)
 }
 EXPORT_SYMBOL(ep93xx_keypad_release_gpio);
 
+/*************************************************************************
+ * EP93xx I2S audio peripheral handling
+ *************************************************************************/
+static struct resource ep93xx_i2s_resource[] = {
+       {
+               .start  = EP93XX_I2S_PHYS_BASE,
+               .end    = EP93XX_I2S_PHYS_BASE + 0x100 - 1,
+               .flags  = IORESOURCE_MEM,
+       },
+};
+
+static struct platform_device ep93xx_i2s_device = {
+       .name           = "ep93xx-i2s",
+       .id             = -1,
+       .num_resources  = ARRAY_SIZE(ep93xx_i2s_resource),
+       .resource       = ep93xx_i2s_resource,
+};
+
+void __init ep93xx_register_i2s(void)
+{
+       platform_device_register(&ep93xx_i2s_device);
+}
+
+#define EP93XX_SYSCON_DEVCFG_I2S_MASK  (EP93XX_SYSCON_DEVCFG_I2SONSSP | \
+                                        EP93XX_SYSCON_DEVCFG_I2SONAC97)
+
+#define EP93XX_I2SCLKDIV_MASK          (EP93XX_SYSCON_I2SCLKDIV_ORIDE | \
+                                        EP93XX_SYSCON_I2SCLKDIV_SPOL)
+
+int ep93xx_i2s_acquire(unsigned i2s_pins, unsigned i2s_config)
+{
+       unsigned val;
+
+       /* Sanity check */
+       if (i2s_pins & ~EP93XX_SYSCON_DEVCFG_I2S_MASK)
+               return -EINVAL;
+       if (i2s_config & ~EP93XX_I2SCLKDIV_MASK)
+               return -EINVAL;
+
+       /* Must have only one of I2SONSSP/I2SONAC97 set */
+       if ((i2s_pins & EP93XX_SYSCON_DEVCFG_I2SONSSP) ==
+           (i2s_pins & EP93XX_SYSCON_DEVCFG_I2SONAC97))
+               return -EINVAL;
+
+       ep93xx_devcfg_clear_bits(EP93XX_SYSCON_DEVCFG_I2S_MASK);
+       ep93xx_devcfg_set_bits(i2s_pins);
+
+       /*
+        * This is potentially racy with the clock api for i2s_mclk, sclk and 
+        * lrclk. Since the i2s driver is the only user of those clocks we
+        * rely on it to prevent parallel use of this function and the 
+        * clock api for the i2s clocks.
+        */
+       val = __raw_readl(EP93XX_SYSCON_I2SCLKDIV);
+       val &= ~EP93XX_I2SCLKDIV_MASK;
+       val |= i2s_config;
+       ep93xx_syscon_swlocked_write(val, EP93XX_SYSCON_I2SCLKDIV);
+
+       return 0;
+}
+EXPORT_SYMBOL(ep93xx_i2s_acquire);
+
+void ep93xx_i2s_release(void)
+{
+       ep93xx_devcfg_clear_bits(EP93XX_SYSCON_DEVCFG_I2S_MASK);
+}
+EXPORT_SYMBOL(ep93xx_i2s_release);
 
 extern void ep93xx_gpio_init(void);
 
index b1e096f0c2d2ff59b4ef2428003b9340f507d12d..c54b3e56ba635a6c3edfc599dcf9154f27c5d43c 100644 (file)
@@ -93,6 +93,7 @@
 /* APB peripherals */
 #define EP93XX_TIMER_BASE              EP93XX_APB_IOMEM(0x00010000)
 
+#define EP93XX_I2S_PHYS_BASE           EP93XX_APB_PHYS(0x00020000)
 #define EP93XX_I2S_BASE                        EP93XX_APB_IOMEM(0x00020000)
 
 #define EP93XX_SECURITY_BASE           EP93XX_APB_IOMEM(0x00030000)
 #define EP93XX_SYSCON_CLKDIV_ESEL      (1<<14)
 #define EP93XX_SYSCON_CLKDIV_PSEL      (1<<13)
 #define EP93XX_SYSCON_CLKDIV_PDIV_SHIFT        8
+#define EP93XX_SYSCON_I2SCLKDIV                EP93XX_SYSCON_REG(0x8c)
+#define EP93XX_SYSCON_I2SCLKDIV_SENA   (1<<31)
+#define EP93XX_SYSCON_I2SCLKDIV_ORIDE   (1<<29)
+#define EP93XX_SYSCON_I2SCLKDIV_SPOL   (1<<19)
+#define EP93XX_I2SCLKDIV_SDIV          (1 << 16)
+#define EP93XX_I2SCLKDIV_LRDIV32       (0 << 17)
+#define EP93XX_I2SCLKDIV_LRDIV64       (1 << 17)
+#define EP93XX_I2SCLKDIV_LRDIV128      (2 << 17)
+#define EP93XX_I2SCLKDIV_LRDIV_MASK    (3 << 17)
 #define EP93XX_SYSCON_KEYTCHCLKDIV     EP93XX_SYSCON_REG(0x90)
 #define EP93XX_SYSCON_KEYTCHCLKDIV_TSEN        (1<<31)
 #define EP93XX_SYSCON_KEYTCHCLKDIV_ADIV        (1<<16)
index 9a4413dd44bb6e1cd2ac6cbc291cfd87b7220d6f..99eff877a146ac40cde7e52743af3cdd64c88119 100644 (file)
@@ -55,6 +55,9 @@ void ep93xx_pwm_release_gpio(struct platform_device *pdev);
 void ep93xx_register_keypad(struct ep93xx_keypad_platform_data *data);
 int ep93xx_keypad_acquire_gpio(struct platform_device *pdev);
 void ep93xx_keypad_release_gpio(struct platform_device *pdev);
+void ep93xx_register_i2s(void);
+int ep93xx_i2s_acquire(unsigned i2s_pins, unsigned i2s_config);
+void ep93xx_i2s_release(void);
 
 void ep93xx_init_devices(void);
 extern struct sys_timer ep93xx_timer;
index 38deaee403971accb292653259ceae9194d20b07..a12c89301297f6bb32985854df3a404dcc18fc97 100644 (file)
@@ -157,6 +157,7 @@ static void __init snappercl15_init_machine(void)
        ep93xx_register_i2c(&snappercl15_i2c_gpio_data, snappercl15_i2c_data,
                            ARRAY_SIZE(snappercl15_i2c_data));
        ep93xx_register_fb(&snappercl15_fb_info);
+       ep93xx_register_i2s();
        platform_device_register(&snappercl15_nand_device);
 }
 
index 6072eaa5e66acf81f080d72374fbfd1dfbe7382c..e1f3efedbcf18717696eb400370f6f7da5864ccc 100644 (file)
@@ -25,6 +25,7 @@
 #include <asm/mach/time.h>
 #include <mach/kirkwood.h>
 #include <mach/bridge-regs.h>
+#include <plat/audio.h>
 #include <plat/cache-feroceon-l2.h>
 #include <plat/ehci-orion.h>
 #include <plat/mvsdio.h>
@@ -864,6 +865,42 @@ struct sys_timer kirkwood_timer = {
        .init = kirkwood_timer_init,
 };
 
+/*****************************************************************************
+ * Audio
+ ****************************************************************************/
+static struct resource kirkwood_i2s_resources[] = {
+       [0] = {
+               .start  = AUDIO_PHYS_BASE,
+               .end    = AUDIO_PHYS_BASE + SZ_16K - 1,
+               .flags  = IORESOURCE_MEM,
+       },
+       [1] = {
+               .start  = IRQ_KIRKWOOD_I2S,
+               .end    = IRQ_KIRKWOOD_I2S,
+               .flags  = IORESOURCE_IRQ,
+       },
+};
+
+static struct kirkwood_asoc_platform_data kirkwood_i2s_data = {
+       .dram        = &kirkwood_mbus_dram_info,
+       .burst       = 128,
+};
+
+static struct platform_device kirkwood_i2s_device = {
+       .name           = "kirkwood-i2s",
+       .id             = -1,
+       .num_resources  = ARRAY_SIZE(kirkwood_i2s_resources),
+       .resource       = kirkwood_i2s_resources,
+       .dev            = {
+               .platform_data  = &kirkwood_i2s_data,
+       },
+};
+
+void __init kirkwood_audio_init(void)
+{
+       kirkwood_clk_ctrl |= CGC_AUDIO;
+       platform_device_register(&kirkwood_i2s_device);
+}
 
 /*****************************************************************************
  * General
@@ -923,6 +960,7 @@ void __init kirkwood_init(void)
        kirkwood_spi_plat_data.tclk = kirkwood_tclk;
        kirkwood_uart0_data[0].uartclk = kirkwood_tclk;
        kirkwood_uart1_data[0].uartclk = kirkwood_tclk;
+       kirkwood_i2s_data.tclk = kirkwood_tclk;
 
        /*
         * Disable propagation of mbus errors to the CPU local bus,
index 05e8a8a5692e8150535973105b1f50bed60bcfea..c9ab1cb3b39fc2a7eb8f396b623c76547de1258b 100644 (file)
@@ -17,6 +17,7 @@ struct mv_sata_platform_data;
 struct mvsdio_platform_data;
 struct mtd_partition;
 struct mtd_info;
+struct kirkwood_asoc_platform_data;
 
 /*
  * Basic Kirkwood init functions used early by machine-setup.
@@ -43,6 +44,7 @@ void kirkwood_uart0_init(void);
 void kirkwood_uart1_init(void);
 void kirkwood_nand_init(struct mtd_partition *parts, int nr_parts, int delay);
 void kirkwood_nand_init_rnb(struct mtd_partition *parts, int nr_parts, int (*dev_ready)(struct mtd_info *));
+void kirkwood_audio_init(void);
 
 extern int kirkwood_tclk;
 extern struct sys_timer kirkwood_timer;
index a15cf0ee22bd802c7aaefb4a04b067540eabcd1b..838151d0744b5cb50ae722d3e710b00755c568b6 100644 (file)
@@ -96,6 +96,9 @@
 
 #define SDIO_PHYS_BASE         (KIRKWOOD_REGS_PHYS_BASE | 0x90000)
 
+#define AUDIO_PHYS_BASE                (KIRKWOOD_REGS_PHYS_BASE | 0xA0000)
+#define AUDIO_VIRT_BASE                (KIRKWOOD_REGS_VIRT_BASE | 0xA0000)
+
 /*
  * Supported devices and revisions.
  */
index ad3f1ec3379689556296ef1ccf3b425c07f8007a..e36067ad3acaa4a223a61531d5dd22848ea977c8 100644 (file)
@@ -15,6 +15,7 @@
 #include <linux/mtd/partitions.h>
 #include <linux/ata_platform.h>
 #include <linux/mv643xx_eth.h>
+#include <linux/i2c.h>
 #include <asm/mach-types.h>
 #include <asm/mach/arch.h>
 #include <mach/kirkwood.h>
@@ -60,6 +61,12 @@ static unsigned int openrd_mpp_config[] __initdata = {
        0
 };
 
+static struct i2c_board_info i2c_board_info[] __initdata = {
+       {
+               I2C_BOARD_INFO("cs42l51", 0x4a),
+       },
+};
+
 static void __init openrd_init(void)
 {
        /*
@@ -80,6 +87,12 @@ static void __init openrd_init(void)
        kirkwood_sdio_init(&openrd_mvsdio_data);
 
        kirkwood_i2c_init();
+
+       if (machine_is_openrd_client()) {
+               i2c_register_board_info(0, i2c_board_info,
+                       ARRAY_SIZE(i2c_board_info));
+               kirkwood_audio_init();
+       }
 }
 
 static int __init openrd_pci_init(void)
index c29337074ad37be754264c44add9ff84ee93b110..8fb5e5345557c0aac0d388ed47c4f3a5d2ae71ec 100644 (file)
@@ -133,7 +133,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,
+               .buffer_size    = 0x80, /* The FIFO has 128 locations */
        },
        {
                .phys_base      = OMAP34XX_MCBSP2_BASE,
@@ -143,7 +143,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,
+               .buffer_size    = 0x500, /* The FIFO has 1024 + 256 locations */
        },
        {
                .phys_base      = OMAP34XX_MCBSP3_BASE,
@@ -153,7 +153,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,
+               .buffer_size    = 0x80, /* The FIFO has 128 locations */
        },
        {
                .phys_base      = OMAP34XX_MCBSP4_BASE,
@@ -162,7 +162,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,
+               .buffer_size    = 0x80, /* The FIFO has 128 locations */
        },
        {
                .phys_base      = OMAP34XX_MCBSP5_BASE,
@@ -171,7 +171,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,
+               .buffer_size    = 0x80, /* The FIFO has 128 locations */
        },
 };
 #define OMAP34XX_MCBSP_PDATA_SZ                ARRAY_SIZE(omap34xx_mcbsp_pdata)
index c34ded523f100e88ddd27d359ed1a6d597f4ac02..63f3c28042396afd1d2333f7bf59e1a78bcf6c54 100644 (file)
@@ -10,6 +10,9 @@ struct imx_ssi_platform_data {
        unsigned int flags;
 #define IMX_SSI_DMA            (1 << 0)
 #define IMX_SSI_USE_AC97       (1 << 1)
+#define IMX_SSI_NET            (1 << 2)
+#define IMX_SSI_SYN            (1 << 3)
+#define IMX_SSI_USE_I2S_SLAVE  (1 << 4)
        void (*ac97_reset) (struct snd_ac97 *ac97);
        void (*ac97_warm_reset)(struct snd_ac97 *ac97);
 };
index 975744f10a5866ad6a53200b6402d27932d5528a..b4ff6a11a8f2862dbc2f0daf1c70167b50284145 100644 (file)
@@ -473,6 +473,7 @@ 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);
+u16 omap_mcbsp_get_fifo_size(unsigned int id);
 u16 omap_mcbsp_get_tx_delay(unsigned int id);
 u16 omap_mcbsp_get_rx_delay(unsigned int id);
 int omap_mcbsp_get_dma_op_mode(unsigned int id);
@@ -483,6 +484,7 @@ 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 u16 omap_mcbsp_get_fifo_size(unsigned int id) { return 0; }
 static inline u16 omap_mcbsp_get_tx_delay(unsigned int id) { return 0; }
 static inline u16 omap_mcbsp_get_rx_delay(unsigned int id) { return 0; }
 static inline int omap_mcbsp_get_dma_op_mode(unsigned int id) { return 0; }
index 7e669c9744d830c5c687671189346f272e8d260f..e31496e35b0f452d4ff9e375855718fc0a078d40 100644 (file)
@@ -481,9 +481,9 @@ int omap_st_is_enabled(unsigned int id)
 EXPORT_SYMBOL(omap_st_is_enabled);
 
 /*
- * omap_mcbsp_set_tx_threshold configures how to deal
- * with transmit threshold. the threshold value and handler can be
- * configure in here.
+ * omap_mcbsp_set_rx_threshold configures the transmit threshold in words.
+ * The threshold parameter is 1 based, and it is converted (threshold - 1)
+ * for the THRSH2 register.
  */
 void omap_mcbsp_set_tx_threshold(unsigned int id, u16 threshold)
 {
@@ -498,14 +498,15 @@ void omap_mcbsp_set_tx_threshold(unsigned int id, u16 threshold)
        }
        mcbsp = id_to_mcbsp_ptr(id);
 
-       MCBSP_WRITE(mcbsp, THRSH2, threshold);
+       if (threshold && threshold <= mcbsp->max_tx_thres)
+               MCBSP_WRITE(mcbsp, THRSH2, threshold - 1);
 }
 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.
+ * omap_mcbsp_set_rx_threshold configures the receive threshold in words.
+ * The threshold parameter is 1 based, and it is converted (threshold - 1)
+ * for the THRSH1 register.
  */
 void omap_mcbsp_set_rx_threshold(unsigned int id, u16 threshold)
 {
@@ -520,7 +521,8 @@ void omap_mcbsp_set_rx_threshold(unsigned int id, u16 threshold)
        }
        mcbsp = id_to_mcbsp_ptr(id);
 
-       MCBSP_WRITE(mcbsp, THRSH1, threshold);
+       if (threshold && threshold <= mcbsp->max_rx_thres)
+               MCBSP_WRITE(mcbsp, THRSH1, threshold - 1);
 }
 EXPORT_SYMBOL(omap_mcbsp_set_rx_threshold);
 
@@ -560,8 +562,20 @@ u16 omap_mcbsp_get_max_rx_threshold(unsigned int id)
 }
 EXPORT_SYMBOL(omap_mcbsp_get_max_rx_threshold);
 
-#define MCBSP2_FIFO_SIZE       0x500 /* 1024 + 256 locations */
-#define MCBSP1345_FIFO_SIZE    0x80  /* 128 locations */
+u16 omap_mcbsp_get_fifo_size(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->pdata->buffer_size;
+}
+EXPORT_SYMBOL(omap_mcbsp_get_fifo_size);
+
 /*
  * omap_mcbsp_get_tx_delay returns the number of used slots in the McBSP FIFO
  */
@@ -580,10 +594,7 @@ u16 omap_mcbsp_get_tx_delay(unsigned int id)
        buffstat = MCBSP_READ(mcbsp, XBUFFSTAT);
 
        /* Number of slots are different in McBSP ports */
-       if (mcbsp->id == 2)
-               return MCBSP2_FIFO_SIZE - buffstat;
-       else
-               return MCBSP1345_FIFO_SIZE - buffstat;
+       return mcbsp->pdata->buffer_size - buffstat;
 }
 EXPORT_SYMBOL(omap_mcbsp_get_tx_delay);
 
@@ -1683,8 +1694,16 @@ 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);
+               /*
+                * Initially configure the maximum thresholds to a safe value.
+                * The McBSP FIFO usage with these values should not go under
+                * 16 locations.
+                * If the whole FIFO without safety buffer is used, than there
+                * is a possibility that the DMA will be not able to push the
+                * new data on time, causing channel shifts in runtime.
+                */
+               mcbsp->max_tx_thres = max_thres(mcbsp) - 0x10;
+               mcbsp->max_rx_thres = max_thres(mcbsp) - 0x10;
                /*
                 * REVISIT: Set dmap_op_mode to THRESHOLD as default
                 * for mcbsp2 instances.
diff --git a/arch/arm/plat-orion/include/plat/audio.h b/arch/arm/plat-orion/include/plat/audio.h
new file mode 100644 (file)
index 0000000..9cf1f78
--- /dev/null
@@ -0,0 +1,11 @@
+#ifndef __PLAT_AUDIO_H
+#define __PLAT_AUDIO_H
+
+#include <linux/mbus.h>
+
+struct kirkwood_asoc_platform_data {
+       u32 tclk;
+       struct mbus_dram_target_info *dram;
+       int burst;
+};
+#endif
index c0227361a876fc2f919aa9bc03276e20898b7a52..9d51d6f358930c68d7c8264e4e0e047a1b15343b 100644 (file)
@@ -12,6 +12,9 @@
  * published by the Free Software Foundation.
  */
 
+#define FSI_PORT_A     0
+#define FSI_PORT_B     1
+
 /* flags format
 
  * 0xABCDEEFF
 #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_FMT_MONO                0
+#define SH_FSI_FMT_MONO_DELAY  1
+#define SH_FSI_FMT_PCM         2
+#define SH_FSI_FMT_I2S         3
+#define SH_FSI_FMT_TDM         4
+#define SH_FSI_FMT_TDM_DELAY   5
+#define SH_FSI_FMT_SPDIF       6
+
 
 #define SH_FSI_IFMT_TDM_CH(x) \
        (SH_FSI_IFMT(TDM)       | SH_FSI_SET_CH_I(x))
 #define SH_FSI_OFMT_TDM_DELAY_CH(x) \
        (SH_FSI_OFMT(TDM_DELAY) | SH_FSI_SET_CH_O(x))
 
+
+/*
+ * set_rate return value
+ *
+ * see ACKMD/BPFMD on
+ *     ACK_MD (FSI2)
+ *     CKG1   (FSI)
+ *
+ * err:  return value < 0
+ *
+ * 0x-00000AB
+ *
+ * A:  ACKMD value
+ * B:  BPFMD value
+ */
+
+#define SH_FSI_ACKMD_MASK      (0xF << 0)
+#define SH_FSI_ACKMD_512       (1 << 0)
+#define SH_FSI_ACKMD_256       (2 << 0)
+#define SH_FSI_ACKMD_128       (3 << 0)
+#define SH_FSI_ACKMD_64                (4 << 0)
+#define SH_FSI_ACKMD_32                (5 << 0)
+
+#define SH_FSI_BPFMD_MASK      (0xF << 4)
+#define SH_FSI_BPFMD_512       (1 << 4)
+#define SH_FSI_BPFMD_256       (2 << 4)
+#define SH_FSI_BPFMD_128       (3 << 4)
+#define SH_FSI_BPFMD_64                (4 << 4)
+#define SH_FSI_BPFMD_32                (5 << 4)
+#define SH_FSI_BPFMD_16                (6 << 4)
+
 struct sh_fsi_platform_info {
        unsigned long porta_flags;
        unsigned long portb_flags;
+       int (*set_rate)(int is_porta, int rate); /* for master mode */
 };
 
 extern struct snd_soc_dai fsi_soc_dai[2];
index 66ff4c124dbdd9c9e38ae103fc6f97343b1cee59..c5d9987bc897adc5ec87a06ec57cadf2fd627471 100644 (file)
 #define SND_SOC_DAPM_POST_PMD  0x8             /* after widget power down */
 #define SND_SOC_DAPM_PRE_REG   0x10    /* before audio path setup */
 #define SND_SOC_DAPM_POST_REG  0x20    /* after audio path setup */
+#define SND_SOC_DAPM_PRE_POST_PMD \
+                               (SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD)
 
 /* convenience event type detection */
 #define SND_SOC_DAPM_EVENT_ON(e)       \
index 697e7ffe39d73dadb229ad9b35973c23bd07f7c2..65e9d03ed4f5e0c34ca833d6e6570de05fc78125 100644 (file)
        .get = xhandler_get, .put = xhandler_put, \
        .private_value = (unsigned long)&xenum }
 
+#define SOC_DOUBLE_R_SX_TLV(xname, xreg_left, xreg_right, xshift,\
+               xmin, xmax, 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_sx, \
+       .get = snd_soc_get_volsw_2r_sx, \
+       .put = snd_soc_put_volsw_2r_sx, \
+       .private_value = (unsigned long)&(struct soc_mixer_control) \
+               {.reg = xreg_left, \
+                .rreg = xreg_right, .shift = xshift, \
+                .min = xmin, .max = xmax} }
+
+
 /*
  * Simplified versions of above macros, declaring a struct and calculating
  * ARRAY_SIZE internally
@@ -329,6 +344,12 @@ int snd_soc_put_volsw_s8(struct snd_kcontrol *kcontrol,
        struct snd_ctl_elem_value *ucontrol);
 int snd_soc_limit_volume(struct snd_soc_codec *codec,
        const char *name, int max);
+int snd_soc_info_volsw_2r_sx(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_info *uinfo);
+int snd_soc_get_volsw_2r_sx(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_value *ucontrol);
+int snd_soc_put_volsw_2r_sx(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_value *ucontrol);
 
 /**
  * struct snd_soc_jack_pin - Describes a pin to update based on jack detection
index 3f428d53195b123b144aaf8e8065296aa92c3588..6c664965679832b871443db2627fd33dd998c77a 100644 (file)
@@ -15,6 +15,8 @@
 
 struct tlv320dac33_platform_data {
        int power_gpio;
+       int mode1_latency; /* latency caused by the i2c writes in us */
+       int auto_fifo_config; /* FIFO config based on the period size */
        int keep_bclk;  /* Keep the BCLK running in FIFO modes */
        u8 burst_bclkdiv;
 };
index 509efb0501767749b74d01de3ee8ed8a6dc81c4d..e475659bd3befa35c377bbbe150f52f52d8d0e99 100644 (file)
@@ -18,6 +18,18 @@ struct uda134x_platform_data {
        struct l3_pins l3;
        void (*power) (int);
        int model;
+       /*
+         ALSA SOC usually puts the device in standby mode when it's not used
+         for sometime. If you unset is_powered_on_standby the driver will
+         turn off the ADC/DAC when this callback is invoked and turn it back
+         on when needed. Unfortunately this will result in a very light bump
+         (it can be audible only with good earphones). If this bothers you
+         set is_powered_on_standby, you will have slightly higher power
+         consumption. Please note that sending the L3 command for ADC is
+         enough to make the bump, so it doesn't make difference if you
+         completely take off power from the codec.
+       */
+       int is_powered_on_standby;
 #define UDA134X_UDA1340 1
 #define UDA134X_UDA1341 2
 #define UDA134X_UDA1344 3
index b1749bc679794ba7910ab0a55d880ee4d7db0e1a..3e598e756e54d1f19c038fa0d41d393fb6fd2eb4 100644 (file)
@@ -28,9 +28,13 @@ source "sound/soc/atmel/Kconfig"
 source "sound/soc/au1x/Kconfig"
 source "sound/soc/blackfin/Kconfig"
 source "sound/soc/davinci/Kconfig"
+source "sound/soc/ep93xx/Kconfig"
 source "sound/soc/fsl/Kconfig"
 source "sound/soc/imx/Kconfig"
+source "sound/soc/jz4740/Kconfig"
+source "sound/soc/nuc900/Kconfig"
 source "sound/soc/omap/Kconfig"
+source "sound/soc/kirkwood/Kconfig"
 source "sound/soc/pxa/Kconfig"
 source "sound/soc/s3c24xx/Kconfig"
 source "sound/soc/s6000/Kconfig"
index 1470141d416734841fb30b065d59aa8e607bf81d..eb183443eee4f355744f86181d9b1ef065b003d2 100644 (file)
@@ -6,9 +6,13 @@ obj-$(CONFIG_SND_SOC)  += atmel/
 obj-$(CONFIG_SND_SOC)  += au1x/
 obj-$(CONFIG_SND_SOC)  += blackfin/
 obj-$(CONFIG_SND_SOC)  += davinci/
+obj-$(CONFIG_SND_SOC)  += ep93xx/
 obj-$(CONFIG_SND_SOC)  += fsl/
 obj-$(CONFIG_SND_SOC)   += imx/
+obj-$(CONFIG_SND_SOC)  += jz4740/
+obj-$(CONFIG_SND_SOC)  += nuc900/
 obj-$(CONFIG_SND_SOC)  += omap/
+obj-$(CONFIG_SND_SOC)  += kirkwood/
 obj-$(CONFIG_SND_SOC)  += pxa/
 obj-$(CONFIG_SND_SOC)  += s3c24xx/
 obj-$(CONFIG_SND_SOC)  += s6000/
index f6b3cc04b34b3dff3b8b07a9403672594a091177..dc5249fba85c71884c613c5b028a9fd173143bf5 100644 (file)
@@ -77,7 +77,6 @@ struct atmel_runtime_data {
        size_t period_size;
 
        dma_addr_t period_ptr;          /* physical address of next period */
-       int periods;                    /* period index of period_ptr */
 
        /* PDC register save */
        u32 pdc_xpr_save;
index 0b59806905d1d815a62acfbf395abbf0bebdec3f..c85844d4845bdc15f24c94c8d4a82dbedcfcc1ec 100644 (file)
@@ -549,7 +549,6 @@ static int atmel_ssc_hw_params(struct snd_pcm_substream *substream,
                printk(KERN_WARNING "atmel_ssc_dai: unsupported DAI format 0x%x\n",
                        ssc_p->daifmt);
                return -EINVAL;
-               break;
        }
        pr_debug("atmel_ssc_hw_params: "
                        "RCMR=%08x RFMR=%08x TCMR=%08x TFMR=%08x\n",
index a61ccd2d505f010fd9720a29c8ce318e7d27819e..d14a5a91a4654dc347af790ace1244479659fb36 100644 (file)
@@ -375,12 +375,10 @@ static int __devinit au1xpsc_ac97_drvprobe(struct platform_device *pdev)
        }
 
        ret = -EBUSY;
-       wd->ioarea = request_mem_region(r->start, r->end - r->start + 1,
-                                       "au1xpsc_ac97");
-       if (!wd->ioarea)
+       if (!request_mem_region(r->start, resource_size(r), pdev->name))
                goto out0;
 
-       wd->mmio = ioremap(r->start, 0xffff);
+       wd->mmio = ioremap(r->start, resource_size(r));
        if (!wd->mmio)
                goto out1;
 
@@ -410,8 +408,7 @@ static int __devinit au1xpsc_ac97_drvprobe(struct platform_device *pdev)
 
        snd_soc_unregister_dai(&au1xpsc_ac97_dai);
 out1:
-       release_resource(wd->ioarea);
-       kfree(wd->ioarea);
+       release_mem_region(r->start, resource_size(r));
 out0:
        kfree(wd);
        return ret;
@@ -420,6 +417,7 @@ out0:
 static int __devexit au1xpsc_ac97_drvremove(struct platform_device *pdev)
 {
        struct au1xpsc_audio_data *wd = platform_get_drvdata(pdev);
+       struct resource *r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 
        if (wd->dmapd)
                au1xpsc_pcm_destroy(wd->dmapd);
@@ -433,8 +431,7 @@ static int __devexit au1xpsc_ac97_drvremove(struct platform_device *pdev)
        au_sync();
 
        iounmap(wd->mmio);
-       release_resource(wd->ioarea);
-       kfree(wd->ioarea);
+       release_mem_region(r->start, resource_size(r));
        kfree(wd);
 
        au1xpsc_ac97_workdata = NULL;   /* MDEV */
index 24454c98d0ee9f62faa5fd56b37bd64e04d598fd..6083fe7799faba3b6b4737136b12d74cc428f59b 100644 (file)
@@ -321,12 +321,10 @@ static int __devinit au1xpsc_i2s_drvprobe(struct platform_device *pdev)
        }
 
        ret = -EBUSY;
-       wd->ioarea = request_mem_region(r->start, r->end - r->start + 1,
-                                       "au1xpsc_i2s");
-       if (!wd->ioarea)
+       if (!request_mem_region(r->start, resource_size(r), pdev->name))
                goto out0;
 
-       wd->mmio = ioremap(r->start, 0xffff);
+       wd->mmio = ioremap(r->start, resource_size(r));
        if (!wd->mmio)
                goto out1;
 
@@ -362,8 +360,7 @@ static int __devinit au1xpsc_i2s_drvprobe(struct platform_device *pdev)
 
        snd_soc_unregister_dai(&au1xpsc_i2s_dai);
 out1:
-       release_resource(wd->ioarea);
-       kfree(wd->ioarea);
+       release_mem_region(r->start, resource_size(r));
 out0:
        kfree(wd);
        return ret;
@@ -372,6 +369,7 @@ out0:
 static int __devexit au1xpsc_i2s_drvremove(struct platform_device *pdev)
 {
        struct au1xpsc_audio_data *wd = platform_get_drvdata(pdev);
+       struct resource *r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 
        if (wd->dmapd)
                au1xpsc_pcm_destroy(wd->dmapd);
@@ -384,8 +382,7 @@ static int __devexit au1xpsc_i2s_drvremove(struct platform_device *pdev)
        au_sync();
 
        iounmap(wd->mmio);
-       release_resource(wd->ioarea);
-       kfree(wd->ioarea);
+       release_mem_region(r->start, resource_size(r));
        kfree(wd);
 
        au1xpsc_i2s_workdata = NULL;    /* MDEV */
index 32d3807d3f5a0d523cdf30a0fde24208f7112db4..093775d4dc3e47185759c737e1bb18338a615b7c 100644 (file)
@@ -32,7 +32,6 @@ struct au1xpsc_audio_data {
        unsigned long rate;
 
        unsigned long pm[2];
-       struct resource *ioarea;
        struct mutex lock;
        struct platform_device *dmapd;
 };
index 523b7fc33f4e7f94b5d1f6f6ffd89f9e904479c5..c0eba51099804fcebc40ee13d5b495b3106448f2 100644 (file)
@@ -255,8 +255,7 @@ EXPORT_SYMBOL_GPL(soc_ac97_ops);
 #ifdef CONFIG_PM
 static int bf5xx_ac97_suspend(struct snd_soc_dai *dai)
 {
-       struct sport_device *sport =
-               (struct sport_device *)dai->private_data;
+       struct sport_device *sport = dai->private_data;
 
        pr_debug("%s : sport %d\n", __func__, dai->id);
        if (!dai->active)
@@ -271,8 +270,7 @@ static int bf5xx_ac97_suspend(struct snd_soc_dai *dai)
 static int bf5xx_ac97_resume(struct snd_soc_dai *dai)
 {
        int ret;
-       struct sport_device *sport =
-               (struct sport_device *)dai->private_data;
+       struct sport_device *sport = dai->private_data;
 
        pr_debug("%s : sport %d\n", __func__, dai->id);
        if (!dai->active)
index 4b360124083e7962482e045a57963776ca3c7d22..24c14269f4bc498601fb920e130b1ff44c5d4028 100644 (file)
@@ -210,8 +210,7 @@ static int bf5xx_tdm_set_channel_map(struct snd_soc_dai *dai,
 #ifdef CONFIG_PM
 static int bf5xx_tdm_suspend(struct snd_soc_dai *dai)
 {
-       struct sport_device *sport =
-               (struct sport_device *)dai->private_data;
+       struct sport_device *sport = dai->private_data;
 
        if (!dai->active)
                return 0;
@@ -225,8 +224,7 @@ static int bf5xx_tdm_suspend(struct snd_soc_dai *dai)
 static int bf5xx_tdm_resume(struct snd_soc_dai *dai)
 {
        int ret;
-       struct sport_device *sport =
-               (struct sport_device *)dai->private_data;
+       struct sport_device *sport = dai->private_data;
 
        if (!dai->active)
                return 0;
index 5da30eb6ad008021619ca57924462fadeaa1e800..83f5c67d3c41630794ee8d2aa3fc54409e240a70 100644 (file)
@@ -22,9 +22,11 @@ config SND_SOC_ALL_CODECS
        select SND_SOC_AK4642 if I2C
        select SND_SOC_AK4671 if I2C
        select SND_SOC_CQ0093VC if MFD_DAVINCI_VOICECODEC
+       select SND_SOC_CS42L51 if I2C
        select SND_SOC_CS4270 if I2C
-       select SND_SOC_MAX9877 if I2C
        select SND_SOC_DA7210 if I2C
+       select SND_SOC_JZ4740 if SOC_JZ4740
+       select SND_SOC_MAX9877 if I2C
        select SND_SOC_PCM3008
        select SND_SOC_SPDIF
        select SND_SOC_SSM2602 if I2C
@@ -48,6 +50,7 @@ config SND_SOC_ALL_CODECS
        select SND_SOC_WM8727
        select SND_SOC_WM8728 if SND_SOC_I2C_AND_SPI
        select SND_SOC_WM8731 if SND_SOC_I2C_AND_SPI
+       select SND_SOC_WM8741 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
@@ -120,13 +123,13 @@ config SND_SOC_AK4671
 config SND_SOC_CQ0093VC
        tristate
 
+config SND_SOC_CS42L51
+       tristate
+
 # Cirrus Logic CS4270 Codec
 config SND_SOC_CS4270
        tristate
 
-config SND_SOC_DA7210
-        tristate
-
 # Cirrus Logic CS4270 Codec VD = 3.3V Errata
 # Select if you are affected by the errata where the part will not function
 # if MCLK divide-by-1.5 is selected and VD is set to 3.3V.  The driver will
@@ -138,9 +141,15 @@ config SND_SOC_CS4270_VD33_ERRATA
 config SND_SOC_CX20442
        tristate
 
+config SND_SOC_JZ4740_CODEC
+       tristate
+
 config SND_SOC_L3
        tristate
 
+config SND_SOC_DA7210
+        tristate
+
 config SND_SOC_PCM3008
        tristate
 
@@ -206,6 +215,9 @@ config SND_SOC_WM8728
 config SND_SOC_WM8731
        tristate
 
+config SND_SOC_WM8741
+       tristate
+
 config SND_SOC_WM8750
        tristate
 
index 91429eab0707d57175143748028e502ce9ebf090..53524095759c62ed322a033d818c78543182749a 100644 (file)
@@ -9,6 +9,7 @@ snd-soc-ak4535-objs := ak4535.o
 snd-soc-ak4642-objs := ak4642.o
 snd-soc-ak4671-objs := ak4671.o
 snd-soc-cq93vc-objs := cq93vc.o
+snd-soc-cs42l51-objs := cs42l51.o
 snd-soc-cs4270-objs := cs4270.o
 snd-soc-cx20442-objs := cx20442.o
 snd-soc-da7210-objs := da7210.o
@@ -34,6 +35,7 @@ snd-soc-wm8711-objs := wm8711.o
 snd-soc-wm8727-objs := wm8727.o
 snd-soc-wm8728-objs := wm8728.o
 snd-soc-wm8731-objs := wm8731.o
+snd-soc-wm8741-objs := wm8741.o
 snd-soc-wm8750-objs := wm8750.o
 snd-soc-wm8753-objs := wm8753.o
 snd-soc-wm8776-objs := wm8776.o
@@ -56,6 +58,7 @@ 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
+snd-soc-jz4740-codec-objs := jz4740.o
 
 # Amp
 snd-soc-max9877-objs := max9877.o
@@ -74,10 +77,12 @@ obj-$(CONFIG_SND_SOC_AK4535)        += snd-soc-ak4535.o
 obj-$(CONFIG_SND_SOC_AK4642)   += snd-soc-ak4642.o
 obj-$(CONFIG_SND_SOC_AK4671)   += snd-soc-ak4671.o
 obj-$(CONFIG_SND_SOC_CQ0093VC) += snd-soc-cq93vc.o
+obj-$(CONFIG_SND_SOC_CS42L51)  += snd-soc-cs42l51.o
 obj-$(CONFIG_SND_SOC_CS4270)   += snd-soc-cs4270.o
 obj-$(CONFIG_SND_SOC_CX20442)  += snd-soc-cx20442.o
 obj-$(CONFIG_SND_SOC_DA7210)   += snd-soc-da7210.o
 obj-$(CONFIG_SND_SOC_L3)       += snd-soc-l3.o
+obj-$(CONFIG_SND_SOC_JZ4740_CODEC)     += snd-soc-jz4740-codec.o
 obj-$(CONFIG_SND_SOC_PCM3008)  += snd-soc-pcm3008.o
 obj-$(CONFIG_SND_SOC_SPDIF)    += snd-soc-spdif.o
 obj-$(CONFIG_SND_SOC_SSM2602)  += snd-soc-ssm2602.o
@@ -99,6 +104,7 @@ obj-$(CONFIG_SND_SOC_WM8711) += snd-soc-wm8711.o
 obj-$(CONFIG_SND_SOC_WM8727)   += snd-soc-wm8727.o
 obj-$(CONFIG_SND_SOC_WM8728)   += snd-soc-wm8728.o
 obj-$(CONFIG_SND_SOC_WM8731)   += snd-soc-wm8731.o
+obj-$(CONFIG_SND_SOC_WM8741)   += snd-soc-wm8741.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
index 217538423225950be0c338a57e3decf0919bb6b1..a01006c8c606ffd613b250bf7f95fbc95ebaa05c 100644 (file)
@@ -272,6 +272,7 @@ static int ad1836_register(struct ad1836_priv *ad1836)
 
        if (ad1836_codec) {
                dev_err(codec->dev, "Another ad1836 is registered\n");
+               kfree(ad1836);
                return -EINVAL;
        }
 
index c8ca1142b2f4c9a351ce1344149f8b8a749e44bc..1def75e4862f60fe2a143470cdb2ae458453e08a 100644 (file)
@@ -24,6 +24,7 @@
 
 /* codec private data */
 struct ad193x_priv {
+       unsigned int sysclk;
        struct snd_soc_codec codec;
        u8 reg_cache[AD193X_NUM_REGS];
 };
@@ -251,15 +252,32 @@ static int ad193x_set_dai_fmt(struct snd_soc_dai *codec_dai,
        return 0;
 }
 
+static int ad193x_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 ad193x_priv *ad193x = snd_soc_codec_get_drvdata(codec);
+       switch (freq) {
+       case 12288000:
+       case 18432000:
+       case 24576000:
+       case 36864000:
+               ad193x->sysclk = freq;
+               return 0;
+       }
+       return -EINVAL;
+}
+
 static int ad193x_hw_params(struct snd_pcm_substream *substream,
                struct snd_pcm_hw_params *params,
                struct snd_soc_dai *dai)
 {
-       int word_len = 0, reg = 0;
+       int word_len = 0, reg = 0, master_rate = 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;
+       struct ad193x_priv *ad193x = snd_soc_codec_get_drvdata(codec);
 
        /* bit size */
        switch (params_format(params)) {
@@ -275,6 +293,25 @@ static int ad193x_hw_params(struct snd_pcm_substream *substream,
                break;
        }
 
+       switch (ad193x->sysclk) {
+       case 12288000:
+               master_rate = AD193X_PLL_INPUT_256;
+               break;
+       case 18432000:
+               master_rate = AD193X_PLL_INPUT_384;
+               break;
+       case 24576000:
+               master_rate = AD193X_PLL_INPUT_512;
+               break;
+       case 36864000:
+               master_rate = AD193X_PLL_INPUT_768;
+               break;
+       }
+
+       reg = snd_soc_read(codec, AD193X_PLL_CLK_CTRL0);
+       reg = (reg & AD193X_PLL_INPUT_MASK) | master_rate;
+       snd_soc_write(codec, AD193X_PLL_CLK_CTRL0, reg);
+
        reg = snd_soc_read(codec, AD193X_DAC_CTRL2);
        reg = (reg & (~AD193X_DAC_WORD_LEN_MASK)) | word_len;
        snd_soc_write(codec, AD193X_DAC_CTRL2, reg);
@@ -348,6 +385,7 @@ static int ad193x_bus_probe(struct device *dev, void *ctrl_data, int bus_type)
        /* pll input: mclki/xi */
        snd_soc_write(codec, AD193X_PLL_CLK_CTRL0, 0x99); /* mclk=24.576Mhz: 0x9D; mclk=12.288Mhz: 0x99 */
        snd_soc_write(codec, AD193X_PLL_CLK_CTRL1, 0x04);
+       ad193x->sysclk = 12288000;
 
        ret = snd_soc_register_codec(codec);
        if (ret != 0) {
@@ -383,6 +421,7 @@ static struct snd_soc_dai_ops ad193x_dai_ops = {
        .hw_params = ad193x_hw_params,
        .digital_mute = ad193x_mute,
        .set_tdm_slot = ad193x_set_tdm_slot,
+       .set_sysclk     = ad193x_set_dai_sysclk,
        .set_fmt = ad193x_set_dai_fmt,
 };
 
index a03c880d52f997737fd1359b6058c3e6dc64b420..654ba64ae04c6d4b0e8ebc89a0138916c37eed1c 100644 (file)
 
 #define AD193X_PLL_CLK_CTRL0    0x800
 #define AD193X_PLL_POWERDOWN           0x01
+#define AD193X_PLL_INPUT_MASK   (~0x6)
+#define AD193X_PLL_INPUT_256    (0 << 1)
+#define AD193X_PLL_INPUT_384    (1 << 1)
+#define AD193X_PLL_INPUT_512    (2 << 1)
+#define AD193X_PLL_INPUT_768    (3 << 1)
 #define AD193X_PLL_CLK_CTRL1    0x801
 #define AD193X_DAC_CTRL0        0x802
 #define AD193X_DAC_POWERDOWN           0x01
index 7528a54102b5b2cdc83b554ae8d8cf85d9f30888..3d7dc55305ec3531838627e7a33f8b22009cfb2f 100644 (file)
  * 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 <linux/slab.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 "ak4642.h"
 
 
 struct snd_soc_codec_device soc_codec_dev_ak4642;
 
+/*
+ * Playback Volume (table 39)
+ *
+ * max : 0x00 : +12.0 dB
+ *       ( 0.5 dB step )
+ * min : 0xFE : -115.0 dB
+ * mute: 0xFF
+ */
+static const DECLARE_TLV_DB_SCALE(out_tlv, -11500, 50, 1);
+
+static const struct snd_kcontrol_new ak4642_snd_controls[] = {
+
+       SOC_DOUBLE_R_TLV("Digital Playback Volume", L_DVC, R_DVC,
+                        0, 0xFF, 1, out_tlv),
+};
+
+
 /* codec private data */
 struct ak4642_priv {
        struct snd_soc_codec codec;
@@ -204,7 +214,6 @@ static int ak4642_dai_startup(struct snd_pcm_substream *substream,
                 *
                 * PLL, Master Mode
                 * Audio I/F Format :MSB justified (ADC & DAC)
-                * Digital Volume: -8dB
                 * Bass Boost Level : Middle
                 *
                 * This operation came from example code of
@@ -214,8 +223,6 @@ static int ak4642_dai_startup(struct snd_pcm_substream *substream,
                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);
                snd_soc_update_bits(codec, PW_MGMT2, PMHP_MASK, PMHP);
                snd_soc_update_bits(codec, PW_MGMT2, HPMTN,     HPMTN);
@@ -491,8 +498,10 @@ static int ak4642_i2c_probe(struct i2c_client *i2c,
        codec->control_data = i2c;
 
        ret = ak4642_init(ak4642);
-       if (ret < 0)
+       if (ret < 0) {
                printk(KERN_ERR "failed to initialise AK4642\n");
+               kfree(ak4642);
+       }
 
        return ret;
 }
@@ -548,6 +557,9 @@ static int ak4642_probe(struct platform_device *pdev)
                goto pcm_err;
        }
 
+       snd_soc_add_controls(ak4642_codec, ak4642_snd_controls,
+                            ARRAY_SIZE(ak4642_snd_controls));
+
        dev_info(&pdev->dev, "AK4642 Audio Codec %s", AK4642_VERSION);
        return ret;
 
diff --git a/sound/soc/codecs/cs42l51.c b/sound/soc/codecs/cs42l51.c
new file mode 100644 (file)
index 0000000..dd9b855
--- /dev/null
@@ -0,0 +1,763 @@
+/*
+ * cs42l51.c
+ *
+ * ASoC Driver for Cirrus Logic CS42L51 codecs
+ *
+ * Copyright (c) 2010 Arnaud Patard <apatard@mandriva.com>
+ *
+ * Based on cs4270.c - Copyright (c) Freescale Semiconductor
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * For now:
+ *  - Only I2C is support. Not SPI
+ *  - master mode *NOT* supported
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/tlv.h>
+#include <sound/initval.h>
+#include <sound/pcm_params.h>
+#include <sound/pcm.h>
+#include <linux/i2c.h>
+
+#include "cs42l51.h"
+
+enum master_slave_mode {
+       MODE_SLAVE,
+       MODE_SLAVE_AUTO,
+       MODE_MASTER,
+};
+
+struct cs42l51_private {
+       unsigned int mclk;
+       unsigned int audio_mode;        /* The mode (I2S or left-justified) */
+       enum master_slave_mode func;
+       struct snd_soc_codec codec;
+       u8 reg_cache[CS42L51_NUMREGS];
+};
+
+static struct snd_soc_codec *cs42l51_codec;
+
+#define CS42L51_FORMATS ( \
+               SNDRV_PCM_FMTBIT_S16_LE  | SNDRV_PCM_FMTBIT_S16_BE  | \
+               SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_S18_3BE | \
+               SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S20_3BE | \
+               SNDRV_PCM_FMTBIT_S24_LE  | SNDRV_PCM_FMTBIT_S24_BE)
+
+static int cs42l51_fill_cache(struct snd_soc_codec *codec)
+{
+       u8 *cache = codec->reg_cache + 1;
+       struct i2c_client *i2c_client = codec->control_data;
+       s32 length;
+
+       length = i2c_smbus_read_i2c_block_data(i2c_client,
+                       CS42L51_FIRSTREG | 0x80, CS42L51_NUMREGS, cache);
+       if (length != CS42L51_NUMREGS) {
+               dev_err(&i2c_client->dev,
+                               "I2C read failure, addr=0x%x (ret=%d vs %d)\n",
+                               i2c_client->addr, length, CS42L51_NUMREGS);
+               return -EIO;
+       }
+
+       return 0;
+}
+
+static int cs42l51_i2c_probe(struct i2c_client *i2c_client,
+       const struct i2c_device_id *id)
+{
+       struct snd_soc_codec *codec;
+       struct cs42l51_private *cs42l51;
+       int ret = 0;
+       int reg;
+
+       if (cs42l51_codec)
+               return -EBUSY;
+
+       /* Verify that we have a CS42L51 */
+       ret = i2c_smbus_read_byte_data(i2c_client, CS42L51_CHIP_REV_ID);
+       if (ret < 0) {
+               dev_err(&i2c_client->dev, "failed to read I2C\n");
+               goto error;
+       }
+
+       if ((ret != CS42L51_MK_CHIP_REV(CS42L51_CHIP_ID, CS42L51_CHIP_REV_A)) &&
+           (ret != CS42L51_MK_CHIP_REV(CS42L51_CHIP_ID, CS42L51_CHIP_REV_B))) {
+               dev_err(&i2c_client->dev, "Invalid chip id\n");
+               ret = -ENODEV;
+               goto error;
+       }
+
+       dev_info(&i2c_client->dev, "found device cs42l51 rev %d\n",
+                               ret & 7);
+
+       cs42l51 = kzalloc(sizeof(struct cs42l51_private), GFP_KERNEL);
+       if (!cs42l51) {
+               dev_err(&i2c_client->dev, "could not allocate codec\n");
+               return -ENOMEM;
+       }
+       codec = &cs42l51->codec;
+
+       mutex_init(&codec->mutex);
+       INIT_LIST_HEAD(&codec->dapm_widgets);
+       INIT_LIST_HEAD(&codec->dapm_paths);
+
+       codec->dev = &i2c_client->dev;
+       codec->name = "CS42L51";
+       codec->owner = THIS_MODULE;
+       codec->dai = &cs42l51_dai;
+       codec->num_dai = 1;
+       snd_soc_codec_set_drvdata(codec, cs42l51);
+
+       codec->control_data = i2c_client;
+       codec->reg_cache = cs42l51->reg_cache;
+       codec->reg_cache_size = CS42L51_NUMREGS;
+       i2c_set_clientdata(i2c_client, codec);
+
+       ret = cs42l51_fill_cache(codec);
+       if (ret < 0) {
+               dev_err(&i2c_client->dev, "failed to fill register cache\n");
+               goto error_alloc;
+       }
+
+       ret = snd_soc_codec_set_cache_io(codec, 8, 8, SND_SOC_I2C);
+       if (ret < 0) {
+               dev_err(&i2c_client->dev, "Failed to set cache I/O: %d\n", ret);
+               goto error_alloc;
+       }
+
+       /*
+        * DAC configuration
+        * - Use signal processor
+        * - auto mute
+        * - vol changes immediate
+        * - no de-emphasize
+        */
+       reg = CS42L51_DAC_CTL_DATA_SEL(1)
+               | CS42L51_DAC_CTL_AMUTE | CS42L51_DAC_CTL_DACSZ(0);
+       ret = snd_soc_write(codec, CS42L51_DAC_CTL, reg);
+       if (ret < 0)
+               goto error_alloc;
+
+       cs42l51_dai.dev = codec->dev;
+       cs42l51_codec = codec;
+
+       ret = snd_soc_register_codec(codec);
+       if (ret != 0) {
+               dev_err(codec->dev, "Failed to register codec: %d\n", ret);
+               goto error_alloc;
+       }
+
+       ret = snd_soc_register_dai(&cs42l51_dai);
+       if (ret < 0) {
+               dev_err(&i2c_client->dev, "failed to register DAIe\n");
+               goto error_reg;
+       }
+
+       return 0;
+
+error_reg:
+       snd_soc_unregister_codec(codec);
+error_alloc:
+       kfree(cs42l51);
+error:
+       return ret;
+}
+
+static int cs42l51_i2c_remove(struct i2c_client *client)
+{
+       struct cs42l51_private *cs42l51 = i2c_get_clientdata(client);
+       snd_soc_unregister_dai(&cs42l51_dai);
+       snd_soc_unregister_codec(cs42l51_codec);
+       cs42l51_codec = NULL;
+       kfree(cs42l51);
+       return 0;
+}
+
+
+static const struct i2c_device_id cs42l51_id[] = {
+       {"cs42l51", 0},
+       {}
+};
+MODULE_DEVICE_TABLE(i2c, cs42l51_id);
+
+static struct i2c_driver cs42l51_i2c_driver = {
+       .driver = {
+               .name = "CS42L51 I2C",
+               .owner = THIS_MODULE,
+       },
+       .id_table = cs42l51_id,
+       .probe = cs42l51_i2c_probe,
+       .remove = cs42l51_i2c_remove,
+};
+
+static int cs42l51_get_chan_mix(struct snd_kcontrol *kcontrol,
+                       struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       unsigned long value = snd_soc_read(codec, CS42L51_PCM_MIXER)&3;
+
+       switch (value) {
+       default:
+       case 0:
+               ucontrol->value.integer.value[0] = 0;
+               break;
+       /* same value : (L+R)/2 and (R+L)/2 */
+       case 1:
+       case 2:
+               ucontrol->value.integer.value[0] = 1;
+               break;
+       case 3:
+               ucontrol->value.integer.value[0] = 2;
+               break;
+       }
+
+       return 0;
+}
+
+#define CHAN_MIX_NORMAL        0x00
+#define CHAN_MIX_BOTH  0x55
+#define CHAN_MIX_SWAP  0xFF
+
+static int cs42l51_set_chan_mix(struct snd_kcontrol *kcontrol,
+                       struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       unsigned char val;
+
+       switch (ucontrol->value.integer.value[0]) {
+       default:
+       case 0:
+               val = CHAN_MIX_NORMAL;
+               break;
+       case 1:
+               val = CHAN_MIX_BOTH;
+               break;
+       case 2:
+               val = CHAN_MIX_SWAP;
+               break;
+       }
+
+       snd_soc_write(codec, CS42L51_PCM_MIXER, val);
+
+       return 1;
+}
+
+static const DECLARE_TLV_DB_SCALE(adc_pcm_tlv, -5150, 50, 0);
+static const DECLARE_TLV_DB_SCALE(tone_tlv, -1050, 150, 0);
+/* This is a lie. after -102 db, it stays at -102 */
+/* maybe a range would be better */
+static const DECLARE_TLV_DB_SCALE(aout_tlv, -11550, 50, 0);
+
+static const DECLARE_TLV_DB_SCALE(boost_tlv, 1600, 1600, 0);
+static const char *chan_mix[] = {
+       "L R",
+       "L+R",
+       "R L",
+};
+
+static const struct soc_enum cs42l51_chan_mix =
+       SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(chan_mix), chan_mix);
+
+static const struct snd_kcontrol_new cs42l51_snd_controls[] = {
+       SOC_DOUBLE_R_SX_TLV("PCM Playback Volume",
+                       CS42L51_PCMA_VOL, CS42L51_PCMB_VOL,
+                       7, 0xffffff99, 0x18, adc_pcm_tlv),
+       SOC_DOUBLE_R("PCM Playback Switch",
+                       CS42L51_PCMA_VOL, CS42L51_PCMB_VOL, 7, 1, 1),
+       SOC_DOUBLE_R_SX_TLV("Analog Playback Volume",
+                       CS42L51_AOUTA_VOL, CS42L51_AOUTB_VOL,
+                       8, 0xffffff19, 0x18, aout_tlv),
+       SOC_DOUBLE_R_SX_TLV("ADC Mixer Volume",
+                       CS42L51_ADCA_VOL, CS42L51_ADCB_VOL,
+                       7, 0xffffff99, 0x18, adc_pcm_tlv),
+       SOC_DOUBLE_R("ADC Mixer Switch",
+                       CS42L51_ADCA_VOL, CS42L51_ADCB_VOL, 7, 1, 1),
+       SOC_SINGLE("Playback Deemphasis Switch", CS42L51_DAC_CTL, 3, 1, 0),
+       SOC_SINGLE("Auto-Mute Switch", CS42L51_DAC_CTL, 2, 1, 0),
+       SOC_SINGLE("Soft Ramp Switch", CS42L51_DAC_CTL, 1, 1, 0),
+       SOC_SINGLE("Zero Cross Switch", CS42L51_DAC_CTL, 0, 0, 0),
+       SOC_DOUBLE_TLV("Mic Boost Volume",
+                       CS42L51_MIC_CTL, 0, 1, 1, 0, boost_tlv),
+       SOC_SINGLE_TLV("Bass Volume", CS42L51_TONE_CTL, 0, 0xf, 1, tone_tlv),
+       SOC_SINGLE_TLV("Treble Volume", CS42L51_TONE_CTL, 4, 0xf, 1, tone_tlv),
+       SOC_ENUM_EXT("PCM channel mixer",
+                       cs42l51_chan_mix,
+                       cs42l51_get_chan_mix, cs42l51_set_chan_mix),
+};
+
+/*
+ * to power down, one must:
+ * 1.) Enable the PDN bit
+ * 2.) enable power-down for the select channels
+ * 3.) disable the PDN bit.
+ */
+static int cs42l51_pdn_event(struct snd_soc_dapm_widget *w,
+               struct snd_kcontrol *kcontrol, int event)
+{
+       unsigned long value;
+
+       value = snd_soc_read(w->codec, CS42L51_POWER_CTL1);
+       value &= ~CS42L51_POWER_CTL1_PDN;
+
+       switch (event) {
+       case SND_SOC_DAPM_PRE_PMD:
+               value |= CS42L51_POWER_CTL1_PDN;
+               break;
+       default:
+       case SND_SOC_DAPM_POST_PMD:
+               break;
+       }
+       snd_soc_update_bits(w->codec, CS42L51_POWER_CTL1,
+               CS42L51_POWER_CTL1_PDN, value);
+
+       return 0;
+}
+
+static const char *cs42l51_dac_names[] = {"Direct PCM",
+       "DSP PCM", "ADC"};
+static const struct soc_enum cs42l51_dac_mux_enum =
+       SOC_ENUM_SINGLE(CS42L51_DAC_CTL, 6, 3, cs42l51_dac_names);
+static const struct snd_kcontrol_new cs42l51_dac_mux_controls =
+       SOC_DAPM_ENUM("Route", cs42l51_dac_mux_enum);
+
+static const char *cs42l51_adcl_names[] = {"AIN1 Left", "AIN2 Left",
+       "MIC Left", "MIC+preamp Left"};
+static const struct soc_enum cs42l51_adcl_mux_enum =
+       SOC_ENUM_SINGLE(CS42L51_ADC_INPUT, 4, 4, cs42l51_adcl_names);
+static const struct snd_kcontrol_new cs42l51_adcl_mux_controls =
+       SOC_DAPM_ENUM("Route", cs42l51_adcl_mux_enum);
+
+static const char *cs42l51_adcr_names[] = {"AIN1 Right", "AIN2 Right",
+       "MIC Right", "MIC+preamp Right"};
+static const struct soc_enum cs42l51_adcr_mux_enum =
+       SOC_ENUM_SINGLE(CS42L51_ADC_INPUT, 6, 4, cs42l51_adcr_names);
+static const struct snd_kcontrol_new cs42l51_adcr_mux_controls =
+       SOC_DAPM_ENUM("Route", cs42l51_adcr_mux_enum);
+
+static const struct snd_soc_dapm_widget cs42l51_dapm_widgets[] = {
+       SND_SOC_DAPM_MICBIAS("Mic Bias", CS42L51_MIC_POWER_CTL, 1, 1),
+       SND_SOC_DAPM_PGA_E("Left PGA", CS42L51_POWER_CTL1, 3, 1, NULL, 0,
+               cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD),
+       SND_SOC_DAPM_PGA_E("Right PGA", CS42L51_POWER_CTL1, 4, 1, NULL, 0,
+               cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD),
+       SND_SOC_DAPM_ADC_E("Left ADC", "Left HiFi Capture",
+               CS42L51_POWER_CTL1, 1, 1,
+               cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD),
+       SND_SOC_DAPM_ADC_E("Right ADC", "Right HiFi Capture",
+               CS42L51_POWER_CTL1, 2, 1,
+               cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD),
+       SND_SOC_DAPM_DAC_E("Left DAC", "Left HiFi Playback",
+               CS42L51_POWER_CTL1, 5, 1,
+               cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD),
+       SND_SOC_DAPM_DAC_E("Right DAC", "Right HiFi Playback",
+               CS42L51_POWER_CTL1, 6, 1,
+               cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD),
+
+       /* analog/mic */
+       SND_SOC_DAPM_INPUT("AIN1L"),
+       SND_SOC_DAPM_INPUT("AIN1R"),
+       SND_SOC_DAPM_INPUT("AIN2L"),
+       SND_SOC_DAPM_INPUT("AIN2R"),
+       SND_SOC_DAPM_INPUT("MICL"),
+       SND_SOC_DAPM_INPUT("MICR"),
+
+       SND_SOC_DAPM_MIXER("Mic Preamp Left",
+               CS42L51_MIC_POWER_CTL, 2, 1, NULL, 0),
+       SND_SOC_DAPM_MIXER("Mic Preamp Right",
+               CS42L51_MIC_POWER_CTL, 3, 1, NULL, 0),
+
+       /* HP */
+       SND_SOC_DAPM_OUTPUT("HPL"),
+       SND_SOC_DAPM_OUTPUT("HPR"),
+
+       /* mux */
+       SND_SOC_DAPM_MUX("DAC Mux", SND_SOC_NOPM, 0, 0,
+               &cs42l51_dac_mux_controls),
+       SND_SOC_DAPM_MUX("PGA-ADC Mux Left", SND_SOC_NOPM, 0, 0,
+               &cs42l51_adcl_mux_controls),
+       SND_SOC_DAPM_MUX("PGA-ADC Mux Right", SND_SOC_NOPM, 0, 0,
+               &cs42l51_adcr_mux_controls),
+};
+
+static const struct snd_soc_dapm_route cs42l51_routes[] = {
+       {"HPL", NULL, "Left DAC"},
+       {"HPR", NULL, "Right DAC"},
+
+       {"Left ADC", NULL, "Left PGA"},
+       {"Right ADC", NULL, "Right PGA"},
+
+       {"Mic Preamp Left",  NULL,  "MICL"},
+       {"Mic Preamp Right", NULL,  "MICR"},
+
+       {"PGA-ADC Mux Left",  "AIN1 Left",        "AIN1L" },
+       {"PGA-ADC Mux Left",  "AIN2 Left",        "AIN2L" },
+       {"PGA-ADC Mux Left",  "MIC Left",         "MICL"  },
+       {"PGA-ADC Mux Left",  "MIC+preamp Left",  "Mic Preamp Left" },
+       {"PGA-ADC Mux Right", "AIN1 Right",       "AIN1R" },
+       {"PGA-ADC Mux Right", "AIN2 Right",       "AIN2R" },
+       {"PGA-ADC Mux Right", "MIC Right",        "MICR" },
+       {"PGA-ADC Mux Right", "MIC+preamp Right", "Mic Preamp Right" },
+
+       {"Left PGA", NULL, "PGA-ADC Mux Left"},
+       {"Right PGA", NULL, "PGA-ADC Mux Right"},
+};
+
+static int cs42l51_set_dai_fmt(struct snd_soc_dai *codec_dai,
+               unsigned int format)
+{
+       struct snd_soc_codec *codec = codec_dai->codec;
+       struct cs42l51_private *cs42l51 = snd_soc_codec_get_drvdata(codec);
+       int ret = 0;
+
+       switch (format & SND_SOC_DAIFMT_FORMAT_MASK) {
+       case SND_SOC_DAIFMT_I2S:
+       case SND_SOC_DAIFMT_LEFT_J:
+       case SND_SOC_DAIFMT_RIGHT_J:
+               cs42l51->audio_mode = format & SND_SOC_DAIFMT_FORMAT_MASK;
+               break;
+       default:
+               dev_err(codec->dev, "invalid DAI format\n");
+               ret = -EINVAL;
+       }
+
+       switch (format & SND_SOC_DAIFMT_MASTER_MASK) {
+       case SND_SOC_DAIFMT_CBM_CFM:
+               cs42l51->func = MODE_MASTER;
+               break;
+       case SND_SOC_DAIFMT_CBS_CFS:
+               cs42l51->func = MODE_SLAVE_AUTO;
+               break;
+       default:
+               ret = -EINVAL;
+               break;
+       }
+
+       return ret;
+}
+
+struct cs42l51_ratios {
+       unsigned int ratio;
+       unsigned char speed_mode;
+       unsigned char mclk;
+};
+
+static struct cs42l51_ratios slave_ratios[] = {
+       {  512, CS42L51_QSM_MODE, 0 }, {  768, CS42L51_QSM_MODE, 0 },
+       { 1024, CS42L51_QSM_MODE, 0 }, { 1536, CS42L51_QSM_MODE, 0 },
+       { 2048, CS42L51_QSM_MODE, 0 }, { 3072, CS42L51_QSM_MODE, 0 },
+       {  256, CS42L51_HSM_MODE, 0 }, {  384, CS42L51_HSM_MODE, 0 },
+       {  512, CS42L51_HSM_MODE, 0 }, {  768, CS42L51_HSM_MODE, 0 },
+       { 1024, CS42L51_HSM_MODE, 0 }, { 1536, CS42L51_HSM_MODE, 0 },
+       {  128, CS42L51_SSM_MODE, 0 }, {  192, CS42L51_SSM_MODE, 0 },
+       {  256, CS42L51_SSM_MODE, 0 }, {  384, CS42L51_SSM_MODE, 0 },
+       {  512, CS42L51_SSM_MODE, 0 }, {  768, CS42L51_SSM_MODE, 0 },
+       {  128, CS42L51_DSM_MODE, 0 }, {  192, CS42L51_DSM_MODE, 0 },
+       {  256, CS42L51_DSM_MODE, 0 }, {  384, CS42L51_DSM_MODE, 0 },
+};
+
+static struct cs42l51_ratios slave_auto_ratios[] = {
+       { 1024, CS42L51_QSM_MODE, 0 }, { 1536, CS42L51_QSM_MODE, 0 },
+       { 2048, CS42L51_QSM_MODE, 1 }, { 3072, CS42L51_QSM_MODE, 1 },
+       {  512, CS42L51_HSM_MODE, 0 }, {  768, CS42L51_HSM_MODE, 0 },
+       { 1024, CS42L51_HSM_MODE, 1 }, { 1536, CS42L51_HSM_MODE, 1 },
+       {  256, CS42L51_SSM_MODE, 0 }, {  384, CS42L51_SSM_MODE, 0 },
+       {  512, CS42L51_SSM_MODE, 1 }, {  768, CS42L51_SSM_MODE, 1 },
+       {  128, CS42L51_DSM_MODE, 0 }, {  192, CS42L51_DSM_MODE, 0 },
+       {  256, CS42L51_DSM_MODE, 1 }, {  384, CS42L51_DSM_MODE, 1 },
+};
+
+static int cs42l51_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 cs42l51_private *cs42l51 = snd_soc_codec_get_drvdata(codec);
+       struct cs42l51_ratios *ratios = NULL;
+       int nr_ratios = 0;
+       unsigned int rates = 0;
+       unsigned int rate_min = -1;
+       unsigned int rate_max = 0;
+       int i;
+
+       cs42l51->mclk = freq;
+
+       switch (cs42l51->func) {
+       case MODE_MASTER:
+               return -EINVAL;
+       case MODE_SLAVE:
+               ratios = slave_ratios;
+               nr_ratios = ARRAY_SIZE(slave_ratios);
+               break;
+       case MODE_SLAVE_AUTO:
+               ratios = slave_auto_ratios;
+               nr_ratios = ARRAY_SIZE(slave_auto_ratios);
+               break;
+       }
+
+       for (i = 0; i < nr_ratios; i++) {
+               unsigned int rate = freq / ratios[i].ratio;
+               rates |= snd_pcm_rate_to_rate_bit(rate);
+               if (rate < rate_min)
+                       rate_min = rate;
+               if (rate > rate_max)
+                       rate_max = rate;
+       }
+       rates &= ~SNDRV_PCM_RATE_KNOT;
+
+       if (!rates) {
+               dev_err(codec->dev, "could not find a valid sample rate\n");
+               return -EINVAL;
+       }
+
+       codec_dai->playback.rates = rates;
+       codec_dai->playback.rate_min = rate_min;
+       codec_dai->playback.rate_max = rate_max;
+
+       codec_dai->capture.rates = rates;
+       codec_dai->capture.rate_min = rate_min;
+       codec_dai->capture.rate_max = rate_max;
+
+       return 0;
+}
+
+static int cs42l51_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 cs42l51_private *cs42l51 = snd_soc_codec_get_drvdata(codec);
+       int ret;
+       unsigned int i;
+       unsigned int rate;
+       unsigned int ratio;
+       struct cs42l51_ratios *ratios = NULL;
+       int nr_ratios = 0;
+       int intf_ctl, power_ctl, fmt;
+
+       switch (cs42l51->func) {
+       case MODE_MASTER:
+               return -EINVAL;
+       case MODE_SLAVE:
+               ratios = slave_ratios;
+               nr_ratios = ARRAY_SIZE(slave_ratios);
+               break;
+       case MODE_SLAVE_AUTO:
+               ratios = slave_auto_ratios;
+               nr_ratios = ARRAY_SIZE(slave_auto_ratios);
+               break;
+       }
+
+       /* Figure out which MCLK/LRCK ratio to use */
+       rate = params_rate(params);     /* Sampling rate, in Hz */
+       ratio = cs42l51->mclk / rate;    /* MCLK/LRCK ratio */
+       for (i = 0; i < nr_ratios; i++) {
+               if (ratios[i].ratio == ratio)
+                       break;
+       }
+
+       if (i == nr_ratios) {
+               /* We did not find a matching ratio */
+               dev_err(codec->dev, "could not find matching ratio\n");
+               return -EINVAL;
+       }
+
+       intf_ctl = snd_soc_read(codec, CS42L51_INTF_CTL);
+       power_ctl = snd_soc_read(codec, CS42L51_MIC_POWER_CTL);
+
+       intf_ctl &= ~(CS42L51_INTF_CTL_MASTER | CS42L51_INTF_CTL_ADC_I2S
+                       | CS42L51_INTF_CTL_DAC_FORMAT(7));
+       power_ctl &= ~(CS42L51_MIC_POWER_CTL_SPEED(3)
+                       | CS42L51_MIC_POWER_CTL_MCLK_DIV2);
+
+       switch (cs42l51->func) {
+       case MODE_MASTER:
+               intf_ctl |= CS42L51_INTF_CTL_MASTER;
+               power_ctl |= CS42L51_MIC_POWER_CTL_SPEED(ratios[i].speed_mode);
+               break;
+       case MODE_SLAVE:
+               power_ctl |= CS42L51_MIC_POWER_CTL_SPEED(ratios[i].speed_mode);
+               break;
+       case MODE_SLAVE_AUTO:
+               power_ctl |= CS42L51_MIC_POWER_CTL_AUTO;
+               break;
+       }
+
+       switch (cs42l51->audio_mode) {
+       case SND_SOC_DAIFMT_I2S:
+               intf_ctl |= CS42L51_INTF_CTL_ADC_I2S;
+               intf_ctl |= CS42L51_INTF_CTL_DAC_FORMAT(CS42L51_DAC_DIF_I2S);
+               break;
+       case SND_SOC_DAIFMT_LEFT_J:
+               intf_ctl |= CS42L51_INTF_CTL_DAC_FORMAT(CS42L51_DAC_DIF_LJ24);
+               break;
+       case SND_SOC_DAIFMT_RIGHT_J:
+               switch (params_format(params)) {
+               case SNDRV_PCM_FORMAT_S16_LE:
+               case SNDRV_PCM_FORMAT_S16_BE:
+                       fmt = CS42L51_DAC_DIF_RJ16;
+                       break;
+               case SNDRV_PCM_FORMAT_S18_3LE:
+               case SNDRV_PCM_FORMAT_S18_3BE:
+                       fmt = CS42L51_DAC_DIF_RJ18;
+                       break;
+               case SNDRV_PCM_FORMAT_S20_3LE:
+               case SNDRV_PCM_FORMAT_S20_3BE:
+                       fmt = CS42L51_DAC_DIF_RJ20;
+                       break;
+               case SNDRV_PCM_FORMAT_S24_LE:
+               case SNDRV_PCM_FORMAT_S24_BE:
+                       fmt = CS42L51_DAC_DIF_RJ24;
+                       break;
+               default:
+                       dev_err(codec->dev, "unknown format\n");
+                       return -EINVAL;
+               }
+               intf_ctl |= CS42L51_INTF_CTL_DAC_FORMAT(fmt);
+               break;
+       default:
+               dev_err(codec->dev, "unknown format\n");
+               return -EINVAL;
+       }
+
+       if (ratios[i].mclk)
+               power_ctl |= CS42L51_MIC_POWER_CTL_MCLK_DIV2;
+
+       ret = snd_soc_write(codec, CS42L51_INTF_CTL, intf_ctl);
+       if (ret < 0)
+               return ret;
+
+       ret = snd_soc_write(codec, CS42L51_MIC_POWER_CTL, power_ctl);
+       if (ret < 0)
+               return ret;
+
+       return 0;
+}
+
+static int cs42l51_dai_mute(struct snd_soc_dai *dai, int mute)
+{
+       struct snd_soc_codec *codec = dai->codec;
+       int reg;
+       int mask = CS42L51_DAC_OUT_CTL_DACA_MUTE|CS42L51_DAC_OUT_CTL_DACB_MUTE;
+
+       reg = snd_soc_read(codec, CS42L51_DAC_OUT_CTL);
+
+       if (mute)
+               reg |= mask;
+       else
+               reg &= ~mask;
+
+       return snd_soc_write(codec, CS42L51_DAC_OUT_CTL, reg);
+}
+
+static struct snd_soc_dai_ops cs42l51_dai_ops = {
+       .hw_params      = cs42l51_hw_params,
+       .set_sysclk     = cs42l51_set_dai_sysclk,
+       .set_fmt        = cs42l51_set_dai_fmt,
+       .digital_mute   = cs42l51_dai_mute,
+};
+
+struct snd_soc_dai cs42l51_dai = {
+       .name = "CS42L51 HiFi",
+       .playback = {
+               .stream_name = "Playback",
+               .channels_min = 1,
+               .channels_max = 2,
+               .rates = SNDRV_PCM_RATE_8000_96000,
+               .formats = CS42L51_FORMATS,
+       },
+       .capture = {
+               .stream_name = "Capture",
+               .channels_min = 1,
+               .channels_max = 2,
+               .rates = SNDRV_PCM_RATE_8000_96000,
+               .formats = CS42L51_FORMATS,
+       },
+       .ops = &cs42l51_dai_ops,
+};
+EXPORT_SYMBOL_GPL(cs42l51_dai);
+
+
+static int cs42l51_probe(struct platform_device *pdev)
+{
+       struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+       struct snd_soc_codec *codec;
+       int ret = 0;
+
+       if (!cs42l51_codec) {
+               dev_err(&pdev->dev, "CS42L51 codec not yet registered\n");
+               return -EINVAL;
+       }
+
+       socdev->card->codec = cs42l51_codec;
+       codec = socdev->card->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");
+               return ret;
+       }
+
+       snd_soc_add_controls(codec, cs42l51_snd_controls,
+               ARRAY_SIZE(cs42l51_snd_controls));
+       snd_soc_dapm_new_controls(codec, cs42l51_dapm_widgets,
+               ARRAY_SIZE(cs42l51_dapm_widgets));
+       snd_soc_dapm_add_routes(codec, cs42l51_routes,
+               ARRAY_SIZE(cs42l51_routes));
+
+       return 0;
+}
+
+
+static int cs42l51_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_device_cs42l51 = {
+       .probe =        cs42l51_probe,
+       .remove =       cs42l51_remove
+};
+EXPORT_SYMBOL_GPL(soc_codec_device_cs42l51);
+
+static int __init cs42l51_init(void)
+{
+       int ret;
+
+       ret = i2c_add_driver(&cs42l51_i2c_driver);
+       if (ret != 0) {
+               printk(KERN_ERR "%s: can't add i2c driver\n", __func__);
+               return ret;
+       }
+       return 0;
+}
+module_init(cs42l51_init);
+
+static void __exit cs42l51_exit(void)
+{
+       i2c_del_driver(&cs42l51_i2c_driver);
+}
+module_exit(cs42l51_exit);
+
+MODULE_AUTHOR("Arnaud Patard <apatard@mandriva.com>");
+MODULE_DESCRIPTION("Cirrus Logic CS42L51 ALSA SoC Codec Driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/cs42l51.h b/sound/soc/codecs/cs42l51.h
new file mode 100644 (file)
index 0000000..8f0bd97
--- /dev/null
@@ -0,0 +1,163 @@
+/*
+ * cs42l51.h
+ *
+ * ASoC Driver for Cirrus Logic CS42L51 codecs
+ *
+ * Copyright (c) 2010 Arnaud Patard <apatard@mandriva.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+#ifndef _CS42L51_H
+#define _CS42L51_H
+
+#define CS42L51_CHIP_ID                        0x1B
+#define CS42L51_CHIP_REV_A             0x00
+#define CS42L51_CHIP_REV_B             0x01
+
+#define CS42L51_CHIP_REV_ID            0x01
+#define CS42L51_MK_CHIP_REV(a, b)      ((a)<<3|(b))
+
+#define CS42L51_POWER_CTL1             0x02
+#define CS42L51_POWER_CTL1_PDN_DACB    (1<<6)
+#define CS42L51_POWER_CTL1_PDN_DACA    (1<<5)
+#define CS42L51_POWER_CTL1_PDN_PGAB    (1<<4)
+#define CS42L51_POWER_CTL1_PDN_PGAA    (1<<3)
+#define CS42L51_POWER_CTL1_PDN_ADCB    (1<<2)
+#define CS42L51_POWER_CTL1_PDN_ADCA    (1<<1)
+#define CS42L51_POWER_CTL1_PDN         (1<<0)
+
+#define CS42L51_MIC_POWER_CTL          0x03
+#define CS42L51_MIC_POWER_CTL_AUTO     (1<<7)
+#define CS42L51_MIC_POWER_CTL_SPEED(x) (((x)&3)<<5)
+#define CS42L51_QSM_MODE               3
+#define CS42L51_HSM_MODE               2
+#define        CS42L51_SSM_MODE                1
+#define CS42L51_DSM_MODE               0
+#define CS42L51_MIC_POWER_CTL_3ST_SP   (1<<4)
+#define CS42L51_MIC_POWER_CTL_PDN_MICB (1<<3)
+#define CS42L51_MIC_POWER_CTL_PDN_MICA (1<<2)
+#define CS42L51_MIC_POWER_CTL_PDN_BIAS (1<<1)
+#define CS42L51_MIC_POWER_CTL_MCLK_DIV2        (1<<0)
+
+#define CS42L51_INTF_CTL               0x04
+#define CS42L51_INTF_CTL_LOOPBACK      (1<<7)
+#define CS42L51_INTF_CTL_MASTER                (1<<6)
+#define CS42L51_INTF_CTL_DAC_FORMAT(x) (((x)&7)<<3)
+#define CS42L51_DAC_DIF_LJ24           0x00
+#define CS42L51_DAC_DIF_I2S            0x01
+#define CS42L51_DAC_DIF_RJ24           0x02
+#define CS42L51_DAC_DIF_RJ20           0x03
+#define CS42L51_DAC_DIF_RJ18           0x04
+#define CS42L51_DAC_DIF_RJ16           0x05
+#define CS42L51_INTF_CTL_ADC_I2S       (1<<2)
+#define CS42L51_INTF_CTL_DIGMIX                (1<<1)
+#define CS42L51_INTF_CTL_MICMIX                (1<<0)
+
+#define CS42L51_MIC_CTL                        0x05
+#define CS42L51_MIC_CTL_ADC_SNGVOL     (1<<7)
+#define CS42L51_MIC_CTL_ADCD_DBOOST    (1<<6)
+#define CS42L51_MIC_CTL_ADCA_DBOOST    (1<<5)
+#define CS42L51_MIC_CTL_MICBIAS_SEL    (1<<4)
+#define CS42L51_MIC_CTL_MICBIAS_LVL(x) (((x)&3)<<2)
+#define CS42L51_MIC_CTL_MICB_BOOST     (1<<1)
+#define CS42L51_MIC_CTL_MICA_BOOST     (1<<0)
+
+#define CS42L51_ADC_CTL                        0x06
+#define CS42L51_ADC_CTL_ADCB_HPFEN     (1<<7)
+#define CS42L51_ADC_CTL_ADCB_HPFRZ     (1<<6)
+#define CS42L51_ADC_CTL_ADCA_HPFEN     (1<<5)
+#define CS42L51_ADC_CTL_ADCA_HPFRZ     (1<<4)
+#define CS42L51_ADC_CTL_SOFTB          (1<<3)
+#define CS42L51_ADC_CTL_ZCROSSB                (1<<2)
+#define CS42L51_ADC_CTL_SOFTA          (1<<1)
+#define CS42L51_ADC_CTL_ZCROSSA                (1<<0)
+
+#define CS42L51_ADC_INPUT              0x07
+#define CS42L51_ADC_INPUT_AINB_MUX(x)  (((x)&3)<<6)
+#define CS42L51_ADC_INPUT_AINA_MUX(x)  (((x)&3)<<4)
+#define CS42L51_ADC_INPUT_INV_ADCB     (1<<3)
+#define CS42L51_ADC_INPUT_INV_ADCA     (1<<2)
+#define CS42L51_ADC_INPUT_ADCB_MUTE    (1<<1)
+#define CS42L51_ADC_INPUT_ADCA_MUTE    (1<<0)
+
+#define CS42L51_DAC_OUT_CTL            0x08
+#define CS42L51_DAC_OUT_CTL_HP_GAIN(x) (((x)&7)<<5)
+#define CS42L51_DAC_OUT_CTL_DAC_SNGVOL (1<<4)
+#define CS42L51_DAC_OUT_CTL_INV_PCMB   (1<<3)
+#define CS42L51_DAC_OUT_CTL_INV_PCMA   (1<<2)
+#define CS42L51_DAC_OUT_CTL_DACB_MUTE  (1<<1)
+#define CS42L51_DAC_OUT_CTL_DACA_MUTE  (1<<0)
+
+#define CS42L51_DAC_CTL                        0x09
+#define CS42L51_DAC_CTL_DATA_SEL(x)    (((x)&3)<<6)
+#define CS42L51_DAC_CTL_FREEZE         (1<<5)
+#define CS42L51_DAC_CTL_DEEMPH         (1<<3)
+#define CS42L51_DAC_CTL_AMUTE          (1<<2)
+#define CS42L51_DAC_CTL_DACSZ(x)       (((x)&3)<<0)
+
+#define CS42L51_ALC_PGA_CTL            0x0A
+#define CS42L51_ALC_PGB_CTL            0x0B
+#define CS42L51_ALC_PGX_ALCX_SRDIS     (1<<7)
+#define CS42L51_ALC_PGX_ALCX_ZCDIS     (1<<6)
+#define CS42L51_ALC_PGX_PGX_VOL(x)     (((x)&0x1f)<<0)
+
+#define CS42L51_ADCA_ATT               0x0C
+#define CS42L51_ADCB_ATT               0x0D
+
+#define CS42L51_ADCA_VOL               0x0E
+#define CS42L51_ADCB_VOL               0x0F
+#define CS42L51_PCMA_VOL               0x10
+#define CS42L51_PCMB_VOL               0x11
+#define CS42L51_MIX_MUTE_ADCMIX                (1<<7)
+#define CS42L51_MIX_VOLUME(x)          (((x)&0x7f)<<0)
+
+#define CS42L51_BEEP_FREQ              0x12
+#define CS42L51_BEEP_VOL               0x13
+#define CS42L51_BEEP_CONF              0x14
+
+#define CS42L51_TONE_CTL               0x15
+#define CS42L51_TONE_CTL_TREB(x)       (((x)&0xf)<<4)
+#define CS42L51_TONE_CTL_BASS(x)       (((x)&0xf)<<0)
+
+#define CS42L51_AOUTA_VOL              0x16
+#define CS42L51_AOUTB_VOL              0x17
+#define CS42L51_PCM_MIXER              0x18
+#define CS42L51_LIMIT_THRES_DIS                0x19
+#define CS42L51_LIMIT_REL              0x1A
+#define CS42L51_LIMIT_ATT              0x1B
+#define CS42L51_ALC_EN                 0x1C
+#define CS42L51_ALC_REL                        0x1D
+#define CS42L51_ALC_THRES              0x1E
+#define CS42L51_NOISE_CONF             0x1F
+
+#define CS42L51_STATUS                 0x20
+#define CS42L51_STATUS_SP_CLKERR       (1<<6)
+#define CS42L51_STATUS_SPEA_OVFL       (1<<5)
+#define CS42L51_STATUS_SPEB_OVFL       (1<<4)
+#define CS42L51_STATUS_PCMA_OVFL       (1<<3)
+#define CS42L51_STATUS_PCMB_OVFL       (1<<2)
+#define CS42L51_STATUS_ADCA_OVFL       (1<<1)
+#define CS42L51_STATUS_ADCB_OVFL       (1<<0)
+
+#define CS42L51_CHARGE_FREQ            0x21
+
+#define CS42L51_FIRSTREG       0x01
+/*
+ * Hack: with register 0x21, it makes 33 registers. Looks like someone in the
+ * i2c layer doesn't like i2c smbus block read of 33 regs. Workaround by using
+ * 32 regs
+ */
+#define CS42L51_LASTREG                0x20
+#define CS42L51_NUMREGS                (CS42L51_LASTREG - CS42L51_FIRSTREG + 1)
+
+extern struct snd_soc_dai cs42l51_dai;
+extern struct snd_soc_codec_device soc_codec_device_cs42l51;
+#endif
index 75af2d6e0e78317f64bba97aed4afb148b2ca979..3c51d6a575237270de5d540bb13a1b93a77829d2 100644 (file)
  * option) any later version.
  */
 
-#include <linux/module.h>
-#include <linux/moduleparam.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 <linux/slab.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/tlv.h>
 #include <sound/initval.h>
-#include <asm/div64.h>
+#include <sound/tlv.h>
 
 #include "da7210.h"
 
 
 #define DA7210_VERSION "0.0.1"
 
+/*
+ * Playback Volume
+ *
+ * max         : 0x3F (+15.0 dB)
+ *                (1.5 dB step)
+ * min         : 0x11 (-54.0 dB)
+ * mute                : 0x10
+ * reserved    : 0x00 - 0x0F
+ *
+ * ** FIXME **
+ *
+ * Reserved area are considered as "mute".
+ * -> min = -79.5 dB
+ */
+static const DECLARE_TLV_DB_SCALE(hp_out_tlv, -7950, 150, 1);
+
+static const struct snd_kcontrol_new da7210_snd_controls[] = {
+
+       SOC_DOUBLE_R_TLV("HeadPhone Playback Volume",
+                        DA7210_HP_L_VOL, DA7210_HP_R_VOL,
+                        0, 0x3F, 0, hp_out_tlv),
+};
+
 /* Codec private data */
 struct da7210_priv {
        struct snd_soc_codec codec;
@@ -227,10 +242,6 @@ static int da7210_startup(struct snd_pcm_substream *substream,
        struct snd_soc_codec *codec = dai->codec;
 
        if (is_play) {
-               /* PlayBack Volume 40 */
-               snd_soc_update_bits(codec, DA7210_HP_L_VOL, 0x3F, 40);
-               snd_soc_update_bits(codec, DA7210_HP_R_VOL, 0x3F, 40);
-
                /* Enable Out */
                snd_soc_update_bits(codec, DA7210_OUTMIX_L, 0x1F, 0x10);
                snd_soc_update_bits(codec, DA7210_OUTMIX_R, 0x1F, 0x10);
@@ -488,7 +499,7 @@ static int da7210_init(struct da7210_priv *da7210)
        ret = snd_soc_register_dai(&da7210_dai);
        if (ret) {
                dev_err(codec->dev, "Failed to register DAI: %d\n", ret);
-               goto init_err;
+               goto codec_err;
        }
 
        /* FIXME
@@ -574,6 +585,8 @@ static int da7210_init(struct da7210_priv *da7210)
 
        return ret;
 
+codec_err:
+       snd_soc_unregister_codec(codec);
 init_err:
        kfree(codec->reg_cache);
        codec->reg_cache = NULL;
@@ -601,8 +614,10 @@ static int __devinit da7210_i2c_probe(struct i2c_client *i2c,
        codec->control_data = i2c;
 
        ret = da7210_init(da7210);
-       if (ret < 0)
+       if (ret < 0) {
                pr_err("Failed to initialise da7210 audio codec\n");
+               kfree(da7210);
+       }
 
        return ret;
 }
@@ -656,6 +671,9 @@ static int da7210_probe(struct platform_device *pdev)
        if (ret < 0)
                goto pcm_err;
 
+       snd_soc_add_controls(da7210_codec, da7210_snd_controls,
+                            ARRAY_SIZE(da7210_snd_controls));
+
        dev_info(&pdev->dev, "DA7210 Audio Codec %s\n", DA7210_VERSION);
 
 pcm_err:
diff --git a/sound/soc/codecs/jz4740.c b/sound/soc/codecs/jz4740.c
new file mode 100644 (file)
index 0000000..66557de
--- /dev/null
@@ -0,0 +1,511 @@
+/*
+ * Copyright (C) 2009-2010, Lars-Peter Clausen <lars@metafoo.de>
+ *
+ * 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.
+ *
+ *  You should have received a copy of the  GNU General Public License along
+ *  with this program; if not, write  to the Free Software Foundation, Inc.,
+ *  675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <linux/delay.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc-dapm.h>
+#include <sound/soc.h>
+
+#define JZ4740_REG_CODEC_1 0x0
+#define JZ4740_REG_CODEC_2 0x1
+
+#define JZ4740_CODEC_1_LINE_ENABLE BIT(29)
+#define JZ4740_CODEC_1_MIC_ENABLE BIT(28)
+#define JZ4740_CODEC_1_SW1_ENABLE BIT(27)
+#define JZ4740_CODEC_1_ADC_ENABLE BIT(26)
+#define JZ4740_CODEC_1_SW2_ENABLE BIT(25)
+#define JZ4740_CODEC_1_DAC_ENABLE BIT(24)
+#define JZ4740_CODEC_1_VREF_DISABLE BIT(20)
+#define JZ4740_CODEC_1_VREF_AMP_DISABLE BIT(19)
+#define JZ4740_CODEC_1_VREF_PULLDOWN BIT(18)
+#define JZ4740_CODEC_1_VREF_LOW_CURRENT BIT(17)
+#define JZ4740_CODEC_1_VREF_HIGH_CURRENT BIT(16)
+#define JZ4740_CODEC_1_HEADPHONE_DISABLE BIT(14)
+#define JZ4740_CODEC_1_HEADPHONE_AMP_CHANGE_ANY BIT(13)
+#define JZ4740_CODEC_1_HEADPHONE_CHARGE BIT(12)
+#define JZ4740_CODEC_1_HEADPHONE_PULLDOWN (BIT(11) | BIT(10))
+#define JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M BIT(9)
+#define JZ4740_CODEC_1_HEADPHONE_POWERDOWN BIT(8)
+#define JZ4740_CODEC_1_SUSPEND BIT(1)
+#define JZ4740_CODEC_1_RESET BIT(0)
+
+#define JZ4740_CODEC_1_LINE_ENABLE_OFFSET 29
+#define JZ4740_CODEC_1_MIC_ENABLE_OFFSET 28
+#define JZ4740_CODEC_1_SW1_ENABLE_OFFSET 27
+#define JZ4740_CODEC_1_ADC_ENABLE_OFFSET 26
+#define JZ4740_CODEC_1_SW2_ENABLE_OFFSET 25
+#define JZ4740_CODEC_1_DAC_ENABLE_OFFSET 24
+#define JZ4740_CODEC_1_HEADPHONE_DISABLE_OFFSET 14
+#define JZ4740_CODEC_1_HEADPHONE_POWERDOWN_OFFSET 8
+
+#define JZ4740_CODEC_2_INPUT_VOLUME_MASK               0x1f0000
+#define JZ4740_CODEC_2_SAMPLE_RATE_MASK                        0x000f00
+#define JZ4740_CODEC_2_MIC_BOOST_GAIN_MASK             0x000030
+#define JZ4740_CODEC_2_HEADPHONE_VOLUME_MASK   0x000003
+
+#define JZ4740_CODEC_2_INPUT_VOLUME_OFFSET             16
+#define JZ4740_CODEC_2_SAMPLE_RATE_OFFSET               8
+#define JZ4740_CODEC_2_MIC_BOOST_GAIN_OFFSET    4
+#define JZ4740_CODEC_2_HEADPHONE_VOLUME_OFFSET  0
+
+static const uint32_t jz4740_codec_regs[] = {
+       0x021b2302, 0x00170803,
+};
+
+struct jz4740_codec {
+       void __iomem *base;
+       struct resource *mem;
+
+       uint32_t reg_cache[2];
+       struct snd_soc_codec codec;
+};
+
+static inline struct jz4740_codec *codec_to_jz4740(struct snd_soc_codec *codec)
+{
+       return container_of(codec, struct jz4740_codec, codec);
+}
+
+static unsigned int jz4740_codec_read(struct snd_soc_codec *codec,
+       unsigned int reg)
+{
+       struct jz4740_codec *jz4740_codec = codec_to_jz4740(codec);
+       return readl(jz4740_codec->base + (reg << 2));
+}
+
+static int jz4740_codec_write(struct snd_soc_codec *codec, unsigned int reg,
+       unsigned int val)
+{
+       struct jz4740_codec *jz4740_codec = codec_to_jz4740(codec);
+
+       jz4740_codec->reg_cache[reg] = val;
+       writel(val, jz4740_codec->base + (reg << 2));
+
+       return 0;
+}
+
+static const struct snd_kcontrol_new jz4740_codec_controls[] = {
+       SOC_SINGLE("Master Playback Volume", JZ4740_REG_CODEC_2,
+                       JZ4740_CODEC_2_HEADPHONE_VOLUME_OFFSET, 3, 0),
+       SOC_SINGLE("Master Capture Volume", JZ4740_REG_CODEC_2,
+                       JZ4740_CODEC_2_INPUT_VOLUME_OFFSET, 31, 0),
+       SOC_SINGLE("Master Playback Switch", JZ4740_REG_CODEC_1,
+                       JZ4740_CODEC_1_HEADPHONE_DISABLE_OFFSET, 1, 1),
+       SOC_SINGLE("Mic Capture Volume", JZ4740_REG_CODEC_2,
+                       JZ4740_CODEC_2_MIC_BOOST_GAIN_OFFSET, 3, 0),
+};
+
+static const struct snd_kcontrol_new jz4740_codec_output_controls[] = {
+       SOC_DAPM_SINGLE("Bypass Switch", JZ4740_REG_CODEC_1,
+                       JZ4740_CODEC_1_SW1_ENABLE_OFFSET, 1, 0),
+       SOC_DAPM_SINGLE("DAC Switch", JZ4740_REG_CODEC_1,
+                       JZ4740_CODEC_1_SW2_ENABLE_OFFSET, 1, 0),
+};
+
+static const struct snd_kcontrol_new jz4740_codec_input_controls[] = {
+       SOC_DAPM_SINGLE("Line Capture Switch", JZ4740_REG_CODEC_1,
+                       JZ4740_CODEC_1_LINE_ENABLE_OFFSET, 1, 0),
+       SOC_DAPM_SINGLE("Mic Capture Switch", JZ4740_REG_CODEC_1,
+                       JZ4740_CODEC_1_MIC_ENABLE_OFFSET, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget jz4740_codec_dapm_widgets[] = {
+       SND_SOC_DAPM_ADC("ADC", "Capture", JZ4740_REG_CODEC_1,
+                       JZ4740_CODEC_1_ADC_ENABLE_OFFSET, 0),
+       SND_SOC_DAPM_DAC("DAC", "Playback", JZ4740_REG_CODEC_1,
+                       JZ4740_CODEC_1_DAC_ENABLE_OFFSET, 0),
+
+       SND_SOC_DAPM_MIXER("Output Mixer", JZ4740_REG_CODEC_1,
+                       JZ4740_CODEC_1_HEADPHONE_POWERDOWN_OFFSET, 1,
+                       jz4740_codec_output_controls,
+                       ARRAY_SIZE(jz4740_codec_output_controls)),
+
+       SND_SOC_DAPM_MIXER_NAMED_CTL("Input Mixer", SND_SOC_NOPM, 0, 0,
+                       jz4740_codec_input_controls,
+                       ARRAY_SIZE(jz4740_codec_input_controls)),
+       SND_SOC_DAPM_MIXER("Line Input", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+       SND_SOC_DAPM_OUTPUT("LOUT"),
+       SND_SOC_DAPM_OUTPUT("ROUT"),
+
+       SND_SOC_DAPM_INPUT("MIC"),
+       SND_SOC_DAPM_INPUT("LIN"),
+       SND_SOC_DAPM_INPUT("RIN"),
+};
+
+static const struct snd_soc_dapm_route jz4740_codec_dapm_routes[] = {
+       {"Line Input", NULL, "LIN"},
+       {"Line Input", NULL, "RIN"},
+
+       {"Input Mixer", "Line Capture Switch", "Line Input"},
+       {"Input Mixer", "Mic Capture Switch", "MIC"},
+
+       {"ADC", NULL, "Input Mixer"},
+
+       {"Output Mixer", "Bypass Switch", "Input Mixer"},
+       {"Output Mixer", "DAC Switch", "DAC"},
+
+       {"LOUT", NULL, "Output Mixer"},
+       {"ROUT", NULL, "Output Mixer"},
+};
+
+static int jz4740_codec_hw_params(struct snd_pcm_substream *substream,
+       struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
+{
+       uint32_t val;
+       struct snd_soc_pcm_runtime *rtd = substream->private_data;
+       struct snd_soc_device *socdev = rtd->socdev;
+       struct snd_soc_codec *codec = socdev->card->codec;
+
+       switch (params_rate(params)) {
+       case 8000:
+               val = 0;
+               break;
+       case 11025:
+               val = 1;
+               break;
+       case 12000:
+               val = 2;
+               break;
+       case 16000:
+               val = 3;
+               break;
+       case 22050:
+               val = 4;
+               break;
+       case 24000:
+               val = 5;
+               break;
+       case 32000:
+               val = 6;
+               break;
+       case 44100:
+               val = 7;
+               break;
+       case 48000:
+               val = 8;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       val <<= JZ4740_CODEC_2_SAMPLE_RATE_OFFSET;
+
+       snd_soc_update_bits(codec, JZ4740_REG_CODEC_2,
+                               JZ4740_CODEC_2_SAMPLE_RATE_MASK, val);
+
+       return 0;
+}
+
+static struct snd_soc_dai_ops jz4740_codec_dai_ops = {
+       .hw_params = jz4740_codec_hw_params,
+};
+
+struct snd_soc_dai jz4740_codec_dai = {
+       .name = "jz4740",
+       .playback = {
+               .stream_name = "Playback",
+               .channels_min = 2,
+               .channels_max = 2,
+               .rates = SNDRV_PCM_RATE_8000_48000,
+               .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8,
+       },
+       .capture = {
+               .stream_name = "Capture",
+               .channels_min = 2,
+               .channels_max = 2,
+               .rates = SNDRV_PCM_RATE_8000_48000,
+               .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8,
+       },
+       .ops = &jz4740_codec_dai_ops,
+       .symmetric_rates = 1,
+};
+EXPORT_SYMBOL_GPL(jz4740_codec_dai);
+
+static void jz4740_codec_wakeup(struct snd_soc_codec *codec)
+{
+       int i;
+       uint32_t *cache = codec->reg_cache;
+
+       snd_soc_update_bits(codec, JZ4740_REG_CODEC_1,
+               JZ4740_CODEC_1_RESET, JZ4740_CODEC_1_RESET);
+       udelay(2);
+
+       snd_soc_update_bits(codec, JZ4740_REG_CODEC_1,
+               JZ4740_CODEC_1_SUSPEND | JZ4740_CODEC_1_RESET, 0);
+
+       for (i = 0; i < ARRAY_SIZE(jz4740_codec_regs); ++i)
+               jz4740_codec_write(codec, i, cache[i]);
+}
+
+static int jz4740_codec_set_bias_level(struct snd_soc_codec *codec,
+       enum snd_soc_bias_level level)
+{
+       unsigned int mask;
+       unsigned int value;
+
+       switch (level) {
+       case SND_SOC_BIAS_ON:
+               break;
+       case SND_SOC_BIAS_PREPARE:
+               mask = JZ4740_CODEC_1_VREF_DISABLE |
+                               JZ4740_CODEC_1_VREF_AMP_DISABLE |
+                               JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M;
+               value = 0;
+
+               snd_soc_update_bits(codec, JZ4740_REG_CODEC_1, mask, value);
+               break;
+       case SND_SOC_BIAS_STANDBY:
+               /* The only way to clear the suspend flag is to reset the codec */
+               if (codec->bias_level == SND_SOC_BIAS_OFF)
+                       jz4740_codec_wakeup(codec);
+
+               mask = JZ4740_CODEC_1_VREF_DISABLE |
+                       JZ4740_CODEC_1_VREF_AMP_DISABLE |
+                       JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M;
+               value = JZ4740_CODEC_1_VREF_DISABLE |
+                       JZ4740_CODEC_1_VREF_AMP_DISABLE |
+                       JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M;
+
+               snd_soc_update_bits(codec, JZ4740_REG_CODEC_1, mask, value);
+               break;
+       case SND_SOC_BIAS_OFF:
+               mask = JZ4740_CODEC_1_SUSPEND;
+               value = JZ4740_CODEC_1_SUSPEND;
+
+               snd_soc_update_bits(codec, JZ4740_REG_CODEC_1, mask, value);
+               break;
+       default:
+               break;
+       }
+
+       codec->bias_level = level;
+
+       return 0;
+}
+
+static struct snd_soc_codec *jz4740_codec_codec;
+
+static int jz4740_codec_dev_probe(struct platform_device *pdev)
+{
+       int ret;
+       struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+       struct snd_soc_codec *codec = jz4740_codec_codec;
+
+       BUG_ON(!codec);
+
+       socdev->card->codec = codec;
+
+       ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+       if (ret) {
+               dev_err(&pdev->dev, "Failed to create pcms: %d\n", ret);
+               return ret;
+       }
+
+       snd_soc_add_controls(codec, jz4740_codec_controls,
+               ARRAY_SIZE(jz4740_codec_controls));
+
+       snd_soc_dapm_new_controls(codec, jz4740_codec_dapm_widgets,
+               ARRAY_SIZE(jz4740_codec_dapm_widgets));
+
+       snd_soc_dapm_add_routes(codec, jz4740_codec_dapm_routes,
+               ARRAY_SIZE(jz4740_codec_dapm_routes));
+
+       snd_soc_dapm_new_widgets(codec);
+
+       return 0;
+}
+
+static int jz4740_codec_dev_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_SLEEP
+
+static int jz4740_codec_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;
+
+       return jz4740_codec_set_bias_level(codec, SND_SOC_BIAS_OFF);
+}
+
+static int jz4740_codec_resume(struct platform_device *pdev)
+{
+       struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+       struct snd_soc_codec *codec = socdev->card->codec;
+
+       return jz4740_codec_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+}
+
+#else
+#define jz4740_codec_suspend NULL
+#define jz4740_codec_resume NULL
+#endif
+
+struct snd_soc_codec_device soc_codec_dev_jz4740_codec = {
+       .probe = jz4740_codec_dev_probe,
+       .remove = jz4740_codec_dev_remove,
+       .suspend = jz4740_codec_suspend,
+       .resume = jz4740_codec_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_jz4740_codec);
+
+static int __devinit jz4740_codec_probe(struct platform_device *pdev)
+{
+       int ret;
+       struct jz4740_codec *jz4740_codec;
+       struct snd_soc_codec *codec;
+       struct resource *mem;
+
+       jz4740_codec = kzalloc(sizeof(*jz4740_codec), GFP_KERNEL);
+       if (!jz4740_codec)
+               return -ENOMEM;
+
+       mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (!mem) {
+               dev_err(&pdev->dev, "Failed to get mmio memory resource\n");
+               ret = -ENOENT;
+               goto err_free_codec;
+       }
+
+       mem = request_mem_region(mem->start, resource_size(mem), pdev->name);
+       if (!mem) {
+               dev_err(&pdev->dev, "Failed to request mmio memory region\n");
+               ret = -EBUSY;
+               goto err_free_codec;
+       }
+
+       jz4740_codec->base = ioremap(mem->start, resource_size(mem));
+       if (!jz4740_codec->base) {
+               dev_err(&pdev->dev, "Failed to ioremap mmio memory\n");
+               ret = -EBUSY;
+               goto err_release_mem_region;
+       }
+       jz4740_codec->mem = mem;
+
+       jz4740_codec_dai.dev = &pdev->dev;
+
+       codec = &jz4740_codec->codec;
+
+       codec->dev              = &pdev->dev;
+       codec->name             = "jz4740";
+       codec->owner            = THIS_MODULE;
+
+       codec->read             = jz4740_codec_read;
+       codec->write            = jz4740_codec_write;
+       codec->set_bias_level   = jz4740_codec_set_bias_level;
+       codec->bias_level       = SND_SOC_BIAS_OFF;
+
+       codec->dai              = &jz4740_codec_dai;
+       codec->num_dai          = 1;
+
+       codec->reg_cache        = jz4740_codec->reg_cache;
+       codec->reg_cache_size   = 2;
+       memcpy(codec->reg_cache, jz4740_codec_regs, sizeof(jz4740_codec_regs));
+
+       mutex_init(&codec->mutex);
+       INIT_LIST_HEAD(&codec->dapm_widgets);
+       INIT_LIST_HEAD(&codec->dapm_paths);
+
+       jz4740_codec_codec = codec;
+
+       snd_soc_update_bits(codec, JZ4740_REG_CODEC_1,
+                       JZ4740_CODEC_1_SW2_ENABLE, JZ4740_CODEC_1_SW2_ENABLE);
+
+       platform_set_drvdata(pdev, jz4740_codec);
+
+       ret = snd_soc_register_codec(codec);
+       if (ret) {
+               dev_err(&pdev->dev, "Failed to register codec\n");
+               goto err_iounmap;
+       }
+
+       ret = snd_soc_register_dai(&jz4740_codec_dai);
+       if (ret) {
+               dev_err(&pdev->dev, "Failed to register codec dai\n");
+               goto err_unregister_codec;
+       }
+
+       jz4740_codec_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+       return 0;
+
+err_unregister_codec:
+       snd_soc_unregister_codec(codec);
+err_iounmap:
+       iounmap(jz4740_codec->base);
+err_release_mem_region:
+       release_mem_region(mem->start, resource_size(mem));
+err_free_codec:
+       kfree(jz4740_codec);
+
+       return ret;
+}
+
+static int __devexit jz4740_codec_remove(struct platform_device *pdev)
+{
+       struct jz4740_codec *jz4740_codec = platform_get_drvdata(pdev);
+       struct resource *mem = jz4740_codec->mem;
+
+       snd_soc_unregister_dai(&jz4740_codec_dai);
+       snd_soc_unregister_codec(&jz4740_codec->codec);
+
+       iounmap(jz4740_codec->base);
+       release_mem_region(mem->start, resource_size(mem));
+
+       platform_set_drvdata(pdev, NULL);
+       kfree(jz4740_codec);
+
+       return 0;
+}
+
+static struct platform_driver jz4740_codec_driver = {
+       .probe = jz4740_codec_probe,
+       .remove = __devexit_p(jz4740_codec_remove),
+       .driver = {
+               .name = "jz4740-codec",
+               .owner = THIS_MODULE,
+       },
+};
+
+static int __init jz4740_codec_init(void)
+{
+       return platform_driver_register(&jz4740_codec_driver);
+}
+module_init(jz4740_codec_init);
+
+static void __exit jz4740_codec_exit(void)
+{
+       platform_driver_unregister(&jz4740_codec_driver);
+}
+module_exit(jz4740_codec_exit);
+
+MODULE_DESCRIPTION("JZ4740 SoC internal codec driver");
+MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:jz4740-codec");
diff --git a/sound/soc/codecs/jz4740.h b/sound/soc/codecs/jz4740.h
new file mode 100644 (file)
index 0000000..b5a0691
--- /dev/null
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2009, Lars-Peter Clausen <lars@metafoo.de>
+ *
+ * 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.
+ *
+ *  You should have received a copy of the  GNU General Public License along
+ *  with this program; if not, write  to the Free Software Foundation, Inc.,
+ *  675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#ifndef __SND_SOC_CODECS_JZ4740_CODEC_H__
+#define __SND_SOC_CODECS_JZ4740_CODEC_H__
+
+extern struct snd_soc_dai jz4740_codec_dai;
+extern struct snd_soc_codec_device soc_codec_dev_jz4740_codec;
+
+#endif
index a63191141052ffa284537fe14d2bc90fd093679a..9119836051a4721abef78468869c07b7bdc409a8 100644 (file)
 
 #include <linux/module.h>
 #include <linux/moduleparam.h>
+#include <linux/slab.h>
 #include <sound/soc.h>
 #include <sound/pcm.h>
+#include <sound/initval.h>
 
 #include "spdif_transciever.h"
 
@@ -26,6 +28,48 @@ MODULE_LICENSE("GPL");
 #define STUB_RATES     SNDRV_PCM_RATE_8000_96000
 #define STUB_FORMATS   SNDRV_PCM_FMTBIT_S16_LE
 
+static struct snd_soc_codec *spdif_dit_codec;
+
+static int spdif_dit_codec_probe(struct platform_device *pdev)
+{
+       struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+       struct snd_soc_codec *codec;
+       int ret;
+
+       if (spdif_dit_codec == NULL) {
+               dev_err(&pdev->dev, "Codec device not registered\n");
+               return -ENODEV;
+       }
+
+       socdev->card->codec = spdif_dit_codec;
+       codec = spdif_dit_codec;
+
+       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 err_create_pcms;
+       }
+
+       return 0;
+
+err_create_pcms:
+       return ret;
+}
+
+static int spdif_dit_codec_remove(struct platform_device *pdev)
+{
+       struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+
+       snd_soc_free_pcms(socdev);
+
+       return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_spdif_dit = {
+       .probe          = spdif_dit_codec_probe,
+       .remove         = spdif_dit_codec_remove,
+}; EXPORT_SYMBOL_GPL(soc_codec_dev_spdif_dit);
+
 struct snd_soc_dai dit_stub_dai = {
        .name           = "DIT",
        .playback       = {
@@ -40,13 +84,61 @@ EXPORT_SYMBOL_GPL(dit_stub_dai);
 
 static int spdif_dit_probe(struct platform_device *pdev)
 {
+       struct snd_soc_codec *codec;
+       int ret;
+
+       if (spdif_dit_codec) {
+               dev_err(&pdev->dev, "Another Codec is registered\n");
+               ret = -EINVAL;
+               goto err_reg_codec;
+       }
+
+       codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+       if (codec == NULL)
+               return -ENOMEM;
+
+       codec->dev = &pdev->dev;
+
+       mutex_init(&codec->mutex);
+
+       INIT_LIST_HEAD(&codec->dapm_widgets);
+       INIT_LIST_HEAD(&codec->dapm_paths);
+
+       codec->name = "spdif-dit";
+       codec->owner = THIS_MODULE;
+       codec->dai = &dit_stub_dai;
+       codec->num_dai = 1;
+
+       spdif_dit_codec = codec;
+
+       ret = snd_soc_register_codec(codec);
+       if (ret < 0) {
+               dev_err(codec->dev, "Failed to register codec: %d\n", ret);
+               goto err_reg_codec;
+       }
+
        dit_stub_dai.dev = &pdev->dev;
-       return snd_soc_register_dai(&dit_stub_dai);
+       ret = snd_soc_register_dai(&dit_stub_dai);
+       if (ret < 0) {
+               dev_err(codec->dev, "Failed to register dai: %d\n", ret);
+               goto err_reg_dai;
+       }
+
+       return 0;
+
+err_reg_dai:
+       snd_soc_unregister_codec(codec);
+err_reg_codec:
+       kfree(spdif_dit_codec);
+       return ret;
 }
 
 static int spdif_dit_remove(struct platform_device *pdev)
 {
        snd_soc_unregister_dai(&dit_stub_dai);
+       snd_soc_unregister_codec(spdif_dit_codec);
+       kfree(spdif_dit_codec);
+       spdif_dit_codec = NULL;
        return 0;
 }
 
index 296f2eb6c4ef610fba29972d86123fe0425b158a..1e102124f5469f96c58fa84d37c2a9fd5315c291 100644 (file)
@@ -12,6 +12,7 @@
 #ifndef CODEC_STUBS_H
 #define CODEC_STUBS_H
 
+extern struct snd_soc_codec_device soc_codec_dev_spdif_dit;
 extern struct snd_soc_dai dit_stub_dai;
 
 #endif /* CODEC_STUBS_H */
index b0bae3508b293c36aa45c2ed0427397402e905a8..0a4b0fef33550f217adf1a597636963e76cb4d48 100644 (file)
@@ -560,13 +560,16 @@ static int tlv320aic23_set_bias_level(struct snd_soc_codec *codec,
        switch (level) {
        case SND_SOC_BIAS_ON:
                /* vref/mid, osc on, dac unmute */
+               reg &= ~(TLV320AIC23_DEVICE_PWR_OFF | TLV320AIC23_OSC_OFF | \
+                       TLV320AIC23_DAC_OFF);
                tlv320aic23_write(codec, TLV320AIC23_PWR, reg);
                break;
        case SND_SOC_BIAS_PREPARE:
                break;
        case SND_SOC_BIAS_STANDBY:
                /* everything off except vref/vmid, */
-               tlv320aic23_write(codec, TLV320AIC23_PWR, reg | 0x0040);
+               tlv320aic23_write(codec, TLV320AIC23_PWR, reg | \
+                       TLV320AIC23_CLK_OFF);
                break;
        case SND_SOC_BIAS_OFF:
                /* everything off, dac mute, inactive */
@@ -615,7 +618,6 @@ static int tlv320aic23_suspend(struct platform_device *pdev,
        struct snd_soc_device *socdev = platform_get_drvdata(pdev);
        struct snd_soc_codec *codec = socdev->card->codec;
 
-       tlv320aic23_write(codec, TLV320AIC23_ACTIVE, 0x0);
        tlv320aic23_set_bias_level(codec, SND_SOC_BIAS_OFF);
 
        return 0;
@@ -632,7 +634,6 @@ static int tlv320aic23_resume(struct platform_device *pdev)
                u16 val = tlv320aic23_read_reg_cache(codec, reg);
                tlv320aic23_write(codec, reg, val);
        }
-
        tlv320aic23_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
 
        return 0;
index 65adc77eada1039e562af90d298793712b358ae6..8651b01ed22358a636abdd7ba2c63e051ae95461 100644 (file)
@@ -49,8 +49,6 @@
 
 #define NSAMPLE_MAX            5700
 
-#define LATENCY_TIME_MS                20
-
 #define MODE7_LTHR             10
 #define MODE7_UTHR             (DAC33_BUFFER_SIZE_SAMPLES - 10)
 
@@ -62,6 +60,9 @@
 #define US_TO_SAMPLES(rate, us) \
        (rate / (1000000 / us))
 
+#define UTHR_FROM_PERIOD_SIZE(samples, playrate, burstrate) \
+       ((samples * 5000) / ((burstrate * 5000) / (burstrate - playrate)))
+
 static void dac33_calculate_times(struct snd_pcm_substream *substream);
 static int dac33_prepare_chip(struct snd_pcm_substream *substream);
 
@@ -107,6 +108,10 @@ struct tlv320dac33_priv {
                                         * this */
        enum dac33_fifo_modes fifo_mode;/* FIFO mode selection */
        unsigned int nsample;           /* burst read amount from host */
+       int mode1_latency;              /* latency caused by the i2c writes in
+                                        * us */
+       int auto_fifo_config;           /* Configure the FIFO based on the
+                                        * period size */
        u8 burst_bclkdiv;               /* BCLK divider value in burst mode */
        unsigned int burst_rate;        /* Interface speed in Burst modes */
 
@@ -120,6 +125,8 @@ struct tlv320dac33_priv {
                                         * samples */
        unsigned int mode7_us_to_lthr;  /* Time to reach lthr from uthr */
 
+       unsigned int uthr;
+
        enum dac33_state state;
 };
 
@@ -442,6 +449,39 @@ static int dac33_set_nsample(struct snd_kcontrol *kcontrol,
        return ret;
 }
 
+static int dac33_get_uthr(struct snd_kcontrol *kcontrol,
+                        struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec);
+
+       ucontrol->value.integer.value[0] = dac33->uthr;
+
+       return 0;
+}
+
+static int dac33_set_uthr(struct snd_kcontrol *kcontrol,
+                        struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec);
+       int ret = 0;
+
+       if (dac33->substream)
+               return -EBUSY;
+
+       if (dac33->uthr == ucontrol->value.integer.value[0])
+               return 0;
+
+       if (ucontrol->value.integer.value[0] < (MODE7_LTHR + 10) ||
+           ucontrol->value.integer.value[0] > MODE7_UTHR)
+               ret = -EINVAL;
+       else
+               dac33->uthr = ucontrol->value.integer.value[0];
+
+       return ret;
+}
+
 static int dac33_get_fifo_mode(struct snd_kcontrol *kcontrol,
                         struct snd_ctl_elem_value *ucontrol)
 {
@@ -503,13 +543,18 @@ static const struct snd_kcontrol_new dac33_snd_controls[] = {
                 DAC33_LINEL_TO_LLO_VOL, DAC33_LINER_TO_RLO_VOL, 0, 127, 1),
 };
 
-static const struct snd_kcontrol_new dac33_nsample_snd_controls[] = {
-       SOC_SINGLE_EXT("nSample", 0, 0, 5900, 0,
-                dac33_get_nsample, dac33_set_nsample),
+static const struct snd_kcontrol_new dac33_mode_snd_controls[] = {
        SOC_ENUM_EXT("FIFO Mode", dac33_fifo_mode_enum,
                 dac33_get_fifo_mode, dac33_set_fifo_mode),
 };
 
+static const struct snd_kcontrol_new dac33_fifo_snd_controls[] = {
+       SOC_SINGLE_EXT("nSample", 0, 0, 5900, 0,
+               dac33_get_nsample, dac33_set_nsample),
+       SOC_SINGLE_EXT("UTHR", 0, 0, MODE7_UTHR, 0,
+                dac33_get_uthr, dac33_set_uthr),
+};
+
 /* Analog bypass */
 static const struct snd_kcontrol_new dac33_dapm_abypassl_control =
        SOC_DAPM_SINGLE("Switch", DAC33_LINEL_TO_LLO_VOL, 7, 1, 1);
@@ -612,7 +657,7 @@ static inline void dac33_prefill_handler(struct tlv320dac33_priv *dac33)
        switch (dac33->fifo_mode) {
        case DAC33_FIFO_MODE1:
                dac33_write16(codec, DAC33_NSAMPLE_MSB,
-                       DAC33_THRREG(dac33->nsample + dac33->alarm_threshold));
+                       DAC33_THRREG(dac33->nsample));
 
                /* Take the timestamps */
                spin_lock_irq(&dac33->lock);
@@ -761,6 +806,10 @@ static void dac33_shutdown(struct snd_pcm_substream *substream,
        struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec);
 
        dac33->substream = NULL;
+
+       /* Reset the nSample restrictions */
+       dac33->nsample_min = 0;
+       dac33->nsample_max = NSAMPLE_MAX;
 }
 
 static int dac33_hw_params(struct snd_pcm_substream *substream,
@@ -985,7 +1034,7 @@ static int dac33_prepare_chip(struct snd_pcm_substream *substream)
                 * Configure the threshold levels, and leave 10 sample space
                 * at the bottom, and also at the top of the FIFO
                 */
-               dac33_write16(codec, DAC33_UTHR_MSB, DAC33_THRREG(MODE7_UTHR));
+               dac33_write16(codec, DAC33_UTHR_MSB, DAC33_THRREG(dac33->uthr));
                dac33_write16(codec, DAC33_LTHR_MSB, DAC33_THRREG(MODE7_LTHR));
                break;
        default:
@@ -1003,57 +1052,71 @@ static void dac33_calculate_times(struct snd_pcm_substream *substream)
        struct snd_soc_device *socdev = rtd->socdev;
        struct snd_soc_codec *codec = socdev->card->codec;
        struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec);
+       unsigned int period_size = substream->runtime->period_size;
+       unsigned int rate = substream->runtime->rate;
        unsigned int nsample_limit;
 
        /* In bypass mode we don't need to calculate */
        if (!dac33->fifo_mode)
                return;
 
-       /* Number of samples (16bit, stereo) in one period */
-       dac33->nsample_min = snd_pcm_lib_period_bytes(substream) / 4;
-
-       /* Number of samples (16bit, stereo) in ALSA buffer */
-       dac33->nsample_max = snd_pcm_lib_buffer_bytes(substream) / 4;
-       /* Subtract one period from the total */
-       dac33->nsample_max -= dac33->nsample_min;
-
-       /* Number of samples for LATENCY_TIME_MS / 2 */
-       dac33->alarm_threshold = substream->runtime->rate /
-                                (1000 / (LATENCY_TIME_MS / 2));
-
-       /* Find and fix up the lowest nsmaple limit */
-       nsample_limit = substream->runtime->rate / (1000 / LATENCY_TIME_MS);
-
-       if (dac33->nsample_min < nsample_limit)
-               dac33->nsample_min = nsample_limit;
-
-       if (dac33->nsample < dac33->nsample_min)
-               dac33->nsample = dac33->nsample_min;
-
-       /*
-        * Find and fix up the highest nsmaple limit
-        * In order to not overflow the DAC33 buffer substract the
-        * alarm_threshold value from the size of the DAC33 buffer
-        */
-       nsample_limit = DAC33_BUFFER_SIZE_SAMPLES - dac33->alarm_threshold;
-
-       if (dac33->nsample_max > nsample_limit)
-               dac33->nsample_max = nsample_limit;
-
-       if (dac33->nsample > dac33->nsample_max)
-               dac33->nsample = dac33->nsample_max;
-
        switch (dac33->fifo_mode) {
        case DAC33_FIFO_MODE1:
+               /* Number of samples under i2c latency */
+               dac33->alarm_threshold = US_TO_SAMPLES(rate,
+                                               dac33->mode1_latency);
+               if (dac33->auto_fifo_config) {
+                       if (period_size <= dac33->alarm_threshold)
+                               /*
+                                * Configure nSamaple to number of periods,
+                                * which covers the latency requironment.
+                                */
+                               dac33->nsample = period_size *
+                                      ((dac33->alarm_threshold / period_size) +
+                                      (dac33->alarm_threshold % period_size ?
+                                      1 : 0));
+                       else
+                               dac33->nsample = period_size;
+               } else {
+                       /* nSample time shall not be shorter than i2c latency */
+                       dac33->nsample_min = dac33->alarm_threshold;
+                       /*
+                        * nSample should not be bigger than alsa buffer minus
+                        * size of one period to avoid overruns
+                        */
+                       dac33->nsample_max = substream->runtime->buffer_size -
+                                               period_size;
+                       nsample_limit = DAC33_BUFFER_SIZE_SAMPLES -
+                                       dac33->alarm_threshold;
+                       if (dac33->nsample_max > nsample_limit)
+                               dac33->nsample_max = nsample_limit;
+
+                       /* Correct the nSample if it is outside of the ranges */
+                       if (dac33->nsample < dac33->nsample_min)
+                               dac33->nsample = dac33->nsample_min;
+                       if (dac33->nsample > dac33->nsample_max)
+                               dac33->nsample = dac33->nsample_max;
+               }
+
                dac33->mode1_us_burst = SAMPLES_TO_US(dac33->burst_rate,
                                                      dac33->nsample);
                dac33->t_stamp1 = 0;
                dac33->t_stamp2 = 0;
                break;
        case DAC33_FIFO_MODE7:
+               if (dac33->auto_fifo_config) {
+                       dac33->uthr = UTHR_FROM_PERIOD_SIZE(
+                                       period_size,
+                                       rate,
+                                       dac33->burst_rate) + 9;
+                       if (dac33->uthr > MODE7_UTHR)
+                               dac33->uthr = MODE7_UTHR;
+                       if (dac33->uthr < (MODE7_LTHR + 10))
+                               dac33->uthr = (MODE7_LTHR + 10);
+               }
                dac33->mode7_us_to_lthr =
-                                       SAMPLES_TO_US(substream->runtime->rate,
-                                               MODE7_UTHR - MODE7_LTHR + 1);
+                               SAMPLES_TO_US(substream->runtime->rate,
+                                       dac33->uthr - MODE7_LTHR + 1);
                dac33->t_stamp1 = 0;
                break;
        default:
@@ -1104,7 +1167,7 @@ static snd_pcm_sframes_t dac33_dai_delay(
        struct snd_soc_codec *codec = socdev->card->codec;
        struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec);
        unsigned long long t0, t1, t_now;
-       unsigned int time_delta;
+       unsigned int time_delta, uthr;
        int samples_out, samples_in, samples;
        snd_pcm_sframes_t delay = 0;
 
@@ -1182,6 +1245,7 @@ static snd_pcm_sframes_t dac33_dai_delay(
        case DAC33_FIFO_MODE7:
                spin_lock(&dac33->lock);
                t0 = dac33->t_stamp1;
+               uthr = dac33->uthr;
                spin_unlock(&dac33->lock);
                t_now = ktime_to_us(ktime_get());
 
@@ -1194,7 +1258,7 @@ static snd_pcm_sframes_t dac33_dai_delay(
                         * Either the timestamps are messed or equal. Report
                         * maximum delay
                         */
-                       delay = MODE7_UTHR;
+                       delay = uthr;
                        goto out;
                }
 
@@ -1208,8 +1272,8 @@ static snd_pcm_sframes_t dac33_dai_delay(
                                        substream->runtime->rate,
                                        time_delta);
 
-                       if (likely(MODE7_UTHR > samples_out))
-                               delay = MODE7_UTHR - samples_out;
+                       if (likely(uthr > samples_out))
+                               delay = uthr - samples_out;
                        else
                                delay = 0;
                } else {
@@ -1227,8 +1291,8 @@ static snd_pcm_sframes_t dac33_dai_delay(
                                        time_delta);
                        delay = MODE7_LTHR + samples_in - samples_out;
 
-                       if (unlikely(delay > MODE7_UTHR))
-                               delay = MODE7_UTHR;
+                       if (unlikely(delay > uthr))
+                               delay = uthr;
                }
                break;
        default:
@@ -1347,10 +1411,15 @@ static int dac33_soc_probe(struct platform_device *pdev)
 
        snd_soc_add_controls(codec, dac33_snd_controls,
                             ARRAY_SIZE(dac33_snd_controls));
-       /* Only add the nSample controls, if we have valid IRQ number */
-       if (dac33->irq >= 0)
-               snd_soc_add_controls(codec, dac33_nsample_snd_controls,
-                                    ARRAY_SIZE(dac33_nsample_snd_controls));
+       /* Only add the FIFO controls, if we have valid IRQ number */
+       if (dac33->irq >= 0) {
+               snd_soc_add_controls(codec, dac33_mode_snd_controls,
+                                    ARRAY_SIZE(dac33_mode_snd_controls));
+               /* FIFO usage controls only, if autoio config is not selected */
+               if (!dac33->auto_fifo_config)
+                       snd_soc_add_controls(codec, dac33_fifo_snd_controls,
+                                       ARRAY_SIZE(dac33_fifo_snd_controls));
+       }
 
        dac33_add_widgets(codec);
 
@@ -1481,9 +1550,14 @@ static int __devinit dac33_i2c_probe(struct i2c_client *client,
        /* Pre calculate the burst rate */
        dac33->burst_rate = BURST_BASEFREQ_HZ / dac33->burst_bclkdiv / 32;
        dac33->keep_bclk = pdata->keep_bclk;
+       dac33->auto_fifo_config = pdata->auto_fifo_config;
+       dac33->mode1_latency = pdata->mode1_latency;
+       if (!dac33->mode1_latency)
+               dac33->mode1_latency = 10000; /* 10ms */
        dac33->irq = client->irq;
        dac33->nsample = NSAMPLE_MAX;
        dac33->nsample_max = NSAMPLE_MAX;
+       dac33->uthr = MODE7_UTHR;
        /* Disable FIFO use by default */
        dac33->fifo_mode = DAC33_FIFO_BYPASS;
 
index b4fcdb01fc49a9cc261c06545c1913d5d9adac14..7b618bbff8844d65f55092780b199d4b213e433b 100644 (file)
  */
 static const u8 twl4030_reg[TWL4030_CACHEREGNUM] = {
        0x00, /* this register not used         */
-       0x91, /* REG_CODEC_MODE         (0x1)   */
-       0xc3, /* REG_OPTION             (0x2)   */
+       0x00, /* REG_CODEC_MODE         (0x1)   */
+       0x00, /* REG_OPTION             (0x2)   */
        0x00, /* REG_UNKNOWN            (0x3)   */
        0x00, /* REG_MICBIAS_CTL        (0x4)   */
-       0x20, /* REG_ANAMICL            (0x5)   */
+       0x00, /* REG_ANAMICL            (0x5)   */
        0x00, /* REG_ANAMICR            (0x6)   */
        0x00, /* REG_AVADC_CTL          (0x7)   */
        0x00, /* REG_ADCMICSEL          (0x8)   */
        0x00, /* REG_DIGMIXING          (0x9)   */
-       0x0c, /* REG_ATXL1PGA           (0xA)   */
-       0x0c, /* REG_ATXR1PGA           (0xB)   */
-       0x00, /* REG_AVTXL2PGA          (0xC)   */
-       0x00, /* REG_AVTXR2PGA          (0xD)   */
+       0x0f, /* REG_ATXL1PGA           (0xA)   */
+       0x0f, /* REG_ATXR1PGA           (0xB)   */
+       0x0f, /* REG_AVTXL2PGA          (0xC)   */
+       0x0f, /* REG_AVTXR2PGA          (0xD)   */
        0x00, /* REG_AUDIO_IF           (0xE)   */
        0x00, /* REG_VOICE_IF           (0xF)   */
-       0x00, /* REG_ARXR1PGA           (0x10)  */
-       0x00, /* REG_ARXL1PGA           (0x11)  */
-       0x6c, /* REG_ARXR2PGA           (0x12)  */
-       0x6c, /* REG_ARXL2PGA           (0x13)  */
-       0x00, /* REG_VRXPGA             (0x14)  */
+       0x3f, /* REG_ARXR1PGA           (0x10)  */
+       0x3f, /* REG_ARXL1PGA           (0x11)  */
+       0x3f, /* REG_ARXR2PGA           (0x12)  */
+       0x3f, /* REG_ARXL2PGA           (0x13)  */
+       0x25, /* REG_VRXPGA             (0x14)  */
        0x00, /* REG_VSTPGA             (0x15)  */
        0x00, /* REG_VRX2ARXPGA         (0x16)  */
        0x00, /* REG_AVDAC_CTL          (0x17)  */
        0x00, /* REG_ARX2VTXPGA         (0x18)  */
-       0x00, /* REG_ARXL1_APGA_CTL     (0x19)  */
-       0x00, /* REG_ARXR1_APGA_CTL     (0x1A)  */
-       0x4a, /* REG_ARXL2_APGA_CTL     (0x1B)  */
-       0x4a, /* REG_ARXR2_APGA_CTL     (0x1C)  */
+       0x32, /* REG_ARXL1_APGA_CTL     (0x19)  */
+       0x32, /* REG_ARXR1_APGA_CTL     (0x1A)  */
+       0x32, /* REG_ARXL2_APGA_CTL     (0x1B)  */
+       0x32, /* REG_ARXR2_APGA_CTL     (0x1C)  */
        0x00, /* REG_ATX2ARXPGA         (0x1D)  */
        0x00, /* REG_BT_IF              (0x1E)  */
-       0x00, /* REG_BTPGA              (0x1F)  */
+       0x55, /* REG_BTPGA              (0x1F)  */
        0x00, /* REG_BTSTPGA            (0x20)  */
        0x00, /* REG_EAR_CTL            (0x21)  */
        0x00, /* REG_HS_SEL             (0x22)  */
@@ -85,32 +85,32 @@ static const u8 twl4030_reg[TWL4030_CACHEREGNUM] = {
        0x00, /* REG_PRECKR_CTL         (0x28)  */
        0x00, /* REG_HFL_CTL            (0x29)  */
        0x00, /* REG_HFR_CTL            (0x2A)  */
-       0x00, /* REG_ALC_CTL            (0x2B)  */
+       0x05, /* REG_ALC_CTL            (0x2B)  */
        0x00, /* REG_ALC_SET1           (0x2C)  */
        0x00, /* REG_ALC_SET2           (0x2D)  */
        0x00, /* REG_BOOST_CTL          (0x2E)  */
        0x00, /* REG_SOFTVOL_CTL        (0x2F)  */
-       0x00, /* REG_DTMF_FREQSEL       (0x30)  */
+       0x13, /* REG_DTMF_FREQSEL       (0x30)  */
        0x00, /* REG_DTMF_TONEXT1H      (0x31)  */
        0x00, /* REG_DTMF_TONEXT1L      (0x32)  */
        0x00, /* REG_DTMF_TONEXT2H      (0x33)  */
        0x00, /* REG_DTMF_TONEXT2L      (0x34)  */
-       0x00, /* REG_DTMF_TONOFF        (0x35)  */
-       0x00, /* REG_DTMF_WANONOFF      (0x36)  */
+       0x79, /* REG_DTMF_TONOFF        (0x35)  */
+       0x11, /* REG_DTMF_WANONOFF      (0x36)  */
        0x00, /* REG_I2S_RX_SCRAMBLE_H  (0x37)  */
        0x00, /* REG_I2S_RX_SCRAMBLE_M  (0x38)  */
        0x00, /* REG_I2S_RX_SCRAMBLE_L  (0x39)  */
        0x06, /* REG_APLL_CTL           (0x3A)  */
        0x00, /* REG_DTMF_CTL           (0x3B)  */
-       0x00, /* REG_DTMF_PGA_CTL2      (0x3C)  */
-       0x00, /* REG_DTMF_PGA_CTL1      (0x3D)  */
+       0x44, /* REG_DTMF_PGA_CTL2      (0x3C)  */
+       0x69, /* REG_DTMF_PGA_CTL1      (0x3D)  */
        0x00, /* REG_MISC_SET_1         (0x3E)  */
        0x00, /* REG_PCMBTMUX           (0x3F)  */
        0x00, /* not used               (0x40)  */
        0x00, /* not used               (0x41)  */
        0x00, /* not used               (0x42)  */
        0x00, /* REG_RX_PATH_SEL        (0x43)  */
-       0x00, /* REG_VDL_APGA_CTL       (0x44)  */
+       0x32, /* REG_VDL_APGA_CTL       (0x44)  */
        0x00, /* REG_VIBRA_CTL          (0x45)  */
        0x00, /* REG_VIBRA_SET          (0x46)  */
        0x00, /* REG_VIBRA_PWM_SET      (0x47)  */
@@ -143,6 +143,9 @@ struct twl4030_priv {
        u8 earpiece_enabled;
        u8 predrivel_enabled, predriver_enabled;
        u8 carkitl_enabled, carkitr_enabled;
+
+       /* Delay needed after enabling the digimic interface */
+       unsigned int digimic_delay;
 };
 
 /*
@@ -244,58 +247,95 @@ static void twl4030_codec_enable(struct snd_soc_codec *codec, int enable)
        udelay(10);
 }
 
-static void twl4030_init_chip(struct snd_soc_codec *codec)
+static inline void twl4030_check_defaults(struct snd_soc_codec *codec)
 {
-       u8 *cache = codec->reg_cache;
-       int i;
+       int i, difference = 0;
+       u8 val;
+
+       dev_dbg(codec->dev, "Checking TWL audio default configuration\n");
+       for (i = 1; i <= TWL4030_REG_MISC_SET_2; i++) {
+               twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val, i);
+               if (val != twl4030_reg[i]) {
+                       difference++;
+                       dev_dbg(codec->dev,
+                                "Reg 0x%02x: chip: 0x%02x driver: 0x%02x\n",
+                                i, val, twl4030_reg[i]);
+               }
+       }
+       dev_dbg(codec->dev, "Found %d non maching registers. %s\n",
+                difference, difference ? "Not OK" : "OK");
+}
 
-       /* clear CODECPDZ prior to setting register defaults */
-       twl4030_codec_enable(codec, 0);
+static inline void twl4030_reset_registers(struct snd_soc_codec *codec)
+{
+       int i;
 
        /* set all audio section registers to reasonable defaults */
        for (i = TWL4030_REG_OPTION; i <= TWL4030_REG_MISC_SET_2; i++)
                if (i != TWL4030_REG_APLL_CTL)
-                       twl4030_write(codec, i, cache[i]);
+                       twl4030_write(codec, i, twl4030_reg[i]);
 
 }
 
-static void twl4030_apll_enable(struct snd_soc_codec *codec, int enable)
+static void twl4030_init_chip(struct platform_device *pdev)
 {
+       struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+       struct twl4030_setup_data *setup = socdev->codec_data;
+       struct snd_soc_codec *codec = socdev->card->codec;
        struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec);
-       int status = -1;
+       u8 reg, byte;
+       int i = 0;
 
-       if (enable) {
-               twl4030->apll_enabled++;
-               if (twl4030->apll_enabled == 1)
-                       status = twl4030_codec_enable_resource(
-                                                       TWL4030_CODEC_RES_APLL);
-       } else {
-               twl4030->apll_enabled--;
-               if (!twl4030->apll_enabled)
-                       status = twl4030_codec_disable_resource(
-                                                       TWL4030_CODEC_RES_APLL);
-       }
+       /* Check defaults, if instructed before anything else */
+       if (setup && setup->check_defaults)
+               twl4030_check_defaults(codec);
 
-       if (status >= 0)
-               twl4030_write_reg_cache(codec, TWL4030_REG_APLL_CTL, status);
-}
+       /* Reset registers, if no setup data or if instructed to do so */
+       if (!setup || (setup && setup->reset_registers))
+               twl4030_reset_registers(codec);
 
-static void twl4030_power_up(struct snd_soc_codec *codec)
-{
-       struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec);
-       u8 anamicl, regmisc1, byte;
-       int i = 0;
+       /* Refresh APLL_CTL register from HW */
+       twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &byte,
+                           TWL4030_REG_APLL_CTL);
+       twl4030_write_reg_cache(codec, TWL4030_REG_APLL_CTL, byte);
+
+       /* anti-pop when changing analog gain */
+       reg = twl4030_read_reg_cache(codec, TWL4030_REG_MISC_SET_1);
+       twl4030_write(codec, TWL4030_REG_MISC_SET_1,
+               reg | TWL4030_SMOOTH_ANAVOL_EN);
 
-       if (twl4030->codec_powered)
+       twl4030_write(codec, TWL4030_REG_OPTION,
+               TWL4030_ATXL1_EN | TWL4030_ATXR1_EN |
+               TWL4030_ARXL2_EN | TWL4030_ARXR2_EN);
+
+       /* REG_ARXR2_APGA_CTL reset according to the TRM: 0dB, DA_EN */
+       twl4030_write(codec, TWL4030_REG_ARXR2_APGA_CTL, 0x32);
+
+       /* Machine dependent setup */
+       if (!setup)
                return;
 
-       /* set CODECPDZ to turn on codec */
-       twl4030_codec_enable(codec, 1);
+       twl4030->digimic_delay = setup->digimic_delay;
+
+       /* Configuration for headset ramp delay from setup data */
+       if (setup->sysclk != twl4030->sysclk)
+               dev_warn(codec->dev,
+                               "Mismatch in APLL mclk: %u (configured: %u)\n",
+                               setup->sysclk, twl4030->sysclk);
+
+       reg = twl4030_read_reg_cache(codec, TWL4030_REG_HS_POPN_SET);
+       reg &= ~TWL4030_RAMP_DELAY;
+       reg |= (setup->ramp_delay_value << 2);
+       twl4030_write_reg_cache(codec, TWL4030_REG_HS_POPN_SET, reg);
 
        /* initiate offset cancellation */
-       anamicl = twl4030_read_reg_cache(codec, TWL4030_REG_ANAMICL);
+       twl4030_codec_enable(codec, 1);
+
+       reg = twl4030_read_reg_cache(codec, TWL4030_REG_ANAMICL);
+       reg &= ~TWL4030_OFFSET_CNCL_SEL;
+       reg |= setup->offset_cncl_path;
        twl4030_write(codec, TWL4030_REG_ANAMICL,
-               anamicl | TWL4030_CNCL_OFFSET_START);
+               reg | TWL4030_CNCL_OFFSET_START);
 
        /* wait for offset cancellation to complete */
        do {
@@ -310,23 +350,28 @@ static void twl4030_power_up(struct snd_soc_codec *codec)
        /* Make sure that the reg_cache has the same value as the HW */
        twl4030_write_reg_cache(codec, TWL4030_REG_ANAMICL, byte);
 
-       /* anti-pop when changing analog gain */
-       regmisc1 = twl4030_read_reg_cache(codec, TWL4030_REG_MISC_SET_1);
-       twl4030_write(codec, TWL4030_REG_MISC_SET_1,
-               regmisc1 | TWL4030_SMOOTH_ANAVOL_EN);
-
-       /* toggle CODECPDZ as per TRM */
        twl4030_codec_enable(codec, 0);
-       twl4030_codec_enable(codec, 1);
 }
 
-/*
- * Unconditional power down
- */
-static void twl4030_power_down(struct snd_soc_codec *codec)
+static void twl4030_apll_enable(struct snd_soc_codec *codec, int enable)
 {
-       /* power down */
-       twl4030_codec_enable(codec, 0);
+       struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec);
+       int status = -1;
+
+       if (enable) {
+               twl4030->apll_enabled++;
+               if (twl4030->apll_enabled == 1)
+                       status = twl4030_codec_enable_resource(
+                                                       TWL4030_CODEC_RES_APLL);
+       } else {
+               twl4030->apll_enabled--;
+               if (!twl4030->apll_enabled)
+                       status = twl4030_codec_disable_resource(
+                                                       TWL4030_CODEC_RES_APLL);
+       }
+
+       if (status >= 0)
+               twl4030_write_reg_cache(codec, TWL4030_REG_APLL_CTL, status);
 }
 
 /* Earpiece */
@@ -500,10 +545,11 @@ static const struct snd_kcontrol_new twl4030_dapm_abypassl2_control =
 static const struct snd_kcontrol_new twl4030_dapm_abypassv_control =
        SOC_DAPM_SINGLE("Switch", TWL4030_REG_VDL_APGA_CTL, 2, 1, 0);
 
-/* Digital bypass gain, 0 mutes the bypass */
+/* Digital bypass gain, mute instead of -30dB */
 static const unsigned int twl4030_dapm_dbypass_tlv[] = {
-       TLV_DB_RANGE_HEAD(2),
-       0, 3, TLV_DB_SCALE_ITEM(-2400, 0, 1),
+       TLV_DB_RANGE_HEAD(3),
+       0, 1, TLV_DB_SCALE_ITEM(-3000, 600, 1),
+       2, 3, TLV_DB_SCALE_ITEM(-2400, 0, 0),
        4, 7, TLV_DB_SCALE_ITEM(-1800, 600, 0),
 };
 
@@ -531,36 +577,6 @@ static const struct snd_kcontrol_new twl4030_dapm_dbypassv_control =
                        TWL4030_REG_VSTPGA, 0, 0x29, 0,
                        twl4030_dapm_dbypassv_tlv);
 
-static int micpath_event(struct snd_soc_dapm_widget *w,
-       struct snd_kcontrol *kcontrol, int event)
-{
-       struct soc_enum *e = (struct soc_enum *)w->kcontrols->private_value;
-       unsigned char adcmicsel, micbias_ctl;
-
-       adcmicsel = twl4030_read_reg_cache(w->codec, TWL4030_REG_ADCMICSEL);
-       micbias_ctl = twl4030_read_reg_cache(w->codec, TWL4030_REG_MICBIAS_CTL);
-       /* Prepare the bits for the given TX path:
-        * shift_l == 0: TX1 microphone path
-        * shift_l == 2: TX2 microphone path */
-       if (e->shift_l) {
-               /* TX2 microphone path */
-               if (adcmicsel & TWL4030_TX2IN_SEL)
-                       micbias_ctl |= TWL4030_MICBIAS2_CTL; /* digimic */
-               else
-                       micbias_ctl &= ~TWL4030_MICBIAS2_CTL;
-       } else {
-               /* TX1 microphone path */
-               if (adcmicsel & TWL4030_TX1IN_SEL)
-                       micbias_ctl |= TWL4030_MICBIAS1_CTL; /* digimic */
-               else
-                       micbias_ctl &= ~TWL4030_MICBIAS1_CTL;
-       }
-
-       twl4030_write(w->codec, TWL4030_REG_MICBIAS_CTL, micbias_ctl);
-
-       return 0;
-}
-
 /*
  * Output PGA builder:
  * Handle the muting and unmuting of the given output (turning off the
@@ -814,6 +830,16 @@ static int headsetrpga_event(struct snd_soc_dapm_widget *w,
        return 0;
 }
 
+static int digimic_event(struct snd_soc_dapm_widget *w,
+               struct snd_kcontrol *kcontrol, int event)
+{
+       struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(w->codec);
+
+       if (twl4030->digimic_delay)
+               mdelay(twl4030->digimic_delay);
+       return 0;
+}
+
 /*
  * Some of the gain controls in TWL (mostly those which are associated with
  * the outputs) are implemented in an interesting way:
@@ -1374,14 +1400,10 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = {
        /* Analog/Digital mic path selection.
           TX1 Left/Right: either analog Left/Right or Digimic0
           TX2 Left/Right: either analog Left/Right or Digimic1 */
-       SND_SOC_DAPM_MUX_E("TX1 Capture Route", SND_SOC_NOPM, 0, 0,
-               &twl4030_dapm_micpathtx1_control, micpath_event,
-               SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD|
-               SND_SOC_DAPM_POST_REG),
-       SND_SOC_DAPM_MUX_E("TX2 Capture Route", SND_SOC_NOPM, 0, 0,
-               &twl4030_dapm_micpathtx2_control, micpath_event,
-               SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD|
-               SND_SOC_DAPM_POST_REG),
+       SND_SOC_DAPM_MUX("TX1 Capture Route", SND_SOC_NOPM, 0, 0,
+               &twl4030_dapm_micpathtx1_control),
+       SND_SOC_DAPM_MUX("TX2 Capture Route", SND_SOC_NOPM, 0, 0,
+               &twl4030_dapm_micpathtx2_control),
 
        /* Analog input mixers for the capture amplifiers */
        SND_SOC_DAPM_MIXER("Analog Left",
@@ -1398,10 +1420,17 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = {
        SND_SOC_DAPM_PGA("ADC Physical Right",
                TWL4030_REG_AVADC_CTL, 1, 0, NULL, 0),
 
-       SND_SOC_DAPM_PGA("Digimic0 Enable",
-               TWL4030_REG_ADCMICSEL, 1, 0, NULL, 0),
-       SND_SOC_DAPM_PGA("Digimic1 Enable",
-               TWL4030_REG_ADCMICSEL, 3, 0, NULL, 0),
+       SND_SOC_DAPM_PGA_E("Digimic0 Enable",
+               TWL4030_REG_ADCMICSEL, 1, 0, NULL, 0,
+               digimic_event, SND_SOC_DAPM_POST_PMU),
+       SND_SOC_DAPM_PGA_E("Digimic1 Enable",
+               TWL4030_REG_ADCMICSEL, 3, 0, NULL, 0,
+               digimic_event, SND_SOC_DAPM_POST_PMU),
+
+       SND_SOC_DAPM_SUPPLY("micbias1 select", TWL4030_REG_MICBIAS_CTL, 5, 0,
+                           NULL, 0),
+       SND_SOC_DAPM_SUPPLY("micbias2 select", TWL4030_REG_MICBIAS_CTL, 6, 0,
+                           NULL, 0),
 
        SND_SOC_DAPM_MICBIAS("Mic Bias 1", TWL4030_REG_MICBIAS_CTL, 0, 0),
        SND_SOC_DAPM_MICBIAS("Mic Bias 2", TWL4030_REG_MICBIAS_CTL, 1, 0),
@@ -1419,8 +1448,11 @@ static const struct snd_soc_dapm_route intercon[] = {
        /* Supply for the digital part (APLL) */
        {"Digital Voice Playback Mixer", NULL, "APLL Enable"},
 
-       {"Digital R1 Playback Mixer", NULL, "AIF Enable"},
-       {"Digital L1 Playback Mixer", NULL, "AIF Enable"},
+       {"DAC Left1", NULL, "AIF Enable"},
+       {"DAC Right1", NULL, "AIF Enable"},
+       {"DAC Left2", NULL, "AIF Enable"},
+       {"DAC Right1", NULL, "AIF Enable"},
+
        {"Digital R2 Playback Mixer", NULL, "AIF Enable"},
        {"Digital L2 Playback Mixer", NULL, "AIF Enable"},
 
@@ -1491,10 +1523,10 @@ static const struct snd_soc_dapm_route intercon[] = {
 
        /* outputs */
        /* Must be always connected (for AIF and APLL) */
-       {"Virtual HiFi OUT", NULL, "Digital L1 Playback Mixer"},
-       {"Virtual HiFi OUT", NULL, "Digital R1 Playback Mixer"},
-       {"Virtual HiFi OUT", NULL, "Digital L2 Playback Mixer"},
-       {"Virtual HiFi OUT", NULL, "Digital R2 Playback Mixer"},
+       {"Virtual HiFi OUT", NULL, "DAC Left1"},
+       {"Virtual HiFi OUT", NULL, "DAC Right1"},
+       {"Virtual HiFi OUT", NULL, "DAC Left2"},
+       {"Virtual HiFi OUT", NULL, "DAC Right2"},
        /* Must be always connected (for APLL) */
        {"Virtual Voice OUT", NULL, "Digital Voice Playback Mixer"},
        /* Physical outputs */
@@ -1531,6 +1563,9 @@ static const struct snd_soc_dapm_route intercon[] = {
        {"Digimic0 Enable", NULL, "DIGIMIC0"},
        {"Digimic1 Enable", NULL, "DIGIMIC1"},
 
+       {"DIGIMIC0", NULL, "micbias1 select"},
+       {"DIGIMIC1", NULL, "micbias2 select"},
+
        /* TX1 Left capture path */
        {"TX1 Capture Route", "Analog", "ADC Physical Left"},
        {"TX1 Capture Route", "Digimic0", "Digimic0 Enable"},
@@ -1605,10 +1640,10 @@ static int twl4030_set_bias_level(struct snd_soc_codec *codec,
                break;
        case SND_SOC_BIAS_STANDBY:
                if (codec->bias_level == SND_SOC_BIAS_OFF)
-                       twl4030_power_up(codec);
+                       twl4030_codec_enable(codec, 1);
                break;
        case SND_SOC_BIAS_OFF:
-               twl4030_power_down(codec);
+               twl4030_codec_enable(codec, 0);
                break;
        }
        codec->bias_level = level;
@@ -1794,13 +1829,6 @@ static int twl4030_hw_params(struct snd_pcm_substream *substream,
                return -EINVAL;
        }
 
-       if (mode != old_mode) {
-               /* change rate and set CODECPDZ */
-               twl4030_codec_enable(codec, 0);
-               twl4030_write(codec, TWL4030_REG_CODEC_MODE, mode);
-               twl4030_codec_enable(codec, 1);
-       }
-
        /* sample size */
        old_format = twl4030_read_reg_cache(codec, TWL4030_REG_AUDIO_IF);
        format = old_format;
@@ -1818,16 +1846,20 @@ static int twl4030_hw_params(struct snd_pcm_substream *substream,
                return -EINVAL;
        }
 
-       if (format != old_format) {
-
-               /* clear CODECPDZ before changing format (codec requirement) */
-               twl4030_codec_enable(codec, 0);
-
-               /* change format */
-               twl4030_write(codec, TWL4030_REG_AUDIO_IF, format);
-
-               /* set CODECPDZ afterwards */
-               twl4030_codec_enable(codec, 1);
+       if (format != old_format || mode != old_mode) {
+               if (twl4030->codec_powered) {
+                       /*
+                        * If the codec is powered, than we need to toggle the
+                        * codec power.
+                        */
+                       twl4030_codec_enable(codec, 0);
+                       twl4030_write(codec, TWL4030_REG_CODEC_MODE, mode);
+                       twl4030_write(codec, TWL4030_REG_AUDIO_IF, format);
+                       twl4030_codec_enable(codec, 1);
+               } else {
+                       twl4030_write(codec, TWL4030_REG_CODEC_MODE, mode);
+                       twl4030_write(codec, TWL4030_REG_AUDIO_IF, format);
+               }
        }
 
        /* Store the important parameters for the DAI configuration and set
@@ -1877,6 +1909,7 @@ static int twl4030_set_dai_fmt(struct snd_soc_dai *codec_dai,
                             unsigned int fmt)
 {
        struct snd_soc_codec *codec = codec_dai->codec;
+       struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec);
        u8 old_format, format;
 
        /* get format */
@@ -1911,15 +1944,17 @@ static int twl4030_set_dai_fmt(struct snd_soc_dai *codec_dai,
        }
 
        if (format != old_format) {
-
-               /* clear CODECPDZ before changing format (codec requirement) */
-               twl4030_codec_enable(codec, 0);
-
-               /* change format */
-               twl4030_write(codec, TWL4030_REG_AUDIO_IF, format);
-
-               /* set CODECPDZ afterwards */
-               twl4030_codec_enable(codec, 1);
+               if (twl4030->codec_powered) {
+                       /*
+                        * If the codec is powered, than we need to toggle the
+                        * codec power.
+                        */
+                       twl4030_codec_enable(codec, 0);
+                       twl4030_write(codec, TWL4030_REG_AUDIO_IF, format);
+                       twl4030_codec_enable(codec, 1);
+               } else {
+                       twl4030_write(codec, TWL4030_REG_AUDIO_IF, format);
+               }
        }
 
        return 0;
@@ -2011,6 +2046,7 @@ static int twl4030_voice_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;
+       struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec);
        u8 old_mode, mode;
 
        /* Enable voice digital filters */
@@ -2035,10 +2071,17 @@ static int twl4030_voice_hw_params(struct snd_pcm_substream *substream,
        }
 
        if (mode != old_mode) {
-               /* change rate and set CODECPDZ */
-               twl4030_codec_enable(codec, 0);
-               twl4030_write(codec, TWL4030_REG_CODEC_MODE, mode);
-               twl4030_codec_enable(codec, 1);
+               if (twl4030->codec_powered) {
+                       /*
+                        * If the codec is powered, than we need to toggle the
+                        * codec power.
+                        */
+                       twl4030_codec_enable(codec, 0);
+                       twl4030_write(codec, TWL4030_REG_CODEC_MODE, mode);
+                       twl4030_codec_enable(codec, 1);
+               } else {
+                       twl4030_write(codec, TWL4030_REG_CODEC_MODE, mode);
+               }
        }
 
        return 0;
@@ -2068,6 +2111,7 @@ static int twl4030_voice_set_dai_fmt(struct snd_soc_dai *codec_dai,
                unsigned int fmt)
 {
        struct snd_soc_codec *codec = codec_dai->codec;
+       struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec);
        u8 old_format, format;
 
        /* get format */
@@ -2099,10 +2143,17 @@ static int twl4030_voice_set_dai_fmt(struct snd_soc_dai *codec_dai,
        }
 
        if (format != old_format) {
-               /* change format and set CODECPDZ */
-               twl4030_codec_enable(codec, 0);
-               twl4030_write(codec, TWL4030_REG_VOICE_IF, format);
-               twl4030_codec_enable(codec, 1);
+               if (twl4030->codec_powered) {
+                       /*
+                        * If the codec is powered, than we need to toggle the
+                        * codec power.
+                        */
+                       twl4030_codec_enable(codec, 0);
+                       twl4030_write(codec, TWL4030_REG_VOICE_IF, format);
+                       twl4030_codec_enable(codec, 1);
+               } else {
+                       twl4030_write(codec, TWL4030_REG_VOICE_IF, format);
+               }
        }
 
        return 0;
@@ -2202,31 +2253,15 @@ static struct snd_soc_codec *twl4030_codec;
 static int twl4030_soc_probe(struct platform_device *pdev)
 {
        struct snd_soc_device *socdev = platform_get_drvdata(pdev);
-       struct twl4030_setup_data *setup = socdev->codec_data;
        struct snd_soc_codec *codec;
-       struct twl4030_priv *twl4030;
        int ret;
 
        BUG_ON(!twl4030_codec);
 
        codec = twl4030_codec;
-       twl4030 = snd_soc_codec_get_drvdata(codec);
        socdev->card->codec = codec;
 
-       /* Configuration for headset ramp delay from setup data */
-       if (setup) {
-               unsigned char hs_pop;
-
-               if (setup->sysclk != twl4030->sysclk)
-                       dev_warn(&pdev->dev,
-                                "Mismatch in APLL mclk: %u (configured: %u)\n",
-                                setup->sysclk, twl4030->sysclk);
-
-               hs_pop = twl4030_read_reg_cache(codec, TWL4030_REG_HS_POPN_SET);
-               hs_pop &= ~TWL4030_RAMP_DELAY;
-               hs_pop |= (setup->ramp_delay_value << 2);
-               twl4030_write_reg_cache(codec, TWL4030_REG_HS_POPN_SET, hs_pop);
-       }
+       twl4030_init_chip(pdev);
 
        /* register pcms */
        ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
@@ -2247,6 +2282,8 @@ static int twl4030_soc_remove(struct platform_device *pdev)
        struct snd_soc_device *socdev = platform_get_drvdata(pdev);
        struct snd_soc_codec *codec = socdev->card->codec;
 
+       /* Reset registers to their chip default before leaving */
+       twl4030_reset_registers(codec);
        twl4030_set_bias_level(codec, SND_SOC_BIAS_OFF);
        snd_soc_free_pcms(socdev);
        snd_soc_dapm_free(socdev);
@@ -2287,6 +2324,7 @@ static int __devinit twl4030_codec_probe(struct platform_device *pdev)
        codec->read = twl4030_read_reg_cache;
        codec->write = twl4030_write;
        codec->set_bias_level = twl4030_set_bias_level;
+       codec->idle_bias_off = 1;
        codec->dai = twl4030_dai;
        codec->num_dai = ARRAY_SIZE(twl4030_dai);
        codec->reg_cache_size = sizeof(twl4030_reg);
@@ -2302,9 +2340,7 @@ static int __devinit twl4030_codec_probe(struct platform_device *pdev)
 
        /* Set the defaults, and power up the codec */
        twl4030->sysclk = twl4030_codec_get_mclk() / 1000;
-       twl4030_init_chip(codec);
        codec->bias_level = SND_SOC_BIAS_OFF;
-       twl4030_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
 
        ret = snd_soc_register_codec(codec);
        if (ret != 0) {
@@ -2322,7 +2358,7 @@ static int __devinit twl4030_codec_probe(struct platform_device *pdev)
        return 0;
 
 error_codec:
-       twl4030_power_down(codec);
+       twl4030_codec_enable(codec, 0);
        kfree(codec->reg_cache);
 error_cache:
        kfree(twl4030);
index f206d242ca3130a932bae30aa3074bd2fb28b98f..6c57430f6e2440c2e94e8c6cd8ae5ecd16fbbc5b 100644 (file)
@@ -41,7 +41,11 @@ extern struct snd_soc_codec_device soc_codec_dev_twl4030;
 
 struct twl4030_setup_data {
        unsigned int ramp_delay_value;
+       unsigned int digimic_delay; /* in ms */
        unsigned int sysclk;
+       unsigned int offset_cncl_path;
+       unsigned int check_defaults:1;
+       unsigned int reset_registers:1;
        unsigned int hs_extmute:1;
        void (*set_hs_extmute)(int mute);
 };
index af36346ff3364bdb37b93b620e3cae6f4fbc6444..64a807f1a8a1812e4b01d891b8809f69ce1bf1f9 100644 (file)
@@ -360,6 +360,13 @@ static int headset_power_mode(struct snd_soc_codec *codec, int high_perf)
        return 0;
 }
 
+static int twl6040_hs_dac_event(struct snd_soc_dapm_widget *w,
+                       struct snd_kcontrol *kcontrol, int event)
+{
+       msleep(1);
+       return 0;
+}
+
 static int twl6040_power_mode_event(struct snd_soc_dapm_widget *w,
                        struct snd_kcontrol *kcontrol, int event)
 {
@@ -371,6 +378,8 @@ static int twl6040_power_mode_event(struct snd_soc_dapm_widget *w,
        else
                priv->non_lp--;
 
+       msleep(1);
+
        return 0;
 }
 
@@ -471,20 +480,6 @@ static const struct snd_kcontrol_new hfdacl_switch_controls =
 static const struct snd_kcontrol_new hfdacr_switch_controls =
        SOC_DAPM_SINGLE("Switch", TWL6040_REG_HFRCTL, 2, 1, 0);
 
-/* Headset driver switches */
-static const struct snd_kcontrol_new hsl_driver_switch_controls =
-       SOC_DAPM_SINGLE("Switch", TWL6040_REG_HSLCTL, 2, 1, 0);
-
-static const struct snd_kcontrol_new hsr_driver_switch_controls =
-       SOC_DAPM_SINGLE("Switch", TWL6040_REG_HSRCTL, 2, 1, 0);
-
-/* Handsfree driver switches */
-static const struct snd_kcontrol_new hfl_driver_switch_controls =
-       SOC_DAPM_SINGLE("Switch", TWL6040_REG_HFLCTL, 4, 1, 0);
-
-static const struct snd_kcontrol_new hfr_driver_switch_controls =
-       SOC_DAPM_SINGLE("Switch", TWL6040_REG_HFRCTL, 4, 1, 0);
-
 static const struct snd_kcontrol_new ep_driver_switch_controls =
        SOC_DAPM_SINGLE("Switch", TWL6040_REG_EARCTL, 0, 1, 0);
 
@@ -548,10 +543,14 @@ static const struct snd_soc_dapm_widget twl6040_dapm_widgets[] = {
                        TWL6040_REG_DMICBCTL, 4, 0),
 
        /* DACs */
-       SND_SOC_DAPM_DAC("HSDAC Left", "Headset Playback",
-                       TWL6040_REG_HSLCTL, 0, 0),
-       SND_SOC_DAPM_DAC("HSDAC Right", "Headset Playback",
-                       TWL6040_REG_HSRCTL, 0, 0),
+       SND_SOC_DAPM_DAC_E("HSDAC Left", "Headset Playback",
+                       TWL6040_REG_HSLCTL, 0, 0,
+                       twl6040_hs_dac_event,
+                       SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+       SND_SOC_DAPM_DAC_E("HSDAC Right", "Headset Playback",
+                       TWL6040_REG_HSRCTL, 0, 0,
+                       twl6040_hs_dac_event,
+                       SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
        SND_SOC_DAPM_DAC_E("HFDAC Left", "Handsfree Playback",
                        TWL6040_REG_HFLCTL, 0, 0,
                        twl6040_power_mode_event,
@@ -571,18 +570,19 @@ static const struct snd_soc_dapm_widget twl6040_dapm_widgets[] = {
        SND_SOC_DAPM_SWITCH("HFDAC Right Playback",
                        SND_SOC_NOPM, 0, 0, &hfdacr_switch_controls),
 
-       SND_SOC_DAPM_SWITCH("Headset Left Driver",
-                       SND_SOC_NOPM, 0, 0, &hsl_driver_switch_controls),
-       SND_SOC_DAPM_SWITCH("Headset Right Driver",
-                       SND_SOC_NOPM, 0, 0, &hsr_driver_switch_controls),
-       SND_SOC_DAPM_SWITCH_E("Handsfree Left Driver",
-                       SND_SOC_NOPM, 0, 0, &hfl_driver_switch_controls,
+       /* Analog playback drivers */
+       SND_SOC_DAPM_PGA_E("Handsfree Left Driver",
+                       TWL6040_REG_HFLCTL, 4, 0, NULL, 0,
                        twl6040_power_mode_event,
                        SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
-       SND_SOC_DAPM_SWITCH_E("Handsfree Right Driver",
-                       SND_SOC_NOPM, 0, 0, &hfr_driver_switch_controls,
+       SND_SOC_DAPM_PGA_E("Handsfree Right Driver",
+                       TWL6040_REG_HFRCTL, 4, 0, NULL, 0,
                        twl6040_power_mode_event,
                        SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+       SND_SOC_DAPM_PGA("Headset Left Driver",
+                       TWL6040_REG_HSLCTL, 2, 0, NULL, 0),
+       SND_SOC_DAPM_PGA("Headset Right Driver",
+                       TWL6040_REG_HSRCTL, 2, 0, NULL, 0),
        SND_SOC_DAPM_SWITCH_E("Earphone Driver",
                        SND_SOC_NOPM, 0, 0, &ep_driver_switch_controls,
                        twl6040_power_mode_event,
@@ -616,8 +616,8 @@ static const struct snd_soc_dapm_route intercon[] = {
        {"HSDAC Left Playback", "Switch", "HSDAC Left"},
        {"HSDAC Right Playback", "Switch", "HSDAC Right"},
 
-       {"Headset Left Driver", "Switch", "HSDAC Left Playback"},
-       {"Headset Right Driver", "Switch", "HSDAC Right Playback"},
+       {"Headset Left Driver", NULL, "HSDAC Left Playback"},
+       {"Headset Right Driver", NULL, "HSDAC Right Playback"},
 
        {"HSOL", NULL, "Headset Left Driver"},
        {"HSOR", NULL, "Headset Right Driver"},
@@ -928,7 +928,7 @@ static int twl6040_set_dai_sysclk(struct snd_soc_dai *codec_dai,
                case 19200000:
                        /* mclk input, pll disabled */
                        hppllctl |= TWL6040_MCLK_19200KHZ |
-                                   TWL6040_HPLLSQRBP |
+                                   TWL6040_HPLLSQRENA |
                                    TWL6040_HPLLBP;
                        break;
                case 26000000:
index 28aac53c97bb799e1b8b731fd75053a15799d4c9..f3b4c1d6a82d1927975d80c303a62d38448bb238 100644 (file)
 #include "uda134x.h"
 
 
-#define POWER_OFF_ON_STANDBY 1
-/*
-  ALSA SOC usually puts the device in standby mode when it's not used
-  for sometime. If you define POWER_OFF_ON_STANDBY the driver will
-  turn off the ADC/DAC when this callback is invoked and turn it back
-  on when needed. Unfortunately this will result in a very light bump
-  (it can be audible only with good earphones). If this bothers you
-  just comment this line, you will have slightly higher power
-  consumption . Please note that sending the L3 command for ADC is
-  enough to make the bump, so it doesn't make difference if you
-  completely take off power from the codec.
- */
-
 #define UDA134X_RATES SNDRV_PCM_RATE_8000_48000
 #define UDA134X_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE | \
                SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_S20_3LE)
@@ -58,7 +45,7 @@ static const char uda134x_reg[UDA134X_REGS_NUM] = {
        /* Extended address registers */
        0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,
        /* Status, data regs */
-       0x00, 0x83, 0x00, 0x40, 0x80, 0x00,
+       0x00, 0x83, 0x00, 0x40, 0x80, 0xC0, 0x00,
 };
 
 /*
@@ -117,6 +104,7 @@ static int uda134x_write(struct snd_soc_codec *codec, unsigned int reg,
        case UDA134X_DATA000:
        case UDA134X_DATA001:
        case UDA134X_DATA010:
+       case UDA134X_DATA011:
                addr = UDA134X_DATA0_ADDR;
                break;
        case UDA134X_DATA1:
@@ -353,8 +341,22 @@ static int uda134x_set_bias_level(struct snd_soc_codec *codec,
        switch (level) {
        case SND_SOC_BIAS_ON:
                /* ADC, DAC on */
-               reg = uda134x_read_reg_cache(codec, UDA134X_STATUS1);
-               uda134x_write(codec, UDA134X_STATUS1, reg | 0x03);
+               switch (pd->model) {
+               case UDA134X_UDA1340:
+               case UDA134X_UDA1344:
+               case UDA134X_UDA1345:
+                       reg = uda134x_read_reg_cache(codec, UDA134X_DATA011);
+                       uda134x_write(codec, UDA134X_DATA011, reg | 0x03);
+                       break;
+               case UDA134X_UDA1341:
+                       reg = uda134x_read_reg_cache(codec, UDA134X_STATUS1);
+                       uda134x_write(codec, UDA134X_STATUS1, reg | 0x03);
+                       break;
+               default:
+                       printk(KERN_ERR "UDA134X SoC codec: "
+                              "unsupported model %d\n", pd->model);
+                       return -EINVAL;
+               }
                break;
        case SND_SOC_BIAS_PREPARE:
                /* power on */
@@ -367,8 +369,22 @@ static int uda134x_set_bias_level(struct snd_soc_codec *codec,
                break;
        case SND_SOC_BIAS_STANDBY:
                /* ADC, DAC power off */
-               reg = uda134x_read_reg_cache(codec, UDA134X_STATUS1);
-               uda134x_write(codec, UDA134X_STATUS1, reg & ~(0x03));
+               switch (pd->model) {
+               case UDA134X_UDA1340:
+               case UDA134X_UDA1344:
+               case UDA134X_UDA1345:
+                       reg = uda134x_read_reg_cache(codec, UDA134X_DATA011);
+                       uda134x_write(codec, UDA134X_DATA011, reg & ~(0x03));
+                       break;
+               case UDA134X_UDA1341:
+                       reg = uda134x_read_reg_cache(codec, UDA134X_STATUS1);
+                       uda134x_write(codec, UDA134X_STATUS1, reg & ~(0x03));
+                       break;
+               default:
+                       printk(KERN_ERR "UDA134X SoC codec: "
+                              "unsupported model %d\n", pd->model);
+                       return -EINVAL;
+               }
                break;
        case SND_SOC_BIAS_OFF:
                /* power off */
@@ -531,9 +547,7 @@ static int uda134x_soc_probe(struct platform_device *pdev)
        codec->num_dai = 1;
        codec->read = uda134x_read_reg_cache;
        codec->write = uda134x_write;
-#ifdef POWER_OFF_ON_STANDBY
-       codec->set_bias_level = uda134x_set_bias_level;
-#endif
+
        INIT_LIST_HEAD(&codec->dapm_widgets);
        INIT_LIST_HEAD(&codec->dapm_paths);
 
@@ -544,6 +558,14 @@ static int uda134x_soc_probe(struct platform_device *pdev)
 
        uda134x_reset(codec);
 
+       if (pd->is_powered_on_standby) {
+               codec->set_bias_level = NULL;
+               uda134x_set_bias_level(codec, SND_SOC_BIAS_ON);
+       } else {
+               codec->set_bias_level = uda134x_set_bias_level;
+               uda134x_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+       }
+
        /* register pcms */
        ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
        if (ret < 0) {
index 94f440490b31f335e9e3b57231e4bce1a7202a21..205f03b3eaf81674de74f4a60e8ac28cddbf4c83 100644 (file)
 #define UDA134X_DATA000 10
 #define UDA134X_DATA001 11
 #define UDA134X_DATA010 12
-#define UDA134X_DATA1  13
+#define UDA134X_DATA011 13
+#define UDA134X_DATA1   14
 
-#define UDA134X_REGS_NUM 14
+#define UDA134X_REGS_NUM 15
 
 #define STATUS0_DAIFMT_MASK (~(7<<1))
 #define STATUS0_SYSCLK_MASK (~(3<<4))
index 002e289d1255f5044afb6c697edd7923effb352f..4bcd168794e1de1e0653d9502730dfba58b3c5fe 100644 (file)
@@ -795,6 +795,8 @@ static int __devinit wm2000_i2c_probe(struct i2c_client *i2c,
 
        dev_set_drvdata(&i2c->dev, wm2000);
        wm2000->anc_eng_ena = 1;
+       wm2000->anc_active = 1;
+       wm2000->spk_ena = 1;
        wm2000->i2c = i2c;
 
        wm2000_reset(wm2000);
index 37242a7d307751d9876acee1c4e955e3029262b7..0ad039b4adf5fd0b12e587a115f91196e921b331 100644 (file)
@@ -482,7 +482,8 @@ static int wm8523_register(struct wm8523_priv *wm8523,
 
        if (wm8523_codec) {
                dev_err(codec->dev, "Another WM8523 is registered\n");
-               return -EINVAL;
+               ret = -EINVAL;
+               goto err;
        }
 
        mutex_init(&codec->mutex);
@@ -570,18 +571,19 @@ static int wm8523_register(struct wm8523_priv *wm8523,
        ret = snd_soc_register_codec(codec);
        if (ret != 0) {
                dev_err(codec->dev, "Failed to register codec: %d\n", ret);
-               return ret;
+               goto err_enable;
        }
 
        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;
+               goto err_codec;
        }
 
        return 0;
 
+err_codec:
+       snd_soc_unregister_codec(codec);
 err_enable:
        regulator_bulk_disable(ARRAY_SIZE(wm8523->supplies), wm8523->supplies);
 err_get:
index effb14eee7d4cfddab8e191798cbea105f0cde63..e2dba07f0260d77279e32b9a433220465d6071f9 100644 (file)
@@ -439,7 +439,8 @@ static int wm8711_register(struct wm8711_priv *wm8711,
 
        if (wm8711_codec) {
                dev_err(codec->dev, "Another WM8711 is registered\n");
-               return -EINVAL;
+               ret = -EINVAL;
+               goto err;
        }
 
        mutex_init(&codec->mutex);
diff --git a/sound/soc/codecs/wm8741.c b/sound/soc/codecs/wm8741.c
new file mode 100644 (file)
index 0000000..b9ea890
--- /dev/null
@@ -0,0 +1,579 @@
+/*
+ * wm8741.c  --  WM8741 ALSA SoC Audio driver
+ *
+ * Copyright 2010 Wolfson Microelectronics plc
+ *
+ * Author: Ian Lartey <ian@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 <linux/slab.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 "wm8741.h"
+
+static struct snd_soc_codec *wm8741_codec;
+struct snd_soc_codec_device soc_codec_dev_wm8741;
+
+#define WM8741_NUM_SUPPLIES 2
+static const char *wm8741_supply_names[WM8741_NUM_SUPPLIES] = {
+       "AVDD",
+       "DVDD",
+};
+
+#define WM8741_NUM_RATES 4
+
+/* codec private data */
+struct wm8741_priv {
+       struct snd_soc_codec codec;
+       u16 reg_cache[WM8741_REGISTER_COUNT];
+       struct regulator_bulk_data supplies[WM8741_NUM_SUPPLIES];
+       unsigned int sysclk;
+       unsigned int rate_constraint_list[WM8741_NUM_RATES];
+       struct snd_pcm_hw_constraint_list rate_constraint;
+};
+
+static const u16 wm8741_reg_defaults[WM8741_REGISTER_COUNT] = {
+       0x0000,     /* R0  - DACLLSB Attenuation */
+       0x0000,     /* R1  - DACLMSB Attenuation */
+       0x0000,     /* R2  - DACRLSB Attenuation */
+       0x0000,     /* R3  - DACRMSB Attenuation */
+       0x0000,     /* R4  - Volume Control */
+       0x000A,     /* R5  - Format Control */
+       0x0000,     /* R6  - Filter Control */
+       0x0000,     /* R7  - Mode Control 1 */
+       0x0002,     /* R8  - Mode Control 2 */
+       0x0000,     /* R9  - Reset */
+       0x0002,     /* R32 - ADDITONAL_CONTROL_1 */
+};
+
+
+static int wm8741_reset(struct snd_soc_codec *codec)
+{
+       return snd_soc_write(codec, WM8741_RESET, 0);
+}
+
+static const DECLARE_TLV_DB_SCALE(dac_tlv_fine, -12700, 13, 0);
+static const DECLARE_TLV_DB_SCALE(dac_tlv, -12700, 400, 0);
+
+static const struct snd_kcontrol_new wm8741_snd_controls[] = {
+SOC_DOUBLE_R_TLV("Fine Playback Volume", WM8741_DACLLSB_ATTENUATION,
+                WM8741_DACRLSB_ATTENUATION, 1, 255, 1, dac_tlv_fine),
+SOC_DOUBLE_R_TLV("Playback Volume", WM8741_DACLMSB_ATTENUATION,
+                WM8741_DACRMSB_ATTENUATION, 0, 511, 1, dac_tlv),
+};
+
+static const struct snd_soc_dapm_widget wm8741_dapm_widgets[] = {
+SND_SOC_DAPM_DAC("DACL", "Playback", SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_DAC("DACR", "Playback", SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_OUTPUT("VOUTLP"),
+SND_SOC_DAPM_OUTPUT("VOUTLN"),
+SND_SOC_DAPM_OUTPUT("VOUTRP"),
+SND_SOC_DAPM_OUTPUT("VOUTRN"),
+};
+
+static const struct snd_soc_dapm_route intercon[] = {
+       { "VOUTLP", NULL, "DACL" },
+       { "VOUTLN", NULL, "DACL" },
+       { "VOUTRP", NULL, "DACR" },
+       { "VOUTRN", NULL, "DACR" },
+};
+
+static int wm8741_add_widgets(struct snd_soc_codec *codec)
+{
+       snd_soc_dapm_new_controls(codec, wm8741_dapm_widgets,
+                                 ARRAY_SIZE(wm8741_dapm_widgets));
+
+       snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon));
+
+       return 0;
+}
+
+static struct {
+       int value;
+       int ratio;
+} lrclk_ratios[WM8741_NUM_RATES] = {
+       { 1, 256 },
+       { 2, 384 },
+       { 3, 512 },
+       { 4, 768 },
+};
+
+
+static int wm8741_startup(struct snd_pcm_substream *substream,
+                         struct snd_soc_dai *dai)
+{
+       struct snd_soc_codec *codec = dai->codec;
+       struct wm8741_priv *wm8741 = snd_soc_codec_get_drvdata(codec);
+
+       /* The set of sample rates that can be supported depends on the
+        * MCLK supplied to the CODEC - enforce this.
+        */
+       if (!wm8741->sysclk) {
+               dev_err(codec->dev,
+                       "No MCLK configured, call set_sysclk() on init\n");
+               return -EINVAL;
+       }
+
+       snd_pcm_hw_constraint_list(substream->runtime, 0,
+                                  SNDRV_PCM_HW_PARAM_RATE,
+                                  &wm8741->rate_constraint);
+
+       return 0;
+}
+
+static int wm8741_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 wm8741_priv *wm8741 = snd_soc_codec_get_drvdata(codec);
+       u16 iface = snd_soc_read(codec, WM8741_FORMAT_CONTROL) & 0x1FC;
+       int i;
+
+       /* Find a supported LRCLK ratio */
+       for (i = 0; i < ARRAY_SIZE(lrclk_ratios); i++) {
+               if (wm8741->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",
+                       wm8741->sysclk / params_rate(params));
+               return -EINVAL;
+       }
+
+       /* bit size */
+       switch (params_format(params)) {
+       case SNDRV_PCM_FORMAT_S16_LE:
+               break;
+       case SNDRV_PCM_FORMAT_S20_3LE:
+               iface |= 0x0001;
+               break;
+       case SNDRV_PCM_FORMAT_S24_LE:
+               iface |= 0x0002;
+               break;
+       case SNDRV_PCM_FORMAT_S32_LE:
+               iface |= 0x0003;
+               break;
+       default:
+               dev_dbg(codec->dev, "wm8741_hw_params:    Unsupported bit size param = %d",
+                       params_format(params));
+               return -EINVAL;
+       }
+
+       dev_dbg(codec->dev, "wm8741_hw_params:    bit size param = %d",
+               params_format(params));
+
+       snd_soc_write(codec, WM8741_FORMAT_CONTROL, iface);
+       return 0;
+}
+
+static int wm8741_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 wm8741_priv *wm8741 = snd_soc_codec_get_drvdata(codec);
+       unsigned int val;
+       int i;
+
+       dev_dbg(codec->dev, "wm8741_set_dai_sysclk info: freq=%dHz\n", freq);
+
+       wm8741->sysclk = freq;
+
+       wm8741->rate_constraint.count = 0;
+
+       for (i = 0; i < ARRAY_SIZE(lrclk_ratios); i++) {
+               dev_dbg(codec->dev, "index = %d, ratio = %d, freq = %d",
+                               i, lrclk_ratios[i].ratio, freq);
+
+               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 32000:
+               case 44100:
+               case 48000:
+               case 64000:
+               case 88200:
+               case 96000:
+                       dev_dbg(codec->dev, "Supported sample rate: %dHz\n",
+                               val);
+                       wm8741->rate_constraint_list[i] = val;
+                       wm8741->rate_constraint.count++;
+                       break;
+               default:
+                       dev_dbg(codec->dev, "Skipping sample rate: %dHz\n",
+                               val);
+               }
+       }
+
+       /* Need at least one supported rate... */
+       if (wm8741->rate_constraint.count == 0)
+               return -EINVAL;
+
+       return 0;
+}
+
+static int wm8741_set_dai_fmt(struct snd_soc_dai *codec_dai,
+               unsigned int fmt)
+{
+       struct snd_soc_codec *codec = codec_dai->codec;
+       u16 iface = snd_soc_read(codec, WM8741_FORMAT_CONTROL) & 0x1C3;
+
+       /* check master/slave audio interface */
+       switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+       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 |= 0x0008;
+               break;
+       case SND_SOC_DAIFMT_RIGHT_J:
+               break;
+       case SND_SOC_DAIFMT_LEFT_J:
+               iface |= 0x0004;
+               break;
+       case SND_SOC_DAIFMT_DSP_A:
+               iface |= 0x0003;
+               break;
+       case SND_SOC_DAIFMT_DSP_B:
+               iface |= 0x0013;
+               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 |= 0x0010;
+               break;
+       case SND_SOC_DAIFMT_IB_NF:
+               iface |= 0x0020;
+               break;
+       case SND_SOC_DAIFMT_NB_IF:
+               iface |= 0x0030;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+
+       dev_dbg(codec->dev, "wm8741_set_dai_fmt:    Format=%x, Clock Inv=%x\n",
+                               fmt & SND_SOC_DAIFMT_FORMAT_MASK,
+                               ((fmt & SND_SOC_DAIFMT_INV_MASK)));
+
+       snd_soc_write(codec, WM8741_FORMAT_CONTROL, iface);
+       return 0;
+}
+
+#define WM8741_RATES (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
+                       SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | \
+                       SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 | \
+                       SNDRV_PCM_RATE_192000)
+
+#define WM8741_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 wm8741_dai_ops = {
+       .startup        = wm8741_startup,
+       .hw_params      = wm8741_hw_params,
+       .set_sysclk     = wm8741_set_dai_sysclk,
+       .set_fmt        = wm8741_set_dai_fmt,
+};
+
+struct snd_soc_dai wm8741_dai = {
+       .name = "WM8741",
+       .playback = {
+               .stream_name = "Playback",
+               .channels_min = 2,  /* Mono modes not yet supported */
+               .channels_max = 2,
+               .rates = WM8741_RATES,
+               .formats = WM8741_FORMATS,
+       },
+       .ops = &wm8741_dai_ops,
+};
+EXPORT_SYMBOL_GPL(wm8741_dai);
+
+#ifdef CONFIG_PM
+static int wm8741_resume(struct platform_device *pdev)
+{
+       struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+       struct snd_soc_codec *codec = socdev->card->codec;
+       u16 *cache = codec->reg_cache;
+       int i;
+
+       /* RESTORE REG Cache */
+       for (i = 0; i < WM8741_REGISTER_COUNT; i++) {
+               if (cache[i] == wm8741_reg_defaults[i] || WM8741_RESET == i)
+                       continue;
+               snd_soc_write(codec, i, cache[i]);
+       }
+       return 0;
+}
+#else
+#define wm8741_suspend NULL
+#define wm8741_resume NULL
+#endif
+
+static int wm8741_probe(struct platform_device *pdev)
+{
+       struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+       struct snd_soc_codec *codec;
+       int ret = 0;
+
+       if (wm8741_codec == NULL) {
+               dev_err(&pdev->dev, "Codec device not registered\n");
+               return -ENODEV;
+       }
+
+       socdev->card->codec = wm8741_codec;
+       codec = wm8741_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, wm8741_snd_controls,
+                            ARRAY_SIZE(wm8741_snd_controls));
+       wm8741_add_widgets(codec);
+
+       return ret;
+
+pcm_err:
+       return ret;
+}
+
+static int wm8741_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_wm8741 = {
+       .probe =        wm8741_probe,
+       .remove =       wm8741_remove,
+       .resume =       wm8741_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_wm8741);
+
+static int wm8741_register(struct wm8741_priv *wm8741,
+                          enum snd_soc_control_type control)
+{
+       int ret;
+       struct snd_soc_codec *codec = &wm8741->codec;
+       int i;
+
+       if (wm8741_codec) {
+               dev_err(codec->dev, "Another WM8741 is registered\n");
+               return -EINVAL;
+       }
+
+       mutex_init(&codec->mutex);
+       INIT_LIST_HEAD(&codec->dapm_widgets);
+       INIT_LIST_HEAD(&codec->dapm_paths);
+
+       snd_soc_codec_set_drvdata(codec, wm8741);
+       codec->name = "WM8741";
+       codec->owner = THIS_MODULE;
+       codec->bias_level = SND_SOC_BIAS_OFF;
+       codec->set_bias_level = NULL;
+       codec->dai = &wm8741_dai;
+       codec->num_dai = 1;
+       codec->reg_cache_size = WM8741_REGISTER_COUNT;
+       codec->reg_cache = &wm8741->reg_cache;
+
+       wm8741->rate_constraint.list = &wm8741->rate_constraint_list[0];
+       wm8741->rate_constraint.count =
+               ARRAY_SIZE(wm8741->rate_constraint_list);
+
+       memcpy(codec->reg_cache, wm8741_reg_defaults,
+               sizeof(wm8741->reg_cache));
+
+       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(wm8741->supplies); i++)
+               wm8741->supplies[i].supply = wm8741_supply_names[i];
+
+       ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(wm8741->supplies),
+                                wm8741->supplies);
+       if (ret != 0) {
+               dev_err(codec->dev, "Failed to request supplies: %d\n", ret);
+               goto err;
+       }
+
+       ret = regulator_bulk_enable(ARRAY_SIZE(wm8741->supplies),
+                                   wm8741->supplies);
+       if (ret != 0) {
+               dev_err(codec->dev, "Failed to enable supplies: %d\n", ret);
+               goto err_get;
+       }
+
+       ret = wm8741_reset(codec);
+       if (ret < 0) {
+               dev_err(codec->dev, "Failed to issue reset\n");
+               goto err_enable;
+       }
+
+       wm8741_dai.dev = codec->dev;
+
+       /* Change some default settings - latch VU */
+       wm8741->reg_cache[WM8741_DACLLSB_ATTENUATION] |= WM8741_UPDATELL;
+       wm8741->reg_cache[WM8741_DACLMSB_ATTENUATION] |= WM8741_UPDATELM;
+       wm8741->reg_cache[WM8741_DACRLSB_ATTENUATION] |= WM8741_UPDATERL;
+       wm8741->reg_cache[WM8741_DACRLSB_ATTENUATION] |= WM8741_UPDATERM;
+
+       wm8741_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(&wm8741_dai);
+       if (ret != 0) {
+               dev_err(codec->dev, "Failed to register DAI: %d\n", ret);
+               snd_soc_unregister_codec(codec);
+               return ret;
+       }
+
+       dev_dbg(codec->dev, "Successful registration\n");
+       return 0;
+
+err_enable:
+       regulator_bulk_disable(ARRAY_SIZE(wm8741->supplies), wm8741->supplies);
+
+err_get:
+       regulator_bulk_free(ARRAY_SIZE(wm8741->supplies), wm8741->supplies);
+
+err:
+       kfree(wm8741);
+       return ret;
+}
+
+static void wm8741_unregister(struct wm8741_priv *wm8741)
+{
+       regulator_bulk_free(ARRAY_SIZE(wm8741->supplies), wm8741->supplies);
+
+       snd_soc_unregister_dai(&wm8741_dai);
+       snd_soc_unregister_codec(&wm8741->codec);
+       kfree(wm8741);
+       wm8741_codec = NULL;
+}
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static __devinit int wm8741_i2c_probe(struct i2c_client *i2c,
+                                     const struct i2c_device_id *id)
+{
+       struct wm8741_priv *wm8741;
+       struct snd_soc_codec *codec;
+
+       wm8741 = kzalloc(sizeof(struct wm8741_priv), GFP_KERNEL);
+       if (wm8741 == NULL)
+               return -ENOMEM;
+
+       codec = &wm8741->codec;
+       codec->hw_write = (hw_write_t)i2c_master_send;
+
+       i2c_set_clientdata(i2c, wm8741);
+       codec->control_data = i2c;
+
+       codec->dev = &i2c->dev;
+
+       return wm8741_register(wm8741, SND_SOC_I2C);
+}
+
+static __devexit int wm8741_i2c_remove(struct i2c_client *client)
+{
+       struct wm8741_priv *wm8741 = i2c_get_clientdata(client);
+       wm8741_unregister(wm8741);
+       return 0;
+}
+
+static const struct i2c_device_id wm8741_i2c_id[] = {
+       { "wm8741", 0 },
+       { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8741_i2c_id);
+
+
+static struct i2c_driver wm8741_i2c_driver = {
+       .driver = {
+               .name = "WM8741",
+               .owner = THIS_MODULE,
+       },
+       .probe =    wm8741_i2c_probe,
+       .remove =   __devexit_p(wm8741_i2c_remove),
+       .id_table = wm8741_i2c_id,
+};
+#endif
+
+static int __init wm8741_modinit(void)
+{
+       int ret;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+       ret = i2c_add_driver(&wm8741_i2c_driver);
+       if (ret != 0) {
+               printk(KERN_ERR "Failed to register WM8741 I2C driver: %d\n",
+                      ret);
+       }
+#endif
+       return 0;
+}
+module_init(wm8741_modinit);
+
+static void __exit wm8741_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+       i2c_del_driver(&wm8741_i2c_driver);
+#endif
+}
+module_exit(wm8741_exit);
+
+MODULE_DESCRIPTION("ASoC WM8741 driver");
+MODULE_AUTHOR("Ian Lartey <ian@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8741.h b/sound/soc/codecs/wm8741.h
new file mode 100644 (file)
index 0000000..fdef6ec
--- /dev/null
@@ -0,0 +1,214 @@
+/*
+ * wm8741.h  --  WM8423 ASoC driver
+ *
+ * Copyright 2010 Wolfson Microelectronics, plc
+ *
+ * Author: Ian Lartey <ian@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 _WM8741_H
+#define _WM8741_H
+
+/*
+ * Register values.
+ */
+#define WM8741_DACLLSB_ATTENUATION              0x00
+#define WM8741_DACLMSB_ATTENUATION              0x01
+#define WM8741_DACRLSB_ATTENUATION              0x02
+#define WM8741_DACRMSB_ATTENUATION              0x03
+#define WM8741_VOLUME_CONTROL                   0x04
+#define WM8741_FORMAT_CONTROL                   0x05
+#define WM8741_FILTER_CONTROL                   0x06
+#define WM8741_MODE_CONTROL_1                   0x07
+#define WM8741_MODE_CONTROL_2                   0x08
+#define WM8741_RESET                            0x09
+#define WM8741_ADDITIONAL_CONTROL_1             0x20
+
+#define WM8741_REGISTER_COUNT                   11
+#define WM8741_MAX_REGISTER                     0x20
+
+/*
+ * Field Definitions.
+ */
+
+/*
+ * R0 (0x00) - DACLLSB_ATTENUATION
+ */
+#define WM8741_UPDATELL                         0x0020  /* UPDATELL */
+#define WM8741_UPDATELL_MASK                    0x0020  /* UPDATELL */
+#define WM8741_UPDATELL_SHIFT                        5  /* UPDATELL */
+#define WM8741_UPDATELL_WIDTH                        1  /* UPDATELL */
+#define WM8741_LAT_4_0_MASK                     0x001F  /* LAT[4:0] - [4:0] */
+#define WM8741_LAT_4_0_SHIFT                         0  /* LAT[4:0] - [4:0] */
+#define WM8741_LAT_4_0_WIDTH                         5  /* LAT[4:0] - [4:0] */
+
+/*
+ * R1 (0x01) - DACLMSB_ATTENUATION
+ */
+#define WM8741_UPDATELM                         0x0020  /* UPDATELM */
+#define WM8741_UPDATELM_MASK                    0x0020  /* UPDATELM */
+#define WM8741_UPDATELM_SHIFT                        5  /* UPDATELM */
+#define WM8741_UPDATELM_WIDTH                        1  /* UPDATELM */
+#define WM8741_LAT_9_5_0_MASK                   0x001F  /* LAT[9:5] - [4:0] */
+#define WM8741_LAT_9_5_0_SHIFT                       0  /* LAT[9:5] - [4:0] */
+#define WM8741_LAT_9_5_0_WIDTH                       5  /* LAT[9:5] - [4:0] */
+
+/*
+ * R2 (0x02) - DACRLSB_ATTENUATION
+ */
+#define WM8741_UPDATERL                         0x0020  /* UPDATERL */
+#define WM8741_UPDATERL_MASK                    0x0020  /* UPDATERL */
+#define WM8741_UPDATERL_SHIFT                        5  /* UPDATERL */
+#define WM8741_UPDATERL_WIDTH                        1  /* UPDATERL */
+#define WM8741_RAT_4_0_MASK                     0x001F  /* RAT[4:0] - [4:0] */
+#define WM8741_RAT_4_0_SHIFT                         0  /* RAT[4:0] - [4:0] */
+#define WM8741_RAT_4_0_WIDTH                         5  /* RAT[4:0] - [4:0] */
+
+/*
+ * R3 (0x03) - DACRMSB_ATTENUATION
+ */
+#define WM8741_UPDATERM                         0x0020  /* UPDATERM */
+#define WM8741_UPDATERM_MASK                    0x0020  /* UPDATERM */
+#define WM8741_UPDATERM_SHIFT                        5  /* UPDATERM */
+#define WM8741_UPDATERM_WIDTH                        1  /* UPDATERM */
+#define WM8741_RAT_9_5_0_MASK                   0x001F  /* RAT[9:5] - [4:0] */
+#define WM8741_RAT_9_5_0_SHIFT                       0  /* RAT[9:5] - [4:0] */
+#define WM8741_RAT_9_5_0_WIDTH                       5  /* RAT[9:5] - [4:0] */
+
+/*
+ * R4 (0x04) - VOLUME_CONTROL
+ */
+#define WM8741_AMUTE                            0x0080  /* AMUTE */
+#define WM8741_AMUTE_MASK                       0x0080  /* AMUTE */
+#define WM8741_AMUTE_SHIFT                           7  /* AMUTE */
+#define WM8741_AMUTE_WIDTH                           1  /* AMUTE */
+#define WM8741_ZFLAG_MASK                       0x0060  /* ZFLAG - [6:5] */
+#define WM8741_ZFLAG_SHIFT                           5  /* ZFLAG - [6:5] */
+#define WM8741_ZFLAG_WIDTH                           2  /* ZFLAG - [6:5] */
+#define WM8741_IZD                              0x0010  /* IZD */
+#define WM8741_IZD_MASK                         0x0010  /* IZD */
+#define WM8741_IZD_SHIFT                             4  /* IZD */
+#define WM8741_IZD_WIDTH                             1  /* IZD */
+#define WM8741_SOFT                             0x0008  /* SOFT MUTE */
+#define WM8741_SOFT_MASK                        0x0008  /* SOFT MUTE */
+#define WM8741_SOFT_SHIFT                            3  /* SOFT MUTE */
+#define WM8741_SOFT_WIDTH                            1  /* SOFT MUTE */
+#define WM8741_ATC                              0x0004  /* ATC */
+#define WM8741_ATC_MASK                         0x0004  /* ATC */
+#define WM8741_ATC_SHIFT                             2  /* ATC */
+#define WM8741_ATC_WIDTH                             1  /* ATC */
+#define WM8741_ATT2DB                           0x0002  /* ATT2DB */
+#define WM8741_ATT2DB_MASK                      0x0002  /* ATT2DB */
+#define WM8741_ATT2DB_SHIFT                          1  /* ATT2DB */
+#define WM8741_ATT2DB_WIDTH                          1  /* ATT2DB */
+#define WM8741_VOL_RAMP                         0x0001  /* VOL_RAMP */
+#define WM8741_VOL_RAMP_MASK                    0x0001  /* VOL_RAMP */
+#define WM8741_VOL_RAMP_SHIFT                        0  /* VOL_RAMP */
+#define WM8741_VOL_RAMP_WIDTH                        1  /* VOL_RAMP */
+
+/*
+ * R5 (0x05) - FORMAT_CONTROL
+ */
+#define WM8741_PWDN                             0x0080  /* PWDN */
+#define WM8741_PWDN_MASK                        0x0080  /* PWDN */
+#define WM8741_PWDN_SHIFT                            7  /* PWDN */
+#define WM8741_PWDN_WIDTH                            1  /* PWDN */
+#define WM8741_REV                              0x0040  /* REV */
+#define WM8741_REV_MASK                         0x0040  /* REV */
+#define WM8741_REV_SHIFT                             6  /* REV */
+#define WM8741_REV_WIDTH                             1  /* REV */
+#define WM8741_BCP                              0x0020  /* BCP */
+#define WM8741_BCP_MASK                         0x0020  /* BCP */
+#define WM8741_BCP_SHIFT                             5  /* BCP */
+#define WM8741_BCP_WIDTH                             1  /* BCP */
+#define WM8741_LRP                              0x0010  /* LRP */
+#define WM8741_LRP_MASK                         0x0010  /* LRP */
+#define WM8741_LRP_SHIFT                             4  /* LRP */
+#define WM8741_LRP_WIDTH                             1  /* LRP */
+#define WM8741_FMT_MASK                         0x000C  /* FMT - [3:2] */
+#define WM8741_FMT_SHIFT                             2  /* FMT - [3:2] */
+#define WM8741_FMT_WIDTH                             2  /* FMT - [3:2] */
+#define WM8741_IWL_MASK                         0x0003  /* IWL - [1:0] */
+#define WM8741_IWL_SHIFT                             0  /* IWL - [1:0] */
+#define WM8741_IWL_WIDTH                             2  /* IWL - [1:0] */
+
+/*
+ * R6 (0x06) - FILTER_CONTROL
+ */
+#define WM8741_ZFLAG_HI                         0x0080  /* ZFLAG_HI */
+#define WM8741_ZFLAG_HI_MASK                    0x0080  /* ZFLAG_HI */
+#define WM8741_ZFLAG_HI_SHIFT                        7  /* ZFLAG_HI */
+#define WM8741_ZFLAG_HI_WIDTH                        1  /* ZFLAG_HI */
+#define WM8741_DEEMPH_MASK                      0x0060  /* DEEMPH - [6:5] */
+#define WM8741_DEEMPH_SHIFT                          5  /* DEEMPH - [6:5] */
+#define WM8741_DEEMPH_WIDTH                          2  /* DEEMPH - [6:5] */
+#define WM8741_DSDFILT_MASK                     0x0018  /* DSDFILT - [4:3] */
+#define WM8741_DSDFILT_SHIFT                         3  /* DSDFILT - [4:3] */
+#define WM8741_DSDFILT_WIDTH                         2  /* DSDFILT - [4:3] */
+#define WM8741_FIRSEL_MASK                      0x0007  /* FIRSEL - [2:0] */
+#define WM8741_FIRSEL_SHIFT                          0  /* FIRSEL - [2:0] */
+#define WM8741_FIRSEL_WIDTH                          3  /* FIRSEL - [2:0] */
+
+/*
+ * R7 (0x07) - MODE_CONTROL_1
+ */
+#define WM8741_MODE8X                           0x0080  /* MODE8X */
+#define WM8741_MODE8X_MASK                      0x0080  /* MODE8X */
+#define WM8741_MODE8X_SHIFT                          7  /* MODE8X */
+#define WM8741_MODE8X_WIDTH                          1  /* MODE8X */
+#define WM8741_OSR_MASK                         0x0060  /* OSR - [6:5] */
+#define WM8741_OSR_SHIFT                             5  /* OSR - [6:5] */
+#define WM8741_OSR_WIDTH                             2  /* OSR - [6:5] */
+#define WM8741_SR_MASK                          0x001C  /* SR - [4:2] */
+#define WM8741_SR_SHIFT                              2  /* SR - [4:2] */
+#define WM8741_SR_WIDTH                              3  /* SR - [4:2] */
+#define WM8741_MODESEL_MASK                     0x0003  /* MODESEL - [1:0] */
+#define WM8741_MODESEL_SHIFT                         0  /* MODESEL - [1:0] */
+#define WM8741_MODESEL_WIDTH                         2  /* MODESEL - [1:0] */
+
+/*
+ * R8 (0x08) - MODE_CONTROL_2
+ */
+#define WM8741_DSD_GAIN                         0x0040  /* DSD_GAIN */
+#define WM8741_DSD_GAIN_MASK                    0x0040  /* DSD_GAIN */
+#define WM8741_DSD_GAIN_SHIFT                        6  /* DSD_GAIN */
+#define WM8741_DSD_GAIN_WIDTH                        1  /* DSD_GAIN */
+#define WM8741_SDOUT                            0x0020  /* SDOUT */
+#define WM8741_SDOUT_MASK                       0x0020  /* SDOUT */
+#define WM8741_SDOUT_SHIFT                           5  /* SDOUT */
+#define WM8741_SDOUT_WIDTH                           1  /* SDOUT */
+#define WM8741_DOUT                             0x0010  /* DOUT */
+#define WM8741_DOUT_MASK                        0x0010  /* DOUT */
+#define WM8741_DOUT_SHIFT                            4  /* DOUT */
+#define WM8741_DOUT_WIDTH                            1  /* DOUT */
+#define WM8741_DIFF_MASK                        0x000C  /* DIFF - [3:2] */
+#define WM8741_DIFF_SHIFT                            2  /* DIFF - [3:2] */
+#define WM8741_DIFF_WIDTH                            2  /* DIFF - [3:2] */
+#define WM8741_DITHER_MASK                      0x0003  /* DITHER - [1:0] */
+#define WM8741_DITHER_SHIFT                          0  /* DITHER - [1:0] */
+#define WM8741_DITHER_WIDTH                          2  /* DITHER - [1:0] */
+
+/*
+ * R32 (0x20) - ADDITONAL_CONTROL_1
+ */
+#define WM8741_DSD_LEVEL                        0x0002  /* DSD_LEVEL */
+#define WM8741_DSD_LEVEL_MASK                   0x0002  /* DSD_LEVEL */
+#define WM8741_DSD_LEVEL_SHIFT                       1  /* DSD_LEVEL */
+#define WM8741_DSD_LEVEL_WIDTH                       1  /* DSD_LEVEL */
+#define WM8741_DSD_NO_NOTCH                     0x0001  /* DSD_NO_NOTCH */
+#define WM8741_DSD_NO_NOTCH_MASK                0x0001  /* DSD_NO_NOTCH */
+#define WM8741_DSD_NO_NOTCH_SHIFT                    0  /* DSD_NO_NOTCH */
+#define WM8741_DSD_NO_NOTCH_WIDTH                    1  /* DSD_NO_NOTCH */
+
+#define  WM8741_SYSCLK 0
+
+extern struct snd_soc_dai wm8741_dai;
+extern struct snd_soc_codec_device soc_codec_dev_wm8741;
+
+#endif
index 9407e193fcc334a57622c02941cdbd64520cd9ec..e2c05e3e323a0bb24010d55bf7e8800c1b1c07c0 100644 (file)
@@ -884,6 +884,7 @@ static int wm8750_i2c_remove(struct i2c_client *client)
 
 static const struct i2c_device_id wm8750_i2c_id[] = {
        { "wm8750", 0 },
+       { "wm8987", 0 }, /* WM8987 is register compatible with WM8750 */
        { }
 };
 MODULE_DEVICE_TABLE(i2c, wm8750_i2c_id);
@@ -925,14 +926,22 @@ static int __devexit wm8750_spi_remove(struct spi_device *spi)
        return 0;
 }
 
+static const struct spi_device_id wm8750_spi_id[] = {
+       { "wm8750", 0 },
+       { "wm8987", 0 },
+       { }
+};
+MODULE_DEVICE_TABLE(spi, wm8750_spi_id);
+
 static struct spi_driver wm8750_spi_driver = {
        .driver = {
-               .name   = "wm8750",
+               .name   = "WM8750 SPI Codec",
                .bus    = &spi_bus_type,
                .owner  = THIS_MODULE,
        },
        .probe          = wm8750_spi_probe,
        .remove         = __devexit_p(wm8750_spi_remove),
+       .id_table       = wm8750_spi_id,
 };
 #endif
 
index 87f14f8675fae3f918b39e6ce82413162d8c3e45..f7dcabf6283c165877fd5a12cd2adcd69f245e5e 100644 (file)
@@ -2433,7 +2433,8 @@ static int wm8904_register(struct wm8904_priv *wm8904,
 
        if (wm8904_codec) {
                dev_err(codec->dev, "Another WM8904 is registered\n");
-               return -EINVAL;
+               ret = -EINVAL;
+               goto err;
        }
 
        mutex_init(&codec->mutex);
@@ -2462,7 +2463,8 @@ static int wm8904_register(struct wm8904_priv *wm8904,
        default:
                dev_err(codec->dev, "Unknown device type %d\n",
                        wm8904->devtype);
-               return -EINVAL;
+               ret = -EINVAL;
+               goto err;
        }
 
        memcpy(codec->reg_cache, wm8904_reg, sizeof(wm8904_reg));
@@ -2566,18 +2568,19 @@ static int wm8904_register(struct wm8904_priv *wm8904,
        ret = snd_soc_register_codec(codec);
        if (ret != 0) {
                dev_err(codec->dev, "Failed to register codec: %d\n", ret);
-               return ret;
+               goto err_enable;
        }
 
        ret = snd_soc_register_dai(&wm8904_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_enable:
        regulator_bulk_disable(ARRAY_SIZE(wm8904->supplies), wm8904->supplies);
 err_get:
index e3c4bbfaae273db4502dfaa58c2e29971271e3f3..f0c11138e61002cf2c6a0f419c64de177f0e9a62 100644 (file)
@@ -845,6 +845,7 @@ static void wm8940_unregister(struct wm8940_priv *wm8940)
 static int wm8940_i2c_probe(struct i2c_client *i2c,
                            const struct i2c_device_id *id)
 {
+       int ret;
        struct wm8940_priv *wm8940;
        struct snd_soc_codec *codec;
 
@@ -858,7 +859,11 @@ static int wm8940_i2c_probe(struct i2c_client *i2c,
        codec->control_data = i2c;
        codec->dev = &i2c->dev;
 
-       return wm8940_register(wm8940, SND_SOC_I2C);
+       ret = wm8940_register(wm8940, SND_SOC_I2C);
+       if (ret < 0)
+               kfree(wm8940);
+
+       return ret;
 }
 
 static int __devexit wm8940_i2c_remove(struct i2c_client *client)
index fedb76452f1b8306f85e5a986463516efb0e0193..5f025593d84d3f4b8bc47aedda98055cd900f8ca 100644 (file)
@@ -964,7 +964,8 @@ static int wm8955_register(struct wm8955_priv *wm8955,
 
        if (wm8955_codec) {
                dev_err(codec->dev, "Another WM8955 is registered\n");
-               return -EINVAL;
+               ret = -EINVAL;
+               goto err;
        }
 
        mutex_init(&codec->mutex);
@@ -1047,18 +1048,19 @@ static int wm8955_register(struct wm8955_priv *wm8955,
        ret = snd_soc_register_codec(codec);
        if (ret != 0) {
                dev_err(codec->dev, "Failed to register codec: %d\n", ret);
-               return ret;
+               goto err_enable;
        }
 
        ret = snd_soc_register_dai(&wm8955_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_enable:
        regulator_bulk_disable(ARRAY_SIZE(wm8955->supplies), wm8955->supplies);
 err_get:
index 7233cc68435adb19a5c43e46113aa12f41a6073d..3c6ee61f6c952220d05a648711eac86230b7cdfa 100644 (file)
@@ -79,12 +79,13 @@ struct wm8960_priv {
        struct snd_soc_dapm_widget *lout1;
        struct snd_soc_dapm_widget *rout1;
        struct snd_soc_dapm_widget *out3;
+       bool deemph;
+       int playback_fs;
 };
 
 #define wm8960_reset(c)        snd_soc_write(c, WM8960_RESET, 0)
 
 /* enumerated controls */
-static const char *wm8960_deemph[] = {"None", "32Khz", "44.1Khz", "48Khz"};
 static const char *wm8960_polarity[] = {"No Inversion", "Left Inverted",
        "Right Inverted", "Stereo Inversion"};
 static const char *wm8960_3d_upper_cutoff[] = {"High", "Low"};
@@ -93,7 +94,6 @@ static const char *wm8960_alcfunc[] = {"Off", "Right", "Left", "Stereo"};
 static const char *wm8960_alcmode[] = {"ALC", "Limiter"};
 
 static const struct soc_enum wm8960_enum[] = {
-       SOC_ENUM_SINGLE(WM8960_DACCTL1, 1, 4, wm8960_deemph),
        SOC_ENUM_SINGLE(WM8960_DACCTL1, 5, 4, wm8960_polarity),
        SOC_ENUM_SINGLE(WM8960_DACCTL2, 5, 4, wm8960_polarity),
        SOC_ENUM_SINGLE(WM8960_3D, 6, 2, wm8960_3d_upper_cutoff),
@@ -102,6 +102,59 @@ static const struct soc_enum wm8960_enum[] = {
        SOC_ENUM_SINGLE(WM8960_ALC3, 8, 2, wm8960_alcmode),
 };
 
+static const int deemph_settings[] = { 0, 32000, 44100, 48000 };
+
+static int wm8960_set_deemph(struct snd_soc_codec *codec)
+{
+       struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+       int val, i, best;
+
+       /* If we're using deemphasis select the nearest available sample
+        * rate.
+        */
+       if (wm8960->deemph) {
+               best = 1;
+               for (i = 2; i < ARRAY_SIZE(deemph_settings); i++) {
+                       if (abs(deemph_settings[i] - wm8960->playback_fs) <
+                           abs(deemph_settings[best] - wm8960->playback_fs))
+                               best = i;
+               }
+
+               val = best << 1;
+       } else {
+               val = 0;
+       }
+
+       dev_dbg(codec->dev, "Set deemphasis %d\n", val);
+
+       return snd_soc_update_bits(codec, WM8960_DACCTL1,
+                                  0x6, val);
+}
+
+static int wm8960_get_deemph(struct snd_kcontrol *kcontrol,
+                            struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+
+       return wm8960->deemph;
+}
+
+static int wm8960_put_deemph(struct snd_kcontrol *kcontrol,
+                            struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+       int deemph = ucontrol->value.enumerated.item[0];
+
+       if (deemph > 1)
+               return -EINVAL;
+
+       wm8960->deemph = deemph;
+
+       return wm8960_set_deemph(codec);
+}
+
 static const DECLARE_TLV_DB_SCALE(adc_tlv, -9700, 50, 0);
 static const DECLARE_TLV_DB_SCALE(dac_tlv, -12700, 50, 1);
 static const DECLARE_TLV_DB_SCALE(bypass_tlv, -2100, 300, 0);
@@ -131,23 +184,24 @@ SOC_SINGLE("Speaker DC Volume", WM8960_CLASSD3, 3, 5, 0),
 SOC_SINGLE("Speaker AC Volume", WM8960_CLASSD3, 0, 5, 0),
 
 SOC_SINGLE("PCM Playback -6dB Switch", WM8960_DACCTL1, 7, 1, 0),
-SOC_ENUM("ADC Polarity", wm8960_enum[1]),
-SOC_ENUM("Playback De-emphasis", wm8960_enum[0]),
+SOC_ENUM("ADC Polarity", wm8960_enum[0]),
 SOC_SINGLE("ADC High Pass Filter Switch", WM8960_DACCTL1, 0, 1, 0),
 
 SOC_ENUM("DAC Polarity", wm8960_enum[2]),
+SOC_SINGLE_BOOL_EXT("DAC Deemphasis Switch", 0,
+                   wm8960_get_deemph, wm8960_put_deemph),
 
-SOC_ENUM("3D Filter Upper Cut-Off", wm8960_enum[3]),
-SOC_ENUM("3D Filter Lower Cut-Off", wm8960_enum[4]),
+SOC_ENUM("3D Filter Upper Cut-Off", wm8960_enum[2]),
+SOC_ENUM("3D Filter Lower Cut-Off", wm8960_enum[3]),
 SOC_SINGLE("3D Volume", WM8960_3D, 1, 15, 0),
 SOC_SINGLE("3D Switch", WM8960_3D, 0, 1, 0),
 
-SOC_ENUM("ALC Function", wm8960_enum[5]),
+SOC_ENUM("ALC Function", wm8960_enum[4]),
 SOC_SINGLE("ALC Max Gain", WM8960_ALC1, 4, 7, 0),
 SOC_SINGLE("ALC Target", WM8960_ALC1, 0, 15, 1),
 SOC_SINGLE("ALC Min Gain", WM8960_ALC2, 4, 7, 0),
 SOC_SINGLE("ALC Hold Time", WM8960_ALC2, 0, 15, 0),
-SOC_ENUM("ALC Mode", wm8960_enum[6]),
+SOC_ENUM("ALC Mode", wm8960_enum[5]),
 SOC_SINGLE("ALC Decay", WM8960_ALC3, 4, 15, 0),
 SOC_SINGLE("ALC Attack", WM8960_ALC3, 0, 15, 0),
 
@@ -433,6 +487,21 @@ static int wm8960_set_dai_fmt(struct snd_soc_dai *codec_dai,
        return 0;
 }
 
+static struct {
+       int rate;
+       unsigned int val;
+} alc_rates[] = {
+       { 48000, 0 },
+       { 44100, 0 },
+       { 32000, 1 },
+       { 22050, 2 },
+       { 24000, 2 },
+       { 16000, 3 },
+       { 11250, 4 },
+       { 12000, 4 },
+       {  8000, 5 },
+};
+
 static int wm8960_hw_params(struct snd_pcm_substream *substream,
                            struct snd_pcm_hw_params *params,
                            struct snd_soc_dai *dai)
@@ -440,7 +509,9 @@ 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;
+       struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
        u16 iface = snd_soc_read(codec, WM8960_IFACE1) & 0xfff3;
+       int i;
 
        /* bit size */
        switch (params_format(params)) {
@@ -454,6 +525,18 @@ static int wm8960_hw_params(struct snd_pcm_substream *substream,
                break;
        }
 
+       /* Update filters for the new rate */
+       if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+               wm8960->playback_fs = params_rate(params);
+               wm8960_set_deemph(codec);
+       } else {
+               for (i = 0; i < ARRAY_SIZE(alc_rates); i++)
+                       if (alc_rates[i].rate == params_rate(params))
+                               snd_soc_update_bits(codec,
+                                                   WM8960_ADDCTL3, 0x7,
+                                                   alc_rates[i].val);
+       }
+
        /* set iface */
        snd_soc_write(codec, WM8960_IFACE1, iface);
        return 0;
index 5b9a756242f1e8176dabf6d414b62e016075d73c..2549d3a297ab957c3bacc608089beef0c688bff2 100644 (file)
@@ -1102,7 +1102,7 @@ static int wm8961_register(struct wm8961_priv *wm8961)
        ret = wm8961_reset(codec);
        if (ret < 0) {
                dev_err(codec->dev, "Failed to issue reset\n");
-               return ret;
+               goto err;
        }
 
        /* Enable class W */
@@ -1147,18 +1147,19 @@ static int wm8961_register(struct wm8961_priv *wm8961)
        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(&wm8961_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(wm8961);
        return ret;
index a2c4b2f37ccac22d01ee364419954a712c22373d..1468fe10cbbe54d57ce5d072b5406261487d2bc8 100644 (file)
@@ -670,7 +670,8 @@ static __devinit int wm8974_register(struct wm8974_priv *wm8974)
 
        if (wm8974_codec) {
                dev_err(codec->dev, "Another WM8974 is registered\n");
-               return -EINVAL;
+               ret = -EINVAL;
+               goto err;
        }
 
        mutex_init(&codec->mutex);
index 51d5f433215c14634b3f1e6875a919aa001fc6cd..8a1ad778e7e345d42780fec7baccbb6e0f9b984e 100644 (file)
@@ -1076,7 +1076,6 @@ static __devinit int wm8978_register(struct wm8978_priv *wm8978)
 err_codec:
        snd_soc_unregister_codec(codec);
 err:
-       kfree(wm8978);
        return ret;
 }
 
@@ -1085,13 +1084,13 @@ static __devexit void wm8978_unregister(struct wm8978_priv *wm8978)
        wm8978_set_bias_level(&wm8978->codec, SND_SOC_BIAS_OFF);
        snd_soc_unregister_dai(&wm8978_dai);
        snd_soc_unregister_codec(&wm8978->codec);
-       kfree(wm8978);
        wm8978_codec = NULL;
 }
 
 static __devinit int wm8978_i2c_probe(struct i2c_client *i2c,
                                      const struct i2c_device_id *id)
 {
+       int ret;
        struct wm8978_priv *wm8978;
        struct snd_soc_codec *codec;
 
@@ -1107,13 +1106,18 @@ static __devinit int wm8978_i2c_probe(struct i2c_client *i2c,
 
        codec->dev = &i2c->dev;
 
-       return wm8978_register(wm8978);
+       ret = wm8978_register(wm8978);
+       if (ret < 0)
+               kfree(wm8978);
+
+       return ret;
 }
 
 static __devexit int wm8978_i2c_remove(struct i2c_client *client)
 {
        struct wm8978_priv *wm8978 = i2c_get_clientdata(client);
        wm8978_unregister(wm8978);
+       kfree(wm8978);
        return 0;
 }
 
index c018772cc430ff3baac5767bb12e71efd63670fb..dd8d909788c1fe2b305d5b6af4f5eb4f58062cbd 100644 (file)
@@ -30,8 +30,6 @@
 
 #include "wm8990.h"
 
-#define WM8990_VERSION "0.2"
-
 /* codec private data */
 struct wm8990_priv {
        unsigned int sysclk;
@@ -1511,8 +1509,6 @@ static int wm8990_probe(struct platform_device *pdev)
        struct wm8990_priv *wm8990;
        int ret;
 
-       pr_info("WM8990 Audio Codec %s\n", WM8990_VERSION);
-
        setup = socdev->codec_data;
        codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
        if (codec == NULL)
index e84a1177f350403a39bb8ac701a5437979ac0e13..a87046a96f2abb01df5bde6d23bc24c29c3bfa3a 100644 (file)
@@ -1677,6 +1677,26 @@ static struct {
 
 static int wm8994_readable(unsigned int reg)
 {
+       switch (reg) {
+       case WM8994_GPIO_1:
+       case WM8994_GPIO_2:
+       case WM8994_GPIO_3:
+       case WM8994_GPIO_4:
+       case WM8994_GPIO_5:
+       case WM8994_GPIO_6:
+       case WM8994_GPIO_7:
+       case WM8994_GPIO_8:
+       case WM8994_GPIO_9:
+       case WM8994_GPIO_10:
+       case WM8994_GPIO_11:
+       case WM8994_INTERRUPT_STATUS_1:
+       case WM8994_INTERRUPT_STATUS_2:
+       case WM8994_INTERRUPT_RAW_STATUS_2:
+               return 1;
+       default:
+               break;
+       }
+
        if (reg >= ARRAY_SIZE(access_masks))
                return 0;
        return access_masks[reg].readable != 0;
@@ -2341,6 +2361,20 @@ SOC_DAPM_SINGLE("AIF2 Switch", WM8994_AIF1_ADC1_RIGHT_MIXER_ROUTING,
                0, 1, 0),
 };
 
+static const struct snd_kcontrol_new aif1adc2l_mix[] = {
+SOC_DAPM_SINGLE("DMIC Switch", WM8994_AIF1_ADC2_LEFT_MIXER_ROUTING,
+               1, 1, 0),
+SOC_DAPM_SINGLE("AIF2 Switch", WM8994_AIF1_ADC2_LEFT_MIXER_ROUTING,
+               0, 1, 0),
+};
+
+static const struct snd_kcontrol_new aif1adc2r_mix[] = {
+SOC_DAPM_SINGLE("DMIC Switch", WM8994_AIF1_ADC2_RIGHT_MIXER_ROUTING,
+               1, 1, 0),
+SOC_DAPM_SINGLE("AIF2 Switch", WM8994_AIF1_ADC2_RIGHT_MIXER_ROUTING,
+               0, 1, 0),
+};
+
 static const struct snd_kcontrol_new aif2dac2l_mix[] = {
 SOC_DAPM_SINGLE("Right Sidetone Switch", WM8994_DAC2_LEFT_MIXER_ROUTING,
                5, 1, 0),
@@ -2472,6 +2506,7 @@ static const struct snd_kcontrol_new aif3adc_mux =
 static const struct snd_soc_dapm_widget wm8994_dapm_widgets[] = {
 SND_SOC_DAPM_INPUT("DMIC1DAT"),
 SND_SOC_DAPM_INPUT("DMIC2DAT"),
+SND_SOC_DAPM_INPUT("Clock"),
 
 SND_SOC_DAPM_SUPPLY("CLK_SYS", SND_SOC_NOPM, 0, 0, clk_sys_event,
                    SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
@@ -2506,6 +2541,11 @@ SND_SOC_DAPM_MIXER("AIF1ADC1L Mixer", SND_SOC_NOPM, 0, 0,
 SND_SOC_DAPM_MIXER("AIF1ADC1R Mixer", SND_SOC_NOPM, 0, 0,
                   aif1adc1r_mix, ARRAY_SIZE(aif1adc1r_mix)),
 
+SND_SOC_DAPM_MIXER("AIF1ADC2L Mixer", SND_SOC_NOPM, 0, 0,
+                  aif1adc2l_mix, ARRAY_SIZE(aif1adc2l_mix)),
+SND_SOC_DAPM_MIXER("AIF1ADC2R Mixer", SND_SOC_NOPM, 0, 0,
+                  aif1adc2r_mix, ARRAY_SIZE(aif1adc2r_mix)),
+
 SND_SOC_DAPM_MIXER("AIF2DAC2L Mixer", SND_SOC_NOPM, 0, 0,
                   aif2dac2l_mix, ARRAY_SIZE(aif2dac2l_mix)),
 SND_SOC_DAPM_MIXER("AIF2DAC2R Mixer", SND_SOC_NOPM, 0, 0,
@@ -2668,6 +2708,14 @@ static const struct snd_soc_dapm_route intercon[] = {
        { "AIF1ADC1R Mixer", "ADC/DMIC Switch", "ADCR Mux" },
        { "AIF1ADC1R Mixer", "AIF2 Switch", "AIF2DACR" },
 
+       { "AIF1ADC2L", NULL, "AIF1ADC2L Mixer" },
+       { "AIF1ADC2L Mixer", "DMIC Switch", "DMIC2L" },
+       { "AIF1ADC2L Mixer", "AIF2 Switch", "AIF2DACL" },
+
+       { "AIF1ADC2R", NULL, "AIF1ADC2R Mixer" },
+       { "AIF1ADC2R Mixer", "DMIC Switch", "DMIC2R" },
+       { "AIF1ADC2R Mixer", "AIF2 Switch", "AIF2DACR" },
+
        /* Pin level routing for AIF3 */
        { "AIF1DAC1L", NULL, "AIF1DAC Mux" },
        { "AIF1DAC1R", NULL, "AIF1DAC Mux" },
@@ -2946,11 +2994,14 @@ static int wm8994_set_fll(struct snd_soc_dai *dai, int id, int src,
        return 0;
 }
 
+static int opclk_divs[] = { 10, 20, 30, 40, 55, 60, 80, 120, 160 };
+
 static int wm8994_set_dai_sysclk(struct snd_soc_dai *dai,
                int clk_id, unsigned int freq, int dir)
 {
        struct snd_soc_codec *codec = dai->codec;
        struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+       int i;
 
        switch (dai->id) {
        case 1:
@@ -2988,6 +3039,25 @@ static int wm8994_set_dai_sysclk(struct snd_soc_dai *dai,
                dev_dbg(dai->dev, "AIF%d using FLL2\n", dai->id);
                break;
 
+       case WM8994_SYSCLK_OPCLK:
+               /* Special case - a division (times 10) is given and
+                * no effect on main clocking. 
+                */
+               if (freq) {
+                       for (i = 0; i < ARRAY_SIZE(opclk_divs); i++)
+                               if (opclk_divs[i] == freq)
+                                       break;
+                       if (i == ARRAY_SIZE(opclk_divs))
+                               return -EINVAL;
+                       snd_soc_update_bits(codec, WM8994_CLOCKING_2,
+                                           WM8994_OPCLK_DIV_MASK, i);
+                       snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_2,
+                                           WM8994_OPCLK_ENA, WM8994_OPCLK_ENA);
+               } else {
+                       snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_2,
+                                           WM8994_OPCLK_ENA, 0);
+               }
+
        default:
                return -EINVAL;
        }
@@ -4004,6 +4074,11 @@ static int wm8994_codec_probe(struct platform_device *pdev)
                            1 << WM8994_AIF2DAC_3D_GAIN_SHIFT,
                            1 << WM8994_AIF2DAC_3D_GAIN_SHIFT);
 
+       /* Unconditionally enable AIF1 ADC TDM mode; it only affects
+        * behaviour on idle TDM clock cycles. */
+       snd_soc_update_bits(codec, WM8994_AIF1_CONTROL_1,
+                           WM8994_AIF1ADC_TDM, WM8994_AIF1ADC_TDM);
+
        wm8994_update_class_w(codec);
 
        ret = snd_soc_register_codec(codec);
index 7072dc539354fbd6d9ac6d5bd5b62251eedd23b2..2e0ca67a8df7ad8f83c83c822190ae09d9395acf 100644 (file)
@@ -20,6 +20,9 @@ extern struct snd_soc_dai wm8994_dai[];
 #define WM8994_SYSCLK_FLL1  3
 #define WM8994_SYSCLK_FLL2  4
 
+/* OPCLK is also configured with set_dai_sysclk, specify division*10 as rate. */
+#define WM8994_SYSCLK_OPCLK 5
+
 #define WM8994_FLL1 1
 #define WM8994_FLL2 2
 
index 13186fb4dcb405ef0c157eccdc95cda9768f4813..76b37ff6c264915c3c4fdc470ebdfe2c3ec1cc0a 100644 (file)
@@ -1356,7 +1356,7 @@ static int wm9081_register(struct wm9081_priv *wm9081,
        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;
+               goto err;
        }
 
        reg = snd_soc_read(codec, WM9081_SOFTWARE_RESET);
@@ -1369,7 +1369,7 @@ static int wm9081_register(struct wm9081_priv *wm9081,
        ret = wm9081_reset(codec);
        if (ret < 0) {
                dev_err(codec->dev, "Failed to issue reset\n");
-               return ret;
+               goto err;
        }
 
        wm9081_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
@@ -1388,18 +1388,19 @@ static int wm9081_register(struct wm9081_priv *wm9081,
        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(&wm9081_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(wm9081);
        return ret;
index 16f1a57da08a597bfd1a12da04f0b3b826bb371d..2cb81538cd9194c9cc278515171d67a45ac39ca6 100644 (file)
@@ -410,6 +410,8 @@ static int hp_event(struct snd_soc_dapm_widget *w,
                                    WM8993_HPOUT1L_DLY |
                                    WM8993_HPOUT1R_DLY, 0);
 
+               snd_soc_write(codec, WM8993_DC_SERVO_0, 0);
+
                snd_soc_update_bits(codec, WM8993_POWER_MANAGEMENT_1,
                                    WM8993_HPOUT1L_ENA | WM8993_HPOUT1R_ENA,
                                    0);
index adadcd3aa1b1b20a2a2b50c84965deeff8fdf2f7..9e8932abf158b7f8d22660081fd73183a73a4469 100644 (file)
@@ -26,6 +26,7 @@
 #include <mach/asp.h>
 
 #include "davinci-pcm.h"
+#include "davinci-i2s.h"
 
 
 /*
 #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_RCR_RFRLEN2(v)   ((v) << 24)
+#define DAVINCI_MCBSP_RCR_RPHASE       BIT(31)
 
 #define DAVINCI_MCBSP_XCR_XWDLEN1(v)   ((v) << 5)
 #define DAVINCI_MCBSP_XCR_XFRLEN1(v)   ((v) << 8)
 #define DAVINCI_MCBSP_XCR_XDATDLY(v)   ((v) << 16)
 #define DAVINCI_MCBSP_XCR_XFIG         (1 << 18)
 #define DAVINCI_MCBSP_XCR_XWDLEN2(v)   ((v) << 21)
+#define DAVINCI_MCBSP_XCR_XFRLEN2(v)   ((v) << 24)
+#define DAVINCI_MCBSP_XCR_XPHASE       BIT(31)
 
 #define DAVINCI_MCBSP_SRGR_FWID(v)     ((v) << 8)
 #define DAVINCI_MCBSP_SRGR_FPER(v)     ((v) << 16)
 #define DAVINCI_MCBSP_SRGR_FSGM                (1 << 28)
+#define DAVINCI_MCBSP_SRGR_CLKSM       BIT(29)
 
 #define DAVINCI_MCBSP_PCR_CLKRP                (1 << 0)
 #define DAVINCI_MCBSP_PCR_CLKXP                (1 << 1)
@@ -116,6 +122,7 @@ static const unsigned char double_fmt[SNDRV_PCM_FORMAT_S32_LE + 1] = {
 };
 
 struct davinci_mcbsp_dev {
+       struct device *dev;
        struct davinci_pcm_dma_params   dma_params[2];
        void __iomem                    *base;
 #define MOD_DSP_A      0
@@ -144,6 +151,11 @@ struct davinci_mcbsp_dev {
         * won't end up being swapped because of the underrun.
         */
        unsigned enable_channel_combine:1;
+
+       unsigned int fmt;
+       int clk_div;
+       int clk_input_pin;
+       bool i2s_accurate_sck;
 };
 
 static inline void davinci_mcbsp_write_reg(struct davinci_mcbsp_dev *dev,
@@ -254,10 +266,12 @@ 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;
+       /* Attention srgr is updated by hw_params! */
        srgr = DAVINCI_MCBSP_SRGR_FSGM |
                DAVINCI_MCBSP_SRGR_FPER(DEFAULT_BITPERSAMPLE * 2 - 1) |
                DAVINCI_MCBSP_SRGR_FWID(DEFAULT_BITPERSAMPLE - 1);
 
+       dev->fmt = fmt;
        /* set master/slave audio interface */
        switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
        case SND_SOC_DAIFMT_CBS_CFS:
@@ -268,11 +282,26 @@ static int davinci_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai,
                        DAVINCI_MCBSP_PCR_CLKRM;
                break;
        case SND_SOC_DAIFMT_CBM_CFS:
-               /* McBSP CLKR pin is the input for the Sample Rate Generator.
-                * McBSP FSR and FSX are driven by the Sample Rate Generator. */
-               pcr = DAVINCI_MCBSP_PCR_SCLKME |
-                       DAVINCI_MCBSP_PCR_FSXM |
-                       DAVINCI_MCBSP_PCR_FSRM;
+               pcr = DAVINCI_MCBSP_PCR_FSRM | DAVINCI_MCBSP_PCR_FSXM;
+               /*
+                * Selection of the clock input pin that is the
+                * input for the Sample Rate Generator.
+                * McBSP FSR and FSX are driven by the Sample Rate
+                * Generator.
+                */
+               switch (dev->clk_input_pin) {
+               case MCBSP_CLKS:
+                       pcr |= DAVINCI_MCBSP_PCR_CLKXM |
+                               DAVINCI_MCBSP_PCR_CLKRM;
+                       break;
+               case MCBSP_CLKR:
+                       pcr |= DAVINCI_MCBSP_PCR_SCLKME;
+                       break;
+               default:
+                       dev_err(dev->dev, "bad clk_input_pin\n");
+                       return -EINVAL;
+               }
+
                break;
        case SND_SOC_DAIFMT_CBM_CFM:
                /* codec is master */
@@ -372,6 +401,18 @@ static int davinci_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai,
        return 0;
 }
 
+static int davinci_i2s_dai_set_clkdiv(struct snd_soc_dai *cpu_dai,
+                               int div_id, int div)
+{
+       struct davinci_mcbsp_dev *dev = cpu_dai->private_data;
+
+       if (div_id != DAVINCI_MCBSP_CLKGDV)
+               return -ENODEV;
+
+       dev->clk_div = div;
+       return 0;
+}
+
 static int davinci_i2s_hw_params(struct snd_pcm_substream *substream,
                                 struct snd_pcm_hw_params *params,
                                 struct snd_soc_dai *dai)
@@ -380,8 +421,8 @@ static int davinci_i2s_hw_params(struct snd_pcm_substream *substream,
        struct davinci_pcm_dma_params *dma_params =
                                        &dev->dma_params[substream->stream];
        struct snd_interval *i = NULL;
-       int mcbsp_word_length;
-       unsigned int rcr, xcr, srgr;
+       int mcbsp_word_length, master;
+       unsigned int rcr, xcr, srgr, clk_div, freq, framesize;
        u32 spcr;
        snd_pcm_format_t fmt;
        unsigned element_cnt = 1;
@@ -396,12 +437,59 @@ static int davinci_i2s_hw_params(struct snd_pcm_substream *substream,
                davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr);
        }
 
-       i = hw_param_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS);
-       srgr = DAVINCI_MCBSP_SRGR_FSGM;
-       srgr |= DAVINCI_MCBSP_SRGR_FWID(snd_interval_value(i) - 1);
+       master = dev->fmt & SND_SOC_DAIFMT_MASTER_MASK;
+       fmt = params_format(params);
+       mcbsp_word_length = asp_word_length[fmt];
 
-       i = hw_param_interval(params, SNDRV_PCM_HW_PARAM_FRAME_BITS);
-       srgr |= DAVINCI_MCBSP_SRGR_FPER(snd_interval_value(i) - 1);
+       switch (master) {
+       case SND_SOC_DAIFMT_CBS_CFS:
+               freq = clk_get_rate(dev->clk);
+               srgr = DAVINCI_MCBSP_SRGR_FSGM |
+                      DAVINCI_MCBSP_SRGR_CLKSM;
+               srgr |= DAVINCI_MCBSP_SRGR_FWID(mcbsp_word_length *
+                                               8 - 1);
+               if (dev->i2s_accurate_sck) {
+                       clk_div = 256;
+                       do {
+                               framesize = (freq / (--clk_div)) /
+                               params->rate_num *
+                                       params->rate_den;
+                       } while (((framesize < 33) || (framesize > 4095)) &&
+                                (clk_div));
+                       clk_div--;
+                       srgr |= DAVINCI_MCBSP_SRGR_FPER(framesize - 1);
+               } else {
+                       /* symmetric waveforms */
+                       clk_div = freq / (mcbsp_word_length * 16) /
+                                 params->rate_num * params->rate_den;
+                       srgr |= DAVINCI_MCBSP_SRGR_FPER(mcbsp_word_length *
+                                                       16 - 1);
+               }
+               clk_div &= 0xFF;
+               srgr |= clk_div;
+               break;
+       case SND_SOC_DAIFMT_CBM_CFS:
+               srgr = DAVINCI_MCBSP_SRGR_FSGM;
+               clk_div = dev->clk_div - 1;
+               srgr |= DAVINCI_MCBSP_SRGR_FWID(mcbsp_word_length * 8 - 1);
+               srgr |= DAVINCI_MCBSP_SRGR_FPER(mcbsp_word_length * 16 - 1);
+               clk_div &= 0xFF;
+               srgr |= clk_div;
+               break;
+       case SND_SOC_DAIFMT_CBM_CFM:
+               /* Clock and frame sync given from external sources */
+               i = hw_param_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS);
+               srgr = DAVINCI_MCBSP_SRGR_FSGM;
+               srgr |= DAVINCI_MCBSP_SRGR_FWID(snd_interval_value(i) - 1);
+               pr_debug("%s - %d  FWID set: re-read srgr = %X\n",
+                       __func__, __LINE__, snd_interval_value(i) - 1);
+
+               i = hw_param_interval(params, SNDRV_PCM_HW_PARAM_FRAME_BITS);
+               srgr |= DAVINCI_MCBSP_SRGR_FPER(snd_interval_value(i) - 1);
+               break;
+       default:
+               return -EINVAL;
+       }
        davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SRGR_REG, srgr);
 
        rcr = DAVINCI_MCBSP_RCR_RFIG;
@@ -426,12 +514,41 @@ static int davinci_i2s_hw_params(struct snd_pcm_substream *substream,
                        element_cnt = 1;
                        fmt = double_fmt[fmt];
                }
+               switch (master) {
+               case SND_SOC_DAIFMT_CBS_CFS:
+               case SND_SOC_DAIFMT_CBS_CFM:
+                       rcr |= DAVINCI_MCBSP_RCR_RFRLEN2(0);
+                       xcr |= DAVINCI_MCBSP_XCR_XFRLEN2(0);
+                       rcr |= DAVINCI_MCBSP_RCR_RPHASE;
+                       xcr |= DAVINCI_MCBSP_XCR_XPHASE;
+                       break;
+               case SND_SOC_DAIFMT_CBM_CFM:
+               case SND_SOC_DAIFMT_CBM_CFS:
+                       rcr |= DAVINCI_MCBSP_RCR_RFRLEN2(element_cnt - 1);
+                       xcr |= DAVINCI_MCBSP_XCR_XFRLEN2(element_cnt - 1);
+                       break;
+               default:
+                       return -EINVAL;
+               }
        }
        dma_params->acnt = dma_params->data_type = data_type[fmt];
        dma_params->fifo_level = 0;
        mcbsp_word_length = asp_word_length[fmt];
-       rcr |= DAVINCI_MCBSP_RCR_RFRLEN1(element_cnt - 1);
-       xcr |= DAVINCI_MCBSP_XCR_XFRLEN1(element_cnt - 1);
+
+       switch (master) {
+       case SND_SOC_DAIFMT_CBS_CFS:
+       case SND_SOC_DAIFMT_CBS_CFM:
+               rcr |= DAVINCI_MCBSP_RCR_RFRLEN1(0);
+               xcr |= DAVINCI_MCBSP_XCR_XFRLEN1(0);
+               break;
+       case SND_SOC_DAIFMT_CBM_CFM:
+       case SND_SOC_DAIFMT_CBM_CFS:
+               rcr |= DAVINCI_MCBSP_RCR_RFRLEN1(element_cnt - 1);
+               xcr |= DAVINCI_MCBSP_XCR_XFRLEN1(element_cnt - 1);
+               break;
+       default:
+               return -EINVAL;
+       }
 
        rcr |= DAVINCI_MCBSP_RCR_RWDLEN1(mcbsp_word_length) |
                DAVINCI_MCBSP_RCR_RWDLEN2(mcbsp_word_length);
@@ -442,6 +559,10 @@ static int davinci_i2s_hw_params(struct snd_pcm_substream *substream,
                davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_XCR_REG, xcr);
        else
                davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_RCR_REG, rcr);
+
+       pr_debug("%s - %d  srgr=%X\n", __func__, __LINE__, srgr);
+       pr_debug("%s - %d  xcr=%X\n", __func__, __LINE__, xcr);
+       pr_debug("%s - %d  rcr=%X\n", __func__, __LINE__, rcr);
        return 0;
 }
 
@@ -500,6 +621,7 @@ static struct snd_soc_dai_ops davinci_i2s_dai_ops = {
        .trigger        = davinci_i2s_trigger,
        .hw_params      = davinci_i2s_hw_params,
        .set_fmt        = davinci_i2s_set_dai_fmt,
+       .set_clkdiv     = davinci_i2s_dai_set_clkdiv,
 
 };
 
@@ -526,6 +648,8 @@ 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, *res;
+       enum dma_event_q asp_chan_q = EVENTQ_0;
+       enum dma_event_q ram_chan_q = EVENTQ_1;
        int ret;
 
        mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
@@ -552,7 +676,17 @@ static int davinci_i2s_probe(struct platform_device *pdev)
                        pdata->sram_size_playback;
                dev->dma_params[SNDRV_PCM_STREAM_CAPTURE].sram_size =
                        pdata->sram_size_capture;
+               dev->clk_input_pin = pdata->clk_input_pin;
+               dev->i2s_accurate_sck = pdata->i2s_accurate_sck;
+               asp_chan_q = pdata->asp_chan_q;
+               ram_chan_q = pdata->ram_chan_q;
        }
+
+       dev->dma_params[SNDRV_PCM_STREAM_PLAYBACK].asp_chan_q   = asp_chan_q;
+       dev->dma_params[SNDRV_PCM_STREAM_PLAYBACK].ram_chan_q   = ram_chan_q;
+       dev->dma_params[SNDRV_PCM_STREAM_CAPTURE].asp_chan_q    = asp_chan_q;
+       dev->dma_params[SNDRV_PCM_STREAM_CAPTURE].ram_chan_q    = ram_chan_q;
+
        dev->clk = clk_get(&pdev->dev, NULL);
        if (IS_ERR(dev->clk)) {
                ret = -ENODEV;
@@ -584,6 +718,7 @@ static int davinci_i2s_probe(struct platform_device *pdev)
                goto err_free_mem;
        }
        dev->dma_params[SNDRV_PCM_STREAM_CAPTURE].channel = res->start;
+       dev->dev = &pdev->dev;
 
        davinci_i2s_dai.private_data = dev;
        davinci_i2s_dai.capture.dma_data = dev->dma_params;
index 241648ce8873014cd6e8a2f52ec6bf11c08b3028..0b1e77b8c27992294ccf364c1038164485cc87ab 100644 (file)
 #ifndef _DAVINCI_I2S_H
 #define _DAVINCI_I2S_H
 
+/* McBSP dividers */
+enum davinci_mcbsp_div {
+       DAVINCI_MCBSP_CLKGDV,              /* Sample rate generator divider */
+};
+
 extern struct snd_soc_dai davinci_i2s_dai;
 
 #endif
index d3955096d87214e842d3ba25d0e45aedf3fbcc12..b24720894af64a183a68aec1925a77419b5d0ad5 100644 (file)
@@ -890,7 +890,8 @@ static int davinci_mcasp_probe(struct platform_device *pdev)
        dev->rxnumevt = pdata->rxnumevt;
 
        dma_data = &dev->dma_params[SNDRV_PCM_STREAM_PLAYBACK];
-       dma_data->eventq_no = pdata->eventq_no;
+       dma_data->asp_chan_q = pdata->asp_chan_q;
+       dma_data->ram_chan_q = pdata->ram_chan_q;
        dma_data->dma_addr = (dma_addr_t) (pdata->tx_dma_offset +
                                                        io_v2p(dev->base));
 
@@ -904,7 +905,8 @@ static int davinci_mcasp_probe(struct platform_device *pdev)
        dma_data->channel = res->start;
 
        dma_data = &dev->dma_params[SNDRV_PCM_STREAM_CAPTURE];
-       dma_data->eventq_no = pdata->eventq_no;
+       dma_data->asp_chan_q = pdata->asp_chan_q;
+       dma_data->ram_chan_q = pdata->ram_chan_q;
        dma_data->dma_addr = (dma_addr_t)(pdata->rx_dma_offset +
                                                        io_v2p(dev->base));
 
index 2dc406f42fe7f5ca5f92a86649b23a439fba18cc..a7124116d2e01aea6832f158db798a8e16c93bfd 100644 (file)
@@ -381,7 +381,7 @@ static int request_ping_pong(struct snd_pcm_substream *substream,
        /* Request ram master channel */
        link = prtd->ram_channel = edma_alloc_channel(EDMA_CHANNEL_ANY,
                                  davinci_pcm_dma_irq, substream,
-                                 EVENTQ_1);
+                                 prtd->params->ram_chan_q);
        if (link < 0)
                goto exit1;
 
@@ -477,7 +477,8 @@ static int davinci_pcm_dma_request(struct snd_pcm_substream *substream)
 
        /* Request asp master DMA channel */
        link = prtd->asp_channel = edma_alloc_channel(params->channel,
-                       davinci_pcm_dma_irq, substream, EVENTQ_0);
+                       davinci_pcm_dma_irq, substream,
+                       prtd->params->asp_chan_q);
        if (link < 0)
                goto exit1;
 
@@ -800,7 +801,7 @@ static void davinci_pcm_free(struct snd_pcm *pcm)
                dma_free_writecombine(pcm->card->dev, buf->bytes,
                                      buf->area, buf->addr);
                buf->area = NULL;
-               iram_dma = (struct snd_dma_buffer *)buf->private_data;
+               iram_dma = buf->private_data;
                if (iram_dma) {
                        sram_free(iram_dma->area, iram_dma->bytes);
                        kfree(iram_dma);
index 0764944cf10f4250dd802400c77bde414eac3226..b799a02333d83b10a896d639ce1c73679ab06bec 100644 (file)
@@ -21,7 +21,8 @@ struct davinci_pcm_dma_params {
        unsigned short acnt;
        dma_addr_t dma_addr;            /* device physical address for DMA */
        unsigned sram_size;
-       enum dma_event_q eventq_no;     /* event queue number */
+       enum dma_event_q asp_chan_q;    /* event queue number for ASP channel */
+       enum dma_event_q ram_chan_q;    /* event queue number for RAM channel */
        unsigned char data_type;        /* xfer data type */
        unsigned char convert_mono_stereo;
        unsigned int fifo_level;
index 9aa980d38231d23fc8e2810b50c243c3987f1612..48678533da7af1641f56c6b3e2dcfca67d8dbedc 100644 (file)
@@ -203,7 +203,7 @@ static int davinci_vcif_probe(struct platform_device *pdev)
        int ret;
 
        davinci_vcif_dev = kzalloc(sizeof(struct davinci_vcif_dev), GFP_KERNEL);
-       if (!davinci_vc) {
+       if (!davinci_vcif_dev) {
                dev_dbg(&pdev->dev,
                        "could not allocate memory for private data\n");
                return -ENOMEM;
diff --git a/sound/soc/ep93xx/Kconfig b/sound/soc/ep93xx/Kconfig
new file mode 100644 (file)
index 0000000..f617f56
--- /dev/null
@@ -0,0 +1,18 @@
+config SND_EP93XX_SOC
+       tristate "SoC Audio support for the Cirrus Logic EP93xx series"
+       depends on ARCH_EP93XX && SND_SOC
+       help
+         Say Y or M if you want to add support for codecs attached to
+         the EP93xx I2S interface.
+
+config SND_EP93XX_SOC_I2S
+       tristate
+
+config SND_EP93XX_SOC_SNAPPERCL15
+        tristate "SoC Audio support for Bluewater Systems Snapper CL15 module"
+        depends on SND_EP93XX_SOC && MACH_SNAPPER_CL15
+        select SND_EP93XX_SOC_I2S
+        select SND_SOC_TLV320AIC23
+        help
+          Say Y or M here if you want to add support for I2S audio on the
+          Bluewater Systems Snapper CL15 module.
diff --git a/sound/soc/ep93xx/Makefile b/sound/soc/ep93xx/Makefile
new file mode 100644 (file)
index 0000000..272e60f
--- /dev/null
@@ -0,0 +1,11 @@
+# EP93xx Platform Support
+snd-soc-ep93xx-objs                            := ep93xx-pcm.o
+snd-soc-ep93xx-i2s-objs                                := ep93xx-i2s.o
+
+obj-$(CONFIG_SND_EP93XX_SOC)                   += snd-soc-ep93xx.o
+obj-$(CONFIG_SND_EP93XX_SOC_I2S)               += snd-soc-ep93xx-i2s.o
+
+# EP93XX Machine Support
+snd-soc-snappercl15-objs                       := snappercl15.o
+
+obj-$(CONFIG_SND_EP93XX_SOC_SNAPPERCL15)       += snd-soc-snappercl15.o
diff --git a/sound/soc/ep93xx/ep93xx-i2s.c b/sound/soc/ep93xx/ep93xx-i2s.c
new file mode 100644 (file)
index 0000000..00b9466
--- /dev/null
@@ -0,0 +1,487 @@
+/*
+ * linux/sound/soc/ep93xx-i2s.c
+ * EP93xx I2S driver
+ *
+ * Copyright (C) 2010 Ryan Mallon <ryan@bluewatersys.com>
+ *
+ * Based on the original driver by:
+ *   Copyright (C) 2007 Chase Douglas <chasedouglas@gmail>
+ *   Copyright (C) 2006 Lennert Buytenhek <buytenh@wantstofly.org>
+ *
+ * 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/slab.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include <mach/hardware.h>
+#include <mach/ep93xx-regs.h>
+#include <mach/dma.h>
+
+#include "ep93xx-pcm.h"
+#include "ep93xx-i2s.h"
+
+#define EP93XX_I2S_TXCLKCFG            0x00
+#define EP93XX_I2S_RXCLKCFG            0x04
+#define EP93XX_I2S_GLCTRL              0x0C
+
+#define EP93XX_I2S_TXLINCTRLDATA       0x28
+#define EP93XX_I2S_TXCTRL              0x2C
+#define EP93XX_I2S_TXWRDLEN            0x30
+#define EP93XX_I2S_TX0EN               0x34
+
+#define EP93XX_I2S_RXLINCTRLDATA       0x58
+#define EP93XX_I2S_RXCTRL              0x5C
+#define EP93XX_I2S_RXWRDLEN            0x60
+#define EP93XX_I2S_RX0EN               0x64
+
+#define EP93XX_I2S_WRDLEN_16           (0 << 0)
+#define EP93XX_I2S_WRDLEN_24           (1 << 0)
+#define EP93XX_I2S_WRDLEN_32           (2 << 0)
+
+#define EP93XX_I2S_LINCTRLDATA_R_JUST  (1 << 2) /* Right justify */
+
+#define EP93XX_I2S_CLKCFG_LRS          (1 << 0) /* lrclk polarity */
+#define EP93XX_I2S_CLKCFG_CKP          (1 << 1) /* Bit clock polarity */
+#define EP93XX_I2S_CLKCFG_REL          (1 << 2) /* First bit transition */
+#define EP93XX_I2S_CLKCFG_MASTER       (1 << 3) /* Master mode */
+#define EP93XX_I2S_CLKCFG_NBCG         (1 << 4) /* Not bit clock gating */
+
+struct ep93xx_i2s_info {
+       struct clk                      *mclk;
+       struct clk                      *sclk;
+       struct clk                      *lrclk;
+       struct ep93xx_pcm_dma_params    *dma_params;
+       struct resource                 *mem;
+       void __iomem                    *regs;
+};
+
+struct ep93xx_pcm_dma_params ep93xx_i2s_dma_params[] = {
+       [SNDRV_PCM_STREAM_PLAYBACK] = {
+               .name           = "i2s-pcm-out",
+               .dma_port       = EP93XX_DMA_M2P_PORT_I2S1,
+       },
+       [SNDRV_PCM_STREAM_CAPTURE] = {
+               .name           = "i2s-pcm-in",
+               .dma_port       = EP93XX_DMA_M2P_PORT_I2S1,
+       },
+};
+
+static inline void ep93xx_i2s_write_reg(struct ep93xx_i2s_info *info,
+                                       unsigned reg, unsigned val)
+{
+       __raw_writel(val, info->regs + reg);
+}
+
+static inline unsigned ep93xx_i2s_read_reg(struct ep93xx_i2s_info *info,
+                                          unsigned reg)
+{
+       return __raw_readl(info->regs + reg);
+}
+
+static void ep93xx_i2s_enable(struct ep93xx_i2s_info *info, int stream)
+{
+       unsigned base_reg;
+       int i;
+
+       if ((ep93xx_i2s_read_reg(info, EP93XX_I2S_TX0EN) & 0x1) == 0 &&
+           (ep93xx_i2s_read_reg(info, EP93XX_I2S_RX0EN) & 0x1) == 0) {
+               /* Enable clocks */
+               clk_enable(info->mclk);
+               clk_enable(info->sclk);
+               clk_enable(info->lrclk);
+
+               /* Enable i2s */
+               ep93xx_i2s_write_reg(info, EP93XX_I2S_GLCTRL, 1);
+       }
+
+       /* Enable fifos */
+       if (stream == SNDRV_PCM_STREAM_PLAYBACK)
+               base_reg = EP93XX_I2S_TX0EN;
+       else
+               base_reg = EP93XX_I2S_RX0EN;
+       for (i = 0; i < 3; i++)
+               ep93xx_i2s_write_reg(info, base_reg + (i * 4), 1);
+}
+
+static void ep93xx_i2s_disable(struct ep93xx_i2s_info *info, int stream)
+{
+       unsigned base_reg;
+       int i;
+
+       /* Disable fifos */
+       if (stream == SNDRV_PCM_STREAM_PLAYBACK)
+               base_reg = EP93XX_I2S_TX0EN;
+       else
+               base_reg = EP93XX_I2S_RX0EN;
+       for (i = 0; i < 3; i++)
+               ep93xx_i2s_write_reg(info, base_reg + (i * 4), 0);
+
+       if ((ep93xx_i2s_read_reg(info, EP93XX_I2S_TX0EN) & 0x1) == 0 &&
+           (ep93xx_i2s_read_reg(info, EP93XX_I2S_RX0EN) & 0x1) == 0) {
+               /* Disable i2s */
+               ep93xx_i2s_write_reg(info, EP93XX_I2S_GLCTRL, 0);
+
+               /* Disable clocks */
+               clk_disable(info->lrclk);
+               clk_disable(info->sclk);
+               clk_disable(info->mclk);
+       }
+}
+
+static int ep93xx_i2s_startup(struct snd_pcm_substream *substream,
+                             struct snd_soc_dai *dai)
+{
+       struct snd_soc_pcm_runtime *rtd = substream->private_data;
+       struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
+       struct ep93xx_i2s_info *info = rtd->dai->cpu_dai->private_data;
+
+       snd_soc_dai_set_dma_data(cpu_dai, substream,
+                                &info->dma_params[substream->stream]);
+       return 0;
+}
+
+static void ep93xx_i2s_shutdown(struct snd_pcm_substream *substream,
+                               struct snd_soc_dai *dai)
+{
+       struct snd_soc_pcm_runtime *rtd = substream->private_data;
+       struct ep93xx_i2s_info *info = rtd->dai->cpu_dai->private_data;
+
+       ep93xx_i2s_disable(info, substream->stream);
+}
+
+static int ep93xx_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai,
+                                 unsigned int fmt)
+{
+       struct ep93xx_i2s_info *info = cpu_dai->private_data;
+       unsigned int clk_cfg, lin_ctrl;
+
+       clk_cfg  = ep93xx_i2s_read_reg(info, EP93XX_I2S_RXCLKCFG);
+       lin_ctrl = ep93xx_i2s_read_reg(info, EP93XX_I2S_RXLINCTRLDATA);
+
+       switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+       case SND_SOC_DAIFMT_I2S:
+               clk_cfg |= EP93XX_I2S_CLKCFG_REL;
+               lin_ctrl &= ~EP93XX_I2S_LINCTRLDATA_R_JUST;
+               break;
+
+       case SND_SOC_DAIFMT_LEFT_J:
+               clk_cfg &= ~EP93XX_I2S_CLKCFG_REL;
+               lin_ctrl &= ~EP93XX_I2S_LINCTRLDATA_R_JUST;
+               break;
+
+       case SND_SOC_DAIFMT_RIGHT_J:
+               clk_cfg &= ~EP93XX_I2S_CLKCFG_REL;
+               lin_ctrl |= EP93XX_I2S_LINCTRLDATA_R_JUST;
+               break;
+
+       default:
+               return -EINVAL;
+       }
+
+       switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+       case SND_SOC_DAIFMT_CBS_CFS:
+               /* CPU is master */
+               clk_cfg |= EP93XX_I2S_CLKCFG_MASTER;
+               break;
+
+       case SND_SOC_DAIFMT_CBM_CFM:
+               /* Codec is master */
+               clk_cfg &= ~EP93XX_I2S_CLKCFG_MASTER;
+               break;
+
+       default:
+               return -EINVAL;
+       }
+
+       switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+       case SND_SOC_DAIFMT_NB_NF:
+               /* Negative bit clock, lrclk low on left word */
+               clk_cfg &= ~(EP93XX_I2S_CLKCFG_CKP | EP93XX_I2S_CLKCFG_REL);
+               break;
+
+       case SND_SOC_DAIFMT_NB_IF:
+               /* Negative bit clock, lrclk low on right word */
+               clk_cfg &= ~EP93XX_I2S_CLKCFG_CKP;
+               clk_cfg |= EP93XX_I2S_CLKCFG_REL;
+               break;
+
+       case SND_SOC_DAIFMT_IB_NF:
+               /* Positive bit clock, lrclk low on left word */
+               clk_cfg |= EP93XX_I2S_CLKCFG_CKP;
+               clk_cfg &= ~EP93XX_I2S_CLKCFG_REL;
+               break;
+
+       case SND_SOC_DAIFMT_IB_IF:
+               /* Positive bit clock, lrclk low on right word */
+               clk_cfg |= EP93XX_I2S_CLKCFG_CKP | EP93XX_I2S_CLKCFG_REL;
+               break;
+       }
+
+       /* Write new register values */
+       ep93xx_i2s_write_reg(info, EP93XX_I2S_RXCLKCFG, clk_cfg);
+       ep93xx_i2s_write_reg(info, EP93XX_I2S_TXCLKCFG, clk_cfg);
+       ep93xx_i2s_write_reg(info, EP93XX_I2S_RXLINCTRLDATA, lin_ctrl);
+       ep93xx_i2s_write_reg(info, EP93XX_I2S_TXLINCTRLDATA, lin_ctrl);
+       return 0;
+}
+
+static int ep93xx_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 snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
+       struct ep93xx_i2s_info *info = cpu_dai->private_data;
+       unsigned word_len, div, sdiv, lrdiv;
+       int found = 0, err;
+
+       switch (params_format(params)) {
+       case SNDRV_PCM_FORMAT_S16_LE:
+               word_len = EP93XX_I2S_WRDLEN_16;
+               break;
+
+       case SNDRV_PCM_FORMAT_S24_LE:
+               word_len = EP93XX_I2S_WRDLEN_24;
+               break;
+
+       case SNDRV_PCM_FORMAT_S32_LE:
+               word_len = EP93XX_I2S_WRDLEN_32;
+               break;
+
+       default:
+               return -EINVAL;
+       }
+
+       if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+               ep93xx_i2s_write_reg(info, EP93XX_I2S_TXWRDLEN, word_len);
+       else
+               ep93xx_i2s_write_reg(info, EP93XX_I2S_RXWRDLEN, word_len);
+
+       /*
+        * Calculate the sdiv (bit clock) and lrdiv (left/right clock) values.
+        * If the lrclk is pulse length is larger than the word size, then the
+        * bit clock will be gated for the unused bits.
+        */
+       div = (clk_get_rate(info->mclk) / params_rate(params)) *
+               params_channels(params);
+       for (sdiv = 2; sdiv <= 4; sdiv += 2)
+               for (lrdiv = 32; lrdiv <= 128; lrdiv <<= 1)
+                       if (sdiv * lrdiv == div) {
+                               found = 1;
+                               goto out;
+                       }
+out:
+       if (!found)
+               return -EINVAL;
+
+       err = clk_set_rate(info->sclk, clk_get_rate(info->mclk) / sdiv);
+       if (err)
+               return err;
+
+       err = clk_set_rate(info->lrclk, clk_get_rate(info->sclk) / lrdiv);
+       if (err)
+               return err;
+
+       ep93xx_i2s_enable(info, substream->stream);
+       return 0;
+}
+
+static int ep93xx_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, int clk_id,
+                                unsigned int freq, int dir)
+{
+       struct ep93xx_i2s_info *info = cpu_dai->private_data;
+
+       if (dir == SND_SOC_CLOCK_IN || clk_id != 0)
+               return -EINVAL;
+
+       return clk_set_rate(info->mclk, freq);
+}
+
+#ifdef CONFIG_PM
+static int ep93xx_i2s_suspend(struct snd_soc_dai *dai)
+{
+       struct ep93xx_i2s_info *info = dai->private_data;
+
+       if (!dai->active)
+               return;
+
+       ep93xx_i2s_disable(info, SNDRV_PCM_STREAM_PLAYBACK);
+       ep93xx_i2s_disable(info, SNDRV_PCM_STREAM_CAPTURE);
+}
+
+static int ep93xx_i2s_resume(struct snd_soc_dai *dai)
+{
+       struct ep93xx_i2s_info *info = dai->private_data;
+
+       if (!dai->active)
+               return;
+
+       ep93xx_i2s_enable(info, SNDRV_PCM_STREAM_PLAYBACK);
+       ep93xx_i2s_enable(info, SNDRV_PCM_STREAM_CAPTURE);
+}
+#else
+#define ep93xx_i2s_suspend     NULL
+#define ep93xx_i2s_resume      NULL
+#endif
+
+static struct snd_soc_dai_ops ep93xx_i2s_dai_ops = {
+       .startup        = ep93xx_i2s_startup,
+       .shutdown       = ep93xx_i2s_shutdown,
+       .hw_params      = ep93xx_i2s_hw_params,
+       .set_sysclk     = ep93xx_i2s_set_sysclk,
+       .set_fmt        = ep93xx_i2s_set_dai_fmt,
+};
+
+#define EP93XX_I2S_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
+                           SNDRV_PCM_FMTBIT_S24_LE | \
+                           SNDRV_PCM_FMTBIT_S32_LE)
+
+struct snd_soc_dai ep93xx_i2s_dai = {
+       .name           = "ep93xx-i2s",
+       .id             = 0,
+       .symmetric_rates= 1,
+       .suspend        = ep93xx_i2s_suspend,
+       .resume         = ep93xx_i2s_resume,
+       .playback       = {
+               .channels_min   = 2,
+               .channels_max   = 2,
+               .rates          = SNDRV_PCM_RATE_8000_48000,
+               .formats        = EP93XX_I2S_FORMATS,
+       },
+       .capture        = {
+                .channels_min  = 2,
+                .channels_max  = 2,
+                .rates         = SNDRV_PCM_RATE_8000_48000,
+                .formats       = EP93XX_I2S_FORMATS,
+       },
+       .ops            = &ep93xx_i2s_dai_ops,
+};
+EXPORT_SYMBOL_GPL(ep93xx_i2s_dai);
+
+static int ep93xx_i2s_probe(struct platform_device *pdev)
+{
+       struct ep93xx_i2s_info *info;
+       struct resource *res;
+       int err;
+
+       info = kzalloc(sizeof(struct ep93xx_i2s_info), GFP_KERNEL);
+       if (!info) {
+               err = -ENOMEM;
+               goto fail;
+       }
+
+       ep93xx_i2s_dai.dev = &pdev->dev;
+       ep93xx_i2s_dai.private_data = info;
+       info->dma_params = ep93xx_i2s_dma_params;
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (!res) {
+               err = -ENODEV;
+               goto fail;
+       }
+
+       info->mem = request_mem_region(res->start, resource_size(res),
+                                      pdev->name);
+       if (!info->mem) {
+               err = -EBUSY;
+               goto fail;
+       }
+
+       info->regs = ioremap(info->mem->start, resource_size(info->mem));
+       if (!info->regs) {
+               err = -ENXIO;
+               goto fail_release_mem;
+       }
+
+       info->mclk = clk_get(&pdev->dev, "mclk");
+       if (IS_ERR(info->mclk)) {
+               err = PTR_ERR(info->mclk);
+               goto fail_unmap_mem;
+       }
+
+       info->sclk = clk_get(&pdev->dev, "sclk");
+       if (IS_ERR(info->sclk)) {
+               err = PTR_ERR(info->sclk);
+               goto fail_put_mclk;
+       }
+
+       info->lrclk = clk_get(&pdev->dev, "lrclk");
+       if (IS_ERR(info->lrclk)) {
+               err = PTR_ERR(info->lrclk);
+               goto fail_put_sclk;
+       }
+
+       err = snd_soc_register_dai(&ep93xx_i2s_dai);
+       if (err)
+               goto fail_put_lrclk;
+
+       return 0;
+
+fail_put_lrclk:
+       clk_put(info->lrclk);
+fail_put_sclk:
+       clk_put(info->sclk);
+fail_put_mclk:
+       clk_put(info->mclk);
+fail_unmap_mem:
+       iounmap(info->regs);
+fail_release_mem:
+       release_mem_region(info->mem->start, resource_size(info->mem));
+       kfree(info);
+fail:
+       return err;
+}
+
+static int __devexit ep93xx_i2s_remove(struct platform_device *pdev)
+{
+       struct ep93xx_i2s_info *info = ep93xx_i2s_dai.private_data;
+
+       snd_soc_unregister_dai(&ep93xx_i2s_dai);
+       clk_put(info->lrclk);
+       clk_put(info->sclk);
+       clk_put(info->mclk);
+       iounmap(info->regs);
+       release_mem_region(info->mem->start, resource_size(info->mem));
+       kfree(info);
+       return 0;
+}
+
+static struct platform_driver ep93xx_i2s_driver = {
+       .probe  = ep93xx_i2s_probe,
+       .remove = __devexit_p(ep93xx_i2s_remove),
+       .driver = {
+               .name   = "ep93xx-i2s",
+               .owner  = THIS_MODULE,
+       },
+};
+
+static int __init ep93xx_i2s_init(void)
+{
+       return platform_driver_register(&ep93xx_i2s_driver);
+}
+
+static void __exit ep93xx_i2s_exit(void)
+{
+       platform_driver_unregister(&ep93xx_i2s_driver);
+}
+
+module_init(ep93xx_i2s_init);
+module_exit(ep93xx_i2s_exit);
+
+MODULE_ALIAS("platform:ep93xx-i2s");
+MODULE_AUTHOR("Ryan Mallon <ryan@bluewatersys.com>");
+MODULE_DESCRIPTION("EP93XX I2S driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/ep93xx/ep93xx-i2s.h b/sound/soc/ep93xx/ep93xx-i2s.h
new file mode 100644 (file)
index 0000000..3bd4ebf
--- /dev/null
@@ -0,0 +1,18 @@
+/*
+ * linux/sound/soc/ep93xx-i2s.h
+ * EP93xx I2S driver
+ *
+ * Copyright (C) 2010 Ryan Mallon <ryan@bluewatersys.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 _EP93XX_SND_SOC_I2S_H
+#define _EP93XX_SND_SOC_I2S_H
+
+extern struct snd_soc_dai ep93xx_i2s_dai;
+
+#endif /* _EP93XX_SND_SOC_I2S_H */
diff --git a/sound/soc/ep93xx/ep93xx-pcm.c b/sound/soc/ep93xx/ep93xx-pcm.c
new file mode 100644 (file)
index 0000000..4ba9384
--- /dev/null
@@ -0,0 +1,319 @@
+/*
+ * linux/sound/arm/ep93xx-pcm.c - EP93xx ALSA PCM interface
+ *
+ * Copyright (C) 2006 Lennert Buytenhek <buytenh@wantstofly.org>
+ * Copyright (C) 2006 Applied Data Systems
+ *
+ * Rewritten for the SoC audio subsystem (Based on PXA2xx code):
+ *   Copyright (c) 2008 Ryan Mallon <ryan@bluewatersys.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/init.h>
+#include <linux/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 <mach/dma.h>
+#include <mach/hardware.h>
+#include <mach/ep93xx-regs.h>
+
+#include "ep93xx-pcm.h"
+
+static const struct snd_pcm_hardware ep93xx_pcm_hardware = {
+       .info                   = (SNDRV_PCM_INFO_MMAP          |
+                                  SNDRV_PCM_INFO_MMAP_VALID    |
+                                  SNDRV_PCM_INFO_INTERLEAVED   |
+                                  SNDRV_PCM_INFO_BLOCK_TRANSFER),
+                                  
+       .rates                  = SNDRV_PCM_RATE_8000_48000,
+       .rate_min               = SNDRV_PCM_RATE_8000,
+       .rate_max               = SNDRV_PCM_RATE_48000,
+       
+       .formats                = (SNDRV_PCM_FMTBIT_S16_LE |
+                                  SNDRV_PCM_FMTBIT_S24_LE |
+                                  SNDRV_PCM_FMTBIT_S32_LE),
+       
+       .buffer_bytes_max       = 131072,
+       .period_bytes_min       = 32,
+       .period_bytes_max       = 32768,
+       .periods_min            = 1,
+       .periods_max            = 32,
+       .fifo_size              = 32,
+};
+
+struct ep93xx_runtime_data
+{
+       struct ep93xx_dma_m2p_client    cl;
+       struct ep93xx_pcm_dma_params    *params;
+       int                             pointer_bytes;
+       struct tasklet_struct           period_tasklet;
+       int                             periods;
+       struct ep93xx_dma_buffer        buf[32];
+};
+
+static void ep93xx_pcm_period_elapsed(unsigned long data)
+{
+       struct snd_pcm_substream *substream = (struct snd_pcm_substream *)data;
+       snd_pcm_period_elapsed(substream);
+}
+
+static void ep93xx_pcm_buffer_started(void *cookie,
+                                     struct ep93xx_dma_buffer *buf)
+{
+}
+
+static void ep93xx_pcm_buffer_finished(void *cookie, 
+                                      struct ep93xx_dma_buffer *buf, 
+                                      int bytes, int error)
+{
+       struct snd_pcm_substream *substream = cookie;
+       struct ep93xx_runtime_data *rtd = substream->runtime->private_data;
+
+       if (buf == rtd->buf + rtd->periods - 1)
+               rtd->pointer_bytes = 0;
+       else
+               rtd->pointer_bytes += buf->size;
+
+       if (!error) {
+               ep93xx_dma_m2p_submit_recursive(&rtd->cl, buf);
+               tasklet_schedule(&rtd->period_tasklet);
+       } else {
+               snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
+       }
+}
+
+static int ep93xx_pcm_open(struct snd_pcm_substream *substream)
+{
+       struct snd_soc_pcm_runtime *soc_rtd = substream->private_data;
+       struct snd_soc_dai *cpu_dai = soc_rtd->dai->cpu_dai;
+       struct ep93xx_pcm_dma_params *dma_params;
+       struct ep93xx_runtime_data *rtd;    
+       int ret;
+
+       dma_params = snd_soc_dai_get_dma_data(cpu_dai, substream);
+       snd_soc_set_runtime_hwparams(substream, &ep93xx_pcm_hardware);
+
+       rtd = kmalloc(sizeof(*rtd), GFP_KERNEL);
+       if (!rtd) 
+               return -ENOMEM;
+
+       memset(&rtd->period_tasklet, 0, sizeof(rtd->period_tasklet));
+       rtd->period_tasklet.func = ep93xx_pcm_period_elapsed;
+       rtd->period_tasklet.data = (unsigned long)substream;
+
+       rtd->cl.name = dma_params->name;
+       rtd->cl.flags = dma_params->dma_port | EP93XX_DMA_M2P_IGNORE_ERROR |
+               ((substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
+                EP93XX_DMA_M2P_TX : EP93XX_DMA_M2P_RX);
+       rtd->cl.cookie = substream;
+       rtd->cl.buffer_started = ep93xx_pcm_buffer_started;
+       rtd->cl.buffer_finished = ep93xx_pcm_buffer_finished;
+       ret = ep93xx_dma_m2p_client_register(&rtd->cl);
+       if (ret < 0) {
+               kfree(rtd);
+               return ret;
+       }
+       
+       substream->runtime->private_data = rtd;
+       return 0;
+}
+
+static int ep93xx_pcm_close(struct snd_pcm_substream *substream)
+{
+       struct ep93xx_runtime_data *rtd = substream->runtime->private_data;
+
+       ep93xx_dma_m2p_client_unregister(&rtd->cl);
+       kfree(rtd);
+       return 0;
+}
+
+static int ep93xx_pcm_hw_params(struct snd_pcm_substream *substream,
+                               struct snd_pcm_hw_params *params)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct ep93xx_runtime_data *rtd = runtime->private_data;
+       size_t totsize = params_buffer_bytes(params);
+       size_t period = params_period_bytes(params);
+       int i;
+
+       snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+       runtime->dma_bytes = totsize;
+
+       rtd->periods = (totsize + period - 1) / period;
+       for (i = 0; i < rtd->periods; i++) {
+               rtd->buf[i].bus_addr = runtime->dma_addr + (i * period);
+               rtd->buf[i].size = period;
+               if ((i + 1) * period > totsize)
+                       rtd->buf[i].size = totsize - (i * period);
+       }
+
+       return 0;
+}
+
+static int ep93xx_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+       snd_pcm_set_runtime_buffer(substream, NULL);
+       return 0;
+}
+
+static int ep93xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+       struct ep93xx_runtime_data *rtd = substream->runtime->private_data;
+       int ret;
+       int i;
+
+       ret = 0;
+       switch (cmd) {
+       case SNDRV_PCM_TRIGGER_START:
+       case SNDRV_PCM_TRIGGER_RESUME:
+       case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+               rtd->pointer_bytes = 0;
+               for (i = 0; i < rtd->periods; i++)
+                       ep93xx_dma_m2p_submit(&rtd->cl, rtd->buf + i);
+               break;
+
+       case SNDRV_PCM_TRIGGER_STOP:
+       case SNDRV_PCM_TRIGGER_SUSPEND:
+       case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+               ep93xx_dma_m2p_flush(&rtd->cl);
+               break;
+
+       default:
+               ret = -EINVAL;
+               break;
+       }
+
+       return ret;
+}
+
+static snd_pcm_uframes_t ep93xx_pcm_pointer(struct snd_pcm_substream *substream)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct ep93xx_runtime_data *rtd = substream->runtime->private_data;
+
+       /* FIXME: implement this with sub-period granularity */
+       return bytes_to_frames(runtime, rtd->pointer_bytes);
+}
+
+static int ep93xx_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 ep93xx_pcm_ops = {
+       .open           = ep93xx_pcm_open,
+       .close          = ep93xx_pcm_close,
+       .ioctl          = snd_pcm_lib_ioctl,
+       .hw_params      = ep93xx_pcm_hw_params,
+       .hw_free        = ep93xx_pcm_hw_free,
+       .trigger        = ep93xx_pcm_trigger,
+       .pointer        = ep93xx_pcm_pointer,
+       .mmap           = ep93xx_pcm_mmap,
+};
+
+static int ep93xx_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 = ep93xx_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_writecombine(pcm->card->dev, size,
+                                          &buf->addr, GFP_KERNEL);
+       buf->bytes = size;
+
+       return (buf->area == NULL) ? -ENOMEM : 0;
+}
+
+static void ep93xx_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 u64 ep93xx_pcm_dmamask = 0xffffffff;
+
+static int ep93xx_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 = &ep93xx_pcm_dmamask;
+       if (!card->dev->coherent_dma_mask)
+               card->dev->coherent_dma_mask = 0xffffffff;
+
+       if (dai->playback.channels_min) {
+               ret = ep93xx_pcm_preallocate_dma_buffer(pcm,
+                                       SNDRV_PCM_STREAM_PLAYBACK);
+               if (ret)
+                       return ret;
+       }
+
+       if (dai->capture.channels_min) {
+               ret = ep93xx_pcm_preallocate_dma_buffer(pcm,
+                                       SNDRV_PCM_STREAM_CAPTURE);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
+}
+
+struct snd_soc_platform ep93xx_soc_platform = {
+       .name           = "ep93xx-audio",
+       .pcm_ops        = &ep93xx_pcm_ops,
+       .pcm_new        = &ep93xx_pcm_new,
+       .pcm_free       = &ep93xx_pcm_free_dma_buffers,
+};
+EXPORT_SYMBOL_GPL(ep93xx_soc_platform);
+
+static int __init ep93xx_soc_platform_init(void)
+{
+       return snd_soc_register_platform(&ep93xx_soc_platform);
+}
+
+static void __exit ep93xx_soc_platform_exit(void)
+{
+       snd_soc_unregister_platform(&ep93xx_soc_platform);
+}
+
+module_init(ep93xx_soc_platform_init);
+module_exit(ep93xx_soc_platform_exit);
+
+MODULE_AUTHOR("Ryan Mallon <ryan@bluewatersys.com>");
+MODULE_DESCRIPTION("EP93xx ALSA PCM interface");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/ep93xx/ep93xx-pcm.h b/sound/soc/ep93xx/ep93xx-pcm.h
new file mode 100644 (file)
index 0000000..4ffdd3f
--- /dev/null
@@ -0,0 +1,22 @@
+/*
+ * sound/soc/ep93xx/ep93xx-pcm.h - EP93xx ALSA PCM interface
+ *
+ * Copyright (C) 2006 Lennert Buytenhek <buytenh@wantstofly.org>
+ * Copyright (C) 2006 Applied Data Systems
+ *
+ * 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 _EP93XX_SND_SOC_PCM_H
+#define _EP93XX_SND_SOC_PCM_H
+
+struct ep93xx_pcm_dma_params {
+       char    *name;
+       int     dma_port;
+};
+
+extern struct snd_soc_platform ep93xx_soc_platform;
+
+#endif /* _EP93XX_SND_SOC_PCM_H */
diff --git a/sound/soc/ep93xx/snappercl15.c b/sound/soc/ep93xx/snappercl15.c
new file mode 100644 (file)
index 0000000..6495534
--- /dev/null
@@ -0,0 +1,150 @@
+/*
+ * snappercl15.c -- SoC audio for Bluewater Systems Snapper CL15 module
+ *
+ * Copyright (C) 2008 Bluewater Systems Ltd
+ * Author: Ryan Mallon <ryan@bluewatersys.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/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+
+#include "../codecs/tlv320aic23.h"
+#include "ep93xx-pcm.h"
+#include "ep93xx-i2s.h"
+
+#define CODEC_CLOCK 5644800
+
+static int snappercl15_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;
+       int err;
+
+       err = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+                                 SND_SOC_DAIFMT_NB_IF |
+                                 SND_SOC_DAIFMT_CBS_CFS);
+
+       err = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | 
+                                 SND_SOC_DAIFMT_NB_IF |                  
+                                 SND_SOC_DAIFMT_CBS_CFS);
+       if (err)
+               return err;
+
+       err = snd_soc_dai_set_sysclk(codec_dai, 0, CODEC_CLOCK, 
+                                    SND_SOC_CLOCK_IN);
+       if (err)
+               return err;
+
+       err = snd_soc_dai_set_sysclk(cpu_dai, 0, CODEC_CLOCK, 
+                                    SND_SOC_CLOCK_OUT);
+       if (err)
+               return err;
+
+       return 0;
+}
+
+static struct snd_soc_ops snappercl15_ops = {
+       .hw_params      = snappercl15_hw_params,
+};
+
+static const struct snd_soc_dapm_widget tlv320aic23_dapm_widgets[] = {
+       SND_SOC_DAPM_HP("Headphone Jack", NULL),
+       SND_SOC_DAPM_LINE("Line In", NULL),
+       SND_SOC_DAPM_MIC("Mic Jack", NULL),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+       {"Headphone Jack", NULL, "LHPOUT"},
+       {"Headphone Jack", NULL, "RHPOUT"},
+
+       {"LLINEIN", NULL, "Line In"},
+       {"RLINEIN", NULL, "Line In"},
+
+       {"MICIN", NULL, "Mic Jack"},
+};
+
+static int snappercl15_tlv320aic23_init(struct snd_soc_codec *codec)
+{
+       snd_soc_dapm_new_controls(codec, tlv320aic23_dapm_widgets,
+                                 ARRAY_SIZE(tlv320aic23_dapm_widgets));
+
+       snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+       return 0;
+}
+
+static struct snd_soc_dai_link snappercl15_dai = {
+       .name           = "tlv320aic23",
+       .stream_name    = "AIC23",
+       .cpu_dai        = &ep93xx_i2s_dai,
+       .codec_dai      = &tlv320aic23_dai,
+       .init           = snappercl15_tlv320aic23_init,
+       .ops            = &snappercl15_ops,
+};
+
+static struct snd_soc_card snd_soc_snappercl15 = {
+       .name           = "Snapper CL15",
+       .platform       = &ep93xx_soc_platform,
+       .dai_link       = &snappercl15_dai,
+       .num_links      = 1,
+};
+
+static struct snd_soc_device snappercl15_snd_devdata = {
+       .card           = &snd_soc_snappercl15,
+       .codec_dev      = &soc_codec_dev_tlv320aic23,
+};
+
+static struct platform_device *snappercl15_snd_device;
+
+static int __init snappercl15_init(void)
+{
+       int ret;
+
+       if (!machine_is_snapper_cl15())
+               return -ENODEV;
+
+       ret = ep93xx_i2s_acquire(EP93XX_SYSCON_DEVCFG_I2SONAC97,
+                                EP93XX_SYSCON_I2SCLKDIV_ORIDE |
+                                EP93XX_SYSCON_I2SCLKDIV_SPOL);
+       if (ret)
+               return ret;
+
+       snappercl15_snd_device = platform_device_alloc("soc-audio", -1);
+       if (!snappercl15_snd_device)
+               return -ENOMEM;
+       
+       platform_set_drvdata(snappercl15_snd_device, &snappercl15_snd_devdata);
+       snappercl15_snd_devdata.dev = &snappercl15_snd_device->dev;
+       ret = platform_device_add(snappercl15_snd_device);
+       if (ret)
+               platform_device_put(snappercl15_snd_device);
+
+       return ret;
+}
+
+static void __exit snappercl15_exit(void)
+{
+       platform_device_unregister(snappercl15_snd_device);
+       ep93xx_i2s_release();
+}
+
+module_init(snappercl15_init);
+module_exit(snappercl15_exit);
+
+MODULE_AUTHOR("Ryan Mallon <ryan@bluewatersys.com>");
+MODULE_DESCRIPTION("ALSA SoC Snapper CL15");
+MODULE_LICENSE("GPL");
+
index 4f455bd6851ffd87102e6003dfd868b12f5b13f8..676841cbae9895a21fbcc15aa34ddef0a8c27c96 100644 (file)
@@ -16,7 +16,6 @@
 
 #include <asm/mpc52xx_psc.h>
 
-#include "mpc5200_psc_i2s.h"
 #include "mpc5200_dma.h"
 
 /**
diff --git a/sound/soc/fsl/mpc5200_psc_i2s.h b/sound/soc/fsl/mpc5200_psc_i2s.h
deleted file mode 100644 (file)
index ce55e07..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-/*
- * Freescale MPC5200 PSC in I2S mode
- * ALSA SoC Digital Audio Interface (DAI) driver
- *
- */
-
-#ifndef __SOUND_SOC_FSL_MPC52xx_PSC_I2S_H__
-#define __SOUND_SOC_FSL_MPC52xx_PSC_I2S_H__
-
-extern struct snd_soc_dai psc_i2s_dai[];
-
-#endif /* __SOUND_SOC_FSL_MPC52xx_PSC_I2S_H__ */
index 252defea93b5f2fdc4e64ad9ae4c484e2b8d9eca..52dac5e3874c33516f8cfb8a6e57ca67a1cd69bc 100644 (file)
@@ -1,4 +1,4 @@
-config SND_IMX_SOC
+menuconfig SND_IMX_SOC
        tristate "SoC Audio for Freescale i.MX CPUs"
        depends on ARCH_MXC
        select SND_PCM
@@ -8,14 +8,12 @@ config SND_IMX_SOC
          Say Y or M if you want to add support for codecs attached to
          the i.MX SSI interface.
 
-config SND_MXC_SOC_SSI
-       tristate
+if SND_IMX_SOC
 
 config SND_MXC_SOC_WM1133_EV1
        tristate "Audio on the the i.MX31ADS with WM1133-EV1 fitted"
-       depends on SND_IMX_SOC && MACH_MX31ADS_WM1133_EV1 && EXPERIMENTAL
+       depends on MACH_MX31ADS_WM1133_EV1 && EXPERIMENTAL
        select SND_SOC_WM8350
-       select SND_MXC_SOC_SSI
        help
          Enable support for audio on the i.MX31ADS with the WM1133-EV1
          PMIC board with WM8835x fitted.
@@ -23,8 +21,17 @@ config SND_MXC_SOC_WM1133_EV1
 config SND_SOC_PHYCORE_AC97
        tristate "SoC Audio support for Phytec phyCORE (and phyCARD) boards"
        depends on MACH_PCM043 || MACH_PCA100
-       select SND_MXC_SOC_SSI
        select SND_SOC_WM9712
        help
          Say Y if you want to add support for SoC audio on Phytec phyCORE
          and phyCARD boards in AC97 mode
+
+config SND_SOC_EUKREA_TLV320
+       tristate "Eukrea TLV320"
+       depends on MACH_EUKREA_MBIMX27_BASEBOARD || MACH_EUKREA_MBIMXSD_BASEBOARD
+       select SND_SOC_TLV320AIC23
+       help
+         Enable I2S based access to the TLV320AIC23B codec attached
+         to the SSI interface
+
+endif  # SND_IMX_SOC
index 2d203635ac116e6126c08fd3626af7cdc49088cc..7bc57baf2b0e9e68aaff7b27564fccc6dbd27113 100644 (file)
@@ -8,8 +8,10 @@ endif
 obj-$(CONFIG_SND_IMX_SOC) += snd-soc-imx.o
 
 # i.MX Machine Support
+snd-soc-eukrea-tlv320-objs := eukrea-tlv320.o
 snd-soc-phycore-ac97-objs := phycore-ac97.o
 snd-soc-wm1133-ev1-objs := wm1133-ev1.o
 
+obj-$(CONFIG_SND_SOC_EUKREA_TLV320) += snd-soc-eukrea-tlv320.o
 obj-$(CONFIG_SND_SOC_PHYCORE_AC97) += snd-soc-phycore-ac97.o
 obj-$(CONFIG_SND_MXC_SOC_WM1133_EV1) += snd-soc-wm1133-ev1.o
diff --git a/sound/soc/imx/eukrea-tlv320.c b/sound/soc/imx/eukrea-tlv320.c
new file mode 100644 (file)
index 0000000..f15dfbd
--- /dev/null
@@ -0,0 +1,137 @@
+/*
+ * eukrea-tlv320.c  --  SoC audio for eukrea_cpuimxXX in I2S mode
+ *
+ * Copyright 2010 Eric Bénard, Eukréa Electromatique <eric@eukrea.com>
+ *
+ * based on sound/soc/s3c24xx/s3c24xx_simtec_tlv320aic23.c
+ * which is Copyright 2009 Simtec Electronics
+ * and on sound/soc/imx/phycore-ac97.c which is
+ * Copyright 2009 Sascha Hauer, Pengutronix <s.hauer@pengutronix.de>
+ * 
+ *  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 <linux/i2c.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <asm/mach-types.h>
+
+#include "../codecs/tlv320aic23.h"
+#include "imx-ssi.h"
+
+#define CODEC_CLOCK 12000000
+
+static int eukrea_tlv320_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;
+       int ret;
+
+       ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+                                 SND_SOC_DAIFMT_NB_NF |
+                                 SND_SOC_DAIFMT_CBM_CFM);
+       if (ret) {
+               pr_err("%s: failed set cpu dai format\n", __func__);
+               return ret;
+       }
+
+       ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+                                 SND_SOC_DAIFMT_NB_NF |
+                                 SND_SOC_DAIFMT_CBM_CFM);
+       if (ret) {
+               pr_err("%s: failed set codec dai format\n", __func__);
+               return ret;
+       }
+
+       ret = snd_soc_dai_set_sysclk(codec_dai, 0,
+                                    CODEC_CLOCK, SND_SOC_CLOCK_OUT);
+       if (ret) {
+               pr_err("%s: failed setting codec sysclk\n", __func__);
+               return ret;
+       }
+       snd_soc_dai_set_tdm_slot(cpu_dai, 0xffffffc, 0xffffffc, 2, 0);
+
+       ret = snd_soc_dai_set_sysclk(cpu_dai, IMX_SSP_SYS_CLK, 0,
+                               SND_SOC_CLOCK_IN);
+       if (ret) {
+               pr_err("can't set CPU system clock IMX_SSP_SYS_CLK\n");
+               return ret;
+       }
+
+       return 0;
+}
+
+static struct snd_soc_ops eukrea_tlv320_snd_ops = {
+       .hw_params      = eukrea_tlv320_hw_params,
+};
+
+static struct snd_soc_dai_link eukrea_tlv320_dai = {
+       .name           = "tlv320aic23",
+       .stream_name    = "TLV320AIC23",
+       .codec_dai      = &tlv320aic23_dai,
+       .ops            = &eukrea_tlv320_snd_ops,
+};
+
+static struct snd_soc_card eukrea_tlv320 = {
+       .name           = "cpuimx-audio",
+       .platform       = &imx_soc_platform,
+       .dai_link       = &eukrea_tlv320_dai,
+       .num_links      = 1,
+};
+
+static struct snd_soc_device eukrea_tlv320_snd_devdata = {
+       .card           = &eukrea_tlv320,
+       .codec_dev      = &soc_codec_dev_tlv320aic23,
+};
+
+static struct platform_device *eukrea_tlv320_snd_device;
+
+static int __init eukrea_tlv320_init(void)
+{
+       int ret;
+
+       if (!machine_is_eukrea_cpuimx27() && !machine_is_eukrea_cpuimx25sd()
+               && !machine_is_eukrea_cpuimx35sd())
+               /* return happy. We might run on a totally different machine */
+               return 0;
+
+       eukrea_tlv320_snd_device = platform_device_alloc("soc-audio", -1);
+       if (!eukrea_tlv320_snd_device)
+               return -ENOMEM;
+
+       eukrea_tlv320_dai.cpu_dai = &imx_ssi_pcm_dai[0];
+
+       platform_set_drvdata(eukrea_tlv320_snd_device, &eukrea_tlv320_snd_devdata);
+       eukrea_tlv320_snd_devdata.dev = &eukrea_tlv320_snd_device->dev;
+       ret = platform_device_add(eukrea_tlv320_snd_device);
+
+       if (ret) {
+               printk(KERN_ERR "ASoC: Platform device allocation failed\n");
+               platform_device_put(eukrea_tlv320_snd_device);
+       }
+
+       return ret;
+}
+
+static void __exit eukrea_tlv320_exit(void)
+{
+       platform_device_unregister(eukrea_tlv320_snd_device);
+}
+
+module_init(eukrea_tlv320_init);
+module_exit(eukrea_tlv320_exit);
+
+MODULE_AUTHOR("Eric Bénard <eric@eukrea.com>");
+MODULE_DESCRIPTION("CPUIMX ALSA SoC driver");
+MODULE_LICENSE("GPL");
index 05f19c9284f4cad5fe11b851a24602e9884b9eca..0a595da4811d343a389709e5656dceb550fbd2dd 100644 (file)
@@ -292,12 +292,16 @@ static int snd_imx_open(struct snd_pcm_substream *substream)
        int ret;
 
        iprtd = kzalloc(sizeof(*iprtd), GFP_KERNEL);
+       if (iprtd == NULL)
+               return -ENOMEM;
        runtime->private_data = iprtd;
 
        ret = snd_pcm_hw_constraint_integer(substream->runtime,
                        SNDRV_PCM_HW_PARAM_PERIODS);
-       if (ret < 0)
+       if (ret < 0) {
+               kfree(iprtd);
                return ret;
+       }
 
        snd_soc_set_runtime_hwparams(substream, &snd_imx_hardware);
        return 0;
index 6b518e07eea9ce46d4f74a092e11bff816d9754b..b2bf27282cd2d10189419236f5463c97c95354b7 100644 (file)
@@ -192,6 +192,8 @@ static int snd_imx_open(struct snd_pcm_substream *substream)
        int ret;
 
        iprtd = kzalloc(sizeof(*iprtd), GFP_KERNEL);
+       if (iprtd == NULL)
+               return -ENOMEM;
        runtime->private_data = iprtd;
 
        iprtd->substream = substream;
@@ -202,8 +204,10 @@ static int snd_imx_open(struct snd_pcm_substream *substream)
 
        ret = snd_pcm_hw_constraint_integer(substream->runtime,
                        SNDRV_PCM_HW_PARAM_PERIODS);
-       if (ret < 0)
+       if (ret < 0) {
+               kfree(iprtd);
                return ret;
+       }
 
        snd_soc_set_runtime_hwparams(substream, &snd_imx_hardware);
        return 0;
index 80b4fee2442b8ac0d4b1eb7306f6304062c09e4d..50f51624c535f5540a376cbde7bcce40c4c64b2e 100644 (file)
@@ -83,8 +83,6 @@ static int imx_ssi_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai,
 /*
  * SSI DAI format configuration.
  * Should only be called when port is inactive (i.e. SSIEN = 0).
- * Note: We don't use the I2S modes but instead manually configure the
- * SSI for I2S because the I2S mode is only a register preset.
  */
 static int imx_ssi_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
 {
@@ -99,6 +97,10 @@ static int imx_ssi_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
                /* data on rising edge of bclk, frame low 1clk before data */
                strcr |= SSI_STCR_TFSI | SSI_STCR_TEFS | SSI_STCR_TXBIT0;
                scr |= SSI_SCR_NET;
+               if (ssi->flags & IMX_SSI_USE_I2S_SLAVE) {
+                       scr &= ~SSI_I2S_MODE_MASK;
+                       scr |= SSI_SCR_I2S_MODE_SLAVE;
+               }
                break;
        case SND_SOC_DAIFMT_LEFT_J:
                /* data on rising edge of bclk, frame high with data */
@@ -143,6 +145,11 @@ static int imx_ssi_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
 
        strcr |= SSI_STCR_TFEN0;
 
+       if (ssi->flags & IMX_SSI_NET)
+               scr |= SSI_SCR_NET;
+       if (ssi->flags & IMX_SSI_SYN)
+               scr |= SSI_SCR_SYN;
+
        writel(strcr, ssi->base + SSI_STCR);
        writel(strcr, ssi->base + SSI_SRCR);
        writel(scr, ssi->base + SSI_SCR);
diff --git a/sound/soc/jz4740/Kconfig b/sound/soc/jz4740/Kconfig
new file mode 100644 (file)
index 0000000..5351cba
--- /dev/null
@@ -0,0 +1,23 @@
+config SND_JZ4740_SOC
+       tristate "SoC Audio for Ingenic JZ4740 SoC"
+       depends on MACH_JZ4740 && SND_SOC
+       help
+         Say Y or M if you want to add support for codecs attached to
+         the JZ4740 I2S interface. You will also need to select the audio
+         interfaces to support below.
+
+config SND_JZ4740_SOC_I2S
+       depends on SND_JZ4740_SOC
+       tristate "SoC Audio (I2S protocol) for Ingenic JZ4740 SoC"
+       help
+         Say Y if you want to use I2S protocol and I2S codec on Ingenic JZ4740
+         based boards.
+
+config SND_JZ4740_SOC_QI_LB60
+       tristate "SoC Audio support for Qi LB60"
+       depends on SND_JZ4740_SOC && JZ4740_QI_LB60
+       select SND_JZ4740_SOC_I2S
+    select SND_SOC_JZ4740_CODEC
+       help
+         Say Y if you want to add support for ASoC audio on the Qi LB60 board
+         a.k.a Qi Ben NanoNote.
diff --git a/sound/soc/jz4740/Makefile b/sound/soc/jz4740/Makefile
new file mode 100644 (file)
index 0000000..be873c1
--- /dev/null
@@ -0,0 +1,13 @@
+#
+# Jz4740 Platform Support
+#
+snd-soc-jz4740-objs := jz4740-pcm.o
+snd-soc-jz4740-i2s-objs := jz4740-i2s.o
+
+obj-$(CONFIG_SND_JZ4740_SOC) += snd-soc-jz4740.o
+obj-$(CONFIG_SND_JZ4740_SOC_I2S) += snd-soc-jz4740-i2s.o
+
+# Jz4740 Machine Support
+snd-soc-qi-lb60-objs := qi_lb60.o
+
+obj-$(CONFIG_SND_JZ4740_SOC_QI_LB60) += snd-soc-qi-lb60.o
diff --git a/sound/soc/jz4740/jz4740-i2s.c b/sound/soc/jz4740/jz4740-i2s.c
new file mode 100644 (file)
index 0000000..eb518f0
--- /dev/null
@@ -0,0 +1,540 @@
+/*
+ *  Copyright (C) 2010, Lars-Peter Clausen <lars@metafoo.de>
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+
+#include <linux/dma-mapping.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 "jz4740-i2s.h"
+#include "jz4740-pcm.h"
+
+#define JZ_REG_AIC_CONF                0x00
+#define JZ_REG_AIC_CTRL                0x04
+#define JZ_REG_AIC_I2S_FMT     0x10
+#define JZ_REG_AIC_FIFO_STATUS 0x14
+#define JZ_REG_AIC_I2S_STATUS  0x1c
+#define JZ_REG_AIC_CLK_DIV     0x30
+#define JZ_REG_AIC_FIFO                0x34
+
+#define JZ_AIC_CONF_FIFO_RX_THRESHOLD_MASK (0xf << 12)
+#define JZ_AIC_CONF_FIFO_TX_THRESHOLD_MASK (0xf <<  8)
+#define JZ_AIC_CONF_OVERFLOW_PLAY_LAST BIT(6)
+#define JZ_AIC_CONF_INTERNAL_CODEC BIT(5)
+#define JZ_AIC_CONF_I2S BIT(4)
+#define JZ_AIC_CONF_RESET BIT(3)
+#define JZ_AIC_CONF_BIT_CLK_MASTER BIT(2)
+#define JZ_AIC_CONF_SYNC_CLK_MASTER BIT(1)
+#define JZ_AIC_CONF_ENABLE BIT(0)
+
+#define JZ_AIC_CONF_FIFO_RX_THRESHOLD_OFFSET 12
+#define JZ_AIC_CONF_FIFO_TX_THRESHOLD_OFFSET 8
+
+#define JZ_AIC_CTRL_OUTPUT_SAMPLE_SIZE_MASK (0x7 << 19)
+#define JZ_AIC_CTRL_INPUT_SAMPLE_SIZE_MASK (0x7 << 16)
+#define JZ_AIC_CTRL_ENABLE_RX_DMA BIT(15)
+#define JZ_AIC_CTRL_ENABLE_TX_DMA BIT(14)
+#define JZ_AIC_CTRL_MONO_TO_STEREO BIT(11)
+#define JZ_AIC_CTRL_SWITCH_ENDIANNESS BIT(10)
+#define JZ_AIC_CTRL_SIGNED_TO_UNSIGNED BIT(9)
+#define JZ_AIC_CTRL_FLUSH              BIT(8)
+#define JZ_AIC_CTRL_ENABLE_ROR_INT BIT(6)
+#define JZ_AIC_CTRL_ENABLE_TUR_INT BIT(5)
+#define JZ_AIC_CTRL_ENABLE_RFS_INT BIT(4)
+#define JZ_AIC_CTRL_ENABLE_TFS_INT BIT(3)
+#define JZ_AIC_CTRL_ENABLE_LOOPBACK BIT(2)
+#define JZ_AIC_CTRL_ENABLE_PLAYBACK BIT(1)
+#define JZ_AIC_CTRL_ENABLE_CAPTURE BIT(0)
+
+#define JZ_AIC_CTRL_OUTPUT_SAMPLE_SIZE_OFFSET 19
+#define JZ_AIC_CTRL_INPUT_SAMPLE_SIZE_OFFSET  16
+
+#define JZ_AIC_I2S_FMT_DISABLE_BIT_CLK BIT(12)
+#define JZ_AIC_I2S_FMT_ENABLE_SYS_CLK BIT(4)
+#define JZ_AIC_I2S_FMT_MSB BIT(0)
+
+#define JZ_AIC_I2S_STATUS_BUSY BIT(2)
+
+#define JZ_AIC_CLK_DIV_MASK 0xf
+
+struct jz4740_i2s {
+       struct resource *mem;
+       void __iomem *base;
+       dma_addr_t phys_base;
+
+       struct clk *clk_aic;
+       struct clk *clk_i2s;
+
+       struct jz4740_pcm_config pcm_config_playback;
+       struct jz4740_pcm_config pcm_config_capture;
+};
+
+static inline uint32_t jz4740_i2s_read(const struct jz4740_i2s *i2s,
+       unsigned int reg)
+{
+       return readl(i2s->base + reg);
+}
+
+static inline void jz4740_i2s_write(const struct jz4740_i2s *i2s,
+       unsigned int reg, uint32_t value)
+{
+       writel(value, i2s->base + reg);
+}
+
+static inline struct jz4740_i2s *jz4740_dai_to_i2s(struct snd_soc_dai *dai)
+{
+       return dai->private_data;
+}
+
+static int jz4740_i2s_startup(struct snd_pcm_substream *substream,
+       struct snd_soc_dai *dai)
+{
+       struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai);
+       uint32_t conf, ctrl;
+
+       if (dai->active)
+               return 0;
+
+       ctrl = jz4740_i2s_read(i2s, JZ_REG_AIC_CTRL);
+       ctrl |= JZ_AIC_CTRL_FLUSH;
+       jz4740_i2s_write(i2s, JZ_REG_AIC_CTRL, ctrl);
+
+       clk_enable(i2s->clk_i2s);
+
+       conf = jz4740_i2s_read(i2s, JZ_REG_AIC_CONF);
+       conf |= JZ_AIC_CONF_ENABLE;
+       jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf);
+
+       return 0;
+}
+
+static void jz4740_i2s_shutdown(struct snd_pcm_substream *substream,
+       struct snd_soc_dai *dai)
+{
+       struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai);
+       uint32_t conf;
+
+       if (!dai->active)
+               return;
+
+       conf = jz4740_i2s_read(i2s, JZ_REG_AIC_CONF);
+       conf &= ~JZ_AIC_CONF_ENABLE;
+       jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf);
+
+       clk_disable(i2s->clk_i2s);
+}
+
+static int jz4740_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
+       struct snd_soc_dai *dai)
+{
+       struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai);
+
+       uint32_t ctrl;
+       uint32_t mask;
+
+       if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+               mask = JZ_AIC_CTRL_ENABLE_PLAYBACK | JZ_AIC_CTRL_ENABLE_TX_DMA;
+       else
+               mask = JZ_AIC_CTRL_ENABLE_CAPTURE | JZ_AIC_CTRL_ENABLE_RX_DMA;
+
+       ctrl = jz4740_i2s_read(i2s, JZ_REG_AIC_CTRL);
+
+       switch (cmd) {
+       case SNDRV_PCM_TRIGGER_START:
+       case SNDRV_PCM_TRIGGER_RESUME:
+       case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+               ctrl |= mask;
+               break;
+       case SNDRV_PCM_TRIGGER_STOP:
+       case SNDRV_PCM_TRIGGER_SUSPEND:
+       case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+               ctrl &= ~mask;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       jz4740_i2s_write(i2s, JZ_REG_AIC_CTRL, ctrl);
+
+       return 0;
+}
+
+static int jz4740_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+       struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai);
+
+       uint32_t format = 0;
+       uint32_t conf;
+
+       conf = jz4740_i2s_read(i2s, JZ_REG_AIC_CONF);
+
+       conf &= ~(JZ_AIC_CONF_BIT_CLK_MASTER | JZ_AIC_CONF_SYNC_CLK_MASTER);
+
+       switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+       case SND_SOC_DAIFMT_CBS_CFS:
+               conf |= JZ_AIC_CONF_BIT_CLK_MASTER | JZ_AIC_CONF_SYNC_CLK_MASTER;
+               format |= JZ_AIC_I2S_FMT_ENABLE_SYS_CLK;
+               break;
+       case SND_SOC_DAIFMT_CBM_CFS:
+               conf |= JZ_AIC_CONF_SYNC_CLK_MASTER;
+               break;
+       case SND_SOC_DAIFMT_CBS_CFM:
+               conf |= JZ_AIC_CONF_BIT_CLK_MASTER;
+               break;
+       case SND_SOC_DAIFMT_CBM_CFM:
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+       case SND_SOC_DAIFMT_MSB:
+               format |= JZ_AIC_I2S_FMT_MSB;
+               break;
+       case SND_SOC_DAIFMT_I2S:
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+       case SND_SOC_DAIFMT_NB_NF:
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf);
+       jz4740_i2s_write(i2s, JZ_REG_AIC_I2S_FMT, format);
+
+       return 0;
+}
+
+static int jz4740_i2s_hw_params(struct snd_pcm_substream *substream,
+       struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
+{
+       struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai);
+       enum jz4740_dma_width dma_width;
+       struct jz4740_pcm_config *pcm_config;
+       unsigned int sample_size;
+       uint32_t ctrl;
+
+       ctrl = jz4740_i2s_read(i2s, JZ_REG_AIC_CTRL);
+
+       switch (params_format(params)) {
+       case SNDRV_PCM_FORMAT_S8:
+               sample_size = 0;
+               dma_width = JZ4740_DMA_WIDTH_8BIT;
+               break;
+       case SNDRV_PCM_FORMAT_S16:
+               sample_size = 1;
+               dma_width = JZ4740_DMA_WIDTH_16BIT;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+               ctrl &= ~JZ_AIC_CTRL_OUTPUT_SAMPLE_SIZE_MASK;
+               ctrl |= sample_size << JZ_AIC_CTRL_OUTPUT_SAMPLE_SIZE_OFFSET;
+               if (params_channels(params) == 1)
+                       ctrl |= JZ_AIC_CTRL_MONO_TO_STEREO;
+               else
+                       ctrl &= ~JZ_AIC_CTRL_MONO_TO_STEREO;
+
+               pcm_config = &i2s->pcm_config_playback;
+               pcm_config->dma_config.dst_width = dma_width;
+
+       } else {
+               ctrl &= ~JZ_AIC_CTRL_INPUT_SAMPLE_SIZE_MASK;
+               ctrl |= sample_size << JZ_AIC_CTRL_INPUT_SAMPLE_SIZE_OFFSET;
+
+               pcm_config = &i2s->pcm_config_capture;
+               pcm_config->dma_config.src_width = dma_width;
+       }
+
+       jz4740_i2s_write(i2s, JZ_REG_AIC_CTRL, ctrl);
+
+       snd_soc_dai_set_dma_data(dai, substream, pcm_config);
+
+       return 0;
+}
+
+static int jz4740_i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id,
+       unsigned int freq, int dir)
+{
+       struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai);
+       struct clk *parent;
+       int ret = 0;
+
+       switch (clk_id) {
+       case JZ4740_I2S_CLKSRC_EXT:
+               parent = clk_get(NULL, "ext");
+               clk_set_parent(i2s->clk_i2s, parent);
+               break;
+       case JZ4740_I2S_CLKSRC_PLL:
+               parent = clk_get(NULL, "pll half");
+               clk_set_parent(i2s->clk_i2s, parent);
+               ret = clk_set_rate(i2s->clk_i2s, freq);
+               break;
+       default:
+               return -EINVAL;
+       }
+       clk_put(parent);
+
+       return ret;
+}
+
+static int jz4740_i2s_suspend(struct snd_soc_dai *dai)
+{
+       struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai);
+       uint32_t conf;
+
+       if (dai->active) {
+               conf = jz4740_i2s_read(i2s, JZ_REG_AIC_CONF);
+               conf &= ~JZ_AIC_CONF_ENABLE;
+               jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf);
+
+               clk_disable(i2s->clk_i2s);
+       }
+
+       clk_disable(i2s->clk_aic);
+
+       return 0;
+}
+
+static int jz4740_i2s_resume(struct snd_soc_dai *dai)
+{
+       struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai);
+       uint32_t conf;
+
+       clk_enable(i2s->clk_aic);
+
+       if (dai->active) {
+               clk_enable(i2s->clk_i2s);
+
+               conf = jz4740_i2s_read(i2s, JZ_REG_AIC_CONF);
+               conf |= JZ_AIC_CONF_ENABLE;
+               jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf);
+       }
+
+       return 0;
+}
+
+static int jz4740_i2s_probe(struct platform_device *pdev, struct snd_soc_dai *dai)
+{
+       struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai);
+       uint32_t conf;
+
+       conf = (7 << JZ_AIC_CONF_FIFO_RX_THRESHOLD_OFFSET) |
+               (8 << JZ_AIC_CONF_FIFO_TX_THRESHOLD_OFFSET) |
+               JZ_AIC_CONF_OVERFLOW_PLAY_LAST |
+               JZ_AIC_CONF_I2S |
+               JZ_AIC_CONF_INTERNAL_CODEC;
+
+       jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, JZ_AIC_CONF_RESET);
+       jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf);
+
+       return 0;
+}
+
+static struct snd_soc_dai_ops jz4740_i2s_dai_ops = {
+       .startup = jz4740_i2s_startup,
+       .shutdown = jz4740_i2s_shutdown,
+       .trigger = jz4740_i2s_trigger,
+       .hw_params = jz4740_i2s_hw_params,
+       .set_fmt = jz4740_i2s_set_fmt,
+       .set_sysclk = jz4740_i2s_set_sysclk,
+};
+
+#define JZ4740_I2S_FMTS (SNDRV_PCM_FMTBIT_S8 | \
+               SNDRV_PCM_FMTBIT_S16_LE)
+
+struct snd_soc_dai jz4740_i2s_dai = {
+       .name = "jz4740-i2s",
+       .probe = jz4740_i2s_probe,
+       .playback = {
+               .channels_min = 1,
+               .channels_max = 2,
+               .rates = SNDRV_PCM_RATE_8000_48000,
+               .formats = JZ4740_I2S_FMTS,
+       },
+       .capture = {
+               .channels_min = 2,
+               .channels_max = 2,
+               .rates = SNDRV_PCM_RATE_8000_48000,
+               .formats = JZ4740_I2S_FMTS,
+       },
+       .symmetric_rates = 1,
+       .ops = &jz4740_i2s_dai_ops,
+       .suspend = jz4740_i2s_suspend,
+       .resume = jz4740_i2s_resume,
+};
+EXPORT_SYMBOL_GPL(jz4740_i2s_dai);
+
+static void __devinit jz4740_i2c_init_pcm_config(struct jz4740_i2s *i2s)
+{
+       struct jz4740_dma_config *dma_config;
+
+       /* Playback */
+       dma_config = &i2s->pcm_config_playback.dma_config;
+       dma_config->src_width = JZ4740_DMA_WIDTH_32BIT,
+       dma_config->transfer_size = JZ4740_DMA_TRANSFER_SIZE_16BYTE;
+       dma_config->request_type = JZ4740_DMA_TYPE_AIC_TRANSMIT;
+       dma_config->flags = JZ4740_DMA_SRC_AUTOINC;
+       dma_config->mode = JZ4740_DMA_MODE_SINGLE;
+       i2s->pcm_config_playback.fifo_addr = i2s->phys_base + JZ_REG_AIC_FIFO;
+
+       /* Capture */
+       dma_config = &i2s->pcm_config_capture.dma_config;
+       dma_config->dst_width = JZ4740_DMA_WIDTH_32BIT,
+       dma_config->transfer_size = JZ4740_DMA_TRANSFER_SIZE_16BYTE;
+       dma_config->request_type = JZ4740_DMA_TYPE_AIC_RECEIVE;
+       dma_config->flags = JZ4740_DMA_DST_AUTOINC;
+       dma_config->mode = JZ4740_DMA_MODE_SINGLE;
+       i2s->pcm_config_capture.fifo_addr = i2s->phys_base + JZ_REG_AIC_FIFO;
+}
+
+static int __devinit jz4740_i2s_dev_probe(struct platform_device *pdev)
+{
+       struct jz4740_i2s *i2s;
+       int ret;
+
+       i2s = kzalloc(sizeof(*i2s), GFP_KERNEL);
+
+       if (!i2s)
+               return -ENOMEM;
+
+       i2s->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (!i2s->mem) {
+               ret = -ENOENT;
+               goto err_free;
+       }
+
+       i2s->mem = request_mem_region(i2s->mem->start, resource_size(i2s->mem),
+                               pdev->name);
+       if (!i2s->mem) {
+               ret = -EBUSY;
+               goto err_free;
+       }
+
+       i2s->base = ioremap_nocache(i2s->mem->start, resource_size(i2s->mem));
+       if (!i2s->base) {
+               ret = -EBUSY;
+               goto err_release_mem_region;
+       }
+
+       i2s->phys_base = i2s->mem->start;
+
+       i2s->clk_aic = clk_get(&pdev->dev, "aic");
+       if (IS_ERR(i2s->clk_aic)) {
+               ret = PTR_ERR(i2s->clk_aic);
+               goto err_iounmap;
+       }
+
+       i2s->clk_i2s = clk_get(&pdev->dev, "i2s");
+       if (IS_ERR(i2s->clk_i2s)) {
+               ret = PTR_ERR(i2s->clk_i2s);
+               goto err_clk_put_aic;
+       }
+
+       clk_enable(i2s->clk_aic);
+
+       jz4740_i2c_init_pcm_config(i2s);
+
+       jz4740_i2s_dai.private_data = i2s;
+       ret = snd_soc_register_dai(&jz4740_i2s_dai);
+
+       if (ret) {
+               dev_err(&pdev->dev, "Failed to register DAI\n");
+               goto err_clk_put_i2s;
+       }
+
+       platform_set_drvdata(pdev, i2s);
+
+       return 0;
+
+err_clk_put_i2s:
+       clk_disable(i2s->clk_aic);
+       clk_put(i2s->clk_i2s);
+err_clk_put_aic:
+       clk_put(i2s->clk_aic);
+err_iounmap:
+       iounmap(i2s->base);
+err_release_mem_region:
+       release_mem_region(i2s->mem->start, resource_size(i2s->mem));
+err_free:
+       kfree(i2s);
+
+       return ret;
+}
+
+static int __devexit jz4740_i2s_dev_remove(struct platform_device *pdev)
+{
+       struct jz4740_i2s *i2s = platform_get_drvdata(pdev);
+
+       snd_soc_unregister_dai(&jz4740_i2s_dai);
+
+       clk_disable(i2s->clk_aic);
+       clk_put(i2s->clk_i2s);
+       clk_put(i2s->clk_aic);
+
+       iounmap(i2s->base);
+       release_mem_region(i2s->mem->start, resource_size(i2s->mem));
+
+       platform_set_drvdata(pdev, NULL);
+       kfree(i2s);
+
+       return 0;
+}
+
+static struct platform_driver jz4740_i2s_driver = {
+       .probe = jz4740_i2s_dev_probe,
+       .remove = __devexit_p(jz4740_i2s_dev_remove),
+       .driver = {
+               .name = "jz4740-i2s",
+               .owner = THIS_MODULE,
+       },
+};
+
+static int __init jz4740_i2s_init(void)
+{
+       return platform_driver_register(&jz4740_i2s_driver);
+}
+module_init(jz4740_i2s_init);
+
+static void __exit jz4740_i2s_exit(void)
+{
+       platform_driver_unregister(&jz4740_i2s_driver);
+}
+module_exit(jz4740_i2s_exit);
+
+MODULE_AUTHOR("Lars-Peter Clausen, <lars@metafoo.de>");
+MODULE_DESCRIPTION("Ingenic JZ4740 SoC I2S driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:jz4740-i2s");
diff --git a/sound/soc/jz4740/jz4740-i2s.h b/sound/soc/jz4740/jz4740-i2s.h
new file mode 100644 (file)
index 0000000..da22ed8
--- /dev/null
@@ -0,0 +1,18 @@
+/*
+ * 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 _JZ4740_I2S_H
+#define _JZ4740_I2S_H
+
+/* I2S clock source */
+#define JZ4740_I2S_CLKSRC_EXT 0
+#define JZ4740_I2S_CLKSRC_PLL 1
+
+#define JZ4740_I2S_BIT_CLK             0
+
+extern struct snd_soc_dai jz4740_i2s_dai;
+
+#endif
diff --git a/sound/soc/jz4740/jz4740-pcm.c b/sound/soc/jz4740/jz4740-pcm.c
new file mode 100644 (file)
index 0000000..ee68d85
--- /dev/null
@@ -0,0 +1,373 @@
+/*
+ *  Copyright (C) 2010, Lars-Peter Clausen <lars@metafoo.de>
+ *
+ *  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.
+ *
+ *  You should have received a copy of the  GNU General Public License along
+ *  with this program; if not, write  to the Free Software Foundation, Inc.,
+ *  675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.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/mach-jz4740/dma.h>
+#include "jz4740-pcm.h"
+
+struct jz4740_runtime_data {
+       unsigned long dma_period;
+       dma_addr_t dma_start;
+       dma_addr_t dma_pos;
+       dma_addr_t dma_end;
+
+       struct jz4740_dma_chan *dma;
+
+       dma_addr_t fifo_addr;
+};
+
+/* identify hardware playback capabilities */
+static const struct snd_pcm_hardware jz4740_pcm_hardware = {
+       .info = SNDRV_PCM_INFO_MMAP |
+               SNDRV_PCM_INFO_MMAP_VALID |
+               SNDRV_PCM_INFO_INTERLEAVED |
+               SNDRV_PCM_INFO_BLOCK_TRANSFER,
+       .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8,
+
+       .rates                  = SNDRV_PCM_RATE_8000_48000,
+       .channels_min           = 1,
+       .channels_max           = 2,
+       .period_bytes_min       = 16,
+       .period_bytes_max       = 2 * PAGE_SIZE,
+       .periods_min            = 2,
+       .periods_max            = 128,
+       .buffer_bytes_max       = 128 * 2 * PAGE_SIZE,
+       .fifo_size              = 32,
+};
+
+static void jz4740_pcm_start_transfer(struct jz4740_runtime_data *prtd,
+       struct snd_pcm_substream *substream)
+{
+       unsigned long count;
+
+       if (prtd->dma_pos == prtd->dma_end)
+               prtd->dma_pos = prtd->dma_start;
+
+       if (prtd->dma_pos + prtd->dma_period > prtd->dma_end)
+               count = prtd->dma_end - prtd->dma_pos;
+       else
+               count = prtd->dma_period;
+
+       jz4740_dma_disable(prtd->dma);
+
+       if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+               jz4740_dma_set_src_addr(prtd->dma, prtd->dma_pos);
+               jz4740_dma_set_dst_addr(prtd->dma, prtd->fifo_addr);
+       } else {
+               jz4740_dma_set_src_addr(prtd->dma, prtd->fifo_addr);
+               jz4740_dma_set_dst_addr(prtd->dma, prtd->dma_pos);
+       }
+
+       jz4740_dma_set_transfer_count(prtd->dma, count);
+
+       prtd->dma_pos += count;
+
+       jz4740_dma_enable(prtd->dma);
+}
+
+static void jz4740_pcm_dma_transfer_done(struct jz4740_dma_chan *dma, int err,
+       void *dev_id)
+{
+       struct snd_pcm_substream *substream = dev_id;
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct jz4740_runtime_data *prtd = runtime->private_data;
+
+       snd_pcm_period_elapsed(substream);
+
+       jz4740_pcm_start_transfer(prtd, substream);
+}
+
+static int jz4740_pcm_hw_params(struct snd_pcm_substream *substream,
+       struct snd_pcm_hw_params *params)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct jz4740_runtime_data *prtd = runtime->private_data;
+       struct snd_soc_pcm_runtime *rtd = substream->private_data;
+       struct jz4740_pcm_config *config;
+
+       config = snd_soc_dai_get_dma_data(rtd->dai->cpu_dai, substream);
+
+       if (!config)
+               return 0;
+
+       if (!prtd->dma) {
+               if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+                       prtd->dma = jz4740_dma_request(substream, "PCM Capture");
+               else
+                       prtd->dma = jz4740_dma_request(substream, "PCM Playback");
+       }
+
+       if (!prtd->dma)
+               return -EBUSY;
+
+       jz4740_dma_configure(prtd->dma, &config->dma_config);
+       prtd->fifo_addr = config->fifo_addr;
+
+       jz4740_dma_set_complete_cb(prtd->dma, jz4740_pcm_dma_transfer_done);
+
+       snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+       runtime->dma_bytes = params_buffer_bytes(params);
+
+       prtd->dma_period = params_period_bytes(params);
+       prtd->dma_start = runtime->dma_addr;
+       prtd->dma_pos = prtd->dma_start;
+       prtd->dma_end = prtd->dma_start + runtime->dma_bytes;
+
+       return 0;
+}
+
+static int jz4740_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+       struct jz4740_runtime_data *prtd = substream->runtime->private_data;
+
+       snd_pcm_set_runtime_buffer(substream, NULL);
+       if (prtd->dma) {
+               jz4740_dma_free(prtd->dma);
+               prtd->dma = NULL;
+       }
+
+       return 0;
+}
+
+static int jz4740_pcm_prepare(struct snd_pcm_substream *substream)
+{
+       struct jz4740_runtime_data *prtd = substream->runtime->private_data;
+
+       if (!prtd->dma)
+               return -EBUSY;
+
+       prtd->dma_pos = prtd->dma_start;
+
+       return 0;
+}
+
+static int jz4740_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct jz4740_runtime_data *prtd = runtime->private_data;
+
+       switch (cmd) {
+       case SNDRV_PCM_TRIGGER_START:
+       case SNDRV_PCM_TRIGGER_RESUME:
+       case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+               jz4740_pcm_start_transfer(prtd, substream);
+               break;
+       case SNDRV_PCM_TRIGGER_STOP:
+       case SNDRV_PCM_TRIGGER_SUSPEND:
+       case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+               jz4740_dma_disable(prtd->dma);
+               break;
+       default:
+               break;
+       }
+
+       return 0;
+}
+
+static snd_pcm_uframes_t jz4740_pcm_pointer(struct snd_pcm_substream *substream)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct jz4740_runtime_data *prtd = runtime->private_data;
+       unsigned long byte_offset;
+       snd_pcm_uframes_t offset;
+       struct jz4740_dma_chan *dma = prtd->dma;
+
+       /* prtd->dma_pos points to the end of the current transfer. So by
+        * subtracting prdt->dma_start we get the offset to the end of the
+        * current period in bytes. By subtracting the residue of the transfer
+        * we get the current offset in bytes. */
+       byte_offset = prtd->dma_pos - prtd->dma_start;
+       byte_offset -= jz4740_dma_get_residue(dma);
+
+       offset = bytes_to_frames(runtime, byte_offset);
+       if (offset >= runtime->buffer_size)
+               offset = 0;
+
+       return offset;
+}
+
+static int jz4740_pcm_open(struct snd_pcm_substream *substream)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct jz4740_runtime_data *prtd;
+
+       prtd = kzalloc(sizeof(*prtd), GFP_KERNEL);
+       if (prtd == NULL)
+               return -ENOMEM;
+
+       snd_soc_set_runtime_hwparams(substream, &jz4740_pcm_hardware);
+
+       runtime->private_data = prtd;
+
+       return 0;
+}
+
+static int jz4740_pcm_close(struct snd_pcm_substream *substream)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct jz4740_runtime_data *prtd = runtime->private_data;
+
+       kfree(prtd);
+
+       return 0;
+}
+
+static int jz4740_pcm_mmap(struct snd_pcm_substream *substream,
+       struct vm_area_struct *vma)
+{
+       return remap_pfn_range(vma, vma->vm_start,
+                       substream->dma_buffer.addr >> PAGE_SHIFT,
+                       vma->vm_end - vma->vm_start, vma->vm_page_prot);
+}
+
+static struct snd_pcm_ops jz4740_pcm_ops = {
+       .open           = jz4740_pcm_open,
+       .close          = jz4740_pcm_close,
+       .ioctl          = snd_pcm_lib_ioctl,
+       .hw_params      = jz4740_pcm_hw_params,
+       .hw_free        = jz4740_pcm_hw_free,
+       .prepare        = jz4740_pcm_prepare,
+       .trigger        = jz4740_pcm_trigger,
+       .pointer        = jz4740_pcm_pointer,
+       .mmap           = jz4740_pcm_mmap,
+};
+
+static int jz4740_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 = jz4740_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_noncoherent(pcm->card->dev, size,
+                                         &buf->addr, GFP_KERNEL);
+       if (!buf->area)
+               return -ENOMEM;
+
+       buf->bytes = size;
+
+       return 0;
+}
+
+static void jz4740_pcm_free(struct snd_pcm *pcm)
+{
+       struct snd_pcm_substream *substream;
+       struct snd_dma_buffer *buf;
+       int stream;
+
+       for (stream = 0; stream < SNDRV_PCM_STREAM_LAST; ++stream) {
+               substream = pcm->streams[stream].substream;
+               if (!substream)
+                       continue;
+
+               buf = &substream->dma_buffer;
+               if (!buf->area)
+                       continue;
+
+               dma_free_noncoherent(pcm->card->dev, buf->bytes, buf->area,
+                               buf->addr);
+               buf->area = NULL;
+       }
+}
+
+static u64 jz4740_pcm_dmamask = DMA_BIT_MASK(32);
+
+int jz4740_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 = &jz4740_pcm_dmamask;
+
+       if (!card->dev->coherent_dma_mask)
+               card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
+
+       if (dai->playback.channels_min) {
+               ret = jz4740_pcm_preallocate_dma_buffer(pcm,
+                       SNDRV_PCM_STREAM_PLAYBACK);
+               if (ret)
+                       goto err;
+       }
+
+       if (dai->capture.channels_min) {
+               ret = jz4740_pcm_preallocate_dma_buffer(pcm,
+                       SNDRV_PCM_STREAM_CAPTURE);
+               if (ret)
+                       goto err;
+       }
+
+err:
+       return ret;
+}
+
+struct snd_soc_platform jz4740_soc_platform = {
+               .name           = "jz4740-pcm",
+               .pcm_ops        = &jz4740_pcm_ops,
+               .pcm_new        = jz4740_pcm_new,
+               .pcm_free       = jz4740_pcm_free,
+};
+EXPORT_SYMBOL_GPL(jz4740_soc_platform);
+
+static int __devinit jz4740_pcm_probe(struct platform_device *pdev)
+{
+       return snd_soc_register_platform(&jz4740_soc_platform);
+}
+
+static int __devexit jz4740_pcm_remove(struct platform_device *pdev)
+{
+       snd_soc_unregister_platform(&jz4740_soc_platform);
+       return 0;
+}
+
+static struct platform_driver jz4740_pcm_driver = {
+       .probe = jz4740_pcm_probe,
+       .remove = __devexit_p(jz4740_pcm_remove),
+       .driver = {
+               .name = "jz4740-pcm",
+               .owner = THIS_MODULE,
+       },
+};
+
+static int __init jz4740_soc_platform_init(void)
+{
+       return platform_driver_register(&jz4740_pcm_driver);
+}
+module_init(jz4740_soc_platform_init);
+
+static void __exit jz4740_soc_platform_exit(void)
+{
+       return platform_driver_unregister(&jz4740_pcm_driver);
+}
+module_exit(jz4740_soc_platform_exit);
+
+MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
+MODULE_DESCRIPTION("Ingenic SoC JZ4740 PCM driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/jz4740/jz4740-pcm.h b/sound/soc/jz4740/jz4740-pcm.h
new file mode 100644 (file)
index 0000000..e3f221e
--- /dev/null
@@ -0,0 +1,22 @@
+/*
+ *
+ * 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 _JZ4740_PCM_H
+#define _JZ4740_PCM_H
+
+#include <linux/dma-mapping.h>
+#include <asm/mach-jz4740/dma.h>
+
+/* platform data */
+extern struct snd_soc_platform jz4740_soc_platform;
+
+struct jz4740_pcm_config {
+       struct jz4740_dma_config dma_config;
+       phys_addr_t fifo_addr;
+};
+
+#endif
diff --git a/sound/soc/jz4740/qi_lb60.c b/sound/soc/jz4740/qi_lb60.c
new file mode 100644 (file)
index 0000000..f15f491
--- /dev/null
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2009, Lars-Peter Clausen <lars@metafoo.de>
+ *
+ * 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.
+ *
+ *  You should have received a copy of the  GNU General Public License along
+ *  with this program; if not, write  to the Free Software Foundation, Inc.,
+ *  675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <linux/gpio.h>
+
+#include "../codecs/jz4740.h"
+#include "jz4740-pcm.h"
+#include "jz4740-i2s.h"
+
+
+#define QI_LB60_SND_GPIO JZ_GPIO_PORTB(29)
+#define QI_LB60_AMP_GPIO JZ_GPIO_PORTD(4)
+
+static int qi_lb60_spk_event(struct snd_soc_dapm_widget *widget,
+                            struct snd_kcontrol *ctrl, int event)
+{
+       int on = 0;
+       if (event & SND_SOC_DAPM_POST_PMU)
+               on = 1;
+       else if (event & SND_SOC_DAPM_PRE_PMD)
+               on = 0;
+
+       gpio_set_value(QI_LB60_SND_GPIO, on);
+       gpio_set_value(QI_LB60_AMP_GPIO, on);
+
+       return 0;
+}
+
+static const struct snd_soc_dapm_widget qi_lb60_widgets[] = {
+       SND_SOC_DAPM_SPK("Speaker", qi_lb60_spk_event),
+       SND_SOC_DAPM_MIC("Mic", NULL),
+};
+
+static const struct snd_soc_dapm_route qi_lb60_routes[] = {
+       {"Mic", NULL, "MIC"},
+       {"Speaker", NULL, "LOUT"},
+       {"Speaker", NULL, "ROUT"},
+};
+
+#define QI_LB60_DAIFMT (SND_SOC_DAIFMT_I2S | \
+                       SND_SOC_DAIFMT_NB_NF | \
+                       SND_SOC_DAIFMT_CBM_CFM)
+
+static int qi_lb60_codec_init(struct snd_soc_codec *codec)
+{
+       int ret;
+       struct snd_soc_dai *cpu_dai = codec->socdev->card->dai_link->cpu_dai;
+
+       snd_soc_dapm_nc_pin(codec, "LIN");
+       snd_soc_dapm_nc_pin(codec, "RIN");
+
+       ret = snd_soc_dai_set_fmt(cpu_dai, QI_LB60_DAIFMT);
+       if (ret < 0) {
+               dev_err(codec->dev, "Failed to set cpu dai format: %d\n", ret);
+               return ret;
+       }
+
+       snd_soc_dapm_new_controls(codec, qi_lb60_widgets, ARRAY_SIZE(qi_lb60_widgets));
+       snd_soc_dapm_add_routes(codec, qi_lb60_routes, ARRAY_SIZE(qi_lb60_routes));
+       snd_soc_dapm_sync(codec);
+
+       return 0;
+}
+
+static struct snd_soc_dai_link qi_lb60_dai = {
+       .name = "jz4740",
+       .stream_name = "jz4740",
+       .cpu_dai = &jz4740_i2s_dai,
+       .codec_dai = &jz4740_codec_dai,
+       .init = qi_lb60_codec_init,
+};
+
+static struct snd_soc_card qi_lb60 = {
+       .name = "QI LB60",
+       .dai_link = &qi_lb60_dai,
+       .num_links = 1,
+       .platform = &jz4740_soc_platform,
+};
+
+static struct snd_soc_device qi_lb60_snd_devdata = {
+       .card = &qi_lb60,
+       .codec_dev = &soc_codec_dev_jz4740_codec,
+};
+
+static struct platform_device *qi_lb60_snd_device;
+
+static int __init qi_lb60_init(void)
+{
+       int ret;
+
+       qi_lb60_snd_device = platform_device_alloc("soc-audio", -1);
+
+       if (!qi_lb60_snd_device)
+               return -ENOMEM;
+
+       ret = gpio_request(QI_LB60_SND_GPIO, "SND");
+       if (ret) {
+               pr_err("qi_lb60 snd: Failed to request SND GPIO(%d): %d\n",
+                               QI_LB60_SND_GPIO, ret);
+               goto err_device_put;
+       }
+
+       ret = gpio_request(QI_LB60_AMP_GPIO, "AMP");
+       if (ret) {
+               pr_err("qi_lb60 snd: Failed to request AMP GPIO(%d): %d\n",
+                               QI_LB60_AMP_GPIO, ret);
+               goto err_gpio_free_snd;
+       }
+
+       gpio_direction_output(QI_LB60_SND_GPIO, 0);
+       gpio_direction_output(QI_LB60_AMP_GPIO, 0);
+
+       platform_set_drvdata(qi_lb60_snd_device, &qi_lb60_snd_devdata);
+       qi_lb60_snd_devdata.dev = &qi_lb60_snd_device->dev;
+
+       ret = platform_device_add(qi_lb60_snd_device);
+       if (ret) {
+               pr_err("qi_lb60 snd: Failed to add snd soc device: %d\n", ret);
+               goto err_unset_pdata;
+       }
+
+        return 0;
+
+err_unset_pdata:
+       platform_set_drvdata(qi_lb60_snd_device, NULL);
+/*err_gpio_free_amp:*/
+       gpio_free(QI_LB60_AMP_GPIO);
+err_gpio_free_snd:
+       gpio_free(QI_LB60_SND_GPIO);
+err_device_put:
+       platform_device_put(qi_lb60_snd_device);
+
+       return ret;
+}
+module_init(qi_lb60_init);
+
+static void __exit qi_lb60_exit(void)
+{
+       gpio_free(QI_LB60_AMP_GPIO);
+       gpio_free(QI_LB60_SND_GPIO);
+       platform_device_unregister(qi_lb60_snd_device);
+}
+module_exit(qi_lb60_exit);
+
+MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
+MODULE_DESCRIPTION("ALSA SoC QI LB60 Audio support");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/kirkwood/Kconfig b/sound/soc/kirkwood/Kconfig
new file mode 100644 (file)
index 0000000..16ec2a2
--- /dev/null
@@ -0,0 +1,20 @@
+config SND_KIRKWOOD_SOC
+       tristate "SoC Audio for the Marvell Kirkwood chip"
+       depends on ARCH_KIRKWOOD
+       help
+         Say Y or M if you want to add support for codecs attached to
+         the Kirkwood I2S interface. You will also need to select the
+         audio interfaces to support below.
+
+config SND_KIRKWOOD_SOC_I2S
+       tristate
+
+config SND_KIRKWOOD_SOC_OPENRD
+       tristate "SoC Audio support for Kirkwood Openrd Client"
+       depends on SND_KIRKWOOD_SOC && MACH_OPENRD_CLIENT
+       select SND_KIRKWOOD_SOC_I2S
+       select SND_SOC_CS42L51
+       help
+         Say Y if you want to add support for SoC audio on
+         Openrd Client.
+
diff --git a/sound/soc/kirkwood/Makefile b/sound/soc/kirkwood/Makefile
new file mode 100644 (file)
index 0000000..33a16dc
--- /dev/null
@@ -0,0 +1,9 @@
+snd-soc-kirkwood-objs := kirkwood-dma.o
+snd-soc-kirkwood-i2s-objs := kirkwood-i2s.o
+
+obj-$(CONFIG_SND_KIRKWOOD_SOC) += snd-soc-kirkwood.o
+obj-$(CONFIG_SND_KIRKWOOD_SOC_I2S) += snd-soc-kirkwood-i2s.o
+
+snd-soc-openrd-objs := kirkwood-openrd.o
+
+obj-$(CONFIG_SND_KIRKWOOD_SOC_OPENRD) += snd-soc-openrd.o
diff --git a/sound/soc/kirkwood/kirkwood-dma.c b/sound/soc/kirkwood/kirkwood-dma.c
new file mode 100644 (file)
index 0000000..a30205b
--- /dev/null
@@ -0,0 +1,383 @@
+/*
+ * kirkwood-dma.c
+ *
+ * (c) 2010 Arnaud Patard <apatard@mandriva.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/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/dma-mapping.h>
+#include <linux/mbus.h>
+#include <sound/soc.h>
+#include "kirkwood-dma.h"
+#include "kirkwood.h"
+
+#define KIRKWOOD_RATES \
+       (SNDRV_PCM_RATE_44100 | \
+        SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000)
+#define KIRKWOOD_FORMATS \
+       (SNDRV_PCM_FMTBIT_S16_LE | \
+        SNDRV_PCM_FMTBIT_S24_LE | \
+        SNDRV_PCM_FMTBIT_S32_LE)
+
+struct kirkwood_dma_priv {
+       struct snd_pcm_substream *play_stream;
+       struct snd_pcm_substream *rec_stream;
+       struct kirkwood_dma_data *data;
+};
+
+static struct snd_pcm_hardware kirkwood_dma_snd_hw = {
+       .info = (SNDRV_PCM_INFO_INTERLEAVED |
+                SNDRV_PCM_INFO_MMAP |
+                SNDRV_PCM_INFO_MMAP_VALID |
+                SNDRV_PCM_INFO_BLOCK_TRANSFER |
+                SNDRV_PCM_INFO_PAUSE),
+       .formats                = KIRKWOOD_FORMATS,
+       .rates                  = KIRKWOOD_RATES,
+       .rate_min               = 44100,
+       .rate_max               = 96000,
+       .channels_min           = 1,
+       .channels_max           = 2,
+       .buffer_bytes_max       = KIRKWOOD_SND_MAX_PERIOD_BYTES * KIRKWOOD_SND_MAX_PERIODS,
+       .period_bytes_min       = KIRKWOOD_SND_MIN_PERIOD_BYTES,
+       .period_bytes_max       = KIRKWOOD_SND_MAX_PERIOD_BYTES,
+       .periods_min            = KIRKWOOD_SND_MIN_PERIODS,
+       .periods_max            = KIRKWOOD_SND_MAX_PERIODS,
+       .fifo_size              = 0,
+};
+
+static u64 kirkwood_dma_dmamask = 0xFFFFFFFFUL;
+
+static irqreturn_t kirkwood_dma_irq(int irq, void *dev_id)
+{
+       struct kirkwood_dma_priv *prdata = dev_id;
+       struct kirkwood_dma_data *priv = prdata->data;
+       unsigned long mask, status, cause;
+
+       mask = readl(priv->io + KIRKWOOD_INT_MASK);
+       status = readl(priv->io + KIRKWOOD_INT_CAUSE) & mask;
+
+       cause = readl(priv->io + KIRKWOOD_ERR_CAUSE);
+       if (unlikely(cause)) {
+               printk(KERN_WARNING "%s: got err interrupt 0x%lx\n",
+                               __func__, cause);
+               writel(cause, priv->io + KIRKWOOD_ERR_CAUSE);
+               return IRQ_HANDLED;
+       }
+
+       /* we've enabled only bytes interrupts ... */
+       if (status & ~(KIRKWOOD_INT_CAUSE_PLAY_BYTES | \
+                       KIRKWOOD_INT_CAUSE_REC_BYTES)) {
+               printk(KERN_WARNING "%s: unexpected interrupt %lx\n",
+                       __func__, status);
+               return IRQ_NONE;
+       }
+
+       /* ack int */
+       writel(status, priv->io + KIRKWOOD_INT_CAUSE);
+
+       if (status & KIRKWOOD_INT_CAUSE_PLAY_BYTES)
+               snd_pcm_period_elapsed(prdata->play_stream);
+
+       if (status & KIRKWOOD_INT_CAUSE_REC_BYTES)
+               snd_pcm_period_elapsed(prdata->rec_stream);
+
+       return IRQ_HANDLED;
+}
+
+static void kirkwood_dma_conf_mbus_windows(void __iomem *base, int win,
+                                       unsigned long dma,
+                                       struct mbus_dram_target_info *dram)
+{
+       int i;
+
+       /* First disable and clear windows */
+       writel(0, base + KIRKWOOD_AUDIO_WIN_CTRL_REG(win));
+       writel(0, base + KIRKWOOD_AUDIO_WIN_BASE_REG(win));
+
+       /* try to find matching cs for current dma address */
+       for (i = 0; i < dram->num_cs; i++) {
+               struct mbus_dram_window *cs = dram->cs + i;
+               if ((cs->base & 0xffff0000) < (dma & 0xffff0000)) {
+                       writel(cs->base & 0xffff0000,
+                               base + KIRKWOOD_AUDIO_WIN_BASE_REG(win));
+                       writel(((cs->size - 1) & 0xffff0000) |
+                               (cs->mbus_attr << 8) |
+                               (dram->mbus_dram_target_id << 4) | 1,
+                               base + KIRKWOOD_AUDIO_WIN_CTRL_REG(win));
+               }
+       }
+}
+
+static int kirkwood_dma_open(struct snd_pcm_substream *substream)
+{
+       int err;
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
+       struct snd_soc_dai *cpu_dai = soc_runtime->dai->cpu_dai;
+       struct kirkwood_dma_data *priv;
+       struct kirkwood_dma_priv *prdata = cpu_dai->private_data;
+       unsigned long addr;
+
+       priv = snd_soc_dai_get_dma_data(cpu_dai, substream);
+       snd_soc_set_runtime_hwparams(substream, &kirkwood_dma_snd_hw);
+
+       /* Ensure that all constraints linked to dma burst are fullfilled */
+       err = snd_pcm_hw_constraint_minmax(runtime,
+                       SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
+                       priv->burst * 2,
+                       KIRKWOOD_AUDIO_BUF_MAX-1);
+       if (err < 0)
+               return err;
+
+       err = snd_pcm_hw_constraint_step(runtime, 0,
+                       SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
+                       priv->burst);
+       if (err < 0)
+               return err;
+
+       err = snd_pcm_hw_constraint_step(substream->runtime, 0,
+                        SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
+                        priv->burst);
+       if (err < 0)
+               return err;
+
+       if (soc_runtime->dai->cpu_dai->private_data == NULL) {
+               prdata = kzalloc(sizeof(struct kirkwood_dma_priv), GFP_KERNEL);
+               if (prdata == NULL)
+                       return -ENOMEM;
+
+               prdata->data = priv;
+
+               err = request_irq(priv->irq, kirkwood_dma_irq, IRQF_SHARED,
+                                 "kirkwood-i2s", prdata);
+               if (err) {
+                       kfree(prdata);
+                       return -EBUSY;
+               }
+
+               soc_runtime->dai->cpu_dai->private_data = prdata;
+
+               /*
+                * Enable Error interrupts. We're only ack'ing them but
+                * it's usefull for diagnostics
+                */
+               writel((unsigned long)-1, priv->io + KIRKWOOD_ERR_MASK);
+       }
+
+       addr = virt_to_phys(substream->dma_buffer.area);
+       if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+               prdata->play_stream = substream;
+               kirkwood_dma_conf_mbus_windows(priv->io,
+                       KIRKWOOD_PLAYBACK_WIN, addr, priv->dram);
+       } else {
+               prdata->rec_stream = substream;
+               kirkwood_dma_conf_mbus_windows(priv->io,
+                       KIRKWOOD_RECORD_WIN, addr, priv->dram);
+       }
+
+       return 0;
+}
+
+static int kirkwood_dma_close(struct snd_pcm_substream *substream)
+{
+       struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
+       struct snd_soc_dai *cpu_dai = soc_runtime->dai->cpu_dai;
+       struct kirkwood_dma_priv *prdata = cpu_dai->private_data;
+       struct kirkwood_dma_data *priv;
+
+       priv = snd_soc_dai_get_dma_data(cpu_dai, substream);
+
+       if (!prdata || !priv)
+               return 0;
+
+       if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+               prdata->play_stream = NULL;
+       else
+               prdata->rec_stream = NULL;
+
+       if (!prdata->play_stream && !prdata->rec_stream) {
+               writel(0, priv->io + KIRKWOOD_ERR_MASK);
+               free_irq(priv->irq, prdata);
+               kfree(prdata);
+               soc_runtime->dai->cpu_dai->private_data = NULL;
+       }
+
+       return 0;
+}
+
+static int kirkwood_dma_hw_params(struct snd_pcm_substream *substream,
+                               struct snd_pcm_hw_params *params)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+
+       snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+       runtime->dma_bytes = params_buffer_bytes(params);
+
+       return 0;
+}
+
+static int kirkwood_dma_hw_free(struct snd_pcm_substream *substream)
+{
+       snd_pcm_set_runtime_buffer(substream, NULL);
+       return 0;
+}
+
+static int kirkwood_dma_prepare(struct snd_pcm_substream *substream)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
+       struct snd_soc_dai *cpu_dai = soc_runtime->dai->cpu_dai;
+       struct kirkwood_dma_data *priv;
+       unsigned long size, count;
+
+       priv = snd_soc_dai_get_dma_data(cpu_dai, substream);
+
+       /* compute buffer size in term of "words" as requested in specs */
+       size = frames_to_bytes(runtime, runtime->buffer_size);
+       size = (size>>2)-1;
+       count = snd_pcm_lib_period_bytes(substream);
+
+       if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+               writel(count, priv->io + KIRKWOOD_PLAY_BYTE_INT_COUNT);
+               writel(runtime->dma_addr, priv->io + KIRKWOOD_PLAY_BUF_ADDR);
+               writel(size, priv->io + KIRKWOOD_PLAY_BUF_SIZE);
+       } else {
+               writel(count, priv->io + KIRKWOOD_REC_BYTE_INT_COUNT);
+               writel(runtime->dma_addr, priv->io + KIRKWOOD_REC_BUF_ADDR);
+               writel(size, priv->io + KIRKWOOD_REC_BUF_SIZE);
+       }
+
+
+       return 0;
+}
+
+static snd_pcm_uframes_t kirkwood_dma_pointer(struct snd_pcm_substream
+                                               *substream)
+{
+       struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
+       struct snd_soc_dai *cpu_dai = soc_runtime->dai->cpu_dai;
+       struct kirkwood_dma_data *priv;
+       snd_pcm_uframes_t count;
+
+       priv = snd_soc_dai_get_dma_data(cpu_dai, substream);
+
+       if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+               count = bytes_to_frames(substream->runtime,
+                       readl(priv->io + KIRKWOOD_PLAY_BYTE_COUNT));
+       else
+               count = bytes_to_frames(substream->runtime,
+                       readl(priv->io + KIRKWOOD_REC_BYTE_COUNT));
+
+       return count;
+}
+
+struct snd_pcm_ops kirkwood_dma_ops = {
+       .open =         kirkwood_dma_open,
+       .close =        kirkwood_dma_close,
+       .ioctl =        snd_pcm_lib_ioctl,
+       .hw_params =    kirkwood_dma_hw_params,
+       .hw_free =      kirkwood_dma_hw_free,
+       .prepare =      kirkwood_dma_prepare,
+       .pointer =      kirkwood_dma_pointer,
+};
+
+static int kirkwood_dma_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 = kirkwood_dma_snd_hw.buffer_bytes_max;
+
+       buf->dev.type = SNDRV_DMA_TYPE_DEV;
+       buf->dev.dev = pcm->card->dev;
+       buf->area = dma_alloc_coherent(pcm->card->dev, size,
+                       &buf->addr, GFP_KERNEL);
+       if (!buf->area)
+               return -ENOMEM;
+       buf->bytes = size;
+       buf->private_data = NULL;
+
+       return 0;
+}
+
+static int kirkwood_dma_new(struct snd_card *card,
+               struct snd_soc_dai *dai, struct snd_pcm *pcm)
+{
+       int ret;
+
+       if (!card->dev->dma_mask)
+               card->dev->dma_mask = &kirkwood_dma_dmamask;
+       if (!card->dev->coherent_dma_mask)
+               card->dev->coherent_dma_mask = 0xffffffff;
+
+       if (dai->playback.channels_min) {
+               ret = kirkwood_dma_preallocate_dma_buffer(pcm,
+                               SNDRV_PCM_STREAM_PLAYBACK);
+               if (ret)
+                       return ret;
+       }
+
+       if (dai->capture.channels_min) {
+               ret = kirkwood_dma_preallocate_dma_buffer(pcm,
+                               SNDRV_PCM_STREAM_CAPTURE);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
+}
+
+static void kirkwood_dma_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(pcm->card->dev, buf->bytes,
+                               buf->area, buf->addr);
+               buf->area = NULL;
+       }
+}
+
+struct snd_soc_platform kirkwood_soc_platform = {
+       .name           = "kirkwood-dma",
+       .pcm_ops        = &kirkwood_dma_ops,
+       .pcm_new        = kirkwood_dma_new,
+       .pcm_free       = kirkwood_dma_free_dma_buffers,
+};
+EXPORT_SYMBOL_GPL(kirkwood_soc_platform);
+
+static int __init kirkwood_soc_platform_init(void)
+{
+       return snd_soc_register_platform(&kirkwood_soc_platform);
+}
+module_init(kirkwood_soc_platform_init);
+
+static void __exit kirkwood_soc_platform_exit(void)
+{
+       snd_soc_unregister_platform(&kirkwood_soc_platform);
+}
+module_exit(kirkwood_soc_platform_exit);
+
+MODULE_AUTHOR("Arnaud Patard <apatard@mandriva.com>");
+MODULE_DESCRIPTION("Marvell Kirkwood Audio DMA module");
+MODULE_LICENSE("GPL");
+
diff --git a/sound/soc/kirkwood/kirkwood-dma.h b/sound/soc/kirkwood/kirkwood-dma.h
new file mode 100644 (file)
index 0000000..ba4454c
--- /dev/null
@@ -0,0 +1,17 @@
+/*
+ * kirkwood-dma.h
+ *
+ * (c) 2010 Arnaud Patard <apatard@mandriva.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 _KIRKWOOD_DMA_H
+#define _KIRKWOOD_DMA_H
+
+extern struct snd_soc_platform kirkwood_soc_platform;
+
+#endif
diff --git a/sound/soc/kirkwood/kirkwood-i2s.c b/sound/soc/kirkwood/kirkwood-i2s.c
new file mode 100644 (file)
index 0000000..981ffc2
--- /dev/null
@@ -0,0 +1,495 @@
+/*
+ * kirkwood-i2s.c
+ *
+ * (c) 2010 Arnaud Patard <apatard@mandriva.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/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/mbus.h>
+#include <linux/delay.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <plat/audio.h>
+#include "kirkwood-i2s.h"
+#include "kirkwood.h"
+
+#define DRV_NAME       "kirkwood-i2s"
+
+#define KIRKWOOD_I2S_RATES \
+       (SNDRV_PCM_RATE_44100 | \
+        SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000)
+#define KIRKWOOD_I2S_FORMATS \
+       (SNDRV_PCM_FMTBIT_S16_LE | \
+        SNDRV_PCM_FMTBIT_S24_LE | \
+        SNDRV_PCM_FMTBIT_S32_LE)
+
+
+struct snd_soc_dai kirkwood_i2s_dai;
+static struct kirkwood_dma_data *priv;
+
+static int kirkwood_i2s_set_fmt(struct snd_soc_dai *cpu_dai,
+               unsigned int fmt)
+{
+       unsigned long mask;
+       unsigned long value;
+
+       switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+       case SND_SOC_DAIFMT_RIGHT_J:
+               mask = KIRKWOOD_I2S_CTL_RJ;
+               break;
+       case SND_SOC_DAIFMT_LEFT_J:
+               mask = KIRKWOOD_I2S_CTL_LJ;
+               break;
+       case SND_SOC_DAIFMT_I2S:
+               mask = KIRKWOOD_I2S_CTL_I2S;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       /*
+        * Set same format for playback and record
+        * This avoids some troubles.
+        */
+       value = readl(priv->io+KIRKWOOD_I2S_PLAYCTL);
+       value &= ~KIRKWOOD_I2S_CTL_JUST_MASK;
+       value |= mask;
+       writel(value, priv->io+KIRKWOOD_I2S_PLAYCTL);
+
+       value = readl(priv->io+KIRKWOOD_I2S_RECCTL);
+       value &= ~KIRKWOOD_I2S_CTL_JUST_MASK;
+       value |= mask;
+       writel(value, priv->io+KIRKWOOD_I2S_RECCTL);
+
+       return 0;
+}
+
+static inline void kirkwood_set_dco(void __iomem *io, unsigned long rate)
+{
+       unsigned long value;
+
+       value = KIRKWOOD_DCO_CTL_OFFSET_0;
+       switch (rate) {
+       default:
+       case 44100:
+               value |= KIRKWOOD_DCO_CTL_FREQ_11;
+               break;
+       case 48000:
+               value |= KIRKWOOD_DCO_CTL_FREQ_12;
+               break;
+       case 96000:
+               value |= KIRKWOOD_DCO_CTL_FREQ_24;
+               break;
+       }
+       writel(value, io + KIRKWOOD_DCO_CTL);
+
+       /* wait for dco locked */
+       do {
+               cpu_relax();
+               value = readl(io + KIRKWOOD_DCO_SPCR_STATUS);
+               value &= KIRKWOOD_DCO_SPCR_STATUS;
+       } while (value == 0);
+}
+
+static int kirkwood_i2s_hw_params(struct snd_pcm_substream *substream,
+                                struct snd_pcm_hw_params *params,
+                                struct snd_soc_dai *dai)
+{
+       unsigned int i2s_reg, reg;
+       unsigned long i2s_value, value;
+
+       if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+               i2s_reg = KIRKWOOD_I2S_PLAYCTL;
+               reg = KIRKWOOD_PLAYCTL;
+       } else {
+               i2s_reg = KIRKWOOD_I2S_RECCTL;
+               reg = KIRKWOOD_RECCTL;
+       }
+
+       /* set dco conf */
+       kirkwood_set_dco(priv->io, params_rate(params));
+
+       i2s_value = readl(priv->io+i2s_reg);
+       i2s_value &= ~KIRKWOOD_I2S_CTL_SIZE_MASK;
+
+       value = readl(priv->io+reg);
+       value &= ~KIRKWOOD_PLAYCTL_SIZE_MASK;
+
+       /*
+        * Size settings in play/rec i2s control regs and play/rec control
+        * regs must be the same.
+        */
+       switch (params_format(params)) {
+       case SNDRV_PCM_FORMAT_S16_LE:
+               i2s_value |= KIRKWOOD_I2S_CTL_SIZE_16;
+               value |= KIRKWOOD_PLAYCTL_SIZE_16_C;
+               break;
+       /*
+        * doesn't work... S20_3LE != kirkwood 20bit format ?
+        *
+       case SNDRV_PCM_FORMAT_S20_3LE:
+               i2s_value |= KIRKWOOD_I2S_CTL_SIZE_20;
+               value |= KIRKWOOD_PLAYCTL_SIZE_20;
+               break;
+       */
+       case SNDRV_PCM_FORMAT_S24_LE:
+               i2s_value |= KIRKWOOD_I2S_CTL_SIZE_24;
+               value |= KIRKWOOD_PLAYCTL_SIZE_24;
+               break;
+       case SNDRV_PCM_FORMAT_S32_LE:
+               i2s_value |= KIRKWOOD_I2S_CTL_SIZE_32;
+               value |= KIRKWOOD_PLAYCTL_SIZE_32;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+               value &= ~KIRKWOOD_PLAYCTL_MONO_MASK;
+               if (params_channels(params) == 1)
+                       value |= KIRKWOOD_PLAYCTL_MONO_BOTH;
+               else
+                       value |= KIRKWOOD_PLAYCTL_MONO_OFF;
+       }
+
+       writel(i2s_value, priv->io+i2s_reg);
+       writel(value, priv->io+reg);
+
+       return 0;
+}
+
+static int kirkwood_i2s_play_trigger(struct snd_pcm_substream *substream,
+                               int cmd, struct snd_soc_dai *dai)
+{
+       unsigned long value;
+
+       /*
+        * specs says KIRKWOOD_PLAYCTL must be read 2 times before
+        * changing it. So read 1 time here and 1 later.
+        */
+       value = readl(priv->io + KIRKWOOD_PLAYCTL);
+
+       switch (cmd) {
+       case SNDRV_PCM_TRIGGER_START:
+               /* stop audio, enable interrupts */
+               value = readl(priv->io + KIRKWOOD_PLAYCTL);
+               value |= KIRKWOOD_PLAYCTL_PAUSE;
+               writel(value, priv->io + KIRKWOOD_PLAYCTL);
+
+               value = readl(priv->io + KIRKWOOD_INT_MASK);
+               value |= KIRKWOOD_INT_CAUSE_PLAY_BYTES;
+               writel(value, priv->io + KIRKWOOD_INT_MASK);
+
+               /* configure audio & enable i2s playback */
+               value = readl(priv->io + KIRKWOOD_PLAYCTL);
+               value &= ~KIRKWOOD_PLAYCTL_BURST_MASK;
+               value &= ~(KIRKWOOD_PLAYCTL_PAUSE | KIRKWOOD_PLAYCTL_I2S_MUTE
+                               | KIRKWOOD_PLAYCTL_SPDIF_EN);
+
+               if (priv->burst == 32)
+                       value |= KIRKWOOD_PLAYCTL_BURST_32;
+               else
+                       value |= KIRKWOOD_PLAYCTL_BURST_128;
+               value |= KIRKWOOD_PLAYCTL_I2S_EN;
+               writel(value, priv->io + KIRKWOOD_PLAYCTL);
+               break;
+
+       case SNDRV_PCM_TRIGGER_STOP:
+               /* stop audio, disable interrupts */
+               value = readl(priv->io + KIRKWOOD_PLAYCTL);
+               value |= KIRKWOOD_PLAYCTL_PAUSE | KIRKWOOD_PLAYCTL_I2S_MUTE;
+               writel(value, priv->io + KIRKWOOD_PLAYCTL);
+
+               value = readl(priv->io + KIRKWOOD_INT_MASK);
+               value &= ~KIRKWOOD_INT_CAUSE_PLAY_BYTES;
+               writel(value, priv->io + KIRKWOOD_INT_MASK);
+
+               /* disable all playbacks */
+               value = readl(priv->io + KIRKWOOD_PLAYCTL);
+               value &= ~(KIRKWOOD_PLAYCTL_I2S_EN | KIRKWOOD_PLAYCTL_SPDIF_EN);
+               writel(value, priv->io + KIRKWOOD_PLAYCTL);
+               break;
+
+       case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+       case SNDRV_PCM_TRIGGER_SUSPEND:
+               value = readl(priv->io + KIRKWOOD_PLAYCTL);
+               value |= KIRKWOOD_PLAYCTL_PAUSE | KIRKWOOD_PLAYCTL_I2S_MUTE;
+               writel(value, priv->io + KIRKWOOD_PLAYCTL);
+               break;
+
+       case SNDRV_PCM_TRIGGER_RESUME:
+       case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+               value = readl(priv->io + KIRKWOOD_PLAYCTL);
+               value &= ~(KIRKWOOD_PLAYCTL_PAUSE | KIRKWOOD_PLAYCTL_I2S_MUTE);
+               writel(value, priv->io + KIRKWOOD_PLAYCTL);
+               break;
+
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int kirkwood_i2s_rec_trigger(struct snd_pcm_substream *substream,
+                               int cmd, struct snd_soc_dai *dai)
+{
+       unsigned long value;
+
+       value = readl(priv->io + KIRKWOOD_RECCTL);
+
+       switch (cmd) {
+       case SNDRV_PCM_TRIGGER_START:
+               /* stop audio, enable interrupts */
+               value = readl(priv->io + KIRKWOOD_RECCTL);
+               value |= KIRKWOOD_RECCTL_PAUSE;
+               writel(value, priv->io + KIRKWOOD_RECCTL);
+
+               value = readl(priv->io + KIRKWOOD_INT_MASK);
+               value |= KIRKWOOD_INT_CAUSE_REC_BYTES;
+               writel(value, priv->io + KIRKWOOD_INT_MASK);
+
+               /* configure audio & enable i2s record */
+               value = readl(priv->io + KIRKWOOD_RECCTL);
+               value &= ~KIRKWOOD_RECCTL_BURST_MASK;
+               value &= ~KIRKWOOD_RECCTL_MONO;
+               value &= ~(KIRKWOOD_RECCTL_PAUSE | KIRKWOOD_RECCTL_MUTE
+                       | KIRKWOOD_RECCTL_SPDIF_EN);
+
+               if (priv->burst == 32)
+                       value |= KIRKWOOD_RECCTL_BURST_32;
+               else
+                       value |= KIRKWOOD_RECCTL_BURST_128;
+               value |= KIRKWOOD_RECCTL_I2S_EN;
+
+               writel(value, priv->io + KIRKWOOD_RECCTL);
+               break;
+
+       case SNDRV_PCM_TRIGGER_STOP:
+               /* stop audio, disable interrupts */
+               value = readl(priv->io + KIRKWOOD_RECCTL);
+               value |= KIRKWOOD_RECCTL_PAUSE | KIRKWOOD_RECCTL_MUTE;
+               writel(value, priv->io + KIRKWOOD_RECCTL);
+
+               value = readl(priv->io + KIRKWOOD_INT_MASK);
+               value &= ~KIRKWOOD_INT_CAUSE_REC_BYTES;
+               writel(value, priv->io + KIRKWOOD_INT_MASK);
+
+               /* disable all records */
+               value = readl(priv->io + KIRKWOOD_RECCTL);
+               value &= ~(KIRKWOOD_RECCTL_I2S_EN | KIRKWOOD_RECCTL_SPDIF_EN);
+               writel(value, priv->io + KIRKWOOD_RECCTL);
+               break;
+
+       case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+       case SNDRV_PCM_TRIGGER_SUSPEND:
+               value = readl(priv->io + KIRKWOOD_RECCTL);
+               value |= KIRKWOOD_RECCTL_PAUSE | KIRKWOOD_RECCTL_MUTE;
+               writel(value, priv->io + KIRKWOOD_RECCTL);
+               break;
+
+       case SNDRV_PCM_TRIGGER_RESUME:
+       case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+               value = readl(priv->io + KIRKWOOD_RECCTL);
+               value &= ~(KIRKWOOD_RECCTL_PAUSE | KIRKWOOD_RECCTL_MUTE);
+               writel(value, priv->io + KIRKWOOD_RECCTL);
+               break;
+
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int kirkwood_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
+                              struct snd_soc_dai *dai)
+{
+       if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+               return kirkwood_i2s_play_trigger(substream, cmd, dai);
+       else
+               return kirkwood_i2s_rec_trigger(substream, cmd, dai);
+
+       return 0;
+}
+
+static int kirkwood_i2s_probe(struct platform_device *pdev,
+                            struct snd_soc_dai *dai)
+{
+       unsigned long value;
+       unsigned int reg_data;
+
+       /* put system in a "safe" state : */
+       /* disable audio interrupts */
+       writel(0xffffffff, priv->io + KIRKWOOD_INT_CAUSE);
+       writel(0, priv->io + KIRKWOOD_INT_MASK);
+
+       reg_data = readl(priv->io + 0x1200);
+       reg_data &= (~(0x333FF8));
+       reg_data |= 0x111D18;
+       writel(reg_data, priv->io + 0x1200);
+
+       msleep(500);
+
+       reg_data = readl(priv->io + 0x1200);
+       reg_data &= (~(0x333FF8));
+       reg_data |= 0x111D18;
+       writel(reg_data, priv->io + 0x1200);
+
+       /* disable playback/record */
+       value = readl(priv->io + KIRKWOOD_PLAYCTL);
+       value &= ~(KIRKWOOD_PLAYCTL_I2S_EN|KIRKWOOD_PLAYCTL_SPDIF_EN);
+       writel(value, priv->io + KIRKWOOD_PLAYCTL);
+
+       value = readl(priv->io + KIRKWOOD_RECCTL);
+       value &= ~(KIRKWOOD_RECCTL_I2S_EN | KIRKWOOD_RECCTL_SPDIF_EN);
+       writel(value, priv->io + KIRKWOOD_RECCTL);
+
+       return 0;
+
+}
+
+static void kirkwood_i2s_remove(struct platform_device *pdev,
+                               struct snd_soc_dai *dai)
+{
+}
+
+static struct snd_soc_dai_ops kirkwood_i2s_dai_ops = {
+       .trigger        = kirkwood_i2s_trigger,
+       .hw_params      = kirkwood_i2s_hw_params,
+       .set_fmt        = kirkwood_i2s_set_fmt,
+};
+
+
+struct snd_soc_dai kirkwood_i2s_dai = {
+       .name = DRV_NAME,
+       .id = 0,
+       .probe = kirkwood_i2s_probe,
+       .remove = kirkwood_i2s_remove,
+       .playback = {
+               .channels_min = 1,
+               .channels_max = 2,
+               .rates = KIRKWOOD_I2S_RATES,
+               .formats = KIRKWOOD_I2S_FORMATS,},
+       .capture = {
+               .channels_min = 1,
+               .channels_max = 2,
+               .rates = KIRKWOOD_I2S_RATES,
+               .formats = KIRKWOOD_I2S_FORMATS,},
+       .ops = &kirkwood_i2s_dai_ops,
+};
+EXPORT_SYMBOL_GPL(kirkwood_i2s_dai);
+
+static __devinit int kirkwood_i2s_dev_probe(struct platform_device *pdev)
+{
+       struct resource *mem;
+       struct kirkwood_asoc_platform_data *data =
+               pdev->dev.platform_data;
+       int err;
+
+       priv = kzalloc(sizeof(struct kirkwood_dma_data), GFP_KERNEL);
+       if (!priv) {
+               dev_err(&pdev->dev, "allocation failed\n");
+               err = -ENOMEM;
+               goto error;
+       }
+
+       mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (!mem) {
+               dev_err(&pdev->dev, "platform_get_resource failed\n");
+               err = -ENXIO;
+               goto err_alloc;
+       }
+
+       priv->mem = request_mem_region(mem->start, SZ_16K, DRV_NAME);
+       if (!priv->mem) {
+               dev_err(&pdev->dev, "request_mem_region failed\n");
+               err = -EBUSY;
+               goto error;
+       }
+
+       priv->io = ioremap(priv->mem->start, SZ_16K);
+       if (!priv->io) {
+               dev_err(&pdev->dev, "ioremap failed\n");
+               err = -ENOMEM;
+               goto err_iomem;
+       }
+
+       priv->irq = platform_get_irq(pdev, 0);
+       if (priv->irq <= 0) {
+               dev_err(&pdev->dev, "platform_get_irq failed\n");
+               err = -ENXIO;
+               goto err_ioremap;
+       }
+
+       if (!data || !data->dram) {
+               dev_err(&pdev->dev, "no platform data ?!\n");
+               err = -EINVAL;
+               goto err_ioremap;
+       }
+
+       priv->dram = data->dram;
+       priv->burst = data->burst;
+
+       kirkwood_i2s_dai.capture.dma_data = priv;
+       kirkwood_i2s_dai.playback.dma_data = priv;
+
+       return snd_soc_register_dai(&kirkwood_i2s_dai);
+
+err_ioremap:
+       iounmap(priv->io);
+err_iomem:
+       release_mem_region(priv->mem->start, SZ_16K);
+err_alloc:
+       kfree(priv);
+error:
+       return err;
+}
+
+static __devexit int kirkwood_i2s_dev_remove(struct platform_device *pdev)
+{
+       if (priv) {
+               iounmap(priv->io);
+               release_mem_region(priv->mem->start, SZ_16K);
+               kfree(priv);
+       }
+       snd_soc_unregister_dai(&kirkwood_i2s_dai);
+       return 0;
+}
+
+static struct platform_driver kirkwood_i2s_driver = {
+       .probe  = kirkwood_i2s_dev_probe,
+       .remove = kirkwood_i2s_dev_remove,
+       .driver = {
+               .name = DRV_NAME,
+               .owner = THIS_MODULE,
+       },
+};
+
+static int __init kirkwood_i2s_init(void)
+{
+       return platform_driver_register(&kirkwood_i2s_driver);
+}
+module_init(kirkwood_i2s_init);
+
+static void __exit kirkwood_i2s_exit(void)
+{
+       platform_driver_unregister(&kirkwood_i2s_driver);
+}
+module_exit(kirkwood_i2s_exit);
+
+/* Module information */
+MODULE_AUTHOR("Arnaud Patard, <apatard@mandriva.com>");
+MODULE_DESCRIPTION("Kirkwood I2S SoC Interface");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:kirkwood-i2s");
diff --git a/sound/soc/kirkwood/kirkwood-i2s.h b/sound/soc/kirkwood/kirkwood-i2s.h
new file mode 100644 (file)
index 0000000..c5595c6
--- /dev/null
@@ -0,0 +1,17 @@
+/*
+ * kirkwood-i2s.h
+ *
+ * (c) 2010 Arnaud Patard <apatard@mandriva.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 _KIRKWOOD_I2S_H
+#define _KIRKWOOD_I2S_H
+
+extern struct snd_soc_dai kirkwood_i2s_dai;
+
+#endif
diff --git a/sound/soc/kirkwood/kirkwood-openrd.c b/sound/soc/kirkwood/kirkwood-openrd.c
new file mode 100644 (file)
index 0000000..0353d06
--- /dev/null
@@ -0,0 +1,126 @@
+/*
+ * kirkwood-openrd.c
+ *
+ * (c) 2010 Arnaud Patard <apatard@mandriva.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/moduleparam.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/soc.h>
+#include <mach/kirkwood.h>
+#include <plat/audio.h>
+#include <asm/mach-types.h>
+#include "kirkwood-i2s.h"
+#include "kirkwood-dma.h"
+#include "../codecs/cs42l51.h"
+
+static int openrd_client_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;
+       int ret;
+       unsigned int freq, fmt;
+
+       fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS;
+       ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
+       if (ret < 0)
+               return ret;
+
+       ret = snd_soc_dai_set_fmt(codec_dai, fmt);
+       if (ret < 0)
+               return ret;
+
+       switch (params_rate(params)) {
+       default:
+       case 44100:
+               freq = 11289600;
+               break;
+       case 48000:
+               freq = 12288000;
+               break;
+       case 96000:
+               freq = 24576000;
+               break;
+       }
+
+       return snd_soc_dai_set_sysclk(codec_dai, 0, freq, SND_SOC_CLOCK_IN);
+
+}
+
+static struct snd_soc_ops openrd_client_ops = {
+       .hw_params = openrd_client_hw_params,
+};
+
+
+static struct snd_soc_dai_link openrd_client_dai[] = {
+{
+       .name = "CS42L51",
+       .stream_name = "CS42L51 HiFi",
+       .cpu_dai = &kirkwood_i2s_dai,
+       .codec_dai = &cs42l51_dai,
+       .ops = &openrd_client_ops,
+},
+};
+
+
+static struct snd_soc_card openrd_client = {
+       .name = "OpenRD Client",
+       .platform = &kirkwood_soc_platform,
+       .dai_link = openrd_client_dai,
+       .num_links = ARRAY_SIZE(openrd_client_dai),
+};
+
+static struct snd_soc_device openrd_client_snd_devdata = {
+       .card = &openrd_client,
+       .codec_dev = &soc_codec_device_cs42l51,
+};
+
+static struct platform_device *openrd_client_snd_device;
+
+static int __init openrd_client_init(void)
+{
+       int ret;
+
+       if (!machine_is_openrd_client())
+               return 0;
+
+       openrd_client_snd_device = platform_device_alloc("soc-audio", -1);
+       if (!openrd_client_snd_device)
+               return -ENOMEM;
+
+       platform_set_drvdata(openrd_client_snd_device,
+                       &openrd_client_snd_devdata);
+       openrd_client_snd_devdata.dev = &openrd_client_snd_device->dev;
+
+       ret = platform_device_add(openrd_client_snd_device);
+       if (ret) {
+               printk(KERN_ERR "%s: platform_device_add failed\n", __func__);
+               platform_device_put(openrd_client_snd_device);
+       }
+
+       return ret;
+}
+
+static void __exit openrd_client_exit(void)
+{
+       platform_device_unregister(openrd_client_snd_device);
+}
+
+module_init(openrd_client_init);
+module_exit(openrd_client_exit);
+
+/* Module information */
+MODULE_AUTHOR("Arnaud Patard <apatard@mandriva.com>");
+MODULE_DESCRIPTION("ALSA SoC OpenRD Client");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:soc-audio");
diff --git a/sound/soc/kirkwood/kirkwood.h b/sound/soc/kirkwood/kirkwood.h
new file mode 100644 (file)
index 0000000..bb6e6a5
--- /dev/null
@@ -0,0 +1,129 @@
+/*
+ * kirkwood.h
+ *
+ * (c) 2010 Arnaud Patard <apatard@mandriva.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 _KIRKWOOD_AUDIO_H
+#define _KIRKWOOD_AUDIO_H
+
+#define KIRKWOOD_RECORD_WIN                    0
+#define KIRKWOOD_PLAYBACK_WIN                  1
+#define KIRKWOOD_MAX_AUDIO_WIN                 2
+
+#define KIRKWOOD_AUDIO_WIN_BASE_REG(win)       (0xA00 + ((win)<<3))
+#define KIRKWOOD_AUDIO_WIN_CTRL_REG(win)       (0xA04 + ((win)<<3))
+
+
+#define KIRKWOOD_RECCTL                        0x1000
+#define KIRKWOOD_RECCTL_SPDIF_EN               (1<<11)
+#define KIRKWOOD_RECCTL_I2S_EN                 (1<<10)
+#define KIRKWOOD_RECCTL_PAUSE                  (1<<9)
+#define KIRKWOOD_RECCTL_MUTE                   (1<<8)
+#define KIRKWOOD_RECCTL_BURST_MASK             (3<<5)
+#define KIRKWOOD_RECCTL_BURST_128              (2<<5)
+#define KIRKWOOD_RECCTL_BURST_32               (1<<5)
+#define KIRKWOOD_RECCTL_MONO                   (1<<4)
+#define KIRKWOOD_RECCTL_MONO_CHAN_RIGHT        (1<<3)
+#define KIRKWOOD_RECCTL_MONO_CHAN_LEFT         (0<<3)
+#define KIRKWOOD_RECCTL_SIZE_MASK              (7<<0)
+#define KIRKWOOD_RECCTL_SIZE_16                (7<<0)
+#define KIRKWOOD_RECCTL_SIZE_16_C              (3<<0)
+#define KIRKWOOD_RECCTL_SIZE_20                (2<<0)
+#define KIRKWOOD_RECCTL_SIZE_24                (1<<0)
+#define KIRKWOOD_RECCTL_SIZE_32                (0<<0)
+
+#define KIRKWOOD_REC_BUF_ADDR                  0x1004
+#define KIRKWOOD_REC_BUF_SIZE                  0x1008
+#define KIRKWOOD_REC_BYTE_COUNT                        0x100C
+
+#define KIRKWOOD_PLAYCTL                       0x1100
+#define KIRKWOOD_PLAYCTL_PLAY_BUSY             (1<<16)
+#define KIRKWOOD_PLAYCTL_BURST_MASK            (3<<11)
+#define KIRKWOOD_PLAYCTL_BURST_128             (2<<11)
+#define KIRKWOOD_PLAYCTL_BURST_32              (1<<11)
+#define KIRKWOOD_PLAYCTL_PAUSE                 (1<<9)
+#define KIRKWOOD_PLAYCTL_SPDIF_MUTE            (1<<8)
+#define KIRKWOOD_PLAYCTL_MONO_MASK             (3<<5)
+#define KIRKWOOD_PLAYCTL_MONO_BOTH             (3<<5)
+#define KIRKWOOD_PLAYCTL_MONO_OFF              (0<<5)
+#define KIRKWOOD_PLAYCTL_I2S_MUTE              (1<<7)
+#define KIRKWOOD_PLAYCTL_SPDIF_EN              (1<<4)
+#define KIRKWOOD_PLAYCTL_I2S_EN                (1<<3)
+#define KIRKWOOD_PLAYCTL_SIZE_MASK             (7<<0)
+#define KIRKWOOD_PLAYCTL_SIZE_16               (7<<0)
+#define KIRKWOOD_PLAYCTL_SIZE_16_C             (3<<0)
+#define KIRKWOOD_PLAYCTL_SIZE_20               (2<<0)
+#define KIRKWOOD_PLAYCTL_SIZE_24               (1<<0)
+#define KIRKWOOD_PLAYCTL_SIZE_32               (0<<0)
+
+#define KIRKWOOD_PLAY_BUF_ADDR                 0x1104
+#define KIRKWOOD_PLAY_BUF_SIZE                 0x1108
+#define KIRKWOOD_PLAY_BYTE_COUNT               0x110C
+
+#define KIRKWOOD_DCO_CTL                       0x1204
+#define KIRKWOOD_DCO_CTL_OFFSET_MASK           (0xFFF<<2)
+#define KIRKWOOD_DCO_CTL_OFFSET_0              (0x800<<2)
+#define KIRKWOOD_DCO_CTL_FREQ_MASK             (3<<0)
+#define KIRKWOOD_DCO_CTL_FREQ_11               (0<<0)
+#define KIRKWOOD_DCO_CTL_FREQ_12               (1<<0)
+#define KIRKWOOD_DCO_CTL_FREQ_24               (2<<0)
+
+#define KIRKWOOD_DCO_SPCR_STATUS               0x120c
+#define KIRKWOOD_DCO_SPCR_STATUS_DCO_LOCK      (1<<16)
+
+#define KIRKWOOD_ERR_CAUSE                     0x1300
+#define KIRKWOOD_ERR_MASK                      0x1304
+
+#define KIRKWOOD_INT_CAUSE                     0x1308
+#define KIRKWOOD_INT_MASK                      0x130C
+#define KIRKWOOD_INT_CAUSE_PLAY_BYTES          (1<<14)
+#define KIRKWOOD_INT_CAUSE_REC_BYTES           (1<<13)
+#define KIRKWOOD_INT_CAUSE_DMA_PLAY_END        (1<<7)
+#define KIRKWOOD_INT_CAUSE_DMA_PLAY_3Q         (1<<6)
+#define KIRKWOOD_INT_CAUSE_DMA_PLAY_HALF       (1<<5)
+#define KIRKWOOD_INT_CAUSE_DMA_PLAY_1Q         (1<<4)
+#define KIRKWOOD_INT_CAUSE_DMA_REC_END         (1<<3)
+#define KIRKWOOD_INT_CAUSE_DMA_REC_3Q          (1<<2)
+#define KIRKWOOD_INT_CAUSE_DMA_REC_HALF        (1<<1)
+#define KIRKWOOD_INT_CAUSE_DMA_REC_1Q          (1<<0)
+
+#define KIRKWOOD_REC_BYTE_INT_COUNT            0x1310
+#define KIRKWOOD_PLAY_BYTE_INT_COUNT           0x1314
+#define KIRKWOOD_BYTE_INT_COUNT_MASK           0xffffff
+
+#define KIRKWOOD_I2S_PLAYCTL                   0x2508
+#define KIRKWOOD_I2S_RECCTL                    0x2408
+#define KIRKWOOD_I2S_CTL_JUST_MASK             (0xf<<26)
+#define KIRKWOOD_I2S_CTL_LJ                    (0<<26)
+#define KIRKWOOD_I2S_CTL_I2S                   (5<<26)
+#define KIRKWOOD_I2S_CTL_RJ                    (8<<26)
+#define KIRKWOOD_I2S_CTL_SIZE_MASK             (3<<30)
+#define KIRKWOOD_I2S_CTL_SIZE_16               (3<<30)
+#define KIRKWOOD_I2S_CTL_SIZE_20               (2<<30)
+#define KIRKWOOD_I2S_CTL_SIZE_24               (1<<30)
+#define KIRKWOOD_I2S_CTL_SIZE_32               (0<<30)
+
+#define KIRKWOOD_AUDIO_BUF_MAX                 (16*1024*1024)
+
+/* Theses values come from the marvell alsa driver */
+/* need to find where they come from               */
+#define KIRKWOOD_SND_MIN_PERIODS               8
+#define KIRKWOOD_SND_MAX_PERIODS               16
+#define KIRKWOOD_SND_MIN_PERIOD_BYTES          0x4000
+#define KIRKWOOD_SND_MAX_PERIOD_BYTES          0x4000
+
+struct kirkwood_dma_data {
+       struct resource *mem;
+       void __iomem *io;
+       int irq;
+       int burst;
+       struct mbus_dram_target_info *dram;
+};
+
+#endif
diff --git a/sound/soc/nuc900/Kconfig b/sound/soc/nuc900/Kconfig
new file mode 100644 (file)
index 0000000..a0ed1c6
--- /dev/null
@@ -0,0 +1,27 @@
+##
+## NUC900 series AC97 API
+##
+config SND_SOC_NUC900
+       tristate "SoC Audio for NUC900 series"
+       depends on ARCH_W90X900
+       help
+         This option enables support for AC97 mode on the NUC900 SoC.
+
+config SND_SOC_NUC900_AC97
+       tristate
+       select AC97_BUS
+       select SND_AC97_CODEC
+       select SND_SOC_AC97_BUS
+
+
+##
+## Boards
+##
+config SND_SOC_NUC900EVB
+       tristate "NUC900 AC97 support for demo board"
+       depends on SND_SOC_NUC900
+       select SND_SOC_NUC900_AC97
+       select SND_SOC_AC97_CODEC
+       help
+         Select this option to enable audio (AC97) on the
+         NUC900 demoboard.
diff --git a/sound/soc/nuc900/Makefile b/sound/soc/nuc900/Makefile
new file mode 100644 (file)
index 0000000..7e46c71
--- /dev/null
@@ -0,0 +1,11 @@
+# NUC900 series audio
+snd-soc-nuc900-pcm-objs := nuc900-pcm.o
+snd-soc-nuc900-ac97-objs := nuc900-ac97.o
+
+obj-$(CONFIG_SND_SOC_NUC900) += snd-soc-nuc900-pcm.o
+obj-$(CONFIG_SND_SOC_NUC900_AC97) += snd-soc-nuc900-ac97.o
+
+# Boards
+snd-soc-nuc900-audio-objs := nuc900-audio.o
+
+obj-$(CONFIG_SND_SOC_NUC900EVB) += snd-soc-nuc900-audio.o
diff --git a/sound/soc/nuc900/nuc900-ac97.c b/sound/soc/nuc900/nuc900-ac97.c
new file mode 100644 (file)
index 0000000..caa7c90
--- /dev/null
@@ -0,0 +1,430 @@
+/*
+ * Copyright (c) 2009-2010 Nuvoton technology corporation.
+ *
+ * Wan ZongShun <mcuos.com@gmail.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;version 2 of the License.
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.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>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include <linux/device.h>
+#include <linux/clk.h>
+
+#include <mach/mfp.h>
+
+#include "nuc900-audio.h"
+
+static DEFINE_MUTEX(ac97_mutex);
+struct nuc900_audio *nuc900_ac97_data;
+
+static int nuc900_checkready(void)
+{
+       struct nuc900_audio *nuc900_audio = nuc900_ac97_data;
+
+       if (!(AUDIO_READ(nuc900_audio->mmio + ACTL_ACIS0) & CODEC_READY))
+               return -EPERM;
+
+       return 0;
+}
+
+/* AC97 controller reads codec register */
+static unsigned short nuc900_ac97_read(struct snd_ac97 *ac97,
+                                       unsigned short reg)
+{
+       struct nuc900_audio *nuc900_audio = nuc900_ac97_data;
+       unsigned long timeout = 0x10000, val;
+
+       mutex_lock(&ac97_mutex);
+
+       val = nuc900_checkready();
+       if (!!val) {
+               dev_err(nuc900_audio->dev, "AC97 codec is not ready\n");
+               goto out;
+       }
+
+       /* set the R_WB bit and write register index */
+       AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS1, R_WB | reg);
+
+       /* set the valid frame bit and valid slots */
+       val = AUDIO_READ(nuc900_audio->mmio + ACTL_ACOS0);
+       val |= (VALID_FRAME | SLOT1_VALID);
+       AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS0, val);
+
+       udelay(100);
+
+       /* polling the AC_R_FINISH */
+       while (!(AUDIO_READ(nuc900_audio->mmio + ACTL_ACCON) & AC_R_FINISH)
+                                                               && timeout--)
+               mdelay(1);
+
+       if (!timeout) {
+               dev_err(nuc900_audio->dev, "AC97 read register time out !\n");
+               val = -EPERM;
+               goto out;
+       }
+
+       val = AUDIO_READ(nuc900_audio->mmio + ACTL_ACOS0) ;
+       val &= ~SLOT1_VALID;
+       AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS0, val);
+
+       if (AUDIO_READ(nuc900_audio->mmio + ACTL_ACIS1) >> 2 != reg) {
+               dev_err(nuc900_audio->dev,
+                               "R_INDEX of REG_ACTL_ACIS1 not match!\n");
+       }
+
+       udelay(100);
+       val = (AUDIO_READ(nuc900_audio->mmio + ACTL_ACIS2) & 0xFFFF);
+
+out:
+       mutex_unlock(&ac97_mutex);
+       return val;
+}
+
+/* AC97 controller writes to codec register */
+static void nuc900_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
+                               unsigned short val)
+{
+       struct nuc900_audio *nuc900_audio = nuc900_ac97_data;
+       unsigned long tmp, timeout = 0x10000;
+
+       mutex_lock(&ac97_mutex);
+
+       tmp = nuc900_checkready();
+       if (!!tmp)
+               dev_err(nuc900_audio->dev, "AC97 codec is not ready\n");
+
+       /* clear the R_WB bit and write register index */
+       AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS1, reg);
+
+       /* write register value */
+       AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS2, val);
+
+       /* set the valid frame bit and valid slots */
+       tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_ACOS0);
+       tmp |= SLOT1_VALID | SLOT2_VALID | VALID_FRAME;
+       AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS0, tmp);
+
+       udelay(100);
+
+       /* polling the AC_W_FINISH */
+       while ((AUDIO_READ(nuc900_audio->mmio + ACTL_ACCON) & AC_W_FINISH)
+                                                               && timeout--)
+               mdelay(1);
+
+       if (!timeout)
+               dev_err(nuc900_audio->dev, "AC97 write register time out !\n");
+
+       tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_ACOS0);
+       tmp &= ~(SLOT1_VALID | SLOT2_VALID);
+       AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS0, tmp);
+
+       mutex_unlock(&ac97_mutex);
+
+}
+
+static void nuc900_ac97_warm_reset(struct snd_ac97 *ac97)
+{
+       struct nuc900_audio *nuc900_audio = nuc900_ac97_data;
+       unsigned long val;
+
+       mutex_lock(&ac97_mutex);
+
+       /* warm reset AC 97 */
+       val = AUDIO_READ(nuc900_audio->mmio + ACTL_ACCON);
+       val |= AC_W_RES;
+       AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACCON, val);
+
+       udelay(100);
+
+       val = nuc900_checkready();
+       if (!!val)
+               dev_err(nuc900_audio->dev, "AC97 codec is not ready\n");
+
+       mutex_unlock(&ac97_mutex);
+}
+
+static void nuc900_ac97_cold_reset(struct snd_ac97 *ac97)
+{
+       struct nuc900_audio *nuc900_audio = nuc900_ac97_data;
+       unsigned long val;
+
+       mutex_lock(&ac97_mutex);
+
+       /* reset Audio Controller */
+       val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET);
+       val |= ACTL_RESET_BIT;
+       AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val);
+
+       val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET);
+       val &= (~ACTL_RESET_BIT);
+       AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val);
+
+       /* reset AC-link interface */
+
+       val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET);
+       val |= AC_RESET;
+       AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val);
+
+       val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET);
+       val &= ~AC_RESET;
+       AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val);
+
+       /* cold reset AC 97 */
+       val = AUDIO_READ(nuc900_audio->mmio + ACTL_ACCON);
+       val |= AC_C_RES;
+       AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACCON, val);
+
+       val = AUDIO_READ(nuc900_audio->mmio + ACTL_ACCON);
+       val &= (~AC_C_RES);
+       AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACCON, val);
+
+       udelay(100);
+
+       mutex_unlock(&ac97_mutex);
+
+}
+
+/* AC97 controller operations */
+struct snd_ac97_bus_ops soc_ac97_ops = {
+       .read           = nuc900_ac97_read,
+       .write          = nuc900_ac97_write,
+       .reset          = nuc900_ac97_cold_reset,
+       .warm_reset     = nuc900_ac97_warm_reset,
+}
+EXPORT_SYMBOL_GPL(soc_ac97_ops);
+
+static int nuc900_ac97_trigger(struct snd_pcm_substream *substream,
+                               int cmd, struct snd_soc_dai *dai)
+{
+       struct nuc900_audio *nuc900_audio = nuc900_ac97_data;
+       int ret;
+       unsigned long val, tmp;
+
+       ret = 0;
+
+       switch (cmd) {
+       case SNDRV_PCM_TRIGGER_START:
+       case SNDRV_PCM_TRIGGER_RESUME:
+               val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET);
+               if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+                       tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_ACOS0);
+                       tmp |= (SLOT3_VALID | SLOT4_VALID | VALID_FRAME);
+                       AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS0, tmp);
+
+                       tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_PSR);
+                       tmp |= (P_DMA_END_IRQ | P_DMA_MIDDLE_IRQ);
+                       AUDIO_WRITE(nuc900_audio->mmio + ACTL_PSR, tmp);
+                       val |= AC_PLAY;
+               } else {
+                       tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_RSR);
+                       tmp |= (R_DMA_END_IRQ | R_DMA_MIDDLE_IRQ);
+
+                       AUDIO_WRITE(nuc900_audio->mmio + ACTL_RSR, tmp);
+                       val |= AC_RECORD;
+               }
+
+               AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val);
+
+               break;
+       case SNDRV_PCM_TRIGGER_STOP:
+       case SNDRV_PCM_TRIGGER_SUSPEND:
+               val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET);
+               if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+                       tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_ACOS0);
+                       tmp &= ~(SLOT3_VALID | SLOT4_VALID);
+                       AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS0, tmp);
+
+                       AUDIO_WRITE(nuc900_audio->mmio + ACTL_PSR, RESET_PRSR);
+                       val &= ~AC_PLAY;
+               } else {
+                       AUDIO_WRITE(nuc900_audio->mmio + ACTL_RSR, RESET_PRSR);
+                       val &= ~AC_RECORD;
+               }
+
+               AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val);
+
+               break;
+       default:
+               ret = -EINVAL;
+       }
+
+       return ret;
+}
+
+static int nuc900_ac97_probe(struct platform_device *pdev,
+                                       struct snd_soc_dai *dai)
+{
+       struct nuc900_audio *nuc900_audio = nuc900_ac97_data;
+       unsigned long val;
+
+       mutex_lock(&ac97_mutex);
+
+       /* enable unit clock */
+       clk_enable(nuc900_audio->clk);
+
+       /* enable audio controller and AC-link interface */
+       val = AUDIO_READ(nuc900_audio->mmio + ACTL_CON);
+       val |= (IIS_AC_PIN_SEL | ACLINK_EN);
+       AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val);
+
+       mutex_unlock(&ac97_mutex);
+
+       return 0;
+}
+
+static void nuc900_ac97_remove(struct platform_device *pdev,
+                                               struct snd_soc_dai *dai)
+{
+       struct nuc900_audio *nuc900_audio = nuc900_ac97_data;
+
+       clk_disable(nuc900_audio->clk);
+}
+
+static struct snd_soc_dai_ops nuc900_ac97_dai_ops = {
+       .trigger        = nuc900_ac97_trigger,
+};
+
+struct snd_soc_dai nuc900_ac97_dai = {
+       .name                   = "nuc900-ac97",
+       .probe                  = nuc900_ac97_probe,
+       .remove                 = nuc900_ac97_remove,
+       .ac97_control           = 1,
+       .playback = {
+               .rates          = SNDRV_PCM_RATE_8000_48000,
+               .formats        = SNDRV_PCM_FMTBIT_S16_LE,
+               .channels_min   = 1,
+               .channels_max   = 2,
+       },
+       .capture = {
+               .rates          = SNDRV_PCM_RATE_8000_48000,
+               .formats        = SNDRV_PCM_FMTBIT_S16_LE,
+               .channels_min   = 1,
+               .channels_max   = 2,
+       },
+       .ops = &nuc900_ac97_dai_ops,
+}
+EXPORT_SYMBOL_GPL(nuc900_ac97_dai);
+
+static int __devinit nuc900_ac97_drvprobe(struct platform_device *pdev)
+{
+       struct nuc900_audio *nuc900_audio;
+       int ret;
+
+       if (nuc900_ac97_data)
+               return -EBUSY;
+
+       nuc900_audio = kzalloc(sizeof(struct nuc900_audio), GFP_KERNEL);
+       if (!nuc900_audio)
+               return -ENOMEM;
+
+       spin_lock_init(&nuc900_audio->lock);
+
+       nuc900_audio->res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (!nuc900_audio->res) {
+               ret = -ENODEV;
+               goto out0;
+       }
+
+       if (!request_mem_region(nuc900_audio->res->start,
+                       resource_size(nuc900_audio->res), pdev->name)) {
+               ret = -EBUSY;
+               goto out0;
+       }
+
+       nuc900_audio->mmio = ioremap(nuc900_audio->res->start,
+                                       resource_size(nuc900_audio->res));
+       if (!nuc900_audio->mmio) {
+               ret = -ENOMEM;
+               goto out1;
+       }
+
+       nuc900_audio->clk = clk_get(&pdev->dev, NULL);
+       if (IS_ERR(nuc900_audio->clk)) {
+               ret = PTR_ERR(nuc900_audio->clk);
+               goto out2;
+       }
+
+       nuc900_audio->irq_num = platform_get_irq(pdev, 0);
+       if (!nuc900_audio->irq_num) {
+               ret = -EBUSY;
+               goto out2;
+       }
+
+       nuc900_ac97_data = nuc900_audio;
+
+       nuc900_audio->dev = nuc900_ac97_dai.dev =  &pdev->dev;
+
+       ret = snd_soc_register_dai(&nuc900_ac97_dai);
+       if (ret)
+               goto out3;
+
+       mfp_set_groupg(nuc900_audio->dev); /* enbale ac97 multifunction pin*/
+
+       return 0;
+
+out3:
+       clk_put(nuc900_audio->clk);
+out2:
+       iounmap(nuc900_audio->mmio);
+out1:
+       release_mem_region(nuc900_audio->res->start,
+                                       resource_size(nuc900_audio->res));
+out0:
+       kfree(nuc900_audio);
+       return ret;
+}
+
+static int __devexit nuc900_ac97_drvremove(struct platform_device *pdev)
+{
+
+       snd_soc_unregister_dai(&nuc900_ac97_dai);
+
+       clk_put(nuc900_ac97_data->clk);
+       iounmap(nuc900_ac97_data->mmio);
+       release_mem_region(nuc900_ac97_data->res->start,
+                               resource_size(nuc900_ac97_data->res));
+
+       nuc900_ac97_data = NULL;
+
+       return 0;
+}
+
+static struct platform_driver nuc900_ac97_driver = {
+       .driver = {
+               .name   = "nuc900-audio",
+               .owner  = THIS_MODULE,
+       },
+       .probe          = nuc900_ac97_drvprobe,
+       .remove         = __devexit_p(nuc900_ac97_drvremove),
+};
+
+static int __init nuc900_ac97_init(void)
+{
+       return platform_driver_register(&nuc900_ac97_driver);
+}
+
+static void __exit nuc900_ac97_exit(void)
+{
+       platform_driver_unregister(&nuc900_ac97_driver);
+}
+
+module_init(nuc900_ac97_init);
+module_exit(nuc900_ac97_exit);
+
+MODULE_AUTHOR("Wan ZongShun <mcuos.com@gmail.com>");
+MODULE_DESCRIPTION("NUC900 AC97 SoC driver!");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:nuc900-ac97");
diff --git a/sound/soc/nuc900/nuc900-audio.c b/sound/soc/nuc900/nuc900-audio.c
new file mode 100644 (file)
index 0000000..72e6f51
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2010 Nuvoton technology corporation.
+ *
+ * Wan ZongShun <mcuos.com@gmail.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;version 2 of the License.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include "../codecs/ac97.h"
+#include "nuc900-audio.h"
+
+static struct snd_soc_dai_link nuc900evb_ac97_dai = {
+       .name           = "AC97",
+       .stream_name    = "AC97 HiFi",
+       .cpu_dai        = &nuc900_ac97_dai,
+       .codec_dai      = &ac97_dai,
+};
+
+static struct snd_soc_card nuc900evb_audio_machine = {
+       .name           = "NUC900EVB_AC97",
+       .dai_link       = &nuc900evb_ac97_dai,
+       .num_links      = 1,
+       .platform       = &nuc900_soc_platform,
+};
+
+static struct snd_soc_device nuc900evb_ac97_devdata = {
+       .card           = &nuc900evb_audio_machine,
+       .codec_dev      = &soc_codec_dev_ac97,
+};
+
+static struct platform_device *nuc900evb_asoc_dev;
+
+static int __init nuc900evb_audio_init(void)
+{
+       int ret;
+
+       ret = -ENOMEM;
+       nuc900evb_asoc_dev = platform_device_alloc("soc-audio", -1);
+       if (!nuc900evb_asoc_dev)
+               goto out;
+
+       /* nuc900 board audio device */
+       platform_set_drvdata(nuc900evb_asoc_dev, &nuc900evb_ac97_devdata);
+
+       nuc900evb_ac97_devdata.dev = &nuc900evb_asoc_dev->dev;
+       ret = platform_device_add(nuc900evb_asoc_dev);
+
+       if (ret) {
+               platform_device_put(nuc900evb_asoc_dev);
+               nuc900evb_asoc_dev = NULL;
+       }
+
+out:
+       return ret;
+}
+
+static void __exit nuc900evb_audio_exit(void)
+{
+       platform_device_unregister(nuc900evb_asoc_dev);
+}
+
+module_init(nuc900evb_audio_init);
+module_exit(nuc900evb_audio_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("NUC900 Series ASoC audio support");
+MODULE_AUTHOR("Wan ZongShun");
diff --git a/sound/soc/nuc900/nuc900-audio.h b/sound/soc/nuc900/nuc900-audio.h
new file mode 100644 (file)
index 0000000..3038f51
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2010 Nuvoton technology corporation.
+ *
+ * Wan ZongShun <mcuos.com@gmail.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;version 2 of the License.
+ *
+ */
+
+#ifndef _NUC900_AUDIO_H
+#define _NUC900_AUDIO_H
+
+#include <linux/io.h>
+
+/* Audio Control Registers */
+#define ACTL_CON               0x00
+#define ACTL_RESET             0x04
+#define ACTL_RDSTB             0x08
+#define ACTL_RDST_LENGTH       0x0C
+#define ACTL_RDSTC             0x10
+#define ACTL_RSR               0x14
+#define ACTL_PDSTB             0x18
+#define ACTL_PDST_LENGTH       0x1C
+#define ACTL_PDSTC             0x20
+#define ACTL_PSR               0x24
+#define ACTL_IISCON            0x28
+#define ACTL_ACCON             0x2C
+#define ACTL_ACOS0             0x30
+#define ACTL_ACOS1             0x34
+#define ACTL_ACOS2             0x38
+#define ACTL_ACIS0             0x3C
+#define ACTL_ACIS1             0x40
+#define ACTL_ACIS2             0x44
+#define ACTL_COUNTER           0x48
+
+/* bit definition of REG_ACTL_CON register */
+#define R_DMA_IRQ              0x1000
+#define T_DMA_IRQ              0x0800
+#define IIS_AC_PIN_SEL         0x0100
+#define FIFO_TH                        0x0080
+#define ADC_EN                 0x0010
+#define M80_EN                 0x0008
+#define ACLINK_EN              0x0004
+#define IIS_EN                 0x0002
+
+/* bit definition of REG_ACTL_RESET register */
+#define W5691_PLAY             0x20000
+#define ACTL_RESET_BIT         0x10000
+#define RECORD_RIGHT_CHNNEL    0x08000
+#define RECORD_LEFT_CHNNEL     0x04000
+#define PLAY_RIGHT_CHNNEL      0x02000
+#define PLAY_LEFT_CHNNEL       0x01000
+#define DAC_PLAY               0x00800
+#define ADC_RECORD             0x00400
+#define M80_PLAY               0x00200
+#define AC_RECORD              0x00100
+#define AC_PLAY                        0x00080
+#define IIS_RECORD             0x00040
+#define IIS_PLAY               0x00020
+#define DAC_RESET              0x00010
+#define ADC_RESET              0x00008
+#define M80_RESET              0x00004
+#define AC_RESET               0x00002
+#define IIS_RESET              0x00001
+
+/* bit definition of REG_ACTL_ACCON register */
+#define AC_BCLK_PU_EN          0x20
+#define AC_R_FINISH            0x10
+#define AC_W_FINISH            0x08
+#define AC_W_RES               0x04
+#define AC_C_RES               0x02
+
+/* bit definition of ACTL_RSR register */
+#define R_FIFO_EMPTY           0x04
+#define R_DMA_END_IRQ          0x02
+#define R_DMA_MIDDLE_IRQ       0x01
+
+/* bit definition of ACTL_PSR register */
+#define P_FIFO_EMPTY           0x04
+#define P_DMA_END_IRQ          0x02
+#define P_DMA_MIDDLE_IRQ       0x01
+
+/* bit definition of ACTL_ACOS0 register */
+#define SLOT1_VALID            0x01
+#define SLOT2_VALID            0x02
+#define SLOT3_VALID            0x04
+#define SLOT4_VALID            0x08
+#define VALID_FRAME            0x10
+
+/* bit definition of ACTL_ACOS1 register */
+#define R_WB                   0x80
+
+#define CODEC_READY            0x10
+#define RESET_PRSR             0x00
+#define AUDIO_WRITE(addr, val) __raw_writel(val, addr)
+#define AUDIO_READ(addr)       __raw_readl(addr)
+
+struct nuc900_audio {
+       void __iomem *mmio;
+       spinlock_t lock;
+       dma_addr_t dma_addr[2];
+       unsigned long buffersize[2];
+       unsigned long irq_num;
+       struct snd_pcm_substream *substream;
+       struct resource *res;
+       struct clk *clk;
+       struct device *dev;
+
+};
+
+extern struct nuc900_audio *nuc900_ac97_data;
+extern struct snd_soc_dai nuc900_ac97_dai;
+extern struct snd_soc_platform nuc900_soc_platform;
+
+#endif /*end _NUC900_AUDIO_H */
diff --git a/sound/soc/nuc900/nuc900-pcm.c b/sound/soc/nuc900/nuc900-pcm.c
new file mode 100644 (file)
index 0000000..e81e803
--- /dev/null
@@ -0,0 +1,354 @@
+/*
+ * Copyright (c) 2010 Nuvoton technology corporation.
+ *
+ * Wan ZongShun <mcuos.com@gmail.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;version 2 of the License.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/io.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 <mach/hardware.h>
+
+#include "nuc900-audio.h"
+
+static const struct snd_pcm_hardware nuc900_pcm_hardware = {
+       .info                   = SNDRV_PCM_INFO_INTERLEAVED |
+                                       SNDRV_PCM_INFO_BLOCK_TRANSFER |
+                                       SNDRV_PCM_INFO_MMAP |
+                                       SNDRV_PCM_INFO_MMAP_VALID |
+                                       SNDRV_PCM_INFO_PAUSE |
+                                       SNDRV_PCM_INFO_RESUME,
+       .formats                = SNDRV_PCM_FMTBIT_S16_LE,
+       .channels_min           = 1,
+       .channels_max           = 2,
+       .buffer_bytes_max       = 4*1024,
+       .period_bytes_min       = 1*1024,
+       .period_bytes_max       = 4*1024,
+       .periods_min            = 1,
+       .periods_max            = 1024,
+};
+
+static int nuc900_dma_hw_params(struct snd_pcm_substream *substream,
+       struct snd_pcm_hw_params *params)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct nuc900_audio *nuc900_audio = runtime->private_data;
+       unsigned long flags;
+       int ret = 0;
+
+       spin_lock_irqsave(&nuc900_audio->lock, flags);
+
+       ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
+       if (ret < 0)
+               return ret;
+
+       nuc900_audio->substream = substream;
+       nuc900_audio->dma_addr[substream->stream] = runtime->dma_addr;
+       nuc900_audio->buffersize[substream->stream] =
+                                               params_buffer_bytes(params);
+
+       spin_unlock_irqrestore(&nuc900_audio->lock, flags);
+
+       return ret;
+}
+
+static void nuc900_update_dma_register(struct snd_pcm_substream *substream,
+                               dma_addr_t dma_addr, size_t count)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct nuc900_audio *nuc900_audio = runtime->private_data;
+       void __iomem *mmio_addr, *mmio_len;
+
+       if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+               mmio_addr = nuc900_audio->mmio + ACTL_PDSTB;
+               mmio_len = nuc900_audio->mmio + ACTL_PDST_LENGTH;
+       } else {
+               mmio_addr = nuc900_audio->mmio + ACTL_RDSTB;
+               mmio_len = nuc900_audio->mmio + ACTL_RDST_LENGTH;
+       }
+
+       AUDIO_WRITE(mmio_addr, dma_addr);
+       AUDIO_WRITE(mmio_len, count);
+}
+
+static void nuc900_dma_start(struct snd_pcm_substream *substream)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct nuc900_audio *nuc900_audio = runtime->private_data;
+       unsigned long val;
+
+       val = AUDIO_READ(nuc900_audio->mmio + ACTL_CON);
+       val |= (T_DMA_IRQ | R_DMA_IRQ);
+       AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val);
+}
+
+static void nuc900_dma_stop(struct snd_pcm_substream *substream)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct nuc900_audio *nuc900_audio = runtime->private_data;
+       unsigned long val;
+
+       val = AUDIO_READ(nuc900_audio->mmio + ACTL_CON);
+       val &= ~(T_DMA_IRQ | R_DMA_IRQ);
+       AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val);
+}
+
+static irqreturn_t nuc900_dma_interrupt(int irq, void *dev_id)
+{
+       struct snd_pcm_substream *substream = dev_id;
+       struct nuc900_audio *nuc900_audio = substream->runtime->private_data;
+       unsigned long val;
+
+       spin_lock(&nuc900_audio->lock);
+
+       val = AUDIO_READ(nuc900_audio->mmio + ACTL_CON);
+
+       if (val & R_DMA_IRQ) {
+               AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val | R_DMA_IRQ);
+
+               val = AUDIO_READ(nuc900_audio->mmio + ACTL_RSR);
+
+               if (val & R_DMA_MIDDLE_IRQ) {
+                       val |= R_DMA_MIDDLE_IRQ;
+                       AUDIO_WRITE(nuc900_audio->mmio + ACTL_RSR, val);
+               }
+
+               if (val & R_DMA_END_IRQ) {
+                       val |= R_DMA_END_IRQ;
+                       AUDIO_WRITE(nuc900_audio->mmio + ACTL_RSR, val);
+               }
+       } else if (val & T_DMA_IRQ) {
+               AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val | T_DMA_IRQ);
+
+               val = AUDIO_READ(nuc900_audio->mmio + ACTL_PSR);
+
+               if (val & P_DMA_MIDDLE_IRQ) {
+                       val |= P_DMA_MIDDLE_IRQ;
+                       AUDIO_WRITE(nuc900_audio->mmio + ACTL_PSR, val);
+               }
+
+               if (val & P_DMA_END_IRQ) {
+                       val |= P_DMA_END_IRQ;
+                       AUDIO_WRITE(nuc900_audio->mmio + ACTL_PSR, val);
+               }
+       } else {
+               dev_err(nuc900_audio->dev, "Wrong DMA interrupt status!\n");
+               spin_unlock(&nuc900_audio->lock);
+               return IRQ_HANDLED;
+       }
+
+       spin_unlock(&nuc900_audio->lock);
+
+       snd_pcm_period_elapsed(substream);
+
+       return IRQ_HANDLED;
+}
+
+static int nuc900_dma_hw_free(struct snd_pcm_substream *substream)
+{
+       snd_pcm_lib_free_pages(substream);
+       return 0;
+}
+
+static int nuc900_dma_prepare(struct snd_pcm_substream *substream)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct nuc900_audio *nuc900_audio = runtime->private_data;
+       unsigned long flags, val;
+
+       spin_lock_irqsave(&nuc900_audio->lock, flags);
+
+       nuc900_update_dma_register(substream,
+                               nuc900_audio->dma_addr[substream->stream],
+                               nuc900_audio->buffersize[substream->stream]);
+
+       val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET);
+
+       switch (runtime->channels) {
+       case 1:
+               if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+                       val &= ~(PLAY_LEFT_CHNNEL | PLAY_RIGHT_CHNNEL);
+                       val |= PLAY_RIGHT_CHNNEL;
+               } else {
+                       val &= ~(RECORD_LEFT_CHNNEL | RECORD_RIGHT_CHNNEL);
+                       val |= RECORD_RIGHT_CHNNEL;
+               }
+               AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val);
+               break;
+       case 2:
+               if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+                       val |= (PLAY_LEFT_CHNNEL | PLAY_RIGHT_CHNNEL);
+               else
+                       val |= (RECORD_LEFT_CHNNEL | RECORD_RIGHT_CHNNEL);
+               AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val);
+               break;
+       default:
+               return -EINVAL;
+       }
+       spin_unlock_irqrestore(&nuc900_audio->lock, flags);
+       return 0;
+}
+
+static int nuc900_dma_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+       int ret = 0;
+
+       switch (cmd) {
+       case SNDRV_PCM_TRIGGER_START:
+       case SNDRV_PCM_TRIGGER_RESUME:
+               nuc900_dma_start(substream);
+               break;
+
+       case SNDRV_PCM_TRIGGER_STOP:
+       case SNDRV_PCM_TRIGGER_SUSPEND:
+               nuc900_dma_stop(substream);
+               break;
+
+       default:
+               ret = -EINVAL;
+               break;
+       }
+
+       return ret;
+}
+
+int nuc900_dma_getposition(struct snd_pcm_substream *substream,
+                                       dma_addr_t *src, dma_addr_t *dst)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct nuc900_audio *nuc900_audio = runtime->private_data;
+
+       if (src != NULL)
+               *src = AUDIO_READ(nuc900_audio->mmio + ACTL_PDSTC);
+
+       if (dst != NULL)
+               *dst = AUDIO_READ(nuc900_audio->mmio + ACTL_RDSTC);
+
+       return 0;
+}
+
+static snd_pcm_uframes_t nuc900_dma_pointer(struct snd_pcm_substream *substream)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       dma_addr_t src, dst;
+       unsigned long res;
+
+       nuc900_dma_getposition(substream, &src, &dst);
+
+       if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+               res = dst - runtime->dma_addr;
+       else
+               res = src - runtime->dma_addr;
+
+       return bytes_to_frames(substream->runtime, res);
+}
+
+static int nuc900_dma_open(struct snd_pcm_substream *substream)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct nuc900_audio *nuc900_audio;
+
+       snd_soc_set_runtime_hwparams(substream, &nuc900_pcm_hardware);
+
+       nuc900_audio = nuc900_ac97_data;
+
+       if (request_irq(nuc900_audio->irq_num, nuc900_dma_interrupt,
+                       IRQF_DISABLED, "nuc900-dma", substream))
+               return -EBUSY;
+
+       runtime->private_data = nuc900_audio;
+
+       return 0;
+}
+
+static int nuc900_dma_close(struct snd_pcm_substream *substream)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct nuc900_audio *nuc900_audio = runtime->private_data;
+
+       free_irq(nuc900_audio->irq_num, substream);
+
+       return 0;
+}
+
+static int nuc900_dma_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 nuc900_dma_ops = {
+       .open           = nuc900_dma_open,
+       .close          = nuc900_dma_close,
+       .ioctl          = snd_pcm_lib_ioctl,
+       .hw_params      = nuc900_dma_hw_params,
+       .hw_free        = nuc900_dma_hw_free,
+       .prepare        = nuc900_dma_prepare,
+       .trigger        = nuc900_dma_trigger,
+       .pointer        = nuc900_dma_pointer,
+       .mmap           = nuc900_dma_mmap,
+};
+
+static void nuc900_dma_free_dma_buffers(struct snd_pcm *pcm)
+{
+       snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+static u64 nuc900_pcm_dmamask = DMA_BIT_MASK(32);
+static int nuc900_dma_new(struct snd_card *card,
+       struct snd_soc_dai *dai, struct snd_pcm *pcm)
+{
+       if (!card->dev->dma_mask)
+               card->dev->dma_mask = &nuc900_pcm_dmamask;
+       if (!card->dev->coherent_dma_mask)
+               card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
+
+       snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+               card->dev, 4 * 1024, (4 * 1024) - 1);
+
+       return 0;
+}
+
+struct snd_soc_platform nuc900_soc_platform = {
+       .name           = "nuc900-dma",
+       .pcm_ops        = &nuc900_dma_ops,
+       .pcm_new        = nuc900_dma_new,
+       .pcm_free       = nuc900_dma_free_dma_buffers,
+}
+EXPORT_SYMBOL_GPL(nuc900_soc_platform);
+
+static int __init nuc900_soc_platform_init(void)
+{
+       return snd_soc_register_platform(&nuc900_soc_platform);
+}
+
+static void __exit nuc900_soc_platform_exit(void)
+{
+       snd_soc_unregister_platform(&nuc900_soc_platform);
+}
+
+module_init(nuc900_soc_platform_init);
+module_exit(nuc900_soc_platform_exit);
+
+MODULE_AUTHOR("Wan ZongShun, <mcuos.com@gmail.com>");
+MODULE_DESCRIPTION("nuc900 Audio DMA module");
+MODULE_LICENSE("GPL");
index 6f44cb4d30b88791f3b81ea9bc7a7b98599e1a66..86f213905e2c0108e261de0f5d73b734cea27e57 100644 (file)
@@ -59,6 +59,7 @@ struct omap_mcbsp_data {
        int                             configured;
        unsigned int                    in_freq;
        int                             clk_div;
+       int                             wlen;
 };
 
 #define to_mcbsp(priv) container_of((priv), struct omap_mcbsp_data, bus_id)
@@ -154,20 +155,51 @@ static void omap_mcbsp_set_threshold(struct snd_pcm_substream *substream)
        struct snd_soc_pcm_runtime *rtd = substream->private_data;
        struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
        struct omap_mcbsp_data *mcbsp_data = to_mcbsp(cpu_dai->private_data);
+       struct omap_pcm_dma_data *dma_data;
        int dma_op_mode = omap_mcbsp_get_dma_op_mode(mcbsp_data->bus_id);
-       int samples;
+       int words;
+
+       dma_data = snd_soc_dai_get_dma_data(rtd->dai->cpu_dai, substream);
 
        /* TODO: Currently, MODE_ELEMENT == MODE_FRAME */
        if (dma_op_mode == MCBSP_DMA_MODE_THRESHOLD)
-               samples = snd_pcm_lib_period_bytes(substream) >> 1;
+               /*
+                * Configure McBSP threshold based on either:
+                * packet_size, when the sDMA is in packet mode, or
+                * based on the period size.
+                */
+               if (dma_data->packet_size)
+                       words = dma_data->packet_size;
+               else
+                       words = snd_pcm_lib_period_bytes(substream) /
+                                                       (mcbsp_data->wlen / 8);
        else
-               samples = 1;
+               words = 1;
 
        /* Configure McBSP internal buffer usage */
        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
-               omap_mcbsp_set_tx_threshold(mcbsp_data->bus_id, samples - 1);
+               omap_mcbsp_set_tx_threshold(mcbsp_data->bus_id, words);
        else
-               omap_mcbsp_set_rx_threshold(mcbsp_data->bus_id, samples - 1);
+               omap_mcbsp_set_rx_threshold(mcbsp_data->bus_id, words);
+}
+
+static int omap_mcbsp_hwrule_min_buffersize(struct snd_pcm_hw_params *params,
+                                   struct snd_pcm_hw_rule *rule)
+{
+       struct snd_interval *buffer_size = hw_param_interval(params,
+                                       SNDRV_PCM_HW_PARAM_BUFFER_SIZE);
+       struct snd_interval *channels = hw_param_interval(params,
+                                       SNDRV_PCM_HW_PARAM_CHANNELS);
+       struct omap_mcbsp_data *mcbsp_data = rule->private;
+       struct snd_interval frames;
+       int size;
+
+       snd_interval_any(&frames);
+       size = omap_mcbsp_get_fifo_size(mcbsp_data->bus_id);
+
+       frames.min = size / channels->min;
+       frames.integer = 1;
+       return snd_interval_refine(buffer_size, &frames);
 }
 
 static int omap_mcbsp_dai_startup(struct snd_pcm_substream *substream,
@@ -182,33 +214,35 @@ static int omap_mcbsp_dai_startup(struct snd_pcm_substream *substream,
        if (!cpu_dai->active)
                err = omap_mcbsp_request(bus_id);
 
+       /*
+        * OMAP3 McBSP FIFO is word structured.
+        * McBSP2 has 1024 + 256 = 1280 word long buffer,
+        * McBSP1,3,4,5 has 128 word long buffer
+        * This means that the size of the FIFO depends on the sample format.
+        * For example on McBSP3:
+        * 16bit samples: size is 128 * 2 = 256 bytes
+        * 32bit samples: size is 128 * 4 = 512 bytes
+        * It is simpler to place constraint for buffer and period based on
+        * channels.
+        * McBSP3 as example again (16 or 32 bit samples):
+        * 1 channel (mono): size is 128 frames (128 words)
+        * 2 channels (stereo): size is 128 / 2 = 64 frames (2 * 64 words)
+        * 4 channels: size is 128 / 4 = 32 frames (4 * 32 words)
+        */
        if (cpu_is_omap343x()) {
-               int dma_op_mode = omap_mcbsp_get_dma_op_mode(bus_id);
-               int max_period;
-
                /*
-                * McBSP2 in OMAP3 has 1024 * 32-bit internal audio buffer.
-                * Set constraint for minimum buffer size to the same than FIFO
-                * size in order to avoid underruns in playback startup because
-                * HW is keeping the DMA request active until FIFO is filled.
-                */
-               if (bus_id == 1)
-                       snd_pcm_hw_constraint_minmax(substream->runtime,
-                                       SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
-                                       4096, UINT_MAX);
-
-               if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
-                       max_period = omap_mcbsp_get_max_tx_threshold(bus_id);
-               else
-                       max_period = omap_mcbsp_get_max_rx_threshold(bus_id);
-
-               max_period++;
-               max_period <<= 1;
-
-               if (dma_op_mode == MCBSP_DMA_MODE_THRESHOLD)
-                       snd_pcm_hw_constraint_minmax(substream->runtime,
-                                               SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
-                                               32, max_period);
+               * Rule for the buffer size. We should not allow
+               * smaller buffer than the FIFO size to avoid underruns
+               */
+               snd_pcm_hw_rule_add(substream->runtime, 0,
+                                   SNDRV_PCM_HW_PARAM_CHANNELS,
+                                   omap_mcbsp_hwrule_min_buffersize,
+                                   mcbsp_data,
+                                   SNDRV_PCM_HW_PARAM_BUFFER_SIZE, -1);
+
+               /* Make sure, that the period size is always even */
+               snd_pcm_hw_constraint_step(substream->runtime, 0,
+                                          SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 2);
        }
 
        return err;
@@ -289,11 +323,14 @@ static int omap_mcbsp_dai_hw_params(struct snd_pcm_substream *substream,
        struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
        struct omap_mcbsp_data *mcbsp_data = to_mcbsp(cpu_dai->private_data);
        struct omap_mcbsp_reg_cfg *regs = &mcbsp_data->regs;
-       int dma, bus_id = mcbsp_data->bus_id, id = cpu_dai->id;
+       struct omap_pcm_dma_data *dma_data;
+       int dma, bus_id = mcbsp_data->bus_id;
        int wlen, channels, wpf, sync_mode = OMAP_DMA_SYNC_ELEMENT;
+       int pkt_size = 0;
        unsigned long port;
        unsigned int format, div, framesize, master;
 
+       dma_data = &omap_mcbsp_dai_dma_params[cpu_dai->id][substream->stream];
        if (cpu_class_is_omap1()) {
                dma = omap1_dma_reqs[bus_id][substream->stream];
                port = omap1_mcbsp_port[bus_id][substream->stream];
@@ -306,35 +343,74 @@ static int omap_mcbsp_dai_hw_params(struct snd_pcm_substream *substream,
        } else if (cpu_is_omap343x()) {
                dma = omap24xx_dma_reqs[bus_id][substream->stream];
                port = omap34xx_mcbsp_port[bus_id][substream->stream];
-               omap_mcbsp_dai_dma_params[id][substream->stream].set_threshold =
-                                               omap_mcbsp_set_threshold;
-               /* TODO: Currently, MODE_ELEMENT == MODE_FRAME */
-               if (omap_mcbsp_get_dma_op_mode(bus_id) ==
-                                               MCBSP_DMA_MODE_THRESHOLD)
-                       sync_mode = OMAP_DMA_SYNC_FRAME;
        } else {
                return -ENODEV;
        }
-       omap_mcbsp_dai_dma_params[id][substream->stream].name =
-               substream->stream ? "Audio Capture" : "Audio Playback";
-       omap_mcbsp_dai_dma_params[id][substream->stream].dma_req = dma;
-       omap_mcbsp_dai_dma_params[id][substream->stream].port_addr = port;
-       omap_mcbsp_dai_dma_params[id][substream->stream].sync_mode = sync_mode;
        switch (params_format(params)) {
        case SNDRV_PCM_FORMAT_S16_LE:
-               omap_mcbsp_dai_dma_params[id][substream->stream].data_type =
-                        OMAP_DMA_DATA_TYPE_S16;
+               dma_data->data_type = OMAP_DMA_DATA_TYPE_S16;
+               wlen = 16;
                break;
        case SNDRV_PCM_FORMAT_S32_LE:
-               omap_mcbsp_dai_dma_params[id][substream->stream].data_type =
-                        OMAP_DMA_DATA_TYPE_S32;
+               dma_data->data_type = OMAP_DMA_DATA_TYPE_S32;
+               wlen = 32;
                break;
        default:
                return -EINVAL;
        }
+       if (cpu_is_omap343x()) {
+               dma_data->set_threshold = omap_mcbsp_set_threshold;
+               /* TODO: Currently, MODE_ELEMENT == MODE_FRAME */
+               if (omap_mcbsp_get_dma_op_mode(bus_id) ==
+                                               MCBSP_DMA_MODE_THRESHOLD) {
+                       int period_words, max_thrsh;
+
+                       period_words = params_period_bytes(params) / (wlen / 8);
+                       if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+                               max_thrsh = omap_mcbsp_get_max_tx_threshold(
+                                                           mcbsp_data->bus_id);
+                       else
+                               max_thrsh = omap_mcbsp_get_max_rx_threshold(
+                                                           mcbsp_data->bus_id);
+                       /*
+                        * If the period contains less or equal number of words,
+                        * we are using the original threshold mode setup:
+                        * McBSP threshold = sDMA frame size = period_size
+                        * Otherwise we switch to sDMA packet mode:
+                        * McBSP threshold = sDMA packet size
+                        * sDMA frame size = period size
+                        */
+                       if (period_words > max_thrsh) {
+                               int divider = 0;
+
+                               /*
+                                * Look for the biggest threshold value, which
+                                * divides the period size evenly.
+                                */
+                               divider = period_words / max_thrsh;
+                               if (period_words % max_thrsh)
+                                       divider++;
+                               while (period_words % divider &&
+                                       divider < period_words)
+                                       divider++;
+                               if (divider == period_words)
+                                       return -EINVAL;
+
+                               pkt_size = period_words / divider;
+                               sync_mode = OMAP_DMA_SYNC_PACKET;
+                       } else {
+                               sync_mode = OMAP_DMA_SYNC_FRAME;
+                       }
+               }
+       }
 
-       snd_soc_dai_set_dma_data(cpu_dai, substream,
-               &omap_mcbsp_dai_dma_params[id][substream->stream]);
+       dma_data->name = substream->stream ? "Audio Capture" : "Audio Playback";
+       dma_data->dma_req = dma;
+       dma_data->port_addr = port;
+       dma_data->sync_mode = sync_mode;
+       dma_data->packet_size = pkt_size;
+
+       snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data);
 
        if (mcbsp_data->configured) {
                /* McBSP already configured by another stream */
@@ -360,7 +436,6 @@ static int omap_mcbsp_dai_hw_params(struct snd_pcm_substream *substream,
        switch (params_format(params)) {
        case SNDRV_PCM_FORMAT_S16_LE:
                /* Set word lengths */
-               wlen = 16;
                regs->rcr2      |= RWDLEN2(OMAP_MCBSP_WORD_16);
                regs->rcr1      |= RWDLEN1(OMAP_MCBSP_WORD_16);
                regs->xcr2      |= XWDLEN2(OMAP_MCBSP_WORD_16);
@@ -368,7 +443,6 @@ static int omap_mcbsp_dai_hw_params(struct snd_pcm_substream *substream,
                break;
        case SNDRV_PCM_FORMAT_S32_LE:
                /* Set word lengths */
-               wlen = 32;
                regs->rcr2      |= RWDLEN2(OMAP_MCBSP_WORD_32);
                regs->rcr1      |= RWDLEN1(OMAP_MCBSP_WORD_32);
                regs->xcr2      |= XWDLEN2(OMAP_MCBSP_WORD_32);
@@ -409,6 +483,7 @@ static int omap_mcbsp_dai_hw_params(struct snd_pcm_substream *substream,
        }
 
        omap_mcbsp_config(bus_id, &mcbsp_data->regs);
+       mcbsp_data->wlen = wlen;
        mcbsp_data->configured = 1;
 
        return 0;
index 87ce842fa2e8e363be14126fcd5b7a074b651759..9eecac135bbbca973645d4391f8f3a6bcff36546 100644 (file)
 
 static struct regulator *omap3pandora_dac_reg;
 
-static int omap3pandora_cmn_hw_params(struct snd_pcm_substream *substream,
-       struct snd_pcm_hw_params *params, unsigned int fmt)
+static int omap3pandora_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;
+       int fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+                 SND_SOC_DAIFMT_CBS_CFS;
        int ret;
 
        /* Set codec DAI configuration */
@@ -91,24 +93,6 @@ static int omap3pandora_cmn_hw_params(struct snd_pcm_substream *substream,
        return 0;
 }
 
-static int omap3pandora_out_hw_params(struct snd_pcm_substream *substream,
-       struct snd_pcm_hw_params *params)
-{
-       return omap3pandora_cmn_hw_params(substream, params,
-                                         SND_SOC_DAIFMT_I2S |
-                                         SND_SOC_DAIFMT_IB_NF |
-                                         SND_SOC_DAIFMT_CBS_CFS);
-}
-
-static int omap3pandora_in_hw_params(struct snd_pcm_substream *substream,
-       struct snd_pcm_hw_params *params)
-{
-       return omap3pandora_cmn_hw_params(substream, params,
-                                         SND_SOC_DAIFMT_I2S |
-                                         SND_SOC_DAIFMT_NB_NF |
-                                         SND_SOC_DAIFMT_CBS_CFS);
-}
-
 static int omap3pandora_dac_event(struct snd_soc_dapm_widget *w,
        struct snd_kcontrol *k, int event)
 {
@@ -231,12 +215,8 @@ static int omap3pandora_in_init(struct snd_soc_codec *codec)
        return snd_soc_dapm_sync(codec);
 }
 
-static struct snd_soc_ops omap3pandora_out_ops = {
-       .hw_params = omap3pandora_out_hw_params,
-};
-
-static struct snd_soc_ops omap3pandora_in_ops = {
-       .hw_params = omap3pandora_in_hw_params,
+static struct snd_soc_ops omap3pandora_ops = {
+       .hw_params = omap3pandora_hw_params,
 };
 
 /* Digital audio interface glue - connects codec <--> CPU */
@@ -246,14 +226,14 @@ static struct snd_soc_dai_link omap3pandora_dai[] = {
                .stream_name = "HiFi Out",
                .cpu_dai = &omap_mcbsp_dai[0],
                .codec_dai = &twl4030_dai[TWL4030_DAI_HIFI],
-               .ops = &omap3pandora_out_ops,
+               .ops = &omap3pandora_ops,
                .init = omap3pandora_out_init,
        }, {
                .name = "TWL4030",
                .stream_name = "Line/Mic In",
                .cpu_dai = &omap_mcbsp_dai[1],
                .codec_dai = &twl4030_dai[TWL4030_DAI_HIFI],
-               .ops = &omap3pandora_in_ops,
+               .ops = &omap3pandora_ops,
                .init = omap3pandora_in_init,
        }
 };
index 47d831ef2dbbff0fd01ba0f9b18810db37d9d118..88052d29617f9da25aac0eb687301d9e6cd207ce 100644 (file)
@@ -27,6 +27,7 @@
 #include <linux/gpio.h>
 #include <linux/platform_device.h>
 #include <sound/core.h>
+#include <sound/jack.h>
 #include <sound/pcm.h>
 #include <sound/soc.h>
 #include <sound/soc-dapm.h>
 #include "omap-pcm.h"
 #include "../codecs/tlv320aic3x.h"
 
+#define RX51_TVOUT_SEL_GPIO            40
+#define RX51_JACK_DETECT_GPIO          177
 /*
  * REVISIT: TWL4030 GPIO base in RX-51. Now statically defined to 192. This
  * gpio is reserved in arch/arm/mach-omap2/board-rx51-peripherals.c
  */
 #define RX51_SPEAKER_AMP_TWL_GPIO      (192 + 7)
 
+enum {
+       RX51_JACK_DISABLED,
+       RX51_JACK_TVOUT,                /* tv-out */
+};
+
 static int rx51_spk_func;
 static int rx51_dmic_func;
+static int rx51_jack_func;
 
 static void rx51_ext_control(struct snd_soc_codec *codec)
 {
@@ -57,6 +66,9 @@ static void rx51_ext_control(struct snd_soc_codec *codec)
        else
                snd_soc_dapm_disable_pin(codec, "DMic");
 
+       gpio_set_value(RX51_TVOUT_SEL_GPIO,
+                      rx51_jack_func == RX51_JACK_TVOUT);
+
        snd_soc_dapm_sync(codec);
 }
 
@@ -162,6 +174,40 @@ static int rx51_set_input(struct snd_kcontrol *kcontrol,
        return 1;
 }
 
+static int rx51_get_jack(struct snd_kcontrol *kcontrol,
+                        struct snd_ctl_elem_value *ucontrol)
+{
+       ucontrol->value.integer.value[0] = rx51_jack_func;
+
+       return 0;
+}
+
+static int rx51_set_jack(struct snd_kcontrol *kcontrol,
+                        struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+       if (rx51_jack_func == ucontrol->value.integer.value[0])
+               return 0;
+
+       rx51_jack_func = ucontrol->value.integer.value[0];
+       rx51_ext_control(codec);
+
+       return 1;
+}
+
+static struct snd_soc_jack rx51_av_jack;
+
+static struct snd_soc_jack_gpio rx51_av_jack_gpios[] = {
+       {
+               .gpio = RX51_JACK_DETECT_GPIO,
+               .name = "avdet-gpio",
+               .report = SND_JACK_VIDEOOUT,
+               .invert = 1,
+               .debounce_time = 200,
+       },
+};
+
 static const struct snd_soc_dapm_widget aic34_dapm_widgets[] = {
        SND_SOC_DAPM_SPK("Ext Spk", rx51_spk_event),
        SND_SOC_DAPM_MIC("DMic", NULL),
@@ -177,10 +223,12 @@ static const struct snd_soc_dapm_route audio_map[] = {
 
 static const char *spk_function[] = {"Off", "On"};
 static const char *input_function[] = {"ADC", "Digital Mic"};
+static const char *jack_function[] = {"Off", "TV-OUT"};
 
 static const struct soc_enum rx51_enum[] = {
        SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(spk_function), spk_function),
        SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(input_function), input_function),
+       SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(jack_function), jack_function),
 };
 
 static const struct snd_kcontrol_new aic34_rx51_controls[] = {
@@ -188,10 +236,13 @@ static const struct snd_kcontrol_new aic34_rx51_controls[] = {
                     rx51_get_spk, rx51_set_spk),
        SOC_ENUM_EXT("Input Select",  rx51_enum[1],
                     rx51_get_input, rx51_set_input),
+       SOC_ENUM_EXT("Jack Function", rx51_enum[2],
+                    rx51_get_jack, rx51_set_jack),
 };
 
 static int rx51_aic34_init(struct snd_soc_codec *codec)
 {
+       struct snd_soc_card *card = codec->socdev->card;
        int err;
 
        /* Set up NC codec pins */
@@ -214,7 +265,16 @@ static int rx51_aic34_init(struct snd_soc_codec *codec)
 
        snd_soc_dapm_sync(codec);
 
-       return 0;
+       /* AV jack detection */
+       err = snd_soc_jack_new(card, "AV Jack",
+                              SND_JACK_VIDEOOUT, &rx51_av_jack);
+       if (err)
+               return err;
+       err = snd_soc_jack_add_gpios(&rx51_av_jack,
+                                    ARRAY_SIZE(rx51_av_jack_gpios),
+                                    rx51_av_jack_gpios);
+
+       return err;
 }
 
 /* Digital audio interface glue - connects codec <--> CPU */
@@ -259,6 +319,11 @@ static int __init rx51_soc_init(void)
        if (!machine_is_nokia_rx51())
                return -ENODEV;
 
+       err = gpio_request(RX51_TVOUT_SEL_GPIO, "tvout_sel");
+       if (err)
+               goto err_gpio_tvout_sel;
+       gpio_direction_output(RX51_TVOUT_SEL_GPIO, 0);
+
        rx51_snd_device = platform_device_alloc("soc-audio", -1);
        if (!rx51_snd_device) {
                err = -ENOMEM;
@@ -277,13 +342,19 @@ static int __init rx51_soc_init(void)
 err2:
        platform_device_put(rx51_snd_device);
 err1:
+       gpio_free(RX51_TVOUT_SEL_GPIO);
+err_gpio_tvout_sel:
 
        return err;
 }
 
 static void __exit rx51_soc_exit(void)
 {
+       snd_soc_jack_free_gpios(&rx51_av_jack, ARRAY_SIZE(rx51_av_jack_gpios),
+                               rx51_av_jack_gpios);
+
        platform_device_unregister(rx51_snd_device);
+       gpio_free(RX51_TVOUT_SEL_GPIO);
 }
 
 module_init(rx51_soc_init);
index 2a7cc222d098f57b139744525612be50157d651f..213963ac3c28ba4aaa236c399f5211356f469625 100644 (file)
@@ -1,6 +1,6 @@
 config SND_S3C24XX_SOC
        tristate "SoC Audio for the Samsung S3CXXXX chips"
-       depends on ARCH_S3C2410 || ARCH_S3C64XX
+       depends on ARCH_S3C2410 || ARCH_S3C64XX || ARCH_S5PC100 || ARCH_S5PV210
        select S3C64XX_DMA if ARCH_S3C64XX
        help
          Say Y or M if you want to add support for codecs attached to
@@ -120,8 +120,14 @@ config SND_S3C24XX_SOC_SIMTEC_HERMES
 
 config SND_SOC_SMDK_WM9713
        tristate "SoC AC97 Audio support for SMDK with WM9713"
-       depends on SND_S3C24XX_SOC && MACH_SMDK6410
+       depends on SND_S3C24XX_SOC && (MACH_SMDK6410 || MACH_SMDKC100 || MACH_SMDKV210 || MACH_SMDKC110)
        select SND_SOC_WM9713
        select SND_S3C_SOC_AC97
        help
          Sat Y if you want to add support for SoC audio on the SMDK.
+
+config SND_S3C64XX_SOC_SMARTQ
+       tristate "SoC I2S Audio support for SmartQ board"
+       depends on SND_S3C24XX_SOC && MACH_SMARTQ
+       select SND_S3C64XX_SOC_I2S
+       select SND_SOC_WM8750
index 81d8dc503f8760660e4b80257aa0e7a47c0fa2ff..50172c385d90b89e7fc327a596efc235f95cca55 100644 (file)
@@ -29,6 +29,7 @@ snd-soc-s3c24xx-simtec-hermes-objs := s3c24xx_simtec_hermes.o
 snd-soc-s3c24xx-simtec-tlv320aic23-objs := s3c24xx_simtec_tlv320aic23.o
 snd-soc-smdk64xx-wm8580-objs := smdk64xx_wm8580.o
 snd-soc-smdk-wm9713-objs := smdk_wm9713.o
+snd-soc-s3c64xx-smartq-wm8987-objs := smartq_wm8987.o
 
 obj-$(CONFIG_SND_S3C24XX_SOC_JIVE_WM8750) += snd-soc-jive-wm8750.o
 obj-$(CONFIG_SND_S3C24XX_SOC_NEO1973_WM8753) += snd-soc-neo1973-wm8753.o
@@ -41,3 +42,4 @@ obj-$(CONFIG_SND_S3C24XX_SOC_SIMTEC_HERMES) += snd-soc-s3c24xx-simtec-hermes.o
 obj-$(CONFIG_SND_S3C24XX_SOC_SIMTEC_TLV320AIC23) += snd-soc-s3c24xx-simtec-tlv320aic23.o
 obj-$(CONFIG_SND_S3C64XX_SOC_WM8580) += snd-soc-smdk64xx-wm8580.o
 obj-$(CONFIG_SND_SOC_SMDK_WM9713) += snd-soc-smdk-wm9713.o
+obj-$(CONFIG_SND_S3C64XX_SOC_SMARTQ) += snd-soc-s3c64xx-smartq-wm8987.o
index ecf4fd04ae9651c2a264c9428fcf6d1caae6777f..31f6d45b6384f2f0e0e4e5a351acc79a6f47520b 100644 (file)
@@ -31,7 +31,6 @@
 #define AC_CMD_DATA(x) (x & 0xffff)
 
 struct s3c_ac97_info {
-       unsigned           state;
        struct clk         *ac97_clk;
        void __iomem       *regs;
        struct mutex       lock;
index 13311c8cf96546bf92207b9f5193b2a62c6ff64c..64376b2aac73ce8854daa3226368aee7e59ae223 100644 (file)
@@ -32,7 +32,8 @@
 
 #undef S3C_IIS_V2_SUPPORTED
 
-#if defined(CONFIG_CPU_S3C2412) || defined(CONFIG_CPU_S3C2413)
+#if defined(CONFIG_CPU_S3C2412) || defined(CONFIG_CPU_S3C2413) \
+       || defined(CONFIG_CPU_S5PV210)
 #define S3C_IIS_V2_SUPPORTED
 #endif
 
diff --git a/sound/soc/s3c24xx/smartq_wm8987.c b/sound/soc/s3c24xx/smartq_wm8987.c
new file mode 100644 (file)
index 0000000..b480348
--- /dev/null
@@ -0,0 +1,295 @@
+/* sound/soc/s3c24xx/smartq_wm8987.c
+ *
+ * Copyright 2010 Maurus Cuelenaere <mcuelenaere@gmail.com>
+ *
+ * Based on smdk6410_wm8987.c
+ *     Copyright 2007 Wolfson Microelectronics PLC. - linux@wolfsonmicro.com
+ *     Graeme Gregory - graeme.gregory@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/module.h>
+#include <linux/platform_device.h>
+#include <linux/gpio.h>
+
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc-dapm.h>
+#include <sound/jack.h>
+
+#include <asm/mach-types.h>
+
+#include "s3c-dma.h"
+#include "s3c64xx-i2s.h"
+
+#include "../codecs/wm8750.h"
+
+/*
+ * WM8987 is register compatible with WM8750, so using that as base driver.
+ */
+
+static struct snd_soc_card snd_soc_smartq;
+
+static int smartq_hifi_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 s3c_i2sv2_rate_calc div;
+       unsigned int clk = 0;
+       int ret;
+
+       s3c_i2sv2_iis_calc_rate(&div, NULL, params_rate(params),
+                               s3c_i2sv2_get_clock(cpu_dai));
+
+       switch (params_rate(params)) {
+       case 8000:
+       case 16000:
+       case 32000:
+       case 48000:
+       case 96000:
+               clk = 12288000;
+               break;
+       case 11025:
+       case 22050:
+       case 44100:
+       case 88200:
+               clk = 11289600;
+               break;
+       }
+
+       /* 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);
+       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;
+
+       /* set the codec system clock for DAC and ADC */
+       ret = snd_soc_dai_set_sysclk(codec_dai, WM8750_SYSCLK, clk,
+                                    SND_SOC_CLOCK_IN);
+       if (ret < 0)
+               return ret;
+
+       /* set MCLK division for sample rate */
+       ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C_I2SV2_DIV_RCLK, div.fs_div);
+       if (ret < 0)
+               return ret;
+
+       /* set prescaler division for sample rate */
+       ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C_I2SV2_DIV_PRESCALER,
+                                    div.clk_div - 1);
+       if (ret < 0)
+               return ret;
+
+       return 0;
+}
+
+/*
+ * SmartQ WM8987 HiFi DAI operations.
+ */
+static struct snd_soc_ops smartq_hifi_ops = {
+       .hw_params = smartq_hifi_hw_params,
+};
+
+static struct snd_soc_jack smartq_jack;
+
+static struct snd_soc_jack_pin smartq_jack_pins[] = {
+       /* Disable speaker when headphone is plugged in */
+       {
+               .pin    = "Internal Speaker",
+               .mask   = SND_JACK_HEADPHONE,
+       },
+};
+
+static struct snd_soc_jack_gpio smartq_jack_gpios[] = {
+       {
+               .gpio           = S3C64XX_GPL(12),
+               .name           = "headphone detect",
+               .report         = SND_JACK_HEADPHONE,
+               .debounce_time  = 200,
+       },
+};
+
+static const struct snd_kcontrol_new wm8987_smartq_controls[] = {
+       SOC_DAPM_PIN_SWITCH("Internal Speaker"),
+       SOC_DAPM_PIN_SWITCH("Headphone Jack"),
+       SOC_DAPM_PIN_SWITCH("Internal Mic"),
+};
+
+static int smartq_speaker_event(struct snd_soc_dapm_widget *w,
+                               struct snd_kcontrol *k,
+                               int event)
+{
+       gpio_set_value(S3C64XX_GPK(12), SND_SOC_DAPM_EVENT_OFF(event));
+
+       return 0;
+}
+
+static const struct snd_soc_dapm_widget wm8987_dapm_widgets[] = {
+       SND_SOC_DAPM_SPK("Internal Speaker", smartq_speaker_event),
+       SND_SOC_DAPM_HP("Headphone Jack", NULL),
+       SND_SOC_DAPM_MIC("Internal Mic", NULL),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+       {"Headphone Jack", NULL, "LOUT2"},
+       {"Headphone Jack", NULL, "ROUT2"},
+
+       {"Internal Speaker", NULL, "LOUT2"},
+       {"Internal Speaker", NULL, "ROUT2"},
+
+       {"Mic Bias", NULL, "Internal Mic"},
+       {"LINPUT2", NULL, "Mic Bias"},
+};
+
+static int smartq_wm8987_init(struct snd_soc_codec *codec)
+{
+       int err = 0;
+
+       /* Add SmartQ specific widgets */
+       snd_soc_dapm_new_controls(codec, wm8987_dapm_widgets,
+                                 ARRAY_SIZE(wm8987_dapm_widgets));
+
+       /* add SmartQ specific controls */
+       err = snd_soc_add_controls(codec, wm8987_smartq_controls,
+                                  ARRAY_SIZE(wm8987_smartq_controls));
+
+       if (err < 0)
+               return err;
+
+       /* setup SmartQ specific audio path */
+       snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+       /* set endpoints to not connected */
+       snd_soc_dapm_nc_pin(codec, "LINPUT1");
+       snd_soc_dapm_nc_pin(codec, "RINPUT1");
+       snd_soc_dapm_nc_pin(codec, "OUT3");
+       snd_soc_dapm_nc_pin(codec, "ROUT1");
+
+       /* set endpoints to default off mode */
+       snd_soc_dapm_enable_pin(codec, "Internal Speaker");
+       snd_soc_dapm_enable_pin(codec, "Internal Mic");
+       snd_soc_dapm_disable_pin(codec, "Headphone Jack");
+
+       err = snd_soc_dapm_sync(codec);
+       if (err)
+               return err;
+
+       /* Headphone jack detection */
+       err = snd_soc_jack_new(&snd_soc_smartq, "Headphone Jack",
+                              SND_JACK_HEADPHONE, &smartq_jack);
+       if (err)
+               return err;
+
+       err = snd_soc_jack_add_pins(&smartq_jack, ARRAY_SIZE(smartq_jack_pins),
+                                   smartq_jack_pins);
+       if (err)
+               return err;
+
+       err = snd_soc_jack_add_gpios(&smartq_jack,
+                                    ARRAY_SIZE(smartq_jack_gpios),
+                                    smartq_jack_gpios);
+
+       return err;
+}
+
+static struct snd_soc_dai_link smartq_dai[] = {
+       {
+               .name           = "wm8987",
+               .stream_name    = "SmartQ Hi-Fi",
+               .cpu_dai        = &s3c64xx_i2s_dai[0],
+               .codec_dai      = &wm8750_dai,
+               .init           = smartq_wm8987_init,
+               .ops            = &smartq_hifi_ops,
+       },
+};
+
+static struct snd_soc_card snd_soc_smartq = {
+       .name = "SmartQ",
+       .platform = &s3c24xx_soc_platform,
+       .dai_link = smartq_dai,
+       .num_links = ARRAY_SIZE(smartq_dai),
+};
+
+static struct snd_soc_device smartq_snd_devdata = {
+       .card = &snd_soc_smartq,
+       .codec_dev = &soc_codec_dev_wm8750,
+};
+
+static struct platform_device *smartq_snd_device;
+
+static int __init smartq_init(void)
+{
+       int ret;
+
+       if (!machine_is_smartq7() && !machine_is_smartq5()) {
+               pr_info("Only SmartQ is supported by this ASoC driver\n");
+               return -ENODEV;
+       }
+
+       smartq_snd_device = platform_device_alloc("soc-audio", -1);
+       if (!smartq_snd_device)
+               return -ENOMEM;
+
+       platform_set_drvdata(smartq_snd_device, &smartq_snd_devdata);
+       smartq_snd_devdata.dev = &smartq_snd_device->dev;
+
+       ret = platform_device_add(smartq_snd_device);
+       if (ret) {
+               platform_device_put(smartq_snd_device);
+               return ret;
+       }
+
+       /* Initialise GPIOs used by amplifiers */
+       ret = gpio_request(S3C64XX_GPK(12), "amplifiers shutdown");
+       if (ret) {
+               dev_err(&smartq_snd_device->dev, "Failed to register GPK12\n");
+               goto err_unregister_device;
+       }
+
+       /* Disable amplifiers */
+       ret = gpio_direction_output(S3C64XX_GPK(12), 1);
+       if (ret) {
+               dev_err(&smartq_snd_device->dev, "Failed to configure GPK12\n");
+               goto err_free_gpio_amp_shut;
+       }
+
+       return 0;
+
+err_free_gpio_amp_shut:
+       gpio_free(S3C64XX_GPK(12));
+err_unregister_device:
+       platform_device_unregister(smartq_snd_device);
+
+       return ret;
+}
+
+static void __exit smartq_exit(void)
+{
+       snd_soc_jack_free_gpios(&smartq_jack, ARRAY_SIZE(smartq_jack_gpios),
+                               smartq_jack_gpios);
+
+       platform_device_unregister(smartq_snd_device);
+}
+
+module_init(smartq_init);
+module_exit(smartq_exit);
+
+/* Module information */
+MODULE_AUTHOR("Maurus Cuelenaere <mcuelenaere@gmail.com>");
+MODULE_DESCRIPTION("ALSA SoC SmartQ WM8987");
+MODULE_LICENSE("GPL");
index 24fd39f38ccb9eb1bca4dd847fd561a19ebe1216..5527b9e88c98ee9f7772a002db499e6e0e274717 100644 (file)
@@ -25,6 +25,9 @@ static struct snd_soc_card smdk;
  * Default CFG switch settings to use this driver:
  *
  *   SMDK6410: Set CFG1 1-3 On, CFG2 1-4 Off
+ *   SMDKC100: Set CFG6 1-3 On, CFG7 1   On
+ *   SMDKC110: Set CFGB10 1-2 Off, CFGB12 1-3 On
+ *   SMDKV210: Set CFGB10 1-2 Off, CFGB12 1-3 On
  */
 
 /*
index 5b9ac1759bd26021eb8570381a5dfd2eb5d7842c..59e3fa7bcb05a7e912d709e7c1c2958097b1f7a0 100644 (file)
@@ -451,16 +451,15 @@ static int __devinit s6000_i2s_probe(struct platform_device *pdev)
                goto err_release_none;
        }
 
-       region = request_mem_region(scbmem->start,
-                                   scbmem->end - scbmem->start + 1,
-                                   pdev->name);
+       region = request_mem_region(scbmem->start, resource_size(scbmem),
+                                                               pdev->name);
        if (!region) {
                dev_err(&pdev->dev, "I2S SCB region already claimed\n");
                ret = -EBUSY;
                goto err_release_none;
        }
 
-       mmio = ioremap(scbmem->start, scbmem->end - scbmem->start + 1);
+       mmio = ioremap(scbmem->start, resource_size(scbmem));
        if (!mmio) {
                dev_err(&pdev->dev, "can't ioremap SCB region\n");
                ret = -ENOMEM;
@@ -474,9 +473,8 @@ static int __devinit s6000_i2s_probe(struct platform_device *pdev)
                goto err_release_map;
        }
 
-       region = request_mem_region(sifmem->start,
-                                   sifmem->end - sifmem->start + 1,
-                                   pdev->name);
+       region = request_mem_region(sifmem->start, resource_size(sifmem),
+                                                               pdev->name);
        if (!region) {
                dev_err(&pdev->dev, "I2S SIF region already claimed\n");
                ret = -EBUSY;
@@ -490,8 +488,8 @@ static int __devinit s6000_i2s_probe(struct platform_device *pdev)
                goto err_release_sif;
        }
 
-       region = request_mem_region(dma1->start, dma1->end - dma1->start + 1,
-                                   pdev->name);
+       region = request_mem_region(dma1->start, resource_size(dma1),
+                                                               pdev->name);
        if (!region) {
                dev_err(&pdev->dev, "I2S DMA region already claimed\n");
                ret = -EBUSY;
@@ -500,9 +498,8 @@ static int __devinit s6000_i2s_probe(struct platform_device *pdev)
 
        dma2 = platform_get_resource(pdev, IORESOURCE_DMA, 1);
        if (dma2) {
-               region = request_mem_region(dma2->start,
-                                           dma2->end - dma2->start + 1,
-                                           pdev->name);
+               region = request_mem_region(dma2->start, resource_size(dma2),
+                                                               pdev->name);
                if (!region) {
                        dev_err(&pdev->dev,
                                "I2S DMA region already claimed\n");
@@ -561,15 +558,15 @@ err_release_dev:
        kfree(dev);
 err_release_dma2:
        if (dma2)
-               release_mem_region(dma2->start, dma2->end - dma2->start + 1);
+               release_mem_region(dma2->start, resource_size(dma2));
 err_release_dma1:
-       release_mem_region(dma1->start, dma1->end - dma1->start + 1);
+       release_mem_region(dma1->start, resource_size(dma1));
 err_release_sif:
-       release_mem_region(sifmem->start, (sifmem->end - sifmem->start) + 1);
+       release_mem_region(sifmem->start, resource_size(sifmem));
 err_release_map:
        iounmap(mmio);
 err_release_scb:
-       release_mem_region(scbmem->start, (scbmem->end - scbmem->start) + 1);
+       release_mem_region(scbmem->start, resource_size(scbmem));
 err_release_none:
        return ret;
 }
@@ -590,19 +587,18 @@ static void __devexit s6000_i2s_remove(struct platform_device *pdev)
        kfree(dev);
 
        region = platform_get_resource(pdev, IORESOURCE_DMA, 0);
-       release_mem_region(region->start, region->end - region->start + 1);
+       release_mem_region(region->start, resource_size(region));
 
        region = platform_get_resource(pdev, IORESOURCE_DMA, 1);
        if (region)
-               release_mem_region(region->start,
-                                  region->end - region->start + 1);
+               release_mem_region(region->start, resource_size(region));
 
        region = platform_get_resource(pdev, IORESOURCE_MEM, 0);
-       release_mem_region(region->start, (region->end - region->start) + 1);
+       release_mem_region(region->start, resource_size(region));
 
        iounmap(mmio);
        region = platform_get_resource(pdev, IORESOURCE_IO, 0);
-       release_mem_region(region->start, (region->end - region->start) + 1);
+       release_mem_region(region->start, resource_size(region));
 }
 
 static struct platform_driver s6000_i2s_driver = {
index a1d14bc3c76f9332ce425868bd22228c4ade1f95..52d7e8ed9c1faa1e9e38015dfe0c04cb5a9a5fbf 100644 (file)
@@ -48,7 +48,7 @@ config SND_SH7760_AC97
 
 config SND_FSI_AK4642
        bool "FSI-AK4642 sound support"
-       depends on SND_SOC_SH4_FSI
+       depends on SND_SOC_SH4_FSI && I2C_SH_MOBILE
        select SND_SOC_AK4642
        help
          This option enables generic sound support for the
@@ -56,7 +56,7 @@ config SND_FSI_AK4642
 
 config SND_FSI_DA7210
        bool "FSI-DA7210 sound support"
-       depends on SND_SOC_SH4_FSI
+       depends on SND_SOC_SH4_FSI && I2C_SH_MOBILE
        select SND_SOC_DA7210
        help
          This option enables generic sound support for the
index be018542314e6d340e1285ad6851ef58f30bbd55..dad575a2262228fc1ffa6511bc036d74beade9dc 100644 (file)
@@ -9,16 +9,7 @@
  * for more details.
  */
 
-#include <linux/module.h>
-#include <linux/moduleparam.h>
 #include <linux/platform_device.h>
-#include <linux/i2c.h>
-#include <linux/io.h>
-#include <sound/core.h>
-#include <sound/pcm.h>
-#include <sound/soc.h>
-#include <sound/soc-dapm.h>
-
 #include <sound/sh_fsi.h>
 #include <../sound/soc/codecs/ak4642.h>
 
@@ -38,7 +29,7 @@ static int fsi_ak4642_dai_init(struct snd_soc_codec *codec)
 static struct snd_soc_dai_link fsi_dai_link = {
        .name           = "AK4642",
        .stream_name    = "AK4642",
-       .cpu_dai        = &fsi_soc_dai[0], /* fsi */
+       .cpu_dai        = &fsi_soc_dai[FSI_PORT_A],
        .codec_dai      = &ak4642_dai,
        .init           = fsi_ak4642_dai_init,
        .ops            = NULL,
@@ -62,7 +53,7 @@ static int __init fsi_ak4642_init(void)
 {
        int ret = -ENOMEM;
 
-       fsi_snd_device = platform_device_alloc("soc-audio", -1);
+       fsi_snd_device = platform_device_alloc("soc-audio", FSI_PORT_A);
        if (!fsi_snd_device)
                goto out;
 
index 33b4d177f46616f10278e6a9cf448f1d9e21ed06..121bbb07bb03b7677280dcf7f8e9316c686de332 100644 (file)
  *  option) any later version.
  */
 
-#include <linux/interrupt.h>
 #include <linux/platform_device.h>
-#include <linux/io.h>
-#include <linux/i2c.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/sh_fsi.h>
 #include "../codecs/da7210.h"
 
@@ -33,7 +24,7 @@ static int fsi_da7210_init(struct snd_soc_codec *codec)
 static struct snd_soc_dai_link fsi_da7210_dai = {
        .name           = "DA7210",
        .stream_name    = "DA7210",
-       .cpu_dai        = &fsi_soc_dai[1], /* FSI B */
+       .cpu_dai        = &fsi_soc_dai[FSI_PORT_B],
        .codec_dai      = &da7210_dai,
        .init           = fsi_da7210_init,
 };
@@ -56,7 +47,7 @@ static int __init fsi_da7210_sound_init(void)
 {
        int ret;
 
-       fsi_da7210_snd_device = platform_device_alloc("soc-audio", -1);
+       fsi_da7210_snd_device = platform_device_alloc("soc-audio", FSI_PORT_B);
        if (!fsi_da7210_snd_device)
                return -ENOMEM;
 
index ec4acac49ebd527e0a1db89336e528566abf948c..58c6bec642decf3093746f577fd387c0688ad459 100644 (file)
  * published by the Free Software Foundation.
  */
 
-#include <linux/init.h>
-#include <linux/module.h>
-#include <linux/platform_device.h>
 #include <linux/delay.h>
-#include <linux/list.h>
 #include <linux/pm_runtime.h>
 #include <linux/io.h>
 #include <linux/slab.h>
-#include <sound/core.h>
-#include <sound/pcm.h>
-#include <sound/initval.h>
 #include <sound/soc.h>
-#include <sound/pcm_params.h>
 #include <sound/sh_fsi.h>
-#include <asm/atomic.h>
 
 #define DO_FMT         0x0000
 #define DOFF_CTL       0x0004
 #define DIDT           0x0020
 #define DODT           0x0024
 #define MUTE_ST                0x0028
-#define REG_END                MUTE_ST
-
+#define OUT_SEL                0x0030
+#define REG_END                OUT_SEL
 
+#define A_MST_CTLR     0x0180
+#define B_MST_CTLR     0x01A0
 #define CPU_INT_ST     0x01F4
 #define CPU_IEMSK      0x01F8
 #define CPU_IMSK       0x01FC
 #define CLK_RST                0x0210
 #define SOFT_RST       0x0214
 #define FIFO_SZ                0x0218
-#define MREG_START     CPU_INT_ST
+#define MREG_START     A_MST_CTLR
 #define MREG_END       FIFO_SZ
 
 /* DO_FMT */
 /* DI_FMT */
-#define CR_FMT(param) ((param) << 4)
-# define CR_MONO       0x0
-# define CR_MONO_D     0x1
-# define CR_PCM                0x2
-# define CR_I2S                0x3
-# define CR_TDM                0x4
-# define CR_TDM_D      0x5
+#define CR_MONO                (0x0 << 4)
+#define CR_MONO_D      (0x1 << 4)
+#define CR_PCM         (0x2 << 4)
+#define CR_I2S         (0x3 << 4)
+#define CR_TDM         (0x4 << 4)
+#define CR_TDM_D       (0x5 << 4)
+#define CR_SPDIF       0x00100120
 
 /* DOFF_CTL */
 /* DIFF_CTL */
 #define ERR_UNDER      0x00000001
 #define ST_ERR         (ERR_OVER | ERR_UNDER)
 
+/* CKG1 */
+#define ACKMD_MASK     0x00007000
+#define BPFMD_MASK     0x00000700
+
+/* A/B MST_CTLR */
+#define BP     (1 << 4)        /* Fix the signal of Biphase output */
+#define SE     (1 << 0)        /* Fix the master clock */
+
 /* CLK_RST */
 #define B_CLK          0x00000010
 #define A_CLK          0x00000001
@@ -119,9 +120,13 @@ struct fsi_priv {
        int period_len;
        int buffer_len;
        int periods;
+
+       u32 mst_ctrl;
 };
 
-struct fsi_regs {
+struct fsi_core {
+       int ver;
+
        u32 int_st;
        u32 iemsk;
        u32 imsk;
@@ -132,7 +137,7 @@ struct fsi_master {
        int irq;
        struct fsi_priv fsia;
        struct fsi_priv fsib;
-       struct fsi_regs *regs;
+       struct fsi_core *core;
        struct sh_fsi_platform_info *info;
        spinlock_t lock;
 };
@@ -169,24 +174,30 @@ static void __fsi_reg_mask_set(u32 reg, u32 mask, u32 data)
 
 static void fsi_reg_write(struct fsi_priv *fsi, u32 reg, u32 data)
 {
-       if (reg > REG_END)
+       if (reg > REG_END) {
+               pr_err("fsi: register access err (%s)\n", __func__);
                return;
+       }
 
        __fsi_reg_write((u32)(fsi->base + reg), data);
 }
 
 static u32 fsi_reg_read(struct fsi_priv *fsi, u32 reg)
 {
-       if (reg > REG_END)
+       if (reg > REG_END) {
+               pr_err("fsi: register access err (%s)\n", __func__);
                return 0;
+       }
 
        return __fsi_reg_read((u32)(fsi->base + reg));
 }
 
 static void fsi_reg_mask_set(struct fsi_priv *fsi, u32 reg, u32 mask, u32 data)
 {
-       if (reg > REG_END)
+       if (reg > REG_END) {
+               pr_err("fsi: register access err (%s)\n", __func__);
                return;
+       }
 
        __fsi_reg_mask_set((u32)(fsi->base + reg), mask, data);
 }
@@ -196,8 +207,10 @@ static void fsi_master_write(struct fsi_master *master, u32 reg, u32 data)
        unsigned long flags;
 
        if ((reg < MREG_START) ||
-           (reg > MREG_END))
+           (reg > MREG_END)) {
+               pr_err("fsi: register access err (%s)\n", __func__);
                return;
+       }
 
        spin_lock_irqsave(&master->lock, flags);
        __fsi_reg_write((u32)(master->base + reg), data);
@@ -210,8 +223,10 @@ static u32 fsi_master_read(struct fsi_master *master, u32 reg)
        unsigned long flags;
 
        if ((reg < MREG_START) ||
-           (reg > MREG_END))
+           (reg > MREG_END)) {
+               pr_err("fsi: register access err (%s)\n", __func__);
                return 0;
+       }
 
        spin_lock_irqsave(&master->lock, flags);
        ret = __fsi_reg_read((u32)(master->base + reg));
@@ -226,8 +241,10 @@ static void fsi_master_mask_set(struct fsi_master *master,
        unsigned long flags;
 
        if ((reg < MREG_START) ||
-           (reg > MREG_END))
+           (reg > MREG_END)) {
+               pr_err("fsi: register access err (%s)\n", __func__);
                return;
+       }
 
        spin_lock_irqsave(&master->lock, flags);
        __fsi_reg_mask_set((u32)(master->base + reg), mask, data);
@@ -349,8 +366,8 @@ static void fsi_irq_enable(struct fsi_priv *fsi, int is_play)
        u32 data = fsi_port_ab_io_bit(fsi, is_play);
        struct fsi_master *master = fsi_get_master(fsi);
 
-       fsi_master_mask_set(master, master->regs->imsk,  data, data);
-       fsi_master_mask_set(master, master->regs->iemsk, data, data);
+       fsi_master_mask_set(master, master->core->imsk,  data, data);
+       fsi_master_mask_set(master, master->core->iemsk, data, data);
 }
 
 static void fsi_irq_disable(struct fsi_priv *fsi, int is_play)
@@ -358,18 +375,18 @@ static void fsi_irq_disable(struct fsi_priv *fsi, int is_play)
        u32 data = fsi_port_ab_io_bit(fsi, is_play);
        struct fsi_master *master = fsi_get_master(fsi);
 
-       fsi_master_mask_set(master, master->regs->imsk,  data, 0);
-       fsi_master_mask_set(master, master->regs->iemsk, data, 0);
+       fsi_master_mask_set(master, master->core->imsk,  data, 0);
+       fsi_master_mask_set(master, master->core->iemsk, data, 0);
 }
 
 static u32 fsi_irq_get_status(struct fsi_master *master)
 {
-       return fsi_master_read(master, master->regs->int_st);
+       return fsi_master_read(master, master->core->int_st);
 }
 
 static void fsi_irq_clear_all_status(struct fsi_master *master)
 {
-       fsi_master_write(master, master->regs->int_st, 0x0000000);
+       fsi_master_write(master, master->core->int_st, 0);
 }
 
 static void fsi_irq_clear_status(struct fsi_priv *fsi)
@@ -381,7 +398,30 @@ static void fsi_irq_clear_status(struct fsi_priv *fsi)
        data |= fsi_port_ab_io_bit(fsi, 1);
 
        /* clear interrupt factor */
-       fsi_master_mask_set(master, master->regs->int_st, data, 0);
+       fsi_master_mask_set(master, master->core->int_st, data, 0);
+}
+
+/************************************************************************
+
+
+               SPDIF master clock function
+
+These functions are used later FSI2
+************************************************************************/
+static void fsi_spdif_clk_ctrl(struct fsi_priv *fsi, int enable)
+{
+       struct fsi_master *master = fsi_get_master(fsi);
+       u32 val = BP | SE;
+
+       if (master->core->ver < 2) {
+               pr_err("fsi: register access err (%s)\n", __func__);
+               return;
+       }
+
+       if (enable)
+               fsi_master_mask_set(master, fsi->mst_ctrl, val, val);
+       else
+               fsi_master_mask_set(master, fsi->mst_ctrl, val, 0);
 }
 
 /************************************************************************
@@ -662,8 +702,8 @@ static int fsi_dai_startup(struct snd_pcm_substream *substream,
                           struct snd_soc_dai *dai)
 {
        struct fsi_priv *fsi = fsi_get_priv(substream);
-       const char *msg;
        u32 flags = fsi_get_info_flags(fsi);
+       struct fsi_master *master = fsi_get_master(fsi);
        u32 fmt;
        u32 reg;
        u32 data;
@@ -700,36 +740,40 @@ static int fsi_dai_startup(struct snd_pcm_substream *substream,
        fmt = is_play ? SH_FSI_GET_OFMT(flags) : SH_FSI_GET_IFMT(flags);
        switch (fmt) {
        case SH_FSI_FMT_MONO:
-               msg = "MONO";
-               data = CR_FMT(CR_MONO);
+               data = CR_MONO;
                fsi->chan = 1;
                break;
        case SH_FSI_FMT_MONO_DELAY:
-               msg = "MONO Delay";
-               data = CR_FMT(CR_MONO_D);
+               data = CR_MONO_D;
                fsi->chan = 1;
                break;
        case SH_FSI_FMT_PCM:
-               msg = "PCM";
-               data = CR_FMT(CR_PCM);
+               data = CR_PCM;
                fsi->chan = 2;
                break;
        case SH_FSI_FMT_I2S:
-               msg = "I2S";
-               data = CR_FMT(CR_I2S);
+               data = CR_I2S;
                fsi->chan = 2;
                break;
        case SH_FSI_FMT_TDM:
-               msg = "TDM";
                fsi->chan = is_play ?
                        SH_FSI_GET_CH_O(flags) : SH_FSI_GET_CH_I(flags);
-               data = CR_FMT(CR_TDM) | (fsi->chan - 1);
+               data = CR_TDM | (fsi->chan - 1);
                break;
        case SH_FSI_FMT_TDM_DELAY:
-               msg = "TDM Delay";
                fsi->chan = is_play ?
                        SH_FSI_GET_CH_O(flags) : SH_FSI_GET_CH_I(flags);
-               data = CR_FMT(CR_TDM_D) | (fsi->chan - 1);
+               data = CR_TDM_D | (fsi->chan - 1);
+               break;
+       case SH_FSI_FMT_SPDIF:
+               if (master->core->ver < 2) {
+                       dev_err(dai->dev, "This FSI can not use SPDIF\n");
+                       return -EINVAL;
+               }
+               data = CR_SPDIF;
+               fsi->chan = 2;
+               fsi_spdif_clk_ctrl(fsi, 1);
+               fsi_reg_mask_set(fsi, OUT_SEL, 0x0010, 0x0010);
                break;
        default:
                dev_err(dai->dev, "unknown format.\n");
@@ -737,12 +781,6 @@ static int fsi_dai_startup(struct snd_pcm_substream *substream,
        }
        fsi_reg_write(fsi, reg, data);
 
-       /*
-        * clear clk reset if master mode
-        */
-       if (is_master)
-               fsi_clk_ctrl(fsi, 1);
-
        /* irq clear */
        fsi_irq_disable(fsi, is_play);
        fsi_irq_clear_status(fsi);
@@ -789,10 +827,93 @@ static int fsi_dai_trigger(struct snd_pcm_substream *substream, int cmd,
        return ret;
 }
 
+static int fsi_dai_hw_params(struct snd_pcm_substream *substream,
+                            struct snd_pcm_hw_params *params,
+                            struct snd_soc_dai *dai)
+{
+       struct fsi_priv *fsi = fsi_get_priv(substream);
+       struct fsi_master *master = fsi_get_master(fsi);
+       int (*set_rate)(int is_porta, int rate) = master->info->set_rate;
+       int fsi_ver = master->core->ver;
+       int is_play = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK);
+       int ret;
+
+       /* if slave mode, set_rate is not needed */
+       if (!fsi_is_master_mode(fsi, is_play))
+               return 0;
+
+       /* it is error if no set_rate */
+       if (!set_rate)
+               return -EIO;
+
+       ret = set_rate(fsi_is_port_a(fsi), params_rate(params));
+       if (ret > 0) {
+               u32 data = 0;
+
+               switch (ret & SH_FSI_ACKMD_MASK) {
+               default:
+                       /* FALL THROUGH */
+               case SH_FSI_ACKMD_512:
+                       data |= (0x0 << 12);
+                       break;
+               case SH_FSI_ACKMD_256:
+                       data |= (0x1 << 12);
+                       break;
+               case SH_FSI_ACKMD_128:
+                       data |= (0x2 << 12);
+                       break;
+               case SH_FSI_ACKMD_64:
+                       data |= (0x3 << 12);
+                       break;
+               case SH_FSI_ACKMD_32:
+                       if (fsi_ver < 2)
+                               dev_err(dai->dev, "unsupported ACKMD\n");
+                       else
+                               data |= (0x4 << 12);
+                       break;
+               }
+
+               switch (ret & SH_FSI_BPFMD_MASK) {
+               default:
+                       /* FALL THROUGH */
+               case SH_FSI_BPFMD_32:
+                       data |= (0x0 << 8);
+                       break;
+               case SH_FSI_BPFMD_64:
+                       data |= (0x1 << 8);
+                       break;
+               case SH_FSI_BPFMD_128:
+                       data |= (0x2 << 8);
+                       break;
+               case SH_FSI_BPFMD_256:
+                       data |= (0x3 << 8);
+                       break;
+               case SH_FSI_BPFMD_512:
+                       data |= (0x4 << 8);
+                       break;
+               case SH_FSI_BPFMD_16:
+                       if (fsi_ver < 2)
+                               dev_err(dai->dev, "unsupported ACKMD\n");
+                       else
+                               data |= (0x7 << 8);
+                       break;
+               }
+
+               fsi_reg_mask_set(fsi, CKG1, (ACKMD_MASK | BPFMD_MASK) , data);
+               udelay(10);
+               fsi_clk_ctrl(fsi, 1);
+               ret = 0;
+       }
+
+       return ret;
+
+}
+
 static struct snd_soc_dai_ops fsi_dai_ops = {
        .startup        = fsi_dai_startup,
        .shutdown       = fsi_dai_shutdown,
        .trigger        = fsi_dai_trigger,
+       .hw_params      = fsi_dai_hw_params,
 };
 
 /************************************************************************
@@ -965,11 +1086,6 @@ static int fsi_probe(struct platform_device *pdev)
        unsigned int irq;
        int ret;
 
-       if (0 != pdev->id) {
-               dev_err(&pdev->dev, "current fsi support id 0 only now\n");
-               return -ENODEV;
-       }
-
        id_entry = pdev->id_entry;
        if (!id_entry) {
                dev_err(&pdev->dev, "unknown fsi device\n");
@@ -998,14 +1114,21 @@ static int fsi_probe(struct platform_device *pdev)
                goto exit_kfree;
        }
 
+       /* master setting */
        master->irq             = irq;
        master->info            = pdev->dev.platform_data;
+       master->core            = (struct fsi_core *)id_entry->driver_data;
+       spin_lock_init(&master->lock);
+
+       /* FSI A setting */
        master->fsia.base       = master->base;
        master->fsia.master     = master;
+       master->fsia.mst_ctrl   = A_MST_CTLR;
+
+       /* FSI B setting */
        master->fsib.base       = master->base + 0x40;
        master->fsib.master     = master;
-       master->regs            = (struct fsi_regs *)id_entry->driver_data;
-       spin_lock_init(&master->lock);
+       master->fsib.mst_ctrl   = B_MST_CTLR;
 
        pm_runtime_enable(&pdev->dev);
        pm_runtime_resume(&pdev->dev);
@@ -1085,21 +1208,27 @@ static struct dev_pm_ops fsi_pm_ops = {
        .runtime_resume         = fsi_runtime_nop,
 };
 
-static struct fsi_regs fsi_regs = {
+static struct fsi_core fsi1_core = {
+       .ver    = 1,
+
+       /* Interrupt */
        .int_st = INT_ST,
        .iemsk  = IEMSK,
        .imsk   = IMSK,
 };
 
-static struct fsi_regs fsi2_regs = {
+static struct fsi_core fsi2_core = {
+       .ver    = 2,
+
+       /* Interrupt */
        .int_st = CPU_INT_ST,
        .iemsk  = CPU_IEMSK,
        .imsk   = CPU_IMSK,
 };
 
 static struct platform_device_id fsi_id_table[] = {
-       { "sh_fsi",     (kernel_ulong_t)&fsi_regs },
-       { "sh_fsi2",    (kernel_ulong_t)&fsi2_regs },
+       { "sh_fsi",     (kernel_ulong_t)&fsi1_core },
+       { "sh_fsi2",    (kernel_ulong_t)&fsi2_core },
 };
 
 static struct platform_driver fsi_driver = {
index 998569d6033066fe9d5e498eece2e9992089a75f..5299932db0b69a19dcda549f2d2a4eea9c095870 100644 (file)
@@ -84,7 +84,7 @@ static int run_delayed_work(struct delayed_work *dwork)
 /* codec register dump */
 static ssize_t soc_codec_reg_show(struct snd_soc_codec *codec, char *buf)
 {
-       int i, step = 1, count = 0;
+       int ret, i, step = 1, count = 0;
 
        if (!codec->reg_cache_size)
                return 0;
@@ -101,12 +101,24 @@ static ssize_t soc_codec_reg_show(struct snd_soc_codec *codec, char *buf)
                if (count >= PAGE_SIZE - 1)
                        break;
 
-               if (codec->display_register)
+               if (codec->display_register) {
                        count += codec->display_register(codec, buf + count,
                                                         PAGE_SIZE - count, i);
-               else
-                       count += snprintf(buf + count, PAGE_SIZE - count,
-                                         "%4x", codec->read(codec, i));
+               } else {
+                       /* If the read fails it's almost certainly due to
+                        * the register being volatile and the device being
+                        * powered off.
+                        */
+                       ret = codec->read(codec, i);
+                       if (ret >= 0)
+                               count += snprintf(buf + count,
+                                                 PAGE_SIZE - count,
+                                                 "%4x", ret);
+                       else
+                               count += snprintf(buf + count,
+                                                 PAGE_SIZE - count,
+                                                 "<no data: %d>", ret);
+               }
 
                if (count >= PAGE_SIZE - 1)
                        break;
@@ -2352,6 +2364,99 @@ int snd_soc_limit_volume(struct snd_soc_codec *codec,
 }
 EXPORT_SYMBOL_GPL(snd_soc_limit_volume);
 
+/**
+ * snd_soc_info_volsw_2r_sx - double with tlv and variable data size
+ *  mixer info callback
+ * @kcontrol: mixer control
+ * @uinfo: control element information
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_info_volsw_2r_sx(struct snd_kcontrol *kcontrol,
+                       struct snd_ctl_elem_info *uinfo)
+{
+       struct soc_mixer_control *mc =
+               (struct soc_mixer_control *)kcontrol->private_value;
+       int max = mc->max;
+       int min = mc->min;
+
+       uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+       uinfo->count = 2;
+       uinfo->value.integer.min = 0;
+       uinfo->value.integer.max = max-min;
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_info_volsw_2r_sx);
+
+/**
+ * snd_soc_get_volsw_2r_sx - double with tlv and variable data size
+ *  mixer get callback
+ * @kcontrol: mixer control
+ * @uinfo: control element information
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_get_volsw_2r_sx(struct snd_kcontrol *kcontrol,
+                       struct snd_ctl_elem_value *ucontrol)
+{
+       struct soc_mixer_control *mc =
+               (struct soc_mixer_control *)kcontrol->private_value;
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       unsigned int mask = (1<<mc->shift)-1;
+       int min = mc->min;
+       int val = snd_soc_read(codec, mc->reg) & mask;
+       int valr = snd_soc_read(codec, mc->rreg) & mask;
+
+       ucontrol->value.integer.value[0] = ((val & 0xff)-min) & mask;
+       ucontrol->value.integer.value[1] = ((valr & 0xff)-min) & mask;
+       return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_get_volsw_2r_sx);
+
+/**
+ * snd_soc_put_volsw_2r_sx - double with tlv and variable data size
+ *  mixer put callback
+ * @kcontrol: mixer control
+ * @uinfo: control element information
+ *
+ * Returns 0 for success.
+ */
+int snd_soc_put_volsw_2r_sx(struct snd_kcontrol *kcontrol,
+                       struct snd_ctl_elem_value *ucontrol)
+{
+       struct soc_mixer_control *mc =
+               (struct soc_mixer_control *)kcontrol->private_value;
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       unsigned int mask = (1<<mc->shift)-1;
+       int min = mc->min;
+       int ret;
+       unsigned int val, valr, oval, ovalr;
+
+       val = ((ucontrol->value.integer.value[0]+min) & 0xff);
+       val &= mask;
+       valr = ((ucontrol->value.integer.value[1]+min) & 0xff);
+       valr &= mask;
+
+       oval = snd_soc_read(codec, mc->reg) & mask;
+       ovalr = snd_soc_read(codec, mc->rreg) & mask;
+
+       ret = 0;
+       if (oval != val) {
+               ret = snd_soc_write(codec, mc->reg, val);
+               if (ret < 0)
+                       return ret;
+       }
+       if (ovalr != valr) {
+               ret = snd_soc_write(codec, mc->rreg, valr);
+               if (ret < 0)
+                       return ret;
+       }
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_put_volsw_2r_sx);
+
 /**
  * snd_soc_dai_set_sysclk - configure DAI system or master clock.
  * @dai: DAI