diff options
137 files changed, 10250 insertions, 2719 deletions
diff --git a/arch/arm/mach-davinci/board-dm365-evm.c b/arch/arm/mach-davinci/board-dm365-evm.c index df4ab21..e78d811 100644 --- a/arch/arm/mach-davinci/board-dm365-evm.c +++ b/arch/arm/mach-davinci/board-dm365-evm.c @@ -605,7 +605,11 @@ static __init void dm365_evm_init(void) /* maybe setup mmc1/etc ... _after_ mmc0 */ evm_init_cpld(); +#ifdef CONFIG_SND_DM365_AIC3X_CODEC dm365_init_asp(&dm365_evm_snd_data); +#elif defined(CONFIG_SND_DM365_VOICE_CODEC) + dm365_init_vc(&dm365_evm_snd_data); +#endif dm365_init_rtc(); dm365_init_ks(&dm365evm_ks_data); diff --git a/arch/arm/mach-omap2/mcbsp.c b/arch/arm/mach-omap2/mcbsp.c index 2f3cad6..c293370 100644 --- a/arch/arm/mach-omap2/mcbsp.c +++ b/arch/arm/mach-omap2/mcbsp.c @@ -187,32 +187,28 @@ static struct omap_mcbsp_platform_data omap44xx_mcbsp_pdata[] = { .phys_base = OMAP44XX_MCBSP1_BASE, .dma_rx_sync = OMAP44XX_DMA_MCBSP1_RX, .dma_tx_sync = OMAP44XX_DMA_MCBSP1_TX, - .rx_irq = INT_24XX_MCBSP1_IRQ_RX, - .tx_irq = INT_24XX_MCBSP1_IRQ_TX, + .tx_irq = OMAP44XX_IRQ_MCBSP1, .ops = &omap2_mcbsp_ops, }, { .phys_base = OMAP44XX_MCBSP2_BASE, .dma_rx_sync = OMAP44XX_DMA_MCBSP2_RX, .dma_tx_sync = OMAP44XX_DMA_MCBSP2_TX, - .rx_irq = INT_24XX_MCBSP2_IRQ_RX, - .tx_irq = INT_24XX_MCBSP2_IRQ_TX, + .tx_irq = OMAP44XX_IRQ_MCBSP2, .ops = &omap2_mcbsp_ops, }, { .phys_base = OMAP44XX_MCBSP3_BASE, .dma_rx_sync = OMAP44XX_DMA_MCBSP3_RX, .dma_tx_sync = OMAP44XX_DMA_MCBSP3_TX, - .rx_irq = INT_24XX_MCBSP3_IRQ_RX, - .tx_irq = INT_24XX_MCBSP3_IRQ_TX, + .tx_irq = OMAP44XX_IRQ_MCBSP3, .ops = &omap2_mcbsp_ops, }, { .phys_base = OMAP44XX_MCBSP4_BASE, .dma_rx_sync = OMAP44XX_DMA_MCBSP4_RX, .dma_tx_sync = OMAP44XX_DMA_MCBSP4_TX, - .rx_irq = INT_24XX_MCBSP4_IRQ_RX, - .tx_irq = INT_24XX_MCBSP4_IRQ_TX, + .tx_irq = OMAP44XX_IRQ_MCBSP4, .ops = &omap2_mcbsp_ops, }, }; diff --git a/arch/arm/mach-s3c2412/dma.c b/arch/arm/mach-s3c2412/dma.c index e880524..7abecfc 100644 --- a/arch/arm/mach-s3c2412/dma.c +++ b/arch/arm/mach-s3c2412/dma.c @@ -30,7 +30,6 @@ #include <mach/regs-mem.h> #include <mach/regs-lcd.h> #include <mach/regs-sdi.h> -#include <plat/regs-s3c2412-iis.h> #include <plat/regs-iis.h> #include <plat/regs-spi.h> @@ -119,13 +118,11 @@ static struct s3c24xx_dma_map __initdata s3c2412_dma_mappings[] = { .name = "i2s-sdi", .channels = MAP(S3C2412_DMAREQSEL_I2SRX), .channels_rx = MAP(S3C2412_DMAREQSEL_I2SRX), - .hw_addr.from = S3C2410_PA_IIS + S3C2412_IISRXD, }, [DMACH_I2S_OUT] = { .name = "i2s-sdo", .channels = MAP(S3C2412_DMAREQSEL_I2STX), .channels_rx = MAP(S3C2412_DMAREQSEL_I2STX), - .hw_addr.to = S3C2410_PA_IIS + S3C2412_IISTXD, }, [DMACH_USB_EP1] = { .name = "usb-ep1", diff --git a/arch/arm/plat-omap/include/plat/mcbsp.h b/arch/arm/plat-omap/include/plat/mcbsp.h index 7de903d..975744f 100644 --- a/arch/arm/plat-omap/include/plat/mcbsp.h +++ b/arch/arm/plat-omap/include/plat/mcbsp.h @@ -149,6 +149,8 @@ #define OMAP_MCBSP_REG_WAKEUPEN 0xA8 #define OMAP_MCBSP_REG_XCCR 0xAC #define OMAP_MCBSP_REG_RCCR 0xB0 +#define OMAP_MCBSP_REG_XBUFFSTAT 0xB4 +#define OMAP_MCBSP_REG_RBUFFSTAT 0xB8 #define OMAP_MCBSP_REG_SSELCR 0xBC #define OMAP_ST_REG_REV 0x00 @@ -471,6 +473,8 @@ 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_tx_delay(unsigned int id); +u16 omap_mcbsp_get_rx_delay(unsigned int id); int omap_mcbsp_get_dma_op_mode(unsigned int id); #else static inline void omap_mcbsp_set_tx_threshold(unsigned int id, u16 threshold) @@ -479,6 +483,8 @@ 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_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; } #endif int omap_mcbsp_request(unsigned int id); diff --git a/arch/arm/plat-omap/mcbsp.c b/arch/arm/plat-omap/mcbsp.c index e1d0440..7e669c9 100644 --- a/arch/arm/plat-omap/mcbsp.c +++ b/arch/arm/plat-omap/mcbsp.c @@ -489,7 +489,7 @@ void omap_mcbsp_set_tx_threshold(unsigned int id, u16 threshold) { struct omap_mcbsp *mcbsp; - if (!cpu_is_omap34xx()) + if (!cpu_is_omap34xx() && !cpu_is_omap44xx()) return; if (!omap_mcbsp_check_valid_id(id)) { @@ -511,7 +511,7 @@ void omap_mcbsp_set_rx_threshold(unsigned int id, u16 threshold) { struct omap_mcbsp *mcbsp; - if (!cpu_is_omap34xx()) + if (!cpu_is_omap34xx() && !cpu_is_omap44xx()) return; if (!omap_mcbsp_check_valid_id(id)) { @@ -560,6 +560,61 @@ 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 */ +/* + * omap_mcbsp_get_tx_delay returns the number of used slots in the McBSP FIFO + */ +u16 omap_mcbsp_get_tx_delay(unsigned int id) +{ + struct omap_mcbsp *mcbsp; + u16 buffstat; + + 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); + + /* Returns the number of free locations in the buffer */ + 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; +} +EXPORT_SYMBOL(omap_mcbsp_get_tx_delay); + +/* + * omap_mcbsp_get_rx_delay returns the number of free slots in the McBSP FIFO + * to reach the threshold value (when the DMA will be triggered to read it) + */ +u16 omap_mcbsp_get_rx_delay(unsigned int id) +{ + struct omap_mcbsp *mcbsp; + u16 buffstat, threshold; + + 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); + + /* Returns the number of used locations in the buffer */ + buffstat = MCBSP_READ(mcbsp, RBUFFSTAT); + /* RX threshold */ + threshold = MCBSP_READ(mcbsp, THRSH1); + + /* Return the number of location till we reach the threshold limit */ + if (threshold <= buffstat) + return 0; + else + return threshold - buffstat; +} +EXPORT_SYMBOL(omap_mcbsp_get_rx_delay); + /* * omap_mcbsp_get_dma_op_mode just return the current configured * operating mode for the mcbsp channel @@ -587,7 +642,7 @@ static inline void omap34xx_mcbsp_request(struct omap_mcbsp *mcbsp) * Enable wakup behavior, smart idle and all wakeups * REVISIT: some wakeups may be unnecessary */ - if (cpu_is_omap34xx()) { + if (cpu_is_omap34xx() || cpu_is_omap44xx()) { u16 syscon; syscon = MCBSP_READ(mcbsp, SYSCON); @@ -610,7 +665,7 @@ static inline void omap34xx_mcbsp_free(struct omap_mcbsp *mcbsp) /* * Disable wakup behavior, smart idle and all wakeups */ - if (cpu_is_omap34xx()) { + if (cpu_is_omap34xx() || cpu_is_omap44xx()) { u16 syscon; syscon = MCBSP_READ(mcbsp, SYSCON); @@ -724,14 +779,17 @@ int omap_mcbsp_request(unsigned int id) goto err_clk_disable; } - init_completion(&mcbsp->rx_irq_completion); - err = request_irq(mcbsp->rx_irq, omap_mcbsp_rx_irq_handler, + if (mcbsp->rx_irq) { + init_completion(&mcbsp->rx_irq_completion); + err = request_irq(mcbsp->rx_irq, + omap_mcbsp_rx_irq_handler, 0, "McBSP", (void *)mcbsp); - if (err != 0) { - dev_err(mcbsp->dev, "Unable to request RX IRQ %d " - "for McBSP%d\n", mcbsp->rx_irq, - mcbsp->id); - goto err_free_irq; + if (err != 0) { + dev_err(mcbsp->dev, "Unable to request RX IRQ %d " + "for McBSP%d\n", mcbsp->rx_irq, + mcbsp->id); + goto err_free_irq; + } } } @@ -781,7 +839,8 @@ void omap_mcbsp_free(unsigned int id) if (mcbsp->io_type == OMAP_MCBSP_IRQ_IO) { /* Free IRQs */ - free_irq(mcbsp->rx_irq, (void *)mcbsp); + if (mcbsp->rx_irq) + free_irq(mcbsp->rx_irq, (void *)mcbsp); free_irq(mcbsp->tx_irq, (void *)mcbsp); } @@ -855,7 +914,7 @@ void omap_mcbsp_start(unsigned int id, int tx, int rx) MCBSP_WRITE(mcbsp, SPCR2, w | (1 << 7)); } - if (cpu_is_omap2430() || cpu_is_omap34xx()) { + if (cpu_is_omap2430() || cpu_is_omap34xx() || cpu_is_omap44xx()) { /* Release the transmitter and receiver */ w = MCBSP_READ_CACHE(mcbsp, XCCR); w &= ~(tx ? XDISABLE : 0); @@ -885,7 +944,7 @@ void omap_mcbsp_stop(unsigned int id, int tx, int rx) /* Reset transmitter */ tx &= 1; - if (cpu_is_omap2430() || cpu_is_omap34xx()) { + if (cpu_is_omap2430() || cpu_is_omap34xx() || cpu_is_omap44xx()) { w = MCBSP_READ_CACHE(mcbsp, XCCR); w |= (tx ? XDISABLE : 0); MCBSP_WRITE(mcbsp, XCCR, w); @@ -895,7 +954,7 @@ void omap_mcbsp_stop(unsigned int id, int tx, int rx) /* Reset receiver */ rx &= 1; - if (cpu_is_omap2430() || cpu_is_omap34xx()) { + if (cpu_is_omap2430() || cpu_is_omap34xx() || cpu_is_omap44xx()) { w = MCBSP_READ_CACHE(mcbsp, RCCR); w |= (rx ? RDISABLE : 0); MCBSP_WRITE(mcbsp, RCCR, w); diff --git a/drivers/gpio/wm8994-gpio.c b/drivers/gpio/wm8994-gpio.c index 7607cc6..2ac9a16 100644 --- a/drivers/gpio/wm8994-gpio.c +++ b/drivers/gpio/wm8994-gpio.c @@ -81,6 +81,18 @@ static void wm8994_gpio_set(struct gpio_chip *chip, unsigned offset, int value) wm8994_set_bits(wm8994, WM8994_GPIO_1 + offset, WM8994_GPN_LVL, value); } +static int wm8994_gpio_to_irq(struct gpio_chip *chip, unsigned offset) +{ + struct wm8994_gpio *wm8994_gpio = to_wm8994_gpio(chip); + struct wm8994 *wm8994 = wm8994_gpio->wm8994; + + if (!wm8994->irq_base) + return -EINVAL; + + return wm8994->irq_base + offset; +} + + #ifdef CONFIG_DEBUG_FS static void wm8994_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip) { diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 2a5a0b7..de3e74c 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -53,6 +53,10 @@ config MFD_SH_MOBILE_SDHI This driver supports the SDHI hardware block found in many SuperH Mobile SoCs. +config MFD_DAVINCI_VOICECODEC + tristate + select MFD_CORE + config MFD_DM355EVM_MSP bool "DaVinci DM355 EVM microcontroller" depends on I2C && MACH_DAVINCI_DM355_EVM @@ -297,9 +301,9 @@ config MFD_WM8350_I2C selected to enable support for the functionality of the chip. config MFD_WM8994 - tristate "Support Wolfson Microelectronics WM8994" + bool "Support Wolfson Microelectronics WM8994" select MFD_CORE - depends on I2C + depends on I2C=y && GENERIC_HARDIRQS help The WM8994 is a highly integrated hi-fi CODEC designed for smartphone applicatiosn. As well as audio functionality it diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 22715ad..87935f9 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_HTC_EGPIO) += htc-egpio.o obj-$(CONFIG_HTC_PASIC3) += htc-pasic3.o obj-$(CONFIG_HTC_I2CPLD) += htc-i2cpld.o +obj-$(CONFIG_MFD_DAVINCI_VOICECODEC) += davinci_voicecodec.o obj-$(CONFIG_MFD_DM355EVM_MSP) += dm355evm_msp.o obj-$(CONFIG_MFD_T7L66XB) += t7l66xb.o tmio_core.o @@ -25,7 +26,7 @@ wm8350-objs := wm8350-core.o wm8350-regmap.o wm8350-gpio.o wm8350-objs += wm8350-irq.o obj-$(CONFIG_MFD_WM8350) += wm8350.o obj-$(CONFIG_MFD_WM8350_I2C) += wm8350-i2c.o -obj-$(CONFIG_MFD_WM8994) += wm8994-core.o +obj-$(CONFIG_MFD_WM8994) += wm8994-core.o wm8994-irq.o obj-$(CONFIG_TPS65010) += tps65010.o obj-$(CONFIG_MENELAUS) += menelaus.o diff --git a/drivers/mfd/davinci_voicecodec.c b/drivers/mfd/davinci_voicecodec.c new file mode 100644 index 0000000..3e75f02 --- /dev/null +++ b/drivers/mfd/davinci_voicecodec.c @@ -0,0 +1,190 @@ +/* + * DaVinci Voice Codec Core Interface for TI platforms + * + * Copyright (C) 2010 Texas Instruments, Inc + * + * Author: Miguel Aguilar <miguel.aguilar@ridgerun.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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/clk.h> + +#include <sound/pcm.h> + +#include <linux/mfd/davinci_voicecodec.h> + +u32 davinci_vc_read(struct davinci_vc *davinci_vc, int reg) +{ + return __raw_readl(davinci_vc->base + reg); +} + +void davinci_vc_write(struct davinci_vc *davinci_vc, + int reg, u32 val) +{ + __raw_writel(val, davinci_vc->base + reg); +} + +static int __init davinci_vc_probe(struct platform_device *pdev) +{ + struct davinci_vc *davinci_vc; + struct resource *res, *mem; + struct mfd_cell *cell = NULL; + int ret; + + davinci_vc = kzalloc(sizeof(struct davinci_vc), GFP_KERNEL); + if (!davinci_vc) { + dev_dbg(&pdev->dev, + "could not allocate memory for private data\n"); + return -ENOMEM; + } + + davinci_vc->clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(davinci_vc->clk)) { + dev_dbg(&pdev->dev, + "could not get the clock for voice codec\n"); + ret = -ENODEV; + goto fail1; + } + clk_enable(davinci_vc->clk); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "no mem resource\n"); + ret = -ENODEV; + goto fail2; + } + + davinci_vc->pbase = res->start; + davinci_vc->base_size = resource_size(res); + + mem = request_mem_region(davinci_vc->pbase, davinci_vc->base_size, + pdev->name); + if (!mem) { + dev_err(&pdev->dev, "VCIF region already claimed\n"); + ret = -EBUSY; + goto fail2; + } + + davinci_vc->base = ioremap(davinci_vc->pbase, davinci_vc->base_size); + if (!davinci_vc->base) { + dev_err(&pdev->dev, "can't ioremap mem resource.\n"); + ret = -ENOMEM; + goto fail3; + } + + res = platform_get_resource(pdev, IORESOURCE_DMA, 0); + if (!res) { + dev_err(&pdev->dev, "no DMA resource\n"); + return -ENXIO; + } + + davinci_vc->davinci_vcif.dma_tx_channel = res->start; + davinci_vc->davinci_vcif.dma_tx_addr = + (dma_addr_t)(io_v2p(davinci_vc->base) + DAVINCI_VC_WFIFO); + + res = platform_get_resource(pdev, IORESOURCE_DMA, 1); + if (!res) { + dev_err(&pdev->dev, "no DMA resource\n"); + return -ENXIO; + } + + davinci_vc->davinci_vcif.dma_rx_channel = res->start; + davinci_vc->davinci_vcif.dma_rx_addr = + (dma_addr_t)(io_v2p(davinci_vc->base) + DAVINCI_VC_RFIFO); + + davinci_vc->dev = &pdev->dev; + davinci_vc->pdev = pdev; + + /* Voice codec interface client */ + cell = &davinci_vc->cells[DAVINCI_VC_VCIF_CELL]; + cell->name = "davinci_vcif"; + cell->driver_data = davinci_vc; + + /* Voice codec CQ93VC client */ + cell = &davinci_vc->cells[DAVINCI_VC_CQ93VC_CELL]; + cell->name = "cq93vc"; + cell->driver_data = davinci_vc; + + ret = mfd_add_devices(&pdev->dev, pdev->id, davinci_vc->cells, + DAVINCI_VC_CELLS, NULL, 0); + if (ret != 0) { + dev_err(&pdev->dev, "fail to register client devices\n"); + goto fail4; + } + + return 0; + +fail4: + iounmap(davinci_vc->base); +fail3: + release_mem_region(davinci_vc->pbase, davinci_vc->base_size); +fail2: + clk_disable(davinci_vc->clk); + clk_put(davinci_vc->clk); + davinci_vc->clk = NULL; +fail1: + kfree(davinci_vc); + + return ret; +} + +static int __devexit davinci_vc_remove(struct platform_device *pdev) +{ + struct davinci_vc *davinci_vc = platform_get_drvdata(pdev); + + mfd_remove_devices(&pdev->dev); + + iounmap(davinci_vc->base); + release_mem_region(davinci_vc->pbase, davinci_vc->base_size); + + clk_disable(davinci_vc->clk); + clk_put(davinci_vc->clk); + davinci_vc->clk = NULL; + + kfree(davinci_vc); + + return 0; +} + +static struct platform_driver davinci_vc_driver = { + .driver = { + .name = "davinci_voicecodec", + .owner = THIS_MODULE, + }, + .remove = __devexit_p(davinci_vc_remove), +}; + +static int __init davinci_vc_init(void) +{ + return platform_driver_probe(&davinci_vc_driver, davinci_vc_probe); +} +module_init(davinci_vc_init); + +static void __exit davinci_vc_exit(void) +{ + platform_driver_unregister(&davinci_vc_driver); +} +module_exit(davinci_vc_exit); + +MODULE_AUTHOR("Miguel Aguilar"); +MODULE_DESCRIPTION("Texas Instruments DaVinci Voice Codec Core Interface"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/twl-core.c b/drivers/mfd/twl-core.c index 562cd49..720e099 100644 --- a/drivers/mfd/twl-core.c +++ b/drivers/mfd/twl-core.c @@ -109,7 +109,7 @@ #endif #if defined(CONFIG_TWL4030_CODEC) || defined(CONFIG_TWL4030_CODEC_MODULE) ||\ - defined(CONFIG_SND_SOC_TWL6030) || defined(CONFIG_SND_SOC_TWL6030_MODULE) + defined(CONFIG_SND_SOC_TWL6040) || defined(CONFIG_SND_SOC_TWL6040_MODULE) #define twl_has_codec() true #else #define twl_has_codec() false @@ -708,7 +708,7 @@ add_children(struct twl4030_platform_data *pdata, unsigned long features) /* Phoenix*/ if (twl_has_codec() && pdata->codec && twl_class_is_6030()) { sub_chip_id = twl_map[TWL_MODULE_AUDIO_VOICE].sid; - child = add_child(sub_chip_id, "twl6030_codec", + child = add_child(sub_chip_id, "twl6040_codec", pdata->codec, sizeof(*pdata->codec), false, 0, 0); if (IS_ERR(child)) diff --git a/drivers/mfd/wm831x-irq.c b/drivers/mfd/wm831x-irq.c index 30132769..4c1122c 100644 --- a/drivers/mfd/wm831x-irq.c +++ b/drivers/mfd/wm831x-irq.c @@ -21,6 +21,7 @@ #include <linux/mfd/wm831x/core.h> #include <linux/mfd/wm831x/pdata.h> +#include <linux/mfd/wm831x/gpio.h> #include <linux/mfd/wm831x/irq.h> #include <linux/delay.h> @@ -388,12 +389,41 @@ static void wm831x_irq_mask(unsigned int irq) wm831x->irq_masks_cur[irq_data->reg - 1] |= irq_data->mask; } +static int wm831x_irq_set_type(unsigned int irq, unsigned int type) +{ + struct wm831x *wm831x = get_irq_chip_data(irq); + int val; + + irq = irq - wm831x->irq_base; + + if (irq < WM831X_IRQ_GPIO_1 || irq > WM831X_IRQ_GPIO_11) + return -EINVAL; + + switch (type) { + case IRQ_TYPE_EDGE_BOTH: + val = WM831X_GPN_INT_MODE; + break; + case IRQ_TYPE_EDGE_RISING: + val = WM831X_GPN_POL; + break; + case IRQ_TYPE_EDGE_FALLING: + val = 0; + break; + default: + return -EINVAL; + } + + return wm831x_set_bits(wm831x, WM831X_GPIO1_CONTROL + irq, + WM831X_GPN_INT_MODE | WM831X_GPN_POL, val); +} + static struct irq_chip wm831x_irq_chip = { .name = "wm831x", .bus_lock = wm831x_irq_lock, .bus_sync_unlock = wm831x_irq_sync_unlock, .mask = wm831x_irq_mask, .unmask = wm831x_irq_unmask, + .set_type = wm831x_irq_set_type, }; /* The processing of the primary interrupt occurs in a thread so that diff --git a/drivers/mfd/wm8994-core.c b/drivers/mfd/wm8994-core.c index cc524df..ec71c936 100644 --- a/drivers/mfd/wm8994-core.c +++ b/drivers/mfd/wm8994-core.c @@ -173,9 +173,34 @@ static struct mfd_cell wm8994_regulator_devs[] = { { .name = "wm8994-ldo", .id = 2 }, }; +static struct resource wm8994_codec_resources[] = { + { + .start = WM8994_IRQ_TEMP_SHUT, + .end = WM8994_IRQ_TEMP_WARN, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource wm8994_gpio_resources[] = { + { + .start = WM8994_IRQ_GPIO(1), + .end = WM8994_IRQ_GPIO(11), + .flags = IORESOURCE_IRQ, + }, +}; + static struct mfd_cell wm8994_devs[] = { - { .name = "wm8994-codec" }, - { .name = "wm8994-gpio" }, + { + .name = "wm8994-codec", + .num_resources = ARRAY_SIZE(wm8994_codec_resources), + .resources = wm8994_codec_resources, + }, + + { + .name = "wm8994-gpio", + .num_resources = ARRAY_SIZE(wm8994_gpio_resources), + .resources = wm8994_gpio_resources, + }, }; /* @@ -236,6 +261,11 @@ static int wm8994_device_resume(struct device *dev) return ret; } + ret = wm8994_write(wm8994, WM8994_INTERRUPT_STATUS_1_MASK, + WM8994_NUM_IRQ_REGS * 2, &wm8994->irq_masks_cur); + if (ret < 0) + dev_err(dev, "Failed to restore interrupt masks: %d\n", ret); + ret = wm8994_write(wm8994, WM8994_LDO_1, WM8994_NUM_LDO_REGS * 2, &wm8994->ldo_regs); if (ret < 0) @@ -348,6 +378,7 @@ static int wm8994_device_init(struct wm8994 *wm8994, unsigned long id, int irq) if (pdata) { + wm8994->irq_base = pdata->irq_base; wm8994->gpio_base = pdata->gpio_base; /* GPIO configuration is only applied if it's non-zero */ @@ -375,16 +406,20 @@ static int wm8994_device_init(struct wm8994 *wm8994, unsigned long id, int irq) WM8994_LDO1_DISCH, 0); } + wm8994_irq_init(wm8994); + ret = mfd_add_devices(wm8994->dev, -1, wm8994_devs, ARRAY_SIZE(wm8994_devs), NULL, 0); if (ret != 0) { dev_err(wm8994->dev, "Failed to add children: %d\n", ret); - goto err_enable; + goto err_irq; } return 0; +err_irq: + wm8994_irq_exit(wm8994); err_enable: regulator_bulk_disable(ARRAY_SIZE(wm8994_main_supplies), wm8994->supplies); @@ -401,6 +436,7 @@ err: static void wm8994_device_exit(struct wm8994 *wm8994) { mfd_remove_devices(wm8994->dev); + wm8994_irq_exit(wm8994); regulator_bulk_disable(ARRAY_SIZE(wm8994_main_supplies), wm8994->supplies); regulator_bulk_free(ARRAY_SIZE(wm8994_main_supplies), wm8994->supplies); @@ -469,6 +505,7 @@ static int wm8994_i2c_probe(struct i2c_client *i2c, wm8994->control_data = i2c; wm8994->read_dev = wm8994_i2c_read_device; wm8994->write_dev = wm8994_i2c_write_device; + wm8994->irq = i2c->irq; return wm8994_device_init(wm8994, id->driver_data, i2c->irq); } diff --git a/drivers/mfd/wm8994-irq.c b/drivers/mfd/wm8994-irq.c new file mode 100644 index 0000000..8400eb1 --- /dev/null +++ b/drivers/mfd/wm8994-irq.c @@ -0,0 +1,310 @@ +/* + * wm8994-irq.c -- Interrupt controller support for Wolfson WM8994 + * + * Copyright 2010 Wolfson Microelectronics PLC. + * + * Author: Mark Brown <broonie@opensource.wolfsonmicro.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/irq.h> +#include <linux/mfd/core.h> +#include <linux/interrupt.h> + +#include <linux/mfd/wm8994/core.h> +#include <linux/mfd/wm8994/registers.h> + +#include <linux/delay.h> + +struct wm8994_irq_data { + int reg; + int mask; +}; + +static struct wm8994_irq_data wm8994_irqs[] = { + [WM8994_IRQ_TEMP_SHUT] = { + .reg = 2, + .mask = WM8994_TEMP_SHUT_EINT, + }, + [WM8994_IRQ_MIC1_DET] = { + .reg = 2, + .mask = WM8994_MIC1_DET_EINT, + }, + [WM8994_IRQ_MIC1_SHRT] = { + .reg = 2, + .mask = WM8994_MIC1_SHRT_EINT, + }, + [WM8994_IRQ_MIC2_DET] = { + .reg = 2, + .mask = WM8994_MIC2_DET_EINT, + }, + [WM8994_IRQ_MIC2_SHRT] = { + .reg = 2, + .mask = WM8994_MIC2_SHRT_EINT, + }, + [WM8994_IRQ_FLL1_LOCK] = { + .reg = 2, + .mask = WM8994_FLL1_LOCK_EINT, + }, + [WM8994_IRQ_FLL2_LOCK] = { + .reg = 2, + .mask = WM8994_FLL2_LOCK_EINT, + }, + [WM8994_IRQ_SRC1_LOCK] = { + .reg = 2, + .mask = WM8994_SRC1_LOCK_EINT, + }, + [WM8994_IRQ_SRC2_LOCK] = { + .reg = 2, + .mask = WM8994_SRC2_LOCK_EINT, + }, + [WM8994_IRQ_AIF1DRC1_SIG_DET] = { + .reg = 2, + .mask = WM8994_AIF1DRC1_SIG_DET, + }, + [WM8994_IRQ_AIF1DRC2_SIG_DET] = { + .reg = 2, + .mask = WM8994_AIF1DRC2_SIG_DET_EINT, + }, + [WM8994_IRQ_AIF2DRC_SIG_DET] = { + .reg = 2, + .mask = WM8994_AIF2DRC_SIG_DET_EINT, + }, + [WM8994_IRQ_FIFOS_ERR] = { + .reg = 2, + .mask = WM8994_FIFOS_ERR_EINT, + }, + [WM8994_IRQ_WSEQ_DONE] = { + .reg = 2, + .mask = WM8994_WSEQ_DONE_EINT, + }, + [WM8994_IRQ_DCS_DONE] = { + .reg = 2, + .mask = WM8994_DCS_DONE_EINT, + }, + [WM8994_IRQ_TEMP_WARN] = { + .reg = 2, + .mask = WM8994_TEMP_WARN_EINT, + }, + [WM8994_IRQ_GPIO(1)] = { + .reg = 1, + .mask = WM8994_GP1_EINT, + }, + [WM8994_IRQ_GPIO(2)] = { + .reg = 1, + .mask = WM8994_GP2_EINT, + }, + [WM8994_IRQ_GPIO(3)] = { + .reg = 1, + .mask = WM8994_GP3_EINT, + }, + [WM8994_IRQ_GPIO(4)] = { + .reg = 1, + .mask = WM8994_GP4_EINT, + }, + [WM8994_IRQ_GPIO(5)] = { + .reg = 1, + .mask = WM8994_GP5_EINT, + }, + [WM8994_IRQ_GPIO(6)] = { + .reg = 1, + .mask = WM8994_GP6_EINT, + }, + [WM8994_IRQ_GPIO(7)] = { + .reg = 1, + .mask = WM8994_GP7_EINT, + }, + [WM8994_IRQ_GPIO(8)] = { + .reg = 1, + .mask = WM8994_GP8_EINT, + }, + [WM8994_IRQ_GPIO(9)] = { + .reg = 1, + .mask = WM8994_GP8_EINT, + }, + [WM8994_IRQ_GPIO(10)] = { + .reg = 1, + .mask = WM8994_GP10_EINT, + }, + [WM8994_IRQ_GPIO(11)] = { + .reg = 1, + .mask = WM8994_GP11_EINT, + }, +}; + +static inline int irq_data_to_status_reg(struct wm8994_irq_data *irq_data) +{ + return WM8994_INTERRUPT_STATUS_1 - 1 + irq_data->reg; +} + +static inline int irq_data_to_mask_reg(struct wm8994_irq_data *irq_data) +{ + return WM8994_INTERRUPT_STATUS_1_MASK - 1 + irq_data->reg; +} + +static inline struct wm8994_irq_data *irq_to_wm8994_irq(struct wm8994 *wm8994, + int irq) +{ + return &wm8994_irqs[irq - wm8994->irq_base]; +} + +static void wm8994_irq_lock(unsigned int irq) +{ + struct wm8994 *wm8994 = get_irq_chip_data(irq); + + mutex_lock(&wm8994->irq_lock); +} + +static void wm8994_irq_sync_unlock(unsigned int irq) +{ + struct wm8994 *wm8994 = get_irq_chip_data(irq); + int i; + + for (i = 0; i < ARRAY_SIZE(wm8994->irq_masks_cur); i++) { + /* If there's been a change in the mask write it back + * to the hardware. */ + if (wm8994->irq_masks_cur[i] != wm8994->irq_masks_cache[i]) { + wm8994->irq_masks_cache[i] = wm8994->irq_masks_cur[i]; + wm8994_reg_write(wm8994, + WM8994_INTERRUPT_STATUS_1_MASK + i, + wm8994->irq_masks_cur[i]); + } + } + + mutex_unlock(&wm8994->irq_lock); +} + +static void wm8994_irq_unmask(unsigned int irq) +{ + struct wm8994 *wm8994 = get_irq_chip_data(irq); + struct wm8994_irq_data *irq_data = irq_to_wm8994_irq(wm8994, irq); + + wm8994->irq_masks_cur[irq_data->reg - 1] &= ~irq_data->mask; +} + +static void wm8994_irq_mask(unsigned int irq) +{ + struct wm8994 *wm8994 = get_irq_chip_data(irq); + struct wm8994_irq_data *irq_data = irq_to_wm8994_irq(wm8994, irq); + + wm8994->irq_masks_cur[irq_data->reg - 1] |= irq_data->mask; +} + +static struct irq_chip wm8994_irq_chip = { + .name = "wm8994", + .bus_lock = wm8994_irq_lock, + .bus_sync_unlock = wm8994_irq_sync_unlock, + .mask = wm8994_irq_mask, + .unmask = wm8994_irq_unmask, +}; + +/* The processing of the primary interrupt occurs in a thread so that + * we can interact with the device over I2C or SPI. */ +static irqreturn_t wm8994_irq_thread(int irq, void *data) +{ + struct wm8994 *wm8994 = data; + unsigned int i; + u16 status[WM8994_NUM_IRQ_REGS]; + int ret; + + ret = wm8994_bulk_read(wm8994, WM8994_INTERRUPT_STATUS_1, + WM8994_NUM_IRQ_REGS, status); + if (ret < 0) { + dev_err(wm8994->dev, "Failed to read interrupt status: %d\n", + ret); + return IRQ_NONE; + } + + /* Apply masking */ + for (i = 0; i < WM8994_NUM_IRQ_REGS; i++) + status[i] &= ~wm8994->irq_masks_cur[i]; + + /* Report */ + for (i = 0; i < ARRAY_SIZE(wm8994_irqs); i++) { + if (status[wm8994_irqs[i].reg - 1] & wm8994_irqs[i].mask) + handle_nested_irq(wm8994->irq_base + i); + } + + /* Ack any unmasked IRQs */ + for (i = 0; i < ARRAY_SIZE(status); i++) { + if (status[i]) + wm8994_reg_write(wm8994, WM8994_INTERRUPT_STATUS_1 + i, + status[i]); + } + + return IRQ_HANDLED; +} + +int wm8994_irq_init(struct wm8994 *wm8994) +{ + int i, cur_irq, ret; + + mutex_init(&wm8994->irq_lock); + + /* Mask the individual interrupt sources */ + for (i = 0; i < ARRAY_SIZE(wm8994->irq_masks_cur); i++) { + wm8994->irq_masks_cur[i] = 0xffff; + wm8994->irq_masks_cache[i] = 0xffff; + wm8994_reg_write(wm8994, WM8994_INTERRUPT_STATUS_1_MASK + i, + 0xffff); + } + + if (!wm8994->irq) { + dev_warn(wm8994->dev, + "No interrupt specified, no interrupts\n"); + wm8994->irq_base = 0; + return 0; + } + + if (!wm8994->irq_base) { + dev_err(wm8994->dev, + "No interrupt base specified, no interrupts\n"); + return 0; + } + + /* Register them with genirq */ + for (cur_irq = wm8994->irq_base; + cur_irq < ARRAY_SIZE(wm8994_irqs) + wm8994->irq_base; + cur_irq++) { + set_irq_chip_data(cur_irq, wm8994); + set_irq_chip_and_handler(cur_irq, &wm8994_irq_chip, + handle_edge_irq); + set_irq_nested_thread(cur_irq, 1); + + /* ARM needs us to explicitly flag the IRQ as valid + * and will set them noprobe when we do so. */ +#ifdef CONFIG_ARM + set_irq_flags(cur_irq, IRQF_VALID); +#else + set_irq_noprobe(cur_irq); +#endif + } + + ret = request_threaded_irq(wm8994->irq, NULL, wm8994_irq_thread, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + "wm8994", wm8994); + if (ret != 0) { + dev_err(wm8994->dev, "Failed to request IRQ %d: %d\n", + wm8994->irq, ret); + return ret; + } + + /* Enable top level interrupt if it was masked */ + wm8994_reg_write(wm8994, WM8994_INTERRUPT_CONTROL, 0); + + return 0; +} + +void wm8994_irq_exit(struct wm8994 *wm8994) +{ + if (wm8994->irq) + free_irq(wm8994->irq, wm8994); +} diff --git a/include/linux/i2c/twl.h b/include/linux/i2c/twl.h index fb6784e..ebd90ce 100644 --- a/include/linux/i2c/twl.h +++ b/include/linux/i2c/twl.h @@ -569,9 +569,9 @@ struct twl4030_codec_data { struct twl4030_codec_audio_data *audio; struct twl4030_codec_vibra_data *vibra; - /* twl6030 */ - int audpwron_gpio; /* audio power-on gpio */ - int naudint_irq; /* audio interrupt */ + /* twl6040 */ + int audpwron_gpio; /* audio power-on gpio */ + int naudint_irq; /* audio interrupt */ }; struct twl4030_platform_data { diff --git a/include/linux/mfd/davinci_voicecodec.h b/include/linux/mfd/davinci_voicecodec.h new file mode 100644 index 0000000..0ab61320 --- /dev/null +++ b/include/linux/mfd/davinci_voicecodec.h @@ -0,0 +1,126 @@ +/* + * DaVinci Voice Codec Core Interface for TI platforms + * + * Copyright (C) 2010 Texas Instruments, Inc + * + * Author: Miguel Aguilar <miguel.aguilar@ridgerun.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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __LINUX_MFD_DAVINCI_VOICECODEC_H_ +#define __LINUX_MFD_DAVINIC_VOICECODEC_H_ + +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/mfd/core.h> + +#include <mach/edma.h> + +/* + * Register values. + */ +#define DAVINCI_VC_PID 0x00 +#define DAVINCI_VC_CTRL 0x04 +#define DAVINCI_VC_INTEN 0x08 +#define DAVINCI_VC_INTSTATUS 0x0c +#define DAVINCI_VC_INTCLR 0x10 +#define DAVINCI_VC_EMUL_CTRL 0x14 +#define DAVINCI_VC_RFIFO 0x20 +#define DAVINCI_VC_WFIFO 0x24 +#define DAVINCI_VC_FIFOSTAT 0x28 +#define DAVINCI_VC_TST_CTRL 0x2C +#define DAVINCI_VC_REG05 0x94 +#define DAVINCI_VC_REG09 0xA4 +#define DAVINCI_VC_REG12 0xB0 + +/* DAVINCI_VC_CTRL bit fields */ +#define DAVINCI_VC_CTRL_MASK 0x5500 +#define DAVINCI_VC_CTRL_RSTADC BIT(0) +#define DAVINCI_VC_CTRL_RSTDAC BIT(1) +#define DAVINCI_VC_CTRL_RD_BITS_8 BIT(4) +#define DAVINCI_VC_CTRL_RD_UNSIGNED BIT(5) +#define DAVINCI_VC_CTRL_WD_BITS_8 BIT(6) +#define DAVINCI_VC_CTRL_WD_UNSIGNED BIT(7) +#define DAVINCI_VC_CTRL_RFIFOEN BIT(8) +#define DAVINCI_VC_CTRL_RFIFOCL BIT(9) +#define DAVINCI_VC_CTRL_RFIFOMD_WORD_1 BIT(10) +#define DAVINCI_VC_CTRL_WFIFOEN BIT(12) +#define DAVINCI_VC_CTRL_WFIFOCL BIT(13) +#define DAVINCI_VC_CTRL_WFIFOMD_WORD_1 BIT(14) + +/* DAVINCI_VC_INT bit fields */ +#define DAVINCI_VC_INT_MASK 0x3F +#define DAVINCI_VC_INT_RDRDY_MASK BIT(0) +#define DAVINCI_VC_INT_RERROVF_MASK BIT(1) +#define DAVINCI_VC_INT_RERRUDR_MASK BIT(2) +#define DAVINCI_VC_INT_WDREQ_MASK BIT(3) +#define DAVINCI_VC_INT_WERROVF_MASKBIT BIT(4) +#define DAVINCI_VC_INT_WERRUDR_MASK BIT(5) + +/* DAVINCI_VC_REG05 bit fields */ +#define DAVINCI_VC_REG05_PGA_GAIN 0x07 + +/* DAVINCI_VC_REG09 bit fields */ +#define DAVINCI_VC_REG09_MUTE 0x40 +#define DAVINCI_VC_REG09_DIG_ATTEN 0x3F + +/* DAVINCI_VC_REG12 bit fields */ +#define DAVINCI_VC_REG12_POWER_ALL_ON 0xFD +#define DAVINCI_VC_REG12_POWER_ALL_OFF 0x00 + +#define DAVINCI_VC_CELLS 2 + +enum davinci_vc_cells { + DAVINCI_VC_VCIF_CELL, + DAVINCI_VC_CQ93VC_CELL, +}; + +struct davinci_vcif { + struct platform_device *pdev; + u32 dma_tx_channel; + u32 dma_rx_channel; + dma_addr_t dma_tx_addr; + dma_addr_t dma_rx_addr; +}; + +struct cq93vc { + struct platform_device *pdev; + struct snd_soc_codec *codec; + u32 sysclk; +}; + +struct davinci_vc; + +struct davinci_vc { + /* Device data */ + struct device *dev; + struct platform_device *pdev; + struct clk *clk; + + /* Memory resources */ + void __iomem *base; + resource_size_t pbase; + size_t base_size; + + /* MFD cells */ + struct mfd_cell cells[DAVINCI_VC_CELLS]; + + /* Client devices */ + struct davinci_vcif davinci_vcif; + struct cq93vc cq93vc; +}; + +#endif diff --git a/include/linux/mfd/wm8350/audio.h b/include/linux/mfd/wm8350/audio.h index d899dc0..a95141e 100644 --- a/include/linux/mfd/wm8350/audio.h +++ b/include/linux/mfd/wm8350/audio.h @@ -492,6 +492,8 @@ */ #define WM8350_JACK_L_LVL 0x0800 #define WM8350_JACK_R_LVL 0x0400 +#define WM8350_JACK_MICSCD_LVL 0x0200 +#define WM8350_JACK_MICSD_LVL 0x0100 /* * WM8350 Platform setup diff --git a/include/linux/mfd/wm8994/core.h b/include/linux/mfd/wm8994/core.h index b06ff28..de79bae 100644 --- a/include/linux/mfd/wm8994/core.h +++ b/include/linux/mfd/wm8994/core.h @@ -15,14 +15,38 @@ #ifndef __MFD_WM8994_CORE_H__ #define __MFD_WM8994_CORE_H__ +#include <linux/interrupt.h> + struct regulator_dev; struct regulator_bulk_data; #define WM8994_NUM_GPIO_REGS 11 -#define WM8994_NUM_LDO_REGS 2 +#define WM8994_NUM_LDO_REGS 2 +#define WM8994_NUM_IRQ_REGS 2 + +#define WM8994_IRQ_TEMP_SHUT 0 +#define WM8994_IRQ_MIC1_DET 1 +#define WM8994_IRQ_MIC1_SHRT 2 +#define WM8994_IRQ_MIC2_DET 3 +#define WM8994_IRQ_MIC2_SHRT 4 +#define WM8994_IRQ_FLL1_LOCK 5 +#define WM8994_IRQ_FLL2_LOCK 6 +#define WM8994_IRQ_SRC1_LOCK 7 +#define WM8994_IRQ_SRC2_LOCK 8 +#define WM8994_IRQ_AIF1DRC1_SIG_DET 9 +#define WM8994_IRQ_AIF1DRC2_SIG_DET 10 +#define WM8994_IRQ_AIF2DRC_SIG_DET 11 +#define WM8994_IRQ_FIFOS_ERR 12 +#define WM8994_IRQ_WSEQ_DONE 13 +#define WM8994_IRQ_DCS_DONE 14 +#define WM8994_IRQ_TEMP_WARN 15 + +/* GPIOs in the chip are numbered from 1-11 */ +#define WM8994_IRQ_GPIO(x) (x + WM8994_IRQ_TEMP_WARN) struct wm8994 { struct mutex io_lock; + struct mutex irq_lock; struct device *dev; int (*read_dev)(struct wm8994 *wm8994, unsigned short reg, @@ -33,6 +57,11 @@ struct wm8994 { void *control_data; int gpio_base; + int irq_base; + + int irq; + u16 irq_masks_cur[WM8994_NUM_IRQ_REGS]; + u16 irq_masks_cache[WM8994_NUM_IRQ_REGS]; /* Used over suspend/resume */ u16 ldo_regs[WM8994_NUM_LDO_REGS]; @@ -51,4 +80,26 @@ int wm8994_set_bits(struct wm8994 *wm8994, unsigned short reg, int wm8994_bulk_read(struct wm8994 *wm8994, unsigned short reg, int count, u16 *buf); + +/* Helper to save on boilerplate */ +static inline int wm8994_request_irq(struct wm8994 *wm8994, int irq, + irq_handler_t handler, const char *name, + void *data) +{ + if (!wm8994->irq_base) + return -EINVAL; + return request_threaded_irq(wm8994->irq_base + irq, NULL, handler, + IRQF_TRIGGER_RISING, name, + data); +} +static inline void wm8994_free_irq(struct wm8994 *wm8994, int irq, void *data) +{ + if (!wm8994->irq_base) + return; + free_irq(wm8994->irq_base + irq, data); +} + +int wm8994_irq_init(struct wm8994 *wm8994); +void wm8994_irq_exit(struct wm8994 *wm8994); + #endif diff --git a/include/linux/mfd/wm8994/pdata.h b/include/linux/mfd/wm8994/pdata.h index 70d6a86..5c51f36 100644 --- a/include/linux/mfd/wm8994/pdata.h +++ b/include/linux/mfd/wm8994/pdata.h @@ -70,6 +70,7 @@ struct wm8994_pdata { struct wm8994_ldo_pdata ldo[WM8994_NUM_LDO]; + int irq_base; /** Base IRQ number for WM8994, required for IRQs */ int num_drc_cfgs; struct wm8994_drc_cfg *drc_cfgs; diff --git a/include/sound/soc-dai.h b/include/sound/soc-dai.h index 0a0b019..377693a 100644 --- a/include/sound/soc-dai.h +++ b/include/sound/soc-dai.h @@ -182,6 +182,12 @@ struct snd_soc_dai_ops { struct snd_soc_dai *); int (*trigger)(struct snd_pcm_substream *, int, struct snd_soc_dai *); + /* + * For hardware based FIFO caused delay reporting. + * Optional. + */ + snd_pcm_sframes_t (*delay)(struct snd_pcm_substream *, + struct snd_soc_dai *); }; /* @@ -215,7 +221,6 @@ struct snd_soc_dai { unsigned int symmetric_rates:1; /* DAI runtime info */ - struct snd_pcm_runtime *runtime; struct snd_soc_codec *codec; unsigned int active; unsigned char pop_wait:1; diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h index c0922a0..66ff4c1 100644 --- a/include/sound/soc-dapm.h +++ b/include/sound/soc-dapm.h @@ -339,6 +339,9 @@ int snd_soc_dapm_disable_pin(struct snd_soc_codec *codec, const char *pin); int snd_soc_dapm_nc_pin(struct snd_soc_codec *codec, const char *pin); int snd_soc_dapm_get_pin_status(struct snd_soc_codec *codec, const char *pin); int snd_soc_dapm_sync(struct snd_soc_codec *codec); +int snd_soc_dapm_force_enable_pin(struct snd_soc_codec *codec, + const char *pin); +int snd_soc_dapm_ignore_suspend(struct snd_soc_codec *codec, const char *pin); /* dapm widget types */ enum snd_soc_dapm_type { @@ -425,9 +428,8 @@ struct snd_soc_dapm_widget { unsigned char connected:1; /* connected codec pin */ unsigned char new:1; /* cnew complete */ unsigned char ext:1; /* has external widgets */ - unsigned char muted:1; /* muted for pop reduction */ - unsigned char suspend:1; /* was active before suspend */ - unsigned char pmdown:1; /* waiting for timeout */ + unsigned char force:1; /* force state */ + unsigned char ignore_suspend:1; /* kept enabled over suspend */ int (*power_check)(struct snd_soc_dapm_widget *w); diff --git a/include/sound/soc.h b/include/sound/soc.h index a57fbfc..697e7ff 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -15,6 +15,7 @@ #include <linux/platform_device.h> #include <linux/types.h> +#include <linux/notifier.h> #include <linux/workqueue.h> #include <linux/interrupt.h> #include <linux/kernel.h> @@ -29,10 +30,10 @@ #define SOC_SINGLE_VALUE(xreg, xshift, xmax, xinvert) \ ((unsigned long)&(struct soc_mixer_control) \ {.reg = xreg, .shift = xshift, .rshift = xshift, .max = xmax, \ - .invert = xinvert}) + .platform_max = xmax, .invert = xinvert}) #define SOC_SINGLE_VALUE_EXT(xreg, xmax, xinvert) \ ((unsigned long)&(struct soc_mixer_control) \ - {.reg = xreg, .max = xmax, .invert = xinvert}) + {.reg = xreg, .max = xmax, .platform_max = xmax, .invert = xinvert}) #define SOC_SINGLE(xname, reg, shift, max, invert) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ .info = snd_soc_info_volsw, .get = snd_soc_get_volsw,\ @@ -52,14 +53,14 @@ .put = snd_soc_put_volsw, \ .private_value = (unsigned long)&(struct soc_mixer_control) \ {.reg = xreg, .shift = shift_left, .rshift = shift_right, \ - .max = xmax, .invert = xinvert} } + .max = xmax, .platform_max = xmax, .invert = xinvert} } #define SOC_DOUBLE_R(xname, reg_left, reg_right, xshift, xmax, xinvert) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ .info = snd_soc_info_volsw_2r, \ .get = snd_soc_get_volsw_2r, .put = snd_soc_put_volsw_2r, \ .private_value = (unsigned long)&(struct soc_mixer_control) \ {.reg = reg_left, .rreg = reg_right, .shift = xshift, \ - .max = xmax, .invert = xinvert} } + .max = xmax, .platform_max = xmax, .invert = xinvert} } #define SOC_DOUBLE_TLV(xname, xreg, shift_left, shift_right, xmax, xinvert, tlv_array) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\ @@ -69,7 +70,7 @@ .put = snd_soc_put_volsw, \ .private_value = (unsigned long)&(struct soc_mixer_control) \ {.reg = xreg, .shift = shift_left, .rshift = shift_right,\ - .max = xmax, .invert = xinvert} } + .max = xmax, .platform_max = xmax, .invert = xinvert} } #define SOC_DOUBLE_R_TLV(xname, reg_left, reg_right, xshift, xmax, xinvert, tlv_array) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\ @@ -79,7 +80,7 @@ .get = snd_soc_get_volsw_2r, .put = snd_soc_put_volsw_2r, \ .private_value = (unsigned long)&(struct soc_mixer_control) \ {.reg = reg_left, .rreg = reg_right, .shift = xshift, \ - .max = xmax, .invert = xinvert} } + .max = xmax, .platform_max = xmax, .invert = xinvert} } #define SOC_DOUBLE_S8_TLV(xname, xreg, xmin, xmax, tlv_array) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \ @@ -88,7 +89,8 @@ .info = snd_soc_info_volsw_s8, .get = snd_soc_get_volsw_s8, \ .put = snd_soc_put_volsw_s8, \ .private_value = (unsigned long)&(struct soc_mixer_control) \ - {.reg = xreg, .min = xmin, .max = xmax} } + {.reg = xreg, .min = xmin, .max = xmax, \ + .platform_max = xmax} } #define SOC_ENUM_DOUBLE(xreg, xshift_l, xshift_r, xmax, xtexts) \ { .reg = xreg, .shift_l = xshift_l, .shift_r = xshift_r, \ .max = xmax, .texts = xtexts } @@ -125,7 +127,7 @@ .get = xhandler_get, .put = xhandler_put, \ .private_value = (unsigned long)&(struct soc_mixer_control) \ {.reg = xreg, .shift = shift_left, .rshift = shift_right, \ - .max = xmax, .invert = xinvert} } + .max = xmax, .platform_max = xmax, .invert = xinvert} } #define SOC_SINGLE_EXT_TLV(xname, xreg, xshift, xmax, xinvert,\ xhandler_get, xhandler_put, tlv_array) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ @@ -145,7 +147,7 @@ .get = xhandler_get, .put = xhandler_put, \ .private_value = (unsigned long)&(struct soc_mixer_control) \ {.reg = xreg, .shift = shift_left, .rshift = shift_right, \ - .max = xmax, .invert = xinvert} } + .max = xmax, .platform_max = xmax, .invert = xinvert} } #define SOC_DOUBLE_R_EXT_TLV(xname, reg_left, reg_right, xshift, xmax, xinvert,\ xhandler_get, xhandler_put, tlv_array) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ @@ -156,7 +158,7 @@ .get = xhandler_get, .put = xhandler_put, \ .private_value = (unsigned long)&(struct soc_mixer_control) \ {.reg = reg_left, .rreg = reg_right, .shift = xshift, \ - .max = xmax, .invert = xinvert} } + .max = xmax, .platform_max = xmax, .invert = xinvert} } #define SOC_SINGLE_BOOL_EXT(xname, xdata, xhandler_get, xhandler_put) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ .info = snd_soc_info_bool_ext, \ @@ -212,6 +214,7 @@ struct snd_soc_dai_mode; struct snd_soc_pcm_runtime; struct snd_soc_dai; struct snd_soc_platform; +struct snd_soc_dai_link; struct snd_soc_codec; struct soc_enum; struct snd_soc_ac97_ops; @@ -260,6 +263,10 @@ int snd_soc_jack_new(struct snd_soc_card *card, const char *id, int type, void snd_soc_jack_report(struct snd_soc_jack *jack, int status, int mask); int snd_soc_jack_add_pins(struct snd_soc_jack *jack, int count, struct snd_soc_jack_pin *pins); +void snd_soc_jack_notifier_register(struct snd_soc_jack *jack, + struct notifier_block *nb); +void snd_soc_jack_notifier_unregister(struct snd_soc_jack *jack, + struct notifier_block *nb); #ifdef CONFIG_GPIOLIB int snd_soc_jack_add_gpios(struct snd_soc_jack *jack, int count, struct snd_soc_jack_gpio *gpios); @@ -320,6 +327,8 @@ int snd_soc_get_volsw_s8(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol); 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); /** * struct snd_soc_jack_pin - Describes a pin to update based on jack detection @@ -363,6 +372,7 @@ struct snd_soc_jack { struct snd_soc_card *card; struct list_head pins; int status; + struct blocking_notifier_head notifier; }; /* SoC PCM stream information */ @@ -374,7 +384,7 @@ struct snd_soc_pcm_stream { unsigned int rate_max; /* max rate */ unsigned int channels_min; /* min channels */ unsigned int channels_max; /* max channels */ - unsigned int active:1; /* stream is in use */ + unsigned int active; /* stream is in use */ void *dma_data; /* used by platform code */ }; @@ -407,7 +417,7 @@ struct snd_soc_codec { struct snd_ac97 *ac97; /* for ad-hoc ac97 devices */ unsigned int active; unsigned int pcm_devs; - void *private_data; + void *drvdata; /* codec IO */ void *control_data; /* codec control (i2c/3wire) data */ @@ -462,14 +472,21 @@ struct snd_soc_platform { int (*probe)(struct platform_device *pdev); int (*remove)(struct platform_device *pdev); - int (*suspend)(struct snd_soc_dai *dai); - int (*resume)(struct snd_soc_dai *dai); + int (*suspend)(struct snd_soc_dai_link *dai_link); + int (*resume)(struct snd_soc_dai_link *dai_link); /* pcm creation and destruction */ int (*pcm_new)(struct snd_card *, struct snd_soc_dai *, struct snd_pcm *); void (*pcm_free)(struct snd_pcm *); + /* + * For platform caused delay reporting. + * Optional. + */ + snd_pcm_sframes_t (*delay)(struct snd_pcm_substream *, + struct snd_soc_dai *); + /* platform stream ops */ struct snd_pcm_ops *pcm_ops; }; @@ -489,6 +506,9 @@ struct snd_soc_dai_link { /* codec/machine specific init - e.g. add machine controls */ int (*init)(struct snd_soc_codec *codec); + /* Keep DAI active over suspend */ + unsigned int ignore_suspend:1; + /* Symmetry requirements */ unsigned int symmetric_rates:1; @@ -553,7 +573,7 @@ struct snd_soc_pcm_runtime { /* mixer control */ struct soc_mixer_control { - int min, max; + int min, max, platform_max; unsigned int reg, rreg, shift, rshift, invert; }; @@ -583,6 +603,17 @@ static inline unsigned int snd_soc_write(struct snd_soc_codec *codec, return codec->write(codec, reg, val); } +static inline void snd_soc_codec_set_drvdata(struct snd_soc_codec *codec, + void *data) +{ + codec->drvdata = data; +} + +static inline void *snd_soc_codec_get_drvdata(struct snd_soc_codec *codec) +{ + return codec->drvdata; +} + #include <sound/soc-dai.h> #endif diff --git a/include/sound/tlv320aic3x.h b/include/sound/tlv320aic3x.h new file mode 100644 index 0000000..b1a5f34 --- /dev/null +++ b/include/sound/tlv320aic3x.h @@ -0,0 +1,17 @@ +/* + * Platform data for Texas Instruments TLV320AIC3x codec + * + * Author: Jarkko Nikula <jhnikula@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#ifndef __TLV320AIC3x_H__ +#define __TLV320AIC3x_H__ + +struct aic3x_pdata { + int gpio_reset; /* < 0 if not used */ +}; + +#endif
\ No newline at end of file diff --git a/include/sound/tlv320dac33-plat.h b/include/sound/tlv320dac33-plat.h index ac06652..3f428d5 100644 --- a/include/sound/tlv320dac33-plat.h +++ b/include/sound/tlv320dac33-plat.h @@ -15,6 +15,7 @@ struct tlv320dac33_platform_data { int power_gpio; + int keep_bclk; /* Keep the BCLK running in FIFO modes */ u8 burst_bclkdiv; }; diff --git a/include/sound/uda134x.h b/include/sound/uda134x.h index 475ef8b..509efb0 100644 --- a/include/sound/uda134x.h +++ b/include/sound/uda134x.h @@ -21,6 +21,7 @@ struct uda134x_platform_data { #define UDA134X_UDA1340 1 #define UDA134X_UDA1341 2 #define UDA134X_UDA1344 3 +#define UDA134X_UDA1345 4 }; #endif /* _UDA134X_H */ diff --git a/include/sound/wm8903.h b/include/sound/wm8903.h new file mode 100644 index 0000000..b4a0db23 --- /dev/null +++ b/include/sound/wm8903.h @@ -0,0 +1,249 @@ +/* + * linux/sound/wm8903.h -- Platform data for WM8903 + * + * Copyright 2010 Wolfson Microelectronics. PLC. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __LINUX_SND_WM8903_H +#define __LINUX_SND_WM8903_H + +/* Used to enable configuration of a GPIO to all zeros */ +#define WM8903_GPIO_NO_CONFIG 0x8000 + +/* + * R6 (0x06) - Mic Bias Control 0 + */ +#define WM8903_MICDET_HYST_ENA 0x0080 /* MICDET_HYST_ENA */ +#define WM8903_MICDET_HYST_ENA_MASK 0x0080 /* MICDET_HYST_ENA */ +#define WM8903_MICDET_HYST_ENA_SHIFT 7 /* MICDET_HYST_ENA */ +#define WM8903_MICDET_HYST_ENA_WIDTH 1 /* MICDET_HYST_ENA */ +#define WM8903_MICDET_THR_MASK 0x0070 /* MICDET_THR - [6:4] */ +#define WM8903_MICDET_THR_SHIFT 4 /* MICDET_THR - [6:4] */ +#define WM8903_MICDET_THR_WIDTH 3 /* MICDET_THR - [6:4] */ +#define WM8903_MICSHORT_THR_MASK 0x000C /* MICSHORT_THR - [3:2] */ +#define WM8903_MICSHORT_THR_SHIFT 2 /* MICSHORT_THR - [3:2] */ +#define WM8903_MICSHORT_THR_WIDTH 2 /* MICSHORT_THR - [3:2] */ +#define WM8903_MICDET_ENA 0x0002 /* MICDET_ENA */ +#define WM8903_MICDET_ENA_MASK 0x0002 /* MICDET_ENA */ +#define WM8903_MICDET_ENA_SHIFT 1 /* MICDET_ENA */ +#define WM8903_MICDET_ENA_WIDTH 1 /* MICDET_ENA */ +#define WM8903_MICBIAS_ENA 0x0001 /* MICBIAS_ENA */ +#define WM8903_MICBIAS_ENA_MASK 0x0001 /* MICBIAS_ENA */ +#define WM8903_MICBIAS_ENA_SHIFT 0 /* MICBIAS_ENA */ +#define WM8903_MICBIAS_ENA_WIDTH 1 /* MICBIAS_ENA */ + +/* + * R116 (0x74) - GPIO Control 1 + */ +#define WM8903_GP1_FN_MASK 0x1F00 /* GP1_FN - [12:8] */ +#define WM8903_GP1_FN_SHIFT 8 /* GP1_FN - [12:8] */ +#define WM8903_GP1_FN_WIDTH 5 /* GP1_FN - [12:8] */ +#define WM8903_GP1_DIR 0x0080 /* GP1_DIR */ +#define WM8903_GP1_DIR_MASK 0x0080 /* GP1_DIR */ +#define WM8903_GP1_DIR_SHIFT 7 /* GP1_DIR */ +#define WM8903_GP1_DIR_WIDTH 1 /* GP1_DIR */ +#define WM8903_GP1_OP_CFG 0x0040 /* GP1_OP_CFG */ +#define WM8903_GP1_OP_CFG_MASK 0x0040 /* GP1_OP_CFG */ +#define WM8903_GP1_OP_CFG_SHIFT 6 /* GP1_OP_CFG */ +#define WM8903_GP1_OP_CFG_WIDTH 1 /* GP1_OP_CFG */ +#define WM8903_GP1_IP_CFG 0x0020 /* GP1_IP_CFG */ +#define WM8903_GP1_IP_CFG_MASK 0x0020 /* GP1_IP_CFG */ +#define WM8903_GP1_IP_CFG_SHIFT 5 /* GP1_IP_CFG */ +#define WM8903_GP1_IP_CFG_WIDTH 1 /* GP1_IP_CFG */ +#define WM8903_GP1_LVL 0x0010 /* GP1_LVL */ +#define WM8903_GP1_LVL_MASK 0x0010 /* GP1_LVL */ +#define WM8903_GP1_LVL_SHIFT 4 /* GP1_LVL */ +#define WM8903_GP1_LVL_WIDTH 1 /* GP1_LVL */ +#define WM8903_GP1_PD 0x0008 /* GP1_PD */ +#define WM8903_GP1_PD_MASK 0x0008 /* GP1_PD */ +#define WM8903_GP1_PD_SHIFT 3 /* GP1_PD */ +#define WM8903_GP1_PD_WIDTH 1 /* GP1_PD */ +#define WM8903_GP1_PU 0x0004 /* GP1_PU */ +#define WM8903_GP1_PU_MASK 0x0004 /* GP1_PU */ +#define WM8903_GP1_PU_SHIFT 2 /* GP1_PU */ +#define WM8903_GP1_PU_WIDTH 1 /* GP1_PU */ +#define WM8903_GP1_INTMODE 0x0002 /* GP1_INTMODE */ +#define WM8903_GP1_INTMODE_MASK 0x0002 /* GP1_INTMODE */ +#define WM8903_GP1_INTMODE_SHIFT 1 /* GP1_INTMODE */ +#define WM8903_GP1_INTMODE_WIDTH 1 /* GP1_INTMODE */ +#define WM8903_GP1_DB 0x0001 /* GP1_DB */ +#define WM8903_GP1_DB_MASK 0x0001 /* GP1_DB */ +#define WM8903_GP1_DB_SHIFT 0 /* GP1_DB */ +#define WM8903_GP1_DB_WIDTH 1 /* GP1_DB */ + +/* + * R117 (0x75) - GPIO Control 2 + */ +#define WM8903_GP2_FN_MASK 0x1F00 /* GP2_FN - [12:8] */ +#define WM8903_GP2_FN_SHIFT 8 /* GP2_FN - [12:8] */ +#define WM8903_GP2_FN_WIDTH 5 /* GP2_FN - [12:8] */ +#define WM8903_GP2_DIR 0x0080 /* GP2_DIR */ +#define WM8903_GP2_DIR_MASK 0x0080 /* GP2_DIR */ +#define WM8903_GP2_DIR_SHIFT 7 /* GP2_DIR */ +#define WM8903_GP2_DIR_WIDTH 1 /* GP2_DIR */ +#define WM8903_GP2_OP_CFG 0x0040 /* GP2_OP_CFG */ +#define WM8903_GP2_OP_CFG_MASK 0x0040 /* GP2_OP_CFG */ +#define WM8903_GP2_OP_CFG_SHIFT 6 /* GP2_OP_CFG */ +#define WM8903_GP2_OP_CFG_WIDTH 1 /* GP2_OP_CFG */ +#define WM8903_GP2_IP_CFG 0x0020 /* GP2_IP_CFG */ +#define WM8903_GP2_IP_CFG_MASK 0x0020 /* GP2_IP_CFG */ +#define WM8903_GP2_IP_CFG_SHIFT 5 /* GP2_IP_CFG */ +#define WM8903_GP2_IP_CFG_WIDTH 1 /* GP2_IP_CFG */ +#define WM8903_GP2_LVL 0x0010 /* GP2_LVL */ +#define WM8903_GP2_LVL_MASK 0x0010 /* GP2_LVL */ +#define WM8903_GP2_LVL_SHIFT 4 /* GP2_LVL */ +#define WM8903_GP2_LVL_WIDTH 1 /* GP2_LVL */ +#define WM8903_GP2_PD 0x0008 /* GP2_PD */ +#define WM8903_GP2_PD_MASK 0x0008 /* GP2_PD */ +#define WM8903_GP2_PD_SHIFT 3 /* GP2_PD */ +#define WM8903_GP2_PD_WIDTH 1 /* GP2_PD */ +#define WM8903_GP2_PU 0x0004 /* GP2_PU */ +#define WM8903_GP2_PU_MASK 0x0004 /* GP2_PU */ +#define WM8903_GP2_PU_SHIFT 2 /* GP2_PU */ +#define WM8903_GP2_PU_WIDTH 1 /* GP2_PU */ +#define WM8903_GP2_INTMODE 0x0002 /* GP2_INTMODE */ +#define WM8903_GP2_INTMODE_MASK 0x0002 /* GP2_INTMODE */ +#define WM8903_GP2_INTMODE_SHIFT 1 /* GP2_INTMODE */ +#define WM8903_GP2_INTMODE_WIDTH 1 /* GP2_INTMODE */ +#define WM8903_GP2_DB 0x0001 /* GP2_DB */ +#define WM8903_GP2_DB_MASK 0x0001 /* GP2_DB */ +#define WM8903_GP2_DB_SHIFT 0 /* GP2_DB */ +#define WM8903_GP2_DB_WIDTH 1 /* GP2_DB */ + +/* + * R118 (0x76) - GPIO Control 3 + */ +#define WM8903_GP3_FN_MASK 0x1F00 /* GP3_FN - [12:8] */ +#define WM8903_GP3_FN_SHIFT 8 /* GP3_FN - [12:8] */ +#define WM8903_GP3_FN_WIDTH 5 /* GP3_FN - [12:8] */ +#define WM8903_GP3_DIR 0x0080 /* GP3_DIR */ +#define WM8903_GP3_DIR_MASK 0x0080 /* GP3_DIR */ +#define WM8903_GP3_DIR_SHIFT 7 /* GP3_DIR */ +#define WM8903_GP3_DIR_WIDTH 1 /* GP3_DIR */ +#define WM8903_GP3_OP_CFG 0x0040 /* GP3_OP_CFG */ +#define WM8903_GP3_OP_CFG_MASK 0x0040 /* GP3_OP_CFG */ +#define WM8903_GP3_OP_CFG_SHIFT 6 /* GP3_OP_CFG */ +#define WM8903_GP3_OP_CFG_WIDTH 1 /* GP3_OP_CFG */ +#define WM8903_GP3_IP_CFG 0x0020 /* GP3_IP_CFG */ +#define WM8903_GP3_IP_CFG_MASK 0x0020 /* GP3_IP_CFG */ +#define WM8903_GP3_IP_CFG_SHIFT 5 /* GP3_IP_CFG */ +#define WM8903_GP3_IP_CFG_WIDTH 1 /* GP3_IP_CFG */ +#define WM8903_GP3_LVL 0x0010 /* GP3_LVL */ +#define WM8903_GP3_LVL_MASK 0x0010 /* GP3_LVL */ +#define WM8903_GP3_LVL_SHIFT 4 /* GP3_LVL */ +#define WM8903_GP3_LVL_WIDTH 1 /* GP3_LVL */ +#define WM8903_GP3_PD 0x0008 /* GP3_PD */ +#define WM8903_GP3_PD_MASK 0x0008 /* GP3_PD */ +#define WM8903_GP3_PD_SHIFT 3 /* GP3_PD */ +#define WM8903_GP3_PD_WIDTH 1 /* GP3_PD */ +#define WM8903_GP3_PU 0x0004 /* GP3_PU */ +#define WM8903_GP3_PU_MASK 0x0004 /* GP3_PU */ +#define WM8903_GP3_PU_SHIFT 2 /* GP3_PU */ +#define WM8903_GP3_PU_WIDTH 1 /* GP3_PU */ +#define WM8903_GP3_INTMODE 0x0002 /* GP3_INTMODE */ +#define WM8903_GP3_INTMODE_MASK 0x0002 /* GP3_INTMODE */ +#define WM8903_GP3_INTMODE_SHIFT 1 /* GP3_INTMODE */ +#define WM8903_GP3_INTMODE_WIDTH 1 /* GP3_INTMODE */ +#define WM8903_GP3_DB 0x0001 /* GP3_DB */ +#define WM8903_GP3_DB_MASK 0x0001 /* GP3_DB */ +#define WM8903_GP3_DB_SHIFT 0 /* GP3_DB */ +#define WM8903_GP3_DB_WIDTH 1 /* GP3_DB */ + +/* + * R119 (0x77) - GPIO Control 4 + */ +#define WM8903_GP4_FN_MASK 0x1F00 /* GP4_FN - [12:8] */ +#define WM8903_GP4_FN_SHIFT 8 /* GP4_FN - [12:8] */ +#define WM8903_GP4_FN_WIDTH 5 /* GP4_FN - [12:8] */ +#define WM8903_GP4_DIR 0x0080 /* GP4_DIR */ +#define WM8903_GP4_DIR_MASK 0x0080 /* GP4_DIR */ +#define WM8903_GP4_DIR_SHIFT 7 /* GP4_DIR */ +#define WM8903_GP4_DIR_WIDTH 1 /* GP4_DIR */ +#define WM8903_GP4_OP_CFG 0x0040 /* GP4_OP_CFG */ +#define WM8903_GP4_OP_CFG_MASK 0x0040 /* GP4_OP_CFG */ +#define WM8903_GP4_OP_CFG_SHIFT 6 /* GP4_OP_CFG */ +#define WM8903_GP4_OP_CFG_WIDTH 1 /* GP4_OP_CFG */ +#define WM8903_GP4_IP_CFG 0x0020 /* GP4_IP_CFG */ +#define WM8903_GP4_IP_CFG_MASK 0x0020 /* GP4_IP_CFG */ +#define WM8903_GP4_IP_CFG_SHIFT 5 /* GP4_IP_CFG */ +#define WM8903_GP4_IP_CFG_WIDTH 1 /* GP4_IP_CFG */ +#define WM8903_GP4_LVL 0x0010 /* GP4_LVL */ +#define WM8903_GP4_LVL_MASK 0x0010 /* GP4_LVL */ +#define WM8903_GP4_LVL_SHIFT 4 /* GP4_LVL */ +#define WM8903_GP4_LVL_WIDTH 1 /* GP4_LVL */ +#define WM8903_GP4_PD 0x0008 /* GP4_PD */ +#define WM8903_GP4_PD_MASK 0x0008 /* GP4_PD */ +#define WM8903_GP4_PD_SHIFT 3 /* GP4_PD */ +#define WM8903_GP4_PD_WIDTH 1 /* GP4_PD */ +#define WM8903_GP4_PU 0x0004 /* GP4_PU */ +#define WM8903_GP4_PU_MASK 0x0004 /* GP4_PU */ +#define WM8903_GP4_PU_SHIFT 2 /* GP4_PU */ +#define WM8903_GP4_PU_WIDTH 1 /* GP4_PU */ +#define WM8903_GP4_INTMODE 0x0002 /* GP4_INTMODE */ +#define WM8903_GP4_INTMODE_MASK 0x0002 /* GP4_INTMODE */ +#define WM8903_GP4_INTMODE_SHIFT 1 /* GP4_INTMODE */ +#define WM8903_GP4_INTMODE_WIDTH 1 /* GP4_INTMODE */ +#define WM8903_GP4_DB 0x0001 /* GP4_DB */ +#define WM8903_GP4_DB_MASK 0x0001 /* GP4_DB */ +#define WM8903_GP4_DB_SHIFT 0 /* GP4_DB */ +#define WM8903_GP4_DB_WIDTH 1 /* GP4_DB */ + +/* + * R120 (0x78) - GPIO Control 5 + */ +#define WM8903_GP5_FN_MASK 0x1F00 /* GP5_FN - [12:8] */ +#define WM8903_GP5_FN_SHIFT 8 /* GP5_FN - [12:8] */ +#define WM8903_GP5_FN_WIDTH 5 /* GP5_FN - [12:8] */ +#define WM8903_GP5_DIR 0x0080 /* GP5_DIR */ +#define WM8903_GP5_DIR_MASK 0x0080 /* GP5_DIR */ +#define WM8903_GP5_DIR_SHIFT 7 /* GP5_DIR */ +#define WM8903_GP5_DIR_WIDTH 1 /* GP5_DIR */ +#define WM8903_GP5_OP_CFG 0x0040 /* GP5_OP_CFG */ +#define WM8903_GP5_OP_CFG_MASK 0x0040 /* GP5_OP_CFG */ +#define WM8903_GP5_OP_CFG_SHIFT 6 /* GP5_OP_CFG */ +#define WM8903_GP5_OP_CFG_WIDTH 1 /* GP5_OP_CFG */ +#define WM8903_GP5_IP_CFG 0x0020 /* GP5_IP_CFG */ +#define WM8903_GP5_IP_CFG_MASK 0x0020 /* GP5_IP_CFG */ +#define WM8903_GP5_IP_CFG_SHIFT 5 /* GP5_IP_CFG */ +#define WM8903_GP5_IP_CFG_WIDTH 1 /* GP5_IP_CFG */ +#define WM8903_GP5_LVL 0x0010 /* GP5_LVL */ +#define WM8903_GP5_LVL_MASK 0x0010 /* GP5_LVL */ +#define WM8903_GP5_LVL_SHIFT 4 /* GP5_LVL */ +#define WM8903_GP5_LVL_WIDTH 1 /* GP5_LVL */ +#define WM8903_GP5_PD 0x0008 /* GP5_PD */ +#define WM8903_GP5_PD_MASK 0x0008 /* GP5_PD */ +#define WM8903_GP5_PD_SHIFT 3 /* GP5_PD */ +#define WM8903_GP5_PD_WIDTH 1 /* GP5_PD */ +#define WM8903_GP5_PU 0x0004 /* GP5_PU */ +#define WM8903_GP5_PU_MASK 0x0004 /* GP5_PU */ +#define WM8903_GP5_PU_SHIFT 2 /* GP5_PU */ +#define WM8903_GP5_PU_WIDTH 1 /* GP5_PU */ +#define WM8903_GP5_INTMODE 0x0002 /* GP5_INTMODE */ +#define WM8903_GP5_INTMODE_MASK 0x0002 /* GP5_INTMODE */ +#define WM8903_GP5_INTMODE_SHIFT 1 /* GP5_INTMODE */ +#define WM8903_GP5_INTMODE_WIDTH 1 /* GP5_INTMODE */ +#define WM8903_GP5_DB 0x0001 /* GP5_DB */ +#define WM8903_GP5_DB_MASK 0x0001 /* GP5_DB */ +#define WM8903_GP5_DB_SHIFT 0 /* GP5_DB */ +#define WM8903_GP5_DB_WIDTH 1 /* GP5_DB */ + +struct wm8903_platform_data { + bool irq_active_low; /* Set if IRQ active low, default high */ + + /* Default register value for R6 (Mic bias), used to configure + * microphone detection. In conjunction with gpio_cfg this + * can be used to route the microphone status signals out onto + * the GPIOs for use with snd_soc_jack_add_gpios(). + */ + u16 micdet_cfg; + + int micdet_delay; /* Delay after microphone detection (ms) */ + + u32 gpio_cfg[5]; /* Default register values for GPIO pin mux */ +}; + +#endif diff --git a/include/sound/wm8904.h b/include/sound/wm8904.h index d66575a..898be3a 100644 --- a/include/sound/wm8904.h +++ b/include/sound/wm8904.h @@ -15,8 +15,111 @@ #ifndef __MFD_WM8994_PDATA_H__ #define __MFD_WM8994_PDATA_H__ -#define WM8904_DRC_REGS 4 -#define WM8904_EQ_REGS 25 +/* Used to enable configuration of a GPIO to all zeros */ +#define WM8904_GPIO_NO_CONFIG 0x8000 + +/* + * R6 (0x06) - Mic Bias Control 0 + */ +#define WM8904_MICDET_THR_MASK 0x0070 /* MICDET_THR - [6:4] */ +#define WM8904_MICDET_THR_SHIFT 4 /* MICDET_THR - [6:4] */ +#define WM8904_MICDET_THR_WIDTH 3 /* MICDET_THR - [6:4] */ +#define WM8904_MICSHORT_THR_MASK 0x000C /* MICSHORT_THR - [3:2] */ +#define WM8904_MICSHORT_THR_SHIFT 2 /* MICSHORT_THR - [3:2] */ +#define WM8904_MICSHORT_THR_WIDTH 2 /* MICSHORT_THR - [3:2] */ +#define WM8904_MICDET_ENA 0x0002 /* MICDET_ENA */ +#define WM8904_MICDET_ENA_MASK 0x0002 /* MICDET_ENA */ +#define WM8904_MICDET_ENA_SHIFT 1 /* MICDET_ENA */ +#define WM8904_MICDET_ENA_WIDTH 1 /* MICDET_ENA */ +#define WM8904_MICBIAS_ENA 0x0001 /* MICBIAS_ENA */ +#define WM8904_MICBIAS_ENA_MASK 0x0001 /* MICBIAS_ENA */ +#define WM8904_MICBIAS_ENA_SHIFT 0 /* MICBIAS_ENA */ +#define WM8904_MICBIAS_ENA_WIDTH 1 /* MICBIAS_ENA */ + +/* + * R7 (0x07) - Mic Bias Control 1 + */ +#define WM8904_MIC_DET_FILTER_ENA 0x8000 /* MIC_DET_FILTER_ENA */ +#define WM8904_MIC_DET_FILTER_ENA_MASK 0x8000 /* MIC_DET_FILTER_ENA */ +#define WM8904_MIC_DET_FILTER_ENA_SHIFT 15 /* MIC_DET_FILTER_ENA */ +#define WM8904_MIC_DET_FILTER_ENA_WIDTH 1 /* MIC_DET_FILTER_ENA */ +#define WM8904_MIC_SHORT_FILTER_ENA 0x4000 /* MIC_SHORT_FILTER_ENA */ +#define WM8904_MIC_SHORT_FILTER_ENA_MASK 0x4000 /* MIC_SHORT_FILTER_ENA */ +#define WM8904_MIC_SHORT_FILTER_ENA_SHIFT 14 /* MIC_SHORT_FILTER_ENA */ +#define WM8904_MIC_SHORT_FILTER_ENA_WIDTH 1 /* MIC_SHORT_FILTER_ENA */ +#define WM8904_MICBIAS_SEL_MASK 0x0007 /* MICBIAS_SEL - [2:0] */ +#define WM8904_MICBIAS_SEL_SHIFT 0 /* MICBIAS_SEL - [2:0] */ +#define WM8904_MICBIAS_SEL_WIDTH 3 /* MICBIAS_SEL - [2:0] */ + + +/* + * R121 (0x79) - GPIO Control 1 + */ +#define WM8904_GPIO1_PU 0x0020 /* GPIO1_PU */ +#define WM8904_GPIO1_PU_MASK 0x0020 /* GPIO1_PU */ +#define WM8904_GPIO1_PU_SHIFT 5 /* GPIO1_PU */ +#define WM8904_GPIO1_PU_WIDTH 1 /* GPIO1_PU */ +#define WM8904_GPIO1_PD 0x0010 /* GPIO1_PD */ +#define WM8904_GPIO1_PD_MASK 0x0010 /* GPIO1_PD */ +#define WM8904_GPIO1_PD_SHIFT 4 /* GPIO1_PD */ +#define WM8904_GPIO1_PD_WIDTH 1 /* GPIO1_PD */ +#define WM8904_GPIO1_SEL_MASK 0x000F /* GPIO1_SEL - [3:0] */ +#define WM8904_GPIO1_SEL_SHIFT 0 /* GPIO1_SEL - [3:0] */ +#define WM8904_GPIO1_SEL_WIDTH 4 /* GPIO1_SEL - [3:0] */ + +/* + * R122 (0x7A) - GPIO Control 2 + */ +#define WM8904_GPIO2_PU 0x0020 /* GPIO2_PU */ +#define WM8904_GPIO2_PU_MASK 0x0020 /* GPIO2_PU */ +#define WM8904_GPIO2_PU_SHIFT 5 /* GPIO2_PU */ +#define WM8904_GPIO2_PU_WIDTH 1 /* GPIO2_PU */ +#define WM8904_GPIO2_PD 0x0010 /* GPIO2_PD */ +#define WM8904_GPIO2_PD_MASK 0x0010 /* GPIO2_PD */ +#define WM8904_GPIO2_PD_SHIFT 4 /* GPIO2_PD */ +#define WM8904_GPIO2_PD_WIDTH 1 /* GPIO2_PD */ +#define WM8904_GPIO2_SEL_MASK 0x000F /* GPIO2_SEL - [3:0] */ +#define WM8904_GPIO2_SEL_SHIFT 0 /* GPIO2_SEL - [3:0] */ +#define WM8904_GPIO2_SEL_WIDTH 4 /* GPIO2_SEL - [3:0] */ + +/* + * R123 (0x7B) - GPIO Control 3 + */ +#define WM8904_GPIO3_PU 0x0020 /* GPIO3_PU */ +#define WM8904_GPIO3_PU_MASK 0x0020 /* GPIO3_PU */ +#define WM8904_GPIO3_PU_SHIFT 5 /* GPIO3_PU */ +#define WM8904_GPIO3_PU_WIDTH 1 /* GPIO3_PU */ +#define WM8904_GPIO3_PD 0x0010 /* GPIO3_PD */ +#define WM8904_GPIO3_PD_MASK 0x0010 /* GPIO3_PD */ +#define WM8904_GPIO3_PD_SHIFT 4 /* GPIO3_PD */ +#define WM8904_GPIO3_PD_WIDTH 1 /* GPIO3_PD */ +#define WM8904_GPIO3_SEL_MASK 0x000F /* GPIO3_SEL - [3:0] */ +#define WM8904_GPIO3_SEL_SHIFT 0 /* GPIO3_SEL - [3:0] */ +#define WM8904_GPIO3_SEL_WIDTH 4 /* GPIO3_SEL - [3:0] */ + +/* + * R124 (0x7C) - GPIO Control 4 + */ +#define WM8904_GPI7_ENA 0x0200 /* GPI7_ENA */ +#define WM8904_GPI7_ENA_MASK 0x0200 /* GPI7_ENA */ +#define WM8904_GPI7_ENA_SHIFT 9 /* GPI7_ENA */ +#define WM8904_GPI7_ENA_WIDTH 1 /* GPI7_ENA */ +#define WM8904_GPI8_ENA 0x0100 /* GPI8_ENA */ +#define WM8904_GPI8_ENA_MASK 0x0100 /* GPI8_ENA */ +#define WM8904_GPI8_ENA_SHIFT 8 /* GPI8_ENA */ +#define WM8904_GPI8_ENA_WIDTH 1 /* GPI8_ENA */ +#define WM8904_GPIO_BCLK_MODE_ENA 0x0080 /* GPIO_BCLK_MODE_ENA */ +#define WM8904_GPIO_BCLK_MODE_ENA_MASK 0x0080 /* GPIO_BCLK_MODE_ENA */ +#define WM8904_GPIO_BCLK_MODE_ENA_SHIFT 7 /* GPIO_BCLK_MODE_ENA */ +#define WM8904_GPIO_BCLK_MODE_ENA_WIDTH 1 /* GPIO_BCLK_MODE_ENA */ +#define WM8904_GPIO_BCLK_SEL_MASK 0x000F /* GPIO_BCLK_SEL - [3:0] */ +#define WM8904_GPIO_BCLK_SEL_SHIFT 0 /* GPIO_BCLK_SEL - [3:0] */ +#define WM8904_GPIO_BCLK_SEL_WIDTH 4 /* GPIO_BCLK_SEL - [3:0] */ + +#define WM8904_MIC_REGS 2 +#define WM8904_GPIO_REGS 4 +#define WM8904_DRC_REGS 4 +#define WM8904_EQ_REGS 25 /** * DRC configurations are specified with a label and a set of register @@ -52,6 +155,9 @@ struct wm8904_pdata { int num_retune_mobile_cfgs; struct wm8904_retune_mobile_cfg *retune_mobile_cfgs; + + u32 gpio_cfg[WM8904_GPIO_REGS]; + u32 mic_cfg[WM8904_MIC_REGS]; }; #endif diff --git a/include/sound/wm8960.h b/include/sound/wm8960.h new file mode 100644 index 0000000..74e9a95 --- /dev/null +++ b/include/sound/wm8960.h @@ -0,0 +1,24 @@ +/* + * wm8960.h -- WM8960 Soc Audio driver platform data + * + * 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 _WM8960_PDATA_H +#define _WM8960_PDATA_H + +#define WM8960_DRES_400R 0 +#define WM8960_DRES_200R 1 +#define WM8960_DRES_600R 2 +#define WM8960_DRES_150R 3 +#define WM8960_DRES_MAX 3 + +struct wm8960_data { + bool capless; /* Headphone outputs configured in capless mode */ + + int dres; /* Discharge resistance for headphone outputs */ +}; + +#endif diff --git a/include/sound/wm9090.h b/include/sound/wm9090.h new file mode 100644 index 0000000..3718928 --- /dev/null +++ b/include/sound/wm9090.h @@ -0,0 +1,28 @@ +/* + * linux/sound/wm9090.h -- Platform data for WM9090 + * + * Copyright 2009, 2010 Wolfson Microelectronics. PLC. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __LINUX_SND_WM9090_H +#define __LINUX_SND_WM9090_H + +struct wm9090_platform_data { + /* Line inputs 1 & 2 can optionally be differential */ + unsigned int lin1_diff:1; + unsigned int lin2_diff:1; + + /* AGC configuration. This is intended to protect the speaker + * against overdriving and will therefore depend on the + * hardware setup with incorrect runtime configuration + * potentially causing hardware damage. + */ + unsigned int agc_ena:1; + u16 agc[3]; +}; + +#endif diff --git a/sound/soc/atmel/atmel-pcm.c b/sound/soc/atmel/atmel-pcm.c index 3e6628c8..f6b3cc0 100644 --- a/sound/soc/atmel/atmel-pcm.c +++ b/sound/soc/atmel/atmel-pcm.c @@ -415,9 +415,12 @@ static void atmel_pcm_free_dma_buffers(struct snd_pcm *pcm) } #ifdef CONFIG_PM -static int atmel_pcm_suspend(struct snd_soc_dai *dai) +static int atmel_pcm_suspend(struct snd_soc_dai_link *dai_link) { - struct snd_pcm_runtime *runtime = dai->runtime; + struct snd_pcm *pcm = dai_link->pcm; + struct snd_pcm_str *stream = &pcm->streams[0]; + struct snd_pcm_substream *substream = stream->substream; + struct snd_pcm_runtime *runtime = substream->runtime; struct atmel_runtime_data *prtd; struct atmel_pcm_dma_params *params; @@ -439,9 +442,12 @@ static int atmel_pcm_suspend(struct snd_soc_dai *dai) return 0; } -static int atmel_pcm_resume(struct snd_soc_dai *dai) +static int atmel_pcm_resume(struct snd_soc_dai_link *dai_link) { - struct snd_pcm_runtime *runtime = dai->runtime; + struct snd_pcm *pcm = dai_link->pcm; + struct snd_pcm_str *stream = &pcm->streams[0]; + struct snd_pcm_substream *substream = stream->substream; + struct snd_pcm_runtime *runtime = substream->runtime; struct atmel_runtime_data *prtd; struct atmel_pcm_dma_params *params; diff --git a/sound/soc/blackfin/Kconfig b/sound/soc/blackfin/Kconfig index 97f1a25..8ef2502 100644 --- a/sound/soc/blackfin/Kconfig +++ b/sound/soc/blackfin/Kconfig @@ -49,13 +49,14 @@ config SND_BF5XX_SOC_AD1836 help Say Y if you want to add support for SoC audio on BF5xx STAMP/EZKIT. -config SND_BF5XX_SOC_AD1938 - tristate "SoC AD1938 Audio support for Blackfin" +config SND_BF5XX_SOC_AD193X + tristate "SoC AD193X Audio support for Blackfin" depends on SND_BF5XX_TDM select SND_BF5XX_SOC_TDM - select SND_SOC_AD1938 + select SND_SOC_AD193X help - Say Y if you want to add support for AD1938 codec on Blackfin. + Say Y if you want to add support for AD193X codec on Blackfin. + This driver supports AD1936, AD1937, AD1938 and AD1939. config SND_BF5XX_AC97 tristate "SoC AC97 Audio for the ADI BF5xx chip" diff --git a/sound/soc/blackfin/Makefile b/sound/soc/blackfin/Makefile index 87e3042..49af3f3 100644 --- a/sound/soc/blackfin/Makefile +++ b/sound/soc/blackfin/Makefile @@ -20,10 +20,10 @@ snd-ad1836-objs := bf5xx-ad1836.o snd-ad1980-objs := bf5xx-ad1980.o snd-ssm2602-objs := bf5xx-ssm2602.o snd-ad73311-objs := bf5xx-ad73311.o -snd-ad1938-objs := bf5xx-ad1938.o +snd-ad193x-objs := bf5xx-ad193x.o obj-$(CONFIG_SND_BF5XX_SOC_AD1836) += snd-ad1836.o obj-$(CONFIG_SND_BF5XX_SOC_AD1980) += snd-ad1980.o obj-$(CONFIG_SND_BF5XX_SOC_SSM2602) += snd-ssm2602.o obj-$(CONFIG_SND_BF5XX_SOC_AD73311) += snd-ad73311.o -obj-$(CONFIG_SND_BF5XX_SOC_AD1938) += snd-ad1938.o +obj-$(CONFIG_SND_BF5XX_SOC_AD193X) += snd-ad193x.o diff --git a/sound/soc/blackfin/bf5xx-ad1938.c b/sound/soc/blackfin/bf5xx-ad193x.c index 2ef1e50..b8c9060 100644 --- a/sound/soc/blackfin/bf5xx-ad1938.c +++ b/sound/soc/blackfin/bf5xx-ad193x.c @@ -1,9 +1,9 @@ /* - * File: sound/soc/blackfin/bf5xx-ad1938.c + * File: sound/soc/blackfin/bf5xx-ad193x.c * Author: Barry Song <Barry.Song@analog.com> * * Created: Thur June 4 2009 - * Description: Board driver for ad1938 sound chip + * Description: Board driver for ad193x sound chip * * Bugs: Enter bugs at http://blackfin.uclinux.org/ * @@ -38,15 +38,15 @@ #include <asm/dma.h> #include <asm/portmux.h> -#include "../codecs/ad1938.h" +#include "../codecs/ad193x.h" #include "bf5xx-sport.h" #include "bf5xx-tdm-pcm.h" #include "bf5xx-tdm.h" -static struct snd_soc_card bf5xx_ad1938; +static struct snd_soc_card bf5xx_ad193x; -static int bf5xx_ad1938_startup(struct snd_pcm_substream *substream) +static int bf5xx_ad193x_startup(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; @@ -55,7 +55,7 @@ static int bf5xx_ad1938_startup(struct snd_pcm_substream *substream) return 0; } -static int bf5xx_ad1938_hw_params(struct snd_pcm_substream *substream, +static int bf5xx_ad193x_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { struct snd_soc_pcm_runtime *rtd = substream->private_data; @@ -89,61 +89,61 @@ static int bf5xx_ad1938_hw_params(struct snd_pcm_substream *substream, return 0; } -static struct snd_soc_ops bf5xx_ad1938_ops = { - .startup = bf5xx_ad1938_startup, - .hw_params = bf5xx_ad1938_hw_params, +static struct snd_soc_ops bf5xx_ad193x_ops = { + .startup = bf5xx_ad193x_startup, + .hw_params = bf5xx_ad193x_hw_params, }; -static struct snd_soc_dai_link bf5xx_ad1938_dai = { - .name = "ad1938", - .stream_name = "AD1938", +static struct snd_soc_dai_link bf5xx_ad193x_dai = { + .name = "ad193x", + .stream_name = "AD193X", .cpu_dai = &bf5xx_tdm_dai, - .codec_dai = &ad1938_dai, - .ops = &bf5xx_ad1938_ops, + .codec_dai = &ad193x_dai, + .ops = &bf5xx_ad193x_ops, }; -static struct snd_soc_card bf5xx_ad1938 = { - .name = "bf5xx_ad1938", +static struct snd_soc_card bf5xx_ad193x = { + .name = "bf5xx_ad193x", .platform = &bf5xx_tdm_soc_platform, - .dai_link = &bf5xx_ad1938_dai, + .dai_link = &bf5xx_ad193x_dai, .num_links = 1, }; -static struct snd_soc_device bf5xx_ad1938_snd_devdata = { - .card = &bf5xx_ad1938, - .codec_dev = &soc_codec_dev_ad1938, +static struct snd_soc_device bf5xx_ad193x_snd_devdata = { + .card = &bf5xx_ad193x, + .codec_dev = &soc_codec_dev_ad193x, }; -static struct platform_device *bfxx_ad1938_snd_device; +static struct platform_device *bfxx_ad193x_snd_device; -static int __init bf5xx_ad1938_init(void) +static int __init bf5xx_ad193x_init(void) { int ret; - bfxx_ad1938_snd_device = platform_device_alloc("soc-audio", -1); - if (!bfxx_ad1938_snd_device) + bfxx_ad193x_snd_device = platform_device_alloc("soc-audio", -1); + if (!bfxx_ad193x_snd_device) return -ENOMEM; - platform_set_drvdata(bfxx_ad1938_snd_device, &bf5xx_ad1938_snd_devdata); - bf5xx_ad1938_snd_devdata.dev = &bfxx_ad1938_snd_device->dev; - ret = platform_device_add(bfxx_ad1938_snd_device); + platform_set_drvdata(bfxx_ad193x_snd_device, &bf5xx_ad193x_snd_devdata); + bf5xx_ad193x_snd_devdata.dev = &bfxx_ad193x_snd_device->dev; + ret = platform_device_add(bfxx_ad193x_snd_device); if (ret) - platform_device_put(bfxx_ad1938_snd_device); + platform_device_put(bfxx_ad193x_snd_device); return ret; } -static void __exit bf5xx_ad1938_exit(void) +static void __exit bf5xx_ad193x_exit(void) { - platform_device_unregister(bfxx_ad1938_snd_device); + platform_device_unregister(bfxx_ad193x_snd_device); } -module_init(bf5xx_ad1938_init); -module_exit(bf5xx_ad1938_exit); +module_init(bf5xx_ad193x_init); +module_exit(bf5xx_ad193x_exit); /* Module information */ MODULE_AUTHOR("Barry Song"); -MODULE_DESCRIPTION("ALSA SoC AD1938 board driver"); +MODULE_DESCRIPTION("ALSA SoC AD193X board driver"); MODULE_LICENSE("GPL"); diff --git a/sound/soc/blackfin/bf5xx-sport.h b/sound/soc/blackfin/bf5xx-sport.h index 2e63dea..a86e8cc 100644 --- a/sound/soc/blackfin/bf5xx-sport.h +++ b/sound/soc/blackfin/bf5xx-sport.h @@ -34,33 +34,7 @@ #include <linux/wait.h> #include <linux/workqueue.h> #include <asm/dma.h> - -struct sport_register { - u16 tcr1; u16 reserved0; - u16 tcr2; u16 reserved1; - u16 tclkdiv; u16 reserved2; - u16 tfsdiv; u16 reserved3; - u32 tx; - u32 reserved_l0; - u32 rx; - u32 reserved_l1; - u16 rcr1; u16 reserved4; - u16 rcr2; u16 reserved5; - u16 rclkdiv; u16 reserved6; - u16 rfsdiv; u16 reserved7; - u16 stat; u16 reserved8; - u16 chnl; u16 reserved9; - u16 mcmc1; u16 reserved10; - u16 mcmc2; u16 reserved11; - u32 mtcs0; - u32 mtcs1; - u32 mtcs2; - u32 mtcs3; - u32 mrcs0; - u32 mrcs1; - u32 mrcs2; - u32 mrcs3; -}; +#include <asm/bfin_sport.h> #define DESC_ELEMENT_COUNT 9 diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 1743d56..31ac553 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -13,7 +13,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_L3 select SND_SOC_AC97_CODEC if SND_SOC_AC97_BUS select SND_SOC_AD1836 if SPI_MASTER - select SND_SOC_AD1938 if SPI_MASTER + select SND_SOC_AD193X if SND_SOC_I2C_AND_SPI select SND_SOC_AD1980 if SND_SOC_AC97_BUS select SND_SOC_ADS117X select SND_SOC_AD73311 if I2C @@ -21,6 +21,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_AK4535 if I2C select SND_SOC_AK4642 if I2C select SND_SOC_AK4671 if I2C + select SND_SOC_CQ0093VC if MFD_DAVINCI_VOICECODEC select SND_SOC_CS4270 if I2C select SND_SOC_MAX9877 if I2C select SND_SOC_DA7210 if I2C @@ -34,6 +35,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_TPA6130A2 if I2C select SND_SOC_TLV320DAC33 if I2C select SND_SOC_TWL4030 if TWL4030_CORE + select SND_SOC_TWL6040 if TWL4030_CORE select SND_SOC_UDA134X select SND_SOC_UDA1380 if I2C select SND_SOC_WM2000 if I2C @@ -64,6 +66,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_WM8993 if I2C select SND_SOC_WM8994 if MFD_WM8994 select SND_SOC_WM9081 if I2C + select SND_SOC_WM9090 if I2C select SND_SOC_WM9705 if SND_SOC_AC97_BUS select SND_SOC_WM9712 if SND_SOC_AC97_BUS select SND_SOC_WM9713 if SND_SOC_AC97_BUS @@ -90,7 +93,7 @@ config SND_SOC_AC97_CODEC config SND_SOC_AD1836 tristate -config SND_SOC_AD1938 +config SND_SOC_AD193X tristate config SND_SOC_AD1980 @@ -114,6 +117,9 @@ config SND_SOC_AK4642 config SND_SOC_AK4671 tristate +config SND_SOC_CQ0093VC + tristate + # Cirrus Logic CS4270 Codec config SND_SOC_CS4270 tristate @@ -164,6 +170,9 @@ config SND_SOC_TWL4030 select TWL4030_CODEC tristate +config SND_SOC_TWL6040 + tristate + config SND_SOC_UDA134X tristate @@ -269,3 +278,6 @@ config SND_SOC_TPA6130A2 config SND_SOC_WM2000 tristate + +config SND_SOC_WM9090 + tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index dd5ce6d..91429ea 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -1,6 +1,6 @@ snd-soc-ac97-objs := ac97.o snd-soc-ad1836-objs := ad1836.o -snd-soc-ad1938-objs := ad1938.o +snd-soc-ad193x-objs := ad193x.o snd-soc-ad1980-objs := ad1980.o snd-soc-ad73311-objs := ad73311.o snd-soc-ads117x-objs := ads117x.o @@ -8,6 +8,7 @@ snd-soc-ak4104-objs := ak4104.o 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-cs4270-objs := cs4270.o snd-soc-cx20442-objs := cx20442.o snd-soc-da7210-objs := da7210.o @@ -21,6 +22,7 @@ snd-soc-tlv320aic26-objs := tlv320aic26.o snd-soc-tlv320aic3x-objs := tlv320aic3x.o snd-soc-tlv320dac33-objs := tlv320dac33.o snd-soc-twl4030-objs := twl4030.o +snd-soc-twl6040-objs := twl6040.o snd-soc-uda134x-objs := uda134x.o snd-soc-uda1380-objs := uda1380.o snd-soc-wm8350-objs := wm8350.o @@ -59,10 +61,11 @@ snd-soc-wm-hubs-objs := wm_hubs.o snd-soc-max9877-objs := max9877.o snd-soc-tpa6130a2-objs := tpa6130a2.o snd-soc-wm2000-objs := wm2000.o +snd-soc-wm9090-objs := wm9090.o obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o obj-$(CONFIG_SND_SOC_AD1836) += snd-soc-ad1836.o -obj-$(CONFIG_SND_SOC_AD1938) += snd-soc-ad1938.o +obj-$(CONFIG_SND_SOC_AD193X) += snd-soc-ad193x.o obj-$(CONFIG_SND_SOC_AD1980) += snd-soc-ad1980.o obj-$(CONFIG_SND_SOC_AD73311) += snd-soc-ad73311.o obj-$(CONFIG_SND_SOC_ADS117X) += snd-soc-ads117x.o @@ -70,6 +73,7 @@ obj-$(CONFIG_SND_SOC_AK4104) += snd-soc-ak4104.o obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o obj-$(CONFIG_SND_SOC_AK4642) += snd-soc-ak4642.o obj-$(CONFIG_SND_SOC_AK4671) += snd-soc-ak4671.o +obj-$(CONFIG_SND_SOC_CQ0093VC) += snd-soc-cq93vc.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 @@ -83,6 +87,7 @@ obj-$(CONFIG_SND_SOC_TLV320AIC26) += snd-soc-tlv320aic26.o obj-$(CONFIG_SND_SOC_TLV320AIC3X) += snd-soc-tlv320aic3x.o obj-$(CONFIG_SND_SOC_TLV320DAC33) += snd-soc-tlv320dac33.o obj-$(CONFIG_SND_SOC_TWL4030) += snd-soc-twl4030.o +obj-$(CONFIG_SND_SOC_TWL6040) += snd-soc-twl6040.o obj-$(CONFIG_SND_SOC_UDA134X) += snd-soc-uda134x.o obj-$(CONFIG_SND_SOC_UDA1380) += snd-soc-uda1380.o obj-$(CONFIG_SND_SOC_WM8350) += snd-soc-wm8350.o @@ -121,3 +126,4 @@ obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o obj-$(CONFIG_SND_SOC_MAX9877) += snd-soc-max9877.o obj-$(CONFIG_SND_SOC_TPA6130A2) += snd-soc-tpa6130a2.o obj-$(CONFIG_SND_SOC_WM2000) += snd-soc-wm2000.o +obj-$(CONFIG_SND_SOC_WM9090) += snd-soc-wm9090.o diff --git a/sound/soc/codecs/ad1836.c b/sound/soc/codecs/ad1836.c index 11b62de..2175384 100644 --- a/sound/soc/codecs/ad1836.c +++ b/sound/soc/codecs/ad1836.c @@ -278,7 +278,7 @@ static int ad1836_register(struct ad1836_priv *ad1836) mutex_init(&codec->mutex); INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); - codec->private_data = ad1836; + snd_soc_codec_set_drvdata(codec, ad1836); codec->reg_cache = ad1836->reg_cache; codec->reg_cache_size = AD1836_NUM_REGS; codec->name = "AD1836"; diff --git a/sound/soc/codecs/ad1938.c b/sound/soc/codecs/ad1938.c deleted file mode 100644 index 240cd15..0000000 --- a/sound/soc/codecs/ad1938.c +++ /dev/null @@ -1,522 +0,0 @@ -/* - * File: sound/soc/codecs/ad1938.c - * Author: Barry Song <Barry.Song@analog.com> - * - * Created: June 04 2009 - * Description: Driver for AD1938 sound chip - * - * Modified: - * Copyright 2009 Analog Devices Inc. - * - * Bugs: Enter bugs at http://blackfin.uclinux.org/ - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, see the file COPYING, or write - * to the Free Software Foundation, Inc., - * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#include <linux/init.h> -#include <linux/slab.h> -#include <linux/module.h> -#include <linux/kernel.h> -#include <linux/device.h> -#include <sound/core.h> -#include <sound/pcm.h> -#include <sound/pcm_params.h> -#include <sound/initval.h> -#include <sound/soc.h> -#include <sound/tlv.h> -#include <sound/soc-dapm.h> -#include <linux/spi/spi.h> -#include "ad1938.h" - -/* codec private data */ -struct ad1938_priv { - struct snd_soc_codec codec; - u8 reg_cache[AD1938_NUM_REGS]; -}; - -/* ad1938 register cache & default register settings */ -static const u8 ad1938_reg[AD1938_NUM_REGS] = { - 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0, -}; - -static struct snd_soc_codec *ad1938_codec; -struct snd_soc_codec_device soc_codec_dev_ad1938; -static int ad1938_register(struct ad1938_priv *ad1938); -static void ad1938_unregister(struct ad1938_priv *ad1938); - -/* - * AD1938 volume/mute/de-emphasis etc. controls - */ -static const char *ad1938_deemp[] = {"None", "48kHz", "44.1kHz", "32kHz"}; - -static const struct soc_enum ad1938_deemp_enum = - SOC_ENUM_SINGLE(AD1938_DAC_CTRL2, 1, 4, ad1938_deemp); - -static const struct snd_kcontrol_new ad1938_snd_controls[] = { - /* DAC volume control */ - SOC_DOUBLE_R("DAC1 Volume", AD1938_DAC_L1_VOL, - AD1938_DAC_R1_VOL, 0, 0xFF, 1), - SOC_DOUBLE_R("DAC2 Volume", AD1938_DAC_L2_VOL, - AD1938_DAC_R2_VOL, 0, 0xFF, 1), - SOC_DOUBLE_R("DAC3 Volume", AD1938_DAC_L3_VOL, - AD1938_DAC_R3_VOL, 0, 0xFF, 1), - SOC_DOUBLE_R("DAC4 Volume", AD1938_DAC_L4_VOL, - AD1938_DAC_R4_VOL, 0, 0xFF, 1), - - /* ADC switch control */ - SOC_DOUBLE("ADC1 Switch", AD1938_ADC_CTRL0, AD1938_ADCL1_MUTE, - AD1938_ADCR1_MUTE, 1, 1), - SOC_DOUBLE("ADC2 Switch", AD1938_ADC_CTRL0, AD1938_ADCL2_MUTE, - AD1938_ADCR2_MUTE, 1, 1), - - /* DAC switch control */ - SOC_DOUBLE("DAC1 Switch", AD1938_DAC_CHNL_MUTE, AD1938_DACL1_MUTE, - AD1938_DACR1_MUTE, 1, 1), - SOC_DOUBLE("DAC2 Switch", AD1938_DAC_CHNL_MUTE, AD1938_DACL2_MUTE, - AD1938_DACR2_MUTE, 1, 1), - SOC_DOUBLE("DAC3 Switch", AD1938_DAC_CHNL_MUTE, AD1938_DACL3_MUTE, - AD1938_DACR3_MUTE, 1, 1), - SOC_DOUBLE("DAC4 Switch", AD1938_DAC_CHNL_MUTE, AD1938_DACL4_MUTE, - AD1938_DACR4_MUTE, 1, 1), - - /* ADC high-pass filter */ - SOC_SINGLE("ADC High Pass Filter Switch", AD1938_ADC_CTRL0, - AD1938_ADC_HIGHPASS_FILTER, 1, 0), - - /* DAC de-emphasis */ - SOC_ENUM("Playback Deemphasis", ad1938_deemp_enum), -}; - -static const struct snd_soc_dapm_widget ad1938_dapm_widgets[] = { - SND_SOC_DAPM_DAC("DAC", "Playback", AD1938_DAC_CTRL0, 0, 1), - SND_SOC_DAPM_ADC("ADC", "Capture", SND_SOC_NOPM, 0, 0), - SND_SOC_DAPM_SUPPLY("PLL_PWR", AD1938_PLL_CLK_CTRL0, 0, 1, NULL, 0), - SND_SOC_DAPM_SUPPLY("ADC_PWR", AD1938_ADC_CTRL0, 0, 1, NULL, 0), - SND_SOC_DAPM_OUTPUT("DAC1OUT"), - SND_SOC_DAPM_OUTPUT("DAC2OUT"), - SND_SOC_DAPM_OUTPUT("DAC3OUT"), - SND_SOC_DAPM_OUTPUT("DAC4OUT"), - SND_SOC_DAPM_INPUT("ADC1IN"), - SND_SOC_DAPM_INPUT("ADC2IN"), -}; - -static const struct snd_soc_dapm_route audio_paths[] = { - { "DAC", NULL, "PLL_PWR" }, - { "ADC", NULL, "PLL_PWR" }, - { "DAC", NULL, "ADC_PWR" }, - { "ADC", NULL, "ADC_PWR" }, - { "DAC1OUT", "DAC1 Switch", "DAC" }, - { "DAC2OUT", "DAC2 Switch", "DAC" }, - { "DAC3OUT", "DAC3 Switch", "DAC" }, - { "DAC4OUT", "DAC4 Switch", "DAC" }, - { "ADC", "ADC1 Switch", "ADC1IN" }, - { "ADC", "ADC2 Switch", "ADC2IN" }, -}; - -/* - * DAI ops entries - */ - -static int ad1938_mute(struct snd_soc_dai *dai, int mute) -{ - struct snd_soc_codec *codec = dai->codec; - int reg; - - reg = snd_soc_read(codec, AD1938_DAC_CTRL2); - reg = (mute > 0) ? reg | AD1938_DAC_MASTER_MUTE : reg & - (~AD1938_DAC_MASTER_MUTE); - snd_soc_write(codec, AD1938_DAC_CTRL2, reg); - - return 0; -} - -static int ad1938_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, - unsigned int rx_mask, int slots, int width) -{ - struct snd_soc_codec *codec = dai->codec; - int dac_reg = snd_soc_read(codec, AD1938_DAC_CTRL1); - int adc_reg = snd_soc_read(codec, AD1938_ADC_CTRL2); - - dac_reg &= ~AD1938_DAC_CHAN_MASK; - adc_reg &= ~AD1938_ADC_CHAN_MASK; - - switch (slots) { - case 2: - dac_reg |= AD1938_DAC_2_CHANNELS << AD1938_DAC_CHAN_SHFT; - adc_reg |= AD1938_ADC_2_CHANNELS << AD1938_ADC_CHAN_SHFT; - break; - case 4: - dac_reg |= AD1938_DAC_4_CHANNELS << AD1938_DAC_CHAN_SHFT; - adc_reg |= AD1938_ADC_4_CHANNELS << AD1938_ADC_CHAN_SHFT; - break; - case 8: - dac_reg |= AD1938_DAC_8_CHANNELS << AD1938_DAC_CHAN_SHFT; - adc_reg |= AD1938_ADC_8_CHANNELS << AD1938_ADC_CHAN_SHFT; - break; - case 16: - dac_reg |= AD1938_DAC_16_CHANNELS << AD1938_DAC_CHAN_SHFT; - adc_reg |= AD1938_ADC_16_CHANNELS << AD1938_ADC_CHAN_SHFT; - break; - default: - return -EINVAL; - } - - snd_soc_write(codec, AD1938_DAC_CTRL1, dac_reg); - snd_soc_write(codec, AD1938_ADC_CTRL2, adc_reg); - - return 0; -} - -static int ad1938_set_dai_fmt(struct snd_soc_dai *codec_dai, - unsigned int fmt) -{ - struct snd_soc_codec *codec = codec_dai->codec; - int adc_reg, dac_reg; - - adc_reg = snd_soc_read(codec, AD1938_ADC_CTRL2); - dac_reg = snd_soc_read(codec, AD1938_DAC_CTRL1); - - /* At present, the driver only support AUX ADC mode(SND_SOC_DAIFMT_I2S - * with TDM) and ADC&DAC TDM mode(SND_SOC_DAIFMT_DSP_A) - */ - switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { - case SND_SOC_DAIFMT_I2S: - adc_reg &= ~AD1938_ADC_SERFMT_MASK; - adc_reg |= AD1938_ADC_SERFMT_TDM; - break; - case SND_SOC_DAIFMT_DSP_A: - adc_reg &= ~AD1938_ADC_SERFMT_MASK; - adc_reg |= AD1938_ADC_SERFMT_AUX; - break; - default: - return -EINVAL; - } - - switch (fmt & SND_SOC_DAIFMT_INV_MASK) { - case SND_SOC_DAIFMT_NB_NF: /* normal bit clock + frame */ - adc_reg &= ~AD1938_ADC_LEFT_HIGH; - adc_reg &= ~AD1938_ADC_BCLK_INV; - dac_reg &= ~AD1938_DAC_LEFT_HIGH; - dac_reg &= ~AD1938_DAC_BCLK_INV; - break; - case SND_SOC_DAIFMT_NB_IF: /* normal bclk + invert frm */ - adc_reg |= AD1938_ADC_LEFT_HIGH; - adc_reg &= ~AD1938_ADC_BCLK_INV; - dac_reg |= AD1938_DAC_LEFT_HIGH; - dac_reg &= ~AD1938_DAC_BCLK_INV; - break; - case SND_SOC_DAIFMT_IB_NF: /* invert bclk + normal frm */ - adc_reg &= ~AD1938_ADC_LEFT_HIGH; - adc_reg |= AD1938_ADC_BCLK_INV; - dac_reg &= ~AD1938_DAC_LEFT_HIGH; - dac_reg |= AD1938_DAC_BCLK_INV; - break; - - case SND_SOC_DAIFMT_IB_IF: /* invert bclk + frm */ - adc_reg |= AD1938_ADC_LEFT_HIGH; - adc_reg |= AD1938_ADC_BCLK_INV; - dac_reg |= AD1938_DAC_LEFT_HIGH; - dac_reg |= AD1938_DAC_BCLK_INV; - break; - default: - return -EINVAL; - } - - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBM_CFM: /* codec clk & frm master */ - adc_reg |= AD1938_ADC_LCR_MASTER; - adc_reg |= AD1938_ADC_BCLK_MASTER; - dac_reg |= AD1938_DAC_LCR_MASTER; - dac_reg |= AD1938_DAC_BCLK_MASTER; - break; - case SND_SOC_DAIFMT_CBS_CFM: /* codec clk slave & frm master */ - adc_reg |= AD1938_ADC_LCR_MASTER; - adc_reg &= ~AD1938_ADC_BCLK_MASTER; - dac_reg |= AD1938_DAC_LCR_MASTER; - dac_reg &= ~AD1938_DAC_BCLK_MASTER; - break; - case SND_SOC_DAIFMT_CBM_CFS: /* codec clk master & frame slave */ - adc_reg &= ~AD1938_ADC_LCR_MASTER; - adc_reg |= AD1938_ADC_BCLK_MASTER; - dac_reg &= ~AD1938_DAC_LCR_MASTER; - dac_reg |= AD1938_DAC_BCLK_MASTER; - break; - case SND_SOC_DAIFMT_CBS_CFS: /* codec clk & frm slave */ - adc_reg &= ~AD1938_ADC_LCR_MASTER; - adc_reg &= ~AD1938_ADC_BCLK_MASTER; - dac_reg &= ~AD1938_DAC_LCR_MASTER; - dac_reg &= ~AD1938_DAC_BCLK_MASTER; - break; - default: - return -EINVAL; - } - - snd_soc_write(codec, AD1938_ADC_CTRL2, adc_reg); - snd_soc_write(codec, AD1938_DAC_CTRL1, dac_reg); - - return 0; -} - -static int ad1938_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params, - struct snd_soc_dai *dai) -{ - int word_len = 0, reg = 0; - - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_device *socdev = rtd->socdev; - struct snd_soc_codec *codec = socdev->card->codec; - - /* bit size */ - switch (params_format(params)) { - case SNDRV_PCM_FORMAT_S16_LE: - word_len = 3; - break; - case SNDRV_PCM_FORMAT_S20_3LE: - word_len = 1; - break; - case SNDRV_PCM_FORMAT_S24_LE: - case SNDRV_PCM_FORMAT_S32_LE: - word_len = 0; - break; - } - - reg = snd_soc_read(codec, AD1938_DAC_CTRL2); - reg = (reg & (~AD1938_DAC_WORD_LEN_MASK)) | word_len; - snd_soc_write(codec, AD1938_DAC_CTRL2, reg); - - reg = snd_soc_read(codec, AD1938_ADC_CTRL1); - reg = (reg & (~AD1938_ADC_WORD_LEN_MASK)) | word_len; - snd_soc_write(codec, AD1938_ADC_CTRL1, reg); - - return 0; -} - -static int __devinit ad1938_spi_probe(struct spi_device *spi) -{ - struct snd_soc_codec *codec; - struct ad1938_priv *ad1938; - - ad1938 = kzalloc(sizeof(struct ad1938_priv), GFP_KERNEL); - if (ad1938 == NULL) - return -ENOMEM; - - codec = &ad1938->codec; - codec->control_data = spi; - codec->dev = &spi->dev; - - dev_set_drvdata(&spi->dev, ad1938); - - return ad1938_register(ad1938); -} - -static int __devexit ad1938_spi_remove(struct spi_device *spi) -{ - struct ad1938_priv *ad1938 = dev_get_drvdata(&spi->dev); - - ad1938_unregister(ad1938); - return 0; -} - -static struct spi_driver ad1938_spi_driver = { - .driver = { - .name = "ad1938", - .owner = THIS_MODULE, - }, - .probe = ad1938_spi_probe, - .remove = __devexit_p(ad1938_spi_remove), -}; - -static struct snd_soc_dai_ops ad1938_dai_ops = { - .hw_params = ad1938_hw_params, - .digital_mute = ad1938_mute, - .set_tdm_slot = ad1938_set_tdm_slot, - .set_fmt = ad1938_set_dai_fmt, -}; - -/* codec DAI instance */ -struct snd_soc_dai ad1938_dai = { - .name = "AD1938", - .playback = { - .stream_name = "Playback", - .channels_min = 2, - .channels_max = 8, - .rates = SNDRV_PCM_RATE_48000, - .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE | - SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE, - }, - .capture = { - .stream_name = "Capture", - .channels_min = 2, - .channels_max = 4, - .rates = SNDRV_PCM_RATE_48000, - .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE | - SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE, - }, - .ops = &ad1938_dai_ops, -}; -EXPORT_SYMBOL_GPL(ad1938_dai); - -static int ad1938_register(struct ad1938_priv *ad1938) -{ - int ret; - struct snd_soc_codec *codec = &ad1938->codec; - - if (ad1938_codec) { - dev_err(codec->dev, "Another ad1938 is registered\n"); - return -EINVAL; - } - - mutex_init(&codec->mutex); - INIT_LIST_HEAD(&codec->dapm_widgets); - INIT_LIST_HEAD(&codec->dapm_paths); - codec->private_data = ad1938; - codec->reg_cache = ad1938->reg_cache; - codec->reg_cache_size = AD1938_NUM_REGS; - codec->name = "AD1938"; - codec->owner = THIS_MODULE; - codec->dai = &ad1938_dai; - codec->num_dai = 1; - INIT_LIST_HEAD(&codec->dapm_widgets); - INIT_LIST_HEAD(&codec->dapm_paths); - - ad1938_dai.dev = codec->dev; - ad1938_codec = codec; - - memcpy(codec->reg_cache, ad1938_reg, AD1938_NUM_REGS); - - ret = snd_soc_codec_set_cache_io(codec, 16, 8, SND_SOC_SPI); - if (ret < 0) { - dev_err(codec->dev, "failed to set cache I/O: %d\n", - ret); - kfree(ad1938); - return ret; - } - - /* default setting for ad1938 */ - - /* unmute dac channels */ - snd_soc_write(codec, AD1938_DAC_CHNL_MUTE, 0x0); - /* de-emphasis: 48kHz, powedown dac */ - snd_soc_write(codec, AD1938_DAC_CTRL2, 0x1A); - /* powerdown dac, dac in tdm mode */ - snd_soc_write(codec, AD1938_DAC_CTRL0, 0x41); - /* high-pass filter enable */ - snd_soc_write(codec, AD1938_ADC_CTRL0, 0x3); - /* sata delay=1, adc aux mode */ - snd_soc_write(codec, AD1938_ADC_CTRL1, 0x43); - /* pll input: mclki/xi */ - snd_soc_write(codec, AD1938_PLL_CLK_CTRL0, 0x9D); - snd_soc_write(codec, AD1938_PLL_CLK_CTRL1, 0x04); - - ret = snd_soc_register_codec(codec); - if (ret != 0) { - dev_err(codec->dev, "Failed to register codec: %d\n", ret); - kfree(ad1938); - return ret; - } - - ret = snd_soc_register_dai(&ad1938_dai); - if (ret != 0) { - dev_err(codec->dev, "Failed to register DAI: %d\n", ret); - snd_soc_unregister_codec(codec); - kfree(ad1938); - return ret; - } - - return 0; -} - -static void ad1938_unregister(struct ad1938_priv *ad1938) -{ - snd_soc_unregister_dai(&ad1938_dai); - snd_soc_unregister_codec(&ad1938->codec); - kfree(ad1938); - ad1938_codec = NULL; -} - -static int ad1938_probe(struct platform_device *pdev) -{ - struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec; - int ret = 0; - - if (ad1938_codec == NULL) { - dev_err(&pdev->dev, "Codec device not registered\n"); - return -ENODEV; - } - - socdev->card->codec = ad1938_codec; - codec = ad1938_codec; - - /* register pcms */ - ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); - if (ret < 0) { - dev_err(codec->dev, "failed to create pcms: %d\n", ret); - goto pcm_err; - } - - snd_soc_add_controls(codec, ad1938_snd_controls, - ARRAY_SIZE(ad1938_snd_controls)); - snd_soc_dapm_new_controls(codec, ad1938_dapm_widgets, - ARRAY_SIZE(ad1938_dapm_widgets)); - snd_soc_dapm_add_routes(codec, audio_paths, ARRAY_SIZE(audio_paths)); - - -pcm_err: - return ret; -} - -/* power down chip */ -static int ad1938_remove(struct platform_device *pdev) -{ - struct snd_soc_device *socdev = platform_get_drvdata(pdev); - - snd_soc_free_pcms(socdev); - snd_soc_dapm_free(socdev); - - return 0; -} - -struct snd_soc_codec_device soc_codec_dev_ad1938 = { - .probe = ad1938_probe, - .remove = ad1938_remove, -}; -EXPORT_SYMBOL_GPL(soc_codec_dev_ad1938); - -static int __init ad1938_init(void) -{ - int ret; - - ret = spi_register_driver(&ad1938_spi_driver); - if (ret != 0) { - printk(KERN_ERR "Failed to register ad1938 SPI driver: %d\n", - ret); - } - - return ret; -} -module_init(ad1938_init); - -static void __exit ad1938_exit(void) -{ - spi_unregister_driver(&ad1938_spi_driver); -} -module_exit(ad1938_exit); - -MODULE_DESCRIPTION("ASoC ad1938 driver"); -MODULE_AUTHOR("Barry Song "); -MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ad1938.h b/sound/soc/codecs/ad1938.h deleted file mode 100644 index fe3c48c..0000000 --- a/sound/soc/codecs/ad1938.h +++ /dev/null @@ -1,100 +0,0 @@ -/* - * File: sound/soc/codecs/ad1836.h - * Based on: - * Author: Barry Song <Barry.Song@analog.com> - * - * Created: May 25, 2009 - * Description: definitions for AD1938 registers - * - * Modified: - * - * Bugs: Enter bugs at http://blackfin.uclinux.org/ - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, see the file COPYING, or write - * to the Free Software Foundation, Inc., - * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#ifndef __AD1938_H__ -#define __AD1938_H__ - -#define AD1938_PLL_CLK_CTRL0 0 -#define AD1938_PLL_POWERDOWN 0x01 -#define AD1938_PLL_CLK_CTRL1 1 -#define AD1938_DAC_CTRL0 2 -#define AD1938_DAC_POWERDOWN 0x01 -#define AD1938_DAC_SERFMT_MASK 0xC0 -#define AD1938_DAC_SERFMT_STEREO (0 << 6) -#define AD1938_DAC_SERFMT_TDM (1 << 6) -#define AD1938_DAC_CTRL1 3 -#define AD1938_DAC_2_CHANNELS 0 -#define AD1938_DAC_4_CHANNELS 1 -#define AD1938_DAC_8_CHANNELS 2 -#define AD1938_DAC_16_CHANNELS 3 -#define AD1938_DAC_CHAN_SHFT 1 -#define AD1938_DAC_CHAN_MASK (3 << AD1938_DAC_CHAN_SHFT) -#define AD1938_DAC_LCR_MASTER (1 << 4) -#define AD1938_DAC_BCLK_MASTER (1 << 5) -#define AD1938_DAC_LEFT_HIGH (1 << 3) -#define AD1938_DAC_BCLK_INV (1 << 7) -#define AD1938_DAC_CTRL2 4 -#define AD1938_DAC_WORD_LEN_MASK 0xC -#define AD1938_DAC_MASTER_MUTE 1 -#define AD1938_DAC_CHNL_MUTE 5 -#define AD1938_DACL1_MUTE 0 -#define AD1938_DACR1_MUTE 1 -#define AD1938_DACL2_MUTE 2 -#define AD1938_DACR2_MUTE 3 -#define AD1938_DACL3_MUTE 4 -#define AD1938_DACR3_MUTE 5 -#define AD1938_DACL4_MUTE 6 -#define AD1938_DACR4_MUTE 7 -#define AD1938_DAC_L1_VOL 6 -#define AD1938_DAC_R1_VOL 7 -#define AD1938_DAC_L2_VOL 8 -#define AD1938_DAC_R2_VOL 9 -#define AD1938_DAC_L3_VOL 10 -#define AD1938_DAC_R3_VOL 11 -#define AD1938_DAC_L4_VOL 12 -#define AD1938_DAC_R4_VOL 13 -#define AD1938_ADC_CTRL0 14 -#define AD1938_ADC_POWERDOWN 0x01 -#define AD1938_ADC_HIGHPASS_FILTER 1 -#define AD1938_ADCL1_MUTE 2 -#define AD1938_ADCR1_MUTE 3 -#define AD1938_ADCL2_MUTE 4 -#define AD1938_ADCR2_MUTE 5 -#define AD1938_ADC_CTRL1 15 -#define AD1938_ADC_SERFMT_MASK 0x60 -#define AD1938_ADC_SERFMT_STEREO (0 << 5) -#define AD1938_ADC_SERFMT_TDM (1 << 2) -#define AD1938_ADC_SERFMT_AUX (2 << 5) -#define AD1938_ADC_WORD_LEN_MASK 0x3 -#define AD1938_ADC_CTRL2 16 -#define AD1938_ADC_2_CHANNELS 0 -#define AD1938_ADC_4_CHANNELS 1 -#define AD1938_ADC_8_CHANNELS 2 -#define AD1938_ADC_16_CHANNELS 3 -#define AD1938_ADC_CHAN_SHFT 4 -#define AD1938_ADC_CHAN_MASK (3 << AD1938_ADC_CHAN_SHFT) -#define AD1938_ADC_LCR_MASTER (1 << 3) -#define AD1938_ADC_BCLK_MASTER (1 << 6) -#define AD1938_ADC_LEFT_HIGH (1 << 2) -#define AD1938_ADC_BCLK_INV (1 << 1) - -#define AD1938_NUM_REGS 17 - -extern struct snd_soc_dai ad1938_dai; -extern struct snd_soc_codec_device soc_codec_dev_ad1938; -#endif diff --git a/sound/soc/codecs/ad193x.c b/sound/soc/codecs/ad193x.c new file mode 100644 index 0000000..c8ca114 --- /dev/null +++ b/sound/soc/codecs/ad193x.c @@ -0,0 +1,547 @@ +/* + * AD193X Audio Codec driver supporting AD1936/7/8/9 + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/spi/spi.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/soc.h> +#include <sound/tlv.h> +#include <sound/soc-dapm.h> +#include "ad193x.h" + +/* codec private data */ +struct ad193x_priv { + struct snd_soc_codec codec; + u8 reg_cache[AD193X_NUM_REGS]; +}; + +/* ad193x register cache & default register settings */ +static const u8 ad193x_reg[AD193X_NUM_REGS] = { + 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0, +}; + +static struct snd_soc_codec *ad193x_codec; +struct snd_soc_codec_device soc_codec_dev_ad193x; + +/* + * AD193X volume/mute/de-emphasis etc. controls + */ +static const char *ad193x_deemp[] = {"None", "48kHz", "44.1kHz", "32kHz"}; + +static const struct soc_enum ad193x_deemp_enum = + SOC_ENUM_SINGLE(AD193X_DAC_CTRL2, 1, 4, ad193x_deemp); + +static const struct snd_kcontrol_new ad193x_snd_controls[] = { + /* DAC volume control */ + SOC_DOUBLE_R("DAC1 Volume", AD193X_DAC_L1_VOL, + AD193X_DAC_R1_VOL, 0, 0xFF, 1), + SOC_DOUBLE_R("DAC2 Volume", AD193X_DAC_L2_VOL, + AD193X_DAC_R2_VOL, 0, 0xFF, 1), + SOC_DOUBLE_R("DAC3 Volume", AD193X_DAC_L3_VOL, + AD193X_DAC_R3_VOL, 0, 0xFF, 1), + SOC_DOUBLE_R("DAC4 Volume", AD193X_DAC_L4_VOL, + AD193X_DAC_R4_VOL, 0, 0xFF, 1), + + /* ADC switch control */ + SOC_DOUBLE("ADC1 Switch", AD193X_ADC_CTRL0, AD193X_ADCL1_MUTE, + AD193X_ADCR1_MUTE, 1, 1), + SOC_DOUBLE("ADC2 Switch", AD193X_ADC_CTRL0, AD193X_ADCL2_MUTE, + AD193X_ADCR2_MUTE, 1, 1), + + /* DAC switch control */ + SOC_DOUBLE("DAC1 Switch", AD193X_DAC_CHNL_MUTE, AD193X_DACL1_MUTE, + AD193X_DACR1_MUTE, 1, 1), + SOC_DOUBLE("DAC2 Switch", AD193X_DAC_CHNL_MUTE, AD193X_DACL2_MUTE, + AD193X_DACR2_MUTE, 1, 1), + SOC_DOUBLE("DAC3 Switch", AD193X_DAC_CHNL_MUTE, AD193X_DACL3_MUTE, + AD193X_DACR3_MUTE, 1, 1), + SOC_DOUBLE("DAC4 Switch", AD193X_DAC_CHNL_MUTE, AD193X_DACL4_MUTE, + AD193X_DACR4_MUTE, 1, 1), + + /* ADC high-pass filter */ + SOC_SINGLE("ADC High Pass Filter Switch", AD193X_ADC_CTRL0, + AD193X_ADC_HIGHPASS_FILTER, 1, 0), + + /* DAC de-emphasis */ + SOC_ENUM("Playback Deemphasis", ad193x_deemp_enum), +}; + +static const struct snd_soc_dapm_widget ad193x_dapm_widgets[] = { + SND_SOC_DAPM_DAC("DAC", "Playback", AD193X_DAC_CTRL0, 0, 1), + SND_SOC_DAPM_ADC("ADC", "Capture", SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_SUPPLY("PLL_PWR", AD193X_PLL_CLK_CTRL0, 0, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC_PWR", AD193X_ADC_CTRL0, 0, 1, NULL, 0), + SND_SOC_DAPM_OUTPUT("DAC1OUT"), + SND_SOC_DAPM_OUTPUT("DAC2OUT"), + SND_SOC_DAPM_OUTPUT("DAC3OUT"), + SND_SOC_DAPM_OUTPUT("DAC4OUT"), + SND_SOC_DAPM_INPUT("ADC1IN"), + SND_SOC_DAPM_INPUT("ADC2IN"), +}; + +static const struct snd_soc_dapm_route audio_paths[] = { + { "DAC", NULL, "PLL_PWR" }, + { "ADC", NULL, "PLL_PWR" }, + { "DAC", NULL, "ADC_PWR" }, + { "ADC", NULL, "ADC_PWR" }, + { "DAC1OUT", "DAC1 Switch", "DAC" }, + { "DAC2OUT", "DAC2 Switch", "DAC" }, + { "DAC3OUT", "DAC3 Switch", "DAC" }, + { "DAC4OUT", "DAC4 Switch", "DAC" }, + { "ADC", "ADC1 Switch", "ADC1IN" }, + { "ADC", "ADC2 Switch", "ADC2IN" }, +}; + +/* + * DAI ops entries + */ + +static int ad193x_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + int reg; + + reg = snd_soc_read(codec, AD193X_DAC_CTRL2); + reg = (mute > 0) ? reg | AD193X_DAC_MASTER_MUTE : reg & + (~AD193X_DAC_MASTER_MUTE); + snd_soc_write(codec, AD193X_DAC_CTRL2, reg); + + return 0; +} + +static int ad193x_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + unsigned int rx_mask, int slots, int width) +{ + struct snd_soc_codec *codec = dai->codec; + int dac_reg = snd_soc_read(codec, AD193X_DAC_CTRL1); + int adc_reg = snd_soc_read(codec, AD193X_ADC_CTRL2); + + dac_reg &= ~AD193X_DAC_CHAN_MASK; + adc_reg &= ~AD193X_ADC_CHAN_MASK; + + switch (slots) { + case 2: + dac_reg |= AD193X_DAC_2_CHANNELS << AD193X_DAC_CHAN_SHFT; + adc_reg |= AD193X_ADC_2_CHANNELS << AD193X_ADC_CHAN_SHFT; + break; + case 4: + dac_reg |= AD193X_DAC_4_CHANNELS << AD193X_DAC_CHAN_SHFT; + adc_reg |= AD193X_ADC_4_CHANNELS << AD193X_ADC_CHAN_SHFT; + break; + case 8: + dac_reg |= AD193X_DAC_8_CHANNELS << AD193X_DAC_CHAN_SHFT; + adc_reg |= AD193X_ADC_8_CHANNELS << AD193X_ADC_CHAN_SHFT; + break; + case 16: + dac_reg |= AD193X_DAC_16_CHANNELS << AD193X_DAC_CHAN_SHFT; + adc_reg |= AD193X_ADC_16_CHANNELS << AD193X_ADC_CHAN_SHFT; + break; + default: + return -EINVAL; + } + + snd_soc_write(codec, AD193X_DAC_CTRL1, dac_reg); + snd_soc_write(codec, AD193X_ADC_CTRL2, adc_reg); + + return 0; +} + +static int ad193x_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + int adc_reg1, adc_reg2, dac_reg; + + adc_reg1 = snd_soc_read(codec, AD193X_ADC_CTRL1); + adc_reg2 = snd_soc_read(codec, AD193X_ADC_CTRL2); + dac_reg = snd_soc_read(codec, AD193X_DAC_CTRL1); + + /* At present, the driver only support AUX ADC mode(SND_SOC_DAIFMT_I2S + * with TDM) and ADC&DAC TDM mode(SND_SOC_DAIFMT_DSP_A) + */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + adc_reg1 &= ~AD193X_ADC_SERFMT_MASK; + adc_reg1 |= AD193X_ADC_SERFMT_TDM; + break; + case SND_SOC_DAIFMT_DSP_A: + adc_reg1 &= ~AD193X_ADC_SERFMT_MASK; + adc_reg1 |= AD193X_ADC_SERFMT_AUX; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: /* normal bit clock + frame */ + adc_reg2 &= ~AD193X_ADC_LEFT_HIGH; + adc_reg2 &= ~AD193X_ADC_BCLK_INV; + dac_reg &= ~AD193X_DAC_LEFT_HIGH; + dac_reg &= ~AD193X_DAC_BCLK_INV; + break; + case SND_SOC_DAIFMT_NB_IF: /* normal bclk + invert frm */ + adc_reg2 |= AD193X_ADC_LEFT_HIGH; + adc_reg2 &= ~AD193X_ADC_BCLK_INV; + dac_reg |= AD193X_DAC_LEFT_HIGH; + dac_reg &= ~AD193X_DAC_BCLK_INV; + break; + case SND_SOC_DAIFMT_IB_NF: /* invert bclk + normal frm */ + adc_reg2 &= ~AD193X_ADC_LEFT_HIGH; + adc_reg2 |= AD193X_ADC_BCLK_INV; + dac_reg &= ~AD193X_DAC_LEFT_HIGH; + dac_reg |= AD193X_DAC_BCLK_INV; + break; + + case SND_SOC_DAIFMT_IB_IF: /* invert bclk + frm */ + adc_reg2 |= AD193X_ADC_LEFT_HIGH; + adc_reg2 |= AD193X_ADC_BCLK_INV; + dac_reg |= AD193X_DAC_LEFT_HIGH; + dac_reg |= AD193X_DAC_BCLK_INV; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: /* codec clk & frm master */ + adc_reg2 |= AD193X_ADC_LCR_MASTER; + adc_reg2 |= AD193X_ADC_BCLK_MASTER; + dac_reg |= AD193X_DAC_LCR_MASTER; + dac_reg |= AD193X_DAC_BCLK_MASTER; + break; + case SND_SOC_DAIFMT_CBS_CFM: /* codec clk slave & frm master */ + adc_reg2 |= AD193X_ADC_LCR_MASTER; + adc_reg2 &= ~AD193X_ADC_BCLK_MASTER; + dac_reg |= AD193X_DAC_LCR_MASTER; + dac_reg &= ~AD193X_DAC_BCLK_MASTER; + break; + case SND_SOC_DAIFMT_CBM_CFS: /* codec clk master & frame slave */ + adc_reg2 &= ~AD193X_ADC_LCR_MASTER; + adc_reg2 |= AD193X_ADC_BCLK_MASTER; + dac_reg &= ~AD193X_DAC_LCR_MASTER; + dac_reg |= AD193X_DAC_BCLK_MASTER; + break; + case SND_SOC_DAIFMT_CBS_CFS: /* codec clk & frm slave */ + adc_reg2 &= ~AD193X_ADC_LCR_MASTER; + adc_reg2 &= ~AD193X_ADC_BCLK_MASTER; + dac_reg &= ~AD193X_DAC_LCR_MASTER; + dac_reg &= ~AD193X_DAC_BCLK_MASTER; + break; + default: + return -EINVAL; + } + + snd_soc_write(codec, AD193X_ADC_CTRL1, adc_reg1); + snd_soc_write(codec, AD193X_ADC_CTRL2, adc_reg2); + snd_soc_write(codec, AD193X_DAC_CTRL1, dac_reg); + + return 0; +} + +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; + + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->card->codec; + + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + word_len = 3; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + word_len = 1; + break; + case SNDRV_PCM_FORMAT_S24_LE: + case SNDRV_PCM_FORMAT_S32_LE: + word_len = 0; + break; + } + + reg = snd_soc_read(codec, AD193X_DAC_CTRL2); + reg = (reg & (~AD193X_DAC_WORD_LEN_MASK)) | word_len; + snd_soc_write(codec, AD193X_DAC_CTRL2, reg); + + reg = snd_soc_read(codec, AD193X_ADC_CTRL1); + reg = (reg & (~AD193X_ADC_WORD_LEN_MASK)) | word_len; + snd_soc_write(codec, AD193X_ADC_CTRL1, reg); + + return 0; +} + +static int ad193x_bus_probe(struct device *dev, void *ctrl_data, int bus_type) +{ + struct snd_soc_codec *codec; + struct ad193x_priv *ad193x; + int ret; + + if (ad193x_codec) { + dev_err(dev, "Another ad193x is registered\n"); + return -EINVAL; + } + + ad193x = kzalloc(sizeof(struct ad193x_priv), GFP_KERNEL); + if (ad193x == NULL) + return -ENOMEM; + + dev_set_drvdata(dev, ad193x); + + codec = &ad193x->codec; + mutex_init(&codec->mutex); + codec->control_data = ctrl_data; + codec->dev = dev; + snd_soc_codec_set_drvdata(codec, ad193x); + codec->reg_cache = ad193x->reg_cache; + codec->reg_cache_size = AD193X_NUM_REGS; + codec->name = "AD193X"; + codec->owner = THIS_MODULE; + codec->dai = &ad193x_dai; + codec->num_dai = 1; + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + ad193x_dai.dev = codec->dev; + ad193x_codec = codec; + + memcpy(codec->reg_cache, ad193x_reg, AD193X_NUM_REGS); + + if (bus_type == SND_SOC_I2C) + ret = snd_soc_codec_set_cache_io(codec, 8, 8, bus_type); + else + ret = snd_soc_codec_set_cache_io(codec, 16, 8, bus_type); + if (ret < 0) { + dev_err(codec->dev, "failed to set cache I/O: %d\n", + ret); + kfree(ad193x); + return ret; + } + + /* default setting for ad193x */ + + /* unmute dac channels */ + snd_soc_write(codec, AD193X_DAC_CHNL_MUTE, 0x0); + /* de-emphasis: 48kHz, powedown dac */ + snd_soc_write(codec, AD193X_DAC_CTRL2, 0x1A); + /* powerdown dac, dac in tdm mode */ + snd_soc_write(codec, AD193X_DAC_CTRL0, 0x41); + /* high-pass filter enable */ + snd_soc_write(codec, AD193X_ADC_CTRL0, 0x3); + /* sata delay=1, adc aux mode */ + snd_soc_write(codec, AD193X_ADC_CTRL1, 0x43); + /* 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); + + ret = snd_soc_register_codec(codec); + if (ret != 0) { + dev_err(codec->dev, "Failed to register codec: %d\n", ret); + kfree(ad193x); + return ret; + } + + ret = snd_soc_register_dai(&ad193x_dai); + if (ret != 0) { + dev_err(codec->dev, "Failed to register DAI: %d\n", ret); + snd_soc_unregister_codec(codec); + kfree(ad193x); + return ret; + } + + return 0; +} + +static int ad193x_bus_remove(struct device *dev) +{ + struct ad193x_priv *ad193x = dev_get_drvdata(dev); + + snd_soc_unregister_dai(&ad193x_dai); + snd_soc_unregister_codec(&ad193x->codec); + kfree(ad193x); + ad193x_codec = NULL; + + return 0; +} + +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_fmt = ad193x_set_dai_fmt, +}; + +/* codec DAI instance */ +struct snd_soc_dai ad193x_dai = { + .name = "AD193X", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 4, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE, + }, + .ops = &ad193x_dai_ops, +}; +EXPORT_SYMBOL_GPL(ad193x_dai); + +static int ad193x_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + + if (ad193x_codec == NULL) { + dev_err(&pdev->dev, "Codec device not registered\n"); + return -ENODEV; + } + + socdev->card->codec = ad193x_codec; + codec = ad193x_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, ad193x_snd_controls, + ARRAY_SIZE(ad193x_snd_controls)); + snd_soc_dapm_new_controls(codec, ad193x_dapm_widgets, + ARRAY_SIZE(ad193x_dapm_widgets)); + snd_soc_dapm_add_routes(codec, audio_paths, ARRAY_SIZE(audio_paths)); + +pcm_err: + return ret; +} + +/* power down chip */ +static int ad193x_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_ad193x = { + .probe = ad193x_probe, + .remove = ad193x_remove, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_ad193x); + +#if defined(CONFIG_SPI_MASTER) +static int __devinit ad193x_spi_probe(struct spi_device *spi) +{ + return ad193x_bus_probe(&spi->dev, spi, SND_SOC_SPI); +} + +static int __devexit ad193x_spi_remove(struct spi_device *spi) +{ + return ad193x_bus_remove(&spi->dev); +} + +static struct spi_driver ad193x_spi_driver = { + .driver = { + .name = "ad193x", + .owner = THIS_MODULE, + }, + .probe = ad193x_spi_probe, + .remove = __devexit_p(ad193x_spi_remove), +}; +#endif + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) +static const struct i2c_device_id ad193x_id[] = { + { "ad1936", 0 }, + { "ad1937", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ad193x_id); + +static int __devinit ad193x_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + return ad193x_bus_probe(&client->dev, client, SND_SOC_I2C); +} + +static int __devexit ad193x_i2c_remove(struct i2c_client *client) +{ + return ad193x_bus_remove(&client->dev); +} + +static struct i2c_driver ad193x_i2c_driver = { + .driver = { + .name = "ad193x", + }, + .probe = ad193x_i2c_probe, + .remove = __devexit_p(ad193x_i2c_remove), + .id_table = ad193x_id, +}; +#endif + +static int __init ad193x_modinit(void) +{ + int ret; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + ret = i2c_add_driver(&ad193x_i2c_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register AD193X I2C driver: %d\n", + ret); + } +#endif + +#if defined(CONFIG_SPI_MASTER) + ret = spi_register_driver(&ad193x_spi_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register AD193X SPI driver: %d\n", + ret); + } +#endif + return ret; +} +module_init(ad193x_modinit); + +static void __exit ad193x_modexit(void) +{ +#if defined(CONFIG_SPI_MASTER) + spi_unregister_driver(&ad193x_spi_driver); +#endif + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_del_driver(&ad193x_i2c_driver); +#endif +} +module_exit(ad193x_modexit); + +MODULE_DESCRIPTION("ASoC ad193x driver"); +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ad193x.h b/sound/soc/codecs/ad193x.h new file mode 100644 index 0000000..a03c880 --- /dev/null +++ b/sound/soc/codecs/ad193x.h @@ -0,0 +1,81 @@ +/* + * AD193X Audio Codec driver + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#ifndef __AD193X_H__ +#define __AD193X_H__ + +#define AD193X_PLL_CLK_CTRL0 0x800 +#define AD193X_PLL_POWERDOWN 0x01 +#define AD193X_PLL_CLK_CTRL1 0x801 +#define AD193X_DAC_CTRL0 0x802 +#define AD193X_DAC_POWERDOWN 0x01 +#define AD193X_DAC_SERFMT_MASK 0xC0 +#define AD193X_DAC_SERFMT_STEREO (0 << 6) +#define AD193X_DAC_SERFMT_TDM (1 << 6) +#define AD193X_DAC_CTRL1 0x803 +#define AD193X_DAC_2_CHANNELS 0 +#define AD193X_DAC_4_CHANNELS 1 +#define AD193X_DAC_8_CHANNELS 2 +#define AD193X_DAC_16_CHANNELS 3 +#define AD193X_DAC_CHAN_SHFT 1 +#define AD193X_DAC_CHAN_MASK (3 << AD193X_DAC_CHAN_SHFT) +#define AD193X_DAC_LCR_MASTER (1 << 4) +#define AD193X_DAC_BCLK_MASTER (1 << 5) +#define AD193X_DAC_LEFT_HIGH (1 << 3) +#define AD193X_DAC_BCLK_INV (1 << 7) +#define AD193X_DAC_CTRL2 0x804 +#define AD193X_DAC_WORD_LEN_MASK 0xC +#define AD193X_DAC_MASTER_MUTE 1 +#define AD193X_DAC_CHNL_MUTE 0x805 +#define AD193X_DACL1_MUTE 0 +#define AD193X_DACR1_MUTE 1 +#define AD193X_DACL2_MUTE 2 +#define AD193X_DACR2_MUTE 3 +#define AD193X_DACL3_MUTE 4 +#define AD193X_DACR3_MUTE 5 +#define AD193X_DACL4_MUTE 6 +#define AD193X_DACR4_MUTE 7 +#define AD193X_DAC_L1_VOL 0x806 +#define AD193X_DAC_R1_VOL 0x807 +#define AD193X_DAC_L2_VOL 0x808 +#define AD193X_DAC_R2_VOL 0x809 +#define AD193X_DAC_L3_VOL 0x80a +#define AD193X_DAC_R3_VOL 0x80b +#define AD193X_DAC_L4_VOL 0x80c +#define AD193X_DAC_R4_VOL 0x80d +#define AD193X_ADC_CTRL0 0x80e +#define AD193X_ADC_POWERDOWN 0x01 +#define AD193X_ADC_HIGHPASS_FILTER 1 +#define AD193X_ADCL1_MUTE 2 +#define AD193X_ADCR1_MUTE 3 +#define AD193X_ADCL2_MUTE 4 +#define AD193X_ADCR2_MUTE 5 +#define AD193X_ADC_CTRL1 0x80f +#define AD193X_ADC_SERFMT_MASK 0x60 +#define AD193X_ADC_SERFMT_STEREO (0 << 5) +#define AD193X_ADC_SERFMT_TDM (1 << 2) +#define AD193X_ADC_SERFMT_AUX (2 << 5) +#define AD193X_ADC_WORD_LEN_MASK 0x3 +#define AD193X_ADC_CTRL2 0x810 +#define AD193X_ADC_2_CHANNELS 0 +#define AD193X_ADC_4_CHANNELS 1 +#define AD193X_ADC_8_CHANNELS 2 +#define AD193X_ADC_16_CHANNELS 3 +#define AD193X_ADC_CHAN_SHFT 4 +#define AD193X_ADC_CHAN_MASK (3 << AD193X_ADC_CHAN_SHFT) +#define AD193X_ADC_LCR_MASTER (1 << 3) +#define AD193X_ADC_BCLK_MASTER (1 << 6) +#define AD193X_ADC_LEFT_HIGH (1 << 2) +#define AD193X_ADC_BCLK_INV (1 << 1) + +#define AD193X_NUM_REGS 17 + +extern struct snd_soc_dai ad193x_dai; +extern struct snd_soc_codec_device soc_codec_dev_ad193x; + +#endif diff --git a/sound/soc/codecs/ak4104.c b/sound/soc/codecs/ak4104.c index bdeb10d..192aebda 100644 --- a/sound/soc/codecs/ak4104.c +++ b/sound/soc/codecs/ak4104.c @@ -222,7 +222,7 @@ static int ak4104_spi_probe(struct spi_device *spi) codec->owner = THIS_MODULE; codec->dai = &ak4104_dai; codec->num_dai = 1; - codec->private_data = ak4104; + snd_soc_codec_set_drvdata(codec, ak4104); codec->control_data = spi; codec->reg_cache = ak4104->reg_cache; codec->reg_cache_size = AK4104_NUM_REGS; diff --git a/sound/soc/codecs/ak4535.c b/sound/soc/codecs/ak4535.c index 352d1d0..d425367 100644 --- a/sound/soc/codecs/ak4535.c +++ b/sound/soc/codecs/ak4535.c @@ -302,7 +302,7 @@ static int ak4535_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 ak4535_priv *ak4535 = codec->private_data; + struct ak4535_priv *ak4535 = snd_soc_codec_get_drvdata(codec); ak4535->sysclk = freq; return 0; @@ -315,7 +315,7 @@ static int ak4535_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 ak4535_priv *ak4535 = codec->private_data; + struct ak4535_priv *ak4535 = snd_soc_codec_get_drvdata(codec); u8 mode2 = ak4535_read_reg_cache(codec, AK4535_MODE2) & ~(0x3 << 5); int rate = params_rate(params), fs = 256; @@ -446,7 +446,6 @@ static int ak4535_resume(struct platform_device *pdev) struct snd_soc_codec *codec = socdev->card->codec; ak4535_sync(codec); ak4535_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - ak4535_set_bias_level(codec, codec->suspend_bias_level); return 0; } @@ -600,7 +599,7 @@ static int ak4535_probe(struct platform_device *pdev) return -ENOMEM; } - codec->private_data = ak4535; + snd_soc_codec_set_drvdata(codec, ak4535); socdev->card->codec = codec; mutex_init(&codec->mutex); INIT_LIST_HEAD(&codec->dapm_widgets); @@ -617,7 +616,7 @@ static int ak4535_probe(struct platform_device *pdev) #endif if (ret != 0) { - kfree(codec->private_data); + kfree(snd_soc_codec_get_drvdata(codec)); kfree(codec); } return ret; @@ -639,7 +638,7 @@ static int ak4535_remove(struct platform_device *pdev) i2c_unregister_device(codec->control_data); i2c_del_driver(&ak4535_i2c_driver); #endif - kfree(codec->private_data); + kfree(snd_soc_codec_get_drvdata(codec)); kfree(codec); return 0; diff --git a/sound/soc/codecs/ak4642.c b/sound/soc/codecs/ak4642.c index 729859c..7528a54 100644 --- a/sound/soc/codecs/ak4642.c +++ b/sound/soc/codecs/ak4642.c @@ -81,12 +81,39 @@ #define AK4642_CACHEREGNUM 0x25 +/* PW_MGMT2 */ +#define HPMTN (1 << 6) +#define PMHPL (1 << 5) +#define PMHPR (1 << 4) +#define MS (1 << 3) /* master/slave select */ +#define MCKO (1 << 1) +#define PMPLL (1 << 0) + +#define PMHP_MASK (PMHPL | PMHPR) +#define PMHP PMHP_MASK + +/* MD_CTL1 */ +#define PLL3 (1 << 7) +#define PLL2 (1 << 6) +#define PLL1 (1 << 5) +#define PLL0 (1 << 4) +#define PLL_MASK (PLL3 | PLL2 | PLL1 | PLL0) + +#define BCKO_MASK (1 << 3) +#define BCKO_64 BCKO_MASK + +/* MD_CTL2 */ +#define FS0 (1 << 0) +#define FS1 (1 << 1) +#define FS2 (1 << 2) +#define FS3 (1 << 5) +#define FS_MASK (FS0 | FS1 | FS2 | FS3) + struct snd_soc_codec_device soc_codec_dev_ak4642; /* codec private data */ struct ak4642_priv { struct snd_soc_codec codec; - unsigned int sysclk; }; static struct snd_soc_codec *ak4642_codec; @@ -177,17 +204,12 @@ static int ak4642_dai_startup(struct snd_pcm_substream *substream, * * PLL, Master Mode * Audio I/F Format :MSB justified (ADC & DAC) - * Sampling Frequency: 44.1kHz - * Digital Volume: −8dB + * Digital Volume: -8dB * Bass Boost Level : Middle * * This operation came from example code of * "ASAHI KASEI AK4642" (japanese) manual p97. - * - * Example code use 0x39, 0x79 value for 0x01 address, - * But we need MCKO (0x02) bit now */ - ak4642_write(codec, 0x05, 0x27); ak4642_write(codec, 0x0f, 0x09); ak4642_write(codec, 0x0e, 0x19); ak4642_write(codec, 0x09, 0x91); @@ -195,15 +217,14 @@ static int ak4642_dai_startup(struct snd_pcm_substream *substream, ak4642_write(codec, 0x0a, 0x28); ak4642_write(codec, 0x0d, 0x28); ak4642_write(codec, 0x00, 0x64); - ak4642_write(codec, 0x01, 0x3b); /* + MCKO bit */ - ak4642_write(codec, 0x01, 0x7b); /* + MCKO bit */ + snd_soc_update_bits(codec, PW_MGMT2, PMHP_MASK, PMHP); + snd_soc_update_bits(codec, PW_MGMT2, HPMTN, HPMTN); } else { /* * start stereo input * * PLL Master Mode * Audio I/F Format:MSB justified (ADC & DAC) - * Sampling Frequency:44.1kHz * Pre MIC AMP:+20dB * MIC Power On * ALC setting:Refer to Table 35 @@ -212,7 +233,6 @@ static int ak4642_dai_startup(struct snd_pcm_substream *substream, * This operation came from example code of * "ASAHI KASEI AK4642" (japanese) manual p94. */ - ak4642_write(codec, 0x05, 0x27); ak4642_write(codec, 0x02, 0x05); ak4642_write(codec, 0x06, 0x3c); ak4642_write(codec, 0x08, 0xe1); @@ -233,8 +253,8 @@ static void ak4642_dai_shutdown(struct snd_pcm_substream *substream, if (is_play) { /* stop headphone output */ - ak4642_write(codec, 0x01, 0x3b); - ak4642_write(codec, 0x01, 0x0b); + snd_soc_update_bits(codec, PW_MGMT2, HPMTN, 0); + snd_soc_update_bits(codec, PW_MGMT2, PMHP_MASK, 0); ak4642_write(codec, 0x00, 0x40); ak4642_write(codec, 0x0e, 0x11); ak4642_write(codec, 0x0f, 0x08); @@ -250,9 +270,111 @@ static int ak4642_dai_set_sysclk(struct snd_soc_dai *codec_dai, int clk_id, unsigned int freq, int dir) { struct snd_soc_codec *codec = codec_dai->codec; - struct ak4642_priv *ak4642 = codec->private_data; + u8 pll; + + switch (freq) { + case 11289600: + pll = PLL2; + break; + case 12288000: + pll = PLL2 | PLL0; + break; + case 12000000: + pll = PLL2 | PLL1; + break; + case 24000000: + pll = PLL2 | PLL1 | PLL0; + break; + case 13500000: + pll = PLL3 | PLL2; + break; + case 27000000: + pll = PLL3 | PLL2 | PLL0; + break; + default: + return -EINVAL; + } + snd_soc_update_bits(codec, MD_CTL1, PLL_MASK, pll); + + return 0; +} + +static int ak4642_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = dai->codec; + u8 data; + u8 bcko; + + data = MCKO | PMPLL; /* use MCKO */ + bcko = 0; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + data |= MS; + bcko = BCKO_64; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + snd_soc_update_bits(codec, PW_MGMT2, MS, data); + snd_soc_update_bits(codec, MD_CTL1, BCKO_MASK, bcko); + + return 0; +} + +static int ak4642_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + u8 rate; + + switch (params_rate(params)) { + case 7350: + rate = FS2; + break; + case 8000: + rate = 0; + break; + case 11025: + rate = FS2 | FS0; + break; + case 12000: + rate = FS0; + break; + case 14700: + rate = FS2 | FS1; + break; + case 16000: + rate = FS1; + break; + case 22050: + rate = FS2 | FS1 | FS0; + break; + case 24000: + rate = FS1 | FS0; + break; + case 29400: + rate = FS3 | FS2 | FS1; + break; + case 32000: + rate = FS3 | FS1; + break; + case 44100: + rate = FS3 | FS2 | FS1 | FS0; + break; + case 48000: + rate = FS3 | FS1 | FS0; + break; + default: + return -EINVAL; + break; + } + snd_soc_update_bits(codec, MD_CTL2, FS_MASK, rate); - ak4642->sysclk = freq; return 0; } @@ -260,6 +382,8 @@ static struct snd_soc_dai_ops ak4642_dai_ops = { .startup = ak4642_dai_startup, .shutdown = ak4642_dai_shutdown, .set_sysclk = ak4642_dai_set_sysclk, + .set_fmt = ak4642_dai_set_fmt, + .hw_params = ak4642_dai_hw_params, }; struct snd_soc_dai ak4642_dai = { @@ -277,6 +401,7 @@ struct snd_soc_dai ak4642_dai = { .rates = SNDRV_PCM_RATE_8000_48000, .formats = SNDRV_PCM_FMTBIT_S16_LE }, .ops = &ak4642_dai_ops, + .symmetric_rates = 1, }; EXPORT_SYMBOL_GPL(ak4642_dai); @@ -307,7 +432,7 @@ static int ak4642_init(struct ak4642_priv *ak4642) INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); - codec->private_data = ak4642; + snd_soc_codec_set_drvdata(codec, ak4642); codec->name = "AK4642"; codec->owner = THIS_MODULE; codec->read = ak4642_read_reg_cache; @@ -338,26 +463,6 @@ static int ak4642_init(struct ak4642_priv *ak4642) goto reg_cache_err; } - /* - * clock setting - * - * Audio I/F Format: MSB justified (ADC & DAC) - * BICK frequency at Master Mode: 64fs - * Input Master Clock Select at PLL Mode: 11.2896MHz - * MCKO: Enable - * Sampling Frequency: 44.1kHz - * - * This operation came from example code of - * "ASAHI KASEI AK4642" (japanese) manual p89. - * - * please fix-me - */ - ak4642_write(codec, 0x01, 0x08); - ak4642_write(codec, 0x04, 0x4a); - ak4642_write(codec, 0x05, 0x27); - ak4642_write(codec, 0x00, 0x40); - ak4642_write(codec, 0x01, 0x0b); - return ret; reg_cache_err: diff --git a/sound/soc/codecs/ak4671.c b/sound/soc/codecs/ak4671.c index 926797a..8756693 100644 --- a/sound/soc/codecs/ak4671.c +++ b/sound/soc/codecs/ak4671.c @@ -702,7 +702,7 @@ static int ak4671_register(struct ak4671_priv *ak4671, INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); - codec->private_data = ak4671; + snd_soc_codec_set_drvdata(codec, ak4671); codec->name = "AK4671"; codec->owner = THIS_MODULE; codec->bias_level = SND_SOC_BIAS_OFF; diff --git a/sound/soc/codecs/cq93vc.c b/sound/soc/codecs/cq93vc.c new file mode 100644 index 0000000..a320fb5 --- /dev/null +++ b/sound/soc/codecs/cq93vc.c @@ -0,0 +1,299 @@ +/* + * ALSA SoC CQ0093 Voice Codec Driver for DaVinci platforms + * + * Copyright (C) 2010 Texas Instruments, Inc + * + * Author: Miguel Aguilar <miguel.aguilar@ridgerun.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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/platform_device.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <linux/mfd/davinci_voicecodec.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dai.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> + +#include <mach/dm365.h> + +#include "cq93vc.h" + +static inline unsigned int cq93vc_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + struct davinci_vc *davinci_vc = codec->control_data; + + return readl(davinci_vc->base + reg); +} + +static inline int cq93vc_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + struct davinci_vc *davinci_vc = codec->control_data; + + writel(value, davinci_vc->base + reg); + + return 0; +} + +static const struct snd_kcontrol_new cq93vc_snd_controls[] = { + SOC_SINGLE("PGA Capture Volume", DAVINCI_VC_REG05, 0, 0x03, 0), + SOC_SINGLE("Mono DAC Playback Volume", DAVINCI_VC_REG09, 0, 0x3f, 0), +}; + +static int cq93vc_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u8 reg = cq93vc_read(codec, DAVINCI_VC_REG09) & ~DAVINCI_VC_REG09_MUTE; + + if (mute) + cq93vc_write(codec, DAVINCI_VC_REG09, + reg | DAVINCI_VC_REG09_MUTE); + else + cq93vc_write(codec, DAVINCI_VC_REG09, reg); + + return 0; +} + +static int cq93vc_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 davinci_vc *davinci_vc = codec->control_data; + + switch (freq) { + case 22579200: + case 27000000: + case 33868800: + davinci_vc->cq93vc.sysclk = freq; + return 0; + } + + return -EINVAL; +} + +static int cq93vc_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + switch (level) { + case SND_SOC_BIAS_ON: + cq93vc_write(codec, DAVINCI_VC_REG12, + DAVINCI_VC_REG12_POWER_ALL_ON); + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + cq93vc_write(codec, DAVINCI_VC_REG12, + DAVINCI_VC_REG12_POWER_ALL_OFF); + break; + case SND_SOC_BIAS_OFF: + /* force all power off */ + cq93vc_write(codec, DAVINCI_VC_REG12, + DAVINCI_VC_REG12_POWER_ALL_OFF); + break; + } + codec->bias_level = level; + + return 0; +} + +#define CQ93VC_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000) +#define CQ93VC_FORMATS (SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE) + +static struct snd_soc_dai_ops cq93vc_dai_ops = { + .digital_mute = cq93vc_mute, + .set_sysclk = cq93vc_set_dai_sysclk, +}; + +struct snd_soc_dai cq93vc_dai = { + .name = "CQ93VC", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = CQ93VC_RATES, + .formats = CQ93VC_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = CQ93VC_RATES, + .formats = CQ93VC_FORMATS,}, + .ops = &cq93vc_dai_ops, +}; +EXPORT_SYMBOL_GPL(cq93vc_dai); + +static int cq93vc_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + cq93vc_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + return 0; +} + +static struct snd_soc_codec *cq93vc_codec; + +static int cq93vc_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct device *dev = &pdev->dev; + struct snd_soc_codec *codec; + int ret; + + socdev->card->codec = cq93vc_codec; + codec = socdev->card->codec; + + /* Register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(dev, "%s: failed to create pcms\n", pdev->name); + return ret; + } + + /* Set controls */ + snd_soc_add_controls(codec, cq93vc_snd_controls, + ARRAY_SIZE(cq93vc_snd_controls)); + + /* Off, with power on */ + cq93vc_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + return 0; +} + +static int cq93vc_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_cq93vc = { + .probe = cq93vc_probe, + .remove = cq93vc_remove, + .resume = cq93vc_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_cq93vc); + +static __init int cq93vc_codec_probe(struct platform_device *pdev) +{ + struct davinci_vc *davinci_vc = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret; + + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) { + dev_dbg(davinci_vc->dev, + "could not allocate memory for codec data\n"); + return -ENOMEM; + } + + davinci_vc->cq93vc.codec = codec; + + cq93vc_dai.dev = &pdev->dev; + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + codec->dev = &pdev->dev; + codec->name = "CQ93VC"; + codec->owner = THIS_MODULE; + codec->read = cq93vc_read; + codec->write = cq93vc_write; + codec->set_bias_level = cq93vc_set_bias_level; + codec->dai = &cq93vc_dai; + codec->num_dai = 1; + codec->control_data = davinci_vc; + + cq93vc_codec = codec; + + ret = snd_soc_register_codec(codec); + if (ret) { + dev_err(davinci_vc->dev, "failed to register codec\n"); + goto fail1; + } + + ret = snd_soc_register_dai(&cq93vc_dai); + if (ret) { + dev_err(davinci_vc->dev, "could register dai\n"); + goto fail2; + } + return 0; + +fail2: + snd_soc_unregister_codec(codec); + +fail1: + kfree(codec); + cq93vc_codec = NULL; + + return ret; +} + +static int __devexit cq93vc_codec_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + snd_soc_unregister_dai(&cq93vc_dai); + snd_soc_unregister_codec(&codec); + + kfree(codec); + cq93vc_codec = NULL; + + return 0; +} + +static struct platform_driver cq93vc_codec_driver = { + .driver = { + .name = "cq93vc", + .owner = THIS_MODULE, + }, + .probe = cq93vc_codec_probe, + .remove = __devexit_p(cq93vc_codec_remove), +}; + +static __init int cq93vc_init(void) +{ + return platform_driver_probe(&cq93vc_codec_driver, cq93vc_codec_probe); +} +module_init(cq93vc_init); + +static __exit void cq93vc_exit(void) +{ + platform_driver_unregister(&cq93vc_codec_driver); +} +module_exit(cq93vc_exit); + +MODULE_DESCRIPTION("Texas Instruments DaVinci ASoC CQ0093 Voice Codec Driver"); +MODULE_AUTHOR("Miguel Aguilar"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cq93vc.h b/sound/soc/codecs/cq93vc.h new file mode 100644 index 0000000..845b196 --- /dev/null +++ b/sound/soc/codecs/cq93vc.h @@ -0,0 +1,29 @@ +/* + * ALSA SoC CQ0093 Voice Codec Driver for DaVinci platforms + * + * Copyright (C) 2010 Texas Instruments, Inc + * + * Author: Miguel Aguilar <miguel.aguilar@ridgerun.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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _CQ93VC_H +#define _CQ93VC_H + +extern struct snd_soc_dai cq93vc_dai; +extern struct snd_soc_codec_device soc_codec_dev_cq93vc; + +#endif diff --git a/sound/soc/codecs/cs4270.c b/sound/soc/codecs/cs4270.c index 81a62d1..30d9492 100644 --- a/sound/soc/codecs/cs4270.c +++ b/sound/soc/codecs/cs4270.c @@ -211,7 +211,7 @@ static int cs4270_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 cs4270_private *cs4270 = codec->private_data; + struct cs4270_private *cs4270 = snd_soc_codec_get_drvdata(codec); unsigned int rates = 0; unsigned int rate_min = -1; unsigned int rate_max = 0; @@ -270,7 +270,7 @@ static int cs4270_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int format) { struct snd_soc_codec *codec = codec_dai->codec; - struct cs4270_private *cs4270 = codec->private_data; + struct cs4270_private *cs4270 = snd_soc_codec_get_drvdata(codec); int ret = 0; /* set DAI format */ @@ -412,7 +412,7 @@ static int cs4270_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 cs4270_private *cs4270 = codec->private_data; + struct cs4270_private *cs4270 = snd_soc_codec_get_drvdata(codec); int ret; unsigned int i; unsigned int rate; @@ -491,7 +491,7 @@ static int cs4270_hw_params(struct snd_pcm_substream *substream, static int cs4270_dai_mute(struct snd_soc_dai *dai, int mute) { struct snd_soc_codec *codec = dai->codec; - struct cs4270_private *cs4270 = codec->private_data; + struct cs4270_private *cs4270 = snd_soc_codec_get_drvdata(codec); int reg6; reg6 = snd_soc_read(codec, CS4270_MUTE); @@ -524,7 +524,7 @@ static int cs4270_soc_put_mute(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - struct cs4270_private *cs4270 = codec->private_data; + struct cs4270_private *cs4270 = snd_soc_codec_get_drvdata(codec); int left = !ucontrol->value.integer.value[0]; int right = !ucontrol->value.integer.value[1]; @@ -600,7 +600,7 @@ static int cs4270_probe(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = cs4270_codec; - struct cs4270_private *cs4270 = codec->private_data; + struct cs4270_private *cs4270 = snd_soc_codec_get_drvdata(codec); int i, ret; /* Connect the codec to the socdev. snd_soc_new_pcms() needs this. */ @@ -657,7 +657,7 @@ static int cs4270_remove(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = cs4270_codec; - struct cs4270_private *cs4270 = codec->private_data; + struct cs4270_private *cs4270 = snd_soc_codec_get_drvdata(codec); snd_soc_free_pcms(socdev); regulator_bulk_disable(ARRAY_SIZE(cs4270->supplies), cs4270->supplies); @@ -730,7 +730,7 @@ static int cs4270_i2c_probe(struct i2c_client *i2c_client, codec->owner = THIS_MODULE; codec->dai = &cs4270_dai; codec->num_dai = 1; - codec->private_data = cs4270; + snd_soc_codec_set_drvdata(codec, cs4270); codec->control_data = i2c_client; codec->read = cs4270_read_reg_cache; codec->write = cs4270_i2c_write; @@ -843,7 +843,7 @@ MODULE_DEVICE_TABLE(i2c, cs4270_id); static int cs4270_soc_suspend(struct platform_device *pdev, pm_message_t mesg) { struct snd_soc_codec *codec = cs4270_codec; - struct cs4270_private *cs4270 = codec->private_data; + struct cs4270_private *cs4270 = snd_soc_codec_get_drvdata(codec); int reg, ret; reg = snd_soc_read(codec, CS4270_PWRCTL) | CS4270_PWRCTL_PDN_ALL; @@ -863,7 +863,7 @@ static int cs4270_soc_suspend(struct platform_device *pdev, pm_message_t mesg) static int cs4270_soc_resume(struct platform_device *pdev) { struct snd_soc_codec *codec = cs4270_codec; - struct cs4270_private *cs4270 = codec->private_data; + struct cs4270_private *cs4270 = snd_soc_codec_get_drvdata(codec); struct i2c_client *i2c_client = codec->control_data; int reg; diff --git a/sound/soc/codecs/cx20442.c b/sound/soc/codecs/cx20442.c index 9f169c4..f07a415 100644 --- a/sound/soc/codecs/cx20442.c +++ b/sound/soc/codecs/cx20442.c @@ -387,7 +387,7 @@ static int cx20442_register(struct cx20442_priv *cx20442) codec->name = "CX20442"; codec->owner = THIS_MODULE; - codec->private_data = cx20442; + snd_soc_codec_set_drvdata(codec, cx20442); codec->dai = &cx20442_dai; codec->num_dai = 1; diff --git a/sound/soc/codecs/da7210.c b/sound/soc/codecs/da7210.c index 366daf1..75af2d6 100644 --- a/sound/soc/codecs/da7210.c +++ b/sound/soc/codecs/da7210.c @@ -56,8 +56,14 @@ #define DA7210_DAI_SRC_SEL 0x25 #define DA7210_DAI_CFG1 0x26 #define DA7210_DAI_CFG3 0x28 +#define DA7210_PLL_DIV1 0x29 +#define DA7210_PLL_DIV2 0x2A #define DA7210_PLL_DIV3 0x2B #define DA7210_PLL 0x2C +#define DA7210_A_HID_UNLOCK 0x8A +#define DA7210_A_TEST_UNLOCK 0x8B +#define DA7210_A_PLL1 0x90 +#define DA7210_A_CP_MODE 0xA7 /* STARTUP1 bit fields */ #define DA7210_SC_MST_EN (1 << 0) @@ -75,15 +81,14 @@ /* INMIX_R bit fields */ #define DA7210_IN_R_EN (1 << 7) -/* ADC_HPF bit fields */ -#define DA7210_ADC_VOICE_EN (1 << 7) - /* ADC bit fields */ #define DA7210_ADC_L_EN (1 << 3) #define DA7210_ADC_R_EN (1 << 7) -/* DAC_HPF fields */ -#define DA7210_DAC_VOICE_EN (1 << 7) +/* DAC/ADC HPF fields */ +#define DA7210_VOICE_F0_MASK (0x7 << 4) +#define DA7210_VOICE_F0_25 (1 << 4) +#define DA7210_VOICE_EN (1 << 7) /* DAC_SEL bit fields */ #define DA7210_DAC_L_SRC_DAI_L (4 << 0) @@ -124,7 +129,19 @@ #define DA7210_PLL_BYP (1 << 6) /* PLL bit fields */ -#define DA7210_PLL_FS_48000 (11 << 0) +#define DA7210_PLL_FS_MASK (0xF << 0) +#define DA7210_PLL_FS_8000 (0x1 << 0) +#define DA7210_PLL_FS_11025 (0x2 << 0) +#define DA7210_PLL_FS_12000 (0x3 << 0) +#define DA7210_PLL_FS_16000 (0x5 << 0) +#define DA7210_PLL_FS_22050 (0x6 << 0) +#define DA7210_PLL_FS_24000 (0x7 << 0) +#define DA7210_PLL_FS_32000 (0x9 << 0) +#define DA7210_PLL_FS_44100 (0xA << 0) +#define DA7210_PLL_FS_48000 (0xB << 0) +#define DA7210_PLL_FS_88200 (0xE << 0) +#define DA7210_PLL_FS_96000 (0xF << 0) +#define DA7210_PLL_EN (0x1 << 7) #define DA7210_VERSION "0.0.1" @@ -165,7 +182,7 @@ static const u8 da7210_reg[] = { static inline u32 da7210_read_reg_cache(struct snd_soc_codec *codec, u32 reg) { u8 *cache = codec->reg_cache; - BUG_ON(reg > ARRAY_SIZE(da7210_reg)); + BUG_ON(reg >= ARRAY_SIZE(da7210_reg)); return cache[reg]; } @@ -242,7 +259,8 @@ static int da7210_hw_params(struct snd_pcm_substream *substream, struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_codec *codec = socdev->card->codec; u32 dai_cfg1; - u32 reg, mask; + u32 hpf_reg, hpf_mask, hpf_value; + u32 fs, bypass; /* set DAI source to Left and Right ADC */ da7210_write(codec, DA7210_DAI_SRC_SEL, @@ -266,25 +284,84 @@ static int da7210_hw_params(struct snd_pcm_substream *substream, da7210_write(codec, DA7210_DAI_CFG1, dai_cfg1); - /* FIXME - * - * It support 48K only now - */ + hpf_reg = (SNDRV_PCM_STREAM_PLAYBACK == substream->stream) ? + DA7210_DAC_HPF : DA7210_ADC_HPF; + switch (params_rate(params)) { + case 8000: + fs = DA7210_PLL_FS_8000; + hpf_mask = DA7210_VOICE_F0_MASK | DA7210_VOICE_EN; + hpf_value = DA7210_VOICE_F0_25 | DA7210_VOICE_EN; + bypass = DA7210_PLL_BYP; + break; + case 11025: + fs = DA7210_PLL_FS_11025; + hpf_mask = DA7210_VOICE_F0_MASK | DA7210_VOICE_EN; + hpf_value = DA7210_VOICE_F0_25 | DA7210_VOICE_EN; + bypass = 0; + break; + case 12000: + fs = DA7210_PLL_FS_12000; + hpf_mask = DA7210_VOICE_F0_MASK | DA7210_VOICE_EN; + hpf_value = DA7210_VOICE_F0_25 | DA7210_VOICE_EN; + bypass = DA7210_PLL_BYP; + break; + case 16000: + fs = DA7210_PLL_FS_16000; + hpf_mask = DA7210_VOICE_F0_MASK | DA7210_VOICE_EN; + hpf_value = DA7210_VOICE_F0_25 | DA7210_VOICE_EN; + bypass = DA7210_PLL_BYP; + break; + case 22050: + fs = DA7210_PLL_FS_22050; + hpf_mask = DA7210_VOICE_EN; + hpf_value = 0; + bypass = 0; + break; + case 32000: + fs = DA7210_PLL_FS_32000; + hpf_mask = DA7210_VOICE_EN; + hpf_value = 0; + bypass = DA7210_PLL_BYP; + break; + case 44100: + fs = DA7210_PLL_FS_44100; + hpf_mask = DA7210_VOICE_EN; + hpf_value = 0; + bypass = 0; + break; case 48000: - if (SNDRV_PCM_STREAM_PLAYBACK == substream->stream) { - reg = DA7210_DAC_HPF; - mask = DA7210_DAC_VOICE_EN; - } else { - reg = DA7210_ADC_HPF; - mask = DA7210_ADC_VOICE_EN; - } + fs = DA7210_PLL_FS_48000; + hpf_mask = DA7210_VOICE_EN; + hpf_value = 0; + bypass = DA7210_PLL_BYP; + break; + case 88200: + fs = DA7210_PLL_FS_88200; + hpf_mask = DA7210_VOICE_EN; + hpf_value = 0; + bypass = 0; + break; + case 96000: + fs = DA7210_PLL_FS_96000; + hpf_mask = DA7210_VOICE_EN; + hpf_value = 0; + bypass = DA7210_PLL_BYP; break; default: return -EINVAL; } - snd_soc_update_bits(codec, reg, mask, 0); + /* Disable active mode */ + snd_soc_update_bits(codec, DA7210_STARTUP1, DA7210_SC_MST_EN, 0); + + snd_soc_update_bits(codec, hpf_reg, hpf_mask, hpf_value); + snd_soc_update_bits(codec, DA7210_PLL, DA7210_PLL_FS_MASK, fs); + snd_soc_update_bits(codec, DA7210_PLL_DIV3, DA7210_PLL_BYP, bypass); + + /* Enable active mode */ + snd_soc_update_bits(codec, DA7210_STARTUP1, + DA7210_SC_MST_EN, DA7210_SC_MST_EN); return 0; } @@ -362,6 +439,7 @@ struct snd_soc_dai da7210_dai = { .formats = DA7210_FORMATS, }, .ops = &da7210_dai_ops, + .symmetric_rates = 1, }; EXPORT_SYMBOL_GPL(da7210_dai); @@ -383,7 +461,7 @@ static int da7210_init(struct da7210_priv *da7210) INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); - codec->private_data = da7210; + snd_soc_codec_set_drvdata(codec, da7210); codec->name = "DA7210"; codec->owner = THIS_MODULE; codec->read = da7210_read; @@ -416,9 +494,23 @@ static int da7210_init(struct da7210_priv *da7210) /* FIXME * * This driver use fixed value here + * And below settings expects MCLK = 12.288MHz + * + * When you select different MCLK, please check... + * DA7210_PLL_DIV1 val + * DA7210_PLL_DIV2 val + * DA7210_PLL_DIV3 val + * DA7210_PLL_DIV3 :: DA7210_MCLK_RANGExxx */ /* + * make sure that DA7210 use bypass mode before start up + */ + da7210_write(codec, DA7210_STARTUP1, 0); + da7210_write(codec, DA7210_PLL_DIV3, + DA7210_MCLK_RANGE_10_20_MHZ | DA7210_PLL_BYP); + + /* * ADC settings */ @@ -454,9 +546,28 @@ static int da7210_init(struct da7210_priv *da7210) /* Diable PLL and bypass it */ da7210_write(codec, DA7210_PLL, DA7210_PLL_FS_48000); - /* Bypass PLL and set MCLK freq rang to 10-20MHz */ - da7210_write(codec, DA7210_PLL_DIV3, + /* + * If 48kHz sound came, it use bypass mode, + * and when it is 44.1kHz, it use PLL. + * + * This time, this driver sets PLL always ON + * and controls bypass/PLL mode by switching + * DA7210_PLL_DIV3 :: DA7210_PLL_BYP bit. + * see da7210_hw_params + */ + da7210_write(codec, DA7210_PLL_DIV1, 0xE5); /* MCLK = 12.288MHz */ + da7210_write(codec, DA7210_PLL_DIV2, 0x99); + da7210_write(codec, DA7210_PLL_DIV3, 0x0A | DA7210_MCLK_RANGE_10_20_MHZ | DA7210_PLL_BYP); + snd_soc_update_bits(codec, DA7210_PLL, DA7210_PLL_EN, DA7210_PLL_EN); + + /* As suggested by Dialog */ + da7210_write(codec, DA7210_A_HID_UNLOCK, 0x8B); /* unlock */ + da7210_write(codec, DA7210_A_TEST_UNLOCK, 0xB4); + da7210_write(codec, DA7210_A_PLL1, 0x01); + da7210_write(codec, DA7210_A_CP_MODE, 0x7C); + da7210_write(codec, DA7210_A_HID_UNLOCK, 0x00); /* re-lock */ + da7210_write(codec, DA7210_A_TEST_UNLOCK, 0x00); /* Activate all enabled subsystem */ da7210_write(codec, DA7210_STARTUP1, DA7210_SC_MST_EN); diff --git a/sound/soc/codecs/ssm2602.c b/sound/soc/codecs/ssm2602.c index 29d0906..b47ed4f 100644 --- a/sound/soc/codecs/ssm2602.c +++ b/sound/soc/codecs/ssm2602.c @@ -140,6 +140,7 @@ SOC_DOUBLE_R("Capture Volume", SSM2602_LINVOL, SSM2602_RINVOL, 0, 31, 0), SOC_DOUBLE_R("Capture Switch", SSM2602_LINVOL, SSM2602_RINVOL, 7, 1, 1), SOC_SINGLE("Mic Boost (+20dB)", SSM2602_APANA, 0, 1, 0), +SOC_SINGLE("Mic Boost2 (+20dB)", SSM2602_APANA, 7, 1, 0), SOC_SINGLE("Mic Switch", SSM2602_APANA, 1, 1, 1), SOC_SINGLE("Sidetone Playback Volume", SSM2602_APANA, 6, 3, 1), @@ -277,7 +278,7 @@ static int ssm2602_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 ssm2602_priv *ssm2602 = codec->private_data; + struct ssm2602_priv *ssm2602 = snd_soc_codec_get_drvdata(codec); struct i2c_client *i2c = codec->control_data; u16 iface = ssm2602_read_reg_cache(codec, SSM2602_IFACE) & 0xfff3; int i = get_coeff(ssm2602->sysclk, params_rate(params)); @@ -322,7 +323,7 @@ static int ssm2602_startup(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 ssm2602_priv *ssm2602 = codec->private_data; + struct ssm2602_priv *ssm2602 = snd_soc_codec_get_drvdata(codec); struct i2c_client *i2c = codec->control_data; struct snd_pcm_runtime *master_runtime; @@ -373,7 +374,7 @@ static void ssm2602_shutdown(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 ssm2602_priv *ssm2602 = codec->private_data; + struct ssm2602_priv *ssm2602 = snd_soc_codec_get_drvdata(codec); /* deactivate */ if (!codec->active) @@ -401,7 +402,7 @@ static int ssm2602_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 ssm2602_priv *ssm2602 = codec->private_data; + struct ssm2602_priv *ssm2602 = snd_soc_codec_get_drvdata(codec); switch (freq) { case 11289600: case 12000000: @@ -559,7 +560,6 @@ static int ssm2602_resume(struct platform_device *pdev) codec->hw_write(codec->control_data, data, 2); } ssm2602_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - ssm2602_set_bias_level(codec, codec->suspend_bias_level); return 0; } @@ -605,8 +605,7 @@ static int ssm2602_init(struct snd_soc_device *socdev) reg = ssm2602_read_reg_cache(codec, SSM2602_ROUT1V); ssm2602_write(codec, SSM2602_ROUT1V, reg | ROUT1V_RLHP_BOTH); /*select Line in as default input*/ - ssm2602_write(codec, SSM2602_APANA, - APANA_ENABLE_MIC_BOOST2 | APANA_SELECT_DAC | + ssm2602_write(codec, SSM2602_APANA, APANA_SELECT_DAC | APANA_ENABLE_MIC_BOOST); ssm2602_write(codec, SSM2602_PWR, 0); @@ -727,7 +726,7 @@ static int ssm2602_probe(struct platform_device *pdev) return -ENOMEM; } - codec->private_data = ssm2602; + snd_soc_codec_set_drvdata(codec, ssm2602); socdev->card->codec = codec; mutex_init(&codec->mutex); INIT_LIST_HEAD(&codec->dapm_widgets); @@ -760,7 +759,7 @@ static int ssm2602_remove(struct platform_device *pdev) i2c_unregister_device(codec->control_data); i2c_del_driver(&ssm2602_i2c_driver); #endif - kfree(codec->private_data); + kfree(snd_soc_codec_get_drvdata(codec)); kfree(codec); return 0; diff --git a/sound/soc/codecs/stac9766.c b/sound/soc/codecs/stac9766.c index 3293629..ee86568 100644 --- a/sound/soc/codecs/stac9766.c +++ b/sound/soc/codecs/stac9766.c @@ -289,9 +289,6 @@ reset: } stac9766_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - if (codec->suspend_bias_level == SND_SOC_BIAS_ON) - stac9766_set_bias_level(codec, SND_SOC_BIAS_ON); - return 0; } @@ -410,7 +407,7 @@ reset_err: pcm_err: snd_soc_free_ac97_codec(codec); codec_err: - kfree(codec->private_data); + kfree(snd_soc_codec_get_drvdata(codec)); cache_err: kfree(socdev->card->codec); socdev->card->codec = NULL; diff --git a/sound/soc/codecs/tlv320aic23.c b/sound/soc/codecs/tlv320aic23.c index 776b79c..b0bae35 100644 --- a/sound/soc/codecs/tlv320aic23.c +++ b/sound/soc/codecs/tlv320aic23.c @@ -634,7 +634,6 @@ static int tlv320aic23_resume(struct platform_device *pdev) } tlv320aic23_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - tlv320aic23_set_bias_level(codec, codec->suspend_bias_level); return 0; } diff --git a/sound/soc/codecs/tlv320aic26.c b/sound/soc/codecs/tlv320aic26.c index b5b7d6a..f0e00fd 100644 --- a/sound/soc/codecs/tlv320aic26.c +++ b/sound/soc/codecs/tlv320aic26.c @@ -49,7 +49,7 @@ struct aic26 { static unsigned int aic26_reg_read(struct snd_soc_codec *codec, unsigned int reg) { - struct aic26 *aic26 = codec->private_data; + struct aic26 *aic26 = snd_soc_codec_get_drvdata(codec); u16 *cache = codec->reg_cache; u16 cmd, value; u8 buffer[2]; @@ -93,7 +93,7 @@ static unsigned int aic26_reg_read_cache(struct snd_soc_codec *codec, static int aic26_reg_write(struct snd_soc_codec *codec, unsigned int reg, unsigned int value) { - struct aic26 *aic26 = codec->private_data; + struct aic26 *aic26 = snd_soc_codec_get_drvdata(codec); u16 *cache = codec->reg_cache; u16 cmd; u8 buffer[4]; @@ -132,7 +132,7 @@ static int aic26_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 aic26 *aic26 = codec->private_data; + struct aic26 *aic26 = snd_soc_codec_get_drvdata(codec); int fsref, divisor, wlen, pval, jval, dval, qval; u16 reg; @@ -199,7 +199,7 @@ static int aic26_hw_params(struct snd_pcm_substream *substream, static int aic26_mute(struct snd_soc_dai *dai, int mute) { struct snd_soc_codec *codec = dai->codec; - struct aic26 *aic26 = codec->private_data; + struct aic26 *aic26 = snd_soc_codec_get_drvdata(codec); u16 reg = aic26_reg_read_cache(codec, AIC26_REG_DAC_GAIN); dev_dbg(&aic26->spi->dev, "aic26_mute(dai=%p, mute=%i)\n", @@ -218,7 +218,7 @@ static int aic26_set_sysclk(struct snd_soc_dai *codec_dai, int clk_id, unsigned int freq, int dir) { struct snd_soc_codec *codec = codec_dai->codec; - struct aic26 *aic26 = codec->private_data; + struct aic26 *aic26 = snd_soc_codec_get_drvdata(codec); dev_dbg(&aic26->spi->dev, "aic26_set_sysclk(dai=%p, clk_id==%i," " freq=%i, dir=%i)\n", @@ -235,7 +235,7 @@ static int aic26_set_sysclk(struct snd_soc_dai *codec_dai, static int aic26_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { struct snd_soc_codec *codec = codec_dai->codec; - struct aic26 *aic26 = codec->private_data; + struct aic26 *aic26 = snd_soc_codec_get_drvdata(codec); dev_dbg(&aic26->spi->dev, "aic26_set_fmt(dai=%p, fmt==%i)\n", codec_dai, fmt); @@ -431,7 +431,7 @@ static int aic26_spi_probe(struct spi_device *spi) /* Setup what we can in the codec structure so that the register * access functions will work as expected. More will be filled * out when it is probed by the SoC CODEC part of this driver */ - aic26->codec.private_data = aic26; + snd_soc_codec_set_drvdata(&aic26->codec, aic26); aic26->codec.name = "aic26"; aic26->codec.owner = THIS_MODULE; aic26->codec.dai = &aic26_dai; diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c index 4a6d56c..71a6990 100644 --- a/sound/soc/codecs/tlv320aic3x.c +++ b/sound/soc/codecs/tlv320aic3x.c @@ -38,6 +38,8 @@ #include <linux/delay.h> #include <linux/pm.h> #include <linux/i2c.h> +#include <linux/gpio.h> +#include <linux/regulator/consumer.h> #include <linux/platform_device.h> #include <linux/slab.h> #include <sound/core.h> @@ -47,16 +49,25 @@ #include <sound/soc-dapm.h> #include <sound/initval.h> #include <sound/tlv.h> +#include <sound/tlv320aic3x.h> #include "tlv320aic3x.h" -#define AIC3X_VERSION "0.2" +#define AIC3X_NUM_SUPPLIES 4 +static const char *aic3x_supply_names[AIC3X_NUM_SUPPLIES] = { + "IOVDD", /* I/O Voltage */ + "DVDD", /* Digital Core Voltage */ + "AVDD", /* Analog DAC Voltage */ + "DRVDD", /* ADC Analog and Output Driver Voltage */ +}; /* codec private data */ struct aic3x_priv { struct snd_soc_codec codec; + struct regulator_bulk_data supplies[AIC3X_NUM_SUPPLIES]; unsigned int sysclk; int master; + int gpio_reset; }; /* @@ -764,7 +775,7 @@ static int aic3x_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 aic3x_priv *aic3x = codec->private_data; + struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec); int codec_clk = 0, bypass_pll = 0, fsref, last_clk = 0; u8 data, j, r, p, pll_q, pll_p = 1, pll_r = 1, pll_j = 1; u16 d, pll_d = 1; @@ -931,7 +942,7 @@ static int aic3x_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 aic3x_priv *aic3x = codec->private_data; + struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec); aic3x->sysclk = freq; return 0; @@ -941,7 +952,7 @@ static int aic3x_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { struct snd_soc_codec *codec = codec_dai->codec; - struct aic3x_priv *aic3x = codec->private_data; + struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec); u8 iface_areg, iface_breg; int delay = 0; @@ -995,12 +1006,13 @@ static int aic3x_set_dai_fmt(struct snd_soc_dai *codec_dai, static int aic3x_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { - struct aic3x_priv *aic3x = codec->private_data; + struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec); u8 reg; switch (level) { case SND_SOC_BIAS_ON: - /* all power is driven by DAPM system */ + break; + case SND_SOC_BIAS_PREPARE: if (aic3x->master) { /* enable pll */ reg = aic3x_read_reg_cache(codec, AIC3X_PLL_PROGA_REG); @@ -1008,48 +1020,9 @@ static int aic3x_set_bias_level(struct snd_soc_codec *codec, reg | PLL_ENABLE); } break; - case SND_SOC_BIAS_PREPARE: - break; case SND_SOC_BIAS_STANDBY: - /* - * all power is driven by DAPM system, - * so output power is safe if bypass was set - */ - if (aic3x->master) { - /* disable pll */ - reg = aic3x_read_reg_cache(codec, AIC3X_PLL_PROGA_REG); - aic3x_write(codec, AIC3X_PLL_PROGA_REG, - reg & ~PLL_ENABLE); - } - break; + /* fall through and disable pll */ case SND_SOC_BIAS_OFF: - /* force all power off */ - reg = aic3x_read_reg_cache(codec, LINE1L_2_LADC_CTRL); - aic3x_write(codec, LINE1L_2_LADC_CTRL, reg & ~LADC_PWR_ON); - reg = aic3x_read_reg_cache(codec, LINE1R_2_RADC_CTRL); - aic3x_write(codec, LINE1R_2_RADC_CTRL, reg & ~RADC_PWR_ON); - - reg = aic3x_read_reg_cache(codec, DAC_PWR); - aic3x_write(codec, DAC_PWR, reg & ~(LDAC_PWR_ON | RDAC_PWR_ON)); - - reg = aic3x_read_reg_cache(codec, HPLOUT_CTRL); - aic3x_write(codec, HPLOUT_CTRL, reg & ~HPLOUT_PWR_ON); - reg = aic3x_read_reg_cache(codec, HPROUT_CTRL); - aic3x_write(codec, HPROUT_CTRL, reg & ~HPROUT_PWR_ON); - - reg = aic3x_read_reg_cache(codec, HPLCOM_CTRL); - aic3x_write(codec, HPLCOM_CTRL, reg & ~HPLCOM_PWR_ON); - reg = aic3x_read_reg_cache(codec, HPRCOM_CTRL); - aic3x_write(codec, HPRCOM_CTRL, reg & ~HPRCOM_PWR_ON); - - reg = aic3x_read_reg_cache(codec, MONOLOPM_CTRL); - aic3x_write(codec, MONOLOPM_CTRL, reg & ~MONOLOPM_PWR_ON); - - reg = aic3x_read_reg_cache(codec, LLOPM_CTRL); - aic3x_write(codec, LLOPM_CTRL, reg & ~LLOPM_PWR_ON); - reg = aic3x_read_reg_cache(codec, RLOPM_CTRL); - aic3x_write(codec, RLOPM_CTRL, reg & ~RLOPM_PWR_ON); - if (aic3x->master) { /* disable pll */ reg = aic3x_read_reg_cache(codec, AIC3X_PLL_PROGA_REG); @@ -1171,7 +1144,7 @@ static int aic3x_resume(struct platform_device *pdev) codec->hw_write(codec->control_data, data, 2); } - aic3x_set_bias_level(codec, codec->suspend_bias_level); + aic3x_set_bias_level(codec, SND_SOC_BIAS_STANDBY); return 0; } @@ -1309,6 +1282,13 @@ static int aic3x_unregister(struct aic3x_priv *aic3x) snd_soc_unregister_dai(&aic3x_dai); snd_soc_unregister_codec(&aic3x->codec); + if (aic3x->gpio_reset >= 0) { + gpio_set_value(aic3x->gpio_reset, 0); + gpio_free(aic3x->gpio_reset); + } + regulator_bulk_disable(ARRAY_SIZE(aic3x->supplies), aic3x->supplies); + regulator_bulk_free(ARRAY_SIZE(aic3x->supplies), aic3x->supplies); + kfree(aic3x); aic3x_codec = NULL; @@ -1330,6 +1310,8 @@ static int aic3x_i2c_probe(struct i2c_client *i2c, { struct snd_soc_codec *codec; struct aic3x_priv *aic3x; + struct aic3x_pdata *pdata = i2c->dev.platform_data; + int ret, i; aic3x = kzalloc(sizeof(struct aic3x_priv), GFP_KERNEL); if (aic3x == NULL) { @@ -1339,13 +1321,53 @@ static int aic3x_i2c_probe(struct i2c_client *i2c, codec = &aic3x->codec; codec->dev = &i2c->dev; - codec->private_data = aic3x; + snd_soc_codec_set_drvdata(codec, aic3x); codec->control_data = i2c; codec->hw_write = (hw_write_t) i2c_master_send; i2c_set_clientdata(i2c, aic3x); + aic3x->gpio_reset = -1; + if (pdata && pdata->gpio_reset >= 0) { + ret = gpio_request(pdata->gpio_reset, "tlv320aic3x reset"); + if (ret != 0) + goto err_gpio; + aic3x->gpio_reset = pdata->gpio_reset; + gpio_direction_output(aic3x->gpio_reset, 0); + } + + for (i = 0; i < ARRAY_SIZE(aic3x->supplies); i++) + aic3x->supplies[i].supply = aic3x_supply_names[i]; + + ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(aic3x->supplies), + aic3x->supplies); + if (ret != 0) { + dev_err(codec->dev, "Failed to request supplies: %d\n", ret); + goto err_get; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(aic3x->supplies), + aic3x->supplies); + if (ret != 0) { + dev_err(codec->dev, "Failed to enable supplies: %d\n", ret); + goto err_enable; + } + + if (aic3x->gpio_reset >= 0) { + udelay(1); + gpio_set_value(aic3x->gpio_reset, 1); + } + return aic3x_register(codec); + +err_enable: + regulator_bulk_free(ARRAY_SIZE(aic3x->supplies), aic3x->supplies); +err_get: + if (aic3x->gpio_reset >= 0) + gpio_free(aic3x->gpio_reset); +err_gpio: + kfree(aic3x); + return ret; } static int aic3x_i2c_remove(struct i2c_client *client) diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c index d1e0e81..65adc77 100644 --- a/sound/soc/codecs/tlv320dac33.c +++ b/sound/soc/codecs/tlv320dac33.c @@ -51,6 +51,20 @@ #define LATENCY_TIME_MS 20 +#define MODE7_LTHR 10 +#define MODE7_UTHR (DAC33_BUFFER_SIZE_SAMPLES - 10) + +#define BURST_BASEFREQ_HZ 49152000 + +#define SAMPLES_TO_US(rate, samples) \ + (1000000000 / ((rate * 1000) / samples)) + +#define US_TO_SAMPLES(rate, us) \ + (rate / (1000000 / us)) + +static void dac33_calculate_times(struct snd_pcm_substream *substream); +static int dac33_prepare_chip(struct snd_pcm_substream *substream); + static struct snd_soc_codec *tlv320dac33_codec; enum dac33_state { @@ -80,6 +94,7 @@ struct tlv320dac33_priv { struct work_struct work; struct snd_soc_codec codec; struct regulator_bulk_data supplies[DAC33_NUM_SUPPLIES]; + struct snd_pcm_substream *substream; int power_gpio; int chip_power; int irq; @@ -93,6 +108,17 @@ struct tlv320dac33_priv { enum dac33_fifo_modes fifo_mode;/* FIFO mode selection */ unsigned int nsample; /* burst read amount from host */ u8 burst_bclkdiv; /* BCLK divider value in burst mode */ + unsigned int burst_rate; /* Interface speed in Burst modes */ + + int keep_bclk; /* Keep the BCLK continuously running + * in FIFO modes */ + spinlock_t lock; + unsigned long long t_stamp1; /* Time stamp for FIFO modes to */ + unsigned long long t_stamp2; /* calculate the FIFO caused delay */ + + unsigned int mode1_us_burst; /* Time to burst read n number of + * samples */ + unsigned int mode7_us_to_lthr; /* Time to reach lthr from uthr */ enum dac33_state state; }; @@ -166,7 +192,7 @@ static inline void dac33_write_reg_cache(struct snd_soc_codec *codec, static int dac33_read(struct snd_soc_codec *codec, unsigned int reg, u8 *value) { - struct tlv320dac33_priv *dac33 = codec->private_data; + struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); int val; *value = reg & 0xff; @@ -191,7 +217,7 @@ static int dac33_read(struct snd_soc_codec *codec, unsigned int reg, static int dac33_write(struct snd_soc_codec *codec, unsigned int reg, unsigned int value) { - struct tlv320dac33_priv *dac33 = codec->private_data; + struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); u8 data[2]; int ret = 0; @@ -218,7 +244,7 @@ static int dac33_write(struct snd_soc_codec *codec, unsigned int reg, static int dac33_write_locked(struct snd_soc_codec *codec, unsigned int reg, unsigned int value) { - struct tlv320dac33_priv *dac33 = codec->private_data; + struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); int ret; mutex_lock(&dac33->mutex); @@ -232,7 +258,7 @@ static int dac33_write_locked(struct snd_soc_codec *codec, unsigned int reg, static int dac33_write16(struct snd_soc_codec *codec, unsigned int reg, unsigned int value) { - struct tlv320dac33_priv *dac33 = codec->private_data; + struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); u8 data[3]; int ret = 0; @@ -262,45 +288,47 @@ static int dac33_write16(struct snd_soc_codec *codec, unsigned int reg, return ret; } -static void dac33_restore_regs(struct snd_soc_codec *codec) +static void dac33_init_chip(struct snd_soc_codec *codec) { - struct tlv320dac33_priv *dac33 = codec->private_data; - u8 *cache = codec->reg_cache; - u8 data[2]; - int i, ret; + struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); - if (!dac33->chip_power) + if (unlikely(!dac33->chip_power)) return; - for (i = DAC33_PWR_CTRL; i <= DAC33_INTP_CTRL_B; i++) { - data[0] = i; - data[1] = cache[i]; - /* Skip the read only registers */ - if ((i >= DAC33_INT_OSC_STATUS && - i <= DAC33_INT_OSC_FREQ_RAT_READ_B) || - (i >= DAC33_FIFO_WPTR_MSB && i <= DAC33_FIFO_IRQ_FLAG) || - i == DAC33_DAC_STATUS_FLAGS || - i == DAC33_SRC_EST_REF_CLK_RATIO_A || - i == DAC33_SRC_EST_REF_CLK_RATIO_B) - continue; - ret = codec->hw_write(codec->control_data, data, 2); - if (ret != 2) - dev_err(codec->dev, "Write failed (%d)\n", ret); - } - for (i = DAC33_LDAC_PWR_CTRL; i <= DAC33_LINEL_TO_LLO_VOL; i++) { - data[0] = i; - data[1] = cache[i]; - ret = codec->hw_write(codec->control_data, data, 2); - if (ret != 2) - dev_err(codec->dev, "Write failed (%d)\n", ret); - } - for (i = DAC33_LINER_TO_RLO_VOL; i <= DAC33_OSC_TRIM; i++) { - data[0] = i; - data[1] = cache[i]; - ret = codec->hw_write(codec->control_data, data, 2); - if (ret != 2) - dev_err(codec->dev, "Write failed (%d)\n", ret); - } + /* 44-46: DAC Control Registers */ + /* A : DAC sample rate Fsref/1.5 */ + dac33_write(codec, DAC33_DAC_CTRL_A, DAC33_DACRATE(0)); + /* B : DAC src=normal, not muted */ + dac33_write(codec, DAC33_DAC_CTRL_B, DAC33_DACSRCR_RIGHT | + DAC33_DACSRCL_LEFT); + /* C : (defaults) */ + dac33_write(codec, DAC33_DAC_CTRL_C, 0x00); + + /* 73 : volume soft stepping control, + clock source = internal osc (?) */ + dac33_write(codec, DAC33_ANA_VOL_SOFT_STEP_CTRL, DAC33_VOLCLKEN); + + dac33_write(codec, DAC33_PWR_CTRL, DAC33_PDNALLB); + + /* Restore only selected registers (gains mostly) */ + dac33_write(codec, DAC33_LDAC_DIG_VOL_CTRL, + dac33_read_reg_cache(codec, DAC33_LDAC_DIG_VOL_CTRL)); + dac33_write(codec, DAC33_RDAC_DIG_VOL_CTRL, + dac33_read_reg_cache(codec, DAC33_RDAC_DIG_VOL_CTRL)); + + dac33_write(codec, DAC33_LINEL_TO_LLO_VOL, + dac33_read_reg_cache(codec, DAC33_LINEL_TO_LLO_VOL)); + dac33_write(codec, DAC33_LINER_TO_RLO_VOL, + dac33_read_reg_cache(codec, DAC33_LINER_TO_RLO_VOL)); +} + +static inline void dac33_read_id(struct snd_soc_codec *codec) +{ + u8 reg; + + dac33_read(codec, DAC33_DEVICE_ID_MSB, ®); + dac33_read(codec, DAC33_DEVICE_ID_LSB, ®); + dac33_read(codec, DAC33_DEVICE_REV_ID, ®); } static inline void dac33_soft_power(struct snd_soc_codec *codec, int power) @@ -311,16 +339,25 @@ static inline void dac33_soft_power(struct snd_soc_codec *codec, int power) if (power) reg |= DAC33_PDNALLB; else - reg &= ~DAC33_PDNALLB; + reg &= ~(DAC33_PDNALLB | DAC33_OSCPDNB | + DAC33_DACRPDNB | DAC33_DACLPDNB); dac33_write(codec, DAC33_PWR_CTRL, reg); } static int dac33_hard_power(struct snd_soc_codec *codec, int power) { - struct tlv320dac33_priv *dac33 = codec->private_data; - int ret; + struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); + int ret = 0; mutex_lock(&dac33->mutex); + + /* Safety check */ + if (unlikely(power == dac33->chip_power)) { + dev_dbg(codec->dev, "Trying to set the same power state: %s\n", + power ? "ON" : "OFF"); + goto exit; + } + if (power) { ret = regulator_bulk_enable(ARRAY_SIZE(dac33->supplies), dac33->supplies); @@ -334,11 +371,6 @@ static int dac33_hard_power(struct snd_soc_codec *codec, int power) gpio_set_value(dac33->power_gpio, 1); dac33->chip_power = 1; - - /* Restore registers */ - dac33_restore_regs(codec); - - dac33_soft_power(codec, 1); } else { dac33_soft_power(codec, 0); if (dac33->power_gpio >= 0) @@ -360,11 +392,27 @@ exit: return ret; } +static int playback_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(w->codec); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + if (likely(dac33->substream)) { + dac33_calculate_times(dac33->substream); + dac33_prepare_chip(dac33->substream); + } + break; + } + return 0; +} + static int dac33_get_nsample(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - struct tlv320dac33_priv *dac33 = codec->private_data; + struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); ucontrol->value.integer.value[0] = dac33->nsample; @@ -375,17 +423,21 @@ static int dac33_set_nsample(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - struct tlv320dac33_priv *dac33 = codec->private_data; + struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); int ret = 0; if (dac33->nsample == ucontrol->value.integer.value[0]) return 0; if (ucontrol->value.integer.value[0] < dac33->nsample_min || - ucontrol->value.integer.value[0] > dac33->nsample_max) + ucontrol->value.integer.value[0] > dac33->nsample_max) { ret = -EINVAL; - else + } else { dac33->nsample = ucontrol->value.integer.value[0]; + /* Re calculate the burst time */ + dac33->mode1_us_burst = SAMPLES_TO_US(dac33->burst_rate, + dac33->nsample); + } return ret; } @@ -394,7 +446,7 @@ static int dac33_get_fifo_mode(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - struct tlv320dac33_priv *dac33 = codec->private_data; + struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); ucontrol->value.integer.value[0] = dac33->fifo_mode; @@ -405,7 +457,7 @@ static int dac33_set_fifo_mode(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - struct tlv320dac33_priv *dac33 = codec->private_data; + struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); int ret = 0; if (dac33->fifo_mode == ucontrol->value.integer.value[0]) @@ -485,6 +537,8 @@ static const struct snd_soc_dapm_widget dac33_dapm_widgets[] = { DAC33_OUT_AMP_PWR_CTRL, 6, 3, 3, 0), SND_SOC_DAPM_REG(snd_soc_dapm_mixer, "Output Right Amp Power", DAC33_OUT_AMP_PWR_CTRL, 4, 3, 3, 0), + + SND_SOC_DAPM_PRE("Prepare Playback", playback_event), }; static const struct snd_soc_dapm_route audio_map[] = { @@ -527,18 +581,21 @@ static int dac33_set_bias_level(struct snd_soc_codec *codec, break; case SND_SOC_BIAS_STANDBY: if (codec->bias_level == SND_SOC_BIAS_OFF) { + /* Coming from OFF, switch on the codec */ ret = dac33_hard_power(codec, 1); if (ret != 0) return ret; - } - dac33_soft_power(codec, 0); + dac33_init_chip(codec); + } break; case SND_SOC_BIAS_OFF: + /* Do not power off, when the codec is already off */ + if (codec->bias_level == SND_SOC_BIAS_OFF) + return 0; ret = dac33_hard_power(codec, 0); if (ret != 0) return ret; - break; } codec->bias_level = level; @@ -555,13 +612,34 @@ 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_THRREG(dac33->nsample + dac33->alarm_threshold)); + + /* Take the timestamps */ + spin_lock_irq(&dac33->lock); + dac33->t_stamp2 = ktime_to_us(ktime_get()); + dac33->t_stamp1 = dac33->t_stamp2; + spin_unlock_irq(&dac33->lock); + dac33_write16(codec, DAC33_PREFILL_MSB, DAC33_THRREG(dac33->alarm_threshold)); + /* Enable Alarm Threshold IRQ with a delay */ + udelay(SAMPLES_TO_US(dac33->burst_rate, + dac33->alarm_threshold)); + dac33_write(codec, DAC33_FIFO_IRQ_MASK, DAC33_MAT); break; case DAC33_FIFO_MODE7: + /* Take the timestamp */ + spin_lock_irq(&dac33->lock); + dac33->t_stamp1 = ktime_to_us(ktime_get()); + /* Move back the timestamp with drain time */ + dac33->t_stamp1 -= dac33->mode7_us_to_lthr; + spin_unlock_irq(&dac33->lock); + dac33_write16(codec, DAC33_PREFILL_MSB, - DAC33_THRREG(10)); + DAC33_THRREG(MODE7_LTHR)); + + /* Enable Upper Threshold IRQ */ + dac33_write(codec, DAC33_FIFO_IRQ_MASK, DAC33_MUT); break; default: dev_warn(codec->dev, "Unhandled FIFO mode: %d\n", @@ -578,6 +656,11 @@ static inline void dac33_playback_handler(struct tlv320dac33_priv *dac33) switch (dac33->fifo_mode) { case DAC33_FIFO_MODE1: + /* Take the timestamp */ + spin_lock_irq(&dac33->lock); + dac33->t_stamp2 = ktime_to_us(ktime_get()); + spin_unlock_irq(&dac33->lock); + dac33_write16(codec, DAC33_NSAMPLE_MSB, DAC33_THRREG(dac33->nsample)); break; @@ -628,31 +711,17 @@ static void dac33_work(struct work_struct *work) static irqreturn_t dac33_interrupt_handler(int irq, void *dev) { struct snd_soc_codec *codec = dev; - struct tlv320dac33_priv *dac33 = codec->private_data; - - queue_work(dac33->dac33_wq, &dac33->work); + struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); - return IRQ_HANDLED; -} + spin_lock(&dac33->lock); + dac33->t_stamp1 = ktime_to_us(ktime_get()); + spin_unlock(&dac33->lock); -static void dac33_shutdown(struct snd_pcm_substream *substream, - 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 tlv320dac33_priv *dac33 = codec->private_data; - unsigned int pwr_ctrl; + /* Do not schedule the workqueue in Mode7 */ + if (dac33->fifo_mode != DAC33_FIFO_MODE7) + queue_work(dac33->dac33_wq, &dac33->work); - /* Stop pending workqueue */ - if (dac33->fifo_mode) - cancel_work_sync(&dac33->work); - - mutex_lock(&dac33->mutex); - pwr_ctrl = dac33_read_reg_cache(codec, DAC33_PWR_CTRL); - pwr_ctrl &= ~(DAC33_OSCPDNB | DAC33_DACRPDNB | DAC33_DACLPDNB); - dac33_write(codec, DAC33_PWR_CTRL, pwr_ctrl); - mutex_unlock(&dac33->mutex); + return IRQ_HANDLED; } static void dac33_oscwait(struct snd_soc_codec *codec) @@ -669,6 +738,31 @@ static void dac33_oscwait(struct snd_soc_codec *codec) "internal oscillator calibration failed\n"); } +static int dac33_startup(struct snd_pcm_substream *substream, + 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 tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); + + /* Stream started, save the substream pointer */ + dac33->substream = substream; + + return 0; +} + +static void dac33_shutdown(struct snd_pcm_substream *substream, + 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 tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); + + dac33->substream = NULL; +} + static int dac33_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) @@ -715,7 +809,7 @@ static int dac33_prepare_chip(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 tlv320dac33_priv *dac33 = codec->private_data; + struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); unsigned int oscset, ratioset, pwr_ctrl, reg_tmp; u8 aictrl_a, aictrl_b, fifoctrl_a; @@ -752,6 +846,17 @@ static int dac33_prepare_chip(struct snd_pcm_substream *substream) } mutex_lock(&dac33->mutex); + + if (!dac33->chip_power) { + /* + * Chip is not powered yet. + * Do the init in the dac33_set_bias_level later. + */ + mutex_unlock(&dac33->mutex); + return 0; + } + + dac33_soft_power(codec, 0); dac33_soft_power(codec, 1); reg_tmp = dac33_read_reg_cache(codec, DAC33_INT_OSC_CTRL); @@ -799,11 +904,10 @@ static int dac33_prepare_chip(struct snd_pcm_substream *substream) case DAC33_FIFO_MODE1: dac33_write(codec, DAC33_FIFO_IRQ_MODE_B, DAC33_ATM(DAC33_FIFO_IRQ_MODE_LEVEL)); - dac33_write(codec, DAC33_FIFO_IRQ_MASK, DAC33_MAT); break; case DAC33_FIFO_MODE7: - /* Disable all interrupts */ - dac33_write(codec, DAC33_FIFO_IRQ_MASK, 0); + dac33_write(codec, DAC33_FIFO_IRQ_MODE_A, + DAC33_UTM(DAC33_FIFO_IRQ_MODE_LEVEL)); break; default: /* in FIFO bypass mode, the interrupts are not used */ @@ -822,7 +926,10 @@ static int dac33_prepare_chip(struct snd_pcm_substream *substream) */ fifoctrl_a &= ~DAC33_FBYPAS; fifoctrl_a &= ~DAC33_FAUTO; - aictrl_b &= ~DAC33_BCLKON; + if (dac33->keep_bclk) + aictrl_b |= DAC33_BCLKON; + else + aictrl_b &= ~DAC33_BCLKON; break; case DAC33_FIFO_MODE7: /* @@ -833,7 +940,10 @@ static int dac33_prepare_chip(struct snd_pcm_substream *substream) */ fifoctrl_a &= ~DAC33_FBYPAS; fifoctrl_a |= DAC33_FAUTO; - aictrl_b &= ~DAC33_BCLKON; + if (dac33->keep_bclk) + aictrl_b |= DAC33_BCLKON; + else + aictrl_b &= ~DAC33_BCLKON; break; default: /* @@ -875,10 +985,8 @@ 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(DAC33_BUFFER_SIZE_SAMPLES - 10)); - dac33_write16(codec, DAC33_LTHR_MSB, - DAC33_THRREG(10)); + dac33_write16(codec, DAC33_UTHR_MSB, DAC33_THRREG(MODE7_UTHR)); + dac33_write16(codec, DAC33_LTHR_MSB, DAC33_THRREG(MODE7_LTHR)); break; default: break; @@ -894,9 +1002,13 @@ static void dac33_calculate_times(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 tlv320dac33_priv *dac33 = codec->private_data; + struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); 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; @@ -930,15 +1042,24 @@ static void dac33_calculate_times(struct snd_pcm_substream *substream) if (dac33->nsample > dac33->nsample_max) dac33->nsample = dac33->nsample_max; -} -static int dac33_pcm_prepare(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) -{ - dac33_calculate_times(substream); - dac33_prepare_chip(substream); + switch (dac33->fifo_mode) { + case DAC33_FIFO_MODE1: + 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: + dac33->mode7_us_to_lthr = + SAMPLES_TO_US(substream->runtime->rate, + MODE7_UTHR - MODE7_LTHR + 1); + dac33->t_stamp1 = 0; + break; + default: + break; + } - return 0; } static int dac33_pcm_trigger(struct snd_pcm_substream *substream, int cmd, @@ -947,7 +1068,7 @@ static int dac33_pcm_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_codec *codec = socdev->card->codec; - struct tlv320dac33_priv *dac33 = codec->private_data; + struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); int ret = 0; switch (cmd) { @@ -974,11 +1095,156 @@ static int dac33_pcm_trigger(struct snd_pcm_substream *substream, int cmd, return ret; } +static snd_pcm_sframes_t dac33_dai_delay( + struct snd_pcm_substream *substream, + 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 tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); + unsigned long long t0, t1, t_now; + unsigned int time_delta; + int samples_out, samples_in, samples; + snd_pcm_sframes_t delay = 0; + + switch (dac33->fifo_mode) { + case DAC33_FIFO_BYPASS: + break; + case DAC33_FIFO_MODE1: + spin_lock(&dac33->lock); + t0 = dac33->t_stamp1; + t1 = dac33->t_stamp2; + spin_unlock(&dac33->lock); + t_now = ktime_to_us(ktime_get()); + + /* We have not started to fill the FIFO yet, delay is 0 */ + if (!t1) + goto out; + + if (t0 > t1) { + /* + * Phase 1: + * After Alarm threshold, and before nSample write + */ + time_delta = t_now - t0; + samples_out = time_delta ? US_TO_SAMPLES( + substream->runtime->rate, + time_delta) : 0; + + if (likely(dac33->alarm_threshold > samples_out)) + delay = dac33->alarm_threshold - samples_out; + else + delay = 0; + } else if ((t_now - t1) <= dac33->mode1_us_burst) { + /* + * Phase 2: + * After nSample write (during burst operation) + */ + time_delta = t_now - t0; + samples_out = time_delta ? US_TO_SAMPLES( + substream->runtime->rate, + time_delta) : 0; + + time_delta = t_now - t1; + samples_in = time_delta ? US_TO_SAMPLES( + dac33->burst_rate, + time_delta) : 0; + + samples = dac33->alarm_threshold; + samples += (samples_in - samples_out); + + if (likely(samples > 0)) + delay = samples; + else + delay = 0; + } else { + /* + * Phase 3: + * After burst operation, before next alarm threshold + */ + time_delta = t_now - t0; + samples_out = time_delta ? US_TO_SAMPLES( + substream->runtime->rate, + time_delta) : 0; + + samples_in = dac33->nsample; + samples = dac33->alarm_threshold; + samples += (samples_in - samples_out); + + if (likely(samples > 0)) + delay = samples > DAC33_BUFFER_SIZE_SAMPLES ? + DAC33_BUFFER_SIZE_SAMPLES : samples; + else + delay = 0; + } + break; + case DAC33_FIFO_MODE7: + spin_lock(&dac33->lock); + t0 = dac33->t_stamp1; + spin_unlock(&dac33->lock); + t_now = ktime_to_us(ktime_get()); + + /* We have not started to fill the FIFO yet, delay is 0 */ + if (!t0) + goto out; + + if (t_now <= t0) { + /* + * Either the timestamps are messed or equal. Report + * maximum delay + */ + delay = MODE7_UTHR; + goto out; + } + + time_delta = t_now - t0; + if (time_delta <= dac33->mode7_us_to_lthr) { + /* + * Phase 1: + * After burst (draining phase) + */ + samples_out = US_TO_SAMPLES( + substream->runtime->rate, + time_delta); + + if (likely(MODE7_UTHR > samples_out)) + delay = MODE7_UTHR - samples_out; + else + delay = 0; + } else { + /* + * Phase 2: + * During burst operation + */ + time_delta = time_delta - dac33->mode7_us_to_lthr; + + samples_out = US_TO_SAMPLES( + substream->runtime->rate, + time_delta); + samples_in = US_TO_SAMPLES( + dac33->burst_rate, + time_delta); + delay = MODE7_LTHR + samples_in - samples_out; + + if (unlikely(delay > MODE7_UTHR)) + delay = MODE7_UTHR; + } + break; + default: + dev_warn(codec->dev, "Unhandled FIFO mode: %d\n", + dac33->fifo_mode); + break; + } +out: + return delay; +} + static int dac33_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 tlv320dac33_priv *dac33 = codec->private_data; + struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); u8 ioc_reg, asrcb_reg; ioc_reg = dac33_read_reg_cache(codec, DAC33_INT_OSC_CTRL); @@ -1008,7 +1274,7 @@ static int dac33_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { struct snd_soc_codec *codec = codec_dai->codec; - struct tlv320dac33_priv *dac33 = codec->private_data; + struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); u8 aictrl_a, aictrl_b; aictrl_a = dac33_read_reg_cache(codec, DAC33_SER_AUDIOIF_CTRL_A); @@ -1059,35 +1325,6 @@ static int dac33_set_dai_fmt(struct snd_soc_dai *codec_dai, return 0; } -static void dac33_init_chip(struct snd_soc_codec *codec) -{ - /* 44-46: DAC Control Registers */ - /* A : DAC sample rate Fsref/1.5 */ - dac33_write(codec, DAC33_DAC_CTRL_A, DAC33_DACRATE(0)); - /* B : DAC src=normal, not muted */ - dac33_write(codec, DAC33_DAC_CTRL_B, DAC33_DACSRCR_RIGHT | - DAC33_DACSRCL_LEFT); - /* C : (defaults) */ - dac33_write(codec, DAC33_DAC_CTRL_C, 0x00); - - /* 64-65 : L&R DAC power control - Line In -> OUT 1V/V Gain, DAC -> OUT 4V/V Gain*/ - dac33_write(codec, DAC33_LDAC_PWR_CTRL, DAC33_LROUT_GAIN(2)); - dac33_write(codec, DAC33_RDAC_PWR_CTRL, DAC33_LROUT_GAIN(2)); - - /* 73 : volume soft stepping control, - clock source = internal osc (?) */ - dac33_write(codec, DAC33_ANA_VOL_SOFT_STEP_CTRL, DAC33_VOLCLKEN); - - /* 66 : LOP/LOM Modes */ - dac33_write(codec, DAC33_OUT_AMP_CM_CTRL, 0xff); - - /* 68 : LOM inverted from LOP */ - dac33_write(codec, DAC33_OUT_AMP_CTRL, (3<<2)); - - dac33_write(codec, DAC33_PWR_CTRL, DAC33_PDNALLB); -} - static int dac33_soc_probe(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); @@ -1099,12 +1336,7 @@ static int dac33_soc_probe(struct platform_device *pdev) codec = tlv320dac33_codec; socdev->card->codec = codec; - dac33 = codec->private_data; - - /* Power up the codec */ - dac33_hard_power(codec, 1); - /* Set default configuration */ - dac33_init_chip(codec); + dac33 = snd_soc_codec_get_drvdata(codec); /* register pcms */ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); @@ -1122,12 +1354,6 @@ static int dac33_soc_probe(struct platform_device *pdev) dac33_add_widgets(codec); - /* power on device */ - dac33_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - - /* Bias level configuration has enabled regulator an extra time */ - regulator_bulk_disable(ARRAY_SIZE(dac33->supplies), dac33->supplies); - return 0; pcm_err: @@ -1164,7 +1390,6 @@ static int dac33_soc_resume(struct platform_device *pdev) struct snd_soc_codec *codec = socdev->card->codec; dac33_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - dac33_set_bias_level(codec, codec->suspend_bias_level); return 0; } @@ -1182,10 +1407,11 @@ EXPORT_SYMBOL_GPL(soc_codec_dev_tlv320dac33); #define DAC33_FORMATS SNDRV_PCM_FMTBIT_S16_LE static struct snd_soc_dai_ops dac33_dai_ops = { + .startup = dac33_startup, .shutdown = dac33_shutdown, .hw_params = dac33_hw_params, - .prepare = dac33_pcm_prepare, .trigger = dac33_pcm_trigger, + .delay = dac33_dai_delay, .set_sysclk = dac33_set_dai_sysclk, .set_fmt = dac33_set_dai_fmt, }; @@ -1221,11 +1447,12 @@ static int __devinit dac33_i2c_probe(struct i2c_client *client, return -ENOMEM; codec = &dac33->codec; - codec->private_data = dac33; + snd_soc_codec_set_drvdata(codec, dac33); codec->control_data = client; mutex_init(&codec->mutex); mutex_init(&dac33->mutex); + spin_lock_init(&dac33->lock); INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); @@ -1236,6 +1463,7 @@ static int __devinit dac33_i2c_probe(struct i2c_client *client, codec->hw_write = (hw_write_t) i2c_master_send; codec->bias_level = SND_SOC_BIAS_OFF; codec->set_bias_level = dac33_set_bias_level; + codec->idle_bias_off = 1; codec->dai = &dac33_dai; codec->num_dai = 1; codec->reg_cache_size = ARRAY_SIZE(dac33_reg); @@ -1250,8 +1478,12 @@ static int __devinit dac33_i2c_probe(struct i2c_client *client, dac33->power_gpio = pdata->power_gpio; dac33->burst_bclkdiv = pdata->burst_bclkdiv; + /* Pre calculate the burst rate */ + dac33->burst_rate = BURST_BASEFREQ_HZ / dac33->burst_bclkdiv / 32; + dac33->keep_bclk = pdata->keep_bclk; dac33->irq = client->irq; dac33->nsample = NSAMPLE_MAX; + dac33->nsample_max = NSAMPLE_MAX; /* Disable FIFO use by default */ dac33->fifo_mode = DAC33_FIFO_BYPASS; @@ -1272,8 +1504,6 @@ static int __devinit dac33_i2c_probe(struct i2c_client *client, goto error_gpio; } gpio_direction_output(dac33->power_gpio, 0); - } else { - dac33->chip_power = 1; } /* Check if the IRQ number is valid and request it */ @@ -1311,12 +1541,14 @@ static int __devinit dac33_i2c_probe(struct i2c_client *client, goto err_get; } - ret = regulator_bulk_enable(ARRAY_SIZE(dac33->supplies), - dac33->supplies); + /* Read the tlv320dac33 ID registers */ + ret = dac33_hard_power(codec, 1); if (ret != 0) { - dev_err(codec->dev, "Failed to enable supplies: %d\n", ret); - goto err_enable; + dev_err(codec->dev, "Failed to power up codec: %d\n", ret); + goto error_codec; } + dac33_read_id(codec); + dac33_hard_power(codec, 0); ret = snd_soc_register_codec(codec); if (ret != 0) { @@ -1331,14 +1563,9 @@ static int __devinit dac33_i2c_probe(struct i2c_client *client, goto error_codec; } - /* Shut down the codec for now */ - dac33_hard_power(codec, 0); - return ret; error_codec: - regulator_bulk_disable(ARRAY_SIZE(dac33->supplies), dac33->supplies); -err_enable: regulator_bulk_free(ARRAY_SIZE(dac33->supplies), dac33->supplies); err_get: if (dac33->irq >= 0) { @@ -1362,7 +1589,9 @@ static int __devexit dac33_i2c_remove(struct i2c_client *client) struct tlv320dac33_priv *dac33; dac33 = i2c_get_clientdata(client); - dac33_hard_power(&dac33->codec, 0); + + if (unlikely(dac33->chip_power)) + dac33_hard_power(&dac33->codec, 0); if (dac33->power_gpio >= 0) gpio_free(dac33->power_gpio); diff --git a/sound/soc/codecs/tpa6130a2.c b/sound/soc/codecs/tpa6130a2.c index 569ad87..99b70e5 100644 --- a/sound/soc/codecs/tpa6130a2.c +++ b/sound/soc/codecs/tpa6130a2.c @@ -36,24 +36,14 @@ static struct i2c_client *tpa6130a2_client; -#define TPA6130A2_NUM_SUPPLIES 2 -static const char *tpa6130a2_supply_names[TPA6130A2_NUM_SUPPLIES] = { - "CPVSS", - "Vdd", -}; - -static const char *tpa6140a2_supply_names[TPA6130A2_NUM_SUPPLIES] = { - "HPVdd", - "AVdd", -}; - /* This struct is used to save the context */ struct tpa6130a2_data { struct mutex mutex; unsigned char regs[TPA6130A2_CACHEREGNUM]; - struct regulator_bulk_data supplies[TPA6130A2_NUM_SUPPLIES]; + struct regulator *supply; int power_gpio; unsigned char power_state; + enum tpa_model id; }; static int tpa6130a2_i2c_read(int reg) @@ -135,11 +125,10 @@ static int tpa6130a2_power(int power) if (data->power_gpio >= 0) gpio_set_value(data->power_gpio, 1); - ret = regulator_bulk_enable(ARRAY_SIZE(data->supplies), - data->supplies); + ret = regulator_enable(data->supply); if (ret != 0) { dev_err(&tpa6130a2_client->dev, - "Failed to enable supplies: %d\n", ret); + "Failed to enable supply: %d\n", ret); goto exit; } @@ -160,11 +149,10 @@ static int tpa6130a2_power(int power) if (data->power_gpio >= 0) gpio_set_value(data->power_gpio, 0); - ret = regulator_bulk_disable(ARRAY_SIZE(data->supplies), - data->supplies); + ret = regulator_disable(data->supply); if (ret != 0) { dev_err(&tpa6130a2_client->dev, - "Failed to disable supplies: %d\n", ret); + "Failed to disable supply: %d\n", ret); goto exit; } @@ -176,7 +164,7 @@ exit: return ret; } -static int tpa6130a2_get_reg(struct snd_kcontrol *kcontrol, +static int tpa6130a2_get_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct soc_mixer_control *mc = @@ -184,7 +172,8 @@ static int tpa6130a2_get_reg(struct snd_kcontrol *kcontrol, struct tpa6130a2_data *data; unsigned int reg = mc->reg; unsigned int shift = mc->shift; - unsigned int mask = mc->max; + int max = mc->max; + unsigned int mask = (1 << fls(max)) - 1; unsigned int invert = mc->invert; BUG_ON(tpa6130a2_client == NULL); @@ -197,13 +186,13 @@ static int tpa6130a2_get_reg(struct snd_kcontrol *kcontrol, if (invert) ucontrol->value.integer.value[0] = - mask - ucontrol->value.integer.value[0]; + max - ucontrol->value.integer.value[0]; mutex_unlock(&data->mutex); return 0; } -static int tpa6130a2_set_reg(struct snd_kcontrol *kcontrol, +static int tpa6130a2_put_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct soc_mixer_control *mc = @@ -211,7 +200,8 @@ static int tpa6130a2_set_reg(struct snd_kcontrol *kcontrol, struct tpa6130a2_data *data; unsigned int reg = mc->reg; unsigned int shift = mc->shift; - unsigned int mask = mc->max; + int max = mc->max; + unsigned int mask = (1 << fls(max)) - 1; unsigned int invert = mc->invert; unsigned int val = (ucontrol->value.integer.value[0] & mask); unsigned int val_reg; @@ -220,7 +210,7 @@ static int tpa6130a2_set_reg(struct snd_kcontrol *kcontrol, data = i2c_get_clientdata(tpa6130a2_client); if (invert) - val = mask - val; + val = max - val; mutex_lock(&data->mutex); @@ -260,10 +250,24 @@ static const unsigned int tpa6130_tlv[] = { static const struct snd_kcontrol_new tpa6130a2_controls[] = { SOC_SINGLE_EXT_TLV("TPA6130A2 Headphone Playback Volume", TPA6130A2_REG_VOL_MUTE, 0, 0x3f, 0, - tpa6130a2_get_reg, tpa6130a2_set_reg, + tpa6130a2_get_volsw, tpa6130a2_put_volsw, tpa6130_tlv), }; +static const unsigned int tpa6140_tlv[] = { + TLV_DB_RANGE_HEAD(3), + 0, 8, TLV_DB_SCALE_ITEM(-5900, 400, 0), + 9, 16, TLV_DB_SCALE_ITEM(-2500, 200, 0), + 17, 31, TLV_DB_SCALE_ITEM(-1000, 100, 0), +}; + +static const struct snd_kcontrol_new tpa6140a2_controls[] = { + SOC_SINGLE_EXT_TLV("TPA6140A2 Headphone Playback Volume", + TPA6130A2_REG_VOL_MUTE, 1, 0x1f, 0, + tpa6130a2_get_volsw, tpa6130a2_put_volsw, + tpa6140_tlv), +}; + /* * Enable or disable channel (left or right) * The bit number for mute and amplifier are the same per channel: @@ -355,8 +359,8 @@ static const struct snd_soc_dapm_widget tpa6130a2_dapm_widgets[] = { 0, 0, tpa6130a2_supply_event, SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), /* Outputs */ - SND_SOC_DAPM_HP("TPA6130A2 Headphone Left", NULL), - SND_SOC_DAPM_HP("TPA6130A2 Headphone Right", NULL), + SND_SOC_DAPM_OUTPUT("TPA6130A2 Headphone Left"), + SND_SOC_DAPM_OUTPUT("TPA6130A2 Headphone Right"), }; static const struct snd_soc_dapm_route audio_map[] = { @@ -369,13 +373,22 @@ static const struct snd_soc_dapm_route audio_map[] = { int tpa6130a2_add_controls(struct snd_soc_codec *codec) { + struct tpa6130a2_data *data; + + BUG_ON(tpa6130a2_client == NULL); + data = i2c_get_clientdata(tpa6130a2_client); + snd_soc_dapm_new_controls(codec, tpa6130a2_dapm_widgets, ARRAY_SIZE(tpa6130a2_dapm_widgets)); snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); - return snd_soc_add_controls(codec, tpa6130a2_controls, - ARRAY_SIZE(tpa6130a2_controls)); + if (data->id == TPA6140A2) + return snd_soc_add_controls(codec, tpa6140a2_controls, + ARRAY_SIZE(tpa6140a2_controls)); + else + return snd_soc_add_controls(codec, tpa6130a2_controls, + ARRAY_SIZE(tpa6130a2_controls)); } EXPORT_SYMBOL_GPL(tpa6130a2_add_controls); @@ -386,7 +399,8 @@ static int __devinit tpa6130a2_probe(struct i2c_client *client, struct device *dev; struct tpa6130a2_data *data; struct tpa6130a2_platform_data *pdata; - int i, ret; + const char *regulator; + int ret; dev = &client->dev; @@ -408,6 +422,7 @@ static int __devinit tpa6130a2_probe(struct i2c_client *client, pdata = client->dev.platform_data; data->power_gpio = pdata->power_gpio; + data->id = pdata->id; mutex_init(&data->mutex); @@ -426,26 +441,22 @@ static int __devinit tpa6130a2_probe(struct i2c_client *client, gpio_direction_output(data->power_gpio, 0); } - switch (pdata->id) { + switch (data->id) { + default: + dev_warn(dev, "Unknown TPA model (%d). Assuming 6130A2\n", + pdata->id); case TPA6130A2: - for (i = 0; i < ARRAY_SIZE(data->supplies); i++) - data->supplies[i].supply = tpa6130a2_supply_names[i]; + regulator = "Vdd"; break; case TPA6140A2: - for (i = 0; i < ARRAY_SIZE(data->supplies); i++) - data->supplies[i].supply = tpa6140a2_supply_names[i];; + regulator = "AVdd"; break; - default: - dev_warn(dev, "Unknown TPA model (%d). Assuming 6130A2\n", - pdata->id); - for (i = 0; i < ARRAY_SIZE(data->supplies); i++) - data->supplies[i].supply = tpa6130a2_supply_names[i]; } - ret = regulator_bulk_get(dev, ARRAY_SIZE(data->supplies), - data->supplies); - if (ret != 0) { - dev_err(dev, "Failed to request supplies: %d\n", ret); + data->supply = regulator_get(dev, regulator); + if (IS_ERR(data->supply)) { + ret = PTR_ERR(data->supply); + dev_err(dev, "Failed to request supply: %d\n", ret); goto err_regulator; } @@ -468,7 +479,7 @@ static int __devinit tpa6130a2_probe(struct i2c_client *client, return 0; err_power: - regulator_bulk_free(ARRAY_SIZE(data->supplies), data->supplies); + regulator_put(data->supply); err_regulator: if (data->power_gpio >= 0) gpio_free(data->power_gpio); @@ -489,7 +500,7 @@ static int __devexit tpa6130a2_remove(struct i2c_client *client) if (data->power_gpio >= 0) gpio_free(data->power_gpio); - regulator_bulk_free(ARRAY_SIZE(data->supplies), data->supplies); + regulator_put(data->supply); kfree(data); tpa6130a2_client = NULL; diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index 520ffd6..b4fcdb0 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -124,6 +124,8 @@ struct twl4030_priv { struct snd_soc_codec codec; unsigned int codec_powered; + + /* reference counts of AIF/APLL users */ unsigned int apll_enabled; struct snd_pcm_substream *master_substream; @@ -136,9 +138,11 @@ struct twl4030_priv { unsigned int sysclk; - /* Headset output state handling */ - unsigned int hsl_enabled; - unsigned int hsr_enabled; + /* Output (with associated amp) states */ + u8 hsl_enabled, hsr_enabled; + u8 earpiece_enabled; + u8 predrivel_enabled, predriver_enabled; + u8 carkitl_enabled, carkitr_enabled; }; /* @@ -174,17 +178,52 @@ static inline void twl4030_write_reg_cache(struct snd_soc_codec *codec, static int twl4030_write(struct snd_soc_codec *codec, unsigned int reg, unsigned int value) { + struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec); + int write_to_reg = 0; + twl4030_write_reg_cache(codec, reg, value); - if (likely(reg < TWL4030_REG_SW_SHADOW)) - return twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, value, - reg); - else - return 0; + if (likely(reg < TWL4030_REG_SW_SHADOW)) { + /* Decide if the given register can be written */ + switch (reg) { + case TWL4030_REG_EAR_CTL: + if (twl4030->earpiece_enabled) + write_to_reg = 1; + break; + case TWL4030_REG_PREDL_CTL: + if (twl4030->predrivel_enabled) + write_to_reg = 1; + break; + case TWL4030_REG_PREDR_CTL: + if (twl4030->predriver_enabled) + write_to_reg = 1; + break; + case TWL4030_REG_PRECKL_CTL: + if (twl4030->carkitl_enabled) + write_to_reg = 1; + break; + case TWL4030_REG_PRECKR_CTL: + if (twl4030->carkitr_enabled) + write_to_reg = 1; + break; + case TWL4030_REG_HS_GAIN_SET: + if (twl4030->hsl_enabled || twl4030->hsr_enabled) + write_to_reg = 1; + break; + default: + /* All other register can be written */ + write_to_reg = 1; + break; + } + if (write_to_reg) + return twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, + value, reg); + } + return 0; } static void twl4030_codec_enable(struct snd_soc_codec *codec, int enable) { - struct twl4030_priv *twl4030 = codec->private_data; + struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec); int mode; if (enable == twl4030->codec_powered) @@ -222,28 +261,28 @@ static void twl4030_init_chip(struct snd_soc_codec *codec) static void twl4030_apll_enable(struct snd_soc_codec *codec, int enable) { - struct twl4030_priv *twl4030 = codec->private_data; - int status; - - if (enable == twl4030->apll_enabled) - return; - - if (enable) - /* Enable PLL */ - status = twl4030_codec_enable_resource(TWL4030_CODEC_RES_APLL); - else - /* Disable PLL */ - status = twl4030_codec_disable_resource(TWL4030_CODEC_RES_APLL); + 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); - - twl4030->apll_enabled = enable; } static void twl4030_power_up(struct snd_soc_codec *codec) { - struct twl4030_priv *twl4030 = codec->private_data; + struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec); u8 anamicl, regmisc1, byte; int i = 0; @@ -526,26 +565,26 @@ static int micpath_event(struct snd_soc_dapm_widget *w, * Output PGA builder: * Handle the muting and unmuting of the given output (turning off the * amplifier associated with the output pin) - * On mute bypass the reg_cache and mute the volume - * On unmute: restore the register content + * On mute bypass the reg_cache and write 0 to the register + * On unmute: restore the register content from the reg_cache * Outputs handled in this way: Earpiece, PreDrivL/R, CarkitL/R */ #define TWL4030_OUTPUT_PGA(pin_name, reg, mask) \ static int pin_name##pga_event(struct snd_soc_dapm_widget *w, \ struct snd_kcontrol *kcontrol, int event) \ { \ - u8 reg_val; \ + struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(w->codec); \ \ switch (event) { \ case SND_SOC_DAPM_POST_PMU: \ + twl4030->pin_name##_enabled = 1; \ twl4030_write(w->codec, reg, \ twl4030_read_reg_cache(w->codec, reg)); \ break; \ case SND_SOC_DAPM_POST_PMD: \ - reg_val = twl4030_read_reg_cache(w->codec, reg); \ - twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, \ - reg_val & (~mask), \ - reg); \ + twl4030->pin_name##_enabled = 0; \ + twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, \ + 0, reg); \ break; \ } \ return 0; \ @@ -636,13 +675,38 @@ static int apll_event(struct snd_soc_dapm_widget *w, return 0; } +static int aif_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + u8 audio_if; + + audio_if = twl4030_read_reg_cache(w->codec, TWL4030_REG_AUDIO_IF); + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* Enable AIF */ + /* enable the PLL before we use it to clock the DAI */ + twl4030_apll_enable(w->codec, 1); + + twl4030_write(w->codec, TWL4030_REG_AUDIO_IF, + audio_if | TWL4030_AIF_EN); + break; + case SND_SOC_DAPM_POST_PMD: + /* disable the DAI before we stop it's source PLL */ + twl4030_write(w->codec, TWL4030_REG_AUDIO_IF, + audio_if & ~TWL4030_AIF_EN); + twl4030_apll_enable(w->codec, 0); + break; + } + return 0; +} + static void headset_ramp(struct snd_soc_codec *codec, int ramp) { struct snd_soc_device *socdev = codec->socdev; struct twl4030_setup_data *setup = socdev->codec_data; unsigned char hs_gain, hs_pop; - struct twl4030_priv *twl4030 = codec->private_data; + struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec); /* Base values for ramp delay calculation: 2^19 - 2^26 */ unsigned int ramp_base[] = {524288, 1048576, 2097152, 4194304, 8388608, 16777216, 33554432, 67108864}; @@ -665,7 +729,10 @@ static void headset_ramp(struct snd_soc_codec *codec, int ramp) /* Headset ramp-up according to the TRM */ hs_pop |= TWL4030_VMID_EN; twl4030_write(codec, TWL4030_REG_HS_POPN_SET, hs_pop); - twl4030_write(codec, TWL4030_REG_HS_GAIN_SET, hs_gain); + /* Actually write to the register */ + twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, + hs_gain, + TWL4030_REG_HS_GAIN_SET); hs_pop |= TWL4030_RAMP_EN; twl4030_write(codec, TWL4030_REG_HS_POPN_SET, hs_pop); /* Wait ramp delay time + 1, so the VMID can settle */ @@ -702,7 +769,7 @@ static void headset_ramp(struct snd_soc_codec *codec, int ramp) static int headsetlpga_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { - struct twl4030_priv *twl4030 = w->codec->private_data; + struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(w->codec); switch (event) { case SND_SOC_DAPM_POST_PMU: @@ -726,7 +793,7 @@ static int headsetlpga_event(struct snd_soc_dapm_widget *w, static int headsetrpga_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { - struct twl4030_priv *twl4030 = w->codec->private_data; + struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(w->codec); switch (event) { case SND_SOC_DAPM_POST_PMU: @@ -918,7 +985,7 @@ static int snd_soc_put_twl4030_opmode_enum_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - struct twl4030_priv *twl4030 = codec->private_data; + struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec); struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; unsigned short val; unsigned short mask, bitmask; @@ -1036,6 +1103,16 @@ static const struct soc_enum twl4030_vibradir_enum = ARRAY_SIZE(twl4030_vibradir_texts), twl4030_vibradir_texts); +/* Digimic Left and right swapping */ +static const char *twl4030_digimicswap_texts[] = { + "Not swapped", "Swapped", +}; + +static const struct soc_enum twl4030_digimicswap_enum = + SOC_ENUM_SINGLE(TWL4030_REG_MISC_SET_1, 0, + ARRAY_SIZE(twl4030_digimicswap_texts), + twl4030_digimicswap_texts); + static const struct snd_kcontrol_new twl4030_snd_controls[] = { /* Codec operation mode control */ SOC_ENUM_EXT("Codec Operation Mode", twl4030_op_modes_enum, @@ -1112,6 +1189,8 @@ static const struct snd_kcontrol_new twl4030_snd_controls[] = { SOC_ENUM("Vibra H-bridge mode", twl4030_vibradirmode_enum), SOC_ENUM("Vibra H-bridge direction", twl4030_vibradir_enum), + + SOC_ENUM("Digimic LR Swap", twl4030_digimicswap_enum), }; static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { @@ -1128,8 +1207,6 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { SND_SOC_DAPM_INPUT("DIGIMIC1"), /* Outputs */ - SND_SOC_DAPM_OUTPUT("OUTL"), - SND_SOC_DAPM_OUTPUT("OUTR"), SND_SOC_DAPM_OUTPUT("EARPIECE"), SND_SOC_DAPM_OUTPUT("PREDRIVEL"), SND_SOC_DAPM_OUTPUT("PREDRIVER"), @@ -1141,6 +1218,11 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { SND_SOC_DAPM_OUTPUT("HFR"), SND_SOC_DAPM_OUTPUT("VIBRA"), + /* AIF and APLL clocks for running DAIs (including loopback) */ + SND_SOC_DAPM_OUTPUT("Virtual HiFi OUT"), + SND_SOC_DAPM_INPUT("Virtual HiFi IN"), + SND_SOC_DAPM_OUTPUT("Virtual Voice OUT"), + /* DACs */ SND_SOC_DAPM_DAC("DAC Right1", "Right Front HiFi Playback", SND_SOC_NOPM, 0, 0), @@ -1204,7 +1286,8 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { SND_SOC_DAPM_SUPPLY("APLL Enable", SND_SOC_NOPM, 0, 0, apll_event, SND_SOC_DAPM_PRE_PMU|SND_SOC_DAPM_POST_PMD), - SND_SOC_DAPM_SUPPLY("AIF Enable", TWL4030_REG_AUDIO_IF, 0, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("AIF Enable", SND_SOC_NOPM, 0, 0, aif_event, + SND_SOC_DAPM_PRE_PMU|SND_SOC_DAPM_POST_PMD), /* Output MIXER controls */ /* Earpiece */ @@ -1334,10 +1417,6 @@ static const struct snd_soc_dapm_route intercon[] = { {"Digital Voice Playback Mixer", NULL, "DAC Voice"}, /* Supply for the digital part (APLL) */ - {"Digital R1 Playback Mixer", NULL, "APLL Enable"}, - {"Digital L1 Playback Mixer", NULL, "APLL Enable"}, - {"Digital R2 Playback Mixer", NULL, "APLL Enable"}, - {"Digital L2 Playback Mixer", NULL, "APLL Enable"}, {"Digital Voice Playback Mixer", NULL, "APLL Enable"}, {"Digital R1 Playback Mixer", NULL, "AIF Enable"}, @@ -1411,8 +1490,14 @@ static const struct snd_soc_dapm_route intercon[] = { {"Vibra Mux", "AudioR2", "DAC Right2"}, /* outputs */ - {"OUTL", NULL, "Analog L2 Playback Mixer"}, - {"OUTR", NULL, "Analog R2 Playback Mixer"}, + /* 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"}, + /* Must be always connected (for APLL) */ + {"Virtual Voice OUT", NULL, "Digital Voice Playback Mixer"}, + /* Physical outputs */ {"EARPIECE", NULL, "Earpiece PGA"}, {"PREDRIVEL", NULL, "PredriveL PGA"}, {"PREDRIVER", NULL, "PredriveR PGA"}, @@ -1426,6 +1511,12 @@ static const struct snd_soc_dapm_route intercon[] = { {"VIBRA", NULL, "Vibra Route"}, /* Capture path */ + /* Must be always connected (for AIF and APLL) */ + {"ADC Virtual Left1", NULL, "Virtual HiFi IN"}, + {"ADC Virtual Right1", NULL, "Virtual HiFi IN"}, + {"ADC Virtual Left2", NULL, "Virtual HiFi IN"}, + {"ADC Virtual Right2", NULL, "Virtual HiFi IN"}, + /* Physical inputs */ {"Analog Left", "Main Mic Capture Switch", "MAINMIC"}, {"Analog Left", "Headset Mic Capture Switch", "HSMIC"}, {"Analog Left", "AUXL Capture Switch", "AUXL"}, @@ -1458,11 +1549,6 @@ static const struct snd_soc_dapm_route intercon[] = { {"ADC Virtual Left2", NULL, "TX2 Capture Route"}, {"ADC Virtual Right2", NULL, "TX2 Capture Route"}, - {"ADC Virtual Left1", NULL, "APLL Enable"}, - {"ADC Virtual Right1", NULL, "APLL Enable"}, - {"ADC Virtual Left2", NULL, "APLL Enable"}, - {"ADC Virtual Right2", NULL, "APLL Enable"}, - {"ADC Virtual Left1", NULL, "AIF Enable"}, {"ADC Virtual Right1", NULL, "AIF Enable"}, {"ADC Virtual Left2", NULL, "AIF Enable"}, @@ -1588,7 +1674,7 @@ static int twl4030_startup(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 = codec->private_data; + struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec); if (twl4030->master_substream) { twl4030->slave_substream = substream; @@ -1619,7 +1705,7 @@ static void twl4030_shutdown(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 = codec->private_data; + struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec); if (twl4030->master_substream == substream) twl4030->master_substream = twl4030->slave_substream; @@ -1645,7 +1731,7 @@ static int twl4030_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 = codec->private_data; + struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec); u8 mode, old_mode, format, old_format; /* If the substream has 4 channel, do the necessary setup */ @@ -1765,7 +1851,7 @@ static int twl4030_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 twl4030_priv *twl4030 = codec->private_data; + struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec); switch (freq) { case 19200000: @@ -1880,7 +1966,7 @@ static int twl4030_voice_startup(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 = codec->private_data; + struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec); u8 mode; /* If the system master clock is not 26MHz, the voice PCM interface is @@ -1962,7 +2048,7 @@ static int twl4030_voice_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 twl4030_priv *twl4030 = codec->private_data; + struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec); if (freq != 26000000) { dev_err(codec->dev, "Unsupported APLL mclk: %u, the Voice" @@ -2108,7 +2194,6 @@ static int twl4030_soc_resume(struct platform_device *pdev) struct snd_soc_codec *codec = socdev->card->codec; twl4030_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - twl4030_set_bias_level(codec, codec->suspend_bias_level); return 0; } @@ -2125,7 +2210,7 @@ static int twl4030_soc_probe(struct platform_device *pdev) BUG_ON(!twl4030_codec); codec = twl4030_codec; - twl4030 = codec->private_data; + twl4030 = snd_soc_codec_get_drvdata(codec); socdev->card->codec = codec; /* Configuration for headset ramp delay from setup data */ @@ -2188,7 +2273,7 @@ static int __devinit twl4030_codec_probe(struct platform_device *pdev) } codec = &twl4030->codec; - codec->private_data = twl4030; + snd_soc_codec_set_drvdata(codec, twl4030); codec->dev = &pdev->dev; twl4030_dai[0].dev = &pdev->dev; twl4030_dai[1].dev = &pdev->dev; diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c new file mode 100644 index 0000000..af36346 --- /dev/null +++ b/sound/soc/codecs/twl6040.c @@ -0,0 +1,1246 @@ +/* + * ALSA SoC TWL6040 codec driver + * + * Author: Misael Lopez Cruz <x0052729@ti.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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/gpio.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/i2c/twl.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 "twl6040.h" + +#define TWL6040_RATES (SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) +#define TWL6040_FORMATS (SNDRV_PCM_FMTBIT_S32_LE) + +/* codec private data */ +struct twl6040_data { + struct snd_soc_codec codec; + int audpwron; + int naudint; + int codec_powered; + int pll; + int non_lp; + unsigned int sysclk; + struct snd_pcm_hw_constraint_list *sysclk_constraints; + struct completion ready; +}; + +/* + * twl6040 register cache & default register settings + */ +static const u8 twl6040_reg[TWL6040_CACHEREGNUM] = { + 0x00, /* not used 0x00 */ + 0x4B, /* TWL6040_ASICID (ro) 0x01 */ + 0x00, /* TWL6040_ASICREV (ro) 0x02 */ + 0x00, /* TWL6040_INTID 0x03 */ + 0x00, /* TWL6040_INTMR 0x04 */ + 0x00, /* TWL6040_NCPCTRL 0x05 */ + 0x00, /* TWL6040_LDOCTL 0x06 */ + 0x60, /* TWL6040_HPPLLCTL 0x07 */ + 0x00, /* TWL6040_LPPLLCTL 0x08 */ + 0x4A, /* TWL6040_LPPLLDIV 0x09 */ + 0x00, /* TWL6040_AMICBCTL 0x0A */ + 0x00, /* TWL6040_DMICBCTL 0x0B */ + 0x18, /* TWL6040_MICLCTL 0x0C - No input selected on Left Mic */ + 0x18, /* TWL6040_MICRCTL 0x0D - No input selected on Right Mic */ + 0x00, /* TWL6040_MICGAIN 0x0E */ + 0x1B, /* TWL6040_LINEGAIN 0x0F */ + 0x00, /* TWL6040_HSLCTL 0x10 */ + 0x00, /* TWL6040_HSRCTL 0x11 */ + 0x00, /* TWL6040_HSGAIN 0x12 */ + 0x00, /* TWL6040_EARCTL 0x13 */ + 0x00, /* TWL6040_HFLCTL 0x14 */ + 0x00, /* TWL6040_HFLGAIN 0x15 */ + 0x00, /* TWL6040_HFRCTL 0x16 */ + 0x00, /* TWL6040_HFRGAIN 0x17 */ + 0x00, /* TWL6040_VIBCTLL 0x18 */ + 0x00, /* TWL6040_VIBDATL 0x19 */ + 0x00, /* TWL6040_VIBCTLR 0x1A */ + 0x00, /* TWL6040_VIBDATR 0x1B */ + 0x00, /* TWL6040_HKCTL1 0x1C */ + 0x00, /* TWL6040_HKCTL2 0x1D */ + 0x00, /* TWL6040_GPOCTL 0x1E */ + 0x00, /* TWL6040_ALB 0x1F */ + 0x00, /* TWL6040_DLB 0x20 */ + 0x00, /* not used 0x21 */ + 0x00, /* not used 0x22 */ + 0x00, /* not used 0x23 */ + 0x00, /* not used 0x24 */ + 0x00, /* not used 0x25 */ + 0x00, /* not used 0x26 */ + 0x00, /* not used 0x27 */ + 0x00, /* TWL6040_TRIM1 0x28 */ + 0x00, /* TWL6040_TRIM2 0x29 */ + 0x00, /* TWL6040_TRIM3 0x2A */ + 0x00, /* TWL6040_HSOTRIM 0x2B */ + 0x00, /* TWL6040_HFOTRIM 0x2C */ + 0x09, /* TWL6040_ACCCTL 0x2D */ + 0x00, /* TWL6040_STATUS (ro) 0x2E */ +}; + +/* + * twl6040 vio/gnd registers: + * registers under vio/gnd supply can be accessed + * before the power-up sequence, after NRESPWRON goes high + */ +static const int twl6040_vio_reg[TWL6040_VIOREGNUM] = { + TWL6040_REG_ASICID, + TWL6040_REG_ASICREV, + TWL6040_REG_INTID, + TWL6040_REG_INTMR, + TWL6040_REG_NCPCTL, + TWL6040_REG_LDOCTL, + TWL6040_REG_AMICBCTL, + TWL6040_REG_DMICBCTL, + TWL6040_REG_HKCTL1, + TWL6040_REG_HKCTL2, + TWL6040_REG_GPOCTL, + TWL6040_REG_TRIM1, + TWL6040_REG_TRIM2, + TWL6040_REG_TRIM3, + TWL6040_REG_HSOTRIM, + TWL6040_REG_HFOTRIM, + TWL6040_REG_ACCCTL, + TWL6040_REG_STATUS, +}; + +/* + * twl6040 vdd/vss registers: + * registers under vdd/vss supplies can only be accessed + * after the power-up sequence + */ +static const int twl6040_vdd_reg[TWL6040_VDDREGNUM] = { + TWL6040_REG_HPPLLCTL, + TWL6040_REG_LPPLLCTL, + TWL6040_REG_LPPLLDIV, + TWL6040_REG_MICLCTL, + TWL6040_REG_MICRCTL, + TWL6040_REG_MICGAIN, + TWL6040_REG_LINEGAIN, + TWL6040_REG_HSLCTL, + TWL6040_REG_HSRCTL, + TWL6040_REG_HSGAIN, + TWL6040_REG_EARCTL, + TWL6040_REG_HFLCTL, + TWL6040_REG_HFLGAIN, + TWL6040_REG_HFRCTL, + TWL6040_REG_HFRGAIN, + TWL6040_REG_VIBCTLL, + TWL6040_REG_VIBDATL, + TWL6040_REG_VIBCTLR, + TWL6040_REG_VIBDATR, + TWL6040_REG_ALB, + TWL6040_REG_DLB, +}; + +/* + * read twl6040 register cache + */ +static inline unsigned int twl6040_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u8 *cache = codec->reg_cache; + + if (reg >= TWL6040_CACHEREGNUM) + return -EIO; + + return cache[reg]; +} + +/* + * write twl6040 register cache + */ +static inline void twl6040_write_reg_cache(struct snd_soc_codec *codec, + u8 reg, u8 value) +{ + u8 *cache = codec->reg_cache; + + if (reg >= TWL6040_CACHEREGNUM) + return; + cache[reg] = value; +} + +/* + * read from twl6040 hardware register + */ +static int twl6040_read_reg_volatile(struct snd_soc_codec *codec, + unsigned int reg) +{ + u8 value; + + if (reg >= TWL6040_CACHEREGNUM) + return -EIO; + + twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &value, reg); + twl6040_write_reg_cache(codec, reg, value); + + return value; +} + +/* + * write to the twl6040 register space + */ +static int twl6040_write(struct snd_soc_codec *codec, + unsigned int reg, unsigned int value) +{ + if (reg >= TWL6040_CACHEREGNUM) + return -EIO; + + twl6040_write_reg_cache(codec, reg, value); + return twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, value, reg); +} + +static void twl6040_init_vio_regs(struct snd_soc_codec *codec) +{ + u8 *cache = codec->reg_cache; + int reg, i; + + /* allow registers to be accessed by i2c */ + twl6040_write(codec, TWL6040_REG_ACCCTL, cache[TWL6040_REG_ACCCTL]); + + for (i = 0; i < TWL6040_VIOREGNUM; i++) { + reg = twl6040_vio_reg[i]; + /* skip read-only registers (ASICID, ASICREV, STATUS) */ + switch (reg) { + case TWL6040_REG_ASICID: + case TWL6040_REG_ASICREV: + case TWL6040_REG_STATUS: + continue; + default: + break; + } + twl6040_write(codec, reg, cache[reg]); + } +} + +static void twl6040_init_vdd_regs(struct snd_soc_codec *codec) +{ + u8 *cache = codec->reg_cache; + int reg, i; + + for (i = 0; i < TWL6040_VDDREGNUM; i++) { + reg = twl6040_vdd_reg[i]; + twl6040_write(codec, reg, cache[reg]); + } +} + +/* twl6040 codec manual power-up sequence */ +static void twl6040_power_up(struct snd_soc_codec *codec) +{ + u8 ncpctl, ldoctl, lppllctl, accctl; + + ncpctl = twl6040_read_reg_cache(codec, TWL6040_REG_NCPCTL); + ldoctl = twl6040_read_reg_cache(codec, TWL6040_REG_LDOCTL); + lppllctl = twl6040_read_reg_cache(codec, TWL6040_REG_LPPLLCTL); + accctl = twl6040_read_reg_cache(codec, TWL6040_REG_ACCCTL); + + /* enable reference system */ + ldoctl |= TWL6040_REFENA; + twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl); + msleep(10); + /* enable internal oscillator */ + ldoctl |= TWL6040_OSCENA; + twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl); + udelay(10); + /* enable high-side ldo */ + ldoctl |= TWL6040_HSLDOENA; + twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl); + udelay(244); + /* enable negative charge pump */ + ncpctl |= TWL6040_NCPENA | TWL6040_NCPOPEN; + twl6040_write(codec, TWL6040_REG_NCPCTL, ncpctl); + udelay(488); + /* enable low-side ldo */ + ldoctl |= TWL6040_LSLDOENA; + twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl); + udelay(244); + /* enable low-power pll */ + lppllctl |= TWL6040_LPLLENA; + twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl); + /* reset state machine */ + accctl |= TWL6040_RESETSPLIT; + twl6040_write(codec, TWL6040_REG_ACCCTL, accctl); + mdelay(5); + accctl &= ~TWL6040_RESETSPLIT; + twl6040_write(codec, TWL6040_REG_ACCCTL, accctl); + /* disable internal oscillator */ + ldoctl &= ~TWL6040_OSCENA; + twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl); +} + +/* twl6040 codec manual power-down sequence */ +static void twl6040_power_down(struct snd_soc_codec *codec) +{ + u8 ncpctl, ldoctl, lppllctl, accctl; + + ncpctl = twl6040_read_reg_cache(codec, TWL6040_REG_NCPCTL); + ldoctl = twl6040_read_reg_cache(codec, TWL6040_REG_LDOCTL); + lppllctl = twl6040_read_reg_cache(codec, TWL6040_REG_LPPLLCTL); + accctl = twl6040_read_reg_cache(codec, TWL6040_REG_ACCCTL); + + /* enable internal oscillator */ + ldoctl |= TWL6040_OSCENA; + twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl); + udelay(10); + /* disable low-power pll */ + lppllctl &= ~TWL6040_LPLLENA; + twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl); + /* disable low-side ldo */ + ldoctl &= ~TWL6040_LSLDOENA; + twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl); + udelay(244); + /* disable negative charge pump */ + ncpctl &= ~(TWL6040_NCPENA | TWL6040_NCPOPEN); + twl6040_write(codec, TWL6040_REG_NCPCTL, ncpctl); + udelay(488); + /* disable high-side ldo */ + ldoctl &= ~TWL6040_HSLDOENA; + twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl); + udelay(244); + /* disable internal oscillator */ + ldoctl &= ~TWL6040_OSCENA; + twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl); + /* disable reference system */ + ldoctl &= ~TWL6040_REFENA; + twl6040_write(codec, TWL6040_REG_LDOCTL, ldoctl); + msleep(10); +} + +/* set headset dac and driver power mode */ +static int headset_power_mode(struct snd_soc_codec *codec, int high_perf) +{ + int hslctl, hsrctl; + int mask = TWL6040_HSDRVMODEL | TWL6040_HSDACMODEL; + + hslctl = twl6040_read_reg_cache(codec, TWL6040_REG_HSLCTL); + hsrctl = twl6040_read_reg_cache(codec, TWL6040_REG_HSRCTL); + + if (high_perf) { + hslctl &= ~mask; + hsrctl &= ~mask; + } else { + hslctl |= mask; + hsrctl |= mask; + } + + twl6040_write(codec, TWL6040_REG_HSLCTL, hslctl); + twl6040_write(codec, TWL6040_REG_HSRCTL, hsrctl); + + return 0; +} + +static int twl6040_power_mode_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec); + + if (SND_SOC_DAPM_EVENT_ON(event)) + priv->non_lp++; + else + priv->non_lp--; + + return 0; +} + +/* audio interrupt handler */ +static irqreturn_t twl6040_naudint_handler(int irq, void *data) +{ + struct snd_soc_codec *codec = data; + struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec); + u8 intid; + + twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &intid, TWL6040_REG_INTID); + + switch (intid) { + case TWL6040_THINT: + dev_alert(codec->dev, "die temp over-limit detection\n"); + break; + case TWL6040_PLUGINT: + case TWL6040_UNPLUGINT: + case TWL6040_HOOKINT: + break; + case TWL6040_HFINT: + dev_alert(codec->dev, "hf drivers over current detection\n"); + break; + case TWL6040_VIBINT: + dev_alert(codec->dev, "vib drivers over current detection\n"); + break; + case TWL6040_READYINT: + complete(&priv->ready); + break; + default: + dev_err(codec->dev, "unknown audio interrupt %d\n", intid); + break; + } + + return IRQ_HANDLED; +} + +/* + * MICATT volume control: + * from -6 to 0 dB in 6 dB steps + */ +static DECLARE_TLV_DB_SCALE(mic_preamp_tlv, -600, 600, 0); + +/* + * MICGAIN volume control: + * from 6 to 30 dB in 6 dB steps + */ +static DECLARE_TLV_DB_SCALE(mic_amp_tlv, 600, 600, 0); + +/* + * HSGAIN volume control: + * from -30 to 0 dB in 2 dB steps + */ +static DECLARE_TLV_DB_SCALE(hs_tlv, -3000, 200, 0); + +/* + * HFGAIN volume control: + * from -52 to 6 dB in 2 dB steps + */ +static DECLARE_TLV_DB_SCALE(hf_tlv, -5200, 200, 0); + +/* + * EPGAIN volume control: + * from -24 to 6 dB in 2 dB steps + */ +static DECLARE_TLV_DB_SCALE(ep_tlv, -2400, 200, 0); + +/* Left analog microphone selection */ +static const char *twl6040_amicl_texts[] = + {"Headset Mic", "Main Mic", "Aux/FM Left", "Off"}; + +/* Right analog microphone selection */ +static const char *twl6040_amicr_texts[] = + {"Headset Mic", "Sub Mic", "Aux/FM Right", "Off"}; + +static const struct soc_enum twl6040_enum[] = { + SOC_ENUM_SINGLE(TWL6040_REG_MICLCTL, 3, 3, twl6040_amicl_texts), + SOC_ENUM_SINGLE(TWL6040_REG_MICRCTL, 3, 3, twl6040_amicr_texts), +}; + +static const struct snd_kcontrol_new amicl_control = + SOC_DAPM_ENUM("Route", twl6040_enum[0]); + +static const struct snd_kcontrol_new amicr_control = + SOC_DAPM_ENUM("Route", twl6040_enum[1]); + +/* Headset DAC playback switches */ +static const struct snd_kcontrol_new hsdacl_switch_controls = + SOC_DAPM_SINGLE("Switch", TWL6040_REG_HSLCTL, 5, 1, 0); + +static const struct snd_kcontrol_new hsdacr_switch_controls = + SOC_DAPM_SINGLE("Switch", TWL6040_REG_HSRCTL, 5, 1, 0); + +/* Handsfree DAC playback switches */ +static const struct snd_kcontrol_new hfdacl_switch_controls = + SOC_DAPM_SINGLE("Switch", TWL6040_REG_HFLCTL, 2, 1, 0); + +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); + +static const struct snd_kcontrol_new twl6040_snd_controls[] = { + /* Capture gains */ + SOC_DOUBLE_TLV("Capture Preamplifier Volume", + TWL6040_REG_MICGAIN, 6, 7, 1, 1, mic_preamp_tlv), + SOC_DOUBLE_TLV("Capture Volume", + TWL6040_REG_MICGAIN, 0, 3, 4, 0, mic_amp_tlv), + + /* Playback gains */ + SOC_DOUBLE_TLV("Headset Playback Volume", + TWL6040_REG_HSGAIN, 0, 4, 0xF, 1, hs_tlv), + SOC_DOUBLE_R_TLV("Handsfree Playback Volume", + TWL6040_REG_HFLGAIN, TWL6040_REG_HFRGAIN, 0, 0x1D, 1, hf_tlv), + SOC_SINGLE_TLV("Earphone Playback Volume", + TWL6040_REG_EARCTL, 1, 0xF, 1, ep_tlv), +}; + +static const struct snd_soc_dapm_widget twl6040_dapm_widgets[] = { + /* Inputs */ + SND_SOC_DAPM_INPUT("MAINMIC"), + SND_SOC_DAPM_INPUT("HSMIC"), + SND_SOC_DAPM_INPUT("SUBMIC"), + SND_SOC_DAPM_INPUT("AFML"), + SND_SOC_DAPM_INPUT("AFMR"), + + /* Outputs */ + SND_SOC_DAPM_OUTPUT("HSOL"), + SND_SOC_DAPM_OUTPUT("HSOR"), + SND_SOC_DAPM_OUTPUT("HFL"), + SND_SOC_DAPM_OUTPUT("HFR"), + SND_SOC_DAPM_OUTPUT("EP"), + + /* Analog input muxes for the capture amplifiers */ + SND_SOC_DAPM_MUX("Analog Left Capture Route", + SND_SOC_NOPM, 0, 0, &amicl_control), + SND_SOC_DAPM_MUX("Analog Right Capture Route", + SND_SOC_NOPM, 0, 0, &amicr_control), + + /* Analog capture PGAs */ + SND_SOC_DAPM_PGA("MicAmpL", + TWL6040_REG_MICLCTL, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("MicAmpR", + TWL6040_REG_MICRCTL, 0, 0, NULL, 0), + + /* ADCs */ + SND_SOC_DAPM_ADC("ADC Left", "Left Front Capture", + TWL6040_REG_MICLCTL, 2, 0), + SND_SOC_DAPM_ADC("ADC Right", "Right Front Capture", + TWL6040_REG_MICRCTL, 2, 0), + + /* Microphone bias */ + SND_SOC_DAPM_MICBIAS("Headset Mic Bias", + TWL6040_REG_AMICBCTL, 0, 0), + SND_SOC_DAPM_MICBIAS("Main Mic Bias", + TWL6040_REG_AMICBCTL, 4, 0), + SND_SOC_DAPM_MICBIAS("Digital Mic1 Bias", + TWL6040_REG_DMICBCTL, 0, 0), + SND_SOC_DAPM_MICBIAS("Digital Mic2 Bias", + 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("HFDAC Left", "Handsfree Playback", + TWL6040_REG_HFLCTL, 0, 0, + twl6040_power_mode_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_DAC_E("HFDAC Right", "Handsfree Playback", + TWL6040_REG_HFRCTL, 0, 0, + twl6040_power_mode_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + /* Analog playback switches */ + SND_SOC_DAPM_SWITCH("HSDAC Left Playback", + SND_SOC_NOPM, 0, 0, &hsdacl_switch_controls), + SND_SOC_DAPM_SWITCH("HSDAC Right Playback", + SND_SOC_NOPM, 0, 0, &hsdacr_switch_controls), + SND_SOC_DAPM_SWITCH("HFDAC Left Playback", + SND_SOC_NOPM, 0, 0, &hfdacl_switch_controls), + 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, + 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, + twl6040_power_mode_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SWITCH_E("Earphone Driver", + SND_SOC_NOPM, 0, 0, &ep_driver_switch_controls, + twl6040_power_mode_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + /* Analog playback PGAs */ + SND_SOC_DAPM_PGA("HFDAC Left PGA", + TWL6040_REG_HFLCTL, 1, 0, NULL, 0), + SND_SOC_DAPM_PGA("HFDAC Right PGA", + TWL6040_REG_HFRCTL, 1, 0, NULL, 0), + +}; + +static const struct snd_soc_dapm_route intercon[] = { + /* Capture path */ + {"Analog Left Capture Route", "Headset Mic", "HSMIC"}, + {"Analog Left Capture Route", "Main Mic", "MAINMIC"}, + {"Analog Left Capture Route", "Aux/FM Left", "AFML"}, + + {"Analog Right Capture Route", "Headset Mic", "HSMIC"}, + {"Analog Right Capture Route", "Sub Mic", "SUBMIC"}, + {"Analog Right Capture Route", "Aux/FM Right", "AFMR"}, + + {"MicAmpL", NULL, "Analog Left Capture Route"}, + {"MicAmpR", NULL, "Analog Right Capture Route"}, + + {"ADC Left", NULL, "MicAmpL"}, + {"ADC Right", NULL, "MicAmpR"}, + + /* Headset playback path */ + {"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"}, + + {"HSOL", NULL, "Headset Left Driver"}, + {"HSOR", NULL, "Headset Right Driver"}, + + /* Earphone playback path */ + {"Earphone Driver", "Switch", "HSDAC Left"}, + {"EP", NULL, "Earphone Driver"}, + + /* Handsfree playback path */ + {"HFDAC Left Playback", "Switch", "HFDAC Left"}, + {"HFDAC Right Playback", "Switch", "HFDAC Right"}, + + {"HFDAC Left PGA", NULL, "HFDAC Left Playback"}, + {"HFDAC Right PGA", NULL, "HFDAC Right Playback"}, + + {"Handsfree Left Driver", "Switch", "HFDAC Left PGA"}, + {"Handsfree Right Driver", "Switch", "HFDAC Right PGA"}, + + {"HFL", NULL, "Handsfree Left Driver"}, + {"HFR", NULL, "Handsfree Right Driver"}, +}; + +static int twl6040_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, twl6040_dapm_widgets, + ARRAY_SIZE(twl6040_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon)); + + snd_soc_dapm_new_widgets(codec); + + return 0; +} + +static int twl6040_power_up_completion(struct snd_soc_codec *codec, + int naudint) +{ + struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec); + int time_left; + u8 intid; + + time_left = wait_for_completion_timeout(&priv->ready, + msecs_to_jiffies(48)); + + if (!time_left) { + twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &intid, + TWL6040_REG_INTID); + if (!(intid & TWL6040_READYINT)) { + dev_err(codec->dev, "timeout waiting for READYINT\n"); + return -ETIMEDOUT; + } + } + + priv->codec_powered = 1; + + return 0; +} + +static int twl6040_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec); + int audpwron = priv->audpwron; + int naudint = priv->naudint; + int ret; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + if (priv->codec_powered) + break; + + if (gpio_is_valid(audpwron)) { + /* use AUDPWRON line */ + gpio_set_value(audpwron, 1); + + /* wait for power-up completion */ + ret = twl6040_power_up_completion(codec, naudint); + if (ret) + return ret; + + /* sync registers updated during power-up sequence */ + twl6040_read_reg_volatile(codec, TWL6040_REG_NCPCTL); + twl6040_read_reg_volatile(codec, TWL6040_REG_LDOCTL); + twl6040_read_reg_volatile(codec, TWL6040_REG_LPPLLCTL); + } else { + /* use manual power-up sequence */ + twl6040_power_up(codec); + priv->codec_powered = 1; + } + + /* initialize vdd/vss registers with reg_cache */ + twl6040_init_vdd_regs(codec); + break; + case SND_SOC_BIAS_OFF: + if (!priv->codec_powered) + break; + + if (gpio_is_valid(audpwron)) { + /* use AUDPWRON line */ + gpio_set_value(audpwron, 0); + + /* power-down sequence latency */ + udelay(500); + + /* sync registers updated during power-down sequence */ + twl6040_read_reg_volatile(codec, TWL6040_REG_NCPCTL); + twl6040_read_reg_volatile(codec, TWL6040_REG_LDOCTL); + twl6040_write_reg_cache(codec, TWL6040_REG_LPPLLCTL, + 0x00); + } else { + /* use manual power-down sequence */ + twl6040_power_down(codec); + } + + priv->codec_powered = 0; + break; + } + + codec->bias_level = level; + + return 0; +} + +/* set of rates for each pll: low-power and high-performance */ + +static unsigned int lp_rates[] = { + 88200, + 96000, +}; + +static struct snd_pcm_hw_constraint_list lp_constraints = { + .count = ARRAY_SIZE(lp_rates), + .list = lp_rates, +}; + +static unsigned int hp_rates[] = { + 96000, +}; + +static struct snd_pcm_hw_constraint_list hp_constraints = { + .count = ARRAY_SIZE(hp_rates), + .list = hp_rates, +}; + +static int twl6040_startup(struct snd_pcm_substream *substream, + 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 twl6040_data *priv = snd_soc_codec_get_drvdata(codec); + + if (!priv->sysclk) { + dev_err(codec->dev, + "no mclk configured, call set_sysclk() on init\n"); + return -EINVAL; + } + + /* + * capture is not supported at 17.64 MHz, + * it's reserved for headset low-power playback scenario + */ + if ((priv->sysclk == 17640000) && substream->stream) { + dev_err(codec->dev, + "capture mode is not supported at %dHz\n", + priv->sysclk); + return -EINVAL; + } + + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + priv->sysclk_constraints); + + return 0; +} + +static int twl6040_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 twl6040_data *priv = snd_soc_codec_get_drvdata(codec); + u8 lppllctl; + int rate; + + /* nothing to do for high-perf pll, it supports only 48 kHz */ + if (priv->pll == TWL6040_HPPLL_ID) + return 0; + + lppllctl = twl6040_read_reg_cache(codec, TWL6040_REG_LPPLLCTL); + + rate = params_rate(params); + switch (rate) { + case 88200: + lppllctl |= TWL6040_LPLLFIN; + priv->sysclk = 17640000; + break; + case 96000: + lppllctl &= ~TWL6040_LPLLFIN; + priv->sysclk = 19200000; + break; + default: + dev_err(codec->dev, "unsupported rate %d\n", rate); + return -EINVAL; + } + + twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl); + + return 0; +} + +static int twl6040_trigger(struct snd_pcm_substream *substream, + int cmd, 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 twl6040_data *priv = snd_soc_codec_get_drvdata(codec); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + /* + * low-power playback mode is restricted + * for headset path only + */ + if ((priv->sysclk == 17640000) && priv->non_lp) { + dev_err(codec->dev, + "some enabled paths aren't supported at %dHz\n", + priv->sysclk); + return -EPERM; + } + break; + default: + break; + } + + return 0; +} + +static int twl6040_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 twl6040_data *priv = snd_soc_codec_get_drvdata(codec); + u8 hppllctl, lppllctl; + + hppllctl = twl6040_read_reg_cache(codec, TWL6040_REG_HPPLLCTL); + lppllctl = twl6040_read_reg_cache(codec, TWL6040_REG_LPPLLCTL); + + switch (clk_id) { + case TWL6040_SYSCLK_SEL_LPPLL: + switch (freq) { + case 32768: + /* headset dac and driver must be in low-power mode */ + headset_power_mode(codec, 0); + + /* clk32k input requires low-power pll */ + lppllctl |= TWL6040_LPLLENA; + twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl); + mdelay(5); + lppllctl &= ~TWL6040_HPLLSEL; + twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl); + hppllctl &= ~TWL6040_HPLLENA; + twl6040_write(codec, TWL6040_REG_HPPLLCTL, hppllctl); + break; + default: + dev_err(codec->dev, "unknown mclk freq %d\n", freq); + return -EINVAL; + } + + /* lppll divider */ + switch (priv->sysclk) { + case 17640000: + lppllctl |= TWL6040_LPLLFIN; + break; + case 19200000: + lppllctl &= ~TWL6040_LPLLFIN; + break; + default: + /* sysclk not yet configured */ + lppllctl &= ~TWL6040_LPLLFIN; + priv->sysclk = 19200000; + break; + } + + twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl); + + priv->pll = TWL6040_LPPLL_ID; + priv->sysclk_constraints = &lp_constraints; + break; + case TWL6040_SYSCLK_SEL_HPPLL: + hppllctl &= ~TWL6040_MCLK_MSK; + + switch (freq) { + case 12000000: + /* mclk input, pll enabled */ + hppllctl |= TWL6040_MCLK_12000KHZ | + TWL6040_HPLLSQRBP | + TWL6040_HPLLENA; + break; + case 19200000: + /* mclk input, pll disabled */ + hppllctl |= TWL6040_MCLK_19200KHZ | + TWL6040_HPLLSQRBP | + TWL6040_HPLLBP; + break; + case 26000000: + /* mclk input, pll enabled */ + hppllctl |= TWL6040_MCLK_26000KHZ | + TWL6040_HPLLSQRBP | + TWL6040_HPLLENA; + break; + case 38400000: + /* clk slicer, pll disabled */ + hppllctl |= TWL6040_MCLK_38400KHZ | + TWL6040_HPLLSQRENA | + TWL6040_HPLLBP; + break; + default: + dev_err(codec->dev, "unknown mclk freq %d\n", freq); + return -EINVAL; + } + + /* headset dac and driver must be in high-performance mode */ + headset_power_mode(codec, 1); + + twl6040_write(codec, TWL6040_REG_HPPLLCTL, hppllctl); + udelay(500); + lppllctl |= TWL6040_HPLLSEL; + twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl); + lppllctl &= ~TWL6040_LPLLENA; + twl6040_write(codec, TWL6040_REG_LPPLLCTL, lppllctl); + + /* high-performance pll can provide only 19.2 MHz */ + priv->pll = TWL6040_HPPLL_ID; + priv->sysclk = 19200000; + priv->sysclk_constraints = &hp_constraints; + break; + default: + dev_err(codec->dev, "unknown clk_id %d\n", clk_id); + return -EINVAL; + } + + return 0; +} + +static struct snd_soc_dai_ops twl6040_dai_ops = { + .startup = twl6040_startup, + .hw_params = twl6040_hw_params, + .trigger = twl6040_trigger, + .set_sysclk = twl6040_set_dai_sysclk, +}; + +struct snd_soc_dai twl6040_dai = { + .name = "twl6040", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 4, + .rates = TWL6040_RATES, + .formats = TWL6040_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = TWL6040_RATES, + .formats = TWL6040_FORMATS, + }, + .ops = &twl6040_dai_ops, +}; +EXPORT_SYMBOL_GPL(twl6040_dai); + +#ifdef CONFIG_PM +static int twl6040_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; + + twl6040_set_bias_level(codec, SND_SOC_BIAS_OFF); + + return 0; +} + +static int twl6040_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + twl6040_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + return 0; +} +#else +#define twl6040_suspend NULL +#define twl6040_resume NULL +#endif + +static struct snd_soc_codec *twl6040_codec; + +static int twl6040_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + + BUG_ON(!twl6040_codec); + + codec = twl6040_codec; + socdev->card->codec = codec; + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(&pdev->dev, "failed to create pcms\n"); + return ret; + } + + snd_soc_add_controls(codec, twl6040_snd_controls, + ARRAY_SIZE(twl6040_snd_controls)); + twl6040_add_widgets(codec); + + if (ret < 0) { + dev_err(&pdev->dev, "failed to register card\n"); + goto card_err; + } + + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + return ret; +} + +static int twl6040_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + twl6040_set_bias_level(codec, SND_SOC_BIAS_OFF); + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_twl6040 = { + .probe = twl6040_probe, + .remove = twl6040_remove, + .suspend = twl6040_suspend, + .resume = twl6040_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_twl6040); + +static int __devinit twl6040_codec_probe(struct platform_device *pdev) +{ + struct twl4030_codec_data *twl_codec = pdev->dev.platform_data; + struct snd_soc_codec *codec; + struct twl6040_data *priv; + int audpwron, naudint; + int ret = 0; + + priv = kzalloc(sizeof(struct twl6040_data), GFP_KERNEL); + if (priv == NULL) + return -ENOMEM; + + if (twl_codec) { + audpwron = twl_codec->audpwron_gpio; + naudint = twl_codec->naudint_irq; + } else { + audpwron = -EINVAL; + naudint = 0; + } + + priv->audpwron = audpwron; + priv->naudint = naudint; + + codec = &priv->codec; + codec->dev = &pdev->dev; + twl6040_dai.dev = &pdev->dev; + + codec->name = "twl6040"; + codec->owner = THIS_MODULE; + codec->read = twl6040_read_reg_cache; + codec->write = twl6040_write; + codec->set_bias_level = twl6040_set_bias_level; + snd_soc_codec_set_drvdata(codec, priv); + codec->dai = &twl6040_dai; + codec->num_dai = 1; + codec->reg_cache_size = ARRAY_SIZE(twl6040_reg); + codec->reg_cache = kmemdup(twl6040_reg, sizeof(twl6040_reg), + GFP_KERNEL); + if (codec->reg_cache == NULL) { + ret = -ENOMEM; + goto cache_err; + } + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + init_completion(&priv->ready); + + if (gpio_is_valid(audpwron)) { + ret = gpio_request(audpwron, "audpwron"); + if (ret) + goto gpio1_err; + + ret = gpio_direction_output(audpwron, 0); + if (ret) + goto gpio2_err; + + priv->codec_powered = 0; + } + + if (naudint) { + /* audio interrupt */ + ret = request_threaded_irq(naudint, NULL, + twl6040_naudint_handler, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "twl6040_codec", codec); + if (ret) + goto gpio2_err; + } else { + if (gpio_is_valid(audpwron)) { + /* enable only codec ready interrupt */ + twl6040_write_reg_cache(codec, TWL6040_REG_INTMR, + ~TWL6040_READYMSK & TWL6040_ALLINT_MSK); + } else { + /* no interrupts at all */ + twl6040_write_reg_cache(codec, TWL6040_REG_INTMR, + TWL6040_ALLINT_MSK); + } + } + + /* init vio registers */ + twl6040_init_vio_regs(codec); + + /* power on device */ + ret = twl6040_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + if (ret) + goto irq_err; + + ret = snd_soc_register_codec(codec); + if (ret) + goto reg_err; + + twl6040_codec = codec; + + ret = snd_soc_register_dai(&twl6040_dai); + if (ret) + goto dai_err; + + return 0; + +dai_err: + snd_soc_unregister_codec(codec); + twl6040_codec = NULL; +reg_err: + twl6040_set_bias_level(codec, SND_SOC_BIAS_OFF); +irq_err: + if (naudint) + free_irq(naudint, codec); +gpio2_err: + if (gpio_is_valid(audpwron)) + gpio_free(audpwron); +gpio1_err: + kfree(codec->reg_cache); +cache_err: + kfree(priv); + return ret; +} + +static int __devexit twl6040_codec_remove(struct platform_device *pdev) +{ + struct twl6040_data *priv = snd_soc_codec_get_drvdata(twl6040_codec); + int audpwron = priv->audpwron; + int naudint = priv->naudint; + + if (gpio_is_valid(audpwron)) + gpio_free(audpwron); + + if (naudint) + free_irq(naudint, twl6040_codec); + + snd_soc_unregister_dai(&twl6040_dai); + snd_soc_unregister_codec(twl6040_codec); + + kfree(twl6040_codec); + twl6040_codec = NULL; + + return 0; +} + +static struct platform_driver twl6040_codec_driver = { + .driver = { + .name = "twl6040_codec", + .owner = THIS_MODULE, + }, + .probe = twl6040_codec_probe, + .remove = __devexit_p(twl6040_codec_remove), +}; + +static int __init twl6040_codec_init(void) +{ + return platform_driver_register(&twl6040_codec_driver); +} +module_init(twl6040_codec_init); + +static void __exit twl6040_codec_exit(void) +{ + platform_driver_unregister(&twl6040_codec_driver); +} +module_exit(twl6040_codec_exit); + +MODULE_DESCRIPTION("ASoC TWL6040 codec driver"); +MODULE_AUTHOR("Misael Lopez Cruz"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/twl6040.h b/sound/soc/codecs/twl6040.h new file mode 100644 index 0000000..c472070 --- /dev/null +++ b/sound/soc/codecs/twl6040.h @@ -0,0 +1,141 @@ +/* + * ALSA SoC TWL6040 codec driver + * + * Author: Misael Lopez Cruz <x0052729@ti.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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __TWL6040_H__ +#define __TWL6040_H__ + +#define TWL6040_REG_ASICID 0x01 +#define TWL6040_REG_ASICREV 0x02 +#define TWL6040_REG_INTID 0x03 +#define TWL6040_REG_INTMR 0x04 +#define TWL6040_REG_NCPCTL 0x05 +#define TWL6040_REG_LDOCTL 0x06 +#define TWL6040_REG_HPPLLCTL 0x07 +#define TWL6040_REG_LPPLLCTL 0x08 +#define TWL6040_REG_LPPLLDIV 0x09 +#define TWL6040_REG_AMICBCTL 0x0A +#define TWL6040_REG_DMICBCTL 0x0B +#define TWL6040_REG_MICLCTL 0x0C +#define TWL6040_REG_MICRCTL 0x0D +#define TWL6040_REG_MICGAIN 0x0E +#define TWL6040_REG_LINEGAIN 0x0F +#define TWL6040_REG_HSLCTL 0x10 +#define TWL6040_REG_HSRCTL 0x11 +#define TWL6040_REG_HSGAIN 0x12 +#define TWL6040_REG_EARCTL 0x13 +#define TWL6040_REG_HFLCTL 0x14 +#define TWL6040_REG_HFLGAIN 0x15 +#define TWL6040_REG_HFRCTL 0x16 +#define TWL6040_REG_HFRGAIN 0x17 +#define TWL6040_REG_VIBCTLL 0x18 +#define TWL6040_REG_VIBDATL 0x19 +#define TWL6040_REG_VIBCTLR 0x1A +#define TWL6040_REG_VIBDATR 0x1B +#define TWL6040_REG_HKCTL1 0x1C +#define TWL6040_REG_HKCTL2 0x1D +#define TWL6040_REG_GPOCTL 0x1E +#define TWL6040_REG_ALB 0x1F +#define TWL6040_REG_DLB 0x20 +#define TWL6040_REG_TRIM1 0x28 +#define TWL6040_REG_TRIM2 0x29 +#define TWL6040_REG_TRIM3 0x2A +#define TWL6040_REG_HSOTRIM 0x2B +#define TWL6040_REG_HFOTRIM 0x2C +#define TWL6040_REG_ACCCTL 0x2D +#define TWL6040_REG_STATUS 0x2E + +#define TWL6040_CACHEREGNUM (TWL6040_REG_STATUS + 1) + +#define TWL6040_VIOREGNUM 18 +#define TWL6040_VDDREGNUM 21 + +/* INTID (0x03) fields */ + +#define TWL6040_THINT 0x01 +#define TWL6040_PLUGINT 0x02 +#define TWL6040_UNPLUGINT 0x04 +#define TWL6040_HOOKINT 0x08 +#define TWL6040_HFINT 0x10 +#define TWL6040_VIBINT 0x20 +#define TWL6040_READYINT 0x40 + +/* INTMR (0x04) fields */ + +#define TWL6040_READYMSK 0x40 +#define TWL6040_ALLINT_MSK 0x7B + +/* NCPCTL (0x05) fields */ + +#define TWL6040_NCPENA 0x01 +#define TWL6040_NCPOPEN 0x40 + +/* LDOCTL (0x06) fields */ + +#define TWL6040_LSLDOENA 0x01 +#define TWL6040_HSLDOENA 0x04 +#define TWL6040_REFENA 0x40 +#define TWL6040_OSCENA 0x80 + +/* HPPLLCTL (0x07) fields */ + +#define TWL6040_HPLLENA 0x01 +#define TWL6040_HPLLRST 0x02 +#define TWL6040_HPLLBP 0x04 +#define TWL6040_HPLLSQRENA 0x08 +#define TWL6040_HPLLSQRBP 0x10 +#define TWL6040_MCLK_12000KHZ (0 << 5) +#define TWL6040_MCLK_19200KHZ (1 << 5) +#define TWL6040_MCLK_26000KHZ (2 << 5) +#define TWL6040_MCLK_38400KHZ (3 << 5) +#define TWL6040_MCLK_MSK 0x60 + +/* LPPLLCTL (0x08) fields */ + +#define TWL6040_LPLLENA 0x01 +#define TWL6040_LPLLRST 0x02 +#define TWL6040_LPLLSEL 0x04 +#define TWL6040_LPLLFIN 0x08 +#define TWL6040_HPLLSEL 0x10 + +/* HSLCTL (0x10) fields */ + +#define TWL6040_HSDACMODEL 0x02 +#define TWL6040_HSDRVMODEL 0x08 + +/* HSRCTL (0x11) fields */ + +#define TWL6040_HSDACMODER 0x02 +#define TWL6040_HSDRVMODER 0x08 + +/* ACCCTL (0x2D) fields */ + +#define TWL6040_RESETSPLIT 0x04 + +#define TWL6040_SYSCLK_SEL_LPPLL 1 +#define TWL6040_SYSCLK_SEL_HPPLL 2 + +#define TWL6040_HPPLL_ID 1 +#define TWL6040_LPPLL_ID 2 + +extern struct snd_soc_dai twl6040_dai; +extern struct snd_soc_codec_device soc_codec_dev_twl6040; + +#endif /* End of __TWL6040_H__ */ diff --git a/sound/soc/codecs/uda134x.c b/sound/soc/codecs/uda134x.c index a8dcd5a..28aac53 100644 --- a/sound/soc/codecs/uda134x.c +++ b/sound/soc/codecs/uda134x.c @@ -175,7 +175,7 @@ static int uda134x_startup(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 uda134x_priv *uda134x = codec->private_data; + struct uda134x_priv *uda134x = snd_soc_codec_get_drvdata(codec); struct snd_pcm_runtime *master_runtime; if (uda134x->master_substream) { @@ -208,7 +208,7 @@ static void uda134x_shutdown(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 uda134x_priv *uda134x = codec->private_data; + struct uda134x_priv *uda134x = snd_soc_codec_get_drvdata(codec); if (uda134x->master_substream == substream) uda134x->master_substream = uda134x->slave_substream; @@ -223,7 +223,7 @@ static int uda134x_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 uda134x_priv *uda134x = codec->private_data; + struct uda134x_priv *uda134x = snd_soc_codec_get_drvdata(codec); u8 hw_params; if (substream == uda134x->slave_substream) { @@ -295,7 +295,7 @@ static int uda134x_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 uda134x_priv *uda134x = codec->private_data; + struct uda134x_priv *uda134x = snd_soc_codec_get_drvdata(codec); pr_debug("%s clk_id: %d, freq: %u, dir: %d\n", __func__, clk_id, freq, dir); @@ -317,7 +317,7 @@ static int uda134x_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { struct snd_soc_codec *codec = codec_dai->codec; - struct uda134x_priv *uda134x = codec->private_data; + struct uda134x_priv *uda134x = snd_soc_codec_get_drvdata(codec); pr_debug("%s fmt: %08X\n", __func__, fmt); @@ -432,6 +432,14 @@ SOC_ENUM("PCM Playback De-emphasis", uda134x_mixer_enum[1]), SOC_SINGLE("DC Filter Enable Switch", UDA134X_STATUS0, 0, 1, 0), }; +static const struct snd_kcontrol_new uda1345_snd_controls[] = { +SOC_SINGLE("Master Playback Volume", UDA134X_DATA000, 0, 0x3F, 1), + +SOC_ENUM("PCM Playback De-emphasis", uda134x_mixer_enum[1]), + +SOC_SINGLE("DC Filter Enable Switch", UDA134X_STATUS0, 0, 1, 0), +}; + static struct snd_soc_dai_ops uda134x_dai_ops = { .startup = uda134x_startup, .shutdown = uda134x_shutdown, @@ -487,6 +495,7 @@ static int uda134x_soc_probe(struct platform_device *pdev) case UDA134X_UDA1340: case UDA134X_UDA1341: case UDA134X_UDA1344: + case UDA134X_UDA1345: break; default: printk(KERN_ERR "UDA134X SoC codec: " @@ -504,7 +513,7 @@ static int uda134x_soc_probe(struct platform_device *pdev) uda134x = kzalloc(sizeof(struct uda134x_priv), GFP_KERNEL); if (uda134x == NULL) goto priv_err; - codec->private_data = uda134x; + snd_soc_codec_set_drvdata(codec, uda134x); codec->reg_cache = kmemdup(uda134x_reg, sizeof(uda134x_reg), GFP_KERNEL); @@ -552,6 +561,10 @@ static int uda134x_soc_probe(struct platform_device *pdev) ret = snd_soc_add_controls(codec, uda1341_snd_controls, ARRAY_SIZE(uda1341_snd_controls)); break; + case UDA134X_UDA1345: + ret = snd_soc_add_controls(codec, uda1345_snd_controls, + ARRAY_SIZE(uda1345_snd_controls)); + break; default: printk(KERN_ERR "%s unknown codec type: %d", __func__, pd->model); @@ -568,7 +581,7 @@ static int uda134x_soc_probe(struct platform_device *pdev) pcm_err: kfree(codec->reg_cache); reg_err: - kfree(codec->private_data); + kfree(snd_soc_codec_get_drvdata(codec)); priv_err: kfree(codec); return ret; @@ -586,7 +599,7 @@ static int uda134x_soc_remove(struct platform_device *pdev) snd_soc_free_pcms(socdev); snd_soc_dapm_free(socdev); - kfree(codec->private_data); + kfree(snd_soc_codec_get_drvdata(codec)); kfree(codec->reg_cache); kfree(codec); diff --git a/sound/soc/codecs/uda1380.c b/sound/soc/codecs/uda1380.c index 9cd0a66..2f925a2 100644 --- a/sound/soc/codecs/uda1380.c +++ b/sound/soc/codecs/uda1380.c @@ -476,7 +476,7 @@ static int uda1380_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_codec *codec = socdev->card->codec; - struct uda1380_priv *uda1380 = codec->private_data; + struct uda1380_priv *uda1380 = snd_soc_codec_get_drvdata(codec); int mixer = uda1380_read_reg_cache(codec, UDA1380_MIXER); switch (cmd) { @@ -670,7 +670,6 @@ static int uda1380_resume(struct platform_device *pdev) codec->hw_write(codec->control_data, data, 2); } uda1380_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - uda1380_set_bias_level(codec, codec->suspend_bias_level); return 0; } @@ -774,7 +773,7 @@ static int uda1380_register(struct uda1380_priv *uda1380) INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); - codec->private_data = uda1380; + snd_soc_codec_set_drvdata(codec, uda1380); codec->name = "UDA1380"; codec->owner = THIS_MODULE; codec->read = uda1380_read_reg_cache; diff --git a/sound/soc/codecs/wm8350.c b/sound/soc/codecs/wm8350.c index 2e0772f..8ae2020 100644 --- a/sound/soc/codecs/wm8350.c +++ b/sound/soc/codecs/wm8350.c @@ -55,6 +55,7 @@ struct wm8350_output { struct wm8350_jack_data { struct snd_soc_jack *jack; int report; + int short_report; }; struct wm8350_data { @@ -63,6 +64,7 @@ struct wm8350_data { struct wm8350_output out2; struct wm8350_jack_data hpl; struct wm8350_jack_data hpr; + struct wm8350_jack_data mic; struct regulator_bulk_data supplies[ARRAY_SIZE(supply_names)]; int fll_freq_out; int fll_freq_in; @@ -94,7 +96,7 @@ static int wm8350_codec_write(struct snd_soc_codec *codec, unsigned int reg, */ static inline int wm8350_out1_ramp_step(struct snd_soc_codec *codec) { - struct wm8350_data *wm8350_data = codec->private_data; + struct wm8350_data *wm8350_data = snd_soc_codec_get_drvdata(codec); struct wm8350_output *out1 = &wm8350_data->out1; struct wm8350 *wm8350 = codec->control_data; int left_complete = 0, right_complete = 0; @@ -160,7 +162,7 @@ static inline int wm8350_out1_ramp_step(struct snd_soc_codec *codec) */ static inline int wm8350_out2_ramp_step(struct snd_soc_codec *codec) { - struct wm8350_data *wm8350_data = codec->private_data; + struct wm8350_data *wm8350_data = snd_soc_codec_get_drvdata(codec); struct wm8350_output *out2 = &wm8350_data->out2; struct wm8350 *wm8350 = codec->control_data; int left_complete = 0, right_complete = 0; @@ -230,7 +232,7 @@ static void wm8350_pga_work(struct work_struct *work) { struct snd_soc_codec *codec = container_of(work, struct snd_soc_codec, delayed_work.work); - struct wm8350_data *wm8350_data = codec->private_data; + struct wm8350_data *wm8350_data = snd_soc_codec_get_drvdata(codec); struct wm8350_output *out1 = &wm8350_data->out1, *out2 = &wm8350_data->out2; int i, out1_complete, out2_complete; @@ -277,7 +279,7 @@ static int pga_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { struct snd_soc_codec *codec = w->codec; - struct wm8350_data *wm8350_data = codec->private_data; + struct wm8350_data *wm8350_data = snd_soc_codec_get_drvdata(codec); struct wm8350_output *out; switch (w->shift) { @@ -322,7 +324,7 @@ static int wm8350_put_volsw_2r_vu(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - struct wm8350_data *wm8350_priv = codec->private_data; + struct wm8350_data *wm8350_priv = snd_soc_codec_get_drvdata(codec); struct wm8350_output *out = NULL; struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value; @@ -365,7 +367,7 @@ static int wm8350_get_volsw_2r(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - struct wm8350_data *wm8350_priv = codec->private_data; + struct wm8350_data *wm8350_priv = snd_soc_codec_get_drvdata(codec); struct wm8350_output *out1 = &wm8350_priv->out1; struct wm8350_output *out2 = &wm8350_priv->out2; struct soc_mixer_control *mc = @@ -1107,7 +1109,7 @@ static int wm8350_set_fll(struct snd_soc_dai *codec_dai, { struct snd_soc_codec *codec = codec_dai->codec; struct wm8350 *wm8350 = codec->control_data; - struct wm8350_data *priv = codec->private_data; + struct wm8350_data *priv = snd_soc_codec_get_drvdata(codec); struct _fll_div fll_div; int ret = 0; u16 fll_1, fll_4; @@ -1159,7 +1161,7 @@ static int wm8350_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { struct wm8350 *wm8350 = codec->control_data; - struct wm8350_data *priv = codec->private_data; + struct wm8350_data *priv = snd_soc_codec_get_drvdata(codec); struct wm8350_audio_platform_data *platform = wm8350->codec.platform_data; u16 pm1; @@ -1335,9 +1337,6 @@ static int wm8350_resume(struct platform_device *pdev) wm8350_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - if (codec->suspend_bias_level == SND_SOC_BIAS_ON) - wm8350_set_bias_level(codec, SND_SOC_BIAS_ON); - return 0; } @@ -1392,12 +1391,13 @@ static irqreturn_t wm8350_hp_jack_handler(int irq, void *data) * @jack: jack to report detection events on * @report: value to report * - * Enables the headphone jack detection of the WM8350. + * Enables the headphone jack detection of the WM8350. If no report + * is specified then detection is disabled. */ int wm8350_hp_jack_detect(struct snd_soc_codec *codec, enum wm8350_jack which, struct snd_soc_jack *jack, int report) { - struct wm8350_data *priv = codec->private_data; + struct wm8350_data *priv = snd_soc_codec_get_drvdata(codec); struct wm8350 *wm8350 = codec->control_data; int irq; int ena; @@ -1421,8 +1421,12 @@ int wm8350_hp_jack_detect(struct snd_soc_codec *codec, enum wm8350_jack which, return -EINVAL; } - wm8350_set_bits(wm8350, WM8350_POWER_MGMT_4, WM8350_TOCLK_ENA); - wm8350_set_bits(wm8350, WM8350_JACK_DETECT, ena); + if (report) { + wm8350_set_bits(wm8350, WM8350_POWER_MGMT_4, WM8350_TOCLK_ENA); + wm8350_set_bits(wm8350, WM8350_JACK_DETECT, ena); + } else { + wm8350_clear_bits(wm8350, WM8350_JACK_DETECT, ena); + } /* Sync status */ wm8350_hp_jack_handler(irq + wm8350->irq_base, priv); @@ -1431,6 +1435,60 @@ int wm8350_hp_jack_detect(struct snd_soc_codec *codec, enum wm8350_jack which, } EXPORT_SYMBOL_GPL(wm8350_hp_jack_detect); +static irqreturn_t wm8350_mic_handler(int irq, void *data) +{ + struct wm8350_data *priv = data; + struct wm8350 *wm8350 = priv->codec.control_data; + u16 reg; + int report = 0; + + reg = wm8350_reg_read(wm8350, WM8350_JACK_PIN_STATUS); + if (reg & WM8350_JACK_MICSCD_LVL) + report |= priv->mic.short_report; + if (reg & WM8350_JACK_MICSD_LVL) + report |= priv->mic.report; + + snd_soc_jack_report(priv->mic.jack, report, + priv->mic.report | priv->mic.short_report); + + return IRQ_HANDLED; +} + +/** + * wm8350_mic_jack_detect - Enable microphone jack detection. + * + * @codec: WM8350 codec + * @jack: jack to report detection events on + * @detect_report: value to report when presence detected + * @short_report: value to report when microphone short detected + * + * Enables the microphone jack detection of the WM8350. If both reports + * are specified as zero then detection is disabled. + */ +int wm8350_mic_jack_detect(struct snd_soc_codec *codec, + struct snd_soc_jack *jack, + int detect_report, int short_report) +{ + struct wm8350_data *priv = snd_soc_codec_get_drvdata(codec); + struct wm8350 *wm8350 = codec->control_data; + + priv->mic.jack = jack; + priv->mic.report = detect_report; + priv->mic.short_report = short_report; + + if (detect_report || short_report) { + wm8350_set_bits(wm8350, WM8350_POWER_MGMT_4, WM8350_TOCLK_ENA); + wm8350_set_bits(wm8350, WM8350_POWER_MGMT_1, + WM8350_MIC_DET_ENA); + } else { + wm8350_clear_bits(wm8350, WM8350_POWER_MGMT_1, + WM8350_MIC_DET_ENA); + } + + return 0; +} +EXPORT_SYMBOL_GPL(wm8350_mic_jack_detect); + static struct snd_soc_codec *wm8350_codec; static int wm8350_probe(struct platform_device *pdev) @@ -1448,7 +1506,7 @@ static int wm8350_probe(struct platform_device *pdev) socdev->card->codec = wm8350_codec; codec = socdev->card->codec; wm8350 = codec->control_data; - priv = codec->private_data; + priv = snd_soc_codec_get_drvdata(codec); /* Enable the codec */ wm8350_set_bits(wm8350, WM8350_POWER_MGMT_5, WM8350_CODEC_ENA); @@ -1494,6 +1552,10 @@ static int wm8350_probe(struct platform_device *pdev) wm8350_register_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_R, wm8350_hp_jack_handler, 0, "Right jack detect", priv); + wm8350_register_irq(wm8350, WM8350_IRQ_CODEC_MICSCD, + wm8350_mic_handler, 0, "Microphone short", priv); + wm8350_register_irq(wm8350, WM8350_IRQ_CODEC_MICD, + wm8350_mic_handler, 0, "Microphone detect", priv); ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); if (ret < 0) { @@ -1515,18 +1577,21 @@ static int wm8350_remove(struct platform_device *pdev) struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = socdev->card->codec; struct wm8350 *wm8350 = codec->control_data; - struct wm8350_data *priv = codec->private_data; + struct wm8350_data *priv = snd_soc_codec_get_drvdata(codec); int ret; wm8350_clear_bits(wm8350, WM8350_JACK_DETECT, WM8350_JDL_ENA | WM8350_JDR_ENA); wm8350_clear_bits(wm8350, WM8350_POWER_MGMT_4, WM8350_TOCLK_ENA); + wm8350_free_irq(wm8350, WM8350_IRQ_CODEC_MICD, priv); + wm8350_free_irq(wm8350, WM8350_IRQ_CODEC_MICSCD, priv); wm8350_free_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_L, priv); wm8350_free_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_R, priv); priv->hpl.jack = NULL; priv->hpr.jack = NULL; + priv->mic.jack = NULL; /* cancel any work waiting to be queued. */ ret = cancel_delayed_work(&codec->delayed_work); @@ -1631,7 +1696,7 @@ static __devinit int wm8350_codec_probe(struct platform_device *pdev) codec->dai = &wm8350_dai; codec->num_dai = 1; codec->reg_cache_size = WM8350_MAX_REGISTER; - codec->private_data = priv; + snd_soc_codec_set_drvdata(codec, priv); codec->control_data = wm8350; /* Put the codec into reset if it wasn't already */ @@ -1663,7 +1728,7 @@ static int __devexit wm8350_codec_remove(struct platform_device *pdev) { struct wm8350 *wm8350 = platform_get_drvdata(pdev); struct snd_soc_codec *codec = wm8350->codec.codec; - struct wm8350_data *priv = codec->private_data; + struct wm8350_data *priv = snd_soc_codec_get_drvdata(codec); snd_soc_unregister_dai(&wm8350_dai); snd_soc_unregister_codec(codec); diff --git a/sound/soc/codecs/wm8350.h b/sound/soc/codecs/wm8350.h index d088eb4..9ed0467 100644 --- a/sound/soc/codecs/wm8350.h +++ b/sound/soc/codecs/wm8350.h @@ -25,5 +25,8 @@ enum wm8350_jack { int wm8350_hp_jack_detect(struct snd_soc_codec *codec, enum wm8350_jack which, struct snd_soc_jack *jack, int report); +int wm8350_mic_jack_detect(struct snd_soc_codec *codec, + struct snd_soc_jack *jack, + int detect_report, int short_report); #endif diff --git a/sound/soc/codecs/wm8400.c b/sound/soc/codecs/wm8400.c index 6acc885..7f5d080 100644 --- a/sound/soc/codecs/wm8400.c +++ b/sound/soc/codecs/wm8400.c @@ -77,7 +77,7 @@ struct wm8400_priv { static inline unsigned int wm8400_read(struct snd_soc_codec *codec, unsigned int reg) { - struct wm8400_priv *wm8400 = codec->private_data; + struct wm8400_priv *wm8400 = snd_soc_codec_get_drvdata(codec); if (reg == WM8400_INTDRIVBITS) return wm8400->fake_register; @@ -91,7 +91,7 @@ static inline unsigned int wm8400_read(struct snd_soc_codec *codec, static int wm8400_write(struct snd_soc_codec *codec, unsigned int reg, unsigned int value) { - struct wm8400_priv *wm8400 = codec->private_data; + struct wm8400_priv *wm8400 = snd_soc_codec_get_drvdata(codec); if (reg == WM8400_INTDRIVBITS) { wm8400->fake_register = value; @@ -102,7 +102,7 @@ static int wm8400_write(struct snd_soc_codec *codec, unsigned int reg, static void wm8400_codec_reset(struct snd_soc_codec *codec) { - struct wm8400_priv *wm8400 = codec->private_data; + struct wm8400_priv *wm8400 = snd_soc_codec_get_drvdata(codec); wm8400_reset_codec_reg_cache(wm8400->wm8400); } @@ -926,7 +926,7 @@ static int wm8400_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 wm8400_priv *wm8400 = codec->private_data; + struct wm8400_priv *wm8400 = snd_soc_codec_get_drvdata(codec); wm8400->sysclk = freq; return 0; @@ -1015,7 +1015,7 @@ static int wm8400_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, unsigned int freq_out) { struct snd_soc_codec *codec = codec_dai->codec; - struct wm8400_priv *wm8400 = codec->private_data; + struct wm8400_priv *wm8400 = snd_soc_codec_get_drvdata(codec); struct fll_factors factors; int ret; u16 reg; @@ -1204,7 +1204,7 @@ static int wm8400_mute(struct snd_soc_dai *dai, int mute) static int wm8400_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { - struct wm8400_priv *wm8400 = codec->private_data; + struct wm8400_priv *wm8400 = snd_soc_codec_get_drvdata(codec); u16 val; int ret; @@ -1467,7 +1467,7 @@ static int wm8400_codec_probe(struct platform_device *dev) return -ENOMEM; codec = &priv->codec; - codec->private_data = priv; + snd_soc_codec_set_drvdata(codec, priv); codec->control_data = dev_get_drvdata(&dev->dev); priv->wm8400 = dev_get_drvdata(&dev->dev); @@ -1530,7 +1530,7 @@ err: static int __exit wm8400_codec_remove(struct platform_device *dev) { - struct wm8400_priv *priv = wm8400_codec->private_data; + struct wm8400_priv *priv = snd_soc_codec_get_drvdata(wm8400_codec); u16 reg; snd_soc_unregister_dai(&wm8400_dai); diff --git a/sound/soc/codecs/wm8510.c b/sound/soc/codecs/wm8510.c index 9000b1d..0f7bcb6 100644 --- a/sound/soc/codecs/wm8510.c +++ b/sound/soc/codecs/wm8510.c @@ -557,7 +557,7 @@ static int wm8510_resume(struct platform_device *pdev) codec->hw_write(codec->control_data, data, 2); } wm8510_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - wm8510_set_bias_level(codec, codec->suspend_bias_level); + return 0; } diff --git a/sound/soc/codecs/wm8523.c b/sound/soc/codecs/wm8523.c index 19cd472..37242a7 100644 --- a/sound/soc/codecs/wm8523.c +++ b/sound/soc/codecs/wm8523.c @@ -138,7 +138,7 @@ static int wm8523_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_codec *codec = dai->codec; - struct wm8523_priv *wm8523 = codec->private_data; + struct wm8523_priv *wm8523 = 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. @@ -164,7 +164,7 @@ static int wm8523_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 wm8523_priv *wm8523 = codec->private_data; + struct wm8523_priv *wm8523 = snd_soc_codec_get_drvdata(codec); int i; u16 aifctrl1 = snd_soc_read(codec, WM8523_AIF_CTRL1); u16 aifctrl2 = snd_soc_read(codec, WM8523_AIF_CTRL2); @@ -211,7 +211,7 @@ static int wm8523_set_dai_sysclk(struct snd_soc_dai *codec_dai, int clk_id, unsigned int freq, int dir) { struct snd_soc_codec *codec = codec_dai->codec; - struct wm8523_priv *wm8523 = codec->private_data; + struct wm8523_priv *wm8523 = snd_soc_codec_get_drvdata(codec); unsigned int val; int i; @@ -318,7 +318,7 @@ static int wm8523_set_dai_fmt(struct snd_soc_dai *codec_dai, static int wm8523_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { - struct wm8523_priv *wm8523 = codec->private_data; + struct wm8523_priv *wm8523 = snd_soc_codec_get_drvdata(codec); int ret, i; switch (level) { @@ -489,7 +489,7 @@ static int wm8523_register(struct wm8523_priv *wm8523, INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); - codec->private_data = wm8523; + snd_soc_codec_set_drvdata(codec, wm8523); codec->name = "WM8523"; codec->owner = THIS_MODULE; codec->bias_level = SND_SOC_BIAS_OFF; diff --git a/sound/soc/codecs/wm8580.c b/sound/soc/codecs/wm8580.c index 8cc9042..c3571ee 100644 --- a/sound/soc/codecs/wm8580.c +++ b/sound/soc/codecs/wm8580.c @@ -412,7 +412,7 @@ static int wm8580_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, { int offset; struct snd_soc_codec *codec = codec_dai->codec; - struct wm8580_priv *wm8580 = codec->private_data; + struct wm8580_priv *wm8580 = snd_soc_codec_get_drvdata(codec); struct pll_state *state; struct _pll_div pll_div; unsigned int reg; @@ -840,7 +840,7 @@ static int wm8580_register(struct wm8580_priv *wm8580, INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); - codec->private_data = wm8580; + snd_soc_codec_set_drvdata(codec, wm8580); codec->name = "WM8580"; codec->owner = THIS_MODULE; codec->bias_level = SND_SOC_BIAS_OFF; diff --git a/sound/soc/codecs/wm8711.c b/sound/soc/codecs/wm8711.c index 8ca3812..effb14e 100644 --- a/sound/soc/codecs/wm8711.c +++ b/sound/soc/codecs/wm8711.c @@ -163,7 +163,7 @@ static int wm8711_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_codec *codec = dai->codec; - struct wm8711_priv *wm8711 = codec->private_data; + struct wm8711_priv *wm8711 = snd_soc_codec_get_drvdata(codec); u16 iface = snd_soc_read(codec, WM8711_IFACE) & 0xfffc; int i = get_coeff(wm8711->sysclk, params_rate(params)); u16 srate = (coeff_div[i].sr << 2) | @@ -227,7 +227,7 @@ static int wm8711_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 wm8711_priv *wm8711 = codec->private_data; + struct wm8711_priv *wm8711 = snd_soc_codec_get_drvdata(codec); switch (freq) { case 11289600: @@ -376,7 +376,7 @@ static int wm8711_resume(struct platform_device *pdev) codec->hw_write(codec->control_data, data, 2); } wm8711_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - wm8711_set_bias_level(codec, codec->suspend_bias_level); + return 0; } @@ -446,7 +446,7 @@ static int wm8711_register(struct wm8711_priv *wm8711, INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); - codec->private_data = wm8711; + snd_soc_codec_set_drvdata(codec, wm8711); codec->name = "WM8711"; codec->owner = THIS_MODULE; codec->bias_level = SND_SOC_BIAS_OFF; diff --git a/sound/soc/codecs/wm8728.c b/sound/soc/codecs/wm8728.c index 07adc37..34be2d2 100644 --- a/sound/soc/codecs/wm8728.c +++ b/sound/soc/codecs/wm8728.c @@ -238,7 +238,7 @@ static int wm8728_resume(struct platform_device *pdev) struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = socdev->card->codec; - wm8728_set_bias_level(codec, codec->suspend_bias_level); + wm8728_set_bias_level(codec, SND_SOC_BIAS_STANDBY); return 0; } diff --git a/sound/soc/codecs/wm8731.c b/sound/soc/codecs/wm8731.c index e7c6bf1..0ab9b63 100644 --- a/sound/soc/codecs/wm8731.c +++ b/sound/soc/codecs/wm8731.c @@ -225,7 +225,7 @@ static int wm8731_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 wm8731_priv *wm8731 = codec->private_data; + struct wm8731_priv *wm8731 = snd_soc_codec_get_drvdata(codec); u16 iface = snd_soc_read(codec, WM8731_IFACE) & 0xfff3; int i = get_coeff(wm8731->sysclk, params_rate(params)); u16 srate = (coeff_div[i].sr << 2) | @@ -292,7 +292,7 @@ static int wm8731_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 wm8731_priv *wm8731 = codec->private_data; + struct wm8731_priv *wm8731 = snd_soc_codec_get_drvdata(codec); switch (freq) { case 11289600: @@ -369,6 +369,10 @@ static int wm8731_set_dai_fmt(struct snd_soc_dai *codec_dai, static int wm8731_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { + struct wm8731_priv *wm8731 = snd_soc_codec_get_drvdata(codec); + int i, ret; + u8 data[2]; + u16 *cache = codec->reg_cache; u16 reg; switch (level) { @@ -377,6 +381,24 @@ static int wm8731_set_bias_level(struct snd_soc_codec *codec, case SND_SOC_BIAS_PREPARE: break; case SND_SOC_BIAS_STANDBY: + if (codec->bias_level == SND_SOC_BIAS_OFF) { + ret = regulator_bulk_enable(ARRAY_SIZE(wm8731->supplies), + wm8731->supplies); + if (ret != 0) + return ret; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < ARRAY_SIZE(wm8731_reg); i++) { + if (cache[i] == wm8731_reg[i]) + continue; + + data[0] = (i << 1) | ((cache[i] >> 8) + & 0x0001); + data[1] = cache[i] & 0x00ff; + codec->hw_write(codec->control_data, data, 2); + } + } + /* Clear PWROFF, gate CLKOUT, everything else as-is */ reg = snd_soc_read(codec, WM8731_PWR) & 0xff7f; snd_soc_write(codec, WM8731_PWR, reg | 0x0040); @@ -384,17 +406,15 @@ static int wm8731_set_bias_level(struct snd_soc_codec *codec, case SND_SOC_BIAS_OFF: snd_soc_write(codec, WM8731_ACTIVE, 0x0); snd_soc_write(codec, WM8731_PWR, 0xffff); + regulator_bulk_disable(ARRAY_SIZE(wm8731->supplies), + wm8731->supplies); break; } codec->bias_level = level; return 0; } -#define WM8731_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ - SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\ - SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\ - SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 |\ - SNDRV_PCM_RATE_96000) +#define WM8731_RATES SNDRV_PCM_RATE_8000_96000 #define WM8731_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE) @@ -432,12 +452,9 @@ static int wm8731_suspend(struct platform_device *pdev, pm_message_t state) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = socdev->card->codec; - struct wm8731_priv *wm8731 = codec->private_data; - snd_soc_write(codec, WM8731_ACTIVE, 0x0); wm8731_set_bias_level(codec, SND_SOC_BIAS_OFF); - regulator_bulk_disable(ARRAY_SIZE(wm8731->supplies), - wm8731->supplies); + return 0; } @@ -445,27 +462,8 @@ static int wm8731_resume(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = socdev->card->codec; - struct wm8731_priv *wm8731 = codec->private_data; - int i, ret; - u8 data[2]; - u16 *cache = codec->reg_cache; - ret = regulator_bulk_enable(ARRAY_SIZE(wm8731->supplies), - wm8731->supplies); - if (ret != 0) - return ret; - - /* Sync reg_cache with the hardware */ - for (i = 0; i < ARRAY_SIZE(wm8731_reg); i++) { - if (cache[i] == wm8731_reg[i]) - continue; - - data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001); - data[1] = cache[i] & 0x00ff; - codec->hw_write(codec->control_data, data, 2); - } wm8731_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - wm8731_set_bias_level(codec, codec->suspend_bias_level); return 0; } @@ -540,7 +538,7 @@ static int wm8731_register(struct wm8731_priv *wm8731, INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); - codec->private_data = wm8731; + snd_soc_codec_set_drvdata(codec, wm8731); codec->name = "WM8731"; codec->owner = THIS_MODULE; codec->bias_level = SND_SOC_BIAS_OFF; @@ -609,6 +607,9 @@ static int wm8731_register(struct wm8731_priv *wm8731, goto err_codec; } + /* Regulators will have been enabled by bias management */ + regulator_bulk_disable(ARRAY_SIZE(wm8731->supplies), wm8731->supplies); + return 0; err_codec: @@ -627,7 +628,6 @@ static void wm8731_unregister(struct wm8731_priv *wm8731) wm8731_set_bias_level(&wm8731->codec, SND_SOC_BIAS_OFF); snd_soc_unregister_dai(&wm8731_dai); snd_soc_unregister_codec(&wm8731->codec); - regulator_bulk_disable(ARRAY_SIZE(wm8731->supplies), wm8731->supplies); regulator_bulk_free(ARRAY_SIZE(wm8731->supplies), wm8731->supplies); kfree(wm8731); wm8731_codec = NULL; @@ -708,7 +708,7 @@ MODULE_DEVICE_TABLE(i2c, wm8731_i2c_id); static struct i2c_driver wm8731_i2c_driver = { .driver = { - .name = "WM8731 I2C Codec", + .name = "wm8731", .owner = THIS_MODULE, }, .probe = wm8731_i2c_probe, diff --git a/sound/soc/codecs/wm8750.c b/sound/soc/codecs/wm8750.c index 2916ed4..9407e19 100644 --- a/sound/soc/codecs/wm8750.c +++ b/sound/soc/codecs/wm8750.c @@ -30,13 +30,6 @@ #include "wm8750.h" -#define WM8750_VERSION "0.12" - -/* codec private data */ -struct wm8750_priv { - unsigned int sysclk; -}; - /* * wm8750 register cache * We can't read the WM8750 register space when we @@ -56,6 +49,13 @@ static const u16 wm8750_reg[] = { 0x0079, 0x0079, 0x0079, /* 40 */ }; +/* codec private data */ +struct wm8750_priv { + unsigned int sysclk; + struct snd_soc_codec codec; + u16 reg_cache[ARRAY_SIZE(wm8750_reg)]; +}; + #define wm8750_reset(c) snd_soc_write(c, WM8750_RESET, 0) /* @@ -483,7 +483,7 @@ static int wm8750_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 wm8750_priv *wm8750 = codec->private_data; + struct wm8750_priv *wm8750 = snd_soc_codec_get_drvdata(codec); switch (freq) { case 11289600: @@ -562,7 +562,7 @@ static int wm8750_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_codec *codec = socdev->card->codec; - struct wm8750_priv *wm8750 = codec->private_data; + struct wm8750_priv *wm8750 = snd_soc_codec_get_drvdata(codec); u16 iface = snd_soc_read(codec, WM8750_IFACE) & 0x1f3; u16 srate = snd_soc_read(codec, WM8750_SRATE) & 0x1c0; int coeff = get_coeff(wm8750->sysclk, params_rate(params)); @@ -614,10 +614,16 @@ static int wm8750_set_bias_level(struct snd_soc_codec *codec, snd_soc_write(codec, WM8750_PWR1, pwr_reg | 0x00c0); break; case SND_SOC_BIAS_PREPARE: - /* set vmid to 5k for quick power up */ - snd_soc_write(codec, WM8750_PWR1, pwr_reg | 0x01c1); break; case SND_SOC_BIAS_STANDBY: + if (codec->bias_level == SND_SOC_BIAS_OFF) { + /* Set VMID to 5k */ + snd_soc_write(codec, WM8750_PWR1, pwr_reg | 0x01c1); + + /* ...and ramp */ + msleep(1000); + } + /* mute dac and set vmid to 500k, enable VREF */ snd_soc_write(codec, WM8750_PWR1, pwr_reg | 0x0141); break; @@ -661,13 +667,6 @@ struct snd_soc_dai wm8750_dai = { }; EXPORT_SYMBOL_GPL(wm8750_dai); -static void wm8750_work(struct work_struct *work) -{ - struct snd_soc_codec *codec = - container_of(work, struct snd_soc_codec, delayed_work.work); - wm8750_set_bias_level(codec, codec->bias_level); -} - static int wm8750_suspend(struct platform_device *pdev, pm_message_t state) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); @@ -696,36 +695,92 @@ static int wm8750_resume(struct platform_device *pdev) wm8750_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - /* charge wm8750 caps */ - if (codec->suspend_bias_level == SND_SOC_BIAS_ON) { - wm8750_set_bias_level(codec, SND_SOC_BIAS_PREPARE); - codec->bias_level = SND_SOC_BIAS_ON; - schedule_delayed_work(&codec->delayed_work, - msecs_to_jiffies(1000)); + return 0; +} + +static struct snd_soc_codec *wm8750_codec; + +static int wm8750_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + + if (!wm8750_codec) { + dev_err(&pdev->dev, "WM8750 codec not yet registered\n"); + return -EINVAL; + } + + socdev->card->codec = wm8750_codec; + codec = wm8750_codec; + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + printk(KERN_ERR "wm8750: failed to create pcms\n"); + goto err; } + snd_soc_add_controls(codec, wm8750_snd_controls, + ARRAY_SIZE(wm8750_snd_controls)); + wm8750_add_widgets(codec); + + return 0; + +err: + return ret; +} + +/* power down chip */ +static int wm8750_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_wm8750 = { + .probe = wm8750_probe, + .remove = wm8750_remove, + .suspend = wm8750_suspend, + .resume = wm8750_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8750); + /* * initialise the WM8750 driver * register the mixer and dsp interfaces with the kernel */ -static int wm8750_init(struct snd_soc_device *socdev, - enum snd_soc_control_type control) +static int wm8750_register(struct wm8750_priv *wm8750, + enum snd_soc_control_type control) { - struct snd_soc_codec *codec = socdev->card->codec; + struct snd_soc_codec *codec = &wm8750->codec; int reg, ret = 0; + if (wm8750_codec) { + dev_err(codec->dev, "Multiple WM8750 devices not supported\n"); + ret = -EINVAL; + goto err; + } + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + codec->name = "WM8750"; codec->owner = THIS_MODULE; + codec->bias_level = SND_SOC_BIAS_STANDBY; codec->set_bias_level = wm8750_set_bias_level; codec->dai = &wm8750_dai; codec->num_dai = 1; - codec->reg_cache_size = ARRAY_SIZE(wm8750_reg); - codec->reg_cache = kmemdup(wm8750_reg, sizeof(wm8750_reg), GFP_KERNEL); - if (codec->reg_cache == NULL) - return -ENOMEM; + codec->reg_cache_size = ARRAY_SIZE(wm8750->reg_cache) + 1; + codec->reg_cache = &wm8750->reg_cache; + snd_soc_codec_set_drvdata(codec, wm8750); + + memcpy(codec->reg_cache, wm8750_reg, sizeof(wm8750->reg_cache)); ret = snd_soc_codec_set_cache_io(codec, 7, 9, control); if (ret < 0) { @@ -739,17 +794,8 @@ static int wm8750_init(struct snd_soc_device *socdev, goto err; } - /* register pcms */ - ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); - if (ret < 0) { - printk(KERN_ERR "wm8750: failed to create pcms\n"); - goto err; - } - /* charge output caps */ - wm8750_set_bias_level(codec, SND_SOC_BIAS_PREPARE); - codec->bias_level = SND_SOC_BIAS_STANDBY; - schedule_delayed_work(&codec->delayed_work, msecs_to_jiffies(1000)); + wm8750_set_bias_level(codec, SND_SOC_BIAS_STANDBY); /* set the update bits */ reg = snd_soc_read(codec, WM8750_LDAC); @@ -769,19 +815,37 @@ static int wm8750_init(struct snd_soc_device *socdev, reg = snd_soc_read(codec, WM8750_RINVOL); snd_soc_write(codec, WM8750_RINVOL, reg | 0x0100); - snd_soc_add_controls(codec, wm8750_snd_controls, - ARRAY_SIZE(wm8750_snd_controls)); - wm8750_add_widgets(codec); - return ret; + wm8750_codec = codec; + ret = snd_soc_register_codec(codec); + if (ret != 0) { + dev_err(codec->dev, "Failed to register codec: %d\n", ret); + goto err; + } + + ret = snd_soc_register_dais(&wm8750_dai, 1); + if (ret != 0) { + dev_err(codec->dev, "Failed to register DAIs: %d\n", ret); + goto err_codec; + } + + return 0; + +err_codec: + snd_soc_unregister_codec(codec); err: - kfree(codec->reg_cache); + kfree(wm8750); return ret; } -/* If the i2c layer weren't so broken, we could pass this kind of data - around */ -static struct snd_soc_device *wm8750_socdev; +static void wm8750_unregister(struct wm8750_priv *wm8750) +{ + wm8750_set_bias_level(&wm8750->codec, SND_SOC_BIAS_OFF); + snd_soc_unregister_dais(&wm8750_dai, 1); + snd_soc_unregister_codec(&wm8750->codec); + kfree(wm8750); + wm8750_codec = NULL; +} #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) @@ -795,24 +859,26 @@ static struct snd_soc_device *wm8750_socdev; static int wm8750_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { - struct snd_soc_device *socdev = wm8750_socdev; - struct snd_soc_codec *codec = socdev->card->codec; - int ret; + struct snd_soc_codec *codec; + struct wm8750_priv *wm8750; + + wm8750 = kzalloc(sizeof(struct wm8750_priv), GFP_KERNEL); + if (wm8750 == NULL) + return -ENOMEM; - i2c_set_clientdata(i2c, codec); + codec = &wm8750->codec; codec->control_data = i2c; + i2c_set_clientdata(i2c, wm8750); - ret = wm8750_init(socdev, SND_SOC_I2C); - if (ret < 0) - pr_err("failed to initialise WM8750\n"); + codec->dev = &i2c->dev; - return ret; + return wm8750_register(wm8750, SND_SOC_I2C); } static int wm8750_i2c_remove(struct i2c_client *client) { - struct snd_soc_codec *codec = i2c_get_clientdata(client); - kfree(codec->reg_cache); + struct wm8750_priv *wm8750 = i2c_get_clientdata(client); + wm8750_unregister(wm8750); return 0; } @@ -831,66 +897,31 @@ static struct i2c_driver wm8750_i2c_driver = { .remove = wm8750_i2c_remove, .id_table = wm8750_i2c_id, }; - -static int wm8750_add_i2c_device(struct platform_device *pdev, - const struct wm8750_setup_data *setup) -{ - struct i2c_board_info info; - struct i2c_adapter *adapter; - struct i2c_client *client; - int ret; - - ret = i2c_add_driver(&wm8750_i2c_driver); - if (ret != 0) { - dev_err(&pdev->dev, "can't add i2c driver\n"); - return ret; - } - - memset(&info, 0, sizeof(struct i2c_board_info)); - info.addr = setup->i2c_address; - strlcpy(info.type, "wm8750", I2C_NAME_SIZE); - - adapter = i2c_get_adapter(setup->i2c_bus); - if (!adapter) { - dev_err(&pdev->dev, "can't get i2c adapter %d\n", - setup->i2c_bus); - goto err_driver; - } - - client = i2c_new_device(adapter, &info); - i2c_put_adapter(adapter); - if (!client) { - dev_err(&pdev->dev, "can't add i2c device at 0x%x\n", - (unsigned int)info.addr); - goto err_driver; - } - - return 0; - -err_driver: - i2c_del_driver(&wm8750_i2c_driver); - return -ENODEV; -} #endif #if defined(CONFIG_SPI_MASTER) static int __devinit wm8750_spi_probe(struct spi_device *spi) { - struct snd_soc_device *socdev = wm8750_socdev; - struct snd_soc_codec *codec = socdev->card->codec; - int ret; + struct snd_soc_codec *codec; + struct wm8750_priv *wm8750; + + wm8750 = kzalloc(sizeof(struct wm8750_priv), GFP_KERNEL); + if (wm8750 == NULL) + return -ENOMEM; + codec = &wm8750->codec; codec->control_data = spi; + codec->dev = &spi->dev; - ret = wm8750_init(socdev, SND_SOC_SPI); - if (ret < 0) - dev_err(&spi->dev, "failed to initialise WM8750\n"); + dev_set_drvdata(&spi->dev, wm8750); - return ret; + return wm8750_register(wm8750, SND_SOC_SPI); } static int __devexit wm8750_spi_remove(struct spi_device *spi) { + struct wm8750_priv *wm8750 = dev_get_drvdata(&spi->dev); + wm8750_unregister(wm8750); return 0; } @@ -905,115 +936,31 @@ static struct spi_driver wm8750_spi_driver = { }; #endif -static int wm8750_probe(struct platform_device *pdev) +static int __init wm8750_modinit(void) { - struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct wm8750_setup_data *setup = socdev->codec_data; - struct snd_soc_codec *codec; - struct wm8750_priv *wm8750; int ret; - - pr_info("WM8750 Audio Codec %s", WM8750_VERSION); - codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); - if (codec == NULL) - return -ENOMEM; - - wm8750 = kzalloc(sizeof(struct wm8750_priv), GFP_KERNEL); - if (wm8750 == NULL) { - kfree(codec); - return -ENOMEM; - } - - codec->private_data = wm8750; - socdev->card->codec = codec; - mutex_init(&codec->mutex); - INIT_LIST_HEAD(&codec->dapm_widgets); - INIT_LIST_HEAD(&codec->dapm_paths); - wm8750_socdev = socdev; - INIT_DELAYED_WORK(&codec->delayed_work, wm8750_work); - - ret = -ENODEV; - #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) - if (setup->i2c_address) { - ret = wm8750_add_i2c_device(pdev, setup); - } + ret = i2c_add_driver(&wm8750_i2c_driver); + if (ret != 0) + pr_err("Failed to register WM8750 I2C driver: %d\n", ret); #endif #if defined(CONFIG_SPI_MASTER) - if (setup->spi) { - ret = spi_register_driver(&wm8750_spi_driver); - if (ret != 0) - printk(KERN_ERR "can't add spi driver"); - } + ret = spi_register_driver(&wm8750_spi_driver); + if (ret != 0) + pr_err("Failed to register WM8750 SPI driver: %d\n", ret); #endif - - if (ret != 0) { - kfree(codec->private_data); - kfree(codec); - } - return ret; -} - -/* - * This function forces any delayed work to be queued and run. - */ -static int run_delayed_work(struct delayed_work *dwork) -{ - int ret; - - /* cancel any work waiting to be queued. */ - ret = cancel_delayed_work(dwork); - - /* if there was any work waiting then we run it now and - * wait for it's completion */ - if (ret) { - schedule_delayed_work(dwork, 0); - flush_scheduled_work(); - } - return ret; + return 0; } +module_init(wm8750_modinit); -/* power down chip */ -static int wm8750_remove(struct platform_device *pdev) +static void __exit wm8750_exit(void) { - struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->card->codec; - - if (codec->control_data) - wm8750_set_bias_level(codec, SND_SOC_BIAS_OFF); - run_delayed_work(&codec->delayed_work); - snd_soc_free_pcms(socdev); - snd_soc_dapm_free(socdev); #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) - i2c_unregister_device(codec->control_data); i2c_del_driver(&wm8750_i2c_driver); #endif #if defined(CONFIG_SPI_MASTER) spi_unregister_driver(&wm8750_spi_driver); #endif - kfree(codec->private_data); - kfree(codec); - - return 0; -} - -struct snd_soc_codec_device soc_codec_dev_wm8750 = { - .probe = wm8750_probe, - .remove = wm8750_remove, - .suspend = wm8750_suspend, - .resume = wm8750_resume, -}; -EXPORT_SYMBOL_GPL(soc_codec_dev_wm8750); - -static int __init wm8750_modinit(void) -{ - return snd_soc_register_dai(&wm8750_dai); -} -module_init(wm8750_modinit); - -static void __exit wm8750_exit(void) -{ - snd_soc_unregister_dai(&wm8750_dai); } module_exit(wm8750_exit); diff --git a/sound/soc/codecs/wm8753.c b/sound/soc/codecs/wm8753.c index 613199a..b59f349 100644 --- a/sound/soc/codecs/wm8753.c +++ b/sound/soc/codecs/wm8753.c @@ -851,7 +851,7 @@ static int wm8753_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 wm8753_priv *wm8753 = codec->private_data; + struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec); switch (freq) { case 11289600: @@ -914,7 +914,7 @@ static int wm8753_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_codec *codec = socdev->card->codec; - struct wm8753_priv *wm8753 = codec->private_data; + struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec); u16 voice = wm8753_read_reg_cache(codec, WM8753_PCM) & 0x01f3; u16 srate = wm8753_read_reg_cache(codec, WM8753_SRATE1) & 0x017f; @@ -1148,7 +1148,7 @@ static int wm8753_i2s_hw_params(struct snd_pcm_substream *substream, struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_codec *codec = socdev->card->codec; - struct wm8753_priv *wm8753 = codec->private_data; + struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec); u16 srate = wm8753_read_reg_cache(codec, WM8753_SRATE1) & 0x01c0; u16 hifi = wm8753_read_reg_cache(codec, WM8753_HIFI) & 0x01f3; int coeff; @@ -1646,7 +1646,7 @@ static int wm8753_register(struct wm8753_priv *wm8753) codec->num_dai = 2; codec->reg_cache_size = ARRAY_SIZE(wm8753->reg_cache) + 1; codec->reg_cache = &wm8753->reg_cache; - codec->private_data = wm8753; + snd_soc_codec_set_drvdata(codec, wm8753); memcpy(codec->reg_cache, wm8753_reg, sizeof(wm8753->reg_cache)); INIT_DELAYED_WORK(&codec->delayed_work, wm8753_work); diff --git a/sound/soc/codecs/wm8776.c b/sound/soc/codecs/wm8776.c index 60b1b3e..7e4a627 100644 --- a/sound/soc/codecs/wm8776.c +++ b/sound/soc/codecs/wm8776.c @@ -227,7 +227,7 @@ static int wm8776_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_codec *codec = dai->codec; - struct wm8776_priv *wm8776 = codec->private_data; + struct wm8776_priv *wm8776 = snd_soc_codec_get_drvdata(codec); int iface_reg, iface; int ratio_shift, master; int i; @@ -304,7 +304,7 @@ static int wm8776_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int freq, int dir) { struct snd_soc_codec *codec = dai->codec; - struct wm8776_priv *wm8776 = codec->private_data; + struct wm8776_priv *wm8776 = snd_soc_codec_get_drvdata(codec); BUG_ON(dai->id >= ARRAY_SIZE(wm8776->sysclk)); @@ -491,7 +491,7 @@ static int wm8776_register(struct wm8776_priv *wm8776, INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); - codec->private_data = wm8776; + snd_soc_codec_set_drvdata(codec, wm8776); codec->name = "WM8776"; codec->owner = THIS_MODULE; codec->bias_level = SND_SOC_BIAS_OFF; diff --git a/sound/soc/codecs/wm8900.c b/sound/soc/codecs/wm8900.c index b7fd96a..5da17a7 100644 --- a/sound/soc/codecs/wm8900.c +++ b/sound/soc/codecs/wm8900.c @@ -745,7 +745,7 @@ static int fll_factors(struct _fll_div *fll_div, unsigned int Fref, static int wm8900_set_fll(struct snd_soc_codec *codec, int fll_id, unsigned int freq_in, unsigned int freq_out) { - struct wm8900_priv *wm8900 = codec->private_data; + struct wm8900_priv *wm8900 = snd_soc_codec_get_drvdata(codec); struct _fll_div fll_div; unsigned int reg; @@ -1132,7 +1132,7 @@ static int wm8900_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; - struct wm8900_priv *wm8900 = codec->private_data; + struct wm8900_priv *wm8900 = snd_soc_codec_get_drvdata(codec); int fll_out = wm8900->fll_out; int fll_in = wm8900->fll_in; int ret; @@ -1156,7 +1156,7 @@ static int wm8900_resume(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = socdev->card->codec; - struct wm8900_priv *wm8900 = codec->private_data; + struct wm8900_priv *wm8900 = snd_soc_codec_get_drvdata(codec); u16 *cache; int i, ret; @@ -1206,7 +1206,7 @@ static __devinit int wm8900_i2c_probe(struct i2c_client *i2c, return -ENOMEM; codec = &wm8900->codec; - codec->private_data = wm8900; + snd_soc_codec_set_drvdata(codec, wm8900); codec->reg_cache = &wm8900->reg_cache[0]; codec->reg_cache_size = WM8900_MAXREG; @@ -1305,7 +1305,7 @@ static __devexit int wm8900_i2c_remove(struct i2c_client *client) wm8900_set_bias_level(wm8900_codec, SND_SOC_BIAS_OFF); wm8900_dai.dev = NULL; - kfree(wm8900_codec->private_data); + kfree(snd_soc_codec_get_drvdata(wm8900_codec)); wm8900_codec = NULL; return 0; diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c index fa5f99f..bf08282 100644 --- a/sound/soc/codecs/wm8903.c +++ b/sound/soc/codecs/wm8903.c @@ -11,26 +11,27 @@ * * TODO: * - TDM mode configuration. - * - Mic detect. * - Digital microphone support. - * - Interrupt support (mic detect and sequencer). */ #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/init.h> +#include <linux/completion.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/jack.h> #include <sound/pcm.h> #include <sound/pcm_params.h> #include <sound/tlv.h> #include <sound/soc.h> #include <sound/soc-dapm.h> #include <sound/initval.h> +#include <sound/wm8903.h> #include "wm8903.h" @@ -222,6 +223,14 @@ struct wm8903_priv { int playback_active; int capture_active; + struct completion wseq; + + struct snd_soc_jack *mic_jack; + int mic_det; + int mic_short; + int mic_last_report; + int mic_delay; + struct snd_pcm_substream *master_substream; struct snd_pcm_substream *slave_substream; }; @@ -244,13 +253,14 @@ static int wm8903_run_sequence(struct snd_soc_codec *codec, unsigned int start) { u16 reg[5]; struct i2c_client *i2c = codec->control_data; + struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec); BUG_ON(start > 48); - /* Enable the sequencer */ + /* Enable the sequencer if it's not already on */ reg[0] = snd_soc_read(codec, WM8903_WRITE_SEQUENCER_0); - reg[0] |= WM8903_WSEQ_ENA; - snd_soc_write(codec, WM8903_WRITE_SEQUENCER_0, reg[0]); + snd_soc_write(codec, WM8903_WRITE_SEQUENCER_0, + reg[0] | WM8903_WSEQ_ENA); dev_dbg(&i2c->dev, "Starting sequence at %d\n", start); @@ -258,20 +268,19 @@ static int wm8903_run_sequence(struct snd_soc_codec *codec, unsigned int start) start | WM8903_WSEQ_START); /* Wait for it to complete. If we have the interrupt wired up then - * we could block waiting for an interrupt, though polling may still - * be desirable for diagnostic purposes. + * that will break us out of the poll early. */ do { - msleep(10); + wait_for_completion_timeout(&wm8903->wseq, + msecs_to_jiffies(10)); reg[4] = snd_soc_read(codec, WM8903_WRITE_SEQUENCER_4); } while (reg[4] & WM8903_WSEQ_BUSY); dev_dbg(&i2c->dev, "Sequence complete\n"); - /* Disable the sequencer again */ - snd_soc_write(codec, WM8903_WRITE_SEQUENCER_0, - reg[0] & ~WM8903_WSEQ_ENA); + /* Disable the sequencer again if we enabled it */ + snd_soc_write(codec, WM8903_WRITE_SEQUENCER_0, reg[0]); return 0; } @@ -412,7 +421,7 @@ static int wm8903_class_w_put(struct snd_kcontrol *kcontrol, { struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol); struct snd_soc_codec *codec = widget->codec; - struct wm8903_priv *wm8903 = codec->private_data; + struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec); struct i2c_client *i2c = codec->control_data; u16 reg; int ret; @@ -993,7 +1002,7 @@ static int wm8903_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 wm8903_priv *wm8903 = codec->private_data; + struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec); wm8903->sysclk = freq; @@ -1221,7 +1230,7 @@ static int wm8903_startup(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 wm8903_priv *wm8903 = codec->private_data; + struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec); struct i2c_client *i2c = codec->control_data; struct snd_pcm_runtime *master_runtime; @@ -1257,7 +1266,7 @@ static void wm8903_shutdown(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 wm8903_priv *wm8903 = codec->private_data; + struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) wm8903->playback_active--; @@ -1277,7 +1286,7 @@ static int wm8903_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 wm8903_priv *wm8903 = codec->private_data; + struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec); struct i2c_client *i2c = codec->control_data; int fs = params_rate(params); int bclk; @@ -1436,6 +1445,116 @@ static int wm8903_hw_params(struct snd_pcm_substream *substream, return 0; } +/** + * wm8903_mic_detect - Enable microphone detection via the WM8903 IRQ + * + * @codec: WM8903 codec + * @jack: jack to report detection events on + * @det: value to report for presence detection + * @shrt: value to report for short detection + * + * Enable microphone detection via IRQ on the WM8903. If GPIOs are + * being used to bring out signals to the processor then only platform + * data configuration is needed for WM8903 and processor GPIOs should + * be configured using snd_soc_jack_add_gpios() instead. + * + * The current threasholds for detection should be configured using + * micdet_cfg in the platform data. Using this function will force on + * the microphone bias for the device. + */ +int wm8903_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack, + int det, int shrt) +{ + struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec); + int irq_mask = WM8903_MICDET_EINT | WM8903_MICSHRT_EINT; + + dev_dbg(codec->dev, "Enabling microphone detection: %x %x\n", + det, shrt); + + /* Store the configuration */ + wm8903->mic_jack = jack; + wm8903->mic_det = det; + wm8903->mic_short = shrt; + + /* Enable interrupts we've got a report configured for */ + if (det) + irq_mask &= ~WM8903_MICDET_EINT; + if (shrt) + irq_mask &= ~WM8903_MICSHRT_EINT; + + snd_soc_update_bits(codec, WM8903_INTERRUPT_STATUS_1_MASK, + WM8903_MICDET_EINT | WM8903_MICSHRT_EINT, + irq_mask); + + if (det && shrt) { + /* Enable mic detection, this may not have been set through + * platform data (eg, if the defaults are OK). */ + snd_soc_update_bits(codec, WM8903_WRITE_SEQUENCER_0, + WM8903_WSEQ_ENA, WM8903_WSEQ_ENA); + snd_soc_update_bits(codec, WM8903_MIC_BIAS_CONTROL_0, + WM8903_MICDET_ENA, WM8903_MICDET_ENA); + } else { + snd_soc_update_bits(codec, WM8903_MIC_BIAS_CONTROL_0, + WM8903_MICDET_ENA, 0); + } + + return 0; +} +EXPORT_SYMBOL_GPL(wm8903_mic_detect); + +static irqreturn_t wm8903_irq(int irq, void *data) +{ + struct wm8903_priv *wm8903 = data; + struct snd_soc_codec *codec = &wm8903->codec; + int mic_report; + int int_pol; + int int_val = 0; + int mask = ~snd_soc_read(codec, WM8903_INTERRUPT_STATUS_1_MASK); + + int_val = snd_soc_read(codec, WM8903_INTERRUPT_STATUS_1) & mask; + + if (int_val & WM8903_WSEQ_BUSY_EINT) { + dev_dbg(codec->dev, "Write sequencer done\n"); + complete(&wm8903->wseq); + } + + /* + * The rest is microphone jack detection. We need to manually + * invert the polarity of the interrupt after each event - to + * simplify the code keep track of the last state we reported + * and just invert the relevant bits in both the report and + * the polarity register. + */ + mic_report = wm8903->mic_last_report; + int_pol = snd_soc_read(codec, WM8903_INTERRUPT_POLARITY_1); + + if (int_val & WM8903_MICSHRT_EINT) { + dev_dbg(codec->dev, "Microphone short (pol=%x)\n", int_pol); + + mic_report ^= wm8903->mic_short; + int_pol ^= WM8903_MICSHRT_INV; + } + + if (int_val & WM8903_MICDET_EINT) { + dev_dbg(codec->dev, "Microphone detect (pol=%x)\n", int_pol); + + mic_report ^= wm8903->mic_det; + int_pol ^= WM8903_MICDET_INV; + + msleep(wm8903->mic_delay); + } + + snd_soc_update_bits(codec, WM8903_INTERRUPT_POLARITY_1, + WM8903_MICSHRT_INV | WM8903_MICDET_INV, int_pol); + + snd_soc_jack_report(wm8903->mic_jack, mic_report, + wm8903->mic_short | wm8903->mic_det); + + wm8903->mic_last_report = mic_report; + + return IRQ_HANDLED; +} + #define WM8903_PLAYBACK_RATES (SNDRV_PCM_RATE_8000 |\ SNDRV_PCM_RATE_11025 | \ SNDRV_PCM_RATE_16000 | \ @@ -1510,7 +1629,6 @@ static int wm8903_resume(struct platform_device *pdev) /* Bring the codec back up to standby first to minimise pop/clicks */ wm8903_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - wm8903_set_bias_level(codec, codec->suspend_bias_level); /* Sync back everything else */ if (tmp_cache) { @@ -1530,9 +1648,11 @@ static struct snd_soc_codec *wm8903_codec; static __devinit int wm8903_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { + struct wm8903_platform_data *pdata = dev_get_platdata(&i2c->dev); struct wm8903_priv *wm8903; struct snd_soc_codec *codec; - int ret; + int ret, i; + int trigger, irq_pol; u16 val; wm8903 = kzalloc(sizeof(struct wm8903_priv), GFP_KERNEL); @@ -1554,8 +1674,9 @@ static __devinit int wm8903_i2c_probe(struct i2c_client *i2c, codec->num_dai = 1; codec->reg_cache_size = ARRAY_SIZE(wm8903->reg_cache); codec->reg_cache = &wm8903->reg_cache[0]; - codec->private_data = wm8903; + snd_soc_codec_set_drvdata(codec, wm8903); codec->volatile_register = wm8903_volatile_register; + init_completion(&wm8903->wseq); i2c_set_clientdata(i2c, codec); codec->control_data = i2c; @@ -1579,6 +1700,53 @@ static __devinit int wm8903_i2c_probe(struct i2c_client *i2c, wm8903_reset(codec); + /* Set up GPIOs and microphone detection */ + if (pdata) { + for (i = 0; i < ARRAY_SIZE(pdata->gpio_cfg); i++) { + if (!pdata->gpio_cfg[i]) + continue; + + snd_soc_write(codec, WM8903_GPIO_CONTROL_1 + i, + pdata->gpio_cfg[i] & 0xffff); + } + + snd_soc_write(codec, WM8903_MIC_BIAS_CONTROL_0, + pdata->micdet_cfg); + + /* Microphone detection needs the WSEQ clock */ + if (pdata->micdet_cfg) + snd_soc_update_bits(codec, WM8903_WRITE_SEQUENCER_0, + WM8903_WSEQ_ENA, WM8903_WSEQ_ENA); + + wm8903->mic_delay = pdata->micdet_delay; + } + + if (i2c->irq) { + if (pdata && pdata->irq_active_low) { + trigger = IRQF_TRIGGER_LOW; + irq_pol = WM8903_IRQ_POL; + } else { + trigger = IRQF_TRIGGER_HIGH; + irq_pol = 0; + } + + snd_soc_update_bits(codec, WM8903_INTERRUPT_CONTROL, + WM8903_IRQ_POL, irq_pol); + + ret = request_threaded_irq(i2c->irq, NULL, wm8903_irq, + trigger | IRQF_ONESHOT, + "wm8903", wm8903); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to request IRQ: %d\n", + ret); + goto err; + } + + /* Enable write sequencer interrupts */ + snd_soc_update_bits(codec, WM8903_INTERRUPT_STATUS_1_MASK, + WM8903_IM_WSEQ_BUSY_EINT, 0); + } + /* power on device */ wm8903_set_bias_level(codec, SND_SOC_BIAS_STANDBY); @@ -1619,7 +1787,7 @@ static __devinit int wm8903_i2c_probe(struct i2c_client *i2c, ret = snd_soc_register_codec(codec); if (ret != 0) { dev_err(&i2c->dev, "Failed to register codec: %d\n", ret); - goto err; + goto err_irq; } ret = snd_soc_register_dai(&wm8903_dai); @@ -1632,6 +1800,9 @@ static __devinit int wm8903_i2c_probe(struct i2c_client *i2c, err_codec: snd_soc_unregister_codec(codec); +err_irq: + if (i2c->irq) + free_irq(i2c->irq, wm8903); err: wm8903_codec = NULL; kfree(wm8903); @@ -1641,13 +1812,17 @@ err: static __devexit int wm8903_i2c_remove(struct i2c_client *client) { struct snd_soc_codec *codec = i2c_get_clientdata(client); + struct wm8903_priv *priv = snd_soc_codec_get_drvdata(codec); snd_soc_unregister_dai(&wm8903_dai); snd_soc_unregister_codec(codec); wm8903_set_bias_level(codec, SND_SOC_BIAS_OFF); - kfree(codec->private_data); + if (client->irq) + free_irq(client->irq, priv); + + kfree(priv); wm8903_codec = NULL; wm8903_dai.dev = NULL; diff --git a/sound/soc/codecs/wm8903.h b/sound/soc/codecs/wm8903.h index 0ea27e2..ce384a2 100644 --- a/sound/soc/codecs/wm8903.h +++ b/sound/soc/codecs/wm8903.h @@ -18,6 +18,10 @@ extern struct snd_soc_dai wm8903_dai; extern struct snd_soc_codec_device soc_codec_dev_wm8903; +extern int wm8903_mic_detect(struct snd_soc_codec *codec, + struct snd_soc_jack *jack, + int det, int shrt); + #define WM8903_MCLK_DIV_2 1 #define WM8903_CLK_SYS 2 #define WM8903_BCLK 3 @@ -173,28 +177,6 @@ extern struct snd_soc_codec_device soc_codec_dev_wm8903; #define WM8903_VMID_RES_5K 4 /* - * R6 (0x06) - Mic Bias Control 0 - */ -#define WM8903_MICDET_HYST_ENA 0x0080 /* MICDET_HYST_ENA */ -#define WM8903_MICDET_HYST_ENA_MASK 0x0080 /* MICDET_HYST_ENA */ -#define WM8903_MICDET_HYST_ENA_SHIFT 7 /* MICDET_HYST_ENA */ -#define WM8903_MICDET_HYST_ENA_WIDTH 1 /* MICDET_HYST_ENA */ -#define WM8903_MICDET_THR_MASK 0x0070 /* MICDET_THR - [6:4] */ -#define WM8903_MICDET_THR_SHIFT 4 /* MICDET_THR - [6:4] */ -#define WM8903_MICDET_THR_WIDTH 3 /* MICDET_THR - [6:4] */ -#define WM8903_MICSHORT_THR_MASK 0x000C /* MICSHORT_THR - [3:2] */ -#define WM8903_MICSHORT_THR_SHIFT 2 /* MICSHORT_THR - [3:2] */ -#define WM8903_MICSHORT_THR_WIDTH 2 /* MICSHORT_THR - [3:2] */ -#define WM8903_MICDET_ENA 0x0002 /* MICDET_ENA */ -#define WM8903_MICDET_ENA_MASK 0x0002 /* MICDET_ENA */ -#define WM8903_MICDET_ENA_SHIFT 1 /* MICDET_ENA */ -#define WM8903_MICDET_ENA_WIDTH 1 /* MICDET_ENA */ -#define WM8903_MICBIAS_ENA 0x0001 /* MICBIAS_ENA */ -#define WM8903_MICBIAS_ENA_MASK 0x0001 /* MICBIAS_ENA */ -#define WM8903_MICBIAS_ENA_SHIFT 0 /* MICBIAS_ENA */ -#define WM8903_MICBIAS_ENA_WIDTH 1 /* MICBIAS_ENA */ - -/* * R8 (0x08) - Analogue DAC 0 */ #define WM8903_DACBIAS_SEL_MASK 0x0018 /* DACBIAS_SEL - [4:3] */ @@ -1135,201 +1117,6 @@ extern struct snd_soc_codec_device soc_codec_dev_wm8903; #define WM8903_MASK_WRITE_ENA_WIDTH 1 /* MASK_WRITE_ENA */ /* - * R116 (0x74) - GPIO Control 1 - */ -#define WM8903_GP1_FN_MASK 0x1F00 /* GP1_FN - [12:8] */ -#define WM8903_GP1_FN_SHIFT 8 /* GP1_FN - [12:8] */ -#define WM8903_GP1_FN_WIDTH 5 /* GP1_FN - [12:8] */ -#define WM8903_GP1_DIR 0x0080 /* GP1_DIR */ -#define WM8903_GP1_DIR_MASK 0x0080 /* GP1_DIR */ -#define WM8903_GP1_DIR_SHIFT 7 /* GP1_DIR */ -#define WM8903_GP1_DIR_WIDTH 1 /* GP1_DIR */ -#define WM8903_GP1_OP_CFG 0x0040 /* GP1_OP_CFG */ -#define WM8903_GP1_OP_CFG_MASK 0x0040 /* GP1_OP_CFG */ -#define WM8903_GP1_OP_CFG_SHIFT 6 /* GP1_OP_CFG */ -#define WM8903_GP1_OP_CFG_WIDTH 1 /* GP1_OP_CFG */ -#define WM8903_GP1_IP_CFG 0x0020 /* GP1_IP_CFG */ -#define WM8903_GP1_IP_CFG_MASK 0x0020 /* GP1_IP_CFG */ -#define WM8903_GP1_IP_CFG_SHIFT 5 /* GP1_IP_CFG */ -#define WM8903_GP1_IP_CFG_WIDTH 1 /* GP1_IP_CFG */ -#define WM8903_GP1_LVL 0x0010 /* GP1_LVL */ -#define WM8903_GP1_LVL_MASK 0x0010 /* GP1_LVL */ -#define WM8903_GP1_LVL_SHIFT 4 /* GP1_LVL */ -#define WM8903_GP1_LVL_WIDTH 1 /* GP1_LVL */ -#define WM8903_GP1_PD 0x0008 /* GP1_PD */ -#define WM8903_GP1_PD_MASK 0x0008 /* GP1_PD */ -#define WM8903_GP1_PD_SHIFT 3 /* GP1_PD */ -#define WM8903_GP1_PD_WIDTH 1 /* GP1_PD */ -#define WM8903_GP1_PU 0x0004 /* GP1_PU */ -#define WM8903_GP1_PU_MASK 0x0004 /* GP1_PU */ -#define WM8903_GP1_PU_SHIFT 2 /* GP1_PU */ -#define WM8903_GP1_PU_WIDTH 1 /* GP1_PU */ -#define WM8903_GP1_INTMODE 0x0002 /* GP1_INTMODE */ -#define WM8903_GP1_INTMODE_MASK 0x0002 /* GP1_INTMODE */ -#define WM8903_GP1_INTMODE_SHIFT 1 /* GP1_INTMODE */ -#define WM8903_GP1_INTMODE_WIDTH 1 /* GP1_INTMODE */ -#define WM8903_GP1_DB 0x0001 /* GP1_DB */ -#define WM8903_GP1_DB_MASK 0x0001 /* GP1_DB */ -#define WM8903_GP1_DB_SHIFT 0 /* GP1_DB */ -#define WM8903_GP1_DB_WIDTH 1 /* GP1_DB */ - -/* - * R117 (0x75) - GPIO Control 2 - */ -#define WM8903_GP2_FN_MASK 0x1F00 /* GP2_FN - [12:8] */ -#define WM8903_GP2_FN_SHIFT 8 /* GP2_FN - [12:8] */ -#define WM8903_GP2_FN_WIDTH 5 /* GP2_FN - [12:8] */ -#define WM8903_GP2_DIR 0x0080 /* GP2_DIR */ -#define WM8903_GP2_DIR_MASK 0x0080 /* GP2_DIR */ -#define WM8903_GP2_DIR_SHIFT 7 /* GP2_DIR */ -#define WM8903_GP2_DIR_WIDTH 1 /* GP2_DIR */ -#define WM8903_GP2_OP_CFG 0x0040 /* GP2_OP_CFG */ -#define WM8903_GP2_OP_CFG_MASK 0x0040 /* GP2_OP_CFG */ -#define WM8903_GP2_OP_CFG_SHIFT 6 /* GP2_OP_CFG */ -#define WM8903_GP2_OP_CFG_WIDTH 1 /* GP2_OP_CFG */ -#define WM8903_GP2_IP_CFG 0x0020 /* GP2_IP_CFG */ -#define WM8903_GP2_IP_CFG_MASK 0x0020 /* GP2_IP_CFG */ -#define WM8903_GP2_IP_CFG_SHIFT 5 /* GP2_IP_CFG */ -#define WM8903_GP2_IP_CFG_WIDTH 1 /* GP2_IP_CFG */ -#define WM8903_GP2_LVL 0x0010 /* GP2_LVL */ -#define WM8903_GP2_LVL_MASK 0x0010 /* GP2_LVL */ -#define WM8903_GP2_LVL_SHIFT 4 /* GP2_LVL */ -#define WM8903_GP2_LVL_WIDTH 1 /* GP2_LVL */ -#define WM8903_GP2_PD 0x0008 /* GP2_PD */ -#define WM8903_GP2_PD_MASK 0x0008 /* GP2_PD */ -#define WM8903_GP2_PD_SHIFT 3 /* GP2_PD */ -#define WM8903_GP2_PD_WIDTH 1 /* GP2_PD */ -#define WM8903_GP2_PU 0x0004 /* GP2_PU */ -#define WM8903_GP2_PU_MASK 0x0004 /* GP2_PU */ -#define WM8903_GP2_PU_SHIFT 2 /* GP2_PU */ -#define WM8903_GP2_PU_WIDTH 1 /* GP2_PU */ -#define WM8903_GP2_INTMODE 0x0002 /* GP2_INTMODE */ -#define WM8903_GP2_INTMODE_MASK 0x0002 /* GP2_INTMODE */ -#define WM8903_GP2_INTMODE_SHIFT 1 /* GP2_INTMODE */ -#define WM8903_GP2_INTMODE_WIDTH 1 /* GP2_INTMODE */ -#define WM8903_GP2_DB 0x0001 /* GP2_DB */ -#define WM8903_GP2_DB_MASK 0x0001 /* GP2_DB */ -#define WM8903_GP2_DB_SHIFT 0 /* GP2_DB */ -#define WM8903_GP2_DB_WIDTH 1 /* GP2_DB */ - -/* - * R118 (0x76) - GPIO Control 3 - */ -#define WM8903_GP3_FN_MASK 0x1F00 /* GP3_FN - [12:8] */ -#define WM8903_GP3_FN_SHIFT 8 /* GP3_FN - [12:8] */ -#define WM8903_GP3_FN_WIDTH 5 /* GP3_FN - [12:8] */ -#define WM8903_GP3_DIR 0x0080 /* GP3_DIR */ -#define WM8903_GP3_DIR_MASK 0x0080 /* GP3_DIR */ -#define WM8903_GP3_DIR_SHIFT 7 /* GP3_DIR */ -#define WM8903_GP3_DIR_WIDTH 1 /* GP3_DIR */ -#define WM8903_GP3_OP_CFG 0x0040 /* GP3_OP_CFG */ -#define WM8903_GP3_OP_CFG_MASK 0x0040 /* GP3_OP_CFG */ -#define WM8903_GP3_OP_CFG_SHIFT 6 /* GP3_OP_CFG */ -#define WM8903_GP3_OP_CFG_WIDTH 1 /* GP3_OP_CFG */ -#define WM8903_GP3_IP_CFG 0x0020 /* GP3_IP_CFG */ -#define WM8903_GP3_IP_CFG_MASK 0x0020 /* GP3_IP_CFG */ -#define WM8903_GP3_IP_CFG_SHIFT 5 /* GP3_IP_CFG */ -#define WM8903_GP3_IP_CFG_WIDTH 1 /* GP3_IP_CFG */ -#define WM8903_GP3_LVL 0x0010 /* GP3_LVL */ -#define WM8903_GP3_LVL_MASK 0x0010 /* GP3_LVL */ -#define WM8903_GP3_LVL_SHIFT 4 /* GP3_LVL */ -#define WM8903_GP3_LVL_WIDTH 1 /* GP3_LVL */ -#define WM8903_GP3_PD 0x0008 /* GP3_PD */ -#define WM8903_GP3_PD_MASK 0x0008 /* GP3_PD */ -#define WM8903_GP3_PD_SHIFT 3 /* GP3_PD */ -#define WM8903_GP3_PD_WIDTH 1 /* GP3_PD */ -#define WM8903_GP3_PU 0x0004 /* GP3_PU */ -#define WM8903_GP3_PU_MASK 0x0004 /* GP3_PU */ -#define WM8903_GP3_PU_SHIFT 2 /* GP3_PU */ -#define WM8903_GP3_PU_WIDTH 1 /* GP3_PU */ -#define WM8903_GP3_INTMODE 0x0002 /* GP3_INTMODE */ -#define WM8903_GP3_INTMODE_MASK 0x0002 /* GP3_INTMODE */ -#define WM8903_GP3_INTMODE_SHIFT 1 /* GP3_INTMODE */ -#define WM8903_GP3_INTMODE_WIDTH 1 /* GP3_INTMODE */ -#define WM8903_GP3_DB 0x0001 /* GP3_DB */ -#define WM8903_GP3_DB_MASK 0x0001 /* GP3_DB */ -#define WM8903_GP3_DB_SHIFT 0 /* GP3_DB */ -#define WM8903_GP3_DB_WIDTH 1 /* GP3_DB */ - -/* - * R119 (0x77) - GPIO Control 4 - */ -#define WM8903_GP4_FN_MASK 0x1F00 /* GP4_FN - [12:8] */ -#define WM8903_GP4_FN_SHIFT 8 /* GP4_FN - [12:8] */ -#define WM8903_GP4_FN_WIDTH 5 /* GP4_FN - [12:8] */ -#define WM8903_GP4_DIR 0x0080 /* GP4_DIR */ -#define WM8903_GP4_DIR_MASK 0x0080 /* GP4_DIR */ -#define WM8903_GP4_DIR_SHIFT 7 /* GP4_DIR */ -#define WM8903_GP4_DIR_WIDTH 1 /* GP4_DIR */ -#define WM8903_GP4_OP_CFG 0x0040 /* GP4_OP_CFG */ -#define WM8903_GP4_OP_CFG_MASK 0x0040 /* GP4_OP_CFG */ -#define WM8903_GP4_OP_CFG_SHIFT 6 /* GP4_OP_CFG */ -#define WM8903_GP4_OP_CFG_WIDTH 1 /* GP4_OP_CFG */ -#define WM8903_GP4_IP_CFG 0x0020 /* GP4_IP_CFG */ -#define WM8903_GP4_IP_CFG_MASK 0x0020 /* GP4_IP_CFG */ -#define WM8903_GP4_IP_CFG_SHIFT 5 /* GP4_IP_CFG */ -#define WM8903_GP4_IP_CFG_WIDTH 1 /* GP4_IP_CFG */ -#define WM8903_GP4_LVL 0x0010 /* GP4_LVL */ -#define WM8903_GP4_LVL_MASK 0x0010 /* GP4_LVL */ -#define WM8903_GP4_LVL_SHIFT 4 /* GP4_LVL */ -#define WM8903_GP4_LVL_WIDTH 1 /* GP4_LVL */ -#define WM8903_GP4_PD 0x0008 /* GP4_PD */ -#define WM8903_GP4_PD_MASK 0x0008 /* GP4_PD */ -#define WM8903_GP4_PD_SHIFT 3 /* GP4_PD */ -#define WM8903_GP4_PD_WIDTH 1 /* GP4_PD */ -#define WM8903_GP4_PU 0x0004 /* GP4_PU */ -#define WM8903_GP4_PU_MASK 0x0004 /* GP4_PU */ -#define WM8903_GP4_PU_SHIFT 2 /* GP4_PU */ -#define WM8903_GP4_PU_WIDTH 1 /* GP4_PU */ -#define WM8903_GP4_INTMODE 0x0002 /* GP4_INTMODE */ -#define WM8903_GP4_INTMODE_MASK 0x0002 /* GP4_INTMODE */ -#define WM8903_GP4_INTMODE_SHIFT 1 /* GP4_INTMODE */ -#define WM8903_GP4_INTMODE_WIDTH 1 /* GP4_INTMODE */ -#define WM8903_GP4_DB 0x0001 /* GP4_DB */ -#define WM8903_GP4_DB_MASK 0x0001 /* GP4_DB */ -#define WM8903_GP4_DB_SHIFT 0 /* GP4_DB */ -#define WM8903_GP4_DB_WIDTH 1 /* GP4_DB */ - -/* - * R120 (0x78) - GPIO Control 5 - */ -#define WM8903_GP5_FN_MASK 0x1F00 /* GP5_FN - [12:8] */ -#define WM8903_GP5_FN_SHIFT 8 /* GP5_FN - [12:8] */ -#define WM8903_GP5_FN_WIDTH 5 /* GP5_FN - [12:8] */ -#define WM8903_GP5_DIR 0x0080 /* GP5_DIR */ -#define WM8903_GP5_DIR_MASK 0x0080 /* GP5_DIR */ -#define WM8903_GP5_DIR_SHIFT 7 /* GP5_DIR */ -#define WM8903_GP5_DIR_WIDTH 1 /* GP5_DIR */ -#define WM8903_GP5_OP_CFG 0x0040 /* GP5_OP_CFG */ -#define WM8903_GP5_OP_CFG_MASK 0x0040 /* GP5_OP_CFG */ -#define WM8903_GP5_OP_CFG_SHIFT 6 /* GP5_OP_CFG */ -#define WM8903_GP5_OP_CFG_WIDTH 1 /* GP5_OP_CFG */ -#define WM8903_GP5_IP_CFG 0x0020 /* GP5_IP_CFG */ -#define WM8903_GP5_IP_CFG_MASK 0x0020 /* GP5_IP_CFG */ -#define WM8903_GP5_IP_CFG_SHIFT 5 /* GP5_IP_CFG */ -#define WM8903_GP5_IP_CFG_WIDTH 1 /* GP5_IP_CFG */ -#define WM8903_GP5_LVL 0x0010 /* GP5_LVL */ -#define WM8903_GP5_LVL_MASK 0x0010 /* GP5_LVL */ -#define WM8903_GP5_LVL_SHIFT 4 /* GP5_LVL */ -#define WM8903_GP5_LVL_WIDTH 1 /* GP5_LVL */ -#define WM8903_GP5_PD 0x0008 /* GP5_PD */ -#define WM8903_GP5_PD_MASK 0x0008 /* GP5_PD */ -#define WM8903_GP5_PD_SHIFT 3 /* GP5_PD */ -#define WM8903_GP5_PD_WIDTH 1 /* GP5_PD */ -#define WM8903_GP5_PU 0x0004 /* GP5_PU */ -#define WM8903_GP5_PU_MASK 0x0004 /* GP5_PU */ -#define WM8903_GP5_PU_SHIFT 2 /* GP5_PU */ -#define WM8903_GP5_PU_WIDTH 1 /* GP5_PU */ -#define WM8903_GP5_INTMODE 0x0002 /* GP5_INTMODE */ -#define WM8903_GP5_INTMODE_MASK 0x0002 /* GP5_INTMODE */ -#define WM8903_GP5_INTMODE_SHIFT 1 /* GP5_INTMODE */ -#define WM8903_GP5_INTMODE_WIDTH 1 /* GP5_INTMODE */ -#define WM8903_GP5_DB 0x0001 /* GP5_DB */ -#define WM8903_GP5_DB_MASK 0x0001 /* GP5_DB */ -#define WM8903_GP5_DB_SHIFT 0 /* GP5_DB */ -#define WM8903_GP5_DB_WIDTH 1 /* GP5_DB */ - -/* * R121 (0x79) - Interrupt Status 1 */ #define WM8903_MICSHRT_EINT 0x8000 /* MICSHRT_EINT */ diff --git a/sound/soc/codecs/wm8904.c b/sound/soc/codecs/wm8904.c index c6f0abc..87f14f8 100644 --- a/sound/soc/codecs/wm8904.c +++ b/sound/soc/codecs/wm8904.c @@ -613,7 +613,7 @@ static int wm8904_reset(struct snd_soc_codec *codec) static int wm8904_configure_clocking(struct snd_soc_codec *codec) { - struct wm8904_priv *wm8904 = codec->private_data; + struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec); unsigned int clock0, clock2, rate; /* Gate the clock while we're updating to avoid misclocking */ @@ -669,7 +669,7 @@ static int wm8904_configure_clocking(struct snd_soc_codec *codec) static void wm8904_set_drc(struct snd_soc_codec *codec) { - struct wm8904_priv *wm8904 = codec->private_data; + struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec); struct wm8904_pdata *pdata = wm8904->pdata; int save, i; @@ -689,7 +689,7 @@ static int wm8904_put_drc_enum(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - struct wm8904_priv *wm8904 = codec->private_data; + struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec); struct wm8904_pdata *pdata = wm8904->pdata; int value = ucontrol->value.integer.value[0]; @@ -707,7 +707,7 @@ static int wm8904_get_drc_enum(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - struct wm8904_priv *wm8904 = codec->private_data; + struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec); ucontrol->value.enumerated.item[0] = wm8904->drc_cfg; @@ -716,7 +716,7 @@ static int wm8904_get_drc_enum(struct snd_kcontrol *kcontrol, static void wm8904_set_retune_mobile(struct snd_soc_codec *codec) { - struct wm8904_priv *wm8904 = codec->private_data; + struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec); struct wm8904_pdata *pdata = wm8904->pdata; int best, best_val, save, i, cfg; @@ -760,7 +760,7 @@ static int wm8904_put_retune_mobile_enum(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - struct wm8904_priv *wm8904 = codec->private_data; + struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec); struct wm8904_pdata *pdata = wm8904->pdata; int value = ucontrol->value.integer.value[0]; @@ -778,7 +778,7 @@ static int wm8904_get_retune_mobile_enum(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - struct wm8904_priv *wm8904 = codec->private_data; + struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec); ucontrol->value.enumerated.item[0] = wm8904->retune_mobile_cfg; @@ -789,7 +789,7 @@ static int deemph_settings[] = { 0, 32000, 44100, 48000 }; static int wm8904_set_deemph(struct snd_soc_codec *codec) { - struct wm8904_priv *wm8904 = codec->private_data; + struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec); int val, i, best; /* If we're using deemphasis select the nearest available sample @@ -818,7 +818,7 @@ static int wm8904_get_deemph(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - struct wm8904_priv *wm8904 = codec->private_data; + struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec); return wm8904->deemph; } @@ -827,7 +827,7 @@ static int wm8904_put_deemph(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - struct wm8904_priv *wm8904 = codec->private_data; + struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec); int deemph = ucontrol->value.enumerated.item[0]; if (deemph > 1) @@ -943,7 +943,7 @@ static int sysclk_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { struct snd_soc_codec *codec = w->codec; - struct wm8904_priv *wm8904 = codec->private_data; + struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec); switch (event) { case SND_SOC_DAPM_PRE_PMU: @@ -981,7 +981,7 @@ static int out_pga_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { struct snd_soc_codec *codec = w->codec; - struct wm8904_priv *wm8904 = codec->private_data; + struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec); int reg, val; int dcs_mask; int dcs_l, dcs_r; @@ -1429,7 +1429,7 @@ static const struct snd_soc_dapm_route wm8912_intercon[] = { static int wm8904_add_widgets(struct snd_soc_codec *codec) { - struct wm8904_priv *wm8904 = codec->private_data; + struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec); snd_soc_dapm_new_controls(codec, wm8904_core_dapm_widgets, ARRAY_SIZE(wm8904_core_dapm_widgets)); @@ -1543,7 +1543,7 @@ static int wm8904_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_codec *codec = dai->codec; - struct wm8904_priv *wm8904 = codec->private_data; + struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec); int ret, i, best, best_val, cur_val; unsigned int aif1 = 0; unsigned int aif2 = 0; @@ -1670,7 +1670,7 @@ static int wm8904_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int freq, int dir) { struct snd_soc_codec *codec = dai->codec; - struct wm8904_priv *priv = codec->private_data; + struct wm8904_priv *priv = snd_soc_codec_get_drvdata(codec); switch (clk_id) { case WM8904_CLK_MCLK: @@ -1786,7 +1786,7 @@ static int wm8904_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width) { struct snd_soc_codec *codec = dai->codec; - struct wm8904_priv *wm8904 = codec->private_data; + struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec); int aif1 = 0; /* Don't need to validate anything if we're turning off TDM */ @@ -1943,7 +1943,7 @@ static int wm8904_set_fll(struct snd_soc_dai *dai, int fll_id, int source, unsigned int Fref, unsigned int Fout) { struct snd_soc_codec *codec = dai->codec; - struct wm8904_priv *wm8904 = codec->private_data; + struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec); struct _fll_div fll_div; int ret, val; int clock2, fll1; @@ -2095,7 +2095,7 @@ static int wm8904_digital_mute(struct snd_soc_dai *codec_dai, int mute) static void wm8904_sync_cache(struct snd_soc_codec *codec) { - struct wm8904_priv *wm8904 = codec->private_data; + struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec); int i; if (!codec->cache_sync) @@ -2122,7 +2122,7 @@ static void wm8904_sync_cache(struct snd_soc_codec *codec) static int wm8904_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { - struct wm8904_priv *wm8904 = codec->private_data; + struct wm8904_priv *wm8904 = snd_soc_codec_get_drvdata(codec); int ret; switch (level) { @@ -2395,7 +2395,7 @@ static int wm8904_probe(struct platform_device *pdev) goto pcm_err; } - wm8904_handle_pdata(codec->private_data); + wm8904_handle_pdata(snd_soc_codec_get_drvdata(codec)); wm8904_add_widgets(codec); @@ -2426,6 +2426,7 @@ EXPORT_SYMBOL_GPL(soc_codec_dev_wm8904); static int wm8904_register(struct wm8904_priv *wm8904, enum snd_soc_control_type control) { + struct wm8904_pdata *pdata = wm8904->pdata; int ret; struct snd_soc_codec *codec = &wm8904->codec; int i; @@ -2439,7 +2440,7 @@ static int wm8904_register(struct wm8904_priv *wm8904, INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); - codec->private_data = wm8904; + snd_soc_codec_set_drvdata(codec, wm8904); codec->name = "WM8904"; codec->owner = THIS_MODULE; codec->bias_level = SND_SOC_BIAS_OFF; @@ -2531,6 +2532,22 @@ static int wm8904_register(struct wm8904_priv *wm8904, WM8904_LINEOUTRZC; wm8904->reg_cache[WM8904_CLOCK_RATES_0] &= ~WM8904_SR_MODE; + /* Apply configuration from the platform data. */ + if (wm8904->pdata) { + for (i = 0; i < WM8904_GPIO_REGS; i++) { + if (!pdata->gpio_cfg[i]) + continue; + + wm8904->reg_cache[WM8904_GPIO_CONTROL_1 + i] + = pdata->gpio_cfg[i] & 0xffff; + } + + /* Zero is the default value for these anyway */ + for (i = 0; i < WM8904_MIC_REGS; i++) + wm8904->reg_cache[WM8904_MIC_BIAS_CONTROL_0 + i] + = pdata->mic_cfg[i]; + } + /* Set Class W by default - this will be managed by the Class * G widget at runtime where bypass paths are available. */ diff --git a/sound/soc/codecs/wm8904.h b/sound/soc/codecs/wm8904.h index b68886d..abe5059 100644 --- a/sound/soc/codecs/wm8904.h +++ b/sound/soc/codecs/wm8904.h @@ -186,39 +186,6 @@ extern struct snd_soc_codec_device soc_codec_dev_wm8904; #define WM8904_VMID_ENA_WIDTH 1 /* VMID_ENA */ /* - * R6 (0x06) - Mic Bias Control 0 - */ -#define WM8904_MICDET_THR_MASK 0x0070 /* MICDET_THR - [6:4] */ -#define WM8904_MICDET_THR_SHIFT 4 /* MICDET_THR - [6:4] */ -#define WM8904_MICDET_THR_WIDTH 3 /* MICDET_THR - [6:4] */ -#define WM8904_MICSHORT_THR_MASK 0x000C /* MICSHORT_THR - [3:2] */ -#define WM8904_MICSHORT_THR_SHIFT 2 /* MICSHORT_THR - [3:2] */ -#define WM8904_MICSHORT_THR_WIDTH 2 /* MICSHORT_THR - [3:2] */ -#define WM8904_MICDET_ENA 0x0002 /* MICDET_ENA */ -#define WM8904_MICDET_ENA_MASK 0x0002 /* MICDET_ENA */ -#define WM8904_MICDET_ENA_SHIFT 1 /* MICDET_ENA */ -#define WM8904_MICDET_ENA_WIDTH 1 /* MICDET_ENA */ -#define WM8904_MICBIAS_ENA 0x0001 /* MICBIAS_ENA */ -#define WM8904_MICBIAS_ENA_MASK 0x0001 /* MICBIAS_ENA */ -#define WM8904_MICBIAS_ENA_SHIFT 0 /* MICBIAS_ENA */ -#define WM8904_MICBIAS_ENA_WIDTH 1 /* MICBIAS_ENA */ - -/* - * R7 (0x07) - Mic Bias Control 1 - */ -#define WM8904_MIC_DET_FILTER_ENA 0x8000 /* MIC_DET_FILTER_ENA */ -#define WM8904_MIC_DET_FILTER_ENA_MASK 0x8000 /* MIC_DET_FILTER_ENA */ -#define WM8904_MIC_DET_FILTER_ENA_SHIFT 15 /* MIC_DET_FILTER_ENA */ -#define WM8904_MIC_DET_FILTER_ENA_WIDTH 1 /* MIC_DET_FILTER_ENA */ -#define WM8904_MIC_SHORT_FILTER_ENA 0x4000 /* MIC_SHORT_FILTER_ENA */ -#define WM8904_MIC_SHORT_FILTER_ENA_MASK 0x4000 /* MIC_SHORT_FILTER_ENA */ -#define WM8904_MIC_SHORT_FILTER_ENA_SHIFT 14 /* MIC_SHORT_FILTER_ENA */ -#define WM8904_MIC_SHORT_FILTER_ENA_WIDTH 1 /* MIC_SHORT_FILTER_ENA */ -#define WM8904_MICBIAS_SEL_MASK 0x0007 /* MICBIAS_SEL - [2:0] */ -#define WM8904_MICBIAS_SEL_SHIFT 0 /* MICBIAS_SEL - [2:0] */ -#define WM8904_MICBIAS_SEL_WIDTH 3 /* MICBIAS_SEL - [2:0] */ - -/* * R8 (0x08) - Analogue DAC 0 */ #define WM8904_DAC_BIAS_SEL_MASK 0x0018 /* DAC_BIAS_SEL - [4:3] */ @@ -1200,70 +1167,6 @@ extern struct snd_soc_codec_device soc_codec_dev_wm8904; #define WM8904_FLL_CLK_REF_SRC_WIDTH 2 /* FLL_CLK_REF_SRC - [1:0] */ /* - * R121 (0x79) - GPIO Control 1 - */ -#define WM8904_GPIO1_PU 0x0020 /* GPIO1_PU */ -#define WM8904_GPIO1_PU_MASK 0x0020 /* GPIO1_PU */ -#define WM8904_GPIO1_PU_SHIFT 5 /* GPIO1_PU */ -#define WM8904_GPIO1_PU_WIDTH 1 /* GPIO1_PU */ -#define WM8904_GPIO1_PD 0x0010 /* GPIO1_PD */ -#define WM8904_GPIO1_PD_MASK 0x0010 /* GPIO1_PD */ -#define WM8904_GPIO1_PD_SHIFT 4 /* GPIO1_PD */ -#define WM8904_GPIO1_PD_WIDTH 1 /* GPIO1_PD */ -#define WM8904_GPIO1_SEL_MASK 0x000F /* GPIO1_SEL - [3:0] */ -#define WM8904_GPIO1_SEL_SHIFT 0 /* GPIO1_SEL - [3:0] */ -#define WM8904_GPIO1_SEL_WIDTH 4 /* GPIO1_SEL - [3:0] */ - -/* - * R122 (0x7A) - GPIO Control 2 - */ -#define WM8904_GPIO2_PU 0x0020 /* GPIO2_PU */ -#define WM8904_GPIO2_PU_MASK 0x0020 /* GPIO2_PU */ -#define WM8904_GPIO2_PU_SHIFT 5 /* GPIO2_PU */ -#define WM8904_GPIO2_PU_WIDTH 1 /* GPIO2_PU */ -#define WM8904_GPIO2_PD 0x0010 /* GPIO2_PD */ -#define WM8904_GPIO2_PD_MASK 0x0010 /* GPIO2_PD */ -#define WM8904_GPIO2_PD_SHIFT 4 /* GPIO2_PD */ -#define WM8904_GPIO2_PD_WIDTH 1 /* GPIO2_PD */ -#define WM8904_GPIO2_SEL_MASK 0x000F /* GPIO2_SEL - [3:0] */ -#define WM8904_GPIO2_SEL_SHIFT 0 /* GPIO2_SEL - [3:0] */ -#define WM8904_GPIO2_SEL_WIDTH 4 /* GPIO2_SEL - [3:0] */ - -/* - * R123 (0x7B) - GPIO Control 3 - */ -#define WM8904_GPIO3_PU 0x0020 /* GPIO3_PU */ -#define WM8904_GPIO3_PU_MASK 0x0020 /* GPIO3_PU */ -#define WM8904_GPIO3_PU_SHIFT 5 /* GPIO3_PU */ -#define WM8904_GPIO3_PU_WIDTH 1 /* GPIO3_PU */ -#define WM8904_GPIO3_PD 0x0010 /* GPIO3_PD */ -#define WM8904_GPIO3_PD_MASK 0x0010 /* GPIO3_PD */ -#define WM8904_GPIO3_PD_SHIFT 4 /* GPIO3_PD */ -#define WM8904_GPIO3_PD_WIDTH 1 /* GPIO3_PD */ -#define WM8904_GPIO3_SEL_MASK 0x000F /* GPIO3_SEL - [3:0] */ -#define WM8904_GPIO3_SEL_SHIFT 0 /* GPIO3_SEL - [3:0] */ -#define WM8904_GPIO3_SEL_WIDTH 4 /* GPIO3_SEL - [3:0] */ - -/* - * R124 (0x7C) - GPIO Control 4 - */ -#define WM8904_GPI7_ENA 0x0200 /* GPI7_ENA */ -#define WM8904_GPI7_ENA_MASK 0x0200 /* GPI7_ENA */ -#define WM8904_GPI7_ENA_SHIFT 9 /* GPI7_ENA */ -#define WM8904_GPI7_ENA_WIDTH 1 /* GPI7_ENA */ -#define WM8904_GPI8_ENA 0x0100 /* GPI8_ENA */ -#define WM8904_GPI8_ENA_MASK 0x0100 /* GPI8_ENA */ -#define WM8904_GPI8_ENA_SHIFT 8 /* GPI8_ENA */ -#define WM8904_GPI8_ENA_WIDTH 1 /* GPI8_ENA */ -#define WM8904_GPIO_BCLK_MODE_ENA 0x0080 /* GPIO_BCLK_MODE_ENA */ -#define WM8904_GPIO_BCLK_MODE_ENA_MASK 0x0080 /* GPIO_BCLK_MODE_ENA */ -#define WM8904_GPIO_BCLK_MODE_ENA_SHIFT 7 /* GPIO_BCLK_MODE_ENA */ -#define WM8904_GPIO_BCLK_MODE_ENA_WIDTH 1 /* GPIO_BCLK_MODE_ENA */ -#define WM8904_GPIO_BCLK_SEL_MASK 0x000F /* GPIO_BCLK_SEL - [3:0] */ -#define WM8904_GPIO_BCLK_SEL_SHIFT 0 /* GPIO_BCLK_SEL - [3:0] */ -#define WM8904_GPIO_BCLK_SEL_WIDTH 4 /* GPIO_BCLK_SEL - [3:0] */ - -/* * R126 (0x7E) - Digital Pulls */ #define WM8904_MCLK_PU 0x0080 /* MCLK_PU */ diff --git a/sound/soc/codecs/wm8940.c b/sound/soc/codecs/wm8940.c index 0c04b47..e3c4bbf 100644 --- a/sound/soc/codecs/wm8940.c +++ b/sound/soc/codecs/wm8940.c @@ -581,7 +581,7 @@ static int wm8940_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 wm8940_priv *wm8940 = codec->private_data; + struct wm8940_priv *wm8940 = snd_soc_codec_get_drvdata(codec); switch (freq) { case 11289600: @@ -692,7 +692,6 @@ static int wm8940_resume(struct platform_device *pdev) ret = wm8940_set_bias_level(codec, SND_SOC_BIAS_STANDBY); if (ret) goto error_ret; - ret = wm8940_set_bias_level(codec, codec->suspend_bias_level); error_ret: return ret; @@ -773,7 +772,7 @@ static int wm8940_register(struct wm8940_priv *wm8940, INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); - codec->private_data = wm8940; + snd_soc_codec_set_drvdata(codec, wm8940); codec->name = "WM8940"; codec->owner = THIS_MODULE; codec->bias_level = SND_SOC_BIAS_OFF; diff --git a/sound/soc/codecs/wm8955.c b/sound/soc/codecs/wm8955.c index c8d7a80..fedb764 100644 --- a/sound/soc/codecs/wm8955.c +++ b/sound/soc/codecs/wm8955.c @@ -235,7 +235,7 @@ static struct { static int wm8955_configure_clocking(struct snd_soc_codec *codec) { - struct wm8955_priv *wm8955 = codec->private_data; + struct wm8955_priv *wm8955 = snd_soc_codec_get_drvdata(codec); int i, ret, val; int clocking = 0; int srate = 0; @@ -353,7 +353,7 @@ static int deemph_settings[] = { 0, 32000, 44100, 48000 }; static int wm8955_set_deemph(struct snd_soc_codec *codec) { - struct wm8955_priv *wm8955 = codec->private_data; + struct wm8955_priv *wm8955 = snd_soc_codec_get_drvdata(codec); int val, i, best; /* If we're using deemphasis select the nearest available sample @@ -382,7 +382,7 @@ static int wm8955_get_deemph(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - struct wm8955_priv *wm8955 = codec->private_data; + struct wm8955_priv *wm8955 = snd_soc_codec_get_drvdata(codec); return wm8955->deemph; } @@ -391,7 +391,7 @@ static int wm8955_put_deemph(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - struct wm8955_priv *wm8955 = codec->private_data; + struct wm8955_priv *wm8955 = snd_soc_codec_get_drvdata(codec); int deemph = ucontrol->value.enumerated.item[0]; if (deemph > 1) @@ -598,7 +598,7 @@ static int wm8955_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_codec *codec = dai->codec; - struct wm8955_priv *wm8955 = codec->private_data; + struct wm8955_priv *wm8955 = snd_soc_codec_get_drvdata(codec); int ret; int wl; @@ -647,7 +647,7 @@ static int wm8955_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int freq, int dir) { struct snd_soc_codec *codec = dai->codec; - struct wm8955_priv *priv = codec->private_data; + struct wm8955_priv *priv = snd_soc_codec_get_drvdata(codec); int div; switch (clk_id) { @@ -770,7 +770,7 @@ static int wm8955_digital_mute(struct snd_soc_dai *codec_dai, int mute) static int wm8955_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { - struct wm8955_priv *wm8955 = codec->private_data; + struct wm8955_priv *wm8955 = snd_soc_codec_get_drvdata(codec); int ret, i; switch (level) { @@ -971,7 +971,7 @@ static int wm8955_register(struct wm8955_priv *wm8955, INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); - codec->private_data = wm8955; + snd_soc_codec_set_drvdata(codec, wm8955); codec->name = "WM8955"; codec->owner = THIS_MODULE; codec->bias_level = SND_SOC_BIAS_OFF; diff --git a/sound/soc/codecs/wm8960.c b/sound/soc/codecs/wm8960.c index f1e63e0..7233cc6 100644 --- a/sound/soc/codecs/wm8960.c +++ b/sound/soc/codecs/wm8960.c @@ -23,6 +23,7 @@ #include <sound/soc-dapm.h> #include <sound/initval.h> #include <sound/tlv.h> +#include <sound/wm8960.h> #include "wm8960.h" @@ -31,8 +32,14 @@ struct snd_soc_codec_device soc_codec_dev_wm8960; /* R25 - Power 1 */ +#define WM8960_VMID_MASK 0x180 #define WM8960_VREF 0x40 +/* R26 - Power 2 */ +#define WM8960_PWR2_LOUT1 0x40 +#define WM8960_PWR2_ROUT1 0x20 +#define WM8960_PWR2_OUT3 0x02 + /* R28 - Anti-pop 1 */ #define WM8960_POBCTRL 0x80 #define WM8960_BUFDCOPEN 0x10 @@ -42,6 +49,7 @@ struct snd_soc_codec_device soc_codec_dev_wm8960; /* R29 - Anti-pop 2 */ #define WM8960_DISOP 0x40 +#define WM8960_DRES_MASK 0x30 /* * wm8960 register cache @@ -68,6 +76,9 @@ static const u16 wm8960_reg[WM8960_CACHEREGNUM] = { struct wm8960_priv { u16 reg_cache[WM8960_CACHEREGNUM]; struct snd_soc_codec codec; + struct snd_soc_dapm_widget *lout1; + struct snd_soc_dapm_widget *rout1; + struct snd_soc_dapm_widget *out3; }; #define wm8960_reset(c) snd_soc_write(c, WM8960_RESET, 0) @@ -226,10 +237,6 @@ SND_SOC_DAPM_MIXER("Right Output Mixer", WM8960_POWER3, 2, 0, &wm8960_routput_mixer[0], ARRAY_SIZE(wm8960_routput_mixer)), -SND_SOC_DAPM_MIXER("Mono Output Mixer", WM8960_POWER2, 1, 0, - &wm8960_mono_out[0], - ARRAY_SIZE(wm8960_mono_out)), - SND_SOC_DAPM_PGA("LOUT1 PGA", WM8960_POWER2, 6, 0, NULL, 0), SND_SOC_DAPM_PGA("ROUT1 PGA", WM8960_POWER2, 5, 0, NULL, 0), @@ -248,6 +255,17 @@ SND_SOC_DAPM_OUTPUT("SPK_RN"), SND_SOC_DAPM_OUTPUT("OUT3"), }; +static const struct snd_soc_dapm_widget wm8960_dapm_widgets_out3[] = { +SND_SOC_DAPM_MIXER("Mono Output Mixer", WM8960_POWER2, 1, 0, + &wm8960_mono_out[0], + ARRAY_SIZE(wm8960_mono_out)), +}; + +/* Represent OUT3 as a PGA so that it gets turned on with LOUT1/ROUT1 */ +static const struct snd_soc_dapm_widget wm8960_dapm_widgets_capless[] = { +SND_SOC_DAPM_PGA("OUT3 VMID", WM8960_POWER2, 1, 0, NULL, 0), +}; + static const struct snd_soc_dapm_route audio_paths[] = { { "Left Boost Mixer", "LINPUT1 Switch", "LINPUT1" }, { "Left Boost Mixer", "LINPUT2 Switch", "LINPUT2" }, @@ -278,9 +296,6 @@ static const struct snd_soc_dapm_route audio_paths[] = { { "Right Output Mixer", "Boost Bypass Switch", "Right Boost Mixer" } , { "Right Output Mixer", "PCM Playback Switch", "Right DAC" }, - { "Mono Output Mixer", "Left Switch", "Left Output Mixer" }, - { "Mono Output Mixer", "Right Switch", "Right Output Mixer" }, - { "LOUT1 PGA", NULL, "Left Output Mixer" }, { "ROUT1 PGA", NULL, "Right Output Mixer" }, @@ -297,17 +312,65 @@ static const struct snd_soc_dapm_route audio_paths[] = { { "SPK_LP", NULL, "Left Speaker Output" }, { "SPK_RN", NULL, "Right Speaker Output" }, { "SPK_RP", NULL, "Right Speaker Output" }, +}; + +static const struct snd_soc_dapm_route audio_paths_out3[] = { + { "Mono Output Mixer", "Left Switch", "Left Output Mixer" }, + { "Mono Output Mixer", "Right Switch", "Right Output Mixer" }, { "OUT3", NULL, "Mono Output Mixer", } }; +static const struct snd_soc_dapm_route audio_paths_capless[] = { + { "HP_L", NULL, "OUT3 VMID" }, + { "HP_R", NULL, "OUT3 VMID" }, + + { "OUT3 VMID", NULL, "Left Output Mixer" }, + { "OUT3 VMID", NULL, "Right Output Mixer" }, +}; + static int wm8960_add_widgets(struct snd_soc_codec *codec) { + struct wm8960_data *pdata = codec->dev->platform_data; + struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + struct snd_soc_dapm_widget *w; + snd_soc_dapm_new_controls(codec, wm8960_dapm_widgets, ARRAY_SIZE(wm8960_dapm_widgets)); snd_soc_dapm_add_routes(codec, audio_paths, ARRAY_SIZE(audio_paths)); + /* In capless mode OUT3 is used to provide VMID for the + * headphone outputs, otherwise it is used as a mono mixer. + */ + if (pdata && pdata->capless) { + snd_soc_dapm_new_controls(codec, wm8960_dapm_widgets_capless, + ARRAY_SIZE(wm8960_dapm_widgets_capless)); + + snd_soc_dapm_add_routes(codec, audio_paths_capless, + ARRAY_SIZE(audio_paths_capless)); + } else { + snd_soc_dapm_new_controls(codec, wm8960_dapm_widgets_out3, + ARRAY_SIZE(wm8960_dapm_widgets_out3)); + + snd_soc_dapm_add_routes(codec, audio_paths_out3, + ARRAY_SIZE(audio_paths_out3)); + } + + /* We need to power up the headphone output stage out of + * sequence for capless mode. To save scanning the widget + * list each time to find the desired power state do so now + * and save the result. + */ + list_for_each_entry(w, &codec->dapm_widgets, list) { + if (strcmp(w->name, "LOUT1 PGA") == 0) + wm8960->lout1 = w; + if (strcmp(w->name, "ROUT1 PGA") == 0) + wm8960->rout1 = w; + if (strcmp(w->name, "OUT3 VMID") == 0) + wm8960->out3 = w; + } + return 0; } @@ -408,10 +471,9 @@ static int wm8960_mute(struct snd_soc_dai *dai, int mute) return 0; } -static int wm8960_set_bias_level(struct snd_soc_codec *codec, - enum snd_soc_bias_level level) +static int wm8960_set_bias_level_out3(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) { - struct wm8960_data *pdata = codec->dev->platform_data; u16 reg; switch (level) { @@ -430,18 +492,8 @@ static int wm8960_set_bias_level(struct snd_soc_codec *codec, if (codec->bias_level == SND_SOC_BIAS_OFF) { /* Enable anti-pop features */ snd_soc_write(codec, WM8960_APOP1, - WM8960_POBCTRL | WM8960_SOFT_ST | - WM8960_BUFDCOPEN | WM8960_BUFIOEN); - - /* Discharge HP output */ - reg = WM8960_DISOP; - if (pdata) - reg |= pdata->dres << 4; - snd_soc_write(codec, WM8960_APOP2, reg); - - msleep(400); - - snd_soc_write(codec, WM8960_APOP2, 0); + WM8960_POBCTRL | WM8960_SOFT_ST | + WM8960_BUFDCOPEN | WM8960_BUFIOEN); /* Enable & ramp VMID at 2x50k */ reg = snd_soc_read(codec, WM8960_POWER1); @@ -472,8 +524,101 @@ static int wm8960_set_bias_level(struct snd_soc_codec *codec, /* Disable VMID and VREF, let them discharge */ snd_soc_write(codec, WM8960_POWER1, 0); msleep(600); + break; + } + + codec->bias_level = level; + + return 0; +} + +static int wm8960_set_bias_level_capless(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + int reg; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + switch (codec->bias_level) { + case SND_SOC_BIAS_STANDBY: + /* Enable anti pop mode */ + snd_soc_update_bits(codec, WM8960_APOP1, + WM8960_POBCTRL | WM8960_SOFT_ST | + WM8960_BUFDCOPEN, + WM8960_POBCTRL | WM8960_SOFT_ST | + WM8960_BUFDCOPEN); + + /* Enable LOUT1, ROUT1 and OUT3 if they're enabled */ + reg = 0; + if (wm8960->lout1 && wm8960->lout1->power) + reg |= WM8960_PWR2_LOUT1; + if (wm8960->rout1 && wm8960->rout1->power) + reg |= WM8960_PWR2_ROUT1; + if (wm8960->out3 && wm8960->out3->power) + reg |= WM8960_PWR2_OUT3; + snd_soc_update_bits(codec, WM8960_POWER2, + WM8960_PWR2_LOUT1 | + WM8960_PWR2_ROUT1 | + WM8960_PWR2_OUT3, reg); + + /* Enable VMID at 2*50k */ + snd_soc_update_bits(codec, WM8960_POWER1, + WM8960_VMID_MASK, 0x80); + + /* Ramp */ + msleep(100); + + /* Enable VREF */ + snd_soc_update_bits(codec, WM8960_POWER1, + WM8960_VREF, WM8960_VREF); + + msleep(100); + break; + + case SND_SOC_BIAS_ON: + /* Enable anti-pop mode */ + snd_soc_update_bits(codec, WM8960_APOP1, + WM8960_POBCTRL | WM8960_SOFT_ST | + WM8960_BUFDCOPEN, + WM8960_POBCTRL | WM8960_SOFT_ST | + WM8960_BUFDCOPEN); + + /* Disable VMID and VREF */ + snd_soc_update_bits(codec, WM8960_POWER1, + WM8960_VREF | WM8960_VMID_MASK, 0); + break; + + default: + break; + } + break; - snd_soc_write(codec, WM8960_APOP1, 0); + case SND_SOC_BIAS_STANDBY: + switch (codec->bias_level) { + case SND_SOC_BIAS_PREPARE: + /* Disable HP discharge */ + snd_soc_update_bits(codec, WM8960_APOP2, + WM8960_DISOP | WM8960_DRES_MASK, + 0); + + /* Disable anti-pop features */ + snd_soc_update_bits(codec, WM8960_APOP1, + WM8960_POBCTRL | WM8960_SOFT_ST | + WM8960_BUFDCOPEN, + WM8960_POBCTRL | WM8960_SOFT_ST | + WM8960_BUFDCOPEN); + break; + + default: + break; + } + break; + + case SND_SOC_BIAS_OFF: break; } @@ -594,10 +739,6 @@ static int wm8960_set_dai_clkdiv(struct snd_soc_dai *codec_dai, u16 reg; switch (div_id) { - case WM8960_SYSCLKSEL: - reg = snd_soc_read(codec, WM8960_CLOCK1) & 0x1fe; - snd_soc_write(codec, WM8960_CLOCK1, reg | div); - break; case WM8960_SYSCLKDIV: reg = snd_soc_read(codec, WM8960_CLOCK1) & 0x1f9; snd_soc_write(codec, WM8960_CLOCK1, reg | div); @@ -663,7 +804,7 @@ static int wm8960_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; - wm8960_set_bias_level(codec, SND_SOC_BIAS_OFF); + codec->set_bias_level(codec, SND_SOC_BIAS_OFF); return 0; } @@ -682,8 +823,8 @@ static int wm8960_resume(struct platform_device *pdev) codec->hw_write(codec->control_data, data, 2); } - wm8960_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - wm8960_set_bias_level(codec, codec->suspend_bias_level); + codec->set_bias_level(codec, SND_SOC_BIAS_STANDBY); + return 0; } @@ -753,6 +894,8 @@ static int wm8960_register(struct wm8960_priv *wm8960, goto err; } + codec->set_bias_level = wm8960_set_bias_level_out3; + if (!pdata) { dev_warn(codec->dev, "No platform data supplied\n"); } else { @@ -760,17 +903,19 @@ static int wm8960_register(struct wm8960_priv *wm8960, dev_err(codec->dev, "Invalid DRES: %d\n", pdata->dres); pdata->dres = 0; } + + if (pdata->capless) + codec->set_bias_level = wm8960_set_bias_level_capless; } mutex_init(&codec->mutex); INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); - codec->private_data = wm8960; + snd_soc_codec_set_drvdata(codec, wm8960); codec->name = "WM8960"; codec->owner = THIS_MODULE; codec->bias_level = SND_SOC_BIAS_OFF; - codec->set_bias_level = wm8960_set_bias_level; codec->dai = &wm8960_dai; codec->num_dai = 1; codec->reg_cache_size = WM8960_CACHEREGNUM; @@ -792,7 +937,7 @@ static int wm8960_register(struct wm8960_priv *wm8960, wm8960_dai.dev = codec->dev; - wm8960_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + codec->set_bias_level(codec, SND_SOC_BIAS_STANDBY); /* Latch the update bits */ reg = snd_soc_read(codec, WM8960_LINVOL); @@ -841,7 +986,7 @@ err: static void wm8960_unregister(struct wm8960_priv *wm8960) { - wm8960_set_bias_level(&wm8960->codec, SND_SOC_BIAS_OFF); + wm8960->codec.set_bias_level(&wm8960->codec, SND_SOC_BIAS_OFF); snd_soc_unregister_dai(&wm8960_dai); snd_soc_unregister_codec(&wm8960->codec); kfree(wm8960); @@ -883,7 +1028,7 @@ MODULE_DEVICE_TABLE(i2c, wm8960_i2c_id); static struct i2c_driver wm8960_i2c_driver = { .driver = { - .name = "WM8960 I2C Codec", + .name = "wm8960", .owner = THIS_MODULE, }, .probe = wm8960_i2c_probe, diff --git a/sound/soc/codecs/wm8960.h b/sound/soc/codecs/wm8960.h index c9af56c..a5ef6548 100644 --- a/sound/soc/codecs/wm8960.h +++ b/sound/soc/codecs/wm8960.h @@ -76,7 +76,6 @@ #define WM8960_OPCLKDIV 2 #define WM8960_DCLKDIV 3 #define WM8960_TOCLKSEL 4 -#define WM8960_SYSCLKSEL 5 #define WM8960_SYSCLK_DIV_1 (0 << 1) #define WM8960_SYSCLK_DIV_2 (2 << 1) @@ -114,14 +113,4 @@ extern struct snd_soc_dai wm8960_dai; extern struct snd_soc_codec_device soc_codec_dev_wm8960; -#define WM8960_DRES_400R 0 -#define WM8960_DRES_200R 1 -#define WM8960_DRES_600R 2 -#define WM8960_DRES_150R 3 -#define WM8960_DRES_MAX 3 - -struct wm8960_data { - int dres; -}; - #endif diff --git a/sound/soc/codecs/wm8961.c b/sound/soc/codecs/wm8961.c index 50634ab7..5b9a756 100644 --- a/sound/soc/codecs/wm8961.c +++ b/sound/soc/codecs/wm8961.c @@ -631,7 +631,7 @@ static int wm8961_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_codec *codec = dai->codec; - struct wm8961_priv *wm8961 = codec->private_data; + struct wm8961_priv *wm8961 = snd_soc_codec_get_drvdata(codec); int i, best, target, fs; u16 reg; @@ -722,7 +722,7 @@ static int wm8961_set_sysclk(struct snd_soc_dai *dai, int clk_id, int dir) { struct snd_soc_codec *codec = dai->codec; - struct wm8961_priv *wm8961 = codec->private_data; + struct wm8961_priv *wm8961 = snd_soc_codec_get_drvdata(codec); u16 reg = snd_soc_read(codec, WM8961_CLOCKING1); if (freq > 33000000) { @@ -1065,7 +1065,7 @@ static int wm8961_register(struct wm8961_priv *wm8961) INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); - codec->private_data = wm8961; + snd_soc_codec_set_drvdata(codec, wm8961); codec->name = "WM8961"; codec->owner = THIS_MODULE; codec->dai = &wm8961_dai; diff --git a/sound/soc/codecs/wm8971.c b/sound/soc/codecs/wm8971.c index a65b781..a99620f 100644 --- a/sound/soc/codecs/wm8971.c +++ b/sound/soc/codecs/wm8971.c @@ -415,7 +415,7 @@ static int wm8971_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 wm8971_priv *wm8971 = codec->private_data; + struct wm8971_priv *wm8971 = snd_soc_codec_get_drvdata(codec); switch (freq) { case 11289600: @@ -494,7 +494,7 @@ static int wm8971_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_codec *codec = socdev->card->codec; - struct wm8971_priv *wm8971 = codec->private_data; + struct wm8971_priv *wm8971 = snd_soc_codec_get_drvdata(codec); u16 iface = snd_soc_read(codec, WM8971_IFACE) & 0x1f3; u16 srate = snd_soc_read(codec, WM8971_SRATE) & 0x1c0; int coeff = get_coeff(wm8971->sysclk, params_rate(params)); @@ -820,7 +820,7 @@ static int wm8971_probe(struct platform_device *pdev) return -ENOMEM; } - codec->private_data = wm8971; + snd_soc_codec_set_drvdata(codec, wm8971); socdev->card->codec = codec; mutex_init(&codec->mutex); INIT_LIST_HEAD(&codec->dapm_widgets); @@ -830,7 +830,7 @@ static int wm8971_probe(struct platform_device *pdev) INIT_DELAYED_WORK(&codec->delayed_work, wm8971_work); wm8971_workq = create_workqueue("wm8971"); if (wm8971_workq == NULL) { - kfree(codec->private_data); + kfree(snd_soc_codec_get_drvdata(codec)); kfree(codec); return -ENOMEM; } @@ -844,7 +844,7 @@ static int wm8971_probe(struct platform_device *pdev) if (ret != 0) { destroy_workqueue(wm8971_workq); - kfree(codec->private_data); + kfree(snd_soc_codec_get_drvdata(codec)); kfree(codec); } @@ -867,7 +867,7 @@ static int wm8971_remove(struct platform_device *pdev) i2c_unregister_device(codec->control_data); i2c_del_driver(&wm8971_i2c_driver); #endif - kfree(codec->private_data); + kfree(snd_soc_codec_get_drvdata(codec)); kfree(codec); return 0; diff --git a/sound/soc/codecs/wm8974.c b/sound/soc/codecs/wm8974.c index 69708c4..a2c4b2f 100644 --- a/sound/soc/codecs/wm8974.c +++ b/sound/soc/codecs/wm8974.c @@ -181,7 +181,7 @@ SOC_SINGLE("ADC 128x Oversampling Switch", WM8974_ADC, 8, 1, 0), static const struct snd_kcontrol_new wm8974_speaker_mixer_controls[] = { SOC_DAPM_SINGLE("Line Bypass Switch", WM8974_SPKMIX, 1, 1, 0), SOC_DAPM_SINGLE("Aux Playback Switch", WM8974_SPKMIX, 5, 1, 0), -SOC_DAPM_SINGLE("PCM Playback Switch", WM8974_SPKMIX, 0, 1, 1), +SOC_DAPM_SINGLE("PCM Playback Switch", WM8974_SPKMIX, 0, 1, 0), }; /* Mono Output Mixer */ @@ -609,7 +609,7 @@ static int wm8974_resume(struct platform_device *pdev) codec->hw_write(codec->control_data, data, 2); } wm8974_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - wm8974_set_bias_level(codec, codec->suspend_bias_level); + return 0; } @@ -677,7 +677,7 @@ static __devinit int wm8974_register(struct wm8974_priv *wm8974) INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); - codec->private_data = wm8974; + snd_soc_codec_set_drvdata(codec, wm8974); codec->name = "WM8974"; codec->owner = THIS_MODULE; codec->bias_level = SND_SOC_BIAS_OFF; diff --git a/sound/soc/codecs/wm8978.c b/sound/soc/codecs/wm8978.c index 526f56b..51d5f43 100644 --- a/sound/soc/codecs/wm8978.c +++ b/sound/soc/codecs/wm8978.c @@ -439,7 +439,7 @@ static int wm8978_enum_mclk(unsigned int f_out, unsigned int f_mclk, */ static int wm8978_configure_pll(struct snd_soc_codec *codec) { - struct wm8978_priv *wm8978 = codec->private_data; + struct wm8978_priv *wm8978 = snd_soc_codec_get_drvdata(codec); struct wm8978_pll_div pll_div; unsigned int f_opclk = wm8978->f_opclk, f_mclk = wm8978->f_mclk, f_256fs = wm8978->f_256fs; @@ -535,7 +535,7 @@ static int wm8978_set_dai_clkdiv(struct snd_soc_dai *codec_dai, int div_id, int div) { struct snd_soc_codec *codec = codec_dai->codec; - struct wm8978_priv *wm8978 = codec->private_data; + struct wm8978_priv *wm8978 = snd_soc_codec_get_drvdata(codec); int ret = 0; switch (div_id) { @@ -580,7 +580,7 @@ static int wm8978_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 wm8978_priv *wm8978 = codec->private_data; + struct wm8978_priv *wm8978 = snd_soc_codec_get_drvdata(codec); int ret = 0; dev_dbg(codec->dev, "%s: ID %d, freq %u\n", __func__, clk_id, freq); @@ -692,7 +692,7 @@ static int wm8978_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 wm8978_priv *wm8978 = codec->private_data; + struct wm8978_priv *wm8978 = snd_soc_codec_get_drvdata(codec); /* Word length mask = 0x60 */ u16 iface_ctl = snd_soc_read(codec, WM8978_AUDIO_INTERFACE) & ~0x60; /* Sampling rate mask = 0xe (for filters) */ @@ -912,7 +912,7 @@ static int wm8978_resume(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = socdev->card->codec; - struct wm8978_priv *wm8978 = codec->private_data; + struct wm8978_priv *wm8978 = snd_soc_codec_get_drvdata(codec); int i; u16 *cache = codec->reg_cache; @@ -1020,7 +1020,7 @@ static __devinit int wm8978_register(struct wm8978_priv *wm8978) INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); - codec->private_data = wm8978; + snd_soc_codec_set_drvdata(codec, wm8978); codec->name = "WM8978"; codec->owner = THIS_MODULE; codec->bias_level = SND_SOC_BIAS_OFF; diff --git a/sound/soc/codecs/wm8988.c b/sound/soc/codecs/wm8988.c index bb18c3e..0417dae 100644 --- a/sound/soc/codecs/wm8988.c +++ b/sound/soc/codecs/wm8988.c @@ -495,7 +495,7 @@ static int wm8988_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 wm8988_priv *wm8988 = codec->private_data; + struct wm8988_priv *wm8988 = snd_soc_codec_get_drvdata(codec); switch (freq) { case 11289600: @@ -585,7 +585,7 @@ static int wm8988_pcm_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_codec *codec = dai->codec; - struct wm8988_priv *wm8988 = codec->private_data; + struct wm8988_priv *wm8988 = 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. @@ -610,7 +610,7 @@ static int wm8988_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_codec *codec = socdev->card->codec; - struct wm8988_priv *wm8988 = codec->private_data; + struct wm8988_priv *wm8988 = snd_soc_codec_get_drvdata(codec); u16 iface = snd_soc_read(codec, WM8988_IFACE) & 0x1f3; u16 srate = snd_soc_read(codec, WM8988_SRATE) & 0x180; int coeff; @@ -833,7 +833,7 @@ static int wm8988_register(struct wm8988_priv *wm8988, INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); - codec->private_data = wm8988; + snd_soc_codec_set_drvdata(codec, wm8988); codec->name = "WM8988"; codec->owner = THIS_MODULE; codec->dai = &wm8988_dai; diff --git a/sound/soc/codecs/wm8990.c b/sound/soc/codecs/wm8990.c index 831f473..7b536d9 100644 --- a/sound/soc/codecs/wm8990.c +++ b/sound/soc/codecs/wm8990.c @@ -1012,7 +1012,7 @@ static int wm8990_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 wm8990_priv *wm8990 = codec->private_data; + struct wm8990_priv *wm8990 = snd_soc_codec_get_drvdata(codec); wm8990->sysclk = freq; return 0; @@ -1524,7 +1524,7 @@ static int wm8990_probe(struct platform_device *pdev) return -ENOMEM; } - codec->private_data = wm8990; + snd_soc_codec_set_drvdata(codec, wm8990); socdev->card->codec = codec; mutex_init(&codec->mutex); INIT_LIST_HEAD(&codec->dapm_widgets); @@ -1541,7 +1541,7 @@ static int wm8990_probe(struct platform_device *pdev) #endif if (ret != 0) { - kfree(codec->private_data); + kfree(snd_soc_codec_get_drvdata(codec)); kfree(codec); } return ret; @@ -1561,7 +1561,7 @@ static int wm8990_remove(struct platform_device *pdev) i2c_unregister_device(codec->control_data); i2c_del_driver(&wm8990_i2c_driver); #endif - kfree(codec->private_data); + kfree(snd_soc_codec_get_drvdata(codec)); kfree(codec); return 0; diff --git a/sound/soc/codecs/wm8993.c b/sound/soc/codecs/wm8993.c index 03e8b1a..d8d300c 100644 --- a/sound/soc/codecs/wm8993.c +++ b/sound/soc/codecs/wm8993.c @@ -371,7 +371,7 @@ static int wm8993_set_fll(struct snd_soc_dai *dai, int fll_id, int source, unsigned int Fref, unsigned int Fout) { struct snd_soc_codec *codec = dai->codec; - struct wm8993_priv *wm8993 = codec->private_data; + struct wm8993_priv *wm8993 = snd_soc_codec_get_drvdata(codec); u16 reg1, reg4, reg5; struct _fll_div fll_div; int ret; @@ -458,7 +458,7 @@ static int wm8993_set_fll(struct snd_soc_dai *dai, int fll_id, int source, static int configure_clock(struct snd_soc_codec *codec) { - struct wm8993_priv *wm8993 = codec->private_data; + struct wm8993_priv *wm8993 = snd_soc_codec_get_drvdata(codec); unsigned int reg; /* This should be done on init() for bypass paths */ @@ -717,7 +717,7 @@ static int class_w_put(struct snd_kcontrol *kcontrol, { struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol); struct snd_soc_codec *codec = widget->codec; - struct wm8993_priv *wm8993 = codec->private_data; + struct wm8993_priv *wm8993 = snd_soc_codec_get_drvdata(codec); int ret; /* Turn it off if we're using the main output mixer */ @@ -949,7 +949,7 @@ static void wm8993_cache_restore(struct snd_soc_codec *codec) static int wm8993_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { - struct wm8993_priv *wm8993 = codec->private_data; + struct wm8993_priv *wm8993 = snd_soc_codec_get_drvdata(codec); int ret; switch (level) { @@ -1047,7 +1047,7 @@ static int wm8993_set_sysclk(struct snd_soc_dai *codec_dai, int clk_id, unsigned int freq, int dir) { struct snd_soc_codec *codec = codec_dai->codec; - struct wm8993_priv *wm8993 = codec->private_data; + struct wm8993_priv *wm8993 = snd_soc_codec_get_drvdata(codec); switch (clk_id) { case WM8993_SYSCLK_MCLK: @@ -1067,7 +1067,7 @@ static int wm8993_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) { struct snd_soc_codec *codec = dai->codec; - struct wm8993_priv *wm8993 = codec->private_data; + struct wm8993_priv *wm8993 = snd_soc_codec_get_drvdata(codec); unsigned int aif1 = snd_soc_read(codec, WM8993_AUDIO_INTERFACE_1); unsigned int aif4 = snd_soc_read(codec, WM8993_AUDIO_INTERFACE_4); @@ -1163,7 +1163,7 @@ static int wm8993_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_codec *codec = dai->codec; - struct wm8993_priv *wm8993 = codec->private_data; + struct wm8993_priv *wm8993 = snd_soc_codec_get_drvdata(codec); int ret, i, best, best_val, cur_val; unsigned int clocking1, clocking3, aif1, aif4; @@ -1328,7 +1328,7 @@ static int wm8993_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width) { struct snd_soc_codec *codec = dai->codec; - struct wm8993_priv *wm8993 = codec->private_data; + struct wm8993_priv *wm8993 = snd_soc_codec_get_drvdata(codec); int aif1 = 0; int aif2 = 0; @@ -1431,7 +1431,7 @@ static int wm8993_probe(struct platform_device *pdev) socdev->card->codec = wm8993_codec; codec = wm8993_codec; - wm8993 = codec->private_data; + wm8993 = snd_soc_codec_get_drvdata(codec); ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); if (ret < 0) { @@ -1478,7 +1478,7 @@ static int wm8993_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; - struct wm8993_priv *wm8993 = codec->private_data; + struct wm8993_priv *wm8993 = snd_soc_codec_get_drvdata(codec); int fll_fout = wm8993->fll_fout; int fll_fref = wm8993->fll_fref; int ret; @@ -1502,7 +1502,7 @@ static int wm8993_resume(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = socdev->card->codec; - struct wm8993_priv *wm8993 = codec->private_data; + struct wm8993_priv *wm8993 = snd_soc_codec_get_drvdata(codec); int ret; wm8993_set_bias_level(codec, SND_SOC_BIAS_STANDBY); @@ -1571,7 +1571,7 @@ static int wm8993_i2c_probe(struct i2c_client *i2c, codec->set_bias_level = wm8993_set_bias_level; codec->dai = &wm8993_dai; codec->num_dai = 1; - codec->private_data = wm8993; + snd_soc_codec_set_drvdata(codec, wm8993); wm8993->hubs_data.hp_startup_mode = 1; wm8993->hubs_data.dcs_codes = -2; diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c index 9da0724..e84a117 100644 --- a/sound/soc/codecs/wm8994.c +++ b/sound/soc/codecs/wm8994.c @@ -62,6 +62,12 @@ static int wm8994_retune_mobile_base[] = { #define WM8994_REG_CACHE_SIZE 0x621 +struct wm8994_micdet { + struct snd_soc_jack *jack; + int det; + int shrt; +}; + /* codec private data */ struct wm8994_priv { struct wm_hubs_data hubs; @@ -87,6 +93,8 @@ struct wm8994_priv { int retune_mobile_cfg[WM8994_NUM_EQ]; struct soc_enum retune_mobile_enum; + struct wm8994_micdet micdet[2]; + struct wm8994_pdata *pdata; }; @@ -1696,13 +1704,15 @@ static int wm8994_volatile(unsigned int reg) static int wm8994_write(struct snd_soc_codec *codec, unsigned int reg, unsigned int value) { - struct wm8994_priv *wm8994 = codec->private_data; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); BUG_ON(reg > WM8994_MAX_REGISTER); if (!wm8994_volatile(reg)) wm8994->reg_cache[reg] = value; + dev_dbg(codec->dev, "0x%x = 0x%x\n", reg, value); + return wm8994_reg_write(codec->control_data, reg, value); } @@ -1721,7 +1731,7 @@ static unsigned int wm8994_read(struct snd_soc_codec *codec, static int configure_aif_clock(struct snd_soc_codec *codec, int aif) { - struct wm8994_priv *wm8994 = codec->private_data; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); int rate; int reg1 = 0; int offset; @@ -1762,6 +1772,11 @@ static int configure_aif_clock(struct snd_soc_codec *codec, int aif) dev_dbg(codec->dev, "Dividing AIF%d clock to %dHz\n", aif + 1, rate); } + + if (rate && rate < 3000000) + dev_warn(codec->dev, "AIF%dCLK is %dHz, should be >=3MHz for optimal performance\n", + aif + 1, rate); + wm8994->aifclk[aif] = rate; snd_soc_update_bits(codec, WM8994_AIF1_CLOCKING_1 + offset, @@ -1773,7 +1788,7 @@ static int configure_aif_clock(struct snd_soc_codec *codec, int aif) static int configure_clock(struct snd_soc_codec *codec) { - struct wm8994_priv *wm8994 = codec->private_data; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); int old, new; /* Bring up the AIF clocks first */ @@ -1870,7 +1885,7 @@ static int wm8994_put_drc_sw(struct snd_kcontrol *kcontrol, static void wm8994_set_drc(struct snd_soc_codec *codec, int drc) { - struct wm8994_priv *wm8994 = codec->private_data; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); struct wm8994_pdata *pdata = wm8994->pdata; int base = wm8994_drc_base[drc]; int cfg = wm8994->drc_cfg[drc]; @@ -1906,7 +1921,7 @@ static int wm8994_put_drc_enum(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - struct wm8994_priv *wm8994 = codec->private_data; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); struct wm8994_pdata *pdata = wm8994->pdata; int drc = wm8994_get_drc(kcontrol->id.name); int value = ucontrol->value.integer.value[0]; @@ -1928,7 +1943,7 @@ static int wm8994_get_drc_enum(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - struct wm8994_priv *wm8994 = codec->private_data; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); int drc = wm8994_get_drc(kcontrol->id.name); ucontrol->value.enumerated.item[0] = wm8994->drc_cfg[drc]; @@ -1938,7 +1953,7 @@ static int wm8994_get_drc_enum(struct snd_kcontrol *kcontrol, static void wm8994_set_retune_mobile(struct snd_soc_codec *codec, int block) { - struct wm8994_priv *wm8994 = codec->private_data; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); struct wm8994_pdata *pdata = wm8994->pdata; int base = wm8994_retune_mobile_base[block]; int iface, best, best_val, save, i, cfg; @@ -2009,7 +2024,7 @@ static int wm8994_put_retune_mobile_enum(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - struct wm8994_priv *wm8994 = codec->private_data; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); struct wm8994_pdata *pdata = wm8994->pdata; int block = wm8994_get_retune_mobile_block(kcontrol->id.name); int value = ucontrol->value.integer.value[0]; @@ -2031,7 +2046,7 @@ static int wm8994_get_retune_mobile_enum(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - struct wm8994_priv *wm8994 = codec->private_data; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); int block = wm8994_get_retune_mobile_block(kcontrol->id.name); ucontrol->value.enumerated.item[0] = wm8994->retune_mobile_cfg[block]; @@ -2182,13 +2197,13 @@ static void wm8994_update_class_w(struct snd_soc_codec *codec) /* Only support direct DAC->headphone paths */ reg = snd_soc_read(codec, WM8994_OUTPUT_MIXER_1); if (!(reg & WM8994_DAC1L_TO_HPOUT1L)) { - dev_dbg(codec->dev, "HPL connected to output mixer\n"); + dev_vdbg(codec->dev, "HPL connected to output mixer\n"); enable = 0; } reg = snd_soc_read(codec, WM8994_OUTPUT_MIXER_2); if (!(reg & WM8994_DAC1R_TO_HPOUT1R)) { - dev_dbg(codec->dev, "HPR connected to output mixer\n"); + dev_vdbg(codec->dev, "HPR connected to output mixer\n"); enable = 0; } @@ -2196,26 +2211,26 @@ static void wm8994_update_class_w(struct snd_soc_codec *codec) reg = snd_soc_read(codec, WM8994_DAC1_LEFT_MIXER_ROUTING); switch (reg) { case WM8994_AIF2DACL_TO_DAC1L: - dev_dbg(codec->dev, "Class W source AIF2DAC\n"); + dev_vdbg(codec->dev, "Class W source AIF2DAC\n"); source = 2 << WM8994_CP_DYN_SRC_SEL_SHIFT; break; case WM8994_AIF1DAC2L_TO_DAC1L: - dev_dbg(codec->dev, "Class W source AIF1DAC2\n"); + dev_vdbg(codec->dev, "Class W source AIF1DAC2\n"); source = 1 << WM8994_CP_DYN_SRC_SEL_SHIFT; break; case WM8994_AIF1DAC1L_TO_DAC1L: - dev_dbg(codec->dev, "Class W source AIF1DAC1\n"); + dev_vdbg(codec->dev, "Class W source AIF1DAC1\n"); source = 0 << WM8994_CP_DYN_SRC_SEL_SHIFT; break; default: - dev_dbg(codec->dev, "DAC mixer setting: %x\n", reg); + dev_vdbg(codec->dev, "DAC mixer setting: %x\n", reg); enable = 0; break; } reg_r = snd_soc_read(codec, WM8994_DAC1_RIGHT_MIXER_ROUTING); if (reg_r != reg) { - dev_dbg(codec->dev, "Left and right DAC mixers different\n"); + dev_vdbg(codec->dev, "Left and right DAC mixers different\n"); enable = 0; } @@ -2777,9 +2792,18 @@ static int wm8994_get_fll_config(struct fll_div *fll, if (freq_in > 1000000) { fll->fll_fratio = 0; - } else { + } else if (freq_in > 256000) { + fll->fll_fratio = 1; + freq_in *= 2; + } else if (freq_in > 128000) { + fll->fll_fratio = 2; + freq_in *= 4; + } else if (freq_in > 64000) { fll->fll_fratio = 3; freq_in *= 8; + } else { + fll->fll_fratio = 4; + freq_in *= 16; } pr_debug("FLL_FRATIO=%d, Fref=%dHz\n", fll->fll_fratio, freq_in); @@ -2812,7 +2836,7 @@ static int wm8994_set_fll(struct snd_soc_dai *dai, int id, int src, unsigned int freq_in, unsigned int freq_out) { struct snd_soc_codec *codec = dai->codec; - struct wm8994_priv *wm8994 = codec->private_data; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); int reg_offset, ret; struct fll_div fll; u16 reg, aif1, aif2; @@ -2836,6 +2860,21 @@ static int wm8994_set_fll(struct snd_soc_dai *dai, int id, int src, return -EINVAL; } + switch (src) { + case 0: + /* Allow no source specification when stopping */ + if (freq_out) + return -EINVAL; + break; + case WM8994_FLL_SRC_MCLK1: + case WM8994_FLL_SRC_MCLK2: + case WM8994_FLL_SRC_LRCLK: + case WM8994_FLL_SRC_BCLK: + break; + default: + return -EINVAL; + } + /* Are we changing anything? */ if (wm8994->fll[id].src == src && wm8994->fll[id].in == freq_in && wm8994->fll[id].out == freq_out) @@ -2876,8 +2915,10 @@ static int wm8994_set_fll(struct snd_soc_dai *dai, int id, int src, fll.n << WM8994_FLL1_N_SHIFT); snd_soc_update_bits(codec, WM8994_FLL1_CONTROL_5 + reg_offset, - WM8994_FLL1_REFCLK_DIV_MASK, - fll.clk_ref_div << WM8994_FLL1_REFCLK_DIV_SHIFT); + WM8994_FLL1_REFCLK_DIV_MASK | + WM8994_FLL1_REFCLK_SRC_MASK, + (fll.clk_ref_div << WM8994_FLL1_REFCLK_DIV_SHIFT) | + (src - 1)); /* Enable (with fractional mode if required) */ if (freq_out) { @@ -2892,6 +2933,7 @@ static int wm8994_set_fll(struct snd_soc_dai *dai, int id, int src, wm8994->fll[id].in = freq_in; wm8994->fll[id].out = freq_out; + wm8994->fll[id].src = src; /* Enable any gated AIF clocks */ snd_soc_update_bits(codec, WM8994_AIF1_CLOCKING_1, @@ -2908,7 +2950,7 @@ 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 = codec->private_data; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); switch (dai->id) { case 1: @@ -3174,7 +3216,7 @@ static int wm8994_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_codec *codec = dai->codec; - struct wm8994_priv *wm8994 = codec->private_data; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); int aif1_reg; int bclk_reg; int lrclk_reg; @@ -3338,6 +3380,36 @@ static int wm8994_aif_mute(struct snd_soc_dai *codec_dai, int mute) return 0; } +static int wm8994_set_tristate(struct snd_soc_dai *codec_dai, int tristate) +{ + struct snd_soc_codec *codec = codec_dai->codec; + int reg, val, mask; + + switch (codec_dai->id) { + case 1: + reg = WM8994_AIF1_MASTER_SLAVE; + mask = WM8994_AIF1_TRI; + break; + case 2: + reg = WM8994_AIF2_MASTER_SLAVE; + mask = WM8994_AIF2_TRI; + break; + case 3: + reg = WM8994_POWER_MANAGEMENT_6; + mask = WM8994_AIF3_TRI; + break; + default: + return -EINVAL; + } + + if (tristate) + val = mask; + else + val = 0; + + return snd_soc_update_bits(codec, reg, mask, reg); +} + #define WM8994_RATES SNDRV_PCM_RATE_8000_96000 #define WM8994_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ @@ -3349,6 +3421,7 @@ static struct snd_soc_dai_ops wm8994_aif1_dai_ops = { .hw_params = wm8994_hw_params, .digital_mute = wm8994_aif_mute, .set_pll = wm8994_set_fll, + .set_tristate = wm8994_set_tristate, }; static struct snd_soc_dai_ops wm8994_aif2_dai_ops = { @@ -3357,6 +3430,11 @@ static struct snd_soc_dai_ops wm8994_aif2_dai_ops = { .hw_params = wm8994_hw_params, .digital_mute = wm8994_aif_mute, .set_pll = wm8994_set_fll, + .set_tristate = wm8994_set_tristate, +}; + +static struct snd_soc_dai_ops wm8994_aif3_dai_ops = { + .set_tristate = wm8994_set_tristate, }; struct snd_soc_dai wm8994_dai[] = { @@ -3400,6 +3478,7 @@ struct snd_soc_dai wm8994_dai[] = { }, { .name = "WM8994 AIF3", + .id = 3, .playback = { .stream_name = "AIF3 Playback", .channels_min = 2, @@ -3414,6 +3493,7 @@ struct snd_soc_dai wm8994_dai[] = { .rates = WM8994_RATES, .formats = WM8994_FORMATS, }, + .ops = &wm8994_aif3_dai_ops, } }; EXPORT_SYMBOL_GPL(wm8994_dai); @@ -3423,7 +3503,7 @@ static int wm8994_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; - struct wm8994_priv *wm8994 = codec->private_data; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); int i, ret; for (i = 0; i < ARRAY_SIZE(wm8994->fll); i++) { @@ -3444,7 +3524,7 @@ static int wm8994_resume(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = socdev->card->codec; - struct wm8994_priv *wm8994 = codec->private_data; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); u16 *reg_cache = codec->reg_cache; int i, ret; @@ -3469,6 +3549,9 @@ static int wm8994_resume(struct platform_device *pdev) wm8994_set_bias_level(codec, SND_SOC_BIAS_STANDBY); for (i = 0; i < ARRAY_SIZE(wm8994->fll); i++) { + if (!wm8994->fll_suspend[i].out) + continue; + ret = wm8994_set_fll(&codec->dai[0], i + 1, wm8994->fll_suspend[i].src, wm8994->fll_suspend[i].in, @@ -3639,7 +3722,7 @@ static int wm8994_probe(struct platform_device *pdev) return ret; } - wm8994_handle_pdata(codec->private_data); + wm8994_handle_pdata(snd_soc_codec_get_drvdata(codec)); wm_hubs_add_analogue_controls(codec); snd_soc_add_controls(codec, wm8994_snd_controls, @@ -3670,6 +3753,96 @@ struct snd_soc_codec_device soc_codec_dev_wm8994 = { }; EXPORT_SYMBOL_GPL(soc_codec_dev_wm8994); +/** + * wm8994_mic_detect - Enable microphone detection via the WM8994 IRQ + * + * @codec: WM8994 codec + * @jack: jack to report detection events on + * @micbias: microphone bias to detect on + * @det: value to report for presence detection + * @shrt: value to report for short detection + * + * Enable microphone detection via IRQ on the WM8994. If GPIOs are + * being used to bring out signals to the processor then only platform + * data configuration is needed for WM8903 and processor GPIOs should + * be configured using snd_soc_jack_add_gpios() instead. + * + * Configuration of detection levels is available via the micbias1_lvl + * and micbias2_lvl platform data members. + */ +int wm8994_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack, + int micbias, int det, int shrt) +{ + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + struct wm8994_micdet *micdet; + int reg; + + switch (micbias) { + case 1: + micdet = &wm8994->micdet[0]; + break; + case 2: + micdet = &wm8994->micdet[1]; + break; + default: + return -EINVAL; + } + + dev_dbg(codec->dev, "Configuring microphone detection on %d: %x %x\n", + micbias, det, shrt); + + /* Store the configuration */ + micdet->jack = jack; + micdet->det = det; + micdet->shrt = shrt; + + /* If either of the jacks is set up then enable detection */ + if (wm8994->micdet[0].jack || wm8994->micdet[1].jack) + reg = WM8994_MICD_ENA; + else + reg = 0; + + snd_soc_update_bits(codec, WM8994_MICBIAS, WM8994_MICD_ENA, reg); + + return 0; +} +EXPORT_SYMBOL_GPL(wm8994_mic_detect); + +static irqreturn_t wm8994_mic_irq(int irq, void *data) +{ + struct wm8994_priv *priv = data; + struct snd_soc_codec *codec = &priv->codec; + int reg; + int report; + + reg = snd_soc_read(codec, WM8994_INTERRUPT_RAW_STATUS_2); + if (reg < 0) { + dev_err(codec->dev, "Failed to read microphone status: %d\n", + reg); + return IRQ_HANDLED; + } + + dev_dbg(codec->dev, "Microphone status: %x\n", reg); + + report = 0; + if (reg & WM8994_MIC1_DET_STS) + report |= priv->micdet[0].det; + if (reg & WM8994_MIC1_SHRT_STS) + report |= priv->micdet[0].shrt; + snd_soc_jack_report(priv->micdet[0].jack, report, + priv->micdet[0].det | priv->micdet[0].shrt); + + report = 0; + if (reg & WM8994_MIC2_DET_STS) + report |= priv->micdet[1].det; + if (reg & WM8994_MIC2_SHRT_STS) + report |= priv->micdet[1].shrt; + snd_soc_jack_report(priv->micdet[1].jack, report, + priv->micdet[1].det | priv->micdet[1].shrt); + + return IRQ_HANDLED; +} + static int wm8994_codec_probe(struct platform_device *pdev) { int ret; @@ -3695,7 +3868,7 @@ static int wm8994_codec_probe(struct platform_device *pdev) INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); - codec->private_data = wm8994; + snd_soc_codec_set_drvdata(codec, wm8994); codec->control_data = dev_get_drvdata(pdev->dev.parent); codec->name = "WM8994"; codec->owner = THIS_MODULE; @@ -3743,6 +3916,30 @@ static int wm8994_codec_probe(struct platform_device *pdev) break; } + ret = wm8994_request_irq(codec->control_data, WM8994_IRQ_MIC1_DET, + wm8994_mic_irq, "Mic 1 detect", wm8994); + if (ret != 0) + dev_warn(&pdev->dev, + "Failed to request Mic1 detect IRQ: %d\n", ret); + + ret = wm8994_request_irq(codec->control_data, WM8994_IRQ_MIC1_SHRT, + wm8994_mic_irq, "Mic 1 short", wm8994); + if (ret != 0) + dev_warn(&pdev->dev, + "Failed to request Mic1 short IRQ: %d\n", ret); + + ret = wm8994_request_irq(codec->control_data, WM8994_IRQ_MIC2_DET, + wm8994_mic_irq, "Mic 2 detect", wm8994); + if (ret != 0) + dev_warn(&pdev->dev, + "Failed to request Mic2 detect IRQ: %d\n", ret); + + ret = wm8994_request_irq(codec->control_data, WM8994_IRQ_MIC2_SHRT, + wm8994_mic_irq, "Mic 2 short", wm8994); + if (ret != 0) + dev_warn(&pdev->dev, + "Failed to request Mic2 short IRQ: %d\n", ret); + /* Remember if AIFnLRCLK is configured as a GPIO. This should be * configured on init - if a system wants to do this dynamically * at runtime we can deal with that then. @@ -3750,7 +3947,7 @@ static int wm8994_codec_probe(struct platform_device *pdev) ret = wm8994_reg_read(codec->control_data, WM8994_GPIO_1); if (ret < 0) { dev_err(codec->dev, "Failed to read GPIO1 state: %d\n", ret); - goto err; + goto err_irq; } if ((ret & WM8994_GPN_FN_MASK) != WM8994_GP_FN_PIN_SPECIFIC) { wm8994->lrclk_shared[0] = 1; @@ -3762,7 +3959,7 @@ static int wm8994_codec_probe(struct platform_device *pdev) ret = wm8994_reg_read(codec->control_data, WM8994_GPIO_6); if (ret < 0) { dev_err(codec->dev, "Failed to read GPIO6 state: %d\n", ret); - goto err; + goto err_irq; } if ((ret & WM8994_GPN_FN_MASK) != WM8994_GP_FN_PIN_SPECIFIC) { wm8994->lrclk_shared[1] = 1; @@ -3812,7 +4009,7 @@ static int wm8994_codec_probe(struct platform_device *pdev) ret = snd_soc_register_codec(codec); if (ret != 0) { dev_err(codec->dev, "Failed to register codec: %d\n", ret); - goto err; + goto err_irq; } ret = snd_soc_register_dais(wm8994_dai, ARRAY_SIZE(wm8994_dai)); @@ -3827,6 +4024,11 @@ static int wm8994_codec_probe(struct platform_device *pdev) err_codec: snd_soc_unregister_codec(codec); +err_irq: + wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC2_SHRT, wm8994); + wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC2_DET, wm8994); + wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC1_SHRT, wm8994); + wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC1_DET, wm8994); err: kfree(wm8994); return ret; @@ -3840,6 +4042,10 @@ static int __devexit wm8994_codec_remove(struct platform_device *pdev) wm8994_set_bias_level(codec, SND_SOC_BIAS_OFF); snd_soc_unregister_dais(wm8994_dai, ARRAY_SIZE(wm8994_dai)); snd_soc_unregister_codec(&wm8994->codec); + wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC2_SHRT, wm8994); + wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC2_DET, wm8994); + wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC1_SHRT, wm8994); + wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC1_DET, wm8994); kfree(wm8994); wm8994_codec = NULL; diff --git a/sound/soc/codecs/wm8994.h b/sound/soc/codecs/wm8994.h index 0a5e142..7072dc5 100644 --- a/sound/soc/codecs/wm8994.h +++ b/sound/soc/codecs/wm8994.h @@ -23,4 +23,12 @@ extern struct snd_soc_dai wm8994_dai[]; #define WM8994_FLL1 1 #define WM8994_FLL2 2 +#define WM8994_FLL_SRC_MCLK1 1 +#define WM8994_FLL_SRC_MCLK2 2 +#define WM8994_FLL_SRC_LRCLK 3 +#define WM8994_FLL_SRC_BCLK 4 + +int wm8994_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack, + int micbias, int det, int shrt); + #endif diff --git a/sound/soc/codecs/wm9081.c b/sound/soc/codecs/wm9081.c index 3a184fc..13186fb 100644 --- a/sound/soc/codecs/wm9081.c +++ b/sound/soc/codecs/wm9081.c @@ -521,7 +521,7 @@ static int fll_factors(struct _fll_div *fll_div, unsigned int Fref, static int wm9081_set_fll(struct snd_soc_codec *codec, int fll_id, unsigned int Fref, unsigned int Fout) { - struct wm9081_priv *wm9081 = codec->private_data; + struct wm9081_priv *wm9081 = snd_soc_codec_get_drvdata(codec); u16 reg1, reg4, reg5; struct _fll_div fll_div; int ret; @@ -607,7 +607,7 @@ static int wm9081_set_fll(struct snd_soc_codec *codec, int fll_id, static int configure_clock(struct snd_soc_codec *codec) { - struct wm9081_priv *wm9081 = codec->private_data; + struct wm9081_priv *wm9081 = snd_soc_codec_get_drvdata(codec); int new_sysclk, i, target; unsigned int reg; int ret = 0; @@ -702,7 +702,7 @@ static int clk_sys_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { struct snd_soc_codec *codec = w->codec; - struct wm9081_priv *wm9081 = codec->private_data; + struct wm9081_priv *wm9081 = snd_soc_codec_get_drvdata(codec); /* This should be done on init() for bypass paths */ switch (wm9081->sysclk_source) { @@ -873,7 +873,7 @@ static int wm9081_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) { struct snd_soc_codec *codec = dai->codec; - struct wm9081_priv *wm9081 = codec->private_data; + struct wm9081_priv *wm9081 = snd_soc_codec_get_drvdata(codec); unsigned int aif2 = snd_soc_read(codec, WM9081_AUDIO_INTERFACE_2); aif2 &= ~(WM9081_AIF_BCLK_INV | WM9081_AIF_LRCLK_INV | @@ -965,7 +965,7 @@ static int wm9081_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_codec *codec = dai->codec; - struct wm9081_priv *wm9081 = codec->private_data; + struct wm9081_priv *wm9081 = snd_soc_codec_get_drvdata(codec); int ret, i, best, best_val, cur_val; unsigned int clk_ctrl2, aif1, aif2, aif3, aif4; @@ -1139,7 +1139,7 @@ static int wm9081_set_sysclk(struct snd_soc_dai *codec_dai, int clk_id, unsigned int freq, int dir) { struct snd_soc_codec *codec = codec_dai->codec; - struct wm9081_priv *wm9081 = codec->private_data; + struct wm9081_priv *wm9081 = snd_soc_codec_get_drvdata(codec); switch (clk_id) { case WM9081_SYSCLK_MCLK: @@ -1159,7 +1159,7 @@ static int wm9081_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width) { struct snd_soc_codec *codec = dai->codec; - struct wm9081_priv *wm9081 = codec->private_data; + struct wm9081_priv *wm9081 = snd_soc_codec_get_drvdata(codec); unsigned int aif1 = snd_soc_read(codec, WM9081_AUDIO_INTERFACE_1); aif1 &= ~(WM9081_AIFDAC_TDM_SLOT_MASK | WM9081_AIFDAC_TDM_MODE_MASK); @@ -1242,7 +1242,7 @@ static int wm9081_probe(struct platform_device *pdev) socdev->card->codec = wm9081_codec; codec = wm9081_codec; - wm9081 = codec->private_data; + wm9081 = snd_soc_codec_get_drvdata(codec); /* register pcms */ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); @@ -1339,7 +1339,7 @@ static int wm9081_register(struct wm9081_priv *wm9081, INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); - codec->private_data = wm9081; + snd_soc_codec_set_drvdata(codec, wm9081); codec->name = "WM9081"; codec->owner = THIS_MODULE; codec->dai = &wm9081_dai; diff --git a/sound/soc/codecs/wm9090.c b/sound/soc/codecs/wm9090.c new file mode 100644 index 0000000..1592250 --- /dev/null +++ b/sound/soc/codecs/wm9090.c @@ -0,0 +1,773 @@ +/* + * ALSA SoC WM9090 driver + * + * Copyright 2009, 2010 Wolfson Microelectronics + * + * Author: Mark Brown <broonie@opensource.wolfsonmicro.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <sound/initval.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/tlv.h> +#include <sound/wm9090.h> + +#include "wm9090.h" + +static struct snd_soc_codec *wm9090_codec; + +static const u16 wm9090_reg_defaults[] = { + 0x9093, /* R0 - Software Reset */ + 0x0006, /* R1 - Power Management (1) */ + 0x6000, /* R2 - Power Management (2) */ + 0x0000, /* R3 - Power Management (3) */ + 0x0000, /* R4 */ + 0x0000, /* R5 */ + 0x01C0, /* R6 - Clocking 1 */ + 0x0000, /* R7 */ + 0x0000, /* R8 */ + 0x0000, /* R9 */ + 0x0000, /* R10 */ + 0x0000, /* R11 */ + 0x0000, /* R12 */ + 0x0000, /* R13 */ + 0x0000, /* R14 */ + 0x0000, /* R15 */ + 0x0000, /* R16 */ + 0x0000, /* R17 */ + 0x0000, /* R18 */ + 0x0000, /* R19 */ + 0x0000, /* R20 */ + 0x0000, /* R21 */ + 0x0003, /* R22 - IN1 Line Control */ + 0x0003, /* R23 - IN2 Line Control */ + 0x0083, /* R24 - IN1 Line Input A Volume */ + 0x0083, /* R25 - IN1 Line Input B Volume */ + 0x0083, /* R26 - IN2 Line Input A Volume */ + 0x0083, /* R27 - IN2 Line Input B Volume */ + 0x002D, /* R28 - Left Output Volume */ + 0x002D, /* R29 - Right Output Volume */ + 0x0000, /* R30 */ + 0x0000, /* R31 */ + 0x0000, /* R32 */ + 0x0000, /* R33 */ + 0x0100, /* R34 - SPKMIXL Attenuation */ + 0x0000, /* R35 */ + 0x0010, /* R36 - SPKOUT Mixers */ + 0x0140, /* R37 - ClassD3 */ + 0x0039, /* R38 - Speaker Volume Left */ + 0x0000, /* R39 */ + 0x0000, /* R40 */ + 0x0000, /* R41 */ + 0x0000, /* R42 */ + 0x0000, /* R43 */ + 0x0000, /* R44 */ + 0x0000, /* R45 - Output Mixer1 */ + 0x0000, /* R46 - Output Mixer2 */ + 0x0100, /* R47 - Output Mixer3 */ + 0x0100, /* R48 - Output Mixer4 */ + 0x0000, /* R49 */ + 0x0000, /* R50 */ + 0x0000, /* R51 */ + 0x0000, /* R52 */ + 0x0000, /* R53 */ + 0x0000, /* R54 - Speaker Mixer */ + 0x0000, /* R55 */ + 0x0000, /* R56 */ + 0x000D, /* R57 - AntiPOP2 */ + 0x0000, /* R58 */ + 0x0000, /* R59 */ + 0x0000, /* R60 */ + 0x0000, /* R61 */ + 0x0000, /* R62 */ + 0x0000, /* R63 */ + 0x0000, /* R64 */ + 0x0000, /* R65 */ + 0x0000, /* R66 */ + 0x0000, /* R67 */ + 0x0000, /* R68 */ + 0x0000, /* R69 */ + 0x0000, /* R70 - Write Sequencer 0 */ + 0x0000, /* R71 - Write Sequencer 1 */ + 0x0000, /* R72 - Write Sequencer 2 */ + 0x0000, /* R73 - Write Sequencer 3 */ + 0x0000, /* R74 - Write Sequencer 4 */ + 0x0000, /* R75 - Write Sequencer 5 */ + 0x1F25, /* R76 - Charge Pump 1 */ + 0x0000, /* R77 */ + 0x0000, /* R78 */ + 0x0000, /* R79 */ + 0x0000, /* R80 */ + 0x0000, /* R81 */ + 0x0000, /* R82 */ + 0x0000, /* R83 */ + 0x0000, /* R84 - DC Servo 0 */ + 0x054A, /* R85 - DC Servo 1 */ + 0x0000, /* R86 */ + 0x0000, /* R87 - DC Servo 3 */ + 0x0000, /* R88 - DC Servo Readback 0 */ + 0x0000, /* R89 - DC Servo Readback 1 */ + 0x0000, /* R90 - DC Servo Readback 2 */ + 0x0000, /* R91 */ + 0x0000, /* R92 */ + 0x0000, /* R93 */ + 0x0000, /* R94 */ + 0x0000, /* R95 */ + 0x0100, /* R96 - Analogue HP 0 */ + 0x0000, /* R97 */ + 0x8640, /* R98 - AGC Control 0 */ + 0xC000, /* R99 - AGC Control 1 */ + 0x0200, /* R100 - AGC Control 2 */ +}; + +/* This struct is used to save the context */ +struct wm9090_priv { + /* We're not really registering as a CODEC since ASoC core + * does not yet support multiple CODECs but having the CODEC + * structure means we can reuse some of the ASoC core + * features. + */ + struct snd_soc_codec codec; + struct mutex mutex; + u16 reg_cache[WM9090_MAX_REGISTER + 1]; + struct wm9090_platform_data pdata; +}; + +static int wm9090_volatile(unsigned int reg) +{ + switch (reg) { + case WM9090_SOFTWARE_RESET: + case WM9090_DC_SERVO_0: + case WM9090_DC_SERVO_READBACK_0: + case WM9090_DC_SERVO_READBACK_1: + case WM9090_DC_SERVO_READBACK_2: + return 1; + + default: + return 0; + } +} + +static void wait_for_dc_servo(struct snd_soc_codec *codec) +{ + unsigned int reg; + int count = 0; + + dev_dbg(codec->dev, "Waiting for DC servo...\n"); + do { + count++; + msleep(1); + reg = snd_soc_read(codec, WM9090_DC_SERVO_READBACK_0); + dev_dbg(codec->dev, "DC servo status: %x\n", reg); + } while ((reg & WM9090_DCS_CAL_COMPLETE_MASK) + != WM9090_DCS_CAL_COMPLETE_MASK && count < 1000); + + if ((reg & WM9090_DCS_CAL_COMPLETE_MASK) + != WM9090_DCS_CAL_COMPLETE_MASK) + dev_err(codec->dev, "Timed out waiting for DC Servo\n"); +} + +static const unsigned int in_tlv[] = { + TLV_DB_RANGE_HEAD(6), + 0, 0, TLV_DB_SCALE_ITEM(-600, 0, 0), + 1, 3, TLV_DB_SCALE_ITEM(-350, 350, 0), + 4, 6, TLV_DB_SCALE_ITEM(600, 600, 0), +}; +static const unsigned int mix_tlv[] = { + TLV_DB_RANGE_HEAD(4), + 0, 2, TLV_DB_SCALE_ITEM(-1200, 300, 0), + 3, 3, TLV_DB_SCALE_ITEM(0, 0, 0), +}; +static const DECLARE_TLV_DB_SCALE(out_tlv, -5700, 100, 0); +static const unsigned int spkboost_tlv[] = { + TLV_DB_RANGE_HEAD(7), + 0, 6, TLV_DB_SCALE_ITEM(0, 150, 0), + 7, 7, TLV_DB_SCALE_ITEM(1200, 0, 0), +}; + +static const struct snd_kcontrol_new wm9090_controls[] = { +SOC_SINGLE_TLV("IN1A Volume", WM9090_IN1_LINE_INPUT_A_VOLUME, 0, 6, 0, + in_tlv), +SOC_SINGLE("IN1A Switch", WM9090_IN1_LINE_INPUT_A_VOLUME, 7, 1, 1), +SOC_SINGLE("IN1A ZC Switch", WM9090_IN1_LINE_INPUT_A_VOLUME, 6, 1, 0), + +SOC_SINGLE_TLV("IN2A Volume", WM9090_IN2_LINE_INPUT_A_VOLUME, 0, 6, 0, + in_tlv), +SOC_SINGLE("IN2A Switch", WM9090_IN2_LINE_INPUT_A_VOLUME, 7, 1, 1), +SOC_SINGLE("IN2A ZC Switch", WM9090_IN2_LINE_INPUT_A_VOLUME, 6, 1, 0), + +SOC_SINGLE("MIXOUTL Switch", WM9090_OUTPUT_MIXER3, 8, 1, 1), +SOC_SINGLE_TLV("MIXOUTL IN1A Volume", WM9090_OUTPUT_MIXER3, 6, 3, 1, + mix_tlv), +SOC_SINGLE_TLV("MIXOUTL IN2A Volume", WM9090_OUTPUT_MIXER3, 2, 3, 1, + mix_tlv), + +SOC_SINGLE("MIXOUTR Switch", WM9090_OUTPUT_MIXER4, 8, 1, 1), +SOC_SINGLE_TLV("MIXOUTR IN1A Volume", WM9090_OUTPUT_MIXER4, 6, 3, 1, + mix_tlv), +SOC_SINGLE_TLV("MIXOUTR IN2A Volume", WM9090_OUTPUT_MIXER4, 2, 3, 1, + mix_tlv), + +SOC_SINGLE("SPKMIX Switch", WM9090_SPKMIXL_ATTENUATION, 8, 1, 1), +SOC_SINGLE_TLV("SPKMIX IN1A Volume", WM9090_SPKMIXL_ATTENUATION, 6, 3, 1, + mix_tlv), +SOC_SINGLE_TLV("SPKMIX IN2A Volume", WM9090_SPKMIXL_ATTENUATION, 2, 3, 1, + mix_tlv), + +SOC_DOUBLE_R_TLV("Headphone Volume", WM9090_LEFT_OUTPUT_VOLUME, + WM9090_RIGHT_OUTPUT_VOLUME, 0, 63, 0, out_tlv), +SOC_DOUBLE_R("Headphone Switch", WM9090_LEFT_OUTPUT_VOLUME, + WM9090_RIGHT_OUTPUT_VOLUME, 6, 1, 1), +SOC_DOUBLE_R("Headphone ZC Switch", WM9090_LEFT_OUTPUT_VOLUME, + WM9090_RIGHT_OUTPUT_VOLUME, 7, 1, 0), + +SOC_SINGLE_TLV("Speaker Volume", WM9090_SPEAKER_VOLUME_LEFT, 0, 63, 0, + out_tlv), +SOC_SINGLE("Speaker Switch", WM9090_SPEAKER_VOLUME_LEFT, 6, 1, 1), +SOC_SINGLE("Speaker ZC Switch", WM9090_SPEAKER_VOLUME_LEFT, 7, 1, 0), +SOC_SINGLE_TLV("Speaker Boost Volume", WM9090_CLASSD3, 3, 7, 0, spkboost_tlv), +}; + +static const struct snd_kcontrol_new wm9090_in1_se_controls[] = { +SOC_SINGLE_TLV("IN1B Volume", WM9090_IN1_LINE_INPUT_B_VOLUME, 0, 6, 0, + in_tlv), +SOC_SINGLE("IN1B Switch", WM9090_IN1_LINE_INPUT_B_VOLUME, 7, 1, 1), +SOC_SINGLE("IN1B ZC Switch", WM9090_IN1_LINE_INPUT_B_VOLUME, 6, 1, 0), + +SOC_SINGLE_TLV("SPKMIX IN1B Volume", WM9090_SPKMIXL_ATTENUATION, 4, 3, 1, + mix_tlv), +SOC_SINGLE_TLV("MIXOUTL IN1B Volume", WM9090_OUTPUT_MIXER3, 4, 3, 1, + mix_tlv), +SOC_SINGLE_TLV("MIXOUTR IN1B Volume", WM9090_OUTPUT_MIXER4, 4, 3, 1, + mix_tlv), +}; + +static const struct snd_kcontrol_new wm9090_in2_se_controls[] = { +SOC_SINGLE_TLV("IN2B Volume", WM9090_IN2_LINE_INPUT_B_VOLUME, 0, 6, 0, + in_tlv), +SOC_SINGLE("IN2B Switch", WM9090_IN2_LINE_INPUT_B_VOLUME, 7, 1, 1), +SOC_SINGLE("IN2B ZC Switch", WM9090_IN2_LINE_INPUT_B_VOLUME, 6, 1, 0), + +SOC_SINGLE_TLV("SPKMIX IN2B Volume", WM9090_SPKMIXL_ATTENUATION, 0, 3, 1, + mix_tlv), +SOC_SINGLE_TLV("MIXOUTL IN2B Volume", WM9090_OUTPUT_MIXER3, 0, 3, 1, + mix_tlv), +SOC_SINGLE_TLV("MIXOUTR IN2B Volume", WM9090_OUTPUT_MIXER4, 0, 3, 1, + mix_tlv), +}; + +static int hp_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + unsigned int reg = snd_soc_read(codec, WM9090_ANALOGUE_HP_0); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + snd_soc_update_bits(codec, WM9090_CHARGE_PUMP_1, + WM9090_CP_ENA, WM9090_CP_ENA); + + msleep(5); + + snd_soc_update_bits(codec, WM9090_POWER_MANAGEMENT_1, + WM9090_HPOUT1L_ENA | WM9090_HPOUT1R_ENA, + WM9090_HPOUT1L_ENA | WM9090_HPOUT1R_ENA); + + reg |= WM9090_HPOUT1L_DLY | WM9090_HPOUT1R_DLY; + snd_soc_write(codec, WM9090_ANALOGUE_HP_0, reg); + + /* Start the DC servo. We don't currently use the + * ability to save the state since we don't have full + * control of the analogue paths and they can change + * DC offsets; see the WM8904 driver for an example of + * doing so. + */ + snd_soc_write(codec, WM9090_DC_SERVO_0, + WM9090_DCS_ENA_CHAN_0 | + WM9090_DCS_ENA_CHAN_1 | + WM9090_DCS_TRIG_STARTUP_1 | + WM9090_DCS_TRIG_STARTUP_0); + wait_for_dc_servo(codec); + + reg |= WM9090_HPOUT1R_OUTP | WM9090_HPOUT1R_RMV_SHORT | + WM9090_HPOUT1L_OUTP | WM9090_HPOUT1L_RMV_SHORT; + snd_soc_write(codec, WM9090_ANALOGUE_HP_0, reg); + break; + + case SND_SOC_DAPM_PRE_PMD: + reg &= ~(WM9090_HPOUT1L_RMV_SHORT | + WM9090_HPOUT1L_DLY | + WM9090_HPOUT1L_OUTP | + WM9090_HPOUT1R_RMV_SHORT | + WM9090_HPOUT1R_DLY | + WM9090_HPOUT1R_OUTP); + + snd_soc_write(codec, WM9090_ANALOGUE_HP_0, reg); + + snd_soc_write(codec, WM9090_DC_SERVO_0, 0); + + snd_soc_update_bits(codec, WM9090_POWER_MANAGEMENT_1, + WM9090_HPOUT1L_ENA | WM9090_HPOUT1R_ENA, + 0); + + snd_soc_update_bits(codec, WM9090_CHARGE_PUMP_1, + WM9090_CP_ENA, 0); + break; + } + + return 0; +} + +static const struct snd_kcontrol_new spkmix[] = { +SOC_DAPM_SINGLE("IN1A Switch", WM9090_SPEAKER_MIXER, 6, 1, 0), +SOC_DAPM_SINGLE("IN1B Switch", WM9090_SPEAKER_MIXER, 4, 1, 0), +SOC_DAPM_SINGLE("IN2A Switch", WM9090_SPEAKER_MIXER, 2, 1, 0), +SOC_DAPM_SINGLE("IN2B Switch", WM9090_SPEAKER_MIXER, 0, 1, 0), +}; + +static const struct snd_kcontrol_new spkout[] = { +SOC_DAPM_SINGLE("Mixer Switch", WM9090_SPKOUT_MIXERS, 4, 1, 0), +}; + +static const struct snd_kcontrol_new mixoutl[] = { +SOC_DAPM_SINGLE("IN1A Switch", WM9090_OUTPUT_MIXER1, 6, 1, 0), +SOC_DAPM_SINGLE("IN1B Switch", WM9090_OUTPUT_MIXER1, 4, 1, 0), +SOC_DAPM_SINGLE("IN2A Switch", WM9090_OUTPUT_MIXER1, 2, 1, 0), +SOC_DAPM_SINGLE("IN2B Switch", WM9090_OUTPUT_MIXER1, 0, 1, 0), +}; + +static const struct snd_kcontrol_new mixoutr[] = { +SOC_DAPM_SINGLE("IN1A Switch", WM9090_OUTPUT_MIXER2, 6, 1, 0), +SOC_DAPM_SINGLE("IN1B Switch", WM9090_OUTPUT_MIXER2, 4, 1, 0), +SOC_DAPM_SINGLE("IN2A Switch", WM9090_OUTPUT_MIXER2, 2, 1, 0), +SOC_DAPM_SINGLE("IN2B Switch", WM9090_OUTPUT_MIXER2, 0, 1, 0), +}; + +static const struct snd_soc_dapm_widget wm9090_dapm_widgets[] = { +SND_SOC_DAPM_INPUT("IN1+"), +SND_SOC_DAPM_INPUT("IN1-"), +SND_SOC_DAPM_INPUT("IN2+"), +SND_SOC_DAPM_INPUT("IN2-"), + +SND_SOC_DAPM_SUPPLY("OSC", WM9090_POWER_MANAGEMENT_1, 3, 0, NULL, 0), + +SND_SOC_DAPM_PGA("IN1A PGA", WM9090_POWER_MANAGEMENT_2, 7, 0, NULL, 0), +SND_SOC_DAPM_PGA("IN1B PGA", WM9090_POWER_MANAGEMENT_2, 6, 0, NULL, 0), +SND_SOC_DAPM_PGA("IN2A PGA", WM9090_POWER_MANAGEMENT_2, 5, 0, NULL, 0), +SND_SOC_DAPM_PGA("IN2B PGA", WM9090_POWER_MANAGEMENT_2, 4, 0, NULL, 0), + +SND_SOC_DAPM_MIXER("SPKMIX", WM9090_POWER_MANAGEMENT_3, 3, 0, + spkmix, ARRAY_SIZE(spkmix)), +SND_SOC_DAPM_MIXER("MIXOUTL", WM9090_POWER_MANAGEMENT_3, 5, 0, + mixoutl, ARRAY_SIZE(mixoutl)), +SND_SOC_DAPM_MIXER("MIXOUTR", WM9090_POWER_MANAGEMENT_3, 4, 0, + mixoutr, ARRAY_SIZE(mixoutr)), + +SND_SOC_DAPM_PGA_E("HP PGA", SND_SOC_NOPM, 0, 0, NULL, 0, + hp_ev, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + +SND_SOC_DAPM_PGA("SPKPGA", WM9090_POWER_MANAGEMENT_3, 8, 0, NULL, 0), +SND_SOC_DAPM_MIXER("SPKOUT", WM9090_POWER_MANAGEMENT_1, 12, 0, + spkout, ARRAY_SIZE(spkout)), + +SND_SOC_DAPM_OUTPUT("HPR"), +SND_SOC_DAPM_OUTPUT("HPL"), +SND_SOC_DAPM_OUTPUT("Speaker"), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + { "IN1A PGA", NULL, "IN1+" }, + { "IN2A PGA", NULL, "IN2+" }, + + { "SPKMIX", "IN1A Switch", "IN1A PGA" }, + { "SPKMIX", "IN2A Switch", "IN2A PGA" }, + + { "MIXOUTL", "IN1A Switch", "IN1A PGA" }, + { "MIXOUTL", "IN2A Switch", "IN2A PGA" }, + + { "MIXOUTR", "IN1A Switch", "IN1A PGA" }, + { "MIXOUTR", "IN2A Switch", "IN2A PGA" }, + + { "HP PGA", NULL, "OSC" }, + { "HP PGA", NULL, "MIXOUTL" }, + { "HP PGA", NULL, "MIXOUTR" }, + + { "HPL", NULL, "HP PGA" }, + { "HPR", NULL, "HP PGA" }, + + { "SPKPGA", NULL, "OSC" }, + { "SPKPGA", NULL, "SPKMIX" }, + + { "SPKOUT", "Mixer Switch", "SPKPGA" }, + + { "Speaker", NULL, "SPKOUT" }, +}; + +static const struct snd_soc_dapm_route audio_map_in1_se[] = { + { "IN1B PGA", NULL, "IN1-" }, + + { "SPKMIX", "IN1B Switch", "IN1B PGA" }, + { "MIXOUTL", "IN1B Switch", "IN1B PGA" }, + { "MIXOUTR", "IN1B Switch", "IN1B PGA" }, +}; + +static const struct snd_soc_dapm_route audio_map_in1_diff[] = { + { "IN1A PGA", NULL, "IN1-" }, +}; + +static const struct snd_soc_dapm_route audio_map_in2_se[] = { + { "IN2B PGA", NULL, "IN2-" }, + + { "SPKMIX", "IN2B Switch", "IN2B PGA" }, + { "MIXOUTL", "IN2B Switch", "IN2B PGA" }, + { "MIXOUTR", "IN2B Switch", "IN2B PGA" }, +}; + +static const struct snd_soc_dapm_route audio_map_in2_diff[] = { + { "IN2A PGA", NULL, "IN2-" }, +}; + +static int wm9090_add_controls(struct snd_soc_codec *codec) +{ + struct wm9090_priv *wm9090 = snd_soc_codec_get_drvdata(codec); + int i; + + snd_soc_dapm_new_controls(codec, wm9090_dapm_widgets, + ARRAY_SIZE(wm9090_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_add_controls(codec, wm9090_controls, + ARRAY_SIZE(wm9090_controls)); + + if (wm9090->pdata.lin1_diff) { + snd_soc_dapm_add_routes(codec, audio_map_in1_diff, + ARRAY_SIZE(audio_map_in1_diff)); + } else { + snd_soc_dapm_add_routes(codec, audio_map_in1_se, + ARRAY_SIZE(audio_map_in1_se)); + snd_soc_add_controls(codec, wm9090_in1_se_controls, + ARRAY_SIZE(wm9090_in1_se_controls)); + } + + if (wm9090->pdata.lin2_diff) { + snd_soc_dapm_add_routes(codec, audio_map_in2_diff, + ARRAY_SIZE(audio_map_in2_diff)); + } else { + snd_soc_dapm_add_routes(codec, audio_map_in2_se, + ARRAY_SIZE(audio_map_in2_se)); + snd_soc_add_controls(codec, wm9090_in2_se_controls, + ARRAY_SIZE(wm9090_in2_se_controls)); + } + + if (wm9090->pdata.agc_ena) { + for (i = 0; i < ARRAY_SIZE(wm9090->pdata.agc); i++) + snd_soc_write(codec, WM9090_AGC_CONTROL_0 + i, + wm9090->pdata.agc[i]); + snd_soc_update_bits(codec, WM9090_POWER_MANAGEMENT_3, + WM9090_AGC_ENA, WM9090_AGC_ENA); + } else { + snd_soc_update_bits(codec, WM9090_POWER_MANAGEMENT_3, + WM9090_AGC_ENA, 0); + } + + return 0; + +} + +/* + * The machine driver should call this from their set_bias_level; if there + * isn't one then this can just be set as the set_bias_level function. + */ +static int wm9090_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u16 *reg_cache = codec->reg_cache; + int i, ret; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + snd_soc_update_bits(codec, WM9090_ANTIPOP2, WM9090_VMID_ENA, + WM9090_VMID_ENA); + snd_soc_update_bits(codec, WM9090_POWER_MANAGEMENT_1, + WM9090_BIAS_ENA | + WM9090_VMID_RES_MASK, + WM9090_BIAS_ENA | + 1 << WM9090_VMID_RES_SHIFT); + msleep(1); /* Probably an overestimate */ + break; + + case SND_SOC_BIAS_STANDBY: + if (codec->bias_level == SND_SOC_BIAS_OFF) { + /* Restore the register cache */ + for (i = 1; i < codec->reg_cache_size; i++) { + if (reg_cache[i] == wm9090_reg_defaults[i]) + continue; + if (wm9090_volatile(i)) + continue; + + ret = snd_soc_write(codec, i, reg_cache[i]); + if (ret != 0) + dev_warn(codec->dev, + "Failed to restore register %d: %d\n", + i, ret); + } + } + + /* We keep VMID off during standby since the combination of + * ground referenced outputs and class D speaker mean that + * latency is not an issue. + */ + snd_soc_update_bits(codec, WM9090_POWER_MANAGEMENT_1, + WM9090_BIAS_ENA | WM9090_VMID_RES_MASK, 0); + snd_soc_update_bits(codec, WM9090_ANTIPOP2, + WM9090_VMID_ENA, 0); + break; + + case SND_SOC_BIAS_OFF: + break; + } + + codec->bias_level = level; + + return 0; +} + +static int wm9090_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + + if (wm9090_codec == NULL) { + dev_err(&pdev->dev, "Codec device not registered\n"); + return -ENODEV; + } + + socdev->card->codec = wm9090_codec; + codec = wm9090_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; + } + + wm9090_add_controls(codec); + + return 0; + +pcm_err: + return ret; +} + +#ifdef CONFIG_PM +static int wm9090_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; + + wm9090_set_bias_level(codec, SND_SOC_BIAS_OFF); + + return 0; +} + +static int wm9090_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + wm9090_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + return 0; +} +#else +#define wm9090_suspend NULL +#define wm9090_resume NULL +#endif + +static int wm9090_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_wm9090 = { + .probe = wm9090_probe, + .remove = wm9090_remove, + .suspend = wm9090_suspend, + .resume = wm9090_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm9090); + +static int wm9090_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm9090_priv *wm9090; + struct snd_soc_codec *codec; + int ret; + + wm9090 = kzalloc(sizeof(*wm9090), GFP_KERNEL); + if (wm9090 == NULL) { + dev_err(&i2c->dev, "Can not allocate memory\n"); + return -ENOMEM; + } + codec = &wm9090->codec; + + if (i2c->dev.platform_data) + memcpy(&wm9090->pdata, i2c->dev.platform_data, + sizeof(wm9090->pdata)); + + wm9090_codec = codec; + + i2c_set_clientdata(i2c, wm9090); + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + codec->control_data = i2c; + snd_soc_codec_set_drvdata(codec, wm9090); + codec->dev = &i2c->dev; + codec->name = "WM9090"; + codec->owner = THIS_MODULE; + codec->bias_level = SND_SOC_BIAS_OFF; + codec->set_bias_level = wm9090_set_bias_level, + codec->reg_cache_size = WM9090_MAX_REGISTER + 1; + codec->reg_cache = &wm9090->reg_cache; + codec->volatile_register = wm9090_volatile; + + ret = snd_soc_codec_set_cache_io(codec, 8, 16, SND_SOC_I2C); + if (ret != 0) { + dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); + goto err; + } + + memcpy(&wm9090->reg_cache, wm9090_reg_defaults, + sizeof(wm9090->reg_cache)); + + ret = snd_soc_read(codec, WM9090_SOFTWARE_RESET); + if (ret < 0) + goto err; + if (ret != wm9090_reg_defaults[WM9090_SOFTWARE_RESET]) { + dev_err(&i2c->dev, "Device is not a WM9090, ID=%x\n", ret); + ret = -EINVAL; + goto err; + } + + ret = snd_soc_write(codec, WM9090_SOFTWARE_RESET, 0); + if (ret < 0) + goto err; + + /* Configure some defaults; they will be written out when we + * bring the bias up. + */ + wm9090->reg_cache[WM9090_IN1_LINE_INPUT_A_VOLUME] |= WM9090_IN1_VU + | WM9090_IN1A_ZC; + wm9090->reg_cache[WM9090_IN1_LINE_INPUT_B_VOLUME] |= WM9090_IN1_VU + | WM9090_IN1B_ZC; + wm9090->reg_cache[WM9090_IN2_LINE_INPUT_A_VOLUME] |= WM9090_IN2_VU + | WM9090_IN2A_ZC; + wm9090->reg_cache[WM9090_IN2_LINE_INPUT_B_VOLUME] |= WM9090_IN2_VU + | WM9090_IN2B_ZC; + wm9090->reg_cache[WM9090_SPEAKER_VOLUME_LEFT] |= + WM9090_SPKOUT_VU | WM9090_SPKOUTL_ZC; + wm9090->reg_cache[WM9090_LEFT_OUTPUT_VOLUME] |= + WM9090_HPOUT1_VU | WM9090_HPOUT1L_ZC; + wm9090->reg_cache[WM9090_RIGHT_OUTPUT_VOLUME] |= + WM9090_HPOUT1_VU | WM9090_HPOUT1R_ZC; + + wm9090->reg_cache[WM9090_CLOCKING_1] |= WM9090_TOCLK_ENA; + + wm9090_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + ret = snd_soc_register_codec(codec); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to register CODEC: %d\n", ret); + goto err_bias; + } + + return 0; + +err_bias: + wm9090_set_bias_level(codec, SND_SOC_BIAS_OFF); +err: + kfree(wm9090); + i2c_set_clientdata(i2c, NULL); + wm9090_codec = NULL; + + return ret; +} + +static int wm9090_i2c_remove(struct i2c_client *i2c) +{ + struct wm9090_priv *wm9090 = i2c_get_clientdata(i2c); + struct snd_soc_codec *codec = &wm9090->codec; + + snd_soc_unregister_codec(codec); + wm9090_set_bias_level(codec, SND_SOC_BIAS_OFF); + kfree(wm9090); + wm9090_codec = NULL; + + return 0; +} + +static const struct i2c_device_id wm9090_id[] = { + { "wm9090", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm9090_id); + +static struct i2c_driver wm9090_i2c_driver = { + .driver = { + .name = "wm9090", + .owner = THIS_MODULE, + }, + .probe = wm9090_i2c_probe, + .remove = __devexit_p(wm9090_i2c_remove), + .id_table = wm9090_id, +}; + +static int __init wm9090_init(void) +{ + return i2c_add_driver(&wm9090_i2c_driver); +} +module_init(wm9090_init); + +static void __exit wm9090_exit(void) +{ + i2c_del_driver(&wm9090_i2c_driver); +} +module_exit(wm9090_exit); + +MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); +MODULE_DESCRIPTION("WM9090 ASoC driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm9090.h b/sound/soc/codecs/wm9090.h new file mode 100644 index 0000000..b08eab9 --- /dev/null +++ b/sound/soc/codecs/wm9090.h @@ -0,0 +1,715 @@ +/* + * ALSA SoC WM9090 driver + * + * Copyright 2009 Wolfson Microelectronics + * + * Author: Mark Brown <broonie@opensource.wolfsonmicro.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef __WM9090_H +#define __WM9090_H + +extern struct snd_soc_codec_device soc_codec_dev_wm9090; + +/* + * Register values. + */ +#define WM9090_SOFTWARE_RESET 0x00 +#define WM9090_POWER_MANAGEMENT_1 0x01 +#define WM9090_POWER_MANAGEMENT_2 0x02 +#define WM9090_POWER_MANAGEMENT_3 0x03 +#define WM9090_CLOCKING_1 0x06 +#define WM9090_IN1_LINE_CONTROL 0x16 +#define WM9090_IN2_LINE_CONTROL 0x17 +#define WM9090_IN1_LINE_INPUT_A_VOLUME 0x18 +#define WM9090_IN1_LINE_INPUT_B_VOLUME 0x19 +#define WM9090_IN2_LINE_INPUT_A_VOLUME 0x1A +#define WM9090_IN2_LINE_INPUT_B_VOLUME 0x1B +#define WM9090_LEFT_OUTPUT_VOLUME 0x1C +#define WM9090_RIGHT_OUTPUT_VOLUME 0x1D +#define WM9090_SPKMIXL_ATTENUATION 0x22 +#define WM9090_SPKOUT_MIXERS 0x24 +#define WM9090_CLASSD3 0x25 +#define WM9090_SPEAKER_VOLUME_LEFT 0x26 +#define WM9090_OUTPUT_MIXER1 0x2D +#define WM9090_OUTPUT_MIXER2 0x2E +#define WM9090_OUTPUT_MIXER3 0x2F +#define WM9090_OUTPUT_MIXER4 0x30 +#define WM9090_SPEAKER_MIXER 0x36 +#define WM9090_ANTIPOP2 0x39 +#define WM9090_WRITE_SEQUENCER_0 0x46 +#define WM9090_WRITE_SEQUENCER_1 0x47 +#define WM9090_WRITE_SEQUENCER_2 0x48 +#define WM9090_WRITE_SEQUENCER_3 0x49 +#define WM9090_WRITE_SEQUENCER_4 0x4A +#define WM9090_WRITE_SEQUENCER_5 0x4B +#define WM9090_CHARGE_PUMP_1 0x4C +#define WM9090_DC_SERVO_0 0x54 +#define WM9090_DC_SERVO_1 0x55 +#define WM9090_DC_SERVO_3 0x57 +#define WM9090_DC_SERVO_READBACK_0 0x58 +#define WM9090_DC_SERVO_READBACK_1 0x59 +#define WM9090_DC_SERVO_READBACK_2 0x5A +#define WM9090_ANALOGUE_HP_0 0x60 +#define WM9090_AGC_CONTROL_0 0x62 +#define WM9090_AGC_CONTROL_1 0x63 +#define WM9090_AGC_CONTROL_2 0x64 + +#define WM9090_REGISTER_COUNT 40 +#define WM9090_MAX_REGISTER 0x64 + +/* + * Field Definitions. + */ + +/* + * R0 (0x00) - Software Reset + */ +#define WM9090_SW_RESET_MASK 0xFFFF /* SW_RESET - [15:0] */ +#define WM9090_SW_RESET_SHIFT 0 /* SW_RESET - [15:0] */ +#define WM9090_SW_RESET_WIDTH 16 /* SW_RESET - [15:0] */ + +/* + * R1 (0x01) - Power Management (1) + */ +#define WM9090_SPKOUTL_ENA 0x1000 /* SPKOUTL_ENA */ +#define WM9090_SPKOUTL_ENA_MASK 0x1000 /* SPKOUTL_ENA */ +#define WM9090_SPKOUTL_ENA_SHIFT 12 /* SPKOUTL_ENA */ +#define WM9090_SPKOUTL_ENA_WIDTH 1 /* SPKOUTL_ENA */ +#define WM9090_HPOUT1L_ENA 0x0200 /* HPOUT1L_ENA */ +#define WM9090_HPOUT1L_ENA_MASK 0x0200 /* HPOUT1L_ENA */ +#define WM9090_HPOUT1L_ENA_SHIFT 9 /* HPOUT1L_ENA */ +#define WM9090_HPOUT1L_ENA_WIDTH 1 /* HPOUT1L_ENA */ +#define WM9090_HPOUT1R_ENA 0x0100 /* HPOUT1R_ENA */ +#define WM9090_HPOUT1R_ENA_MASK 0x0100 /* HPOUT1R_ENA */ +#define WM9090_HPOUT1R_ENA_SHIFT 8 /* HPOUT1R_ENA */ +#define WM9090_HPOUT1R_ENA_WIDTH 1 /* HPOUT1R_ENA */ +#define WM9090_OSC_ENA 0x0008 /* OSC_ENA */ +#define WM9090_OSC_ENA_MASK 0x0008 /* OSC_ENA */ +#define WM9090_OSC_ENA_SHIFT 3 /* OSC_ENA */ +#define WM9090_OSC_ENA_WIDTH 1 /* OSC_ENA */ +#define WM9090_VMID_RES_MASK 0x0006 /* VMID_RES - [2:1] */ +#define WM9090_VMID_RES_SHIFT 1 /* VMID_RES - [2:1] */ +#define WM9090_VMID_RES_WIDTH 2 /* VMID_RES - [2:1] */ +#define WM9090_BIAS_ENA 0x0001 /* BIAS_ENA */ +#define WM9090_BIAS_ENA_MASK 0x0001 /* BIAS_ENA */ +#define WM9090_BIAS_ENA_SHIFT 0 /* BIAS_ENA */ +#define WM9090_BIAS_ENA_WIDTH 1 /* BIAS_ENA */ + +/* + * R2 (0x02) - Power Management (2) + */ +#define WM9090_TSHUT 0x8000 /* TSHUT */ +#define WM9090_TSHUT_MASK 0x8000 /* TSHUT */ +#define WM9090_TSHUT_SHIFT 15 /* TSHUT */ +#define WM9090_TSHUT_WIDTH 1 /* TSHUT */ +#define WM9090_TSHUT_ENA 0x4000 /* TSHUT_ENA */ +#define WM9090_TSHUT_ENA_MASK 0x4000 /* TSHUT_ENA */ +#define WM9090_TSHUT_ENA_SHIFT 14 /* TSHUT_ENA */ +#define WM9090_TSHUT_ENA_WIDTH 1 /* TSHUT_ENA */ +#define WM9090_TSHUT_OPDIS 0x2000 /* TSHUT_OPDIS */ +#define WM9090_TSHUT_OPDIS_MASK 0x2000 /* TSHUT_OPDIS */ +#define WM9090_TSHUT_OPDIS_SHIFT 13 /* TSHUT_OPDIS */ +#define WM9090_TSHUT_OPDIS_WIDTH 1 /* TSHUT_OPDIS */ +#define WM9090_IN1A_ENA 0x0080 /* IN1A_ENA */ +#define WM9090_IN1A_ENA_MASK 0x0080 /* IN1A_ENA */ +#define WM9090_IN1A_ENA_SHIFT 7 /* IN1A_ENA */ +#define WM9090_IN1A_ENA_WIDTH 1 /* IN1A_ENA */ +#define WM9090_IN1B_ENA 0x0040 /* IN1B_ENA */ +#define WM9090_IN1B_ENA_MASK 0x0040 /* IN1B_ENA */ +#define WM9090_IN1B_ENA_SHIFT 6 /* IN1B_ENA */ +#define WM9090_IN1B_ENA_WIDTH 1 /* IN1B_ENA */ +#define WM9090_IN2A_ENA 0x0020 /* IN2A_ENA */ +#define WM9090_IN2A_ENA_MASK 0x0020 /* IN2A_ENA */ +#define WM9090_IN2A_ENA_SHIFT 5 /* IN2A_ENA */ +#define WM9090_IN2A_ENA_WIDTH 1 /* IN2A_ENA */ +#define WM9090_IN2B_ENA 0x0010 /* IN2B_ENA */ +#define WM9090_IN2B_ENA_MASK 0x0010 /* IN2B_ENA */ +#define WM9090_IN2B_ENA_SHIFT 4 /* IN2B_ENA */ +#define WM9090_IN2B_ENA_WIDTH 1 /* IN2B_ENA */ + +/* + * R3 (0x03) - Power Management (3) + */ +#define WM9090_AGC_ENA 0x4000 /* AGC_ENA */ +#define WM9090_AGC_ENA_MASK 0x4000 /* AGC_ENA */ +#define WM9090_AGC_ENA_SHIFT 14 /* AGC_ENA */ +#define WM9090_AGC_ENA_WIDTH 1 /* AGC_ENA */ +#define WM9090_SPKLVOL_ENA 0x0100 /* SPKLVOL_ENA */ +#define WM9090_SPKLVOL_ENA_MASK 0x0100 /* SPKLVOL_ENA */ +#define WM9090_SPKLVOL_ENA_SHIFT 8 /* SPKLVOL_ENA */ +#define WM9090_SPKLVOL_ENA_WIDTH 1 /* SPKLVOL_ENA */ +#define WM9090_MIXOUTL_ENA 0x0020 /* MIXOUTL_ENA */ +#define WM9090_MIXOUTL_ENA_MASK 0x0020 /* MIXOUTL_ENA */ +#define WM9090_MIXOUTL_ENA_SHIFT 5 /* MIXOUTL_ENA */ +#define WM9090_MIXOUTL_ENA_WIDTH 1 /* MIXOUTL_ENA */ +#define WM9090_MIXOUTR_ENA 0x0010 /* MIXOUTR_ENA */ +#define WM9090_MIXOUTR_ENA_MASK 0x0010 /* MIXOUTR_ENA */ +#define WM9090_MIXOUTR_ENA_SHIFT 4 /* MIXOUTR_ENA */ +#define WM9090_MIXOUTR_ENA_WIDTH 1 /* MIXOUTR_ENA */ +#define WM9090_SPKMIX_ENA 0x0008 /* SPKMIX_ENA */ +#define WM9090_SPKMIX_ENA_MASK 0x0008 /* SPKMIX_ENA */ +#define WM9090_SPKMIX_ENA_SHIFT 3 /* SPKMIX_ENA */ +#define WM9090_SPKMIX_ENA_WIDTH 1 /* SPKMIX_ENA */ + +/* + * R6 (0x06) - Clocking 1 + */ +#define WM9090_TOCLK_RATE 0x8000 /* TOCLK_RATE */ +#define WM9090_TOCLK_RATE_MASK 0x8000 /* TOCLK_RATE */ +#define WM9090_TOCLK_RATE_SHIFT 15 /* TOCLK_RATE */ +#define WM9090_TOCLK_RATE_WIDTH 1 /* TOCLK_RATE */ +#define WM9090_TOCLK_ENA 0x4000 /* TOCLK_ENA */ +#define WM9090_TOCLK_ENA_MASK 0x4000 /* TOCLK_ENA */ +#define WM9090_TOCLK_ENA_SHIFT 14 /* TOCLK_ENA */ +#define WM9090_TOCLK_ENA_WIDTH 1 /* TOCLK_ENA */ + +/* + * R22 (0x16) - IN1 Line Control + */ +#define WM9090_IN1_DIFF 0x0002 /* IN1_DIFF */ +#define WM9090_IN1_DIFF_MASK 0x0002 /* IN1_DIFF */ +#define WM9090_IN1_DIFF_SHIFT 1 /* IN1_DIFF */ +#define WM9090_IN1_DIFF_WIDTH 1 /* IN1_DIFF */ +#define WM9090_IN1_CLAMP 0x0001 /* IN1_CLAMP */ +#define WM9090_IN1_CLAMP_MASK 0x0001 /* IN1_CLAMP */ +#define WM9090_IN1_CLAMP_SHIFT 0 /* IN1_CLAMP */ +#define WM9090_IN1_CLAMP_WIDTH 1 /* IN1_CLAMP */ + +/* + * R23 (0x17) - IN2 Line Control + */ +#define WM9090_IN2_DIFF 0x0002 /* IN2_DIFF */ +#define WM9090_IN2_DIFF_MASK 0x0002 /* IN2_DIFF */ +#define WM9090_IN2_DIFF_SHIFT 1 /* IN2_DIFF */ +#define WM9090_IN2_DIFF_WIDTH 1 /* IN2_DIFF */ +#define WM9090_IN2_CLAMP 0x0001 /* IN2_CLAMP */ +#define WM9090_IN2_CLAMP_MASK 0x0001 /* IN2_CLAMP */ +#define WM9090_IN2_CLAMP_SHIFT 0 /* IN2_CLAMP */ +#define WM9090_IN2_CLAMP_WIDTH 1 /* IN2_CLAMP */ + +/* + * R24 (0x18) - IN1 Line Input A Volume + */ +#define WM9090_IN1_VU 0x0100 /* IN1_VU */ +#define WM9090_IN1_VU_MASK 0x0100 /* IN1_VU */ +#define WM9090_IN1_VU_SHIFT 8 /* IN1_VU */ +#define WM9090_IN1_VU_WIDTH 1 /* IN1_VU */ +#define WM9090_IN1A_MUTE 0x0080 /* IN1A_MUTE */ +#define WM9090_IN1A_MUTE_MASK 0x0080 /* IN1A_MUTE */ +#define WM9090_IN1A_MUTE_SHIFT 7 /* IN1A_MUTE */ +#define WM9090_IN1A_MUTE_WIDTH 1 /* IN1A_MUTE */ +#define WM9090_IN1A_ZC 0x0040 /* IN1A_ZC */ +#define WM9090_IN1A_ZC_MASK 0x0040 /* IN1A_ZC */ +#define WM9090_IN1A_ZC_SHIFT 6 /* IN1A_ZC */ +#define WM9090_IN1A_ZC_WIDTH 1 /* IN1A_ZC */ +#define WM9090_IN1A_VOL_MASK 0x0007 /* IN1A_VOL - [2:0] */ +#define WM9090_IN1A_VOL_SHIFT 0 /* IN1A_VOL - [2:0] */ +#define WM9090_IN1A_VOL_WIDTH 3 /* IN1A_VOL - [2:0] */ + +/* + * R25 (0x19) - IN1 Line Input B Volume + */ +#define WM9090_IN1_VU 0x0100 /* IN1_VU */ +#define WM9090_IN1_VU_MASK 0x0100 /* IN1_VU */ +#define WM9090_IN1_VU_SHIFT 8 /* IN1_VU */ +#define WM9090_IN1_VU_WIDTH 1 /* IN1_VU */ +#define WM9090_IN1B_MUTE 0x0080 /* IN1B_MUTE */ +#define WM9090_IN1B_MUTE_MASK 0x0080 /* IN1B_MUTE */ +#define WM9090_IN1B_MUTE_SHIFT 7 /* IN1B_MUTE */ +#define WM9090_IN1B_MUTE_WIDTH 1 /* IN1B_MUTE */ +#define WM9090_IN1B_ZC 0x0040 /* IN1B_ZC */ +#define WM9090_IN1B_ZC_MASK 0x0040 /* IN1B_ZC */ +#define WM9090_IN1B_ZC_SHIFT 6 /* IN1B_ZC */ +#define WM9090_IN1B_ZC_WIDTH 1 /* IN1B_ZC */ +#define WM9090_IN1B_VOL_MASK 0x0007 /* IN1B_VOL - [2:0] */ +#define WM9090_IN1B_VOL_SHIFT 0 /* IN1B_VOL - [2:0] */ +#define WM9090_IN1B_VOL_WIDTH 3 /* IN1B_VOL - [2:0] */ + +/* + * R26 (0x1A) - IN2 Line Input A Volume + */ +#define WM9090_IN2_VU 0x0100 /* IN2_VU */ +#define WM9090_IN2_VU_MASK 0x0100 /* IN2_VU */ +#define WM9090_IN2_VU_SHIFT 8 /* IN2_VU */ +#define WM9090_IN2_VU_WIDTH 1 /* IN2_VU */ +#define WM9090_IN2A_MUTE 0x0080 /* IN2A_MUTE */ +#define WM9090_IN2A_MUTE_MASK 0x0080 /* IN2A_MUTE */ +#define WM9090_IN2A_MUTE_SHIFT 7 /* IN2A_MUTE */ +#define WM9090_IN2A_MUTE_WIDTH 1 /* IN2A_MUTE */ +#define WM9090_IN2A_ZC 0x0040 /* IN2A_ZC */ +#define WM9090_IN2A_ZC_MASK 0x0040 /* IN2A_ZC */ +#define WM9090_IN2A_ZC_SHIFT 6 /* IN2A_ZC */ +#define WM9090_IN2A_ZC_WIDTH 1 /* IN2A_ZC */ +#define WM9090_IN2A_VOL_MASK 0x0007 /* IN2A_VOL - [2:0] */ +#define WM9090_IN2A_VOL_SHIFT 0 /* IN2A_VOL - [2:0] */ +#define WM9090_IN2A_VOL_WIDTH 3 /* IN2A_VOL - [2:0] */ + +/* + * R27 (0x1B) - IN2 Line Input B Volume + */ +#define WM9090_IN2_VU 0x0100 /* IN2_VU */ +#define WM9090_IN2_VU_MASK 0x0100 /* IN2_VU */ +#define WM9090_IN2_VU_SHIFT 8 /* IN2_VU */ +#define WM9090_IN2_VU_WIDTH 1 /* IN2_VU */ +#define WM9090_IN2B_MUTE 0x0080 /* IN2B_MUTE */ +#define WM9090_IN2B_MUTE_MASK 0x0080 /* IN2B_MUTE */ +#define WM9090_IN2B_MUTE_SHIFT 7 /* IN2B_MUTE */ +#define WM9090_IN2B_MUTE_WIDTH 1 /* IN2B_MUTE */ +#define WM9090_IN2B_ZC 0x0040 /* IN2B_ZC */ +#define WM9090_IN2B_ZC_MASK 0x0040 /* IN2B_ZC */ +#define WM9090_IN2B_ZC_SHIFT 6 /* IN2B_ZC */ +#define WM9090_IN2B_ZC_WIDTH 1 /* IN2B_ZC */ +#define WM9090_IN2B_VOL_MASK 0x0007 /* IN2B_VOL - [2:0] */ +#define WM9090_IN2B_VOL_SHIFT 0 /* IN2B_VOL - [2:0] */ +#define WM9090_IN2B_VOL_WIDTH 3 /* IN2B_VOL - [2:0] */ + +/* + * R28 (0x1C) - Left Output Volume + */ +#define WM9090_HPOUT1_VU 0x0100 /* HPOUT1_VU */ +#define WM9090_HPOUT1_VU_MASK 0x0100 /* HPOUT1_VU */ +#define WM9090_HPOUT1_VU_SHIFT 8 /* HPOUT1_VU */ +#define WM9090_HPOUT1_VU_WIDTH 1 /* HPOUT1_VU */ +#define WM9090_HPOUT1L_ZC 0x0080 /* HPOUT1L_ZC */ +#define WM9090_HPOUT1L_ZC_MASK 0x0080 /* HPOUT1L_ZC */ +#define WM9090_HPOUT1L_ZC_SHIFT 7 /* HPOUT1L_ZC */ +#define WM9090_HPOUT1L_ZC_WIDTH 1 /* HPOUT1L_ZC */ +#define WM9090_HPOUT1L_MUTE 0x0040 /* HPOUT1L_MUTE */ +#define WM9090_HPOUT1L_MUTE_MASK 0x0040 /* HPOUT1L_MUTE */ +#define WM9090_HPOUT1L_MUTE_SHIFT 6 /* HPOUT1L_MUTE */ +#define WM9090_HPOUT1L_MUTE_WIDTH 1 /* HPOUT1L_MUTE */ +#define WM9090_HPOUT1L_VOL_MASK 0x003F /* HPOUT1L_VOL - [5:0] */ +#define WM9090_HPOUT1L_VOL_SHIFT 0 /* HPOUT1L_VOL - [5:0] */ +#define WM9090_HPOUT1L_VOL_WIDTH 6 /* HPOUT1L_VOL - [5:0] */ + +/* + * R29 (0x1D) - Right Output Volume + */ +#define WM9090_HPOUT1_VU 0x0100 /* HPOUT1_VU */ +#define WM9090_HPOUT1_VU_MASK 0x0100 /* HPOUT1_VU */ +#define WM9090_HPOUT1_VU_SHIFT 8 /* HPOUT1_VU */ +#define WM9090_HPOUT1_VU_WIDTH 1 /* HPOUT1_VU */ +#define WM9090_HPOUT1R_ZC 0x0080 /* HPOUT1R_ZC */ +#define WM9090_HPOUT1R_ZC_MASK 0x0080 /* HPOUT1R_ZC */ +#define WM9090_HPOUT1R_ZC_SHIFT 7 /* HPOUT1R_ZC */ +#define WM9090_HPOUT1R_ZC_WIDTH 1 /* HPOUT1R_ZC */ +#define WM9090_HPOUT1R_MUTE 0x0040 /* HPOUT1R_MUTE */ +#define WM9090_HPOUT1R_MUTE_MASK 0x0040 /* HPOUT1R_MUTE */ +#define WM9090_HPOUT1R_MUTE_SHIFT 6 /* HPOUT1R_MUTE */ +#define WM9090_HPOUT1R_MUTE_WIDTH 1 /* HPOUT1R_MUTE */ +#define WM9090_HPOUT1R_VOL_MASK 0x003F /* HPOUT1R_VOL - [5:0] */ +#define WM9090_HPOUT1R_VOL_SHIFT 0 /* HPOUT1R_VOL - [5:0] */ +#define WM9090_HPOUT1R_VOL_WIDTH 6 /* HPOUT1R_VOL - [5:0] */ + +/* + * R34 (0x22) - SPKMIXL Attenuation + */ +#define WM9090_SPKMIX_MUTE 0x0100 /* SPKMIX_MUTE */ +#define WM9090_SPKMIX_MUTE_MASK 0x0100 /* SPKMIX_MUTE */ +#define WM9090_SPKMIX_MUTE_SHIFT 8 /* SPKMIX_MUTE */ +#define WM9090_SPKMIX_MUTE_WIDTH 1 /* SPKMIX_MUTE */ +#define WM9090_IN1A_SPKMIX_VOL_MASK 0x00C0 /* IN1A_SPKMIX_VOL - [7:6] */ +#define WM9090_IN1A_SPKMIX_VOL_SHIFT 6 /* IN1A_SPKMIX_VOL - [7:6] */ +#define WM9090_IN1A_SPKMIX_VOL_WIDTH 2 /* IN1A_SPKMIX_VOL - [7:6] */ +#define WM9090_IN1B_SPKMIX_VOL_MASK 0x0030 /* IN1B_SPKMIX_VOL - [5:4] */ +#define WM9090_IN1B_SPKMIX_VOL_SHIFT 4 /* IN1B_SPKMIX_VOL - [5:4] */ +#define WM9090_IN1B_SPKMIX_VOL_WIDTH 2 /* IN1B_SPKMIX_VOL - [5:4] */ +#define WM9090_IN2A_SPKMIX_VOL_MASK 0x000C /* IN2A_SPKMIX_VOL - [3:2] */ +#define WM9090_IN2A_SPKMIX_VOL_SHIFT 2 /* IN2A_SPKMIX_VOL - [3:2] */ +#define WM9090_IN2A_SPKMIX_VOL_WIDTH 2 /* IN2A_SPKMIX_VOL - [3:2] */ +#define WM9090_IN2B_SPKMIX_VOL_MASK 0x0003 /* IN2B_SPKMIX_VOL - [1:0] */ +#define WM9090_IN2B_SPKMIX_VOL_SHIFT 0 /* IN2B_SPKMIX_VOL - [1:0] */ +#define WM9090_IN2B_SPKMIX_VOL_WIDTH 2 /* IN2B_SPKMIX_VOL - [1:0] */ + +/* + * R36 (0x24) - SPKOUT Mixers + */ +#define WM9090_SPKMIXL_TO_SPKOUTL 0x0010 /* SPKMIXL_TO_SPKOUTL */ +#define WM9090_SPKMIXL_TO_SPKOUTL_MASK 0x0010 /* SPKMIXL_TO_SPKOUTL */ +#define WM9090_SPKMIXL_TO_SPKOUTL_SHIFT 4 /* SPKMIXL_TO_SPKOUTL */ +#define WM9090_SPKMIXL_TO_SPKOUTL_WIDTH 1 /* SPKMIXL_TO_SPKOUTL */ + +/* + * R37 (0x25) - ClassD3 + */ +#define WM9090_SPKOUTL_BOOST_MASK 0x0038 /* SPKOUTL_BOOST - [5:3] */ +#define WM9090_SPKOUTL_BOOST_SHIFT 3 /* SPKOUTL_BOOST - [5:3] */ +#define WM9090_SPKOUTL_BOOST_WIDTH 3 /* SPKOUTL_BOOST - [5:3] */ + +/* + * R38 (0x26) - Speaker Volume Left + */ +#define WM9090_SPKOUT_VU 0x0100 /* SPKOUT_VU */ +#define WM9090_SPKOUT_VU_MASK 0x0100 /* SPKOUT_VU */ +#define WM9090_SPKOUT_VU_SHIFT 8 /* SPKOUT_VU */ +#define WM9090_SPKOUT_VU_WIDTH 1 /* SPKOUT_VU */ +#define WM9090_SPKOUTL_ZC 0x0080 /* SPKOUTL_ZC */ +#define WM9090_SPKOUTL_ZC_MASK 0x0080 /* SPKOUTL_ZC */ +#define WM9090_SPKOUTL_ZC_SHIFT 7 /* SPKOUTL_ZC */ +#define WM9090_SPKOUTL_ZC_WIDTH 1 /* SPKOUTL_ZC */ +#define WM9090_SPKOUTL_MUTE 0x0040 /* SPKOUTL_MUTE */ +#define WM9090_SPKOUTL_MUTE_MASK 0x0040 /* SPKOUTL_MUTE */ +#define WM9090_SPKOUTL_MUTE_SHIFT 6 /* SPKOUTL_MUTE */ +#define WM9090_SPKOUTL_MUTE_WIDTH 1 /* SPKOUTL_MUTE */ +#define WM9090_SPKOUTL_VOL_MASK 0x003F /* SPKOUTL_VOL - [5:0] */ +#define WM9090_SPKOUTL_VOL_SHIFT 0 /* SPKOUTL_VOL - [5:0] */ +#define WM9090_SPKOUTL_VOL_WIDTH 6 /* SPKOUTL_VOL - [5:0] */ + +/* + * R45 (0x2D) - Output Mixer1 + */ +#define WM9090_IN1A_TO_MIXOUTL 0x0040 /* IN1A_TO_MIXOUTL */ +#define WM9090_IN1A_TO_MIXOUTL_MASK 0x0040 /* IN1A_TO_MIXOUTL */ +#define WM9090_IN1A_TO_MIXOUTL_SHIFT 6 /* IN1A_TO_MIXOUTL */ +#define WM9090_IN1A_TO_MIXOUTL_WIDTH 1 /* IN1A_TO_MIXOUTL */ +#define WM9090_IN2A_TO_MIXOUTL 0x0004 /* IN2A_TO_MIXOUTL */ +#define WM9090_IN2A_TO_MIXOUTL_MASK 0x0004 /* IN2A_TO_MIXOUTL */ +#define WM9090_IN2A_TO_MIXOUTL_SHIFT 2 /* IN2A_TO_MIXOUTL */ +#define WM9090_IN2A_TO_MIXOUTL_WIDTH 1 /* IN2A_TO_MIXOUTL */ + +/* + * R46 (0x2E) - Output Mixer2 + */ +#define WM9090_IN1A_TO_MIXOUTR 0x0040 /* IN1A_TO_MIXOUTR */ +#define WM9090_IN1A_TO_MIXOUTR_MASK 0x0040 /* IN1A_TO_MIXOUTR */ +#define WM9090_IN1A_TO_MIXOUTR_SHIFT 6 /* IN1A_TO_MIXOUTR */ +#define WM9090_IN1A_TO_MIXOUTR_WIDTH 1 /* IN1A_TO_MIXOUTR */ +#define WM9090_IN1B_TO_MIXOUTR 0x0010 /* IN1B_TO_MIXOUTR */ +#define WM9090_IN1B_TO_MIXOUTR_MASK 0x0010 /* IN1B_TO_MIXOUTR */ +#define WM9090_IN1B_TO_MIXOUTR_SHIFT 4 /* IN1B_TO_MIXOUTR */ +#define WM9090_IN1B_TO_MIXOUTR_WIDTH 1 /* IN1B_TO_MIXOUTR */ +#define WM9090_IN2A_TO_MIXOUTR 0x0004 /* IN2A_TO_MIXOUTR */ +#define WM9090_IN2A_TO_MIXOUTR_MASK 0x0004 /* IN2A_TO_MIXOUTR */ +#define WM9090_IN2A_TO_MIXOUTR_SHIFT 2 /* IN2A_TO_MIXOUTR */ +#define WM9090_IN2A_TO_MIXOUTR_WIDTH 1 /* IN2A_TO_MIXOUTR */ +#define WM9090_IN2B_TO_MIXOUTR 0x0001 /* IN2B_TO_MIXOUTR */ +#define WM9090_IN2B_TO_MIXOUTR_MASK 0x0001 /* IN2B_TO_MIXOUTR */ +#define WM9090_IN2B_TO_MIXOUTR_SHIFT 0 /* IN2B_TO_MIXOUTR */ +#define WM9090_IN2B_TO_MIXOUTR_WIDTH 1 /* IN2B_TO_MIXOUTR */ + +/* + * R47 (0x2F) - Output Mixer3 + */ +#define WM9090_MIXOUTL_MUTE 0x0100 /* MIXOUTL_MUTE */ +#define WM9090_MIXOUTL_MUTE_MASK 0x0100 /* MIXOUTL_MUTE */ +#define WM9090_MIXOUTL_MUTE_SHIFT 8 /* MIXOUTL_MUTE */ +#define WM9090_MIXOUTL_MUTE_WIDTH 1 /* MIXOUTL_MUTE */ +#define WM9090_IN1A_MIXOUTL_VOL_MASK 0x00C0 /* IN1A_MIXOUTL_VOL - [7:6] */ +#define WM9090_IN1A_MIXOUTL_VOL_SHIFT 6 /* IN1A_MIXOUTL_VOL - [7:6] */ +#define WM9090_IN1A_MIXOUTL_VOL_WIDTH 2 /* IN1A_MIXOUTL_VOL - [7:6] */ +#define WM9090_IN2A_MIXOUTL_VOL_MASK 0x000C /* IN2A_MIXOUTL_VOL - [3:2] */ +#define WM9090_IN2A_MIXOUTL_VOL_SHIFT 2 /* IN2A_MIXOUTL_VOL - [3:2] */ +#define WM9090_IN2A_MIXOUTL_VOL_WIDTH 2 /* IN2A_MIXOUTL_VOL - [3:2] */ + +/* + * R48 (0x30) - Output Mixer4 + */ +#define WM9090_MIXOUTR_MUTE 0x0100 /* MIXOUTR_MUTE */ +#define WM9090_MIXOUTR_MUTE_MASK 0x0100 /* MIXOUTR_MUTE */ +#define WM9090_MIXOUTR_MUTE_SHIFT 8 /* MIXOUTR_MUTE */ +#define WM9090_MIXOUTR_MUTE_WIDTH 1 /* MIXOUTR_MUTE */ +#define WM9090_IN1A_MIXOUTR_VOL_MASK 0x00C0 /* IN1A_MIXOUTR_VOL - [7:6] */ +#define WM9090_IN1A_MIXOUTR_VOL_SHIFT 6 /* IN1A_MIXOUTR_VOL - [7:6] */ +#define WM9090_IN1A_MIXOUTR_VOL_WIDTH 2 /* IN1A_MIXOUTR_VOL - [7:6] */ +#define WM9090_IN1B_MIXOUTR_VOL_MASK 0x0030 /* IN1B_MIXOUTR_VOL - [5:4] */ +#define WM9090_IN1B_MIXOUTR_VOL_SHIFT 4 /* IN1B_MIXOUTR_VOL - [5:4] */ +#define WM9090_IN1B_MIXOUTR_VOL_WIDTH 2 /* IN1B_MIXOUTR_VOL - [5:4] */ +#define WM9090_IN2A_MIXOUTR_VOL_MASK 0x000C /* IN2A_MIXOUTR_VOL - [3:2] */ +#define WM9090_IN2A_MIXOUTR_VOL_SHIFT 2 /* IN2A_MIXOUTR_VOL - [3:2] */ +#define WM9090_IN2A_MIXOUTR_VOL_WIDTH 2 /* IN2A_MIXOUTR_VOL - [3:2] */ +#define WM9090_IN2B_MIXOUTR_VOL_MASK 0x0003 /* IN2B_MIXOUTR_VOL - [1:0] */ +#define WM9090_IN2B_MIXOUTR_VOL_SHIFT 0 /* IN2B_MIXOUTR_VOL - [1:0] */ +#define WM9090_IN2B_MIXOUTR_VOL_WIDTH 2 /* IN2B_MIXOUTR_VOL - [1:0] */ + +/* + * R54 (0x36) - Speaker Mixer + */ +#define WM9090_IN1A_TO_SPKMIX 0x0040 /* IN1A_TO_SPKMIX */ +#define WM9090_IN1A_TO_SPKMIX_MASK 0x0040 /* IN1A_TO_SPKMIX */ +#define WM9090_IN1A_TO_SPKMIX_SHIFT 6 /* IN1A_TO_SPKMIX */ +#define WM9090_IN1A_TO_SPKMIX_WIDTH 1 /* IN1A_TO_SPKMIX */ +#define WM9090_IN1B_TO_SPKMIX 0x0010 /* IN1B_TO_SPKMIX */ +#define WM9090_IN1B_TO_SPKMIX_MASK 0x0010 /* IN1B_TO_SPKMIX */ +#define WM9090_IN1B_TO_SPKMIX_SHIFT 4 /* IN1B_TO_SPKMIX */ +#define WM9090_IN1B_TO_SPKMIX_WIDTH 1 /* IN1B_TO_SPKMIX */ +#define WM9090_IN2A_TO_SPKMIX 0x0004 /* IN2A_TO_SPKMIX */ +#define WM9090_IN2A_TO_SPKMIX_MASK 0x0004 /* IN2A_TO_SPKMIX */ +#define WM9090_IN2A_TO_SPKMIX_SHIFT 2 /* IN2A_TO_SPKMIX */ +#define WM9090_IN2A_TO_SPKMIX_WIDTH 1 /* IN2A_TO_SPKMIX */ +#define WM9090_IN2B_TO_SPKMIX 0x0001 /* IN2B_TO_SPKMIX */ +#define WM9090_IN2B_TO_SPKMIX_MASK 0x0001 /* IN2B_TO_SPKMIX */ +#define WM9090_IN2B_TO_SPKMIX_SHIFT 0 /* IN2B_TO_SPKMIX */ +#define WM9090_IN2B_TO_SPKMIX_WIDTH 1 /* IN2B_TO_SPKMIX */ + +/* + * R57 (0x39) - AntiPOP2 + */ +#define WM9090_VMID_BUF_ENA 0x0008 /* VMID_BUF_ENA */ +#define WM9090_VMID_BUF_ENA_MASK 0x0008 /* VMID_BUF_ENA */ +#define WM9090_VMID_BUF_ENA_SHIFT 3 /* VMID_BUF_ENA */ +#define WM9090_VMID_BUF_ENA_WIDTH 1 /* VMID_BUF_ENA */ +#define WM9090_VMID_ENA 0x0001 /* VMID_ENA */ +#define WM9090_VMID_ENA_MASK 0x0001 /* VMID_ENA */ +#define WM9090_VMID_ENA_SHIFT 0 /* VMID_ENA */ +#define WM9090_VMID_ENA_WIDTH 1 /* VMID_ENA */ + +/* + * R70 (0x46) - Write Sequencer 0 + */ +#define WM9090_WSEQ_ENA 0x0100 /* WSEQ_ENA */ +#define WM9090_WSEQ_ENA_MASK 0x0100 /* WSEQ_ENA */ +#define WM9090_WSEQ_ENA_SHIFT 8 /* WSEQ_ENA */ +#define WM9090_WSEQ_ENA_WIDTH 1 /* WSEQ_ENA */ +#define WM9090_WSEQ_WRITE_INDEX_MASK 0x000F /* WSEQ_WRITE_INDEX - [3:0] */ +#define WM9090_WSEQ_WRITE_INDEX_SHIFT 0 /* WSEQ_WRITE_INDEX - [3:0] */ +#define WM9090_WSEQ_WRITE_INDEX_WIDTH 4 /* WSEQ_WRITE_INDEX - [3:0] */ + +/* + * R71 (0x47) - Write Sequencer 1 + */ +#define WM9090_WSEQ_DATA_WIDTH_MASK 0x7000 /* WSEQ_DATA_WIDTH - [14:12] */ +#define WM9090_WSEQ_DATA_WIDTH_SHIFT 12 /* WSEQ_DATA_WIDTH - [14:12] */ +#define WM9090_WSEQ_DATA_WIDTH_WIDTH 3 /* WSEQ_DATA_WIDTH - [14:12] */ +#define WM9090_WSEQ_DATA_START_MASK 0x0F00 /* WSEQ_DATA_START - [11:8] */ +#define WM9090_WSEQ_DATA_START_SHIFT 8 /* WSEQ_DATA_START - [11:8] */ +#define WM9090_WSEQ_DATA_START_WIDTH 4 /* WSEQ_DATA_START - [11:8] */ +#define WM9090_WSEQ_ADDR_MASK 0x00FF /* WSEQ_ADDR - [7:0] */ +#define WM9090_WSEQ_ADDR_SHIFT 0 /* WSEQ_ADDR - [7:0] */ +#define WM9090_WSEQ_ADDR_WIDTH 8 /* WSEQ_ADDR - [7:0] */ + +/* + * R72 (0x48) - Write Sequencer 2 + */ +#define WM9090_WSEQ_EOS 0x4000 /* WSEQ_EOS */ +#define WM9090_WSEQ_EOS_MASK 0x4000 /* WSEQ_EOS */ +#define WM9090_WSEQ_EOS_SHIFT 14 /* WSEQ_EOS */ +#define WM9090_WSEQ_EOS_WIDTH 1 /* WSEQ_EOS */ +#define WM9090_WSEQ_DELAY_MASK 0x0F00 /* WSEQ_DELAY - [11:8] */ +#define WM9090_WSEQ_DELAY_SHIFT 8 /* WSEQ_DELAY - [11:8] */ +#define WM9090_WSEQ_DELAY_WIDTH 4 /* WSEQ_DELAY - [11:8] */ +#define WM9090_WSEQ_DATA_MASK 0x00FF /* WSEQ_DATA - [7:0] */ +#define WM9090_WSEQ_DATA_SHIFT 0 /* WSEQ_DATA - [7:0] */ +#define WM9090_WSEQ_DATA_WIDTH 8 /* WSEQ_DATA - [7:0] */ + +/* + * R73 (0x49) - Write Sequencer 3 + */ +#define WM9090_WSEQ_ABORT 0x0200 /* WSEQ_ABORT */ +#define WM9090_WSEQ_ABORT_MASK 0x0200 /* WSEQ_ABORT */ +#define WM9090_WSEQ_ABORT_SHIFT 9 /* WSEQ_ABORT */ +#define WM9090_WSEQ_ABORT_WIDTH 1 /* WSEQ_ABORT */ +#define WM9090_WSEQ_START 0x0100 /* WSEQ_START */ +#define WM9090_WSEQ_START_MASK 0x0100 /* WSEQ_START */ +#define WM9090_WSEQ_START_SHIFT 8 /* WSEQ_START */ +#define WM9090_WSEQ_START_WIDTH 1 /* WSEQ_START */ +#define WM9090_WSEQ_START_INDEX_MASK 0x003F /* WSEQ_START_INDEX - [5:0] */ +#define WM9090_WSEQ_START_INDEX_SHIFT 0 /* WSEQ_START_INDEX - [5:0] */ +#define WM9090_WSEQ_START_INDEX_WIDTH 6 /* WSEQ_START_INDEX - [5:0] */ + +/* + * R74 (0x4A) - Write Sequencer 4 + */ +#define WM9090_WSEQ_BUSY 0x0001 /* WSEQ_BUSY */ +#define WM9090_WSEQ_BUSY_MASK 0x0001 /* WSEQ_BUSY */ +#define WM9090_WSEQ_BUSY_SHIFT 0 /* WSEQ_BUSY */ +#define WM9090_WSEQ_BUSY_WIDTH 1 /* WSEQ_BUSY */ + +/* + * R75 (0x4B) - Write Sequencer 5 + */ +#define WM9090_WSEQ_CURRENT_INDEX_MASK 0x003F /* WSEQ_CURRENT_INDEX - [5:0] */ +#define WM9090_WSEQ_CURRENT_INDEX_SHIFT 0 /* WSEQ_CURRENT_INDEX - [5:0] */ +#define WM9090_WSEQ_CURRENT_INDEX_WIDTH 6 /* WSEQ_CURRENT_INDEX - [5:0] */ + +/* + * R76 (0x4C) - Charge Pump 1 + */ +#define WM9090_CP_ENA 0x8000 /* CP_ENA */ +#define WM9090_CP_ENA_MASK 0x8000 /* CP_ENA */ +#define WM9090_CP_ENA_SHIFT 15 /* CP_ENA */ +#define WM9090_CP_ENA_WIDTH 1 /* CP_ENA */ + +/* + * R84 (0x54) - DC Servo 0 + */ +#define WM9090_DCS_TRIG_SINGLE_1 0x2000 /* DCS_TRIG_SINGLE_1 */ +#define WM9090_DCS_TRIG_SINGLE_1_MASK 0x2000 /* DCS_TRIG_SINGLE_1 */ +#define WM9090_DCS_TRIG_SINGLE_1_SHIFT 13 /* DCS_TRIG_SINGLE_1 */ +#define WM9090_DCS_TRIG_SINGLE_1_WIDTH 1 /* DCS_TRIG_SINGLE_1 */ +#define WM9090_DCS_TRIG_SINGLE_0 0x1000 /* DCS_TRIG_SINGLE_0 */ +#define WM9090_DCS_TRIG_SINGLE_0_MASK 0x1000 /* DCS_TRIG_SINGLE_0 */ +#define WM9090_DCS_TRIG_SINGLE_0_SHIFT 12 /* DCS_TRIG_SINGLE_0 */ +#define WM9090_DCS_TRIG_SINGLE_0_WIDTH 1 /* DCS_TRIG_SINGLE_0 */ +#define WM9090_DCS_TRIG_SERIES_1 0x0200 /* DCS_TRIG_SERIES_1 */ +#define WM9090_DCS_TRIG_SERIES_1_MASK 0x0200 /* DCS_TRIG_SERIES_1 */ +#define WM9090_DCS_TRIG_SERIES_1_SHIFT 9 /* DCS_TRIG_SERIES_1 */ +#define WM9090_DCS_TRIG_SERIES_1_WIDTH 1 /* DCS_TRIG_SERIES_1 */ +#define WM9090_DCS_TRIG_SERIES_0 0x0100 /* DCS_TRIG_SERIES_0 */ +#define WM9090_DCS_TRIG_SERIES_0_MASK 0x0100 /* DCS_TRIG_SERIES_0 */ +#define WM9090_DCS_TRIG_SERIES_0_SHIFT 8 /* DCS_TRIG_SERIES_0 */ +#define WM9090_DCS_TRIG_SERIES_0_WIDTH 1 /* DCS_TRIG_SERIES_0 */ +#define WM9090_DCS_TRIG_STARTUP_1 0x0020 /* DCS_TRIG_STARTUP_1 */ +#define WM9090_DCS_TRIG_STARTUP_1_MASK 0x0020 /* DCS_TRIG_STARTUP_1 */ +#define WM9090_DCS_TRIG_STARTUP_1_SHIFT 5 /* DCS_TRIG_STARTUP_1 */ +#define WM9090_DCS_TRIG_STARTUP_1_WIDTH 1 /* DCS_TRIG_STARTUP_1 */ +#define WM9090_DCS_TRIG_STARTUP_0 0x0010 /* DCS_TRIG_STARTUP_0 */ +#define WM9090_DCS_TRIG_STARTUP_0_MASK 0x0010 /* DCS_TRIG_STARTUP_0 */ +#define WM9090_DCS_TRIG_STARTUP_0_SHIFT 4 /* DCS_TRIG_STARTUP_0 */ +#define WM9090_DCS_TRIG_STARTUP_0_WIDTH 1 /* DCS_TRIG_STARTUP_0 */ +#define WM9090_DCS_TRIG_DAC_WR_1 0x0008 /* DCS_TRIG_DAC_WR_1 */ +#define WM9090_DCS_TRIG_DAC_WR_1_MASK 0x0008 /* DCS_TRIG_DAC_WR_1 */ +#define WM9090_DCS_TRIG_DAC_WR_1_SHIFT 3 /* DCS_TRIG_DAC_WR_1 */ +#define WM9090_DCS_TRIG_DAC_WR_1_WIDTH 1 /* DCS_TRIG_DAC_WR_1 */ +#define WM9090_DCS_TRIG_DAC_WR_0 0x0004 /* DCS_TRIG_DAC_WR_0 */ +#define WM9090_DCS_TRIG_DAC_WR_0_MASK 0x0004 /* DCS_TRIG_DAC_WR_0 */ +#define WM9090_DCS_TRIG_DAC_WR_0_SHIFT 2 /* DCS_TRIG_DAC_WR_0 */ +#define WM9090_DCS_TRIG_DAC_WR_0_WIDTH 1 /* DCS_TRIG_DAC_WR_0 */ +#define WM9090_DCS_ENA_CHAN_1 0x0002 /* DCS_ENA_CHAN_1 */ +#define WM9090_DCS_ENA_CHAN_1_MASK 0x0002 /* DCS_ENA_CHAN_1 */ +#define WM9090_DCS_ENA_CHAN_1_SHIFT 1 /* DCS_ENA_CHAN_1 */ +#define WM9090_DCS_ENA_CHAN_1_WIDTH 1 /* DCS_ENA_CHAN_1 */ +#define WM9090_DCS_ENA_CHAN_0 0x0001 /* DCS_ENA_CHAN_0 */ +#define WM9090_DCS_ENA_CHAN_0_MASK 0x0001 /* DCS_ENA_CHAN_0 */ +#define WM9090_DCS_ENA_CHAN_0_SHIFT 0 /* DCS_ENA_CHAN_0 */ +#define WM9090_DCS_ENA_CHAN_0_WIDTH 1 /* DCS_ENA_CHAN_0 */ + +/* + * R85 (0x55) - DC Servo 1 + */ +#define WM9090_DCS_SERIES_NO_01_MASK 0x0FE0 /* DCS_SERIES_NO_01 - [11:5] */ +#define WM9090_DCS_SERIES_NO_01_SHIFT 5 /* DCS_SERIES_NO_01 - [11:5] */ +#define WM9090_DCS_SERIES_NO_01_WIDTH 7 /* DCS_SERIES_NO_01 - [11:5] */ +#define WM9090_DCS_TIMER_PERIOD_01_MASK 0x000F /* DCS_TIMER_PERIOD_01 - [3:0] */ +#define WM9090_DCS_TIMER_PERIOD_01_SHIFT 0 /* DCS_TIMER_PERIOD_01 - [3:0] */ +#define WM9090_DCS_TIMER_PERIOD_01_WIDTH 4 /* DCS_TIMER_PERIOD_01 - [3:0] */ + +/* + * R87 (0x57) - DC Servo 3 + */ +#define WM9090_DCS_DAC_WR_VAL_1_MASK 0xFF00 /* DCS_DAC_WR_VAL_1 - [15:8] */ +#define WM9090_DCS_DAC_WR_VAL_1_SHIFT 8 /* DCS_DAC_WR_VAL_1 - [15:8] */ +#define WM9090_DCS_DAC_WR_VAL_1_WIDTH 8 /* DCS_DAC_WR_VAL_1 - [15:8] */ +#define WM9090_DCS_DAC_WR_VAL_0_MASK 0x00FF /* DCS_DAC_WR_VAL_0 - [7:0] */ +#define WM9090_DCS_DAC_WR_VAL_0_SHIFT 0 /* DCS_DAC_WR_VAL_0 - [7:0] */ +#define WM9090_DCS_DAC_WR_VAL_0_WIDTH 8 /* DCS_DAC_WR_VAL_0 - [7:0] */ + +/* + * R88 (0x58) - DC Servo Readback 0 + */ +#define WM9090_DCS_CAL_COMPLETE_MASK 0x0300 /* DCS_CAL_COMPLETE - [9:8] */ +#define WM9090_DCS_CAL_COMPLETE_SHIFT 8 /* DCS_CAL_COMPLETE - [9:8] */ +#define WM9090_DCS_CAL_COMPLETE_WIDTH 2 /* DCS_CAL_COMPLETE - [9:8] */ +#define WM9090_DCS_DAC_WR_COMPLETE_MASK 0x0030 /* DCS_DAC_WR_COMPLETE - [5:4] */ +#define WM9090_DCS_DAC_WR_COMPLETE_SHIFT 4 /* DCS_DAC_WR_COMPLETE - [5:4] */ +#define WM9090_DCS_DAC_WR_COMPLETE_WIDTH 2 /* DCS_DAC_WR_COMPLETE - [5:4] */ +#define WM9090_DCS_STARTUP_COMPLETE_MASK 0x0003 /* DCS_STARTUP_COMPLETE - [1:0] */ +#define WM9090_DCS_STARTUP_COMPLETE_SHIFT 0 /* DCS_STARTUP_COMPLETE - [1:0] */ +#define WM9090_DCS_STARTUP_COMPLETE_WIDTH 2 /* DCS_STARTUP_COMPLETE - [1:0] */ + +/* + * R89 (0x59) - DC Servo Readback 1 + */ +#define WM9090_DCS_DAC_WR_VAL_1_RD_MASK 0x00FF /* DCS_DAC_WR_VAL_1_RD - [7:0] */ +#define WM9090_DCS_DAC_WR_VAL_1_RD_SHIFT 0 /* DCS_DAC_WR_VAL_1_RD - [7:0] */ +#define WM9090_DCS_DAC_WR_VAL_1_RD_WIDTH 8 /* DCS_DAC_WR_VAL_1_RD - [7:0] */ + +/* + * R90 (0x5A) - DC Servo Readback 2 + */ +#define WM9090_DCS_DAC_WR_VAL_0_RD_MASK 0x00FF /* DCS_DAC_WR_VAL_0_RD - [7:0] */ +#define WM9090_DCS_DAC_WR_VAL_0_RD_SHIFT 0 /* DCS_DAC_WR_VAL_0_RD - [7:0] */ +#define WM9090_DCS_DAC_WR_VAL_0_RD_WIDTH 8 /* DCS_DAC_WR_VAL_0_RD - [7:0] */ + +/* + * R96 (0x60) - Analogue HP 0 + */ +#define WM9090_HPOUT1L_RMV_SHORT 0x0080 /* HPOUT1L_RMV_SHORT */ +#define WM9090_HPOUT1L_RMV_SHORT_MASK 0x0080 /* HPOUT1L_RMV_SHORT */ +#define WM9090_HPOUT1L_RMV_SHORT_SHIFT 7 /* HPOUT1L_RMV_SHORT */ +#define WM9090_HPOUT1L_RMV_SHORT_WIDTH 1 /* HPOUT1L_RMV_SHORT */ +#define WM9090_HPOUT1L_OUTP 0x0040 /* HPOUT1L_OUTP */ +#define WM9090_HPOUT1L_OUTP_MASK 0x0040 /* HPOUT1L_OUTP */ +#define WM9090_HPOUT1L_OUTP_SHIFT 6 /* HPOUT1L_OUTP */ +#define WM9090_HPOUT1L_OUTP_WIDTH 1 /* HPOUT1L_OUTP */ +#define WM9090_HPOUT1L_DLY 0x0020 /* HPOUT1L_DLY */ +#define WM9090_HPOUT1L_DLY_MASK 0x0020 /* HPOUT1L_DLY */ +#define WM9090_HPOUT1L_DLY_SHIFT 5 /* HPOUT1L_DLY */ +#define WM9090_HPOUT1L_DLY_WIDTH 1 /* HPOUT1L_DLY */ +#define WM9090_HPOUT1R_RMV_SHORT 0x0008 /* HPOUT1R_RMV_SHORT */ +#define WM9090_HPOUT1R_RMV_SHORT_MASK 0x0008 /* HPOUT1R_RMV_SHORT */ +#define WM9090_HPOUT1R_RMV_SHORT_SHIFT 3 /* HPOUT1R_RMV_SHORT */ +#define WM9090_HPOUT1R_RMV_SHORT_WIDTH 1 /* HPOUT1R_RMV_SHORT */ +#define WM9090_HPOUT1R_OUTP 0x0004 /* HPOUT1R_OUTP */ +#define WM9090_HPOUT1R_OUTP_MASK 0x0004 /* HPOUT1R_OUTP */ +#define WM9090_HPOUT1R_OUTP_SHIFT 2 /* HPOUT1R_OUTP */ +#define WM9090_HPOUT1R_OUTP_WIDTH 1 /* HPOUT1R_OUTP */ +#define WM9090_HPOUT1R_DLY 0x0002 /* HPOUT1R_DLY */ +#define WM9090_HPOUT1R_DLY_MASK 0x0002 /* HPOUT1R_DLY */ +#define WM9090_HPOUT1R_DLY_SHIFT 1 /* HPOUT1R_DLY */ +#define WM9090_HPOUT1R_DLY_WIDTH 1 /* HPOUT1R_DLY */ + +/* + * R98 (0x62) - AGC Control 0 + */ +#define WM9090_AGC_CLIP_ENA 0x8000 /* AGC_CLIP_ENA */ +#define WM9090_AGC_CLIP_ENA_MASK 0x8000 /* AGC_CLIP_ENA */ +#define WM9090_AGC_CLIP_ENA_SHIFT 15 /* AGC_CLIP_ENA */ +#define WM9090_AGC_CLIP_ENA_WIDTH 1 /* AGC_CLIP_ENA */ +#define WM9090_AGC_CLIP_THR_MASK 0x0F00 /* AGC_CLIP_THR - [11:8] */ +#define WM9090_AGC_CLIP_THR_SHIFT 8 /* AGC_CLIP_THR - [11:8] */ +#define WM9090_AGC_CLIP_THR_WIDTH 4 /* AGC_CLIP_THR - [11:8] */ +#define WM9090_AGC_CLIP_ATK_MASK 0x0070 /* AGC_CLIP_ATK - [6:4] */ +#define WM9090_AGC_CLIP_ATK_SHIFT 4 /* AGC_CLIP_ATK - [6:4] */ +#define WM9090_AGC_CLIP_ATK_WIDTH 3 /* AGC_CLIP_ATK - [6:4] */ +#define WM9090_AGC_CLIP_DCY_MASK 0x0007 /* AGC_CLIP_DCY - [2:0] */ +#define WM9090_AGC_CLIP_DCY_SHIFT 0 /* AGC_CLIP_DCY - [2:0] */ +#define WM9090_AGC_CLIP_DCY_WIDTH 3 /* AGC_CLIP_DCY - [2:0] */ + +/* + * R99 (0x63) - AGC Control 1 + */ +#define WM9090_AGC_PWR_ENA 0x8000 /* AGC_PWR_ENA */ +#define WM9090_AGC_PWR_ENA_MASK 0x8000 /* AGC_PWR_ENA */ +#define WM9090_AGC_PWR_ENA_SHIFT 15 /* AGC_PWR_ENA */ +#define WM9090_AGC_PWR_ENA_WIDTH 1 /* AGC_PWR_ENA */ +#define WM9090_AGC_PWR_AVG 0x1000 /* AGC_PWR_AVG */ +#define WM9090_AGC_PWR_AVG_MASK 0x1000 /* AGC_PWR_AVG */ +#define WM9090_AGC_PWR_AVG_SHIFT 12 /* AGC_PWR_AVG */ +#define WM9090_AGC_PWR_AVG_WIDTH 1 /* AGC_PWR_AVG */ +#define WM9090_AGC_PWR_THR_MASK 0x0F00 /* AGC_PWR_THR - [11:8] */ +#define WM9090_AGC_PWR_THR_SHIFT 8 /* AGC_PWR_THR - [11:8] */ +#define WM9090_AGC_PWR_THR_WIDTH 4 /* AGC_PWR_THR - [11:8] */ +#define WM9090_AGC_PWR_ATK_MASK 0x0070 /* AGC_PWR_ATK - [6:4] */ +#define WM9090_AGC_PWR_ATK_SHIFT 4 /* AGC_PWR_ATK - [6:4] */ +#define WM9090_AGC_PWR_ATK_WIDTH 3 /* AGC_PWR_ATK - [6:4] */ +#define WM9090_AGC_PWR_DCY_MASK 0x0007 /* AGC_PWR_DCY - [2:0] */ +#define WM9090_AGC_PWR_DCY_SHIFT 0 /* AGC_PWR_DCY - [2:0] */ +#define WM9090_AGC_PWR_DCY_WIDTH 3 /* AGC_PWR_DCY - [2:0] */ + +/* + * R100 (0x64) - AGC Control 2 + */ +#define WM9090_AGC_RAMP 0x0100 /* AGC_RAMP */ +#define WM9090_AGC_RAMP_MASK 0x0100 /* AGC_RAMP */ +#define WM9090_AGC_RAMP_SHIFT 8 /* AGC_RAMP */ +#define WM9090_AGC_RAMP_WIDTH 1 /* AGC_RAMP */ +#define WM9090_AGC_MINGAIN_MASK 0x003F /* AGC_MINGAIN - [5:0] */ +#define WM9090_AGC_MINGAIN_SHIFT 0 /* AGC_MINGAIN - [5:0] */ +#define WM9090_AGC_MINGAIN_WIDTH 6 /* AGC_MINGAIN - [5:0] */ + +#endif diff --git a/sound/soc/codecs/wm9712.c b/sound/soc/codecs/wm9712.c index 2f48a8a..28790a2 100644 --- a/sound/soc/codecs/wm9712.c +++ b/sound/soc/codecs/wm9712.c @@ -632,9 +632,6 @@ static int wm9712_soc_resume(struct platform_device *pdev) } } - if (codec->suspend_bias_level == SND_SOC_BIAS_ON) - wm9712_set_bias_level(codec, SND_SOC_BIAS_ON); - return ret; } diff --git a/sound/soc/codecs/wm9713.c b/sound/soc/codecs/wm9713.c index 2fca514..34e0c91 100644 --- a/sound/soc/codecs/wm9713.c +++ b/sound/soc/codecs/wm9713.c @@ -764,7 +764,7 @@ static void pll_factors(struct _pll_div *pll_div, unsigned int source) static int wm9713_set_pll(struct snd_soc_codec *codec, int pll_id, unsigned int freq_in, unsigned int freq_out) { - struct wm9713_priv *wm9713 = codec->private_data; + struct wm9713_priv *wm9713 = snd_soc_codec_get_drvdata(codec); u16 reg, reg2; struct _pll_div pll_div; @@ -1175,7 +1175,7 @@ static int wm9713_soc_resume(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = socdev->card->codec; - struct wm9713_priv *wm9713 = codec->private_data; + struct wm9713_priv *wm9713 = snd_soc_codec_get_drvdata(codec); int i, ret; u16 *cache = codec->reg_cache; @@ -1201,9 +1201,6 @@ static int wm9713_soc_resume(struct platform_device *pdev) } } - if (codec->suspend_bias_level == SND_SOC_BIAS_ON) - wm9713_set_bias_level(codec, SND_SOC_BIAS_ON); - return ret; } @@ -1228,8 +1225,9 @@ static int wm9713_soc_probe(struct platform_device *pdev) codec->reg_cache_size = sizeof(wm9713_reg); codec->reg_cache_step = 2; - codec->private_data = kzalloc(sizeof(struct wm9713_priv), GFP_KERNEL); - if (codec->private_data == NULL) { + snd_soc_codec_set_drvdata(codec, kzalloc(sizeof(struct wm9713_priv), + GFP_KERNEL)); + if (snd_soc_codec_get_drvdata(codec) == NULL) { ret = -ENOMEM; goto priv_err; } @@ -1280,7 +1278,7 @@ pcm_err: snd_soc_free_ac97_codec(codec); codec_err: - kfree(codec->private_data); + kfree(snd_soc_codec_get_drvdata(codec)); priv_err: kfree(codec->reg_cache); @@ -1302,7 +1300,7 @@ static int wm9713_soc_remove(struct platform_device *pdev) snd_soc_dapm_free(socdev); snd_soc_free_pcms(socdev); snd_soc_free_ac97_codec(codec); - kfree(codec->private_data); + kfree(snd_soc_codec_get_drvdata(codec)); kfree(codec->reg_cache); kfree(codec); return 0; diff --git a/sound/soc/codecs/wm_hubs.c b/sound/soc/codecs/wm_hubs.c index e1f225a..16f1a57 100644 --- a/sound/soc/codecs/wm_hubs.c +++ b/sound/soc/codecs/wm_hubs.c @@ -91,7 +91,7 @@ static void wait_for_dc_servo(struct snd_soc_codec *codec, unsigned int op) */ static void calibrate_dc_servo(struct snd_soc_codec *codec) { - struct wm_hubs_data *hubs = codec->private_data; + struct wm_hubs_data *hubs = snd_soc_codec_get_drvdata(codec); u16 reg, reg_l, reg_r, dcs_cfg; /* Set for 32 series updates */ @@ -127,6 +127,8 @@ static void calibrate_dc_servo(struct snd_soc_codec *codec) break; } + dev_dbg(codec->dev, "DCS input: %x %x\n", reg_l, reg_r); + /* HPOUT1L */ if (reg_l + hubs->dcs_codes > 0 && reg_l + hubs->dcs_codes < 0xff) @@ -139,6 +141,8 @@ static void calibrate_dc_servo(struct snd_soc_codec *codec) reg_r += hubs->dcs_codes; dcs_cfg |= reg_r; + dev_dbg(codec->dev, "DCS result: %x\n", dcs_cfg); + /* Do it */ snd_soc_write(codec, WM8993_DC_SERVO_3, dcs_cfg); wait_for_dc_servo(codec, @@ -154,7 +158,7 @@ static int wm8993_put_dc_servo(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - struct wm_hubs_data *hubs = codec->private_data; + struct wm_hubs_data *hubs = snd_soc_codec_get_drvdata(codec); int ret; ret = snd_soc_put_volsw_2r(kcontrol, ucontrol); @@ -327,7 +331,7 @@ static int hp_supply_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { struct snd_soc_codec *codec = w->codec; - struct wm_hubs_data *hubs = codec->private_data; + struct wm_hubs_data *hubs = snd_soc_codec_get_drvdata(codec); switch (event) { case SND_SOC_DAPM_PRE_PMU: @@ -397,14 +401,14 @@ static int hp_event(struct snd_soc_dapm_widget *w, case SND_SOC_DAPM_PRE_PMD: snd_soc_update_bits(codec, WM8993_ANALOGUE_HP_0, - WM8993_HPOUT1L_DLY | - WM8993_HPOUT1R_DLY | + WM8993_HPOUT1L_OUTP | + WM8993_HPOUT1R_OUTP | WM8993_HPOUT1L_RMV_SHORT | WM8993_HPOUT1R_RMV_SHORT, 0); snd_soc_update_bits(codec, WM8993_ANALOGUE_HP_0, - WM8993_HPOUT1L_OUTP | - WM8993_HPOUT1R_OUTP, 0); + WM8993_HPOUT1L_DLY | + WM8993_HPOUT1R_DLY, 0); snd_soc_update_bits(codec, WM8993_POWER_MANAGEMENT_1, WM8993_HPOUT1L_ENA | WM8993_HPOUT1R_ENA, diff --git a/sound/soc/davinci/Kconfig b/sound/soc/davinci/Kconfig index 047ee39..6bbf001 100644 --- a/sound/soc/davinci/Kconfig +++ b/sound/soc/davinci/Kconfig @@ -12,15 +12,38 @@ config SND_DAVINCI_SOC_I2S config SND_DAVINCI_SOC_MCASP tristate +config SND_DAVINCI_SOC_VCIF + tristate + config SND_DAVINCI_SOC_EVM tristate "SoC Audio support for DaVinci DM6446, DM355 or DM365 EVM" depends on SND_DAVINCI_SOC - depends on MACH_DAVINCI_EVM || MACH_DAVINCI_DM355_EVM || MACH_DAVINCI_DM365_EVM + depends on MACH_DAVINCI_EVM || MACH_DAVINCI_DM355_EVM || MACH_DAVINCI_DM365_EVM select SND_DAVINCI_SOC_I2S select SND_SOC_TLV320AIC3X help Say Y if you want to add support for SoC audio on TI - DaVinci DM6446 or DM355 EVM platforms. + DaVinci DM6446, DM355 or DM365 EVM platforms. + +choice + prompt "DM365 codec select" + depends on SND_DAVINCI_SOC_EVM + depends on MACH_DAVINCI_DM365_EVM + default SND_DM365_EXTERNAL_CODEC + +config SND_DM365_AIC3X_CODEC + bool "Audio Codec - AIC3101" + help + Say Y if you want to add support for AIC3101 audio codec + +config SND_DM365_VOICE_CODEC + bool "Voice Codec - CQ93VC" + select MFD_DAVINCI_VOICECODEC + select SND_DAVINCI_SOC_VCIF + select SND_SOC_CQ0093VC + help + Say Y if you want to add support for SoC On-chip voice codec +endchoice config SND_DM6467_SOC_EVM tristate "SoC Audio support for DaVinci DM6467 EVM" diff --git a/sound/soc/davinci/Makefile b/sound/soc/davinci/Makefile index a6939d7..a93679d 100644 --- a/sound/soc/davinci/Makefile +++ b/sound/soc/davinci/Makefile @@ -2,10 +2,12 @@ snd-soc-davinci-objs := davinci-pcm.o snd-soc-davinci-i2s-objs := davinci-i2s.o snd-soc-davinci-mcasp-objs:= davinci-mcasp.o +snd-soc-davinci-vcif-objs:= davinci-vcif.o obj-$(CONFIG_SND_DAVINCI_SOC) += snd-soc-davinci.o obj-$(CONFIG_SND_DAVINCI_SOC_I2S) += snd-soc-davinci-i2s.o obj-$(CONFIG_SND_DAVINCI_SOC_MCASP) += snd-soc-davinci-mcasp.o +obj-$(CONFIG_SND_DAVINCI_SOC_VCIF) += snd-soc-davinci-vcif.o # DAVINCI Machine Support snd-soc-evm-objs := davinci-evm.o diff --git a/sound/soc/davinci/davinci-evm.c b/sound/soc/davinci/davinci-evm.c index 7ccbe66..97f74d6 100644 --- a/sound/soc/davinci/davinci-evm.c +++ b/sound/soc/davinci/davinci-evm.c @@ -28,10 +28,12 @@ #include <mach/mux.h> #include "../codecs/tlv320aic3x.h" +#include "../codecs/cq93vc.h" #include "../codecs/spdif_transciever.h" #include "davinci-pcm.h" #include "davinci-i2s.h" #include "davinci-mcasp.h" +#include "davinci-vcif.h" #define AUDIO_FORMAT (SND_SOC_DAIFMT_DSP_B | \ SND_SOC_DAIFMT_CBM_CFM | SND_SOC_DAIFMT_IB_NF) @@ -81,10 +83,24 @@ static int evm_hw_params(struct snd_pcm_substream *substream, return 0; } +static int evm_spdif_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + + /* set cpu DAI configuration */ + return snd_soc_dai_set_fmt(cpu_dai, AUDIO_FORMAT); +} + static struct snd_soc_ops evm_ops = { .hw_params = evm_hw_params, }; +static struct snd_soc_ops evm_spdif_ops = { + .hw_params = evm_spdif_hw_params, +}; + /* davinci-evm machine dapm widgets */ static const struct snd_soc_dapm_widget aic3x_dapm_widgets[] = { SND_SOC_DAPM_HP("Headphone Jack", NULL), @@ -151,6 +167,22 @@ static struct snd_soc_dai_link evm_dai = { .ops = &evm_ops, }; +static struct snd_soc_dai_link dm365_evm_dai = { +#ifdef CONFIG_SND_DM365_AIC3X_CODEC + .name = "TLV320AIC3X", + .stream_name = "AIC3X", + .cpu_dai = &davinci_i2s_dai, + .codec_dai = &aic3x_dai, + .init = evm_aic3x_init, + .ops = &evm_ops, +#elif defined(CONFIG_SND_DM365_VOICE_CODEC) + .name = "Voice Codec - CQ93VC", + .stream_name = "CQ93", + .cpu_dai = &davinci_vcif_dai, + .codec_dai = &cq93vc_dai, +#endif +}; + static struct snd_soc_dai_link dm6467_evm_dai[] = { { .name = "TLV320AIC3X", @@ -165,7 +197,7 @@ static struct snd_soc_dai_link dm6467_evm_dai[] = { .stream_name = "spdif", .cpu_dai = &davinci_mcasp_dai[DAVINCI_MCASP_DIT_DAI], .codec_dai = &dit_stub_dai, - .ops = &evm_ops, + .ops = &evm_spdif_ops, }, }; static struct snd_soc_dai_link da8xx_evm_dai = { @@ -177,7 +209,7 @@ static struct snd_soc_dai_link da8xx_evm_dai = { .ops = &evm_ops, }; -/* davinci dm6446, dm355 or dm365 evm audio machine driver */ +/* davinci dm6446, dm355 evm audio machine driver */ static struct snd_soc_card snd_soc_card_evm = { .name = "DaVinci EVM", .platform = &davinci_soc_platform, @@ -185,6 +217,15 @@ static struct snd_soc_card snd_soc_card_evm = { .num_links = 1, }; +/* davinci dm365 evm audio machine driver */ +static struct snd_soc_card dm365_snd_soc_card_evm = { + .name = "DaVinci DM365 EVM", + .platform = &davinci_soc_platform, + .dai_link = &dm365_evm_dai, + .num_links = 1, +}; + + /* davinci dm6467 evm audio machine driver */ static struct snd_soc_card dm6467_snd_soc_card_evm = { .name = "DaVinci DM6467 EVM", @@ -217,6 +258,17 @@ static struct snd_soc_device evm_snd_devdata = { }; /* evm audio subsystem */ +static struct snd_soc_device dm365_evm_snd_devdata = { + .card = &dm365_snd_soc_card_evm, +#ifdef CONFIG_SND_DM365_AIC3X_CODEC + .codec_dev = &soc_codec_dev_aic3x, + .codec_data = &aic3x_setup, +#elif defined(CONFIG_SND_DM365_VOICE_CODEC) + .codec_dev = &soc_codec_dev_cq93vc, +#endif +}; + +/* evm audio subsystem */ static struct snd_soc_device dm6467_evm_snd_devdata = { .card = &dm6467_snd_soc_card_evm, .codec_dev = &soc_codec_dev_aic3x, @@ -244,12 +296,15 @@ static int __init evm_init(void) int index; int ret; - if (machine_is_davinci_evm() || machine_is_davinci_dm365_evm()) { + if (machine_is_davinci_evm()) { evm_snd_dev_data = &evm_snd_devdata; index = 0; } else if (machine_is_davinci_dm355_evm()) { evm_snd_dev_data = &evm_snd_devdata; index = 1; + } else if (machine_is_davinci_dm365_evm()) { + evm_snd_dev_data = &dm365_evm_snd_devdata; + index = 0; } else if (machine_is_davinci_dm6467_evm()) { evm_snd_dev_data = &dm6467_evm_snd_devdata; index = 0; diff --git a/sound/soc/davinci/davinci-vcif.c b/sound/soc/davinci/davinci-vcif.c new file mode 100644 index 0000000..9aa980d --- /dev/null +++ b/sound/soc/davinci/davinci-vcif.c @@ -0,0 +1,274 @@ +/* + * ALSA SoC Voice Codec Interface for TI DAVINCI processor + * + * Copyright (C) 2010 Texas Instruments. + * + * Author: Miguel Aguilar <miguel.aguilar@ridgerun.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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/mfd/davinci_voicecodec.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/soc.h> + +#include "davinci-pcm.h" +#include "davinci-i2s.h" +#include "davinci-vcif.h" + +#define MOD_REG_BIT(val, mask, set) do { \ + if (set) { \ + val |= mask; \ + } else { \ + val &= ~mask; \ + } \ +} while (0) + +struct davinci_vcif_dev { + struct davinci_vc *davinci_vc; + struct davinci_pcm_dma_params dma_params[2]; +}; + +static void davinci_vcif_start(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct davinci_vcif_dev *davinci_vcif_dev = + rtd->dai->cpu_dai->private_data; + struct davinci_vc *davinci_vc = davinci_vcif_dev->davinci_vc; + u32 w; + + /* Start the sample generator and enable transmitter/receiver */ + w = readl(davinci_vc->base + DAVINCI_VC_CTRL); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + MOD_REG_BIT(w, DAVINCI_VC_CTRL_RSTDAC, 1); + else + MOD_REG_BIT(w, DAVINCI_VC_CTRL_RSTADC, 1); + + writel(w, davinci_vc->base + DAVINCI_VC_CTRL); +} + +static void davinci_vcif_stop(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct davinci_vcif_dev *davinci_vcif_dev = + rtd->dai->cpu_dai->private_data; + struct davinci_vc *davinci_vc = davinci_vcif_dev->davinci_vc; + u32 w; + + /* Reset transmitter/receiver and sample rate/frame sync generators */ + w = readl(davinci_vc->base + DAVINCI_VC_CTRL); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + MOD_REG_BIT(w, DAVINCI_VC_CTRL_RSTDAC, 0); + else + MOD_REG_BIT(w, DAVINCI_VC_CTRL_RSTADC, 0); + + writel(w, davinci_vc->base + DAVINCI_VC_CTRL); +} + +static int davinci_vcif_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct davinci_vcif_dev *davinci_vcif_dev = dai->private_data; + struct davinci_vc *davinci_vc = davinci_vcif_dev->davinci_vc; + struct davinci_pcm_dma_params *dma_params = + &davinci_vcif_dev->dma_params[substream->stream]; + u32 w; + + /* Restart the codec before setup */ + davinci_vcif_stop(substream); + davinci_vcif_start(substream); + + /* General line settings */ + writel(DAVINCI_VC_CTRL_MASK, davinci_vc->base + DAVINCI_VC_CTRL); + + writel(DAVINCI_VC_INT_MASK, davinci_vc->base + DAVINCI_VC_INTCLR); + + writel(DAVINCI_VC_INT_MASK, davinci_vc->base + DAVINCI_VC_INTEN); + + w = readl(davinci_vc->base + DAVINCI_VC_CTRL); + + /* Determine xfer data type */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_U8: + dma_params->data_type = 0; + + MOD_REG_BIT(w, DAVINCI_VC_CTRL_RD_BITS_8 | + DAVINCI_VC_CTRL_RD_UNSIGNED | + DAVINCI_VC_CTRL_WD_BITS_8 | + DAVINCI_VC_CTRL_WD_UNSIGNED, 1); + break; + case SNDRV_PCM_FORMAT_S8: + dma_params->data_type = 1; + + MOD_REG_BIT(w, DAVINCI_VC_CTRL_RD_BITS_8 | + DAVINCI_VC_CTRL_WD_BITS_8, 1); + + MOD_REG_BIT(w, DAVINCI_VC_CTRL_RD_UNSIGNED | + DAVINCI_VC_CTRL_WD_UNSIGNED, 0); + break; + case SNDRV_PCM_FORMAT_S16_LE: + dma_params->data_type = 2; + + MOD_REG_BIT(w, DAVINCI_VC_CTRL_RD_BITS_8 | + DAVINCI_VC_CTRL_RD_UNSIGNED | + DAVINCI_VC_CTRL_WD_BITS_8 | + DAVINCI_VC_CTRL_WD_UNSIGNED, 0); + break; + default: + printk(KERN_WARNING "davinci-vcif: unsupported PCM format"); + return -EINVAL; + } + + dma_params->acnt = dma_params->data_type; + + writel(w, davinci_vc->base + DAVINCI_VC_CTRL); + + return 0; +} + +static int davinci_vcif_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + davinci_vcif_start(substream); + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + davinci_vcif_stop(substream); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +#define DAVINCI_VCIF_RATES SNDRV_PCM_RATE_8000_48000 + +static struct snd_soc_dai_ops davinci_vcif_dai_ops = { + .trigger = davinci_vcif_trigger, + .hw_params = davinci_vcif_hw_params, +}; + +struct snd_soc_dai davinci_vcif_dai = { + .name = "davinci-vcif", + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = DAVINCI_VCIF_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rates = DAVINCI_VCIF_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .ops = &davinci_vcif_dai_ops, + +}; +EXPORT_SYMBOL_GPL(davinci_vcif_dai); + +static int davinci_vcif_probe(struct platform_device *pdev) +{ + struct davinci_vc *davinci_vc = platform_get_drvdata(pdev); + struct davinci_vcif_dev *davinci_vcif_dev; + int ret; + + davinci_vcif_dev = kzalloc(sizeof(struct davinci_vcif_dev), GFP_KERNEL); + if (!davinci_vc) { + dev_dbg(&pdev->dev, + "could not allocate memory for private data\n"); + return -ENOMEM; + } + + /* DMA tx params */ + davinci_vcif_dev->davinci_vc = davinci_vc; + davinci_vcif_dev->dma_params[SNDRV_PCM_STREAM_PLAYBACK].channel = + davinci_vc->davinci_vcif.dma_tx_channel; + davinci_vcif_dev->dma_params[SNDRV_PCM_STREAM_PLAYBACK].dma_addr = + davinci_vc->davinci_vcif.dma_tx_addr; + + /* DMA rx params */ + davinci_vcif_dev->dma_params[SNDRV_PCM_STREAM_CAPTURE].channel = + davinci_vc->davinci_vcif.dma_rx_channel; + davinci_vcif_dev->dma_params[SNDRV_PCM_STREAM_CAPTURE].dma_addr = + davinci_vc->davinci_vcif.dma_rx_addr; + + davinci_vcif_dai.dev = &pdev->dev; + davinci_vcif_dai.capture.dma_data = davinci_vcif_dev->dma_params; + davinci_vcif_dai.playback.dma_data = davinci_vcif_dev->dma_params; + davinci_vcif_dai.private_data = davinci_vcif_dev; + + ret = snd_soc_register_dai(&davinci_vcif_dai); + if (ret != 0) { + dev_err(&pdev->dev, "could not register dai\n"); + goto fail; + } + + return 0; + +fail: + kfree(davinci_vcif_dev); + + return ret; +} + +static int davinci_vcif_remove(struct platform_device *pdev) +{ + snd_soc_unregister_dai(&davinci_vcif_dai); + + return 0; +} + +static struct platform_driver davinci_vcif_driver = { + .probe = davinci_vcif_probe, + .remove = davinci_vcif_remove, + .driver = { + .name = "davinci_vcif", + .owner = THIS_MODULE, + }, +}; + +static int __init davinci_vcif_init(void) +{ + return platform_driver_probe(&davinci_vcif_driver, davinci_vcif_probe); +} +module_init(davinci_vcif_init); + +static void __exit davinci_vcif_exit(void) +{ + platform_driver_unregister(&davinci_vcif_driver); +} +module_exit(davinci_vcif_exit); + +MODULE_AUTHOR("Miguel Aguilar"); +MODULE_DESCRIPTION("Texas Instruments DaVinci ASoC Voice Codec Interface"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/davinci/davinci-vcif.h b/sound/soc/davinci/davinci-vcif.h new file mode 100644 index 0000000..571c994 --- /dev/null +++ b/sound/soc/davinci/davinci-vcif.h @@ -0,0 +1,28 @@ +/* + * ALSA SoC Voice Codec Interface for TI DAVINCI processor + * + * Copyright (C) 2010 Texas Instruments. + * + * Author: Miguel Aguilar <miguel.aguilar@ridgerun.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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _DAVINCI_VCIF_H +#define _DAVINCI_VCIF_H + +extern struct snd_soc_dai davinci_vcif_dai; + +#endif diff --git a/sound/soc/imx/Kconfig b/sound/soc/imx/Kconfig index 7174b4c..eba9b9d 100644 --- a/sound/soc/imx/Kconfig +++ b/sound/soc/imx/Kconfig @@ -11,3 +11,11 @@ config SND_IMX_SOC config SND_MXC_SOC_SSI tristate +config SND_MXC_SOC_WM1133_EV1 + tristate "Audio on the the i.MX31ADS with WM1133-EV1 fitted" + depends on SND_IMX_SOC && 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. diff --git a/sound/soc/imx/Makefile b/sound/soc/imx/Makefile index 9f8bb92..2d20363 100644 --- a/sound/soc/imx/Makefile +++ b/sound/soc/imx/Makefile @@ -9,4 +9,7 @@ obj-$(CONFIG_SND_IMX_SOC) += snd-soc-imx.o # i.MX Machine Support snd-soc-phycore-ac97-objs := phycore-ac97.o +snd-soc-wm1133-ev1-objs := wm1133-ev1.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/wm1133-ev1.c b/sound/soc/imx/wm1133-ev1.c new file mode 100644 index 0000000..a6e7d94 --- /dev/null +++ b/sound/soc/imx/wm1133-ev1.c @@ -0,0 +1,308 @@ +/* + * wm1133-ev1.c - Audio for WM1133-EV1 on i.MX31ADS + * + * Copyright (c) 2010 Wolfson Microelectronics plc + * Author: Mark Brown <broonie@opensource.wolfsonmicro.com> + * + * Based on an earlier driver for the same hardware by Liam Girdwood. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License 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 <linux/clk.h> +#include <sound/core.h> +#include <sound/jack.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> + +#include <mach/audmux.h> + +#include "imx-ssi.h" +#include "../codecs/wm8350.h" + +/* There is a silicon mic on the board optionally connected via a solder pad + * SP1. Define this to enable it. + */ +#undef USE_SIMIC + +struct _wm8350_audio { + unsigned int channels; + snd_pcm_format_t format; + unsigned int rate; + unsigned int sysclk; + unsigned int bclkdiv; + unsigned int clkdiv; + unsigned int lr_rate; +}; + +/* in order of power consumption per rate (lowest first) */ +static const struct _wm8350_audio wm8350_audio[] = { + /* 16bit mono modes */ + {1, SNDRV_PCM_FORMAT_S16_LE, 8000, 12288000 >> 1, + WM8350_BCLK_DIV_48, WM8350_DACDIV_3, 16,}, + + /* 16 bit stereo modes */ + {2, SNDRV_PCM_FORMAT_S16_LE, 8000, 12288000, + WM8350_BCLK_DIV_48, WM8350_DACDIV_6, 32,}, + {2, SNDRV_PCM_FORMAT_S16_LE, 16000, 12288000, + WM8350_BCLK_DIV_24, WM8350_DACDIV_3, 32,}, + {2, SNDRV_PCM_FORMAT_S16_LE, 32000, 12288000, + WM8350_BCLK_DIV_12, WM8350_DACDIV_1_5, 32,}, + {2, SNDRV_PCM_FORMAT_S16_LE, 48000, 12288000, + WM8350_BCLK_DIV_8, WM8350_DACDIV_1, 32,}, + {2, SNDRV_PCM_FORMAT_S16_LE, 96000, 24576000, + WM8350_BCLK_DIV_8, WM8350_DACDIV_1, 32,}, + {2, SNDRV_PCM_FORMAT_S16_LE, 11025, 11289600, + WM8350_BCLK_DIV_32, WM8350_DACDIV_4, 32,}, + {2, SNDRV_PCM_FORMAT_S16_LE, 22050, 11289600, + WM8350_BCLK_DIV_16, WM8350_DACDIV_2, 32,}, + {2, SNDRV_PCM_FORMAT_S16_LE, 44100, 11289600, + WM8350_BCLK_DIV_8, WM8350_DACDIV_1, 32,}, + {2, SNDRV_PCM_FORMAT_S16_LE, 88200, 22579200, + WM8350_BCLK_DIV_8, WM8350_DACDIV_1, 32,}, + + /* 24bit stereo modes */ + {2, SNDRV_PCM_FORMAT_S24_LE, 48000, 12288000, + WM8350_BCLK_DIV_4, WM8350_DACDIV_1, 64,}, + {2, SNDRV_PCM_FORMAT_S24_LE, 96000, 24576000, + WM8350_BCLK_DIV_4, WM8350_DACDIV_1, 64,}, + {2, SNDRV_PCM_FORMAT_S24_LE, 44100, 11289600, + WM8350_BCLK_DIV_4, WM8350_DACDIV_1, 64,}, + {2, SNDRV_PCM_FORMAT_S24_LE, 88200, 22579200, + WM8350_BCLK_DIV_4, WM8350_DACDIV_1, 64,}, +}; + +static int wm1133_ev1_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 i, found = 0; + snd_pcm_format_t format = params_format(params); + unsigned int rate = params_rate(params); + unsigned int channels = params_channels(params); + u32 dai_format; + + /* find the correct audio parameters */ + for (i = 0; i < ARRAY_SIZE(wm8350_audio); i++) { + if (rate == wm8350_audio[i].rate && + format == wm8350_audio[i].format && + channels == wm8350_audio[i].channels) { + found = 1; + break; + } + } + if (!found) + return -EINVAL; + + /* codec FLL input is 14.75 MHz from MCLK */ + snd_soc_dai_set_pll(codec_dai, 0, 0, 14750000, wm8350_audio[i].sysclk); + + dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM; + + /* set codec DAI configuration */ + snd_soc_dai_set_fmt(codec_dai, dai_format); + + /* set cpu DAI configuration */ + snd_soc_dai_set_fmt(cpu_dai, dai_format); + + /* TODO: The SSI driver should figure this out for us */ + switch (channels) { + case 2: + snd_soc_dai_set_tdm_slot(cpu_dai, 0xffffffc, 0xffffffc, 2, 0); + break; + case 1: + snd_soc_dai_set_tdm_slot(cpu_dai, 0xffffffe, 0xffffffe, 1, 0); + break; + default: + return -EINVAL; + } + + /* set MCLK as the codec system clock for DAC and ADC */ + snd_soc_dai_set_sysclk(codec_dai, WM8350_MCLK_SEL_PLL_MCLK, + wm8350_audio[i].sysclk, SND_SOC_CLOCK_IN); + + /* set codec BCLK division for sample rate */ + snd_soc_dai_set_clkdiv(codec_dai, WM8350_BCLK_CLKDIV, + wm8350_audio[i].bclkdiv); + + /* DAI is synchronous and clocked with DAC LRCLK & ADC LRC */ + snd_soc_dai_set_clkdiv(codec_dai, + WM8350_DACLR_CLKDIV, wm8350_audio[i].lr_rate); + snd_soc_dai_set_clkdiv(codec_dai, + WM8350_ADCLR_CLKDIV, wm8350_audio[i].lr_rate); + + /* now configure DAC and ADC clocks */ + snd_soc_dai_set_clkdiv(codec_dai, + WM8350_DAC_CLKDIV, wm8350_audio[i].clkdiv); + + snd_soc_dai_set_clkdiv(codec_dai, + WM8350_ADC_CLKDIV, wm8350_audio[i].clkdiv); + + return 0; +} + +static struct snd_soc_ops wm1133_ev1_ops = { + .hw_params = wm1133_ev1_hw_params, +}; + +static const struct snd_soc_dapm_widget wm1133_ev1_widgets[] = { +#ifdef USE_SIMIC + SND_SOC_DAPM_MIC("SiMIC", NULL), +#endif + SND_SOC_DAPM_MIC("Mic1 Jack", NULL), + SND_SOC_DAPM_MIC("Mic2 Jack", NULL), + SND_SOC_DAPM_LINE("Line In Jack", NULL), + SND_SOC_DAPM_LINE("Line Out Jack", NULL), + SND_SOC_DAPM_HP("Headphone Jack", NULL), +}; + +/* imx32ads soc_card audio map */ +static const struct snd_soc_dapm_route wm1133_ev1_map[] = { + +#ifdef USE_SIMIC + /* SiMIC --> IN1LN (with automatic bias) via SP1 */ + { "IN1LN", NULL, "Mic Bias" }, + { "Mic Bias", NULL, "SiMIC" }, +#endif + + /* Mic 1 Jack --> IN1LN and IN1LP (with automatic bias) */ + { "IN1LN", NULL, "Mic Bias" }, + { "IN1LP", NULL, "Mic1 Jack" }, + { "Mic Bias", NULL, "Mic1 Jack" }, + + /* Mic 2 Jack --> IN1RN and IN1RP (with automatic bias) */ + { "IN1RN", NULL, "Mic Bias" }, + { "IN1RP", NULL, "Mic2 Jack" }, + { "Mic Bias", NULL, "Mic2 Jack" }, + + /* Line in Jack --> AUX (L+R) */ + { "IN3R", NULL, "Line In Jack" }, + { "IN3L", NULL, "Line In Jack" }, + + /* Out1 --> Headphone Jack */ + { "Headphone Jack", NULL, "OUT1R" }, + { "Headphone Jack", NULL, "OUT1L" }, + + /* Out1 --> Line Out Jack */ + { "Line Out Jack", NULL, "OUT2R" }, + { "Line Out Jack", NULL, "OUT2L" }, +}; + +static struct snd_soc_jack hp_jack; + +static struct snd_soc_jack_pin hp_jack_pins[] = { + { .pin = "Headphone Jack", .mask = SND_JACK_HEADPHONE }, +}; + +static struct snd_soc_jack mic_jack; + +static struct snd_soc_jack_pin mic_jack_pins[] = { + { .pin = "Mic1 Jack", .mask = SND_JACK_MICROPHONE }, + { .pin = "Mic2 Jack", .mask = SND_JACK_MICROPHONE }, +}; + +static int wm1133_ev1_init(struct snd_soc_codec *codec) +{ + struct snd_soc_card *card = codec->socdev->card; + + snd_soc_dapm_new_controls(codec, wm1133_ev1_widgets, + ARRAY_SIZE(wm1133_ev1_widgets)); + + snd_soc_dapm_add_routes(codec, wm1133_ev1_map, + ARRAY_SIZE(wm1133_ev1_map)); + + /* Headphone jack detection */ + snd_soc_jack_new(card, "Headphone", SND_JACK_HEADPHONE, &hp_jack); + snd_soc_jack_add_pins(&hp_jack, ARRAY_SIZE(hp_jack_pins), + hp_jack_pins); + wm8350_hp_jack_detect(codec, WM8350_JDR, &hp_jack, SND_JACK_HEADPHONE); + + /* Microphone jack detection */ + snd_soc_jack_new(card, "Microphone", + SND_JACK_MICROPHONE | SND_JACK_BTN_0, &mic_jack); + snd_soc_jack_add_pins(&mic_jack, ARRAY_SIZE(mic_jack_pins), + mic_jack_pins); + wm8350_mic_jack_detect(codec, &mic_jack, SND_JACK_MICROPHONE, + SND_JACK_BTN_0); + + snd_soc_dapm_force_enable_pin(codec, "Mic Bias"); + + return 0; +} + + +static struct snd_soc_dai_link wm1133_ev1_dai = { + .name = "WM1133-EV1", + .stream_name = "Audio", + .cpu_dai = &imx_ssi_pcm_dai[0], + .codec_dai = &wm8350_dai, + .init = wm1133_ev1_init, + .ops = &wm1133_ev1_ops, + .symmetric_rates = 1, +}; + +static struct snd_soc_card wm1133_ev1 = { + .name = "WM1133-EV1", + .platform = &imx_soc_platform, + .dai_link = &wm1133_ev1_dai, + .num_links = 1, +}; + +static struct snd_soc_device wm1133_ev1_snd_devdata = { + .card = &wm1133_ev1, + .codec_dev = &soc_codec_dev_wm8350, +}; + +static struct platform_device *wm1133_ev1_snd_device; + +static int __init wm1133_ev1_audio_init(void) +{ + int ret; + unsigned int ptcr, pdcr; + + /* SSI0 mastered by port 5 */ + ptcr = MXC_AUDMUX_V2_PTCR_SYN | + MXC_AUDMUX_V2_PTCR_TFSDIR | + MXC_AUDMUX_V2_PTCR_TFSEL(MX31_AUDMUX_PORT5_SSI_PINS_5) | + MXC_AUDMUX_V2_PTCR_TCLKDIR | + MXC_AUDMUX_V2_PTCR_TCSEL(MX31_AUDMUX_PORT5_SSI_PINS_5); + pdcr = MXC_AUDMUX_V2_PDCR_RXDSEL(MX31_AUDMUX_PORT5_SSI_PINS_5); + mxc_audmux_v2_configure_port(MX31_AUDMUX_PORT1_SSI0, ptcr, pdcr); + + ptcr = MXC_AUDMUX_V2_PTCR_SYN; + pdcr = MXC_AUDMUX_V2_PDCR_RXDSEL(MX31_AUDMUX_PORT1_SSI0); + mxc_audmux_v2_configure_port(MX31_AUDMUX_PORT5_SSI_PINS_5, ptcr, pdcr); + + wm1133_ev1_snd_device = platform_device_alloc("soc-audio", -1); + if (!wm1133_ev1_snd_device) + return -ENOMEM; + + platform_set_drvdata(wm1133_ev1_snd_device, &wm1133_ev1_snd_devdata); + wm1133_ev1_snd_devdata.dev = &wm1133_ev1_snd_device->dev; + ret = platform_device_add(wm1133_ev1_snd_device); + + if (ret) + platform_device_put(wm1133_ev1_snd_device); + + return ret; +} +module_init(wm1133_ev1_audio_init); + +static void __exit wm1133_ev1_audio_exit(void) +{ + platform_device_unregister(wm1133_ev1_snd_device); +} +module_exit(wm1133_ev1_audio_exit); + +MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); +MODULE_DESCRIPTION("Audio for WM1133-EV1 on i.MX31ADS"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/omap/Kconfig b/sound/soc/omap/Kconfig index f11963c..d542ea2 100644 --- a/sound/soc/omap/Kconfig +++ b/sound/soc/omap/Kconfig @@ -18,6 +18,16 @@ config SND_OMAP_SOC_N810 help Say Y if you want to add support for SoC audio on Nokia N810. +config SND_OMAP_SOC_RX51 + tristate "SoC Audio support for Nokia RX-51" + depends on SND_OMAP_SOC && MACH_NOKIA_RX51 + select OMAP_MCBSP + select SND_OMAP_SOC_MCBSP + select SND_SOC_TLV320AIC3X + help + Say Y if you want to add support for SoC audio on Nokia RX-51 + hardware. This is also known as Nokia N900 product. + config SND_OMAP_SOC_AMS_DELTA tristate "SoC Audio support for Amstrad E3 (Delta) videophone" depends on SND_OMAP_SOC && MACH_AMS_DELTA @@ -88,6 +98,15 @@ config SND_OMAP_SOC_SDP3430 Say Y if you want to add support for SoC audio on Texas Instruments SDP3430. +config SND_OMAP_SOC_SDP4430 + tristate "SoC Audio support for Texas Instruments SDP4430" + depends on TWL4030_CORE && SND_OMAP_SOC && MACH_OMAP_4430SDP + select SND_OMAP_SOC_MCPDM + select SND_SOC_TWL6040 + help + Say Y if you want to add support for SoC audio on Texas Instruments + SDP4430. + config SND_OMAP_SOC_OMAP3_PANDORA tristate "SoC Audio support for OMAP3 Pandora" depends on TWL4030_CORE && SND_OMAP_SOC && MACH_OMAP3_PANDORA diff --git a/sound/soc/omap/Makefile b/sound/soc/omap/Makefile index 0bc00ca..ba9fc65 100644 --- a/sound/soc/omap/Makefile +++ b/sound/soc/omap/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_SND_OMAP_SOC_MCPDM) += snd-soc-omap-mcpdm.o # OMAP Machine Support snd-soc-n810-objs := n810.o +snd-soc-rx51-objs := rx51.o snd-soc-ams-delta-objs := ams-delta.o snd-soc-osk5912-objs := osk5912.o snd-soc-overo-objs := overo.o @@ -16,12 +17,14 @@ snd-soc-omap2evm-objs := omap2evm.o snd-soc-omap3evm-objs := omap3evm.o snd-soc-am3517evm-objs := am3517evm.o snd-soc-sdp3430-objs := sdp3430.o +snd-soc-sdp4430-objs := sdp4430.o snd-soc-omap3pandora-objs := omap3pandora.o snd-soc-omap3beagle-objs := omap3beagle.o snd-soc-zoom2-objs := zoom2.o snd-soc-igep0020-objs := igep0020.o obj-$(CONFIG_SND_OMAP_SOC_N810) += snd-soc-n810.o +obj-$(CONFIG_SND_OMAP_SOC_RX51) += snd-soc-rx51.o obj-$(CONFIG_SND_OMAP_SOC_AMS_DELTA) += snd-soc-ams-delta.o obj-$(CONFIG_SND_OMAP_SOC_OSK5912) += snd-soc-osk5912.o obj-$(CONFIG_SND_OMAP_SOC_OVERO) += snd-soc-overo.o @@ -29,6 +32,7 @@ obj-$(CONFIG_SND_OMAP_SOC_OMAP2EVM) += snd-soc-omap2evm.o obj-$(CONFIG_SND_OMAP_SOC_OMAP3EVM) += snd-soc-omap3evm.o obj-$(CONFIG_SND_OMAP_SOC_AM3517EVM) += snd-soc-am3517evm.o obj-$(CONFIG_SND_OMAP_SOC_SDP3430) += snd-soc-sdp3430.o +obj-$(CONFIG_SND_OMAP_SOC_SDP4430) += snd-soc-sdp4430.o obj-$(CONFIG_SND_OMAP_SOC_OMAP3_PANDORA) += snd-soc-omap3pandora.o obj-$(CONFIG_SND_OMAP_SOC_OMAP3_BEAGLE) += snd-soc-omap3beagle.o obj-$(CONFIG_SND_OMAP_SOC_ZOOM2) += snd-soc-zoom2.o diff --git a/sound/soc/omap/mcpdm.c b/sound/soc/omap/mcpdm.c index 1dab4c1..90b8bf7 100644 --- a/sound/soc/omap/mcpdm.c +++ b/sound/soc/omap/mcpdm.c @@ -1,5 +1,5 @@ /* - * mcpdm.c -- McPDM interface driver + * mcpdm.c -- McPDM interface driver * * Author: Jorge Eduardo Candelaria <x0107209@ti.com> * Copyright (C) 2009 - Texas Instruments, Inc. @@ -39,46 +39,46 @@ static struct omap_mcpdm *mcpdm; static inline void omap_mcpdm_write(u16 reg, u32 val) { - __raw_writel(val, mcpdm->io_base + reg); + __raw_writel(val, mcpdm->io_base + reg); } static inline int omap_mcpdm_read(u16 reg) { - return __raw_readl(mcpdm->io_base + reg); + return __raw_readl(mcpdm->io_base + reg); } static void omap_mcpdm_reg_dump(void) { - dev_dbg(mcpdm->dev, "***********************\n"); - dev_dbg(mcpdm->dev, "IRQSTATUS_RAW: 0x%04x\n", - omap_mcpdm_read(MCPDM_IRQSTATUS_RAW)); - dev_dbg(mcpdm->dev, "IRQSTATUS: 0x%04x\n", - omap_mcpdm_read(MCPDM_IRQSTATUS)); - dev_dbg(mcpdm->dev, "IRQENABLE_SET: 0x%04x\n", - omap_mcpdm_read(MCPDM_IRQENABLE_SET)); - dev_dbg(mcpdm->dev, "IRQENABLE_CLR: 0x%04x\n", - omap_mcpdm_read(MCPDM_IRQENABLE_CLR)); - dev_dbg(mcpdm->dev, "IRQWAKE_EN: 0x%04x\n", - omap_mcpdm_read(MCPDM_IRQWAKE_EN)); - dev_dbg(mcpdm->dev, "DMAENABLE_SET: 0x%04x\n", - omap_mcpdm_read(MCPDM_DMAENABLE_SET)); - dev_dbg(mcpdm->dev, "DMAENABLE_CLR: 0x%04x\n", - omap_mcpdm_read(MCPDM_DMAENABLE_CLR)); - dev_dbg(mcpdm->dev, "DMAWAKEEN: 0x%04x\n", - omap_mcpdm_read(MCPDM_DMAWAKEEN)); - dev_dbg(mcpdm->dev, "CTRL: 0x%04x\n", - omap_mcpdm_read(MCPDM_CTRL)); - dev_dbg(mcpdm->dev, "DN_DATA: 0x%04x\n", - omap_mcpdm_read(MCPDM_DN_DATA)); - dev_dbg(mcpdm->dev, "UP_DATA: 0x%04x\n", - omap_mcpdm_read(MCPDM_UP_DATA)); - dev_dbg(mcpdm->dev, "FIFO_CTRL_DN: 0x%04x\n", - omap_mcpdm_read(MCPDM_FIFO_CTRL_DN)); - dev_dbg(mcpdm->dev, "FIFO_CTRL_UP: 0x%04x\n", - omap_mcpdm_read(MCPDM_FIFO_CTRL_UP)); - dev_dbg(mcpdm->dev, "DN_OFFSET: 0x%04x\n", - omap_mcpdm_read(MCPDM_DN_OFFSET)); - dev_dbg(mcpdm->dev, "***********************\n"); + dev_dbg(mcpdm->dev, "***********************\n"); + dev_dbg(mcpdm->dev, "IRQSTATUS_RAW: 0x%04x\n", + omap_mcpdm_read(MCPDM_IRQSTATUS_RAW)); + dev_dbg(mcpdm->dev, "IRQSTATUS: 0x%04x\n", + omap_mcpdm_read(MCPDM_IRQSTATUS)); + dev_dbg(mcpdm->dev, "IRQENABLE_SET: 0x%04x\n", + omap_mcpdm_read(MCPDM_IRQENABLE_SET)); + dev_dbg(mcpdm->dev, "IRQENABLE_CLR: 0x%04x\n", + omap_mcpdm_read(MCPDM_IRQENABLE_CLR)); + dev_dbg(mcpdm->dev, "IRQWAKE_EN: 0x%04x\n", + omap_mcpdm_read(MCPDM_IRQWAKE_EN)); + dev_dbg(mcpdm->dev, "DMAENABLE_SET: 0x%04x\n", + omap_mcpdm_read(MCPDM_DMAENABLE_SET)); + dev_dbg(mcpdm->dev, "DMAENABLE_CLR: 0x%04x\n", + omap_mcpdm_read(MCPDM_DMAENABLE_CLR)); + dev_dbg(mcpdm->dev, "DMAWAKEEN: 0x%04x\n", + omap_mcpdm_read(MCPDM_DMAWAKEEN)); + dev_dbg(mcpdm->dev, "CTRL: 0x%04x\n", + omap_mcpdm_read(MCPDM_CTRL)); + dev_dbg(mcpdm->dev, "DN_DATA: 0x%04x\n", + omap_mcpdm_read(MCPDM_DN_DATA)); + dev_dbg(mcpdm->dev, "UP_DATA: 0x%04x\n", + omap_mcpdm_read(MCPDM_UP_DATA)); + dev_dbg(mcpdm->dev, "FIFO_CTRL_DN: 0x%04x\n", + omap_mcpdm_read(MCPDM_FIFO_CTRL_DN)); + dev_dbg(mcpdm->dev, "FIFO_CTRL_UP: 0x%04x\n", + omap_mcpdm_read(MCPDM_FIFO_CTRL_UP)); + dev_dbg(mcpdm->dev, "DN_OFFSET: 0x%04x\n", + omap_mcpdm_read(MCPDM_DN_OFFSET)); + dev_dbg(mcpdm->dev, "***********************\n"); } /* @@ -87,26 +87,26 @@ static void omap_mcpdm_reg_dump(void) */ static void omap_mcpdm_reset_capture(int reset) { - int ctrl = omap_mcpdm_read(MCPDM_CTRL); + int ctrl = omap_mcpdm_read(MCPDM_CTRL); - if (reset) - ctrl |= SW_UP_RST; - else - ctrl &= ~SW_UP_RST; + if (reset) + ctrl |= SW_UP_RST; + else + ctrl &= ~SW_UP_RST; - omap_mcpdm_write(MCPDM_CTRL, ctrl); + omap_mcpdm_write(MCPDM_CTRL, ctrl); } static void omap_mcpdm_reset_playback(int reset) { - int ctrl = omap_mcpdm_read(MCPDM_CTRL); + int ctrl = omap_mcpdm_read(MCPDM_CTRL); - if (reset) - ctrl |= SW_DN_RST; - else - ctrl &= ~SW_DN_RST; + if (reset) + ctrl |= SW_DN_RST; + else + ctrl &= ~SW_DN_RST; - omap_mcpdm_write(MCPDM_CTRL, ctrl); + omap_mcpdm_write(MCPDM_CTRL, ctrl); } /* @@ -115,14 +115,14 @@ static void omap_mcpdm_reset_playback(int reset) */ void omap_mcpdm_start(int stream) { - int ctrl = omap_mcpdm_read(MCPDM_CTRL); + int ctrl = omap_mcpdm_read(MCPDM_CTRL); - if (stream) - ctrl |= mcpdm->up_channels; - else - ctrl |= mcpdm->dn_channels; + if (stream) + ctrl |= mcpdm->up_channels; + else + ctrl |= mcpdm->dn_channels; - omap_mcpdm_write(MCPDM_CTRL, ctrl); + omap_mcpdm_write(MCPDM_CTRL, ctrl); } /* @@ -131,14 +131,14 @@ void omap_mcpdm_start(int stream) */ void omap_mcpdm_stop(int stream) { - int ctrl = omap_mcpdm_read(MCPDM_CTRL); + int ctrl = omap_mcpdm_read(MCPDM_CTRL); - if (stream) - ctrl &= ~mcpdm->up_channels; - else - ctrl &= ~mcpdm->dn_channels; + if (stream) + ctrl &= ~mcpdm->up_channels; + else + ctrl &= ~mcpdm->dn_channels; - omap_mcpdm_write(MCPDM_CTRL, ctrl); + omap_mcpdm_write(MCPDM_CTRL, ctrl); } /* @@ -147,38 +147,38 @@ void omap_mcpdm_stop(int stream) */ int omap_mcpdm_capture_open(struct omap_mcpdm_link *uplink) { - int irq_mask = 0; - int ctrl; + int irq_mask = 0; + int ctrl; - if (!uplink) - return -EINVAL; + if (!uplink) + return -EINVAL; - mcpdm->uplink = uplink; + mcpdm->uplink = uplink; - /* Enable irq request generation */ - irq_mask |= uplink->irq_mask & MCPDM_UPLINK_IRQ_MASK; - omap_mcpdm_write(MCPDM_IRQENABLE_SET, irq_mask); + /* Enable irq request generation */ + irq_mask |= uplink->irq_mask & MCPDM_UPLINK_IRQ_MASK; + omap_mcpdm_write(MCPDM_IRQENABLE_SET, irq_mask); - /* Configure uplink threshold */ - if (uplink->threshold > UP_THRES_MAX) - uplink->threshold = UP_THRES_MAX; + /* Configure uplink threshold */ + if (uplink->threshold > UP_THRES_MAX) + uplink->threshold = UP_THRES_MAX; - omap_mcpdm_write(MCPDM_FIFO_CTRL_UP, uplink->threshold); + omap_mcpdm_write(MCPDM_FIFO_CTRL_UP, uplink->threshold); - /* Configure DMA controller */ - omap_mcpdm_write(MCPDM_DMAENABLE_SET, DMA_UP_ENABLE); + /* Configure DMA controller */ + omap_mcpdm_write(MCPDM_DMAENABLE_SET, DMA_UP_ENABLE); - /* Set pdm out format */ - ctrl = omap_mcpdm_read(MCPDM_CTRL); - ctrl &= ~PDMOUTFORMAT; - ctrl |= uplink->format & PDMOUTFORMAT; + /* Set pdm out format */ + ctrl = omap_mcpdm_read(MCPDM_CTRL); + ctrl &= ~PDMOUTFORMAT; + ctrl |= uplink->format & PDMOUTFORMAT; - /* Uplink channels */ - mcpdm->up_channels = uplink->channels & (PDM_UP_MASK | PDM_STATUS_MASK); + /* Uplink channels */ + mcpdm->up_channels = uplink->channels & (PDM_UP_MASK | PDM_STATUS_MASK); - omap_mcpdm_write(MCPDM_CTRL, ctrl); + omap_mcpdm_write(MCPDM_CTRL, ctrl); - return 0; + return 0; } /* @@ -187,38 +187,38 @@ int omap_mcpdm_capture_open(struct omap_mcpdm_link *uplink) */ int omap_mcpdm_playback_open(struct omap_mcpdm_link *downlink) { - int irq_mask = 0; - int ctrl; + int irq_mask = 0; + int ctrl; - if (!downlink) - return -EINVAL; + if (!downlink) + return -EINVAL; - mcpdm->downlink = downlink; + mcpdm->downlink = downlink; - /* Enable irq request generation */ - irq_mask |= downlink->irq_mask & MCPDM_DOWNLINK_IRQ_MASK; - omap_mcpdm_write(MCPDM_IRQENABLE_SET, irq_mask); + /* Enable irq request generation */ + irq_mask |= downlink->irq_mask & MCPDM_DOWNLINK_IRQ_MASK; + omap_mcpdm_write(MCPDM_IRQENABLE_SET, irq_mask); - /* Configure uplink threshold */ - if (downlink->threshold > DN_THRES_MAX) - downlink->threshold = DN_THRES_MAX; + /* Configure uplink threshold */ + if (downlink->threshold > DN_THRES_MAX) + downlink->threshold = DN_THRES_MAX; - omap_mcpdm_write(MCPDM_FIFO_CTRL_DN, downlink->threshold); + omap_mcpdm_write(MCPDM_FIFO_CTRL_DN, downlink->threshold); - /* Enable DMA request generation */ - omap_mcpdm_write(MCPDM_DMAENABLE_SET, DMA_DN_ENABLE); + /* Enable DMA request generation */ + omap_mcpdm_write(MCPDM_DMAENABLE_SET, DMA_DN_ENABLE); - /* Set pdm out format */ - ctrl = omap_mcpdm_read(MCPDM_CTRL); - ctrl &= ~PDMOUTFORMAT; - ctrl |= downlink->format & PDMOUTFORMAT; + /* Set pdm out format */ + ctrl = omap_mcpdm_read(MCPDM_CTRL); + ctrl &= ~PDMOUTFORMAT; + ctrl |= downlink->format & PDMOUTFORMAT; - /* Downlink channels */ - mcpdm->dn_channels = downlink->channels & (PDM_DN_MASK | PDM_CMD_MASK); + /* Downlink channels */ + mcpdm->dn_channels = downlink->channels & (PDM_DN_MASK | PDM_CMD_MASK); - omap_mcpdm_write(MCPDM_CTRL, ctrl); + omap_mcpdm_write(MCPDM_CTRL, ctrl); - return 0; + return 0; } /* @@ -227,24 +227,24 @@ int omap_mcpdm_playback_open(struct omap_mcpdm_link *downlink) */ int omap_mcpdm_capture_close(struct omap_mcpdm_link *uplink) { - int irq_mask = 0; + int irq_mask = 0; - if (!uplink) - return -EINVAL; + if (!uplink) + return -EINVAL; - /* Disable irq request generation */ - irq_mask |= uplink->irq_mask & MCPDM_UPLINK_IRQ_MASK; - omap_mcpdm_write(MCPDM_IRQENABLE_CLR, irq_mask); + /* Disable irq request generation */ + irq_mask |= uplink->irq_mask & MCPDM_UPLINK_IRQ_MASK; + omap_mcpdm_write(MCPDM_IRQENABLE_CLR, irq_mask); - /* Disable DMA request generation */ - omap_mcpdm_write(MCPDM_DMAENABLE_CLR, DMA_UP_ENABLE); + /* Disable DMA request generation */ + omap_mcpdm_write(MCPDM_DMAENABLE_CLR, DMA_UP_ENABLE); - /* Clear Downlink channels */ - mcpdm->up_channels = 0; + /* Clear Downlink channels */ + mcpdm->up_channels = 0; - mcpdm->uplink = NULL; + mcpdm->uplink = NULL; - return 0; + return 0; } /* @@ -253,124 +253,124 @@ int omap_mcpdm_capture_close(struct omap_mcpdm_link *uplink) */ int omap_mcpdm_playback_close(struct omap_mcpdm_link *downlink) { - int irq_mask = 0; + int irq_mask = 0; - if (!downlink) - return -EINVAL; + if (!downlink) + return -EINVAL; - /* Disable irq request generation */ - irq_mask |= downlink->irq_mask & MCPDM_DOWNLINK_IRQ_MASK; - omap_mcpdm_write(MCPDM_IRQENABLE_CLR, irq_mask); + /* Disable irq request generation */ + irq_mask |= downlink->irq_mask & MCPDM_DOWNLINK_IRQ_MASK; + omap_mcpdm_write(MCPDM_IRQENABLE_CLR, irq_mask); - /* Disable DMA request generation */ - omap_mcpdm_write(MCPDM_DMAENABLE_CLR, DMA_DN_ENABLE); + /* Disable DMA request generation */ + omap_mcpdm_write(MCPDM_DMAENABLE_CLR, DMA_DN_ENABLE); - /* clear Downlink channels */ - mcpdm->dn_channels = 0; + /* clear Downlink channels */ + mcpdm->dn_channels = 0; - mcpdm->downlink = NULL; + mcpdm->downlink = NULL; - return 0; + return 0; } static irqreturn_t omap_mcpdm_irq_handler(int irq, void *dev_id) { - struct omap_mcpdm *mcpdm_irq = dev_id; - int irq_status; - - irq_status = omap_mcpdm_read(MCPDM_IRQSTATUS); - - /* Acknowledge irq event */ - omap_mcpdm_write(MCPDM_IRQSTATUS, irq_status); - - if (irq & MCPDM_DN_IRQ_FULL) { - dev_err(mcpdm_irq->dev, "DN FIFO error %x\n", irq_status); - omap_mcpdm_reset_playback(1); - omap_mcpdm_playback_open(mcpdm_irq->downlink); - omap_mcpdm_reset_playback(0); - } - - if (irq & MCPDM_DN_IRQ_EMPTY) { - dev_err(mcpdm_irq->dev, "DN FIFO error %x\n", irq_status); - omap_mcpdm_reset_playback(1); - omap_mcpdm_playback_open(mcpdm_irq->downlink); - omap_mcpdm_reset_playback(0); - } - - if (irq & MCPDM_DN_IRQ) { - dev_dbg(mcpdm_irq->dev, "DN write request\n"); - } - - if (irq & MCPDM_UP_IRQ_FULL) { - dev_err(mcpdm_irq->dev, "UP FIFO error %x\n", irq_status); - omap_mcpdm_reset_capture(1); - omap_mcpdm_capture_open(mcpdm_irq->uplink); - omap_mcpdm_reset_capture(0); - } - - if (irq & MCPDM_UP_IRQ_EMPTY) { - dev_err(mcpdm_irq->dev, "UP FIFO error %x\n", irq_status); - omap_mcpdm_reset_capture(1); - omap_mcpdm_capture_open(mcpdm_irq->uplink); - omap_mcpdm_reset_capture(0); - } - - if (irq & MCPDM_UP_IRQ) { - dev_dbg(mcpdm_irq->dev, "UP write request\n"); - } - - return IRQ_HANDLED; + struct omap_mcpdm *mcpdm_irq = dev_id; + int irq_status; + + irq_status = omap_mcpdm_read(MCPDM_IRQSTATUS); + + /* Acknowledge irq event */ + omap_mcpdm_write(MCPDM_IRQSTATUS, irq_status); + + if (irq & MCPDM_DN_IRQ_FULL) { + dev_err(mcpdm_irq->dev, "DN FIFO error %x\n", irq_status); + omap_mcpdm_reset_playback(1); + omap_mcpdm_playback_open(mcpdm_irq->downlink); + omap_mcpdm_reset_playback(0); + } + + if (irq & MCPDM_DN_IRQ_EMPTY) { + dev_err(mcpdm_irq->dev, "DN FIFO error %x\n", irq_status); + omap_mcpdm_reset_playback(1); + omap_mcpdm_playback_open(mcpdm_irq->downlink); + omap_mcpdm_reset_playback(0); + } + + if (irq & MCPDM_DN_IRQ) { + dev_dbg(mcpdm_irq->dev, "DN write request\n"); + } + + if (irq & MCPDM_UP_IRQ_FULL) { + dev_err(mcpdm_irq->dev, "UP FIFO error %x\n", irq_status); + omap_mcpdm_reset_capture(1); + omap_mcpdm_capture_open(mcpdm_irq->uplink); + omap_mcpdm_reset_capture(0); + } + + if (irq & MCPDM_UP_IRQ_EMPTY) { + dev_err(mcpdm_irq->dev, "UP FIFO error %x\n", irq_status); + omap_mcpdm_reset_capture(1); + omap_mcpdm_capture_open(mcpdm_irq->uplink); + omap_mcpdm_reset_capture(0); + } + + if (irq & MCPDM_UP_IRQ) { + dev_dbg(mcpdm_irq->dev, "UP write request\n"); + } + + return IRQ_HANDLED; } int omap_mcpdm_request(void) { - int ret; + int ret; - clk_enable(mcpdm->clk); + clk_enable(mcpdm->clk); - spin_lock(&mcpdm->lock); + spin_lock(&mcpdm->lock); - if (!mcpdm->free) { - dev_err(mcpdm->dev, "McPDM interface is in use\n"); - spin_unlock(&mcpdm->lock); - ret = -EBUSY; - goto err; - } - mcpdm->free = 0; + if (!mcpdm->free) { + dev_err(mcpdm->dev, "McPDM interface is in use\n"); + spin_unlock(&mcpdm->lock); + ret = -EBUSY; + goto err; + } + mcpdm->free = 0; - spin_unlock(&mcpdm->lock); + spin_unlock(&mcpdm->lock); - /* Disable lines while request is ongoing */ - omap_mcpdm_write(MCPDM_CTRL, 0x00); + /* Disable lines while request is ongoing */ + omap_mcpdm_write(MCPDM_CTRL, 0x00); - ret = request_irq(mcpdm->irq, omap_mcpdm_irq_handler, - 0, "McPDM", (void *)mcpdm); - if (ret) { - dev_err(mcpdm->dev, "Request for McPDM IRQ failed\n"); - goto err; - } + ret = request_irq(mcpdm->irq, omap_mcpdm_irq_handler, + 0, "McPDM", (void *)mcpdm); + if (ret) { + dev_err(mcpdm->dev, "Request for McPDM IRQ failed\n"); + goto err; + } - return 0; + return 0; err: - clk_disable(mcpdm->clk); - return ret; + clk_disable(mcpdm->clk); + return ret; } void omap_mcpdm_free(void) { - spin_lock(&mcpdm->lock); - if (mcpdm->free) { - dev_err(mcpdm->dev, "McPDM interface is already free\n"); - spin_unlock(&mcpdm->lock); - return; - } - mcpdm->free = 1; - spin_unlock(&mcpdm->lock); - - clk_disable(mcpdm->clk); - - free_irq(mcpdm->irq, (void *)mcpdm); + spin_lock(&mcpdm->lock); + if (mcpdm->free) { + dev_err(mcpdm->dev, "McPDM interface is already free\n"); + spin_unlock(&mcpdm->lock); + return; + } + mcpdm->free = 1; + spin_unlock(&mcpdm->lock); + + clk_disable(mcpdm->clk); + + free_irq(mcpdm->irq, (void *)mcpdm); } /* Enable/disable DC offset cancelation for the analog @@ -378,108 +378,108 @@ void omap_mcpdm_free(void) */ int omap_mcpdm_set_offset(int offset1, int offset2) { - int offset; + int offset; - if ((offset1 > DN_OFST_MAX) || (offset2 > DN_OFST_MAX)) - return -EINVAL; + if ((offset1 > DN_OFST_MAX) || (offset2 > DN_OFST_MAX)) + return -EINVAL; - offset = (offset1 << DN_OFST_RX1) | (offset2 << DN_OFST_RX2); + offset = (offset1 << DN_OFST_RX1) | (offset2 << DN_OFST_RX2); - /* offset cancellation for channel 1 */ - if (offset1) - offset |= DN_OFST_RX1_EN; - else - offset &= ~DN_OFST_RX1_EN; + /* offset cancellation for channel 1 */ + if (offset1) + offset |= DN_OFST_RX1_EN; + else + offset &= ~DN_OFST_RX1_EN; - /* offset cancellation for channel 2 */ - if (offset2) - offset |= DN_OFST_RX2_EN; - else - offset &= ~DN_OFST_RX2_EN; + /* offset cancellation for channel 2 */ + if (offset2) + offset |= DN_OFST_RX2_EN; + else + offset &= ~DN_OFST_RX2_EN; - omap_mcpdm_write(MCPDM_DN_OFFSET, offset); + omap_mcpdm_write(MCPDM_DN_OFFSET, offset); - return 0; + return 0; } static int __devinit omap_mcpdm_probe(struct platform_device *pdev) { - struct resource *res; - int ret = 0; - - mcpdm = kzalloc(sizeof(struct omap_mcpdm), GFP_KERNEL); - if (!mcpdm) { - ret = -ENOMEM; - goto exit; - } - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (res == NULL) { - dev_err(&pdev->dev, "no resource\n"); - goto err_resource; - } - - spin_lock_init(&mcpdm->lock); - mcpdm->free = 1; - mcpdm->io_base = ioremap(res->start, resource_size(res)); - if (!mcpdm->io_base) { - ret = -ENOMEM; - goto err_resource; - } - - mcpdm->irq = platform_get_irq(pdev, 0); - - mcpdm->clk = clk_get(&pdev->dev, "pdm_ck"); - if (IS_ERR(mcpdm->clk)) { - ret = PTR_ERR(mcpdm->clk); - dev_err(&pdev->dev, "unable to get pdm_ck: %d\n", ret); - goto err_clk; - } - - mcpdm->dev = &pdev->dev; - platform_set_drvdata(pdev, mcpdm); - - return 0; + struct resource *res; + int ret = 0; + + mcpdm = kzalloc(sizeof(struct omap_mcpdm), GFP_KERNEL); + if (!mcpdm) { + ret = -ENOMEM; + goto exit; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, "no resource\n"); + goto err_resource; + } + + spin_lock_init(&mcpdm->lock); + mcpdm->free = 1; + mcpdm->io_base = ioremap(res->start, resource_size(res)); + if (!mcpdm->io_base) { + ret = -ENOMEM; + goto err_resource; + } + + mcpdm->irq = platform_get_irq(pdev, 0); + + mcpdm->clk = clk_get(&pdev->dev, "pdm_ck"); + if (IS_ERR(mcpdm->clk)) { + ret = PTR_ERR(mcpdm->clk); + dev_err(&pdev->dev, "unable to get pdm_ck: %d\n", ret); + goto err_clk; + } + + mcpdm->dev = &pdev->dev; + platform_set_drvdata(pdev, mcpdm); + + return 0; err_clk: - iounmap(mcpdm->io_base); + iounmap(mcpdm->io_base); err_resource: - kfree(mcpdm); + kfree(mcpdm); exit: - return ret; + return ret; } static int __devexit omap_mcpdm_remove(struct platform_device *pdev) { - struct omap_mcpdm *mcpdm_ptr = platform_get_drvdata(pdev); + struct omap_mcpdm *mcpdm_ptr = platform_get_drvdata(pdev); - platform_set_drvdata(pdev, NULL); + platform_set_drvdata(pdev, NULL); - clk_put(mcpdm_ptr->clk); + clk_put(mcpdm_ptr->clk); - iounmap(mcpdm_ptr->io_base); + iounmap(mcpdm_ptr->io_base); - mcpdm_ptr->clk = NULL; - mcpdm_ptr->free = 0; - mcpdm_ptr->dev = NULL; + mcpdm_ptr->clk = NULL; + mcpdm_ptr->free = 0; + mcpdm_ptr->dev = NULL; - kfree(mcpdm_ptr); + kfree(mcpdm_ptr); - return 0; + return 0; } static struct platform_driver omap_mcpdm_driver = { - .probe = omap_mcpdm_probe, - .remove = __devexit_p(omap_mcpdm_remove), - .driver = { - .name = "omap-mcpdm", - }, + .probe = omap_mcpdm_probe, + .remove = __devexit_p(omap_mcpdm_remove), + .driver = { + .name = "omap-mcpdm", + }, }; static struct platform_device *omap_mcpdm_device; static int __init omap_mcpdm_init(void) { - return platform_driver_register(&omap_mcpdm_driver); + return platform_driver_register(&omap_mcpdm_driver); } arch_initcall(omap_mcpdm_init); diff --git a/sound/soc/omap/omap-mcbsp.c b/sound/soc/omap/omap-mcbsp.c index 8ad9dc9..6f44cb4 100644 --- a/sound/soc/omap/omap-mcbsp.c +++ b/sound/soc/omap/omap-mcbsp.c @@ -256,6 +256,31 @@ static int omap_mcbsp_dai_trigger(struct snd_pcm_substream *substream, int cmd, return err; } +static snd_pcm_sframes_t omap_mcbsp_dai_delay( + 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 omap_mcbsp_data *mcbsp_data = to_mcbsp(cpu_dai->private_data); + u16 fifo_use; + snd_pcm_sframes_t delay; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + fifo_use = omap_mcbsp_get_tx_delay(mcbsp_data->bus_id); + else + fifo_use = omap_mcbsp_get_rx_delay(mcbsp_data->bus_id); + + /* + * Divide the used locations with the channel count to get the + * FIFO usage in samples (don't care about partial samples in the + * buffer). + */ + delay = fifo_use / substream->runtime->channels; + + return delay; +} + static int omap_mcbsp_dai_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) @@ -295,8 +320,18 @@ static int omap_mcbsp_dai_hw_params(struct snd_pcm_substream *substream, 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; - omap_mcbsp_dai_dma_params[id][substream->stream].data_type = - OMAP_DMA_DATA_TYPE_S16; + 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; + break; + case SNDRV_PCM_FORMAT_S32_LE: + omap_mcbsp_dai_dma_params[id][substream->stream].data_type = + OMAP_DMA_DATA_TYPE_S32; + break; + default: + return -EINVAL; + } snd_soc_dai_set_dma_data(cpu_dai, substream, &omap_mcbsp_dai_dma_params[id][substream->stream]); @@ -308,7 +343,8 @@ static int omap_mcbsp_dai_hw_params(struct snd_pcm_substream *substream, format = mcbsp_data->fmt & SND_SOC_DAIFMT_FORMAT_MASK; wpf = channels = params_channels(params); - if (channels == 2 && format == SND_SOC_DAIFMT_I2S) { + if (channels == 2 && (format == SND_SOC_DAIFMT_I2S || + format == SND_SOC_DAIFMT_LEFT_J)) { /* Use dual-phase frames */ regs->rcr2 |= RPHASE; regs->xcr2 |= XPHASE; @@ -330,6 +366,14 @@ static int omap_mcbsp_dai_hw_params(struct snd_pcm_substream *substream, regs->xcr2 |= XWDLEN2(OMAP_MCBSP_WORD_16); regs->xcr1 |= XWDLEN1(OMAP_MCBSP_WORD_16); 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); + regs->xcr1 |= XWDLEN1(OMAP_MCBSP_WORD_32); + break; default: /* Unsupported PCM format */ return -EINVAL; @@ -353,6 +397,7 @@ static int omap_mcbsp_dai_hw_params(struct snd_pcm_substream *substream, /* Set FS period and length in terms of bit clock periods */ switch (format) { case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_LEFT_J: regs->srgr2 |= FPER(framesize - 1); regs->srgr1 |= FWID((framesize >> 1) - 1); break; @@ -404,6 +449,14 @@ static int omap_mcbsp_dai_set_dai_fmt(struct snd_soc_dai *cpu_dai, regs->rcr2 |= RDATDLY(1); regs->xcr2 |= XDATDLY(1); break; + case SND_SOC_DAIFMT_LEFT_J: + /* 0-bit data delay */ + regs->rcr2 |= RDATDLY(0); + regs->xcr2 |= XDATDLY(0); + regs->spcr1 |= RJUST(2); + /* Invert FS polarity configuration */ + temp_fmt ^= SND_SOC_DAIFMT_NB_IF; + break; case SND_SOC_DAIFMT_DSP_A: /* 1-bit data delay */ regs->rcr2 |= RDATDLY(1); @@ -609,6 +662,7 @@ static struct snd_soc_dai_ops omap_mcbsp_dai_ops = { .startup = omap_mcbsp_dai_startup, .shutdown = omap_mcbsp_dai_shutdown, .trigger = omap_mcbsp_dai_trigger, + .delay = omap_mcbsp_dai_delay, .hw_params = omap_mcbsp_dai_hw_params, .set_fmt = omap_mcbsp_dai_set_dai_fmt, .set_clkdiv = omap_mcbsp_dai_set_clkdiv, @@ -623,13 +677,15 @@ static struct snd_soc_dai_ops omap_mcbsp_dai_ops = { .channels_min = 1, \ .channels_max = 16, \ .rates = OMAP_MCBSP_RATES, \ - .formats = SNDRV_PCM_FMTBIT_S16_LE, \ + .formats = SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S32_LE, \ }, \ .capture = { \ .channels_min = 1, \ .channels_max = 16, \ .rates = OMAP_MCBSP_RATES, \ - .formats = SNDRV_PCM_FMTBIT_S16_LE, \ + .formats = SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S32_LE, \ }, \ .ops = &omap_mcbsp_dai_ops, \ .private_data = &mcbsp_data[(link_id)].bus_id, \ diff --git a/sound/soc/omap/omap3pandora.c b/sound/soc/omap/omap3pandora.c index de10f76..87ce842 100644 --- a/sound/soc/omap/omap3pandora.c +++ b/sound/soc/omap/omap3pandora.c @@ -188,8 +188,6 @@ static int omap3pandora_out_init(struct snd_soc_codec *codec) int ret; /* All TWL4030 output pins are floating */ - snd_soc_dapm_nc_pin(codec, "OUTL"); - snd_soc_dapm_nc_pin(codec, "OUTR"); snd_soc_dapm_nc_pin(codec, "EARPIECE"); snd_soc_dapm_nc_pin(codec, "PREDRIVEL"); snd_soc_dapm_nc_pin(codec, "PREDRIVER"); diff --git a/sound/soc/omap/rx51.c b/sound/soc/omap/rx51.c new file mode 100644 index 0000000..47d831e --- /dev/null +++ b/sound/soc/omap/rx51.c @@ -0,0 +1,294 @@ +/* + * rx51.c -- SoC audio for Nokia RX-51 + * + * Copyright (C) 2008 - 2009 Nokia Corporation + * + * Contact: Peter Ujfalusi <peter.ujfalusi@nokia.com> + * Eduardo Valentin <eduardo.valentin@nokia.com> + * Jarkko Nikula <jhnikula@gmail.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as 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. + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include <linux/delay.h> +#include <linux/gpio.h> +#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 "omap-mcbsp.h" +#include "omap-pcm.h" +#include "../codecs/tlv320aic3x.h" + +/* + * 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) + +static int rx51_spk_func; +static int rx51_dmic_func; + +static void rx51_ext_control(struct snd_soc_codec *codec) +{ + if (rx51_spk_func) + snd_soc_dapm_enable_pin(codec, "Ext Spk"); + else + snd_soc_dapm_disable_pin(codec, "Ext Spk"); + if (rx51_dmic_func) + snd_soc_dapm_enable_pin(codec, "DMic"); + else + snd_soc_dapm_disable_pin(codec, "DMic"); + + snd_soc_dapm_sync(codec); +} + +static int rx51_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->socdev->card->codec; + + snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_CHANNELS, 2, 2); + rx51_ext_control(codec); + + return 0; +} + +static int rx51_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; + + /* Set codec DAI configuration */ + err = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_IB_NF | + SND_SOC_DAIFMT_CBM_CFM); + if (err < 0) + return err; + + /* Set cpu DAI configuration */ + err = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_IB_NF | + SND_SOC_DAIFMT_CBM_CFM); + if (err < 0) + return err; + + /* Set the codec system clock for DAC and ADC */ + return snd_soc_dai_set_sysclk(codec_dai, 0, 19200000, + SND_SOC_CLOCK_IN); +} + +static struct snd_soc_ops rx51_ops = { + .startup = rx51_startup, + .hw_params = rx51_hw_params, +}; + +static int rx51_get_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = rx51_spk_func; + + return 0; +} + +static int rx51_set_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + if (rx51_spk_func == ucontrol->value.integer.value[0]) + return 0; + + rx51_spk_func = ucontrol->value.integer.value[0]; + rx51_ext_control(codec); + + return 1; +} + +static int rx51_spk_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + if (SND_SOC_DAPM_EVENT_ON(event)) + gpio_set_value(RX51_SPEAKER_AMP_TWL_GPIO, 1); + else + gpio_set_value(RX51_SPEAKER_AMP_TWL_GPIO, 0); + + return 0; +} + +static int rx51_get_input(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = rx51_dmic_func; + + return 0; +} + +static int rx51_set_input(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + if (rx51_dmic_func == ucontrol->value.integer.value[0]) + return 0; + + rx51_dmic_func = ucontrol->value.integer.value[0]; + rx51_ext_control(codec); + + return 1; +} + +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), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + {"Ext Spk", NULL, "HPLOUT"}, + {"Ext Spk", NULL, "HPROUT"}, + + {"DMic Rate 64", NULL, "Mic Bias 2V"}, + {"Mic Bias 2V", NULL, "DMic"}, +}; + +static const char *spk_function[] = {"Off", "On"}; +static const char *input_function[] = {"ADC", "Digital Mic"}; + +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), +}; + +static const struct snd_kcontrol_new aic34_rx51_controls[] = { + SOC_ENUM_EXT("Speaker Function", rx51_enum[0], + rx51_get_spk, rx51_set_spk), + SOC_ENUM_EXT("Input Select", rx51_enum[1], + rx51_get_input, rx51_set_input), +}; + +static int rx51_aic34_init(struct snd_soc_codec *codec) +{ + int err; + + /* Set up NC codec pins */ + snd_soc_dapm_nc_pin(codec, "MIC3L"); + snd_soc_dapm_nc_pin(codec, "MIC3R"); + snd_soc_dapm_nc_pin(codec, "LINE1R"); + + /* Add RX-51 specific controls */ + err = snd_soc_add_controls(codec, aic34_rx51_controls, + ARRAY_SIZE(aic34_rx51_controls)); + if (err < 0) + return err; + + /* Add RX-51 specific widgets */ + snd_soc_dapm_new_controls(codec, aic34_dapm_widgets, + ARRAY_SIZE(aic34_dapm_widgets)); + + /* Set up RX-51 specific audio path audio_map */ + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_sync(codec); + + return 0; +} + +/* Digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link rx51_dai[] = { + { + .name = "TLV320AIC34", + .stream_name = "AIC34", + .cpu_dai = &omap_mcbsp_dai[0], + .codec_dai = &aic3x_dai, + .init = rx51_aic34_init, + .ops = &rx51_ops, + }, +}; + +/* Audio private data */ +static struct aic3x_setup_data rx51_aic34_setup = { + .gpio_func[0] = AIC3X_GPIO1_FUNC_DISABLED, + .gpio_func[1] = AIC3X_GPIO2_FUNC_DIGITAL_MIC_INPUT, +}; + +/* Audio card */ +static struct snd_soc_card rx51_sound_card = { + .name = "RX-51", + .dai_link = rx51_dai, + .num_links = ARRAY_SIZE(rx51_dai), + .platform = &omap_soc_platform, +}; + +/* Audio subsystem */ +static struct snd_soc_device rx51_snd_devdata = { + .card = &rx51_sound_card, + .codec_dev = &soc_codec_dev_aic3x, + .codec_data = &rx51_aic34_setup, +}; + +static struct platform_device *rx51_snd_device; + +static int __init rx51_soc_init(void) +{ + int err; + + if (!machine_is_nokia_rx51()) + return -ENODEV; + + rx51_snd_device = platform_device_alloc("soc-audio", -1); + if (!rx51_snd_device) { + err = -ENOMEM; + goto err1; + } + + platform_set_drvdata(rx51_snd_device, &rx51_snd_devdata); + rx51_snd_devdata.dev = &rx51_snd_device->dev; + *(unsigned int *)rx51_dai[0].cpu_dai->private_data = 1; /* McBSP2 */ + + err = platform_device_add(rx51_snd_device); + if (err) + goto err2; + + return 0; +err2: + platform_device_put(rx51_snd_device); +err1: + + return err; +} + +static void __exit rx51_soc_exit(void) +{ + platform_device_unregister(rx51_snd_device); +} + +module_init(rx51_soc_init); +module_exit(rx51_soc_exit); + +MODULE_AUTHOR("Nokia Corporation"); +MODULE_DESCRIPTION("ALSA SoC Nokia RX-51"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/omap/sdp4430.c b/sound/soc/omap/sdp4430.c new file mode 100644 index 0000000..4ebbde6 --- /dev/null +++ b/sound/soc/omap/sdp4430.c @@ -0,0 +1,233 @@ +/* + * sdp4430.c -- SoC audio for TI OMAP4430 SDP + * + * Author: Misael Lopez Cruz <x0052729@ti.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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include <linux/clk.h> +#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 <plat/hardware.h> +#include <plat/mux.h> + +#include "mcpdm.h" +#include "omap-mcpdm.h" +#include "omap-pcm.h" +#include "../codecs/twl6040.h" + +static int twl6040_power_mode; + +static int sdp4430_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; + int clk_id, freq; + int ret; + + if (twl6040_power_mode) { + clk_id = TWL6040_SYSCLK_SEL_HPPLL; + freq = 38400000; + } else { + clk_id = TWL6040_SYSCLK_SEL_LPPLL; + freq = 32768; + } + + /* set the codec mclk */ + ret = snd_soc_dai_set_sysclk(codec_dai, clk_id, freq, + SND_SOC_CLOCK_IN); + if (ret) { + printk(KERN_ERR "can't set codec system clock\n"); + return ret; + } +} + +static struct snd_soc_ops sdp4430_ops = { + .hw_params = sdp4430_hw_params, +}; + +static int sdp4430_get_power_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = twl6040_power_mode; + return 0; +} + +static int sdp4430_set_power_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + if (twl6040_power_mode == ucontrol->value.integer.value[0]) + return 0; + + twl6040_power_mode = ucontrol->value.integer.value[0]; + + return 1; +} + +static const char *power_texts[] = {"Low-Power", "High-Performance"}; + +static const struct soc_enum sdp4430_enum[] = { + SOC_ENUM_SINGLE_EXT(2, power_texts), +}; + +static const struct snd_kcontrol_new sdp4430_controls[] = { + SOC_ENUM_EXT("TWL6040 Power Mode", sdp4430_enum[0], + sdp4430_get_power_mode, sdp4430_set_power_mode), +}; + +/* SDP4430 machine DAPM */ +static const struct snd_soc_dapm_widget sdp4430_twl6040_dapm_widgets[] = { + SND_SOC_DAPM_MIC("Ext Mic", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_HP("Headset Stereophone", NULL), + SND_SOC_DAPM_SPK("Earphone Spk", NULL), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* External Mics: MAINMIC, SUBMIC with bias*/ + {"MAINMIC", NULL, "Main Mic Bias"}, + {"SUBMIC", NULL, "Main Mic Bias"}, + {"Main Mic Bias", NULL, "Ext Mic"}, + + /* External Speakers: HFL, HFR */ + {"Ext Spk", NULL, "HFL"}, + {"Ext Spk", NULL, "HFR"}, + + /* Headset Mic: HSMIC with bias */ + {"HSMIC", NULL, "Headset Mic Bias"}, + {"Headset Mic Bias", NULL, "Headset Mic"}, + + /* Headset Stereophone (Headphone): HSOL, HSOR */ + {"Headset Stereophone", NULL, "HSOL"}, + {"Headset Stereophone", NULL, "HSOR"}, + + /* Earphone speaker */ + {"Earphone Spk", NULL, "EP"}, +}; + +static int sdp4430_twl6040_init(struct snd_soc_codec *codec) +{ + int ret; + + /* Add SDP4430 specific controls */ + ret = snd_soc_add_controls(codec, sdp4430_controls, + ARRAY_SIZE(sdp4430_controls)); + if (ret) + return ret; + + /* Add SDP4430 specific widgets */ + ret = snd_soc_dapm_new_controls(codec, sdp4430_twl6040_dapm_widgets, + ARRAY_SIZE(sdp4430_twl6040_dapm_widgets)); + if (ret) + return ret; + + /* Set up SDP4430 specific audio path audio_map */ + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + /* SDP4430 connected pins */ + snd_soc_dapm_enable_pin(codec, "Ext Mic"); + snd_soc_dapm_enable_pin(codec, "Ext Spk"); + snd_soc_dapm_enable_pin(codec, "Headset Mic"); + snd_soc_dapm_enable_pin(codec, "Headset Stereophone"); + + /* TWL6040 not connected pins */ + snd_soc_dapm_nc_pin(codec, "AFML"); + snd_soc_dapm_nc_pin(codec, "AFMR"); + + ret = snd_soc_dapm_sync(codec); + + return ret; +} + +/* Digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link sdp4430_dai = { + .name = "TWL6040", + .stream_name = "TWL6040", + .cpu_dai = &omap_mcpdm_dai, + .codec_dai = &twl6040_dai, + .init = sdp4430_twl6040_init, + .ops = &sdp4430_ops, +}; + +/* Audio machine driver */ +static struct snd_soc_card snd_soc_sdp4430 = { + .name = "SDP4430", + .platform = &omap_soc_platform, + .dai_link = &sdp4430_dai, + .num_links = 1, +}; + +/* Audio subsystem */ +static struct snd_soc_device sdp4430_snd_devdata = { + .card = &snd_soc_sdp4430, + .codec_dev = &soc_codec_dev_twl6040, +}; + +static struct platform_device *sdp4430_snd_device; + +static int __init sdp4430_soc_init(void) +{ + int ret; + + if (!machine_is_omap_4430sdp()) { + pr_debug("Not SDP4430!\n"); + return -ENODEV; + } + printk(KERN_INFO "SDP4430 SoC init\n"); + + sdp4430_snd_device = platform_device_alloc("soc-audio", -1); + if (!sdp4430_snd_device) { + printk(KERN_ERR "Platform device allocation failed\n"); + return -ENOMEM; + } + + platform_set_drvdata(sdp4430_snd_device, &sdp4430_snd_devdata); + sdp4430_snd_devdata.dev = &sdp4430_snd_device->dev; + + ret = platform_device_add(sdp4430_snd_device); + if (ret) + goto err; + + /* Codec starts in HP mode */ + twl6040_power_mode = 1; + + return 0; + +err: + printk(KERN_ERR "Unable to add platform device\n"); + platform_device_put(sdp4430_snd_device); + return ret; +} +module_init(sdp4430_soc_init); + +static void __exit sdp4430_soc_exit(void) +{ + platform_device_unregister(sdp4430_snd_device); +} +module_exit(sdp4430_soc_exit); + +MODULE_AUTHOR("Misael Lopez Cruz <x0052729@ti.com>"); +MODULE_DESCRIPTION("ALSA SoC SDP4430"); +MODULE_LICENSE("GPL"); + diff --git a/sound/soc/omap/zoom2.c b/sound/soc/omap/zoom2.c index f90a2ac..50a94ee7 100644 --- a/sound/soc/omap/zoom2.c +++ b/sound/soc/omap/zoom2.c @@ -181,9 +181,6 @@ static int zoom2_twl4030_init(struct snd_soc_codec *codec) snd_soc_dapm_nc_pin(codec, "CARKITMIC"); snd_soc_dapm_nc_pin(codec, "DIGIMIC0"); snd_soc_dapm_nc_pin(codec, "DIGIMIC1"); - - snd_soc_dapm_nc_pin(codec, "OUTL"); - snd_soc_dapm_nc_pin(codec, "OUTR"); snd_soc_dapm_nc_pin(codec, "EARPIECE"); snd_soc_dapm_nc_pin(codec, "PREDRIVEL"); snd_soc_dapm_nc_pin(codec, "PREDRIVER"); diff --git a/sound/soc/pxa/Kconfig b/sound/soc/pxa/Kconfig index 376e14a..495a36f 100644 --- a/sound/soc/pxa/Kconfig +++ b/sound/soc/pxa/Kconfig @@ -42,6 +42,14 @@ config SND_PXA2XX_SOC_SPITZ Say Y if you want to add support for SoC audio on Sharp Zaurus SL-Cxx00 models (Spitz, Borzoi and Akita). +config SND_PXA2XX_SOC_Z2 + tristate "SoC Audio support for Zipit Z2" + depends on SND_PXA2XX_SOC && MACH_ZIPIT2 + select SND_PXA2XX_SOC_I2S + select SND_SOC_WM8750 + help + Say Y if you want to add support for SoC audio on Zipit Z2. + config SND_PXA2XX_SOC_POODLE tristate "SoC Audio support for Poodle" depends on SND_PXA2XX_SOC && MACH_POODLE diff --git a/sound/soc/pxa/Makefile b/sound/soc/pxa/Makefile index f3e08fd..caa03d8 100644 --- a/sound/soc/pxa/Makefile +++ b/sound/soc/pxa/Makefile @@ -22,6 +22,7 @@ snd-soc-palm27x-objs := palm27x.o snd-soc-zylonite-objs := zylonite.o snd-soc-magician-objs := magician.o snd-soc-mioa701-objs := mioa701_wm9713.o +snd-soc-z2-objs := z2.o snd-soc-imote2-objs := imote2.o snd-soc-raumfeld-objs := raumfeld.o @@ -36,6 +37,7 @@ obj-$(CONFIG_SND_PXA2XX_SOC_EM_X270) += snd-soc-em-x270.o obj-$(CONFIG_SND_PXA2XX_SOC_PALM27X) += snd-soc-palm27x.o obj-$(CONFIG_SND_PXA2XX_SOC_MAGICIAN) += snd-soc-magician.o obj-$(CONFIG_SND_PXA2XX_SOC_MIOA701) += snd-soc-mioa701.o +obj-$(CONFIG_SND_PXA2XX_SOC_Z2) += snd-soc-z2.o obj-$(CONFIG_SND_SOC_ZYLONITE) += snd-soc-zylonite.o obj-$(CONFIG_SND_PXA2XX_SOC_IMOTE2) += snd-soc-imote2.o obj-$(CONFIG_SND_SOC_RAUMFELD) += snd-soc-raumfeld.o diff --git a/sound/soc/pxa/spitz.c b/sound/soc/pxa/spitz.c index c4cd2ac..1941a35 100644 --- a/sound/soc/pxa/spitz.c +++ b/sound/soc/pxa/spitz.c @@ -322,19 +322,44 @@ static struct snd_soc_card snd_soc_spitz = { .num_links = 1, }; -/* spitz audio private data */ -static struct wm8750_setup_data spitz_wm8750_setup = { - .i2c_bus = 0, - .i2c_address = 0x1b, -}; - /* spitz audio subsystem */ static struct snd_soc_device spitz_snd_devdata = { .card = &snd_soc_spitz, .codec_dev = &soc_codec_dev_wm8750, - .codec_data = &spitz_wm8750_setup, }; +/* + * FIXME: This is a temporary bodge to avoid cross-tree merge issues. + * New drivers should register the wm8750 I2C device in the machine + * setup code (under arch/arm for ARM systems). + */ +static int wm8750_i2c_register(void) +{ + struct i2c_board_info info; + struct i2c_adapter *adapter; + struct i2c_client *client; + + memset(&info, 0, sizeof(struct i2c_board_info)); + info.addr = 0x1b; + strlcpy(info.type, "wm8750", I2C_NAME_SIZE); + + adapter = i2c_get_adapter(0); + if (!adapter) { + printk(KERN_ERR "can't get i2c adapter 0\n"); + return -ENODEV; + } + + client = i2c_new_device(adapter, &info); + i2c_put_adapter(adapter); + if (!client) { + printk(KERN_ERR "can't add i2c device at 0x%x\n", + (unsigned int)info.addr); + return -ENODEV; + } + + return 0; +} + static struct platform_device *spitz_snd_device; static int __init spitz_init(void) @@ -344,6 +369,10 @@ static int __init spitz_init(void) if (!(machine_is_spitz() || machine_is_borzoi() || machine_is_akita())) return -ENODEV; + ret = wm8750_i2c_setup(); + if (ret != 0) + return ret; + spitz_snd_device = platform_device_alloc("soc-audio", -1); if (!spitz_snd_device) return -ENOMEM; diff --git a/sound/soc/pxa/z2.c b/sound/soc/pxa/z2.c new file mode 100644 index 0000000..4e4d2fa --- /dev/null +++ b/sound/soc/pxa/z2.c @@ -0,0 +1,246 @@ +/* + * linux/sound/soc/pxa/z2.c + * + * SoC Audio driver for Aeronix Zipit Z2 + * + * Copyright (C) 2009 Ken McGuire <kenm@desertweyr.com> + * Copyright (C) 2010 Marek Vasut <marek.vasut@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/gpio.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/jack.h> + +#include <asm/mach-types.h> +#include <mach/hardware.h> +#include <mach/audio.h> +#include <mach/z2.h> + +#include "../codecs/wm8750.h" +#include "pxa2xx-pcm.h" +#include "pxa2xx-i2s.h" + +static struct snd_soc_card snd_soc_z2; + +static int z2_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; + unsigned int clk = 0; + int ret = 0; + + switch (params_rate(params)) { + case 8000: + case 16000: + case 48000: + case 96000: + clk = 12288000; + break; + case 11025: + case 22050: + case 44100: + 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 the I2S system clock as input (unused) */ + ret = snd_soc_dai_set_sysclk(cpu_dai, PXA2XX_I2S_SYSCLK, 0, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + return 0; +} + +static struct snd_soc_jack hs_jack; + +/* Headset jack detection DAPM pins */ +static struct snd_soc_jack_pin hs_jack_pins[] = { + { + .pin = "Mic Jack", + .mask = SND_JACK_MICROPHONE, + }, + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, +}; + +/* Headset jack detection gpios */ +static struct snd_soc_jack_gpio hs_jack_gpios[] = { + { + .gpio = GPIO37_ZIPITZ2_HEADSET_DETECT, + .name = "hsdet-gpio", + .report = SND_JACK_HEADSET, + .debounce_time = 200, + }, +}; + +/* z2 machine dapm widgets */ +static const struct snd_soc_dapm_widget wm8750_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), + + /* headset is a mic and mono headphone */ + SND_SOC_DAPM_HP("Headset Jack", NULL), +}; + +/* Z2 machine audio_map */ +static const struct snd_soc_dapm_route audio_map[] = { + + /* headphone connected to LOUT1, ROUT1 */ + {"Headphone Jack", NULL, "LOUT1"}, + {"Headphone Jack", NULL, "ROUT1"}, + + /* ext speaker connected to LOUT2, ROUT2 */ + {"Ext Spk", NULL , "ROUT2"}, + {"Ext Spk", NULL , "LOUT2"}, + + /* mic is connected to R input 2 - with bias */ + {"RINPUT2", NULL, "Mic Bias"}, + {"Mic Bias", NULL, "Mic Jack"}, +}; + +/* + * Logic for a wm8750 as connected on a Z2 Device + */ +static int z2_wm8750_init(struct snd_soc_codec *codec) +{ + int ret; + + /* NC codec pins */ + snd_soc_dapm_disable_pin(codec, "LINPUT3"); + snd_soc_dapm_disable_pin(codec, "RINPUT3"); + snd_soc_dapm_disable_pin(codec, "OUT3"); + snd_soc_dapm_disable_pin(codec, "MONO"); + + /* Add z2 specific widgets */ + snd_soc_dapm_new_controls(codec, wm8750_dapm_widgets, + ARRAY_SIZE(wm8750_dapm_widgets)); + + /* Set up z2 specific audio paths */ + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + ret = snd_soc_dapm_sync(codec); + if (ret) + goto err; + + /* Jack detection API stuff */ + ret = snd_soc_jack_new(&snd_soc_z2, "Headset Jack", SND_JACK_HEADSET, + &hs_jack); + if (ret) + goto err; + + ret = snd_soc_jack_add_pins(&hs_jack, ARRAY_SIZE(hs_jack_pins), + hs_jack_pins); + if (ret) + goto err; + + ret = snd_soc_jack_add_gpios(&hs_jack, ARRAY_SIZE(hs_jack_gpios), + hs_jack_gpios); + if (ret) + goto err; + + return 0; + +err: + return ret; +} + +static struct snd_soc_ops z2_ops = { + .hw_params = z2_hw_params, +}; + +/* z2 digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link z2_dai = { + .name = "wm8750", + .stream_name = "WM8750", + .cpu_dai = &pxa_i2s_dai, + .codec_dai = &wm8750_dai, + .init = z2_wm8750_init, + .ops = &z2_ops, +}; + +/* z2 audio machine driver */ +static struct snd_soc_card snd_soc_z2 = { + .name = "Z2", + .platform = &pxa2xx_soc_platform, + .dai_link = &z2_dai, + .num_links = 1, +}; + +/* z2 audio subsystem */ +static struct snd_soc_device z2_snd_devdata = { + .card = &snd_soc_z2, + .codec_dev = &soc_codec_dev_wm8750, +}; + +static struct platform_device *z2_snd_device; + +static int __init z2_init(void) +{ + int ret; + + if (!machine_is_zipit2()) + return -ENODEV; + + z2_snd_device = platform_device_alloc("soc-audio", -1); + if (!z2_snd_device) + return -ENOMEM; + + platform_set_drvdata(z2_snd_device, &z2_snd_devdata); + z2_snd_devdata.dev = &z2_snd_device->dev; + ret = platform_device_add(z2_snd_device); + + if (ret) + platform_device_put(z2_snd_device); + + return ret; +} + +static void __exit z2_exit(void) +{ + platform_device_unregister(z2_snd_device); +} + +module_init(z2_init); +module_exit(z2_exit); + +MODULE_AUTHOR("Ken McGuire <kenm@desertweyr.com>, " + "Marek Vasut <marek.vasut@gmail.com>"); +MODULE_DESCRIPTION("ALSA SoC ZipitZ2"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/s3c24xx/Kconfig b/sound/soc/s3c24xx/Kconfig index 15fe57e..2a7cc22 100644 --- a/sound/soc/s3c24xx/Kconfig +++ b/sound/soc/s3c24xx/Kconfig @@ -24,6 +24,11 @@ config SND_S3C64XX_SOC_I2S select SND_S3C_I2SV2_SOC select S3C64XX_DMA +config SND_S3C64XX_SOC_I2S_V4 + tristate + select SND_S3C_I2SV2_SOC + select S3C64XX_DMA + config SND_S3C_SOC_PCM tristate @@ -59,12 +64,11 @@ config SND_S3C24XX_SOC_JIVE_WM8750 config SND_S3C64XX_SOC_WM8580 tristate "SoC I2S Audio support for WM8580 on SMDK64XX" - depends on SND_S3C24XX_SOC && (MACH_SMDK6400 || MACH_SMDK6410) - depends on BROKEN + depends on SND_S3C24XX_SOC && MACH_SMDK6410 select SND_SOC_WM8580 - select SND_S3C64XX_SOC_I2S + select SND_S3C64XX_SOC_I2S_V4 help - Sat Y if you want to add support for SoC audio on the SMDK64XX. + Say Y if you want to add support for SoC audio on the SMDK6410. config SND_S3C24XX_SOC_SMDK2443_WM9710 tristate "SoC AC97 Audio support for SMDK2443 - WM9710" diff --git a/sound/soc/s3c24xx/Makefile b/sound/soc/s3c24xx/Makefile index df071a3..81d8dc5 100644 --- a/sound/soc/s3c24xx/Makefile +++ b/sound/soc/s3c24xx/Makefile @@ -4,6 +4,7 @@ snd-soc-s3c24xx-i2s-objs := s3c24xx-i2s.o snd-soc-s3c2412-i2s-objs := s3c2412-i2s.o snd-soc-s3c64xx-i2s-objs := s3c64xx-i2s.o snd-soc-s3c-ac97-objs := s3c-ac97.o +snd-soc-s3c64xx-i2s-v4-objs := s3c64xx-i2s-v4.o snd-soc-s3c-i2s-v2-objs := s3c-i2s-v2.o snd-soc-s3c-pcm-objs := s3c-pcm.o @@ -12,6 +13,7 @@ obj-$(CONFIG_SND_S3C24XX_SOC_I2S) += snd-soc-s3c24xx-i2s.o obj-$(CONFIG_SND_S3C_SOC_AC97) += snd-soc-s3c-ac97.o obj-$(CONFIG_SND_S3C2412_SOC_I2S) += snd-soc-s3c2412-i2s.o obj-$(CONFIG_SND_S3C64XX_SOC_I2S) += snd-soc-s3c64xx-i2s.o +obj-$(CONFIG_SND_S3C64XX_SOC_I2S_V4) += snd-soc-s3c64xx-i2s-v4.o obj-$(CONFIG_SND_S3C_I2SV2_SOC) += snd-soc-s3c-i2s-v2.o obj-$(CONFIG_SND_S3C_SOC_PCM) += snd-soc-s3c-pcm.o diff --git a/sound/soc/s3c24xx/jive_wm8750.c b/sound/soc/s3c24xx/jive_wm8750.c index 59dc2c6..8c108b1 100644 --- a/sound/soc/s3c24xx/jive_wm8750.c +++ b/sound/soc/s3c24xx/jive_wm8750.c @@ -70,7 +70,7 @@ static int jive_hw_params(struct snd_pcm_substream *substream, } s3c_i2sv2_iis_calc_rate(&div, NULL, params_rate(params), - s3c2412_get_iisclk()); + s3c_i2sv2_get_clock(cpu_dai)); /* set codec DAI configuration */ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | @@ -152,15 +152,10 @@ static struct snd_soc_card snd_soc_machine_jive = { .num_links = 1, }; -/* jive audio private data */ -static struct wm8750_setup_data jive_wm8750_setup = { -}; - /* jive audio subsystem */ static struct snd_soc_device jive_snd_devdata = { .card = &snd_soc_machine_jive, .codec_dev = &soc_codec_dev_wm8750, - .codec_data = &jive_wm8750_setup, }; static struct platform_device *jive_snd_device; diff --git a/sound/soc/s3c24xx/neo1973_gta02_wm8753.c b/sound/soc/s3c24xx/neo1973_gta02_wm8753.c index dea83d3..209c259 100644 --- a/sound/soc/s3c24xx/neo1973_gta02_wm8753.c +++ b/sound/soc/s3c24xx/neo1973_gta02_wm8753.c @@ -362,6 +362,14 @@ static int neo1973_gta02_wm8753_init(struct snd_soc_codec *codec) snd_soc_dapm_disable_pin(codec, "Handset Mic"); snd_soc_dapm_disable_pin(codec, "Handset Spk"); + /* allow audio paths from the GSM modem to run during suspend */ + snd_soc_dapm_ignore_suspend(codec, "Stereo Out"); + snd_soc_dapm_ignore_suspend(codec, "GSM Line Out"); + snd_soc_dapm_ignore_suspend(codec, "GSM Line In"); + snd_soc_dapm_ignore_suspend(codec, "Headset Mic"); + snd_soc_dapm_ignore_suspend(codec, "Handset Mic"); + snd_soc_dapm_ignore_suspend(codec, "Handset Spk"); + snd_soc_dapm_sync(codec); return 0; diff --git a/arch/arm/plat-samsung/include/plat/regs-s3c2412-iis.h b/sound/soc/s3c24xx/regs-i2s-v2.h index abf2fbc..5e5e568 100644 --- a/arch/arm/plat-samsung/include/plat/regs-s3c2412-iis.h +++ b/sound/soc/s3c24xx/regs-i2s-v2.h @@ -20,6 +20,24 @@ #define S3C2412_IISTXD (0x10) #define S3C2412_IISRXD (0x14) +#define S5PC1XX_IISFICS 0x18 +#define S5PC1XX_IISTXDS 0x1C + +#define S5PC1XX_IISCON_SW_RST (1 << 31) +#define S5PC1XX_IISCON_FRXOFSTATUS (1 << 26) +#define S5PC1XX_IISCON_FRXORINTEN (1 << 25) +#define S5PC1XX_IISCON_FTXSURSTAT (1 << 24) +#define S5PC1XX_IISCON_FTXSURINTEN (1 << 23) +#define S5PC1XX_IISCON_TXSDMAPAUSE (1 << 20) +#define S5PC1XX_IISCON_TXSDMACTIVE (1 << 18) + +#define S3C64XX_IISCON_FTXURSTATUS (1 << 17) +#define S3C64XX_IISCON_FTXURINTEN (1 << 16) +#define S3C64XX_IISCON_TXFIFO2_EMPTY (1 << 15) +#define S3C64XX_IISCON_TXFIFO1_EMPTY (1 << 14) +#define S3C64XX_IISCON_TXFIFO2_FULL (1 << 13) +#define S3C64XX_IISCON_TXFIFO1_FULL (1 << 12) + #define S3C2412_IISCON_LRINDEX (1 << 11) #define S3C2412_IISCON_TXFIFO_EMPTY (1 << 10) #define S3C2412_IISCON_RXFIFO_EMPTY (1 << 9) @@ -33,18 +51,30 @@ #define S3C2412_IISCON_RXDMA_ACTIVE (1 << 1) #define S3C2412_IISCON_IIS_ACTIVE (1 << 0) +#define S5PC1XX_IISMOD_OPCLK_CDCLK_OUT (0 << 30) +#define S5PC1XX_IISMOD_OPCLK_CDCLK_IN (1 << 30) +#define S5PC1XX_IISMOD_OPCLK_BCLK_OUT (2 << 30) +#define S5PC1XX_IISMOD_OPCLK_PCLK (3 << 30) +#define S5PC1XX_IISMOD_OPCLK_MASK (3 << 30) +#define S5PC1XX_IISMOD_TXS_IDMA (1 << 28) /* Sec_TXFIFO use I-DMA */ +#define S5PC1XX_IISMOD_BLCS_MASK 0x3 +#define S5PC1XX_IISMOD_BLCS_SHIFT 26 +#define S5PC1XX_IISMOD_BLCP_MASK 0x3 +#define S5PC1XX_IISMOD_BLCP_SHIFT 24 + +#define S3C64XX_IISMOD_C2DD_HHALF (1 << 21) /* Discard Higher-half */ +#define S3C64XX_IISMOD_C2DD_LHALF (1 << 20) /* Discard Lower-half */ +#define S3C64XX_IISMOD_C1DD_HHALF (1 << 19) +#define S3C64XX_IISMOD_C1DD_LHALF (1 << 18) +#define S3C64XX_IISMOD_DC2_EN (1 << 17) +#define S3C64XX_IISMOD_DC1_EN (1 << 16) #define S3C64XX_IISMOD_BLC_16BIT (0 << 13) #define S3C64XX_IISMOD_BLC_8BIT (1 << 13) #define S3C64XX_IISMOD_BLC_24BIT (2 << 13) #define S3C64XX_IISMOD_BLC_MASK (3 << 13) -#define S3C64XX_IISMOD_IMS_PCLK (0 << 10) -#define S3C64XX_IISMOD_IMS_SYSMUX (1 << 10) - -#define S3C2412_IISMOD_MASTER_INTERNAL (0 << 10) -#define S3C2412_IISMOD_MASTER_EXTERNAL (1 << 10) -#define S3C2412_IISMOD_SLAVE (2 << 10) -#define S3C2412_IISMOD_MASTER_MASK (3 << 10) +#define S3C2412_IISMOD_IMS_SYSMUX (1 << 10) +#define S3C2412_IISMOD_SLAVE (1 << 11) #define S3C2412_IISMOD_MODE_TXONLY (0 << 8) #define S3C2412_IISMOD_MODE_RXONLY (1 << 8) #define S3C2412_IISMOD_MODE_TXRX (2 << 8) @@ -71,12 +101,15 @@ #define S3C2412_IISPSR_PSREN (1 << 15) +#define S3C64XX_IISFIC_TX2COUNT(x) (((x) >> 24) & 0xf) +#define S3C64XX_IISFIC_TX1COUNT(x) (((x) >> 16) & 0xf) + #define S3C2412_IISFIC_TXFLUSH (1 << 15) #define S3C2412_IISFIC_RXFLUSH (1 << 7) #define S3C2412_IISFIC_TXCOUNT(x) (((x) >> 8) & 0xf) #define S3C2412_IISFIC_RXCOUNT(x) (((x) >> 0) & 0xf) - +#define S5PC1XX_IISFICS_TXFLUSH (1 << 15) +#define S5PC1XX_IISFICS_TXCOUNT(x) (((x) >> 8) & 0x7f) #endif /* __ASM_ARCH_REGS_S3C2412_IIS_H */ - diff --git a/sound/soc/s3c24xx/s3c-i2s-v2.c b/sound/soc/s3c24xx/s3c-i2s-v2.c index 8851594..13311c8 100644 --- a/sound/soc/s3c24xx/s3c-i2s-v2.c +++ b/sound/soc/s3c24xx/s3c-i2s-v2.c @@ -16,24 +16,17 @@ * option) any later version. */ -#include <linux/init.h> -#include <linux/module.h> -#include <linux/device.h> #include <linux/delay.h> #include <linux/clk.h> -#include <linux/kernel.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 <plat/regs-s3c2412-iis.h> - #include <mach/dma.h> +#include "regs-i2s-v2.h" #include "s3c-i2s-v2.h" #include "s3c-dma.h" @@ -272,35 +265,14 @@ static int s3c2412_i2s_set_fmt(struct snd_soc_dai *cpu_dai, iismod = readl(i2s->regs + S3C2412_IISMOD); pr_debug("hw_params r: IISMOD: %x \n", iismod); -#if defined(CONFIG_CPU_S3C2412) || defined(CONFIG_CPU_S3C2413) -#define IISMOD_MASTER_MASK S3C2412_IISMOD_MASTER_MASK -#define IISMOD_SLAVE S3C2412_IISMOD_SLAVE -#define IISMOD_MASTER S3C2412_IISMOD_MASTER_INTERNAL -#endif - -#if defined(CONFIG_PLAT_S3C64XX) -/* From Rev1.1 datasheet, we have two master and two slave modes: - * IMS[11:10]: - * 00 = master mode, fed from PCLK - * 01 = master mode, fed from CLKAUDIO - * 10 = slave mode, using PCLK - * 11 = slave mode, using I2SCLK - */ -#define IISMOD_MASTER_MASK (1 << 11) -#define IISMOD_SLAVE (1 << 11) -#define IISMOD_MASTER (0 << 11) -#endif - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { case SND_SOC_DAIFMT_CBM_CFM: i2s->master = 0; - iismod &= ~IISMOD_MASTER_MASK; - iismod |= IISMOD_SLAVE; + iismod |= S3C2412_IISMOD_SLAVE; break; case SND_SOC_DAIFMT_CBS_CFS: i2s->master = 1; - iismod &= ~IISMOD_MASTER_MASK; - iismod |= IISMOD_MASTER; + iismod &= ~S3C2412_IISMOD_SLAVE; break; default: pr_err("unknwon master/slave format\n"); @@ -332,7 +304,7 @@ static int s3c2412_i2s_set_fmt(struct snd_soc_dai *cpu_dai, return 0; } -static int s3c2412_i2s_hw_params(struct snd_pcm_substream *substream, +static int s3c_i2sv2_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *socdai) { @@ -355,37 +327,67 @@ static int s3c2412_i2s_hw_params(struct snd_pcm_substream *substream, iismod = readl(i2s->regs + S3C2412_IISMOD); pr_debug("%s: r: IISMOD: %x\n", __func__, iismod); -#if defined(CONFIG_CPU_S3C2412) || defined(CONFIG_CPU_S3C2413) + iismod &= ~S3C64XX_IISMOD_BLC_MASK; + /* Sample size */ switch (params_format(params)) { case SNDRV_PCM_FORMAT_S8: - iismod |= S3C2412_IISMOD_8BIT; + iismod |= S3C64XX_IISMOD_BLC_8BIT; break; case SNDRV_PCM_FORMAT_S16_LE: - iismod &= ~S3C2412_IISMOD_8BIT; + break; + case SNDRV_PCM_FORMAT_S24_LE: + iismod |= S3C64XX_IISMOD_BLC_24BIT; break; } -#endif -#ifdef CONFIG_PLAT_S3C64XX - iismod &= ~(S3C64XX_IISMOD_BLC_MASK | S3C2412_IISMOD_BCLK_MASK); - /* Sample size */ - switch (params_format(params)) { - case SNDRV_PCM_FORMAT_S8: - /* 8 bit sample, 16fs BCLK */ - iismod |= (S3C64XX_IISMOD_BLC_8BIT | S3C2412_IISMOD_BCLK_16FS); + writel(iismod, i2s->regs + S3C2412_IISMOD); + pr_debug("%s: w: IISMOD: %x\n", __func__, iismod); + + return 0; +} + +static int s3c_i2sv2_set_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + struct s3c_i2sv2_info *i2s = to_info(cpu_dai); + u32 iismod = readl(i2s->regs + S3C2412_IISMOD); + + pr_debug("Entered %s\n", __func__); + pr_debug("%s r: IISMOD: %x\n", __func__, iismod); + + switch (clk_id) { + case S3C_I2SV2_CLKSRC_PCLK: + iismod &= ~S3C2412_IISMOD_IMS_SYSMUX; break; - case SNDRV_PCM_FORMAT_S16_LE: - /* 16 bit sample, 32fs BCLK */ + + case S3C_I2SV2_CLKSRC_AUDIOBUS: + iismod |= S3C2412_IISMOD_IMS_SYSMUX; break; - case SNDRV_PCM_FORMAT_S24_LE: - /* 24 bit sample, 48fs BCLK */ - iismod |= (S3C64XX_IISMOD_BLC_24BIT | S3C2412_IISMOD_BCLK_48FS); + + case S3C_I2SV2_CLKSRC_CDCLK: + /* Error if controller doesn't have the CDCLKCON bit */ + if (!(i2s->feature & S3C_FEATURE_CDCLKCON)) + return -EINVAL; + + switch (dir) { + case SND_SOC_CLOCK_IN: + iismod |= S3C64XX_IISMOD_CDCLKCON; + break; + case SND_SOC_CLOCK_OUT: + iismod &= ~S3C64XX_IISMOD_CDCLKCON; + break; + default: + return -EINVAL; + } break; + + default: + return -EINVAL; } -#endif writel(iismod, i2s->regs + S3C2412_IISMOD); - pr_debug("%s: w: IISMOD: %x\n", __func__, iismod); + pr_debug("%s w: IISMOD: %x\n", __func__, iismod); + return 0; } @@ -472,29 +474,25 @@ static int s3c2412_i2s_set_clkdiv(struct snd_soc_dai *cpu_dai, switch (div_id) { case S3C_I2SV2_DIV_BCLK: - if (div > 3) { - /* convert value to bit field */ - - switch (div) { - case 16: - div = S3C2412_IISMOD_BCLK_16FS; - break; + switch (div) { + case 16: + div = S3C2412_IISMOD_BCLK_16FS; + break; - case 32: - div = S3C2412_IISMOD_BCLK_32FS; - break; + case 32: + div = S3C2412_IISMOD_BCLK_32FS; + break; - case 24: - div = S3C2412_IISMOD_BCLK_24FS; - break; + case 24: + div = S3C2412_IISMOD_BCLK_24FS; + break; - case 48: - div = S3C2412_IISMOD_BCLK_48FS; - break; + case 48: + div = S3C2412_IISMOD_BCLK_48FS; + break; - default: - return -EINVAL; - } + default: + return -EINVAL; } reg = readl(i2s->regs + S3C2412_IISMOD); @@ -505,29 +503,25 @@ static int s3c2412_i2s_set_clkdiv(struct snd_soc_dai *cpu_dai, break; case S3C_I2SV2_DIV_RCLK: - if (div > 3) { - /* convert value to bit field */ - - switch (div) { - case 256: - div = S3C2412_IISMOD_RCLK_256FS; - break; + switch (div) { + case 256: + div = S3C2412_IISMOD_RCLK_256FS; + break; - case 384: - div = S3C2412_IISMOD_RCLK_384FS; - break; + case 384: + div = S3C2412_IISMOD_RCLK_384FS; + break; - case 512: - div = S3C2412_IISMOD_RCLK_512FS; - break; + case 512: + div = S3C2412_IISMOD_RCLK_512FS; + break; - case 768: - div = S3C2412_IISMOD_RCLK_768FS; - break; + case 768: + div = S3C2412_IISMOD_RCLK_768FS; + break; - default: - return -EINVAL; - } + default: + return -EINVAL; } reg = readl(i2s->regs + S3C2412_IISMOD); @@ -553,6 +547,33 @@ static int s3c2412_i2s_set_clkdiv(struct snd_soc_dai *cpu_dai, return 0; } +static snd_pcm_sframes_t s3c2412_i2s_delay(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct s3c_i2sv2_info *i2s = to_info(dai); + u32 reg = readl(i2s->regs + S3C2412_IISFIC); + snd_pcm_sframes_t delay; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + delay = S3C2412_IISFIC_TXCOUNT(reg); + else + delay = S3C2412_IISFIC_RXCOUNT(reg); + + return delay; +} + +struct clk *s3c_i2sv2_get_clock(struct snd_soc_dai *cpu_dai) +{ + struct s3c_i2sv2_info *i2s = to_info(cpu_dai); + u32 iismod = readl(i2s->regs + S3C2412_IISMOD); + + if (iismod & S3C2412_IISMOD_IMS_SYSMUX) + return i2s->iis_cclk; + else + return i2s->iis_pclk; +} +EXPORT_SYMBOL_GPL(s3c_i2sv2_get_clock); + /* default table of all avaialable root fs divisors */ static unsigned int iis_fs_tab[] = { 256, 512, 384, 768 }; @@ -735,9 +756,15 @@ int s3c_i2sv2_register_dai(struct snd_soc_dai *dai) struct snd_soc_dai_ops *ops = dai->ops; ops->trigger = s3c2412_i2s_trigger; - ops->hw_params = s3c2412_i2s_hw_params; + if (!ops->hw_params) + ops->hw_params = s3c_i2sv2_hw_params; ops->set_fmt = s3c2412_i2s_set_fmt; ops->set_clkdiv = s3c2412_i2s_set_clkdiv; + ops->set_sysclk = s3c_i2sv2_set_sysclk; + + /* Allow overriding by (for example) IISv4 */ + if (!ops->delay) + ops->delay = s3c2412_i2s_delay; dai->suspend = s3c2412_i2s_suspend; dai->resume = s3c2412_i2s_resume; diff --git a/sound/soc/s3c24xx/s3c-i2s-v2.h b/sound/soc/s3c24xx/s3c-i2s-v2.h index ecf8eaa..766f43a 100644 --- a/sound/soc/s3c24xx/s3c-i2s-v2.h +++ b/sound/soc/s3c24xx/s3c-i2s-v2.h @@ -25,10 +25,20 @@ #define S3C_I2SV2_DIV_RCLK (2) #define S3C_I2SV2_DIV_PRESCALER (3) +#define S3C_I2SV2_CLKSRC_PCLK 0 +#define S3C_I2SV2_CLKSRC_AUDIOBUS 1 +#define S3C_I2SV2_CLKSRC_CDCLK 2 + +/* Set this flag for I2S controllers that have the bit IISMOD[12] + * bridge/break RCLK signal and external Xi2sCDCLK pin. + */ +#define S3C_FEATURE_CDCLKCON (1 << 0) + /** * struct s3c_i2sv2_info - S3C I2S-V2 information * @dev: The parent device passed to use from the probe. * @regs: The pointer to the device registe block. + * @feature: Set of bit-flags indicating features of the controller. * @master: True if the I2S core is the I2S bit clock master. * @dma_playback: DMA information for playback channel. * @dma_capture: DMA information for capture channel. @@ -43,9 +53,10 @@ struct s3c_i2sv2_info { struct device *dev; void __iomem *regs; + u32 feature; + struct clk *iis_pclk; struct clk *iis_cclk; - struct clk *iis_clk; unsigned char master; @@ -57,6 +68,8 @@ struct s3c_i2sv2_info { u32 suspend_iispsr; }; +extern struct clk *s3c_i2sv2_get_clock(struct snd_soc_dai *cpu_dai); + struct s3c_i2sv2_rate_calc { unsigned int clk_div; /* for prescaler */ unsigned int fs_div; /* for root frame clock */ diff --git a/sound/soc/s3c24xx/s3c2412-i2s.c b/sound/soc/s3c24xx/s3c2412-i2s.c index 359e593..709adef 100644 --- a/sound/soc/s3c24xx/s3c2412-i2s.c +++ b/sound/soc/s3c24xx/s3c2412-i2s.c @@ -32,12 +32,11 @@ #include <sound/soc.h> #include <mach/hardware.h> -#include <plat/regs-s3c2412-iis.h> - #include <mach/regs-gpio.h> #include <mach/dma.h> #include "s3c-dma.h" +#include "regs-i2s-v2.h" #include "s3c2412-i2s.h" #define S3C2412_I2S_DEBUG 0 @@ -66,44 +65,11 @@ static struct s3c_dma_params s3c2412_i2s_pcm_stereo_in = { static struct s3c_i2sv2_info s3c2412_i2s; -/* - * Set S3C2412 Clock source - */ -static int s3c2412_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, - int clk_id, unsigned int freq, int dir) +static inline struct s3c_i2sv2_info *to_info(struct snd_soc_dai *cpu_dai) { - u32 iismod = readl(s3c2412_i2s.regs + S3C2412_IISMOD); - - pr_debug("%s(%p, %d, %u, %d)\n", __func__, cpu_dai, clk_id, - freq, dir); - - switch (clk_id) { - case S3C2412_CLKSRC_PCLK: - s3c2412_i2s.master = 1; - iismod &= ~S3C2412_IISMOD_MASTER_MASK; - iismod |= S3C2412_IISMOD_MASTER_INTERNAL; - break; - case S3C2412_CLKSRC_I2SCLK: - s3c2412_i2s.master = 0; - iismod &= ~S3C2412_IISMOD_MASTER_MASK; - iismod |= S3C2412_IISMOD_MASTER_EXTERNAL; - break; - default: - return -EINVAL; - } - - writel(iismod, s3c2412_i2s.regs + S3C2412_IISMOD); - return 0; + return cpu_dai->private_data; } - -struct clk *s3c2412_get_iisclk(void) -{ - return s3c2412_i2s.iis_clk; -} -EXPORT_SYMBOL_GPL(s3c2412_get_iisclk); - - static int s3c2412_i2s_probe(struct platform_device *pdev, struct snd_soc_dai *dai) { @@ -142,13 +108,48 @@ static int s3c2412_i2s_probe(struct platform_device *pdev, return 0; } +static int s3c2412_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + struct s3c_i2sv2_info *i2s = to_info(cpu_dai); + struct s3c_dma_params *dma_data; + u32 iismod; + + pr_debug("Entered %s\n", __func__); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dma_data = i2s->dma_playback; + else + dma_data = i2s->dma_capture; + + snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data); + + iismod = readl(i2s->regs + S3C2412_IISMOD); + pr_debug("%s: r: IISMOD: %x\n", __func__, iismod); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + iismod |= S3C2412_IISMOD_8BIT; + break; + case SNDRV_PCM_FORMAT_S16_LE: + iismod &= ~S3C2412_IISMOD_8BIT; + break; + } + + writel(iismod, i2s->regs + S3C2412_IISMOD); + pr_debug("%s: w: IISMOD: %x\n", __func__, iismod); + + return 0; +} + #define S3C2412_I2S_RATES \ (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_16000 | \ SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) static struct snd_soc_dai_ops s3c2412_i2s_dai_ops = { - .set_sysclk = s3c2412_i2s_set_sysclk, + .hw_params = s3c2412_i2s_hw_params, }; struct snd_soc_dai s3c2412_i2s_dai = { diff --git a/sound/soc/s3c24xx/s3c2412-i2s.h b/sound/soc/s3c24xx/s3c2412-i2s.h index 92848e5..0b5686b 100644 --- a/sound/soc/s3c24xx/s3c2412-i2s.h +++ b/sound/soc/s3c24xx/s3c2412-i2s.h @@ -21,10 +21,8 @@ #define S3C2412_DIV_RCLK S3C_I2SV2_DIV_RCLK #define S3C2412_DIV_PRESCALER S3C_I2SV2_DIV_PRESCALER -#define S3C2412_CLKSRC_PCLK (0) -#define S3C2412_CLKSRC_I2SCLK (1) - -extern struct clk *s3c2412_get_iisclk(void); +#define S3C2412_CLKSRC_PCLK S3C_I2SV2_CLKSRC_PCLK +#define S3C2412_CLKSRC_I2SCLK S3C_I2SV2_CLKSRC_AUDIOBUS extern struct snd_soc_dai s3c2412_i2s_dai; diff --git a/sound/soc/s3c24xx/s3c64xx-i2s-v4.c b/sound/soc/s3c24xx/s3c64xx-i2s-v4.c new file mode 100644 index 0000000..06db130 --- /dev/null +++ b/sound/soc/s3c24xx/s3c64xx-i2s-v4.c @@ -0,0 +1,209 @@ +/* sound/soc/s3c24xx/s3c64xx-i2s-v4.c + * + * ALSA SoC Audio Layer - S3C64XX I2Sv4 driver + * Copyright (c) 2010 Samsung Electronics Co. Ltd + * Author: Jaswinder Singh <jassi.brar@samsung.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/clk.h> +#include <linux/gpio.h> +#include <linux/io.h> + +#include <sound/soc.h> +#include <sound/pcm_params.h> + +#include <mach/gpio-bank-c.h> +#include <mach/gpio-bank-h.h> +#include <plat/gpio-cfg.h> + +#include <mach/map.h> +#include <mach/dma.h> + +#include "s3c-dma.h" +#include "regs-i2s-v2.h" +#include "s3c64xx-i2s.h" + +static struct s3c2410_dma_client s3c64xx_dma_client_out = { + .name = "I2Sv4 PCM Stereo out" +}; + +static struct s3c2410_dma_client s3c64xx_dma_client_in = { + .name = "I2Sv4 PCM Stereo in" +}; + +static struct s3c_dma_params s3c64xx_i2sv4_pcm_stereo_out; +static struct s3c_dma_params s3c64xx_i2sv4_pcm_stereo_in; +static struct s3c_i2sv2_info s3c64xx_i2sv4; + +struct snd_soc_dai s3c64xx_i2s_v4_dai; +EXPORT_SYMBOL_GPL(s3c64xx_i2s_v4_dai); + +static inline struct s3c_i2sv2_info *to_info(struct snd_soc_dai *cpu_dai) +{ + return cpu_dai->private_data; +} + +static int s3c64xx_i2sv4_probe(struct platform_device *pdev, + struct snd_soc_dai *dai) +{ + /* configure GPIO for i2s port */ + s3c_gpio_cfgpin(S3C64XX_GPC(4), S3C64XX_GPC4_I2S_V40_DO0); + s3c_gpio_cfgpin(S3C64XX_GPC(5), S3C64XX_GPC5_I2S_V40_DO1); + s3c_gpio_cfgpin(S3C64XX_GPC(7), S3C64XX_GPC7_I2S_V40_DO2); + s3c_gpio_cfgpin(S3C64XX_GPH(6), S3C64XX_GPH6_I2S_V40_BCLK); + s3c_gpio_cfgpin(S3C64XX_GPH(7), S3C64XX_GPH7_I2S_V40_CDCLK); + s3c_gpio_cfgpin(S3C64XX_GPH(8), S3C64XX_GPH8_I2S_V40_LRCLK); + s3c_gpio_cfgpin(S3C64XX_GPH(9), S3C64XX_GPH9_I2S_V40_DI); + + return 0; +} + +static int s3c_i2sv4_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + struct s3c_i2sv2_info *i2s = to_info(cpu_dai); + struct s3c_dma_params *dma_data; + u32 iismod; + + dev_dbg(cpu_dai->dev, "Entered %s\n", __func__); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dma_data = i2s->dma_playback; + else + dma_data = i2s->dma_capture; + + snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data); + + iismod = readl(i2s->regs + S3C2412_IISMOD); + dev_dbg(cpu_dai->dev, "%s: r: IISMOD: %x\n", __func__, iismod); + + iismod &= ~S3C64XX_IISMOD_BLC_MASK; + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + iismod |= S3C64XX_IISMOD_BLC_8BIT; + break; + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S24_LE: + iismod |= S3C64XX_IISMOD_BLC_24BIT; + break; + } + + writel(iismod, i2s->regs + S3C2412_IISMOD); + dev_dbg(cpu_dai->dev, "%s: w: IISMOD: %x\n", __func__, iismod); + + return 0; +} + +static struct snd_soc_dai_ops s3c64xx_i2sv4_dai_ops = { + .hw_params = s3c_i2sv4_hw_params, +}; + +static __devinit int s3c64xx_i2sv4_dev_probe(struct platform_device *pdev) +{ + struct s3c_i2sv2_info *i2s; + struct snd_soc_dai *dai; + int ret; + + i2s = &s3c64xx_i2sv4; + dai = &s3c64xx_i2s_v4_dai; + + if (dai->dev) { + dev_dbg(dai->dev, "%s: \ + I2Sv4 instance already registered!\n", __func__); + return -EBUSY; + } + + dai->dev = &pdev->dev; + dai->name = "s3c64xx-i2s-v4"; + dai->id = 0; + dai->symmetric_rates = 1; + dai->playback.channels_min = 2; + dai->playback.channels_max = 2; + dai->playback.rates = S3C64XX_I2S_RATES; + dai->playback.formats = S3C64XX_I2S_FMTS; + dai->capture.channels_min = 2; + dai->capture.channels_max = 2; + dai->capture.rates = S3C64XX_I2S_RATES; + dai->capture.formats = S3C64XX_I2S_FMTS; + dai->probe = s3c64xx_i2sv4_probe; + dai->ops = &s3c64xx_i2sv4_dai_ops; + + i2s->feature |= S3C_FEATURE_CDCLKCON; + + i2s->dma_capture = &s3c64xx_i2sv4_pcm_stereo_in; + i2s->dma_playback = &s3c64xx_i2sv4_pcm_stereo_out; + + i2s->dma_capture->channel = DMACH_HSI_I2SV40_RX; + i2s->dma_capture->dma_addr = S3C64XX_PA_IISV4 + S3C2412_IISRXD; + i2s->dma_playback->channel = DMACH_HSI_I2SV40_TX; + i2s->dma_playback->dma_addr = S3C64XX_PA_IISV4 + S3C2412_IISTXD; + + i2s->dma_capture->client = &s3c64xx_dma_client_in; + i2s->dma_capture->dma_size = 4; + i2s->dma_playback->client = &s3c64xx_dma_client_out; + i2s->dma_playback->dma_size = 4; + + i2s->iis_cclk = clk_get(&pdev->dev, "audio-bus"); + if (IS_ERR(i2s->iis_cclk)) { + dev_err(&pdev->dev, "failed to get audio-bus\n"); + ret = PTR_ERR(i2s->iis_cclk); + goto err; + } + + clk_enable(i2s->iis_cclk); + + ret = s3c_i2sv2_probe(pdev, dai, i2s, 0); + if (ret) + goto err_clk; + + ret = s3c_i2sv2_register_dai(dai); + if (ret != 0) + goto err_i2sv2; + + return 0; + +err_i2sv2: + /* Not implemented for I2Sv2 core yet */ +err_clk: + clk_put(i2s->iis_cclk); +err: + return ret; +} + +static __devexit int s3c64xx_i2sv4_dev_remove(struct platform_device *pdev) +{ + dev_err(&pdev->dev, "Device removal not yet supported\n"); + return 0; +} + +static struct platform_driver s3c64xx_i2sv4_driver = { + .probe = s3c64xx_i2sv4_dev_probe, + .remove = s3c64xx_i2sv4_dev_remove, + .driver = { + .name = "s3c64xx-iis-v4", + .owner = THIS_MODULE, + }, +}; + +static int __init s3c64xx_i2sv4_init(void) +{ + return platform_driver_register(&s3c64xx_i2sv4_driver); +} +module_init(s3c64xx_i2sv4_init); + +static void __exit s3c64xx_i2sv4_exit(void) +{ + platform_driver_unregister(&s3c64xx_i2sv4_driver); +} +module_exit(s3c64xx_i2sv4_exit); + +/* Module information */ +MODULE_AUTHOR("Jaswinder Singh, <jassi.brar@samsung.com>"); +MODULE_DESCRIPTION("S3C64XX I2Sv4 SoC Interface"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/s3c24xx/s3c64xx-i2s.c b/sound/soc/s3c24xx/s3c64xx-i2s.c index a72c251..1d85cb8 100644 --- a/sound/soc/s3c24xx/s3c64xx-i2s.c +++ b/sound/soc/s3c24xx/s3c64xx-i2s.c @@ -12,16 +12,12 @@ * published by the Free Software Foundation. */ -#include <linux/init.h> -#include <linux/module.h> -#include <linux/device.h> #include <linux/clk.h> #include <linux/gpio.h> #include <linux/io.h> #include <sound/soc.h> -#include <plat/regs-s3c2412-iis.h> #include <mach/gpio-bank-d.h> #include <mach/gpio-bank-e.h> #include <plat/gpio-cfg.h> @@ -30,6 +26,7 @@ #include <mach/dma.h> #include "s3c-dma.h" +#include "regs-i2s-v2.h" #include "s3c64xx-i2s.h" /* The value should be set to maximum of the total number @@ -57,55 +54,6 @@ static inline struct s3c_i2sv2_info *to_info(struct snd_soc_dai *cpu_dai) return cpu_dai->private_data; } -static int s3c64xx_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, - int clk_id, unsigned int freq, int dir) -{ - struct s3c_i2sv2_info *i2s = to_info(cpu_dai); - u32 iismod = readl(i2s->regs + S3C2412_IISMOD); - - switch (clk_id) { - case S3C64XX_CLKSRC_PCLK: - iismod &= ~S3C64XX_IISMOD_IMS_SYSMUX; - break; - - case S3C64XX_CLKSRC_MUX: - iismod |= S3C64XX_IISMOD_IMS_SYSMUX; - break; - - case S3C64XX_CLKSRC_CDCLK: - switch (dir) { - case SND_SOC_CLOCK_IN: - iismod |= S3C64XX_IISMOD_CDCLKCON; - break; - case SND_SOC_CLOCK_OUT: - iismod &= ~S3C64XX_IISMOD_CDCLKCON; - break; - default: - return -EINVAL; - } - break; - - default: - return -EINVAL; - } - - writel(iismod, i2s->regs + S3C2412_IISMOD); - - return 0; -} - -struct clk *s3c64xx_i2s_get_clock(struct snd_soc_dai *dai) -{ - struct s3c_i2sv2_info *i2s = to_info(dai); - u32 iismod = readl(i2s->regs + S3C2412_IISMOD); - - if (iismod & S3C64XX_IISMOD_IMS_SYSMUX) - return i2s->iis_cclk; - else - return i2s->iis_pclk; -} -EXPORT_SYMBOL_GPL(s3c64xx_i2s_get_clock); - static int s3c64xx_i2s_probe(struct platform_device *pdev, struct snd_soc_dai *dai) { @@ -130,18 +78,7 @@ static int s3c64xx_i2s_probe(struct platform_device *pdev, } -#define S3C64XX_I2S_RATES \ - (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_16000 | \ - SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ - SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) - -#define S3C64XX_I2S_FMTS \ - (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE |\ - SNDRV_PCM_FMTBIT_S24_LE) - -static struct snd_soc_dai_ops s3c64xx_i2s_dai_ops = { - .set_sysclk = s3c64xx_i2s_set_sysclk, -}; +static struct snd_soc_dai_ops s3c64xx_i2s_dai_ops; static __devinit int s3c64xx_iis_dev_probe(struct platform_device *pdev) { @@ -171,6 +108,8 @@ static __devinit int s3c64xx_iis_dev_probe(struct platform_device *pdev) dai->probe = s3c64xx_i2s_probe; dai->ops = &s3c64xx_i2s_dai_ops; + i2s->feature |= S3C_FEATURE_CDCLKCON; + i2s->dma_capture = &s3c64xx_i2s_pcm_stereo_in[pdev->id]; i2s->dma_playback = &s3c64xx_i2s_pcm_stereo_out[pdev->id]; diff --git a/sound/soc/s3c24xx/s3c64xx-i2s.h b/sound/soc/s3c24xx/s3c64xx-i2s.h index abe7253..7a40f43 100644 --- a/sound/soc/s3c24xx/s3c64xx-i2s.h +++ b/sound/soc/s3c24xx/s3c64xx-i2s.h @@ -23,12 +23,20 @@ struct clk; #define S3C64XX_DIV_RCLK S3C_I2SV2_DIV_RCLK #define S3C64XX_DIV_PRESCALER S3C_I2SV2_DIV_PRESCALER -#define S3C64XX_CLKSRC_PCLK (0) -#define S3C64XX_CLKSRC_MUX (1) -#define S3C64XX_CLKSRC_CDCLK (2) +#define S3C64XX_CLKSRC_PCLK S3C_I2SV2_CLKSRC_PCLK +#define S3C64XX_CLKSRC_MUX S3C_I2SV2_CLKSRC_AUDIOBUS +#define S3C64XX_CLKSRC_CDCLK S3C_I2SV2_CLKSRC_CDCLK -extern struct snd_soc_dai s3c64xx_i2s_dai[]; +#define S3C64XX_I2S_RATES \ + (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_16000 | \ + SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) + +#define S3C64XX_I2S_FMTS \ + (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S24_LE) -extern struct clk *s3c64xx_i2s_get_clock(struct snd_soc_dai *dai); +extern struct snd_soc_dai s3c64xx_i2s_dai[]; +extern struct snd_soc_dai s3c64xx_i2s_v4_dai; #endif /* __SND_SOC_S3C24XX_S3C64XX_I2S_H */ diff --git a/sound/soc/s3c24xx/smdk64xx_wm8580.c b/sound/soc/s3c24xx/smdk64xx_wm8580.c index efe4901..07e8e51 100644 --- a/sound/soc/s3c24xx/smdk64xx_wm8580.c +++ b/sound/soc/s3c24xx/smdk64xx_wm8580.c @@ -22,8 +22,6 @@ #include "s3c-dma.h" #include "s3c64xx-i2s.h" -#define S3C64XX_I2S_V4 2 - /* SMDK64XX has a 12MHZ crystal attached to WM8580 */ #define SMDK64XX_WM8580_FREQ 12000000 @@ -215,7 +213,7 @@ static struct snd_soc_dai_link smdk64xx_dai[] = { { /* Primary Playback i/f */ .name = "WM8580 PAIF RX", .stream_name = "Playback", - .cpu_dai = &s3c64xx_i2s_dai[S3C64XX_I2S_V4], + .cpu_dai = &s3c64xx_i2s_v4_dai, .codec_dai = &wm8580_dai[WM8580_DAI_PAIFRX], .init = smdk64xx_wm8580_init_paifrx, .ops = &smdk64xx_ops, @@ -223,7 +221,7 @@ static struct snd_soc_dai_link smdk64xx_dai[] = { { /* Primary Capture i/f */ .name = "WM8580 PAIF TX", .stream_name = "Capture", - .cpu_dai = &s3c64xx_i2s_dai[S3C64XX_I2S_V4], + .cpu_dai = &s3c64xx_i2s_v4_dai, .codec_dai = &wm8580_dai[WM8580_DAI_PAIFTX], .init = smdk64xx_wm8580_init_paiftx, .ops = &smdk64xx_ops, diff --git a/sound/soc/sh/Kconfig b/sound/soc/sh/Kconfig index f07f6d8..a1d14bc 100644 --- a/sound/soc/sh/Kconfig +++ b/sound/soc/sh/Kconfig @@ -1,5 +1,5 @@ menu "SoC Audio support for SuperH" - depends on SUPERH + depends on SUPERH || ARCH_SHMOBILE config SND_SOC_PCM_SH7760 tristate "SoC Audio support for Renesas SH7760" @@ -22,7 +22,6 @@ config SND_SOC_SH4_SSI config SND_SOC_SH4_FSI tristate "SH4 FSI support" - depends on CPU_SUBTYPE_SH7724 help This option enables FSI sound support diff --git a/sound/soc/sh/fsi-ak4642.c b/sound/soc/sh/fsi-ak4642.c index 5263ab1..be01854 100644 --- a/sound/soc/sh/fsi-ak4642.c +++ b/sound/soc/sh/fsi-ak4642.c @@ -22,11 +22,25 @@ #include <sound/sh_fsi.h> #include <../sound/soc/codecs/ak4642.h> +static int fsi_ak4642_dai_init(struct snd_soc_codec *codec) +{ + int ret; + + ret = snd_soc_dai_set_fmt(&ak4642_dai, SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_sysclk(&ak4642_dai, 0, 11289600, 0); + + return ret; +} + static struct snd_soc_dai_link fsi_dai_link = { .name = "AK4642", .stream_name = "AK4642", .cpu_dai = &fsi_soc_dai[0], /* fsi */ .codec_dai = &ak4642_dai, + .init = fsi_ak4642_dai_init, .ops = NULL, }; diff --git a/sound/soc/sh/fsi.c b/sound/soc/sh/fsi.c index 8dc966f..3396a0d 100644 --- a/sound/soc/sh/fsi.c +++ b/sound/soc/sh/fsi.c @@ -41,14 +41,19 @@ #define MUTE_ST 0x0028 #define REG_END MUTE_ST + +#define CPU_INT_ST 0x01F4 +#define CPU_IEMSK 0x01F8 +#define CPU_IMSK 0x01FC #define INT_ST 0x0200 #define IEMSK 0x0204 #define IMSK 0x0208 #define MUTE 0x020C #define CLK_RST 0x0210 #define SOFT_RST 0x0214 -#define MREG_START INT_ST -#define MREG_END SOFT_RST +#define FIFO_SZ 0x0218 +#define MREG_START CPU_INT_ST +#define MREG_END FIFO_SZ /* DO_FMT */ /* DI_FMT */ @@ -80,6 +85,17 @@ #define INT_A_IN (1 << 4) #define INT_A_OUT (1 << 0) +/* SOFT_RST */ +#define PBSR (1 << 12) /* Port B Software Reset */ +#define PASR (1 << 8) /* Port A Software Reset */ +#define IR (1 << 4) /* Interrupt Reset */ +#define FSISR (1 << 0) /* Software Reset */ + +/* FIFO_SZ */ +#define OUT_SZ_MASK 0x7 +#define BO_SZ_SHIFT 8 +#define AO_SZ_SHIFT 0 + #define FSI_RATES SNDRV_PCM_RATE_8000_96000 #define FSI_FMTS (SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE) @@ -105,11 +121,18 @@ struct fsi_priv { int periods; }; +struct fsi_regs { + u32 int_st; + u32 iemsk; + u32 imsk; +}; + struct fsi_master { void __iomem *base; int irq; struct fsi_priv fsia; struct fsi_priv fsib; + struct fsi_regs *regs; struct sh_fsi_platform_info *info; spinlock_t lock; }; @@ -317,7 +340,7 @@ static int fsi_get_fifo_residue(struct fsi_priv *fsi, int is_play) /************************************************************************ - ctrl function + irq function ************************************************************************/ @@ -326,8 +349,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, IMSK, data, data); - fsi_master_mask_set(master, IEMSK, data, data); + fsi_master_mask_set(master, master->regs->imsk, data, data); + fsi_master_mask_set(master, master->regs->iemsk, data, data); } static void fsi_irq_disable(struct fsi_priv *fsi, int is_play) @@ -335,10 +358,39 @@ 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, IMSK, data, 0); - fsi_master_mask_set(master, IEMSK, data, 0); + fsi_master_mask_set(master, master->regs->imsk, data, 0); + fsi_master_mask_set(master, master->regs->iemsk, data, 0); +} + +static u32 fsi_irq_get_status(struct fsi_master *master) +{ + return fsi_master_read(master, master->regs->int_st); +} + +static void fsi_irq_clear_all_status(struct fsi_master *master) +{ + fsi_master_write(master, master->regs->int_st, 0x0000000); } +static void fsi_irq_clear_status(struct fsi_priv *fsi) +{ + u32 data = 0; + struct fsi_master *master = fsi_get_master(fsi); + + data |= fsi_port_ab_io_bit(fsi, 0); + data |= fsi_port_ab_io_bit(fsi, 1); + + /* clear interrupt factor */ + fsi_master_mask_set(master, master->regs->int_st, data, 0); +} + +/************************************************************************ + + + ctrl function + + +************************************************************************/ static void fsi_clk_ctrl(struct fsi_priv *fsi, int enable) { u32 val = fsi_is_port_a(fsi) ? (1 << 0) : (1 << 4); @@ -350,41 +402,61 @@ static void fsi_clk_ctrl(struct fsi_priv *fsi, int enable) fsi_master_mask_set(master, CLK_RST, val, 0); } -static void fsi_irq_init(struct fsi_priv *fsi, int is_play) +static void fsi_fifo_init(struct fsi_priv *fsi, + int is_play, + struct snd_soc_dai *dai) { - u32 data; - u32 ctrl; + struct fsi_master *master = fsi_get_master(fsi); + u32 ctrl, shift, i; - data = fsi_port_ab_io_bit(fsi, is_play); - ctrl = is_play ? DOFF_CTL : DIFF_CTL; + /* get on-chip RAM capacity */ + shift = fsi_master_read(master, FIFO_SZ); + shift >>= fsi_is_port_a(fsi) ? AO_SZ_SHIFT : BO_SZ_SHIFT; + shift &= OUT_SZ_MASK; + fsi->fifo_max = 256 << shift; + dev_dbg(dai->dev, "fifo = %d words\n", fsi->fifo_max); - /* set IMSK */ - fsi_irq_disable(fsi, is_play); + /* + * The maximum number of sample data varies depending + * on the number of channels selected for the format. + * + * FIFOs are used in 4-channel units in 3-channel mode + * and in 8-channel units in 5- to 7-channel mode + * meaning that more FIFOs than the required size of DPRAM + * are used. + * + * ex) if 256 words of DP-RAM is connected + * 1 channel: 256 (256 x 1 = 256) + * 2 channels: 128 (128 x 2 = 256) + * 3 channels: 64 ( 64 x 3 = 192) + * 4 channels: 64 ( 64 x 4 = 256) + * 5 channels: 32 ( 32 x 5 = 160) + * 6 channels: 32 ( 32 x 6 = 192) + * 7 channels: 32 ( 32 x 7 = 224) + * 8 channels: 32 ( 32 x 8 = 256) + */ + for (i = 1; i < fsi->chan; i <<= 1) + fsi->fifo_max >>= 1; + dev_dbg(dai->dev, "%d channel %d store\n", fsi->chan, fsi->fifo_max); + + ctrl = is_play ? DOFF_CTL : DIFF_CTL; /* set interrupt generation factor */ fsi_reg_write(fsi, ctrl, IRQ_HALF); /* clear FIFO */ fsi_reg_mask_set(fsi, ctrl, FIFO_CLR, FIFO_CLR); - - /* clear interrupt factor */ - fsi_master_mask_set(fsi_get_master(fsi), INT_ST, data, 0); } static void fsi_soft_all_reset(struct fsi_master *master) { - u32 status = fsi_master_read(master, SOFT_RST); - /* port AB reset */ - status &= 0x000000ff; - fsi_master_write(master, SOFT_RST, status); + fsi_master_mask_set(master, SOFT_RST, PASR | PBSR, 0); mdelay(10); /* soft reset */ - status &= 0x000000f0; - fsi_master_write(master, SOFT_RST, status); - status |= 0x00000001; - fsi_master_write(master, SOFT_RST, status); + fsi_master_mask_set(master, SOFT_RST, FSISR, 0); + fsi_master_mask_set(master, SOFT_RST, FSISR, FSISR); mdelay(10); } @@ -559,12 +631,11 @@ static int fsi_data_pop(struct fsi_priv *fsi, int startup) static irqreturn_t fsi_interrupt(int irq, void *data) { struct fsi_master *master = data; - u32 status = fsi_master_read(master, SOFT_RST) & ~0x00000010; - u32 int_st = fsi_master_read(master, INT_ST); + u32 int_st = fsi_irq_get_status(master); /* clear irq status */ - fsi_master_write(master, SOFT_RST, status); - fsi_master_write(master, SOFT_RST, status | 0x00000010); + fsi_master_mask_set(master, SOFT_RST, IR, 0); + fsi_master_mask_set(master, SOFT_RST, IR, IR); if (int_st & INT_A_OUT) fsi_data_push(&master->fsia, 0); @@ -575,7 +646,7 @@ static irqreturn_t fsi_interrupt(int irq, void *data) if (int_st & INT_B_IN) fsi_data_pop(&master->fsib, 0); - fsi_master_write(master, INT_ST, 0x0000000); + fsi_irq_clear_all_status(master); return IRQ_HANDLED; } @@ -669,29 +740,6 @@ static int fsi_dai_startup(struct snd_pcm_substream *substream, dev_err(dai->dev, "unknown format.\n"); return -EINVAL; } - - switch (fsi->chan) { - case 1: - fsi->fifo_max = 256; - break; - case 2: - fsi->fifo_max = 128; - break; - case 3: - case 4: - fsi->fifo_max = 64; - break; - case 5: - case 6: - case 7: - case 8: - fsi->fifo_max = 32; - break; - default: - dev_err(dai->dev, "channel size error.\n"); - return -EINVAL; - } - fsi_reg_write(fsi, reg, data); /* @@ -700,8 +748,12 @@ static int fsi_dai_startup(struct snd_pcm_substream *substream, if (is_master) fsi_clk_ctrl(fsi, 1); - /* irq setting */ - fsi_irq_init(fsi, is_play); + /* irq clear */ + fsi_irq_disable(fsi, is_play); + fsi_irq_clear_status(fsi); + + /* fifo init */ + fsi_fifo_init(fsi, is_play, dai); return ret; } @@ -913,6 +965,7 @@ EXPORT_SYMBOL_GPL(fsi_soc_platform); static int fsi_probe(struct platform_device *pdev) { struct fsi_master *master; + const struct platform_device_id *id_entry; struct resource *res; unsigned int irq; int ret; @@ -922,6 +975,12 @@ static int fsi_probe(struct platform_device *pdev) return -ENODEV; } + id_entry = pdev->id_entry; + if (!id_entry) { + dev_err(&pdev->dev, "unknown fsi device\n"); + return -ENODEV; + } + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); irq = platform_get_irq(pdev, 0); if (!res || (int)irq <= 0) { @@ -950,6 +1009,7 @@ static int fsi_probe(struct platform_device *pdev) master->fsia.master = master; master->fsib.base = master->base + 0x40; master->fsib.master = master; + master->regs = (struct fsi_regs *)id_entry->driver_data; spin_lock_init(&master->lock); pm_runtime_enable(&pdev->dev); @@ -962,7 +1022,8 @@ static int fsi_probe(struct platform_device *pdev) fsi_soft_all_reset(master); - ret = request_irq(irq, &fsi_interrupt, IRQF_DISABLED, "fsi", master); + ret = request_irq(irq, &fsi_interrupt, IRQF_DISABLED, + id_entry->name, master); if (ret) { dev_err(&pdev->dev, "irq request err\n"); goto exit_iounmap; @@ -1029,6 +1090,23 @@ static struct dev_pm_ops fsi_pm_ops = { .runtime_resume = fsi_runtime_nop, }; +static struct fsi_regs fsi_regs = { + .int_st = INT_ST, + .iemsk = IEMSK, + .imsk = IMSK, +}; + +static struct fsi_regs fsi2_regs = { + .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 }, +}; + static struct platform_driver fsi_driver = { .driver = { .name = "sh_fsi", @@ -1036,6 +1114,7 @@ static struct platform_driver fsi_driver = { }, .probe = fsi_probe, .remove = fsi_remove, + .id_table = fsi_id_table, }; static int __init fsi_mobile_init(void) diff --git a/sound/soc/soc-cache.c b/sound/soc/soc-cache.c index 5869dc3..472af38 100644 --- a/sound/soc/soc-cache.c +++ b/sound/soc/soc-cache.c @@ -44,6 +44,8 @@ static int snd_soc_4_12_write(struct snd_soc_codec *codec, unsigned int reg, return 0; } + dev_dbg(codec->dev, "0x%x = 0x%x\n", reg, value); + ret = codec->hw_write(codec->control_data, data, 2); if (ret == 2) return 0; @@ -112,6 +114,8 @@ static int snd_soc_7_9_write(struct snd_soc_codec *codec, unsigned int reg, return 0; } + dev_dbg(codec->dev, "0x%x = 0x%x\n", reg, value); + ret = codec->hw_write(codec->control_data, data, 2); if (ret == 2) return 0; @@ -159,7 +163,8 @@ static int snd_soc_8_8_write(struct snd_soc_codec *codec, unsigned int reg, BUG_ON(codec->volatile_register); - data[0] = reg & 0xff; + reg &= 0xff; + data[0] = reg; data[1] = value & 0xff; if (reg < codec->reg_cache_size) @@ -170,6 +175,8 @@ static int snd_soc_8_8_write(struct snd_soc_codec *codec, unsigned int reg, return 0; } + dev_dbg(codec->dev, "0x%x = 0x%x\n", reg, value); + if (codec->hw_write(codec->control_data, data, 2) == 2) return 0; else @@ -180,6 +187,7 @@ static unsigned int snd_soc_8_8_read(struct snd_soc_codec *codec, unsigned int reg) { u8 *cache = codec->reg_cache; + reg &= 0xff; if (reg >= codec->reg_cache_size) return -1; return cache[reg]; @@ -203,6 +211,8 @@ static int snd_soc_8_16_write(struct snd_soc_codec *codec, unsigned int reg, return 0; } + dev_dbg(codec->dev, "0x%x = 0x%x\n", reg, value); + if (codec->hw_write(codec->control_data, data, 3) == 3) return 0; else @@ -226,6 +236,40 @@ static unsigned int snd_soc_8_16_read(struct snd_soc_codec *codec, } #if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE)) +static unsigned int snd_soc_8_8_read_i2c(struct snd_soc_codec *codec, + unsigned int r) +{ + struct i2c_msg xfer[2]; + u8 reg = r; + u8 data; + int ret; + struct i2c_client *client = codec->control_data; + + /* Write register */ + xfer[0].addr = client->addr; + xfer[0].flags = 0; + xfer[0].len = 1; + xfer[0].buf = ® + + /* Read data */ + xfer[1].addr = client->addr; + xfer[1].flags = I2C_M_RD; + xfer[1].len = 1; + xfer[1].buf = &data; + + ret = i2c_transfer(client->adapter, xfer, 2); + if (ret != 2) { + dev_err(&client->dev, "i2c_transfer() returned %d\n", ret); + return 0; + } + + return data; +} +#else +#define snd_soc_8_8_read_i2c NULL +#endif + +#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE)) static unsigned int snd_soc_8_16_read_i2c(struct snd_soc_codec *codec, unsigned int r) { @@ -326,6 +370,8 @@ static int snd_soc_16_8_write(struct snd_soc_codec *codec, unsigned int reg, return 0; } + dev_dbg(codec->dev, "0x%x = 0x%x\n", reg, value); + ret = codec->hw_write(codec->control_data, data, 3); if (ret == 3) return 0; @@ -366,6 +412,86 @@ static int snd_soc_16_8_spi_write(void *control_data, const char *data, #define snd_soc_16_8_spi_write NULL #endif +#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE)) +static unsigned int snd_soc_16_16_read_i2c(struct snd_soc_codec *codec, + unsigned int r) +{ + struct i2c_msg xfer[2]; + u16 reg = cpu_to_be16(r); + u16 data; + int ret; + struct i2c_client *client = codec->control_data; + + /* Write register */ + xfer[0].addr = client->addr; + xfer[0].flags = 0; + xfer[0].len = 2; + xfer[0].buf = (u8 *)® + + /* Read data */ + xfer[1].addr = client->addr; + xfer[1].flags = I2C_M_RD; + xfer[1].len = 2; + xfer[1].buf = (u8 *)&data; + + ret = i2c_transfer(client->adapter, xfer, 2); + if (ret != 2) { + dev_err(&client->dev, "i2c_transfer() returned %d\n", ret); + return 0; + } + + return be16_to_cpu(data); +} +#else +#define snd_soc_16_16_read_i2c NULL +#endif + +static unsigned int snd_soc_16_16_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + + if (reg >= codec->reg_cache_size || + snd_soc_codec_volatile_register(codec, reg)) { + if (codec->cache_only) + return -EINVAL; + + return codec->hw_read(codec, reg); + } + + return cache[reg]; +} + +static int snd_soc_16_16_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u16 *cache = codec->reg_cache; + u8 data[4]; + int ret; + + data[0] = (reg >> 8) & 0xff; + data[1] = reg & 0xff; + data[2] = (value >> 8) & 0xff; + data[3] = value & 0xff; + + if (reg < codec->reg_cache_size) + cache[reg] = value; + + if (codec->cache_only) { + codec->cache_sync = 1; + return 0; + } + + dev_dbg(codec->dev, "0x%x = 0x%x\n", reg, value); + + ret = codec->hw_write(codec->control_data, data, 4); + if (ret == 4) + return 0; + if (ret < 0) + return ret; + else + return -EIO; +} static struct { int addr_bits; @@ -388,6 +514,7 @@ static struct { { .addr_bits = 8, .data_bits = 8, .write = snd_soc_8_8_write, .read = snd_soc_8_8_read, + .i2c_read = snd_soc_8_8_read_i2c, }, { .addr_bits = 8, .data_bits = 16, @@ -400,6 +527,11 @@ static struct { .i2c_read = snd_soc_16_8_read_i2c, .spi_write = snd_soc_16_8_spi_write, }, + { + .addr_bits = 16, .data_bits = 16, + .write = snd_soc_16_16_write, .read = snd_soc_16_16_read, + .i2c_read = snd_soc_16_16_read_i2c, + }, }; /** diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index ad7f952..998569d 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -316,7 +316,7 @@ static int soc_pcm_apply_symmetry(struct snd_pcm_substream *substream) if (codec_dai->symmetric_rates || cpu_dai->symmetric_rates || machine->symmetric_rates) { - dev_dbg(card->dev, "Symmetry forces %dHz rate\n", + dev_dbg(card->dev, "Symmetry forces %dHz rate\n", machine->rate); ret = snd_pcm_hw_constraint_minmax(substream->runtime, @@ -405,6 +405,12 @@ static int soc_pcm_open(struct snd_pcm_substream *substream) codec_dai->playback.formats & cpu_dai->playback.formats; runtime->hw.rates = codec_dai->playback.rates & cpu_dai->playback.rates; + if (codec_dai->playback.rates + & (SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_CONTINUOUS)) + runtime->hw.rates |= cpu_dai->playback.rates; + if (cpu_dai->playback.rates + & (SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_CONTINUOUS)) + runtime->hw.rates |= codec_dai->playback.rates; } else { runtime->hw.rate_min = max(codec_dai->capture.rate_min, @@ -422,6 +428,12 @@ static int soc_pcm_open(struct snd_pcm_substream *substream) codec_dai->capture.formats & cpu_dai->capture.formats; runtime->hw.rates = codec_dai->capture.rates & cpu_dai->capture.rates; + if (codec_dai->capture.rates + & (SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_CONTINUOUS)) + runtime->hw.rates |= cpu_dai->capture.rates; + if (cpu_dai->capture.rates + & (SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_CONTINUOUS)) + runtime->hw.rates |= codec_dai->capture.rates; } snd_pcm_limit_hw_rates(runtime); @@ -455,12 +467,15 @@ static int soc_pcm_open(struct snd_pcm_substream *substream) pr_debug("asoc: min rate %d max rate %d\n", runtime->hw.rate_min, runtime->hw.rate_max); - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - cpu_dai->playback.active = codec_dai->playback.active = 1; - else - cpu_dai->capture.active = codec_dai->capture.active = 1; - cpu_dai->active = codec_dai->active = 1; - cpu_dai->runtime = runtime; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + cpu_dai->playback.active++; + codec_dai->playback.active++; + } else { + cpu_dai->capture.active++; + codec_dai->capture.active++; + } + cpu_dai->active++; + codec_dai->active++; card->codec->active++; mutex_unlock(&pcm_mutex); return 0; @@ -536,15 +551,16 @@ static int soc_codec_close(struct snd_pcm_substream *substream) mutex_lock(&pcm_mutex); - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - cpu_dai->playback.active = codec_dai->playback.active = 0; - else - cpu_dai->capture.active = codec_dai->capture.active = 0; - - if (codec_dai->playback.active == 0 && - codec_dai->capture.active == 0) { - cpu_dai->active = codec_dai->active = 0; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + cpu_dai->playback.active--; + codec_dai->playback.active--; + } else { + cpu_dai->capture.active--; + codec_dai->capture.active--; } + + cpu_dai->active--; + codec_dai->active--; codec->active--; /* Muting the DAC suppresses artifacts caused during digital @@ -564,7 +580,6 @@ static int soc_codec_close(struct snd_pcm_substream *substream) if (platform->pcm_ops->close) platform->pcm_ops->close(substream); - cpu_dai->runtime = NULL; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { /* start delayed pop wq here for playback streams */ @@ -802,6 +817,41 @@ static int soc_pcm_trigger(struct snd_pcm_substream *substream, int cmd) return 0; } +/* + * soc level wrapper for pointer callback + * If cpu_dai, codec_dai, platform driver has the delay callback, than + * the runtime->delay will be updated accordingly. + */ +static snd_pcm_uframes_t soc_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_card *card = socdev->card; + struct snd_soc_platform *platform = card->platform; + struct snd_soc_dai_link *machine = rtd->dai; + struct snd_soc_dai *cpu_dai = machine->cpu_dai; + struct snd_soc_dai *codec_dai = machine->codec_dai; + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_uframes_t offset = 0; + snd_pcm_sframes_t delay = 0; + + if (platform->pcm_ops->pointer) + offset = platform->pcm_ops->pointer(substream); + + if (cpu_dai->ops->delay) + delay += cpu_dai->ops->delay(substream, cpu_dai); + + if (codec_dai->ops->delay) + delay += codec_dai->ops->delay(substream, codec_dai); + + if (platform->delay) + delay += platform->delay(substream, codec_dai); + + runtime->delay = delay; + + return offset; +} + /* ASoC PCM operations */ static struct snd_pcm_ops soc_pcm_ops = { .open = soc_pcm_open, @@ -810,6 +860,7 @@ static struct snd_pcm_ops soc_pcm_ops = { .hw_free = soc_pcm_hw_free, .prepare = soc_pcm_prepare, .trigger = soc_pcm_trigger, + .pointer = soc_pcm_pointer, }; #ifdef CONFIG_PM @@ -843,23 +894,35 @@ static int soc_suspend(struct device *dev) /* mute any active DAC's */ for (i = 0; i < card->num_links; i++) { struct snd_soc_dai *dai = card->dai_link[i].codec_dai; + + if (card->dai_link[i].ignore_suspend) + continue; + if (dai->ops->digital_mute && dai->playback.active) dai->ops->digital_mute(dai, 1); } /* suspend all pcms */ - for (i = 0; i < card->num_links; i++) + for (i = 0; i < card->num_links; i++) { + if (card->dai_link[i].ignore_suspend) + continue; + snd_pcm_suspend_all(card->dai_link[i].pcm); + } if (card->suspend_pre) card->suspend_pre(pdev, PMSG_SUSPEND); for (i = 0; i < card->num_links; i++) { struct snd_soc_dai *cpu_dai = card->dai_link[i].cpu_dai; + + if (card->dai_link[i].ignore_suspend) + continue; + if (cpu_dai->suspend && !cpu_dai->ac97_control) cpu_dai->suspend(cpu_dai); if (platform->suspend) - platform->suspend(cpu_dai); + platform->suspend(&card->dai_link[i]); } /* close any waiting streams and save state */ @@ -868,6 +931,10 @@ static int soc_suspend(struct device *dev) for (i = 0; i < codec->num_dai; i++) { char *stream = codec->dai[i].playback.stream_name; + + if (card->dai_link[i].ignore_suspend) + continue; + if (stream != NULL) snd_soc_dapm_stream_event(codec, stream, SND_SOC_DAPM_STREAM_SUSPEND); @@ -877,11 +944,26 @@ static int soc_suspend(struct device *dev) SND_SOC_DAPM_STREAM_SUSPEND); } - if (codec_dev->suspend) - codec_dev->suspend(pdev, PMSG_SUSPEND); + /* If there are paths active then the CODEC will be held with + * bias _ON and should not be suspended. */ + if (codec_dev->suspend) { + switch (codec->bias_level) { + case SND_SOC_BIAS_STANDBY: + case SND_SOC_BIAS_OFF: + codec_dev->suspend(pdev, PMSG_SUSPEND); + break; + default: + dev_dbg(socdev->dev, "CODEC is on over suspend\n"); + break; + } + } for (i = 0; i < card->num_links; i++) { struct snd_soc_dai *cpu_dai = card->dai_link[i].cpu_dai; + + if (card->dai_link[i].ignore_suspend) + continue; + if (cpu_dai->suspend && cpu_dai->ac97_control) cpu_dai->suspend(cpu_dai); } @@ -913,20 +995,44 @@ static void soc_resume_deferred(struct work_struct *work) dev_dbg(socdev->dev, "starting resume work\n"); + /* Bring us up into D2 so that DAPM starts enabling things */ + snd_power_change_state(codec->card, SNDRV_CTL_POWER_D2); + if (card->resume_pre) card->resume_pre(pdev); for (i = 0; i < card->num_links; i++) { struct snd_soc_dai *cpu_dai = card->dai_link[i].cpu_dai; + + if (card->dai_link[i].ignore_suspend) + continue; + if (cpu_dai->resume && cpu_dai->ac97_control) cpu_dai->resume(cpu_dai); } - if (codec_dev->resume) - codec_dev->resume(pdev); + /* If the CODEC was idle over suspend then it will have been + * left with bias OFF or STANDBY and suspended so we must now + * resume. Otherwise the suspend was suppressed. + */ + if (codec_dev->resume) { + switch (codec->bias_level) { + case SND_SOC_BIAS_STANDBY: + case SND_SOC_BIAS_OFF: + codec_dev->resume(pdev); + break; + default: + dev_dbg(socdev->dev, "CODEC was on over suspend\n"); + break; + } + } for (i = 0; i < codec->num_dai; i++) { char *stream = codec->dai[i].playback.stream_name; + + if (card->dai_link[i].ignore_suspend) + continue; + if (stream != NULL) snd_soc_dapm_stream_event(codec, stream, SND_SOC_DAPM_STREAM_RESUME); @@ -939,16 +1045,24 @@ static void soc_resume_deferred(struct work_struct *work) /* unmute any active DACs */ for (i = 0; i < card->num_links; i++) { struct snd_soc_dai *dai = card->dai_link[i].codec_dai; + + if (card->dai_link[i].ignore_suspend) + continue; + if (dai->ops->digital_mute && dai->playback.active) dai->ops->digital_mute(dai, 0); } for (i = 0; i < card->num_links; i++) { struct snd_soc_dai *cpu_dai = card->dai_link[i].cpu_dai; + + if (card->dai_link[i].ignore_suspend) + continue; + if (cpu_dai->resume && !cpu_dai->ac97_control) cpu_dai->resume(cpu_dai); if (platform->resume) - platform->resume(cpu_dai); + platform->resume(&card->dai_link[i]); } if (card->resume_post) @@ -1233,26 +1347,25 @@ static int soc_remove(struct platform_device *pdev) struct snd_soc_platform *platform = card->platform; struct snd_soc_codec_device *codec_dev = socdev->codec_dev; - if (!card->instantiated) - return 0; + if (card->instantiated) { + run_delayed_work(&card->delayed_work); - run_delayed_work(&card->delayed_work); + if (platform->remove) + platform->remove(pdev); - if (platform->remove) - platform->remove(pdev); + if (codec_dev->remove) + codec_dev->remove(pdev); - if (codec_dev->remove) - codec_dev->remove(pdev); + for (i = 0; i < card->num_links; i++) { + struct snd_soc_dai *cpu_dai = card->dai_link[i].cpu_dai; + if (cpu_dai->remove) + cpu_dai->remove(pdev, cpu_dai); + } - for (i = 0; i < card->num_links; i++) { - struct snd_soc_dai *cpu_dai = card->dai_link[i].cpu_dai; - if (cpu_dai->remove) - cpu_dai->remove(pdev, cpu_dai); + if (card->remove) + card->remove(pdev); } - if (card->remove) - card->remove(pdev); - snd_soc_unregister_card(card); return 0; @@ -1336,7 +1449,6 @@ static int soc_new_pcm(struct snd_soc_device *socdev, dai_link->pcm = pcm; pcm->private_data = rtd; soc_pcm_ops.mmap = platform->pcm_ops->mmap; - soc_pcm_ops.pointer = platform->pcm_ops->pointer; soc_pcm_ops.ioctl = platform->pcm_ops->ioctl; soc_pcm_ops.copy = platform->pcm_ops->copy; soc_pcm_ops.silence = platform->pcm_ops->silence; @@ -1906,18 +2018,22 @@ int snd_soc_info_volsw(struct snd_kcontrol *kcontrol, { struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value; - int max = mc->max; + int platform_max; unsigned int shift = mc->shift; unsigned int rshift = mc->rshift; - if (max == 1 && !strstr(kcontrol->id.name, " Volume")) + if (!mc->platform_max) + mc->platform_max = mc->max; + platform_max = mc->platform_max; + + if (platform_max == 1 && !strstr(kcontrol->id.name, " Volume")) uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; else uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; uinfo->count = shift == rshift ? 1 : 2; uinfo->value.integer.min = 0; - uinfo->value.integer.max = max; + uinfo->value.integer.max = platform_max; return 0; } EXPORT_SYMBOL_GPL(snd_soc_info_volsw); @@ -2015,16 +2131,20 @@ int snd_soc_info_volsw_2r(struct snd_kcontrol *kcontrol, { struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value; - int max = mc->max; + int platform_max; - if (max == 1 && !strstr(kcontrol->id.name, " Volume")) + if (!mc->platform_max) + mc->platform_max = mc->max; + platform_max = mc->platform_max; + + if (platform_max == 1 && !strstr(kcontrol->id.name, " Volume")) uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; else uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; uinfo->count = 2; uinfo->value.integer.min = 0; - uinfo->value.integer.max = max; + uinfo->value.integer.max = platform_max; return 0; } EXPORT_SYMBOL_GPL(snd_soc_info_volsw_2r); @@ -2125,13 +2245,17 @@ int snd_soc_info_volsw_s8(struct snd_kcontrol *kcontrol, { struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value; - int max = mc->max; + int platform_max; int min = mc->min; + if (!mc->platform_max) + mc->platform_max = mc->max; + platform_max = mc->platform_max; + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; uinfo->count = 2; uinfo->value.integer.min = 0; - uinfo->value.integer.max = max-min; + uinfo->value.integer.max = platform_max - min; return 0; } EXPORT_SYMBOL_GPL(snd_soc_info_volsw_s8); @@ -2190,6 +2314,45 @@ int snd_soc_put_volsw_s8(struct snd_kcontrol *kcontrol, EXPORT_SYMBOL_GPL(snd_soc_put_volsw_s8); /** + * snd_soc_limit_volume - Set new limit to an existing volume control. + * + * @codec: where to look for the control + * @name: Name of the control + * @max: new maximum limit + * + * Return 0 for success, else error. + */ +int snd_soc_limit_volume(struct snd_soc_codec *codec, + const char *name, int max) +{ + struct snd_card *card = codec->card; + struct snd_kcontrol *kctl; + struct soc_mixer_control *mc; + int found = 0; + int ret = -EINVAL; + + /* Sanity check for name and max */ + if (unlikely(!name || max <= 0)) + return -EINVAL; + + list_for_each_entry(kctl, &card->controls, list) { + if (!strncmp(kctl->id.name, name, sizeof(kctl->id.name))) { + found = 1; + break; + } + } + if (found) { + mc = (struct soc_mixer_control *)kctl->private_value; + if (max <= mc->max) { + mc->platform_max = max; + ret = 0; + } + } + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_limit_volume); + +/** * snd_soc_dai_set_sysclk - configure DAI system or master clock. * @dai: DAI * @clk_id: DAI specific clock ID diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 7c28f40..03cb7c0 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -98,7 +98,6 @@ static void pop_dbg(u32 pop_time, const char *fmt, ...) if (pop_time) { vprintk(fmt, args); - pop_wait(pop_time); } va_end(args); @@ -315,62 +314,14 @@ static int dapm_update_bits(struct snd_soc_dapm_widget *widget) pop_dbg(codec->pop_time, "pop test %s : %s in %d ms\n", widget->name, widget->power ? "on" : "off", codec->pop_time); - snd_soc_write(codec, widget->reg, new); pop_wait(codec->pop_time); + snd_soc_write(codec, widget->reg, new); } pr_debug("reg %x old %x new %x change %d\n", widget->reg, old, new, change); return change; } -/* ramps the volume up or down to minimise pops before or after a - * DAPM power event */ -static int dapm_set_pga(struct snd_soc_dapm_widget *widget, int power) -{ - const struct snd_kcontrol_new *k = widget->kcontrols; - - if (widget->muted && !power) - return 0; - if (!widget->muted && power) - return 0; - - if (widget->num_kcontrols && k) { - struct soc_mixer_control *mc = - (struct soc_mixer_control *)k->private_value; - unsigned int reg = mc->reg; - unsigned int shift = mc->shift; - int max = mc->max; - unsigned int mask = (1 << fls(max)) - 1; - unsigned int invert = mc->invert; - - if (power) { - int i; - /* power up has happended, increase volume to last level */ - if (invert) { - for (i = max; i > widget->saved_value; i--) - snd_soc_update_bits(widget->codec, reg, mask, i); - } else { - for (i = 0; i < widget->saved_value; i++) - snd_soc_update_bits(widget->codec, reg, mask, i); - } - widget->muted = 0; - } else { - /* power down is about to occur, decrease volume to mute */ - int val = snd_soc_read(widget->codec, reg); - int i = widget->saved_value = (val >> shift) & mask; - if (invert) { - for (; i < mask; i++) - snd_soc_update_bits(widget->codec, reg, mask, i); - } else { - for (; i > 0; i--) - snd_soc_update_bits(widget->codec, reg, mask, i); - } - widget->muted = 1; - } - } - return 0; -} - /* create new dapm mixer control */ static int dapm_new_mixer(struct snd_soc_codec *codec, struct snd_soc_dapm_widget *w) @@ -465,20 +416,10 @@ err: static int dapm_new_pga(struct snd_soc_codec *codec, struct snd_soc_dapm_widget *w) { - struct snd_kcontrol *kcontrol; - int ret = 0; - - if (!w->num_kcontrols) - return -EINVAL; + if (w->num_kcontrols) + pr_err("asoc: PGA controls not supported: '%s'\n", w->name); - kcontrol = snd_soc_cnew(&w->kcontrols[0], w, w->name); - ret = snd_ctl_add(codec->card, kcontrol); - if (ret < 0) { - printk(KERN_ERR "asoc: failed to add kcontrol %s\n", w->name); - return ret; - } - - return ret; + return 0; } /* reset 'walked' bit for each dapm path */ @@ -490,6 +431,25 @@ static inline void dapm_clear_walk(struct snd_soc_codec *codec) p->walked = 0; } +/* We implement power down on suspend by checking the power state of + * the ALSA card - when we are suspending the ALSA state for the card + * is set to D3. + */ +static int snd_soc_dapm_suspend_check(struct snd_soc_dapm_widget *widget) +{ + struct snd_soc_codec *codec = widget->codec; + + switch (snd_power_get_state(codec->card)) { + case SNDRV_CTL_POWER_D3hot: + case SNDRV_CTL_POWER_D3cold: + if (widget->ignore_suspend) + pr_debug("%s ignoring suspend\n", widget->name); + return widget->ignore_suspend; + default: + return 1; + } +} + /* * Recursively check for a completed path to an active or physically connected * output widget. Returns number of complete paths. @@ -506,7 +466,7 @@ static int is_connected_output_ep(struct snd_soc_dapm_widget *widget) case snd_soc_dapm_adc: case snd_soc_dapm_aif_out: if (widget->active) - return 1; + return snd_soc_dapm_suspend_check(widget); default: break; } @@ -514,12 +474,12 @@ static int is_connected_output_ep(struct snd_soc_dapm_widget *widget) if (widget->connected) { /* connected pin ? */ if (widget->id == snd_soc_dapm_output && !widget->ext) - return 1; + return snd_soc_dapm_suspend_check(widget); /* connected jack or spk ? */ if (widget->id == snd_soc_dapm_hp || widget->id == snd_soc_dapm_spk || (widget->id == snd_soc_dapm_line && !list_empty(&widget->sources))) - return 1; + return snd_soc_dapm_suspend_check(widget); } list_for_each_entry(path, &widget->sinks, list_source) { @@ -552,7 +512,7 @@ static int is_connected_input_ep(struct snd_soc_dapm_widget *widget) case snd_soc_dapm_dac: case snd_soc_dapm_aif_in: if (widget->active) - return 1; + return snd_soc_dapm_suspend_check(widget); default: break; } @@ -560,16 +520,16 @@ static int is_connected_input_ep(struct snd_soc_dapm_widget *widget) if (widget->connected) { /* connected pin ? */ if (widget->id == snd_soc_dapm_input && !widget->ext) - return 1; + return snd_soc_dapm_suspend_check(widget); /* connected VMID/Bias for lower pops */ if (widget->id == snd_soc_dapm_vmid) - return 1; + return snd_soc_dapm_suspend_check(widget); /* connected jack ? */ if (widget->id == snd_soc_dapm_mic || (widget->id == snd_soc_dapm_line && !list_empty(&widget->sinks))) - return 1; + return snd_soc_dapm_suspend_check(widget); } list_for_each_entry(path, &widget->sources, list_sink) { @@ -634,16 +594,8 @@ static int dapm_generic_apply_power(struct snd_soc_dapm_widget *w) return ret; } - /* Lower PGA volume to reduce pops */ - if (w->id == snd_soc_dapm_pga && !w->power) - dapm_set_pga(w, w->power); - dapm_update_bits(w); - /* Raise PGA volume to reduce pops */ - if (w->id == snd_soc_dapm_pga && w->power) - dapm_set_pga(w, w->power); - /* power up post event */ if (w->power && w->event && (w->event_flags & SND_SOC_DAPM_POST_PMU)) { @@ -810,10 +762,6 @@ static void dapm_seq_run_coalesced(struct snd_soc_codec *codec, pr_err("%s: pre event failed: %d\n", w->name, ret); } - - /* Lower PGA volume to reduce pops */ - if (w->id == snd_soc_dapm_pga && !w->power) - dapm_set_pga(w, w->power); } if (reg >= 0) { @@ -825,10 +773,6 @@ static void dapm_seq_run_coalesced(struct snd_soc_codec *codec, } list_for_each_entry(w, pending, power_list) { - /* Raise PGA volume to reduce pops */ - if (w->id == snd_soc_dapm_pga && w->power) - dapm_set_pga(w, w->power); - /* power up post event */ if (w->power && w->event && (w->event_flags & SND_SOC_DAPM_POST_PMU)) { @@ -973,19 +917,12 @@ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) if (!w->power_check) continue; - /* If we're suspending then pull down all the - * power. */ - switch (event) { - case SND_SOC_DAPM_STREAM_SUSPEND: - power = 0; - break; - - default: + if (!w->force) power = w->power_check(w); - if (power) - sys_power = 1; - break; - } + else + power = 1; + if (power) + sys_power = 1; if (w->power == power) continue; @@ -1076,6 +1013,7 @@ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) pop_dbg(codec->pop_time, "DAPM sequencing finished, waiting %dms\n", codec->pop_time); + pop_wait(codec->pop_time); return 0; } @@ -1338,6 +1276,9 @@ static int snd_soc_dapm_set_pin(struct snd_soc_codec *codec, if (!strcmp(w->name, pin)) { pr_debug("dapm: %s: pin %s\n", codec->name, pin); w->connected = status; + /* Allow disabling of forced pins */ + if (status == 0) + w->force = 0; return 0; } } @@ -1594,12 +1535,6 @@ int snd_soc_dapm_get_volsw(struct snd_kcontrol *kcontrol, unsigned int invert = mc->invert; unsigned int mask = (1 << fls(max)) - 1; - /* return the saved value if we are powered down */ - if (widget->id == snd_soc_dapm_pga && !widget->power) { - ucontrol->value.integer.value[0] = widget->saved_value; - return 0; - } - ucontrol->value.integer.value[0] = (snd_soc_read(widget->codec, reg) >> shift) & mask; if (shift != rshift) @@ -1659,13 +1594,6 @@ int snd_soc_dapm_put_volsw(struct snd_kcontrol *kcontrol, mutex_lock(&widget->codec->mutex); widget->value = val; - /* save volume value if the widget is powered down */ - if (widget->id == snd_soc_dapm_pga && !widget->power) { - widget->saved_value = val; - mutex_unlock(&widget->codec->mutex); - return 1; - } - if (snd_soc_test_bits(widget->codec, reg, val_mask, val)) { if (val) /* new connection */ @@ -2094,18 +2022,8 @@ int snd_soc_dapm_stream_event(struct snd_soc_codec *codec, w->active = 0; break; case SND_SOC_DAPM_STREAM_SUSPEND: - if (w->active) - w->suspend = 1; - w->active = 0; - break; case SND_SOC_DAPM_STREAM_RESUME: - if (w->suspend) { - w->active = 1; - w->suspend = 0; - } - break; case SND_SOC_DAPM_STREAM_PAUSE_PUSH: - break; case SND_SOC_DAPM_STREAM_PAUSE_RELEASE: break; } @@ -2135,6 +2053,36 @@ int snd_soc_dapm_enable_pin(struct snd_soc_codec *codec, const char *pin) EXPORT_SYMBOL_GPL(snd_soc_dapm_enable_pin); /** + * snd_soc_dapm_force_enable_pin - force a pin to be enabled + * @codec: SoC codec + * @pin: pin name + * + * Enables input/output pin regardless of any other state. This is + * intended for use with microphone bias supplies used in microphone + * jack detection. + * + * NOTE: snd_soc_dapm_sync() needs to be called after this for DAPM to + * do any widget power switching. + */ +int snd_soc_dapm_force_enable_pin(struct snd_soc_codec *codec, const char *pin) +{ + struct snd_soc_dapm_widget *w; + + list_for_each_entry(w, &codec->dapm_widgets, list) { + if (!strcmp(w->name, pin)) { + pr_debug("dapm: %s: pin %s\n", codec->name, pin); + w->connected = 1; + w->force = 1; + return 0; + } + } + + pr_err("dapm: %s: configuring unknown pin %s\n", codec->name, pin); + return -EINVAL; +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_force_enable_pin); + +/** * snd_soc_dapm_disable_pin - disable pin. * @codec: SoC codec * @pin: pin name @@ -2192,6 +2140,33 @@ int snd_soc_dapm_get_pin_status(struct snd_soc_codec *codec, const char *pin) EXPORT_SYMBOL_GPL(snd_soc_dapm_get_pin_status); /** + * snd_soc_dapm_ignore_suspend - ignore suspend status for DAPM endpoint + * @codec: audio codec + * @pin: audio signal pin endpoint (or start point) + * + * Mark the given endpoint or pin as ignoring suspend. When the + * system is disabled a path between two endpoints flagged as ignoring + * suspend will not be disabled. The path must already be enabled via + * normal means at suspend time, it will not be turned on if it was not + * already enabled. + */ +int snd_soc_dapm_ignore_suspend(struct snd_soc_codec *codec, const char *pin) +{ + struct snd_soc_dapm_widget *w; + + list_for_each_entry(w, &codec->dapm_widgets, list) { + if (!strcmp(w->name, pin)) { + w->ignore_suspend = 1; + return 0; + } + } + + pr_err("Unknown DAPM pin: %s\n", pin); + return -EINVAL; +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_ignore_suspend); + +/** * snd_soc_dapm_free - free dapm resources * @socdev: SoC device * diff --git a/sound/soc/soc-jack.c b/sound/soc/soc-jack.c index 3c07a94..29159e1 100644 --- a/sound/soc/soc-jack.c +++ b/sound/soc/soc-jack.c @@ -37,6 +37,7 @@ int snd_soc_jack_new(struct snd_soc_card *card, const char *id, int type, { jack->card = card; INIT_LIST_HEAD(&jack->pins); + BLOCKING_INIT_NOTIFIER_HEAD(&jack->notifier); return snd_jack_new(card->codec->card, id, type, &jack->jack); } @@ -63,10 +64,9 @@ void snd_soc_jack_report(struct snd_soc_jack *jack, int status, int mask) int enable; int oldstatus; - if (!jack) { - WARN_ON_ONCE(!jack); + if (!jack) return; - } + codec = jack->card->codec; mutex_lock(&codec->mutex); @@ -93,6 +93,9 @@ void snd_soc_jack_report(struct snd_soc_jack *jack, int status, int mask) snd_soc_dapm_disable_pin(codec, pin->pin); } + /* Report before the DAPM sync to help users updating micbias status */ + blocking_notifier_call_chain(&jack->notifier, status, NULL); + snd_soc_dapm_sync(codec); snd_jack_report(jack->jack, status); @@ -143,6 +146,40 @@ int snd_soc_jack_add_pins(struct snd_soc_jack *jack, int count, } EXPORT_SYMBOL_GPL(snd_soc_jack_add_pins); +/** + * snd_soc_jack_notifier_register - Register a notifier for jack status + * + * @jack: ASoC jack + * @nb: Notifier block to register + * + * Register for notification of the current status of the jack. Note + * that it is not possible to report additional jack events in the + * callback from the notifier, this is intended to support + * applications such as enabling electrical detection only when a + * mechanical detection event has occurred. + */ +void snd_soc_jack_notifier_register(struct snd_soc_jack *jack, + struct notifier_block *nb) +{ + blocking_notifier_chain_register(&jack->notifier, nb); +} +EXPORT_SYMBOL_GPL(snd_soc_jack_notifier_register); + +/** + * snd_soc_jack_notifier_unregister - Unregister a notifier for jack status + * + * @jack: ASoC jack + * @nb: Notifier block to unregister + * + * Stop notifying for status changes. + */ +void snd_soc_jack_notifier_unregister(struct snd_soc_jack *jack, + struct notifier_block *nb) +{ + blocking_notifier_chain_unregister(&jack->notifier, nb); +} +EXPORT_SYMBOL_GPL(snd_soc_jack_notifier_unregister); + #ifdef CONFIG_GPIOLIB /* gpio detect */ static void snd_soc_jack_gpio_detect(struct snd_soc_jack_gpio *gpio) |