diff options
78 files changed, 6236 insertions, 1783 deletions
diff --git a/arch/arm/mach-davinci/board-dm365-evm.c b/arch/arm/mach-davinci/board-dm365-evm.c index d15bece..512174a 100644 --- a/arch/arm/mach-davinci/board-dm365-evm.c +++ b/arch/arm/mach-davinci/board-dm365-evm.c @@ -604,7 +604,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/plat-omap/include/plat/mcbsp.h b/arch/arm/plat-omap/include/plat/mcbsp.h index 3974835..1bd7021 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 52dfcc8..4ff7a11 100644 --- a/arch/arm/plat-omap/mcbsp.c +++ b/arch/arm/plat-omap/mcbsp.c @@ -559,6 +559,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 diff --git a/drivers/gpio/wm8994-gpio.c b/drivers/gpio/wm8994-gpio.c index de28b4a..3ee35ba 100644 --- a/drivers/gpio/wm8994-gpio.c +++ b/drivers/gpio/wm8994-gpio.c @@ -80,6 +80,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/wm8994-core.c b/drivers/mfd/wm8994-core.c index 844e1c1..39cde82 100644 --- a/drivers/mfd/wm8994-core.c +++ b/drivers/mfd/wm8994-core.c @@ -172,9 +172,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, + }, }; /* @@ -235,6 +260,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) @@ -347,6 +377,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 */ @@ -374,16 +405,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); @@ -400,6 +435,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); @@ -468,6 +504,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..0ab6132 --- /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/jack.h b/include/sound/jack.h index f236e42..d90b9fa 100644 --- a/include/sound/jack.h +++ b/include/sound/jack.h @@ -42,6 +42,11 @@ enum snd_jack_types { SND_JACK_MECHANICAL = 0x0008, /* If detected separately */ SND_JACK_VIDEOOUT = 0x0010, SND_JACK_AVOUT = SND_JACK_LINEOUT | SND_JACK_VIDEOOUT, + + /* Kept separate from switches to facilitate implementation */ + SND_JACK_BTN_0 = 0x4000, + SND_JACK_BTN_1 = 0x2000, + SND_JACK_BTN_2 = 0x1000, }; struct snd_jack { @@ -50,6 +55,7 @@ struct snd_jack { int type; const char *id; char name[100]; + unsigned int key[3]; /* Keep in sync with definitions above */ void *private_data; void (*private_free)(struct snd_jack *); }; @@ -59,6 +65,8 @@ struct snd_jack { int snd_jack_new(struct snd_card *card, const char *id, int type, struct snd_jack **jack); void snd_jack_set_parent(struct snd_jack *jack, struct device *parent); +int snd_jack_set_key(struct snd_jack *jack, enum snd_jack_types type, + int keytype); void snd_jack_report(struct snd_jack *jack, int status); 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..d5d6ba8 100644 --- a/include/sound/soc-dapm.h +++ b/include/sound/soc-dapm.h @@ -339,6 +339,8 @@ 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); /* dapm widget types */ enum snd_soc_dapm_type { @@ -425,9 +427,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 */ int (*power_check)(struct snd_soc_dapm_widget *w); diff --git a/include/sound/soc.h b/include/sound/soc.h index a57fbfc..4ab3dad 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> @@ -212,6 +213,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 +262,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); @@ -363,6 +369,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 +381,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 */ }; @@ -462,14 +469,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; }; 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/wm8903.h b/include/sound/wm8903.h new file mode 100644 index 0000000..b4a0db2 --- /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/sound/core/jack.c b/sound/core/jack.c index f705eec..3813e7b 100644 --- a/sound/core/jack.c +++ b/sound/core/jack.c @@ -23,7 +23,7 @@ #include <sound/jack.h> #include <sound/core.h> -static int jack_types[] = { +static int jack_switch_types[] = { SW_HEADPHONE_INSERT, SW_MICROPHONE_INSERT, SW_LINEOUT_INSERT, @@ -55,7 +55,7 @@ static int snd_jack_dev_register(struct snd_device *device) { struct snd_jack *jack = device->device_data; struct snd_card *card = device->card; - int err; + int err, i; snprintf(jack->name, sizeof(jack->name), "%s %s", card->shortname, jack->id); @@ -65,6 +65,19 @@ static int snd_jack_dev_register(struct snd_device *device) if (!jack->input_dev->dev.parent) jack->input_dev->dev.parent = snd_card_get_device_link(card); + /* Add capabilities for any keys that are enabled */ + for (i = 0; i < ARRAY_SIZE(jack->key); i++) { + int testbit = SND_JACK_BTN_0 >> i; + + if (!(jack->type & testbit)) + continue; + + if (!jack->key[i]) + jack->key[i] = BTN_0 + i; + + input_set_capability(jack->input_dev, EV_KEY, jack->key[i]); + } + err = input_register_device(jack->input_dev); if (err == 0) jack->registered = 1; @@ -112,10 +125,10 @@ int snd_jack_new(struct snd_card *card, const char *id, int type, jack->type = type; - for (i = 0; i < ARRAY_SIZE(jack_types); i++) + for (i = 0; i < ARRAY_SIZE(jack_switch_types); i++) if (type & (1 << i)) input_set_capability(jack->input_dev, EV_SW, - jack_types[i]); + jack_switch_types[i]); err = snd_device_new(card, SNDRV_DEV_JACK, jack, &ops); if (err < 0) @@ -151,6 +164,43 @@ void snd_jack_set_parent(struct snd_jack *jack, struct device *parent) EXPORT_SYMBOL(snd_jack_set_parent); /** + * snd_jack_set_key - Set a key mapping on a jack + * + * @jack: The jack to configure + * @type: Jack report type for this key + * @keytype: Input layer key type to be reported + * + * Map a SND_JACK_BTN_ button type to an input layer key, allowing + * reporting of keys on accessories via the jack abstraction. If no + * mapping is provided but keys are enabled in the jack type then + * BTN_n numeric buttons will be reported. + * + * Note that this is intended to be use by simple devices with small + * numbers of keys that can be reported. It is also possible to + * access the input device directly - devices with complex input + * capabilities on accessories should consider doing this rather than + * using this abstraction. + * + * This function may only be called prior to registration of the jack. + */ +int snd_jack_set_key(struct snd_jack *jack, enum snd_jack_types type, + int keytype) +{ + int key = fls(SND_JACK_BTN_0) - fls(type); + + WARN_ON(jack->registered); + + if (!keytype || key >= ARRAY_SIZE(jack->key)) + return -EINVAL; + + jack->type |= type; + jack->key[key] = keytype; + + return 0; +} +EXPORT_SYMBOL(snd_jack_set_key); + +/** * snd_jack_report - Report the current status of a jack * * @jack: The jack to report status for @@ -163,10 +213,19 @@ void snd_jack_report(struct snd_jack *jack, int status) if (!jack) return; - for (i = 0; i < ARRAY_SIZE(jack_types); i++) { + for (i = 0; i < ARRAY_SIZE(jack->key); i++) { + int testbit = SND_JACK_BTN_0 >> i; + + if (jack->type & testbit) + input_report_key(jack->input_dev, jack->key[i], + status & testbit); + } + + for (i = 0; i < ARRAY_SIZE(jack_switch_types); i++) { int testbit = 1 << i; if (jack->type & testbit) - input_report_switch(jack->input_dev, jack_types[i], + input_report_switch(jack->input_dev, + jack_switch_types[i], status & testbit); } diff --git a/sound/soc/atmel/atmel-pcm.c b/sound/soc/atmel/atmel-pcm.c index 3e6628c..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..bc0ab47 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 @@ -90,7 +92,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 +116,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 +169,9 @@ config SND_SOC_TWL4030 select TWL4030_CODEC tristate +config SND_SOC_TWL6040 + tristate + config SND_SOC_UDA134X tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index dd5ce6d..3379041 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 @@ -62,7 +64,7 @@ snd-soc-wm2000-objs := wm2000.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 +72,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 +86,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 diff --git a/sound/soc/codecs/ad1938.c b/sound/soc/codecs/ad1938.c deleted file mode 100644 index c233810..0000000 --- a/sound/soc/codecs/ad1938.c +++ /dev/null @@ -1,521 +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/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..4bfd66b --- /dev/null +++ b/sound/soc/codecs/ad193x.c @@ -0,0 +1,545 @@ +/* + * 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_reg, dac_reg; + + adc_reg = 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_reg &= ~AD193X_ADC_SERFMT_MASK; + adc_reg |= AD193X_ADC_SERFMT_TDM; + break; + case SND_SOC_DAIFMT_DSP_A: + adc_reg &= ~AD193X_ADC_SERFMT_MASK; + adc_reg |= 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_reg &= ~AD193X_ADC_LEFT_HIGH; + adc_reg &= ~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_reg |= AD193X_ADC_LEFT_HIGH; + adc_reg &= ~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_reg &= ~AD193X_ADC_LEFT_HIGH; + adc_reg |= 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_reg |= AD193X_ADC_LEFT_HIGH; + adc_reg |= 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_reg |= AD193X_ADC_LCR_MASTER; + adc_reg |= 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_reg |= AD193X_ADC_LCR_MASTER; + adc_reg &= ~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_reg &= ~AD193X_ADC_LCR_MASTER; + adc_reg |= 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_reg &= ~AD193X_ADC_LCR_MASTER; + adc_reg &= ~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_CTRL2, adc_reg); + 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; + codec->private_data = 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/ak4642.c b/sound/soc/codecs/ak4642.c index 3ef16bb..de1809d 100644 --- a/sound/soc/codecs/ak4642.c +++ b/sound/soc/codecs/ak4642.c @@ -80,12 +80,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; @@ -176,17 +203,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); @@ -194,15 +216,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 @@ -211,7 +232,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); @@ -232,8 +252,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); @@ -249,9 +269,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; } @@ -259,6 +381,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 = { @@ -276,6 +400,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); @@ -337,26 +462,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/cq93vc.c b/sound/soc/codecs/cq93vc.c new file mode 100644 index 0000000..8f19b93 --- /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, codec->suspend_bias_level); + + 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/da7210.c b/sound/soc/codecs/da7210.c index cf2975a..35b8816 100644 --- a/sound/soc/codecs/da7210.c +++ b/sound/soc/codecs/da7210.c @@ -55,8 +55,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) @@ -74,15 +80,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) @@ -123,7 +128,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" @@ -241,7 +258,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, @@ -265,25 +283,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; } @@ -361,6 +438,7 @@ struct snd_soc_dai da7210_dai = { .formats = DA7210_FORMATS, }, .ops = &da7210_dai_ops, + .symmetric_rates = 1, }; EXPORT_SYMBOL_GPL(da7210_dai); @@ -415,9 +493,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 */ @@ -453,9 +545,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 d2ff1cd..942f5dc 100644 --- a/sound/soc/codecs/ssm2602.c +++ b/sound/soc/codecs/ssm2602.c @@ -139,6 +139,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), @@ -604,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); diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c index d50f169..ee2e1e3 100644 --- a/sound/soc/codecs/tlv320dac33.c +++ b/sound/soc/codecs/tlv320dac33.c @@ -93,6 +93,8 @@ struct tlv320dac33_priv { unsigned int nsample; /* burst read amount from host */ u8 burst_bclkdiv; /* BCLK divider value in burst mode */ + int keep_bclk; /* Keep the BCLK continuously running + * in FIFO modes */ enum dac33_state state; }; @@ -310,7 +312,8 @@ 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); } @@ -634,26 +637,6 @@ static irqreturn_t dac33_interrupt_handler(int irq, void *dev) return IRQ_HANDLED; } -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; - - /* 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); -} - static void dac33_oscwait(struct snd_soc_codec *codec) { int timeout = 20; @@ -751,6 +734,7 @@ static int dac33_prepare_chip(struct snd_pcm_substream *substream) } mutex_lock(&dac33->mutex); + dac33_soft_power(codec, 0); dac33_soft_power(codec, 1); reg_tmp = dac33_read_reg_cache(codec, DAC33_INT_OSC_CTRL); @@ -821,7 +805,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: /* @@ -832,7 +819,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: /* @@ -1181,7 +1171,6 @@ EXPORT_SYMBOL_GPL(soc_codec_dev_tlv320dac33); #define DAC33_FORMATS SNDRV_PCM_FMTBIT_S16_LE static struct snd_soc_dai_ops dac33_dai_ops = { - .shutdown = dac33_shutdown, .hw_params = dac33_hw_params, .prepare = dac33_pcm_prepare, .trigger = dac33_pcm_trigger, @@ -1249,6 +1238,7 @@ static int __devinit dac33_i2c_probe(struct i2c_client *client, dac33->power_gpio = pdata->power_gpio; dac33->burst_bclkdiv = pdata->burst_bclkdiv; + dac33->keep_bclk = pdata->keep_bclk; dac33->irq = client->irq; dac33->nsample = NSAMPLE_MAX; /* Disable FIFO use by default */ diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index 6f5d4af..bf59b8a 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -135,9 +135,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; }; /* @@ -173,12 +175,47 @@ 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 = codec->private_data; + 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) @@ -525,26 +562,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 = w->codec->private_data; \ \ 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; \ @@ -664,7 +701,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 */ diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c new file mode 100644 index 0000000..108c51a --- /dev/null +++ b/sound/soc/codecs/twl6040.c @@ -0,0 +1,1228 @@ +/* + * 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 = codec->private_data; + + 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 = codec->private_data; + 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); + +/* 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 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), + +}; + +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"), + + /* 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), + + /* 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"}, + + /* 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 = codec->private_data; + 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 = codec->private_data; + 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 = codec->private_data; + + 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 = codec->private_data; + 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 = codec->private_data; + + 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 = codec->private_data; + 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); + twl6040_set_bias_level(codec, codec->suspend_bias_level); + + 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; + codec->private_data = 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 = twl6040_codec->private_data; + 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/wm8350.c b/sound/soc/codecs/wm8350.c index df2c6d9..8ac92f5 100644 --- a/sound/soc/codecs/wm8350.c +++ b/sound/soc/codecs/wm8350.c @@ -54,6 +54,7 @@ struct wm8350_output { struct wm8350_jack_data { struct snd_soc_jack *jack; int report; + int short_report; }; struct wm8350_data { @@ -62,6 +63,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; @@ -1391,7 +1393,8 @@ 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) @@ -1420,8 +1423,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); @@ -1430,6 +1437,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 = codec->private_data; + 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) @@ -1493,6 +1554,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) { @@ -1521,11 +1586,14 @@ static int wm8350_remove(struct platform_device *pdev) 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); 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/wm8750.c b/sound/soc/codecs/wm8750.c index 475c67a..ee08408 100644 --- a/sound/soc/codecs/wm8750.c +++ b/sound/soc/codecs/wm8750.c @@ -29,8 +29,6 @@ #include "wm8750.h" -#define WM8750_VERSION "0.12" - /* codec private data */ struct wm8750_priv { unsigned int sysclk; @@ -613,10 +611,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; @@ -660,13 +664,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); @@ -695,14 +692,6 @@ 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; } @@ -746,9 +735,7 @@ static int wm8750_init(struct snd_soc_device *socdev, } /* 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); @@ -912,7 +899,6 @@ static int wm8750_probe(struct platform_device *pdev) 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; @@ -929,7 +915,6 @@ static int wm8750_probe(struct platform_device *pdev) 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; @@ -953,25 +938,6 @@ static int wm8750_probe(struct platform_device *pdev) 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; -} - /* power down chip */ static int wm8750_remove(struct platform_device *pdev) { @@ -980,7 +946,6 @@ static int wm8750_remove(struct platform_device *pdev) 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) diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c index 3595bd5..134b175 100644 --- a/sound/soc/codecs/wm8903.c +++ b/sound/soc/codecs/wm8903.c @@ -11,25 +11,26 @@ * * 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 <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" @@ -221,6 +222,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; }; @@ -243,13 +252,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 = codec->private_data; 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); @@ -257,20 +267,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; } @@ -1435,6 +1444,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 = codec->private_data; + 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 | \ @@ -1529,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); @@ -1555,6 +1676,7 @@ static __devinit int wm8903_i2c_probe(struct i2c_client *i2c, codec->reg_cache = &wm8903->reg_cache[0]; codec->private_data = wm8903; codec->volatile_register = wm8903_volatile_register; + init_completion(&wm8903->wseq); i2c_set_clientdata(i2c, codec); codec->control_data = i2c; @@ -1578,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); @@ -1618,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); @@ -1631,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); @@ -1640,12 +1812,16 @@ err: static __devexit int wm8903_i2c_remove(struct i2c_client *client) { struct snd_soc_codec *codec = i2c_get_clientdata(client); + struct wm8903_priv *priv = codec->private_data; snd_soc_unregister_dai(&wm8903_dai); snd_soc_unregister_codec(codec); wm8903_set_bias_level(codec, SND_SOC_BIAS_OFF); + if (client->irq) + free_irq(client->irq, priv); + kfree(codec->private_data); wm8903_codec = 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 593e47d..c5b50d7 100644 --- a/sound/soc/codecs/wm8904.c +++ b/sound/soc/codecs/wm8904.c @@ -2425,6 +2425,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; @@ -2530,6 +2531,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/wm8960.c b/sound/soc/codecs/wm8960.c index d07bcc1..c2960d3 100644 --- a/sound/soc/codecs/wm8960.c +++ b/sound/soc/codecs/wm8960.c @@ -22,6 +22,7 @@ #include <sound/soc-dapm.h> #include <sound/initval.h> #include <sound/tlv.h> +#include <sound/wm8960.h> #include "wm8960.h" @@ -30,8 +31,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 @@ -41,6 +48,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 @@ -67,6 +75,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) @@ -225,10 +236,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), @@ -247,6 +254,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" }, @@ -277,9 +295,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" }, @@ -296,17 +311,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 = codec->private_data; + 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; } @@ -407,10 +470,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) { @@ -429,18 +491,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); @@ -471,8 +523,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 = codec->private_data; + 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; + + 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; - snd_soc_write(codec, WM8960_APOP1, 0); + case SND_SOC_BIAS_OFF: break; } @@ -662,7 +807,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; } @@ -681,8 +826,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); + codec->set_bias_level(codec, codec->suspend_bias_level); return 0; } @@ -752,6 +897,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 { @@ -759,6 +906,9 @@ 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); @@ -769,7 +919,6 @@ static int wm8960_register(struct wm8960_priv *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; @@ -791,7 +940,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); @@ -840,7 +989,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); @@ -882,7 +1031,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..d67bfe1 100644 --- a/sound/soc/codecs/wm8960.h +++ b/sound/soc/codecs/wm8960.h @@ -114,14 +114,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/wm8994.c b/sound/soc/codecs/wm8994.c index f8355ac..e00201e 100644 --- a/sound/soc/codecs/wm8994.c +++ b/sound/soc/codecs/wm8994.c @@ -61,6 +61,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; @@ -86,6 +92,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; }; @@ -3337,6 +3345,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 |\ @@ -3348,6 +3386,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 = { @@ -3356,6 +3395,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[] = { @@ -3399,6 +3443,7 @@ struct snd_soc_dai wm8994_dai[] = { }, { .name = "WM8994 AIF3", + .id = 3, .playback = { .stream_name = "AIF3 Playback", .channels_min = 2, @@ -3413,6 +3458,7 @@ struct snd_soc_dai wm8994_dai[] = { .rates = WM8994_RATES, .formats = WM8994_FORMATS, }, + .ops = &wm8994_aif3_dai_ops, } }; EXPORT_SYMBOL_GPL(wm8994_dai); @@ -3669,6 +3715,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 = codec->private_data; + 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; @@ -3742,6 +3878,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. @@ -3749,7 +3909,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; @@ -3761,7 +3921,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; @@ -3811,7 +3971,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)); @@ -3826,6 +3986,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; @@ -3839,6 +4004,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..79d5915 100644 --- a/sound/soc/codecs/wm8994.h +++ b/sound/soc/codecs/wm8994.h @@ -23,4 +23,7 @@ extern struct snd_soc_dai wm8994_dai[]; #define WM8994_FLL1 1 #define WM8994_FLL2 2 +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/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/mcpdm.c b/sound/soc/omap/mcpdm.c index ad8df6c..479d3b6 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. @@ -38,46 +38,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"); } /* @@ -86,26 +86,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); } /* @@ -114,14 +114,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); } /* @@ -130,14 +130,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); } /* @@ -146,38 +146,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; } /* @@ -186,38 +186,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; } /* @@ -226,24 +226,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; } /* @@ -252,124 +252,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 @@ -377,108 +377,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..2d33a89 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) @@ -308,7 +333,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; @@ -353,6 +379,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 +431,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 +644,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, diff --git a/sound/soc/s3c24xx/s3c-i2s-v2.c b/sound/soc/s3c24xx/s3c-i2s-v2.c index 8851594..865f931 100644 --- a/sound/soc/s3c24xx/s3c-i2s-v2.c +++ b/sound/soc/s3c24xx/s3c-i2s-v2.c @@ -16,18 +16,12 @@ * 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> @@ -332,7 +326,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,34 +349,18 @@ 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) - 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; - } -#endif - -#ifdef CONFIG_PLAT_S3C64XX - iismod &= ~(S3C64XX_IISMOD_BLC_MASK | S3C2412_IISMOD_BCLK_MASK); + iismod &= ~S3C64XX_IISMOD_BLC_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); + iismod |= S3C64XX_IISMOD_BLC_8BIT; break; case SNDRV_PCM_FORMAT_S16_LE: - /* 16 bit sample, 32fs BCLK */ break; case SNDRV_PCM_FORMAT_S24_LE: - /* 24 bit sample, 48fs BCLK */ - iismod |= (S3C64XX_IISMOD_BLC_24BIT | S3C2412_IISMOD_BCLK_48FS); + iismod |= S3C64XX_IISMOD_BLC_24BIT; break; } -#endif writel(iismod, i2s->regs + S3C2412_IISMOD); pr_debug("%s: w: IISMOD: %x\n", __func__, iismod); @@ -472,29 +450,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 +479,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 +523,21 @@ 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; +} + /* default table of all avaialable root fs divisors */ static unsigned int iis_fs_tab[] = { 256, 512, 384, 768 }; @@ -735,10 +720,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; + /* 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..b094d3c 100644 --- a/sound/soc/s3c24xx/s3c-i2s-v2.h +++ b/sound/soc/s3c24xx/s3c-i2s-v2.h @@ -25,6 +25,10 @@ #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 + /** * struct s3c_i2sv2_info - S3C I2S-V2 information * @dev: The parent device passed to use from the probe. diff --git a/sound/soc/s3c24xx/s3c2412-i2s.c b/sound/soc/s3c24xx/s3c2412-i2s.c index 359e593..f3148f9 100644 --- a/sound/soc/s3c24xx/s3c2412-i2s.c +++ b/sound/soc/s3c24xx/s3c2412-i2s.c @@ -103,6 +103,10 @@ struct clk *s3c2412_get_iisclk(void) } EXPORT_SYMBOL_GPL(s3c2412_get_iisclk); +static inline struct s3c_i2sv2_info *to_info(struct snd_soc_dai *cpu_dai) +{ + return cpu_dai->private_data; +} static int s3c2412_i2s_probe(struct platform_device *pdev, struct snd_soc_dai *dai) @@ -142,6 +146,41 @@ 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 | \ @@ -149,6 +188,7 @@ static int s3c2412_i2s_probe(struct platform_device *pdev, 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..60cac00 100644 --- a/sound/soc/s3c24xx/s3c2412-i2s.h +++ b/sound/soc/s3c24xx/s3c2412-i2s.h @@ -21,8 +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) +#define S3C2412_CLKSRC_PCLK S3C_I2SV2_CLKSRC_PCLK +#define S3C2412_CLKSRC_I2SCLK S3C_I2SV2_CLKSRC_AUDIOBUS extern struct clk *s3c2412_get_iisclk(void); diff --git a/sound/soc/s3c24xx/s3c64xx-i2s.c b/sound/soc/s3c24xx/s3c64xx-i2s.c index a72c251..ab1fa15 100644 --- a/sound/soc/s3c24xx/s3c64xx-i2s.c +++ b/sound/soc/s3c24xx/s3c64xx-i2s.c @@ -12,9 +12,6 @@ * 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> @@ -130,15 +127,6 @@ 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, }; diff --git a/sound/soc/s3c24xx/s3c64xx-i2s.h b/sound/soc/s3c24xx/s3c64xx-i2s.h index abe7253..53d2a0a 100644 --- a/sound/soc/s3c24xx/s3c64xx-i2s.h +++ b/sound/soc/s3c24xx/s3c64xx-i2s.h @@ -23,9 +23,18 @@ 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 + +#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 snd_soc_dai s3c64xx_i2s_dai[]; 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 993abb7..f14bbb0 100644 --- a/sound/soc/sh/fsi.c +++ b/sound/soc/sh/fsi.c @@ -40,14 +40,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 */ @@ -79,6 +84,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) @@ -104,11 +120,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; }; @@ -316,7 +339,7 @@ static int fsi_get_fifo_residue(struct fsi_priv *fsi, int is_play) /************************************************************************ - ctrl function + irq function ************************************************************************/ @@ -325,8 +348,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) @@ -334,10 +357,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); @@ -349,41 +401,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); } @@ -558,12 +630,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); @@ -574,7 +645,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; } @@ -668,29 +739,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); /* @@ -699,8 +747,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; } @@ -912,6 +964,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; @@ -921,6 +974,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) { @@ -949,6 +1008,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); @@ -961,7 +1021,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; @@ -1028,6 +1089,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", @@ -1035,6 +1113,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..9dfe9a5 100644 --- a/sound/soc/soc-cache.c +++ b/sound/soc/soc-cache.c @@ -159,7 +159,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) @@ -180,6 +181,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]; @@ -226,6 +228,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) { @@ -366,6 +402,84 @@ 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; + } + + 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 +502,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 +515,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 d0efd5e..d59076e 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -315,7 +315,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, @@ -404,6 +404,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, @@ -421,6 +427,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); @@ -454,12 +466,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; @@ -535,15 +550,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 @@ -563,7 +579,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 */ @@ -801,6 +816,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, @@ -809,6 +859,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 @@ -858,7 +909,7 @@ static int soc_suspend(struct device *dev) 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 */ @@ -947,7 +998,7 @@ static void soc_resume_deferred(struct work_struct *work) 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) @@ -1232,26 +1283,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; @@ -1335,7 +1385,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; diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 6c33510..476dbe6 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -97,7 +97,6 @@ static void pop_dbg(u32 pop_time, const char *fmt, ...) if (pop_time) { vprintk(fmt, args); - pop_wait(pop_time); } va_end(args); @@ -314,62 +313,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) @@ -464,20 +415,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 */ @@ -633,16 +574,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)) { @@ -809,10 +742,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) { @@ -824,10 +753,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)) { @@ -980,7 +905,10 @@ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) break; default: - power = w->power_check(w); + if (!w->force) + power = w->power_check(w); + else + power = 1; if (power) sys_power = 1; break; @@ -1075,6 +1003,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; } @@ -1337,6 +1266,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; } } @@ -1593,12 +1525,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) @@ -1658,13 +1584,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 */ @@ -2134,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 diff --git a/sound/soc/soc-jack.c b/sound/soc/soc-jack.c index 3c07a94..f8fd22c 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); } @@ -93,6 +94,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 +147,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) |