diff options
40 files changed, 3370 insertions, 166 deletions
diff --git a/arch/arm/mach-omap2/mcbsp.c b/arch/arm/mach-omap2/mcbsp.c index a5c0f0435cd6..7d22caf60090 100644 --- a/arch/arm/mach-omap2/mcbsp.c +++ b/arch/arm/mach-omap2/mcbsp.c @@ -129,6 +129,7 @@ static struct omap_mcbsp_platform_data omap34xx_mcbsp_pdata[] = { .rx_irq = INT_24XX_MCBSP1_IRQ_RX, .tx_irq = INT_24XX_MCBSP1_IRQ_TX, .ops = &omap2_mcbsp_ops, + .buffer_size = 0x6F, }, { .phys_base = OMAP34XX_MCBSP2_BASE, @@ -137,6 +138,7 @@ static struct omap_mcbsp_platform_data omap34xx_mcbsp_pdata[] = { .rx_irq = INT_24XX_MCBSP2_IRQ_RX, .tx_irq = INT_24XX_MCBSP2_IRQ_TX, .ops = &omap2_mcbsp_ops, + .buffer_size = 0x3FF, }, { .phys_base = OMAP34XX_MCBSP3_BASE, @@ -145,6 +147,7 @@ static struct omap_mcbsp_platform_data omap34xx_mcbsp_pdata[] = { .rx_irq = INT_24XX_MCBSP3_IRQ_RX, .tx_irq = INT_24XX_MCBSP3_IRQ_TX, .ops = &omap2_mcbsp_ops, + .buffer_size = 0x6F, }, { .phys_base = OMAP34XX_MCBSP4_BASE, @@ -153,6 +156,7 @@ static struct omap_mcbsp_platform_data omap34xx_mcbsp_pdata[] = { .rx_irq = INT_24XX_MCBSP4_IRQ_RX, .tx_irq = INT_24XX_MCBSP4_IRQ_TX, .ops = &omap2_mcbsp_ops, + .buffer_size = 0x6F, }, { .phys_base = OMAP34XX_MCBSP5_BASE, @@ -161,6 +165,7 @@ static struct omap_mcbsp_platform_data omap34xx_mcbsp_pdata[] = { .rx_irq = INT_24XX_MCBSP5_IRQ_RX, .tx_irq = INT_24XX_MCBSP5_IRQ_TX, .ops = &omap2_mcbsp_ops, + .buffer_size = 0x6F, }, }; #define OMAP34XX_MCBSP_PDATA_SZ ARRAY_SIZE(omap34xx_mcbsp_pdata) diff --git a/arch/arm/plat-omap/include/mach/mcbsp.h b/arch/arm/plat-omap/include/mach/mcbsp.h index 57249bb1e9bc..70e950e295e1 100644 --- a/arch/arm/plat-omap/include/mach/mcbsp.h +++ b/arch/arm/plat-omap/include/mach/mcbsp.h @@ -134,6 +134,11 @@ #define OMAP_MCBSP_REG_XCERG 0x74 #define OMAP_MCBSP_REG_XCERH 0x78 #define OMAP_MCBSP_REG_SYSCON 0x8C +#define OMAP_MCBSP_REG_THRSH2 0x90 +#define OMAP_MCBSP_REG_THRSH1 0x94 +#define OMAP_MCBSP_REG_IRQST 0xA0 +#define OMAP_MCBSP_REG_IRQEN 0xA4 +#define OMAP_MCBSP_REG_WAKEUPEN 0xA8 #define OMAP_MCBSP_REG_XCCR 0xAC #define OMAP_MCBSP_REG_RCCR 0xB0 @@ -249,8 +254,27 @@ #define RDISABLE 0x0001 /********************** McBSP SYSCONFIG bit definitions ********************/ +#define CLOCKACTIVITY(value) ((value)<<8) +#define SIDLEMODE(value) ((value)<<3) +#define ENAWAKEUP 0x0004 #define SOFTRST 0x0002 +/********************** McBSP DMA operating modes **************************/ +#define MCBSP_DMA_MODE_ELEMENT 0 +#define MCBSP_DMA_MODE_THRESHOLD 1 +#define MCBSP_DMA_MODE_FRAME 2 + +/********************** McBSP WAKEUPEN bit definitions *********************/ +#define XEMPTYEOFEN 0x4000 +#define XRDYEN 0x0400 +#define XEOFEN 0x0200 +#define XFSXEN 0x0100 +#define XSYNCERREN 0x0080 +#define RRDYEN 0x0008 +#define REOFEN 0x0004 +#define RFSREN 0x0002 +#define RSYNCERREN 0x0001 + /* we don't do multichannel for now */ struct omap_mcbsp_reg_cfg { u16 spcr2; @@ -344,6 +368,9 @@ struct omap_mcbsp_platform_data { u8 dma_rx_sync, dma_tx_sync; u16 rx_irq, tx_irq; struct omap_mcbsp_ops *ops; +#ifdef CONFIG_ARCH_OMAP34XX + u16 buffer_size; +#endif }; struct omap_mcbsp { @@ -377,6 +404,11 @@ struct omap_mcbsp { struct omap_mcbsp_platform_data *pdata; struct clk *iclk; struct clk *fclk; +#ifdef CONFIG_ARCH_OMAP34XX + int dma_op_mode; + u16 max_tx_thres; + u16 max_rx_thres; +#endif }; extern struct omap_mcbsp **mcbsp_ptr; extern int omap_mcbsp_count; @@ -385,10 +417,27 @@ int omap_mcbsp_init(void); void omap_mcbsp_register_board_cfg(struct omap_mcbsp_platform_data *config, int size); void omap_mcbsp_config(unsigned int id, const struct omap_mcbsp_reg_cfg * config); +#ifdef CONFIG_ARCH_OMAP34XX +void omap_mcbsp_set_tx_threshold(unsigned int id, u16 threshold); +void omap_mcbsp_set_rx_threshold(unsigned int id, u16 threshold); +u16 omap_mcbsp_get_max_tx_threshold(unsigned int id); +u16 omap_mcbsp_get_max_rx_threshold(unsigned int id); +int omap_mcbsp_get_dma_op_mode(unsigned int id); +#else +static inline void omap_mcbsp_set_tx_threshold(unsigned int id, u16 threshold) +{ } +static inline void omap_mcbsp_set_rx_threshold(unsigned int id, u16 threshold) +{ } +static inline u16 omap_mcbsp_get_max_tx_threshold(unsigned int id) { return 0; } +static inline u16 omap_mcbsp_get_max_rx_threshold(unsigned int id) { return 0; } +static inline int omap_mcbsp_get_dma_op_mode(unsigned int id) { return 0; } +#endif int omap_mcbsp_request(unsigned int id); void omap_mcbsp_free(unsigned int id); void omap_mcbsp_start(unsigned int id, int tx, int rx); void omap_mcbsp_stop(unsigned int id, int tx, int rx); +void omap_mcbsp_xmit_enable(unsigned int id, u8 enable); +void omap_mcbsp_recv_enable(unsigned int id, u8 enable); void omap_mcbsp_xmit_word(unsigned int id, u32 word); u32 omap_mcbsp_recv_word(unsigned int id); diff --git a/arch/arm/plat-omap/mcbsp.c b/arch/arm/plat-omap/mcbsp.c index a3d2313460b3..b63a7209b41c 100644 --- a/arch/arm/plat-omap/mcbsp.c +++ b/arch/arm/plat-omap/mcbsp.c @@ -198,6 +198,170 @@ void omap_mcbsp_config(unsigned int id, const struct omap_mcbsp_reg_cfg *config) } EXPORT_SYMBOL(omap_mcbsp_config); +#ifdef CONFIG_ARCH_OMAP34XX +/* + * omap_mcbsp_set_tx_threshold configures how to deal + * with transmit threshold. the threshold value and handler can be + * configure in here. + */ +void omap_mcbsp_set_tx_threshold(unsigned int id, u16 threshold) +{ + struct omap_mcbsp *mcbsp; + void __iomem *io_base; + + if (!cpu_is_omap34xx()) + return; + + if (!omap_mcbsp_check_valid_id(id)) { + printk(KERN_ERR "%s: Invalid id (%d)\n", __func__, id + 1); + return; + } + mcbsp = id_to_mcbsp_ptr(id); + io_base = mcbsp->io_base; + + OMAP_MCBSP_WRITE(io_base, THRSH2, threshold); +} +EXPORT_SYMBOL(omap_mcbsp_set_tx_threshold); + +/* + * omap_mcbsp_set_rx_threshold configures how to deal + * with receive threshold. the threshold value and handler can be + * configure in here. + */ +void omap_mcbsp_set_rx_threshold(unsigned int id, u16 threshold) +{ + struct omap_mcbsp *mcbsp; + void __iomem *io_base; + + if (!cpu_is_omap34xx()) + return; + + if (!omap_mcbsp_check_valid_id(id)) { + printk(KERN_ERR "%s: Invalid id (%d)\n", __func__, id + 1); + return; + } + mcbsp = id_to_mcbsp_ptr(id); + io_base = mcbsp->io_base; + + OMAP_MCBSP_WRITE(io_base, THRSH1, threshold); +} +EXPORT_SYMBOL(omap_mcbsp_set_rx_threshold); + +/* + * omap_mcbsp_get_max_tx_thres just return the current configured + * maximum threshold for transmission + */ +u16 omap_mcbsp_get_max_tx_threshold(unsigned int id) +{ + struct omap_mcbsp *mcbsp; + + if (!omap_mcbsp_check_valid_id(id)) { + printk(KERN_ERR "%s: Invalid id (%d)\n", __func__, id + 1); + return -ENODEV; + } + mcbsp = id_to_mcbsp_ptr(id); + + return mcbsp->max_tx_thres; +} +EXPORT_SYMBOL(omap_mcbsp_get_max_tx_threshold); + +/* + * omap_mcbsp_get_max_rx_thres just return the current configured + * maximum threshold for reception + */ +u16 omap_mcbsp_get_max_rx_threshold(unsigned int id) +{ + struct omap_mcbsp *mcbsp; + + if (!omap_mcbsp_check_valid_id(id)) { + printk(KERN_ERR "%s: Invalid id (%d)\n", __func__, id + 1); + return -ENODEV; + } + mcbsp = id_to_mcbsp_ptr(id); + + return mcbsp->max_rx_thres; +} +EXPORT_SYMBOL(omap_mcbsp_get_max_rx_threshold); + +/* + * omap_mcbsp_get_dma_op_mode just return the current configured + * operating mode for the mcbsp channel + */ +int omap_mcbsp_get_dma_op_mode(unsigned int id) +{ + struct omap_mcbsp *mcbsp; + int dma_op_mode; + + if (!omap_mcbsp_check_valid_id(id)) { + printk(KERN_ERR "%s: Invalid id (%u)\n", __func__, id + 1); + return -ENODEV; + } + mcbsp = id_to_mcbsp_ptr(id); + + spin_lock_irq(&mcbsp->lock); + dma_op_mode = mcbsp->dma_op_mode; + spin_unlock_irq(&mcbsp->lock); + + return dma_op_mode; +} +EXPORT_SYMBOL(omap_mcbsp_get_dma_op_mode); + +static inline void omap34xx_mcbsp_request(struct omap_mcbsp *mcbsp) +{ + /* + * Enable wakup behavior, smart idle and all wakeups + * REVISIT: some wakeups may be unnecessary + */ + if (cpu_is_omap34xx()) { + u16 syscon; + + syscon = OMAP_MCBSP_READ(mcbsp->io_base, SYSCON); + syscon &= ~(ENAWAKEUP | SIDLEMODE(0x03) | CLOCKACTIVITY(0x03)); + + spin_lock_irq(&mcbsp->lock); + if (mcbsp->dma_op_mode == MCBSP_DMA_MODE_THRESHOLD) { + syscon |= (ENAWAKEUP | SIDLEMODE(0x02) | + CLOCKACTIVITY(0x02)); + OMAP_MCBSP_WRITE(mcbsp->io_base, WAKEUPEN, + XRDYEN | RRDYEN); + } else { + syscon |= SIDLEMODE(0x01); + } + spin_unlock_irq(&mcbsp->lock); + + OMAP_MCBSP_WRITE(mcbsp->io_base, SYSCON, syscon); + } +} + +static inline void omap34xx_mcbsp_free(struct omap_mcbsp *mcbsp) +{ + /* + * Disable wakup behavior, smart idle and all wakeups + */ + if (cpu_is_omap34xx()) { + u16 syscon; + + syscon = OMAP_MCBSP_READ(mcbsp->io_base, SYSCON); + syscon &= ~(ENAWAKEUP | SIDLEMODE(0x03) | CLOCKACTIVITY(0x03)); + /* + * HW bug workaround - If no_idle mode is taken, we need to + * go to smart_idle before going to always_idle, or the + * device will not hit retention anymore. + */ + syscon |= SIDLEMODE(0x02); + OMAP_MCBSP_WRITE(mcbsp->io_base, SYSCON, syscon); + + syscon &= ~(SIDLEMODE(0x03)); + OMAP_MCBSP_WRITE(mcbsp->io_base, SYSCON, syscon); + + OMAP_MCBSP_WRITE(mcbsp->io_base, WAKEUPEN, 0); + } +} +#else +static inline void omap34xx_mcbsp_request(struct omap_mcbsp *mcbsp) {} +static inline void omap34xx_mcbsp_free(struct omap_mcbsp *mcbsp) {} +#endif + /* * We can choose between IRQ based or polled IO. * This needs to be called before omap_mcbsp_request(). @@ -257,6 +421,9 @@ int omap_mcbsp_request(unsigned int id) clk_enable(mcbsp->iclk); clk_enable(mcbsp->fclk); + /* Do procedure specific to omap34xx arch, if applicable */ + omap34xx_mcbsp_request(mcbsp); + /* * Make sure that transmitter, receiver and sample-rate generator are * not running before activating IRQs. @@ -305,6 +472,9 @@ void omap_mcbsp_free(unsigned int id) if (mcbsp->pdata && mcbsp->pdata->ops && mcbsp->pdata->ops->free) mcbsp->pdata->ops->free(id); + /* Do procedure specific to omap34xx arch, if applicable */ + omap34xx_mcbsp_free(mcbsp); + clk_disable(mcbsp->fclk); clk_disable(mcbsp->iclk); @@ -365,7 +535,13 @@ void omap_mcbsp_start(unsigned int id, int tx, int rx) w = OMAP_MCBSP_READ(io_base, SPCR1); OMAP_MCBSP_WRITE(io_base, SPCR1, w | (rx & 1)); - udelay(100); + /* + * Worst case: CLKSRG*2 = 8000khz: (1/8000) * 2 * 2 usec + * REVISIT: 100us may give enough time for two CLKSRG, however + * due to some unknown PM related, clock gating etc. reason it + * is now at 500us. + */ + udelay(500); if (idle) { /* Start frame sync */ @@ -412,6 +588,58 @@ void omap_mcbsp_stop(unsigned int id, int tx, int rx) } EXPORT_SYMBOL(omap_mcbsp_stop); +void omap_mcbsp_xmit_enable(unsigned int id, u8 enable) +{ + struct omap_mcbsp *mcbsp; + void __iomem *io_base; + u16 w; + + if (!(cpu_is_omap2430() || cpu_is_omap34xx())) + return; + + if (!omap_mcbsp_check_valid_id(id)) { + printk(KERN_ERR "%s: Invalid id (%d)\n", __func__, id + 1); + return; + } + + mcbsp = id_to_mcbsp_ptr(id); + io_base = mcbsp->io_base; + + w = OMAP_MCBSP_READ(io_base, XCCR); + + if (enable) + OMAP_MCBSP_WRITE(io_base, XCCR, w & ~(XDISABLE)); + else + OMAP_MCBSP_WRITE(io_base, XCCR, w | XDISABLE); +} +EXPORT_SYMBOL(omap_mcbsp_xmit_enable); + +void omap_mcbsp_recv_enable(unsigned int id, u8 enable) +{ + struct omap_mcbsp *mcbsp; + void __iomem *io_base; + u16 w; + + if (!(cpu_is_omap2430() || cpu_is_omap34xx())) + return; + + if (!omap_mcbsp_check_valid_id(id)) { + printk(KERN_ERR "%s: Invalid id (%d)\n", __func__, id + 1); + return; + } + + mcbsp = id_to_mcbsp_ptr(id); + io_base = mcbsp->io_base; + + w = OMAP_MCBSP_READ(io_base, RCCR); + + if (enable) + OMAP_MCBSP_WRITE(io_base, RCCR, w & ~(RDISABLE)); + else + OMAP_MCBSP_WRITE(io_base, RCCR, w | RDISABLE); +} +EXPORT_SYMBOL(omap_mcbsp_recv_enable); + /* polled mcbsp i/o operations */ int omap_mcbsp_pollwrite(unsigned int id, u16 buf) { @@ -897,6 +1125,147 @@ void omap_mcbsp_set_spi_mode(unsigned int id, } EXPORT_SYMBOL(omap_mcbsp_set_spi_mode); +#ifdef CONFIG_ARCH_OMAP34XX +#define max_thres(m) (mcbsp->pdata->buffer_size) +#define valid_threshold(m, val) ((val) <= max_thres(m)) +#define THRESHOLD_PROP_BUILDER(prop) \ +static ssize_t prop##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct omap_mcbsp *mcbsp = dev_get_drvdata(dev); \ + \ + return sprintf(buf, "%u\n", mcbsp->prop); \ +} \ + \ +static ssize_t prop##_store(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t size) \ +{ \ + struct omap_mcbsp *mcbsp = dev_get_drvdata(dev); \ + unsigned long val; \ + int status; \ + \ + status = strict_strtoul(buf, 0, &val); \ + if (status) \ + return status; \ + \ + if (!valid_threshold(mcbsp, val)) \ + return -EDOM; \ + \ + mcbsp->prop = val; \ + return size; \ +} \ + \ +static DEVICE_ATTR(prop, 0644, prop##_show, prop##_store); + +THRESHOLD_PROP_BUILDER(max_tx_thres); +THRESHOLD_PROP_BUILDER(max_rx_thres); + +static ssize_t dma_op_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct omap_mcbsp *mcbsp = dev_get_drvdata(dev); + int dma_op_mode; + + spin_lock_irq(&mcbsp->lock); + dma_op_mode = mcbsp->dma_op_mode; + spin_unlock_irq(&mcbsp->lock); + + return sprintf(buf, "current mode: %d\n" + "possible mode values are:\n" + "%d - %s\n" + "%d - %s\n" + "%d - %s\n", + dma_op_mode, + MCBSP_DMA_MODE_ELEMENT, "element mode", + MCBSP_DMA_MODE_THRESHOLD, "threshold mode", + MCBSP_DMA_MODE_FRAME, "frame mode"); +} + +static ssize_t dma_op_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct omap_mcbsp *mcbsp = dev_get_drvdata(dev); + unsigned long val; + int status; + + status = strict_strtoul(buf, 0, &val); + if (status) + return status; + + spin_lock_irq(&mcbsp->lock); + + if (!mcbsp->free) { + size = -EBUSY; + goto unlock; + } + + if (val > MCBSP_DMA_MODE_FRAME || val < MCBSP_DMA_MODE_ELEMENT) { + size = -EINVAL; + goto unlock; + } + + mcbsp->dma_op_mode = val; + +unlock: + spin_unlock_irq(&mcbsp->lock); + + return size; +} + +static DEVICE_ATTR(dma_op_mode, 0644, dma_op_mode_show, dma_op_mode_store); + +static const struct attribute *additional_attrs[] = { + &dev_attr_max_tx_thres.attr, + &dev_attr_max_rx_thres.attr, + &dev_attr_dma_op_mode.attr, + NULL, +}; + +static const struct attribute_group additional_attr_group = { + .attrs = (struct attribute **)additional_attrs, +}; + +static inline int __devinit omap_additional_add(struct device *dev) +{ + return sysfs_create_group(&dev->kobj, &additional_attr_group); +} + +static inline void __devexit omap_additional_remove(struct device *dev) +{ + sysfs_remove_group(&dev->kobj, &additional_attr_group); +} + +static inline void __devinit omap34xx_device_init(struct omap_mcbsp *mcbsp) +{ + mcbsp->dma_op_mode = MCBSP_DMA_MODE_ELEMENT; + if (cpu_is_omap34xx()) { + mcbsp->max_tx_thres = max_thres(mcbsp); + mcbsp->max_rx_thres = max_thres(mcbsp); + /* + * REVISIT: Set dmap_op_mode to THRESHOLD as default + * for mcbsp2 instances. + */ + if (omap_additional_add(mcbsp->dev)) + dev_warn(mcbsp->dev, + "Unable to create additional controls\n"); + } else { + mcbsp->max_tx_thres = -EINVAL; + mcbsp->max_rx_thres = -EINVAL; + } +} + +static inline void __devexit omap34xx_device_exit(struct omap_mcbsp *mcbsp) +{ + if (cpu_is_omap34xx()) + omap_additional_remove(mcbsp->dev); +} +#else +static inline void __devinit omap34xx_device_init(struct omap_mcbsp *mcbsp) {} +static inline void __devexit omap34xx_device_exit(struct omap_mcbsp *mcbsp) {} +#endif /* CONFIG_ARCH_OMAP34XX */ + /* * McBSP1 and McBSP3 are directly mapped on 1610 and 1510. * 730 has only 2 McBSP, and both of them are MPU peripherals. @@ -967,6 +1336,10 @@ static int __devinit omap_mcbsp_probe(struct platform_device *pdev) mcbsp->dev = &pdev->dev; mcbsp_ptr[id] = mcbsp; platform_set_drvdata(pdev, mcbsp); + + /* Initialize mcbsp properties for OMAP34XX if needed / applicable */ + omap34xx_device_init(mcbsp); + return 0; err_fclk: @@ -990,6 +1363,8 @@ static int __devexit omap_mcbsp_remove(struct platform_device *pdev) mcbsp->pdata->ops->free) mcbsp->pdata->ops->free(mcbsp->id); + omap34xx_device_exit(mcbsp); + clk_disable(mcbsp->fclk); clk_disable(mcbsp->iclk); clk_put(mcbsp->fclk); diff --git a/arch/arm/plat-s3c/include/plat/audio-simtec.h b/arch/arm/plat-s3c/include/plat/audio-simtec.h new file mode 100644 index 000000000000..0f440b9168db --- /dev/null +++ b/arch/arm/plat-s3c/include/plat/audio-simtec.h @@ -0,0 +1,37 @@ +/* arch/arm/plat-s3c/include/plat/audio-simtec.h + * + * Copyright 2008 Simtec Electronics + * http://armlinux.simtec.co.uk/ + * Ben Dooks <ben@simtec.co.uk> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Simtec Audio support. +*/ + +/** + * struct s3c24xx_audio_simtec_pdata - platform data for simtec audio + * @use_mpllin: Select codec clock from MPLLin + * @output_cdclk: Need to output CDCLK to the codec + * @have_mic: Set if we have a MIC socket + * @have_lout: Set if we have a LineOut socket + * @amp_gpio: GPIO pin to enable the AMP + * @amp_gain: Option GPIO to control AMP gain + */ +struct s3c24xx_audio_simtec_pdata { + unsigned int use_mpllin:1; + unsigned int output_cdclk:1; + + unsigned int have_mic:1; + unsigned int have_lout:1; + + int amp_gpio; + int amp_gain[2]; + + void (*startup)(void); +}; + +extern int simtec_audio_add(const char *codec_name, + struct s3c24xx_audio_simtec_pdata *pdata); diff --git a/arch/arm/plat-s3c/include/plat/regs-s3c2412-iis.h b/arch/arm/plat-s3c/include/plat/regs-s3c2412-iis.h index 0fad7571030e..07659dad1748 100644 --- a/arch/arm/plat-s3c/include/plat/regs-s3c2412-iis.h +++ b/arch/arm/plat-s3c/include/plat/regs-s3c2412-iis.h @@ -33,6 +33,11 @@ #define S3C2412_IISCON_RXDMA_ACTIVE (1 << 1) #define S3C2412_IISCON_IIS_ACTIVE (1 << 0) +#define S3C64XX_IISMOD_BLC_16BIT (0 << 13) +#define S3C64XX_IISMOD_BLC_8BIT (1 << 13) +#define S3C64XX_IISMOD_BLC_24BIT (2 << 13) +#define S3C64XX_IISMOD_BLC_MASK (3 << 13) + #define S3C64XX_IISMOD_IMS_PCLK (0 << 10) #define S3C64XX_IISMOD_IMS_SYSMUX (1 << 10) diff --git a/include/sound/sh_fsi.h b/include/sound/sh_fsi.h new file mode 100644 index 000000000000..c0227361a876 --- /dev/null +++ b/include/sound/sh_fsi.h @@ -0,0 +1,83 @@ +#ifndef __SOUND_FSI_H +#define __SOUND_FSI_H + +/* + * Fifo-attached Serial Interface (FSI) support for SH7724 + * + * Copyright (C) 2009 Renesas Solutions Corp. + * Kuninori Morimoto <morimoto.kuninori@renesas.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +/* flags format + + * 0xABCDEEFF + * + * A: channel size for TDM (input) + * B: channel size for TDM (ooutput) + * C: inversion + * D: mode + * E: input format + * F: output format + */ + +#include <linux/clk.h> +#include <sound/soc.h> + +/* TDM channel */ +#define SH_FSI_SET_CH_I(x) ((x & 0xF) << 28) +#define SH_FSI_SET_CH_O(x) ((x & 0xF) << 24) + +#define SH_FSI_CH_IMASK 0xF0000000 +#define SH_FSI_CH_OMASK 0x0F000000 +#define SH_FSI_GET_CH_I(x) ((x & SH_FSI_CH_IMASK) >> 28) +#define SH_FSI_GET_CH_O(x) ((x & SH_FSI_CH_OMASK) >> 24) + +/* clock inversion */ +#define SH_FSI_INVERSION_MASK 0x00F00000 +#define SH_FSI_LRM_INV (1 << 20) +#define SH_FSI_BRM_INV (1 << 21) +#define SH_FSI_LRS_INV (1 << 22) +#define SH_FSI_BRS_INV (1 << 23) + +/* mode */ +#define SH_FSI_MODE_MASK 0x000F0000 +#define SH_FSI_IN_SLAVE_MODE (1 << 16) /* default master mode */ +#define SH_FSI_OUT_SLAVE_MODE (1 << 17) /* default master mode */ + +/* DI format */ +#define SH_FSI_FMT_MASK 0x000000FF +#define SH_FSI_IFMT(x) (((SH_FSI_FMT_ ## x) & SH_FSI_FMT_MASK) << 8) +#define SH_FSI_OFMT(x) (((SH_FSI_FMT_ ## x) & SH_FSI_FMT_MASK) << 0) +#define SH_FSI_GET_IFMT(x) ((x >> 8) & SH_FSI_FMT_MASK) +#define SH_FSI_GET_OFMT(x) ((x >> 0) & SH_FSI_FMT_MASK) + +#define SH_FSI_FMT_MONO (1 << 0) +#define SH_FSI_FMT_MONO_DELAY (1 << 1) +#define SH_FSI_FMT_PCM (1 << 2) +#define SH_FSI_FMT_I2S (1 << 3) +#define SH_FSI_FMT_TDM (1 << 4) +#define SH_FSI_FMT_TDM_DELAY (1 << 5) + +#define SH_FSI_IFMT_TDM_CH(x) \ + (SH_FSI_IFMT(TDM) | SH_FSI_SET_CH_I(x)) +#define SH_FSI_IFMT_TDM_DELAY_CH(x) \ + (SH_FSI_IFMT(TDM_DELAY) | SH_FSI_SET_CH_I(x)) + +#define SH_FSI_OFMT_TDM_CH(x) \ + (SH_FSI_OFMT(TDM) | SH_FSI_SET_CH_O(x)) +#define SH_FSI_OFMT_TDM_DELAY_CH(x) \ + (SH_FSI_OFMT(TDM_DELAY) | SH_FSI_SET_CH_O(x)) + +struct sh_fsi_platform_info { + unsigned long porta_flags; + unsigned long portb_flags; +}; + +extern struct snd_soc_dai fsi_soc_dai[2]; +extern struct snd_soc_platform fsi_soc_platform; + +#endif /* __SOUND_FSI_H */ diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h index 338840510617..c1410e3191e3 100644 --- a/include/sound/soc-dapm.h +++ b/include/sound/soc-dapm.h @@ -289,6 +289,7 @@ void snd_soc_dapm_shutdown(struct snd_soc_device *socdev); /* dapm sys fs - used by the core */ int snd_soc_dapm_sys_add(struct device *dev); +void snd_soc_dapm_debugfs_init(struct snd_soc_codec *codec); /* dapm audio pin control and status */ int snd_soc_dapm_enable_pin(struct snd_soc_codec *codec, const char *pin); diff --git a/include/sound/soc.h b/include/sound/soc.h index dbb1702688cd..0758a1b3ca44 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -416,6 +416,7 @@ struct snd_soc_codec { #ifdef CONFIG_DEBUG_FS struct dentry *debugfs_reg; struct dentry *debugfs_pop_time; + struct dentry *debugfs_dapm; #endif }; diff --git a/sound/arm/pxa2xx-ac97.c b/sound/arm/pxa2xx-ac97.c index 6c00ea45d5cb..757b480bd5b5 100644 --- a/sound/arm/pxa2xx-ac97.c +++ b/sound/arm/pxa2xx-ac97.c @@ -207,8 +207,8 @@ static int __devinit pxa2xx_ac97_probe(struct platform_device *dev) snprintf(card->longname, sizeof(card->longname), "%s (%s)", dev->dev.driver->name, card->mixername); - if (pdata && pdata->codec_data) - snd_ac97_dev_add_pdata(ac97_bus->codec[0], pdata->codec_pdata); + if (pdata && pdata->codec_data[0]) + snd_ac97_dev_add_pdata(ac97_bus->codec[0], pdata->codec_pdata[0]); snd_card_set_dev(card, &dev->dev); ret = snd_card_register(card); if (ret == 0) { diff --git a/sound/arm/pxa2xx-pcm-lib.c b/sound/arm/pxa2xx-pcm-lib.c index 6205f37d547c..743ac6a29065 100644 --- a/sound/arm/pxa2xx-pcm-lib.c +++ b/sound/arm/pxa2xx-pcm-lib.c @@ -136,6 +136,9 @@ int __pxa2xx_pcm_prepare(struct snd_pcm_substream *substream) { struct pxa2xx_runtime_data *prtd = substream->runtime->private_data; + if (!prtd || !prtd->params) + return 0; + DCSR(prtd->dma_ch) &= ~DCSR_RUN; DCSR(prtd->dma_ch) = 0; DCMD(prtd->dma_ch) = 0; diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 167a5ce06cdf..0edca93af3b0 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -18,6 +18,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_AD73311 if I2C select SND_SOC_AK4104 if SPI_MASTER select SND_SOC_AK4535 if I2C + select SND_SOC_AK4642 if I2C select SND_SOC_CS4270 if I2C select SND_SOC_MAX9877 if I2C select SND_SOC_PCM3008 @@ -92,6 +93,9 @@ config SND_SOC_AK4104 config SND_SOC_AK4535 tristate +config SND_SOC_AK4642 + tristate + # Cirrus Logic CS4270 Codec config SND_SOC_CS4270 tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index fbab43bbe3a7..fb4af28486ba 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -5,6 +5,7 @@ snd-soc-ad1980-objs := ad1980.o snd-soc-ad73311-objs := ad73311.o snd-soc-ak4104-objs := ak4104.o snd-soc-ak4535-objs := ak4535.o +snd-soc-ak4642-objs := ak4642.o snd-soc-cs4270-objs := cs4270.o snd-soc-cx20442-objs := cx20442.o snd-soc-l3-objs := l3.o @@ -54,6 +55,7 @@ obj-$(CONFIG_SND_SOC_AD1980) += snd-soc-ad1980.o obj-$(CONFIG_SND_SOC_AD73311) += snd-soc-ad73311.o obj-$(CONFIG_SND_SOC_AK4104) += snd-soc-ak4104.o obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o +obj-$(CONFIG_SND_SOC_AK4642) += snd-soc-ak4642.o obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o obj-$(CONFIG_SND_SOC_CX20442) += snd-soc-cx20442.o obj-$(CONFIG_SND_SOC_L3) += snd-soc-l3.o diff --git a/sound/soc/codecs/ak4642.c b/sound/soc/codecs/ak4642.c new file mode 100644 index 000000000000..e057c7b578df --- /dev/null +++ b/sound/soc/codecs/ak4642.c @@ -0,0 +1,502 @@ +/* + * ak4642.c -- AK4642/AK4643 ALSA Soc Audio driver + * + * Copyright (C) 2009 Renesas Solutions Corp. + * Kuninori Morimoto <morimoto.kuninori@renesas.com> + * + * Based on wm8731.c by Richard Purdie + * Based on ak4535.c by Richard Purdie + * Based on wm8753.c by Liam Girdwood + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +/* ** CAUTION ** + * + * This is very simple driver. + * It can use headphone output / stereo input only + * + * AK4642 is not tested. + * AK4643 is tested. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> + +#include "ak4642.h" + +#define AK4642_VERSION "0.0.1" + +#define PW_MGMT1 0x00 +#define PW_MGMT2 0x01 +#define SG_SL1 0x02 +#define SG_SL2 0x03 +#define MD_CTL1 0x04 +#define MD_CTL2 0x05 +#define TIMER 0x06 +#define ALC_CTL1 0x07 +#define ALC_CTL2 0x08 +#define L_IVC 0x09 +#define L_DVC 0x0a +#define ALC_CTL3 0x0b +#define R_IVC 0x0c +#define R_DVC 0x0d +#define MD_CTL3 0x0e +#define MD_CTL4 0x0f +#define PW_MGMT3 0x10 +#define DF_S 0x11 +#define FIL3_0 0x12 +#define FIL3_1 0x13 +#define FIL3_2 0x14 +#define FIL3_3 0x15 +#define EQ_0 0x16 +#define EQ_1 0x17 +#define EQ_2 0x18 +#define EQ_3 0x19 +#define EQ_4 0x1a +#define EQ_5 0x1b +#define FIL1_0 0x1c +#define FIL1_1 0x1d +#define FIL1_2 0x1e +#define FIL1_3 0x1f +#define PW_MGMT4 0x20 +#define MD_CTL5 0x21 +#define LO_MS 0x22 +#define HP_MS 0x23 +#define SPK_MS 0x24 + +#define AK4642_CACHEREGNUM 0x25 + +struct snd_soc_codec_device soc_codec_dev_ak4642; + +/* codec private data */ +struct ak4642_priv { + struct snd_soc_codec codec; + unsigned int sysclk; +}; + +static struct snd_soc_codec *ak4642_codec; + +/* + * ak4642 register cache + */ +static const u16 ak4642_reg[AK4642_CACHEREGNUM] = { + 0x0000, 0x0000, 0x0001, 0x0000, + 0x0002, 0x0000, 0x0000, 0x0000, + 0x00e1, 0x00e1, 0x0018, 0x0000, + 0x00e1, 0x0018, 0x0011, 0x0008, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, +}; + +/* + * read ak4642 register cache + */ +static inline unsigned int ak4642_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + if (reg >= AK4642_CACHEREGNUM) + return -1; + return cache[reg]; +} + +/* + * write ak4642 register cache + */ +static inline void ak4642_write_reg_cache(struct snd_soc_codec *codec, + u16 reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + if (reg >= AK4642_CACHEREGNUM) + return; + + cache[reg] = value; +} + +/* + * write to the AK4642 register space + */ +static int ak4642_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[2]; + + /* data is + * D15..D8 AK4642 register offset + * D7...D0 register data + */ + data[0] = reg & 0xff; + data[1] = value & 0xff; + + if (codec->hw_write(codec->control_data, data, 2) == 2) { + ak4642_write_reg_cache(codec, reg, value); + return 0; + } else + return -EIO; +} + +static int ak4642_sync(struct snd_soc_codec *codec) +{ + u16 *cache = codec->reg_cache; + int i, r = 0; + + for (i = 0; i < AK4642_CACHEREGNUM; i++) + r |= ak4642_write(codec, i, cache[i]); + + return r; +}; + +static int ak4642_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + int is_play = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + struct snd_soc_codec *codec = dai->codec; + + if (is_play) { + /* + * start headphone output + * + * PLL, Master Mode + * Audio I/F Format :MSB justified (ADC & DAC) + * Sampling Frequency: 44.1kHz + * Digital Volume: −8dB + * Bass Boost Level : Middle + * + * This operation came from example code of + * "ASAHI KASEI AK4642" (japanese) manual p97. + * + * Example code use 0x39, 0x79 value for 0x01 address, + * But we need MCKO (0x02) bit now + */ + ak4642_write(codec, 0x05, 0x27); + ak4642_write(codec, 0x0f, 0x09); + ak4642_write(codec, 0x0e, 0x19); + ak4642_write(codec, 0x09, 0x91); + ak4642_write(codec, 0x0c, 0x91); + ak4642_write(codec, 0x0a, 0x28); + ak4642_write(codec, 0x0d, 0x28); + ak4642_write(codec, 0x00, 0x64); + ak4642_write(codec, 0x01, 0x3b); /* + MCKO bit */ + ak4642_write(codec, 0x01, 0x7b); /* + MCKO bit */ + } else { + /* + * start stereo input + * + * PLL Master Mode + * Audio I/F Format:MSB justified (ADC & DAC) + * Sampling Frequency:44.1kHz + * Pre MIC AMP:+20dB + * MIC Power On + * ALC setting:Refer to Table 35 + * ALC bit=“1” + * + * This operation came from example code of + * "ASAHI KASEI AK4642" (japanese) manual p94. + */ + ak4642_write(codec, 0x05, 0x27); + ak4642_write(codec, 0x02, 0x05); + ak4642_write(codec, 0x06, 0x3c); + ak4642_write(codec, 0x08, 0xe1); + ak4642_write(codec, 0x0b, 0x00); + ak4642_write(codec, 0x07, 0x21); + ak4642_write(codec, 0x00, 0x41); + ak4642_write(codec, 0x10, 0x01); + } + + return 0; +} + +static void ak4642_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + int is_play = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + struct snd_soc_codec *codec = dai->codec; + + if (is_play) { + /* stop headphone output */ + ak4642_write(codec, 0x01, 0x3b); + ak4642_write(codec, 0x01, 0x0b); + ak4642_write(codec, 0x00, 0x40); + ak4642_write(codec, 0x0e, 0x11); + ak4642_write(codec, 0x0f, 0x08); + } else { + /* stop stereo input */ + ak4642_write(codec, 0x00, 0x40); + ak4642_write(codec, 0x10, 0x00); + ak4642_write(codec, 0x07, 0x01); + } +} + +static int ak4642_dai_set_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct ak4642_priv *ak4642 = codec->private_data; + + ak4642->sysclk = freq; + return 0; +} + +static struct snd_soc_dai_ops ak4642_dai_ops = { + .startup = ak4642_dai_startup, + .shutdown = ak4642_dai_shutdown, + .set_sysclk = ak4642_dai_set_sysclk, +}; + +struct snd_soc_dai ak4642_dai = { + .name = "AK4642", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE }, + .ops = &ak4642_dai_ops, +}; +EXPORT_SYMBOL_GPL(ak4642_dai); + +static int ak4642_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + ak4642_sync(codec); + return 0; +} + +/* + * initialise the AK4642 driver + * register the mixer and dsp interfaces with the kernel + */ +static int ak4642_init(struct ak4642_priv *ak4642) +{ + struct snd_soc_codec *codec = &ak4642->codec; + int ret = 0; + + if (ak4642_codec) { + dev_err(codec->dev, "Another ak4642 is registered\n"); + return -EINVAL; + } + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + codec->private_data = ak4642; + codec->name = "AK4642"; + codec->owner = THIS_MODULE; + codec->read = ak4642_read_reg_cache; + codec->write = ak4642_write; + codec->dai = &ak4642_dai; + codec->num_dai = 1; + codec->hw_write = (hw_write_t)i2c_master_send; + codec->reg_cache_size = ARRAY_SIZE(ak4642_reg); + codec->reg_cache = kmemdup(ak4642_reg, + sizeof(ak4642_reg), GFP_KERNEL); + + if (!codec->reg_cache) + return -ENOMEM; + + ak4642_dai.dev = codec->dev; + ak4642_codec = codec; + + ret = snd_soc_register_codec(codec); + if (ret) { + dev_err(codec->dev, "Failed to register codec: %d\n", ret); + goto reg_cache_err; + } + + ret = snd_soc_register_dai(&ak4642_dai); + if (ret) { + dev_err(codec->dev, "Failed to register DAI: %d\n", ret); + snd_soc_unregister_codec(codec); + goto reg_cache_err; + } + + /* + * clock setting + * + * Audio I/F Format: MSB justified (ADC & DAC) + * BICK frequency at Master Mode: 64fs + * Input Master Clock Select at PLL Mode: 11.2896MHz + * MCKO: Enable + * Sampling Frequency: 44.1kHz + * + * This operation came from example code of + * "ASAHI KASEI AK4642" (japanese) manual p89. + * + * please fix-me + */ + ak4642_write(codec, 0x01, 0x08); + ak4642_write(codec, 0x04, 0x4a); + ak4642_write(codec, 0x05, 0x27); + ak4642_write(codec, 0x00, 0x40); + ak4642_write(codec, 0x01, 0x0b); + + return ret; + +reg_cache_err: + kfree(codec->reg_cache); + codec->reg_cache = NULL; + + return ret; +} + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) +static int ak4642_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct ak4642_priv *ak4642; + struct snd_soc_codec *codec; + int ret; + + ak4642 = kzalloc(sizeof(struct ak4642_priv), GFP_KERNEL); + if (!ak4642) + return -ENOMEM; + + codec = &ak4642->codec; + codec->dev = &i2c->dev; + + i2c_set_clientdata(i2c, ak4642); + codec->control_data = i2c; + + ret = ak4642_init(ak4642); + if (ret < 0) + printk(KERN_ERR "failed to initialise AK4642\n"); + + return ret; +} + +static int ak4642_i2c_remove(struct i2c_client *client) +{ + struct ak4642_priv *ak4642 = i2c_get_clientdata(client); + + snd_soc_unregister_dai(&ak4642_dai); + snd_soc_unregister_codec(&ak4642->codec); + kfree(ak4642->codec.reg_cache); + kfree(ak4642); + ak4642_codec = NULL; + + return 0; +} + +static const struct i2c_device_id ak4642_i2c_id[] = { + { "ak4642", 0 }, + { "ak4643", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ak4642_i2c_id); + +static struct i2c_driver ak4642_i2c_driver = { + .driver = { + .name = "AK4642 I2C Codec", + .owner = THIS_MODULE, + }, + .probe = ak4642_i2c_probe, + .remove = ak4642_i2c_remove, + .id_table = ak4642_i2c_id, +}; + +#endif + +static int ak4642_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + int ret; + + if (!ak4642_codec) { + dev_err(&pdev->dev, "Codec device not registered\n"); + return -ENODEV; + } + + socdev->card->codec = ak4642_codec; + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + printk(KERN_ERR "ak4642: failed to create pcms\n"); + goto pcm_err; + } + + ret = snd_soc_init_card(socdev); + if (ret < 0) { + printk(KERN_ERR "ak4642: failed to register card\n"); + goto card_err; + } + + dev_info(&pdev->dev, "AK4642 Audio Codec %s", AK4642_VERSION); + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + return ret; + +} + +/* power down chip */ +static int ak4642_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_ak4642 = { + .probe = ak4642_probe, + .remove = ak4642_remove, + .resume = ak4642_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_ak4642); + +static int __init ak4642_modinit(void) +{ + int ret; +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + ret = i2c_add_driver(&ak4642_i2c_driver); +#endif + return ret; + +} +module_init(ak4642_modinit); + +static void __exit ak4642_exit(void) +{ +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_del_driver(&ak4642_i2c_driver); +#endif + +} +module_exit(ak4642_exit); + +MODULE_DESCRIPTION("Soc AK4642 driver"); +MODULE_AUTHOR("Kuninori Morimoto <morimoto.kuninori@renesas.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ak4642.h b/sound/soc/codecs/ak4642.h new file mode 100644 index 000000000000..e476833d314e --- /dev/null +++ b/sound/soc/codecs/ak4642.h @@ -0,0 +1,20 @@ +/* + * ak4642.h -- AK4642 Soc Audio driver + * + * Copyright (C) 2009 Renesas Solutions Corp. + * Kuninori Morimoto <morimoto.kuninori@renesas.com> + * + * Based on ak4535.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _AK4642_H +#define _AK4642_H + +extern struct snd_soc_dai ak4642_dai; +extern struct snd_soc_codec_device soc_codec_dev_ak4642; + +#endif diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c index 126b15b18aeb..5d547675b853 100644 --- a/sound/soc/codecs/tlv320aic3x.c +++ b/sound/soc/codecs/tlv320aic3x.c @@ -53,6 +53,7 @@ /* codec private data */ struct aic3x_priv { + struct snd_soc_codec codec; unsigned int sysclk; int master; }; @@ -1156,11 +1157,13 @@ static int aic3x_resume(struct platform_device *pdev) * initialise the AIC3X driver * register the mixer and dsp interfaces with the kernel */ -static int aic3x_init(struct snd_soc_device *socdev) +static int aic3x_init(struct snd_soc_codec *codec) { - struct snd_soc_codec *codec = socdev->card->codec; - struct aic3x_setup_data *setup = socdev->codec_data; - int reg, ret = 0; + int reg; + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); codec->name = "tlv320aic3x"; codec->owner = THIS_MODULE; @@ -1177,13 +1180,6 @@ static int aic3x_init(struct snd_soc_device *socdev) aic3x_write(codec, AIC3X_PAGE_SELECT, PAGE0_SELECT); aic3x_write(codec, AIC3X_RESET, SOFT_RESET); - /* register pcms */ - ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); - if (ret < 0) { - printk(KERN_ERR "aic3x: failed to create pcms\n"); - goto pcm_err; - } - /* DAC default volume and mute */ aic3x_write(codec, LDAC_VOL, DEFAULT_VOL | MUTE_ON); aic3x_write(codec, RDAC_VOL, DEFAULT_VOL | MUTE_ON); @@ -1250,30 +1246,51 @@ static int aic3x_init(struct snd_soc_device *socdev) /* off, with power on */ aic3x_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - /* setup GPIO functions */ - aic3x_write(codec, AIC3X_GPIO1_REG, (setup->gpio_func[0] & 0xf) << 4); - aic3x_write(codec, AIC3X_GPIO2_REG, (setup->gpio_func[1] & 0xf) << 4); + return 0; +} - snd_soc_add_controls(codec, aic3x_snd_controls, - ARRAY_SIZE(aic3x_snd_controls)); - aic3x_add_widgets(codec); - ret = snd_soc_init_card(socdev); +static struct snd_soc_codec *aic3x_codec; + +static int aic3x_register(struct snd_soc_codec *codec) +{ + int ret; + + ret = aic3x_init(codec); if (ret < 0) { - printk(KERN_ERR "aic3x: failed to register card\n"); - goto card_err; + dev_err(codec->dev, "Failed to initialise device\n"); + return ret; } - return ret; + aic3x_codec = codec; -card_err: - snd_soc_free_pcms(socdev); - snd_soc_dapm_free(socdev); -pcm_err: - kfree(codec->reg_cache); - return ret; + ret = snd_soc_register_codec(codec); + if (ret) { + dev_err(codec->dev, "Failed to register codec\n"); + return ret; + } + + ret = snd_soc_register_dai(&aic3x_dai); + if (ret) { + dev_err(codec->dev, "Failed to register dai\n"); + snd_soc_unregister_codec(codec); + return ret; + } + + return 0; } -static struct snd_soc_device *aic3x_socdev; +static int aic3x_unregister(struct aic3x_priv *aic3x) +{ + aic3x_set_bias_level(&aic3x->codec, SND_SOC_BIAS_OFF); + + snd_soc_unregister_dai(&aic3x_dai); + snd_soc_unregister_codec(&aic3x->codec); + + kfree(aic3x); + aic3x_codec = NULL; + + return 0; +} #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) /* @@ -1288,28 +1305,36 @@ static struct snd_soc_device *aic3x_socdev; static int aic3x_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { - struct snd_soc_device *socdev = aic3x_socdev; - struct snd_soc_codec *codec = socdev->card->codec; - int ret; + struct snd_soc_codec *codec; + struct aic3x_priv *aic3x; - i2c_set_clientdata(i2c, codec); + aic3x = kzalloc(sizeof(struct aic3x_priv), GFP_KERNEL); + if (aic3x == NULL) { + dev_err(&i2c->dev, "failed to create private data\n"); + return -ENOMEM; + } + + codec = &aic3x->codec; + codec->dev = &i2c->dev; + codec->private_data = aic3x; codec->control_data = i2c; + codec->hw_write = (hw_write_t) i2c_master_send; - ret = aic3x_init(socdev); - if (ret < 0) - printk(KERN_ERR "aic3x: failed to initialise AIC3X\n"); - return ret; + i2c_set_clientdata(i2c, aic3x); + + return aic3x_register(codec); } static int aic3x_i2c_remove(struct i2c_client *client) { - struct snd_soc_codec *codec = i2c_get_clientdata(client); - kfree(codec->reg_cache); - return 0; + struct aic3x_priv *aic3x = i2c_get_clientdata(client); + + return aic3x_unregister(aic3x); } static const struct i2c_device_id aic3x_i2c_id[] = { { "tlv320aic3x", 0 }, + { "tlv320aic33", 0 }, { } }; MODULE_DEVICE_TABLE(i2c, aic3x_i2c_id); @@ -1320,50 +1345,28 @@ static struct i2c_driver aic3x_i2c_driver = { .name = "aic3x I2C Codec", .owner = THIS_MODULE, }, - .probe = aic3x_i2c_probe, + .probe = aic3x_i2c_probe, .remove = aic3x_i2c_remove, .id_table = aic3x_i2c_id, }; -static int aic3x_add_i2c_device(struct platform_device *pdev, - const struct aic3x_setup_data *setup) +static inline void aic3x_i2c_init(void) { - struct i2c_board_info info; - struct i2c_adapter *adapter; - struct i2c_client *client; int ret; ret = i2c_add_driver(&aic3x_i2c_driver); - if (ret != 0) { - dev_err(&pdev->dev, "can't add i2c driver\n"); - return ret; - } - - memset(&info, 0, sizeof(struct i2c_board_info)); - info.addr = setup->i2c_address; - strlcpy(info.type, "tlv320aic3x", I2C_NAME_SIZE); - - adapter = i2c_get_adapter(setup->i2c_bus); - if (!adapter) { - dev_err(&pdev->dev, "can't get i2c adapter %d\n", - setup->i2c_bus); - goto err_driver; - } - - client = i2c_new_device(adapter, &info); - i2c_put_adapter(adapter); - if (!client) { - dev_err(&pdev->dev, "can't add i2c device at 0x%x\n", - (unsigned int)info.addr); - goto err_driver; - } - - return 0; + if (ret) + printk(KERN_ERR "%s: error regsitering i2c driver, %d\n", + __func__, ret); +} -err_driver: +static inline void aic3x_i2c_exit(void) +{ i2c_del_driver(&aic3x_i2c_driver); - return -ENODEV; } +#else +static inline void aic3x_i2c_init(void) { } +static inline void aic3x_i2c_exit(void) { } #endif static int aic3x_probe(struct platform_device *pdev) @@ -1371,42 +1374,52 @@ static int aic3x_probe(struct platform_device *pdev) struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct aic3x_setup_data *setup; struct snd_soc_codec *codec; - struct aic3x_priv *aic3x; int ret = 0; - printk(KERN_INFO "AIC3X Audio Codec %s\n", AIC3X_VERSION); + codec = aic3x_codec; + if (!codec) { + dev_err(&pdev->dev, "Codec not registered\n"); + return -ENODEV; + } + socdev->card->codec = codec; setup = socdev->codec_data; - codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); - if (codec == NULL) - return -ENOMEM; - aic3x = kzalloc(sizeof(struct aic3x_priv), GFP_KERNEL); - if (aic3x == NULL) { - kfree(codec); - return -ENOMEM; + if (!setup) { + dev_err(&pdev->dev, "No setup data supplied\n"); + return -EINVAL; } - codec->private_data = aic3x; - socdev->card->codec = codec; - mutex_init(&codec->mutex); - INIT_LIST_HEAD(&codec->dapm_widgets); - INIT_LIST_HEAD(&codec->dapm_paths); + /* setup GPIO functions */ + aic3x_write(codec, AIC3X_GPIO1_REG, (setup->gpio_func[0] & 0xf) << 4); + aic3x_write(codec, AIC3X_GPIO2_REG, (setup->gpio_func[1] & 0xf) << 4); - aic3x_socdev = socdev; -#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) - if (setup->i2c_address) { - codec->hw_write = (hw_write_t) i2c_master_send; - ret = aic3x_add_i2c_device(pdev, setup); + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + printk(KERN_ERR "aic3x: failed to create pcms\n"); + goto pcm_err; } -#else - /* Add other interfaces here */ -#endif - if (ret != 0) { - kfree(codec->private_data); - kfree(codec); + snd_soc_add_controls(codec, aic3x_snd_controls, + ARRAY_SIZE(aic3x_snd_controls)); + + aic3x_add_widgets(codec); + + ret = snd_soc_init_card(socdev); + if (ret < 0) { + printk(KERN_ERR "aic3x: failed to register card\n"); + goto card_err; } + + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + +pcm_err: + kfree(codec->reg_cache); return ret; } @@ -1421,12 +1434,8 @@ static int aic3x_remove(struct platform_device *pdev) snd_soc_free_pcms(socdev); snd_soc_dapm_free(socdev); -#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) - i2c_unregister_device(codec->control_data); - i2c_del_driver(&aic3x_i2c_driver); -#endif - kfree(codec->private_data); - kfree(codec); + + kfree(codec->reg_cache); return 0; } @@ -1441,13 +1450,15 @@ EXPORT_SYMBOL_GPL(soc_codec_dev_aic3x); static int __init aic3x_modinit(void) { - return snd_soc_register_dai(&aic3x_dai); + aic3x_i2c_init(); + + return 0; } module_init(aic3x_modinit); static void __exit aic3x_exit(void) { - snd_soc_unregister_dai(&aic3x_dai); + aic3x_i2c_exit(); } module_exit(aic3x_exit); diff --git a/sound/soc/codecs/tlv320aic3x.h b/sound/soc/codecs/tlv320aic3x.h index ac827e578c4d..9af1c886213c 100644 --- a/sound/soc/codecs/tlv320aic3x.h +++ b/sound/soc/codecs/tlv320aic3x.h @@ -282,8 +282,6 @@ int aic3x_headset_detected(struct snd_soc_codec *codec); int aic3x_button_pressed(struct snd_soc_codec *codec); struct aic3x_setup_data { - int i2c_bus; - unsigned short i2c_address; unsigned int gpio_func[2]; }; diff --git a/sound/soc/codecs/wm8993.c b/sound/soc/codecs/wm8993.c index ff9b63b0ff87..d9987999e92c 100644 --- a/sound/soc/codecs/wm8993.c +++ b/sound/soc/codecs/wm8993.c @@ -218,6 +218,8 @@ struct wm8993_priv { struct snd_soc_codec codec; int master; int sysclk_source; + int tdm_slots; + int tdm_width; unsigned int mclk_rate; unsigned int sysclk_rate; unsigned int fs; @@ -519,7 +521,7 @@ static int configure_clock(struct snd_soc_codec *codec) dev_dbg(codec->dev, "Using %dHz MCLK\n", wm8993->mclk_rate); reg = wm8993_read(codec, WM8993_CLOCKING_2); - reg &= ~WM8993_SYSCLK_SRC; + reg &= ~(WM8993_MCLK_DIV | WM8993_SYSCLK_SRC); if (wm8993->mclk_rate > 13500000) { reg |= WM8993_MCLK_DIV; wm8993->sysclk_rate = wm8993->mclk_rate / 2; @@ -527,8 +529,6 @@ static int configure_clock(struct snd_soc_codec *codec) reg &= ~WM8993_MCLK_DIV; wm8993->sysclk_rate = wm8993->mclk_rate; } - reg &= ~WM8993_MCLK_DIV; - reg &= ~(WM8993_MCLK_DIV | WM8993_SYSCLK_SRC); wm8993_write(codec, WM8993_CLOCKING_2, reg); break; @@ -1189,24 +1189,30 @@ static int wm8993_hw_params(struct snd_pcm_substream *substream, /* What BCLK do we need? */ wm8993->fs = params_rate(params); wm8993->bclk = 2 * wm8993->fs; - switch (params_format(params)) { - case SNDRV_PCM_FORMAT_S16_LE: - wm8993->bclk *= 16; - break; - case SNDRV_PCM_FORMAT_S20_3LE: - wm8993->bclk *= 20; - aif1 |= 0x8; - break; - case SNDRV_PCM_FORMAT_S24_LE: - wm8993->bclk *= 24; - aif1 |= 0x10; - break; - case SNDRV_PCM_FORMAT_S32_LE: - wm8993->bclk *= 32; - aif1 |= 0x18; - break; - default: - return -EINVAL; + if (wm8993->tdm_slots) { + dev_dbg(codec->dev, "Configuring for %d %d bit TDM slots\n", + wm8993->tdm_slots, wm8993->tdm_width); + wm8993->bclk *= wm8993->tdm_width * wm8993->tdm_slots; + } else { + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + wm8993->bclk *= 16; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + wm8993->bclk *= 20; + aif1 |= 0x8; + break; + case SNDRV_PCM_FORMAT_S24_LE: + wm8993->bclk *= 24; + aif1 |= 0x10; + break; + case SNDRV_PCM_FORMAT_S32_LE: + wm8993->bclk *= 32; + aif1 |= 0x18; + break; + default: + return -EINVAL; + } } dev_dbg(codec->dev, "Target BCLK is %dHz\n", wm8993->bclk); @@ -1325,12 +1331,67 @@ static int wm8993_digital_mute(struct snd_soc_dai *codec_dai, int mute) return 0; } +static int wm8993_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + unsigned int rx_mask, int slots, int slot_width) +{ + struct snd_soc_codec *codec = dai->codec; + struct wm8993_priv *wm8993 = codec->private_data; + int aif1 = 0; + int aif2 = 0; + + /* Don't need to validate anything if we're turning off TDM */ + if (slots == 0) { + wm8993->tdm_slots = 0; + goto out; + } + + /* Note that we allow configurations we can't handle ourselves - + * for example, we can generate clocks for slots 2 and up even if + * we can't use those slots ourselves. + */ + aif1 |= WM8993_AIFADC_TDM; + aif2 |= WM8993_AIFDAC_TDM; + + switch (rx_mask) { + case 3: + break; + case 0xc: + aif1 |= WM8993_AIFADC_TDM_CHAN; + break; + default: + return -EINVAL; + } + + + switch (tx_mask) { + case 3: + break; + case 0xc: + aif2 |= WM8993_AIFDAC_TDM_CHAN; + break; + default: + return -EINVAL; + } + +out: + wm8993->tdm_width = slot_width; + wm8993->tdm_slots = slots / 2; + + snd_soc_update_bits(codec, WM8993_AUDIO_INTERFACE_1, + WM8993_AIFADC_TDM | WM8993_AIFADC_TDM_CHAN, aif1); + snd_soc_update_bits(codec, WM8993_AUDIO_INTERFACE_2, + WM8993_AIFDAC_TDM | WM8993_AIFDAC_TDM_CHAN, aif2); + + return 0; +} + static struct snd_soc_dai_ops wm8993_ops = { .set_sysclk = wm8993_set_sysclk, .set_fmt = wm8993_set_dai_fmt, .hw_params = wm8993_hw_params, .digital_mute = wm8993_digital_mute, .set_pll = wm8993_set_fll, + .set_tdm_slot = wm8993_set_tdm_slot, }; #define WM8993_RATES SNDRV_PCM_RATE_8000_48000 diff --git a/sound/soc/codecs/wm9705.c b/sound/soc/codecs/wm9705.c index fa88b463e71f..e7d2840d9e59 100644 --- a/sound/soc/codecs/wm9705.c +++ b/sound/soc/codecs/wm9705.c @@ -406,7 +406,7 @@ static int wm9705_soc_probe(struct platform_device *pdev) ret = snd_soc_init_card(socdev); if (ret < 0) { printk(KERN_ERR "wm9705: failed to register card\n"); - goto pcm_err; + goto reset_err; } return 0; diff --git a/sound/soc/codecs/wm_hubs.c b/sound/soc/codecs/wm_hubs.c index e8fc474ba5cf..41699bd1986c 100644 --- a/sound/soc/codecs/wm_hubs.c +++ b/sound/soc/codecs/wm_hubs.c @@ -18,7 +18,6 @@ #include <linux/pm.h> #include <linux/i2c.h> #include <linux/platform_device.h> -#include <linux/regulator/consumer.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/pcm_params.h> @@ -474,12 +473,6 @@ SND_SOC_DAPM_MIXER("MIXINL", WM8993_POWER_MANAGEMENT_2, 9, 0, SND_SOC_DAPM_MIXER("MIXINR", WM8993_POWER_MANAGEMENT_2, 8, 0, mixinr, ARRAY_SIZE(mixinr)), -SND_SOC_DAPM_ADC("ADCL", "Capture", WM8993_POWER_MANAGEMENT_2, 1, 0), -SND_SOC_DAPM_ADC("ADCR", "Capture", WM8993_POWER_MANAGEMENT_2, 0, 0), - -SND_SOC_DAPM_DAC("DACL", "Playback", WM8993_POWER_MANAGEMENT_3, 1, 0), -SND_SOC_DAPM_DAC("DACR", "Playback", WM8993_POWER_MANAGEMENT_3, 0, 0), - SND_SOC_DAPM_MIXER("Left Output Mixer", WM8993_POWER_MANAGEMENT_3, 5, 0, left_output_mixer, ARRAY_SIZE(left_output_mixer)), SND_SOC_DAPM_MIXER("Right Output Mixer", WM8993_POWER_MANAGEMENT_3, 4, 0, diff --git a/sound/soc/davinci/davinci-evm.c b/sound/soc/davinci/davinci-evm.c index 46c1b0cb1d1b..0190c1bea4e3 100644 --- a/sound/soc/davinci/davinci-evm.c +++ b/sound/soc/davinci/davinci-evm.c @@ -14,6 +14,7 @@ #include <linux/timer.h> #include <linux/interrupt.h> #include <linux/platform_device.h> +#include <linux/i2c.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/soc.h> @@ -207,8 +208,6 @@ static struct snd_soc_card da850_snd_soc_card = { /* evm audio private data */ static struct aic3x_setup_data evm_aic3x_setup = { - .i2c_bus = 1, - .i2c_address = 0x1b, }; /* dm6467 evm audio private data */ @@ -251,6 +250,13 @@ static struct snd_soc_device da850_evm_snd_devdata = { static struct platform_device *evm_snd_device; +/* temporary i2c device creation until this can be moved into the machine + * support file. +*/ +static struct i2c_board_info i2c_device[] = { + { I2C_BOARD_INFO("tlv320aic33", 0x1b), } +}; + static int __init evm_init(void) { struct snd_soc_device *evm_snd_dev_data; @@ -275,6 +281,8 @@ static int __init evm_init(void) } else return -EINVAL; + i2c_register_board_info(1, i2c_device, ARRAY_SIZE(i2c_device)); + evm_snd_device = platform_device_alloc("soc-audio", index); if (!evm_snd_device) return -ENOMEM; diff --git a/sound/soc/omap/n810.c b/sound/soc/omap/n810.c index b60b1dfbc435..0a505938e42b 100644 --- a/sound/soc/omap/n810.c +++ b/sound/soc/omap/n810.c @@ -22,6 +22,7 @@ */ #include <linux/clk.h> +#include <linux/i2c.h> #include <linux/platform_device.h> #include <sound/core.h> #include <sound/pcm.h> @@ -322,8 +323,6 @@ static struct snd_soc_card snd_soc_n810 = { /* Audio private data */ static struct aic3x_setup_data n810_aic33_setup = { - .i2c_bus = 2, - .i2c_address = 0x18, .gpio_func[0] = AIC3X_GPIO1_FUNC_DISABLED, .gpio_func[1] = AIC3X_GPIO2_FUNC_DIGITAL_MIC_INPUT, }; @@ -337,6 +336,13 @@ static struct snd_soc_device n810_snd_devdata = { static struct platform_device *n810_snd_device; +/* temporary i2c device creation until this can be moved into the machine + * support file. +*/ +static struct i2c_board_info i2c_device[] = { + { I2C_BOARD_INFO("tlv320aic3x", 0x1b), } +}; + static int __init n810_soc_init(void) { int err; @@ -345,6 +351,8 @@ static int __init n810_soc_init(void) if (!(machine_is_nokia_n810() || machine_is_nokia_n810_wimax())) return -ENODEV; + i2c_register_board_info(1, i2c_device, ARRAY_SIZE(i2c_device)); + n810_snd_device = platform_device_alloc("soc-audio", -1); if (!n810_snd_device) return -ENOMEM; diff --git a/sound/soc/omap/omap-mcbsp.c b/sound/soc/omap/omap-mcbsp.c index 6a837ffd5d0b..f5387d962f5d 100644 --- a/sound/soc/omap/omap-mcbsp.c +++ b/sound/soc/omap/omap-mcbsp.c @@ -139,27 +139,67 @@ static const unsigned long omap34xx_mcbsp_port[][2] = { static const unsigned long omap34xx_mcbsp_port[][2] = {}; #endif +static void omap_mcbsp_set_threshold(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + struct omap_mcbsp_data *mcbsp_data = to_mcbsp(cpu_dai->private_data); + int dma_op_mode = omap_mcbsp_get_dma_op_mode(mcbsp_data->bus_id); + int samples; + + /* TODO: Currently, MODE_ELEMENT == MODE_FRAME */ + if (dma_op_mode == MCBSP_DMA_MODE_THRESHOLD) + samples = snd_pcm_lib_period_bytes(substream) >> 1; + else + samples = 1; + + /* Configure McBSP internal buffer usage */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + omap_mcbsp_set_tx_threshold(mcbsp_data->bus_id, samples - 1); + else + omap_mcbsp_set_rx_threshold(mcbsp_data->bus_id, samples - 1); +} + static int omap_mcbsp_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; struct omap_mcbsp_data *mcbsp_data = to_mcbsp(cpu_dai->private_data); + int bus_id = mcbsp_data->bus_id; int err = 0; - if (cpu_is_omap343x() && mcbsp_data->bus_id == 1) { + if (!cpu_dai->active) + err = omap_mcbsp_request(bus_id); + + if (cpu_is_omap343x()) { + int dma_op_mode = omap_mcbsp_get_dma_op_mode(bus_id); + int max_period; + /* * McBSP2 in OMAP3 has 1024 * 32-bit internal audio buffer. * Set constraint for minimum buffer size to the same than FIFO * size in order to avoid underruns in playback startup because * HW is keeping the DMA request active until FIFO is filled. */ - snd_pcm_hw_constraint_minmax(substream->runtime, - SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 4096, UINT_MAX); - } + if (bus_id == 1) + snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + 4096, UINT_MAX); - if (!cpu_dai->active) - err = omap_mcbsp_request(mcbsp_data->bus_id); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + max_period = omap_mcbsp_get_max_tx_threshold(bus_id); + else + max_period = omap_mcbsp_get_max_rx_threshold(bus_id); + + max_period++; + max_period <<= 1; + + if (dma_op_mode == MCBSP_DMA_MODE_THRESHOLD) + snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + 32, max_period); + } return err; } @@ -191,6 +231,11 @@ static int omap_mcbsp_dai_trigger(struct snd_pcm_substream *substream, int cmd, case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: mcbsp_data->active++; omap_mcbsp_start(mcbsp_data->bus_id, play, !play); + /* Make sure data transfer is frame synchronized */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + omap_mcbsp_xmit_enable(mcbsp_data->bus_id, 1); + else + omap_mcbsp_recv_enable(mcbsp_data->bus_id, 1); break; case SNDRV_PCM_TRIGGER_STOP: @@ -215,7 +260,7 @@ static int omap_mcbsp_dai_hw_params(struct snd_pcm_substream *substream, struct omap_mcbsp_data *mcbsp_data = to_mcbsp(cpu_dai->private_data); struct omap_mcbsp_reg_cfg *regs = &mcbsp_data->regs; int dma, bus_id = mcbsp_data->bus_id, id = cpu_dai->id; - int wlen, channels, wpf; + int wlen, channels, wpf, sync_mode = OMAP_DMA_SYNC_ELEMENT; unsigned long port; unsigned int format; @@ -231,6 +276,12 @@ static int omap_mcbsp_dai_hw_params(struct snd_pcm_substream *substream, } else if (cpu_is_omap343x()) { dma = omap24xx_dma_reqs[bus_id][substream->stream]; port = omap34xx_mcbsp_port[bus_id][substream->stream]; + omap_mcbsp_dai_dma_params[id][substream->stream].set_threshold = + omap_mcbsp_set_threshold; + /* TODO: Currently, MODE_ELEMENT == MODE_FRAME */ + if (omap_mcbsp_get_dma_op_mode(bus_id) == + MCBSP_DMA_MODE_THRESHOLD) + sync_mode = OMAP_DMA_SYNC_FRAME; } else { return -ENODEV; } @@ -238,6 +289,7 @@ static int omap_mcbsp_dai_hw_params(struct snd_pcm_substream *substream, substream->stream ? "Audio Capture" : "Audio Playback"; omap_mcbsp_dai_dma_params[id][substream->stream].dma_req = dma; omap_mcbsp_dai_dma_params[id][substream->stream].port_addr = port; + omap_mcbsp_dai_dma_params[id][substream->stream].sync_mode = sync_mode; cpu_dai->dma_data = &omap_mcbsp_dai_dma_params[id][substream->stream]; if (mcbsp_data->configured) { @@ -321,8 +373,11 @@ static int omap_mcbsp_dai_set_dai_fmt(struct snd_soc_dai *cpu_dai, /* Generic McBSP register settings */ regs->spcr2 |= XINTM(3) | FREE; regs->spcr1 |= RINTM(3); - regs->rcr2 |= RFIG; - regs->xcr2 |= XFIG; + /* RFIG and XFIG are not defined in 34xx */ + if (!cpu_is_omap34xx()) { + regs->rcr2 |= RFIG; + regs->xcr2 |= XFIG; + } if (cpu_is_omap2430() || cpu_is_omap34xx()) { regs->xccr = DXENDLY(1) | XDMAEN; regs->rccr = RFULL_CYCLE | RDMAEN; @@ -333,11 +388,15 @@ static int omap_mcbsp_dai_set_dai_fmt(struct snd_soc_dai *cpu_dai, /* 1-bit data delay */ regs->rcr2 |= RDATDLY(1); regs->xcr2 |= XDATDLY(1); + regs->rccr |= RFULL_CYCLE | RDMAEN | RDISABLE; + regs->xccr |= (DXENDLY(1) | XDMAEN | XDISABLE); break; case SND_SOC_DAIFMT_DSP_A: /* 1-bit data delay */ regs->rcr2 |= RDATDLY(1); regs->xcr2 |= XDATDLY(1); + regs->rccr |= RFULL_CYCLE | RDMAEN | RDISABLE; + regs->xccr |= (DXENDLY(1) | XDMAEN | XDISABLE); /* Invert FS polarity configuration */ temp_fmt ^= SND_SOC_DAIFMT_NB_IF; break; diff --git a/sound/soc/omap/omap-pcm.c b/sound/soc/omap/omap-pcm.c index 12e14c01068e..5735945788bf 100644 --- a/sound/soc/omap/omap-pcm.c +++ b/sound/soc/omap/omap-pcm.c @@ -162,7 +162,7 @@ static int omap_pcm_prepare(struct snd_pcm_substream *substream) */ dma_params.data_type = OMAP_DMA_DATA_TYPE_S16; dma_params.trigger = dma_data->dma_req; - dma_params.sync_mode = OMAP_DMA_SYNC_ELEMENT; + dma_params.sync_mode = dma_data->sync_mode; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { dma_params.src_amode = OMAP_DMA_AMODE_POST_INC; dma_params.dst_amode = OMAP_DMA_AMODE_CONSTANT; @@ -195,6 +195,9 @@ static int omap_pcm_prepare(struct snd_pcm_substream *substream) else omap_enable_dma_irq(prtd->dma_ch, OMAP_DMA_FRAME_IRQ); + omap_set_dma_src_burst_mode(prtd->dma_ch, OMAP_DMA_DATA_BURST_16); + omap_set_dma_dest_burst_mode(prtd->dma_ch, OMAP_DMA_DATA_BURST_16); + return 0; } @@ -202,6 +205,7 @@ static int omap_pcm_trigger(struct snd_pcm_substream *substream, int cmd) { struct snd_pcm_runtime *runtime = substream->runtime; struct omap_runtime_data *prtd = runtime->private_data; + struct omap_pcm_dma_data *dma_data = prtd->dma_data; unsigned long flags; int ret = 0; @@ -211,6 +215,10 @@ static int omap_pcm_trigger(struct snd_pcm_substream *substream, int cmd) case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: prtd->period_index = 0; + /* Configure McBSP internal buffer usage */ + if (dma_data->set_threshold) + dma_data->set_threshold(substream); + omap_start_dma(prtd->dma_ch); break; @@ -307,7 +315,7 @@ static struct snd_pcm_ops omap_pcm_ops = { .mmap = omap_pcm_mmap, }; -static u64 omap_pcm_dmamask = DMA_BIT_MASK(32); +static u64 omap_pcm_dmamask = DMA_BIT_MASK(64); static int omap_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) @@ -357,7 +365,7 @@ static int omap_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, if (!card->dev->dma_mask) card->dev->dma_mask = &omap_pcm_dmamask; if (!card->dev->coherent_dma_mask) - card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + card->dev->coherent_dma_mask = DMA_BIT_MASK(64); if (dai->playback.channels_min) { ret = omap_pcm_preallocate_dma_buffer(pcm, diff --git a/sound/soc/omap/omap-pcm.h b/sound/soc/omap/omap-pcm.h index 8d9d26916b05..38a821dd4118 100644 --- a/sound/soc/omap/omap-pcm.h +++ b/sound/soc/omap/omap-pcm.h @@ -29,6 +29,8 @@ struct omap_pcm_dma_data { char *name; /* stream identifier */ int dma_req; /* DMA request line */ unsigned long port_addr; /* transmit/receive register */ + int sync_mode; /* DMA sync mode */ + void (*set_threshold)(struct snd_pcm_substream *substream); }; extern struct snd_soc_platform omap_soc_platform; diff --git a/sound/soc/pxa/pxa2xx-ac97.c b/sound/soc/pxa/pxa2xx-ac97.c index 7330e5c5b9df..e9ae7b3a7e00 100644 --- a/sound/soc/pxa/pxa2xx-ac97.c +++ b/sound/soc/pxa/pxa2xx-ac97.c @@ -251,8 +251,8 @@ static int __devinit pxa2xx_ac97_dev_probe(struct platform_device *pdev) for (i = 0; i < ARRAY_SIZE(pxa_ac97_dai); i++) { pxa_ac97_dai[i].dev = &pdev->dev; - if (pdata && pdata->codec_pdata) - pxa_ac97_dai[i].ac97_pdata = pdata->codec_pdata; + if (pdata && pdata->codec_pdata[0]) + pxa_ac97_dai[i].ac97_pdata = pdata->codec_pdata[0]; } /* Punt most of the init to the SoC probe; we may need the machine diff --git a/sound/soc/s3c24xx/Kconfig b/sound/soc/s3c24xx/Kconfig index 808de5c5caa7..68fef00bd7f3 100644 --- a/sound/soc/s3c24xx/Kconfig +++ b/sound/soc/s3c24xx/Kconfig @@ -1,6 +1,7 @@ config SND_S3C24XX_SOC tristate "SoC Audio for the Samsung S3CXXXX chips" - depends on ARCH_S3C2410 + depends on ARCH_S3C2410 || ARCH_S3C64XX + select S3C64XX_DMA if ARCH_S3C64XX help Say Y or M if you want to add support for codecs attached to the S3C24XX AC97 or I2S interfaces. You will also need to @@ -79,3 +80,22 @@ config SND_S3C24XX_SOC_S3C24XX_UDA134X select SND_S3C24XX_SOC_I2S select SND_SOC_L3 select SND_SOC_UDA134X + +config SND_S3C24XX_SOC_SIMTEC + tristate + help + Internal node for common S3C24XX/Simtec suppor + +config SND_S3C24XX_SOC_SIMTEC_TLV320AIC23 + tristate "SoC I2S Audio support for TLV320AIC23 on Simtec boards" + depends on SND_S3C24XX_SOC + select SND_S3C24XX_SOC_I2S + select SND_SOC_TLV320AIC23 + select SND_S3C24XX_SOC_SIMTEC + +config SND_S3C24XX_SOC_SIMTEC_HERMES + tristate "SoC I2S Audio support for Simtec Hermes board" + depends on SND_S3C24XX_SOC + select SND_S3C24XX_SOC_I2S + select SND_SOC_TLV320AIC3X + select SND_S3C24XX_SOC_SIMTEC diff --git a/sound/soc/s3c24xx/Makefile b/sound/soc/s3c24xx/Makefile index eb219b016499..99f5a7dd3fc6 100644 --- a/sound/soc/s3c24xx/Makefile +++ b/sound/soc/s3c24xx/Makefile @@ -20,6 +20,9 @@ snd-soc-neo1973-gta02-wm8753-objs := neo1973_gta02_wm8753.o snd-soc-smdk2443-wm9710-objs := smdk2443_wm9710.o snd-soc-ln2440sbc-alc650-objs := ln2440sbc_alc650.o snd-soc-s3c24xx-uda134x-objs := s3c24xx_uda134x.o +snd-soc-s3c24xx-simtec-objs := s3c24xx_simtec.o +snd-soc-s3c24xx-simtec-hermes-objs := s3c24xx_simtec_hermes.o +snd-soc-s3c24xx-simtec-tlv320aic23-objs := s3c24xx_simtec_tlv320aic23.o obj-$(CONFIG_SND_S3C24XX_SOC_JIVE_WM8750) += snd-soc-jive-wm8750.o obj-$(CONFIG_SND_S3C24XX_SOC_NEO1973_WM8753) += snd-soc-neo1973-wm8753.o @@ -27,3 +30,7 @@ obj-$(CONFIG_SND_S3C24XX_SOC_NEO1973_GTA02_WM8753) += snd-soc-neo1973-gta02-wm87 obj-$(CONFIG_SND_S3C24XX_SOC_SMDK2443_WM9710) += snd-soc-smdk2443-wm9710.o obj-$(CONFIG_SND_S3C24XX_SOC_LN2440SBC_ALC650) += snd-soc-ln2440sbc-alc650.o obj-$(CONFIG_SND_S3C24XX_SOC_S3C24XX_UDA134X) += snd-soc-s3c24xx-uda134x.o +obj-$(CONFIG_SND_S3C24XX_SOC_SIMTEC) += snd-soc-s3c24xx-simtec.o +obj-$(CONFIG_SND_S3C24XX_SOC_SIMTEC_HERMES) += snd-soc-s3c24xx-simtec-hermes.o +obj-$(CONFIG_SND_S3C24XX_SOC_SIMTEC_TLV320AIC23) += snd-soc-s3c24xx-simtec-tlv320aic23.o + diff --git a/sound/soc/s3c24xx/s3c-i2s-v2.c b/sound/soc/s3c24xx/s3c-i2s-v2.c index 1a283170ca92..ebfb2f631054 100644 --- a/sound/soc/s3c24xx/s3c-i2s-v2.c +++ b/sound/soc/s3c24xx/s3c-i2s-v2.c @@ -357,19 +357,19 @@ static int s3c2412_i2s_hw_params(struct snd_pcm_substream *substream, #endif #ifdef CONFIG_PLAT_S3C64XX - iismod &= ~0x606; + iismod &= ~(S3C64XX_IISMOD_BLC_MASK | S3C2412_IISMOD_BCLK_MASK); /* Sample size */ switch (params_format(params)) { case SNDRV_PCM_FORMAT_S8: /* 8 bit sample, 16fs BCLK */ - iismod |= 0x2004; + iismod |= (S3C64XX_IISMOD_BLC_8BIT | S3C2412_IISMOD_BCLK_16FS); 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 |= 0x4002; + iismod |= (S3C64XX_IISMOD_BLC_24BIT | S3C2412_IISMOD_BCLK_48FS); break; } #endif diff --git a/sound/soc/s3c24xx/s3c24xx-pcm.c b/sound/soc/s3c24xx/s3c24xx-pcm.c index eecfa5eba06b..8a931964ce46 100644 --- a/sound/soc/s3c24xx/s3c24xx-pcm.c +++ b/sound/soc/s3c24xx/s3c24xx-pcm.c @@ -318,6 +318,7 @@ static int s3c24xx_pcm_open(struct snd_pcm_substream *substream) pr_debug("Entered %s\n", __func__); + snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); snd_soc_set_runtime_hwparams(substream, &s3c24xx_pcm_hardware); prtd = kzalloc(sizeof(struct s3c24xx_runtime_data), GFP_KERNEL); diff --git a/sound/soc/s3c24xx/s3c24xx_simtec.c b/sound/soc/s3c24xx/s3c24xx_simtec.c new file mode 100644 index 000000000000..1966e0d5652d --- /dev/null +++ b/sound/soc/s3c24xx/s3c24xx_simtec.c @@ -0,0 +1,394 @@ +/* sound/soc/s3c24xx/s3c24xx_simtec.c + * + * Copyright 2009 Simtec Electronics + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/platform_device.h> +#include <linux/gpio.h> +#include <linux/clk.h> +#include <linux/i2c.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> + +#include <plat/audio-simtec.h> + +#include "s3c24xx-pcm.h" +#include "s3c24xx-i2s.h" +#include "s3c24xx_simtec.h" + +static struct s3c24xx_audio_simtec_pdata *pdata; +static struct clk *xtal_clk; + +static int spk_gain; +static int spk_unmute; + +/** + * speaker_gain_get - read the speaker gain setting. + * @kcontrol: The control for the speaker gain. + * @ucontrol: The value that needs to be updated. + * + * Read the value for the AMP gain control. + */ +static int speaker_gain_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = spk_gain; + return 0; +} + +/** + * speaker_gain_set - set the value of the speaker amp gain + * @value: The value to write. + */ +static void speaker_gain_set(int value) +{ + gpio_set_value_cansleep(pdata->amp_gain[0], value & 1); + gpio_set_value_cansleep(pdata->amp_gain[1], value >> 1); +} + +/** + * speaker_gain_put - set the speaker gain setting. + * @kcontrol: The control for the speaker gain. + * @ucontrol: The value that needs to be set. + * + * Set the value of the speaker gain from the specified + * @ucontrol setting. + * + * Note, if the speaker amp is muted, then we do not set a gain value + * as at-least one of the ICs that is fitted will try and power up even + * if the main control is set to off. + */ +static int speaker_gain_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int value = ucontrol->value.integer.value[0]; + + spk_gain = value; + + if (!spk_unmute) + speaker_gain_set(value); + + return 0; +} + +static const struct snd_kcontrol_new amp_gain_controls[] = { + SOC_SINGLE_EXT("Speaker Gain", 0, 0, 3, 0, + speaker_gain_get, speaker_gain_put), +}; + +/** + * spk_unmute_state - set the unmute state of the speaker + * @to: zero to unmute, non-zero to ununmute. + */ +static void spk_unmute_state(int to) +{ + pr_debug("%s: to=%d\n", __func__, to); + + spk_unmute = to; + gpio_set_value(pdata->amp_gpio, to); + + /* if we're umuting, also re-set the gain */ + if (to && pdata->amp_gain[0] > 0) + speaker_gain_set(spk_gain); +} + +/** + * speaker_unmute_get - read the speaker unmute setting. + * @kcontrol: The control for the speaker gain. + * @ucontrol: The value that needs to be updated. + * + * Read the value for the AMP gain control. + */ +static int speaker_unmute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = spk_unmute; + return 0; +} + +/** + * speaker_unmute_put - set the speaker unmute setting. + * @kcontrol: The control for the speaker gain. + * @ucontrol: The value that needs to be set. + * + * Set the value of the speaker gain from the specified + * @ucontrol setting. + */ +static int speaker_unmute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + spk_unmute_state(ucontrol->value.integer.value[0]); + return 0; +} + +/* This is added as a manual control as the speaker amps create clicks + * when their power state is changed, which are far more noticeable than + * anything produced by the CODEC itself. + */ +static const struct snd_kcontrol_new amp_unmute_controls[] = { + SOC_SINGLE_EXT("Speaker Switch", 0, 0, 1, 0, + speaker_unmute_get, speaker_unmute_put), +}; + +void simtec_audio_init(struct snd_soc_codec *codec) +{ + if (pdata->amp_gpio > 0) { + pr_debug("%s: adding amp routes\n", __func__); + + snd_soc_add_controls(codec, amp_unmute_controls, + ARRAY_SIZE(amp_unmute_controls)); + } + + if (pdata->amp_gain[0] > 0) { + pr_debug("%s: adding amp controls\n", __func__); + snd_soc_add_controls(codec, amp_gain_controls, + ARRAY_SIZE(amp_gain_controls)); + } +} +EXPORT_SYMBOL_GPL(simtec_audio_init); + +#define CODEC_CLOCK 12000000 + +/** + * simtec_hw_params - update hardware parameters + * @substream: The audio substream instance. + * @params: The parameters requested. + * + * Update the codec data routing and configuration settings + * from the supplied data. + */ +static int simtec_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + int ret; + + /* Set the CODEC as the bus clock master, I2S */ + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM); + if (ret) { + pr_err("%s: failed set cpu dai format\n", __func__); + return ret; + } + + /* Set the CODEC as the bus clock master */ + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM); + if (ret) { + pr_err("%s: failed set codec dai format\n", __func__); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, 0, + CODEC_CLOCK, SND_SOC_CLOCK_IN); + if (ret) { + pr_err( "%s: failed setting codec sysclk\n", __func__); + return ret; + } + + if (pdata->use_mpllin) { + ret = snd_soc_dai_set_sysclk(cpu_dai, S3C24XX_CLKSRC_MPLL, + 0, SND_SOC_CLOCK_OUT); + + if (ret) { + pr_err("%s: failed to set MPLLin as clksrc\n", + __func__); + return ret; + } + } + + if (pdata->output_cdclk) { + int cdclk_scale; + + cdclk_scale = clk_get_rate(xtal_clk) / CODEC_CLOCK; + cdclk_scale--; + + ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER, + cdclk_scale); + } + + return 0; +} + +static int simtec_call_startup(struct s3c24xx_audio_simtec_pdata *pd) +{ + /* call any board supplied startup code, this currently only + * covers the bast/vr1000 which have a CPLD in the way of the + * LRCLK */ + if (pd->startup) + pd->startup(); + + return 0; +} + +static struct snd_soc_ops simtec_snd_ops = { + .hw_params = simtec_hw_params, +}; + +/** + * attach_gpio_amp - get and configure the necessary gpios + * @dev: The device we're probing. + * @pd: The platform data supplied by the board. + * + * If there is a GPIO based amplifier attached to the board, claim + * the necessary GPIO lines for it, and set default values. + */ +static int attach_gpio_amp(struct device *dev, + struct s3c24xx_audio_simtec_pdata *pd) +{ + int ret; + + /* attach gpio amp gain (if any) */ + if (pdata->amp_gain[0] > 0) { + ret = gpio_request(pd->amp_gain[0], "gpio-amp-gain0"); + if (ret) { + dev_err(dev, "cannot get amp gpio gain0\n"); + return ret; + } + + ret = gpio_request(pd->amp_gain[1], "gpio-amp-gain1"); + if (ret) { + dev_err(dev, "cannot get amp gpio gain1\n"); + gpio_free(pdata->amp_gain[0]); + return ret; + } + + gpio_direction_output(pd->amp_gain[0], 0); + gpio_direction_output(pd->amp_gain[1], 0); + } + + /* note, curently we assume GPA0 isn't valid amp */ + if (pdata->amp_gpio > 0) { + ret = gpio_request(pd->amp_gpio, "gpio-amp"); + if (ret) { + dev_err(dev, "cannot get amp gpio %d (%d)\n", + pd->amp_gpio, ret); + goto err_amp; + } + + /* set the amp off at startup */ + spk_unmute_state(0); + } + + return 0; + +err_amp: + if (pd->amp_gain[0] > 0) { + gpio_free(pd->amp_gain[0]); + gpio_free(pd->amp_gain[1]); + } + + return ret; +} + +static void detach_gpio_amp(struct s3c24xx_audio_simtec_pdata *pd) +{ + if (pd->amp_gain[0] > 0) { + gpio_free(pd->amp_gain[0]); + gpio_free(pd->amp_gain[1]); + } + + if (pd->amp_gpio > 0) + gpio_free(pd->amp_gpio); +} + +#ifdef CONFIG_PM +int simtec_audio_resume(struct device *dev) +{ + simtec_call_startup(pdata); + return 0; +} + +struct dev_pm_ops simtec_audio_pmops = { + .resume = simtec_audio_resume, +}; +EXPORT_SYMBOL_GPL(simtec_audio_pmops); +#endif + +int __devinit simtec_audio_core_probe(struct platform_device *pdev, + struct snd_soc_device *socdev) +{ + struct platform_device *snd_dev; + int ret; + + socdev->card->dai_link->ops = &simtec_snd_ops; + + pdata = pdev->dev.platform_data; + if (!pdata) { + dev_err(&pdev->dev, "no platform data supplied\n"); + return -EINVAL; + } + + simtec_call_startup(pdata); + + xtal_clk = clk_get(&pdev->dev, "xtal"); + if (IS_ERR(xtal_clk)) { + dev_err(&pdev->dev, "could not get clkout0\n"); + return -EINVAL; + } + + dev_info(&pdev->dev, "xtal rate is %ld\n", clk_get_rate(xtal_clk)); + + ret = attach_gpio_amp(&pdev->dev, pdata); + if (ret) + goto err_clk; + + snd_dev = platform_device_alloc("soc-audio", -1); + if (!snd_dev) { + dev_err(&pdev->dev, "failed to alloc soc-audio devicec\n"); + ret = -ENOMEM; + goto err_gpio; + } + + platform_set_drvdata(snd_dev, socdev); + socdev->dev = &snd_dev->dev; + + ret = platform_device_add(snd_dev); + if (ret) { + dev_err(&pdev->dev, "failed to add soc-audio dev\n"); + goto err_pdev; + } + + platform_set_drvdata(pdev, snd_dev); + return 0; + +err_pdev: + platform_device_put(snd_dev); + +err_gpio: + detach_gpio_amp(pdata); + +err_clk: + clk_put(xtal_clk); + return ret; +} +EXPORT_SYMBOL_GPL(simtec_audio_core_probe); + +int __devexit simtec_audio_remove(struct platform_device *pdev) +{ + struct platform_device *snd_dev = platform_get_drvdata(pdev); + + platform_device_unregister(snd_dev); + + detach_gpio_amp(pdata); + clk_put(xtal_clk); + return 0; +} +EXPORT_SYMBOL_GPL(simtec_audio_remove); + +MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>"); +MODULE_DESCRIPTION("ALSA SoC Simtec Audio common support"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/s3c24xx/s3c24xx_simtec.h b/sound/soc/s3c24xx/s3c24xx_simtec.h new file mode 100644 index 000000000000..2714203af161 --- /dev/null +++ b/sound/soc/s3c24xx/s3c24xx_simtec.h @@ -0,0 +1,22 @@ +/* sound/soc/s3c24xx/s3c24xx_simtec.h + * + * Copyright 2009 Simtec Electronics + * + * 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. +*/ + +extern void simtec_audio_init(struct snd_soc_codec *codec); + +extern int simtec_audio_core_probe(struct platform_device *pdev, + struct snd_soc_device *socdev); + +extern int simtec_audio_remove(struct platform_device *pdev); + +#ifdef CONFIG_PM +extern struct dev_pm_ops simtec_audio_pmops; +#define simtec_audio_pm &simtec_audio_pmops +#else +#define simtec_audio_pm NULL +#endif diff --git a/sound/soc/s3c24xx/s3c24xx_simtec_hermes.c b/sound/soc/s3c24xx/s3c24xx_simtec_hermes.c new file mode 100644 index 000000000000..8346bd96eaf5 --- /dev/null +++ b/sound/soc/s3c24xx/s3c24xx_simtec_hermes.c @@ -0,0 +1,153 @@ +/* sound/soc/s3c24xx/s3c24xx_simtec_hermes.c + * + * Copyright 2009 Simtec Electronics + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +#include <linux/module.h> +#include <linux/clk.h> +#include <linux/platform_device.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> + +#include <plat/audio-simtec.h> + +#include "s3c24xx-pcm.h" +#include "s3c24xx-i2s.h" +#include "s3c24xx_simtec.h" + +#include "../codecs/tlv320aic3x.h" + +static const struct snd_soc_dapm_widget dapm_widgets[] = { + SND_SOC_DAPM_LINE("GSM Out", NULL), + SND_SOC_DAPM_LINE("GSM In", NULL), + SND_SOC_DAPM_LINE("Line In", NULL), + SND_SOC_DAPM_LINE("Line Out", NULL), + SND_SOC_DAPM_LINE("ZV", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_HP("Headphone Jack", NULL), +}; + +static const struct snd_soc_dapm_route base_map[] = { + /* Headphone connected to HP{L,R}OUT and HP{L,R}COM */ + + { "Headphone Jack", NULL, "HPLOUT" }, + { "Headphone Jack", NULL, "HPLCOM" }, + { "Headphone Jack", NULL, "HPROUT" }, + { "Headphone Jack", NULL, "HPRCOM" }, + + /* ZV connected to Line1 */ + + { "LINE1L", NULL, "ZV" }, + { "LINE1R", NULL, "ZV" }, + + /* Line In connected to Line2 */ + + { "LINE2L", NULL, "Line In" }, + { "LINE2R", NULL, "Line In" }, + + /* Microphone connected to MIC3R and MIC_BIAS */ + + { "MIC3L", NULL, "Mic Jack" }, + + /* GSM connected to MONO_LOUT and MIC3L (in) */ + + { "GSM Out", NULL, "MONO_LOUT" }, + { "MIC3L", NULL, "GSM In" }, + + /* Speaker is connected to LINEOUT{LN,LP,RN,RP}, however we are + * not using the DAPM to power it up and down as there it makes + * a click when powering up. */ +}; + +/** + * simtec_hermes_init - initialise and add controls + * @codec; The codec instance to attach to. + * + * Attach our controls and configure the necessary codec + * mappings for our sound card instance. +*/ +static int simtec_hermes_init(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, dapm_widgets, + ARRAY_SIZE(dapm_widgets)); + + snd_soc_dapm_add_routes(codec, base_map, ARRAY_SIZE(base_map)); + + snd_soc_dapm_enable_pin(codec, "Headphone Jack"); + snd_soc_dapm_enable_pin(codec, "Line In"); + snd_soc_dapm_enable_pin(codec, "Line Out"); + snd_soc_dapm_enable_pin(codec, "Mic Jack"); + + simtec_audio_init(codec); + snd_soc_dapm_sync(codec); + + return 0; +} + +static struct aic3x_setup_data codec_setup = { +}; + +static struct snd_soc_dai_link simtec_dai_aic33 = { + .name = "tlv320aic33", + .stream_name = "TLV320AIC33", + .cpu_dai = &s3c24xx_i2s_dai, + .codec_dai = &aic3x_dai, + .init = simtec_hermes_init, +}; + +/* simtec audio machine driver */ +static struct snd_soc_card snd_soc_machine_simtec_aic33 = { + .name = "Simtec-Hermes", + .platform = &s3c24xx_soc_platform, + .dai_link = &simtec_dai_aic33, + .num_links = 1, +}; + +/* simtec audio subsystem */ +static struct snd_soc_device simtec_snd_devdata_aic33 = { + .card = &snd_soc_machine_simtec_aic33, + .codec_dev = &soc_codec_dev_aic3x, + .codec_data = &codec_setup, +}; + +static int __devinit simtec_audio_hermes_probe(struct platform_device *pd) +{ + dev_info(&pd->dev, "probing....\n"); + return simtec_audio_core_probe(pd, &simtec_snd_devdata_aic33); +} + +static struct platform_driver simtec_audio_hermes_platdrv = { + .driver = { + .owner = THIS_MODULE, + .name = "s3c24xx-simtec-hermes-snd", + .pm = simtec_audio_pm, + }, + .probe = simtec_audio_hermes_probe, + .remove = __devexit_p(simtec_audio_remove), +}; + +MODULE_ALIAS("platform:s3c24xx-simtec-hermes-snd"); + +static int __init simtec_hermes_modinit(void) +{ + return platform_driver_register(&simtec_audio_hermes_platdrv); +} + +static void __exit simtec_hermes_modexit(void) +{ + platform_driver_unregister(&simtec_audio_hermes_platdrv); +} + +module_init(simtec_hermes_modinit); +module_exit(simtec_hermes_modexit); + +MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>"); +MODULE_DESCRIPTION("ALSA SoC Simtec Audio support"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/s3c24xx/s3c24xx_simtec_tlv320aic23.c b/sound/soc/s3c24xx/s3c24xx_simtec_tlv320aic23.c new file mode 100644 index 000000000000..25797e096175 --- /dev/null +++ b/sound/soc/s3c24xx/s3c24xx_simtec_tlv320aic23.c @@ -0,0 +1,137 @@ +/* sound/soc/s3c24xx/s3c24xx_simtec_tlv320aic23.c + * + * Copyright 2009 Simtec Electronics + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +#include <linux/module.h> +#include <linux/clk.h> +#include <linux/platform_device.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> + +#include <plat/audio-simtec.h> + +#include "s3c24xx-pcm.h" +#include "s3c24xx-i2s.h" +#include "s3c24xx_simtec.h" + +#include "../codecs/tlv320aic23.h" + +/* supported machines: + * + * Machine Connections AMP + * ------- ----------- --- + * BAST MIC, HPOUT, LOUT, LIN TPA2001D1 (HPOUTL,R) (gain hardwired) + * VR1000 HPOUT, LIN None + * VR2000 LIN, LOUT, MIC, HP LM4871 (HPOUTL,R) + * DePicture LIN, LOUT, MIC, HP LM4871 (HPOUTL,R) + * Anubis LIN, LOUT, MIC, HP TPA2001D1 (HPOUTL,R) + */ + +static const struct snd_soc_dapm_widget dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_LINE("Line In", NULL), + SND_SOC_DAPM_LINE("Line Out", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), +}; + +static const struct snd_soc_dapm_route base_map[] = { + { "Headphone Jack", NULL, "LHPOUT"}, + { "Headphone Jack", NULL, "RHPOUT"}, + + { "Line Out", NULL, "LOUT" }, + { "Line Out", NULL, "ROUT" }, + + { "LLINEIN", NULL, "Line In"}, + { "RLINEIN", NULL, "Line In"}, + + { "MICIN", NULL, "Mic Jack"}, +}; + +/** + * simtec_tlv320aic23_init - initialise and add controls + * @codec; The codec instance to attach to. + * + * Attach our controls and configure the necessary codec + * mappings for our sound card instance. +*/ +static int simtec_tlv320aic23_init(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, dapm_widgets, + ARRAY_SIZE(dapm_widgets)); + + snd_soc_dapm_add_routes(codec, base_map, ARRAY_SIZE(base_map)); + + snd_soc_dapm_enable_pin(codec, "Headphone Jack"); + snd_soc_dapm_enable_pin(codec, "Line In"); + snd_soc_dapm_enable_pin(codec, "Line Out"); + snd_soc_dapm_enable_pin(codec, "Mic Jack"); + + simtec_audio_init(codec); + snd_soc_dapm_sync(codec); + + return 0; +} + +static struct snd_soc_dai_link simtec_dai_aic23 = { + .name = "tlv320aic23", + .stream_name = "TLV320AIC23", + .cpu_dai = &s3c24xx_i2s_dai, + .codec_dai = &tlv320aic23_dai, + .init = simtec_tlv320aic23_init, +}; + +/* simtec audio machine driver */ +static struct snd_soc_card snd_soc_machine_simtec_aic23 = { + .name = "Simtec", + .platform = &s3c24xx_soc_platform, + .dai_link = &simtec_dai_aic23, + .num_links = 1, +}; + +/* simtec audio subsystem */ +static struct snd_soc_device simtec_snd_devdata_aic23 = { + .card = &snd_soc_machine_simtec_aic23, + .codec_dev = &soc_codec_dev_tlv320aic23, +}; + +static int __devinit simtec_audio_tlv320aic23_probe(struct platform_device *pd) +{ + return simtec_audio_core_probe(pd, &simtec_snd_devdata_aic23); +} + +static struct platform_driver simtec_audio_tlv320aic23_platdrv = { + .driver = { + .owner = THIS_MODULE, + .name = "s3c24xx-simtec-tlv320aic23", + .pm = simtec_audio_pm, + }, + .probe = simtec_audio_tlv320aic23_probe, + .remove = __devexit_p(simtec_audio_remove), +}; + +MODULE_ALIAS("platform:s3c24xx-simtec-tlv320aic23"); + +static int __init simtec_tlv320aic23_modinit(void) +{ + return platform_driver_register(&simtec_audio_tlv320aic23_platdrv); +} + +static void __exit simtec_tlv320aic23_modexit(void) +{ + platform_driver_unregister(&simtec_audio_tlv320aic23_platdrv); +} + +module_init(simtec_tlv320aic23_modinit); +module_exit(simtec_tlv320aic23_modexit); + +MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>"); +MODULE_DESCRIPTION("ALSA SoC Simtec Audio support"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/s6000/s6105-ipcam.c b/sound/soc/s6000/s6105-ipcam.c index b5f95f9781c1..c1b40ac22c05 100644 --- a/sound/soc/s6000/s6105-ipcam.c +++ b/sound/soc/s6000/s6105-ipcam.c @@ -14,6 +14,7 @@ #include <linux/timer.h> #include <linux/interrupt.h> #include <linux/platform_device.h> +#include <linux/i2c.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/soc.h> @@ -189,8 +190,6 @@ static struct snd_soc_card snd_soc_card_s6105 = { /* s6105 audio private data */ static struct aic3x_setup_data s6105_aic3x_setup = { - .i2c_bus = 0, - .i2c_address = 0x18, }; /* s6105 audio subsystem */ @@ -211,10 +210,19 @@ static struct s6000_snd_platform_data __initdata s6105_snd_data = { static struct platform_device *s6105_snd_device; +/* temporary i2c device creation until this can be moved into the machine + * support file. +*/ +static struct i2c_board_info i2c_device[] = { + { I2C_BOARD_INFO("tlv320aic33", 0x18), } +}; + static int __init s6105_init(void) { int ret; + i2c_register_board_info(0, i2c_device, ARRAY_SIZE(i2c_device)); + s6105_snd_device = platform_device_alloc("soc-audio", -1); if (!s6105_snd_device) return -ENOMEM; diff --git a/sound/soc/sh/Kconfig b/sound/soc/sh/Kconfig index 54bd604012af..9154b4363db3 100644 --- a/sound/soc/sh/Kconfig +++ b/sound/soc/sh/Kconfig @@ -20,7 +20,12 @@ config SND_SOC_SH4_HAC config SND_SOC_SH4_SSI tristate - +config SND_SOC_SH4_FSI + tristate "SH4 FSI support" + depends on CPU_SUBTYPE_SH7724 + select SH_DMA + help + This option enables FSI sound support ## ## Boards @@ -35,4 +40,12 @@ config SND_SH7760_AC97 This option enables generic sound support for the first AC97 unit of the SH7760. +config SND_FSI_AK4642 + bool "FSI-AK4642 sound support" + depends on SND_SOC_SH4_FSI + select SND_SOC_AK4642 + help + This option enables generic sound support for the + FSI - AK4642 unit + endmenu diff --git a/sound/soc/sh/Makefile b/sound/soc/sh/Makefile index a8e8ab81cc6a..a6997872f24e 100644 --- a/sound/soc/sh/Makefile +++ b/sound/soc/sh/Makefile @@ -5,10 +5,14 @@ obj-$(CONFIG_SND_SOC_PCM_SH7760) += snd-soc-dma-sh7760.o ## audio units found on some SH-4 snd-soc-hac-objs := hac.o snd-soc-ssi-objs := ssi.o +snd-soc-fsi-objs := fsi.o obj-$(CONFIG_SND_SOC_SH4_HAC) += snd-soc-hac.o obj-$(CONFIG_SND_SOC_SH4_SSI) += snd-soc-ssi.o +obj-$(CONFIG_SND_SOC_SH4_FSI) += snd-soc-fsi.o ## boards snd-soc-sh7760-ac97-objs := sh7760-ac97.o +snd-soc-fsi-ak4642-objs := fsi-ak4642.o obj-$(CONFIG_SND_SH7760_AC97) += snd-soc-sh7760-ac97.o +obj-$(CONFIG_SND_FSI_AK4642) += snd-soc-fsi-ak4642.o diff --git a/sound/soc/sh/fsi-ak4642.c b/sound/soc/sh/fsi-ak4642.c new file mode 100644 index 000000000000..c7af09729c6e --- /dev/null +++ b/sound/soc/sh/fsi-ak4642.c @@ -0,0 +1,107 @@ +/* + * FSI-AK464x sound support for ms7724se + * + * Copyright (C) 2009 Renesas Solutions Corp. + * Kuninori Morimoto <morimoto.kuninori@renesas.com> + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/platform_device.h> +#include <linux/i2c.h> +#include <linux/io.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> + +#include <sound/sh_fsi.h> +#include <../sound/soc/codecs/ak4642.h> + +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, + .ops = NULL, +}; + +static struct snd_soc_card fsi_soc_card = { + .name = "FSI", + .platform = &fsi_soc_platform, + .dai_link = &fsi_dai_link, + .num_links = 1, +}; + +static struct snd_soc_device fsi_snd_devdata = { + .card = &fsi_soc_card, + .codec_dev = &soc_codec_dev_ak4642, +}; + +#define AK4642_BUS 0 +#define AK4642_ADR 0x12 +static int ak4642_add_i2c_device(void) +{ + struct i2c_board_info info; + struct i2c_adapter *adapter; + struct i2c_client *client; + + memset(&info, 0, sizeof(struct i2c_board_info)); + info.addr = AK4642_ADR; + strlcpy(info.type, "ak4642", I2C_NAME_SIZE); + + adapter = i2c_get_adapter(AK4642_BUS); + if (!adapter) { + printk(KERN_DEBUG "can't get i2c adapter\n"); + return -ENODEV; + } + + client = i2c_new_device(adapter, &info); + i2c_put_adapter(adapter); + if (!client) { + printk(KERN_DEBUG "can't add i2c device\n"); + return -ENODEV; + } + + return 0; +} + +static struct platform_device *fsi_snd_device; + +static int __init fsi_ak4642_init(void) +{ + int ret = -ENOMEM; + + ak4642_add_i2c_device(); + + fsi_snd_device = platform_device_alloc("soc-audio", -1); + if (!fsi_snd_device) + goto out; + + platform_set_drvdata(fsi_snd_device, + &fsi_snd_devdata); + fsi_snd_devdata.dev = &fsi_snd_device->dev; + ret = platform_device_add(fsi_snd_device); + + if (ret) + platform_device_put(fsi_snd_device); + +out: + return ret; +} + +static void __exit fsi_ak4642_exit(void) +{ + platform_device_unregister(fsi_snd_device); +} + +module_init(fsi_ak4642_init); +module_exit(fsi_ak4642_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Generic SH4 FSI-AK4642 sound card"); +MODULE_AUTHOR("Kuninori Morimoto <morimoto.kuninori@renesas.com>"); diff --git a/sound/soc/sh/fsi.c b/sound/soc/sh/fsi.c new file mode 100644 index 000000000000..44123248b630 --- /dev/null +++ b/sound/soc/sh/fsi.c @@ -0,0 +1,1004 @@ +/* + * Fifo-attached Serial Interface (FSI) support for SH7724 + * + * Copyright (C) 2009 Renesas Solutions Corp. + * Kuninori Morimoto <morimoto.kuninori@renesas.com> + * + * Based on ssi.c + * Copyright (c) 2007 Manuel Lauss <mano@roarinelk.homelinux.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/list.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/initval.h> +#include <sound/soc.h> +#include <sound/pcm_params.h> +#include <sound/sh_fsi.h> +#include <asm/atomic.h> +#include <asm/dma.h> +#include <asm/dma-sh.h> + +#define DO_FMT 0x0000 +#define DOFF_CTL 0x0004 +#define DOFF_ST 0x0008 +#define DI_FMT 0x000C +#define DIFF_CTL 0x0010 +#define DIFF_ST 0x0014 +#define CKG1 0x0018 +#define CKG2 0x001C +#define DIDT 0x0020 +#define DODT 0x0024 +#define MUTE_ST 0x0028 +#define REG_END MUTE_ST + +#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 + +/* DO_FMT */ +/* DI_FMT */ +#define CR_FMT(param) ((param) << 4) +# define CR_MONO 0x0 +# define CR_MONO_D 0x1 +# define CR_PCM 0x2 +# define CR_I2S 0x3 +# define CR_TDM 0x4 +# define CR_TDM_D 0x5 + +/* DOFF_CTL */ +/* DIFF_CTL */ +#define IRQ_HALF 0x00100000 +#define FIFO_CLR 0x00000001 + +/* DOFF_ST */ +#define ERR_OVER 0x00000010 +#define ERR_UNDER 0x00000001 + +/* CLK_RST */ +#define B_CLK 0x00000010 +#define A_CLK 0x00000001 + +/* INT_ST */ +#define INT_B_IN (1 << 12) +#define INT_B_OUT (1 << 8) +#define INT_A_IN (1 << 4) +#define INT_A_OUT (1 << 0) + +#define FSI_RATES SNDRV_PCM_RATE_8000_96000 + +#define FSI_FMTS (SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE) + +/************************************************************************ + + + struct + + +************************************************************************/ +struct fsi_priv { + void __iomem *base; + struct snd_pcm_substream *substream; + + int fifo_max; + int chan; + int dma_chan; + + int byte_offset; + int period_len; + int buffer_len; + int periods; +}; + +struct fsi_master { + void __iomem *base; + int irq; + struct clk *clk; + struct fsi_priv fsia; + struct fsi_priv fsib; + struct sh_fsi_platform_info *info; +}; + +static struct fsi_master *master; + +/************************************************************************ + + + basic read write function + + +************************************************************************/ +static int __fsi_reg_write(u32 reg, u32 data) +{ + /* valid data area is 24bit */ + data &= 0x00ffffff; + + return ctrl_outl(data, reg); +} + +static u32 __fsi_reg_read(u32 reg) +{ + return ctrl_inl(reg); +} + +static int __fsi_reg_mask_set(u32 reg, u32 mask, u32 data) +{ + u32 val = __fsi_reg_read(reg); + + val &= ~mask; + val |= data & mask; + + return __fsi_reg_write(reg, val); +} + +static int fsi_reg_write(struct fsi_priv *fsi, u32 reg, u32 data) +{ + if (reg > REG_END) + return -1; + + return __fsi_reg_write((u32)(fsi->base + reg), data); +} + +static u32 fsi_reg_read(struct fsi_priv *fsi, u32 reg) +{ + if (reg > REG_END) + return 0; + + return __fsi_reg_read((u32)(fsi->base + reg)); +} + +static int fsi_reg_mask_set(struct fsi_priv *fsi, u32 reg, u32 mask, u32 data) +{ + if (reg > REG_END) + return -1; + + return __fsi_reg_mask_set((u32)(fsi->base + reg), mask, data); +} + +static int fsi_master_write(u32 reg, u32 data) +{ + if ((reg < MREG_START) || + (reg > MREG_END)) + return -1; + + return __fsi_reg_write((u32)(master->base + reg), data); +} + +static u32 fsi_master_read(u32 reg) +{ + if ((reg < MREG_START) || + (reg > MREG_END)) + return 0; + + return __fsi_reg_read((u32)(master->base + reg)); +} + +static int fsi_master_mask_set(u32 reg, u32 mask, u32 data) +{ + if ((reg < MREG_START) || + (reg > MREG_END)) + return -1; + + return __fsi_reg_mask_set((u32)(master->base + reg), mask, data); +} + +/************************************************************************ + + + basic function + + +************************************************************************/ +static struct fsi_priv *fsi_get(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd; + struct fsi_priv *fsi = NULL; + + if (!substream || !master) + return NULL; + + rtd = substream->private_data; + switch (rtd->dai->cpu_dai->id) { + case 0: + fsi = &master->fsia; + break; + case 1: + fsi = &master->fsib; + break; + } + + return fsi; +} + +static int fsi_is_port_a(struct fsi_priv *fsi) +{ + /* return + * 1 : port a + * 0 : port b + */ + + if (fsi == &master->fsia) + return 1; + + return 0; +} + +static u32 fsi_get_info_flags(struct fsi_priv *fsi) +{ + int is_porta = fsi_is_port_a(fsi); + + return is_porta ? master->info->porta_flags : + master->info->portb_flags; +} + +static int fsi_is_master_mode(struct fsi_priv *fsi, int is_play) +{ + u32 mode; + u32 flags = fsi_get_info_flags(fsi); + + mode = is_play ? SH_FSI_OUT_SLAVE_MODE : SH_FSI_IN_SLAVE_MODE; + + /* return + * 1 : master mode + * 0 : slave mode + */ + + return (mode & flags) != mode; +} + +static u32 fsi_port_ab_io_bit(struct fsi_priv *fsi, int is_play) +{ + int is_porta = fsi_is_port_a(fsi); + u32 data; + + if (is_porta) + data = is_play ? (1 << 0) : (1 << 4); + else + data = is_play ? (1 << 8) : (1 << 12); + + return data; +} + +static void fsi_stream_push(struct fsi_priv *fsi, + struct snd_pcm_substream *substream, + u32 buffer_len, + u32 period_len) +{ + fsi->substream = substream; + fsi->buffer_len = buffer_len; + fsi->period_len = period_len; + fsi->byte_offset = 0; + fsi->periods = 0; +} + +static void fsi_stream_pop(struct fsi_priv *fsi) +{ + fsi->substream = NULL; + fsi->buffer_len = 0; + fsi->period_len = 0; + fsi->byte_offset = 0; + fsi->periods = 0; +} + +static int fsi_get_fifo_residue(struct fsi_priv *fsi, int is_play) +{ + u32 status; + u32 reg = is_play ? DOFF_ST : DIFF_ST; + int residue; + + status = fsi_reg_read(fsi, reg); + residue = 0x1ff & (status >> 8); + residue *= fsi->chan; + + return residue; +} + +static int fsi_get_residue(struct fsi_priv *fsi, int is_play) +{ + int residue; + int width; + struct snd_pcm_runtime *runtime; + + runtime = fsi->substream->runtime; + + /* get 1 channel data width */ + width = frames_to_bytes(runtime, 1) / fsi->chan; + + if (2 == width) + residue = fsi_get_fifo_residue(fsi, is_play); + else + residue = get_dma_residue(fsi->dma_chan); + + return residue; +} + +/************************************************************************ + + + basic dma function + + +************************************************************************/ +#define PORTA_DMA 0 +#define PORTB_DMA 1 + +static int fsi_get_dma_chan(void) +{ + if (0 != request_dma(PORTA_DMA, "fsia")) + return -EIO; + + if (0 != request_dma(PORTB_DMA, "fsib")) { + free_dma(PORTA_DMA); + return -EIO; + } + + master->fsia.dma_chan = PORTA_DMA; + master->fsib.dma_chan = PORTB_DMA; + + return 0; +} + +static void fsi_free_dma_chan(void) +{ + dma_wait_for_completion(PORTA_DMA); + dma_wait_for_completion(PORTB_DMA); + free_dma(PORTA_DMA); + free_dma(PORTB_DMA); + + master->fsia.dma_chan = -1; + master->fsib.dma_chan = -1; +} + +/************************************************************************ + + + ctrl function + + +************************************************************************/ +static void fsi_irq_enable(struct fsi_priv *fsi, int is_play) +{ + u32 data = fsi_port_ab_io_bit(fsi, is_play); + + fsi_master_mask_set(IMSK, data, data); + fsi_master_mask_set(IEMSK, data, data); +} + +static void fsi_irq_disable(struct fsi_priv *fsi, int is_play) +{ + u32 data = fsi_port_ab_io_bit(fsi, is_play); + + fsi_master_mask_set(IMSK, data, 0); + fsi_master_mask_set(IEMSK, data, 0); +} + +static void fsi_clk_ctrl(struct fsi_priv *fsi, int enable) +{ + u32 val = fsi_is_port_a(fsi) ? (1 << 0) : (1 << 4); + + if (enable) + fsi_master_mask_set(CLK_RST, val, val); + else + fsi_master_mask_set(CLK_RST, val, 0); +} + +static void fsi_irq_init(struct fsi_priv *fsi, int is_play) +{ + u32 data; + u32 ctrl; + + data = fsi_port_ab_io_bit(fsi, is_play); + ctrl = is_play ? DOFF_CTL : DIFF_CTL; + + /* set IMSK */ + fsi_irq_disable(fsi, is_play); + + /* 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(INT_ST, data, 0); +} + +static void fsi_soft_all_reset(void) +{ + u32 status = fsi_master_read(SOFT_RST); + + /* port AB reset */ + status &= 0x000000ff; + fsi_master_write(SOFT_RST, status); + mdelay(10); + + /* soft reset */ + status &= 0x000000f0; + fsi_master_write(SOFT_RST, status); + status |= 0x00000001; + fsi_master_write(SOFT_RST, status); + mdelay(10); +} + +static void fsi_16data_push(struct fsi_priv *fsi, + struct snd_pcm_runtime *runtime, + int send) +{ + u16 *dma_start; + u32 snd; + int i; + + /* get dma start position for FSI */ + dma_start = (u16 *)runtime->dma_area; + dma_start += fsi->byte_offset / 2; + + /* + * soft dma + * FSI can not use DMA when 16bpp + */ + for (i = 0; i < send; i++) { + snd = (u32)dma_start[i]; + fsi_reg_write(fsi, DODT, snd << 8); + } +} + +static void fsi_32data_push(struct fsi_priv *fsi, + struct snd_pcm_runtime *runtime, + int send) +{ + u32 *dma_start; + + /* get dma start position for FSI */ + dma_start = (u32 *)runtime->dma_area; + dma_start += fsi->byte_offset / 4; + + dma_wait_for_completion(fsi->dma_chan); + dma_configure_channel(fsi->dma_chan, (SM_INC|0x400|TS_32|TM_BUR)); + dma_write(fsi->dma_chan, (u32)dma_start, + (u32)(fsi->base + DODT), send * 4); +} + +/* playback interrupt */ +static int fsi_data_push(struct fsi_priv *fsi) +{ + struct snd_pcm_runtime *runtime; + struct snd_pcm_substream *substream = NULL; + int send; + int fifo_free; + int width; + + if (!fsi || + !fsi->substream || + !fsi->substream->runtime) + return -EINVAL; + + runtime = fsi->substream->runtime; + + /* FSI FIFO has limit. + * So, this driver can not send periods data at a time + */ + if (fsi->byte_offset >= + fsi->period_len * (fsi->periods + 1)) { + + substream = fsi->substream; + fsi->periods = (fsi->periods + 1) % runtime->periods; + + if (0 == fsi->periods) + fsi->byte_offset = 0; + } + + /* get 1 channel data width */ + width = frames_to_bytes(runtime, 1) / fsi->chan; + + /* get send size for alsa */ + send = (fsi->buffer_len - fsi->byte_offset) / width; + + /* get FIFO free size */ + fifo_free = (fsi->fifo_max * fsi->chan) - fsi_get_fifo_residue(fsi, 1); + + /* size check */ + if (fifo_free < send) + send = fifo_free; + + if (2 == width) + fsi_16data_push(fsi, runtime, send); + else if (4 == width) + fsi_32data_push(fsi, runtime, send); + else + return -EINVAL; + + fsi->byte_offset += send * width; + + fsi_irq_enable(fsi, 1); + + if (substream) + snd_pcm_period_elapsed(substream); + + return 0; +} + +static irqreturn_t fsi_interrupt(int irq, void *data) +{ + u32 status = fsi_master_read(SOFT_RST) & ~0x00000010; + u32 int_st = fsi_master_read(INT_ST); + + /* clear irq status */ + fsi_master_write(SOFT_RST, status); + fsi_master_write(SOFT_RST, status | 0x00000010); + + if (int_st & INT_A_OUT) + fsi_data_push(&master->fsia); + if (int_st & INT_B_OUT) + fsi_data_push(&master->fsib); + + fsi_master_write(INT_ST, 0x0000000); + + return IRQ_HANDLED; +} + +/************************************************************************ + + + dai ops + + +************************************************************************/ +static int fsi_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct fsi_priv *fsi = fsi_get(substream); + const char *msg; + u32 flags = fsi_get_info_flags(fsi); + u32 fmt; + u32 reg; + u32 data; + int is_play = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + int is_master; + int ret = 0; + + clk_enable(master->clk); + + /* CKG1 */ + data = is_play ? (1 << 0) : (1 << 4); + is_master = fsi_is_master_mode(fsi, is_play); + if (is_master) + fsi_reg_mask_set(fsi, CKG1, data, data); + else + fsi_reg_mask_set(fsi, CKG1, data, 0); + + /* clock inversion (CKG2) */ + data = 0; + switch (SH_FSI_INVERSION_MASK & flags) { + case SH_FSI_LRM_INV: + data = 1 << 12; + break; + case SH_FSI_BRM_INV: + data = 1 << 8; + break; + case SH_FSI_LRS_INV: + data = 1 << 4; + break; + case SH_FSI_BRS_INV: + data = 1 << 0; + break; + } + fsi_reg_write(fsi, CKG2, data); + + /* do fmt, di fmt */ + data = 0; + reg = is_play ? DO_FMT : DI_FMT; + fmt = is_play ? SH_FSI_GET_OFMT(flags) : SH_FSI_GET_IFMT(flags); + switch (fmt) { + case SH_FSI_FMT_MONO: + msg = "MONO"; + data = CR_FMT(CR_MONO); + fsi->chan = 1; + break; + case SH_FSI_FMT_MONO_DELAY: + msg = "MONO Delay"; + data = CR_FMT(CR_MONO_D); + fsi->chan = 1; + break; + case SH_FSI_FMT_PCM: + msg = "PCM"; + data = CR_FMT(CR_PCM); + fsi->chan = 2; + break; + case SH_FSI_FMT_I2S: + msg = "I2S"; + data = CR_FMT(CR_I2S); + fsi->chan = 2; + break; + case SH_FSI_FMT_TDM: + msg = "TDM"; + data = CR_FMT(CR_TDM) | (fsi->chan - 1); + fsi->chan = is_play ? + SH_FSI_GET_CH_O(flags) : SH_FSI_GET_CH_I(flags); + break; + case SH_FSI_FMT_TDM_DELAY: + msg = "TDM Delay"; + data = CR_FMT(CR_TDM_D) | (fsi->chan - 1); + fsi->chan = is_play ? + SH_FSI_GET_CH_O(flags) : SH_FSI_GET_CH_I(flags); + break; + default: + 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); + dev_dbg(dai->dev, "use %s format (%d channel) use %d DMAC\n", + msg, fsi->chan, fsi->dma_chan); + + /* + * clear clk reset if master mode + */ + if (is_master) + fsi_clk_ctrl(fsi, 1); + + /* irq setting */ + fsi_irq_init(fsi, is_play); + + return ret; +} + +static void fsi_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct fsi_priv *fsi = fsi_get(substream); + int is_play = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + + fsi_irq_disable(fsi, is_play); + fsi_clk_ctrl(fsi, 0); + + clk_disable(master->clk); +} + +static int fsi_dai_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct fsi_priv *fsi = fsi_get(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int is_play = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + int ret = 0; + + /* capture not supported */ + if (!is_play) + return -ENODEV; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + fsi_stream_push(fsi, substream, + frames_to_bytes(runtime, runtime->buffer_size), + frames_to_bytes(runtime, runtime->period_size)); + ret = fsi_data_push(fsi); + break; + case SNDRV_PCM_TRIGGER_STOP: + fsi_irq_disable(fsi, is_play); + fsi_stream_pop(fsi); + break; + } + + return ret; +} + +static struct snd_soc_dai_ops fsi_dai_ops = { + .startup = fsi_dai_startup, + .shutdown = fsi_dai_shutdown, + .trigger = fsi_dai_trigger, +}; + +/************************************************************************ + + + pcm ops + + +************************************************************************/ +static struct snd_pcm_hardware fsi_pcm_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE, + .formats = FSI_FMTS, + .rates = FSI_RATES, + .rate_min = 8000, + .rate_max = 192000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = 64 * 1024, + .period_bytes_min = 32, + .period_bytes_max = 8192, + .periods_min = 1, + .periods_max = 32, + .fifo_size = 256, +}; + +static int fsi_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int ret = 0; + + snd_soc_set_runtime_hwparams(substream, &fsi_pcm_hardware); + + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + + return ret; +} + +static int fsi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); +} + +static int fsi_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +static snd_pcm_uframes_t fsi_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsi_priv *fsi = fsi_get(substream); + int is_play = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + long location; + + location = (fsi->byte_offset - 1) - fsi_get_residue(fsi, is_play); + if (location < 0) + location = 0; + + return bytes_to_frames(runtime, location); +} + +static struct snd_pcm_ops fsi_pcm_ops = { + .open = fsi_pcm_open, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = fsi_hw_params, + .hw_free = fsi_hw_free, + .pointer = fsi_pointer, +}; + +/************************************************************************ + + + snd_soc_platform + + +************************************************************************/ +#define PREALLOC_BUFFER (32 * 1024) +#define PREALLOC_BUFFER_MAX (32 * 1024) + +static void fsi_pcm_free(struct snd_pcm *pcm) +{ + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +static int fsi_pcm_new(struct snd_card *card, + struct snd_soc_dai *dai, + struct snd_pcm *pcm) +{ + /* + * dont use SNDRV_DMA_TYPE_DEV, since it will oops the SH kernel + * in MMAP mode (i.e. aplay -M) + */ + return snd_pcm_lib_preallocate_pages_for_all( + pcm, + SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data(GFP_KERNEL), + PREALLOC_BUFFER, PREALLOC_BUFFER_MAX); +} + +/************************************************************************ + + + alsa struct + + +************************************************************************/ +struct snd_soc_dai fsi_soc_dai[] = { + { + .name = "FSIA", + .id = 0, + .playback = { + .rates = FSI_RATES, + .formats = FSI_FMTS, + .channels_min = 1, + .channels_max = 8, + }, + /* capture not supported */ + .ops = &fsi_dai_ops, + }, + { + .name = "FSIB", + .id = 1, + .playback = { + .rates = FSI_RATES, + .formats = FSI_FMTS, + .channels_min = 1, + .channels_max = 8, + }, + /* capture not supported */ + .ops = &fsi_dai_ops, + }, +}; +EXPORT_SYMBOL_GPL(fsi_soc_dai); + +struct snd_soc_platform fsi_soc_platform = { + .name = "fsi-pcm", + .pcm_ops = &fsi_pcm_ops, + .pcm_new = fsi_pcm_new, + .pcm_free = fsi_pcm_free, +}; +EXPORT_SYMBOL_GPL(fsi_soc_platform); + +/************************************************************************ + + + platform function + + +************************************************************************/ +static int fsi_probe(struct platform_device *pdev) +{ + struct resource *res; + char clk_name[8]; + unsigned int irq; + int ret; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + irq = platform_get_irq(pdev, 0); + if (!res || !irq) { + dev_err(&pdev->dev, "Not enough FSI platform resources.\n"); + ret = -ENODEV; + goto exit; + } + + master = kzalloc(sizeof(*master), GFP_KERNEL); + if (!master) { + dev_err(&pdev->dev, "Could not allocate master\n"); + ret = -ENOMEM; + goto exit; + } + + master->base = ioremap_nocache(res->start, resource_size(res)); + if (!master->base) { + ret = -ENXIO; + dev_err(&pdev->dev, "Unable to ioremap FSI registers.\n"); + goto exit_kfree; + } + + master->irq = irq; + master->info = pdev->dev.platform_data; + master->fsia.base = master->base; + master->fsib.base = master->base + 0x40; + + master->fsia.dma_chan = -1; + master->fsib.dma_chan = -1; + + ret = fsi_get_dma_chan(); + if (ret < 0) { + dev_err(&pdev->dev, "cannot get dma api\n"); + goto exit_iounmap; + } + + /* FSI is based on SPU mstp */ + snprintf(clk_name, sizeof(clk_name), "spu%d", pdev->id); + master->clk = clk_get(NULL, clk_name); + if (IS_ERR(master->clk)) { + dev_err(&pdev->dev, "cannot get %s mstp\n", clk_name); + ret = -EIO; + goto exit_free_dma; + } + + fsi_soc_dai[0].dev = &pdev->dev; + fsi_soc_dai[1].dev = &pdev->dev; + + fsi_soft_all_reset(); + + ret = request_irq(irq, &fsi_interrupt, IRQF_DISABLED, "fsi", master); + if (ret) { + dev_err(&pdev->dev, "irq request err\n"); + goto exit_free_dma; + } + + ret = snd_soc_register_platform(&fsi_soc_platform); + if (ret < 0) { + dev_err(&pdev->dev, "cannot snd soc register\n"); + goto exit_free_irq; + } + + return snd_soc_register_dais(fsi_soc_dai, ARRAY_SIZE(fsi_soc_dai)); + +exit_free_irq: + free_irq(irq, master); +exit_free_dma: + fsi_free_dma_chan(); +exit_iounmap: + iounmap(master->base); +exit_kfree: + kfree(master); + master = NULL; +exit: + return ret; +} + +static int fsi_remove(struct platform_device *pdev) +{ + snd_soc_unregister_dais(fsi_soc_dai, ARRAY_SIZE(fsi_soc_dai)); + snd_soc_unregister_platform(&fsi_soc_platform); + + clk_put(master->clk); + + fsi_free_dma_chan(); + + free_irq(master->irq, master); + + iounmap(master->base); + kfree(master); + master = NULL; + return 0; +} + +static struct platform_driver fsi_driver = { + .driver = { + .name = "sh_fsi", + }, + .probe = fsi_probe, + .remove = fsi_remove, +}; + +static int __init fsi_mobile_init(void) +{ + return platform_driver_register(&fsi_driver); +} + +static void __exit fsi_mobile_exit(void) +{ + platform_driver_unregister(&fsi_driver); +} +module_init(fsi_mobile_init); +module_exit(fsi_mobile_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("SuperH onchip FSI audio driver"); +MODULE_AUTHOR("Kuninori Morimoto <morimoto.kuninori@renesas.com>"); diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index e984a17cd656..7ff04ad2a97e 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -1267,10 +1267,18 @@ static void soc_init_codec_debugfs(struct snd_soc_codec *codec) if (!codec->debugfs_pop_time) printk(KERN_WARNING "Failed to create pop time debugfs file\n"); + + codec->debugfs_dapm = debugfs_create_dir("dapm", debugfs_root); + if (!codec->debugfs_dapm) + printk(KERN_WARNING + "Failed to create DAPM debugfs directory\n"); + + snd_soc_dapm_debugfs_init(codec); } static void soc_cleanup_codec_debugfs(struct snd_soc_codec *codec) { + debugfs_remove_recursive(codec->debugfs_dapm); debugfs_remove(codec->debugfs_pop_time); debugfs_remove(codec->debugfs_reg); } diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index a225e5a290c6..0d8b08ef8731 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -37,6 +37,7 @@ #include <linux/bitops.h> #include <linux/platform_device.h> #include <linux/jiffies.h> +#include <linux/debugfs.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/pcm_params.h> @@ -152,8 +153,12 @@ static int snd_soc_dapm_set_bias_level(struct snd_soc_device *socdev, if (card->set_bias_level) ret = card->set_bias_level(card, level); - if (ret == 0 && codec->set_bias_level) - ret = codec->set_bias_level(codec, level); + if (ret == 0) { + if (codec->set_bias_level) + ret = codec->set_bias_level(codec, level); + else + codec->bias_level = level; + } return ret; } @@ -1097,6 +1102,92 @@ static void dbg_dump_dapm(struct snd_soc_codec* codec, const char *action) } #endif +#ifdef CONFIG_DEBUG_FS +static int dapm_widget_power_open_file(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t dapm_widget_power_read_file(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct snd_soc_dapm_widget *w = file->private_data; + char *buf; + int in, out; + ssize_t ret; + struct snd_soc_dapm_path *p = NULL; + + buf = kmalloc(PAGE_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + in = is_connected_input_ep(w); + dapm_clear_walk(w->codec); + out = is_connected_output_ep(w); + dapm_clear_walk(w->codec); + + ret = snprintf(buf, PAGE_SIZE, "%s: %s in %d out %d\n", + w->name, w->power ? "On" : "Off", in, out); + + if (w->active && w->sname) + ret += snprintf(buf, PAGE_SIZE - ret, " stream %s active\n", + w->sname); + + list_for_each_entry(p, &w->sources, list_sink) { + if (p->connect) + ret += snprintf(buf + ret, PAGE_SIZE - ret, + " in %s %s\n", + p->name ? p->name : "static", + p->source->name); + } + list_for_each_entry(p, &w->sinks, list_source) { + if (p->connect) + ret += snprintf(buf + ret, PAGE_SIZE - ret, + " out %s %s\n", + p->name ? p->name : "static", + p->sink->name); + } + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, ret); + + kfree(buf); + return ret; +} + +static const struct file_operations dapm_widget_power_fops = { + .open = dapm_widget_power_open_file, + .read = dapm_widget_power_read_file, +}; + +void snd_soc_dapm_debugfs_init(struct snd_soc_codec *codec) +{ + struct snd_soc_dapm_widget *w; + struct dentry *d; + + if (!codec->debugfs_dapm) + return; + + list_for_each_entry(w, &codec->dapm_widgets, list) { + if (!w->name) + continue; + + d = debugfs_create_file(w->name, 0444, + codec->debugfs_dapm, w, + &dapm_widget_power_fops); + if (!d) + printk(KERN_WARNING + "ASoC: Failed to create %s debugfs file\n", + w->name); + } +} +#else +void snd_soc_dapm_debugfs_init(struct snd_soc_codec *codec) +{ +} +#endif + /* test and update the power status of a mux widget */ static int dapm_mux_update_power(struct snd_soc_dapm_widget *widget, struct snd_kcontrol *kcontrol, int mask, |