/* Copyright (c) 2010-2011, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "msm8660-pcm.h" #include "../codecs/timpani.h" #define PM8058_GPIO_BASE NR_MSM_GPIOS #define PM8901_GPIO_BASE (PM8058_GPIO_BASE + \ PM8058_GPIOS + PM8058_MPPS) #define PM8901_GPIO_PM_TO_SYS(pm_gpio) (pm_gpio + PM8901_GPIO_BASE) #define GPIO_EXPANDER_GPIO_BASE \ (PM8901_GPIO_BASE + PM8901_MPPS) static struct clk *rx_osr_clk; static struct clk *rx_bit_clk; static struct clk *tx_osr_clk; static struct clk *tx_bit_clk; static int rx_hw_param_status; static int tx_hw_param_status; /* Platform specific logic */ static int timpani_rx_route_enable(void) { int ret = 0; pr_debug("%s\n", __func__); ret = gpio_request(109, "I2S_Clock"); if (ret != 0) { pr_err("%s: I2s clk gpio 109 request" "failed\n", __func__); return ret; } return ret; } static int timpani_rx_route_disable(void) { int ret = 0; pr_debug("%s\n", __func__); gpio_free(109); return ret; } #define GPIO_CLASS_D1_EN (GPIO_EXPANDER_GPIO_BASE + 0) #define PM8901_MPP_3 (2) /* PM8901 MPP starts from 0 */ static void config_class_d1_gpio(int enable) { int rc; if (enable) { rc = gpio_request(GPIO_CLASS_D1_EN, "CLASSD1_EN"); if (rc) { pr_err("%s: spkr pamp gpio %d request" "failed\n", __func__, GPIO_CLASS_D1_EN); return; } gpio_direction_output(GPIO_CLASS_D1_EN, 1); gpio_set_value_cansleep(GPIO_CLASS_D1_EN, 1); } else { gpio_set_value_cansleep(GPIO_CLASS_D1_EN, 0); gpio_free(GPIO_CLASS_D1_EN); } } static void config_class_d0_gpio(int enable) { int rc; if (enable) { rc = pm8901_mpp_config_digital_out(PM8901_MPP_3, PM8901_MPP_DIG_LEVEL_MSMIO, 1); if (rc) { pr_err("%s: CLASS_D0_EN failed\n", __func__); return; } rc = gpio_request(PM8901_GPIO_PM_TO_SYS(PM8901_MPP_3), "CLASSD0_EN"); if (rc) { pr_err("%s: spkr pamp gpio pm8901 mpp3 request" "failed\n", __func__); pm8901_mpp_config_digital_out(PM8901_MPP_3, PM8901_MPP_DIG_LEVEL_MSMIO, 0); return; } gpio_direction_output(PM8901_GPIO_PM_TO_SYS(PM8901_MPP_3), 1); gpio_set_value_cansleep(PM8901_GPIO_PM_TO_SYS(PM8901_MPP_3), 1); } else { pm8901_mpp_config_digital_out(PM8901_MPP_3, PM8901_MPP_DIG_LEVEL_MSMIO, 0); gpio_set_value_cansleep(PM8901_GPIO_PM_TO_SYS(PM8901_MPP_3), 0); gpio_free(PM8901_GPIO_PM_TO_SYS(PM8901_MPP_3)); } } static void timpani_poweramp_on(void) { pr_debug("%s: enable stereo spkr amp\n", __func__); timpani_rx_route_enable(); config_class_d0_gpio(1); config_class_d1_gpio(1); } static void timpani_poweramp_off(void) { pr_debug("%s: disable stereo spkr amp\n", __func__); timpani_rx_route_disable(); config_class_d0_gpio(0); config_class_d1_gpio(0); } static int msm8660_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { int rate = params_rate(params); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { if (rx_hw_param_status) return 0; clk_set_rate(rx_osr_clk, rate * 256); rx_hw_param_status++; } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { if (tx_hw_param_status) return 0; clk_set_rate(tx_osr_clk, rate * 256); tx_hw_param_status++; } return 0; } static int msm8660_startup(struct snd_pcm_substream *substream) { int ret = 0; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { rx_osr_clk = clk_get(NULL, "i2s_spkr_osr_clk"); if (IS_ERR(rx_osr_clk)) { pr_debug("Failed to get i2s_spkr_osr_clk\n"); return PTR_ERR(rx_osr_clk); } /* Master clock OSR 256 */ /* Initially set to Lowest sample rate Needed */ clk_set_rate(rx_osr_clk, 8000 * 256); ret = clk_prepare_enable(rx_osr_clk); if (ret != 0) { pr_debug("Unable to enable i2s_spkr_osr_clk\n"); clk_put(rx_osr_clk); return ret; } rx_bit_clk = clk_get(NULL, "i2s_spkr_bit_clk"); if (IS_ERR(rx_bit_clk)) { pr_debug("Failed to get i2s_spkr_bit_clk\n"); clk_disable_unprepare(rx_osr_clk); clk_put(rx_osr_clk); return PTR_ERR(rx_bit_clk); } clk_set_rate(rx_bit_clk, 8); ret = clk_prepare_enable(rx_bit_clk); if (ret != 0) { pr_debug("Unable to enable i2s_spkr_bit_clk\n"); clk_put(rx_bit_clk); clk_disable_unprepare(rx_osr_clk); clk_put(rx_osr_clk); return ret; } timpani_poweramp_on(); msleep(30); /* End of platform specific logic */ } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { tx_osr_clk = clk_get(NULL, "i2s_mic_osr_clk"); if (IS_ERR(tx_osr_clk)) { pr_debug("Failed to get i2s_mic_osr_clk\n"); return PTR_ERR(tx_osr_clk); } /* Master clock OSR 256 */ clk_set_rate(tx_osr_clk, 8000 * 256); ret = clk_prepare_enable(tx_osr_clk); if (ret != 0) { pr_debug("Unable to enable i2s_mic_osr_clk\n"); clk_put(tx_osr_clk); return ret; } tx_bit_clk = clk_get(NULL, "i2s_mic_bit_clk"); if (IS_ERR(tx_bit_clk)) { pr_debug("Failed to get i2s_mic_bit_clk\n"); clk_disable_unprepare(tx_osr_clk); clk_put(tx_osr_clk); return PTR_ERR(tx_bit_clk); } clk_set_rate(tx_bit_clk, 8); ret = clk_prepare_enable(tx_bit_clk); if (ret != 0) { pr_debug("Unable to enable i2s_mic_bit_clk\n"); clk_put(tx_bit_clk); clk_disable_unprepare(tx_osr_clk); clk_put(tx_osr_clk); return ret; } msm_snddev_enable_dmic_power(); msleep(30); } return ret; } /* * TODO: rx/tx_hw_param_status should be a counter in the below code * when driver starts supporting mutisession else setting it to 0 * will stop audio in all sessions. */ static void msm8660_shutdown(struct snd_pcm_substream *substream) { if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { rx_hw_param_status = 0; timpani_poweramp_off(); msleep(30); if (rx_bit_clk) { clk_disable_unprepare(rx_bit_clk); clk_put(rx_bit_clk); rx_bit_clk = NULL; } if (rx_osr_clk) { clk_disable_unprepare(rx_osr_clk); clk_put(rx_osr_clk); rx_osr_clk = NULL; } } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { tx_hw_param_status = 0; msm_snddev_disable_dmic_power(); msleep(30); if (tx_bit_clk) { clk_disable_unprepare(tx_bit_clk); clk_put(tx_bit_clk); tx_bit_clk = NULL; } if (tx_osr_clk) { clk_disable_unprepare(tx_osr_clk); clk_put(tx_osr_clk); tx_osr_clk = NULL; } } } static struct snd_soc_ops machine_ops = { .startup = msm8660_startup, .shutdown = msm8660_shutdown, .hw_params = msm8660_hw_params, }; /* Digital audio interface glue - connects codec <---> CPU */ static struct snd_soc_dai_link msm8660_dai[] = { { .name = "Audio Rx", .stream_name = "Audio Rx", .cpu_dai = &msm_cpu_dai[0], .codec_dai = &timpani_codec_dai[0], .ops = &machine_ops, }, { .name = "Audio Tx", .stream_name = "Audio Tx", .cpu_dai = &msm_cpu_dai[5], .codec_dai = &timpani_codec_dai[1], .ops = &machine_ops, } }; struct snd_soc_card snd_soc_card_msm8660 = { .name = "msm8660-pcm-audio", .dai_link = msm8660_dai, .num_links = ARRAY_SIZE(msm8660_dai), .platform = &msm8660_soc_platform, }; /* msm_audio audio subsystem */ static struct snd_soc_device msm_snd_devdata = { .card = &snd_soc_card_msm8660, .codec_dev = &soc_codec_dev_timpani, }; static struct platform_device *msm_snd_device; static int __init msm_audio_init(void) { int ret; msm_snd_device = platform_device_alloc("soc-audio", 0); if (!msm_snd_device) { pr_err("Platform device allocation failed\n"); return -ENOMEM; } platform_set_drvdata(msm_snd_device, &msm_snd_devdata); msm_snd_devdata.dev = &msm_snd_device->dev; ret = platform_device_add(msm_snd_device); if (ret) { platform_device_put(msm_snd_device); return ret; } return ret; } module_init(msm_audio_init); static void __exit msm_audio_exit(void) { platform_device_unregister(msm_snd_device); } module_exit(msm_audio_exit); MODULE_DESCRIPTION("ALSA SoC MSM8660"); MODULE_LICENSE("GPL v2");