blob: 06318bc7f78aff4cb8ca2e5cab53ebbb074844bc [file] [log] [blame]
/* Copyright (c) 2009-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 <linux/module.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/err.h>
#include <linux/gpio.h>
#include <mach/qdsp5v2/aux_pcm.h>
#include <linux/delay.h>
#include <mach/debug_mm.h>
/*----------------------------------------------------------------------------
* Preprocessor Definitions and Constants
* -------------------------------------------------------------------------*/
/* define offset of registers here, may put them into platform data */
#define AUX_CODEC_CTL_OFFSET 0x00
#define PCM_PATH_CTL_OFFSET 0x04
#define AUX_CODEC_CTL_OUT_OFFSET 0x08
/* define some bit values in PCM_PATH_CTL register */
#define PCM_PATH_CTL__ADSP_CTL_EN_BMSK 0x8
/* mask and shift */
#define AUX_CODEC_CTL_ADSP_CODEC_CTL_EN_BMSK 0x800
#define AUX_CODEC_CTL_PCM_SYNC_LONG_BMSK 0x400
#define AUX_CODEC_CTL_PCM_SYNC_SHORT_BMSK 0x200
#define AUX_CODEC_CTL_I2S_SAMPLE_CLK_SRC_BMSK 0x80
#define AUX_CODEC_CTL_I2S_SAMPLE_CLK_MODE_BMSK 0x40
#define AUX_CODEC_CTL_I2S_RX_MODE_BMSK 0x20
#define AUX_CODEC_CTL_I2S_CLK_MODE_BMSK 0x10
#define AUX_CODEC_CTL_AUX_PCM_MODE_BMSK 0x0b
#define AUX_CODEC_CTL_AUX_CODEC_MODE_BMSK 0x02
/* AUX PCM MODE */
#define MASTER_PRIM_PCM_SHORT 0
#define MASTER_AUX_PCM_LONG 1
#define SLAVE_PRIM_PCM_SHORT 2
struct aux_pcm_state {
void __iomem *aux_pcm_base; /* configure aux pcm through Scorpion */
int dout;
int din;
int syncout;
int clkin_a;
};
static struct aux_pcm_state the_aux_pcm_state;
static void __iomem *get_base_addr(struct aux_pcm_state *aux_pcm)
{
return aux_pcm->aux_pcm_base;
}
/* Set who control aux pcm : adsp or MSM */
void aux_codec_adsp_codec_ctl_en(bool msm_adsp_en)
{
void __iomem *baddr = get_base_addr(&the_aux_pcm_state);
uint32_t val;
if (!IS_ERR(baddr)) {
val = readl(baddr + AUX_CODEC_CTL_OFFSET);
if (msm_adsp_en) { /* adsp */
writel(
((val & ~AUX_CODEC_CTL_ADSP_CODEC_CTL_EN_BMSK) |
AUX_CODEC_CTL__ADSP_CODEC_CTL_EN__ADSP_V),
baddr + AUX_CODEC_CTL_OFFSET);
} else { /* MSM */
writel(
((val & ~AUX_CODEC_CTL_ADSP_CODEC_CTL_EN_BMSK) |
AUX_CODEC_CTL__ADSP_CODEC_CTL_EN__MSM_V),
baddr + AUX_CODEC_CTL_OFFSET);
}
}
mb();
}
/* Set who control aux pcm path: adsp or MSM */
void aux_codec_pcm_path_ctl_en(bool msm_adsp_en)
{
void __iomem *baddr = get_base_addr(&the_aux_pcm_state);
uint32_t val;
if (!IS_ERR(baddr)) {
val = readl(baddr + PCM_PATH_CTL_OFFSET);
if (msm_adsp_en) { /* adsp */
writel(
((val & ~PCM_PATH_CTL__ADSP_CTL_EN_BMSK) |
PCM_PATH_CTL__ADSP_CTL_EN__ADSP_V),
baddr + PCM_PATH_CTL_OFFSET);
} else { /* MSM */
writel(
((val & ~PCM_PATH_CTL__ADSP_CTL_EN_BMSK) |
PCM_PATH_CTL__ADSP_CTL_EN__MSM_V),
baddr + PCM_PATH_CTL_OFFSET);
}
}
mb();
return;
}
EXPORT_SYMBOL(aux_codec_pcm_path_ctl_en);
int aux_pcm_gpios_request(void)
{
int rc = 0;
MM_DBG("aux_pcm_gpios_request\n");
rc = gpio_request(the_aux_pcm_state.dout, "AUX PCM DOUT");
if (rc) {
MM_ERR("GPIO request for AUX PCM DOUT failed\n");
return rc;
}
rc = gpio_request(the_aux_pcm_state.din, "AUX PCM DIN");
if (rc) {
MM_ERR("GPIO request for AUX PCM DIN failed\n");
gpio_free(the_aux_pcm_state.dout);
return rc;
}
rc = gpio_request(the_aux_pcm_state.syncout, "AUX PCM SYNC OUT");
if (rc) {
MM_ERR("GPIO request for AUX PCM SYNC OUT failed\n");
gpio_free(the_aux_pcm_state.dout);
gpio_free(the_aux_pcm_state.din);
return rc;
}
rc = gpio_request(the_aux_pcm_state.clkin_a, "AUX PCM CLKIN A");
if (rc) {
MM_ERR("GPIO request for AUX PCM CLKIN A failed\n");
gpio_free(the_aux_pcm_state.dout);
gpio_free(the_aux_pcm_state.din);
gpio_free(the_aux_pcm_state.syncout);
return rc;
}
return rc;
}
EXPORT_SYMBOL(aux_pcm_gpios_request);
void aux_pcm_gpios_free(void)
{
MM_DBG(" aux_pcm_gpios_free \n");
/*
* Feed silence frames before close to prevent buzzing sound in BT at
* call end. This fix is applicable only to Marimba BT.
*/
gpio_tlmm_config(GPIO_CFG(the_aux_pcm_state.dout, 0, GPIO_CFG_OUTPUT,
GPIO_CFG_NO_PULL, GPIO_CFG_2MA), GPIO_CFG_ENABLE);
gpio_set_value(the_aux_pcm_state.dout, 0);
msleep(20);
gpio_tlmm_config(GPIO_CFG(the_aux_pcm_state.dout, 1, GPIO_CFG_OUTPUT,
GPIO_CFG_NO_PULL, GPIO_CFG_2MA), GPIO_CFG_ENABLE);
gpio_free(the_aux_pcm_state.dout);
gpio_free(the_aux_pcm_state.din);
gpio_free(the_aux_pcm_state.syncout);
gpio_free(the_aux_pcm_state.clkin_a);
}
EXPORT_SYMBOL(aux_pcm_gpios_free);
static int get_aux_pcm_gpios(struct platform_device *pdev)
{
int rc = 0;
struct resource *res;
/* Claim all of the GPIOs. */
res = platform_get_resource_byname(pdev, IORESOURCE_IO,
"aux_pcm_dout");
if (!res) {
MM_ERR("%s: failed to get gpio AUX PCM DOUT\n", __func__);
return -ENODEV;
}
the_aux_pcm_state.dout = res->start;
res = platform_get_resource_byname(pdev, IORESOURCE_IO,
"aux_pcm_din");
if (!res) {
MM_ERR("%s: failed to get gpio AUX PCM DIN\n", __func__);
return -ENODEV;
}
the_aux_pcm_state.din = res->start;
res = platform_get_resource_byname(pdev, IORESOURCE_IO,
"aux_pcm_syncout");
if (!res) {
MM_ERR("%s: failed to get gpio AUX PCM SYNC OUT\n", __func__);
return -ENODEV;
}
the_aux_pcm_state.syncout = res->start;
res = platform_get_resource_byname(pdev, IORESOURCE_IO,
"aux_pcm_clkin_a");
if (!res) {
MM_ERR("%s: failed to get gpio AUX PCM CLKIN A\n", __func__);
return -ENODEV;
}
the_aux_pcm_state.clkin_a = res->start;
return rc;
}
static int aux_pcm_probe(struct platform_device *pdev)
{
int rc = 0;
struct resource *mem_src;
MM_DBG("aux_pcm_probe \n");
mem_src = platform_get_resource_byname(pdev, IORESOURCE_MEM,
"aux_codec_reg_addr");
if (!mem_src) {
rc = -ENODEV;
goto done;
}
the_aux_pcm_state.aux_pcm_base = ioremap(mem_src->start,
(mem_src->end - mem_src->start) + 1);
if (!the_aux_pcm_state.aux_pcm_base) {
rc = -ENOMEM;
goto done;
}
rc = get_aux_pcm_gpios(pdev);
if (rc) {
MM_ERR("GPIO configuration failed\n");
rc = -ENODEV;
}
done: return rc;
}
static int aux_pcm_remove(struct platform_device *pdev)
{
iounmap(the_aux_pcm_state.aux_pcm_base);
return 0;
}
static struct platform_driver aux_pcm_driver = {
.probe = aux_pcm_probe,
.remove = aux_pcm_remove,
.driver = {
.name = "msm_aux_pcm",
.owner = THIS_MODULE,
},
};
static int __init aux_pcm_init(void)
{
return platform_driver_register(&aux_pcm_driver);
}
static void __exit aux_pcm_exit(void)
{
platform_driver_unregister(&aux_pcm_driver);
}
module_init(aux_pcm_init);
module_exit(aux_pcm_exit);
MODULE_DESCRIPTION("MSM AUX PCM driver");
MODULE_LICENSE("GPL v2");