From e77125105bbfe71f325466cdf9a16e496c96ac7a Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 17 Aug 2010 23:40:24 +0100 Subject: ASoC: Support non-crystal master clocks for WM8731 Instead of unconditionally enabling the crystal oscillator on the WM8731 only enable it when explicitly selected via set_sysclk(), allowing machine drivers to specify that they drive a clock into MCLK alone. This avoids any conflicts between the oscillator and the external MCLK source and saves power for systems which do not need the oscillator. This should also deliver a small power saving on systems using the crystal since the oscillator will only be enabled when the ADC or DAC is active. Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- sound/soc/atmel/sam9g20_wm8731.c | 2 +- sound/soc/au1x/db1200.c | 2 +- sound/soc/codecs/wm8731.c | 31 +++++++++++++++++++++++++++++-- sound/soc/codecs/wm8731.h | 4 +++- sound/soc/pxa/corgi.c | 2 +- sound/soc/pxa/poodle.c | 2 +- 6 files changed, 36 insertions(+), 7 deletions(-) (limited to 'sound') diff --git a/sound/soc/atmel/sam9g20_wm8731.c b/sound/soc/atmel/sam9g20_wm8731.c index 66a6f18..8399ac4 100644 --- a/sound/soc/atmel/sam9g20_wm8731.c +++ b/sound/soc/atmel/sam9g20_wm8731.c @@ -146,7 +146,7 @@ static int at91sam9g20ek_wm8731_init(struct snd_soc_pcm_runtime *rtd) "at91sam9g20ek_wm8731 " ": at91sam9g20ek_wm8731_init() called\n"); - ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK, + ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK_XTAL, MCLK_RATE, SND_SOC_CLOCK_IN); if (ret < 0) { printk(KERN_ERR "Failed to set WM8731 SYSCLK: %d\n", ret); diff --git a/sound/soc/au1x/db1200.c b/sound/soc/au1x/db1200.c index 8780c90..d8dc822 100644 --- a/sound/soc/au1x/db1200.c +++ b/sound/soc/au1x/db1200.c @@ -49,7 +49,7 @@ static int db1200_i2s_startup(struct snd_pcm_substream *substream) int ret; /* WM8731 has its own 12MHz crystal */ - snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK, + snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK_XTAL, 12000000, SND_SOC_CLOCK_IN); /* codec is bitclock and lrclk master */ diff --git a/sound/soc/codecs/wm8731.c b/sound/soc/codecs/wm8731.c index 19844fc..56f5408 100644 --- a/sound/soc/codecs/wm8731.c +++ b/sound/soc/codecs/wm8731.c @@ -46,6 +46,7 @@ struct wm8731_priv { struct regulator_bulk_data supplies[WM8731_NUM_SUPPLIES]; u16 reg_cache[WM8731_CACHEREGNUM]; unsigned int sysclk; + int sysclk_type; }; @@ -110,6 +111,7 @@ static const struct snd_kcontrol_new wm8731_input_mux_controls = SOC_DAPM_ENUM("Input Select", wm8731_enum[0]); static const struct snd_soc_dapm_widget wm8731_dapm_widgets[] = { +SND_SOC_DAPM_SUPPLY("OSC", WM8731_PWR, 5, 1, NULL, 0), SND_SOC_DAPM_MIXER("Output Mixer", WM8731_PWR, 4, 1, &wm8731_output_mixer_controls[0], ARRAY_SIZE(wm8731_output_mixer_controls)), @@ -127,7 +129,18 @@ SND_SOC_DAPM_INPUT("RLINEIN"), SND_SOC_DAPM_INPUT("LLINEIN"), }; +static int wm8731_check_osc(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + struct wm8731_priv *wm8731 = snd_soc_codec_get_drvdata(source->codec); + + return wm8731->sysclk_type == WM8731_SYSCLK_MCLK; +} + static const struct snd_soc_dapm_route intercon[] = { + {"DAC", NULL, "OSC", wm8731_check_osc}, + {"ADC", NULL, "OSC", wm8731_check_osc}, + /* output mixer */ {"Output Mixer", "Line Bypass Switch", "Line Input"}, {"Output Mixer", "HiFi Playback Switch", "DAC"}, @@ -285,6 +298,15 @@ static int wm8731_set_dai_sysclk(struct snd_soc_dai *codec_dai, struct snd_soc_codec *codec = codec_dai->codec; struct wm8731_priv *wm8731 = snd_soc_codec_get_drvdata(codec); + switch (clk_id) { + case WM8731_SYSCLK_XTAL: + case WM8731_SYSCLK_MCLK: + wm8731->sysclk_type = clk_id; + break; + default: + return -EINVAL; + } + switch (freq) { case 11289600: case 12000000: @@ -292,9 +314,14 @@ static int wm8731_set_dai_sysclk(struct snd_soc_dai *codec_dai, case 16934400: case 18432000: wm8731->sysclk = freq; - return 0; + break; + default: + return -EINVAL; } - return -EINVAL; + + snd_soc_dapm_sync(codec); + + return 0; } diff --git a/sound/soc/codecs/wm8731.h b/sound/soc/codecs/wm8731.h index 73a70e2..e9c0c76 100644 --- a/sound/soc/codecs/wm8731.h +++ b/sound/soc/codecs/wm8731.h @@ -31,7 +31,9 @@ #define WM8731_CACHEREGNUM 10 -#define WM8731_SYSCLK 0 +#define WM8731_SYSCLK_XTAL 1 +#define WM8731_SYSCLK_MCLK 2 + #define WM8731_DAI 0 #endif diff --git a/sound/soc/pxa/corgi.c b/sound/soc/pxa/corgi.c index 555689c..97e9423 100644 --- a/sound/soc/pxa/corgi.c +++ b/sound/soc/pxa/corgi.c @@ -149,7 +149,7 @@ static int corgi_hw_params(struct snd_pcm_substream *substream, return ret; /* set the codec system clock for DAC and ADC */ - ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK, clk, + ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK_XTAL, clk, SND_SOC_CLOCK_IN); if (ret < 0) return ret; diff --git a/sound/soc/pxa/poodle.c b/sound/soc/pxa/poodle.c index add0e1c..fa752f6 100644 --- a/sound/soc/pxa/poodle.c +++ b/sound/soc/pxa/poodle.c @@ -128,7 +128,7 @@ static int poodle_hw_params(struct snd_pcm_substream *substream, return ret; /* set the codec system clock for DAC and ADC */ - ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK, clk, + ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK_XTAL, clk, SND_SOC_CLOCK_IN); if (ret < 0) return ret; -- cgit v1.1 From dad965f07be946726c6153cf578d089ea991f41e Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 18 Aug 2010 16:25:59 +0100 Subject: ASoC: Fix device name for AT91SAM9G20-EK devices A couple of typos in the multi-component conversion. Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- sound/soc/atmel/sam9g20_wm8731.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/soc/atmel/sam9g20_wm8731.c b/sound/soc/atmel/sam9g20_wm8731.c index 8399ac4..cf029a8 100644 --- a/sound/soc/atmel/sam9g20_wm8731.c +++ b/sound/soc/atmel/sam9g20_wm8731.c @@ -183,8 +183,8 @@ static struct snd_soc_dai_link at91sam9g20ek_dai = { .cpu_dai_name = "atmel-ssc-dai.0", .codec_dai_name = "wm8731-hifi", .init = at91sam9g20ek_wm8731_init, - .platform_name = "atmel_pcm-audio", - .codec_name = "wm8731-codec.0-001a", + .platform_name = "atmel-pcm-audio", + .codec_name = "wm8731-codec.0-001b", .ops = &at91sam9g20ek_ops, }; -- cgit v1.1 From abfa4eae0bd2723859931631771ac275f97cada4 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 18 Aug 2010 16:29:37 +0100 Subject: ASoC: Add simplfied device registration for Atmel SSC devices Since the SSC is already being registered as a device under arch and the DMA and SSC hardware are pretty much the same provide a simplified device registration function for the Atmel SSC which will add the ASoC-specific devices within the ASoC code, parenting the SSC device off the actual SSC device. Also use it in the sam9g20-ek driver. Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- sound/soc/atmel/atmel_ssc_dai.c | 57 +++++++++++++++++++++++++++++++++++++--- sound/soc/atmel/atmel_ssc_dai.h | 2 ++ sound/soc/atmel/sam9g20_wm8731.c | 6 +++++ 3 files changed, 62 insertions(+), 3 deletions(-) (limited to 'sound') diff --git a/sound/soc/atmel/atmel_ssc_dai.c b/sound/soc/atmel/atmel_ssc_dai.c index eabf66a..5d230ce 100644 --- a/sound/soc/atmel/atmel_ssc_dai.c +++ b/sound/soc/atmel/atmel_ssc_dai.c @@ -789,13 +789,14 @@ static struct snd_soc_dai_driver atmel_ssc_dai[NUM_SSC_DEVICES] = { static __devinit int asoc_ssc_probe(struct platform_device *pdev) { - return snd_soc_register_dais(&pdev->dev, atmel_ssc_dai, - ARRAY_SIZE(atmel_ssc_dai)); + BUG_ON(pdev->id < 0); + BUG_ON(pdev->id >= ARRAY_SIZE(atmel_ssc_dai)); + return snd_soc_register_dai(&pdev->dev, &atmel_ssc_dai[pdev->id]); } static int __devexit asoc_ssc_remove(struct platform_device *pdev) { - snd_soc_unregister_dais(&pdev->dev, ARRAY_SIZE(atmel_ssc_dai)); + snd_soc_unregister_dai(&pdev->dev); return 0; } @@ -809,6 +810,56 @@ static struct platform_driver asoc_ssc_driver = { .remove = __devexit_p(asoc_ssc_remove), }; +/** + * atmel_ssc_set_audio - Allocate the specified SSC for audio use. + */ +int atmel_ssc_set_audio(int ssc_id) +{ + struct ssc_device *ssc; + static struct platform_device *dma_pdev; + struct platform_device *ssc_pdev; + int ret; + + if (ssc_id < 0 || ssc_id >= ARRAY_SIZE(atmel_ssc_dai)) + return -EINVAL; + + /* Allocate a dummy device for DMA if we don't have one already */ + if (!dma_pdev) { + dma_pdev = platform_device_alloc("atmel-pcm-audio", -1); + if (!dma_pdev) + return -ENOMEM; + + ret = platform_device_add(dma_pdev); + if (ret < 0) { + platform_device_put(dma_pdev); + dma_pdev = NULL; + return ret; + } + } + + ssc_pdev = platform_device_alloc("atmel-ssc-dai", ssc_id); + if (!ssc_pdev) { + ssc_free(ssc); + return -ENOMEM; + } + + /* If we can grab the SSC briefly to parent the DAI device off it */ + ssc = ssc_request(ssc_id); + if (IS_ERR(ssc)) + pr_warn("Unable to parent ASoC SSC DAI on SSC: %ld\n", + PTR_ERR(ssc)); + else + ssc_pdev->dev.parent = &(ssc->pdev->dev); + ssc_free(ssc); + + ret = platform_device_add(ssc_pdev); + if (ret < 0) + platform_device_put(ssc_pdev); + + return ret; +} +EXPORT_SYMBOL_GPL(atmel_ssc_set_audio); + static int __init snd_atmel_ssc_init(void) { return platform_driver_register(&asoc_ssc_driver); diff --git a/sound/soc/atmel/atmel_ssc_dai.h b/sound/soc/atmel/atmel_ssc_dai.h index 392a469..5d4f0f9 100644 --- a/sound/soc/atmel/atmel_ssc_dai.h +++ b/sound/soc/atmel/atmel_ssc_dai.h @@ -117,4 +117,6 @@ struct atmel_ssc_info { struct atmel_ssc_state ssc_state; }; +int atmel_ssc_set_audio(int ssc); + #endif /* _AT91_SSC_DAI_H */ diff --git a/sound/soc/atmel/sam9g20_wm8731.c b/sound/soc/atmel/sam9g20_wm8731.c index cf029a8..293569d 100644 --- a/sound/soc/atmel/sam9g20_wm8731.c +++ b/sound/soc/atmel/sam9g20_wm8731.c @@ -205,6 +205,12 @@ static int __init at91sam9g20ek_init(void) if (!(machine_is_at91sam9g20ek() || machine_is_at91sam9g20ek_2mmc())) return -ENODEV; + ret = atmel_ssc_set_audio(0); + if (ret != 0) { + pr_err("Failed to set SSC 0 for audio: %d\n", ret); + return ret; + } + /* * Codec MCLK is supplied by PCK0 - set it up. */ -- cgit v1.1 From f213f4b51777408c12bf6b890a9bcae385f7698f Mon Sep 17 00:00:00 2001 From: Haojian Zhuang Date: Thu, 19 Aug 2010 00:35:25 +0800 Subject: ASoC: add 88pm860x codec driver Add 88PM860x codec driver. 88PM860x codec supports two interfaces. And it also supports headset/mic/hook/short detection. Signed-off-by: Haojian Zhuang Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/codecs/88pm860x-codec.c | 1486 +++++++++++++++++++++++++++++++++++++ sound/soc/codecs/88pm860x-codec.h | 97 +++ sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + 4 files changed, 1589 insertions(+) create mode 100644 sound/soc/codecs/88pm860x-codec.c create mode 100644 sound/soc/codecs/88pm860x-codec.h (limited to 'sound') diff --git a/sound/soc/codecs/88pm860x-codec.c b/sound/soc/codecs/88pm860x-codec.c new file mode 100644 index 0000000..01d19e9 --- /dev/null +++ b/sound/soc/codecs/88pm860x-codec.c @@ -0,0 +1,1486 @@ +/* + * 88pm860x-codec.c -- 88PM860x ALSA SoC Audio Driver + * + * Copyright 2010 Marvell International Ltd. + * Author: Haojian Zhuang + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "88pm860x-codec.h" + +#define MAX_NAME_LEN 20 +#define REG_CACHE_SIZE 0x40 +#define REG_CACHE_BASE 0xb0 + +/* Status Register 1 (0x01) */ +#define REG_STATUS_1 0x01 +#define MIC_STATUS (1 << 7) +#define HOOK_STATUS (1 << 6) +#define HEADSET_STATUS (1 << 5) + +/* Mic Detection Register (0x37) */ +#define REG_MIC_DET 0x37 +#define CONTINUOUS_POLLING (3 << 1) +#define EN_MIC_DET (1 << 0) +#define MICDET_MASK 0x07 + +/* Headset Detection Register (0x38) */ +#define REG_HS_DET 0x38 +#define EN_HS_DET (1 << 0) + +/* Misc2 Register (0x42) */ +#define REG_MISC2 0x42 +#define AUDIO_PLL (1 << 5) +#define AUDIO_SECTION_RESET (1 << 4) +#define AUDIO_SECTION_ON (1 << 3) + +/* PCM Interface Register 2 (0xb1) */ +#define PCM_INF2_BCLK (1 << 6) /* Bit clock polarity */ +#define PCM_INF2_FS (1 << 5) /* Frame Sync polarity */ +#define PCM_INF2_MASTER (1 << 4) /* Master / Slave */ +#define PCM_INF2_18WL (1 << 3) /* 18 / 16 bits */ +#define PCM_GENERAL_I2S 0 +#define PCM_EXACT_I2S 1 +#define PCM_LEFT_I2S 2 +#define PCM_RIGHT_I2S 3 +#define PCM_SHORT_FS 4 +#define PCM_LONG_FS 5 +#define PCM_MODE_MASK 7 + +/* I2S Interface Register 4 (0xbe) */ +#define I2S_EQU_BYP (1 << 6) + +/* DAC Offset Register (0xcb) */ +#define DAC_MUTE (1 << 7) +#define MUTE_LEFT (1 << 6) +#define MUTE_RIGHT (1 << 2) + +/* ADC Analog Register 1 (0xd0) */ +#define REG_ADC_ANA_1 0xd0 +#define MIC1BIAS_MASK 0x60 + +/* Earpiece/Speaker Control Register 2 (0xda) */ +#define REG_EAR2 0xda +#define RSYNC_CHANGE (1 << 2) + +/* Audio Supplies Register 2 (0xdc) */ +#define REG_SUPPLIES2 0xdc +#define LDO15_READY (1 << 4) +#define LDO15_EN (1 << 3) +#define CPUMP_READY (1 << 2) +#define CPUMP_EN (1 << 1) +#define AUDIO_EN (1 << 0) +#define SUPPLY_MASK (LDO15_EN | CPUMP_EN | AUDIO_EN) + +/* Audio Enable Register 1 (0xdd) */ +#define ADC_MOD_RIGHT (1 << 1) +#define ADC_MOD_LEFT (1 << 0) + +/* Audio Enable Register 2 (0xde) */ +#define ADC_LEFT (1 << 5) +#define ADC_RIGHT (1 << 4) + +/* DAC Enable Register 2 (0xe1) */ +#define DAC_LEFT (1 << 5) +#define DAC_RIGHT (1 << 4) +#define MODULATOR (1 << 3) + +/* Shorts Register (0xeb) */ +#define REG_SHORTS 0xeb +#define CLR_SHORT_LO2 (1 << 7) +#define SHORT_LO2 (1 << 6) +#define CLR_SHORT_LO1 (1 << 5) +#define SHORT_LO1 (1 << 4) +#define CLR_SHORT_HS2 (1 << 3) +#define SHORT_HS2 (1 << 2) +#define CLR_SHORT_HS1 (1 << 1) +#define SHORT_HS1 (1 << 0) + +/* + * This widget should be just after DAC & PGA in DAPM power-on sequence and + * before DAC & PGA in DAPM power-off sequence. + */ +#define PM860X_DAPM_OUTPUT(wname, wevent) \ +{ .id = snd_soc_dapm_pga, .name = wname, .reg = SND_SOC_NOPM, \ + .shift = 0, .invert = 0, .kcontrols = NULL, \ + .num_kcontrols = 0, .event = wevent, \ + .event_flags = SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD, } + +struct pm860x_det { + struct snd_soc_jack *hp_jack; + struct snd_soc_jack *mic_jack; + int hp_det; + int mic_det; + int hook_det; + int hs_shrt; + int lo_shrt; +}; + +struct pm860x_priv { + unsigned int sysclk; + unsigned int pcmclk; + unsigned int dir; + unsigned int filter; + struct snd_soc_codec *codec; + struct i2c_client *i2c; + struct pm860x_chip *chip; + struct pm860x_det det; + + int irq[4]; + unsigned char name[4][MAX_NAME_LEN]; + unsigned char reg_cache[REG_CACHE_SIZE]; +}; + +/* -9450dB to 0dB in 150dB steps ( mute instead of -9450dB) */ +static const DECLARE_TLV_DB_SCALE(dpga_tlv, -9450, 150, 1); + +/* -9dB to 0db in 3dB steps */ +static const DECLARE_TLV_DB_SCALE(adc_tlv, -900, 300, 0); + +/* {-23, -17, -13.5, -11, -9, -6, -3, 0}dB */ +static const unsigned int mic_tlv[] = { + TLV_DB_RANGE_HEAD(5), + 0, 0, TLV_DB_SCALE_ITEM(-2300, 0, 0), + 1, 1, TLV_DB_SCALE_ITEM(-1700, 0, 0), + 2, 2, TLV_DB_SCALE_ITEM(-1350, 0, 0), + 3, 3, TLV_DB_SCALE_ITEM(-1100, 0, 0), + 4, 7, TLV_DB_SCALE_ITEM(-900, 300, 0), +}; + +/* {0, 0, 0, -6, 0, 6, 12, 18}dB */ +static const unsigned int aux_tlv[] = { + TLV_DB_RANGE_HEAD(2), + 0, 2, TLV_DB_SCALE_ITEM(0, 0, 0), + 3, 7, TLV_DB_SCALE_ITEM(-600, 600, 0), +}; + +/* {-16, -13, -10, -7, -5.2, -3,3, -2.2, 0}dB, mute instead of -16dB */ +static const unsigned int out_tlv[] = { + TLV_DB_RANGE_HEAD(4), + 0, 3, TLV_DB_SCALE_ITEM(-1600, 300, 1), + 4, 4, TLV_DB_SCALE_ITEM(-520, 0, 0), + 5, 5, TLV_DB_SCALE_ITEM(-330, 0, 0), + 6, 7, TLV_DB_SCALE_ITEM(-220, 220, 0), +}; + +static const unsigned int st_tlv[] = { + TLV_DB_RANGE_HEAD(8), + 0, 1, TLV_DB_SCALE_ITEM(-12041, 602, 0), + 2, 3, TLV_DB_SCALE_ITEM(-11087, 250, 0), + 4, 5, TLV_DB_SCALE_ITEM(-10643, 158, 0), + 6, 7, TLV_DB_SCALE_ITEM(-10351, 116, 0), + 8, 9, TLV_DB_SCALE_ITEM(-10133, 92, 0), + 10, 13, TLV_DB_SCALE_ITEM(-9958, 70, 0), + 14, 17, TLV_DB_SCALE_ITEM(-9689, 53, 0), + 18, 271, TLV_DB_SCALE_ITEM(-9484, 37, 0), +}; + +/* Sidetone Gain = M * 2^(-5-N) */ +struct st_gain { + unsigned int db; + unsigned int m; + unsigned int n; +}; + +static struct st_gain st_table[] = { + {-12041, 1, 15}, {-11439, 1, 14}, {-11087, 3, 15}, {-10837, 1, 13}, + {-10643, 5, 15}, {-10485, 3, 14}, {-10351, 7, 15}, {-10235, 1, 12}, + {-10133, 9, 15}, {-10041, 5, 14}, { -9958, 11, 15}, { -9883, 3, 13}, + { -9813, 13, 15}, { -9749, 7, 14}, { -9689, 15, 15}, { -9633, 1, 11}, + { -9580, 17, 15}, { -9531, 9, 14}, { -9484, 19, 15}, { -9439, 5, 13}, + { -9397, 21, 15}, { -9356, 11, 14}, { -9318, 23, 15}, { -9281, 3, 12}, + { -9245, 25, 15}, { -9211, 13, 14}, { -9178, 27, 15}, { -9147, 7, 13}, + { -9116, 29, 15}, { -9087, 15, 14}, { -9058, 31, 15}, { -9031, 1, 10}, + { -8978, 17, 14}, { -8929, 9, 13}, { -8882, 19, 14}, { -8837, 5, 12}, + { -8795, 21, 14}, { -8754, 11, 13}, { -8716, 23, 14}, { -8679, 3, 11}, + { -8643, 25, 14}, { -8609, 13, 13}, { -8576, 27, 14}, { -8545, 7, 12}, + { -8514, 29, 14}, { -8485, 15, 13}, { -8456, 31, 14}, { -8429, 1, 9}, + { -8376, 17, 13}, { -8327, 9, 12}, { -8280, 19, 13}, { -8235, 5, 11}, + { -8193, 21, 13}, { -8152, 11, 12}, { -8114, 23, 13}, { -8077, 3, 10}, + { -8041, 25, 13}, { -8007, 13, 12}, { -7974, 27, 13}, { -7943, 7, 11}, + { -7912, 29, 13}, { -7883, 15, 12}, { -7854, 31, 13}, { -7827, 1, 8}, + { -7774, 17, 12}, { -7724, 9, 11}, { -7678, 19, 12}, { -7633, 5, 10}, + { -7591, 21, 12}, { -7550, 11, 11}, { -7512, 23, 12}, { -7475, 3, 9}, + { -7439, 25, 12}, { -7405, 13, 11}, { -7372, 27, 12}, { -7341, 7, 10}, + { -7310, 29, 12}, { -7281, 15, 11}, { -7252, 31, 12}, { -7225, 1, 7}, + { -7172, 17, 11}, { -7122, 9, 10}, { -7075, 19, 11}, { -7031, 5, 9}, + { -6989, 21, 11}, { -6948, 11, 10}, { -6910, 23, 11}, { -6873, 3, 8}, + { -6837, 25, 11}, { -6803, 13, 10}, { -6770, 27, 11}, { -6739, 7, 9}, + { -6708, 29, 11}, { -6679, 15, 10}, { -6650, 31, 11}, { -6623, 1, 6}, + { -6570, 17, 10}, { -6520, 9, 9}, { -6473, 19, 10}, { -6429, 5, 8}, + { -6386, 21, 10}, { -6346, 11, 9}, { -6307, 23, 10}, { -6270, 3, 7}, + { -6235, 25, 10}, { -6201, 13, 9}, { -6168, 27, 10}, { -6137, 7, 8}, + { -6106, 29, 10}, { -6077, 15, 9}, { -6048, 31, 10}, { -6021, 1, 5}, + { -5968, 17, 9}, { -5918, 9, 8}, { -5871, 19, 9}, { -5827, 5, 7}, + { -5784, 21, 9}, { -5744, 11, 8}, { -5705, 23, 9}, { -5668, 3, 6}, + { -5633, 25, 9}, { -5599, 13, 8}, { -5566, 27, 9}, { -5535, 7, 7}, + { -5504, 29, 9}, { -5475, 15, 8}, { -5446, 31, 9}, { -5419, 1, 4}, + { -5366, 17, 8}, { -5316, 9, 7}, { -5269, 19, 8}, { -5225, 5, 6}, + { -5182, 21, 8}, { -5142, 11, 7}, { -5103, 23, 8}, { -5066, 3, 5}, + { -5031, 25, 8}, { -4997, 13, 7}, { -4964, 27, 8}, { -4932, 7, 6}, + { -4902, 29, 8}, { -4873, 15, 7}, { -4844, 31, 8}, { -4816, 1, 3}, + { -4764, 17, 7}, { -4714, 9, 6}, { -4667, 19, 7}, { -4623, 5, 5}, + { -4580, 21, 7}, { -4540, 11, 6}, { -4501, 23, 7}, { -4464, 3, 4}, + { -4429, 25, 7}, { -4395, 13, 6}, { -4362, 27, 7}, { -4330, 7, 5}, + { -4300, 29, 7}, { -4270, 15, 6}, { -4242, 31, 7}, { -4214, 1, 2}, + { -4162, 17, 6}, { -4112, 9, 5}, { -4065, 19, 6}, { -4021, 5, 4}, + { -3978, 21, 6}, { -3938, 11, 5}, { -3899, 23, 6}, { -3862, 3, 3}, + { -3827, 25, 6}, { -3793, 13, 5}, { -3760, 27, 6}, { -3728, 7, 4}, + { -3698, 29, 6}, { -3668, 15, 5}, { -3640, 31, 6}, { -3612, 1, 1}, + { -3560, 17, 5}, { -3510, 9, 4}, { -3463, 19, 5}, { -3419, 5, 3}, + { -3376, 21, 5}, { -3336, 11, 4}, { -3297, 23, 5}, { -3260, 3, 2}, + { -3225, 25, 5}, { -3191, 13, 4}, { -3158, 27, 5}, { -3126, 7, 3}, + { -3096, 29, 5}, { -3066, 15, 4}, { -3038, 31, 5}, { -3010, 1, 0}, + { -2958, 17, 4}, { -2908, 9, 3}, { -2861, 19, 4}, { -2816, 5, 2}, + { -2774, 21, 4}, { -2734, 11, 3}, { -2695, 23, 4}, { -2658, 3, 1}, + { -2623, 25, 4}, { -2589, 13, 3}, { -2556, 27, 4}, { -2524, 7, 2}, + { -2494, 29, 4}, { -2464, 15, 3}, { -2436, 31, 4}, { -2408, 2, 0}, + { -2356, 17, 3}, { -2306, 9, 2}, { -2259, 19, 3}, { -2214, 5, 1}, + { -2172, 21, 3}, { -2132, 11, 2}, { -2093, 23, 3}, { -2056, 3, 0}, + { -2021, 25, 3}, { -1987, 13, 2}, { -1954, 27, 3}, { -1922, 7, 1}, + { -1892, 29, 3}, { -1862, 15, 2}, { -1834, 31, 3}, { -1806, 4, 0}, + { -1754, 17, 2}, { -1704, 9, 1}, { -1657, 19, 2}, { -1612, 5, 0}, + { -1570, 21, 2}, { -1530, 11, 1}, { -1491, 23, 2}, { -1454, 6, 0}, + { -1419, 25, 2}, { -1384, 13, 1}, { -1352, 27, 2}, { -1320, 7, 0}, + { -1290, 29, 2}, { -1260, 15, 1}, { -1232, 31, 2}, { -1204, 8, 0}, + { -1151, 17, 1}, { -1102, 9, 0}, { -1055, 19, 1}, { -1010, 10, 0}, + { -968, 21, 1}, { -928, 11, 0}, { -889, 23, 1}, { -852, 12, 0}, + { -816, 25, 1}, { -782, 13, 0}, { -750, 27, 1}, { -718, 14, 0}, + { -688, 29, 1}, { -658, 15, 0}, { -630, 31, 1}, { -602, 16, 0}, + { -549, 17, 0}, { -500, 18, 0}, { -453, 19, 0}, { -408, 20, 0}, + { -366, 21, 0}, { -325, 22, 0}, { -287, 23, 0}, { -250, 24, 0}, + { -214, 25, 0}, { -180, 26, 0}, { -148, 27, 0}, { -116, 28, 0}, + { -86, 29, 0}, { -56, 30, 0}, { -28, 31, 0}, { 0, 0, 0}, +}; + +static int pm860x_volatile(unsigned int reg) +{ + BUG_ON(reg >= REG_CACHE_SIZE); + + switch (reg) { + case PM860X_AUDIO_SUPPLIES_2: + return 1; + } + + return 0; +} + +static unsigned int pm860x_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + unsigned char *cache = codec->reg_cache; + + BUG_ON(reg >= REG_CACHE_SIZE); + + if (pm860x_volatile(reg)) + return cache[reg]; + + reg += REG_CACHE_BASE; + + return pm860x_reg_read(codec->control_data, reg); +} + +static int pm860x_write_reg_cache(struct snd_soc_codec *codec, + unsigned int reg, unsigned int value) +{ + unsigned char *cache = codec->reg_cache; + + BUG_ON(reg >= REG_CACHE_SIZE); + + if (!pm860x_volatile(reg)) + cache[reg] = (unsigned char)value; + + reg += REG_CACHE_BASE; + + return pm860x_reg_write(codec->control_data, reg, value); +} + +static int snd_soc_get_volsw_2r_st(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned int reg = mc->reg; + unsigned int reg2 = mc->rreg; + int val[2], val2[2], i; + + val[0] = snd_soc_read(codec, reg) & 0x3f; + val[1] = (snd_soc_read(codec, PM860X_SIDETONE_SHIFT) >> 4) & 0xf; + val2[0] = snd_soc_read(codec, reg2) & 0x3f; + val2[1] = (snd_soc_read(codec, PM860X_SIDETONE_SHIFT)) & 0xf; + + for (i = 0; i < ARRAY_SIZE(st_table); i++) { + if ((st_table[i].m == val[0]) && (st_table[i].n == val[1])) + ucontrol->value.integer.value[0] = i; + if ((st_table[i].m == val2[0]) && (st_table[i].n == val2[1])) + ucontrol->value.integer.value[1] = i; + } + return 0; +} + +static int snd_soc_put_volsw_2r_st(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned int reg = mc->reg; + unsigned int reg2 = mc->rreg; + int err; + unsigned int val, val2; + + val = ucontrol->value.integer.value[0]; + val2 = ucontrol->value.integer.value[1]; + + err = snd_soc_update_bits(codec, reg, 0x3f, st_table[val].m); + if (err < 0) + return err; + err = snd_soc_update_bits(codec, PM860X_SIDETONE_SHIFT, 0xf0, + st_table[val].n << 4); + if (err < 0) + return err; + + err = snd_soc_update_bits(codec, reg2, 0x3f, st_table[val2].m); + if (err < 0) + return err; + err = snd_soc_update_bits(codec, PM860X_SIDETONE_SHIFT, 0x0f, + st_table[val2].n); + return err; +} + +static int snd_soc_get_volsw_2r_out(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned int reg = mc->reg; + unsigned int reg2 = mc->rreg; + unsigned int shift = mc->shift; + int max = mc->max, val, val2; + unsigned int mask = (1 << fls(max)) - 1; + + val = snd_soc_read(codec, reg) >> shift; + val2 = snd_soc_read(codec, reg2) >> shift; + ucontrol->value.integer.value[0] = (max - val) & mask; + ucontrol->value.integer.value[1] = (max - val2) & mask; + + return 0; +} + +static int snd_soc_put_volsw_2r_out(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned int reg = mc->reg; + unsigned int reg2 = mc->rreg; + unsigned int shift = mc->shift; + int max = mc->max; + unsigned int mask = (1 << fls(max)) - 1; + int err; + unsigned int val, val2, val_mask; + + val_mask = mask << shift; + val = ((max - ucontrol->value.integer.value[0]) & mask); + val2 = ((max - ucontrol->value.integer.value[1]) & mask); + + val = val << shift; + val2 = val2 << shift; + + err = snd_soc_update_bits(codec, reg, val_mask, val); + if (err < 0) + return err; + + err = snd_soc_update_bits(codec, reg2, val_mask, val2); + return err; +} + +/* DAPM Widget Events */ +/* + * A lot registers are belong to RSYNC domain. It requires enabling RSYNC bit + * after updating these registers. Otherwise, these updated registers won't + * be effective. + */ +static int pm860x_rsync_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + /* + * In order to avoid current on the load, mute power-on and power-off + * should be transients. + * Unmute by DAC_MUTE. It should be unmuted when DAPM sequence is + * finished. + */ + snd_soc_update_bits(codec, PM860X_DAC_OFFSET, DAC_MUTE, 0); + snd_soc_update_bits(codec, PM860X_EAR_CTRL_2, + RSYNC_CHANGE, RSYNC_CHANGE); + return 0; +} + +static int pm860x_dac_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + unsigned int dac = 0; + int data; + + if (!strcmp(w->name, "Left DAC")) + dac = DAC_LEFT; + if (!strcmp(w->name, "Right DAC")) + dac = DAC_RIGHT; + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + if (dac) { + /* Auto mute in power-on sequence. */ + dac |= MODULATOR; + snd_soc_update_bits(codec, PM860X_DAC_OFFSET, + DAC_MUTE, DAC_MUTE); + snd_soc_update_bits(codec, PM860X_EAR_CTRL_2, + RSYNC_CHANGE, RSYNC_CHANGE); + /* update dac */ + snd_soc_update_bits(codec, PM860X_DAC_EN_2, + dac, dac); + } + break; + case SND_SOC_DAPM_PRE_PMD: + if (dac) { + /* Auto mute in power-off sequence. */ + snd_soc_update_bits(codec, PM860X_DAC_OFFSET, + DAC_MUTE, DAC_MUTE); + snd_soc_update_bits(codec, PM860X_EAR_CTRL_2, + RSYNC_CHANGE, RSYNC_CHANGE); + /* update dac */ + data = snd_soc_read(codec, PM860X_DAC_EN_2); + data &= ~dac; + if (!(data & (DAC_LEFT | DAC_RIGHT))) + data &= ~MODULATOR; + snd_soc_write(codec, PM860X_DAC_EN_2, data); + } + break; + } + return 0; +} + +static const char *pm860x_opamp_texts[] = {"-50%", "-25%", "0%", "75%"}; + +static const char *pm860x_pa_texts[] = {"-33%", "0%", "33%", "66%"}; + +static const struct soc_enum pm860x_hs1_opamp_enum = + SOC_ENUM_SINGLE(PM860X_HS1_CTRL, 5, 4, pm860x_opamp_texts); + +static const struct soc_enum pm860x_hs2_opamp_enum = + SOC_ENUM_SINGLE(PM860X_HS2_CTRL, 5, 4, pm860x_opamp_texts); + +static const struct soc_enum pm860x_hs1_pa_enum = + SOC_ENUM_SINGLE(PM860X_HS1_CTRL, 3, 4, pm860x_pa_texts); + +static const struct soc_enum pm860x_hs2_pa_enum = + SOC_ENUM_SINGLE(PM860X_HS2_CTRL, 3, 4, pm860x_pa_texts); + +static const struct soc_enum pm860x_lo1_opamp_enum = + SOC_ENUM_SINGLE(PM860X_LO1_CTRL, 5, 4, pm860x_opamp_texts); + +static const struct soc_enum pm860x_lo2_opamp_enum = + SOC_ENUM_SINGLE(PM860X_LO2_CTRL, 5, 4, pm860x_opamp_texts); + +static const struct soc_enum pm860x_lo1_pa_enum = + SOC_ENUM_SINGLE(PM860X_LO1_CTRL, 3, 4, pm860x_pa_texts); + +static const struct soc_enum pm860x_lo2_pa_enum = + SOC_ENUM_SINGLE(PM860X_LO2_CTRL, 3, 4, pm860x_pa_texts); + +static const struct soc_enum pm860x_spk_pa_enum = + SOC_ENUM_SINGLE(PM860X_EAR_CTRL_1, 5, 4, pm860x_pa_texts); + +static const struct soc_enum pm860x_ear_pa_enum = + SOC_ENUM_SINGLE(PM860X_EAR_CTRL_2, 0, 4, pm860x_pa_texts); + +static const struct soc_enum pm860x_spk_ear_opamp_enum = + SOC_ENUM_SINGLE(PM860X_EAR_CTRL_1, 3, 4, pm860x_opamp_texts); + +static const struct snd_kcontrol_new pm860x_snd_controls[] = { + SOC_DOUBLE_R_TLV("ADC Capture Volume", PM860X_ADC_ANA_2, + PM860X_ADC_ANA_3, 6, 3, 0, adc_tlv), + SOC_DOUBLE_TLV("AUX Capture Volume", PM860X_ADC_ANA_3, 0, 3, 7, 0, + aux_tlv), + SOC_SINGLE_TLV("MIC1 Capture Volume", PM860X_ADC_ANA_2, 0, 7, 0, + mic_tlv), + SOC_SINGLE_TLV("MIC3 Capture Volume", PM860X_ADC_ANA_2, 3, 7, 0, + mic_tlv), + SOC_DOUBLE_R_EXT_TLV("Sidetone Volume", PM860X_SIDETONE_L_GAIN, + PM860X_SIDETONE_R_GAIN, 0, ARRAY_SIZE(st_table)-1, + 0, snd_soc_get_volsw_2r_st, + snd_soc_put_volsw_2r_st, st_tlv), + SOC_SINGLE_TLV("Speaker Playback Volume", PM860X_EAR_CTRL_1, + 0, 7, 0, out_tlv), + SOC_DOUBLE_R_TLV("Line Playback Volume", PM860X_LO1_CTRL, + PM860X_LO2_CTRL, 0, 7, 0, out_tlv), + SOC_DOUBLE_R_TLV("Headset Playback Volume", PM860X_HS1_CTRL, + PM860X_HS2_CTRL, 0, 7, 0, out_tlv), + SOC_DOUBLE_R_EXT_TLV("Hifi Left Playback Volume", + PM860X_HIFIL_GAIN_LEFT, + PM860X_HIFIL_GAIN_RIGHT, 0, 63, 0, + snd_soc_get_volsw_2r_out, + snd_soc_put_volsw_2r_out, dpga_tlv), + SOC_DOUBLE_R_EXT_TLV("Hifi Right Playback Volume", + PM860X_HIFIR_GAIN_LEFT, + PM860X_HIFIR_GAIN_RIGHT, 0, 63, 0, + snd_soc_get_volsw_2r_out, + snd_soc_put_volsw_2r_out, dpga_tlv), + SOC_DOUBLE_R_EXT_TLV("Lofi Playback Volume", PM860X_LOFI_GAIN_LEFT, + PM860X_LOFI_GAIN_RIGHT, 0, 63, 0, + snd_soc_get_volsw_2r_out, + snd_soc_put_volsw_2r_out, dpga_tlv), + SOC_ENUM("Headset1 Operational Amplifier Current", + pm860x_hs1_opamp_enum), + SOC_ENUM("Headset2 Operational Amplifier Current", + pm860x_hs2_opamp_enum), + SOC_ENUM("Headset1 Amplifier Current", pm860x_hs1_pa_enum), + SOC_ENUM("Headset2 Amplifier Current", pm860x_hs2_pa_enum), + SOC_ENUM("Lineout1 Operational Amplifier Current", + pm860x_lo1_opamp_enum), + SOC_ENUM("Lineout2 Operational Amplifier Current", + pm860x_lo2_opamp_enum), + SOC_ENUM("Lineout1 Amplifier Current", pm860x_lo1_pa_enum), + SOC_ENUM("Lineout2 Amplifier Current", pm860x_lo2_pa_enum), + SOC_ENUM("Speaker Operational Amplifier Current", + pm860x_spk_ear_opamp_enum), + SOC_ENUM("Speaker Amplifier Current", pm860x_spk_pa_enum), + SOC_ENUM("Earpiece Amplifier Current", pm860x_ear_pa_enum), +}; + +/* + * DAPM Controls + */ + +/* PCM Switch / PCM Interface */ +static const struct snd_kcontrol_new pcm_switch_controls = + SOC_DAPM_SINGLE("Switch", PM860X_ADC_EN_2, 0, 1, 0); + +/* AUX1 Switch */ +static const struct snd_kcontrol_new aux1_switch_controls = + SOC_DAPM_SINGLE("Switch", PM860X_ANA_TO_ANA, 4, 1, 0); + +/* AUX2 Switch */ +static const struct snd_kcontrol_new aux2_switch_controls = + SOC_DAPM_SINGLE("Switch", PM860X_ANA_TO_ANA, 5, 1, 0); + +/* Left Ex. PA Switch */ +static const struct snd_kcontrol_new lepa_switch_controls = + SOC_DAPM_SINGLE("Switch", PM860X_DAC_EN_2, 2, 1, 0); + +/* Right Ex. PA Switch */ +static const struct snd_kcontrol_new repa_switch_controls = + SOC_DAPM_SINGLE("Switch", PM860X_DAC_EN_2, 1, 1, 0); + +/* PCM Mux / Mux7 */ +static const char *aif1_text[] = { + "PCM L", "PCM R", +}; + +static const struct soc_enum aif1_enum = + SOC_ENUM_SINGLE(PM860X_PCM_IFACE_3, 6, 2, aif1_text); + +static const struct snd_kcontrol_new aif1_mux = + SOC_DAPM_ENUM("PCM Mux", aif1_enum); + +/* I2S Mux / Mux9 */ +static const char *i2s_din_text[] = { + "DIN", "DIN1", +}; + +static const struct soc_enum i2s_din_enum = + SOC_ENUM_SINGLE(PM860X_I2S_IFACE_3, 1, 2, i2s_din_text); + +static const struct snd_kcontrol_new i2s_din_mux = + SOC_DAPM_ENUM("I2S DIN Mux", i2s_din_enum); + +/* I2S Mic Mux / Mux8 */ +static const char *i2s_mic_text[] = { + "Ex PA", "ADC", +}; + +static const struct soc_enum i2s_mic_enum = + SOC_ENUM_SINGLE(PM860X_I2S_IFACE_3, 4, 2, i2s_mic_text); + +static const struct snd_kcontrol_new i2s_mic_mux = + SOC_DAPM_ENUM("I2S Mic Mux", i2s_mic_enum); + +/* ADCL Mux / Mux2 */ +static const char *adcl_text[] = { + "ADCR", "ADCL", +}; + +static const struct soc_enum adcl_enum = + SOC_ENUM_SINGLE(PM860X_PCM_IFACE_3, 4, 2, adcl_text); + +static const struct snd_kcontrol_new adcl_mux = + SOC_DAPM_ENUM("ADC Left Mux", adcl_enum); + +/* ADCR Mux / Mux3 */ +static const char *adcr_text[] = { + "ADCL", "ADCR", +}; + +static const struct soc_enum adcr_enum = + SOC_ENUM_SINGLE(PM860X_PCM_IFACE_3, 2, 2, adcr_text); + +static const struct snd_kcontrol_new adcr_mux = + SOC_DAPM_ENUM("ADC Right Mux", adcr_enum); + +/* ADCR EC Mux / Mux6 */ +static const char *adcr_ec_text[] = { + "ADCR", "EC", +}; + +static const struct soc_enum adcr_ec_enum = + SOC_ENUM_SINGLE(PM860X_ADC_EN_2, 3, 2, adcr_ec_text); + +static const struct snd_kcontrol_new adcr_ec_mux = + SOC_DAPM_ENUM("ADCR EC Mux", adcr_ec_enum); + +/* EC Mux / Mux4 */ +static const char *ec_text[] = { + "Left", "Right", "Left + Right", +}; + +static const struct soc_enum ec_enum = + SOC_ENUM_SINGLE(PM860X_EC_PATH, 1, 3, ec_text); + +static const struct snd_kcontrol_new ec_mux = + SOC_DAPM_ENUM("EC Mux", ec_enum); + +static const char *dac_text[] = { + "No input", "Right", "Left", "No input", +}; + +/* DAC Headset 1 Mux / Mux10 */ +static const struct soc_enum dac_hs1_enum = + SOC_ENUM_SINGLE(PM860X_ANA_INPUT_SEL_1, 0, 4, dac_text); + +static const struct snd_kcontrol_new dac_hs1_mux = + SOC_DAPM_ENUM("DAC HS1 Mux", dac_hs1_enum); + +/* DAC Headset 2 Mux / Mux11 */ +static const struct soc_enum dac_hs2_enum = + SOC_ENUM_SINGLE(PM860X_ANA_INPUT_SEL_1, 2, 4, dac_text); + +static const struct snd_kcontrol_new dac_hs2_mux = + SOC_DAPM_ENUM("DAC HS2 Mux", dac_hs2_enum); + +/* DAC Lineout 1 Mux / Mux12 */ +static const struct soc_enum dac_lo1_enum = + SOC_ENUM_SINGLE(PM860X_ANA_INPUT_SEL_1, 4, 4, dac_text); + +static const struct snd_kcontrol_new dac_lo1_mux = + SOC_DAPM_ENUM("DAC LO1 Mux", dac_lo1_enum); + +/* DAC Lineout 2 Mux / Mux13 */ +static const struct soc_enum dac_lo2_enum = + SOC_ENUM_SINGLE(PM860X_ANA_INPUT_SEL_1, 6, 4, dac_text); + +static const struct snd_kcontrol_new dac_lo2_mux = + SOC_DAPM_ENUM("DAC LO2 Mux", dac_lo2_enum); + +/* DAC Spearker Earphone Mux / Mux14 */ +static const struct soc_enum dac_spk_ear_enum = + SOC_ENUM_SINGLE(PM860X_ANA_INPUT_SEL_2, 0, 4, dac_text); + +static const struct snd_kcontrol_new dac_spk_ear_mux = + SOC_DAPM_ENUM("DAC SP Mux", dac_spk_ear_enum); + +/* Headset 1 Mux / Mux15 */ +static const char *in_text[] = { + "Digital", "Analog", +}; + +static const struct soc_enum hs1_enum = + SOC_ENUM_SINGLE(PM860X_ANA_TO_ANA, 0, 2, in_text); + +static const struct snd_kcontrol_new hs1_mux = + SOC_DAPM_ENUM("Headset1 Mux", hs1_enum); + +/* Headset 2 Mux / Mux16 */ +static const struct soc_enum hs2_enum = + SOC_ENUM_SINGLE(PM860X_ANA_TO_ANA, 1, 2, in_text); + +static const struct snd_kcontrol_new hs2_mux = + SOC_DAPM_ENUM("Headset2 Mux", hs2_enum); + +/* Lineout 1 Mux / Mux17 */ +static const struct soc_enum lo1_enum = + SOC_ENUM_SINGLE(PM860X_ANA_TO_ANA, 2, 2, in_text); + +static const struct snd_kcontrol_new lo1_mux = + SOC_DAPM_ENUM("Lineout1 Mux", lo1_enum); + +/* Lineout 2 Mux / Mux18 */ +static const struct soc_enum lo2_enum = + SOC_ENUM_SINGLE(PM860X_ANA_TO_ANA, 3, 2, in_text); + +static const struct snd_kcontrol_new lo2_mux = + SOC_DAPM_ENUM("Lineout2 Mux", lo2_enum); + +/* Speaker Earpiece Demux */ +static const char *spk_text[] = { + "Earpiece", "Speaker", +}; + +static const struct soc_enum spk_enum = + SOC_ENUM_SINGLE(PM860X_ANA_TO_ANA, 6, 2, spk_text); + +static const struct snd_kcontrol_new spk_demux = + SOC_DAPM_ENUM("Speaker Earpiece Demux", spk_enum); + +/* MIC Mux / Mux1 */ +static const char *mic_text[] = { + "Mic 1", "Mic 2", +}; + +static const struct soc_enum mic_enum = + SOC_ENUM_SINGLE(PM860X_ADC_ANA_4, 4, 2, mic_text); + +static const struct snd_kcontrol_new mic_mux = + SOC_DAPM_ENUM("MIC Mux", mic_enum); + +static const struct snd_soc_dapm_widget pm860x_dapm_widgets[] = { + SND_SOC_DAPM_AIF_IN("PCM SDI", "PCM Playback", 0, + PM860X_ADC_EN_2, 0, 0), + SND_SOC_DAPM_AIF_OUT("PCM SDO", "PCM Capture", 0, + PM860X_PCM_IFACE_3, 1, 1), + + + SND_SOC_DAPM_AIF_IN("I2S DIN", "I2S Playback", 0, + PM860X_DAC_EN_2, 0, 0), + SND_SOC_DAPM_AIF_IN("I2S DIN1", "I2S Playback", 0, + PM860X_DAC_EN_2, 0, 0), + SND_SOC_DAPM_AIF_OUT("I2S DOUT", "I2S Capture", 0, + PM860X_I2S_IFACE_3, 5, 1), + SND_SOC_DAPM_MUX("I2S Mic Mux", SND_SOC_NOPM, 0, 0, &i2s_mic_mux), + SND_SOC_DAPM_MUX("ADC Left Mux", SND_SOC_NOPM, 0, 0, &adcl_mux), + SND_SOC_DAPM_MUX("ADC Right Mux", SND_SOC_NOPM, 0, 0, &adcr_mux), + SND_SOC_DAPM_MUX("EC Mux", SND_SOC_NOPM, 0, 0, &ec_mux), + SND_SOC_DAPM_MUX("ADCR EC Mux", SND_SOC_NOPM, 0, 0, &adcr_ec_mux), + SND_SOC_DAPM_SWITCH("Left EPA", SND_SOC_NOPM, 0, 0, + &lepa_switch_controls), + SND_SOC_DAPM_SWITCH("Right EPA", SND_SOC_NOPM, 0, 0, + &repa_switch_controls), + + SND_SOC_DAPM_REG(snd_soc_dapm_supply, "Left ADC MOD", PM860X_ADC_EN_1, + 0, 1, 1, 0), + SND_SOC_DAPM_REG(snd_soc_dapm_supply, "Right ADC MOD", PM860X_ADC_EN_1, + 1, 1, 1, 0), + SND_SOC_DAPM_ADC("Left ADC", NULL, PM860X_ADC_EN_2, 5, 0), + SND_SOC_DAPM_ADC("Right ADC", NULL, PM860X_ADC_EN_2, 4, 0), + + SND_SOC_DAPM_SWITCH("AUX1 Switch", SND_SOC_NOPM, 0, 0, + &aux1_switch_controls), + SND_SOC_DAPM_SWITCH("AUX2 Switch", SND_SOC_NOPM, 0, 0, + &aux2_switch_controls), + + SND_SOC_DAPM_MUX("MIC Mux", SND_SOC_NOPM, 0, 0, &mic_mux), + SND_SOC_DAPM_MICBIAS("Mic1 Bias", PM860X_ADC_ANA_1, 2, 0), + SND_SOC_DAPM_MICBIAS("Mic3 Bias", PM860X_ADC_ANA_1, 7, 0), + SND_SOC_DAPM_PGA("MIC1 Volume", PM860X_ADC_EN_1, 2, 0, NULL, 0), + SND_SOC_DAPM_PGA("MIC3 Volume", PM860X_ADC_EN_1, 3, 0, NULL, 0), + SND_SOC_DAPM_PGA("AUX1 Volume", PM860X_ADC_EN_1, 4, 0, NULL, 0), + SND_SOC_DAPM_PGA("AUX2 Volume", PM860X_ADC_EN_1, 5, 0, NULL, 0), + SND_SOC_DAPM_PGA("Sidetone PGA", PM860X_ADC_EN_2, 1, 0, NULL, 0), + SND_SOC_DAPM_PGA("Lofi PGA", PM860X_ADC_EN_2, 2, 0, NULL, 0), + + SND_SOC_DAPM_INPUT("AUX1"), + SND_SOC_DAPM_INPUT("AUX2"), + SND_SOC_DAPM_INPUT("MIC1P"), + SND_SOC_DAPM_INPUT("MIC1N"), + SND_SOC_DAPM_INPUT("MIC2P"), + SND_SOC_DAPM_INPUT("MIC2N"), + SND_SOC_DAPM_INPUT("MIC3P"), + SND_SOC_DAPM_INPUT("MIC3N"), + + SND_SOC_DAPM_DAC_E("Left DAC", NULL, SND_SOC_NOPM, 0, 0, + pm860x_dac_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_DAC_E("Right DAC", NULL, SND_SOC_NOPM, 0, 0, + pm860x_dac_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), + + SND_SOC_DAPM_MUX("I2S DIN Mux", SND_SOC_NOPM, 0, 0, &i2s_din_mux), + SND_SOC_DAPM_MUX("DAC HS1 Mux", SND_SOC_NOPM, 0, 0, &dac_hs1_mux), + SND_SOC_DAPM_MUX("DAC HS2 Mux", SND_SOC_NOPM, 0, 0, &dac_hs2_mux), + SND_SOC_DAPM_MUX("DAC LO1 Mux", SND_SOC_NOPM, 0, 0, &dac_lo1_mux), + SND_SOC_DAPM_MUX("DAC LO2 Mux", SND_SOC_NOPM, 0, 0, &dac_lo2_mux), + SND_SOC_DAPM_MUX("DAC SP Mux", SND_SOC_NOPM, 0, 0, &dac_spk_ear_mux), + SND_SOC_DAPM_MUX("Headset1 Mux", SND_SOC_NOPM, 0, 0, &hs1_mux), + SND_SOC_DAPM_MUX("Headset2 Mux", SND_SOC_NOPM, 0, 0, &hs2_mux), + SND_SOC_DAPM_MUX("Lineout1 Mux", SND_SOC_NOPM, 0, 0, &lo1_mux), + SND_SOC_DAPM_MUX("Lineout2 Mux", SND_SOC_NOPM, 0, 0, &lo2_mux), + SND_SOC_DAPM_MUX("Speaker Earpiece Demux", SND_SOC_NOPM, 0, 0, + &spk_demux), + + + SND_SOC_DAPM_PGA("Headset1 PGA", PM860X_DAC_EN_1, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Headset2 PGA", PM860X_DAC_EN_1, 1, 0, NULL, 0), + SND_SOC_DAPM_OUTPUT("HS1"), + SND_SOC_DAPM_OUTPUT("HS2"), + SND_SOC_DAPM_PGA("Lineout1 PGA", PM860X_DAC_EN_1, 2, 0, NULL, 0), + SND_SOC_DAPM_PGA("Lineout2 PGA", PM860X_DAC_EN_1, 3, 0, NULL, 0), + SND_SOC_DAPM_OUTPUT("LINEOUT1"), + SND_SOC_DAPM_OUTPUT("LINEOUT2"), + SND_SOC_DAPM_PGA("Earpiece PGA", PM860X_DAC_EN_1, 4, 0, NULL, 0), + SND_SOC_DAPM_OUTPUT("EARP"), + SND_SOC_DAPM_OUTPUT("EARN"), + SND_SOC_DAPM_PGA("Speaker PGA", PM860X_DAC_EN_1, 5, 0, NULL, 0), + SND_SOC_DAPM_OUTPUT("LSP"), + SND_SOC_DAPM_OUTPUT("LSN"), + SND_SOC_DAPM_REG(snd_soc_dapm_supply, "VCODEC", PM860X_AUDIO_SUPPLIES_2, + 0, SUPPLY_MASK, SUPPLY_MASK, 0), + + PM860X_DAPM_OUTPUT("RSYNC", pm860x_rsync_event), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* supply */ + {"Left DAC", NULL, "VCODEC"}, + {"Right DAC", NULL, "VCODEC"}, + {"Left ADC", NULL, "VCODEC"}, + {"Right ADC", NULL, "VCODEC"}, + {"Left ADC", NULL, "Left ADC MOD"}, + {"Right ADC", NULL, "Right ADC MOD"}, + + /* PCM/AIF1 Inputs */ + {"PCM SDO", NULL, "ADC Left Mux"}, + {"PCM SDO", NULL, "ADCR EC Mux"}, + + /* PCM/AFI2 Outputs */ + {"Lofi PGA", NULL, "PCM SDI"}, + {"Lofi PGA", NULL, "Sidetone PGA"}, + {"Left DAC", NULL, "Lofi PGA"}, + {"Right DAC", NULL, "Lofi PGA"}, + + /* I2S/AIF2 Inputs */ + {"MIC Mux", "Mic 1", "MIC1P"}, + {"MIC Mux", "Mic 1", "MIC1N"}, + {"MIC Mux", "Mic 2", "MIC2P"}, + {"MIC Mux", "Mic 2", "MIC2N"}, + {"MIC1 Volume", NULL, "MIC Mux"}, + {"MIC3 Volume", NULL, "MIC3P"}, + {"MIC3 Volume", NULL, "MIC3N"}, + {"Left ADC", NULL, "MIC1 Volume"}, + {"Right ADC", NULL, "MIC3 Volume"}, + {"ADC Left Mux", "ADCR", "Right ADC"}, + {"ADC Left Mux", "ADCL", "Left ADC"}, + {"ADC Right Mux", "ADCL", "Left ADC"}, + {"ADC Right Mux", "ADCR", "Right ADC"}, + {"Left EPA", "Switch", "Left DAC"}, + {"Right EPA", "Switch", "Right DAC"}, + {"EC Mux", "Left", "Left DAC"}, + {"EC Mux", "Right", "Right DAC"}, + {"EC Mux", "Left + Right", "Left DAC"}, + {"EC Mux", "Left + Right", "Right DAC"}, + {"ADCR EC Mux", "ADCR", "ADC Right Mux"}, + {"ADCR EC Mux", "EC", "EC Mux"}, + {"I2S Mic Mux", "Ex PA", "Left EPA"}, + {"I2S Mic Mux", "Ex PA", "Right EPA"}, + {"I2S Mic Mux", "ADC", "ADC Left Mux"}, + {"I2S Mic Mux", "ADC", "ADCR EC Mux"}, + {"I2S DOUT", NULL, "I2S Mic Mux"}, + + /* I2S/AIF2 Outputs */ + {"I2S DIN Mux", "DIN", "I2S DIN"}, + {"I2S DIN Mux", "DIN1", "I2S DIN1"}, + {"Left DAC", NULL, "I2S DIN Mux"}, + {"Right DAC", NULL, "I2S DIN Mux"}, + {"DAC HS1 Mux", "Left", "Left DAC"}, + {"DAC HS1 Mux", "Right", "Right DAC"}, + {"DAC HS2 Mux", "Left", "Left DAC"}, + {"DAC HS2 Mux", "Right", "Right DAC"}, + {"DAC LO1 Mux", "Left", "Left DAC"}, + {"DAC LO1 Mux", "Right", "Right DAC"}, + {"DAC LO2 Mux", "Left", "Left DAC"}, + {"DAC LO2 Mux", "Right", "Right DAC"}, + {"Headset1 Mux", "Digital", "DAC HS1 Mux"}, + {"Headset2 Mux", "Digital", "DAC HS2 Mux"}, + {"Lineout1 Mux", "Digital", "DAC LO1 Mux"}, + {"Lineout2 Mux", "Digital", "DAC LO2 Mux"}, + {"Headset1 PGA", NULL, "Headset1 Mux"}, + {"Headset2 PGA", NULL, "Headset2 Mux"}, + {"Lineout1 PGA", NULL, "Lineout1 Mux"}, + {"Lineout2 PGA", NULL, "Lineout2 Mux"}, + {"DAC SP Mux", "Left", "Left DAC"}, + {"DAC SP Mux", "Right", "Right DAC"}, + {"Speaker Earpiece Demux", "Speaker", "DAC SP Mux"}, + {"Speaker PGA", NULL, "Speaker Earpiece Demux"}, + {"Earpiece PGA", NULL, "Speaker Earpiece Demux"}, + + {"RSYNC", NULL, "Headset1 PGA"}, + {"RSYNC", NULL, "Headset2 PGA"}, + {"RSYNC", NULL, "Lineout1 PGA"}, + {"RSYNC", NULL, "Lineout2 PGA"}, + {"RSYNC", NULL, "Speaker PGA"}, + {"RSYNC", NULL, "Speaker PGA"}, + {"RSYNC", NULL, "Earpiece PGA"}, + {"RSYNC", NULL, "Earpiece PGA"}, + + {"HS1", NULL, "RSYNC"}, + {"HS2", NULL, "RSYNC"}, + {"LINEOUT1", NULL, "RSYNC"}, + {"LINEOUT2", NULL, "RSYNC"}, + {"LSP", NULL, "RSYNC"}, + {"LSN", NULL, "RSYNC"}, + {"EARP", NULL, "RSYNC"}, + {"EARN", NULL, "RSYNC"}, +}; + +/* + * Use MUTE_LEFT & MUTE_RIGHT to implement digital mute. + * These bits can also be used to mute. + */ +static int pm860x_digital_mute(struct snd_soc_dai *codec_dai, int mute) +{ + struct snd_soc_codec *codec = codec_dai->codec; + int data = 0, mask = MUTE_LEFT | MUTE_RIGHT; + + if (mute) + data = mask; + snd_soc_update_bits(codec, PM860X_DAC_OFFSET, mask, data); + snd_soc_update_bits(codec, PM860X_EAR_CTRL_2, + RSYNC_CHANGE, RSYNC_CHANGE); + return 0; +} + +static int pm860x_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + unsigned char inf = 0, mask = 0; + + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + inf &= ~PCM_INF2_18WL; + break; + case SNDRV_PCM_FORMAT_S18_3LE: + inf |= PCM_INF2_18WL; + break; + default: + return -EINVAL; + } + mask |= PCM_INF2_18WL; + snd_soc_update_bits(codec, PM860X_PCM_IFACE_2, mask, inf); + + /* sample rate */ + switch (params_rate(params)) { + case 8000: + inf = 0; + break; + case 16000: + inf = 3; + break; + case 32000: + inf = 6; + break; + case 48000: + inf = 8; + break; + default: + return -EINVAL; + } + snd_soc_update_bits(codec, PM860X_PCM_RATE, 0x0f, inf); + + return 0; +} + +static int pm860x_pcm_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct pm860x_priv *pm860x = snd_soc_codec_get_drvdata(codec); + unsigned char inf = 0, mask = 0; + int ret = -EINVAL; + + mask |= PCM_INF2_BCLK | PCM_INF2_FS | PCM_INF2_MASTER; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + case SND_SOC_DAIFMT_CBM_CFS: + if (pm860x->dir == PM860X_CLK_DIR_OUT) { + inf |= PCM_INF2_MASTER; + ret = 0; + } + break; + case SND_SOC_DAIFMT_CBS_CFS: + if (pm860x->dir == PM860X_CLK_DIR_IN) { + inf &= ~PCM_INF2_MASTER; + ret = 0; + } + break; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + inf |= PCM_EXACT_I2S; + ret = 0; + break; + } + mask |= PCM_MODE_MASK; + if (ret) + return ret; + snd_soc_update_bits(codec, PM860X_PCM_IFACE_2, mask, inf); + return 0; +} + +static int pm860x_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct pm860x_priv *pm860x = snd_soc_codec_get_drvdata(codec); + + if (dir == PM860X_CLK_DIR_OUT) + pm860x->dir = PM860X_CLK_DIR_OUT; + else { + pm860x->dir = PM860X_CLK_DIR_IN; + return -EINVAL; + } + + return 0; +} + +static int pm860x_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + unsigned char inf; + + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + inf = 0; + break; + case SNDRV_PCM_FORMAT_S18_3LE: + inf = PCM_INF2_18WL; + break; + default: + return -EINVAL; + } + snd_soc_update_bits(codec, PM860X_I2S_IFACE_2, PCM_INF2_18WL, inf); + + /* sample rate */ + switch (params_rate(params)) { + case 8000: + inf = 0; + break; + case 11025: + inf = 1; + break; + case 16000: + inf = 3; + break; + case 22050: + inf = 4; + break; + case 32000: + inf = 6; + break; + case 44100: + inf = 7; + break; + case 48000: + inf = 8; + break; + default: + return -EINVAL; + } + snd_soc_update_bits(codec, PM860X_I2S_IFACE_4, 0xf, inf); + + return 0; +} + +static int pm860x_i2s_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct pm860x_priv *pm860x = snd_soc_codec_get_drvdata(codec); + unsigned char inf = 0, mask = 0; + + mask |= PCM_INF2_BCLK | PCM_INF2_FS | PCM_INF2_MASTER; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + if (pm860x->dir == PM860X_CLK_DIR_OUT) + inf |= PCM_INF2_MASTER; + else + return -EINVAL; + break; + case SND_SOC_DAIFMT_CBS_CFS: + if (pm860x->dir == PM860X_CLK_DIR_IN) + inf &= ~PCM_INF2_MASTER; + else + return -EINVAL; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + inf |= PCM_EXACT_I2S; + break; + default: + return -EINVAL; + } + mask |= PCM_MODE_MASK; + snd_soc_update_bits(codec, PM860X_I2S_IFACE_2, mask, inf); + return 0; +} + +static int pm860x_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + int data; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + break; + + case SND_SOC_BIAS_STANDBY: + if (codec->bias_level == SND_SOC_BIAS_OFF) { + /* Enable Audio PLL & Audio section */ + data = AUDIO_PLL | AUDIO_SECTION_RESET + | AUDIO_SECTION_ON; + pm860x_reg_write(codec->control_data, REG_MISC2, data); + } + break; + + case SND_SOC_BIAS_OFF: + data = AUDIO_PLL | AUDIO_SECTION_RESET | AUDIO_SECTION_ON; + pm860x_set_bits(codec->control_data, REG_MISC2, data, 0); + break; + } + codec->bias_level = level; + return 0; +} + +static struct snd_soc_dai_ops pm860x_pcm_dai_ops = { + .digital_mute = pm860x_digital_mute, + .hw_params = pm860x_pcm_hw_params, + .set_fmt = pm860x_pcm_set_dai_fmt, + .set_sysclk = pm860x_set_dai_sysclk, +}; + +static struct snd_soc_dai_ops pm860x_i2s_dai_ops = { + .digital_mute = pm860x_digital_mute, + .hw_params = pm860x_i2s_hw_params, + .set_fmt = pm860x_i2s_set_dai_fmt, + .set_sysclk = pm860x_set_dai_sysclk, +}; + +#define PM860X_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | \ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000) + +static struct snd_soc_dai_driver pm860x_dai[] = { + { + /* DAI PCM */ + .name = "88pm860x-pcm", + .id = 1, + .playback = { + .stream_name = "PCM Playback", + .channels_min = 2, + .channels_max = 2, + .rates = PM860X_RATES, + .formats = SNDRV_PCM_FORMAT_S16_LE | \ + SNDRV_PCM_FORMAT_S18_3LE, + }, + .capture = { + .stream_name = "PCM Capture", + .channels_min = 2, + .channels_max = 2, + .rates = PM860X_RATES, + .formats = SNDRV_PCM_FORMAT_S16_LE | \ + SNDRV_PCM_FORMAT_S18_3LE, + }, + .ops = &pm860x_pcm_dai_ops, + }, { + /* DAI I2S */ + .name = "88pm860x-i2s", + .id = 2, + .playback = { + .stream_name = "I2S Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FORMAT_S16_LE | \ + SNDRV_PCM_FORMAT_S18_3LE, + }, + .capture = { + .stream_name = "I2S Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FORMAT_S16_LE | \ + SNDRV_PCM_FORMAT_S18_3LE, + }, + .ops = &pm860x_i2s_dai_ops, + }, +}; + +static irqreturn_t pm860x_codec_handler(int irq, void *data) +{ + struct pm860x_priv *pm860x = data; + int status, shrt, report = 0, mic_report = 0; + int mask; + + status = pm860x_reg_read(pm860x->i2c, REG_STATUS_1); + shrt = pm860x_reg_read(pm860x->i2c, REG_SHORTS); + mask = pm860x->det.hs_shrt | pm860x->det.hook_det | pm860x->det.lo_shrt + | pm860x->det.hp_det; + + if ((pm860x->det.hp_det & SND_JACK_HEADPHONE) + && (status & HEADSET_STATUS)) + report |= SND_JACK_HEADPHONE; + + if ((pm860x->det.mic_det & SND_JACK_MICROPHONE) + && (status & MIC_STATUS)) + mic_report |= SND_JACK_MICROPHONE; + + if (pm860x->det.hs_shrt && (shrt & (SHORT_HS1 | SHORT_HS2))) + report |= pm860x->det.hs_shrt; + + if (pm860x->det.hook_det && (status & HOOK_STATUS)) + report |= pm860x->det.hook_det; + + if (pm860x->det.lo_shrt && (shrt & (SHORT_LO1 | SHORT_LO2))) + report |= pm860x->det.lo_shrt; + + if (report) + snd_soc_jack_report(pm860x->det.hp_jack, report, mask); + if (mic_report) + snd_soc_jack_report(pm860x->det.mic_jack, SND_JACK_MICROPHONE, + SND_JACK_MICROPHONE); + + dev_dbg(pm860x->codec->dev, "headphone report:0x%x, mask:%x\n", + report, mask); + dev_dbg(pm860x->codec->dev, "microphone report:0x%x\n", mic_report); + return IRQ_HANDLED; +} + +int pm860x_hs_jack_detect(struct snd_soc_codec *codec, + struct snd_soc_jack *jack, + int det, int hook, int hs_shrt, int lo_shrt) +{ + struct pm860x_priv *pm860x = snd_soc_codec_get_drvdata(codec); + int data; + + pm860x->det.hp_jack = jack; + pm860x->det.hp_det = det; + pm860x->det.hook_det = hook; + pm860x->det.hs_shrt = hs_shrt; + pm860x->det.lo_shrt = lo_shrt; + + if (det & SND_JACK_HEADPHONE) + pm860x_set_bits(codec->control_data, REG_HS_DET, + EN_HS_DET, EN_HS_DET); + /* headset short detect */ + if (hs_shrt) { + data = CLR_SHORT_HS2 | CLR_SHORT_HS1; + pm860x_set_bits(codec->control_data, REG_SHORTS, data, data); + } + /* Lineout short detect */ + if (lo_shrt) { + data = CLR_SHORT_LO2 | CLR_SHORT_LO1; + pm860x_set_bits(codec->control_data, REG_SHORTS, data, data); + } + + /* sync status */ + pm860x_codec_handler(0, pm860x); + return 0; +} +EXPORT_SYMBOL_GPL(pm860x_hs_jack_detect); + +int pm860x_mic_jack_detect(struct snd_soc_codec *codec, + struct snd_soc_jack *jack, int det) +{ + struct pm860x_priv *pm860x = snd_soc_codec_get_drvdata(codec); + + pm860x->det.mic_jack = jack; + pm860x->det.mic_det = det; + + if (det & SND_JACK_MICROPHONE) + pm860x_set_bits(codec->control_data, REG_MIC_DET, + MICDET_MASK, MICDET_MASK); + + /* sync status */ + pm860x_codec_handler(0, pm860x); + return 0; +} +EXPORT_SYMBOL_GPL(pm860x_mic_jack_detect); + +static int pm860x_probe(struct snd_soc_codec *codec) +{ + struct pm860x_priv *pm860x = snd_soc_codec_get_drvdata(codec); + int i, ret; + + pm860x->codec = codec; + + codec->control_data = pm860x->i2c; + + for (i = 0; i < 4; i++) { + ret = request_threaded_irq(pm860x->irq[i], NULL, + pm860x_codec_handler, IRQF_ONESHOT, + pm860x->name[i], pm860x); + if (ret < 0) { + dev_err(codec->dev, "Failed to request IRQ!\n"); + goto out_irq; + } + } + + pm860x_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + ret = pm860x_bulk_read(codec->control_data, REG_CACHE_BASE, + REG_CACHE_SIZE, codec->reg_cache); + if (ret < 0) { + dev_err(codec->dev, "Failed to fill register cache: %d\n", + ret); + goto out_codec; + } + + snd_soc_add_controls(codec, pm860x_snd_controls, + ARRAY_SIZE(pm860x_snd_controls)); + snd_soc_dapm_new_controls(codec, pm860x_dapm_widgets, + ARRAY_SIZE(pm860x_dapm_widgets)); + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + return 0; + +out_codec: + i = 3; +out_irq: + for (; i >= 0; i--) + free_irq(pm860x->irq[i], pm860x); + return -EINVAL; +} + +static int pm860x_remove(struct snd_soc_codec *codec) +{ + struct pm860x_priv *pm860x = snd_soc_codec_get_drvdata(codec); + int i; + + for (i = 3; i >= 0; i--) + free_irq(pm860x->irq[i], pm860x); + pm860x_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static struct snd_soc_codec_driver soc_codec_dev_pm860x = { + .probe = pm860x_probe, + .remove = pm860x_remove, + .read = pm860x_read_reg_cache, + .write = pm860x_write_reg_cache, + .reg_cache_size = REG_CACHE_SIZE, + .reg_word_size = sizeof(u8), + .set_bias_level = pm860x_set_bias_level, +}; + +static int __devinit pm860x_codec_probe(struct platform_device *pdev) +{ + struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent); + struct pm860x_priv *pm860x; + struct resource *res; + int i, ret; + + pm860x = kzalloc(sizeof(struct pm860x_priv), GFP_KERNEL); + if (pm860x == NULL) + return -ENOMEM; + + pm860x->chip = chip; + pm860x->i2c = (chip->id == CHIP_PM8607) ? chip->client + : chip->companion; + platform_set_drvdata(pdev, pm860x); + + for (i = 0; i < 4; i++) { + res = platform_get_resource(pdev, IORESOURCE_IRQ, i); + if (!res) { + dev_err(&pdev->dev, "Failed to get IRQ resources\n"); + goto out; + } + pm860x->irq[i] = res->start + chip->irq_base; + strncpy(pm860x->name[i], res->name, MAX_NAME_LEN); + } + + ret = snd_soc_register_codec(&pdev->dev, &soc_codec_dev_pm860x, + pm860x_dai, ARRAY_SIZE(pm860x_dai)); + if (ret) { + dev_err(&pdev->dev, "Failed to register codec\n"); + goto out; + } + return ret; + +out: + platform_set_drvdata(pdev, NULL); + kfree(pm860x); + return -EINVAL; +} + +static int __devexit pm860x_codec_remove(struct platform_device *pdev) +{ + struct pm860x_priv *pm860x = platform_get_drvdata(pdev); + + snd_soc_unregister_codec(&pdev->dev); + platform_set_drvdata(pdev, NULL); + kfree(pm860x); + return 0; +} + +static struct platform_driver pm860x_codec_driver = { + .driver = { + .name = "88pm860x-codec", + .owner = THIS_MODULE, + }, + .probe = pm860x_codec_probe, + .remove = __devexit_p(pm860x_codec_remove), +}; + +static __init int pm860x_init(void) +{ + return platform_driver_register(&pm860x_codec_driver); +} +module_init(pm860x_init); + +static __exit void pm860x_exit(void) +{ + platform_driver_unregister(&pm860x_codec_driver); +} +module_exit(pm860x_exit); + +MODULE_DESCRIPTION("ASoC 88PM860x driver"); +MODULE_AUTHOR("Haojian Zhuang "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:88pm860x-codec"); + diff --git a/sound/soc/codecs/88pm860x-codec.h b/sound/soc/codecs/88pm860x-codec.h new file mode 100644 index 0000000..3364ba4 --- /dev/null +++ b/sound/soc/codecs/88pm860x-codec.h @@ -0,0 +1,97 @@ +/* + * 88pm860x-codec.h -- 88PM860x ALSA SoC Audio Driver + * + * Copyright 2010 Marvell International Ltd. + * Haojian Zhuang + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __88PM860X_H +#define __88PM860X_H + +/* The offset of these registers are 0xb0 */ +#define PM860X_PCM_IFACE_1 0x00 +#define PM860X_PCM_IFACE_2 0x01 +#define PM860X_PCM_IFACE_3 0x02 +#define PM860X_PCM_RATE 0x03 +#define PM860X_EC_PATH 0x04 +#define PM860X_SIDETONE_L_GAIN 0x05 +#define PM860X_SIDETONE_R_GAIN 0x06 +#define PM860X_SIDETONE_SHIFT 0x07 +#define PM860X_ADC_OFFSET_1 0x08 +#define PM860X_ADC_OFFSET_2 0x09 +#define PM860X_DMIC_DELAY 0x0a + +#define PM860X_I2S_IFACE_1 0x0b +#define PM860X_I2S_IFACE_2 0x0c +#define PM860X_I2S_IFACE_3 0x0d +#define PM860X_I2S_IFACE_4 0x0e +#define PM860X_EQUALIZER_N0_1 0x0f +#define PM860X_EQUALIZER_N0_2 0x10 +#define PM860X_EQUALIZER_N1_1 0x11 +#define PM860X_EQUALIZER_N1_2 0x12 +#define PM860X_EQUALIZER_D1_1 0x13 +#define PM860X_EQUALIZER_D1_2 0x14 +#define PM860X_LOFI_GAIN_LEFT 0x15 +#define PM860X_LOFI_GAIN_RIGHT 0x16 +#define PM860X_HIFIL_GAIN_LEFT 0x17 +#define PM860X_HIFIL_GAIN_RIGHT 0x18 +#define PM860X_HIFIR_GAIN_LEFT 0x19 +#define PM860X_HIFIR_GAIN_RIGHT 0x1a +#define PM860X_DAC_OFFSET 0x1b +#define PM860X_OFFSET_LEFT_1 0x1c +#define PM860X_OFFSET_LEFT_2 0x1d +#define PM860X_OFFSET_RIGHT_1 0x1e +#define PM860X_OFFSET_RIGHT_2 0x1f +#define PM860X_ADC_ANA_1 0x20 +#define PM860X_ADC_ANA_2 0x21 +#define PM860X_ADC_ANA_3 0x22 +#define PM860X_ADC_ANA_4 0x23 +#define PM860X_ANA_TO_ANA 0x24 +#define PM860X_HS1_CTRL 0x25 +#define PM860X_HS2_CTRL 0x26 +#define PM860X_LO1_CTRL 0x27 +#define PM860X_LO2_CTRL 0x28 +#define PM860X_EAR_CTRL_1 0x29 +#define PM860X_EAR_CTRL_2 0x2a +#define PM860X_AUDIO_SUPPLIES_1 0x2b +#define PM860X_AUDIO_SUPPLIES_2 0x2c +#define PM860X_ADC_EN_1 0x2d +#define PM860X_ADC_EN_2 0x2e +#define PM860X_DAC_EN_1 0x2f +#define PM860X_DAC_EN_2 0x31 +#define PM860X_AUDIO_CAL_1 0x32 +#define PM860X_AUDIO_CAL_2 0x33 +#define PM860X_AUDIO_CAL_3 0x34 +#define PM860X_AUDIO_CAL_4 0x35 +#define PM860X_AUDIO_CAL_5 0x36 +#define PM860X_ANA_INPUT_SEL_1 0x37 +#define PM860X_ANA_INPUT_SEL_2 0x38 + +#define PM860X_PCM_IFACE_4 0x39 +#define PM860X_I2S_IFACE_5 0x3a + +#define PM860X_SHORTS 0x3b +#define PM860X_PLL_ADJ_1 0x3c +#define PM860X_PLL_ADJ_2 0x3d + +/* bits definition */ +#define PM860X_CLK_DIR_IN 0 +#define PM860X_CLK_DIR_OUT 1 + +#define PM860X_DET_HEADSET (1 << 0) +#define PM860X_DET_MIC (1 << 1) +#define PM860X_DET_HOOK (1 << 2) +#define PM860X_SHORT_HEADSET (1 << 3) +#define PM860X_SHORT_LINEOUT (1 << 4) +#define PM860X_DET_MASK 0x1F + +extern int pm860x_hs_jack_detect(struct snd_soc_codec *, struct snd_soc_jack *, + int, int, int, int); +extern int pm860x_mic_jack_detect(struct snd_soc_codec *, struct snd_soc_jack *, + int); + +#endif /* __88PM860X_H */ diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index bfdd92b..a3cfc18 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -10,6 +10,7 @@ config SND_SOC_I2C_AND_SPI config SND_SOC_ALL_CODECS tristate "Build all ASoC CODEC drivers" + select SND_SOC_88PM860X if MFD_88PM860X select SND_SOC_L3 select SND_SOC_AC97_CODEC if SND_SOC_AC97_BUS select SND_SOC_AD1836 if SPI_MASTER @@ -85,6 +86,9 @@ config SND_SOC_ALL_CODECS If unsure select "N". +config SND_SOC_88PM860X + tristate + config SND_SOC_WM_HUBS tristate default y if SND_SOC_WM8993=y || SND_SOC_WM8994=y diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 9c3c39f..b9c4358 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -1,3 +1,4 @@ +snd-soc-88pm860x-objs := 88pm860x-codec.o snd-soc-ac97-objs := ac97.o snd-soc-ad1836-objs := ad1836.o snd-soc-ad193x-objs := ad193x.o @@ -67,6 +68,7 @@ snd-soc-tpa6130a2-objs := tpa6130a2.o snd-soc-wm2000-objs := wm2000.o snd-soc-wm9090-objs := wm9090.o +obj-$(CONFIG_SND_SOC_88PM860X) += snd-soc-88pm860x.o obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o obj-$(CONFIG_SND_SOC_AD1836) += snd-soc-ad1836.o obj-$(CONFIG_SND_SOC_AD193X) += snd-soc-ad193x.o -- cgit v1.1 From b0547a70db38ee851a32306ee2e1e43a1e0d28ea Mon Sep 17 00:00:00 2001 From: Haojian Zhuang Date: Thu, 19 Aug 2010 00:36:00 +0800 Subject: ASoC: add tavorevb3 machine driver for 88pm860x Signed-off-by: Haojian Zhuang Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/pxa/Kconfig | 9 +++ sound/soc/pxa/Makefile | 2 + sound/soc/pxa/tavorevb3.c | 200 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 211 insertions(+) create mode 100644 sound/soc/pxa/tavorevb3.c (limited to 'sound') diff --git a/sound/soc/pxa/Kconfig b/sound/soc/pxa/Kconfig index e30c832..04ddc7b 100644 --- a/sound/soc/pxa/Kconfig +++ b/sound/soc/pxa/Kconfig @@ -117,6 +117,15 @@ config SND_PXA2XX_SOC_PALM27X Say Y if you want to add support for SoC audio on Palm T|X, T5, E2 or LifeDrive handheld computer. +config SND_SOC_TAVOREVB3 + tristate "SoC Audio support for Marvell Tavor EVB3" + depends on SND_PXA2XX_SOC && MACH_TAVOREVB3 + select SND_PXA_SOC_SSP + select SND_SOC_88PM860X + help + Say Y if you want to add support for SoC audio on the + Marvell Saarb reference platform. + config SND_SOC_ZYLONITE tristate "SoC Audio support for Marvell Zylonite" depends on SND_PXA2XX_SOC && MACH_ZYLONITE diff --git a/sound/soc/pxa/Makefile b/sound/soc/pxa/Makefile index caa03d8..315941f 100644 --- a/sound/soc/pxa/Makefile +++ b/sound/soc/pxa/Makefile @@ -19,6 +19,7 @@ snd-soc-e800-objs := e800_wm9712.o snd-soc-spitz-objs := spitz.o snd-soc-em-x270-objs := em-x270.o snd-soc-palm27x-objs := palm27x.o +snd-soc-tavorevb3-objs := tavorevb3.o snd-soc-zylonite-objs := zylonite.o snd-soc-magician-objs := magician.o snd-soc-mioa701-objs := mioa701_wm9713.o @@ -38,6 +39,7 @@ obj-$(CONFIG_SND_PXA2XX_SOC_PALM27X) += snd-soc-palm27x.o obj-$(CONFIG_SND_PXA2XX_SOC_MAGICIAN) += snd-soc-magician.o obj-$(CONFIG_SND_PXA2XX_SOC_MIOA701) += snd-soc-mioa701.o obj-$(CONFIG_SND_PXA2XX_SOC_Z2) += snd-soc-z2.o +obj-$(CONFIG_SND_SOC_TAVOREVB3) += snd-soc-tavorevb3.o obj-$(CONFIG_SND_SOC_ZYLONITE) += snd-soc-zylonite.o obj-$(CONFIG_SND_PXA2XX_SOC_IMOTE2) += snd-soc-imote2.o obj-$(CONFIG_SND_SOC_RAUMFELD) += snd-soc-raumfeld.o diff --git a/sound/soc/pxa/tavorevb3.c b/sound/soc/pxa/tavorevb3.c new file mode 100644 index 0000000..248c283 --- /dev/null +++ b/sound/soc/pxa/tavorevb3.c @@ -0,0 +1,200 @@ +/* + * tavorevb3.c -- SoC audio for Tavor EVB3 + * + * Copyright (C) 2010 Marvell International Ltd. + * Haojian Zhuang + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "../codecs/88pm860x-codec.h" +#include "pxa-ssp.h" + +static int evb3_pm860x_init(struct snd_soc_pcm_runtime *rtd); + +static struct platform_device *evb3_snd_device; + +static struct snd_soc_jack hs_jack, mic_jack; + +static struct snd_soc_jack_pin hs_jack_pins[] = { + { .pin = "Headset Stereophone", .mask = SND_JACK_HEADPHONE, }, +}; + +static struct snd_soc_jack_pin mic_jack_pins[] = { + { .pin = "Headset Mic 2", .mask = SND_JACK_MICROPHONE, }, +}; + +/* tavorevb3 machine dapm widgets */ +static const struct snd_soc_dapm_widget evb3_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headset Stereophone", NULL), + SND_SOC_DAPM_LINE("Lineout Out 1", NULL), + SND_SOC_DAPM_LINE("Lineout Out 2", NULL), + SND_SOC_DAPM_SPK("Ext Speaker", NULL), + SND_SOC_DAPM_MIC("Ext Mic 1", NULL), + SND_SOC_DAPM_MIC("Headset Mic 2", NULL), + SND_SOC_DAPM_MIC("Ext Mic 3", NULL), +}; + +/* tavorevb3 machine audio map */ +static const struct snd_soc_dapm_route audio_map[] = { + {"Headset Stereophone", NULL, "HS1"}, + {"Headset Stereophone", NULL, "HS2"}, + + {"Ext Speaker", NULL, "LSP"}, + {"Ext Speaker", NULL, "LSN"}, + + {"Lineout Out 1", NULL, "LINEOUT1"}, + {"Lineout Out 2", NULL, "LINEOUT2"}, + + {"MIC1P", NULL, "Mic1 Bias"}, + {"MIC1N", NULL, "Mic1 Bias"}, + {"Mic1 Bias", NULL, "Ext Mic 1"}, + + {"MIC2P", NULL, "Mic1 Bias"}, + {"MIC2N", NULL, "Mic1 Bias"}, + {"Mic1 Bias", NULL, "Headset Mic 2"}, + + {"MIC3P", NULL, "Mic3 Bias"}, + {"MIC3N", NULL, "Mic3 Bias"}, + {"Mic3 Bias", NULL, "Ext Mic 3"}, +}; + +static int evb3_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int width = snd_pcm_format_physical_width(params_format(params)); + int ret; + + ret = snd_soc_dai_set_sysclk(cpu_dai, PXA_SSP_CLK_NET_PLL, 0, + PM860X_CLK_DIR_OUT); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, 0, 0, PM860X_CLK_DIR_OUT); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_tdm_slot(cpu_dai, 3, 3, 2, width); + return ret; +} + +static struct snd_soc_ops evb3_i2s_ops = { + .hw_params = evb3_i2s_hw_params, +}; + +static struct snd_soc_dai_link evb3_dai[] = { + { + .name = "88PM860x I2S", + .stream_name = "I2S Audio", + .cpu_dai_name = "pxa-ssp-dai.1", + .codec_dai_name = "88pm860x-i2s", + .platform_name = "pxa-pcm-audio", + .codec_name = "88pm860x-codec", + .init = evb3_pm860x_init, + .ops = &evb3_i2s_ops, + }, +}; + +static struct snd_soc_card snd_soc_card_evb3 = { + .name = "Tavor EVB3", + .dai_link = evb3_dai, + .num_links = ARRAY_SIZE(evb3_dai), +}; + +static int evb3_pm860x_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_codec *codec = rtd->codec; + int ret; + + snd_soc_dapm_new_controls(codec, evb3_dapm_widgets, + ARRAY_SIZE(evb3_dapm_widgets)); + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + /* connected pins */ + snd_soc_dapm_enable_pin(codec, "Ext Speaker"); + snd_soc_dapm_enable_pin(codec, "Ext Mic 1"); + snd_soc_dapm_enable_pin(codec, "Ext Mic 3"); + snd_soc_dapm_disable_pin(codec, "Headset Mic 2"); + snd_soc_dapm_disable_pin(codec, "Headset Stereophone"); + + ret = snd_soc_dapm_sync(codec); + if (ret) + return ret; + + /* Headset jack detection */ + snd_soc_jack_new(codec, "Headphone Jack", SND_JACK_HEADPHONE + | SND_JACK_BTN_0 | SND_JACK_BTN_1 | SND_JACK_BTN_2, + &hs_jack); + snd_soc_jack_add_pins(&hs_jack, ARRAY_SIZE(hs_jack_pins), + hs_jack_pins); + snd_soc_jack_new(codec, "Microphone Jack", SND_JACK_MICROPHONE, + &mic_jack); + snd_soc_jack_add_pins(&mic_jack, ARRAY_SIZE(mic_jack_pins), + mic_jack_pins); + + /* headphone, microphone detection & headset short detection */ + pm860x_hs_jack_detect(codec, &hs_jack, SND_JACK_HEADPHONE, + SND_JACK_BTN_0, SND_JACK_BTN_1, SND_JACK_BTN_2); + pm860x_mic_jack_detect(codec, &hs_jack, SND_JACK_MICROPHONE); + return 0; +} + +static int __init tavorevb3_init(void) +{ + int ret; + + if (!machine_is_tavorevb3()) + return -ENODEV; + evb3_snd_device = platform_device_alloc("soc-audio", -1); + if (!evb3_snd_device) + return -ENOMEM; + + platform_set_drvdata(evb3_snd_device, &snd_soc_card_evb3); + + ret = platform_device_add(evb3_snd_device); + if (ret) + platform_device_put(evb3_snd_device); + + return ret; +} + +static void __exit tavorevb3_exit(void) +{ + platform_device_unregister(evb3_snd_device); +} + +module_init(tavorevb3_init); +module_exit(tavorevb3_exit); + +MODULE_AUTHOR("Haojian Zhuang "); +MODULE_DESCRIPTION("ALSA SoC 88PM860x Tavor EVB3"); +MODULE_LICENSE("GPL"); -- cgit v1.1 From b6905d0b1652efddb96cefdb3c8552cac8d98ed2 Mon Sep 17 00:00:00 2001 From: Haojian Zhuang Date: Thu, 19 Aug 2010 00:36:30 +0800 Subject: ASoC: add saarb machine driver for 88pm860x 88PM860x codec is used in Marvell saarb development board. 88PM860x codec is used as master mode for SSP communication. Only I2S format is supported. Signed-off-by: Haojian Zhuang Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/pxa/Kconfig | 9 +++ sound/soc/pxa/Makefile | 2 + sound/soc/pxa/saarb.c | 200 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 211 insertions(+) create mode 100644 sound/soc/pxa/saarb.c (limited to 'sound') diff --git a/sound/soc/pxa/Kconfig b/sound/soc/pxa/Kconfig index 04ddc7b..37f191b 100644 --- a/sound/soc/pxa/Kconfig +++ b/sound/soc/pxa/Kconfig @@ -117,6 +117,15 @@ config SND_PXA2XX_SOC_PALM27X Say Y if you want to add support for SoC audio on Palm T|X, T5, E2 or LifeDrive handheld computer. +config SND_SOC_SAARB + tristate "SoC Audio support for Marvell Saarb" + depends on SND_PXA2XX_SOC && MACH_SAARB + select SND_PXA_SOC_SSP + select SND_SOC_88PM860X + help + Say Y if you want to add support for SoC audio on the + Marvell Saarb reference platform. + config SND_SOC_TAVOREVB3 tristate "SoC Audio support for Marvell Tavor EVB3" depends on SND_PXA2XX_SOC && MACH_TAVOREVB3 diff --git a/sound/soc/pxa/Makefile b/sound/soc/pxa/Makefile index 315941f..0766016 100644 --- a/sound/soc/pxa/Makefile +++ b/sound/soc/pxa/Makefile @@ -19,6 +19,7 @@ snd-soc-e800-objs := e800_wm9712.o snd-soc-spitz-objs := spitz.o snd-soc-em-x270-objs := em-x270.o snd-soc-palm27x-objs := palm27x.o +snd-soc-saarb-objs := saarb.o snd-soc-tavorevb3-objs := tavorevb3.o snd-soc-zylonite-objs := zylonite.o snd-soc-magician-objs := magician.o @@ -39,6 +40,7 @@ obj-$(CONFIG_SND_PXA2XX_SOC_PALM27X) += snd-soc-palm27x.o obj-$(CONFIG_SND_PXA2XX_SOC_MAGICIAN) += snd-soc-magician.o obj-$(CONFIG_SND_PXA2XX_SOC_MIOA701) += snd-soc-mioa701.o obj-$(CONFIG_SND_PXA2XX_SOC_Z2) += snd-soc-z2.o +obj-$(CONFIG_SND_SOC_SAARB) += snd-soc-saarb.o obj-$(CONFIG_SND_SOC_TAVOREVB3) += snd-soc-tavorevb3.o obj-$(CONFIG_SND_SOC_ZYLONITE) += snd-soc-zylonite.o obj-$(CONFIG_SND_PXA2XX_SOC_IMOTE2) += snd-soc-imote2.o diff --git a/sound/soc/pxa/saarb.c b/sound/soc/pxa/saarb.c new file mode 100644 index 0000000..d63cb47 --- /dev/null +++ b/sound/soc/pxa/saarb.c @@ -0,0 +1,200 @@ +/* + * saarb.c -- SoC audio for saarb + * + * Copyright (C) 2010 Marvell International Ltd. + * Haojian Zhuang + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "../codecs/88pm860x-codec.h" +#include "pxa-ssp.h" + +static int saarb_pm860x_init(struct snd_soc_pcm_runtime *rtd); + +static struct platform_device *saarb_snd_device; + +static struct snd_soc_jack hs_jack, mic_jack; + +static struct snd_soc_jack_pin hs_jack_pins[] = { + { .pin = "Headset Stereophone", .mask = SND_JACK_HEADPHONE, }, +}; + +static struct snd_soc_jack_pin mic_jack_pins[] = { + { .pin = "Headset Mic 2", .mask = SND_JACK_MICROPHONE, }, +}; + +/* saarb machine dapm widgets */ +static const struct snd_soc_dapm_widget saarb_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Stereophone", NULL), + SND_SOC_DAPM_LINE("Lineout Out 1", NULL), + SND_SOC_DAPM_LINE("Lineout Out 2", NULL), + SND_SOC_DAPM_SPK("Ext Speaker", NULL), + SND_SOC_DAPM_MIC("Ext Mic 1", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Ext Mic 3", NULL), +}; + +/* saarb machine audio map */ +static const struct snd_soc_dapm_route audio_map[] = { + {"Headset Stereophone", NULL, "HS1"}, + {"Headset Stereophone", NULL, "HS2"}, + + {"Ext Speaker", NULL, "LSP"}, + {"Ext Speaker", NULL, "LSN"}, + + {"Lineout Out 1", NULL, "LINEOUT1"}, + {"Lineout Out 2", NULL, "LINEOUT2"}, + + {"MIC1P", NULL, "Mic1 Bias"}, + {"MIC1N", NULL, "Mic1 Bias"}, + {"Mic1 Bias", NULL, "Ext Mic 1"}, + + {"MIC2P", NULL, "Mic1 Bias"}, + {"MIC2N", NULL, "Mic1 Bias"}, + {"Mic1 Bias", NULL, "Headset Mic 2"}, + + {"MIC3P", NULL, "Mic3 Bias"}, + {"MIC3N", NULL, "Mic3 Bias"}, + {"Mic3 Bias", NULL, "Ext Mic 3"}, +}; + +static int saarb_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int width = snd_pcm_format_physical_width(params_format(params)); + int ret; + + ret = snd_soc_dai_set_sysclk(cpu_dai, PXA_SSP_CLK_NET_PLL, 0, + PM860X_CLK_DIR_OUT); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, 0, 0, PM860X_CLK_DIR_OUT); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_tdm_slot(cpu_dai, 3, 3, 2, width); + + return ret; +} + +static struct snd_soc_ops saarb_i2s_ops = { + .hw_params = saarb_i2s_hw_params, +}; + +static struct snd_soc_dai_link saarb_dai[] = { + { + .name = "88PM860x I2S", + .stream_name = "I2S Audio", + .cpu_dai_name = "pxa-ssp-dai.1", + .codec_dai_name = "88pm860x-i2s", + .platform_name = "pxa-pcm-audio", + .codec_name = "88pm860x-codec", + .init = saarb_pm860x_init, + .ops = &saarb_i2s_ops, + }, +}; + +static struct snd_soc_card snd_soc_card_saarb = { + .name = "Saarb", + .dai_link = saarb_dai, + .num_links = ARRAY_SIZE(saarb_dai), +}; + +static int saarb_pm860x_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_codec *codec = rtd->codec; + int ret; + + snd_soc_dapm_new_controls(codec, saarb_dapm_widgets, + ARRAY_SIZE(saarb_dapm_widgets)); + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + /* connected pins */ + snd_soc_dapm_enable_pin(codec, "Ext Speaker"); + snd_soc_dapm_enable_pin(codec, "Ext Mic 1"); + snd_soc_dapm_enable_pin(codec, "Ext Mic 3"); + snd_soc_dapm_disable_pin(codec, "Headset Mic 2"); + snd_soc_dapm_disable_pin(codec, "Headset Stereophone"); + + ret = snd_soc_dapm_sync(codec); + if (ret) + return ret; + + /* Headset jack detection */ + snd_soc_jack_new(codec, "Headphone Jack", SND_JACK_HEADPHONE + | SND_JACK_BTN_0 | SND_JACK_BTN_1 | SND_JACK_BTN_2, + &hs_jack); + snd_soc_jack_add_pins(&hs_jack, ARRAY_SIZE(hs_jack_pins), + hs_jack_pins); + snd_soc_jack_new(codec, "Microphone Jack", SND_JACK_MICROPHONE, + &mic_jack); + snd_soc_jack_add_pins(&mic_jack, ARRAY_SIZE(mic_jack_pins), + mic_jack_pins); + + /* headphone, microphone detection & headset short detection */ + pm860x_hs_jack_detect(codec, &hs_jack, SND_JACK_HEADPHONE, + SND_JACK_BTN_0, SND_JACK_BTN_1, SND_JACK_BTN_2); + pm860x_mic_jack_detect(codec, &hs_jack, SND_JACK_MICROPHONE); + return 0; +} + +static int __init saarb_init(void) +{ + int ret; + + if (!machine_is_saarb()) + return -ENODEV; + saarb_snd_device = platform_device_alloc("soc-audio", -1); + if (!saarb_snd_device) + return -ENOMEM; + + platform_set_drvdata(saarb_snd_device, &snd_soc_card_saarb); + + ret = platform_device_add(saarb_snd_device); + if (ret) + platform_device_put(saarb_snd_device); + + return ret; +} + +static void __exit saarb_exit(void) +{ + platform_device_unregister(saarb_snd_device); +} + +module_init(saarb_init); +module_exit(saarb_exit); + +MODULE_AUTHOR("Haojian Zhuang "); +MODULE_DESCRIPTION("ALSA SoC 88PM860x Saarb"); +MODULE_LICENSE("GPL"); -- cgit v1.1 From 140176159597ea1f23dcccb47b5c38fdf7c7faa8 Mon Sep 17 00:00:00 2001 From: Randolph Chung Date: Thu, 19 Aug 2010 12:06:17 +0100 Subject: ASoC: Configure symmetric rates for tlv320aic3x The tlv320aic3x codec driver only supports symmetric rates for capture/ playback. Set the flag in the DAI accordingly. Signed-off-by: Randolph Chung Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/codecs/tlv320aic3x.c | 1 + 1 file changed, 1 insertion(+) (limited to 'sound') diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c index 43fd9c1..867bf1f 100644 --- a/sound/soc/codecs/tlv320aic3x.c +++ b/sound/soc/codecs/tlv320aic3x.c @@ -1117,6 +1117,7 @@ static struct snd_soc_dai_driver aic3x_dai = { .rates = AIC3X_RATES, .formats = AIC3X_FORMATS,}, .ops = &aic3x_dai_ops, + .symmetric_rates = 1, }; static int aic3x_suspend(struct snd_soc_codec *codec, pm_message_t state) -- cgit v1.1 From 5394637a246f4709e6f9c62b6af2356f49ef46a7 Mon Sep 17 00:00:00 2001 From: Janusz Krzysztofik Date: Thu, 19 Aug 2010 15:15:50 +0200 Subject: ASoC: Use a more adequate name for the CX20442 codec DAI In the process of unification of codec DAI names while implementing multi-component, the CX20442 codec DAI has been renamed to "cx20442-hifi". This new name seems not adequate for a 8kHz voice codec. Use a better name, "cx20442-voice", as suggested by Liam Girdwood. Signed-off-by: Janusz Krzysztofik Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/codecs/cx20442.c | 2 +- sound/soc/omap/ams-delta.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/soc/codecs/cx20442.c b/sound/soc/codecs/cx20442.c index cf4323d..e8d27c8 100644 --- a/sound/soc/codecs/cx20442.c +++ b/sound/soc/codecs/cx20442.c @@ -318,7 +318,7 @@ EXPORT_SYMBOL_GPL(v253_ops); */ static struct snd_soc_dai_driver cx20442_dai = { - .name = "cx20442-hifi", + .name = "cx20442-voice", .playback = { .stream_name = "Playback", .channels_min = 1, diff --git a/sound/soc/omap/ams-delta.c b/sound/soc/omap/ams-delta.c index 9d88efa..438146a 100644 --- a/sound/soc/omap/ams-delta.c +++ b/sound/soc/omap/ams-delta.c @@ -584,7 +584,7 @@ static struct snd_soc_dai_link ams_delta_dai_link = { .name = "CX20442", .stream_name = "CX20442", .cpu_dai_name ="omap-mcbsp-dai.0", - .codec_dai_name = "cx20442-hifi", + .codec_dai_name = "cx20442-voice", .init = ams_delta_cx20442_init, .platform_name = "omap-pcm-audio", .codec_name = "cx20442-codec", -- cgit v1.1 From b67696b40f2e7f890d017db3c6805ff90cb392b6 Mon Sep 17 00:00:00 2001 From: Axel Lin Date: Thu, 19 Aug 2010 14:40:25 +0800 Subject: ASoC: e740_wm9705 - free gpio in e740_exit() In e740_init(), we call gpio_request() for GPIO_E740_MIC_ON, GPIO_E740_AMP_ON and GPIO_E740_WM9705_nAVDD2. We should free the these gpio accordingly in e740_exit(). Signed-off-by: Axel Lin Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/pxa/e740_wm9705.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'sound') diff --git a/sound/soc/pxa/e740_wm9705.c b/sound/soc/pxa/e740_wm9705.c index f614607..c82cedb 100644 --- a/sound/soc/pxa/e740_wm9705.c +++ b/sound/soc/pxa/e740_wm9705.c @@ -198,6 +198,9 @@ free_mic_amp_gpio: static void __exit e740_exit(void) { platform_device_unregister(e740_snd_device); + gpio_free(GPIO_E740_WM9705_nAVDD2); + gpio_free(GPIO_E740_AMP_ON); + gpio_free(GPIO_E740_MIC_ON); } module_init(e740_init); -- cgit v1.1 From c04019d450a885a095a2ca38fcd5db8d57cd2718 Mon Sep 17 00:00:00 2001 From: Timur Tabi Date: Thu, 19 Aug 2010 16:43:42 -0500 Subject: ASoC: add support for separate codec DAIs to the fsl_dma driver Some codecs have separate DAIs for playback and capture, so the DMA driver should allocate a DMA buffer only for the streams that are valid when the driver is opened. Signed-off-by: Timur Tabi Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_dma.c | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) (limited to 'sound') diff --git a/sound/soc/fsl/fsl_dma.c b/sound/soc/fsl/fsl_dma.c index 57774cb..5a6f56d 100644 --- a/sound/soc/fsl/fsl_dma.c +++ b/sound/soc/fsl/fsl_dma.c @@ -303,21 +303,29 @@ static int fsl_dma_new(struct snd_card *card, struct snd_soc_dai *dai, if (!card->dev->coherent_dma_mask) card->dev->coherent_dma_mask = fsl_dma_dmamask; - ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, card->dev, - fsl_dma_hardware.buffer_bytes_max, - &pcm->streams[0].substream->dma_buffer); - if (ret) { - dev_err(card->dev, "can't allocate playback dma buffer\n"); - return ret; + /* Some codecs have separate DAIs for playback and capture, so we + * should allocate a DMA buffer only for the streams that are valid. + */ + + if (dai->driver->playback.channels_min) { + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, card->dev, + fsl_dma_hardware.buffer_bytes_max, + &pcm->streams[0].substream->dma_buffer); + if (ret) { + dev_err(card->dev, "can't alloc playback dma buffer\n"); + return ret; + } } - ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, card->dev, - fsl_dma_hardware.buffer_bytes_max, - &pcm->streams[1].substream->dma_buffer); - if (ret) { - snd_dma_free_pages(&pcm->streams[0].substream->dma_buffer); - dev_err(card->dev, "can't allocate capture dma buffer\n"); - return ret; + if (dai->driver->capture.channels_min) { + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, card->dev, + fsl_dma_hardware.buffer_bytes_max, + &pcm->streams[1].substream->dma_buffer); + if (ret) { + snd_dma_free_pages(&pcm->streams[0].substream->dma_buffer); + dev_err(card->dev, "can't alloc capture dma buffer\n"); + return ret; + } } return 0; -- cgit v1.1 From 6184f105aa75009e6d380b59316305079a44a6ee Mon Sep 17 00:00:00 2001 From: Randolph Chung Date: Fri, 20 Aug 2010 12:47:53 +0800 Subject: ASoC: Add support for tlv320aic3007 to tlv320aic3x codec. This patch adds support for the tlv320aic3007 codec to the tlv320aic3x driver. The tlv320aic3007 is similar to the aic31, but has an additional class-D speaker amp. The speaker amp control register overlaps with the mono output register of other codecs in this family, so we add logic to identify the actual codec being registered to set things up accordingly. Signed-off-by: Randolph Chung Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/codecs/tlv320aic3x.c | 79 ++++++++++++++++++++++++++++++++++++------ sound/soc/codecs/tlv320aic3x.h | 2 ++ 2 files changed, 71 insertions(+), 10 deletions(-) (limited to 'sound') diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c index 867bf1f..c074657 100644 --- a/sound/soc/codecs/tlv320aic3x.c +++ b/sound/soc/codecs/tlv320aic3x.c @@ -12,11 +12,11 @@ * * Notes: * The AIC3X is a driver for a low power stereo audio - * codecs aic31, aic32, aic33. + * codecs aic31, aic32, aic33, aic3007. * * It supports full aic33 codec functionality. - * The compatibility with aic32, aic31 is as follows: - * aic32 | aic31 + * The compatibility with aic32, aic31 and aic3007 is as follows: + * aic32/aic3007 | aic31 * --------------------------------------- * MONO_LOUT -> N/A | MONO_LOUT -> N/A * | IN1L -> LINE1L @@ -70,6 +70,10 @@ struct aic3x_priv { unsigned int sysclk; int master; int gpio_reset; +#define AIC3X_MODEL_3X 0 +#define AIC3X_MODEL_33 1 +#define AIC3X_MODEL_3007 2 + u16 model; }; /* @@ -361,6 +365,14 @@ static const struct snd_kcontrol_new aic3x_snd_controls[] = { SOC_ENUM("ADC HPF Cut-off", aic3x_enum[ADC_HPF_ENUM]), }; +/* + * Class-D amplifier gain. From 0 to 18 dB in 6 dB steps + */ +static DECLARE_TLV_DB_SCALE(classd_amp_tlv, 0, 600, 0); + +static const struct snd_kcontrol_new aic3x_classd_amp_gain_ctrl = + SOC_DOUBLE_TLV("Class-D Amplifier Gain", CLASSD_CTRL, 6, 4, 3, 0, classd_amp_tlv); + /* Left DAC Mux */ static const struct snd_kcontrol_new aic3x_left_dac_mux_controls = SOC_DAPM_ENUM("Route", aic3x_enum[LDAC_ENUM]); @@ -589,6 +601,15 @@ static const struct snd_soc_dapm_widget aic3x_dapm_widgets[] = { SND_SOC_DAPM_INPUT("LINE2R"), }; +static const struct snd_soc_dapm_widget aic3007_dapm_widgets[] = { + /* Class-D outputs */ + SND_SOC_DAPM_PGA("Left Class-D Out", CLASSD_CTRL, 3, 0, NULL, 0), + SND_SOC_DAPM_PGA("Right Class-D Out", CLASSD_CTRL, 2, 0, NULL, 0), + + SND_SOC_DAPM_OUTPUT("SPOP"), + SND_SOC_DAPM_OUTPUT("SPOM"), +}; + static const struct snd_soc_dapm_route intercon[] = { /* Left Output */ {"Left DAC Mux", "DAC_L1", "Left DAC"}, @@ -759,14 +780,30 @@ static const struct snd_soc_dapm_route intercon[] = { {"GPIO1 dmic modclk", NULL, "DMic Rate 32"}, }; +static const struct snd_soc_dapm_route intercon_3007[] = { + /* Class-D outputs */ + {"Left Class-D Out", NULL, "Left Line Out"}, + {"Right Class-D Out", NULL, "Left Line Out"}, + {"SPOP", NULL, "Left Class-D Out"}, + {"SPOM", NULL, "Right Class-D Out"}, +}; + static int aic3x_add_widgets(struct snd_soc_codec *codec) { + struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec); + snd_soc_dapm_new_controls(codec, aic3x_dapm_widgets, ARRAY_SIZE(aic3x_dapm_widgets)); /* set up audio path interconnects */ snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon)); + if (aic3x->model == AIC3X_MODEL_3007) { + snd_soc_dapm_new_controls(codec, aic3007_dapm_widgets, + ARRAY_SIZE(aic3007_dapm_widgets)); + snd_soc_dapm_add_routes(codec, intercon_3007, ARRAY_SIZE(intercon_3007)); + } + return 0; } @@ -1151,6 +1188,7 @@ static int aic3x_resume(struct snd_soc_codec *codec) */ static int aic3x_init(struct snd_soc_codec *codec) { + struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec); int reg; aic3x_write(codec, AIC3X_PAGE_SELECT, PAGE0_SELECT); @@ -1219,6 +1257,17 @@ static int aic3x_init(struct snd_soc_codec *codec) aic3x_write(codec, LINE2L_2_MONOLOPM_VOL, DEFAULT_VOL); aic3x_write(codec, LINE2R_2_MONOLOPM_VOL, DEFAULT_VOL); + if (aic3x->model == AIC3X_MODEL_3007) { + /* Class-D speaker driver init; datasheet p. 46 */ + aic3x_write(codec, AIC3X_PAGE_SELECT, 0x0D); + aic3x_write(codec, 0xD, 0x0D); + aic3x_write(codec, 0x8, 0x5C); + aic3x_write(codec, 0x8, 0x5D); + aic3x_write(codec, 0x8, 0x5C); + aic3x_write(codec, AIC3X_PAGE_SELECT, 0x00); + aic3x_write(codec, CLASSD_CTRL, 0); + } + /* off, with power on */ aic3x_set_bias_level(codec, SND_SOC_BIAS_STANDBY); @@ -1244,6 +1293,8 @@ static int aic3x_probe(struct snd_soc_codec *codec) snd_soc_add_controls(codec, aic3x_snd_controls, ARRAY_SIZE(aic3x_snd_controls)); + if (aic3x->model == AIC3X_MODEL_3007) + snd_soc_add_controls(codec, &aic3x_classd_amp_gain_ctrl, 1); aic3x_add_widgets(codec); @@ -1275,6 +1326,14 @@ static struct snd_soc_codec_driver soc_codec_dev_aic3x = { * 0x18, 0x19, 0x1A, 0x1B */ +static const struct i2c_device_id aic3x_i2c_id[] = { + [AIC3X_MODEL_3X] = { "tlv320aic3x", 0 }, + [AIC3X_MODEL_33] = { "tlv320aic33", 0 }, + [AIC3X_MODEL_3007] = { "tlv320aic3007", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, aic3x_i2c_id); + /* * If the i2c layer weren't so broken, we could pass this kind of data * around @@ -1286,6 +1345,7 @@ static int aic3x_i2c_probe(struct i2c_client *i2c, struct aic3x_setup_data *setup = pdata->setup; struct aic3x_priv *aic3x; int ret, i; + const struct i2c_device_id *tbl; aic3x = kzalloc(sizeof(struct aic3x_priv), GFP_KERNEL); if (aic3x == NULL) { @@ -1306,6 +1366,12 @@ static int aic3x_i2c_probe(struct i2c_client *i2c, gpio_direction_output(aic3x->gpio_reset, 0); } + for (tbl = aic3x_i2c_id; tbl->name[0]; tbl++) { + if (!strcmp(tbl->name, id->name)) + break; + } + aic3x->model = tbl - aic3x_i2c_id; + for (i = 0; i < ARRAY_SIZE(aic3x->supplies); i++) aic3x->supplies[i].supply = aic3x_supply_names[i]; @@ -1360,13 +1426,6 @@ static int aic3x_i2c_remove(struct i2c_client *client) return 0; } -static const struct i2c_device_id aic3x_i2c_id[] = { - { "tlv320aic3x", 0 }, - { "tlv320aic33", 0 }, - { } -}; -MODULE_DEVICE_TABLE(i2c, aic3x_i2c_id); - /* machine i2c codec control layer */ static struct i2c_driver aic3x_i2c_driver = { .driver = { diff --git a/sound/soc/codecs/tlv320aic3x.h b/sound/soc/codecs/tlv320aic3x.h index f6e3d9b..98e4439 100644 --- a/sound/soc/codecs/tlv320aic3x.h +++ b/sound/soc/codecs/tlv320aic3x.h @@ -111,6 +111,8 @@ #define DACL1_2_MONOLOPM_VOL 75 #define DACR1_2_MONOLOPM_VOL 78 #define MONOLOPM_CTRL 79 +/* Class-D speaker driver on tlv320aic3007 */ +#define CLASSD_CTRL 73 /* Line Output Plus/Minus control registers */ #define LINE2L_2_LLOPM_VOL 80 #define LINE2L_2_RLOPM_VOL 87 -- cgit v1.1 From b9c1261db46a4afdaebf08233d1c1e47f2d93979 Mon Sep 17 00:00:00 2001 From: Axel Lin Date: Fri, 20 Aug 2010 13:23:36 +0800 Subject: ASoC: remove include of pxa2xx-pcm.h in pxa2xx-ac97.c Fix reference to moved header file, which was unused anyway. This change fixes below build error: CC sound/soc/pxa/pxa2xx-ac97.o sound/soc/pxa/pxa2xx-ac97.c:27:24: error: pxa2xx-pcm.h: No such file or directory make[3]: *** [sound/soc/pxa/pxa2xx-ac97.o] Error 1 make[2]: *** [sound/soc/pxa] Error 2 make[1]: *** [sound/soc] Error 2 make: *** [sound] Error 2 Signed-off-by: Axel Lin Signed-off-by: Haojian Zhuang Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/pxa/pxa2xx-ac97.c | 1 - 1 file changed, 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/pxa/pxa2xx-ac97.c b/sound/soc/pxa/pxa2xx-ac97.c index 9c2bafa..ac51c6d 100644 --- a/sound/soc/pxa/pxa2xx-ac97.c +++ b/sound/soc/pxa/pxa2xx-ac97.c @@ -24,7 +24,6 @@ #include #include -#include "pxa2xx-pcm.h" #include "pxa2xx-ac97.h" static void pxa2xx_ac97_warm_reset(struct snd_ac97 *ac97) -- cgit v1.1 From 27ef3744f85bbbd00175ce7e9ac46b52089ee83c Mon Sep 17 00:00:00 2001 From: Timur Tabi Date: Thu, 19 Aug 2010 17:11:40 -0500 Subject: ASoC: add support for the Freescale P1022 DS reference board The Freescale P1022 is a dual-core e500-based SOC with multimedia capabilities, specifically the same SSI audio controller on the MPC8610. The P1022 DS reference board includes a P1022 and a Wolfson Microelectronics WM8776 codec. Signed-off-by: Timur Tabi Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/fsl/Kconfig | 24 +- sound/soc/fsl/Makefile | 8 +- sound/soc/fsl/p1022_ds.c | 590 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 614 insertions(+), 8 deletions(-) create mode 100644 sound/soc/fsl/p1022_ds.c (limited to 'sound') diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig index 9818687..d754d34 100644 --- a/sound/soc/fsl/Kconfig +++ b/sound/soc/fsl/Kconfig @@ -1,24 +1,36 @@ config SND_MPC52xx_DMA tristate -# ASoC platform support for the Freescale MPC8610 SOC. This compiles drivers -# for the SSI and the Elo DMA controller. You will still need to select -# a platform driver and a codec driver. -config SND_SOC_MPC8610 +# ASoC platform support for the Freescale PowerPC SOCs that have an SSI and +# an Elo DMA controller, such as the MPC8610 and P1022. You will still need to +# select a platform driver and a codec driver. +config SND_SOC_POWERPC_SSI tristate - depends on MPC8610 + depends on FSL_SOC config SND_SOC_MPC8610_HPCD tristate "ALSA SoC support for the Freescale MPC8610 HPCD board" # I2C is necessary for the CS4270 driver depends on MPC8610_HPCD && I2C - select SND_SOC_MPC8610 + select SND_SOC_POWERPC_SSI select SND_SOC_CS4270 select SND_SOC_CS4270_VD33_ERRATA default y if MPC8610_HPCD help Say Y if you want to enable audio on the Freescale MPC8610 HPCD. +config SND_SOC_P1022_DS + tristate "ALSA SoC support for the Freescale P1022 DS board" + # I2C is necessary for the WM8776 driver + depends on P1022_DS && I2C + select SND_SOC_POWERPC_SSI + select SND_SOC_WM8776 + default y if P1022_DS + help + Say Y if you want to enable audio on the Freescale P1022 DS board. + This will also include the Wolfson Microelectronics WM8776 codec + driver. + config SND_SOC_MPC5200_I2S tristate "Freescale MPC5200 PSC in I2S mode driver" depends on PPC_MPC52xx && PPC_BESTCOMM diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile index 7e472a5..b4a38c0 100644 --- a/sound/soc/fsl/Makefile +++ b/sound/soc/fsl/Makefile @@ -2,10 +2,14 @@ snd-soc-mpc8610-hpcd-objs := mpc8610_hpcd.o obj-$(CONFIG_SND_SOC_MPC8610_HPCD) += snd-soc-mpc8610-hpcd.o -# MPC8610 Platform Support +# P1022 DS Machine Support +snd-soc-p1022-ds-objs := p1022_ds.o +obj-$(CONFIG_SND_SOC_P1022_DS) += snd-soc-p1022-ds.o + +# Freescale PowerPC SSI/DMA Platform Support snd-soc-fsl-ssi-objs := fsl_ssi.o snd-soc-fsl-dma-objs := fsl_dma.o -obj-$(CONFIG_SND_SOC_MPC8610) += snd-soc-fsl-ssi.o snd-soc-fsl-dma.o +obj-$(CONFIG_SND_SOC_POWERPC_SSI) += snd-soc-fsl-ssi.o snd-soc-fsl-dma.o # MPC5200 Platform Support obj-$(CONFIG_SND_MPC52xx_DMA) += mpc5200_dma.o diff --git a/sound/soc/fsl/p1022_ds.c b/sound/soc/fsl/p1022_ds.c new file mode 100644 index 0000000..f8176e8 --- /dev/null +++ b/sound/soc/fsl/p1022_ds.c @@ -0,0 +1,590 @@ +/** + * Freescale P1022DS ALSA SoC Machine driver + * + * Author: Timur Tabi + * + * Copyright 2010 Freescale Semiconductor, Inc. + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include +#include +#include +#include +#include +#include + +#include "fsl_dma.h" +#include "fsl_ssi.h" + +/* P1022-specific PMUXCR and DMUXCR bit definitions */ + +#define CCSR_GUTS_PMUXCR_UART0_I2C1_MASK 0x0001c000 +#define CCSR_GUTS_PMUXCR_UART0_I2C1_UART0_SSI 0x00010000 +#define CCSR_GUTS_PMUXCR_UART0_I2C1_SSI 0x00018000 + +#define CCSR_GUTS_PMUXCR_SSI_DMA_TDM_MASK 0x00000c00 +#define CCSR_GUTS_PMUXCR_SSI_DMA_TDM_SSI 0x00000000 + +#define CCSR_GUTS_DMUXCR_PAD 1 /* DMA controller/channel set to pad */ +#define CCSR_GUTS_DMUXCR_SSI 2 /* DMA controller/channel set to SSI */ + +/* + * Set the DMACR register in the GUTS + * + * The DMACR register determines the source of initiated transfers for each + * channel on each DMA controller. Rather than have a bunch of repetitive + * macros for the bit patterns, we just have a function that calculates + * them. + * + * guts: Pointer to GUTS structure + * co: The DMA controller (0 or 1) + * ch: The channel on the DMA controller (0, 1, 2, or 3) + * device: The device to set as the target (CCSR_GUTS_DMUXCR_xxx) + */ +static inline void guts_set_dmuxcr(struct ccsr_guts_85xx __iomem *guts, + unsigned int co, unsigned int ch, unsigned int device) +{ + unsigned int shift = 16 + (8 * (1 - co) + 2 * (3 - ch)); + + clrsetbits_be32(&guts->dmuxcr, 3 << shift, device << shift); +} + +/* There's only one global utilities register */ +static phys_addr_t guts_phys; + +#define DAI_NAME_SIZE 32 + +/** + * machine_data: machine-specific ASoC device data + * + * This structure contains data for a single sound platform device on an + * P1022 DS. Some of the data is taken from the device tree. + */ +struct machine_data { + struct snd_soc_dai_link dai[2]; + struct snd_soc_card card; + unsigned int dai_format; + unsigned int codec_clk_direction; + unsigned int cpu_clk_direction; + unsigned int clk_frequency; + unsigned int ssi_id; /* 0 = SSI1, 1 = SSI2, etc */ + unsigned int dma_id[2]; /* 0 = DMA1, 1 = DMA2, etc */ + unsigned int dma_channel_id[2]; /* 0 = ch 0, 1 = ch 1, etc*/ + char codec_name[DAI_NAME_SIZE]; + char platform_name[2][DAI_NAME_SIZE]; /* One for each DMA channel */ +}; + +/** + * p1022_ds_machine_probe: initialize the board + * + * This function is used to initialize the board-specific hardware. + * + * Here we program the DMACR and PMUXCR registers. + */ +static int p1022_ds_machine_probe(struct platform_device *sound_device) +{ + struct snd_soc_card *card = platform_get_drvdata(sound_device); + struct machine_data *mdata = + container_of(card, struct machine_data, card); + struct ccsr_guts_85xx __iomem *guts; + + guts = ioremap(guts_phys, sizeof(struct ccsr_guts_85xx)); + if (!guts) { + dev_err(card->dev, "could not map global utilities\n"); + return -ENOMEM; + } + + /* Enable SSI Tx signal */ + clrsetbits_be32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_UART0_I2C1_MASK, + CCSR_GUTS_PMUXCR_UART0_I2C1_UART0_SSI); + + /* Enable SSI Rx signal */ + clrsetbits_be32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_SSI_DMA_TDM_MASK, + CCSR_GUTS_PMUXCR_SSI_DMA_TDM_SSI); + + /* Enable DMA Channel for SSI */ + guts_set_dmuxcr(guts, mdata->dma_id[0], mdata->dma_channel_id[0], + CCSR_GUTS_DMUXCR_SSI); + + guts_set_dmuxcr(guts, mdata->dma_id[1], mdata->dma_channel_id[1], + CCSR_GUTS_DMUXCR_SSI); + + iounmap(guts); + + return 0; +} + +/** + * p1022_ds_startup: program the board with various hardware parameters + * + * This function takes board-specific information, like clock frequencies + * and serial data formats, and passes that information to the codec and + * transport drivers. + */ +static int p1022_ds_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct machine_data *mdata = + container_of(rtd->card, struct machine_data, card); + struct device *dev = rtd->card->dev; + int ret = 0; + + /* Tell the codec driver what the serial protocol is. */ + ret = snd_soc_dai_set_fmt(rtd->codec_dai, mdata->dai_format); + if (ret < 0) { + dev_err(dev, "could not set codec driver audio format\n"); + return ret; + } + + /* + * Tell the codec driver what the MCLK frequency is, and whether it's + * a slave or master. + */ + ret = snd_soc_dai_set_sysclk(rtd->codec_dai, 0, mdata->clk_frequency, + mdata->codec_clk_direction); + if (ret < 0) { + dev_err(dev, "could not set codec driver clock params\n"); + return ret; + } + + return 0; +} + +/** + * p1022_ds_machine_remove: Remove the sound device + * + * This function is called to remove the sound device for one SSI. We + * de-program the DMACR and PMUXCR register. + */ +static int p1022_ds_machine_remove(struct platform_device *sound_device) +{ + struct snd_soc_card *card = platform_get_drvdata(sound_device); + struct machine_data *mdata = + container_of(card, struct machine_data, card); + struct ccsr_guts_85xx __iomem *guts; + + guts = ioremap(guts_phys, sizeof(struct ccsr_guts_85xx)); + if (!guts) { + dev_err(card->dev, "could not map global utilities\n"); + return -ENOMEM; + } + + /* Restore the signal routing */ + clrbits32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_UART0_I2C1_MASK); + clrbits32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_SSI_DMA_TDM_MASK); + guts_set_dmuxcr(guts, mdata->dma_id[0], mdata->dma_channel_id[0], 0); + guts_set_dmuxcr(guts, mdata->dma_id[1], mdata->dma_channel_id[1], 0); + + iounmap(guts); + + return 0; +} + +/** + * p1022_ds_ops: ASoC machine driver operations + */ +static struct snd_soc_ops p1022_ds_ops = { + .startup = p1022_ds_startup, +}; + +/** + * get_node_by_phandle_name - get a node by its phandle name + * + * This function takes a node, the name of a property in that node, and a + * compatible string. Assuming the property is a phandle to another node, + * it returns that node, (optionally) if that node is compatible. + * + * If the property is not a phandle, or the node it points to is not compatible + * with the specific string, then NULL is returned. + */ +static struct device_node *get_node_by_phandle_name(struct device_node *np, + const char *name, const char *compatible) +{ + np = of_parse_phandle(np, name, 0); + if (!np) + return NULL; + + if (!of_device_is_compatible(np, compatible)) { + of_node_put(np); + return NULL; + } + + return np; +} + +/** + * get_parent_cell_index -- return the cell-index of the parent of a node + * + * Return the value of the cell-index property of the parent of the given + * node. This is used for DMA channel nodes that need to know the DMA ID + * of the controller they are on. + */ +static int get_parent_cell_index(struct device_node *np) +{ + struct device_node *parent = of_get_parent(np); + const u32 *iprop; + int ret = -1; + + if (!parent) + return -1; + + iprop = of_get_property(parent, "cell-index", NULL); + if (iprop) + ret = *iprop; + + of_node_put(parent); + + return ret; +} + +/** + * codec_node_dev_name - determine the dev_name for a codec node + * + * This function determines the dev_name for an I2C node. This is the name + * that would be returned by dev_name() if this device_node were part of a + * 'struct device' It's ugly and hackish, but it works. + * + * The dev_name for such devices include the bus number and I2C address. For + * example, "cs4270-codec.0-004f". + */ +static int codec_node_dev_name(struct device_node *np, char *buf, size_t len) +{ + const u32 *iprop; + int bus, addr; + char temp[DAI_NAME_SIZE]; + + of_modalias_node(np, temp, DAI_NAME_SIZE); + + iprop = of_get_property(np, "reg", NULL); + if (!iprop) + return -EINVAL; + + addr = *iprop; + + bus = get_parent_cell_index(np); + if (bus < 0) + return bus; + + snprintf(buf, len, "%s-codec.%u-%04x", temp, bus, addr); + + return 0; +} + +static int get_dma_channel(struct device_node *ssi_np, + const char *compatible, + struct snd_soc_dai_link *dai, + unsigned int *dma_channel_id, + unsigned int *dma_id) +{ + struct resource res; + struct device_node *dma_channel_np; + const u32 *iprop; + int ret; + + dma_channel_np = get_node_by_phandle_name(ssi_np, compatible, + "fsl,ssi-dma-channel"); + if (!dma_channel_np) + return -EINVAL; + + /* Determine the dev_name for the device_node. This code mimics the + * behavior of of_device_make_bus_id(). We need this because ASoC uses + * the dev_name() of the device to match the platform (DMA) device with + * the CPU (SSI) device. It's all ugly and hackish, but it works (for + * now). + * + * dai->platform name should already point to an allocated buffer. + */ + ret = of_address_to_resource(dma_channel_np, 0, &res); + if (ret) + return ret; + snprintf((char *)dai->platform_name, DAI_NAME_SIZE, "%llx.%s", + (unsigned long long) res.start, dma_channel_np->name); + + iprop = of_get_property(dma_channel_np, "cell-index", NULL); + if (!iprop) { + of_node_put(dma_channel_np); + return -EINVAL; + } + + *dma_channel_id = *iprop; + *dma_id = get_parent_cell_index(dma_channel_np); + of_node_put(dma_channel_np); + + return 0; +} + +/** + * p1022_ds_probe: platform probe function for the machine driver + * + * Although this is a machine driver, the SSI node is the "master" node with + * respect to audio hardware connections. Therefore, we create a new ASoC + * device for each new SSI node that has a codec attached. + */ +static int p1022_ds_probe(struct platform_device *pdev) +{ + struct device *dev = pdev->dev.parent; + /* ssi_pdev is the platform device for the SSI node that probed us */ + struct platform_device *ssi_pdev = + container_of(dev, struct platform_device, dev); + struct device_node *np = ssi_pdev->dev.of_node; + struct device_node *codec_np = NULL; + struct platform_device *sound_device = NULL; + struct machine_data *mdata; + int ret = -ENODEV; + const char *sprop; + const u32 *iprop; + + /* Find the codec node for this SSI. */ + codec_np = of_parse_phandle(np, "codec-handle", 0); + if (!codec_np) { + dev_err(dev, "could not find codec node\n"); + return -EINVAL; + } + + mdata = kzalloc(sizeof(struct machine_data), GFP_KERNEL); + if (!mdata) + return -ENOMEM; + + mdata->dai[0].cpu_dai_name = dev_name(&ssi_pdev->dev); + mdata->dai[0].ops = &p1022_ds_ops; + + /* Determine the codec name, it will be used as the codec DAI name */ + ret = codec_node_dev_name(codec_np, mdata->codec_name, DAI_NAME_SIZE); + if (ret) { + dev_err(&pdev->dev, "invalid codec node %s\n", + codec_np->full_name); + ret = -EINVAL; + goto error; + } + mdata->dai[0].codec_name = mdata->codec_name; + + /* We register two DAIs per SSI, one for playback and the other for + * capture. We support codecs that have separate DAIs for both playback + * and capture. + */ + memcpy(&mdata->dai[1], &mdata->dai[0], sizeof(struct snd_soc_dai_link)); + + /* The DAI names from the codec (snd_soc_dai_driver.name) */ + mdata->dai[0].codec_dai_name = "wm8776-hifi-playback"; + mdata->dai[1].codec_dai_name = "wm8776-hifi-capture"; + + /* Get the device ID */ + iprop = of_get_property(np, "cell-index", NULL); + if (!iprop) { + dev_err(&pdev->dev, "cell-index property not found\n"); + ret = -EINVAL; + goto error; + } + mdata->ssi_id = *iprop; + + /* Get the serial format and clock direction. */ + sprop = of_get_property(np, "fsl,mode", NULL); + if (!sprop) { + dev_err(&pdev->dev, "fsl,mode property not found\n"); + ret = -EINVAL; + goto error; + } + + if (strcasecmp(sprop, "i2s-slave") == 0) { + mdata->dai_format = SND_SOC_DAIFMT_I2S; + mdata->codec_clk_direction = SND_SOC_CLOCK_OUT; + mdata->cpu_clk_direction = SND_SOC_CLOCK_IN; + + /* In i2s-slave mode, the codec has its own clock source, so we + * need to get the frequency from the device tree and pass it to + * the codec driver. + */ + iprop = of_get_property(codec_np, "clock-frequency", NULL); + if (!iprop || !*iprop) { + dev_err(&pdev->dev, "codec bus-frequency " + "property is missing or invalid\n"); + ret = -EINVAL; + goto error; + } + mdata->clk_frequency = *iprop; + } else if (strcasecmp(sprop, "i2s-master") == 0) { + mdata->dai_format = SND_SOC_DAIFMT_I2S; + mdata->codec_clk_direction = SND_SOC_CLOCK_IN; + mdata->cpu_clk_direction = SND_SOC_CLOCK_OUT; + } else if (strcasecmp(sprop, "lj-slave") == 0) { + mdata->dai_format = SND_SOC_DAIFMT_LEFT_J; + mdata->codec_clk_direction = SND_SOC_CLOCK_OUT; + mdata->cpu_clk_direction = SND_SOC_CLOCK_IN; + } else if (strcasecmp(sprop, "lj-master") == 0) { + mdata->dai_format = SND_SOC_DAIFMT_LEFT_J; + mdata->codec_clk_direction = SND_SOC_CLOCK_IN; + mdata->cpu_clk_direction = SND_SOC_CLOCK_OUT; + } else if (strcasecmp(sprop, "rj-slave") == 0) { + mdata->dai_format = SND_SOC_DAIFMT_RIGHT_J; + mdata->codec_clk_direction = SND_SOC_CLOCK_OUT; + mdata->cpu_clk_direction = SND_SOC_CLOCK_IN; + } else if (strcasecmp(sprop, "rj-master") == 0) { + mdata->dai_format = SND_SOC_DAIFMT_RIGHT_J; + mdata->codec_clk_direction = SND_SOC_CLOCK_IN; + mdata->cpu_clk_direction = SND_SOC_CLOCK_OUT; + } else if (strcasecmp(sprop, "ac97-slave") == 0) { + mdata->dai_format = SND_SOC_DAIFMT_AC97; + mdata->codec_clk_direction = SND_SOC_CLOCK_OUT; + mdata->cpu_clk_direction = SND_SOC_CLOCK_IN; + } else if (strcasecmp(sprop, "ac97-master") == 0) { + mdata->dai_format = SND_SOC_DAIFMT_AC97; + mdata->codec_clk_direction = SND_SOC_CLOCK_IN; + mdata->cpu_clk_direction = SND_SOC_CLOCK_OUT; + } else { + dev_err(&pdev->dev, + "unrecognized fsl,mode property '%s'\n", sprop); + ret = -EINVAL; + goto error; + } + + if (!mdata->clk_frequency) { + dev_err(&pdev->dev, "unknown clock frequency\n"); + ret = -EINVAL; + goto error; + } + + /* Find the playback DMA channel to use. */ + mdata->dai[0].platform_name = mdata->platform_name[0]; + ret = get_dma_channel(np, "fsl,playback-dma", &mdata->dai[0], + &mdata->dma_channel_id[0], + &mdata->dma_id[0]); + if (ret) { + dev_err(&pdev->dev, "missing/invalid playback DMA phandle\n"); + goto error; + } + + /* Find the capture DMA channel to use. */ + mdata->dai[1].platform_name = mdata->platform_name[1]; + ret = get_dma_channel(np, "fsl,capture-dma", &mdata->dai[1], + &mdata->dma_channel_id[1], + &mdata->dma_id[1]); + if (ret) { + dev_err(&pdev->dev, "missing/invalid capture DMA phandle\n"); + goto error; + } + + /* Initialize our DAI data structure. */ + mdata->dai[0].stream_name = "playback"; + mdata->dai[1].stream_name = "capture"; + mdata->dai[0].name = mdata->dai[0].stream_name; + mdata->dai[1].name = mdata->dai[1].stream_name; + + mdata->card.probe = p1022_ds_machine_probe; + mdata->card.remove = p1022_ds_machine_remove; + mdata->card.name = pdev->name; /* The platform driver name */ + mdata->card.num_links = 2; + mdata->card.dai_link = mdata->dai; + + /* Allocate a new audio platform device structure */ + sound_device = platform_device_alloc("soc-audio", -1); + if (!sound_device) { + dev_err(&pdev->dev, "platform device alloc failed\n"); + ret = -ENOMEM; + goto error; + } + + /* Associate the card data with the sound device */ + platform_set_drvdata(sound_device, &mdata->card); + + /* Register with ASoC */ + ret = platform_device_add(sound_device); + if (ret) { + dev_err(&pdev->dev, "platform device add failed\n"); + goto error; + } + + of_node_put(codec_np); + + return 0; + +error: + of_node_put(codec_np); + + if (sound_device) + platform_device_unregister(sound_device); + + kfree(mdata); + + return ret; +} + +/** + * p1022_ds_remove: remove the platform device + * + * This function is called when the platform device is removed. + */ +static int __devexit p1022_ds_remove(struct platform_device *pdev) +{ + struct platform_device *sound_device = dev_get_drvdata(&pdev->dev); + struct snd_soc_card *card = platform_get_drvdata(sound_device); + struct machine_data *mdata = + container_of(card, struct machine_data, card); + + platform_device_unregister(sound_device); + + kfree(mdata); + sound_device->dev.platform_data = NULL; + + dev_set_drvdata(&pdev->dev, NULL); + + return 0; +} + +static struct platform_driver p1022_ds_driver = { + .probe = p1022_ds_probe, + .remove = __devexit_p(p1022_ds_remove), + .driver = { + /* The name must match the 'model' property in the device tree, + * in lowercase letters, but only the part after that last + * comma. This is because some model properties have a "fsl," + * prefix. + */ + .name = "snd-soc-p1022", + .owner = THIS_MODULE, + }, +}; + +/** + * p1022_ds_init: machine driver initialization. + * + * This function is called when this module is loaded. + */ +static int __init p1022_ds_init(void) +{ + struct device_node *guts_np; + struct resource res; + + pr_info("Freescale P1022 DS ALSA SoC machine driver\n"); + + /* Get the physical address of the global utilities registers */ + guts_np = of_find_compatible_node(NULL, NULL, "fsl,p1022-guts"); + if (of_address_to_resource(guts_np, 0, &res)) { + pr_err("p1022-ds: missing/invalid global utilities node\n"); + return -EINVAL; + } + guts_phys = res.start; + of_node_put(guts_np); + + return platform_driver_register(&p1022_ds_driver); +} + +/** + * p1022_ds_exit: machine driver exit + * + * This function is called when this driver is unloaded. + */ +static void __exit p1022_ds_exit(void) +{ + platform_driver_unregister(&p1022_ds_driver); +} + +module_init(p1022_ds_init); +module_exit(p1022_ds_exit); + +MODULE_AUTHOR("Timur Tabi "); +MODULE_DESCRIPTION("Freescale P1022 DS ALSA SoC machine driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.1 From 26b01ccdc8ded270a1a65721b99acacf0c44e088 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 18 Aug 2010 20:20:55 +0100 Subject: ASoC: Don't call DAI registration for CODECs with no DAI Otherwise we generate worrying (but benign) warnings for amps. Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- sound/soc/soc-core.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'sound') diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 7093c17..65352c7 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -3122,10 +3122,12 @@ int snd_soc_register_codec(struct device *dev, fixup_codec_formats(&dai_drv[i].capture); } - /* register DAIs */ - ret = snd_soc_register_dais(dev, dai_drv, num_dai); - if (ret < 0) + /* register any DAIs */ + if (num_dai) { + ret = snd_soc_register_dais(dev, dai_drv, num_dai); + if (ret < 0) goto error; + } mutex_lock(&client_mutex); list_add(&codec->list, &codec_list); @@ -3164,8 +3166,9 @@ void snd_soc_unregister_codec(struct device *dev) return; found: - for (i = 0; i < codec->num_dai; i++) - snd_soc_unregister_dai(dev); + if (codec->num_dai) + for (i = 0; i < codec->num_dai; i++) + snd_soc_unregister_dai(dev); mutex_lock(&client_mutex); list_del(&codec->list); -- cgit v1.1 From 38fec7272bc033b75a0eb8976c56c2024d371b7d Mon Sep 17 00:00:00 2001 From: Timur Tabi Date: Thu, 19 Aug 2010 15:26:58 -0500 Subject: ASoC: mpc8610: replace of_device with platform_device 'struct of_device' no longer exists, and its functionality has been merged into platform_device. Update the MPC8610 HPCD audio drivers (fsl_ssi, fsl_dma, and mpc8610_hpcd) accordingly. Also add a #include for slab.h, which is now needed for kmalloc and kfree. Signed-off-by: Timur Tabi Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_dma.c | 23 ++++++++++++----------- sound/soc/fsl/fsl_ssi.c | 42 +++++++++++++++++++++--------------------- sound/soc/fsl/mpc8610_hpcd.c | 10 ++++++---- 3 files changed, 39 insertions(+), 36 deletions(-) (limited to 'sound') diff --git a/sound/soc/fsl/fsl_dma.c b/sound/soc/fsl/fsl_dma.c index f039e8d..4cf98c0 100644 --- a/sound/soc/fsl/fsl_dma.c +++ b/sound/soc/fsl/fsl_dma.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -895,11 +896,11 @@ static struct snd_pcm_ops fsl_dma_ops = { .pointer = fsl_dma_pointer, }; -static int __devinit fsl_soc_dma_probe(struct of_device *of_dev, +static int __devinit fsl_soc_dma_probe(struct platform_device *pdev, const struct of_device_id *match) { struct dma_object *dma; - struct device_node *np = of_dev->dev.of_node; + struct device_node *np = pdev->dev.of_node; struct device_node *ssi_np; struct resource res; const uint32_t *iprop; @@ -908,13 +909,13 @@ static int __devinit fsl_soc_dma_probe(struct of_device *of_dev, /* Find the SSI node that points to us. */ ssi_np = find_ssi_node(np); if (!ssi_np) { - dev_err(&of_dev->dev, "cannot find parent SSI node\n"); + dev_err(&pdev->dev, "cannot find parent SSI node\n"); return -ENODEV; } ret = of_address_to_resource(ssi_np, 0, &res); if (ret) { - dev_err(&of_dev->dev, "could not determine resources for %s\n", + dev_err(&pdev->dev, "could not determine resources for %s\n", ssi_np->full_name); of_node_put(ssi_np); return ret; @@ -922,7 +923,7 @@ static int __devinit fsl_soc_dma_probe(struct of_device *of_dev, dma = kzalloc(sizeof(*dma) + strlen(np->full_name), GFP_KERNEL); if (!dma) { - dev_err(&of_dev->dev, "could not allocate dma object\n"); + dev_err(&pdev->dev, "could not allocate dma object\n"); of_node_put(ssi_np); return -ENOMEM; } @@ -945,9 +946,9 @@ static int __devinit fsl_soc_dma_probe(struct of_device *of_dev, of_node_put(ssi_np); - ret = snd_soc_register_platform(&of_dev->dev, &dma->dai); + ret = snd_soc_register_platform(&pdev->dev, &dma->dai); if (ret) { - dev_err(&of_dev->dev, "could not register platform\n"); + dev_err(&pdev->dev, "could not register platform\n"); kfree(dma); return ret; } @@ -955,16 +956,16 @@ static int __devinit fsl_soc_dma_probe(struct of_device *of_dev, dma->channel = of_iomap(np, 0); dma->irq = irq_of_parse_and_map(np, 0); - dev_set_drvdata(&of_dev->dev, dma); + dev_set_drvdata(&pdev->dev, dma); return 0; } -static int __devexit fsl_soc_dma_remove(struct of_device *of_dev) +static int __devexit fsl_soc_dma_remove(struct platform_device *pdev) { - struct dma_object *dma = dev_get_drvdata(&of_dev->dev); + struct dma_object *dma = dev_get_drvdata(&pdev->dev); - snd_soc_unregister_platform(&of_dev->dev); + snd_soc_unregister_platform(&pdev->dev); iounmap(dma->channel); irq_dispose_mapping(dma->irq); kfree(dma); diff --git a/sound/soc/fsl/fsl_ssi.c b/sound/soc/fsl/fsl_ssi.c index d1c855a..4cc167a 100644 --- a/sound/soc/fsl/fsl_ssi.c +++ b/sound/soc/fsl/fsl_ssi.c @@ -624,13 +624,13 @@ static void make_lowercase(char *s) } } -static int __devinit fsl_ssi_probe(struct of_device *of_dev, +static int __devinit fsl_ssi_probe(struct platform_device *pdev, const struct of_device_id *match) { struct fsl_ssi_private *ssi_private; int ret = 0; struct device_attribute *dev_attr = NULL; - struct device_node *np = of_dev->dev.of_node; + struct device_node *np = pdev->dev.of_node; const char *p, *sprop; const uint32_t *iprop; struct resource res; @@ -645,14 +645,14 @@ static int __devinit fsl_ssi_probe(struct of_device *of_dev, /* Check for a codec-handle property. */ if (!of_get_property(np, "codec-handle", NULL)) { - dev_err(&of_dev->dev, "missing codec-handle property\n"); + dev_err(&pdev->dev, "missing codec-handle property\n"); return -ENODEV; } /* We only support the SSI in "I2S Slave" mode */ sprop = of_get_property(np, "fsl,mode", NULL); if (!sprop || strcmp(sprop, "i2s-slave")) { - dev_notice(&of_dev->dev, "mode %s is unsupported\n", sprop); + dev_notice(&pdev->dev, "mode %s is unsupported\n", sprop); return -ENODEV; } @@ -661,7 +661,7 @@ static int __devinit fsl_ssi_probe(struct of_device *of_dev, ssi_private = kzalloc(sizeof(struct fsl_ssi_private) + strlen(p), GFP_KERNEL); if (!ssi_private) { - dev_err(&of_dev->dev, "could not allocate DAI object\n"); + dev_err(&pdev->dev, "could not allocate DAI object\n"); return -ENOMEM; } @@ -675,7 +675,7 @@ static int __devinit fsl_ssi_probe(struct of_device *of_dev, /* Get the addresses and IRQ */ ret = of_address_to_resource(np, 0, &res); if (ret) { - dev_err(&of_dev->dev, "could not determine device resources\n"); + dev_err(&pdev->dev, "could not determine device resources\n"); kfree(ssi_private); return ret; } @@ -703,19 +703,19 @@ static int __devinit fsl_ssi_probe(struct of_device *of_dev, dev_attr->attr.mode = S_IRUGO; dev_attr->show = fsl_sysfs_ssi_show; - ret = device_create_file(&of_dev->dev, dev_attr); + ret = device_create_file(&pdev->dev, dev_attr); if (ret) { - dev_err(&of_dev->dev, "could not create sysfs %s file\n", + dev_err(&pdev->dev, "could not create sysfs %s file\n", ssi_private->dev_attr.attr.name); goto error; } /* Register with ASoC */ - dev_set_drvdata(&of_dev->dev, ssi_private); + dev_set_drvdata(&pdev->dev, ssi_private); - ret = snd_soc_register_dai(&of_dev->dev, &ssi_private->cpu_dai_drv); + ret = snd_soc_register_dai(&pdev->dev, &ssi_private->cpu_dai_drv); if (ret) { - dev_err(&of_dev->dev, "failed to register DAI: %d\n", ret); + dev_err(&pdev->dev, "failed to register DAI: %d\n", ret); goto error; } @@ -733,20 +733,20 @@ static int __devinit fsl_ssi_probe(struct of_device *of_dev, make_lowercase(name); ssi_private->pdev = - platform_device_register_data(&of_dev->dev, name, 0, NULL, 0); + platform_device_register_data(&pdev->dev, name, 0, NULL, 0); if (IS_ERR(ssi_private->pdev)) { ret = PTR_ERR(ssi_private->pdev); - dev_err(&of_dev->dev, "failed to register platform: %d\n", ret); + dev_err(&pdev->dev, "failed to register platform: %d\n", ret); goto error; } return 0; error: - snd_soc_unregister_dai(&of_dev->dev); - dev_set_drvdata(&of_dev->dev, NULL); + snd_soc_unregister_dai(&pdev->dev); + dev_set_drvdata(&pdev->dev, NULL); if (dev_attr) - device_remove_file(&of_dev->dev, dev_attr); + device_remove_file(&pdev->dev, dev_attr); irq_dispose_mapping(ssi_private->irq); iounmap(ssi_private->ssi); kfree(ssi_private); @@ -754,16 +754,16 @@ error: return ret; } -static int fsl_ssi_remove(struct of_device *of_dev) +static int fsl_ssi_remove(struct platform_device *pdev) { - struct fsl_ssi_private *ssi_private = dev_get_drvdata(&of_dev->dev); + struct fsl_ssi_private *ssi_private = dev_get_drvdata(&pdev->dev); platform_device_unregister(ssi_private->pdev); - snd_soc_unregister_dai(&of_dev->dev); - device_remove_file(&of_dev->dev, &ssi_private->dev_attr); + snd_soc_unregister_dai(&pdev->dev); + device_remove_file(&pdev->dev, &ssi_private->dev_attr); kfree(ssi_private); - dev_set_drvdata(&of_dev->dev, NULL); + dev_set_drvdata(&pdev->dev, NULL); return 0; } diff --git a/sound/soc/fsl/mpc8610_hpcd.c b/sound/soc/fsl/mpc8610_hpcd.c index 38339c1..0d7dcf1 100644 --- a/sound/soc/fsl/mpc8610_hpcd.c +++ b/sound/soc/fsl/mpc8610_hpcd.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -323,9 +324,10 @@ static int get_dma_channel(struct device_node *ssi_np, static int mpc8610_hpcd_probe(struct platform_device *pdev) { struct device *dev = pdev->dev.parent; - /* of_dev is the OF device for the SSI node that probed us */ - struct of_device *of_dev = container_of(dev, struct of_device, dev); - struct device_node *np = of_dev->dev.of_node; + /* ssi_pdev is the platform device for the SSI node that probed us */ + struct platform_device *ssi_pdev = + container_of(dev, struct platform_device, dev); + struct device_node *np = ssi_pdev->dev.of_node; struct device_node *codec_np = NULL; struct platform_device *sound_device = NULL; struct mpc8610_hpcd_data *machine_data; @@ -348,7 +350,7 @@ static int mpc8610_hpcd_probe(struct platform_device *pdev) if (!machine_data) return -ENOMEM; - machine_data->dai[0].cpu_dai_name = dev_name(&of_dev->dev); + machine_data->dai[0].cpu_dai_name = dev_name(&ssi_pdev->dev); machine_data->dai[0].ops = &mpc8610_hpcd_ops; /* Determine the codec name, it will be used as the codec DAI name */ -- cgit v1.1