Commit 9fa298dc authored by Tony Lindgren's avatar Tony Lindgren

REMOVE OMAP LEGACY CODE: Delete all old omap specific sound drivers

All omap boards should be using sound/soc. Any development must be
done on the alsa mailing list with linux-omap list cc'd.
Signed-off-by: default avatarTony Lindgren <tony@atomide.com>
parent d8376cc4
......@@ -50,68 +50,5 @@ config SND_PXA2XX_AC97
Say Y or M if you want to support any AC97 codec attached to
the PXA2xx AC97 interface.
config SND_OMAP_AIC23
tristate "OMAP AIC23 alsa driver (osk5912)"
depends on ARCH_OMAP && SND
select SND_PCM
#select I2C
#select I2C_OMAP if ARCH_OMAP
select SENSORS_TLV320AIC23
help
Say Y here if you have a OSK platform board
and want to use its AIC23 audio chip.
To compile this driver as a module, choose M here: the module
will be called snd-omap-aic23.
config SND_OMAP_TSC2101
tristate "OMAP TSC2101 alsa driver"
depends on ARCH_OMAP && SND
select SND_PCM
select SPI_TSC2101
help
Say Y here if you have a OMAP platform board
and want to use its TSC2101 audio chip. Driver has
been tested with H2 and iPAQ h6300.
To compile this driver as a module, choose M here: the module
will be called snd-omap-tsc2101.
config SND_SX1
tristate "Siemens SX1 Egold alsa driver"
depends on ARCH_OMAP && SND
select SND_PCM
help
Say Y here if you have a OMAP310 based Siemens SX1.
To compile this driver as a module, choose M here: the module
will be called snd-omap-sx1.
config SND_OMAP_TSC2102
tristate "OMAP TSC2102 alsa driver"
depends on ARCH_OMAP && SND
select SND_PCM
select SPI_TSC2102
help
Say Y here if you have an OMAP platform board
and want to use its TSC2102 audio chip.
To compile this driver as a module, choose M here: the module
will be called snd-omap-tsc2102.
config SND_OMAP24XX_EAC
tristate "Audio driver for OMAP24xx EAC"
depends on SND
help
Audio driver for Enhanced Audio Controller found in TI's OMAP24xx
processors.
Currently contains only low-level support functions for
initializing EAC HW, creating ALSA sound card instance for it
and registering mixer controls implemented by a codec driver.
PCM stream is expected to be under DSP co-processor control.
To compile this driver as a module, choose M here: the module
will be called snd-omap24xx-eac.
endif # SND_ARM
......@@ -17,5 +17,3 @@ snd-pxa2xx-lib-$(CONFIG_SND_PXA2XX_LIB_AC97) += pxa2xx-ac97-lib.o
obj-$(CONFIG_SND_PXA2XX_AC97) += snd-pxa2xx-ac97.o
snd-pxa2xx-ac97-objs := pxa2xx-ac97.o
obj-$(CONFIG_SND) += omap/
#
## Makefile for ALSA OMAP
#
#
obj-$(CONFIG_SND_OMAP_AIC23) += snd-omap-alsa-aic23.o
snd-omap-alsa-aic23-objs := omap-alsa.o omap-alsa-dma.o omap-alsa-aic23.o omap-alsa-aic23-mixer.o
obj-$(CONFIG_SND_OMAP_TSC2101) += snd-omap-alsa-tsc2101.o
snd-omap-alsa-tsc2101-objs := omap-alsa.o omap-alsa-dma.o omap-alsa-tsc2101.o omap-alsa-tsc2101-mixer.o
obj-$(CONFIG_SND_OMAP_TSC2102) += snd-omap-alsa-tsc2102.o
snd-omap-alsa-tsc2102-objs := omap-alsa.o omap-alsa-dma.o omap-alsa-tsc2102.o omap-alsa-tsc2102-mixer.o
obj-$(CONFIG_SND_SX1) += snd-omap-alsa-sx1.o
snd-omap-alsa-sx1-objs := omap-alsa.o omap-alsa-dma.o omap-alsa-sx1.o omap-alsa-sx1-mixer.o
obj-$(CONFIG_SND_OMAP24XX_EAC) += snd-omap24xx-eac.o
snd-omap24xx-eac-objs := eac.o
/*
* linux/sound/arm/omap/omap-alsa-eac.c
*
* OMAP24xx Enhanced Audio Controller sound driver
*
* Copyright (C) 2006 Nokia Corporation
*
* Contact: Jarkko Nikula <jarkko.nikula@nokia.com>
* Juha Yrjl
*
* Definitions:
* Copyright (C) 2004 Texas Instruments, Inc.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
*/
#define DEBUG
#include <linux/device.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/err.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <asm/io.h>
#include <mach/eac.h>
#include <sound/core.h>
#include <sound/initval.h>
#define EAC_CPCFR1 0x0000
#define EAC_CPCFR2 0x0004
#define EAC_CPCFR3 0x0008
#define EAC_CPCFR4 0x000C
#define EAC_CPTCTL 0x0010
#define EAC_CPTTADR 0x0014
#define EAC_CPTDATL 0x0018
#define EAC_CPTDATH 0x001C
#define EAC_CPTVSLL 0x0020
#define EAC_CPTVSLH 0x0024
#define EAC_MPCTR 0x0040
#define EAC_MPMCCFR 0x0044
#define EAC_BPCTR 0x0060
#define EAC_BPMCCFR 0x0064
#define EAC_AMSCFR 0x0080
#define EAC_AMVCTR 0x0084
#define EAC_AM1VCTR 0x0088
#define EAC_AM2VCTR 0x008C
#define EAC_AM3VCTR 0x0090
#define EAC_ASTCTR 0x0094
#define EAC_APD1LCR 0x0098
#define EAC_APD1RCR 0x009C
#define EAC_APD2LCR 0x00A0
#define EAC_APD2RCR 0x00A4
#define EAC_APD3LCR 0x00A8
#define EAC_APD3RCR 0x00AC
#define EAC_APD4R 0x00B0
#define EAC_ADWR 0x00B4
#define EAC_ADRDR 0x00B8
#define EAC_AGCFR 0x00BC
#define EAC_AGCTR 0x00C0
#define EAC_AGCFR2 0x00C4
#define EAC_AGCFR3 0x00C8
#define EAC_MBPDMACTR 0x00CC
#define EAC_MPDDMARR 0x00D0
#define EAC_MPDDMAWR 0x00D4
#define EAC_MPUDMARR 0x00D8
#define EAC_MPUDMAWR 0x00E0
#define EAC_BPDDMARR 0x00E4
#define EAC_BPDDMAWR 0x00E8
#define EAC_BPUDMARR 0x00EC
#define EAC_BPUDMAWR 0x00F0
#define EAC_VERSION 0x0100
#define EAC_SYSCONFIG 0x0104
#define EAC_SYSSTATUS 0x0108
/* CPTCTL */
#define CPTCTL_RXF (1 << 7) /* receive data register full */
#define CPTCTL_RXIE (1 << 6) /* receive interrupt enable */
#define CPTCTL_TXE (1 << 5) /* transmit register empty */
#define CPTCTL_TXIE (1 << 4) /* transmit interrupt enable */
#define CPTCTL_CPEN (1 << 3) /* codec port enable */
#define CPTCTL_CRST (1 << 0) /* external codec reset */
/* CPCFR1 */
#define CPCFR1_MTSL(val) ((val & 0x1f) << 3) /* number of time slots per frame */
#define CPCFR1_MTSL_BITS (0x1f << 3)
#define CPCFR1_MODE(val) ((val & 0x7) << 0) /* codec port interface mode */
#define CPCFR1_MODE_BITS (0x7 << 0)
/* CPCFR2 */
#define CPCFR2_TSLOL(val) ((val & 0x3) << 6) /* time slot 0 length in number of serial clock (CLK_BIT) cycles */
#define CPCFR2_TSLOL_BITS (0x3 << 6)
#define CPCFR2_BPTSL(val) ((val & 0x7) << 3) /* number of data bits per audio time slot */
#define CPCFR2_BPTSL_BITS (0x7 << 3)
#define CPCFR2_TSLL(val) ((val & 0x7) << 0) /* time slot lenght (except slot 0) in number of serial clock cycles */
#define CPCFR2_TSLL_BITS (0x7 << 0)
/* CPCFR3 */
#define CPCFR3_DDLY (1 << 7) /* data delay: data bits start according to SYNC signal leading edge */
#define CPCFR3_TRSEN (1 << 6) /* 3-state enable: data serial output state during nonvalid audio frames */
#define CPCFR3_CLKBP (1 << 5) /* clock polarity */
#define CPCFR3_CSYNCP (1 << 4) /* cp_sync(synchro) polarity */
#define CPCFR3_CSYNCL (1 << 3) /* csync length */
/* bit 2 reserved */
#define CPCFR3_CSCLKD (1 << 1) /* cp_sclk port (serial clock) direction */
#define CPCFR3_CSYNCD (1 << 0) /* cp_sync (synchro) direction */
/* CPCFR4 */
#define CPCFR4_ATSL(val) ((val & 0xf) << 4) /* audio time slots for secondary communication address and data values */
#define CPCFR4_ATSL_BITS (0xf << 4)
#define CPCFR4_CLKS (1 << 3) /* clock source */
#define CPCFR4_DIVB(val) ((val & 0x7) << 0) /* cp_sclk driver value */
#define CPCFR4_DIVB_BITS (0x7 << 0)
/* AGCFR */
#define AGCFR_MN_ST (1 << 10) /* mono/stereo audio file */
#define AGCFR_B8_16 (1 << 9) /* 8 bits/16 bits audio file */
#define AGCFR_LI_BI (1 << 8) /* audio file endianism */
#define AGCFR_FSINT(val) ((val & 0x3) << 6) /* intermediate sample frequency for DMA read and write operations */
#define AGCFR_FINST_BITS (0x3 << 6)
#define AGCFR_FSINT_8000 (0) /* 8000 Hz */
#define AGCFR_FSINT_11025 (1) /* 11025 Hz */
#define AGCFR_FSINT_22050 (2) /* 22050 Hz */
#define AGCFR_FSINT_44100 (3) /* 44100 Hz */
#define AGCFR_AUD_CKSRC(val)((val & 0x3) << 4) /* audio processing clock source */
#define AGCFR_AUD_CKSRC_BITS (0x3 << 4)
#define AGCFR_M_CKSRC (1 << 3) /* modem interface clock source */
#define AGCFR_MCLK_OUT (1 << 1)
#define AGCFR_MCLK (1 << 0)
/* AGCTR */
#define AGCTR_AUDRD (1 << 15) /* audio ready */
#define AGCTR_AUDRDI (1 << 14) /* audio ready interrupt status */
#define AGCTR_AUDRDIEN (1 << 13) /* audio ready interrupt enable */
#define AGCTR_DMAREN (1 << 12) /* audio files play operation */
#define AGCTR_DMAWEN (1 << 11) /* audio file record operation */
/* bits 10:4 reserved */
#define AGCTR_MCLK_EN (1 << 3) /* internal MCLK enable */
#define AGCTR_OSCMCLK_EN (1 << 2) /* OSCMCLK_EN output for MCLK oscillator control */
#define AGCTR_AUDEN (1 << 1) /* audio processing enable/disable */
#define AGCTR_EACPWD (1 << 0) /* EAC operation */
/* AGCFR2 */
#define AGCFR2_BT_MD_WIDEBAND (1 << 5) /* the BT device and modem AuSPIs wide-band mode */
#define AGCFR2_MCLK_I2S_N11M_12M (1 << 4) /* MCLK freq indicator for audio operations */
#define AGCFR2_I2S_N44K_48K (1 << 3) /* Frame sample frecuency of I2S codec port, does not generate value */
#define AGCFR2_FSINT2(val) ((val & 0x7) << 0) /* intermediate sample frequency for DMA channel read and write operations */
#define AGCFR2_FSINT2_BITS (0x7 << 0)
#define AGCFR2_FSINT2_8000 (0) /* 8000 Hz */
#define AGCFR2_FSINT2_11025 (1) /* 11025 Hz */
#define AGCFR2_FSINT2_22050 (2) /* 22050 Hz */
#define AGCFR2_FSINT2_44100 (3) /* 44100 Hz */
#define AGCFR2_FSINT2_48000 (4) /* 48000 Hz */
#define AGCFR2_FSINT2_FSINT (7) /* based on AGCFR/FSINT */
/* AGCFR3 */
#define AGCFR3_CP_TR_DMA (1 << 15) /* codec port transparent DMA (to audio DMAs) */
#define AGCFR3_BT_TR_DMA (1 << 14) /* BT transparent DMA (to BT UL write & DL read DMAs */
#define AGCFR3_MD_TR_DMA (1 << 13) /* modem transparent DMA (to modem UL write and DL read DMAs) */
#define AGCFR3_FSINT(val) ((val & 0xf) << 9) /* FSINT */
#define AGCFR3_FSINT_BITS (0xf << 9)
#define AGCFR3_FSINT_8000 (0) /* 8000 Hz */
#define AGCFR3_FSINT_11025 (1) /* 11025 Hz */
#define AGCFR3_FSINT_16000 (2) /* 16000 Hz */
#define AGCFR3_FSINT_22050 (3) /* 22050 Hz */
#define AGCFR3_FSINT_24000 (4) /* 24000 Hz */
#define AGCFR3_FSINT_32000 (5) /* 32000 Hz */
#define AGCFR3_FSINT_44100 (6) /* 44100 Hz */
#define AGCFR3_FSINT_48000 (7) /* 48000 Hz */
#define AGCFR3_FSINT_FSINT (15) /* based on AGCFR2/AGCFR */
#define AGCFR3_BT_CKSRC(val) ((val & 0x3) << 7) /* BT port clock selection */
#define AGCFR3_BT_CKSRC_BITS (0x3 << 7)
#define AGCFR3_MD_CKSRC(val) ((val & 0x3) << 5) /* modem port clock source */
#define AGCFR3_MD_CKSRC_BITS (0x3 << 5)
#define AGCFR3_AUD_CKSRC(val) ((val & 0x7) << 2) /* audio and codec port clock source */
#define AGCFR3_AUD_CKSRC_BITS (0x7 << 2)
#define AGCFR3_CLK12MINT_SEL (1 << 1) /* internal 12MHz clock source */
#define AGCFR3_MCLKINT_SEL (1 << 0) /* internal codec master clock source */
/* AMSCFR */
#define AMSCFR_K12 (1 << 11) /* K12 switch open/close */
#define AMSCFR_K11 (1 << 10)
#define AMSCFR_K10 (1 << 9)
#define AMSCFR_K9 (1 << 8)
#define AMSCFR_K8 (1 << 7)
#define AMSCFR_K7 (1 << 6)
#define AMSCFR_K6 (1 << 5)
#define AMSCFR_K5 (1 << 4)
#define AMSCFR_K4 (1 << 3)
#define AMSCFR_K3 (1 << 2)
#define AMSCFR_K2 (1 << 1)
#define AMSCFR_K1 (1 << 0)
/* AMVCTR */
#define AMVCTR_GWO_BITS (0xff << 8)
#define AMVCTR_GWO(val) ((val & 0xff) << 8) /* Gain on write DMA operation */
#define AMVCTR_GRO_BITS (0xff << 0)
#define AMVCTR_GRO(val) ((val & 0xff) << 0) /* Gain on read DMA operation */
/* AM1VCTR */
#define AM1VCTR_MUTE (1 << 15) /* mute/no mute on mixer output */
#define AM1VCTR_GINB(val) ((val & 0x7f) << 8) /* gain on input B */
#define AM1VCTR_GINB_BITS (0x7f << 8)
#define AM1VCTR_GINA(val) ((val & 0x7f) << 0) /* gain on input A */
#define AM1VCTR_GINA_BITS (0x7f << 0)
/* AM2VCTR */
#define AM2VCTR_MUTE (1 << 15) /* mute/no mute on mixer output */
#define AM2VCTR_GINB(val) ((val & 0x7f) << 8) /* gain on input B */
#define AM2VCTR_GINB_BITS (0x7f << 8)
#define AM2VCTR_GINA(val) ((val & 0x7f) << 0) /* gain on input A */
#define AM2VCTR_GINA_BITS (0x7f << 0)
/* AM3VCTR */
#define AM3VCTR_MUTE (1 << 15) /* mute/no mute */
#define AM3VCTR_GINB(val) ((val & 0x7f) << 8) /* gain on input B */
#define AM3VCTR_GINB_BITS (0x7f << 8)
#define AM3VCTR_GINA(val) ((val & 0x7f) << 0) /* gain on input A */
#define AM3VCTR_GINA_BITS (0x7f << 0)
/* ASTCTR */
#define ASTCTR_ATT(val) ((val & 0x7f) << 1) /* Attenuation of side tone */
#define ASTCTR_ATT_BITS (0x7f << 1)
#define ASTCTR_ATTEN (1 << 0) /* side tone enabled/disabled */
/* internal structure of the EAC driver */
struct omap_eac {
struct mutex mutex;
void __iomem * base;
struct platform_device * pdev;
struct eac_platform_data * pdata;
struct snd_card * card;
struct clk * fck;
struct clk * ick;
struct eac_codec * codec;
unsigned clocks_enabled:1;
};
static char *id = SNDRV_DEFAULT_STR1;
module_param(id, charp, 0444);
MODULE_PARM_DESC(id, "ID string for OMAP24xx EAC");
#define MOD_REG_BIT(val, mask, set) do { \
if (set) \
val |= mask; \
else \
val &= ~mask; \
} while(0)
static inline void eac_write_reg(struct omap_eac *eac, int idx, u16 val)
{
__raw_writew(val, eac->base + idx);
}
static inline u16 eac_read_reg(struct omap_eac *eac, int idx)
{
return __raw_readw(eac->base + idx);
}
static int eac_get_clocks(struct omap_eac *eac)
{
eac->ick = clk_get(NULL, "eac_ick");
if (IS_ERR(eac->ick)) {
dev_err(&eac->pdev->dev, "Could not get eac_ick");
return -ENODEV;
}
eac->fck = clk_get(NULL, "eac_fck");
if (IS_ERR(eac->fck)) {
dev_err(&eac->pdev->dev, "Could not get eac_fck");
clk_put(eac->ick);
return -ENODEV;
}
return 0;
}
static void eac_put_clocks(struct omap_eac *eac)
{
clk_put(eac->fck);
clk_put(eac->ick);
}
static int eac_enable_clocks(struct omap_eac *eac)
{
int err = 0;
if (eac->clocks_enabled)
return 0;
if (eac->pdata != NULL && eac->pdata->enable_ext_clocks != NULL) {
if ((err = eac->pdata->enable_ext_clocks(&eac->pdev->dev)) != 0)
return err;
}
clk_enable(eac->ick);
clk_enable(eac->fck);
eac->clocks_enabled = 1;
return 0;
}
static void eac_disable_clocks(struct omap_eac *eac)
{
if (!eac->clocks_enabled)
return;
eac->clocks_enabled = 0;
clk_disable(eac->fck);
clk_disable(eac->ick);
if (eac->pdata != NULL && eac->pdata->disable_ext_clocks != NULL)
eac->pdata->disable_ext_clocks(&eac->pdev->dev);
}
static int eac_reset(struct omap_eac *eac)
{
int i;
/* step 1 (see TRM) */
/* first, let's reset the EAC */
eac_write_reg(eac, EAC_SYSCONFIG, 0x2);
/* step 2 (see TRM) */
eac_write_reg(eac, EAC_AGCTR, AGCTR_MCLK_EN | AGCTR_AUDEN);
/* step 3 (see TRM) */
/* wait until reset done */
i = 10000;
while (!(eac_read_reg(eac, EAC_SYSSTATUS) & 1)) {
if (--i == 0)
return -ENODEV;
udelay(1);
}
return 0;
}
static int eac_calc_agcfr3_fsint(int rate)
{
int fsint;
if (rate >= 48000)
fsint = AGCFR3_FSINT_48000;
else if (rate >= 44100)
fsint = AGCFR3_FSINT_44100;
else if (rate >= 32000)
fsint = AGCFR3_FSINT_32000;
else if (rate >= 24000)
fsint = AGCFR3_FSINT_24000;
else if (rate >= 22050)
fsint = AGCFR3_FSINT_22050;
else if (rate >= 16000)
fsint = AGCFR3_FSINT_16000;
else if (rate >= 11025)
fsint = AGCFR3_FSINT_11025;
else
fsint = AGCFR3_FSINT_8000;
return fsint;
}
static int eac_configure_pcm(struct omap_eac *eac, struct eac_codec *conf)
{
dev_err(&eac->pdev->dev,
"EAC codec port configuration for PCM not implemented\n");
return -ENODEV;
}
static int eac_configure_ac97(struct omap_eac *eac, struct eac_codec *conf)
{
dev_err(&eac->pdev->dev,
"EAC codec port configuration for AC97 not implemented\n");
return -ENODEV;
}
static int eac_configure_i2s(struct omap_eac *eac, struct eac_codec *conf)
{
u16 cpcfr1, cpcfr2, cpcfr3, cpcfr4;
cpcfr1 = eac_read_reg(eac, EAC_CPCFR1);
cpcfr2 = eac_read_reg(eac, EAC_CPCFR2);
cpcfr3 = eac_read_reg(eac, EAC_CPCFR3);
cpcfr4 = eac_read_reg(eac, EAC_CPCFR4);
cpcfr1 &= ~(CPCFR1_MODE_BITS | CPCFR1_MTSL_BITS);
cpcfr1 |= CPCFR1_MTSL(1); /* 2 timeslots per frame (I2S default) */
/* audio time slot configuration for I2S mode */
cpcfr2 &= ~(CPCFR2_TSLL_BITS | CPCFR2_BPTSL_BITS | CPCFR2_TSLOL_BITS);
cpcfr2 |= CPCFR2_TSLOL(0); /* time slot 0 length same as TSLL */
cpcfr2 |= CPCFR2_BPTSL(1); /* 16 data bits per time slot */
cpcfr2 |= CPCFR2_TSLL(1); /* time slot length 16 serial clock cycles */
/* I2S link configuration */
MOD_REG_BIT(cpcfr3, CPCFR3_DDLY,
conf->codec_conf.i2s.sync_delay_enable); /* 0/1 clk delay */
/* data serial output enabled during nonvalid audio frames, clock
* polarity = falling edge, CSYNC lenght equal to time slot0 length */
MOD_REG_BIT(cpcfr3, CPCFR3_TRSEN, 1);
MOD_REG_BIT(cpcfr3, CPCFR3_CLKBP, 1);
MOD_REG_BIT(cpcfr3, CPCFR3_CSYNCL, 1);
cpcfr4 &= ~(CPCFR4_DIVB_BITS | CPCFR4_ATSL_BITS);
cpcfr4 |= CPCFR4_DIVB(7); /* CP_SCLK = MCLK / 8 */
/* configuration for normal I2S or polarity-changed I2S */
if (!conf->codec_conf.i2s.polarity_changed_mode) {
cpcfr1 |= CPCFR1_MODE(4); /* I2S mode */
MOD_REG_BIT(cpcfr3, CPCFR3_CSYNCP, 0); /* CP_SYNC active low */
/* audio time slots configuration for I2S */
cpcfr4 |= CPCFR4_ATSL(0);
} else {
cpcfr1 |= CPCFR1_MODE(1); /* PCM mode/polarity-changed I2S */
MOD_REG_BIT(cpcfr3, CPCFR3_CSYNCP, 1); /* CP_SYNC active
high */
/* audio time slots configuration for polarity-changed I2S */
cpcfr4 |= CPCFR4_ATSL(0xf);
};
/* master/slave configuration */
if (conf->codec_mode == EAC_CODEC_I2S_MASTER) {
/* EAC is master. Set CP_SCLK and CP_SYNC as outputs */
MOD_REG_BIT(cpcfr3, CPCFR3_CSCLKD, 0);
MOD_REG_BIT(cpcfr3, CPCFR3_CSYNCD, 0);
} else {
/* EAC is slave. Set CP_SCLK and CP_SYNC as inputs */
MOD_REG_BIT(cpcfr3, CPCFR3_CSCLKD, 1);
MOD_REG_BIT(cpcfr3, CPCFR3_CSYNCD, 1);
}
eac_write_reg(eac, EAC_CPCFR1, cpcfr1);
eac_write_reg(eac, EAC_CPCFR2, cpcfr2);
eac_write_reg(eac, EAC_CPCFR3, cpcfr3);
eac_write_reg(eac, EAC_CPCFR4, cpcfr4);
return 0;
}
static int eac_codec_port_init(struct omap_eac *eac, struct eac_codec *conf)
{
u16 agcfr, agcfr2, agcfr3, agctr;
u16 cpctl, reg;
int err = 0, i;
/* use internal MCLK gating before doing full configuration for it.
* Partial or misconfigured MCLK will cause that access to some of the
* EAC registers causes "external abort on linefetch". Same happens
* also when using external clock as a MCLK source and if that clock is
* either missing or not having a right rate (e.g. half of it) */
agcfr3 = eac_read_reg(eac, EAC_AGCFR3);
MOD_REG_BIT(agcfr3, AGCFR3_MCLKINT_SEL, 1); /* 96 Mhz / 8.5 */
eac_write_reg(eac, EAC_AGCFR3, agcfr3);
/* disable codec port, enable access to config registers */
cpctl = eac_read_reg(eac, EAC_CPTCTL);
MOD_REG_BIT(cpctl, CPTCTL_CPEN, 0);
eac_write_reg(eac, EAC_CPTCTL, cpctl);
agcfr = eac_read_reg(eac, EAC_AGCFR);
agctr = eac_read_reg(eac, EAC_AGCTR);
agcfr2 = eac_read_reg(eac, EAC_AGCFR2);
/* MCLK source and frequency configuration */
MOD_REG_BIT(agcfr, AGCFR_MCLK, 0);
switch (conf->mclk_src) {
case EAC_MCLK_EXT_2x11289600:
MOD_REG_BIT(agcfr, AGCFR_MCLK, 1); /* div by 2 path */
MOD_REG_BIT(agcfr, AGCFR_MCLK_OUT, 1); /* div by 2 */
case EAC_MCLK_EXT_11289600:
MOD_REG_BIT(agcfr, AGCFR_MCLK, 1);
MOD_REG_BIT(agcfr2, AGCFR2_I2S_N44K_48K, 0); /* 44.1 kHz */
MOD_REG_BIT(agcfr2, AGCFR2_MCLK_I2S_N11M_12M, 0); /* 11.2896 */
MOD_REG_BIT(agcfr3, AGCFR3_MCLKINT_SEL, 0);
break;
case EAC_MCLK_EXT_2x12288000:
MOD_REG_BIT(agcfr, AGCFR_MCLK, 1); /* div by 2 path */
MOD_REG_BIT(agcfr, AGCFR_MCLK_OUT, 1); /* div by 2 */
case EAC_MCLK_EXT_12288000:
MOD_REG_BIT(agcfr2, AGCFR2_I2S_N44K_48K, 1); /* 48 kHz */
MOD_REG_BIT(agcfr2, AGCFR2_MCLK_I2S_N11M_12M, 1); /* 12.288 */
MOD_REG_BIT(agcfr3, AGCFR3_MCLKINT_SEL, 0);
break;
default:
/* internal MCLK gating */
break;
}
MOD_REG_BIT(agctr, AGCTR_MCLK_EN, 1);
MOD_REG_BIT(agctr, AGCTR_OSCMCLK_EN, 1); /* oscillator enabled? */
/* use MCLK just configured above as audio & codec port clock source */
agcfr3 &= ~AGCFR3_AUD_CKSRC_BITS;
agcfr3 |= AGCFR3_AUD_CKSRC(0);
/* audio data format */
MOD_REG_BIT(agcfr, AGCFR_MN_ST, 1); /* stereo file */
MOD_REG_BIT(agcfr, AGCFR_B8_16, 1); /* 16 bit audio file */
MOD_REG_BIT(agcfr, AGCFR_LI_BI, 0); /* little endian stream */
/* there are FSINT configuration bits in AGCFR, AGCFR2 and AGCFR3
* registers but it seems that it is just enough to set in AGCFR3
* only */
agcfr3 &= ~AGCFR3_FSINT_BITS;
agcfr3 |= AGCFR3_FSINT(eac_calc_agcfr3_fsint(conf->default_rate));
/* transparent DMA enable bits */
MOD_REG_BIT(agcfr3, AGCFR3_MD_TR_DMA, 1); /* modem */
MOD_REG_BIT(agcfr3, AGCFR3_BT_TR_DMA, 1); /* BT */
if (conf->codec_mode != EAC_CODEC_I2S_SLAVE)
MOD_REG_BIT(agcfr3, AGCFR3_CP_TR_DMA, 0);
else
MOD_REG_BIT(agcfr3, AGCFR3_CP_TR_DMA, 1);
/* step 4 (see TRM) */
eac_write_reg(eac, EAC_AGCFR3, agcfr3);
/* pre-write AGCTR now (finally in step 10) in order to get MCLK
* settings effective (especially when using external MCLK) */
eac_write_reg(eac, EAC_AGCTR, agctr);
eac_write_reg(eac, EAC_AGCFR2, agcfr2);
/* step 5 (see TRM) */
eac_write_reg(eac, EAC_AGCFR, agcfr);
/* step 6 (see TRM) */
/* wait until audio reset done */
i = 10000;
while (!(eac_read_reg(eac, EAC_SYSSTATUS) & (1 << 3))) {
if (--i == 0)
return -ETIMEDOUT;
udelay(1);
}
/* step 7 (see TRM) */
reg = eac_read_reg(eac, EAC_AMSCFR);
MOD_REG_BIT(reg, AMSCFR_K1, 1); /* K1 switch closed */
MOD_REG_BIT(reg, AMSCFR_K5, 1); /* K5 switch closed */
MOD_REG_BIT(reg, AMSCFR_K2, 0); /* K2 switch open */
MOD_REG_BIT(reg, AMSCFR_K6, 0); /* K6 switch open */
eac_write_reg(eac, EAC_AMSCFR, reg);
/* step 8 (see TRM) */
switch (conf->codec_mode) {
case EAC_CODEC_PCM:
err = eac_configure_pcm(eac, conf);
break;
case EAC_CODEC_AC97:
err = eac_configure_ac97(eac, conf);
break;
default:
err = eac_configure_i2s(eac, conf);
break;
}
/* step 9 (see TRM) */
MOD_REG_BIT(cpctl, CPTCTL_CPEN, 1); /* codec port enable */
MOD_REG_BIT(cpctl, CPTCTL_RXIE, 1); /* receive int enable */
MOD_REG_BIT(cpctl, CPTCTL_TXIE, 1); /* transmit int enable */
eac_write_reg(eac, EAC_CPTCTL, cpctl);
/* step 10 (see TRM) */
/* enable playing & recording */
MOD_REG_BIT(agctr, AGCTR_DMAREN, 1); /* playing enabled (DMA R) */
MOD_REG_BIT(agctr, AGCTR_DMAWEN, 1); /* recording enabled (DMA W) */
MOD_REG_BIT(agctr, AGCTR_AUDEN, 1); /* audio processing enabled */
eac_write_reg(eac, EAC_AGCTR, agctr);
/* audio mixer1, no mute on mixer output, gain = 0 dB */
reg = eac_read_reg(eac, EAC_AM1VCTR);
MOD_REG_BIT(reg, AM1VCTR_MUTE, 0);
reg = ((reg & ~AM1VCTR_GINB_BITS) | (AM1VCTR_GINB(0x67)));
eac_write_reg(eac, EAC_AM1VCTR, reg);
/* audio mixer3, no mute on mixer output, gain = 0 dB */
reg = eac_read_reg(eac, EAC_AM3VCTR);
MOD_REG_BIT(reg, AM3VCTR_MUTE, 0);
reg = ((reg & ~AM3VCTR_GINB_BITS) | (AM3VCTR_GINB(0x67)));
eac_write_reg(eac, EAC_AM3VCTR, reg);
/* audio side tone disabled */
eac_write_reg(eac, EAC_ASTCTR, 0x0);
return 0;
}
int eac_set_mode(struct device *dev, int play, int rec)
{
struct omap_eac *eac = dev_get_drvdata(dev);
#ifdef DEBUG
printk(KERN_DEBUG "EAC mode: play %s, rec %s\n",
play ? "enabled" : "disabled",
rec ? "enabled" : "disabled");
#endif
BUG_ON(eac == NULL);
mutex_lock(&eac->mutex);
if (play || rec) {
/* activate clocks */
eac_enable_clocks(eac);
/* power-up codec */
if (eac->codec != NULL && eac->codec->set_power != NULL)
eac->codec->set_power(eac->codec->private_data,
play, rec);
} else {
/* shutdown codec */
if (eac->codec != NULL && eac->codec->set_power != NULL)
eac->codec->set_power(eac->codec->private_data, 0, 0);
/* de-activate clocks */
eac_disable_clocks(eac);
}
mutex_unlock(&eac->mutex);
return 0;
}
int eac_register_codec(struct device *dev, struct eac_codec *codec)
{
struct omap_eac *eac = dev_get_drvdata(dev);
struct snd_card *card = eac->card;
int err;
BUG_ON(eac->codec != NULL);
mutex_lock(&eac->mutex);
eac->codec = codec;
eac_enable_clocks(eac);
err = eac_codec_port_init(eac, codec);
eac_disable_clocks(eac);
mutex_unlock(&eac->mutex);
if (err)
return err;
/* register mixer controls implemented by a codec driver */
if (codec->register_controls != NULL) {
err = codec->register_controls(codec->private_data, card);
if (err)
return err;
}
if (codec->short_name != NULL) {
sprintf(card->longname, "%s with codec %s", card->shortname,
codec->short_name);
strcpy(card->mixername, codec->short_name);
}
err = snd_card_register(card);
return err;
}
void eac_unregister_codec(struct device *dev)
{
struct omap_eac *eac = dev_get_drvdata(dev);
BUG_ON(eac->codec == NULL);
eac_set_mode(dev, 0, 0);
snd_card_disconnect(eac->card);
eac->codec = NULL;
}
static int __devinit eac_probe(struct platform_device *pdev)
{
struct eac_platform_data *pdata = pdev->dev.platform_data;
struct snd_card *card;
struct omap_eac *eac;
struct resource *res;
int err;
eac = kzalloc(sizeof(*eac), GFP_KERNEL);
if (!eac)
return -ENOMEM;
mutex_init(&eac->mutex);
eac->pdev = pdev;
platform_set_drvdata(pdev, eac);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (res == NULL) {
err = -ENODEV;
goto err1;
}
eac->base = ioremap(res->start, res->end - res->start + 1);
eac->pdata = pdata;
/* pre-initialize EAC hw */
err = eac_get_clocks(eac);
if (err)
goto err1;
err = eac_enable_clocks(eac);
if (err)
goto err2;
err = eac_reset(eac);
if (err)
goto err3;
dev_info(&pdev->dev, "EAC version: %d.%d\n",
eac_read_reg(eac, EAC_VERSION) >> 4,
eac_read_reg(eac, EAC_VERSION) & 0x0f);
eac_disable_clocks(eac);
/* create soundcard instance */
card = snd_card_new(-1, id, THIS_MODULE, 0);
if (card == NULL) {
err = -ENOMEM;
goto err3;
}
eac->card = card;
strcpy(card->driver, "EAC");
strcpy(card->shortname, "OMAP24xx EAC");
sprintf(card->longname, "%s", card->shortname);
strcpy(card->mixername, "EAC Mixer");
if (eac->pdata->init) {
err = eac->pdata->init(&pdev->dev);
if (err < 0) {
printk("init %d\n", err);
goto err4;
}
}
return 0;
err4:
snd_card_free(card);
err3:
eac_disable_clocks(eac);
err2:
eac_put_clocks(eac);
err1:
kfree(eac);
return err;
}
static int __devexit eac_remove(struct platform_device *pdev)
{
struct omap_eac *eac = platform_get_drvdata(pdev);
struct snd_card *card = eac->card;
snd_card_free(card);
eac_disable_clocks(eac);
eac_put_clocks(eac);
iounmap(eac->base);
platform_set_drvdata(pdev, NULL);
return 0;
}
static struct platform_driver eac_driver = {
.driver = {
.name = "omap24xx-eac",
.bus = &platform_bus_type,
},
.probe = eac_probe,
.remove = eac_remove,
};
static int __init eac_init(void)
{
return platform_driver_register(&eac_driver);
}
static void __exit eac_exit(void)
{
platform_driver_unregister(&eac_driver);
}
module_init(eac_init);
module_exit(eac_exit);
MODULE_AUTHOR("Jarkko Nikula <jarkko.nikula@nokia.com>");
MODULE_LICENSE("GPL");
/*
* sound/arm/omap/omap-alsa-aic23-mixer.c
*
* Alsa Driver Mixer for generic codecs for omap boards
*
* Copyright (C) 2005 Instituto Nokia de Tecnologia - INdT - Manaus Brazil
* Written by David Cohen, Daniel Petrini
* {david.cohen, daniel.petrini}@indt.org.br
*
* Based on es1688_lib.c,
* Copyright (c) by Jaroslav Kysela <perex@suse.cz>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
* NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 675 Mass Ave, Cambridge, MA 02139, USA.
*
* History:
*
* 2005-08-02 INdT Kernel Team - Alsa mixer driver for omap osk.
* Creation of new file omap-alsa-mixer.c.
* Initial version with aic23 codec for osk5912
*/
#include <linux/kernel.h>
#include <mach/aic23.h>
#include <mach/omap-alsa.h>
#include "omap-alsa-aic23.h"
#include <sound/initval.h>
#include <sound/control.h>
MODULE_AUTHOR("David Cohen");
MODULE_AUTHOR("Daniel Petrini");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("OMAP Alsa mixer driver for ALSA");
/*
* Codec dependent region
*/
/* Codec AIC23 */
#if defined(CONFIG_SENSORS_TLV320AIC23) || \
defined(CONFIG_SENSORS_TLV320AIC23_MODULE)
#define MIXER_NAME "Mixer AIC23"
#define SND_OMAP_WRITE(reg, val) audio_aic23_write(reg, val)
#endif
/* Callback Functions */
#define OMAP_BOOL(xname, xindex, reg, reg_index, mask, invert) \
{ \
.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
.name = xname, \
.index = xindex, \
.info = snd_omap_info_bool, \
.get = snd_omap_get_bool, \
.put = snd_omap_put_bool, \
.private_value = reg | (reg_index << 8) | (invert << 10) | \
(mask << 12) \
}
#define OMAP_MUX(xname, reg, reg_index, mask) \
{ \
.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
.name = xname, \
.info = snd_omap_info_mux, \
.get = snd_omap_get_mux, \
.put = snd_omap_put_mux, \
.private_value = reg | (reg_index << 8) | (mask << 10) \
}
#define OMAP_SINGLE(xname, xindex, reg, reg_index, reg_val, mask) \
{\
.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
.name = xname, \
.index = xindex, \
.info = snd_omap_info_single, \
.get = snd_omap_get_single, \
.put = snd_omap_put_single, \
.private_value = reg | (reg_val << 8) | (reg_index << 16) |\
(mask << 18) \
}
#define OMAP_DOUBLE(xname, xindex, left_reg, right_reg, reg_index, mask) \
{\
.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
.name = xname, \
.index = xindex, \
.info = snd_omap_info_double, \
.get = snd_omap_get_double, \
.put = snd_omap_put_double, \
.private_value = left_reg | (right_reg << 8) | (reg_index << 16) | \
(mask << 18) \
}
/* Local Registers */
enum snd_device_index {
PCM_INDEX = 0,
LINE_INDEX,
AAC_INDEX, /* Analog Audio Control: reg = l_reg */
};
struct {
u16 l_reg;
u16 r_reg;
u8 sw;
} omap_regs[3];
#ifdef CONFIG_PM
struct {
u16 l_reg;
u16 r_reg;
u8 sw;
} omap_pm_regs[3];
#endif
u16 snd_sidetone[6] = {
SIDETONE_18,
SIDETONE_12,
SIDETONE_9,
SIDETONE_6,
SIDETONE_0,
0
};
/* Begin Bool Functions */
static int snd_omap_info_bool(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 1;
return 0;
}
static int snd_omap_get_bool(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
int mic_index = (kcontrol->private_value >> 8) & 0x03;
u16 mask = (kcontrol->private_value >> 12) & 0xff;
int invert = (kcontrol->private_value >> 10) & 0x03;
if (invert)
ucontrol->value.integer.value[0] =
(omap_regs[mic_index].l_reg & mask) ? 0 : 1;
else
ucontrol->value.integer.value[0] =
(omap_regs[mic_index].l_reg & mask) ? 1 : 0;
return 0;
}
static int snd_omap_put_bool(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
int mic_index = (kcontrol->private_value >> 8) & 0x03;
u16 mask = (kcontrol->private_value >> 12) & 0xff;
u16 reg = kcontrol->private_value & 0xff;
int invert = (kcontrol->private_value >> 10) & 0x03;
int changed = 1;
if (ucontrol->value.integer.value[0]) /* XOR */
if (invert)
omap_regs[mic_index].l_reg &= ~mask;
else
omap_regs[mic_index].l_reg |= mask;
else
if (invert)
omap_regs[mic_index].l_reg |= mask;
else
omap_regs[mic_index].l_reg &= ~mask;
SND_OMAP_WRITE(reg, omap_regs[mic_index].l_reg);
return changed;
}
/* End Bool Functions */
/* Begin Mux Functions */
static int snd_omap_info_mux(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
/* Mic = 0
* Line = 1 */
static char *texts[2] = { "Mic", "Line" };
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
uinfo->count = 1;
uinfo->value.enumerated.items = 2;
if (uinfo->value.enumerated.item > 1)
uinfo->value.enumerated.item = 1;
strcpy(uinfo->value.enumerated.name,
texts[uinfo->value.enumerated.item]);
return 0;
}
static int snd_omap_get_mux(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
u16 mask = (kcontrol->private_value >> 10) & 0xff;
int mux_idx = (kcontrol->private_value >> 8) & 0x03;
ucontrol->value.enumerated.item[0] =
(omap_regs[mux_idx].l_reg & mask) ? 0 /* Mic */ : 1 /* Line */;
return 0;
}
static int snd_omap_put_mux(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
u16 reg = kcontrol->private_value & 0xff;
u16 mask = (kcontrol->private_value >> 10) & 0xff;
int mux_index = (kcontrol->private_value >> 8) & 0x03;
int changed = 1;
if (!ucontrol->value.integer.value[0])
omap_regs[mux_index].l_reg |= mask; /* AIC23: Mic */
else
omap_regs[mux_index].l_reg &= ~mask; /* AIC23: Line */
SND_OMAP_WRITE(reg, omap_regs[mux_index].l_reg);
return changed;
}
/* End Mux Functions */
/* Begin Single Functions */
static int snd_omap_info_single(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
int mask = (kcontrol->private_value >> 18) & 0xff;
int reg_val = (kcontrol->private_value >> 8) & 0xff;
uinfo->type = mask ? SNDRV_CTL_ELEM_TYPE_INTEGER :
SNDRV_CTL_ELEM_TYPE_BOOLEAN;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = reg_val-1;
return 0;
}
static int snd_omap_get_single(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
u16 reg_val = (kcontrol->private_value >> 8) & 0xff;
ucontrol->value.integer.value[0] = snd_sidetone[reg_val];
return 0;
}
static int snd_omap_put_single(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
u16 reg_index = (kcontrol->private_value >> 16) & 0x03;
u16 mask = (kcontrol->private_value >> 18) & 0x1ff;
u16 reg = kcontrol->private_value & 0xff;
u16 reg_val = (kcontrol->private_value >> 8) & 0xff;
int changed = 0;
/* Volume */
if ((omap_regs[reg_index].l_reg !=
(ucontrol->value.integer.value[0] & mask))) {
changed = 1;
omap_regs[reg_index].l_reg &= ~mask;
omap_regs[reg_index].l_reg |=
snd_sidetone[ucontrol->value.integer.value[0]];
snd_sidetone[reg_val] = ucontrol->value.integer.value[0];
SND_OMAP_WRITE(reg, omap_regs[reg_index].l_reg);
} else {
changed = 0;
}
return changed;
}
/* End Single Functions */
/* Begin Double Functions */
static int snd_omap_info_double(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
/*
* mask == 0 : Switch
* mask != 0 : Volume
*/
int mask = (kcontrol->private_value >> 18) & 0xff;
uinfo->type = mask ? SNDRV_CTL_ELEM_TYPE_INTEGER :
SNDRV_CTL_ELEM_TYPE_BOOLEAN;
uinfo->count = mask ? 2 : 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = mask ? mask : 1;
return 0;
}
static int snd_omap_get_double(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
/*
* mask == 0 : Switch
* mask != 0 : Volume
*/
int mask = (kcontrol->private_value >> 18) & 0xff;
int vol_index = (kcontrol->private_value >> 16) & 0x03;
if (!mask) {
/* Switch */
ucontrol->value.integer.value[0] = omap_regs[vol_index].sw;
} else {
/* Volume */
ucontrol->value.integer.value[0] = omap_regs[vol_index].l_reg;
ucontrol->value.integer.value[1] = omap_regs[vol_index].r_reg;
}
return 0;
}
static int snd_omap_put_double(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
/* mask == 0 : Switch
* mask != 0 : Volume */
int vol_index = (kcontrol->private_value >> 16) & 0x03;
int mask = (kcontrol->private_value >> 18) & 0xff;
int left_reg = kcontrol->private_value & 0xff;
int right_reg = (kcontrol->private_value >> 8) & 0xff;
int changed = 0;
if (!mask) {
/* Switch */
if (!ucontrol->value.integer.value[0]) {
SND_OMAP_WRITE(left_reg, 0x00);
SND_OMAP_WRITE(right_reg, 0x00);
} else {
SND_OMAP_WRITE(left_reg, omap_regs[vol_index].l_reg);
SND_OMAP_WRITE(right_reg, omap_regs[vol_index].r_reg);
}
changed = 1;
omap_regs[vol_index].sw = ucontrol->value.integer.value[0];
} else {
/* Volume */
if ((omap_regs[vol_index].l_reg !=
(ucontrol->value.integer.value[0] & mask)) ||
(omap_regs[vol_index].r_reg !=
(ucontrol->value.integer.value[1] & mask))) {
changed = 1;
omap_regs[vol_index].l_reg &= ~mask;
omap_regs[vol_index].r_reg &= ~mask;
omap_regs[vol_index].l_reg |=
(ucontrol->value.integer.value[0] & mask);
omap_regs[vol_index].r_reg |=
(ucontrol->value.integer.value[1] & mask);
if (omap_regs[vol_index].sw) {
/* write to registers only if sw is actived */
SND_OMAP_WRITE(left_reg,
omap_regs[vol_index].l_reg);
SND_OMAP_WRITE(right_reg,
omap_regs[vol_index].r_reg);
}
} else {
changed = 0;
}
}
return changed;
}
/* End Double Functions */
static struct snd_kcontrol_new snd_omap_controls[] = {
OMAP_DOUBLE("PCM Playback Switch", 0, LEFT_CHANNEL_VOLUME_ADDR,
RIGHT_CHANNEL_VOLUME_ADDR, PCM_INDEX, 0x00),
OMAP_DOUBLE("PCM Playback Volume", 0, LEFT_CHANNEL_VOLUME_ADDR,
RIGHT_CHANNEL_VOLUME_ADDR, PCM_INDEX,
OUTPUT_VOLUME_MASK),
OMAP_BOOL("Line Playback Switch", 0, ANALOG_AUDIO_CONTROL_ADDR,
AAC_INDEX, BYPASS_ON, 0),
OMAP_DOUBLE("Line Capture Switch", 0, LEFT_LINE_VOLUME_ADDR,
RIGHT_LINE_VOLUME_ADDR, LINE_INDEX, 0x00),
OMAP_DOUBLE("Line Capture Volume", 0, LEFT_LINE_VOLUME_ADDR,
RIGHT_LINE_VOLUME_ADDR, LINE_INDEX, INPUT_VOLUME_MASK),
OMAP_BOOL("Mic Playback Switch", 0, ANALOG_AUDIO_CONTROL_ADDR,
AAC_INDEX, STE_ENABLED, 0),
OMAP_SINGLE("Mic Playback Volume", 0, ANALOG_AUDIO_CONTROL_ADDR,
AAC_INDEX, 5, SIDETONE_MASK),
OMAP_BOOL("Mic Capture Switch", 0, ANALOG_AUDIO_CONTROL_ADDR,
AAC_INDEX, MICM_MUTED, 1),
OMAP_BOOL("Mic Booster Playback Switch", 0, ANALOG_AUDIO_CONTROL_ADDR,
AAC_INDEX, MICB_20DB, 0),
OMAP_MUX("Capture Source", ANALOG_AUDIO_CONTROL_ADDR, AAC_INDEX,
INSEL_MIC),
};
#ifdef CONFIG_PM
void snd_omap_suspend_mixer(void)
{
/* Saves current values to wake-up correctly */
omap_pm_regs[LINE_INDEX].l_reg = omap_regs[LINE_INDEX].l_reg;
omap_pm_regs[LINE_INDEX].r_reg = omap_regs[LINE_INDEX].l_reg;
omap_pm_regs[LINE_INDEX].sw = omap_regs[LINE_INDEX].sw;
omap_pm_regs[AAC_INDEX].l_reg = omap_regs[AAC_INDEX].l_reg;
omap_pm_regs[PCM_INDEX].l_reg = omap_regs[PCM_INDEX].l_reg;
omap_pm_regs[PCM_INDEX].r_reg = omap_regs[PCM_INDEX].r_reg;
omap_pm_regs[PCM_INDEX].sw = omap_regs[PCM_INDEX].sw;
}
void snd_omap_resume_mixer(void)
{
/* Line's saved values */
omap_regs[LINE_INDEX].l_reg = omap_pm_regs[LINE_INDEX].l_reg;
omap_regs[LINE_INDEX].r_reg = omap_pm_regs[LINE_INDEX].l_reg;
omap_regs[LINE_INDEX].sw = omap_pm_regs[LINE_INDEX].sw;
SND_OMAP_WRITE(LEFT_LINE_VOLUME_ADDR, omap_pm_regs[LINE_INDEX].l_reg);
SND_OMAP_WRITE(RIGHT_LINE_VOLUME_ADDR, omap_pm_regs[LINE_INDEX].l_reg);
/* Analog Audio Control's saved values */
omap_regs[AAC_INDEX].l_reg = omap_pm_regs[AAC_INDEX].l_reg;
SND_OMAP_WRITE(ANALOG_AUDIO_CONTROL_ADDR, omap_regs[AAC_INDEX].l_reg);
/* Headphone's saved values */
omap_regs[PCM_INDEX].l_reg = omap_pm_regs[PCM_INDEX].l_reg;
omap_regs[PCM_INDEX].r_reg = omap_pm_regs[PCM_INDEX].r_reg;
omap_regs[PCM_INDEX].sw = omap_pm_regs[PCM_INDEX].sw;
SND_OMAP_WRITE(LEFT_CHANNEL_VOLUME_ADDR,
omap_pm_regs[PCM_INDEX].l_reg);
SND_OMAP_WRITE(RIGHT_CHANNEL_VOLUME_ADDR,
omap_pm_regs[PCM_INDEX].r_reg);
}
#endif
void snd_omap_init_mixer(void)
{
u16 vol_reg;
/* Line's default values */
omap_regs[LINE_INDEX].l_reg = DEFAULT_INPUT_VOLUME & INPUT_VOLUME_MASK;
omap_regs[LINE_INDEX].r_reg = DEFAULT_INPUT_VOLUME & INPUT_VOLUME_MASK;
omap_regs[LINE_INDEX].sw = 0;
SND_OMAP_WRITE(LEFT_LINE_VOLUME_ADDR,
DEFAULT_INPUT_VOLUME & INPUT_VOLUME_MASK);
SND_OMAP_WRITE(RIGHT_LINE_VOLUME_ADDR,
DEFAULT_INPUT_VOLUME & INPUT_VOLUME_MASK);
/* Analog Audio Control's default values */
omap_regs[AAC_INDEX].l_reg = DEFAULT_ANALOG_AUDIO_CONTROL;
/* Headphone's default values */
vol_reg = LZC_ON;
vol_reg &= ~OUTPUT_VOLUME_MASK;
vol_reg |= DEFAULT_OUTPUT_VOLUME;
omap_regs[PCM_INDEX].l_reg = DEFAULT_OUTPUT_VOLUME;
omap_regs[PCM_INDEX].r_reg = DEFAULT_OUTPUT_VOLUME;
omap_regs[PCM_INDEX].sw = 1;
SND_OMAP_WRITE(LEFT_CHANNEL_VOLUME_ADDR, vol_reg);
SND_OMAP_WRITE(RIGHT_CHANNEL_VOLUME_ADDR, vol_reg);
}
int snd_omap_mixer(struct snd_card_omap_codec *chip)
{
struct snd_card *card;
unsigned int idx;
int err;
snd_assert(chip != NULL && chip->card != NULL, return -EINVAL);
card = chip->card;
strcpy(card->mixername, MIXER_NAME);
/* Registering alsa mixer controls */
for (idx = 0; idx < ARRAY_SIZE(snd_omap_controls); idx++) {
err = snd_ctl_add(card,
snd_ctl_new1(&snd_omap_controls[idx], chip));
if (err < 0)
return err;
}
return 0;
}
/*
* arch/arm/mach-omap1/omap-alsa-aic23.c
*
* Alsa codec Driver for AIC23 chip on OSK5912 platform board
*
* Copyright (C) 2005 Instituto Nokia de Tecnologia - INdT - Manaus Brazil
* Written by Daniel Petrini, David Cohen, Anderson Briglia
* {daniel.petrini, david.cohen, anderson.briglia}@indt.org.br
*
* Copyright (C) 2006 Mika Laitio <lamikr@cc.jyu.fi>
*
* Based in former alsa driver for osk and oss driver
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/initval.h>
#include <sound/control.h>
#include <linux/clk.h>
#include <mach/clock.h>
#include <mach/aic23.h>
#include <mach/omap-alsa.h>
#include "omap-alsa-aic23.h"
static struct clk *aic23_mclk;
/* aic23 related */
static const struct aic23_samplerate_reg_info
rate_reg_info[NUMBER_SAMPLE_RATES_SUPPORTED] = {
{4000, 0x06, 1}, /* 4000 */
{8000, 0x06, 0}, /* 8000 */
{16000, 0x0C, 1}, /* 16000 */
{22050, 0x11, 1}, /* 22050 */
{24000, 0x00, 1}, /* 24000 */
{32000, 0x0C, 0}, /* 32000 */
{44100, 0x11, 0}, /* 44100 */
{48000, 0x00, 0}, /* 48000 */
{88200, 0x1F, 0}, /* 88200 */
{96000, 0x0E, 0}, /* 96000 */
};
/*
* Hardware capabilities
*/
/*
* DAC USB-mode sampling rates (MCLK = 12 MHz)
* The rates and rate_reg_into MUST be in the same order
*/
static unsigned int rates[] = {
4000, 8000, 16000, 22050,
24000, 32000, 44100,
48000, 88200, 96000,
};
static struct snd_pcm_hw_constraint_list aic23_hw_constraints_rates = {
.count = ARRAY_SIZE(rates),
.list = rates,
.mask = 0,
};
static struct snd_pcm_hardware aic23_snd_omap_alsa_playback = {
.info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID),
.formats = (SNDRV_PCM_FMTBIT_S16_LE),
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 |
SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |
SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 |
SNDRV_PCM_RATE_KNOT),
.rate_min = 8000,
.rate_max = 96000,
.channels_min = 2,
.channels_max = 2,
.buffer_bytes_max = 128 * 1024,
.period_bytes_min = 32,
.period_bytes_max = 8 * 1024,
.periods_min = 16,
.periods_max = 255,
.fifo_size = 0,
};
static struct snd_pcm_hardware aic23_snd_omap_alsa_capture = {
.info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID),
.formats = (SNDRV_PCM_FMTBIT_S16_LE),
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 |
SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |
SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 |
SNDRV_PCM_RATE_KNOT),
.rate_min = 8000,
.rate_max = 96000,
.channels_min = 2,
.channels_max = 2,
.buffer_bytes_max = 128 * 1024,
.period_bytes_min = 32,
.period_bytes_max = 8 * 1024,
.periods_min = 16,
.periods_max = 255,
.fifo_size = 0,
};
/*
* Codec/mcbsp init and configuration section
* codec dependent code.
*/
/* TLV320AIC23 is a write only device */
void audio_aic23_write(u8 address, u16 data)
{
aic23_write_value(address, data);
}
EXPORT_SYMBOL_GPL(audio_aic23_write);
/*
* Sample rate changing
*/
void aic23_set_samplerate(long rate)
{
u8 count = 0;
u16 data = 0;
/* Fix the rate if it has a wrong value */
if (rate >= 96000)
rate = 96000;
else if (rate >= 88200)
rate = 88200;
else if (rate >= 48000)
rate = 48000;
else if (rate >= 44100)
rate = 44100;
else if (rate >= 32000)
rate = 32000;
else if (rate >= 24000)
rate = 24000;
else if (rate >= 22050)
rate = 22050;
else if (rate >= 16000)
rate = 16000;
else if (rate >= 8000)
rate = 8000;
else
rate = 4000;
/* Search for the right sample rate */
/* Verify what happens if the rate is not supported
* now it goes to 96Khz */
while ((rate_reg_info[count].sample_rate != rate) &&
(count < (NUMBER_SAMPLE_RATES_SUPPORTED - 1))) {
count++;
}
data = (rate_reg_info[count].divider << CLKIN_SHIFT) |
(rate_reg_info[count].control << BOSR_SHIFT) | USB_CLK_ON;
audio_aic23_write(SAMPLE_RATE_CONTROL_ADDR, data);
}
inline void aic23_configure(void)
{
/* Reset codec */
audio_aic23_write(RESET_CONTROL_ADDR, 0);
/* Initialize the AIC23 internal state */
/*
* Analog audio path control, DAC selected,
* delete INSEL_MIC for line-in
*/
audio_aic23_write(ANALOG_AUDIO_CONTROL_ADDR,
DEFAULT_ANALOG_AUDIO_CONTROL);
/* Digital audio path control, de-emphasis control 44.1kHz */
audio_aic23_write(DIGITAL_AUDIO_CONTROL_ADDR, DEEMP_44K);
/* Digital audio interface, master/slave mode, I2S, 16 bit */
#ifdef AIC23_MASTER
audio_aic23_write(DIGITAL_AUDIO_FORMAT_ADDR,
MS_MASTER | IWL_16 | FOR_DSP);
#else
audio_aic23_write(DIGITAL_AUDIO_FORMAT_ADDR, IWL_16 | FOR_DSP);
#endif
/* Enable digital interface */
audio_aic23_write(DIGITAL_INTERFACE_ACT_ADDR, ACT_ON);
}
/*
* OMAP MCBSP clock configuration and Power Management
*
* Here we have some functions that allow clock to be enabled and
* disabled only when needed. Besides doing clock configuration
* it allows turn on/turn off audio when necessary.
*/
/*
* Do clock framework mclk search
*/
void aic23_clock_setup(void)
{
aic23_mclk = clk_get(0, "mclk");
}
/*
* Do some sanity check, set clock rate, starts it and
* turn codec audio on
*/
int aic23_clock_on(void)
{
uint curRate;
if (clk_get_usecount(aic23_mclk) > 0) {
/* MCLK is already in use */
printk(KERN_WARNING
"MCLK in use at %d Hz. We change it to %d Hz\n",
(uint) clk_get_rate(aic23_mclk),
CODEC_CLOCK);
}
curRate = (uint)clk_get_rate(aic23_mclk);
if (curRate != CODEC_CLOCK) {
if (clk_set_rate(aic23_mclk, CODEC_CLOCK)) {
printk(KERN_ERR
"Cannot set MCLK for AIC23 CODEC\n");
return -ECANCELED;
}
}
clk_enable(aic23_mclk);
printk(KERN_DEBUG
"MCLK = %d [%d], usecount = %d\n",
(uint) clk_get_rate(aic23_mclk), CODEC_CLOCK,
clk_get_usecount(aic23_mclk));
/* Now turn the audio on */
audio_aic23_write(POWER_DOWN_CONTROL_ADDR,
~DEVICE_POWER_OFF & ~OUT_OFF & ~DAC_OFF &
~ADC_OFF & ~MIC_OFF & ~LINE_OFF);
return 0;
}
/*
* Do some sanity check, turn clock off and then turn
* codec audio off
*/
int aic23_clock_off(void)
{
if (clk_get_usecount(aic23_mclk) > 0) {
if (clk_get_rate(aic23_mclk) != CODEC_CLOCK) {
printk(KERN_WARNING
"MCLK for audio should be %d Hz. But is %d Hz\n",
(uint) clk_get_rate(aic23_mclk),
CODEC_CLOCK);
}
clk_disable(aic23_mclk);
}
audio_aic23_write(POWER_DOWN_CONTROL_ADDR,
DEVICE_POWER_OFF | OUT_OFF | DAC_OFF |
ADC_OFF | MIC_OFF | LINE_OFF);
return 0;
}
int aic23_get_default_samplerate(void)
{
return DEFAULT_SAMPLE_RATE;
}
static int __devinit snd_omap_alsa_aic23_probe(struct platform_device *pdev)
{
int ret;
struct omap_alsa_codec_config *codec_cfg;
codec_cfg = pdev->dev.platform_data;
if (codec_cfg != NULL) {
codec_cfg->hw_constraints_rates = &aic23_hw_constraints_rates;
codec_cfg->snd_omap_alsa_playback =
&aic23_snd_omap_alsa_playback;
codec_cfg->snd_omap_alsa_capture = &aic23_snd_omap_alsa_capture;
codec_cfg->codec_configure_dev = aic23_configure;
codec_cfg->codec_set_samplerate = aic23_set_samplerate;
codec_cfg->codec_clock_setup = aic23_clock_setup;
codec_cfg->codec_clock_on = aic23_clock_on;
codec_cfg->codec_clock_off = aic23_clock_off;
codec_cfg->get_default_samplerate =
aic23_get_default_samplerate;
ret = snd_omap_alsa_post_probe(pdev, codec_cfg);
} else
ret = -ENODEV;
return ret;
}
static struct platform_driver omap_alsa_driver = {
.probe = snd_omap_alsa_aic23_probe,
.remove = snd_omap_alsa_remove,
.suspend = snd_omap_alsa_suspend,
.resume = snd_omap_alsa_resume,
.driver = {
.name = "omap_alsa_mcbsp",
},
};
static int __init omap_alsa_aic23_init(void)
{
int err;
ADEBUG();
err = platform_driver_register(&omap_alsa_driver);
return err;
}
static void __exit omap_alsa_aic23_exit(void)
{
ADEBUG();
platform_driver_unregister(&omap_alsa_driver);
}
module_init(omap_alsa_aic23_init);
module_exit(omap_alsa_aic23_exit);
/*
* sound/arm/omap-alsa-aic23.h
*
* Alsa Driver for AIC23 codec on OSK5912 platform board
*
* Copyright (C) 2005 Instituto Nokia de Tecnologia - INdT - Manaus Brazil
* Written by Daniel Petrini, David Cohen, Anderson Briglia
* {daniel.petrini, david.cohen, anderson.briglia}@indt.org.br
*
* Copyright (C) 2006 Mika Laitio <lamikr@cc.jyu.fi>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#ifndef __OMAP_ALSA_AIC23_H
#define __OMAP_ALSA_AIC23_H
#include <mach/dma.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <mach/mcbsp.h>
/* Define to set the AIC23 as the master w.r.t McBSP */
#define AIC23_MASTER
#define NUMBER_SAMPLE_RATES_SUPPORTED 10
/*
* AUDIO related MACROS
*/
#ifndef DEFAULT_BITPERSAMPLE
#define DEFAULT_BITPERSAMPLE 16
#endif
#define DEFAULT_SAMPLE_RATE 44100
#define CODEC_CLOCK 12000000
#define AUDIO_MCBSP OMAP_MCBSP1
#define DEFAULT_OUTPUT_VOLUME 0x60
#define DEFAULT_INPUT_VOLUME 0x00 /* 0 ==> mute line in */
#define OUTPUT_VOLUME_MIN LHV_MIN
#define OUTPUT_VOLUME_MAX LHV_MAX
#define OUTPUT_VOLUME_RANGE (OUTPUT_VOLUME_MAX - OUTPUT_VOLUME_MIN)
#define OUTPUT_VOLUME_MASK OUTPUT_VOLUME_MAX
#define INPUT_VOLUME_MIN LIV_MIN
#define INPUT_VOLUME_MAX LIV_MAX
#define INPUT_VOLUME_RANGE (INPUT_VOLUME_MAX - INPUT_VOLUME_MIN)
#define INPUT_VOLUME_MASK INPUT_VOLUME_MAX
#define SIDETONE_MASK 0x1c0
#define SIDETONE_0 0x100
#define SIDETONE_6 0x000
#define SIDETONE_9 0x040
#define SIDETONE_12 0x080
#define SIDETONE_18 0x0c0
#define DEFAULT_ANALOG_AUDIO_CONTROL (DAC_SELECTED | STE_ENABLED | \
BYPASS_ON | INSEL_MIC | MICB_20DB)
struct aic23_samplerate_reg_info {
u32 sample_rate;
u8 control; /* SR3, SR2, SR1, SR0 and BOSR */
u8 divider; /* if 0 CLKIN = MCLK, if 1 CLKIN = MCLK/2 */
};
extern int aic23_write_value(u8 reg, u16 value);
/*
* Defines codec specific function pointers that can be used from the
* common omap-alsa base driver for all omap codecs. (tsc2101 and aic23)
*/
void audio_aic23_write(u8 address, u16 data);
void define_codec_functions(struct omap_alsa_codec_config *codec_config);
inline void aic23_configure(void);
void aic23_set_samplerate(long rate);
void aic23_clock_setup(void);
int aic23_clock_on(void);
int aic23_clock_off(void);
int aic23_get_default_samplerate(void);
#endif
/*
* sound/arm/omap/omap-alsa-dma.c
*
* Common audio DMA handling for the OMAP processors
*
* Copyright (C) 2006 Mika Laitio <lamikr@cc.jyu.fi>
*
* Copyright (C) 2005 Instituto Nokia de Tecnologia - INdT - Manaus Brazil
*
* Copyright (C) 2004 Texas Instruments, Inc.
*
* Copyright (C) 2000, 2001 Nicolas Pitre <nico@cam.org>
*
* This package is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*
* History:
*
* 2004-06-07 Sriram Kannan - Created new file from omap_audio_dma_intfc.c.
* This file will contain only the DMA interface
* and buffer handling of OMAP audio driver.
*
* 2004-06-22 Sriram Kannan - removed legacy code (auto-init). Self-linking
* of DMA logical channel.
*
* 2004-08-12 Nishanth Menon - Modified to integrate Audio requirements on
* 1610, 1710 platforms
*
* 2004-11-01 Nishanth Menon - 16xx platform code base modified to support
* multi channel chaining.
*
* 2004-12-15 Nishanth Menon - Improved 16xx platform channel logic
* introduced - tasklets, queue handling updated
*
* 2005-07-19 INdT Kernel Team - Alsa port. Creation of new file
* omap-alsa-dma.c based in omap-audio-dma-intfc.c
* oss file. Support for aic23 codec. Removal of
* buffer handling (Alsa does that), modifications
* in dma handling and port to alsa structures.
*
* 2005-12-18 Dirk Behme - Added L/R Channel Interchange fix as proposed
* by Ajaya Babu
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/pm.h>
#include <linux/errno.h>
#include <linux/sound.h>
#include <linux/soundcard.h>
#include <linux/sysrq.h>
#include <linux/interrupt.h>
#include <linux/dma-mapping.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/semaphore.h>
#include <mach/hardware.h>
#include <mach/dma.h>
#include <mach/mcbsp.h>
#include <mach/omap-alsa.h>
#include "omap-alsa-dma.h"
#undef DEBUG
/*
* Channel Queue Handling macros
* tail always points to the current free entry
* Head always points to the current entry being used
* end is either head or tail
*/
#define AUDIO_QUEUE_INIT(s) s->dma_q_head = s->dma_q_tail = s->dma_q_count = 0;
#define AUDIO_QUEUE_FULL(s) (nr_linked_channels == s->dma_q_count)
#define AUDIO_QUEUE_LAST(s) (1 == s->dma_q_count)
#define AUDIO_QUEUE_EMPTY(s) (0 == s->dma_q_count)
#define __AUDIO_INCREMENT_QUEUE(end) ((end) = ((end)+1) % nr_linked_channels)
#define AUDIO_INCREMENT_HEAD(s) \
do { \
__AUDIO_INCREMENT_QUEUE(s->dma_q_head); \
s->dma_q_count--; \
} while (0)
#define AUDIO_INCREMENT_TAIL(s) \
do { \
__AUDIO_INCREMENT_QUEUE(s->dma_q_tail); \
s->dma_q_count++; \
} while (0)
/* DMA buffer fragmentation sizes */
#define MAX_DMA_SIZE 0x1000000 /* todo: sync with alsa */
/* #define CUT_DMA_SIZE 0x1000 */
/* TODO: To be moved to more appropriate location */
#define DCSR_ERROR 0x3
#define DCSR_END_BLOCK (1 << 5)
#define DCSR_SYNC_SET (1 << 6)
#define DCCR_FS (1 << 5)
#define DCCR_PRIO (1 << 6)
#define DCCR_AI (1 << 8)
#define DCCR_REPEAT (1 << 9)
/* if 0 the channel works in 3.1 compatible mode */
#define DCCR_N31COMP (1 << 10)
#define DCCR_EP (1 << 11)
#define DCCR_SRC_AMODE_BIT 12
#define DCCR_SRC_AMODE_MASK (0x3<<12)
#define DCCR_DST_AMODE_BIT 14
#define DCCR_DST_AMODE_MASK (0x3<<14)
#define AMODE_CONST 0x0
#define AMODE_POST_INC 0x1
#define AMODE_SINGLE_INDEX 0x2
#define AMODE_DOUBLE_INDEX 0x3
/* Data structures */
DEFINE_SPINLOCK(dma_list_lock);
static char nr_linked_channels = 1;
/* Module specific functions */
static void sound_dma_irq_handler(int lch, u16 ch_status, void *data);
static int audio_set_dma_params_play(int channel, dma_addr_t dma_ptr,
u_int dma_size);
static int audio_set_dma_params_capture(int channel, dma_addr_t dma_ptr,
u_int dma_size);
static int audio_start_dma_chain(struct audio_stream *s);
/*
* DMA channel requests
*/
static void omap_sound_dma_link_lch(void *data)
{
struct audio_stream *s = (struct audio_stream *) data;
int *chan = s->lch;
int i;
FN_IN;
if (s->linked) {
FN_OUT(1);
return;
}
for (i = 0; i < nr_linked_channels; i++) {
int cur_chan = chan[i];
int nex_chan =
((nr_linked_channels - 1 ==
i) ? chan[0] : chan[i + 1]);
omap_dma_link_lch(cur_chan, nex_chan);
}
s->linked = 1;
FN_OUT(0);
}
int omap_request_alsa_sound_dma(int device_id, const char *device_name,
void *data, int **channels)
{
int i, err = 0;
int *chan = NULL;
FN_IN;
if (unlikely((NULL == channels) || (NULL == device_name))) {
BUG();
return -EPERM;
}
/* Try allocate memory for the num channels */
*channels = kmalloc(sizeof(int) * nr_linked_channels, GFP_KERNEL);
chan = *channels;
if (NULL == chan) {
ERR("No Memory for channel allocs!\n");
FN_OUT(-ENOMEM);
return -ENOMEM;
}
spin_lock(&dma_list_lock);
for (i = 0; i < nr_linked_channels; i++) {
err = omap_request_dma(device_id,
device_name,
sound_dma_irq_handler,
data,
&chan[i]);
/* Handle Failure condition here */
if (err < 0) {
int j;
for (j = 0; j < i; j++)
omap_free_dma(chan[j]);
spin_unlock(&dma_list_lock);
kfree(chan);
*channels = NULL;
ERR("Error in requesting channel %d=0x%x\n", i,
err);
FN_OUT(err);
return err;
}
}
/* Chain the channels together */
if (!cpu_is_omap15xx())
omap_sound_dma_link_lch(data);
spin_unlock(&dma_list_lock);
FN_OUT(0);
return 0;
}
EXPORT_SYMBOL(omap_request_alsa_sound_dma);
/*
* DMA channel requests Freeing
*/
static void omap_sound_dma_unlink_lch(void *data)
{
struct audio_stream *s = (struct audio_stream *)data;
int *chan = s->lch;
int i;
FN_IN;
if (!s->linked) {
FN_OUT(1);
return;
}
for (i = 0; i < nr_linked_channels; i++) {
int cur_chan = chan[i];
int nex_chan =
((nr_linked_channels - 1 ==
i) ? chan[0] : chan[i + 1]);
omap_dma_unlink_lch(cur_chan, nex_chan);
}
s->linked = 0;
FN_OUT(0);
}
int omap_free_alsa_sound_dma(void *data, int **channels)
{
int i;
int *chan = NULL;
FN_IN;
if (unlikely(NULL == channels)) {
BUG();
return -EPERM;
}
if (unlikely(NULL == *channels)) {
BUG();
return -EPERM;
}
chan = (*channels);
if (!cpu_is_omap15xx())
omap_sound_dma_unlink_lch(data);
for (i = 0; i < nr_linked_channels; i++) {
int cur_chan = chan[i];
omap_stop_dma(cur_chan);
omap_free_dma(cur_chan);
}
kfree(*channels);
*channels = NULL;
FN_OUT(0);
return 0;
}
EXPORT_SYMBOL(omap_free_alsa_sound_dma);
/*
* Stop all the DMA channels of the stream
*/
void omap_stop_alsa_sound_dma(struct audio_stream *s)
{
int *chan = s->lch;
int i;
FN_IN;
if (unlikely(NULL == chan)) {
BUG();
return;
}
for (i = 0; i < nr_linked_channels; i++) {
int cur_chan = chan[i];
omap_stop_dma(cur_chan);
}
s->started = 0;
FN_OUT(0);
return;
}
EXPORT_SYMBOL(omap_stop_alsa_sound_dma);
/*
* Clear any pending transfers
*/
void omap_clear_alsa_sound_dma(struct audio_stream *s)
{
FN_IN;
omap_clear_dma(s->lch[s->dma_q_head]);
FN_OUT(0);
return;
}
EXPORT_SYMBOL(omap_clear_alsa_sound_dma);
/*
* DMA related functions
*/
static int audio_set_dma_params_play(int channel, dma_addr_t dma_ptr,
u_int dma_size)
{
int dt = 0x1; /* data type 16 */
int cen = 32; /* Stereo */
int cfn = dma_size / (2 * cen);
FN_IN;
omap_set_dma_dest_params(channel, 0x05, 0x00,
(OMAP1510_MCBSP1_BASE + 0x06),
0, 0);
omap_set_dma_src_params(channel, 0x00, 0x01, dma_ptr,
0, 0);
omap_set_dma_transfer_params(channel, dt, cen, cfn, 0x00, 0, 0);
FN_OUT(0);
return 0;
}
static int audio_set_dma_params_capture(int channel, dma_addr_t dma_ptr,
u_int dma_size)
{
int dt = 0x1; /* data type 16 */
int cen = 32; /* stereo */
int cfn = dma_size / (2 * cen);
FN_IN;
omap_set_dma_src_params(channel, 0x05, 0x00,
(OMAP1510_MCBSP1_BASE + 0x02),
0, 0);
omap_set_dma_dest_params(channel, 0x00, 0x01, dma_ptr, 0, 0);
omap_set_dma_transfer_params(channel, dt, cen, cfn, 0x00, 0, 0);
FN_OUT(0);
return 0;
}
static int audio_start_dma_chain(struct audio_stream *s)
{
int channel = s->lch[s->dma_q_head];
FN_IN;
if (!s->started) {
s->hw_stop(); /* stops McBSP Interface */
omap_start_dma(channel);
s->started = 1;
s->hw_start(); /* start McBSP interface */
} else if (cpu_is_omap310())
omap_start_dma(channel);
/* else the dma itself will progress forward with out our help */
FN_OUT(0);
return 0;
}
/*
* Start DMA -
* Do the initial set of work to initialize all the channels as required.
* We shall then initate a transfer
*/
int omap_start_alsa_sound_dma(struct audio_stream *s,
dma_addr_t dma_ptr,
u_int dma_size)
{
int ret = -EPERM;
FN_IN;
if (unlikely(dma_size > MAX_DMA_SIZE)) {
ERR("DmaSoundDma: Start: overflowed %d-%d\n", dma_size,
MAX_DMA_SIZE);
return -EOVERFLOW;
}
if (s->stream_id == SNDRV_PCM_STREAM_PLAYBACK) {
/* playback */
ret =
audio_set_dma_params_play(s->lch[s->dma_q_tail],
dma_ptr, dma_size);
} else {
ret =
audio_set_dma_params_capture(s->lch[s->dma_q_tail],
dma_ptr, dma_size);
}
if (ret != 0) {
ret = -3; /* indicate queue full */
goto sound_out;
}
AUDIO_INCREMENT_TAIL(s);
ret = audio_start_dma_chain(s);
if (ret)
ERR("dma start failed");
sound_out:
FN_OUT(ret);
return ret;
}
EXPORT_SYMBOL(omap_start_alsa_sound_dma);
/*
* ISRs have to be short and smart..
* Here we call alsa handling, after some error checking
*/
static void sound_dma_irq_handler(int sound_curr_lch, u16 ch_status,
void *data)
{
int dma_status = ch_status;
struct audio_stream *s = (struct audio_stream *) data;
FN_IN;
/* some register checking */
DPRINTK("lch=%d,status=0x%x, dma_status=%d, data=%p\n",
sound_curr_lch, ch_status, dma_status, data);
if (dma_status & (DCSR_ERROR)) {
omap_stop_dma(sound_curr_lch);
ERR("DCSR_ERROR!\n");
FN_OUT(-1);
return;
}
if (ch_status & DCSR_END_BLOCK)
callback_omap_alsa_sound_dma(s);
FN_OUT(0);
return;
}
MODULE_AUTHOR("Texas Instruments");
MODULE_DESCRIPTION("Common DMA handling for Audio driver on OMAP processors");
MODULE_LICENSE("GPL");
/*
* linux/sound/arm/omap/omap-alsa-dma.h
*
* Common audio DMA handling for the OMAP processors
*
* Copyright (C) 2006 Mika Laitio <lamikr@cc.jyu.fi>
*
* Copyright (C) 2005 Instituto Nokia de Tecnologia - INdT - Manaus Brazil
*
* Copyright (C) 2004 Texas Instruments, Inc.
*
* Copyright (C) 2000, 2001 Nicolas Pitre <nico@cam.org>
*
* This package is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*
* History:
*
*
* 2004/08/12 Nishanth Menon - Modified to integrate Audio requirements on
* 1610, 1710 platforms
*
* 2005/07/25 INdT Kernel Team - Renamed to omap-alsa-dma.h. Ported to Alsa.
*/
#ifndef __OMAP_AUDIO_ALSA_DMA_H
#define __OMAP_AUDIO_ALSA_DMA_H
#include <mach/omap-alsa.h>
/* Global data structures */
typedef void (*dma_callback_t) (int lch, u16 ch_status, void *data);
/* arch specific functions */
void omap_clear_alsa_sound_dma(struct audio_stream *s);
int omap_request_alsa_sound_dma(int device_id, const char *device_name,
void *data, int **channels);
int omap_free_alsa_sound_dma(void *data, int **channels);
int omap_start_alsa_sound_dma(struct audio_stream *s, dma_addr_t dma_ptr,
u_int dma_size);
void omap_stop_alsa_sound_dma(struct audio_stream *s);
#endif
/*
* sound/arm/omap/omap-alsa-sx1-mixer.c
*
* Alsa codec Driver for Siemens SX1 board.
* based on omap-alsa-tsc2101-mixer.c
*
* Copyright (C) 2006 Vladimir Ananiev (vovan888 at gmail com)
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
#include "omap-alsa-sx1.h"
#include "omap-alsa-sx1-mixer.h"
#include <linux/types.h>
#include <sound/initval.h>
#include <sound/control.h>
static int current_playback_target = PLAYBACK_TARGET_LOUDSPEAKER;
static int current_rec_src = REC_SRC_SINGLE_ENDED_MICIN_HED;
static int current_volume; /* current volume, we cant read it */
static int current_fm_volume; /* current FM radio volume, we cant read it */
/*
* Select SX1 recording source.
*/
static void set_record_source(int val)
{
/* TODO Recording is done on McBSP2 and Mic only */
current_rec_src = val;
}
static int set_mixer_volume(int mixer_vol)
{
int ret, i;
if ((mixer_vol < 0) || (mixer_vol > 9)) {
printk(KERN_ERR "Trying a bad mixer volume (%d)!\n", mixer_vol);
return -EPERM;
}
ret = (current_volume != mixer_vol);
current_volume = mixer_vol; /* set current volume, we cant read it */
i = cn_sx1snd_send(DAC_VOLUME_UPDATE, mixer_vol, 0);
if (i)
return i;
return ret;
}
static void set_loudspeaker_to_playback_target(void)
{
/* TODO */
cn_sx1snd_send(DAC_SETAUDIODEVICE, SX1_DEVICE_SPEAKER, 0);
current_playback_target = PLAYBACK_TARGET_LOUDSPEAKER;
}
static void set_headphone_to_playback_target(void)
{
/* TODO */
cn_sx1snd_send(DAC_SETAUDIODEVICE, SX1_DEVICE_HEADPHONE, 0);
current_playback_target = PLAYBACK_TARGET_HEADPHONE;
}
static void set_telephone_to_playback_target(void)
{
/* TODO */
cn_sx1snd_send(DAC_SETAUDIODEVICE, SX1_DEVICE_PHONE, 0);
current_playback_target = PLAYBACK_TARGET_CELLPHONE;
}
static void set_telephone_to_record_source(void)
{
cn_sx1snd_send(DAC_SETAUDIODEVICE, SX1_DEVICE_PHONE, 0);
}
static void init_playback_targets(void)
{
set_loudspeaker_to_playback_target();
set_mixer_volume(DEFAULT_OUTPUT_VOLUME);
}
/*
* Initializes SX1 record source (to mic) and playback target (to loudspeaker)
*/
void snd_omap_init_mixer(void)
{
/* Select headset to record source */
set_record_source(REC_SRC_SINGLE_ENDED_MICIN_HED);
/* Init loudspeaker as a default playback target*/
init_playback_targets();
}
/* ---------------------------------------------------------------------- */
static int pcm_playback_target_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
static char *texts[PLAYBACK_TARGET_COUNT] = {
"Loudspeaker", "Headphone", "Cellphone"
};
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
uinfo->count = 1;
uinfo->value.enumerated.items = PLAYBACK_TARGET_COUNT;
if (uinfo->value.enumerated.item > PLAYBACK_TARGET_COUNT - 1)
uinfo->value.enumerated.item = PLAYBACK_TARGET_COUNT - 1;
strcpy(uinfo->value.enumerated.name,
texts[uinfo->value.enumerated.item]);
return 0;
}
static int pcm_playback_target_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
ucontrol->value.integer.value[0] = current_playback_target;
return 0;
}
static int pcm_playback_target_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
int ret_val = 0;
int cur_val = ucontrol->value.integer.value[0];
if ((cur_val >= 0) &&
(cur_val < PLAYBACK_TARGET_COUNT) &&
(cur_val != current_playback_target)) {
if (cur_val == PLAYBACK_TARGET_LOUDSPEAKER) {
set_record_source(REC_SRC_SINGLE_ENDED_MICIN_HED);
set_loudspeaker_to_playback_target();
} else if (cur_val == PLAYBACK_TARGET_HEADPHONE) {
set_record_source(REC_SRC_SINGLE_ENDED_MICIN_HND);
set_headphone_to_playback_target();
} else if (cur_val == PLAYBACK_TARGET_CELLPHONE) {
set_telephone_to_record_source();
set_telephone_to_playback_target();
}
ret_val = 1;
}
return ret_val;
}
/*-----------------------------------------------------------*/
static int pcm_playback_volume_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 9;
return 0;
}
static int pcm_playback_volume_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
ucontrol->value.integer.value[0] = current_volume;
return 0;
}
static int pcm_playback_volume_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
return set_mixer_volume(ucontrol->value.integer.value[0]);
}
static int pcm_playback_switch_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 1;
return 0;
}
static int pcm_playback_switch_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
ucontrol->value.integer.value[0] = 1;
return 0;
}
static int pcm_playback_switch_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
return 0;
}
/* ----------------------------------------------------------- */
static int headset_playback_volume_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 9;
return 0;
}
static int headset_playback_volume_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
ucontrol->value.integer.value[0] = current_volume;
return 0;
}
static int headset_playback_volume_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
return set_mixer_volume(ucontrol->value.integer.value[0]);
}
static int headset_playback_switch_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 1;
return 0;
}
static int headset_playback_switch_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
ucontrol->value.integer.value[0] = 1;
return 0;
}
static int headset_playback_switch_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
/* mute/unmute headset */
#if 0
return adc_pga_unmute_control(ucontrol->value.integer.value[0],
TSC2101_HEADSET_GAIN_CTRL,
15);
#endif
return 0;
}
/* ----------------------------------------------------------- */
static int fmradio_playback_volume_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 9;
return 0;
}
static int fmradio_playback_volume_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
ucontrol->value.integer.value[0] = current_fm_volume;
return 0;
}
static int fmradio_playback_volume_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
int ret = current_fm_volume != ucontrol->value.integer.value[0];
int i;
current_fm_volume = ucontrol->value.integer.value[0];
i = cn_sx1snd_send(DAC_FMRADIO_OPEN, current_fm_volume, 0);
if (i)
return i;
return ret;
}
static int fmradio_playback_switch_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 1;
return 0;
}
static int fmradio_playback_switch_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
ucontrol->value.integer.value[0] = 1;
return 0;
}
static int fmradio_playback_switch_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
/* mute/unmute FM radio */
if (ucontrol->value.integer.value[0])
cn_sx1snd_send(DAC_FMRADIO_OPEN, current_fm_volume, 0);
else
cn_sx1snd_send(DAC_FMRADIO_CLOSE, 0, 0);
return 0;
}
/* ----------------------------------------------------------- */
static int cellphone_input_switch_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 1;
return 0;
}
static int cellphone_input_switch_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
ucontrol->value.integer.value[0] = 1;
return 0;
}
static int cellphone_input_switch_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
#if 0
return adc_pga_unmute_control(ucontrol->value.integer.value[0],
TSC2101_BUZZER_GAIN_CTRL, 15);
#endif
return 0;
}
/* ----------------------------------------------------------- */
static int buzzer_input_switch_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 1;
return 0;
}
static int buzzer_input_switch_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
ucontrol->value.integer.value[0] = 1;
return 0;
}
static int buzzer_input_switch_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
#if 0
return adc_pga_unmute_control(ucontrol->value.integer.value[0],
TSC2101_BUZZER_GAIN_CTRL, 6);
#endif
return 0;
}
/*-----------------------------------------------------------*/
static struct snd_kcontrol_new egold_control[] __devinitdata = {
{
.name = "Playback Playback Route",
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.index = 0,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.info = pcm_playback_target_info,
.get = pcm_playback_target_get,
.put = pcm_playback_target_put,
}, {
.name = "Master Playback Volume",
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.index = 0,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.info = pcm_playback_volume_info,
.get = pcm_playback_volume_get,
.put = pcm_playback_volume_put,
}, {
.name = "Master Playback Switch",
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.index = 0,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.info = pcm_playback_switch_info,
.get = pcm_playback_switch_get,
.put = pcm_playback_switch_put,
}, {
.name = "Headset Playback Volume",
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.index = 1,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.info = headset_playback_volume_info,
.get = headset_playback_volume_get,
.put = headset_playback_volume_put,
}, {
.name = "Headset Playback Switch",
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.index = 1,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.info = headset_playback_switch_info,
.get = headset_playback_switch_get,
.put = headset_playback_switch_put,
}, {
.name = "FM Playback Volume",
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.index = 2,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.info = fmradio_playback_volume_info,
.get = fmradio_playback_volume_get,
.put = fmradio_playback_volume_put,
}, {
.name = "FM Playback Switch",
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.index = 2,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.info = fmradio_playback_switch_info,
.get = fmradio_playback_switch_get,
.put = fmradio_playback_switch_put,
}, {
.name = "Cellphone Input Switch",
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.index = 0,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.info = cellphone_input_switch_info,
.get = cellphone_input_switch_get,
.put = cellphone_input_switch_put,
}, {
.name = "Buzzer Input Switch",
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.index = 0,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.info = buzzer_input_switch_info,
.get = buzzer_input_switch_get,
.put = buzzer_input_switch_put,
}
};
#ifdef CONFIG_PM
void snd_omap_suspend_mixer(void)
{
}
void snd_omap_resume_mixer(void)
{
snd_omap_init_mixer();
}
#endif
int snd_omap_mixer(struct snd_card_omap_codec *egold)
{
int i = 0;
int err = 0;
if (!egold)
return -EINVAL;
for (i = 0; i < ARRAY_SIZE(egold_control); i++) {
err = snd_ctl_add(egold->card,
snd_ctl_new1(&egold_control[i], egold->card));
if (err < 0)
return err;
}
return 0;
}
/*
* sound/arm/omap/omap-alsa-sx1-mixer.h
*
* Alsa codec Driver for Siemens SX1 board.
* based on omap-alsa-tsc2101-mixer.c
*
* Copyright (C) 2006 Vladimir Ananiev (vovan888 at gmail com)
*
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
#ifndef OMAPALSASX1MIXER_H_
#define OMAPALSASX1MIXER_H_
#include "omap-alsa-dma.h"
#define PLAYBACK_TARGET_COUNT 0x03
#define PLAYBACK_TARGET_LOUDSPEAKER 0x00
#define PLAYBACK_TARGET_HEADPHONE 0x01
#define PLAYBACK_TARGET_CELLPHONE 0x02
/* following are used for register 03h Mixer PGA control bits
D7-D5 for selecting record source */
#define REC_SRC_TARGET_COUNT 0x08
/* OSS code referred to MIXER_LINE */
#define REC_SRC_SINGLE_ENDED_MICIN_HED 0x00
/* OSS code referred to MIXER_MIC */
#define REC_SRC_SINGLE_ENDED_MICIN_HND 0x01
#define REC_SRC_SINGLE_ENDED_AUX1 0x02
#define REC_SRC_SINGLE_ENDED_AUX2 0x03
#define REC_SRC_MICIN_HED_AND_AUX1 0x04
#define REC_SRC_MICIN_HED_AND_AUX2 0x05
#define REC_SRC_MICIN_HND_AND_AUX1 0x06
#define REC_SRC_MICIN_HND_AND_AUX2 0x07
#define DEFAULT_OUTPUT_VOLUME 5 /* default output volume to dac dgc */
#define DEFAULT_INPUT_VOLUME 2 /* default record volume */
#endif
/*
* Alsa codec Driver for Siemens SX1 board.
* based on omap-alsa-tsc2101.c and cn_test.c example by Evgeniy Polyakov
*
* Copyright (C) 2006 Vladimir Ananiev (vovan888 at gmail com)
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include <linux/delay.h>
#include <linux/soundcard.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/connector.h>
#include <linux/slab.h>
#include <linux/pm.h>
#include <mach/dma.h>
#include <mach/clock.h>
#include <mach/gpio.h>
#include <mach/mcbsp.h>
#include <mach/omap-alsa.h>
#include "omap-alsa-sx1.h"
/* Connector implementation */
static struct cb_id cn_sx1snd_id = { CN_IDX_SX1SND, CN_VAL_SX1SND };
static char cn_sx1snd_name[] = "cn_sx1snd";
static void cn_sx1snd_callback(void *data)
{
struct cn_msg *msg = (struct cn_msg *)data;
printk(KERN_INFO
"%s: %lu: idx=%x, val=%x, seq=%u, ack=%u, len=%d: %s.\n",
__func__, jiffies, msg->id.idx, msg->id.val,
msg->seq, msg->ack, msg->len, (char *)msg->data);
}
/* Send IPC message to sound server */
int cn_sx1snd_send(unsigned int cmd, unsigned int arg1, unsigned int arg2)
{
struct cn_msg *m;
unsigned short data[3];
int err;
m = kzalloc(sizeof(*m) + sizeof(data), gfp_any());
if (!m)
return -1;
memcpy(&m->id, &cn_sx1snd_id, sizeof(m->id));
m->seq = 1;
m->len = sizeof(data);
data[0] = (unsigned short)cmd;
data[1] = (unsigned short)arg1;
data[2] = (unsigned short)arg2;
memcpy(m + 1, data, m->len);
err = cn_netlink_send(m, CN_IDX_SX1SND, gfp_any());
snd_printd("sent= %02X %02X %02X, err=%d\n", cmd, arg1, arg2, err);
kfree(m);
if (err == -ESRCH)
return -1; /* there are no listeners on socket */
return 0;
}
/* Hardware capabilities
*
* DAC USB-mode sampling rates (MCLK = 12 MHz)
* The rates and rate_reg_into MUST be in the same order
*/
static unsigned int rates[] = {
8000, 11025, 12000,
16000, 22050, 24000,
32000, 44100, 48000,
};
static struct snd_pcm_hw_constraint_list egold_hw_constraints_rates = {
.count = ARRAY_SIZE(rates),
.list = rates,
.mask = 0,
};
static struct snd_pcm_hardware egold_snd_omap_alsa_playback = {
.info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID),
.formats = (SNDRV_PCM_FMTBIT_S16_LE),
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |
SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 |
SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |
SNDRV_PCM_RATE_KNOT),
.rate_min = 8000,
.rate_max = 48000,
.channels_min = 2,
.channels_max = 2,
.buffer_bytes_max = 128 * 1024,
.period_bytes_min = 32,
.period_bytes_max = 8 * 1024,
.periods_min = 16,
.periods_max = 255,
.fifo_size = 0,
};
static struct snd_pcm_hardware egold_snd_omap_alsa_capture = {
.info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID),
.formats = (SNDRV_PCM_FMTBIT_S16_LE),
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |
SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 |
SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |
SNDRV_PCM_RATE_KNOT),
.rate_min = 8000,
.rate_max = 48000,
.channels_min = 2,
.channels_max = 2,
.buffer_bytes_max = 128 * 1024,
.period_bytes_min = 32,
.period_bytes_max = 8 * 1024,
.periods_min = 16,
.periods_max = 255,
.fifo_size = 0,
};
static long current_rate = -1; /* current rate in egold format 0..8 */
/*
* ALSA operations according to board file
*/
/*
* Sample rate changing
*/
static void egold_set_samplerate(long sample_rate)
{
int egold_rate = 0;
int clkgdv = 0;
u16 srgr1, srgr2;
/* Set the sample rate */
#if 0
/* fw15: 5005E490 - divs are different !!! */
clkgdv = CODEC_CLOCK / (sample_rate * (DEFAULT_BITPERSAMPLE * 2 - 1));
#endif
switch (sample_rate) {
case 8000:
clkgdv = 71;
egold_rate = FRQ_8000;
break;
case 11025:
clkgdv = 51;
egold_rate = FRQ_11025;
break;
case 12000:
clkgdv = 47;
egold_rate = FRQ_12000;
break;
case 16000:
clkgdv = 35;
egold_rate = FRQ_16000;
break;
case 22050:
clkgdv = 25;
egold_rate = FRQ_22050;
break;
case 24000:
clkgdv = 23;
egold_rate = FRQ_24000;
break;
case 32000:
clkgdv = 17;
egold_rate = FRQ_32000;
break;
case 44100:
clkgdv = 12;
egold_rate = FRQ_44100;
break;
case 48000:
clkgdv = 11;
egold_rate = FRQ_48000;
break;
}
srgr1 = (FWID(DEFAULT_BITPERSAMPLE - 1) | CLKGDV(clkgdv));
srgr2 = ((FSGM | FPER(DEFAULT_BITPERSAMPLE * 2 - 1)));
OMAP_MCBSP_WRITE(OMAP1510_MCBSP1_BASE, SRGR2, srgr2);
OMAP_MCBSP_WRITE(OMAP1510_MCBSP1_BASE, SRGR1, srgr1);
current_rate = egold_rate;
snd_printd("set samplerate=%ld\n", sample_rate);
}
static void egold_configure(void)
{
}
/*
* Omap MCBSP clock and Power Management configuration
*
* Here we have some functions that allows clock to be enabled and
* disabled only when needed. Besides doing clock configuration
* it allows turn on/turn off audio when necessary.
*/
/*
* Do clock framework mclk search
*/
static void egold_clock_setup(void)
{
omap_request_gpio(OSC_EN);
omap_set_gpio_direction(OSC_EN, 0); /* output */
snd_printd("\n");
}
/*
* Do some sanity check, set clock rate, starts it and turn codec audio on
*/
static int egold_clock_on(void)
{
omap_set_gpio_dataout(OSC_EN, 1);
egold_set_samplerate(44100); /* TODO */
cn_sx1snd_send(DAC_SETAUDIODEVICE, SX1_DEVICE_SPEAKER, 0);
cn_sx1snd_send(DAC_OPEN_DEFAULT, current_rate , 4);
snd_printd("\n");
return 0;
}
/*
* Do some sanity check, turn clock off and then turn codec audio off
*/
static int egold_clock_off(void)
{
cn_sx1snd_send(DAC_CLOSE, 0 , 0);
cn_sx1snd_send(DAC_SETAUDIODEVICE, SX1_DEVICE_PHONE, 0);
omap_set_gpio_dataout(OSC_EN, 0);
snd_printd("\n");
return 0;
}
static int egold_get_default_samplerate(void)
{
snd_printd("\n");
return DEFAULT_SAMPLE_RATE;
}
static int __init snd_omap_alsa_egold_probe(struct platform_device *pdev)
{
int ret;
struct omap_alsa_codec_config *codec_cfg;
codec_cfg = pdev->dev.platform_data;
if (!codec_cfg)
return -ENODEV;
codec_cfg->hw_constraints_rates = &egold_hw_constraints_rates;
codec_cfg->snd_omap_alsa_playback = &egold_snd_omap_alsa_playback;
codec_cfg->snd_omap_alsa_capture = &egold_snd_omap_alsa_capture;
codec_cfg->codec_configure_dev = egold_configure;
codec_cfg->codec_set_samplerate = egold_set_samplerate;
codec_cfg->codec_clock_setup = egold_clock_setup;
codec_cfg->codec_clock_on = egold_clock_on;
codec_cfg->codec_clock_off = egold_clock_off;
codec_cfg->get_default_samplerate = egold_get_default_samplerate;
ret = snd_omap_alsa_post_probe(pdev, codec_cfg);
snd_printd("\n");
return ret;
}
static struct platform_driver omap_alsa_driver = {
.probe = snd_omap_alsa_egold_probe,
.remove = snd_omap_alsa_remove,
.suspend = snd_omap_alsa_suspend,
.resume = snd_omap_alsa_resume,
.driver = {
.name = "omap_alsa_mcbsp",
},
};
static int __init omap_alsa_egold_init(void)
{
int retval;
retval = cn_add_callback(&cn_sx1snd_id, cn_sx1snd_name,
cn_sx1snd_callback);
if (retval)
printk(KERN_WARNING "cn_sx1snd failed to register\n");
return platform_driver_register(&omap_alsa_driver);
}
static void __exit omap_alsa_egold_exit(void)
{
cn_del_callback(&cn_sx1snd_id);
platform_driver_unregister(&omap_alsa_driver);
}
module_init(omap_alsa_egold_init);
module_exit(omap_alsa_egold_exit);
/*
* Based on omap-alsa-tsc2101.h
*
* Alsa Driver for Siemens SX1.
* Copyright (C) 2006 Vladimir Ananiev (vovan888 at gmail com)
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#ifndef OMAP_ALSA_SX1_H_
#define OMAP_ALSA_SX1_H_
#include <linux/types.h>
#define NUMBER_SAMPLE_RATES_SUPPORTED 9
/*
* AUDIO related MACROS
*/
#ifndef DEFAULT_BITPERSAMPLE
#define DEFAULT_BITPERSAMPLE 16
#endif
#define DEFAULT_SAMPLE_RATE 44100
/* fw15: 18356000 */
#define CODEC_CLOCK 18359000
/* McBSP for playing music */
#define AUDIO_MCBSP OMAP_MCBSP1
/* McBSP for record/play audio from phone and mic */
#define AUDIO_MCBSP_PCM OMAP_MCBSP2
/* gpio pin for enable/disable clock */
#define OSC_EN 2
/* Send IPC message to sound server */
extern int cn_sx1snd_send(unsigned int cmd, unsigned int arg1,
unsigned int arg2);
/* cmd for IPC_GROUP_DAC */
#define DAC_VOLUME_UPDATE 0
#define DAC_SETAUDIODEVICE 1
#define DAC_OPEN_RING 2
#define DAC_OPEN_DEFAULT 3
#define DAC_CLOSE 4
#define DAC_FMRADIO_OPEN 5
#define DAC_FMRADIO_CLOSE 6
#define DAC_PLAYTONE 7
/* cmd for IPC_GROUP_PCM */
#define PCM_PLAY (0+8)
#define PCM_RECORD (1+8)
#define PCM_CLOSE (2+8)
/* for DAC_SETAUDIODEVICE */
#define SX1_DEVICE_SPEAKER 0
#define SX1_DEVICE_HEADPHONE 4
#define SX1_DEVICE_PHONE 3
/* frequencies for MdaDacOpenDefaultL, MdaDacOpenRingL */
#define FRQ_8000 0
#define FRQ_11025 1
#define FRQ_12000 2
#define FRQ_16000 3
#define FRQ_22050 4
#define FRQ_24000 5
#define FRQ_32000 6
#define FRQ_44100 7
#define FRQ_48000 8
#endif
/*
* sound/arm/omap/omap-alsa-tsc2101-mixer.c
*
* Alsa Driver for TSC2101 codec for OMAP platform boards.
*
* Copyright (C) 2005 Mika Laitio <lamikr@cc.jyu.fi> and
* Everett Coleman II <gcc80x86@fuzzyneural.net>
*
* Board initialization code is based on the code in TSC2101 OSS driver.
* Copyright (C) 2004 Texas Instruments, Inc.
* Written by Nishanth Menon and Sriram Kannan
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
* NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 675 Mass Ave, Cambridge, MA 02139, USA.
*
* History:
*
* 2006-03-01 Mika Laitio - Mixer for the tsc2101 driver used in omap boards.
* Can switch between headset and loudspeaker playback,
* mute and unmute dgc, set dgc volume. Record source switch,
* keyclick, buzzer and headset volume and handset volume control
* are still missing.
*
*/
#include "omap-alsa-tsc2101.h"
#include "omap-alsa-tsc2101-mixer.h"
#include <linux/spi/tsc2101.h>
#include <linux/types.h>
#include <sound/initval.h>
#include <sound/control.h>
#ifdef DEBUG
#define M_DPRINTK(ARGS...) \
do { \
printk(KERN_INFO "<%s>: ", __func__); \
printk(ARGS); \
} while (0)
#else
#define M_DPRINTK(ARGS...) /* nop */
#endif
#define CHECK_BIT(INDX, ARG) (((ARG) & TSC2101_BIT(INDX)) >> INDX)
#define IS_UNMUTED(INDX, ARG) (((CHECK_BIT(INDX, ARG)) == 0))
#define DGC_DALVL_EXTRACT(ARG) ((ARG & 0x7f00) >> 8)
#define DGC_DARVL_EXTRACT(ARG) ((ARG & 0x007f))
#define HGC_ADPGA_HED_EXTRACT(ARG) ((ARG & 0x7f00) >> 8)
#define HNGC_ADPGA_HND_EXTRACT(ARG) ((ARG & 0x7f00) >> 8)
#define BGC_ADPGA_BGC_EXTRACT(ARG) ((ARG & 0x7f00) >> 8)
static int current_playback_target = PLAYBACK_TARGET_LOUDSPEAKER;
static int current_rec_src = REC_SRC_SINGLE_ENDED_MICIN_HED;
/*
* Simplified write for the tsc2101 audio registers.
*/
inline void omap_tsc2101_audio_write(u8 address, u16 data)
{
tsc2101_write_sync(mcbsp_dev.tsc2101_dev, PAGE2_AUDIO_CODEC_REGISTERS,
address, data);
}
/*
* Simplified read for the tsc2101 audio registers.
*/
inline u16 omap_tsc2101_audio_read(u8 address)
{
return (tsc2101_read_sync(mcbsp_dev.tsc2101_dev,
PAGE2_AUDIO_CODEC_REGISTERS, address));
}
/*
* For selecting tsc2101 recourd source.
*/
static void set_record_source(int val)
{
u16 data;
/*
* Mute Analog Sidetone
* Analog sidetone gain db?
* Input selected by MICSEL connected to ADC
*/
data = MPC_ASTMU | MPC_ASTG(0x45);
data &= ~MPC_MICSEL(7); /* clear all MICSEL bits */
data |= MPC_MICSEL(val);
data |= MPC_MICADC;
omap_tsc2101_audio_write(TSC2101_MIXER_PGA_CTRL, data);
current_rec_src = val;
}
/*
* Converts the Alsa mixer volume (0 - 100) to real
* Digital Gain Control (DGC) value that can be written
* or read from the TSC2101 registry.
*
* Note that the number "OUTPUT_VOLUME_MAX" is smaller than OUTPUT_VOLUME_MIN
* because DGC works as a volume decreaser. (The more bigger value is put
* to DGC, the more the volume of controlled channel is decreased)
*
* In addition the TCS2101 chip would allow the maximum
* volume reduction be 63.5 DB
* but according to some tests user can not hear anything with this chip
* when the volume is set to be less than 25 db.
* Therefore this function will return a value
* that means 38.5 db (63.5 db - 25 db)
* reduction in the channel volume, when mixer is set to 0.
* For mixer value 100, this will return a value that means
* 0 db volume reduction.
* ([mute_left_bit]0000000[mute_right_bit]0000000)
*/
int get_mixer_volume_as_dac_gain_control_volume(int vol)
{
u16 retVal;
/* Convert 0 -> 100 volume to 0x7F(min) -> y(max) volume range */
retVal = ((vol * OUTPUT_VOLUME_RANGE) / 100) + OUTPUT_VOLUME_MAX;
/* invert the value for getting the proper range 0 min and 100 max */
retVal = OUTPUT_VOLUME_MIN - retVal;
return retVal;
}
/*
* Converts the Alsa mixer volume (0 - 100) to TSC2101
* Digital Gain Control (DGC) volume. Alsa mixer volume 0
* is converted to value meaning the volume reduction of -38.5 db
* and Alsa mixer volume 100 is converted to value meaning the
* reduction of 0 db.
*/
int set_mixer_volume_as_dac_gain_control_volume(int mixerVolL, int mixerVolR)
{
u16 val;
int retVal;
int volL;
int volR;
if ((mixerVolL < 0) ||
(mixerVolL > 100) ||
(mixerVolR < 0) ||
(mixerVolR > 100)) {
printk(KERN_ERR "Trying a bad mixer volume as dac gain control"
" volume value, left (%d), right (%d)!\n", mixerVolL,
mixerVolR);
return -EPERM;
}
M_DPRINTK("mixer volume left = %d, right = %d\n", mixerVolL, mixerVolR);
volL = get_mixer_volume_as_dac_gain_control_volume(mixerVolL);
volR = get_mixer_volume_as_dac_gain_control_volume(mixerVolR);
val = omap_tsc2101_audio_read(TSC2101_DAC_GAIN_CTRL);
/* keep the old mute bit settings */
val &= ~(DGC_DALVL(OUTPUT_VOLUME_MIN) |
DGC_DARVL(OUTPUT_VOLUME_MIN));
val |= DGC_DALVL(volL) | DGC_DARVL(volR);
retVal = 2;
if (retVal)
omap_tsc2101_audio_write(TSC2101_DAC_GAIN_CTRL, val);
M_DPRINTK("to registry: left = %d, right = %d, total = %d\n",
DGC_DALVL_EXTRACT(val), DGC_DARVL_EXTRACT(val), val);
return retVal;
}
/*
* If unmuteLeft/unmuteRight == 0 --> mute
* If unmuteLeft/unmuteRight == 1 --> unmute
*/
int dac_gain_control_unmute(int unmuteLeft, int unmuteRight)
{
u16 val;
int count;
count = 0;
val = omap_tsc2101_audio_read(TSC2101_DAC_GAIN_CTRL);
/*
* in alsa mixer 1 --> on, 0 == off. In tsc2101 registry 1 --> off,
* 0 --> on so if values are same, it's time to change the registry
* value.
*/
if (unmuteLeft != IS_UNMUTED(15, val)) {
if (unmuteLeft == 0) {
/* mute --> turn bit on */
val = val | DGC_DALMU;
} else {
/* unmute --> turn bit off */
val = val & ~DGC_DALMU;
}
count++;
} /* L */
if (unmuteRight != IS_UNMUTED(7, val)) {
if (unmuteRight == 0) {
/* mute --> turn bit on */
val = val | DGC_DARMU;
} else {
/* unmute --> turn bit off */
val = val & ~DGC_DARMU;
}
count++;
} /* R */
if (count) {
omap_tsc2101_audio_write(TSC2101_DAC_GAIN_CTRL, val);
M_DPRINTK("changed value, is_unmuted left = %d, right = %d\n",
IS_UNMUTED(15, val),
IS_UNMUTED(7, val));
}
return count;
}
/*
* unmute: 0 --> mute, 1 --> unmute
* page2RegIndx: Registry index in tsc2101 page2.
* muteBitIndx: Index number for the bit in registry that indicates whether
* muted or unmuted.
*/
int adc_pga_unmute_control(int unmute, int page2regIndx, int muteBitIndx)
{
int count;
u16 val;
count = 0;
val = omap_tsc2101_audio_read(page2regIndx);
/*
* in alsa mixer 1 --> on, 0 == off. In tsc2101 registry 1 --> off,
* 0 --> on so if the values are same, it's time to change the
* registry value...
*/
if (unmute != IS_UNMUTED(muteBitIndx, val)) {
if (unmute == 0) {
/* mute --> turn bit on */
val = val | TSC2101_BIT(muteBitIndx);
} else {
/* unmute --> turn bit off */
val = val & ~TSC2101_BIT(muteBitIndx);
}
M_DPRINTK("changed value, is_unmuted = %d\n",
IS_UNMUTED(muteBitIndx, val));
count++;
}
if (count)
omap_tsc2101_audio_write(page2regIndx, val);
return count;
}
/*
* Converts the DGC registry value read from the TSC2101 registry to
* Alsa mixer volume format (0 - 100).
*/
int get_dac_gain_control_volume_as_mixer_volume(u16 vol)
{
u16 retVal;
retVal = OUTPUT_VOLUME_MIN - vol;
retVal = ((retVal - OUTPUT_VOLUME_MAX) * 100) / OUTPUT_VOLUME_RANGE;
/* fix scaling error */
if ((retVal > 0) && (retVal < 100))
retVal++;
return retVal;
}
/*
* Converts the headset gain control volume (0 - 63.5 db)
* to Alsa mixer volume (0 - 100)
*/
int get_headset_gain_control_volume_as_mixer_volume(u16 registerVal)
{
u16 retVal;
retVal = ((registerVal * 100) / INPUT_VOLUME_RANGE);
return retVal;
}
/*
* Converts the handset gain control volume (0 - 63.5 db)
* to Alsa mixer volume (0 - 100)
*/
int get_handset_gain_control_volume_as_mixer_volume(u16 registerVal)
{
return get_headset_gain_control_volume_as_mixer_volume(registerVal);
}
/*
* Converts the Alsa mixer volume (0 - 100) to
* headset gain control volume (0 - 63.5 db)
*/
int get_mixer_volume_as_headset_gain_control_volume(u16 mixerVal)
{
u16 retVal;
retVal = ((mixerVal * INPUT_VOLUME_RANGE) / 100) + INPUT_VOLUME_MIN;
return retVal;
}
/*
* Writes Alsa mixer volume (0 - 100) to TSC2101 headset volume registry in
* a TSC2101 format. (0 - 63.5 db)
* In TSC2101 OSS driver this functionality was controlled with "SET_LINE"
* parameter.
*/
int set_mixer_volume_as_headset_gain_control_volume(int mixerVol)
{
int volume;
int retVal;
u16 val;
if (mixerVol < 0 || mixerVol > 100) {
M_DPRINTK("Trying a bad headset mixer volume value(%d)!\n",
mixerVol);
return -EPERM;
}
M_DPRINTK("mixer volume = %d\n", mixerVol);
/*
* Convert 0 -> 100 volume to 0x0(min) -> 0x7D(max) volume range
* NOTE: 0 is minimum volume and not mute
*/
volume = get_mixer_volume_as_headset_gain_control_volume(mixerVol);
val = omap_tsc2101_audio_read(TSC2101_HEADSET_GAIN_CTRL);
/* preserve the old mute settings */
val &= ~(HGC_ADPGA_HED(INPUT_VOLUME_MAX));
val |= HGC_ADPGA_HED(volume);
omap_tsc2101_audio_write(TSC2101_HEADSET_GAIN_CTRL, val);
retVal = 1;
M_DPRINTK("to registry = %d\n", val);
return retVal;
}
/*
* Writes Alsa mixer volume (0 - 100) to TSC2101 handset volume registry in
* a TSC2101 format. (0 - 63.5 db)
* In TSC2101 OSS driver this functionality was controlled with
* "SET_MIC" parameter.
*/
int set_mixer_volume_as_handset_gain_control_volume(int mixerVol)
{
int volume;
int retVal;
u16 val;
if (mixerVol < 0 || mixerVol > 100) {
M_DPRINTK("Trying a bad mic mixer volume value(%d)!\n",
mixerVol);
return -EPERM;
}
M_DPRINTK("mixer volume = %d\n", mixerVol);
/*
* Convert 0 -> 100 volume to 0x0(min) -> 0x7D(max) volume range
* NOTE: 0 is minimum volume and not mute
*/
volume = get_mixer_volume_as_headset_gain_control_volume(mixerVol);
val = omap_tsc2101_audio_read(TSC2101_HANDSET_GAIN_CTRL);
/* preserve the old mute settigns */
val &= ~(HNGC_ADPGA_HND(INPUT_VOLUME_MAX));
val |= HNGC_ADPGA_HND(volume);
omap_tsc2101_audio_write(TSC2101_HANDSET_GAIN_CTRL, val);
retVal = 1;
M_DPRINTK("to registry = %d\n", val);
return retVal;
}
void set_loudspeaker_to_playback_target(void)
{
/* power down SPK1, SPK2 and loudspeaker */
omap_tsc2101_audio_write(TSC2101_CODEC_POWER_CTRL,
CPC_SP1PWDN | CPC_SP2PWDN | CPC_LDAPWDF);
/*
* ADC, DAC, Analog Sidetone, cellphone, buzzer softstepping enabled
* 1dB AGC hysteresis
* MICes bias 2V
*/
omap_tsc2101_audio_write(TSC2101_AUDIO_CTRL_4, AC4_MB_HED(0));
/*
* DAC left and right routed to SPK1/SPK2
* SPK1/SPK2 unmuted
* Keyclicks routed to SPK1/SPK2 */
omap_tsc2101_audio_write(TSC2101_AUDIO_CTRL_5,
AC5_DIFFIN |
AC5_DAC2SPK1(3) | AC5_AST2SPK1 | AC5_KCL2SPK1 |
AC5_DAC2SPK2(3) | AC5_AST2SPK2 | AC5_KCL2SPK2);
/*
* routing selected to SPK1 goes also to OUT8P/OUT8N. (loudspeaker)
* analog sidetone routed to loudspeaker
* buzzer pga routed to loudspeaker
* keyclick routing to loudspeaker
* cellphone input routed to loudspeaker
* mic selection (control register 04h/page2) routed to cell phone
* output (CP_OUT)
* routing selected for SPK1 goes also to cellphone output (CP_OUT)
* OUT8P/OUT8N (loudspeakers) unmuted (0 = unmuted)
* Cellphone output is not muted (0 = unmuted)
* Enable loudspeaker short protection control (0 = enable protection)
* VGND short protection control (0 = enable protection)
*/
omap_tsc2101_audio_write(TSC2101_AUDIO_CTRL_6,
AC6_SPL2LSK | AC6_AST2LSK | AC6_BUZ2LSK | AC6_KCL2LSK |
AC6_CPI2LSK | AC6_MIC2CPO | AC6_SPL2CPO);
current_playback_target = PLAYBACK_TARGET_LOUDSPEAKER;
}
void set_headphone_to_playback_target(void)
{
/* power down SPK1, SPK2 and loudspeaker */
omap_tsc2101_audio_write(TSC2101_CODEC_POWER_CTRL,
CPC_SP1PWDN | CPC_SP2PWDN | CPC_LDAPWDF);
/*
* ADC, DAC, Analog Sidetone, cellphone, buzzer softstepping enabled
 * 1dB AGC hysteresis
* MICes bias 2V
*/
omap_tsc2101_audio_write(TSC2101_AUDIO_CTRL_4, AC4_MB_HED(0));
/*
* DAC left and right routed to SPK1/SPK2
* SPK1/SPK2 unmuted
* Keyclicks routed to SPK1/SPK2
*/
omap_tsc2101_audio_write(TSC2101_AUDIO_CTRL_5,
AC5_DAC2SPK1(3) | AC5_AST2SPK1 | AC5_KCL2SPK1 |
AC5_DAC2SPK2(3) | AC5_AST2SPK2 | AC5_KCL2SPK2 |
AC5_HDSCPTC);
/* OUT8P/OUT8N muted, CPOUT muted */
omap_tsc2101_audio_write(TSC2101_AUDIO_CTRL_6,
AC6_MUTLSPK | AC6_MUTSPK2 | AC6_LDSCPTC |
AC6_VGNDSCPTC);
current_playback_target = PLAYBACK_TARGET_HEADPHONE;
}
void set_telephone_to_playback_target(void)
{
/*
* 0110 1101 0101 1100
* power down MICBIAS_HED, Analog sidetone, SPK2, DAC,
* Driver virtual ground, loudspeaker. Values D2-d5 are flags.
*/
omap_tsc2101_audio_write(TSC2101_CODEC_POWER_CTRL,
CPC_MBIAS_HED | CPC_ASTPWD | CPC_SP2PWDN | CPC_DAPWDN |
CPC_VGPWDN | CPC_LSPWDN);
/*
* 0010 1010 0100 0000
* ADC, DAC, Analog Sidetone, cellphone, buzzer softstepping enabled
* 1dB AGC hysteresis
* MICes bias 2V
*/
omap_tsc2101_audio_write(TSC2101_AUDIO_CTRL_4,
AC4_MB_HND | AC4_MB_HED(0) | AC4_AGCHYS(1) |
AC4_BISTPD | AC4_ASSTPD | AC4_DASTPD);
printk(KERN_INFO "set_telephone_to_playback_target(), "
"TSC2101_AUDIO_CTRL_4 = %d\n",
omap_tsc2101_audio_read(TSC2101_AUDIO_CTRL_4));
/*
* 1110 0010 0000 0010
* DAC left and right routed to SPK1/SPK2
* SPK1/SPK2 unmuted
* keyclicks routed to SPK1/SPK2
*/
omap_tsc2101_audio_write(TSC2101_AUDIO_CTRL_5,
AC5_DIFFIN | AC5_DAC2SPK1(3) |
AC5_CPI2SPK1 | AC5_MUTSPK2);
omap_tsc2101_audio_write(TSC2101_AUDIO_CTRL_6,
AC6_MIC2CPO | AC6_MUTLSPK |
AC6_LDSCPTC | AC6_VGNDSCPTC | AC6_CAPINTF);
current_playback_target = PLAYBACK_TARGET_CELLPHONE;
}
/*
* 1100 0101 1101 0000
*
* #define MPC_ASTMU TSC2101_BIT(15)
* #define MPC_ASTG(ARG) (((ARG) & 0x7F) << 8)
* #define MPC_MICSEL(ARG) (((ARG) & 0x07) << 5)
* #define MPC_MICADC TSC2101_BIT(4)
* #define MPC_CPADC TSC2101_BIT(3)
* #define MPC_ASTGF (0x01)
*/
static void set_telephone_to_record_source(void)
{
u16 val;
/*
* D0 = 0:
* --> AGC is off for handset input.
* --> ADC PGA is controlled by the ADMUT_HDN + ADPGA_HND
* (D15, D14-D8)
* D4 - D1 = 0000
* --> AGC time constant for handset input,
* attack time = 8 mc, decay time = 100 ms
* D7 - D5 = 000
* --> AGC Target gain for handset input = -5.5 db
* D14 - D8 = 011 1100
* --> ADC handset PGA settings = 60 = 30 db
* D15 = 0
* --> Handset input ON (unmuted)
*/
val = 0x3c00; /* 0011 1100 0000 0000 = 60 = 30 */
omap_tsc2101_audio_write(TSC2101_HANDSET_GAIN_CTRL, val);
/*
* D0 = 0
* --> AGC is off for headset/Aux input
* --> ADC headset/Aux PGA is contoller by
* ADMUT_HED + ADPGA_HED
* (D15, D14-D8)
* D4 - D1 = 0000
* --> Agc constant for headset/Aux input,
* attack time = 8 mc, decay time = 100 ms
* D7 - D5 = 000
* --> AGC target gain for headset input = -5.5 db
* D14 - D8 = 000 0000
* --> Adc headset/AUX pga settings = 0 db
* D15 = 1
* --> Headset/AUX input muted
*
* Mute headset aux input
*/
val = 0x8000; /* 1000 0000 0000 0000 */
omap_tsc2101_audio_write(TSC2101_HEADSET_GAIN_CTRL, val);
set_record_source(REC_SRC_MICIN_HND_AND_AUX1);
/*
* hacks start
* D0 = flag, Headset/Aux or handset PGA flag
* --> & with 1 (= 1 -->gain applied == pga
* register settings)
* D1 = 0, DAC channel PGA soft stepping control
* --> 0.5 db change every WCLK
* D2 = flag, DAC right channel PGA flag
* --> & with 1
* D3 = flag, DAC left channel PGA flag
* -- > & with 1
* D7 - D4 = 0001, keyclick length
* --> 4 periods key clicks
* D10 - D8 = 100, keyclick frequency
* --> 1 kHz,
* D11 = 0, Headset/Aux or handset soft stepping control
* --> 0,5 db change every WCLK or ADWS
* D14 -D12 = 100, Keyclick applitude control
* --> Medium amplitude
* D15 = 0, keyclick disabled
*/
val = omap_tsc2101_audio_read(TSC2101_AUDIO_CTRL_2);
val = val & 0x441d;
val = val | 0x4410; /* D14, D10, D4 bits == 1 */
omap_tsc2101_audio_write(TSC2101_AUDIO_CTRL_2, val);
/*
* D0 = 0 (reserved, write always 0)
* D1 = flag,
* --> & with 1
* D2 - D5 = 0000 (reserved, write always 0000)
* D6 = 1
* --> MICBIAS_HND = 2.0 v
* D8 - D7 = 00
* --> MICBIAS_HED = 3.3 v
* D10 - D9 = 01,
* --> Mic AGC hysteric selection = 2 db
* D11 = 1,
* --> Disable buzzer PGA soft stepping
* D12 = 0,
* --> Enable CELL phone PGA soft stepping control
* D13 = 1
* --> Disable analog sidetone soft
* stepping control
* D14 = 0
* --> Enable DAC PGA soft stepping control
* D15 = 0,
* --> Enable headset/Aux or Handset soft
* stepping control
*/
val = omap_tsc2101_audio_read(TSC2101_AUDIO_CTRL_4);
val = val & 0x2a42; /* 0010 1010 0100 0010 */
val = val | 0x2a40; /* bits D13, D11, D9, D6 == 1 */
omap_tsc2101_audio_write(TSC2101_AUDIO_CTRL_4, val);
printk(KERN_INFO "set_telephone_to_record_source(), "
"TSC2101_AUDIO_CTRL_4 = %d\n",
omap_tsc2101_audio_read(TSC2101_AUDIO_CTRL_4));
/*
* D0 = 0
* --> reserved, write always = 0
* D1 = flag, read only
* --> & with 1
* D5 - D2 = 1111, Buzzer input PGA settings
* --> 0 db
* D6 = 1,
* --> power down buzzer input pga
* D7 = flag, read only
* --> & with 1
* D14 - D8 = 101 1101
* --> 12 DB
* D15 = 0
* --> power up cell phone input PGA
*/
val = omap_tsc2101_audio_read(TSC2101_BUZZER_GAIN_CTRL);
val = val & 0x5dfe;
/* bits, D14, D12, D11, D10, D8, D6, D5,D4,D3,D2 */
val = val | 0x5dfe;
omap_tsc2101_audio_write(TSC2101_BUZZER_GAIN_CTRL, val);
/*
* D6 - D0 = 000 1001
* --> -4.5 db for DAC right channel volume control
* D7 = 1
* --> DAC right channel muted
* D14 - D8 = 000 1001
* --> -4.5 db for DAC left channel volume control
* D15 = 1
* --> DAC left channel muted
*/
/* val = omap_tsc2101_audio_read(TSC2101_DAC_GAIN_CTRL); */
val = 0x8989;
omap_tsc2101_audio_write(TSC2101_DAC_GAIN_CTRL, val);
/*
* 0000 0000 0100 0000
*
* D1 - D0 = 0
* --> GPIO 1 pin output is three stated
* D2 = 0
* --> Disaple GPIO2 for CLKOUT mode
* D3 = 0
* --> Disable GPUI1 for interrupt detection
* D4 = 0
* --> Disable GPIO2 for headset detection interrupt
* D5 = reserved, always 0
* D7 - D6 = 01
* --> 8 ms clitch detection
* D8 = reserved, write only 0
* D10 -D9 = 00
* --> 16 ms de-bouncing
* for glitch detection during headset detection
* D11 = flag for button press
* D12 = flag for headset detection
* D14-D13 = 00
* --> type of headset detected = 00 == no stereo
* headset deected
* D15 = 0
* --> Disable headset detection
*/
val = 0x40;
omap_tsc2101_audio_write(TSC2101_AUDIO_CTRL_7, val);
}
/*
* Checks whether the headset is detected.
* If headset is detected, the type is returned. Type can be
* 0x01 = stereo headset detected
* 0x02 = cellurar headset detected
* 0x03 = stereo + cellurar headset detected
* If headset is not detected 0 is returned.
*/
u16 get_headset_detected(void)
{
u16 curDetected;
u16 curType;
u16 curVal;
curType = 0; /* not detected */
curVal = omap_tsc2101_audio_read(TSC2101_AUDIO_CTRL_7);
curDetected = curVal & AC7_HDDETFL;
if (curDetected) {
printk(KERN_INFO "headset detected, checking type from %d \n",
curVal);
curType = ((curVal & 0x6000) >> 13);
printk(KERN_INFO "headset type detected = %d \n", curType);
} else {
printk(KERN_INFO "headset not detected\n");
}
return curType;
}
void init_playback_targets(void)
{
u16 val;
set_loudspeaker_to_playback_target();
/*
* Left line input volume control
* = SET_LINE in the OSS driver
*/
set_mixer_volume_as_headset_gain_control_volume(DEFAULT_INPUT_VOLUME);
/*
* Set headset to be controllable by handset mixer
* AGC enable for handset input
* Handset input not muted
*/
val = omap_tsc2101_audio_read(TSC2101_HANDSET_GAIN_CTRL);
val = val | HNGC_AGCEN_HND;
val = val & ~HNGC_ADMUT_HND;
omap_tsc2101_audio_write(TSC2101_HANDSET_GAIN_CTRL, val);
/*
* mic input volume control
* SET_MIC in the OSS driver
*/
set_mixer_volume_as_handset_gain_control_volume(DEFAULT_INPUT_VOLUME);
/*
* Left/Right headphone channel volume control
* Zero-cross detect on
*/
set_mixer_volume_as_dac_gain_control_volume(DEFAULT_OUTPUT_VOLUME,
DEFAULT_OUTPUT_VOLUME);
/* unmute */
dac_gain_control_unmute(1, 1);
}
/*
* Initializes tsc2101 recourd source (to line) and playback target
* (to loudspeaker)
*/
void snd_omap_init_mixer(void)
{
FN_IN;
/* Headset/Hook switch detect enabled */
omap_tsc2101_audio_write(TSC2101_AUDIO_CTRL_7, AC7_DETECT);
/* Select headset to record source (MIC_INHED)*/
set_record_source(REC_SRC_SINGLE_ENDED_MICIN_HED);
/* Init loudspeaker as a default playback target*/
init_playback_targets();
FN_OUT(0);
}
static int __pcm_playback_target_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
static char *texts[PLAYBACK_TARGET_COUNT] = {
"Loudspeaker", "Headphone", "Cellphone"
};
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
uinfo->count = 1;
uinfo->value.enumerated.items = PLAYBACK_TARGET_COUNT;
if (uinfo->value.enumerated.item > PLAYBACK_TARGET_COUNT - 1)
uinfo->value.enumerated.item = PLAYBACK_TARGET_COUNT - 1;
strcpy(uinfo->value.enumerated.name,
texts[uinfo->value.enumerated.item]);
return 0;
}
static int __pcm_playback_target_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
ucontrol->value.integer.value[0] = current_playback_target;
return 0;
}
static int __pcm_playback_target_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
int retVal;
int curVal;
retVal = 0;
curVal = ucontrol->value.integer.value[0];
if ((curVal >= 0) &&
(curVal < PLAYBACK_TARGET_COUNT) &&
(curVal != current_playback_target)) {
if (curVal == PLAYBACK_TARGET_LOUDSPEAKER) {
set_record_source(REC_SRC_SINGLE_ENDED_MICIN_HED);
set_loudspeaker_to_playback_target();
} else if (curVal == PLAYBACK_TARGET_HEADPHONE) {
set_record_source(REC_SRC_SINGLE_ENDED_MICIN_HND);
set_headphone_to_playback_target();
} else if (curVal == PLAYBACK_TARGET_CELLPHONE) {
set_telephone_to_record_source();
set_telephone_to_playback_target();
}
retVal = 1;
}
return retVal;
}
static int __pcm_playback_volume_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 2;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 100;
return 0;
}
/*
* Alsa mixer interface function for getting the volume read from the DGC in a
* 0 -100 alsa mixer format.
*/
static int __pcm_playback_volume_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
u16 volL;
u16 volR;
u16 val;
val = omap_tsc2101_audio_read(TSC2101_DAC_GAIN_CTRL);
M_DPRINTK("registry value = %d!\n", val);
volL = DGC_DALVL_EXTRACT(val);
volR = DGC_DARVL_EXTRACT(val);
/* make sure that other bits are not on */
volL = volL & ~DGC_DALMU;
volR = volR & ~DGC_DARMU;
volL = get_dac_gain_control_volume_as_mixer_volume(volL);
volR = get_dac_gain_control_volume_as_mixer_volume(volR);
ucontrol->value.integer.value[0] = volL; /* L */
ucontrol->value.integer.value[1] = volR; /* R */
M_DPRINTK("mixer volume left = %ld, right = %ld\n",
ucontrol->value.integer.value[0],
ucontrol->value.integer.value[1]);
return 0;
}
static int __pcm_playback_volume_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
return set_mixer_volume_as_dac_gain_control_volume(
ucontrol->value.integer.value[0],
ucontrol->value.integer.value[1]);
}
static int __pcm_playback_switch_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
uinfo->count = 2;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 1;
return 0;
}
/*
* When DGC_DALMU (bit 15) is 1, the left channel is muted.
* When DGC_DALMU is 0, left channel is not muted.
* Same logic apply also for the right channel.
*/
static int __pcm_playback_switch_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
u16 val = omap_tsc2101_audio_read(TSC2101_DAC_GAIN_CTRL);
ucontrol->value.integer.value[0] = IS_UNMUTED(15, val); /* left */
ucontrol->value.integer.value[1] = IS_UNMUTED(7, val); /* right */
return 0;
}
static int __pcm_playback_switch_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
return dac_gain_control_unmute(ucontrol->value.integer.value[0],
ucontrol->value.integer.value[1]);
}
static int __headset_playback_volume_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 100;
return 0;
}
static int __headset_playback_volume_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
u16 val;
u16 vol;
val = omap_tsc2101_audio_read(TSC2101_HEADSET_GAIN_CTRL);
M_DPRINTK("registry value = %d\n", val);
vol = HGC_ADPGA_HED_EXTRACT(val);
vol = vol & ~HGC_ADMUT_HED;
vol = get_headset_gain_control_volume_as_mixer_volume(vol);
ucontrol->value.integer.value[0] = vol;
M_DPRINTK("mixer volume returned = %ld\n",
ucontrol->value.integer.value[0]);
return 0;
}
static int __headset_playback_volume_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
return set_mixer_volume_as_headset_gain_control_volume(
ucontrol->value.integer.value[0]);
}
static int __headset_playback_switch_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 1;
return 0;
}
/*
* When HGC_ADMUT_HED (bit 15) is 1, the headset is muted.
* When HGC_ADMUT_HED is 0, headset is not muted.
*/
static int __headset_playback_switch_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
u16 val = omap_tsc2101_audio_read(TSC2101_HEADSET_GAIN_CTRL);
ucontrol->value.integer.value[0] = IS_UNMUTED(15, val);
return 0;
}
static int __headset_playback_switch_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
/* mute/unmute headset */
return adc_pga_unmute_control(ucontrol->value.integer.value[0],
TSC2101_HEADSET_GAIN_CTRL,
15);
}
static int __handset_playback_volume_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 100;
return 0;
}
static int __handset_playback_volume_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
u16 val;
u16 vol;
val = omap_tsc2101_audio_read(TSC2101_HANDSET_GAIN_CTRL);
M_DPRINTK("registry value = %d\n", val);
vol = HNGC_ADPGA_HND_EXTRACT(val);
vol = vol & ~HNGC_ADMUT_HND;
vol = get_handset_gain_control_volume_as_mixer_volume(vol);
ucontrol->value.integer.value[0] = vol;
M_DPRINTK("mixer volume returned = %ld\n",
ucontrol->value.integer.value[0]);
return 0;
}
static int __handset_playback_volume_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
return set_mixer_volume_as_handset_gain_control_volume(
ucontrol->value.integer.value[0]);
}
static int __handset_playback_switch_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 1;
return 0;
}
/*
* When HNGC_ADMUT_HND (bit 15) is 1, the handset is muted.
* When HNGC_ADMUT_HND is 0, handset is not muted.
*/
static int __handset_playback_switch_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
u16 val = omap_tsc2101_audio_read(TSC2101_HANDSET_GAIN_CTRL);
ucontrol->value.integer.value[0] = IS_UNMUTED(15, val);
return 0;
}
static int __handset_playback_switch_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
/* handset mute/unmute */
return adc_pga_unmute_control(ucontrol->value.integer.value[0],
TSC2101_HANDSET_GAIN_CTRL,
15);
}
static int __cellphone_input_switch_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 1;
return 0;
}
/*
* When BGC_MUT_CP (bit 15) = 1, power down cellphone input pga.
* When BGC_MUT_CP = 0, power up cellphone input pga.
*/
static int __cellphone_input_switch_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
u16 val = omap_tsc2101_audio_read(TSC2101_BUZZER_GAIN_CTRL);
ucontrol->value.integer.value[0] = IS_UNMUTED(15, val);
return 0;
}
static int __cellphone_input_switch_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
return adc_pga_unmute_control(ucontrol->value.integer.value[0],
TSC2101_BUZZER_GAIN_CTRL,
15);
}
static int __buzzer_input_switch_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 1;
return 0;
}
/*
* When BGC_MUT_BU (bit 6) = 1, power down cellphone input pga.
* When BGC_MUT_BU = 0, power up cellphone input pga.
*/
static int __buzzer_input_switch_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
u16 val = omap_tsc2101_audio_read(TSC2101_BUZZER_GAIN_CTRL);
ucontrol->value.integer.value[0] = IS_UNMUTED(6, val);
return 0;
}
static int __buzzer_input_switch_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
return adc_pga_unmute_control(ucontrol->value.integer.value[0],
TSC2101_BUZZER_GAIN_CTRL,
6);
}
static struct snd_kcontrol_new tsc2101_control[] __devinitdata = {
{
.name = "Target Playback Route",
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.index = 0,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.info = __pcm_playback_target_info,
.get = __pcm_playback_target_get,
.put = __pcm_playback_target_put,
}, {
.name = "Master Playback Volume",
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.index = 0,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.info = __pcm_playback_volume_info,
.get = __pcm_playback_volume_get,
.put = __pcm_playback_volume_put,
}, {
.name = "Master Playback Switch",
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.index = 0,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.info = __pcm_playback_switch_info,
.get = __pcm_playback_switch_get,
.put = __pcm_playback_switch_put,
}, {
.name = "Headset Playback Volume",
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.index = 0,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.info = __headset_playback_volume_info,
.get = __headset_playback_volume_get,
.put = __headset_playback_volume_put,
}, {
.name = "Headset Playback Switch",
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.index = 0,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.info = __headset_playback_switch_info,
.get = __headset_playback_switch_get,
.put = __headset_playback_switch_put,
}, {
.name = "Handset Playback Volume",
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.index = 0,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.info = __handset_playback_volume_info,
.get = __handset_playback_volume_get,
.put = __handset_playback_volume_put,
}, {
.name = "Handset Playback Switch",
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.index = 0,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.info = __handset_playback_switch_info,
.get = __handset_playback_switch_get,
.put = __handset_playback_switch_put,
}, {
.name = "Cellphone Input Switch",
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.index = 0,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.info = __cellphone_input_switch_info,
.get = __cellphone_input_switch_get,
.put = __cellphone_input_switch_put,
}, {
.name = "Buzzer Input Switch",
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.index = 0,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.info = __buzzer_input_switch_info,
.get = __buzzer_input_switch_get,
.put = __buzzer_input_switch_put,
}
};
#ifdef CONFIG_PM
void snd_omap_suspend_mixer(void)
{
}
void snd_omap_resume_mixer(void)
{
snd_omap_init_mixer();
}
#endif
int snd_omap_mixer(struct snd_card_omap_codec *tsc2101)
{
int i = 0;
int err = 0;
if (!tsc2101)
return -EINVAL;
for (i = 0; i < ARRAY_SIZE(tsc2101_control); i++) {
err = snd_ctl_add(tsc2101->card,
snd_ctl_new1(&tsc2101_control[i],
tsc2101->card));
if (err < 0)
return err;
}
return 0;
}
/*
* sound/arm/omap/omap-alsa-tsc2101-mixer.h
*
* Alsa Driver for TSC2101 codec for OMAP platform boards.
*
* Copyright (C) 2005 Mika Laitio <lamikr@cc.jyu.fi> and
* Everett Coleman II <gcc80x86@fuzzyneural.net>
*
* Based on the ideas in omap-aic23.c and sa11xx-uda1341.c
* Copyright (C) 2005 Instituto Nokia de Tecnologia - INdT - Manaus Brazil
* Copyright (C) 2002 Tomas Kasparek <tomas.kasparek@seznam.cz>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
* NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 675 Mass Ave, Cambridge, MA 02139, USA.
*
* History:
*
* 2006-03-01 Mika Laitio - Mixer for the tsc2101 driver used in omap boards.
* Can switch between headset and loudspeaker playback,
* mute and unmute dgc, set dgc volume. Record source switch,
* keyclick, buzzer and headset volume and handset volume control
* are still missing.
*/
#ifndef OMAPALSATSC2101MIXER_H_
#define OMAPALSATSC2101MIXER_H_
#include <asm/hardware/tsc2101.h>
#include "omap-alsa-dma.h"
/* tsc2101 DAC gain control volume specific */
#define OUTPUT_VOLUME_MIN 0x7F /* 1111111 = -63.5 DB */
#define OUTPUT_VOLUME_MAX 0x32 /* 110010 */
#define OUTPUT_VOLUME_RANGE (OUTPUT_VOLUME_MIN - OUTPUT_VOLUME_MAX)
/* use input vol of 75 for 0dB gain */
#define INPUT_VOLUME_MIN 0x0
#define INPUT_VOLUME_MAX 0x7D
#define INPUT_VOLUME_RANGE (INPUT_VOLUME_MAX - INPUT_VOLUME_MIN)
#define PLAYBACK_TARGET_COUNT 0x03
#define PLAYBACK_TARGET_LOUDSPEAKER 0x00
#define PLAYBACK_TARGET_HEADPHONE 0x01
#define PLAYBACK_TARGET_CELLPHONE 0x02
/*
* Following are used for register 03h Mixer PGA control bits D7-D5 for
* selecting record source
*/
#define REC_SRC_TARGET_COUNT 0x08
/* oss code referred to MIXER_LINE */
#define REC_SRC_SINGLE_ENDED_MICIN_HED 0x00
/* oss code referred to MIXER_MIC */
#define REC_SRC_SINGLE_ENDED_MICIN_HND 0x01
#define REC_SRC_SINGLE_ENDED_AUX1 0x02
#define REC_SRC_SINGLE_ENDED_AUX2 0x03
#define REC_SRC_MICIN_HED_AND_AUX1 0x04
#define REC_SRC_MICIN_HED_AND_AUX2 0x05
#define REC_SRC_MICIN_HND_AND_AUX1 0x06
#define REC_SRC_MICIN_HND_AND_AUX2 0x07
/* default output volume to dac dgc */
#define DEFAULT_OUTPUT_VOLUME 90
/* default record volume */
#define DEFAULT_INPUT_VOLUME 20
#define TSC2101_AUDIO_CODEC_REGISTERS_PAGE2 (2)
extern struct mcbsp_dev_info mcbsp_dev;
#endif /*OMAPALSATSC2101MIXER_H_*/
/*
* sound/arm/omap/omap-alsa-tsc2101.c
*
* Alsa codec Driver for TSC2101 chip for OMAP platform boards.
* Code obtained from oss omap drivers
*
* Copyright (C) 2004 Texas Instruments, Inc.
* Written by Nishanth Menon and Sriram Kannan
*
* Copyright (C) 2006 Instituto Nokia de Tecnologia - INdT - Manaus Brazil
* Alsa modularization by Daniel Petrini (d.pensator@gmail.com)
*
* Copyright (C) 2006 Mika Laitio <lamikr@cc.jyu.fi>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include <linux/delay.h>
#include <linux/soundcard.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/spi/tsc2101.h>
#include <linux/io.h>
#include <linux/slab.h>
#ifdef CONFIG_PM
#include <linux/pm.h>
#endif
#include <asm/mach-types.h>
#include <mach/dma.h>
#include <mach/clock.h>
#include <mach/mcbsp.h>
#include <asm/hardware/tsc2101.h>
#include <mach/omap-alsa.h>
#include "omap-alsa-tsc2101.h"
struct mcbsp_dev_info mcbsp_dev;
static struct clk *tsc2101_mclk;
/* #define DUMP_TSC2101_AUDIO_REGISTERS */
#undef DUMP_TSC2101_AUDIO_REGISTERS
/*
* Hardware capabilities
*/
/*
* DAC USB-mode sampling rates (MCLK = 12 MHz)
* The rates and rate_reg_into MUST be in the same order
*/
static unsigned int rates[] = {
7350, 8000, 8018, 8727,
8820, 9600, 11025, 12000,
14700, 16000, 22050, 24000,
29400, 32000, 44100, 48000,
};
static struct snd_pcm_hw_constraint_list tsc2101_hw_constraints_rates = {
.count = ARRAY_SIZE(rates),
.list = rates,
.mask = 0,
};
static const struct tsc2101_samplerate_reg_info
rate_reg_info[NUMBER_SAMPLE_RATES_SUPPORTED] = {
/* Div 6 */
{7350, 7, 1},
{8000, 7, 0},
/* Div 5.5 */
{8018, 6, 1},
{8727, 6, 0},
/* Div 5 */
{8820, 5, 1},
{9600, 5, 0},
/* Div 4 */
{11025, 4, 1},
{12000, 4, 0},
/* Div 3 */
{14700, 3, 1},
{16000, 3, 0},
/* Div 2 */
{22050, 2, 1},
{24000, 2, 0},
/* Div 1.5 */
{29400, 1, 1},
{32000, 1, 0},
/* Div 1 */
{44100, 0, 1},
{48000, 0, 0},
};
static struct snd_pcm_hardware tsc2101_snd_omap_alsa_playback = {
.info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID,
#ifdef CONFIG_MACH_OMAP_H6300
.formats = SNDRV_PCM_FMTBIT_S8,
#else
.formats = SNDRV_PCM_FMTBIT_S16_LE,
#endif
.rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |
SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 |
SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |
SNDRV_PCM_RATE_KNOT,
.rate_min = 7350,
.rate_max = 48000,
.channels_min = 2,
.channels_max = 2,
.buffer_bytes_max = 128 * 1024,
.period_bytes_min = 32,
.period_bytes_max = 8 * 1024,
.periods_min = 16,
.periods_max = 255,
.fifo_size = 0,
};
static struct snd_pcm_hardware tsc2101_snd_omap_alsa_capture = {
.info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
.rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |
SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 |
SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |
SNDRV_PCM_RATE_KNOT,
.rate_min = 7350,
.rate_max = 48000,
.channels_min = 2,
.channels_max = 2,
.buffer_bytes_max = 128 * 1024,
.period_bytes_min = 32,
.period_bytes_max = 8 * 1024,
.periods_min = 16,
.periods_max = 255,
.fifo_size = 0,
};
/*
* Simplified write for tsc2101 audio registers.
*/
inline void tsc2101_audio_write(u8 address, u16 data)
{
tsc2101_write_sync(mcbsp_dev.tsc2101_dev, PAGE2_AUDIO_CODEC_REGISTERS,
address, data);
}
/*
* Simplified read for tsc2101 audio registers.
*/
inline u16 tsc2101_audio_read(u8 address)
{
return (tsc2101_read_sync(mcbsp_dev.tsc2101_dev,
PAGE2_AUDIO_CODEC_REGISTERS, address));
}
#ifdef DUMP_TSC2101_AUDIO_REGISTERS
void dump_tsc2101_audio_reg(void)
{
dev_dbg(&mcbsp_dev.mcbsp_dev->dev,
"TSC2101_AUDIO_CTRL_1 = 0x%04x\n",
tsc2101_audio_read(TSC2101_AUDIO_CTRL_1));
dev_dbg(&mcbsp_dev.mcbsp_dev->dev,
"TSC2101_HEADSET_GAIN_CTRL = 0x%04x\n",
tsc2101_audio_read(TSC2101_HEADSET_GAIN_CTRL));
dev_dbg(&mcbsp_dev.mcbsp_dev->dev,
"TSC2101_DAC_GAIN_CTRL = 0x%04x\n",
tsc2101_audio_read(TSC2101_DAC_GAIN_CTRL));
dev_dbg(&mcbsp_dev.mcbsp_dev->dev,
"TSC2101_MIXER_PGA_CTRL = 0x%04x\n",
tsc2101_audio_read(TSC2101_MIXER_PGA_CTRL));
dev_dbg(&mcbsp_dev.mcbsp_dev->dev,
"TSC2101_AUDIO_CTRL_2 = 0x%04x\n",
tsc2101_audio_read(TSC2101_AUDIO_CTRL_2));
dev_dbg(&mcbsp_dev.mcbsp_dev->dev,
"TSC2101_CODEC_POWER_CTRL = 0x%04x\n",
tsc2101_audio_read(TSC2101_CODEC_POWER_CTRL));
dev_dbg(&mcbsp_dev.mcbsp_dev->dev,
"TSC2101_AUDIO_CTRL_3 = 0x%04x\n",
tsc2101_audio_read(TSC2101_AUDIO_CTRL_3));
dev_dbg(&mcbsp_dev.mcbsp_dev->dev,
"TSC2101_LCH_BASS_BOOST_N0 = 0x%04x\n",
tsc2101_audio_read(TSC2101_LCH_BASS_BOOST_N0));
dev_dbg(&mcbsp_dev.mcbsp_dev->dev,
"TSC2101_LCH_BASS_BOOST_N1 = 0x%04x\n",
tsc2101_audio_read(TSC2101_LCH_BASS_BOOST_N1));
dev_dbg(&mcbsp_dev.mcbsp_dev->dev,
"TSC2101_LCH_BASS_BOOST_N2 = 0x%04x\n",
tsc2101_audio_read(TSC2101_LCH_BASS_BOOST_N2));
dev_dbg(&mcbsp_dev.mcbsp_dev->dev,
"TSC2101_LCH_BASS_BOOST_N3 = 0x%04x\n",
tsc2101_audio_read(TSC2101_LCH_BASS_BOOST_N3));
dev_dbg(&mcbsp_dev.mcbsp_dev->dev,
"TSC2101_LCH_BASS_BOOST_N4 = 0x%04x\n",
tsc2101_audio_read(TSC2101_LCH_BASS_BOOST_N4));
dev_dbg(&mcbsp_dev.mcbsp_dev->dev,
"TSC2101_LCH_BASS_BOOST_N5 = 0x%04x\n",
tsc2101_audio_read(TSC2101_LCH_BASS_BOOST_N5));
dev_dbg(&mcbsp_dev.mcbsp_dev->dev,
"TSC2101_LCH_BASS_BOOST_D1 = 0x%04x\n",
tsc2101_audio_read(TSC2101_LCH_BASS_BOOST_D1));
dev_dbg(&mcbsp_dev.mcbsp_dev->dev,
"TSC2101_LCH_BASS_BOOST_D2 = 0x%04x\n",
tsc2101_audio_read(TSC2101_LCH_BASS_BOOST_D2));
dev_dbg(&mcbsp_dev.mcbsp_dev->dev,
"TSC2101_LCH_BASS_BOOST_D4 = 0x%04x\n",
tsc2101_audio_read(TSC2101_LCH_BASS_BOOST_D4));
dev_dbg(&mcbsp_dev.mcbsp_dev->dev,
"TSC2101_LCH_BASS_BOOST_D5 = 0x%04x\n",
tsc2101_audio_read(TSC2101_LCH_BASS_BOOST_D5));
dev_dbg(&mcbsp_dev.mcbsp_dev->dev,
"TSC2101_RCH_BASS_BOOST_N0 = 0x%04x\n",
tsc2101_audio_read(TSC2101_RCH_BASS_BOOST_N0));
dev_dbg(&mcbsp_dev.mcbsp_dev->dev,
"TSC2101_RCH_BASS_BOOST_N1 = 0x%04x\n",
tsc2101_audio_read(TSC2101_RCH_BASS_BOOST_N1));
dev_dbg(&mcbsp_dev.mcbsp_dev->dev,
"TSC2101_RCH_BASS_BOOST_N2 = 0x%04x\n",
tsc2101_audio_read(TSC2101_RCH_BASS_BOOST_N2));
dev_dbg(&mcbsp_dev.mcbsp_dev->dev,
"TSC2101_RCH_BASS_BOOST_N3 = 0x%04x\n",
tsc2101_audio_read(TSC2101_RCH_BASS_BOOST_N3));
dev_dbg(&mcbsp_dev.mcbsp_dev->dev,
"TSC2101_RCH_BASS_BOOST_N4 = 0x%04x\n",
tsc2101_audio_read(TSC2101_RCH_BASS_BOOST_N4));
dev_dbg(&mcbsp_dev.mcbsp_dev->dev,
"TSC2101_RCH_BASS_BOOST_N5 = 0x%04x\n",
tsc2101_audio_read(TSC2101_RCH_BASS_BOOST_N5));
dev_dbg(&mcbsp_dev.mcbsp_dev->dev,
"TSC2101_RCH_BASS_BOOST_D1 = 0x%04x\n",
tsc2101_audio_read(TSC2101_RCH_BASS_BOOST_D1));
dev_dbg(&mcbsp_dev.mcbsp_dev->dev,
"TSC2101_RCH_BASS_BOOST_D2 = 0x%04x\n",
tsc2101_audio_read(TSC2101_RCH_BASS_BOOST_D2));
dev_dbg(&mcbsp_dev.mcbsp_dev->dev,
"TSC2101_RCH_BASS_BOOST_D4 = 0x%04x\n",
tsc2101_audio_read(TSC2101_RCH_BASS_BOOST_D4));
dev_dbg(&mcbsp_dev.mcbsp_dev->dev,
"TSC2101_RCH_BASS_BOOST_D5 = 0x%04x\n",
tsc2101_audio_read(TSC2101_RCH_BASS_BOOST_D5));
dev_dbg(&mcbsp_dev.mcbsp_dev->dev,
"TSC2101_PLL_PROG_1 = 0x%04x\n",
tsc2101_audio_read(TSC2101_PLL_PROG_1));
dev_dbg(&mcbsp_dev.mcbsp_dev->dev,
"TSC2101_PLL_PROG_1 = 0x%04x\n",
tsc2101_audio_read(TSC2101_PLL_PROG_2));
dev_dbg(&mcbsp_dev.mcbsp_dev->dev,
"TSC2101_AUDIO_CTRL_4 = 0x%04x\n",
tsc2101_audio_read(TSC2101_AUDIO_CTRL_4));
dev_dbg(&mcbsp_dev.mcbsp_dev->dev,
"TSC2101_HANDSET_GAIN_CTRL = 0x%04x\n",
tsc2101_audio_read(TSC2101_HANDSET_GAIN_CTRL));
dev_dbg(&mcbsp_dev.mcbsp_dev->dev,
"TSC2101_BUZZER_GAIN_CTRL = 0x%04x\n",
tsc2101_audio_read(TSC2101_BUZZER_GAIN_CTRL));
dev_dbg(&mcbsp_dev.mcbsp_dev->dev,
"TSC2101_AUDIO_CTRL_5 = 0x%04x\n",
tsc2101_audio_read(TSC2101_AUDIO_CTRL_5));
dev_dbg(&mcbsp_dev.mcbsp_dev->dev,
"TSC2101_AUDIO_CTRL_6 = 0x%04x\n",
tsc2101_audio_read(TSC2101_AUDIO_CTRL_6));
dev_dbg(&mcbsp_dev.mcbsp_dev->dev,
"TSC2101_AUDIO_CTRL_7 = 0x%04x\n",
tsc2101_audio_read(TSC2101_AUDIO_CTRL_7));
dev_dbg(&mcbsp_dev.mcbsp_dev->dev,
"TSC2101_GPIO_CTRL = 0x%04x\n",
tsc2101_audio_read(TSC2101_GPIO_CTRL));
dev_dbg(&mcbsp_dev.mcbsp_dev->dev,
"TSC2101_AGC_CTRL = 0x%04x\n",
tsc2101_audio_read(TSC2101_AGC_CTRL));
dev_dbg(&mcbsp_dev.mcbsp_dev->dev,
"TSC2101_POWERDOWN_STS = 0x%04x\n",
tsc2101_audio_read(TSC2101_POWERDOWN_STS));
dev_dbg(&mcbsp_dev.mcbsp_dev->dev,
"TSC2101_MIC_AGC_CONTROL = 0x%04x\n",
tsc2101_audio_read(TSC2101_MIC_AGC_CONTROL));
dev_dbg(&mcbsp_dev.mcbsp_dev->dev,
"TSC2101_CELL_AGC_CONTROL = 0x%04x\n",
tsc2101_audio_read(TSC2101_CELL_AGC_CONTROL));
}
#endif
/*
* ALSA operations according to board file
*/
/*
* Sample rate changing
*/
void tsc2101_set_samplerate(long sample_rate)
{
u8 count = 0;
u16 data = 0;
int clkgdv = 0;
u16 srgr1, srgr2;
/* wait for any frame to complete */
udelay(125);
ADEBUG();
sample_rate = sample_rate;
/* Search for the right sample rate */
while ((rate_reg_info[count].sample_rate != sample_rate) &&
(count < NUMBER_SAMPLE_RATES_SUPPORTED)) {
count++;
}
if (count == NUMBER_SAMPLE_RATES_SUPPORTED) {
printk(KERN_ERR "Invalid Sample Rate %d requested\n",
(int) sample_rate);
return;
}
/* Set AC1 */
data = tsc2101_audio_read(TSC2101_AUDIO_CTRL_1);
/* Clear prev settings */
data &= ~(AC1_DACFS(0x07) | AC1_ADCFS(0x07));
data |= AC1_DACFS(rate_reg_info[count].divisor) |
AC1_ADCFS(rate_reg_info[count].divisor);
tsc2101_audio_write(TSC2101_AUDIO_CTRL_1, data);
/* Set the AC3 */
data = tsc2101_audio_read(TSC2101_AUDIO_CTRL_3);
/*Clear prev settings */
data &= ~(AC3_REFFS | AC3_SLVMS);
data |= (rate_reg_info[count].fs_44kHz) ? AC3_REFFS : 0;
#ifdef TSC_MASTER
data |= AC3_SLVMS;
#endif /* #ifdef TSC_MASTER */
tsc2101_audio_write(TSC2101_AUDIO_CTRL_3, data);
/*
* Program the PLLs. This code assumes that the 12 Mhz MCLK is in use.
* If MCLK rate is something else, these values must be changed.
* See the tsc2101 specification for the details.
*/
if (rate_reg_info[count].fs_44kHz) {
/* samplerate = (44.1kHZ / x), where x is int. */
tsc2101_audio_write(TSC2101_PLL_PROG_1, PLL1_PLLSEL |
/* PVAL 1; I_VAL 7 */
PLL1_PVAL(1) | PLL1_I_VAL(7));
/* D_VAL 5264 */
tsc2101_audio_write(TSC2101_PLL_PROG_2, PLL2_D_VAL(0x1490));
} else {
/* samplerate = (48.kHZ / x), where x is int. */
tsc2101_audio_write(TSC2101_PLL_PROG_1, PLL1_PLLSEL |
/* PVAL 1; I_VAL 8 */
PLL1_PVAL(1) | PLL1_I_VAL(8));
/* D_VAL 1920 */
tsc2101_audio_write(TSC2101_PLL_PROG_2, PLL2_D_VAL(0x780));
}
/* Set the sample rate */
#ifndef TSC_MASTER
clkgdv = CODEC_CLOCK / (sample_rate * (DEFAULT_BITPERSAMPLE * 2 - 1));
if (clkgdv)
srgr1 = (FWID(DEFAULT_BITPERSAMPLE - 1) | CLKGDV(clkgdv));
else
return (1);
/* Stereo Mode */
srgr2 = (CLKSM | FSGM | FPER(DEFAULT_BITPERSAMPLE * 2 - 1));
#else
srgr1 = (FWID(DEFAULT_BITPERSAMPLE - 1) | CLKGDV(clkgdv));
srgr2 = ((GSYNC | CLKSP | FSGM | FPER(DEFAULT_BITPERSAMPLE * 2 - 1)));
#endif /* end of #ifdef TSC_MASTER */
OMAP_MCBSP_WRITE(OMAP1610_MCBSP1_BASE, SRGR2, srgr2);
OMAP_MCBSP_WRITE(OMAP1610_MCBSP1_BASE, SRGR1, srgr1);
}
void tsc2101_configure(void)
{
}
/*
* Omap MCBSP clock and Power Management configuration
*
* Here we have some functions that allows clock to be enabled and
* disabled only when needed. Besides doing clock configuration
* it allows turn on/turn off audio when necessary.
*/
/*
* Do clock framework mclk search
*/
void tsc2101_clock_setup(void)
{
tsc2101_mclk = clk_get(0, "mclk");
}
/*
* Do some sanity check, set clock rate, starts it and turn codec audio on
*/
int tsc2101_clock_on(void)
{
int curUseCount;
uint curRate;
int err;
curUseCount = clk_get_usecount(tsc2101_mclk);
DPRINTK("clock use count = %d\n", curUseCount);
if (curUseCount > 0) {
/* MCLK is already in use */
printk(KERN_WARNING
"MCLK already in use at %d Hz. We change it to %d Hz\n",
(uint) clk_get_rate(tsc2101_mclk),
CODEC_CLOCK);
}
curRate = (uint)clk_get_rate(tsc2101_mclk);
if (curRate != CODEC_CLOCK) {
err = clk_set_rate(tsc2101_mclk, CODEC_CLOCK);
if (err) {
printk(KERN_WARNING "Cannot set MCLK clock rate for "
"TSC2101 CODEC, error code = %d\n", err);
return -ECANCELED;
}
}
err = clk_enable(tsc2101_mclk);
curRate = (uint)clk_get_rate(tsc2101_mclk);
curUseCount = clk_get_usecount(tsc2101_mclk);
DPRINTK("MCLK = %d [%d], usecount = %d, clk_enable retval = %d\n",
curRate,
CODEC_CLOCK,
curUseCount,
err);
/* Now turn the audio on */
tsc2101_write_sync(mcbsp_dev.tsc2101_dev, PAGE2_AUDIO_CODEC_REGISTERS,
TSC2101_CODEC_POWER_CTRL,
0x0000);
return 0;
}
/*
* Do some sanity check, turn clock off and then turn codec audio off
*/
int tsc2101_clock_off(void)
{
int curUseCount;
int curRate;
curUseCount = clk_get_usecount(tsc2101_mclk);
DPRINTK("clock use count = %d\n", curUseCount);
if (curUseCount > 0) {
curRate = clk_get_rate(tsc2101_mclk);
DPRINTK("clock rate = %d\n", curRate);
if (curRate != CODEC_CLOCK) {
printk(KERN_WARNING
"MCLK for audio should be %d Hz. But is %d Hz\n",
(uint) clk_get_rate(tsc2101_mclk),
CODEC_CLOCK);
}
clk_disable(tsc2101_mclk);
DPRINTK("clock disabled\n");
}
tsc2101_audio_write(TSC2101_CODEC_POWER_CTRL,
~(CPC_SP1PWDN | CPC_SP2PWDN | CPC_BASSBC));
DPRINTK("audio codec off\n");
return 0;
}
int tsc2101_get_default_samplerate(void)
{
return DEFAULT_SAMPLE_RATE;
}
static int __devinit snd_omap_alsa_tsc2101_probe(struct platform_device *pdev)
{
struct spi_device *tsc2101;
int ret;
struct omap_alsa_codec_config *codec_cfg;
tsc2101 = dev_get_drvdata(&pdev->dev);
if (tsc2101 == NULL) {
dev_err(&pdev->dev, "no platform data\n");
return -ENODEV;
}
if (strncmp(tsc2101->modalias, "tsc2101", 8) != 0) {
dev_err(&pdev->dev, "tsc2101 not found\n");
return -EINVAL;
}
mcbsp_dev.mcbsp_dev = pdev;
mcbsp_dev.tsc2101_dev = tsc2101;
codec_cfg = pdev->dev.platform_data;
if (codec_cfg != NULL) {
codec_cfg->hw_constraints_rates =
&tsc2101_hw_constraints_rates;
codec_cfg->snd_omap_alsa_playback =
&tsc2101_snd_omap_alsa_playback;
codec_cfg->snd_omap_alsa_capture =
&tsc2101_snd_omap_alsa_capture;
codec_cfg->codec_configure_dev = tsc2101_configure;
codec_cfg->codec_set_samplerate = tsc2101_set_samplerate;
codec_cfg->codec_clock_setup = tsc2101_clock_setup;
codec_cfg->codec_clock_on = tsc2101_clock_on;
codec_cfg->codec_clock_off = tsc2101_clock_off;
codec_cfg->get_default_samplerate =
tsc2101_get_default_samplerate;
ret = snd_omap_alsa_post_probe(pdev, codec_cfg);
} else
ret = -ENODEV;
return ret;
}
static struct platform_driver omap_alsa_driver = {
.probe = snd_omap_alsa_tsc2101_probe,
.remove = snd_omap_alsa_remove,
.suspend = snd_omap_alsa_suspend,
.resume = snd_omap_alsa_resume,
.driver = {
.name = "omap_alsa_mcbsp",
},
};
static int __init omap_alsa_tsc2101_init(void)
{
ADEBUG();
#ifdef DUMP_TSC2101_AUDIO_REGISTERS
printk(KERN_INFO "omap_alsa_tsc2101_init()\n");
dump_tsc2101_audio_reg();
#endif
return platform_driver_register(&omap_alsa_driver);
}
static void __exit omap_alsa_tsc2101_exit(void)
{
ADEBUG();
#ifdef DUMP_TSC2101_AUDIO_REGISTERS
printk(KERN_INFO "omap_alsa_tsc2101_exit()\n");
dump_tsc2101_audio_reg();
#endif
platform_driver_unregister(&omap_alsa_driver);
}
module_init(omap_alsa_tsc2101_init);
module_exit(omap_alsa_tsc2101_exit);
/*
* sound/arm/omap/omap-alsa-tsc2101.h
*
* Alsa Driver for TSC2101 codec for OMAP platform boards.
*
* Based on former omap-aic23.h and tsc2101 OSS drivers.
* Copyright (C) 2004 Texas Instruments, Inc.
* Written by Nishanth Menon and Sriram Kannan
*
* Copyright (C) 2006 Instituto Nokia de Tecnologia - INdT - Manaus Brazil
* Alsa modularization by Daniel Petrini (d.pensator@gmail.com)
*
* Copyright (C) 2006 Mika Laitio <lamikr@cc.jyu.fi>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#ifndef OMAP_ALSA_TSC2101_H_
#define OMAP_ALSA_TSC2101_H_
#include <linux/types.h>
/* Define to set the tsc as the master w.r.t McBSP or EAC */
#define TSC_MASTER
#define NUMBER_SAMPLE_RATES_SUPPORTED 16
/*
* AUDIO related MACROS
*/
#ifndef DEFAULT_BITPERSAMPLE
#define DEFAULT_BITPERSAMPLE 16
#endif
#define DEFAULT_SAMPLE_RATE 44100
/* FIXME codec clock rate is board-specific */
#define CODEC_CLOCK 12000000
#define PAGE2_AUDIO_CODEC_REGISTERS (2)
struct mcbsp_dev_info {
struct platform_device *mcbsp_dev;
struct spi_device *tsc2101_dev;
};
struct tsc2101_samplerate_reg_info {
u16 sample_rate;
u8 divisor;
u8 fs_44kHz; /* if 0 48 khz, if 1 44.1 khz fsref */
};
/*
* Defines codec specific function pointers that can be used from the
* common omap-alse base driver for all omap codecs. (tsc2101 and aic23)
*/
inline void tsc2101_configure(void);
void tsc2101_set_samplerate(long rate);
void tsc2101_clock_setup(void);
int tsc2101_clock_on(void);
int tsc2101_clock_off(void);
int tsc2101_get_default_samplerate(void);
#endif /*OMAP_ALSA_TSC2101_H_*/
/*
* sound/arm/omap/omap-alsa-tsc2102-mixer.c
*
* Alsa mixer driver for TSC2102 chip for OMAP platforms.
*
* Copyright (c) 2006 Andrzej Zaborowski <balrog@zabor.org>
* Code based on the TSC2101 ALSA driver.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
* NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <linux/types.h>
#include <linux/spi/tsc2102.h>
#include <mach/omap-alsa.h>
#include <sound/initval.h>
#include <sound/control.h>
#include "omap-alsa-tsc2102.h"
#include "omap-alsa-dma.h"
static int vol[2], mute[2], filter[2];
/*
* Converts the Alsa mixer volume (0 - 100) to actual Digital
* Gain Control (DGC) value that can be written or read from the
* TSC2102 registers.
*
* Note that the number "OUTPUT_VOLUME_MAX" is smaller than
* OUTPUT_VOLUME_MIN because DGC works as a volume decreaser. (The
* higher the value sent to DAC, the more the volume of controlled
* channel is decreased)
*/
static void set_dac_gain_stereo(int left_ch, int right_ch)
{
int lch, rch;
if (left_ch > 100)
vol[0] = 100;
else if (left_ch < 0)
vol[0] = 0;
else
vol[0] = left_ch;
lch = OUTPUT_VOLUME_MIN - vol[0] *
(OUTPUT_VOLUME_MIN - OUTPUT_VOLUME_MAX) / 100;
if (right_ch > 100)
vol[1] = 100;
else if (right_ch < 0)
vol[1] = 0;
else
vol[1] = right_ch;
rch = OUTPUT_VOLUME_MIN - vol[1] *
(OUTPUT_VOLUME_MIN - OUTPUT_VOLUME_MAX) / 100;
tsc2102_set_volume(lch, rch);
}
void init_playback_targets(void)
{
set_dac_gain_stereo(DEFAULT_OUTPUT_VOLUME, DEFAULT_OUTPUT_VOLUME);
/* Unmute */
tsc2102_set_mute(0, 0);
mute[0] = 0;
mute[1] = 0;
filter[0] = 0;
filter[1] = 0;
}
/*
* Initializes TSC 2102 and playback target.
*/
void snd_omap_init_mixer(void)
{
FN_IN;
init_playback_targets();
FN_OUT(0);
}
static int __pcm_playback_volume_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 2;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 100;
return 0;
}
static int __pcm_playback_volume_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
ucontrol->value.integer.value[0] = vol[0]; /* L */
ucontrol->value.integer.value[1] = vol[1]; /* R */
return 0;
}
static int __pcm_playback_volume_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
set_dac_gain_stereo(
ucontrol->value.integer.value[0], /* L */
ucontrol->value.integer.value[1]); /* R */
return 1;
}
static int __pcm_playback_switch_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
uinfo->count = 2;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 1;
return 0;
}
static int __pcm_playback_switch_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
ucontrol->value.integer.value[0] = !mute[0]; /* L */
ucontrol->value.integer.value[1] = !mute[1]; /* R */
return 0;
}
static int __pcm_playback_switch_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
mute[0] = (ucontrol->value.integer.value[0] == 0); /* L */
mute[1] = (ucontrol->value.integer.value[1] == 0); /* R */
tsc2102_set_mute(mute[0], mute[1]);
return 1;
}
static int __pcm_playback_deemphasis_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 1;
return 0;
}
static int __pcm_playback_deemphasis_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
ucontrol->value.integer.value[0] = filter[0];
return 0;
}
static int __pcm_playback_deemphasis_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
filter[0] = (ucontrol->value.integer.value[0] > 0);
tsc2102_set_deemphasis(filter[0]);
return 1;
}
static int __pcm_playback_bassboost_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 1;
return 0;
}
static int __pcm_playback_bassboost_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
ucontrol->value.integer.value[0] = filter[1];
return 0;
}
static int __pcm_playback_bassboost_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
filter[1] = (ucontrol->value.integer.value[0] > 0);
tsc2102_set_bassboost(filter[1]);
return 1;
}
static struct snd_kcontrol_new tsc2102_controls[] __devinitdata = {
{
.name = "Master Playback Volume",
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.index = 0,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.info = __pcm_playback_volume_info,
.get = __pcm_playback_volume_get,
.put = __pcm_playback_volume_put,
},
{
.name = "Master Playback Switch",
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.index = 0,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.info = __pcm_playback_switch_info,
.get = __pcm_playback_switch_get,
.put = __pcm_playback_switch_put,
},
{
.name = "De-emphasis Filter Switch",
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.index = 0,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.info = __pcm_playback_deemphasis_info,
.get = __pcm_playback_deemphasis_get,
.put = __pcm_playback_deemphasis_put,
},
{
.name = "Bass-boost Filter Switch",
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.index = 0,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.info = __pcm_playback_bassboost_info,
.get = __pcm_playback_bassboost_get,
.put = __pcm_playback_bassboost_put,
},
};
#ifdef CONFIG_PM
void snd_omap_suspend_mixer(void)
{
/* Nothing to do */
}
void snd_omap_resume_mixer(void)
{
/* The chip was reset, restore the last used values */
set_dac_gain_stereo(vol[0], vol[1]);
tsc2102_set_mute(mute[0], mute[1]);
tsc2102_set_deemphasis(filter[0]);
tsc2102_set_bassboost(filter[1]);
}
#endif
int snd_omap_mixer(struct snd_card_omap_codec *tsc2102)
{
int i, err;
if (!tsc2102)
return -EINVAL;
for (i = 0; i < ARRAY_SIZE(tsc2102_controls); i++) {
err = snd_ctl_add(tsc2102->card,
snd_ctl_new1(&tsc2102_controls[i],
tsc2102->card));
if (err < 0)
return err;
}
return 0;
}
/*
* sound/arm/omap/omap-alsa-tsc2102.c
*
* Alsa codec driver for TSC2102 chip for OMAP platforms.
*
* Copyright (c) 2006 Andrzej Zaborowski <balrog@zabor.org>
* Code based on the TSC2101 ALSA driver.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include <linux/delay.h>
#include <linux/soundcard.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/module.h>
#include <linux/io.h>
#include <linux/spi/tsc2102.h>
#include <mach/dma.h>
#include <mach/clock.h>
#include <mach/omap-alsa.h>
#include "omap-alsa-tsc2102.h"
static struct clk *tsc2102_bclk;
/*
* Hardware capabilities
*/
/* DAC sampling rates (BCLK = 12 MHz) */
static unsigned int rates[] = {
7350, 8000, 8820, 9600, 11025, 12000, 14700,
16000, 22050, 24000, 29400, 32000, 44100, 48000,
};
static struct snd_pcm_hw_constraint_list tsc2102_hw_constraints_rates = {
.count = ARRAY_SIZE(rates),
.list = rates,
.mask = 0,
};
static struct snd_pcm_hardware tsc2102_snd_omap_alsa_playback = {
.info = SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
.rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |
SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |
SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |
SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_KNOT,
.rate_min = 7350,
.rate_max = 48000,
.channels_min = 2,
.channels_max = 2,
.buffer_bytes_max = 128 * 1024,
.period_bytes_min = 32,
.period_bytes_max = 8 * 1024,
.periods_min = 16,
.periods_max = 255,
.fifo_size = 0,
};
#ifdef DUMP_TSC2102_AUDIO_REGISTERS
static void dump_tsc2102_audio_regs(void)
{
printk(KERN_INFO "TSC2102_AUDIO1_CTRL = 0x%04x\n",
tsc2102_read_sync(TSC2102_AUDIO1_CTRL));
printk(KERN_INFO "TSC2102_DAC_GAIN_CTRL = 0x%04x\n",
tsc2102_read_sync(TSC2102_DAC_GAIN_CTRL));
printk(KERN_INFO "TSC2102_AUDIO2_CTRL = 0x%04x\n",
tsc2102_read_sync(TSC2102_AUDIO2_CTRL));
printk(KERN_INFO "TSC2102_DAC_POWER_CTRL = 0x%04x\n",
tsc2102_read_sync(TSC2102_DAC_POWER_CTRL));
printk(KERN_INFO "TSC2102_AUDIO3_CTRL = 0x%04x\n",
tsc2102_read_sync(TSC2102_AUDIO_CTRL_3));
printk(KERN_INFO "TSC2102_LCH_BASS_BOOST_N0 = 0x%04x\n",
tsc2102_read_sync(TSC2102_LCH_BASS_BOOST_N0));
printk(KERN_INFO "TSC2102_LCH_BASS_BOOST_N1 = 0x%04x\n",
tsc2102_read_sync(TSC2102_LCH_BASS_BOOST_N1));
printk(KERN_INFO "TSC2102_LCH_BASS_BOOST_N2 = 0x%04x\n",
tsc2102_read_sync(TSC2102_LCH_BASS_BOOST_N2));
printk(KERN_INFO "TSC2102_LCH_BASS_BOOST_N3 = 0x%04x\n",
tsc2102_read_sync(TSC2102_LCH_BASS_BOOST_N3));
printk(KERN_INFO "TSC2102_LCH_BASS_BOOST_N4 = 0x%04x\n",
tsc2102_read_sync(TSC2102_LCH_BASS_BOOST_N4));
printk(KERN_INFO "TSC2102_LCH_BASS_BOOST_N5 = 0x%04x\n",
tsc2102_read_sync(TSC2102_LCH_BASS_BOOST_N5));
printk(KERN_INFO "TSC2102_LCH_BASS_BOOST_D1 = 0x%04x\n",
tsc2102_read_sync(TSC2102_LCH_BASS_BOOST_D1));
printk(KERN_INFO "TSC2102_LCH_BASS_BOOST_D2 = 0x%04x\n",
tsc2102_read_sync(TSC2102_LCH_BASS_BOOST_D2));
printk(KERN_INFO "TSC2102_LCH_BASS_BOOST_D4 = 0x%04x\n",
tsc2102_read_sync(TSC2102_LCH_BASS_BOOST_D4));
printk(KERN_INFO "TSC2102_LCH_BASS_BOOST_D5 = 0x%04x\n",
tsc2102_read_sync(TSC2102_LCH_BASS_BOOST_D5));
printk(KERN_INFO "TSC2102_RCH_BASS_BOOST_N0 = 0x%04x\n",
tsc2102_read_sync(TSC2102_RCH_BASS_BOOST_N0));
printk(KERN_INFO "TSC2102_RCH_BASS_BOOST_N1 = 0x%04x\n",
tsc2102_read_sync(TSC2102_RCH_BASS_BOOST_N1));
printk(KERN_INFO "TSC2102_RCH_BASS_BOOST_N2 = 0x%04x\n",
tsc2102_read_sync(TSC2102_RCH_BASS_BOOST_N2));
printk(KERN_INFO "TSC2102_RCH_BASS_BOOST_N3 = 0x%04x\n",
tsc2102_read_sync(TSC2102_RCH_BASS_BOOST_N3));
printk(KERN_INFO "TSC2102_RCH_BASS_BOOST_N4 = 0x%04x\n",
tsc2102_read_sync(TSC2102_RCH_BASS_BOOST_N4));
printk(KERN_INFO "TSC2102_RCH_BASS_BOOST_N5 = 0x%04x\n",
tsc2102_read_sync(TSC2102_RCH_BASS_BOOST_N5));
printk(KERN_INFO "TSC2102_RCH_BASS_BOOST_D1 = 0x%04x\n",
tsc2102_read_sync(TSC2102_RCH_BASS_BOOST_D1));
printk(KERN_INFO "TSC2102_RCH_BASS_BOOST_D2 = 0x%04x\n",
tsc2102_read_sync(TSC2102_RCH_BASS_BOOST_D2));
printk(KERN_INFO "TSC2102_RCH_BASS_BOOST_D4 = 0x%04x\n",
tsc2102_read_sync(TSC2102_RCH_BASS_BOOST_D4));
printk(KERN_INFO "TSC2102_RCH_BASS_BOOST_D5 = 0x%04x\n",
tsc2102_read_sync(TSC2102_RCH_BASS_BOOST_D5));
printk(KERN_INFO "TSC2102_PLL1_CTRL = 0x%04x\n",
tsc2102_read_sync(TSC2102_PLL1_CTRL));
printk(KERN_INFO "TSC2102_PLL2_CTRL = 0x%04x\n",
tsc2102_read_sync(TSC2102_PLL2_CTRL));
printk(KERN_INFO "TSC2102_AUDIO4_CTRL = 0x%04x\n",
tsc2102_read_sync(TSC2102_AUDIO4_CTRL));
}
#endif
/*
* ALSA operations according to board file
*/
static long current_rate;
/*
* Sample rate changing
*/
static void tsc2102_set_samplerate(long sample_rate)
{
int clkgdv = 0;
u16 srgr1, srgr2;
if (sample_rate == current_rate)
return;
current_rate = 0;
if (tsc2102_set_rate(sample_rate))
return;
/* Set the sample rate */
#ifndef TSC_MASTER
clkgdv = CODEC_CLOCK / (sample_rate * (DEFAULT_BITPERSAMPLE * 2 - 1));
if (clkgdv)
srgr1 = (FWID(DEFAULT_BITPERSAMPLE - 1) | CLKGDV(clkgdv));
else
return;
/* Stereo Mode */
srgr2 = CLKSM | FSGM | FPER(DEFAULT_BITPERSAMPLE * 2 - 1);
#else
srgr1 = FWID(DEFAULT_BITPERSAMPLE - 1) | CLKGDV(clkgdv);
srgr2 = GSYNC | CLKSP | FSGM | FPER(DEFAULT_BITPERSAMPLE * 2 - 1);
#endif
OMAP_MCBSP_WRITE(OMAP1510_MCBSP1_BASE, SRGR2, srgr2);
OMAP_MCBSP_WRITE(OMAP1510_MCBSP1_BASE, SRGR1, srgr1);
current_rate = sample_rate;
}
static void tsc2102_configure(void)
{
tsc2102_dac_power(1);
#ifdef TSC_MASTER
tsc2102_set_i2s_master(1);
#else
tsc2102_set_i2s_master(0);
#endif
}
/*
* Omap McBSP clock and Power Management configuration
*
* Here we have some functions that allow clock to be enabled and
* disabled only when needed. Besides doing clock configuration
* they allow turn audio on and off when necessary.
*/
/*
* Do clock framework bclk search
*/
static void tsc2102_clock_setup(void)
{
tsc2102_bclk = clk_get(0, "bclk");
}
/*
* Do some sanity checks, set clock rate, start it.
*/
static int tsc2102_clock_on(void)
{
int err;
if (clk_get_usecount(tsc2102_bclk) > 0 &&
clk_get_rate(tsc2102_bclk) != CODEC_CLOCK) {
/* BCLK is already in use */
printk(KERN_WARNING
"BCLK already in use at %d Hz. We change it to %d Hz\n",
(uint) clk_get_rate(tsc2102_bclk), CODEC_CLOCK);
err = clk_set_rate(tsc2102_bclk, CODEC_CLOCK);
if (err)
printk(KERN_WARNING "Cannot set BCLK clock rate "
"for TSC2102 codec, error code = %d\n", err);
}
clk_enable(tsc2102_bclk);
return 0;
}
/*
* Turn off the audio codec and then stop the clock.
*/
static int tsc2102_clock_off(void)
{
DPRINTK("clock use count = %d\n", clk_get_usecount(tsc2102_bclk));
clk_disable(tsc2102_bclk);
return 0;
}
static int tsc2102_get_default_samplerate(void)
{
return DEFAULT_SAMPLE_RATE;
}
static int snd_omap_alsa_tsc2102_suspend(
struct platform_device *pdev, pm_message_t state)
{
tsc2102_dac_power(0);
current_rate = 0;
return snd_omap_alsa_suspend(pdev, state);
}
static int snd_omap_alsa_tsc2102_resume(struct platform_device *pdev)
{
tsc2102_dac_power(1);
#ifdef TSC_MASTER
tsc2102_set_i2s_master(1);
#else
tsc2102_set_i2s_master(0);
#endif
return snd_omap_alsa_resume(pdev);
}
static int __init snd_omap_alsa_tsc2102_probe(struct platform_device *pdev)
{
int ret;
struct omap_alsa_codec_config *codec_cfg = pdev->dev.platform_data;
if (codec_cfg) {
codec_cfg->hw_constraints_rates =
&tsc2102_hw_constraints_rates;
codec_cfg->snd_omap_alsa_playback =
&tsc2102_snd_omap_alsa_playback;
codec_cfg->codec_configure_dev = tsc2102_configure;
codec_cfg->codec_set_samplerate = tsc2102_set_samplerate;
codec_cfg->codec_clock_setup = tsc2102_clock_setup;
codec_cfg->codec_clock_on = tsc2102_clock_on;
codec_cfg->codec_clock_off = tsc2102_clock_off;
codec_cfg->get_default_samplerate =
tsc2102_get_default_samplerate;
ret = snd_omap_alsa_post_probe(pdev, codec_cfg);
} else
ret = -ENODEV;
return ret;
}
static int snd_omap_alsa_tsc2102_remove(struct platform_device *pdev)
{
tsc2102_dac_power(0);
return snd_omap_alsa_remove(pdev);
}
static struct platform_driver omap_alsa_driver = {
.probe = snd_omap_alsa_tsc2102_probe,
.remove = snd_omap_alsa_tsc2102_remove,
.suspend = snd_omap_alsa_tsc2102_suspend,
.resume = snd_omap_alsa_tsc2102_resume,
.driver = {
.name = "tsc2102-alsa",
.owner = THIS_MODULE,
},
};
static int __init omap_alsa_tsc2102_init(void)
{
int err;
ADEBUG();
err = platform_driver_register(&omap_alsa_driver);
return err;
}
static void __exit omap_alsa_tsc2102_exit(void)
{
ADEBUG();
platform_driver_unregister(&omap_alsa_driver);
}
module_init(omap_alsa_tsc2102_init);
module_exit(omap_alsa_tsc2102_exit);
/*
* sound/arm/omap/omap-alsa-tsc2102.h
*
* Alsa codec driver for TSC2102 chip for OMAP platforms.
*
* Copyright (c) 2006 Andrzej Zaborowski <balrog@zabor.org>
* Code based on the TSC2101 ALSA driver.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#ifndef OMAP_ALSA_TSC2102_H_
#define OMAP_ALSA_TSC2102_H_
/* Define to set the tsc as the master w.r.t McBSP */
#define TSC_MASTER
/*
* Audio related macros
*/
#ifndef DEFAULT_BITPERSAMPLE
#define DEFAULT_BITPERSAMPLE 16
#endif
#define DEFAULT_SAMPLE_RATE 44100
/* FIXME codec clock rate is board-specific */
#define CODEC_CLOCK 12000000
/*
* ALSA mixer related macros
*/
#define OUTPUT_VOLUME_MIN 0x7f /* 1111111 = -63.5 dB */
#define OUTPUT_VOLUME_MAX 0x00 /* 0000000 */
#define OUTPUT_VOLUME_RANGE (OUTPUT_VOLUME_MIN - OUTPUT_VOLUME_MAX)
#define DEFAULT_OUTPUT_VOLUME 90 /* Default output volume */
#endif /* OMAP_ALSA_TSC2102_H_ */
/*
* sound/arm/omap-alsa.c
*
* Alsa Driver for OMAP
*
* Copyright (C) 2005 Instituto Nokia de Tecnologia - INdT - Manaus Brazil
* Written by Daniel Petrini, David Cohen, Anderson Briglia
* {daniel.petrini, david.cohen, anderson.briglia}@indt.org.br
*
* Copyright (C) 2006 Mika Laitio <lamikr@cc.jyu.fi>
*
* Based on sa11xx-uda1341.c,
* Copyright (C) 2002 Tomas Kasparek <tomas.kasparek@seznam.cz>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
* NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 675 Mass Ave, Cambridge, MA 02139, USA.
*
* History:
*
* 2005-07-29 INdT Kernel Team - Alsa driver for omap osk. Creation of new
* file omap-aic23.c
*
* 2005-12-18 Dirk Behme - Added L/R Channel Interchange fix as proposed
* by Ajaya Babu
*
*/
#include <linux/platform_device.h>
#ifdef CONFIG_PM
#include <linux/pm.h>
#endif
#include <sound/core.h>
#include <sound/pcm.h>
#include <mach/omap-alsa.h>
#include "omap-alsa-dma.h"
MODULE_AUTHOR("Mika Laitio");
MODULE_AUTHOR("Daniel Petrini");
MODULE_AUTHOR("David Cohen");
MODULE_AUTHOR("Anderson Briglia");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("OMAP driver for ALSA");
MODULE_ALIAS("omap_alsa_mcbsp.1");
static char *id;
static struct snd_card_omap_codec *alsa_codec;
static struct omap_alsa_codec_config *alsa_codec_config;
/* FIXME: Please change to use omap asoc framework instead, this can be racy */
static dma_addr_t dma_start_pos;
/*
* HW interface start and stop helper functions
*/
static int audio_ifc_start(void)
{
omap_mcbsp_start(AUDIO_MCBSP);
return 0;
}
static int audio_ifc_stop(void)
{
omap_mcbsp_stop(AUDIO_MCBSP);
return 0;
}
static void omap_alsa_audio_init(struct snd_card_omap_codec *omap_alsa)
{
/* Setup DMA stuff */
omap_alsa->s[SNDRV_PCM_STREAM_PLAYBACK].id = "Alsa omap out";
omap_alsa->s[SNDRV_PCM_STREAM_PLAYBACK].stream_id =
SNDRV_PCM_STREAM_PLAYBACK;
omap_alsa->s[SNDRV_PCM_STREAM_PLAYBACK].dma_dev =
OMAP_DMA_MCBSP1_TX;
omap_alsa->s[SNDRV_PCM_STREAM_PLAYBACK].hw_start =
audio_ifc_start;
omap_alsa->s[SNDRV_PCM_STREAM_PLAYBACK].hw_stop =
audio_ifc_stop;
omap_alsa->s[SNDRV_PCM_STREAM_CAPTURE].id = "Alsa omap in";
omap_alsa->s[SNDRV_PCM_STREAM_CAPTURE].stream_id =
SNDRV_PCM_STREAM_CAPTURE;
omap_alsa->s[SNDRV_PCM_STREAM_CAPTURE].dma_dev =
OMAP_DMA_MCBSP1_RX;
omap_alsa->s[SNDRV_PCM_STREAM_CAPTURE].hw_start =
audio_ifc_start;
omap_alsa->s[SNDRV_PCM_STREAM_CAPTURE].hw_stop =
audio_ifc_stop;
}
/*
* DMA functions
* Depends on omap-alsa-dma.c functions and (omap) dma.c
*/
static int audio_dma_request(struct audio_stream *s,
void (*callback) (void *))
{
int err;
ADEBUG();
err = omap_request_alsa_sound_dma(s->dma_dev, s->id, s, &s->lch);
if (err < 0)
printk(KERN_ERR "Unable to grab audio dma 0x%x\n", s->dma_dev);
return err;
}
static int audio_dma_free(struct audio_stream *s)
{
int err = 0;
ADEBUG();
err = omap_free_alsa_sound_dma(s, &s->lch);
if (err < 0)
printk(KERN_ERR "Unable to free audio dma channels!\n");
return err;
}
/*
* This function should calculate the current position of the dma in the
* buffer. It will help alsa middle layer to continue update the buffer.
* Its correctness is crucial for good functioning.
*/
static u_int audio_get_dma_pos(struct audio_stream *s)
{
struct snd_pcm_substream *substream = s->stream;
struct snd_pcm_runtime *runtime = substream->runtime;
unsigned int offset;
unsigned long flags;
dma_addr_t count;
ADEBUG();
/* this must be called w/ interrupts locked as requested in dma.c */
spin_lock_irqsave(&s->dma_lock, flags);
/* For the current period let's see where we are */
count = omap_get_dma_src_pos(s->lch[s->dma_q_head]) - dma_start_pos;
spin_unlock_irqrestore(&s->dma_lock, flags);
/* Now, the position related to the end of that period */
offset = bytes_to_frames(runtime, s->offset) -
bytes_to_frames(runtime, count);
if (offset >= runtime->buffer_size)
offset = 0;
return offset;
}
/*
* this stops the dma and clears the dma ptrs
*/
static void audio_stop_dma(struct audio_stream *s)
{
unsigned long flags;
ADEBUG();
spin_lock_irqsave(&s->dma_lock, flags);
s->active = 0;
s->period = 0;
s->periods = 0;
/* this stops the dma channel and clears the buffer ptrs */
omap_stop_alsa_sound_dma(s);
omap_clear_alsa_sound_dma(s);
spin_unlock_irqrestore(&s->dma_lock, flags);
}
/*
* Main dma routine, requests dma according where you are in main alsa buffer
*/
static void audio_process_dma(struct audio_stream *s)
{
struct snd_pcm_substream *substream = s->stream;
struct snd_pcm_runtime *runtime;
unsigned int dma_size;
unsigned int offset;
int ret;
ADEBUG();
runtime = substream->runtime;
if (s->active) {
dma_size = frames_to_bytes(runtime, runtime->period_size);
offset = dma_size * s->period;
snd_assert(dma_size <= DMA_BUF_SIZE, return);
/*
* On omap1510 based devices, we need to call the stop_dma
* before calling the start_dma or we will not receive the
* irq from DMA after the first transfered/played buffer.
* (invocation of callback_omap_alsa_sound_dma() method).
*/
if (cpu_is_omap1510())
omap_stop_alsa_sound_dma(s);
dma_start_pos = (dma_addr_t)runtime->dma_area + offset;
ret = omap_start_alsa_sound_dma(s, dma_start_pos, dma_size);
if (ret) {
printk(KERN_ERR "audio_process_dma: cannot"
" queue DMA buffer (%i)\n", ret);
return;
}
s->period++;
s->period %= runtime->periods;
s->periods++;
s->offset = offset;
}
}
/*
* This is called when dma IRQ occurs at the end of each transmited block
*/
void callback_omap_alsa_sound_dma(void *data)
{
struct audio_stream *s = data;
ADEBUG();
/*
* If we are getting a callback for an active stream then we inform
* the PCM middle layer we've finished a period
*/
if (s->active)
snd_pcm_period_elapsed(s->stream);
spin_lock(&s->dma_lock);
if (s->periods > 0)
s->periods--;
audio_process_dma(s);
spin_unlock(&s->dma_lock);
}
/*
* Alsa section
* PCM settings and callbacks
*/
static int snd_omap_alsa_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct snd_card_omap_codec *chip =
snd_pcm_substream_chip(substream);
int stream_id = substream->pstr->stream;
struct audio_stream *s = &chip->s[stream_id];
int err = 0;
ADEBUG();
/* note local interrupts are already disabled in the midlevel code */
spin_lock(&s->dma_lock);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
/* requested stream startup */
s->active = 1;
audio_process_dma(s);
break;
case SNDRV_PCM_TRIGGER_STOP:
/* requested stream shutdown */
audio_stop_dma(s);
break;
default:
err = -EINVAL;
break;
}
spin_unlock(&s->dma_lock);
return err;
}
static int snd_omap_alsa_prepare(struct snd_pcm_substream *substream)
{
struct snd_card_omap_codec *chip = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
struct audio_stream *s = &chip->s[substream->pstr->stream];
ADEBUG();
/* set requested samplerate */
alsa_codec_config->codec_set_samplerate(runtime->rate);
chip->samplerate = runtime->rate;
s->period = 0;
s->periods = 0;
return 0;
}
static snd_pcm_uframes_t
snd_omap_alsa_pointer(struct snd_pcm_substream *substream)
{
struct snd_card_omap_codec *chip = snd_pcm_substream_chip(substream);
ADEBUG();
return audio_get_dma_pos(&chip->s[substream->pstr->stream]);
}
static int snd_card_omap_alsa_open(struct snd_pcm_substream *substream)
{
struct snd_card_omap_codec *chip =
snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
int stream_id = substream->pstr->stream;
int err;
ADEBUG();
chip->s[stream_id].stream = substream;
alsa_codec_config->codec_clock_on();
if (stream_id == SNDRV_PCM_STREAM_PLAYBACK)
runtime->hw = *(alsa_codec_config->snd_omap_alsa_playback);
else
runtime->hw = *(alsa_codec_config->snd_omap_alsa_capture);
err = snd_pcm_hw_constraint_integer(runtime,
SNDRV_PCM_HW_PARAM_PERIODS);
if (err < 0)
return err;
err = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
alsa_codec_config->hw_constraints_rates);
if (err < 0)
return err;
return 0;
}
static int snd_card_omap_alsa_close(struct snd_pcm_substream *substream)
{
struct snd_card_omap_codec *chip = snd_pcm_substream_chip(substream);
ADEBUG();
alsa_codec_config->codec_clock_off();
chip->s[substream->pstr->stream].stream = NULL;
return 0;
}
/* HW params & free */
static int snd_omap_alsa_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 snd_omap_alsa_hw_free(struct snd_pcm_substream *substream)
{
return snd_pcm_lib_free_pages(substream);
}
/* pcm operations */
static struct snd_pcm_ops snd_card_omap_alsa_playback_ops = {
.open = snd_card_omap_alsa_open,
.close = snd_card_omap_alsa_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = snd_omap_alsa_hw_params,
.hw_free = snd_omap_alsa_hw_free,
.prepare = snd_omap_alsa_prepare,
.trigger = snd_omap_alsa_trigger,
.pointer = snd_omap_alsa_pointer,
};
static struct snd_pcm_ops snd_card_omap_alsa_capture_ops = {
.open = snd_card_omap_alsa_open,
.close = snd_card_omap_alsa_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = snd_omap_alsa_hw_params,
.hw_free = snd_omap_alsa_hw_free,
.prepare = snd_omap_alsa_prepare,
.trigger = snd_omap_alsa_trigger,
.pointer = snd_omap_alsa_pointer,
};
/*
* Alsa init and exit section
* Inits pcm alsa structures, allocate the alsa buffer, suspend, resume
*/
static int __init snd_card_omap_alsa_pcm(struct snd_card_omap_codec *omap_alsa,
int device)
{
struct snd_pcm *pcm;
int err;
ADEBUG();
err = snd_pcm_new(omap_alsa->card, "OMAP PCM", device, 1, 1, &pcm);
if (err < 0)
return err;
/* sets up initial buffer with continuous allocation */
snd_pcm_lib_preallocate_pages_for_all(pcm,
SNDRV_DMA_TYPE_CONTINUOUS,
snd_dma_continuous_data
(GFP_KERNEL),
128 * 1024, 128 * 1024);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
&snd_card_omap_alsa_playback_ops);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
&snd_card_omap_alsa_capture_ops);
pcm->private_data = omap_alsa;
pcm->info_flags = 0;
strcpy(pcm->name, "omap alsa pcm");
omap_alsa_audio_init(omap_alsa);
/* setup DMA controller */
audio_dma_request(&omap_alsa->s[SNDRV_PCM_STREAM_PLAYBACK],
callback_omap_alsa_sound_dma);
audio_dma_request(&omap_alsa->s[SNDRV_PCM_STREAM_CAPTURE],
callback_omap_alsa_sound_dma);
omap_alsa->pcm = pcm;
return 0;
}
#ifdef CONFIG_PM
/*
* Driver suspend/resume - calls alsa functions. Some hints from aaci.c
*/
int snd_omap_alsa_suspend(struct platform_device *pdev, pm_message_t state)
{
struct snd_card_omap_codec *chip;
struct snd_card *card = platform_get_drvdata(pdev);
if (card->power_state != SNDRV_CTL_POWER_D3hot) {
chip = card->private_data;
if (chip->card->power_state != SNDRV_CTL_POWER_D3hot) {
snd_power_change_state(chip->card,
SNDRV_CTL_POWER_D3hot);
snd_pcm_suspend_all(chip->pcm);
/* Mutes and turn clock off */
alsa_codec_config->codec_clock_off();
snd_omap_suspend_mixer();
}
}
return 0;
}
int snd_omap_alsa_resume(struct platform_device *pdev)
{
struct snd_card_omap_codec *chip;
struct snd_card *card = platform_get_drvdata(pdev);
if (card->power_state != SNDRV_CTL_POWER_D0) {
chip = card->private_data;
if (chip->card->power_state != SNDRV_CTL_POWER_D0) {
snd_power_change_state(chip->card, SNDRV_CTL_POWER_D0);
alsa_codec_config->codec_clock_on();
snd_omap_resume_mixer();
}
}
return 0;
}
#endif /* CONFIG_PM */
void snd_omap_alsa_free(struct snd_card *card)
{
struct snd_card_omap_codec *chip = card->private_data;
ADEBUG();
/*
* Turn off codec after it is done.
* Can't do it immediately, since it may still have
* buffered data.
*/
schedule_timeout_interruptible(2);
omap_mcbsp_stop(AUDIO_MCBSP);
omap_mcbsp_free(AUDIO_MCBSP);
audio_dma_free(&chip->s[SNDRV_PCM_STREAM_PLAYBACK]);
audio_dma_free(&chip->s[SNDRV_PCM_STREAM_CAPTURE]);
}
/* module init & exit */
/*
* Inits alsa soudcard structure.
* Called by the probe method in codec after function pointers has been set.
*/
int snd_omap_alsa_post_probe(struct platform_device *pdev,
struct omap_alsa_codec_config *config)
{
int err = 0;
int def_rate;
struct snd_card *card;
ADEBUG();
alsa_codec_config = config;
alsa_codec_config->codec_clock_setup();
alsa_codec_config->codec_clock_on();
omap_mcbsp_request(AUDIO_MCBSP);
omap_mcbsp_stop(AUDIO_MCBSP);
omap_mcbsp_config(AUDIO_MCBSP, alsa_codec_config->mcbsp_regs_alsa);
omap_mcbsp_start(AUDIO_MCBSP);
if (alsa_codec_config && alsa_codec_config->codec_configure_dev)
alsa_codec_config->codec_configure_dev();
alsa_codec_config->codec_clock_off();
/* register the soundcard */
card = snd_card_new(-1, id, THIS_MODULE, sizeof(alsa_codec));
if (card == NULL)
goto nodev1;
alsa_codec = kcalloc(1, sizeof(*alsa_codec), GFP_KERNEL);
if (alsa_codec == NULL)
goto nodev2;
card->private_data = (void *)alsa_codec;
card->private_free = snd_omap_alsa_free;
alsa_codec->card = card;
def_rate = alsa_codec_config->get_default_samplerate();
alsa_codec->samplerate = def_rate;
spin_lock_init(&alsa_codec->s[0].dma_lock);
spin_lock_init(&alsa_codec->s[1].dma_lock);
/* mixer */
err = snd_omap_mixer(alsa_codec);
if (err < 0)
goto nodev3;
/* PCM */
err = snd_card_omap_alsa_pcm(alsa_codec, 0);
if (err < 0)
goto nodev3;
strcpy(card->driver, "OMAP_ALSA");
strcpy(card->shortname, alsa_codec_config->name);
sprintf(card->longname, alsa_codec_config->name);
snd_omap_init_mixer();
snd_card_set_dev(card, &pdev->dev);
err = snd_card_register(card);
if (err == 0) {
printk(KERN_INFO "audio support initialized\n");
platform_set_drvdata(pdev, card);
return 0;
}
nodev3:
kfree(alsa_codec);
nodev2:
snd_card_free(card);
nodev1:
omap_mcbsp_stop(AUDIO_MCBSP);
omap_mcbsp_free(AUDIO_MCBSP);
return err;
}
int snd_omap_alsa_remove(struct platform_device *pdev)
{
struct snd_card *card = platform_get_drvdata(pdev);
struct snd_card_omap_codec *chip = card->private_data;
snd_card_free(card);
alsa_codec = NULL;
card->private_data = NULL;
kfree(chip);
platform_set_drvdata(pdev, NULL);
return 0;
}
......@@ -7,10 +7,6 @@
obj-$(CONFIG_SOUND_OSS) += sound.o
obj-$(CONFIG_SOUND_OMAP) += omap-audio-dma-intfc.o omap-audio.o
obj-$(CONFIG_SOUND_OMAP_TSC2101)+= omap-audio-tsc2101.o
obj-$(CONFIG_SOUND_OMAP_AIC23) += omap-audio-aic23.o
# Please leave it as is, cause the link order is significant !
obj-$(CONFIG_SOUND_SH_DAC_AUDIO) += sh_dac_audio.o
......
/*
* linux/sound/oss/omap-audio-aic23.c
*
* Glue audio driver for TI TLV320AIC23 codec
*
* Copyright (c) 2000 Nicolas Pitre <nico@cam.org>
* Copyright (C) 2001, Steve Johnson <stevej@ridgerun.com>
* Copyright (C) 2004 Texas Instruments, Inc.
* Copyright (C) 2005 Dirk Behme <dirk.behme@de.bosch.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
* NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/delay.h>
#include <linux/pm.h>
#include <linux/errno.h>
#include <linux/sound.h>
#include <linux/soundcard.h>
#include <linux/clk.h>
#include <linux/mutex.h>
#include <asm/uaccess.h>
#include <mach/hardware.h>
#include <asm/io.h>
#include <asm/mach-types.h>
#include <mach/mcbsp.h>
#include <mach/fpga.h>
#include <mach/aic23.h>
#include <mach/clock.h>
#include "omap-audio.h"
#include "omap-audio-dma-intfc.h"
#ifdef CONFIG_PROC_FS
#include <linux/proc_fs.h>
#define PROC_START_FILE "driver/aic23-audio-start"
#define PROC_STOP_FILE "driver/aic23-audio-stop"
#endif
//#define DEBUG
#ifdef DEBUG
#define DPRINTK(ARGS...) printk("<%s>: ",__FUNCTION__);printk(ARGS)
#else
#define DPRINTK( x... )
#endif
#define CODEC_NAME "AIC23"
#if CONFIG_MACH_OMAP_OSK
#define PLATFORM_NAME "OMAP OSK"
#elif CONFIG_MACH_OMAP_INNOVATOR
#define PLATFORM_NAME "OMAP INNOVATOR"
#else
#error "Unsupported plattform"
#endif
/* Define to set the AIC23 as the master w.r.t McBSP */
#define AIC23_MASTER
#define CODEC_CLOCK 12000000
/*
* AUDIO related MACROS
*/
#define DEFAULT_BITPERSAMPLE 16
#define AUDIO_RATE_DEFAULT 44100
/* Select the McBSP For Audio */
#define AUDIO_MCBSP OMAP_MCBSP1
#define REC_MASK (SOUND_MASK_LINE | SOUND_MASK_MIC)
#define DEV_MASK (REC_MASK | SOUND_MASK_VOLUME)
#define SET_VOLUME 1
#define SET_LINE 2
#define DEFAULT_OUTPUT_VOLUME 93
#define DEFAULT_INPUT_VOLUME 0 /* 0 ==> mute line in */
#define OUTPUT_VOLUME_MIN LHV_MIN
#define OUTPUT_VOLUME_MAX LHV_MAX
#define OUTPUT_VOLUME_RANGE (OUTPUT_VOLUME_MAX - OUTPUT_VOLUME_MIN)
#define OUTPUT_VOLUME_MASK OUTPUT_VOLUME_MAX
#define INPUT_VOLUME_MIN LIV_MIN
#define INPUT_VOLUME_MAX LIV_MAX
#define INPUT_VOLUME_RANGE (INPUT_VOLUME_MAX - INPUT_VOLUME_MIN)
#define INPUT_VOLUME_MASK INPUT_VOLUME_MAX
#define NUMBER_SAMPLE_RATES_SUPPORTED 9
/*
* HW interface start and stop helper functions
*/
static int audio_ifc_start(void)
{
omap_mcbsp_start(AUDIO_MCBSP);
return 0;
}
static int audio_ifc_stop(void)
{
omap_mcbsp_stop(AUDIO_MCBSP);
return 0;
}
static audio_stream_t output_stream = {
.id = "AIC23 out",
.dma_dev = OMAP_DMA_MCBSP1_TX,
.input_or_output = FMODE_WRITE,
.hw_start = audio_ifc_start,
.hw_stop = audio_ifc_stop
};
static audio_stream_t input_stream = {
.id = "AIC23 in",
.dma_dev = OMAP_DMA_MCBSP1_RX,
.input_or_output = FMODE_READ,
.hw_start = audio_ifc_start,
.hw_stop = audio_ifc_stop
};
static struct clk *aic23_mclk = 0;
static int audio_dev_id, mixer_dev_id;
static struct aic23_local_info {
u8 volume;
u16 volume_reg;
u8 line;
u8 mic;
u16 input_volume_reg;
int mod_cnt;
} aic23_local;
struct sample_rate_reg_info {
u32 sample_rate;
u8 control; /* SR3, SR2, SR1, SR0 and BOSR */
u8 divider; /* if 0 CLKIN = MCLK, if 1 CLKIN = MCLK/2 */
};
/* To Store the default sample rate */
static long audio_samplerate = AUDIO_RATE_DEFAULT;
/* DAC USB-mode sampling rates (MCLK = 12 MHz) */
static const struct sample_rate_reg_info
reg_info[NUMBER_SAMPLE_RATES_SUPPORTED] = {
{96000, 0x0E, 0},
{88200, 0x1F, 0},
{48000, 0x00, 0},
{44100, 0x11, 0},
{32000, 0x0C, 0},
{24000, 0x00, 1},
{16000, 0x0C, 1},
{ 8000, 0x06, 0},
{ 4000, 0x06, 1},
};
static struct omap_mcbsp_reg_cfg initial_config = {
.spcr2 = FREE | FRST | GRST | XRST | XINTM(3),
.spcr1 = RINTM(3) | RRST,
.rcr2 = RPHASE | RFRLEN2(OMAP_MCBSP_WORD_8) |
RWDLEN2(OMAP_MCBSP_WORD_16) | RDATDLY(0),
.rcr1 = RFRLEN1(OMAP_MCBSP_WORD_8) | RWDLEN1(OMAP_MCBSP_WORD_16),
.xcr2 = XPHASE | XFRLEN2(OMAP_MCBSP_WORD_8) |
XWDLEN2(OMAP_MCBSP_WORD_16) | XDATDLY(0) | XFIG,
.xcr1 = XFRLEN1(OMAP_MCBSP_WORD_8) | XWDLEN1(OMAP_MCBSP_WORD_16),
.srgr1 = FWID(DEFAULT_BITPERSAMPLE - 1),
.srgr2 = GSYNC | CLKSP | FSGM | FPER(DEFAULT_BITPERSAMPLE * 2 - 1),
#ifndef AIC23_MASTER
/* configure McBSP to be the I2S master */
.pcr0 = FSXM | FSRM | CLKXM | CLKRM | CLKXP | CLKRP,
#else
/* configure McBSP to be the I2S slave */
.pcr0 = CLKXP | CLKRP,
#endif /* AIC23_MASTER */
};
static void omap_aic23_initialize(void *dummy);
static void omap_aic23_shutdown(void *dummy);
static int omap_aic23_ioctl(struct inode *inode, struct file *file,
uint cmd, ulong arg);
static int omap_aic23_probe(void);
#ifdef MODULE
static void omap_aic23_remove(void);
#endif
static int omap_aic23_suspend(void);
static int omap_aic23_resume(void);
static inline void aic23_configure(void);
static int mixer_open(struct inode *inode, struct file *file);
static int mixer_release(struct inode *inode, struct file *file);
static int mixer_ioctl(struct inode *inode, struct file *file, uint cmd,
ulong arg);
#ifdef CONFIG_PROC_FS
static int codec_start(char *buf, char **start, off_t offset, int count,
int *eof, void *data);
static int codec_stop(char *buf, char **start, off_t offset, int count,
int *eof, void *data);
#endif
/* File Op structure for mixer */
static struct file_operations omap_mixer_fops = {
.open = mixer_open,
.release = mixer_release,
.ioctl = mixer_ioctl,
.owner = THIS_MODULE
};
/* To store characteristic info regarding the codec for the audio driver */
static audio_state_t aic23_state = {
.output_stream = &output_stream,
.input_stream = &input_stream,
/* .need_tx_for_rx = 1, //Once the Full Duplex works */
.need_tx_for_rx = 0,
.hw_init = omap_aic23_initialize,
.hw_shutdown = omap_aic23_shutdown,
.client_ioctl = omap_aic23_ioctl,
.hw_probe = omap_aic23_probe,
.hw_remove = __exit_p(omap_aic23_remove),
.hw_suspend = omap_aic23_suspend,
.hw_resume = omap_aic23_resume,
};
/* This will be defined in the audio.h */
static struct file_operations *omap_audio_fops;
extern int aic23_write_value(u8 reg, u16 value);
/* TLV320AIC23 is a write only device */
static __inline__ void audio_aic23_write(u8 address, u16 data)
{
aic23_write_value(address, data);
}
static int aic23_update(int flag, int val)
{
u16 volume;
/* Ignore separate left/right channel for now,
even the codec does support it. */
val &= 0xff;
if (val < 0 || val > 100) {
printk(KERN_ERR "Trying a bad volume value(%d)!\n",val);
return -EPERM;
}
switch (flag) {
case SET_VOLUME:
// Convert 0 -> 100 volume to 0x00 (LHV_MIN) -> 0x7f (LHV_MAX)
// volume range
volume = ((val * OUTPUT_VOLUME_RANGE) / 100) + OUTPUT_VOLUME_MIN;
// R/LHV[6:0] 1111111 (+6dB) to 0000000 (-73dB) in 1db steps,
// default 1111001 (0dB)
aic23_local.volume_reg &= ~OUTPUT_VOLUME_MASK;
aic23_local.volume_reg |= volume;
audio_aic23_write(LEFT_CHANNEL_VOLUME_ADDR, aic23_local.volume_reg);
audio_aic23_write(RIGHT_CHANNEL_VOLUME_ADDR, aic23_local.volume_reg);
break;
case SET_LINE:
// Convert 0 -> 100 volume to 0x0 (LIV_MIN) -> 0x1f (LIV_MAX)
// volume range
volume = ((val * INPUT_VOLUME_RANGE) / 100) + INPUT_VOLUME_MIN;
// R/LIV[4:0] 11111 (+12dB) to 00000 (-34.5dB) in 1.5dB steps,
// default 10111 (0dB)
aic23_local.input_volume_reg &= ~INPUT_VOLUME_MASK;
aic23_local.input_volume_reg |= volume;
audio_aic23_write(LEFT_LINE_VOLUME_ADDR, aic23_local.input_volume_reg);
audio_aic23_write(RIGHT_LINE_VOLUME_ADDR, aic23_local.input_volume_reg);
break;
}
return 0;
}
static int mixer_open(struct inode *inode, struct file *file)
{
/* Any mixer specific initialization */
return 0;
}
static int mixer_release(struct inode *inode, struct file *file)
{
/* Any mixer specific Un-initialization */
return 0;
}
static int
mixer_ioctl(struct inode *inode, struct file *file, uint cmd, ulong arg)
{
int val;
int ret = 0;
int nr = _IOC_NR(cmd);
/*
* We only accept mixer (type 'M') ioctls.
*/
if (_IOC_TYPE(cmd) != 'M')
return -EINVAL;
DPRINTK(" 0x%08x\n", cmd);
if (cmd == SOUND_MIXER_INFO) {
struct mixer_info mi;
strncpy(mi.id, "AIC23", sizeof(mi.id));
strncpy(mi.name, "TI AIC23", sizeof(mi.name));
mi.modify_counter = aic23_local.mod_cnt;
return copy_to_user((void *)arg, &mi, sizeof(mi));
}
if (_IOC_DIR(cmd) & _IOC_WRITE) {
ret = get_user(val, (int *)arg);
if (ret)
goto out;
switch (nr) {
case SOUND_MIXER_VOLUME:
aic23_local.volume = val;
aic23_local.mod_cnt++;
ret = aic23_update(SET_VOLUME, val);
break;
case SOUND_MIXER_LINE:
aic23_local.line = val;
aic23_local.mod_cnt++;
ret = aic23_update(SET_LINE, val);
break;
case SOUND_MIXER_MIC:
aic23_local.mic = val;
aic23_local.mod_cnt++;
ret = aic23_update(SET_LINE, val);
break;
case SOUND_MIXER_RECSRC:
break;
default:
ret = -EINVAL;
}
}
if (ret == 0 && _IOC_DIR(cmd) & _IOC_READ) {
ret = 0;
switch (nr) {
case SOUND_MIXER_VOLUME:
val = aic23_local.volume;
break;
case SOUND_MIXER_LINE:
val = aic23_local.line;
break;
case SOUND_MIXER_MIC:
val = aic23_local.mic;
break;
case SOUND_MIXER_RECSRC:
val = REC_MASK;
break;
case SOUND_MIXER_RECMASK:
val = REC_MASK;
break;
case SOUND_MIXER_DEVMASK:
val = DEV_MASK;
break;
case SOUND_MIXER_CAPS:
val = 0;
break;
case SOUND_MIXER_STEREODEVS:
val = 0;
break;
default:
val = 0;
ret = -EINVAL;
break;
}
if (ret == 0)
ret = put_user(val, (int *)arg);
}
out:
return ret;
}
int omap_set_samplerate(long sample_rate)
{
u8 count = 0;
u16 data = 0;
/* wait for any frame to complete */
udelay(125);
/* Search for the right sample rate */
while ((reg_info[count].sample_rate != sample_rate) &&
(count < NUMBER_SAMPLE_RATES_SUPPORTED)) {
count++;
}
if (count == NUMBER_SAMPLE_RATES_SUPPORTED) {
printk(KERN_ERR "Invalid Sample Rate %d requested\n",
(int)sample_rate);
return -EPERM;
}
if (machine_is_omap_innovator()) {
/* set the CODEC clock input source to 12.000MHz */
fpga_write(fpga_read(OMAP1510_FPGA_POWER) & ~0x01,
OMAP1510_FPGA_POWER);
}
data = (reg_info[count].divider << CLKIN_SHIFT) |
(reg_info[count].control << BOSR_SHIFT) | USB_CLK_ON;
audio_aic23_write(SAMPLE_RATE_CONTROL_ADDR, data);
audio_samplerate = sample_rate;
#ifndef AIC23_MASTER
{
int clkgdv = 0;
/*
Set Sample Rate at McBSP
Formula :
Codec System Clock = CODEC_CLOCK, or half if clock_divider = 1;
clkgdv = ((Codec System Clock / (SampleRate * BitsPerSample * 2)) - 1);
FWID = BitsPerSample - 1;
FPER = (BitsPerSample * 2) - 1;
*/
if (reg_info[count].divider)
clkgdv = CODEC_CLOCK / 2;
else
clkgdv = CODEC_CLOCK;
clkgdv = (clkgdv / (sample_rate * DEFAULT_BITPERSAMPLE * 2)) - 1;
initial_config.srgr1 = (FWID(DEFAULT_BITPERSAMPLE - 1) | CLKGDV(clkgdv));
initial_config.srgr2 =
(CLKSM | FSGM | FPER(DEFAULT_BITPERSAMPLE * 2 - 1));
omap_mcbsp_config(AUDIO_MCBSP, &initial_config);
}
#endif /* AIC23_MASTER */
return 0;
}
static void omap_aic23_initialize(void *dummy)
{
DPRINTK("entry\n");
/* initialize with default sample rate */
audio_samplerate = AUDIO_RATE_DEFAULT;
omap_mcbsp_request(AUDIO_MCBSP);
/* if configured, then stop mcbsp */
omap_mcbsp_stop(AUDIO_MCBSP);
omap_mcbsp_config(AUDIO_MCBSP, &initial_config);
omap_mcbsp_start(AUDIO_MCBSP);
aic23_configure();
DPRINTK("exit\n");
}
static void omap_aic23_shutdown(void *dummy)
{
/*
Turn off codec after it is done.
Can't do it immediately, since it may still have
buffered data.
Wait 20ms (arbitrary value) and then turn it off.
*/
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout(2);
omap_mcbsp_stop(AUDIO_MCBSP);
omap_mcbsp_free(AUDIO_MCBSP);
audio_aic23_write(RESET_CONTROL_ADDR, 0);
audio_aic23_write(POWER_DOWN_CONTROL_ADDR, 0xff);
}
static inline void aic23_configure()
{
/* Reset codec */
audio_aic23_write(RESET_CONTROL_ADDR, 0);
/* Initialize the AIC23 internal state */
/* Left/Right line input volume control */
aic23_local.line = DEFAULT_INPUT_VOLUME;
aic23_local.mic = DEFAULT_INPUT_VOLUME;
aic23_update(SET_LINE, DEFAULT_INPUT_VOLUME);
/* Left/Right headphone channel volume control */
/* Zero-cross detect on */
aic23_local.volume_reg = LZC_ON;
aic23_update(SET_VOLUME, aic23_local.volume);
/* Analog audio path control, DAC selected, delete INSEL_MIC for line in */
audio_aic23_write(ANALOG_AUDIO_CONTROL_ADDR, DAC_SELECTED | INSEL_MIC);
/* Digital audio path control, de-emphasis control 44.1kHz */
audio_aic23_write(DIGITAL_AUDIO_CONTROL_ADDR, DEEMP_44K);
/* Power control, everything is on */
audio_aic23_write(POWER_DOWN_CONTROL_ADDR, 0);
/* Digital audio interface, master/slave mode, I2S, 16 bit */
#ifdef AIC23_MASTER
audio_aic23_write(DIGITAL_AUDIO_FORMAT_ADDR, MS_MASTER | IWL_16 | FOR_DSP);
#else
audio_aic23_write(DIGITAL_AUDIO_FORMAT_ADDR, IWL_16 | FOR_DSP);
#endif /* AIC23_MASTER */
/* Enable digital interface */
audio_aic23_write(DIGITAL_INTERFACE_ACT_ADDR, ACT_ON);
/* clock configuration */
omap_set_samplerate(audio_samplerate);
}
static int
omap_aic23_ioctl(struct inode *inode, struct file *file, uint cmd, ulong arg)
{
long val;
int ret = 0;
DPRINTK(" 0x%08x\n", cmd);
/*
* These are platform dependent ioctls which are not handled by the
* generic omap-audio module.
*/
switch (cmd) {
case SNDCTL_DSP_STEREO:
ret = get_user(val, (int *)arg);
if (ret)
return ret;
/* the AIC23 is stereo only */
ret = (val == 0) ? -EINVAL : 1;
return put_user(ret, (int *)arg);
case SNDCTL_DSP_CHANNELS:
case SOUND_PCM_READ_CHANNELS:
/* the AIC23 is stereo only */
return put_user(2, (long *)arg);
case SNDCTL_DSP_SPEED:
ret = get_user(val, (long *)arg);
if (ret)
break;
ret = omap_set_samplerate(val);
if (ret)
break;
/* fall through */
case SOUND_PCM_READ_RATE:
return put_user(audio_samplerate, (long *)arg);
case SOUND_PCM_READ_BITS:
case SNDCTL_DSP_SETFMT:
case SNDCTL_DSP_GETFMTS:
/* we can do 16-bit only */
return put_user(AFMT_S16_LE, (long *)arg);
default:
/* Maybe this is meant for the mixer (As per OSS Docs) */
return mixer_ioctl(inode, file, cmd, arg);
}
return ret;
}
static int omap_aic23_probe(void)
{
/* Get the fops from audio oss driver */
if (!(omap_audio_fops = audio_get_fops())) {
printk(KERN_ERR "Unable to get the file operations for AIC23 OSS driver\n");
audio_unregister_codec(&aic23_state);
return -EPERM;
}
aic23_local.volume = DEFAULT_OUTPUT_VOLUME;
/* register devices */
audio_dev_id = register_sound_dsp(omap_audio_fops, -1);
mixer_dev_id = register_sound_mixer(&omap_mixer_fops, -1);
#ifdef CONFIG_PROC_FS
create_proc_read_entry(PROC_START_FILE, 0 /* default mode */ ,
NULL /* parent dir */ ,
codec_start, NULL /* client data */ );
create_proc_read_entry(PROC_STOP_FILE, 0 /* default mode */ ,
NULL /* parent dir */ ,
codec_stop, NULL /* client data */ );
#endif
/* Announcement Time */
printk(KERN_INFO PLATFORM_NAME " " CODEC_NAME
" audio support initialized\n");
return 0;
}
#ifdef MODULE
static void __exit omap_aic23_remove(void)
{
/* Un-Register the codec with the audio driver */
unregister_sound_dsp(audio_dev_id);
unregister_sound_mixer(mixer_dev_id);
#ifdef CONFIG_PROC_FS
remove_proc_entry(PROC_START_FILE, NULL);
remove_proc_entry(PROC_STOP_FILE, NULL);
#endif
}
#endif /* MODULE */
static int omap_aic23_suspend(void)
{
/* Empty for the moment */
return 0;
}
static int omap_aic23_resume(void)
{
/* Empty for the moment */
return 0;
}
static int __init audio_aic23_init(void)
{
int err = 0;
if (machine_is_omap_h2() || machine_is_omap_h3())
return -ENODEV;
mutex_init(&aic23_state.mutex);
if (machine_is_omap_osk()) {
/* Set MCLK to be clock input for AIC23 */
aic23_mclk = clk_get(0, "mclk");
if(clk_get_rate( aic23_mclk) != CODEC_CLOCK){
/* MCLK ist not at CODEC_CLOCK */
if( clk_get_usecount(aic23_mclk) > 0 ){
/* MCLK is already in use */
printk(KERN_WARNING "MCLK in use at %d Hz. We change it to %d Hz\n",
(uint)clk_get_rate( aic23_mclk), CODEC_CLOCK);
}
if( clk_set_rate( aic23_mclk, CODEC_CLOCK ) ){
printk(KERN_ERR "Cannot set MCLK for AIC23 CODEC\n");
return -ECANCELED;
}
}
clk_enable( aic23_mclk );
DPRINTK("MCLK = %d [%d], usecount = %d\n",(uint)clk_get_rate( aic23_mclk ),
CODEC_CLOCK, clk_get_usecount( aic23_mclk));
}
if (machine_is_omap_innovator()) {
u8 fpga;
/*
Turn on chip select for CODEC (shared with touchscreen).
Don't turn it back off, in case touch screen needs it.
*/
fpga = fpga_read(OMAP1510_FPGA_TOUCHSCREEN);
fpga |= 0x4;
fpga_write(fpga, OMAP1510_FPGA_TOUCHSCREEN);
}
/* register the codec with the audio driver */
if ((err = audio_register_codec(&aic23_state))) {
printk(KERN_ERR
"Failed to register AIC23 driver with Audio OSS Driver\n");
}
return err;
}
static void __exit audio_aic23_exit(void)
{
(void)audio_unregister_codec(&aic23_state);
return;
}
#ifdef CONFIG_PROC_FS
static int codec_start(char *buf, char **start, off_t offset, int count,
int *eof, void *data)
{
void *foo = NULL;
omap_aic23_initialize(foo);
printk("AIC23 codec initialization done.\n");
return 0;
}
static int codec_stop(char *buf, char **start, off_t offset, int count,
int *eof, void *data)
{
void *foo = NULL;
omap_aic23_shutdown(foo);
printk("AIC23 codec shutdown.\n");
return 0;
}
#endif /* CONFIG_PROC_FS */
module_init(audio_aic23_init);
module_exit(audio_aic23_exit);
MODULE_AUTHOR("Dirk Behme <dirk.behme@de.bosch.com>");
MODULE_DESCRIPTION("Glue audio driver for the TI AIC23 codec.");
MODULE_LICENSE("GPL");
/*
* linux/sound/oss/omap-audio-dma-intfc.c
*
* Common audio DMA handling for the OMAP processors
*
* Copyright (C) 2004 Texas Instruments, Inc.
*
* Copyright (C) 2000, 2001 Nicolas Pitre <nico@cam.org>
*
* This package is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*
* History:
*
* 2004-06-07 Sriram Kannan - Created new file from omap_audio_dma_intfc.c. This file
* will contain only the DMA interface and buffer handling of OMAP
* audio driver.
*
* 2004-06-22 Sriram Kannan - removed legacy code (auto-init). Self-linking of DMA logical channel.
*
* 2004-08-12 Nishanth Menon - Modified to integrate Audio requirements on 1610,1710 platforms
*
* 2004-11-01 Nishanth Menon - 16xx platform code base modified to support multi channel chaining.
*
* 2004-12-15 Nishanth Menon - Improved 16xx platform channel logic introduced - tasklets, queue handling updated
*
* 2005-12-10 Dirk Behme - Added L/R Channel Interchange fix as proposed by Ajaya Babu
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/pm.h>
#include <linux/errno.h>
#include <linux/sound.h>
#include <linux/soundcard.h>
#include <linux/sysrq.h>
#include <linux/interrupt.h>
#include <linux/dma-mapping.h>
#include <linux/completion.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <mach/hardware.h>
#include <asm/semaphore.h>
#include <mach/dma.h>
#include "omap-audio-dma-intfc.h"
#include <mach/mcbsp.h>
#include "omap-audio.h"
#undef DEBUG
//#define DEBUG
#ifdef DEBUG
#define DPRINTK(ARGS...) printk(KERN_INFO "<%s>: ",__FUNCTION__);printk(ARGS)
#define FN_IN printk(KERN_INFO "[%s]: start\n", __FUNCTION__)
#define FN_OUT(n) printk(KERN_INFO "[%s]: end(%u)\n",__FUNCTION__, n)
#else
#define DPRINTK( x... )
#define FN_IN
#define FN_OUT(x)
#endif
#define ERR(ARGS...) printk(KERN_ERR "{%s}-ERROR: ", __FUNCTION__);printk(ARGS);
#define AUDIO_NAME "omap-audio"
#define AUDIO_NBFRAGS_DEFAULT 8
#define AUDIO_FRAGSIZE_DEFAULT 8192
#define AUDIO_ACTIVE(state) ((state)->rd_ref || (state)->wr_ref)
#define SPIN_ADDR (dma_addr_t)0
#define SPIN_SIZE 2048
/* Channel Queue Handling macros
* tail always points to the current free entry
* Head always points to the current entry being used
* end is either head or tail
*/
#define AUDIO_QUEUE_INIT(s) s->dma_q_head = s->dma_q_tail = s->dma_q_count = 0;
#define AUDIO_QUEUE_FULL(s) (nr_linked_channels == s->dma_q_count)
#define AUDIO_QUEUE_LAST(s) (1 == s->dma_q_count)
#define AUDIO_QUEUE_EMPTY(s) (0 == s->dma_q_count)
#define __AUDIO_INCREMENT_QUEUE(end) ((end)=((end)+1) % nr_linked_channels)
#define AUDIO_INCREMENT_HEAD(s) __AUDIO_INCREMENT_QUEUE(s->dma_q_head); s->dma_q_count--;
#define AUDIO_INCREMENT_TAIL(s) __AUDIO_INCREMENT_QUEUE(s->dma_q_tail); s->dma_q_count++;
/* DMA buffer fragmentation sizes */
#define MAX_DMA_SIZE 0x1000000
#define CUT_DMA_SIZE 0x1000
/* TODO: To be moved to more appropriate location */
#define DCSR_ERROR 0x3
#define DCSR_SYNC_SET (1 << 6)
#define DCCR_FS (1 << 5)
#define DCCR_PRIO (1 << 6)
#define DCCR_AI (1 << 8)
#define DCCR_REPEAT (1 << 9)
/* if 0 the channel works in 3.1 compatible mode*/
#define DCCR_N31COMP (1 << 10)
#define DCCR_EP (1 << 11)
#define DCCR_SRC_AMODE_BIT 12
#define DCCR_SRC_AMODE_MASK (0x3<<12)
#define DCCR_DST_AMODE_BIT 14
#define DCCR_DST_AMODE_MASK (0x3<<14)
#define AMODE_CONST 0x0
#define AMODE_POST_INC 0x1
#define AMODE_SINGLE_INDEX 0x2
#define AMODE_DOUBLE_INDEX 0x3
/**************************** DATA STRUCTURES *****************************************/
static spinlock_t dma_list_lock = SPIN_LOCK_UNLOCKED;
struct audio_isr_work_item {
int current_lch;
u16 ch_status;
audio_stream_t *s;
};
static char work_item_running = 0;
static char nr_linked_channels = 1;
static struct audio_isr_work_item work1, work2;
/*********************************** MODULE SPECIFIC FUNCTIONS PROTOTYPES *************/
static void audio_dsr_handler(unsigned long);
static DECLARE_TASKLET(audio_isr_work1, audio_dsr_handler,
(unsigned long)&work1);
static DECLARE_TASKLET(audio_isr_work2, audio_dsr_handler,
(unsigned long)&work2);
static void sound_dma_irq_handler(int lch, u16 ch_status, void *data);
static void audio_dma_callback(int lch, u16 ch_status, void *data);
static int omap_start_sound_dma(audio_stream_t * s, dma_addr_t dma_ptr,
u_int size);
static int audio_set_dma_params_play(int channel, dma_addr_t dma_ptr,
u_int dma_size);
static int audio_set_dma_params_capture(int channel, dma_addr_t dma_ptr,
u_int dma_size);
static int audio_start_dma_chain(audio_stream_t * s);
/*********************************** GLOBAL FUNCTIONS DEFINTIONS ***********************/
/***************************************************************************************
*
* Buffer creation/destruction
*
**************************************************************************************/
int audio_setup_buf(audio_stream_t * s)
{
int frag;
int dmasize = 0;
char *dmabuf = NULL;
dma_addr_t dmaphys = 0;
FN_IN;
if (s->buffers) {
FN_OUT(1);
return -EBUSY;
}
s->buffers = kmalloc(sizeof(audio_buf_t) * s->nbfrags, GFP_KERNEL);
if (!s->buffers)
goto err;
memset(s->buffers, 0, sizeof(audio_buf_t) * s->nbfrags);
for (frag = 0; frag < s->nbfrags; frag++) {
audio_buf_t *b = &s->buffers[frag];
/*
* Let's allocate non-cached memory for DMA buffers.
* We try to allocate all memory at once.
* If this fails (a common reason is memory fragmentation),
* then we allocate more smaller buffers.
*/
if (!dmasize) {
dmasize = (s->nbfrags - frag) * s->fragsize;
do {
dmabuf =
dma_alloc_coherent(NULL, dmasize, &dmaphys,
0);
if (!dmabuf)
dmasize -= s->fragsize;
}
while (!dmabuf && dmasize);
if (!dmabuf)
goto err;
b->master = dmasize;
memzero(dmabuf, dmasize);
}
b->data = dmabuf;
b->dma_addr = dmaphys;
dmabuf += s->fragsize;
dmaphys += s->fragsize;
dmasize -= s->fragsize;
}
s->usr_head = s->dma_head = s->dma_tail = 0;
AUDIO_QUEUE_INIT(s);
s->started = 0;
s->bytecount = 0;
s->fragcount = 0;
init_completion(&s->wfc);
s->wfc.done = s->nbfrags;
FN_OUT(0);
return 0;
err:
audio_discard_buf(s);
FN_OUT(1);
return -ENOMEM;
}
void audio_discard_buf(audio_stream_t * s)
{
FN_IN;
/* ensure DMA isn't using those buffers */
audio_reset(s);
if (s->buffers) {
int frag;
for (frag = 0; frag < s->nbfrags; frag++) {
if (!s->buffers[frag].master)
continue;
dma_free_coherent(NULL,
s->buffers[frag].master,
s->buffers[frag].data,
s->buffers[frag].dma_addr);
}
kfree(s->buffers);
s->buffers = NULL;
}
FN_OUT(0);
}
/***************************************************************************************
*
* DMA channel requests
*
**************************************************************************************/
static void omap_sound_dma_link_lch(void *data)
{
audio_stream_t *s = (audio_stream_t *) data;
int *chan = s->lch;
int i;
FN_IN;
if (s->linked) {
FN_OUT(1);
return;
}
for (i = 0; i < nr_linked_channels; i++) {
int cur_chan = chan[i];
int nex_chan =
((nr_linked_channels - 1 ==
i) ? chan[0] : chan[i + 1]);
omap_dma_link_lch(cur_chan, nex_chan);
}
s->linked = 1;
FN_OUT(0);
}
int
omap_request_sound_dma(int device_id, const char *device_name, void *data,
int **channels)
{
int i, err = 0;
int *chan = NULL;
FN_IN;
if (unlikely((NULL == channels) || (NULL == device_name))) {
BUG();
return -EPERM;
}
/* Try allocate memory for the num channels */
*channels =
(int *)kmalloc(sizeof(int) * nr_linked_channels,
GFP_KERNEL);
chan = *channels;
if (NULL == chan) {
ERR("No Memory for channel allocs!\n");
FN_OUT(-ENOMEM);
return -ENOMEM;
}
spin_lock(&dma_list_lock);
for (i = 0; i < nr_linked_channels; i++) {
err =
omap_request_dma(device_id, device_name,
sound_dma_irq_handler, data, &chan[i]);
/* Handle Failure condition here */
if (err < 0) {
int j;
for (j = 0; j < i; j++) {
omap_free_dma(chan[j]);
}
spin_unlock(&dma_list_lock);
kfree(chan);
*channels = NULL;
ERR("Error in requesting channel %d=0x%x\n", i, err);
FN_OUT(err);
return err;
}
}
/* Chain the channels together */
if (!cpu_is_omap15xx())
omap_sound_dma_link_lch(data);
spin_unlock(&dma_list_lock);
FN_OUT(0);
return 0;
}
/***************************************************************************************
*
* DMA channel requests Freeing
*
**************************************************************************************/
static void omap_sound_dma_unlink_lch(void *data)
{
audio_stream_t *s = (audio_stream_t *) data;
int *chan = s->lch;
int i;
FN_IN;
if (!s->linked) {
FN_OUT(1);
return;
}
for (i = 0; i < nr_linked_channels; i++) {
int cur_chan = chan[i];
int nex_chan =
((nr_linked_channels - 1 ==
i) ? chan[0] : chan[i + 1]);
omap_dma_unlink_lch(cur_chan, nex_chan);
}
s->linked = 0;
FN_OUT(0);
}
int omap_free_sound_dma(void *data, int **channels)
{
int i;
int *chan = NULL;
FN_IN;
if (unlikely(NULL == channels)) {
BUG();
return -EPERM;
}
if (unlikely(NULL == *channels)) {
BUG();
return -EPERM;
}
chan = (*channels);
if (!cpu_is_omap15xx())
omap_sound_dma_unlink_lch(data);
for (i = 0; i < nr_linked_channels; i++) {
int cur_chan = chan[i];
omap_stop_dma(cur_chan);
omap_free_dma(cur_chan);
}
kfree(*channels);
*channels = NULL;
FN_OUT(0);
return 0;
}
/***************************************************************************************
*
* Process DMA requests - This will end up starting the transfer. Proper fragments of
* Transfers will be initiated.
*
**************************************************************************************/
int audio_process_dma(audio_stream_t * s)
{
int ret = 0;
unsigned long flags;
FN_IN;
/* Dont let the ISR over ride touching the in_use flag */
local_irq_save(flags);
if (1 == s->in_use) {
local_irq_restore(flags);
ERR("Called again while In Use\n");
return 0;
}
s->in_use = 1;
local_irq_restore(flags);
if (s->stopped)
goto spin;
if (s->dma_spinref > 0 && s->pending_frags) {
s->dma_spinref = 0;
DMA_CLEAR(s);
}
while (s->pending_frags) {
audio_buf_t *b = &s->buffers[s->dma_head];
u_int dma_size = s->fragsize - b->offset;
if (dma_size > MAX_DMA_SIZE)
dma_size = CUT_DMA_SIZE;
ret =
omap_start_sound_dma(s, b->dma_addr + b->offset, dma_size);
if (ret) {
goto process_out;
}
b->dma_ref++;
b->offset += dma_size;
if (b->offset >= s->fragsize) {
s->pending_frags--;
if (++s->dma_head >= s->nbfrags)
s->dma_head = 0;
}
}
spin:
if (s->spin_idle) {
int spincnt = 0;
ERR("we are spinning\n");
while (omap_start_sound_dma(s, SPIN_ADDR, SPIN_SIZE) == 0)
spincnt++;
/*
* Note: if there is still a data buffer being
* processed then the ref count is negative. This
* allows for the DMA termination to be accounted in
* the proper order. Of course dma_spinref can't be
* greater than 0 if dma_ref is not 0 since we kill
* the spinning above as soon as there is real data to process.
*/
if (s->buffers && s->buffers[s->dma_tail].dma_ref)
spincnt = -spincnt;
s->dma_spinref += spincnt;
}
process_out:
s->in_use = 0;
FN_OUT(ret);
return ret;
}
/***************************************************************************************
*
* Prime Rx - Since the recieve buffer has no time limit as to when it would arrive,
* we need to prime it
*
**************************************************************************************/
void audio_prime_rx(audio_state_t * state)
{
audio_stream_t *is = state->input_stream;
FN_IN;
if (state->need_tx_for_rx) {
/*
* With some codecs like the Philips UDA1341 we must ensure
* there is an output stream at any time while recording since
* this is how the UDA1341 gets its clock from the SA1100.
* So while there is no playback data to send, the output DMA
* will spin with all zeroes. We use the cache flush special
* area for that.
*/
state->output_stream->spin_idle = 1;
audio_process_dma(state->output_stream);
}
is->pending_frags = is->nbfrags;
init_completion(&is->wfc);
is->wfc.done = 0;
is->active = 1;
audio_process_dma(is);
FN_OUT(0);
return;
}
/***************************************************************************************
*
* set the fragment size
*
**************************************************************************************/
int audio_set_fragments(audio_stream_t * s, int val)
{
FN_IN;
if (s->active)
return -EBUSY;
if (s->buffers)
audio_discard_buf(s);
s->nbfrags = (val >> 16) & 0x7FFF;
val &= 0xFFFF;
if (val < 4)
val = 4;
if (val > 15)
val = 15;
s->fragsize = 1 << val;
if (s->nbfrags < 2)
s->nbfrags = 2;
if (s->nbfrags * s->fragsize > 128 * 1024)
s->nbfrags = 128 * 1024 / s->fragsize;
FN_OUT(0);
if (audio_setup_buf(s))
return -ENOMEM;
return val | (s->nbfrags << 16);
}
/***************************************************************************************
*
* Sync up the buffers before we shutdown, else under-run errors will happen
*
**************************************************************************************/
int audio_sync(struct file *file)
{
audio_state_t *state = file->private_data;
audio_stream_t *s = state->output_stream;
audio_buf_t *b;
u_int shiftval = 0;
unsigned long flags;
DECLARE_WAITQUEUE(wait, current);
FN_IN;
if (!(file->f_mode & FMODE_WRITE) || !s->buffers || s->mapped) {
FN_OUT(1);
return 0;
}
/*
* Send current buffer if it contains data. Be sure to send
* a full sample count.
*/
b = &s->buffers[s->usr_head];
if (b->offset &= ~3) {
/* Wait for a buffer to become free */
if (wait_for_completion_interruptible(&s->wfc))
return 0;
/*
* HACK ALERT !
* To avoid increased complexity in the rest of the code
* where full fragment sizes are assumed, we cheat a little
* with the start pointer here and don't forget to restore
* it later.
*/
/* As this is a last frag we need only one dma channel
* to complete. So it's need to unlink dma channels
* to avoid empty dma work.
*/
if (!cpu_is_omap15xx() && AUDIO_QUEUE_EMPTY(s))
omap_sound_dma_unlink_lch(s);
shiftval = s->fragsize - b->offset;
b->offset = shiftval;
b->dma_addr -= shiftval;
b->data -= shiftval;
local_irq_save(flags);
s->bytecount -= shiftval;
if (++s->usr_head >= s->nbfrags)
s->usr_head = 0;
s->pending_frags++;
audio_process_dma(s);
local_irq_restore(flags);
}
/* Let's wait for all buffers to complete */
set_current_state(TASK_INTERRUPTIBLE);
add_wait_queue(&s->wq, &wait);
while ((s->pending_frags || (s->wfc.done < s->nbfrags))
&& !signal_pending(current)) {
schedule();
set_current_state(TASK_INTERRUPTIBLE);
}
set_current_state(TASK_RUNNING);
remove_wait_queue(&s->wq, &wait);
/* undo the pointer hack above */
if (shiftval) {
local_irq_save(flags);
b->dma_addr += shiftval;
b->data += shiftval;
/* ensure sane DMA code behavior if not yet processed */
if (b->offset != 0)
b->offset = s->fragsize;
local_irq_restore(flags);
}
FN_OUT(0);
return 0;
}
/***************************************************************************************
*
* Stop all the DMA channels of the stream
*
**************************************************************************************/
void audio_stop_dma(audio_stream_t * s)
{
int *chan = s->lch;
int i;
FN_IN;
if (unlikely(NULL == chan)) {
BUG();
return;
}
for (i = 0; i < nr_linked_channels; i++) {
int cur_chan = chan[i];
omap_stop_dma(cur_chan);
}
s->started = 0;
FN_OUT(0);
return;
}
/***************************************************************************************
*
* Get the dma posn
*
**************************************************************************************/
u_int audio_get_dma_pos(audio_stream_t * s)
{
audio_buf_t *b = &s->buffers[s->dma_tail];
u_int offset;
FN_IN;
if (b->dma_ref) {
offset = omap_get_dma_src_pos(s->lch[s->dma_q_head]) - b->dma_addr;
if (offset >= s->fragsize)
offset = s->fragsize - 4;
} else if (s->pending_frags) {
offset = b->offset;
} else {
offset = 0;
}
FN_OUT(offset);
return offset;
}
/***************************************************************************************
*
* Reset the audio buffers
*
**************************************************************************************/
void audio_reset(audio_stream_t * s)
{
FN_IN;
if (s->buffers) {
audio_stop_dma(s);
s->buffers[s->dma_head].offset = 0;
s->buffers[s->usr_head].offset = 0;
s->usr_head = s->dma_head;
s->pending_frags = 0;
init_completion(&s->wfc);
s->wfc.done = s->nbfrags;
}
s->active = 0;
s->stopped = 0;
s->started = 0;
FN_OUT(0);
return;
}
/***************************************************************************************
*
* Clear any pending transfers
*
**************************************************************************************/
void omap_clear_sound_dma(audio_stream_t * s)
{
FN_IN;
omap_clear_dma(s->lch[s->dma_q_head]);
FN_OUT(0);
return;
}
/***************************************************************************************
*
* DMA related functions
*
**************************************************************************************/
static int audio_set_dma_params_play(int channel, dma_addr_t dma_ptr,
u_int dma_size)
{
int dt = 0x1; /* data type 16 */
int cen = 32; /* Stereo */
int cfn = dma_size / (2 * cen);
unsigned long dest_start;
int dest_port = 0;
int sync_dev = 0;
FN_IN;
if (cpu_is_omap15xx() || cpu_is_omap16xx()) {
dest_start = AUDIO_MCBSP_DATAWRITE;
dest_port = OMAP_DMA_PORT_MPUI;
}
if (cpu_is_omap24xx()) {
dest_start = AUDIO_MCBSP_DATAWRITE;
sync_dev = AUDIO_DMA_TX;
}
omap_set_dma_dest_params(channel, dest_port, OMAP_DMA_AMODE_CONSTANT, dest_start, 0, 0);
omap_set_dma_src_params(channel, 0, OMAP_DMA_AMODE_POST_INC, dma_ptr, 0, 0);
omap_set_dma_transfer_params(channel, dt, cen, cfn, OMAP_DMA_SYNC_ELEMENT, sync_dev, 0);
FN_OUT(0);
return 0;
}
static int audio_set_dma_params_capture(int channel, dma_addr_t dma_ptr,
u_int dma_size)
{
int dt = 0x1; /* data type 16 */
int cen = 16; /* mono */
int cfn = dma_size / (2 * cen);
unsigned long src_start;
int src_port = 0;
int sync_dev = 0;
int src_sync = 0;
FN_IN;
if (cpu_is_omap15xx() || cpu_is_omap16xx()) {
src_start = AUDIO_MCBSP_DATAREAD;
src_port = OMAP_DMA_PORT_MPUI;
}
if (cpu_is_omap24xx()) {
src_start = AUDIO_MCBSP_DATAREAD;
sync_dev = AUDIO_DMA_RX;
src_sync = 1;
}
omap_set_dma_src_params(channel, src_port, OMAP_DMA_AMODE_CONSTANT, src_start, 0, 0);
omap_set_dma_dest_params(channel, 0, OMAP_DMA_AMODE_POST_INC, dma_ptr, 0, 0);
omap_set_dma_transfer_params(channel, dt, cen, cfn, OMAP_DMA_SYNC_ELEMENT, sync_dev, src_sync);
FN_OUT(0);
return 0;
}
static int audio_start_dma_chain(audio_stream_t * s)
{
int channel = s->lch[s->dma_q_head];
FN_IN;
if (!s->started) {
s->hw_stop(); /* stops McBSP Interface */
omap_start_dma(channel);
s->started = 1;
s->hw_start(); /* start McBSP interface */
}
/* else the dma itself will progress forward with out our help */
FN_OUT(0);
return 0;
}
/* Start DMA -
* Do the initial set of work to initialize all the channels as required.
* We shall then initate a transfer
*/
static int omap_start_sound_dma(audio_stream_t * s, dma_addr_t dma_ptr,
u_int dma_size)
{
int ret = -EPERM;
FN_IN;
if (unlikely(dma_size > MAX_DMA_SIZE)) {
ERR("DmaSoundDma: Start: overflowed %d-%d\n", dma_size,
MAX_DMA_SIZE);
return -EOVERFLOW;
}
if (AUDIO_QUEUE_FULL(s)) {
ret = -2;
goto sound_out;
}
if (s->input_or_output == FMODE_WRITE)
/*playback */
{
ret =
audio_set_dma_params_play(s->lch[s->dma_q_tail], dma_ptr,
dma_size);
} else {
ret =
audio_set_dma_params_capture(s->lch[s->dma_q_tail], dma_ptr,
dma_size);
}
if (ret != 0) {
ret = -2; /* indicate queue full */
goto sound_out;
}
AUDIO_INCREMENT_TAIL(s);
ret = audio_start_dma_chain(s);
if (ret) {
ERR("dma start failed");
}
sound_out:
FN_OUT(ret);
return ret;
}
/***************************************************************************************
*
* ISR related functions
*
**************************************************************************************/
/* The work item handler */
static void audio_dsr_handler(unsigned long inData)
{
void *data = (void *)inData;
struct audio_isr_work_item *work = data;
audio_stream_t *s = (work->s);
int sound_curr_lch = work->current_lch;
u16 ch_status = work->ch_status;
FN_IN;
DPRINTK("lch=%d,status=0x%x, data=%p as=%p\n", sound_curr_lch,
ch_status, data, s);
if (AUDIO_QUEUE_EMPTY(s)) {
ERR("Interrupt(%d) for empty queue(h=%d, T=%d)???\n",
sound_curr_lch, s->dma_q_head, s->dma_q_tail);
ERR("nbfrag=%d,pendfrags=%d,USR-H=%d, QH-%d QT-%d\n",
s->nbfrags, s->pending_frags, s->usr_head, s->dma_head,
s->dma_tail);
FN_OUT(-1);
return;
}
AUDIO_INCREMENT_HEAD(s); /* Empty the queue */
/* Try to fill again */
audio_dma_callback(sound_curr_lch, ch_status, s);
FN_OUT(0);
}
/* Macro to trace the IRQ calls - checks for multi-channel irqs */
//#define IRQ_TRACE
#ifdef IRQ_TRACE
#define MAX_UP 10
static char xyz[MAX_UP] = { 0 };
static int h = 0;
#endif
/* ISRs have to be short and smart.. So we transfer every heavy duty stuff to the
* work item
*/
static void sound_dma_irq_handler(int sound_curr_lch, u16 ch_status, void *data)
{
int dma_status = ch_status;
audio_stream_t *s = (audio_stream_t *) data;
FN_IN;
#ifdef IRQ_TRACE
xyz[h++] = '0' + sound_curr_lch;
if (h == MAX_UP - 1) {
printk("%s-", xyz);
h = 0;
}
#endif
DPRINTK("lch=%d,status=0x%x, dma_status=%d, data=%p\n", sound_curr_lch,
ch_status, dma_status, data);
if (dma_status & (DCSR_ERROR)) {
if (cpu_is_omap15xx() || cpu_is_omap16xx())
omap_stop_dma(sound_curr_lch);
ERR("DCSR_ERROR!\n");
FN_OUT(-1);
return;
}
if (AUDIO_QUEUE_LAST(s))
audio_stop_dma(s);
/* Start the work item - we ping pong the work items */
if (!work_item_running) {
work1.current_lch = sound_curr_lch;
work1.ch_status = ch_status;
work1.s = s;
/* schedule tasklet 1 */
tasklet_schedule(&audio_isr_work1);
work_item_running = 1;
} else {
work2.current_lch = sound_curr_lch;
work2.ch_status = ch_status;
work2.s = s;
/* schedule tasklet 2 */
tasklet_schedule(&audio_isr_work2);
work_item_running = 0;
}
FN_OUT(0);
return;
}
/* The call back that handles buffer stuff */
static void audio_dma_callback(int lch, u16 ch_status, void *data)
{
audio_stream_t *s = data;
audio_buf_t *b = &s->buffers[s->dma_tail];
FN_IN;
if (s->dma_spinref > 0) {
s->dma_spinref--;
} else if (!s->buffers) {
printk(KERN_CRIT
"omap_audio: received DMA IRQ for non existent buffers!\n");
return;
} else if (b->dma_ref && --b->dma_ref == 0 && b->offset >= s->fragsize) {
/* This fragment is done */
b->offset = 0;
s->bytecount += s->fragsize;
s->fragcount++;
s->dma_spinref = -s->dma_spinref;
if (++s->dma_tail >= s->nbfrags)
s->dma_tail = 0;
if (!s->mapped)
complete(&s->wfc);
else
s->pending_frags++;
wake_up(&s->wq);
}
audio_process_dma(s);
FN_OUT(0);
return;
}
/*********************************************************************************
*
* audio_get_dma_callback(): return the dma interface call back function
*
*********************************************************************************/
dma_callback_t audio_get_dma_callback(void)
{
FN_IN;
FN_OUT(0);
return audio_dma_callback;
}
static int __init audio_dma_init(void)
{
if (!cpu_is_omap15xx())
nr_linked_channels = 2;
return 0;
}
static void __exit audio_dma_exit(void)
{
/* Nothing */
}
module_init(audio_dma_init);
module_exit(audio_dma_exit);
MODULE_AUTHOR("Texas Instruments");
MODULE_DESCRIPTION("Common DMA handling for Audio driver on OMAP processors");
MODULE_LICENSE("GPL");
EXPORT_SYMBOL(omap_clear_sound_dma);
EXPORT_SYMBOL(omap_request_sound_dma);
EXPORT_SYMBOL(omap_free_sound_dma);
EXPORT_SYMBOL(audio_get_dma_callback);
EXPORT_SYMBOL(audio_setup_buf);
EXPORT_SYMBOL(audio_process_dma);
EXPORT_SYMBOL(audio_prime_rx);
EXPORT_SYMBOL(audio_set_fragments);
EXPORT_SYMBOL(audio_sync);
EXPORT_SYMBOL(audio_stop_dma);
EXPORT_SYMBOL(audio_get_dma_pos);
EXPORT_SYMBOL(audio_reset);
EXPORT_SYMBOL(audio_discard_buf);
/*
* linux/sound/oss/omap-audio-dma-intfc.h
*
* Common audio DMA handling for the OMAP processors
*
* Copyright (C) 2004 Texas Instruments, Inc.
*
* Copyright (C) 2000, 2001 Nicolas Pitre <nico@cam.org>
*
* This package is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*
* History:
*
* 2004/08/12 Nishanth Menon - Modified to integrate Audio requirements on 1610,1710 platforms
*/
#ifndef __OMAP_AUDIO_DMA_INTFC_H
#define __OMAP_AUDIO_DMA_INTFC_H
/************************** INCLUDES *************************************/
/* Requires omap-audio.h */
#include "omap-audio.h"
/************************** GLOBAL MACROS *************************************/
/* Provide the Macro interfaces common across platforms */
#define DMA_REQUEST(e,s, cb) {e=omap_request_sound_dma(s->dma_dev, s->id, s, &s->lch);}
#define DMA_FREE(s) omap_free_sound_dma(s, &s->lch)
#define DMA_CLEAR(s) omap_clear_sound_dma(s)
/************************** GLOBAL DATA STRUCTURES *********************************/
typedef void (*dma_callback_t) (int lch, u16 ch_status, void *data);
/************************** GLOBAL FUNCTIONS ***************************************/
dma_callback_t audio_get_dma_callback(void);
int audio_setup_buf(audio_stream_t * s);
int audio_process_dma(audio_stream_t * s);
void audio_prime_rx(audio_state_t * state);
int audio_set_fragments(audio_stream_t * s, int val);
int audio_sync(struct file *file);
void audio_stop_dma(audio_stream_t * s);
u_int audio_get_dma_pos(audio_stream_t * s);
void audio_reset(audio_stream_t * s);
void audio_discard_buf(audio_stream_t * s);
/**************** ARCH SPECIFIC FUNCIONS *******************************************/
void omap_clear_sound_dma(audio_stream_t * s);
int omap_request_sound_dma(int device_id, const char *device_name, void *data,
int **channels);
int omap_free_sound_dma(void *data, int **channels);
#endif /* #ifndef __OMAP_AUDIO_DMA_INTFC_H */
/*
* linux/sound/oss/omap-audio-tsc2101.c
*
* Glue driver for TSC2101 for OMAP processors
*
* Copyright (C) 2004 Texas Instruments, Inc.
*
* This package is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*
* History:
* -------
* 2004-08-12 Nishanth Menon - Modified to integrate Audio requirements on 1610,1710 platforms.
* 2004-09-14 Sriram Kannan - Added /proc support for asynchronous starting/stopping the codec
* (without affecting the normal driver flow).
* 2004-11-04 Nishanth Menon - Support for power management
* 2004-11-07 Nishanth Menon - Support for Common TSC access b/w Touchscreen and audio drivers
*/
/***************************** INCLUDES ************************************/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include <linux/pm.h>
#include <linux/errno.h>
#include <linux/sound.h>
#include <linux/soundcard.h>
#include <linux/mutex.h>
#include <asm/uaccess.h>
#include <mach/hardware.h>
#include <mach/dma.h>
#include <asm/io.h>
#include <mach/mux.h>
#include <mach/io.h>
#include <asm/mach-types.h>
#include "omap-audio.h"
#include "omap-audio-dma-intfc.h"
#include <mach/mcbsp.h>
#ifdef CONFIG_ARCH_OMAP16XX
#include <../drivers/ssi/omap-uwire.h>
#include <mach/dsp_common.h>
#elif defined(CONFIG_ARCH_OMAP24XX)
#else
#error "Unsupported configuration"
#endif
#include <asm/hardware/tsc2101.h>
#include <../drivers/ssi/omap-tsc2101.h>
/***************************** MACROS ************************************/
#define PROC_SUPPORT
#ifdef PROC_SUPPORT
#include <linux/proc_fs.h>
#define PROC_START_FILE "driver/tsc2101-audio-start"
#define PROC_STOP_FILE "driver/tsc2101-audio-stop"
#endif
#define CODEC_NAME "TSC2101"
#ifdef CONFIG_ARCH_OMAP16XX
#define PLATFORM_NAME "OMAP16XX"
#elif defined(CONFIG_ARCH_OMAP24XX)
#define PLATFORM_NAME "OMAP2"
#endif
/* Define to set the tsc as the master w.r.t McBSP */
#define TSC_MASTER
/*
* AUDIO related MACROS
*/
#define DEFAULT_BITPERSAMPLE 16
#define AUDIO_RATE_DEFAULT 44100
#define PAGE2_AUDIO_CODEC_REGISTERS (2)
#define LEAVE_CS 0x80
/* Select the McBSP For Audio */
/* 16XX is MCBSP1 and 24XX is MCBSP2*/
/* see include/asm-arm/arch-omap/mcbsp.h */
#ifndef AUDIO_MCBSP
#error "UnSupported Configuration"
#endif
#define REC_MASK (SOUND_MASK_LINE | SOUND_MASK_MIC)
#define DEV_MASK (REC_MASK | SOUND_MASK_VOLUME)
#define SET_VOLUME 1
#define SET_LINE 2
#define SET_MIC 3
#define SET_RECSRC 4
#define DEFAULT_VOLUME 93
#define DEFAULT_INPUT_VOLUME 20 /* An minimal volume */
/* Tsc Audio Specific */
#define NUMBER_SAMPLE_RATES_SUPPORTED 16
#define OUTPUT_VOLUME_MIN 0x7F
#define OUTPUT_VOLUME_MAX 0x32
#define OUTPUT_VOLUME_RANGE (OUTPUT_VOLUME_MIN - OUTPUT_VOLUME_MAX)
#define OUTPUT_VOLUME_MASK OUTPUT_VOLUME_MIN
#define DEFAULT_VOLUME_LEVEL OUTPUT_VOLUME_MAX
/* use input vol of 75 for 0dB gain */
#define INPUT_VOLUME_MIN 0x0
#define INPUT_VOLUME_MAX 0x7D
#define INPUT_VOLUME_RANGE (INPUT_VOLUME_MAX - INPUT_VOLUME_MIN)
#define INPUT_VOLUME_MASK INPUT_VOLUME_MAX
/*********** Debug Macros ********/
/* To Generate a rather shrill tone -test the entire path */
//#define TONE_GEN
/* To Generate a tone for each keyclick - test the tsc,spi paths*/
//#define TEST_KEYCLICK
/* To dump the tsc registers for debug */
//#define TSC_DUMP_REGISTERS
#ifdef DPRINTK
#undef DPRINTK
#endif
#undef DEBUG
//#define DEBUG
#ifdef DEBUG
#define DPRINTK(ARGS...) printk(KERN_INFO "<%s>: ",__FUNCTION__);printk(ARGS)
#define FN_IN printk(KERN_INFO "[%s]: start\n", __FUNCTION__)
#define FN_OUT(n) printk(KERN_INFO "[%s]: end(%u)\n",__FUNCTION__, n)
#else
#define DPRINTK( x... )
#define FN_IN
#define FN_OUT(n)
#endif
/***************************** Data Structures **********************************/
static int audio_ifc_start(void)
{
omap_mcbsp_start(AUDIO_MCBSP);
return 0;
}
static int audio_ifc_stop(void)
{
omap_mcbsp_stop(AUDIO_MCBSP);
return 0;
}
static audio_stream_t output_stream = {
.id = "TSC2101 out",
.dma_dev = AUDIO_DMA_TX,
.input_or_output = FMODE_WRITE,
.hw_start = audio_ifc_start,
.hw_stop = audio_ifc_stop,
};
static audio_stream_t input_stream = {
.id = "TSC2101 in",
.dma_dev = AUDIO_DMA_RX,
.input_or_output = FMODE_READ,
.hw_start = audio_ifc_start,
.hw_stop = audio_ifc_stop,
};
static int audio_dev_id, mixer_dev_id;
typedef struct {
u8 volume;
u8 line;
u8 mic;
int recsrc;
int mod_cnt;
} tsc2101_local_info;
static tsc2101_local_info tsc2101_local = {
volume: DEFAULT_VOLUME,
line: DEFAULT_INPUT_VOLUME,
mic: DEFAULT_INPUT_VOLUME,
recsrc: SOUND_MASK_LINE,
mod_cnt: 0
};
struct sample_rate_reg_info {
u16 sample_rate;
u8 divisor;
u8 fs_44kHz; /* if 0 48 khz, if 1 44.1 khz fsref */
};
/* To Store the default sample rate */
static long audio_samplerate = AUDIO_RATE_DEFAULT;
static const struct sample_rate_reg_info
reg_info[NUMBER_SAMPLE_RATES_SUPPORTED] = {
/* Div 1 */
{48000, 0, 0},
{44100, 0, 1},
/* Div 1.5 */
{32000, 1, 0},
{29400, 1, 1},
/* Div 2 */
{24000, 2, 0},
{22050, 2, 1},
/* Div 3 */
{16000, 3, 0},
{14700, 3, 1},
/* Div 4 */
{12000, 4, 0},
{11025, 4, 1},
/* Div 5 */
{9600, 5, 0},
{8820, 5, 1},
/* Div 5.5 */
{8727, 6, 0},
{8018, 6, 1},
/* Div 6 */
{8000, 7, 0},
{7350, 7, 1},
};
static struct omap_mcbsp_reg_cfg initial_config = {
.spcr2 = FREE | FRST | GRST | XRST | XINTM(3),
.spcr1 = RINTM(3) | RRST,
.rcr2 = RPHASE | RFRLEN2(OMAP_MCBSP_WORD_8) |
RWDLEN2(OMAP_MCBSP_WORD_16) | RDATDLY(1),
.rcr1 = RFRLEN1(OMAP_MCBSP_WORD_8) | RWDLEN1(OMAP_MCBSP_WORD_16),
.xcr2 = XPHASE | XFRLEN2(OMAP_MCBSP_WORD_8) |
XWDLEN2(OMAP_MCBSP_WORD_16) | XDATDLY(1) | XFIG,
.xcr1 = XFRLEN1(OMAP_MCBSP_WORD_8) | XWDLEN1(OMAP_MCBSP_WORD_16),
.srgr1 = FWID(15),
.srgr2 = GSYNC | CLKSP | FSGM | FPER(31),
/* platform specific initialization */
#ifdef CONFIG_MACH_OMAP_H2
.pcr0 = CLKXM | CLKRM | FSXP | FSRP | CLKXP | CLKRP,
#elif defined(CONFIG_MACH_OMAP_H3) || defined(CONFIG_MACH_OMAP_H4) || defined(CONFIG_MACH_OMAP_APOLLON)
#ifndef TSC_MASTER
.pcr0 = FSXM | FSRM | CLKXM | CLKRM | CLKXP | CLKRP,
#else
.pcr0 = CLKRM | SCLKME | FSXP | FSRP | CLKXP | CLKRP,
#endif /* tsc Master defs */
#endif /* platform specific inits */
};
/***************************** MODULES SPECIFIC FUNCTION PROTOTYPES ********************/
static void omap_tsc2101_initialize(void *dummy);
static void omap_tsc2101_shutdown(void *dummy);
static int omap_tsc2101_ioctl(struct inode *inode, struct file *file,
uint cmd, ulong arg);
static int omap_tsc2101_probe(void);
static void omap_tsc2101_remove(void);
static int omap_tsc2101_suspend(void);
static int omap_tsc2101_resume(void);
static void tsc2101_configure(void);
static int mixer_open(struct inode *inode, struct file *file);
static int mixer_release(struct inode *inode, struct file *file);
static int mixer_ioctl(struct inode *inode, struct file *file, uint cmd,
ulong arg);
#ifdef TEST_KEYCLICK
void tsc2101_testkeyclick(void);
#endif
#ifdef TONE_GEN
void toneGen(void);
#endif
#ifdef TSC_DUMP_REGISTERS
static void tsc2101_dumpRegisters(void);
#endif
#ifdef PROC_SUPPORT
static int codec_start(char *buf, char **start, off_t offset, int count,
int *eof, void *data);
static int codec_stop(char *buf, char **start, off_t offset, int count,
int *eof, void *data);
static void tsc2101_start(void);
#endif
/******************** DATA STRUCTURES USING FUNCTION POINTERS **************************/
/* File Op structure for mixer */
static struct file_operations omap_mixer_fops = {
.open = mixer_open,
.release = mixer_release,
.ioctl = mixer_ioctl,
.owner = THIS_MODULE
};
/* To store characteristic info regarding the codec for the audio driver */
static audio_state_t tsc2101_state = {
.output_stream = &output_stream,
.input_stream = &input_stream,
/* .need_tx_for_rx = 1, //Once the Full Duplex works */
.need_tx_for_rx = 0,
.hw_init = omap_tsc2101_initialize,
.hw_shutdown = omap_tsc2101_shutdown,
.client_ioctl = omap_tsc2101_ioctl,
.hw_probe = omap_tsc2101_probe,
.hw_remove = omap_tsc2101_remove,
.hw_suspend = omap_tsc2101_suspend,
.hw_resume = omap_tsc2101_resume,
};
/* This will be defined in the Audio.h */
static struct file_operations *omap_audio_fops;
/***************************** MODULES SPECIFIC FUNCTIONs *******************************/
/*********************************************************************************
*
* Simplified write for tsc Audio
*
*********************************************************************************/
static __inline__ void audio_tsc2101_write(u8 address, u16 data)
{
omap_tsc2101_write(PAGE2_AUDIO_CODEC_REGISTERS, address, data);
}
/*********************************************************************************
*
* Simplified read for tsc Audio
*
*********************************************************************************/
static __inline__ u16 audio_tsc2101_read(u8 address)
{
return (omap_tsc2101_read(PAGE2_AUDIO_CODEC_REGISTERS, address));
}
/*********************************************************************************
*
* tsc2101_update()
* Volume Adj etc
*
********************************************************************************/
static int tsc2101_update(int flag, int val)
{
u16 volume;
u16 data;
FN_IN;
switch (flag) {
case SET_VOLUME:
if (val < 0 || val > 100) {
printk(KERN_ERR "Trying a bad volume value(%d)!\n", val);
return -EPERM;
}
/* Convert 0 -> 100 volume to 0x7F(min) -> y(max) volume range */
volume =
((val * OUTPUT_VOLUME_RANGE) / 100) + OUTPUT_VOLUME_MAX;
/* invert the value for getting the proper range 0 min and 100 max */
volume = OUTPUT_VOLUME_MIN - volume;
data = audio_tsc2101_read(TSC2101_DAC_GAIN_CTRL);
data &=
~(DGC_DALVL(OUTPUT_VOLUME_MIN) |
DGC_DARVL(OUTPUT_VOLUME_MIN));
data |= DGC_DALVL(volume) | DGC_DARVL(volume);
audio_tsc2101_write(TSC2101_DAC_GAIN_CTRL, data);
data = audio_tsc2101_read(TSC2101_DAC_GAIN_CTRL);
break;
case SET_LINE:
if (val < 0 || val > 100) {
printk(KERN_ERR "Trying a bad volume value(%d)!\n", val);
return -EPERM;
}
/* Convert 0 -> 100 volume to 0x0(min) -> 0x7D(max) volume range */
/* NOTE: 0 is minimum volume and not mute */
volume = ((val * INPUT_VOLUME_RANGE) / 100) + INPUT_VOLUME_MIN;
/* Handset Input not muted, AGC for Handset In off */
audio_tsc2101_write(TSC2101_HEADSET_GAIN_CTRL,
HGC_ADPGA_HED(volume));
break;
case SET_MIC:
if (val < 0 || val > 100) {
printk(KERN_ERR "Trying a bad volume value(%d)!\n", val);
return -EPERM;
}
/* Convert 0 -> 100 volume to 0x0(min) -> 0x7D(max) volume range */
/* NOTE: 0 is minimum volume and not mute */
volume = ((val * INPUT_VOLUME_RANGE) / 100) + INPUT_VOLUME_MIN;
/* Handset Input not muted, AGC for Handset In off */
audio_tsc2101_write(TSC2101_HANDSET_GAIN_CTRL,
HNGC_ADPGA_HND(volume));
break;
case SET_RECSRC:
/*
* If more than one recording device selected,
* disable the device that is currently in use.
*/
if (hweight32(val) > 1)
val &= ~tsc2101_local.recsrc;
data = audio_tsc2101_read(TSC2101_MIXER_PGA_CTRL);
data &= ~MPC_MICSEL(7); /* clear all MICSEL bits */
if (val == SOUND_MASK_MIC) {
data |= MPC_MICSEL(1);
audio_tsc2101_write(TSC2101_MIXER_PGA_CTRL, data);
}
else if (val == SOUND_MASK_LINE) {
data |= MPC_MICSEL(0);
audio_tsc2101_write(TSC2101_MIXER_PGA_CTRL, data);
}
else {
printk(KERN_WARNING "omap1610-tsc2101: Wrong RECSRC"
" value specified\n");
return -EINVAL;
}
tsc2101_local.recsrc = val;
break;
default:
printk(KERN_WARNING "omap1610-tsc2101: Wrong tsc2101_update "
"flag specified\n");
break;
}
FN_OUT(0);
return 0;
}
/*********************************************************************************
*
* mixer_open()
*
********************************************************************************/
static int mixer_open(struct inode *inode, struct file *file)
{
/* Any mixer specific initialization */
/* Initalize the tsc2101 */
omap_tsc2101_enable();
return 0;
}
/*********************************************************************************
*
* mixer_release()
*
********************************************************************************/
static int mixer_release(struct inode *inode, struct file *file)
{
/* Any mixer specific Un-initialization */
omap_tsc2101_disable();
return 0;
}
/*********************************************************************************
*
* mixer_ioctl()
*
********************************************************************************/
static int
mixer_ioctl(struct inode *inode, struct file *file, uint cmd, ulong arg)
{
int val;
int gain;
int ret = 0;
int nr = _IOC_NR(cmd);
/*
* We only accept mixer (type 'M') ioctls.
*/
FN_IN;
if (_IOC_TYPE(cmd) != 'M')
return -EINVAL;
DPRINTK(" 0x%08x\n", cmd);
if (cmd == SOUND_MIXER_INFO) {
struct mixer_info mi;
strncpy(mi.id, "TSC2101", sizeof(mi.id));
strncpy(mi.name, "TI TSC2101", sizeof(mi.name));
mi.modify_counter = tsc2101_local.mod_cnt;
FN_OUT(1);
return copy_to_user((void __user *)arg, &mi, sizeof(mi));
}
if (_IOC_DIR(cmd) & _IOC_WRITE) {
ret = get_user(val, (int __user *)arg);
if (ret)
goto out;
/* Ignore separate left/right channel for now,
* even the codec does support it.
*/
gain = val & 255;
switch (nr) {
case SOUND_MIXER_VOLUME:
tsc2101_local.volume = val;
tsc2101_local.mod_cnt++;
ret = tsc2101_update(SET_VOLUME, gain);
break;
case SOUND_MIXER_LINE:
tsc2101_local.line = val;
tsc2101_local.mod_cnt++;
ret = tsc2101_update(SET_LINE, gain);
break;
case SOUND_MIXER_MIC:
tsc2101_local.mic = val;
tsc2101_local.mod_cnt++;
ret = tsc2101_update(SET_MIC, gain);
break;
case SOUND_MIXER_RECSRC:
if ((val & SOUND_MASK_LINE) ||
(val & SOUND_MASK_MIC)) {
if (tsc2101_local.recsrc != val) {
tsc2101_local.mod_cnt++;
tsc2101_update(SET_RECSRC, val);
}
}
else {
ret = -EINVAL;
}
break;
default:
ret = -EINVAL;
}
}
if (ret == 0 && _IOC_DIR(cmd) & _IOC_READ) {
ret = 0;
switch (nr) {
case SOUND_MIXER_VOLUME:
val = tsc2101_local.volume;
val = (tsc2101_local.volume << 8) |
tsc2101_local.volume;
break;
case SOUND_MIXER_LINE:
val = (tsc2101_local.line << 8) |
tsc2101_local.line;
break;
case SOUND_MIXER_MIC:
val = (tsc2101_local.mic << 8) |
tsc2101_local.mic;
break;
case SOUND_MIXER_RECSRC:
val = tsc2101_local.recsrc;
break;
case SOUND_MIXER_RECMASK:
val = REC_MASK;
break;
case SOUND_MIXER_DEVMASK:
val = DEV_MASK;
break;
case SOUND_MIXER_CAPS:
val = 0;
break;
case SOUND_MIXER_STEREODEVS:
val = SOUND_MASK_VOLUME;
break;
default:
val = 0;
printk(KERN_WARNING "omap1610-tsc2101: unknown mixer "
"read ioctl flag specified\n");
ret = -EINVAL;
break;
}
if (ret == 0)
ret = put_user(val, (int __user *)arg);
}
out:
FN_OUT(0);
return ret;
}
/*********************************************************************************
*
* omap_set_samplerate()
*
********************************************************************************/
static int omap_set_samplerate(long sample_rate)
{
u8 count = 0;
u16 data = 0;
int clkgdv = 0;
/* wait for any frame to complete */
udelay(125);
/* Search for the right sample rate */
while ((reg_info[count].sample_rate != sample_rate) &&
(count < NUMBER_SAMPLE_RATES_SUPPORTED)) {
count++;
}
if (count == NUMBER_SAMPLE_RATES_SUPPORTED) {
printk(KERN_ERR "Invalid Sample Rate %d requested\n",
(int)sample_rate);
return -EPERM;
}
/* Set AC1 */
data = audio_tsc2101_read(TSC2101_AUDIO_CTRL_1);
/*Clear prev settings */
data &= ~(AC1_DACFS(0x07) | AC1_ADCFS(0x07));
data |=
AC1_DACFS(reg_info[count].divisor) | AC1_ADCFS(reg_info[count].
divisor);
audio_tsc2101_write(TSC2101_AUDIO_CTRL_1, data);
/* Set the AC3 */
data = audio_tsc2101_read(TSC2101_AUDIO_CTRL_3);
/*Clear prev settings */
data &= ~(AC3_REFFS | AC3_SLVMS);
data |= (reg_info[count].fs_44kHz) ? AC3_REFFS : 0;
#ifdef TSC_MASTER
data |= AC3_SLVMS;
#endif /* #ifdef TSC_MASTER */
audio_tsc2101_write(TSC2101_AUDIO_CTRL_3, data);
/* program the PLLs */
if (reg_info[count].fs_44kHz) {
/* 44.1 khz - 12 MHz Mclk */
audio_tsc2101_write(TSC2101_PLL_PROG_1, PLL1_PLLSEL | PLL1_PVAL(1) | PLL1_I_VAL(7)); /* PVAL 1; I_VAL 7 */
audio_tsc2101_write(TSC2101_PLL_PROG_2, PLL2_D_VAL(0x1490)); /* D_VAL 5264 */
} else {
/* 48 khz - 12 Mhz Mclk */
audio_tsc2101_write(TSC2101_PLL_PROG_1, PLL1_PLLSEL | PLL1_PVAL(1) | PLL1_I_VAL(8)); /* PVAL 1; I_VAL 8 */
audio_tsc2101_write(TSC2101_PLL_PROG_2, PLL2_D_VAL(0x780)); /* D_VAL 1920 */
}
audio_samplerate = sample_rate;
/* Set the sample rate */
#ifndef TSC_MASTER
clkgdv =
DEFAULT_MCBSP_CLOCK / (sample_rate *
(DEFAULT_BITPERSAMPLE * 2 - 1));
if (clkgdv)
initial_config.srgr1 =
(FWID(DEFAULT_BITPERSAMPLE - 1) | CLKGDV(clkgdv));
else
return (1);
/* Stereo Mode */
initial_config.srgr2 =
(CLKSM | FSGM | FPER(DEFAULT_BITPERSAMPLE * 2 - 1));
#else
initial_config.srgr1 =
(FWID(DEFAULT_BITPERSAMPLE - 1) | CLKGDV(clkgdv));
initial_config.srgr2 =
((GSYNC | CLKSP | FSGM | FPER(DEFAULT_BITPERSAMPLE * 2 - 1)));
#endif /* end of #ifdef TSC_MASTER */
omap_mcbsp_config(AUDIO_MCBSP, &initial_config);
return 0;
}
/*********************************************************************************
*
* omap_tsc2101_initialize() [hw_init() ]
*
********************************************************************************/
static void omap_tsc2101_initialize(void *dummy)
{
DPRINTK("omap_tsc2101_initialize entry\n");
/* initialize with default sample rate */
audio_samplerate = AUDIO_RATE_DEFAULT;
omap_mcbsp_request(AUDIO_MCBSP);
/* if configured, then stop mcbsp */
omap_mcbsp_stop(AUDIO_MCBSP);
omap_tsc2101_enable();
omap_mcbsp_config(AUDIO_MCBSP, &initial_config);
omap_mcbsp_start(AUDIO_MCBSP);
tsc2101_configure();
#ifdef TEST_KEYCLICK
tsc2101_testkeyclick();
#endif
#ifdef TONE_GEN
toneGen();
#endif
DPRINTK("omap_tsc2101_initialize exit\n");
}
/*********************************************************************************
*
* omap_tsc2101_shutdown() [hw_shutdown() ]
*
********************************************************************************/
static void omap_tsc2101_shutdown(void *dummy)
{
/*
Turn off codec after it is done.
Can't do it immediately, since it may still have
buffered data.
Wait 20ms (arbitrary value) and then turn it off.
*/
FN_IN;
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout(2);
omap_mcbsp_stop(AUDIO_MCBSP);
omap_mcbsp_free(AUDIO_MCBSP);
audio_tsc2101_write(TSC2101_CODEC_POWER_CTRL,
~(CPC_SP1PWDN | CPC_SP2PWDN | CPC_BASSBC));
omap_tsc2101_disable();
FN_OUT(0);
}
/*********************************************************************************
*
* tsc2101_configure
*
********************************************************************************/
static void tsc2101_configure(void)
{
FN_IN;
audio_tsc2101_write(TSC2101_CODEC_POWER_CTRL, 0x0000);
/*Mute Analog Sidetone */
/*Select MIC_INHED input for headset */
/*Cell Phone In not connected */
audio_tsc2101_write(TSC2101_MIXER_PGA_CTRL,
MPC_ASTMU | MPC_ASTG(0x40) | MPC_MICADC);
/* Set record source */
tsc2101_update(SET_RECSRC, tsc2101_local.recsrc);
/* ADC, DAC, Analog Sidetone, cellphone, buzzer softstepping enabled */
/* 1dB AGC hysteresis */
/* MICes bias 2V */
audio_tsc2101_write(TSC2101_AUDIO_CTRL_4, AC4_MB_HED(0));
/* Set codec output volume */
audio_tsc2101_write(TSC2101_DAC_GAIN_CTRL, 0x0000);
/* DAC left and right routed to SPK2 */
/* SPK1/2 unmuted */
audio_tsc2101_write(TSC2101_AUDIO_CTRL_5,
AC5_DAC2SPK1(3) | AC5_AST2SPK1 | AC5_KCL2SPK1 |
AC5_DAC2SPK2(3) | AC5_AST2SPK2 | AC5_KCL2SPK2 |
AC5_HDSCPTC);
/* OUT8P/N muted, CPOUT muted */
audio_tsc2101_write(TSC2101_AUDIO_CTRL_6,
AC6_MUTLSPK | AC6_MUTSPK2 | AC6_LDSCPTC |
AC6_VGNDSCPTC);
/* Headset/Hook switch detect disabled */
audio_tsc2101_write(TSC2101_AUDIO_CTRL_7, 0x0000);
/* Left line input volume control */
tsc2101_update(SET_LINE, tsc2101_local.line);
/* mic input volume control */
tsc2101_update(SET_MIC, tsc2101_local.mic);
/* Left/Right headphone channel volume control */
/* Zero-cross detect on */
tsc2101_update(SET_VOLUME, tsc2101_local.volume);
/* clock configuration */
omap_set_samplerate(audio_samplerate);
#ifdef TSC_DUMP_REGISTERS
tsc2101_dumpRegisters();
#endif
FN_OUT(0);
}
#ifdef PROC_SUPPORT
static void tsc2101_start(void)
{
FN_IN;
audio_tsc2101_write(TSC2101_CODEC_POWER_CTRL, 0x0000);
/*Mute Analog Sidetone */
/*Select MIC_INHED input for headset */
/*Cell Phone In not connected */
audio_tsc2101_write(TSC2101_MIXER_PGA_CTRL,
MPC_ASTMU | MPC_ASTG(0x40) | MPC_MICADC);
/* Set record source */
tsc2101_update(SET_RECSRC, tsc2101_local.recsrc);
/* ADC, DAC, Analog Sidetone, cellphone, buzzer softstepping enabled */
/* 1dB AGC hysteresis */
/* MICes bias 2V */
audio_tsc2101_write(TSC2101_AUDIO_CTRL_4, AC4_MB_HED(0));
/* Set codec output volume */
audio_tsc2101_write(TSC2101_DAC_GAIN_CTRL, 0x0000);
/* DAC left and right routed to SPK2 */
/* SPK1/2 unmuted */
audio_tsc2101_write(TSC2101_AUDIO_CTRL_5,
AC5_DAC2SPK1(3) | AC5_AST2SPK1 | AC5_KCL2SPK1 |
AC5_DAC2SPK2(3) | AC5_AST2SPK2 | AC5_KCL2SPK2 |
AC5_HDSCPTC);
/* OUT8P/N muted, CPOUT muted */
audio_tsc2101_write(TSC2101_AUDIO_CTRL_6,
AC6_MUTLSPK | AC6_MUTSPK2 | AC6_LDSCPTC |
AC6_VGNDSCPTC);
/* Headset/Hook switch detect disabled */
audio_tsc2101_write(TSC2101_AUDIO_CTRL_7, 0x0000);
/* Left line input volume control */
tsc2101_update(SET_LINE, tsc2101_local.line);
/* mic input volume control */
tsc2101_update(SET_MIC, tsc2101_local.mic);
/* Left/Right headphone channel volume control */
/* Zero-cross detect on */
tsc2101_update(SET_VOLUME, tsc2101_local.volume);
FN_OUT(0);
}
#endif
/******************************************************************************************
*
* All generic ioctl's are handled by audio_ioctl() [File: omap-audio.c]. This
* routine handles some platform specific ioctl's
*
******************************************************************************************/
static int
omap_tsc2101_ioctl(struct inode *inode, struct file *file, uint cmd, ulong arg)
{
long val;
int ret = 0;
DPRINTK(" 0x%08x\n", cmd);
/*
* These are platform dependent ioctls which are not handled by the
* generic omap-audio module.
*/
switch (cmd) {
case SNDCTL_DSP_STEREO:
ret = get_user(val, (int __user *)arg);
if (ret)
return ret;
/* the AIC23 is stereo only */
ret = (val == 0) ? -EINVAL : 1;
FN_OUT(1);
return put_user(ret, (int __user *)arg);
case SNDCTL_DSP_CHANNELS:
case SOUND_PCM_READ_CHANNELS:
/* the AIC23 is stereo only */
FN_OUT(2);
return put_user(2, (long __user *)arg);
case SNDCTL_DSP_SPEED:
ret = get_user(val, (long __user *)arg);
if (ret)
break;
ret = omap_set_samplerate(val);
if (ret)
break;
/* fall through */
case SOUND_PCM_READ_RATE:
FN_OUT(3);
return put_user(audio_samplerate, (long __user *)arg);
case SOUND_PCM_READ_BITS:
case SNDCTL_DSP_SETFMT:
case SNDCTL_DSP_GETFMTS:
/* we can do 16-bit only */
FN_OUT(4);
return put_user(AFMT_S16_LE, (long __user *)arg);
default:
/* Maybe this is meant for the mixer (As per OSS Docs) */
FN_OUT(5);
return mixer_ioctl(inode, file, cmd, arg);
}
FN_OUT(0);
return ret;
}
/*********************************************************************************
*
* module_probe for TSC2101
*
********************************************************************************/
static int omap_tsc2101_probe(void)
{
FN_IN;
/* Get the fops from audio oss driver */
if (!(omap_audio_fops = audio_get_fops())) {
printk(KERN_ERR "Unable to Get the FOPs of Audio OSS driver\n");
audio_unregister_codec(&tsc2101_state);
return -EPERM;
}
/* register devices */
audio_dev_id = register_sound_dsp(omap_audio_fops, -1);
mixer_dev_id = register_sound_mixer(&omap_mixer_fops, -1);
#ifdef PROC_SUPPORT
create_proc_read_entry(PROC_START_FILE, 0 /* default mode */ ,
NULL /* parent dir */ ,
codec_start, NULL /* client data */ );
create_proc_read_entry(PROC_STOP_FILE, 0 /* default mode */ ,
NULL /* parent dir */ ,
codec_stop, NULL /* client data */ );
#endif
/* Announcement Time */
printk(KERN_INFO PLATFORM_NAME " " CODEC_NAME
" Audio support initialized\n");
FN_OUT(0);
return 0;
}
/*********************************************************************************
*
* Module Remove for TSC2101
*
********************************************************************************/
static void omap_tsc2101_remove(void)
{
FN_IN;
/* Un-Register the codec with the audio driver */
unregister_sound_dsp(audio_dev_id);
unregister_sound_mixer(mixer_dev_id);
#ifdef PROC_SUPPORT
remove_proc_entry(PROC_START_FILE, NULL);
remove_proc_entry(PROC_STOP_FILE, NULL);
#endif
FN_OUT(0);
}
/*********************************************************************************
*
* Module Suspend for TSC2101
*
********************************************************************************/
static int omap_tsc2101_suspend(void)
{
FN_OUT(0);
return 0;
}
/*********************************************************************************
*
* Module Resume for TSC2101
*
********************************************************************************/
static int omap_tsc2101_resume(void)
{
FN_OUT(0);
return 0;
}
/*********************************************************************************
*
* module_init for TSC2101
*
********************************************************************************/
static int __init audio_tsc2101_init(void)
{
int err = 0;
FN_IN;
if (machine_is_omap_osk() || machine_is_omap_innovator())
return -ENODEV;
mutex_init(&tsc2101_state.mutex);
/* register the codec with the audio driver */
if ((err = audio_register_codec(&tsc2101_state))) {
printk(KERN_ERR
"Failed to register TSC driver with Audio OSS Driver\n");
}
FN_OUT(err);
return err;
}
/*********************************************************************************
*
* module_exit for TSC2101
*
********************************************************************************/
static void __exit audio_tsc2101_exit(void)
{
FN_IN;
(void)audio_unregister_codec(&tsc2101_state);
FN_OUT(0);
return;
}
/**************************** DEBUG FUNCTIONS ***********************************/
/*********************************************************************************
* TEST_KEYCLICK:
* This is a test to generate various keyclick sound on tsc.
* verifies if the tsc and the spi interfaces are operational.
*
********************************************************************************/
#ifdef TEST_KEYCLICK
void tsc2101_testkeyclick(void)
{
u8 freq = 0;
u16 old_reg_val, reg_val;
u32 uDummyVal = 0;
u32 uTryVal = 0;
old_reg_val = audio_tsc2101_read(TSC2101_AUDIO_CTRL_2);
/* Keyclick active, max amplitude and longest key click len(32 period) */
printk(KERN_INFO " TESTING KEYCLICK\n Listen carefully NOW....\n");
printk(KERN_INFO " OLD REG VAL=0x%x\n", old_reg_val);
/* try all frequencies */
for (; freq < 8; freq++) {
/* Keyclick active, max amplitude and longest key click len(32 period) */
reg_val = old_reg_val | AC2_KCLAC(0x7) | AC2_KCLLN(0xF);
uDummyVal = 0;
uTryVal = 0;
printk(KERN_INFO "\n\nTrying frequency %d reg val= 0x%x\n",
freq, reg_val | AC2_KCLFRQ(freq) | AC2_KCLEN);
audio_tsc2101_write(TSC2101_AUDIO_CTRL_2,
reg_val | AC2_KCLFRQ(freq) | AC2_KCLEN);
printk("DONE. Wait 10 ms ...\n");
/* wait till the kclk bit is auto cleared! time out also to be considered. */
while (audio_tsc2101_read(TSC2101_AUDIO_CTRL_2) & AC2_KCLEN) {
udelay(3);
uTryVal++;
if (uTryVal > 2000) {
printk(KERN_ERR
"KEYCLICK TIMED OUT! freq val=%d, POSSIBLE ERROR!\n",
freq);
printk(KERN_INFO
"uTryVal == %d: Read back new reg val= 0x%x\n",
uTryVal,
audio_tsc2101_read
(TSC2101_AUDIO_CTRL_2));
/* clear */
audio_tsc2101_write(TSC2101_AUDIO_CTRL_2, 0x00);
break;
}
}
}
/* put the old value back */
audio_tsc2101_write(TSC2101_AUDIO_CTRL_2, old_reg_val);
printk(KERN_INFO " KEYCLICK TEST COMPLETE\n");
} /* End of tsc2101_testkeyclick */
#endif /* TEST_KEYCLICK */
/*********************************************************************************
* TONEGEN:
* This is a test to generate a rather unpleasant sound..
* verifies if the mcbsp is active (requires MCBSP_DIRECT_RW to be active on McBSP)
*
********************************************************************************/
#ifdef TONE_GEN
/* Generates a shrill tone */
u16 tone[] = {
0x0ce4, 0x0ce4, 0x1985, 0x1985, 0x25A1, 0x25A1, 0x30FD, 0x30FE,
0x3B56, 0x3B55, 0x447A, 0x447A, 0x4C3B, 0x4C3C, 0x526D, 0x526C,
0x56F1, 0x56F1, 0x59B1, 0x59B1, 0x5A9E, 0x5A9D, 0x59B1, 0x59B2,
0x56F3, 0x56F2, 0x526D, 0x526D, 0x4C3B, 0x4C3B, 0x447C, 0x447C,
0x3B5A, 0x3B59, 0x30FE, 0x30FE, 0x25A5, 0x25A6, 0x1989, 0x198A,
0x0CE5, 0x0CE3, 0x0000, 0x0000, 0xF31C, 0xF31C, 0xE677, 0xE676,
0xDA5B, 0xDA5B, 0xCF03, 0xCF03, 0xC4AA, 0xC4AA, 0xBB83, 0xBB83,
0xB3C5, 0xB3C5, 0xAD94, 0xAD94, 0xA90D, 0xA90E, 0xA64F, 0xA64E,
0xA562, 0xA563, 0xA64F, 0xA64F, 0xA910, 0xA90F, 0xAD93, 0xAD94,
0xB3C4, 0xB3C4, 0xBB87, 0xBB86, 0xC4AB, 0xC4AB, 0xCF03, 0xCF03,
0xDA5B, 0xDA5A, 0xE67B, 0xE67B, 0xF31B, 0xF3AC, 0x0000, 0x0000,
0x0CE4, 0x0CE4, 0x1985, 0x1985, 0x25A1, 0x25A1, 0x30FD, 0x30FE,
0x3B56, 0x3B55, 0x447A, 0x447A, 0x4C3B, 0x4C3C, 0x526D, 0x526C,
0x56F1, 0x56F1, 0x59B1, 0x59B1, 0x5A9E, 0x5A9D, 0x59B1, 0x59B2,
0x56F3, 0x56F2, 0x526D, 0x526D, 0x4C3B, 0x4C3B, 0x447C, 0x447C,
0x3B5A, 0x3B59, 0x30FE, 0x30FE, 0x25A5, 0x25A6, 0x1989, 0x198A,
0x0CE5, 0x0CE3, 0x0000, 0x0000, 0xF31C, 0xF31C, 0xE677, 0xE676,
0xDA5B, 0xDA5B, 0xCF03, 0xCF03, 0xC4AA, 0xC4AA, 0xBB83, 0xBB83,
0xB3C5, 0xB3C5, 0xAD94, 0xAD94, 0xA90D, 0xA90E, 0xA64F, 0xA64E,
0xA562, 0xA563, 0xA64F, 0xA64F, 0xA910, 0xA90F, 0xAD93, 0xAD94,
0xB3C4, 0xB3C4, 0xBB87, 0xBB86, 0xC4AB, 0xC4AB, 0xCF03, 0xCF03,
0xDA5B, 0xDA5A, 0xE67B, 0xE67B, 0xF31B, 0xF3AC, 0x0000, 0x0000,
0x0CE4, 0x0CE4, 0x1985, 0x1985, 0x25A1, 0x25A1, 0x30FD, 0x30FE,
0x3B56, 0x3B55, 0x447A, 0x447A, 0x4C3B, 0x4C3C, 0x526D, 0x526C,
0x56F1, 0x56F1, 0x59B1, 0x59B1, 0x5A9E, 0x5A9D, 0x59B1, 0x59B2,
0x56F3, 0x56F2, 0x526D, 0x526D, 0x4C3B, 0x4C3B, 0x447C, 0x447C,
0x3B5A, 0x3B59, 0x30FE, 0x30FE, 0x25A5, 0x25A6, 0x1989, 0x198A,
0x0CE5, 0x0CE3, 0x0000, 0x0000, 0xF31C, 0xF31C, 0xE677, 0xE676,
0xDA5B, 0xDA5B, 0xCF03, 0xCF03, 0xC4AA, 0xC4AA, 0xBB83, 0xBB83,
0xB3C5, 0xB3C5, 0xAD94, 0xAD94, 0xA90D, 0xA90E, 0xA64F, 0xA64E,
0xA562, 0xA563, 0xA64F, 0xA64F, 0xA910, 0xA90F, 0xAD93, 0xAD94,
0xB3C4, 0xB3C4, 0xBB87, 0xBB86, 0xC4AB, 0xC4AB, 0xCF03, 0xCF03,
0xDA5B, 0xDA5A, 0xE67B, 0xE67B, 0xF31B, 0xF3AC, 0x0000, 0x0000
};
void toneGen(void)
{
int count = 0;
int ret = 0;
printk(KERN_INFO "TONE GEN TEST :");
for (count = 0; count < 5000; count++) {
int bytes;
for (bytes = 0; bytes < sizeof(tone) / 2; bytes++) {
ret = omap_mcbsp_pollwrite(AUDIO_MCBSP, tone[bytes]);
if (ret == -1) {
/* retry */
bytes--;
} else if (ret == -2) {
printk(KERN_INFO "ERROR:bytes=%d\n", bytes);
return;
}
}
}
printk(KERN_INFO "SUCCESS\n");
}
#endif /* End of TONE_GEN */
/*********************************************************************************
*
* TSC_DUMP_REGISTERS:
* This will dump the entire register set of Page 2 tsc2101.
* Useful for major goof ups
*
********************************************************************************/
#ifdef TSC_DUMP_REGISTERS
static void tsc2101_dumpRegisters(void)
{
int i = 0;
u16 data = 0;
printk("TSC 2101 Register dump for Page 2 \n");
for (i = 0; i < 0x27; i++) {
data = audio_tsc2101_read(i);
printk(KERN_INFO "Register[%x]=0x%04x\n", i, data);
}
}
#endif /* End of #ifdef TSC_DUMP_REGISTERS */
#ifdef PROC_SUPPORT
static int codec_start(char *buf, char **start, off_t offset, int count,
int *eof, void *data)
{
omap_tsc2101_enable();
tsc2101_start();
printk("Codec initialization done.\n");
return 0;
}
static int codec_stop(char *buf, char **start, off_t offset, int count,
int *eof, void *data)
{
omap_tsc2101_disable();
audio_tsc2101_write(TSC2101_CODEC_POWER_CTRL,
~(CPC_SP1PWDN | CPC_SP2PWDN | CPC_BASSBC));
printk("Codec shutdown.\n");
return 0;
}
#endif
/*********************************************************************************
*
* Other misc management, registration etc
*
********************************************************************************/
module_init(audio_tsc2101_init);
module_exit(audio_tsc2101_exit);
MODULE_AUTHOR("Texas Instruments");
MODULE_DESCRIPTION
("Glue audio driver for the TI OMAP1610/OMAP1710 TSC2101 codec.");
MODULE_LICENSE("GPL");
/*
* linux/sound/oss/omap-audio.c
*
* Common audio handling for the OMAP processors
*
* Copyright (C) 2004 Texas Instruments, Inc.
*
* Copyright (C) 2000, 2001 Nicolas Pitre <nico@cam.org>
*
* This package is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*
* History:
*
* 2004/08/12 Nishanth Menon - Modified to integrate Audio requirements on 1610,1710 platforms
*
* 2004-11-01 Nishanth Menon - modified to support 16xx and 17xx
* platform multi channel chaining.
*
* 2004-11-04 Nishanth Menon - Added support for power management
*
* 2004-12-17 Nishanth Menon - Provided proper module handling support
*/
/***************************** INCLUDES ************************************/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/pm.h>
#include <linux/errno.h>
#include <linux/sound.h>
#include <linux/soundcard.h>
#include <linux/sysrq.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/completion.h>
#include <linux/mutex.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <mach/hardware.h>
#include "omap-audio-dma-intfc.h"
#include "omap-audio.h"
/***************************** MACROS ************************************/
#undef DEBUG
//#define DEBUG
#ifdef DEBUG
#define DPRINTK printk
#define FN_IN printk("[omap_audio.c:[%s] start\n", __FUNCTION__)
#define FN_OUT(n) printk("[omap_audio.c:[%s] end(%d)\n", __FUNCTION__ , n)
#else
#define DPRINTK( x... )
#define FN_IN
#define FN_OUT(x)
#endif
#define OMAP_AUDIO_NAME "omap-audio"
#define AUDIO_NBFRAGS_DEFAULT 8
#define AUDIO_FRAGSIZE_DEFAULT 8192
/* HACK ALERT!: These values will bave to be tuned as this is a trade off b/w
* Sampling Rate vs buffer size and delay we are prepared to do before giving up
*/
#define MAX_QUEUE_FULL_RETRIES 1000000
#define QUEUE_WAIT_TIME 10
#define AUDIO_ACTIVE(state) ((state)->rd_ref || (state)->wr_ref)
#define SPIN_ADDR (dma_addr_t)0
#define SPIN_SIZE 2048
/***************************** MODULES SPECIFIC FUNCTION PROTOTYPES ********************/
static int audio_write(struct file *file, const char __user *buffer,
size_t count, loff_t * ppos);
static int audio_read(struct file *file, char __user *buffer, size_t count,
loff_t * ppos);
static int audio_mmap(struct file *file, struct vm_area_struct *vma);
static unsigned int audio_poll(struct file *file,
struct poll_table_struct *wait);
static loff_t audio_llseek(struct file *file, loff_t offset, int origin);
static int audio_ioctl(struct inode *inode, struct file *file, uint cmd,
ulong arg);
static int audio_open(struct inode *inode, struct file *file);
static int audio_release(struct inode *inode, struct file *file);
static int audio_probe(struct platform_device *pdev);
static int audio_remove(struct platform_device *pdev);
static void audio_shutdown(struct platform_device *pdev);
static int audio_suspend(struct platform_device *pdev, pm_message_t mesg);
static int audio_resume(struct platform_device *pdev);
static void audio_free(struct device *dev);
/***************************** Data Structures **********************************/
/*
* The function pointer set to be registered by the codec.
*/
static audio_state_t audio_state = { NULL };
/* DMA Call back function */
static dma_callback_t audio_dma_callback = NULL;
/* File Ops structure */
static struct file_operations omap_audio_fops = {
.open = audio_open,
.release = audio_release,
.write = audio_write,
.read = audio_read,
.mmap = audio_mmap,
.poll = audio_poll,
.ioctl = audio_ioctl,
.llseek = audio_llseek,
.owner = THIS_MODULE
};
/* Driver information */
static struct platform_driver omap_audio_driver = {
.probe = audio_probe,
.remove = audio_remove,
.suspend = audio_suspend,
.shutdown = audio_shutdown,
.resume = audio_resume,
.driver = {
.name = OMAP_AUDIO_NAME,
},
};
/* Device Information */
static struct platform_device omap_audio_device = {
.name = OMAP_AUDIO_NAME,
.dev = {
.driver_data = &audio_state,
.release = audio_free,
},
.id = 0,
};
/***************************** GLOBAL FUNCTIONs **********************************/
/* Power Management Functions for Linux Device Model */
/* DEBUG PUPOSES ONLY! */
#ifdef CONFIG_PM
//#undef CONFIG_PM
#endif
#ifdef CONFIG_PM
/*********************************************************************************
*
* audio_ldm_suspend(): Suspend operation
*
*********************************************************************************/
static int audio_ldm_suspend(void *data)
{
audio_state_t *state = data;
FN_IN;
/*
* Reject the suspend request if we are already actively transmitting data
* Rationale: We dont want to be suspended while in the middle of a call!
*/
if (AUDIO_ACTIVE(state) && state->hw_init) {
printk(KERN_ERR "Audio device Active, Cannot Suspend");
return -EPERM;
#if 0
/* NOTE:
* This Piece of code is commented out in hope
* That one day we would need to suspend the device while
* audio operations are in progress and resume the operations
* once the resume is done.
* This is just a sample implementation of how it could be done.
* Currently NOT SUPPORTED
*/
audio_stream_t *is = state->input_stream;
audio_stream_t *os = state->output_stream;
int stopstate;
if (is && is->buffers) {
printk("IS Suspend\n");
stopstate = is->stopped;
audio_stop_dma(is);
DMA_CLEAR(is);
is->dma_spinref = 0;
is->stopped = stopstate;
}
if (os && os->buffers) {
printk("OS Suspend\n");
stopstate = os->stopped;
audio_stop_dma(os);
DMA_CLEAR(os);
os->dma_spinref = 0;
os->stopped = stopstate;
}
#endif
}
FN_OUT(0);
return 0;
}
/*********************************************************************************
*
* audio_ldm_resume(): Resume Operations
*
*********************************************************************************/
static int audio_ldm_resume(void *data)
{
audio_state_t *state = data;
FN_IN;
if (AUDIO_ACTIVE(state) && state->hw_init) {
/* Should never occur - since we never suspend with active state */
BUG();
return -EPERM;
#if 0
/* NOTE:
* This Piece of code is commented out in hope
* That one day we would need to suspend the device while
* audio operations are in progress and resume the operations
* once the resume is done.
* This is just a sample implementation of how it could be done.
* Currently NOT SUPPORTED
*/
audio_stream_t *is = state->input_stream;
audio_stream_t *os = state->output_stream;
if (os && os->buffers) {
printk("OS Resume\n");
audio_reset(os);
audio_process_dma(os);
}
if (is && is->buffers) {
printk("IS Resume\n");
audio_reset(is);
audio_process_dma(is);
}
#endif
}
FN_OUT(0);
return 0;
}
#endif /* End of #ifdef CONFIG_PM */
/*********************************************************************************
*
* audio_free(): The Audio driver release function
* This is a dummy function required by the platform driver
*
*********************************************************************************/
static void audio_free(struct device *dev)
{
/* Nothing to Release! */
}
/*********************************************************************************
*
* audio_probe(): The Audio driver probe function
* WARNING!!!! : It is expected that the codec would have registered with us by now
*
*********************************************************************************/
static int audio_probe(struct platform_device *pdev)
{
int ret;
FN_IN;
if (!audio_state.hw_probe) {
printk(KERN_ERR "Probe Function Not Registered\n");
return -ENODEV;
}
ret = audio_state.hw_probe();
FN_OUT(ret);
return ret;
}
/*********************************************************************************
*
* audio_remove() Function to handle removal operations
*
*********************************************************************************/
static int audio_remove(struct platform_device *pdev)
{
FN_IN;
if (audio_state.hw_remove) {
audio_state.hw_remove();
}
FN_OUT(0);
return 0;
}
/*********************************************************************************
*
* audio_shutdown(): Function to handle shutdown operations
*
*********************************************************************************/
static void audio_shutdown(struct platform_device *pdev)
{
FN_IN;
if (audio_state.hw_cleanup) {
audio_state.hw_cleanup();
}
FN_OUT(0);
return;
}
/*********************************************************************************
*
* audio_suspend(): Function to handle suspend operations
*
*********************************************************************************/
static int audio_suspend(struct platform_device *pdev, pm_message_t mesg)
{
int ret = 0;
#ifdef CONFIG_PM
void *data = pdev->dev.driver_data;
FN_IN;
if (audio_state.hw_suspend) {
ret = audio_ldm_suspend(data);
if (ret == 0)
ret = audio_state.hw_suspend();
}
if (ret) {
printk(KERN_INFO "Audio Suspend Failed \n");
} else {
printk(KERN_INFO "Audio Suspend Success \n");
}
#endif /* CONFIG_PM */
FN_OUT(ret);
return ret;
}
/*********************************************************************************
*
* audio_resume(): Function to handle resume operations
*
*********************************************************************************/
static int audio_resume(struct platform_device *pdev)
{
int ret = 0;
#ifdef CONFIG_PM
void *data = pdev->dev.driver_data;
FN_IN;
if (audio_state.hw_resume) {
ret = audio_ldm_resume(data);
if (ret == 0)
ret = audio_state.hw_resume();
}
if (ret) {
printk(KERN_INFO " Audio Resume Failed \n");
} else {
printk(KERN_INFO " Audio Resume Success \n");
}
#endif /* CONFIG_PM */
FN_OUT(ret);
return ret;
}
/*********************************************************************************
*
* audio_get_fops(): Return the fops required to get the function pointers of
* OMAP Audio Driver
*
*********************************************************************************/
struct file_operations *audio_get_fops(void)
{
FN_IN;
FN_OUT(0);
return &omap_audio_fops;
}
/*********************************************************************************
*
* audio_register_codec(): Register a Codec fn points using this function
* WARNING!!!!! : Codecs should ensure that they do so! no sanity checks
* during runtime is done due to obvious performance
* penalties.
*
*********************************************************************************/
int audio_register_codec(audio_state_t * codec_state)
{
int ret;
FN_IN;
/* We dont handle multiple codecs now */
if (audio_state.hw_init) {
printk(KERN_ERR " Codec Already registered\n");
return -EPERM;
}
/* Grab the dma Callback */
audio_dma_callback = audio_get_dma_callback();
if (!audio_dma_callback) {
printk(KERN_ERR "Unable to get call back function\n");
return -EPERM;
}
/* Sanity checks */
if (!codec_state) {
printk(KERN_ERR "NULL ARGUMENT!\n");
return -EPERM;
}
if (!codec_state->hw_probe || !codec_state->hw_init
|| !codec_state->hw_shutdown || !codec_state->client_ioctl) {
printk(KERN_ERR
"Required Fn Entry point Missing probe=%p init=%p,down=%p,ioctl=%p!\n",
codec_state->hw_probe, codec_state->hw_init,
codec_state->hw_shutdown, codec_state->client_ioctl);
return -EPERM;
}
memcpy(&audio_state, codec_state, sizeof(audio_state_t));
mutex_init(&audio_state.mutex);
ret = platform_device_register(&omap_audio_device);
if (ret != 0) {
printk(KERN_ERR "Platform dev_register failed =%d\n", ret);
ret = -ENODEV;
goto register_out;
}
ret = platform_driver_register(&omap_audio_driver);
if (ret != 0) {
printk(KERN_ERR "Device Register failed =%d\n", ret);
ret = -ENODEV;
platform_device_unregister(&omap_audio_device);
goto register_out;
}
register_out:
FN_OUT(ret);
return ret;
}
/*********************************************************************************
*
* audio_unregister_codec(): Un-Register a Codec using this function
*
*********************************************************************************/
int audio_unregister_codec(audio_state_t * codec_state)
{
FN_IN;
/* We dont handle multiple codecs now */
if (!audio_state.hw_init) {
printk(KERN_ERR " No Codec registered\n");
return -EPERM;
}
/* Security check */
if (audio_state.hw_init != codec_state->hw_init) {
printk(KERN_ERR
" Attempt to unregister codec which was not registered with us\n");
return -EPERM;
}
platform_driver_unregister(&omap_audio_driver);
platform_device_unregister(&omap_audio_device);
memset(&audio_state, 0, sizeof(audio_state_t));
FN_OUT(0);
return 0;
}
/***************************** MODULES SPECIFIC FUNCTION *************************/
/*********************************************************************************
*
* audio_write(): Exposed to write() call
*
*********************************************************************************/
static int
audio_write(struct file *file, const char __user *buffer,
size_t count, loff_t * ppos)
{
const char __user *buffer0 = buffer;
audio_state_t *state = file->private_data;
audio_stream_t *s = state->output_stream;
int chunksize, ret = 0;
DPRINTK("audio_write: count=%d\n", count);
if (*ppos != file->f_pos) {
printk("FPOS not ppos ppos=0x%x fpos =0x%x\n", (u32) * ppos,
(u32) file->f_pos);
return -ESPIPE;
}
if (s->mapped) {
printk("s already mapped\n");
return -ENXIO;
}
if (!s->buffers && audio_setup_buf(s)) {
printk("NO MEMORY\n");
return -ENOMEM;
}
while (count > 0) {
audio_buf_t *b = &s->buffers[s->usr_head];
/* Wait for a buffer to become free */
if (file->f_flags & O_NONBLOCK) {
ret = -EAGAIN;
if (!s->wfc.done)
break;
}
ret = -ERESTARTSYS;
if (wait_for_completion_interruptible(&s->wfc))
break;
/* Feed the current buffer */
chunksize = s->fragsize - b->offset;
if (chunksize > count)
chunksize = count;
DPRINTK("write %d to %d\n", chunksize, s->usr_head);
if (copy_from_user(b->data + b->offset, buffer, chunksize)) {
printk(KERN_ERR "Audio: CopyFrom User failed \n");
complete(&s->wfc);
return -EFAULT;
}
buffer += chunksize;
count -= chunksize;
b->offset += chunksize;
if (b->offset < s->fragsize) {
complete(&s->wfc);
break;
}
/* Update pointers and send current fragment to DMA */
b->offset = 0;
if (++s->usr_head >= s->nbfrags)
s->usr_head = 0;
/* Add the num of frags pending */
s->pending_frags++;
s->active = 1;
audio_process_dma(s);
}
if ((buffer - buffer0))
ret = buffer - buffer0;
DPRINTK("audio_write: return=%d\n", ret);
return ret;
}
/*********************************************************************************
*
* audio_read(): Exposed as read() function
*
*********************************************************************************/
static int
audio_read(struct file *file, char __user *buffer, size_t count, loff_t * ppos)
{
char __user *buffer0 = buffer;
audio_state_t *state = file->private_data;
audio_stream_t *s = state->input_stream;
int chunksize, ret = 0;
unsigned long flags;
DPRINTK("audio_read: count=%d\n", count);
if (*ppos != file->f_pos) {
printk("AudioRead - FPOS not ppos ppos=0x%x fpos =0x%x\n",
(u32) * ppos, (u32) file->f_pos);
return -ESPIPE;
}
if (s->mapped) {
printk("AudioRead - s already mapped\n");
return -ENXIO;
}
if (!s->active) {
if (!s->buffers && audio_setup_buf(s)) {
printk("AudioRead - No Memory\n");
return -ENOMEM;
}
audio_prime_rx(state);
}
while (count > 0) {
audio_buf_t *b = &s->buffers[s->usr_head];
/* Wait for a buffer to become full */
if (file->f_flags & O_NONBLOCK) {
ret = -EAGAIN;
if (!s->wfc.done)
break;
}
ret = -ERESTARTSYS;
if (wait_for_completion_interruptible(&s->wfc))
break;
/* Grab data from the current buffer */
chunksize = s->fragsize - b->offset;
if (chunksize > count)
chunksize = count;
DPRINTK("read %d from %d\n", chunksize, s->usr_head);
if (copy_to_user(buffer, b->data + b->offset, chunksize)) {
complete(&s->wfc);
return -EFAULT;
}
buffer += chunksize;
count -= chunksize;
b->offset += chunksize;
if (b->offset < s->fragsize) {
complete(&s->wfc);
break;
}
/* Update pointers and return current fragment to DMA */
local_irq_save(flags);
b->offset = 0;
if (++s->usr_head >= s->nbfrags)
s->usr_head = 0;
s->pending_frags++;
local_irq_restore(flags);
audio_process_dma(s);
}
if ((buffer - buffer0))
ret = buffer - buffer0;
DPRINTK("audio_read: return=%d\n", ret);
return ret;
}
/*********************************************************************************
*
* audio_mmap(): Exposed as mmap Function
* !!WARNING: Still under development
*
*********************************************************************************/
static int audio_mmap(struct file *file, struct vm_area_struct *vma)
{
audio_state_t *state = file->private_data;
audio_stream_t *s;
unsigned long size, vma_addr;
int i, ret;
FN_IN;
if (vma->vm_pgoff != 0)
return -EINVAL;
if (vma->vm_flags & VM_WRITE) {
if (!state->wr_ref)
return -EINVAL;;
s = state->output_stream;
} else if (vma->vm_flags & VM_READ) {
if (!state->rd_ref)
return -EINVAL;
s = state->input_stream;
} else
return -EINVAL;
if (s->mapped)
return -EINVAL;
size = vma->vm_end - vma->vm_start;
if (size != s->fragsize * s->nbfrags)
return -EINVAL;
if (!s->buffers && audio_setup_buf(s))
return -ENOMEM;
vma_addr = vma->vm_start;
for (i = 0; i < s->nbfrags; i++) {
audio_buf_t *buf = &s->buffers[i];
if (!buf->master)
continue;
ret =
remap_pfn_range(vma, vma_addr, buf->dma_addr >> PAGE_SHIFT,
buf->master, vma->vm_page_prot);
if (ret)
return ret;
vma_addr += buf->master;
}
s->mapped = 1;
FN_OUT(0);
return 0;
}
/*********************************************************************************
*
* audio_poll(): Exposed as poll function
*
*********************************************************************************/
static unsigned int
audio_poll(struct file *file, struct poll_table_struct *wait)
{
audio_state_t *state = file->private_data;
audio_stream_t *is = state->input_stream;
audio_stream_t *os = state->output_stream;
unsigned int mask = 0;
DPRINTK("audio_poll(): mode=%s%s\n",
(file->f_mode & FMODE_READ) ? "r" : "",
(file->f_mode & FMODE_WRITE) ? "w" : "");
if (file->f_mode & FMODE_READ) {
/* Start audio input if not already active */
if (!is->active) {
if (!is->buffers && audio_setup_buf(is))
return -ENOMEM;
audio_prime_rx(state);
}
poll_wait(file, &is->wq, wait);
}
if (file->f_mode & FMODE_WRITE) {
if (!os->buffers && audio_setup_buf(os))
return -ENOMEM;
poll_wait(file, &os->wq, wait);
}
if (file->f_mode & FMODE_READ)
if ((is->mapped && is->bytecount > 0) ||
(!is->mapped && is->wfc.done > 0))
mask |= POLLIN | POLLRDNORM;
if (file->f_mode & FMODE_WRITE)
if ((os->mapped && os->bytecount > 0) ||
(!os->mapped && os->wfc.done > 0))
mask |= POLLOUT | POLLWRNORM;
DPRINTK("audio_poll() returned mask of %s%s\n",
(mask & POLLIN) ? "r" : "", (mask & POLLOUT) ? "w" : "");
FN_OUT(mask);
return mask;
}
/*********************************************************************************
*
* audio_llseek(): Exposed as lseek() function.
*
*********************************************************************************/
static loff_t audio_llseek(struct file *file, loff_t offset, int origin)
{
FN_IN;
FN_OUT(0);
return -ESPIPE;
}
/*********************************************************************************
*
* audio_ioctl(): Handles generic ioctls. If there is a request for something this
* fn cannot handle, its then given to client specific ioctl routine, that will take
* up platform specific requests
*
*********************************************************************************/
static int
audio_ioctl(struct inode *inode, struct file *file, uint cmd, ulong arg)
{
audio_state_t *state = file->private_data;
audio_stream_t *os = state->output_stream;
audio_stream_t *is = state->input_stream;
long val;
DPRINTK(__FILE__ " audio_ioctl 0x%08x\n", cmd);
/* dispatch based on command */
switch (cmd) {
case OSS_GETVERSION:
return put_user(SOUND_VERSION, (int __user *)arg);
case SNDCTL_DSP_GETBLKSIZE:
if (file->f_mode & FMODE_WRITE)
return put_user(os->fragsize, (int __user *)arg);
else
return put_user(is->fragsize, (int __user *)arg);
case SNDCTL_DSP_GETCAPS:
val = DSP_CAP_REALTIME | DSP_CAP_TRIGGER | DSP_CAP_MMAP;
if (is && os)
val |= DSP_CAP_DUPLEX;
FN_OUT(1);
return put_user(val, (int __user *)arg);
case SNDCTL_DSP_SETFRAGMENT:
if (get_user(val, (long __user *)arg)) {
FN_OUT(2);
return -EFAULT;
}
if (file->f_mode & FMODE_READ) {
int ret = audio_set_fragments(is, val);
if (ret < 0) {
FN_OUT(3);
return ret;
}
ret = put_user(ret, (int __user *)arg);
if (ret) {
FN_OUT(4);
return ret;
}
}
if (file->f_mode & FMODE_WRITE) {
int ret = audio_set_fragments(os, val);
if (ret < 0) {
FN_OUT(5);
return ret;
}
ret = put_user(ret, (int __user *)arg);
if (ret) {
FN_OUT(6);
return ret;
}
}
FN_OUT(7);
return 0;
case SNDCTL_DSP_SYNC:
FN_OUT(8);
return audio_sync(file);
case SNDCTL_DSP_SETDUPLEX:
FN_OUT(9);
return 0;
case SNDCTL_DSP_POST:
FN_OUT(10);
return 0;
case SNDCTL_DSP_GETTRIGGER:
val = 0;
if (file->f_mode & FMODE_READ && is->active && !is->stopped)
val |= PCM_ENABLE_INPUT;
if (file->f_mode & FMODE_WRITE && os->active && !os->stopped)
val |= PCM_ENABLE_OUTPUT;
FN_OUT(11);
return put_user(val, (int __user *)arg);
case SNDCTL_DSP_SETTRIGGER:
if (get_user(val, (int __user *)arg)) {
FN_OUT(12);
return -EFAULT;
}
if (file->f_mode & FMODE_READ) {
if (val & PCM_ENABLE_INPUT) {
unsigned long flags;
if (!is->active) {
if (!is->buffers && audio_setup_buf(is)) {
FN_OUT(13);
return -ENOMEM;
}
audio_prime_rx(state);
}
local_irq_save(flags);
is->stopped = 0;
local_irq_restore(flags);
audio_process_dma(is);
} else {
audio_stop_dma(is);
}
}
if (file->f_mode & FMODE_WRITE) {
if (val & PCM_ENABLE_OUTPUT) {
unsigned long flags;
if (!os->buffers && audio_setup_buf(os)) {
FN_OUT(14);
return -ENOMEM;
}
local_irq_save(flags);
if (os->mapped && !os->pending_frags) {
os->pending_frags = os->nbfrags;
init_completion(&os->wfc);
os->wfc.done = 0;
os->active = 1;
}
os->stopped = 0;
local_irq_restore(flags);
audio_process_dma(os);
} else {
audio_stop_dma(os);
}
}
FN_OUT(15);
return 0;
case SNDCTL_DSP_GETOPTR:
case SNDCTL_DSP_GETIPTR:
{
count_info inf = { 0, };
audio_stream_t *s =
(cmd == SNDCTL_DSP_GETOPTR) ? os : is;
int bytecount, offset;
unsigned long flags;
if ((s == is && !(file->f_mode & FMODE_READ)) ||
(s == os && !(file->f_mode & FMODE_WRITE))) {
FN_OUT(16);
return -EINVAL;
}
if (s->active) {
local_irq_save(flags);
offset = audio_get_dma_pos(s);
inf.ptr = s->dma_tail * s->fragsize + offset;
bytecount = s->bytecount + offset;
s->bytecount = -offset;
inf.blocks = s->fragcount;
s->fragcount = 0;
local_irq_restore(flags);
if (bytecount < 0)
bytecount = 0;
inf.bytes = bytecount;
}
FN_OUT(17);
return copy_to_user((void __user *)arg, &inf, sizeof(inf));
}
case SNDCTL_DSP_GETOSPACE:
case SNDCTL_DSP_GETISPACE:
{
audio_buf_info inf = { 0, };
audio_stream_t *s =
(cmd == SNDCTL_DSP_GETOSPACE) ? os : is;
if ((s == is && !(file->f_mode & FMODE_READ)) ||
(s == os && !(file->f_mode & FMODE_WRITE))) {
FN_OUT(18);
return -EINVAL;
}
if (!s->buffers && audio_setup_buf(s)) {
FN_OUT(19);
return -ENOMEM;
}
inf.bytes = s->wfc.done * s->fragsize;
inf.fragments = inf.bytes / s->fragsize;
inf.fragsize = s->fragsize;
inf.fragstotal = s->nbfrags;
FN_OUT(20);
return copy_to_user((void __user *)arg, &inf, sizeof(inf));
}
case SNDCTL_DSP_NONBLOCK:
file->f_flags |= O_NONBLOCK;
FN_OUT(21);
return 0;
case SNDCTL_DSP_RESET:
if (file->f_mode & FMODE_READ) {
audio_reset(is);
if (state->need_tx_for_rx) {
unsigned long flags;
local_irq_save(flags);
os->spin_idle = 0;
local_irq_restore(flags);
}
}
if (file->f_mode & FMODE_WRITE) {
audio_reset(os);
}
FN_OUT(22);
return 0;
default:
/*
* Let the client of this module handle the
* non generic ioctls
*/
FN_OUT(23);
return state->client_ioctl(inode, file, cmd, arg);
}
FN_OUT(0);
return 0;
}
/*********************************************************************************
*
* audio_open(): Exposed as open() function
*
*********************************************************************************/
static int audio_open(struct inode *inode, struct file *file)
{
audio_state_t *state = (&audio_state);
audio_stream_t *os = state->output_stream;
audio_stream_t *is = state->input_stream;
int err, need_tx_dma;
static unsigned char tsc2101_init_flag = 0;
FN_IN;
/* Lock the module */
if (!try_module_get(THIS_MODULE)) {
printk(KERN_CRIT "Failed to get module\n");
return -ESTALE;
}
/* Lock the codec module */
if (!try_module_get(state->owner)) {
printk(KERN_CRIT "Failed to get codec module\n");
module_put(THIS_MODULE);
return -ESTALE;
}
mutex_lock(&state->mutex);
/* access control */
err = -ENODEV;
if ((file->f_mode & FMODE_WRITE) && !os)
goto out;
if ((file->f_mode & FMODE_READ) && !is)
goto out;
err = -EBUSY;
if ((file->f_mode & FMODE_WRITE) && state->wr_ref)
goto out;
if ((file->f_mode & FMODE_READ) && state->rd_ref)
goto out;
err = -EINVAL;
if ((file->f_mode & FMODE_READ) && state->need_tx_for_rx && !os)
goto out;
/* request DMA channels */
need_tx_dma = ((file->f_mode & FMODE_WRITE) ||
((file->f_mode & FMODE_READ) && state->need_tx_for_rx));
if (state->wr_ref || (state->rd_ref && state->need_tx_for_rx))
need_tx_dma = 0;
if (need_tx_dma) {
DMA_REQUEST(err, os, audio_dma_callback);
if (err < 0)
goto out;
}
if (file->f_mode & FMODE_READ) {
DMA_REQUEST(err, is, audio_dma_callback);
if (err < 0) {
if (need_tx_dma)
DMA_FREE(os);
goto out;
}
}
/* now complete initialisation */
if (!AUDIO_ACTIVE(state)) {
if (state->hw_init && !tsc2101_init_flag) {
state->hw_init(state->data);
tsc2101_init_flag = 0;
}
}
if ((file->f_mode & FMODE_WRITE)) {
state->wr_ref = 1;
audio_reset(os);
os->fragsize = AUDIO_FRAGSIZE_DEFAULT;
os->nbfrags = AUDIO_NBFRAGS_DEFAULT;
os->mapped = 0;
init_waitqueue_head(&os->wq);
}
if (file->f_mode & FMODE_READ) {
state->rd_ref = 1;
audio_reset(is);
is->fragsize = AUDIO_FRAGSIZE_DEFAULT;
is->nbfrags = AUDIO_NBFRAGS_DEFAULT;
is->mapped = 0;
init_waitqueue_head(&is->wq);
}
file->private_data = state;
err = 0;
out:
mutex_unlock(&state->mutex);
if (err) {
module_put(state->owner);
module_put(THIS_MODULE);
}
FN_OUT(err);
return err;
}
/*********************************************************************************
*
* audio_release(): Exposed as release function()
*
*********************************************************************************/
static int audio_release(struct inode *inode, struct file *file)
{
audio_state_t *state = file->private_data;
audio_stream_t *os = state->output_stream;
audio_stream_t *is = state->input_stream;
FN_IN;
mutex_lock(&state->mutex);
if (file->f_mode & FMODE_READ) {
audio_discard_buf(is);
DMA_FREE(is);
is->dma_spinref = 0;
if (state->need_tx_for_rx) {
os->spin_idle = 0;
if (!state->wr_ref) {
DMA_FREE(os);
os->dma_spinref = 0;
}
}
state->rd_ref = 0;
}
if (file->f_mode & FMODE_WRITE) {
audio_sync(file);
audio_discard_buf(os);
if (!state->need_tx_for_rx || !state->rd_ref) {
DMA_FREE(os);
os->dma_spinref = 0;
}
state->wr_ref = 0;
}
if (!AUDIO_ACTIVE(state)) {
if (state->hw_shutdown)
state->hw_shutdown(state->data);
}
mutex_unlock(&state->mutex);
module_put(state->owner);
module_put(THIS_MODULE);
FN_OUT(0);
return 0;
}
EXPORT_SYMBOL(audio_register_codec);
EXPORT_SYMBOL(audio_unregister_codec);
EXPORT_SYMBOL(audio_get_fops);
MODULE_AUTHOR("Texas Instruments");
MODULE_DESCRIPTION("Common audio handling for OMAP processors");
MODULE_LICENSE("GPL");
/*
* linux/sound/oss/omap-audio.h
*
* Common audio handling for the OMAP processors
*
* Copyright (C) 2004 Texas Instruments, Inc.
*
* Copyright (C) 2000, 2001 Nicolas Pitre <nico@cam.org>
*
* This package is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*
* History
* -------
* 2004/08/12 Nishanth Menon - Modified to integrate Audio requirements on 1610,1710 platforms
*
* 2004/04/04 Nishanth menon - Added hooks for power management
*
* 2005/12/10 Dirk Behme - Added L/R Channel Interchange fix as proposed by Ajaya Babu
*/
#ifndef __OMAP_AUDIO_H
#define __OMAP_AUDIO_H
/* Requires dma.h */
#include <mach/dma.h>
/*
* Buffer Management
*/
typedef struct {
int offset; /* current offset */
char *data; /* points to actual buffer */
dma_addr_t dma_addr; /* physical buffer address */
int dma_ref; /* DMA refcount */
int master; /* owner for buffer allocation, contain size when true */
} audio_buf_t;
/*
* Structure describing the data stream related information
*/
typedef struct {
char *id; /* identification string */
audio_buf_t *buffers; /* pointer to audio buffer structures */
u_int usr_head; /* user fragment index */
u_int dma_head; /* DMA fragment index to go */
u_int dma_tail; /* DMA fragment index to complete */
u_int fragsize; /* fragment i.e. buffer size */
u_int nbfrags; /* nbr of fragments i.e. buffers */
u_int pending_frags; /* Fragments sent to DMA */
int dma_dev; /* device identifier for DMA */
#ifdef OMAP_DMA_CHAINING_SUPPORT
lch_chain *dma_chain;
dma_regs_t *dma_regs; /* points to our DMA registers */
#else
char started; /* to store if the chain was started or not */
int dma_q_head; /* DMA Channel Q Head */
int dma_q_tail; /* DMA Channel Q Tail */
char dma_q_count; /* DMA Channel Q Count */
char in_use; /* Is this is use? */
int *lch; /* Chain of channels this stream is linked to */
#endif
int input_or_output; /* Direction of this data stream */
int bytecount; /* nbr of processed bytes */
int fragcount; /* nbr of fragment transitions */
struct completion wfc; /* wait for "nbfrags" fragment completion */
wait_queue_head_t wq; /* for poll */
int dma_spinref; /* DMA is spinning */
unsigned mapped:1; /* mmap()'ed buffers */
unsigned active:1; /* actually in progress */
unsigned stopped:1; /* might be active but stopped */
unsigned spin_idle:1; /* have DMA spin on zeros when idle */
unsigned linked:1; /* dma channels linked */
int (*hw_start)(void); /* interface to start HW interface, e.g. McBSP */
int (*hw_stop)(void); /* interface to stop HW interface, e.g. McBSP */
} audio_stream_t;
/*
* State structure for one instance
*/
typedef struct {
struct module *owner; /* Codec module ID */
audio_stream_t *output_stream;
audio_stream_t *input_stream;
unsigned rd_ref:1; /* open reference for recording */
unsigned wr_ref:1; /* open reference for playback */
unsigned need_tx_for_rx:1; /* if data must be sent while receiving */
void *data;
void (*hw_init) (void *);
void (*hw_shutdown) (void *);
int (*client_ioctl) (struct inode *, struct file *, uint, ulong);
int (*hw_probe) (void);
void (*hw_remove) (void);
void (*hw_cleanup) (void);
int (*hw_suspend) (void);
int (*hw_resume) (void);
struct pm_dev *pm_dev;
struct mutex mutex; /* to protect against races in attach() */
} audio_state_t;
#ifdef AUDIO_PM
void audio_ldm_suspend(void *data);
void audio_ldm_resume(void *data);
#endif
/* Register a Codec using this function */
extern int audio_register_codec(audio_state_t * codec_state);
/* Un-Register a Codec using this function */
extern int audio_unregister_codec(audio_state_t * codec_state);
/* Function to provide fops of omap audio driver */
extern struct file_operations *audio_get_fops(void);
/* Function to initialize the device info for audio driver */
extern int audio_dev_init(void);
/* Function to un-initialize the device info for audio driver */
void audio_dev_uninit(void);
#endif /* End of #ifndef __OMAP_AUDIO_H */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment