blob: 38bb65ab38f600e53fe91dc27f37317452f4aa56 [file] [log] [blame]
/* Copyright (c) 2010-2012, 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 <linux/module.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <linux/mfd/msm-adie-codec.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/wakelock.h>
#include <linux/pmic8058-othc.h>
#include <linux/slab.h>
#include <linux/regulator/consumer.h>
#include <linux/moduleparam.h>
#include <linux/pm_qos.h>
#include <asm/uaccess.h>
#include <mach/qdsp6v2/audio_dev_ctl.h>
#include <mach/qdsp6v2/audio_acdb.h>
#include <mach/vreg.h>
#include <mach/pmic.h>
#include <mach/debug_mm.h>
#include <mach/cpuidle.h>
#include <sound/q6afe.h>
#include <sound/apr_audio.h>
#include "snddev_icodec.h"
#define SNDDEV_ICODEC_PCM_SZ 32 /* 16 bit / sample stereo mode */
#define SNDDEV_ICODEC_MUL_FACTOR 3 /* Multi by 8 Shift by 3 */
#define SNDDEV_ICODEC_CLK_RATE(freq) \
(((freq) * (SNDDEV_ICODEC_PCM_SZ)) << (SNDDEV_ICODEC_MUL_FACTOR))
#define SNDDEV_LOW_POWER_MODE 0
#define SNDDEV_HIGH_POWER_MODE 1
/* Voltage required for S4 in microVolts, 2.2V or 2200000microvolts */
#define SNDDEV_VREG_8058_S4_VOLTAGE (2200000)
/* Load Current required for S4 in microAmps,
36mA - 56mA */
#define SNDDEV_VREG_LOW_POWER_LOAD (36000)
#define SNDDEV_VREG_HIGH_POWER_LOAD (56000)
bool msm_codec_i2s_slave_mode;
/* Context for each internal codec sound device */
struct snddev_icodec_state {
struct snddev_icodec_data *data;
struct adie_codec_path *adie_path;
u32 sample_rate;
u32 enabled;
};
/* Global state for the driver */
struct snddev_icodec_drv_state {
struct mutex rx_lock;
struct mutex lb_lock;
struct mutex tx_lock;
u32 rx_active; /* ensure one rx device at a time */
u32 tx_active; /* ensure one tx device at a time */
struct clk *rx_osrclk;
struct clk *rx_bitclk;
struct clk *tx_osrclk;
struct clk *tx_bitclk;
struct pm_qos_request rx_pm_qos_req;
struct pm_qos_request tx_pm_qos_req;
/* handle to pmic8058 regulator smps4 */
struct regulator *snddev_vreg;
};
static struct snddev_icodec_drv_state snddev_icodec_drv;
struct regulator *vreg_init(void)
{
int rc;
struct regulator *vreg_ptr;
vreg_ptr = regulator_get(NULL, "8058_s4");
if (IS_ERR(vreg_ptr)) {
pr_err("%s: regulator_get 8058_s4 failed\n", __func__);
return NULL;
}
rc = regulator_set_voltage(vreg_ptr, SNDDEV_VREG_8058_S4_VOLTAGE,
SNDDEV_VREG_8058_S4_VOLTAGE);
if (rc == 0)
return vreg_ptr;
else
return NULL;
}
static void vreg_deinit(struct regulator *vreg)
{
regulator_put(vreg);
}
static void vreg_mode_vote(struct regulator *vreg, int enable, int mode)
{
int rc;
if (enable) {
rc = regulator_enable(vreg);
if (rc != 0)
pr_err("%s:Enabling regulator failed\n", __func__);
else {
if (mode)
regulator_set_optimum_mode(vreg,
SNDDEV_VREG_HIGH_POWER_LOAD);
else
regulator_set_optimum_mode(vreg,
SNDDEV_VREG_LOW_POWER_LOAD);
}
} else {
rc = regulator_disable(vreg);
if (rc != 0)
pr_err("%s:Disabling regulator failed\n", __func__);
}
}
struct msm_cdcclk_ctl_state {
unsigned int rx_mclk;
unsigned int rx_mclk_requested;
unsigned int tx_mclk;
unsigned int tx_mclk_requested;
};
static struct msm_cdcclk_ctl_state the_msm_cdcclk_ctl_state;
static int msm_snddev_rx_mclk_request(void)
{
int rc = 0;
rc = gpio_request(the_msm_cdcclk_ctl_state.rx_mclk,
"MSM_SNDDEV_RX_MCLK");
if (rc < 0) {
pr_err("%s: GPIO request for MSM SNDDEV RX failed\n", __func__);
return rc;
}
the_msm_cdcclk_ctl_state.rx_mclk_requested = 1;
return rc;
}
static int msm_snddev_tx_mclk_request(void)
{
int rc = 0;
rc = gpio_request(the_msm_cdcclk_ctl_state.tx_mclk,
"MSM_SNDDEV_TX_MCLK");
if (rc < 0) {
pr_err("%s: GPIO request for MSM SNDDEV TX failed\n", __func__);
return rc;
}
the_msm_cdcclk_ctl_state.tx_mclk_requested = 1;
return rc;
}
static void msm_snddev_rx_mclk_free(void)
{
if (the_msm_cdcclk_ctl_state.rx_mclk_requested) {
gpio_free(the_msm_cdcclk_ctl_state.rx_mclk);
the_msm_cdcclk_ctl_state.rx_mclk_requested = 0;
}
}
static void msm_snddev_tx_mclk_free(void)
{
if (the_msm_cdcclk_ctl_state.tx_mclk_requested) {
gpio_free(the_msm_cdcclk_ctl_state.tx_mclk);
the_msm_cdcclk_ctl_state.tx_mclk_requested = 0;
}
}
static int get_msm_cdcclk_ctl_gpios(struct platform_device *pdev)
{
int rc = 0;
struct resource *res;
/* Claim all of the GPIOs. */
res = platform_get_resource_byname(pdev, IORESOURCE_IO,
"msm_snddev_rx_mclk");
if (!res) {
pr_err("%s: failed to get gpio MSM SNDDEV RX\n", __func__);
return -ENODEV;
}
the_msm_cdcclk_ctl_state.rx_mclk = res->start;
the_msm_cdcclk_ctl_state.rx_mclk_requested = 0;
res = platform_get_resource_byname(pdev, IORESOURCE_IO,
"msm_snddev_tx_mclk");
if (!res) {
pr_err("%s: failed to get gpio MSM SNDDEV TX\n", __func__);
return -ENODEV;
}
the_msm_cdcclk_ctl_state.tx_mclk = res->start;
the_msm_cdcclk_ctl_state.tx_mclk_requested = 0;
return rc;
}
static int msm_cdcclk_ctl_probe(struct platform_device *pdev)
{
int rc = 0;
rc = get_msm_cdcclk_ctl_gpios(pdev);
if (rc < 0) {
pr_err("%s: GPIO configuration failed\n", __func__);
return -ENODEV;
}
return rc;
}
static struct platform_driver msm_cdcclk_ctl_driver = {
.probe = msm_cdcclk_ctl_probe,
.driver = { .name = "msm_cdcclk_ctl"}
};
static int snddev_icodec_open_lb(struct snddev_icodec_state *icodec)
{
int trc;
struct snddev_icodec_drv_state *drv = &snddev_icodec_drv;
/* Voting for low power is ok here as all use cases are
* supported in low power mode.
*/
if (drv->snddev_vreg)
vreg_mode_vote(drv->snddev_vreg, 1,
SNDDEV_LOW_POWER_MODE);
if (icodec->data->voltage_on)
icodec->data->voltage_on();
trc = adie_codec_open(icodec->data->profile, &icodec->adie_path);
if (IS_ERR_VALUE(trc))
pr_err("%s: adie codec open failed\n", __func__);
else
adie_codec_setpath(icodec->adie_path,
icodec->sample_rate, 256);
if (icodec->adie_path)
adie_codec_proceed_stage(icodec->adie_path,
ADIE_CODEC_DIGITAL_ANALOG_READY);
if (icodec->data->pamp_on)
icodec->data->pamp_on();
icodec->enabled = 1;
return 0;
}
static int initialize_msm_icodec_gpios(struct platform_device *pdev)
{
int rc = 0;
struct resource *res;
int i = 0;
int *reg_defaults = pdev->dev.platform_data;
while ((res = platform_get_resource(pdev, IORESOURCE_IO, i))) {
rc = gpio_request(res->start, res->name);
if (rc) {
pr_err("%s: icodec gpio %d request failed\n", __func__,
res->start);
goto err;
} else {
/* This platform data structure only works if all gpio
* resources are to be used only in output mode.
* If gpio resources are added which are to be used in
* input mode, then the platform data structure will
* have to be changed.
*/
gpio_direction_output(res->start, reg_defaults[i]);
gpio_free(res->start);
}
i++;
}
err:
return rc;
}
static int msm_icodec_gpio_probe(struct platform_device *pdev)
{
int rc = 0;
rc = initialize_msm_icodec_gpios(pdev);
if (rc < 0) {
pr_err("%s: GPIO configuration failed\n", __func__);
return -ENODEV;
}
return rc;
}
static struct platform_driver msm_icodec_gpio_driver = {
.probe = msm_icodec_gpio_probe,
.driver = { .name = "msm_icodec_gpio"}
};
static int snddev_icodec_open_rx(struct snddev_icodec_state *icodec)
{
int trc;
int afe_channel_mode;
union afe_port_config afe_config;
struct snddev_icodec_drv_state *drv = &snddev_icodec_drv;
pm_qos_update_request(&drv->rx_pm_qos_req,
msm_cpuidle_get_deep_idle_latency());
if (drv->snddev_vreg) {
if (!strcmp(icodec->data->name, "headset_stereo_rx"))
vreg_mode_vote(drv->snddev_vreg, 1,
SNDDEV_LOW_POWER_MODE);
else
vreg_mode_vote(drv->snddev_vreg, 1,
SNDDEV_HIGH_POWER_MODE);
}
msm_snddev_rx_mclk_request();
drv->rx_osrclk = clk_get_sys(NULL, "i2s_spkr_osr_clk");
if (IS_ERR(drv->rx_osrclk))
pr_err("%s master clock Error\n", __func__);
trc = clk_set_rate(drv->rx_osrclk,
SNDDEV_ICODEC_CLK_RATE(icodec->sample_rate));
if (IS_ERR_VALUE(trc)) {
pr_err("ERROR setting m clock1\n");
goto error_invalid_freq;
}
clk_prepare_enable(drv->rx_osrclk);
drv->rx_bitclk = clk_get_sys(NULL, "i2s_spkr_bit_clk");
if (IS_ERR(drv->rx_bitclk))
pr_err("%s clock Error\n", __func__);
/* Master clock = Sample Rate * OSR rate bit clock
* OSR Rate bit clock = bit/sample * channel master
* clock / bit clock = divider value = 8
*/
if (msm_codec_i2s_slave_mode) {
pr_info("%s: configuring bit clock for slave mode\n",
__func__);
trc = clk_set_rate(drv->rx_bitclk, 0);
} else
trc = clk_set_rate(drv->rx_bitclk, 8);
if (IS_ERR_VALUE(trc)) {
pr_err("ERROR setting m clock1\n");
goto error_adie;
}
clk_prepare_enable(drv->rx_bitclk);
if (icodec->data->voltage_on)
icodec->data->voltage_on();
/* Configure ADIE */
trc = adie_codec_open(icodec->data->profile, &icodec->adie_path);
if (IS_ERR_VALUE(trc))
pr_err("%s: adie codec open failed\n", __func__);
else
adie_codec_setpath(icodec->adie_path,
icodec->sample_rate, 256);
/* OSR default to 256, can be changed for power optimization
* If OSR is to be changed, need clock API for setting the divider
*/
switch (icodec->data->channel_mode) {
case 2:
afe_channel_mode = MSM_AFE_STEREO;
break;
case 1:
default:
afe_channel_mode = MSM_AFE_MONO;
break;
}
afe_config.mi2s.channel = afe_channel_mode;
afe_config.mi2s.bitwidth = 16;
afe_config.mi2s.line = 1;
afe_config.mi2s.format = MSM_AFE_I2S_FORMAT_LPCM;
if (msm_codec_i2s_slave_mode)
afe_config.mi2s.ws = 0;
else
afe_config.mi2s.ws = 1;
trc = afe_open(icodec->data->copp_id, &afe_config, icodec->sample_rate);
if (trc < 0)
pr_err("%s: afe open failed, trc = %d\n", __func__, trc);
/* Enable ADIE */
if (icodec->adie_path) {
adie_codec_proceed_stage(icodec->adie_path,
ADIE_CODEC_DIGITAL_READY);
adie_codec_proceed_stage(icodec->adie_path,
ADIE_CODEC_DIGITAL_ANALOG_READY);
}
if (msm_codec_i2s_slave_mode)
adie_codec_set_master_mode(icodec->adie_path, 1);
else
adie_codec_set_master_mode(icodec->adie_path, 0);
/* Enable power amplifier */
if (icodec->data->pamp_on) {
if (icodec->data->pamp_on()) {
pr_err("%s: Error turning on rx power\n", __func__);
goto error_pamp;
}
}
icodec->enabled = 1;
pm_qos_update_request(&drv->rx_pm_qos_req, PM_QOS_DEFAULT_VALUE);
return 0;
error_pamp:
error_adie:
clk_disable_unprepare(drv->rx_osrclk);
error_invalid_freq:
pr_err("%s: encounter error\n", __func__);
pm_qos_update_request(&drv->rx_pm_qos_req, PM_QOS_DEFAULT_VALUE);
return -ENODEV;
}
static int snddev_icodec_open_tx(struct snddev_icodec_state *icodec)
{
int trc;
int afe_channel_mode;
union afe_port_config afe_config;
struct snddev_icodec_drv_state *drv = &snddev_icodec_drv;;
pm_qos_update_request(&drv->tx_pm_qos_req,
msm_cpuidle_get_deep_idle_latency());
if (drv->snddev_vreg)
vreg_mode_vote(drv->snddev_vreg, 1, SNDDEV_HIGH_POWER_MODE);
/* Reuse pamp_on for TX platform-specific setup */
if (icodec->data->pamp_on) {
if (icodec->data->pamp_on()) {
pr_err("%s: Error turning on tx power\n", __func__);
goto error_pamp;
}
}
msm_snddev_tx_mclk_request();
drv->tx_osrclk = clk_get_sys(NULL, "i2s_mic_osr_clk");
if (IS_ERR(drv->tx_osrclk))
pr_err("%s master clock Error\n", __func__);
trc = clk_set_rate(drv->tx_osrclk,
SNDDEV_ICODEC_CLK_RATE(icodec->sample_rate));
if (IS_ERR_VALUE(trc)) {
pr_err("ERROR setting m clock1\n");
goto error_invalid_freq;
}
clk_prepare_enable(drv->tx_osrclk);
drv->tx_bitclk = clk_get_sys(NULL, "i2s_mic_bit_clk");
if (IS_ERR(drv->tx_bitclk))
pr_err("%s clock Error\n", __func__);
/* Master clock = Sample Rate * OSR rate bit clock
* OSR Rate bit clock = bit/sample * channel master
* clock / bit clock = divider value = 8
*/
if (msm_codec_i2s_slave_mode) {
pr_info("%s: configuring bit clock for slave mode\n",
__func__);
trc = clk_set_rate(drv->tx_bitclk, 0);
} else
trc = clk_set_rate(drv->tx_bitclk, 8);
clk_prepare_enable(drv->tx_bitclk);
/* Enable ADIE */
trc = adie_codec_open(icodec->data->profile, &icodec->adie_path);
if (IS_ERR_VALUE(trc))
pr_err("%s: adie codec open failed\n", __func__);
else
adie_codec_setpath(icodec->adie_path,
icodec->sample_rate, 256);
switch (icodec->data->channel_mode) {
case 2:
afe_channel_mode = MSM_AFE_STEREO;
break;
case 1:
default:
afe_channel_mode = MSM_AFE_MONO;
break;
}
afe_config.mi2s.channel = afe_channel_mode;
afe_config.mi2s.bitwidth = 16;
afe_config.mi2s.line = 1;
afe_config.mi2s.format = MSM_AFE_I2S_FORMAT_LPCM;
if (msm_codec_i2s_slave_mode)
afe_config.mi2s.ws = 0;
else
afe_config.mi2s.ws = 1;
trc = afe_open(icodec->data->copp_id, &afe_config, icodec->sample_rate);
if (icodec->adie_path) {
adie_codec_proceed_stage(icodec->adie_path,
ADIE_CODEC_DIGITAL_READY);
adie_codec_proceed_stage(icodec->adie_path,
ADIE_CODEC_DIGITAL_ANALOG_READY);
}
if (msm_codec_i2s_slave_mode)
adie_codec_set_master_mode(icodec->adie_path, 1);
else
adie_codec_set_master_mode(icodec->adie_path, 0);
icodec->enabled = 1;
pm_qos_update_request(&drv->tx_pm_qos_req, PM_QOS_DEFAULT_VALUE);
return 0;
error_invalid_freq:
if (icodec->data->pamp_off)
icodec->data->pamp_off();
pr_err("%s: encounter error\n", __func__);
error_pamp:
pm_qos_update_request(&drv->tx_pm_qos_req, PM_QOS_DEFAULT_VALUE);
return -ENODEV;
}
static int snddev_icodec_close_lb(struct snddev_icodec_state *icodec)
{
struct snddev_icodec_drv_state *drv = &snddev_icodec_drv;
/* Disable power amplifier */
if (icodec->data->pamp_off)
icodec->data->pamp_off();
if (drv->snddev_vreg)
vreg_mode_vote(drv->snddev_vreg, 0, SNDDEV_LOW_POWER_MODE);
if (icodec->adie_path) {
adie_codec_proceed_stage(icodec->adie_path,
ADIE_CODEC_DIGITAL_OFF);
adie_codec_close(icodec->adie_path);
icodec->adie_path = NULL;
}
if (icodec->data->voltage_off)
icodec->data->voltage_off();
return 0;
}
static int snddev_icodec_close_rx(struct snddev_icodec_state *icodec)
{
struct snddev_icodec_drv_state *drv = &snddev_icodec_drv;
pm_qos_update_request(&drv->rx_pm_qos_req,
msm_cpuidle_get_deep_idle_latency());
if (drv->snddev_vreg)
vreg_mode_vote(drv->snddev_vreg, 0, SNDDEV_HIGH_POWER_MODE);
/* Disable power amplifier */
if (icodec->data->pamp_off)
icodec->data->pamp_off();
/* Disable ADIE */
if (icodec->adie_path) {
adie_codec_proceed_stage(icodec->adie_path,
ADIE_CODEC_DIGITAL_OFF);
adie_codec_close(icodec->adie_path);
icodec->adie_path = NULL;
}
afe_close(icodec->data->copp_id);
if (icodec->data->voltage_off)
icodec->data->voltage_off();
clk_disable_unprepare(drv->rx_bitclk);
clk_disable_unprepare(drv->rx_osrclk);
msm_snddev_rx_mclk_free();
icodec->enabled = 0;
pm_qos_update_request(&drv->rx_pm_qos_req, PM_QOS_DEFAULT_VALUE);
return 0;
}
static int snddev_icodec_close_tx(struct snddev_icodec_state *icodec)
{
struct snddev_icodec_drv_state *drv = &snddev_icodec_drv;
pm_qos_update_request(&drv->tx_pm_qos_req,
msm_cpuidle_get_deep_idle_latency());
if (drv->snddev_vreg)
vreg_mode_vote(drv->snddev_vreg, 0, SNDDEV_HIGH_POWER_MODE);
/* Disable ADIE */
if (icodec->adie_path) {
adie_codec_proceed_stage(icodec->adie_path,
ADIE_CODEC_DIGITAL_OFF);
adie_codec_close(icodec->adie_path);
icodec->adie_path = NULL;
}
afe_close(icodec->data->copp_id);
clk_disable_unprepare(drv->tx_bitclk);
clk_disable_unprepare(drv->tx_osrclk);
msm_snddev_tx_mclk_free();
/* Reuse pamp_off for TX platform-specific setup */
if (icodec->data->pamp_off)
icodec->data->pamp_off();
icodec->enabled = 0;
pm_qos_update_request(&drv->tx_pm_qos_req, PM_QOS_DEFAULT_VALUE);
return 0;
}
static int snddev_icodec_set_device_volume_impl(
struct msm_snddev_info *dev_info, u32 volume)
{
struct snddev_icodec_state *icodec;
int rc = 0;
icodec = dev_info->private_data;
if (icodec->data->dev_vol_type & SNDDEV_DEV_VOL_DIGITAL) {
rc = adie_codec_set_device_digital_volume(icodec->adie_path,
icodec->data->channel_mode, volume);
if (rc < 0) {
pr_err("%s: unable to set_device_digital_volume for"
"%s volume in percentage = %u\n",
__func__, dev_info->name, volume);
return rc;
}
} else if (icodec->data->dev_vol_type & SNDDEV_DEV_VOL_ANALOG) {
rc = adie_codec_set_device_analog_volume(icodec->adie_path,
icodec->data->channel_mode, volume);
if (rc < 0) {
pr_err("%s: unable to set_device_analog_volume for"
"%s volume in percentage = %u\n",
__func__, dev_info->name, volume);
return rc;
}
} else {
pr_err("%s: Invalid device volume control\n", __func__);
return -EPERM;
}
return rc;
}
static int snddev_icodec_open(struct msm_snddev_info *dev_info)
{
int rc = 0;
struct snddev_icodec_state *icodec;
struct snddev_icodec_drv_state *drv = &snddev_icodec_drv;
if (!dev_info) {
rc = -EINVAL;
goto error;
}
icodec = dev_info->private_data;
if (icodec->data->capability & SNDDEV_CAP_RX) {
mutex_lock(&drv->rx_lock);
if (drv->rx_active) {
mutex_unlock(&drv->rx_lock);
pr_err("%s: rx_active is set, return EBUSY\n",
__func__);
rc = -EBUSY;
goto error;
}
rc = snddev_icodec_open_rx(icodec);
if (!IS_ERR_VALUE(rc)) {
if ((icodec->data->dev_vol_type & (
SNDDEV_DEV_VOL_DIGITAL |
SNDDEV_DEV_VOL_ANALOG)))
rc = snddev_icodec_set_device_volume_impl(
dev_info, dev_info->dev_volume);
if (!IS_ERR_VALUE(rc))
drv->rx_active = 1;
else
pr_err("%s: set_device_volume_impl"
" error(rx) = %d\n", __func__, rc);
}
mutex_unlock(&drv->rx_lock);
} else if (icodec->data->capability & SNDDEV_CAP_LB) {
mutex_lock(&drv->lb_lock);
rc = snddev_icodec_open_lb(icodec);
if (!IS_ERR_VALUE(rc)) {
if ((icodec->data->dev_vol_type & (
SNDDEV_DEV_VOL_DIGITAL |
SNDDEV_DEV_VOL_ANALOG)))
rc = snddev_icodec_set_device_volume_impl(
dev_info, dev_info->dev_volume);
}
mutex_unlock(&drv->lb_lock);
} else {
mutex_lock(&drv->tx_lock);
if (drv->tx_active) {
mutex_unlock(&drv->tx_lock);
pr_err("%s: tx_active is set, return EBUSY\n",
__func__);
rc = -EBUSY;
goto error;
}
rc = snddev_icodec_open_tx(icodec);
if (!IS_ERR_VALUE(rc)) {
if ((icodec->data->dev_vol_type & (
SNDDEV_DEV_VOL_DIGITAL |
SNDDEV_DEV_VOL_ANALOG)))
rc = snddev_icodec_set_device_volume_impl(
dev_info, dev_info->dev_volume);
if (!IS_ERR_VALUE(rc))
drv->tx_active = 1;
else
pr_err("%s: set_device_volume_impl"
" error(tx) = %d\n", __func__, rc);
}
mutex_unlock(&drv->tx_lock);
}
error:
return rc;
}
static int snddev_icodec_close(struct msm_snddev_info *dev_info)
{
int rc = 0;
struct snddev_icodec_state *icodec;
struct snddev_icodec_drv_state *drv = &snddev_icodec_drv;
if (!dev_info) {
rc = -EINVAL;
goto error;
}
icodec = dev_info->private_data;
if (icodec->data->capability & SNDDEV_CAP_RX) {
mutex_lock(&drv->rx_lock);
if (!drv->rx_active) {
mutex_unlock(&drv->rx_lock);
pr_err("%s: rx_active not set, return\n", __func__);
rc = -EPERM;
goto error;
}
rc = snddev_icodec_close_rx(icodec);
if (!IS_ERR_VALUE(rc))
drv->rx_active = 0;
else
pr_err("%s: close rx failed, rc = %d\n", __func__, rc);
mutex_unlock(&drv->rx_lock);
} else if (icodec->data->capability & SNDDEV_CAP_LB) {
mutex_lock(&drv->lb_lock);
rc = snddev_icodec_close_lb(icodec);
mutex_unlock(&drv->lb_lock);
} else {
mutex_lock(&drv->tx_lock);
if (!drv->tx_active) {
mutex_unlock(&drv->tx_lock);
pr_err("%s: tx_active not set, return\n", __func__);
rc = -EPERM;
goto error;
}
rc = snddev_icodec_close_tx(icodec);
if (!IS_ERR_VALUE(rc))
drv->tx_active = 0;
else
pr_err("%s: close tx failed, rc = %d\n", __func__, rc);
mutex_unlock(&drv->tx_lock);
}
error:
return rc;
}
static int snddev_icodec_check_freq(u32 req_freq)
{
int rc = -EINVAL;
if ((req_freq != 0) && (req_freq >= 8000) && (req_freq <= 48000)) {
if ((req_freq == 8000) || (req_freq == 11025) ||
(req_freq == 12000) || (req_freq == 16000) ||
(req_freq == 22050) || (req_freq == 24000) ||
(req_freq == 32000) || (req_freq == 44100) ||
(req_freq == 48000)) {
rc = 0;
} else
pr_info("%s: Unsupported Frequency:%d\n", __func__,
req_freq);
}
return rc;
}
static int snddev_icodec_set_freq(struct msm_snddev_info *dev_info, u32 rate)
{
int rc;
struct snddev_icodec_state *icodec;
if (!dev_info) {
rc = -EINVAL;
goto error;
}
icodec = dev_info->private_data;
if (adie_codec_freq_supported(icodec->data->profile, rate) != 0) {
pr_err("%s: adie_codec_freq_supported() failed\n", __func__);
rc = -EINVAL;
goto error;
} else {
if (snddev_icodec_check_freq(rate) != 0) {
pr_err("%s: check_freq failed\n", __func__);
rc = -EINVAL;
goto error;
} else
icodec->sample_rate = rate;
}
if (icodec->enabled) {
snddev_icodec_close(dev_info);
snddev_icodec_open(dev_info);
}
return icodec->sample_rate;
error:
return rc;
}
static int snddev_icodec_enable_sidetone(struct msm_snddev_info *dev_info,
u32 enable, uint16_t gain)
{
int rc = 0;
struct snddev_icodec_state *icodec;
struct snddev_icodec_drv_state *drv = &snddev_icodec_drv;
if (!dev_info) {
pr_err("invalid dev_info\n");
rc = -EINVAL;
goto error;
}
icodec = dev_info->private_data;
if (icodec->data->capability & SNDDEV_CAP_RX) {
mutex_lock(&drv->rx_lock);
if (!drv->rx_active || !dev_info->opened) {
pr_err("dev not active\n");
rc = -EPERM;
mutex_unlock(&drv->rx_lock);
goto error;
}
rc = afe_sidetone(PRIMARY_I2S_TX, PRIMARY_I2S_RX, enable, gain);
if (rc < 0)
pr_err("%s: AFE command sidetone failed\n", __func__);
mutex_unlock(&drv->rx_lock);
} else {
rc = -EINVAL;
pr_err("rx device only\n");
}
error:
return rc;
}
static int snddev_icodec_enable_anc(struct msm_snddev_info *dev_info,
u32 enable)
{
int rc = 0;
struct adie_codec_anc_data *reg_writes;
struct acdb_cal_block cal_block;
struct snddev_icodec_state *icodec;
struct snddev_icodec_drv_state *drv = &snddev_icodec_drv;
pr_info("%s: enable=%d\n", __func__, enable);
if (!dev_info) {
pr_err("invalid dev_info\n");
rc = -EINVAL;
goto error;
}
icodec = dev_info->private_data;
if ((icodec->data->capability & SNDDEV_CAP_RX) &&
(icodec->data->capability & SNDDEV_CAP_ANC)) {
mutex_lock(&drv->rx_lock);
if (!drv->rx_active || !dev_info->opened) {
pr_err("dev not active\n");
rc = -EPERM;
mutex_unlock(&drv->rx_lock);
goto error;
}
if (enable) {
get_anc_cal(&cal_block);
reg_writes = (struct adie_codec_anc_data *)
cal_block.cal_kvaddr;
if (reg_writes == NULL) {
pr_err("error, no calibration data\n");
rc = -1;
mutex_unlock(&drv->rx_lock);
goto error;
}
rc = adie_codec_enable_anc(icodec->adie_path,
1, reg_writes);
} else {
rc = adie_codec_enable_anc(icodec->adie_path,
0, NULL);
}
mutex_unlock(&drv->rx_lock);
} else {
rc = -EINVAL;
pr_err("rx and ANC device only\n");
}
error:
return rc;
}
int snddev_icodec_set_device_volume(struct msm_snddev_info *dev_info,
u32 volume)
{
struct snddev_icodec_state *icodec;
struct mutex *lock;
struct snddev_icodec_drv_state *drv = &snddev_icodec_drv;
int rc = -EPERM;
if (!dev_info) {
pr_info("%s : device not intilized.\n", __func__);
return -EINVAL;
}
icodec = dev_info->private_data;
if (!(icodec->data->dev_vol_type & (SNDDEV_DEV_VOL_DIGITAL
| SNDDEV_DEV_VOL_ANALOG))) {
pr_info("%s : device %s does not support device volume "
"control.", __func__, dev_info->name);
return -EPERM;
}
dev_info->dev_volume = volume;
if (icodec->data->capability & SNDDEV_CAP_RX)
lock = &drv->rx_lock;
else if (icodec->data->capability & SNDDEV_CAP_LB)
lock = &drv->lb_lock;
else
lock = &drv->tx_lock;
mutex_lock(lock);
rc = snddev_icodec_set_device_volume_impl(dev_info,
dev_info->dev_volume);
mutex_unlock(lock);
return rc;
}
static int snddev_icodec_probe(struct platform_device *pdev)
{
int rc = 0;
struct snddev_icodec_data *pdata;
struct msm_snddev_info *dev_info;
struct snddev_icodec_state *icodec;
if (!pdev || !pdev->dev.platform_data) {
printk(KERN_ALERT "Invalid caller\n");
rc = -1;
goto error;
}
pdata = pdev->dev.platform_data;
if ((pdata->capability & SNDDEV_CAP_RX) &&
(pdata->capability & SNDDEV_CAP_TX)) {
pr_err("%s: invalid device data either RX or TX\n", __func__);
goto error;
}
icodec = kzalloc(sizeof(struct snddev_icodec_state), GFP_KERNEL);
if (!icodec) {
rc = -ENOMEM;
goto error;
}
dev_info = kmalloc(sizeof(struct msm_snddev_info), GFP_KERNEL);
if (!dev_info) {
kfree(icodec);
rc = -ENOMEM;
goto error;
}
dev_info->name = pdata->name;
dev_info->copp_id = pdata->copp_id;
dev_info->private_data = (void *) icodec;
dev_info->dev_ops.open = snddev_icodec_open;
dev_info->dev_ops.close = snddev_icodec_close;
dev_info->dev_ops.set_freq = snddev_icodec_set_freq;
dev_info->dev_ops.set_device_volume = snddev_icodec_set_device_volume;
dev_info->capability = pdata->capability;
dev_info->opened = 0;
msm_snddev_register(dev_info);
icodec->data = pdata;
icodec->sample_rate = pdata->default_sample_rate;
dev_info->sample_rate = pdata->default_sample_rate;
dev_info->channel_mode = pdata->channel_mode;
if (pdata->capability & SNDDEV_CAP_RX)
dev_info->dev_ops.enable_sidetone =
snddev_icodec_enable_sidetone;
else
dev_info->dev_ops.enable_sidetone = NULL;
if (pdata->capability & SNDDEV_CAP_ANC) {
dev_info->dev_ops.enable_anc =
snddev_icodec_enable_anc;
} else {
dev_info->dev_ops.enable_anc = NULL;
}
error:
return rc;
}
static int snddev_icodec_remove(struct platform_device *pdev)
{
return 0;
}
static struct platform_driver snddev_icodec_driver = {
.probe = snddev_icodec_probe,
.remove = snddev_icodec_remove,
.driver = { .name = "snddev_icodec" }
};
module_param(msm_codec_i2s_slave_mode, bool, 0);
MODULE_PARM_DESC(msm_codec_i2s_slave_mode, "Set MSM to I2S slave clock mode");
static int __init snddev_icodec_init(void)
{
s32 rc;
struct snddev_icodec_drv_state *icodec_drv = &snddev_icodec_drv;
rc = platform_driver_register(&snddev_icodec_driver);
if (IS_ERR_VALUE(rc)) {
pr_err("%s: platform_driver_register for snddev icodec failed\n",
__func__);
goto error_snddev_icodec_driver;
}
rc = platform_driver_register(&msm_cdcclk_ctl_driver);
if (IS_ERR_VALUE(rc)) {
pr_err("%s: platform_driver_register for msm snddev failed\n",
__func__);
goto error_msm_cdcclk_ctl_driver;
}
rc = platform_driver_register(&msm_icodec_gpio_driver);
if (IS_ERR_VALUE(rc)) {
pr_err("%s: platform_driver_register for msm snddev gpio failed\n",
__func__);
goto error_msm_icodec_gpio_driver;
}
mutex_init(&icodec_drv->rx_lock);
mutex_init(&icodec_drv->lb_lock);
mutex_init(&icodec_drv->tx_lock);
icodec_drv->rx_active = 0;
icodec_drv->tx_active = 0;
icodec_drv->snddev_vreg = vreg_init();
pm_qos_add_request(&icodec_drv->tx_pm_qos_req, PM_QOS_CPU_DMA_LATENCY,
PM_QOS_DEFAULT_VALUE);
pm_qos_add_request(&icodec_drv->rx_pm_qos_req, PM_QOS_CPU_DMA_LATENCY,
PM_QOS_DEFAULT_VALUE);
return 0;
error_msm_icodec_gpio_driver:
platform_driver_unregister(&msm_cdcclk_ctl_driver);
error_msm_cdcclk_ctl_driver:
platform_driver_unregister(&snddev_icodec_driver);
error_snddev_icodec_driver:
return -ENODEV;
}
static void __exit snddev_icodec_exit(void)
{
struct snddev_icodec_drv_state *icodec_drv = &snddev_icodec_drv;
platform_driver_unregister(&snddev_icodec_driver);
platform_driver_unregister(&msm_cdcclk_ctl_driver);
platform_driver_unregister(&msm_icodec_gpio_driver);
clk_put(icodec_drv->rx_osrclk);
clk_put(icodec_drv->tx_osrclk);
if (icodec_drv->snddev_vreg) {
vreg_deinit(icodec_drv->snddev_vreg);
icodec_drv->snddev_vreg = NULL;
}
return;
}
module_init(snddev_icodec_init);
module_exit(snddev_icodec_exit);
MODULE_DESCRIPTION("ICodec Sound Device driver");
MODULE_VERSION("1.0");
MODULE_LICENSE("GPL v2");