1122 lines
28 KiB
C
1122 lines
28 KiB
C
/* 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");
|