blob: c4e0feeaa0a5ed8ae4afb5e9464c04e73b0977b4 [file] [log] [blame]
/* Copyright (c) 2009-2011, Code Aurora Forum. 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/delay.h>
#include <linux/clk.h>
#include <mach/qdsp5v2/lpa.h>
#include <mach/qdsp5v2/lpa_hw.h>
#include <mach/qdsp5v2/msm_lpa.h>
#include <mach/debug_mm.h>
#define LPA_REG_WRITEL(drv, val, reg) writel(val, drv->baseaddr + reg)
#define LPA_REG_READL(drv, reg) readl(drv->baseaddr + reg)
/* bit 2:0 is reserved because watermarks have to be 64-bit aligned */
#define LLB_WATERMARK_VAL_MASK 0x00000003
#define LPA_STATUS_SBUF_EN 0x01
struct lpa_drv {
void __iomem *baseaddr;
u32 obuf_hlb_size;
u32 dsp_proc_id;
u32 app_proc_id;
struct lpa_mem_config nosb_config;
struct lpa_mem_config sb_config;
u32 status;
u32 watermark_bytes;
u32 watermark_aheadtime;
u32 sample_boundary;
};
struct lpa_state {
struct lpa_drv lpa_drv; /* One instance for now */
u32 assigned;
struct mutex lpa_lock;
};
struct lpa_state the_lpa_state;
static void lpa_enable_codec(struct lpa_drv *lpa, bool enable)
{
u32 val;
val = LPA_REG_READL(lpa, LPA_OBUF_CODEC);
val = enable ? (val | LPA_OBUF_CODEC_CODEC_INTF_EN_BMSK) :
(val & ~LPA_OBUF_CODEC_CODEC_INTF_EN_BMSK);
val |= LPA_OBUF_CODEC_LOAD_BMSK;
LPA_REG_WRITEL(lpa, val, LPA_OBUF_CODEC);
mb();
}
static void lpa_reset(struct lpa_drv *lpa)
{
u32 status;
struct clk *adsp_clk;
/* Need to make sure not disable clock while other device is enabled */
adsp_clk = clk_get(NULL, "adsp_clk");
if (!adsp_clk) {
MM_ERR("failed to get adsp clk\n");
goto error;
}
clk_enable(adsp_clk);
lpa_enable_codec(lpa, 0);
LPA_REG_WRITEL(lpa, (LPA_OBUF_RESETS_MISR_RESET |
LPA_OBUF_RESETS_OVERALL_RESET), LPA_OBUF_RESETS);
do {
status = LPA_REG_READL(lpa, LPA_OBUF_STATUS);
} while (!(status & LPA_OBUF_STATUS_RESET_DONE));
LPA_REG_WRITEL(lpa, LPA_OBUF_ACK_RESET_DONE_BMSK, LPA_OBUF_ACK);
mb();
clk_disable(adsp_clk);
clk_put(adsp_clk);
error:
return;
}
static void lpa_config_hlb_addr(struct lpa_drv *lpa)
{
u32 val, min_addr = 0, max_addr = min_addr + lpa->obuf_hlb_size;
val = (min_addr & LPA_OBUF_HLB_MIN_ADDR_SEG_BMSK) |
LPA_OBUF_HLB_MIN_ADDR_LOAD_BMSK;
LPA_REG_WRITEL(lpa, val, LPA_OBUF_HLB_MIN_ADDR);
val = max_addr & LPA_OBUF_HLB_MAX_ADDR_SEG_BMSK;
LPA_REG_WRITEL(lpa, val, LPA_OBUF_HLB_MAX_ADDR);
}
static void lpa_powerup_mem_bank(struct lpa_drv *lpa,
struct lpa_mem_bank_select *bank)
{
u32 status, val;
status = LPA_REG_READL(lpa, LPA_OBUF_MEMORY_CONTROL);
val = ((*((u32 *) bank)) << LPA_OBUF_MEM_CTL_PWRUP_SHFT) &
LPA_OBUF_MEM_CTL_PWRUP_BMSK;
val |= status;
LPA_REG_WRITEL(lpa, val, LPA_OBUF_MEMORY_CONTROL);
}
static void lpa_enable_interrupt(struct lpa_drv *lpa, u32 proc_id)
{
u32 val;
proc_id &= LPA_OBUF_INTR_EN_BMSK;
val = 0x1 << proc_id;
LPA_REG_WRITEL(lpa, val, LPA_OBUF_INTR_ENABLE);
}
static void lpa_config_llb_addr(struct lpa_drv *lpa, u32 min_addr, u32 max_addr)
{
u32 val;
val = (min_addr & LPA_OBUF_LLB_MIN_ADDR_SEG_BMSK) |
LPA_OBUF_LLB_MIN_ADDR_LOAD_BMSK;
LPA_REG_WRITEL(lpa, val, LPA_OBUF_LLB_MIN_ADDR);
val = max_addr & LPA_OBUF_LLB_MAX_ADDR_SEG_BMSK;
LPA_REG_WRITEL(lpa, val, LPA_OBUF_LLB_MAX_ADDR);
}
static void lpa_config_sb_addr(struct lpa_drv *lpa, u32 min_addr, u32 max_addr)
{
u32 val;
val = (min_addr & LPA_OBUF_SB_MIN_ADDR_SEG_BMSK) |
LPA_OBUF_SB_MIN_ADDR_LOAD_BMSK;
LPA_REG_WRITEL(lpa, val, LPA_OBUF_SB_MIN_ADDR);
val = max_addr & LPA_OBUF_SB_MAX_ADDR_SEG_BMSK;
LPA_REG_WRITEL(lpa, val, LPA_OBUF_SB_MAX_ADDR);
}
static void lpa_switch_sb(struct lpa_drv *lpa)
{
if (lpa->status & LPA_STATUS_SBUF_EN) {
lpa_config_llb_addr(lpa, lpa->sb_config.llb_min_addr,
lpa->sb_config.llb_max_addr);
lpa_config_sb_addr(lpa, lpa->sb_config.sb_min_addr,
lpa->sb_config.sb_max_addr);
} else {
lpa_config_llb_addr(lpa, lpa->nosb_config.llb_min_addr,
lpa->nosb_config.llb_max_addr);
lpa_config_sb_addr(lpa, lpa->nosb_config.sb_min_addr,
lpa->nosb_config.sb_max_addr);
}
}
static u8 lpa_req_wmark_id(struct lpa_drv *lpa)
{
return (u8) (LPA_REG_READL(lpa, LPA_OBUF_WMARK_ASSIGN) &
LPA_OBUF_WMARK_ASSIGN_BMSK);
}
static void lpa_enable_llb_wmark(struct lpa_drv *lpa, u32 wmark_ctrl,
u32 wmark_id, u32 cpu_id)
{
u32 val;
wmark_id = (wmark_id > 3) ? 0 : wmark_id;
val = LPA_REG_READL(lpa, LPA_OBUF_WMARK_n_LLB_ADDR(wmark_id));
val &= ~LPA_OBUF_LLB_WMARK_CTRL_BMSK;
val &= ~LPA_OBUF_LLB_WMARK_MAP_BMSK;
val |= (wmark_ctrl << LPA_OBUF_LLB_WMARK_CTRL_SHFT) &
LPA_OBUF_LLB_WMARK_CTRL_BMSK;
val |= (cpu_id << LPA_OBUF_LLB_WMARK_MAP_SHFT) &
LPA_OBUF_LLB_WMARK_MAP_BMSK;
LPA_REG_WRITEL(lpa, val, LPA_OBUF_WMARK_n_LLB_ADDR(wmark_id));
}
static void lpa_enable_sb_wmark(struct lpa_drv *lpa, u32 wmark_ctrl,
u32 cpu_id)
{
u32 val;
val = LPA_REG_READL(lpa, LPA_OBUF_WMARK_SB);
val &= ~LPA_OBUF_SB_WMARK_CTRL_BMSK;
val &= ~LPA_OBUF_SB_WMARK_MAP_BMSK;
val |= (wmark_ctrl << LPA_OBUF_SB_WMARK_CTRL_SHFT) &
LPA_OBUF_SB_WMARK_CTRL_BMSK;
val |= (cpu_id << LPA_OBUF_SB_WMARK_MAP_SHFT) &
LPA_OBUF_SB_WMARK_MAP_BMSK;
LPA_REG_WRITEL(lpa, val, LPA_OBUF_WMARK_SB);
}
static void lpa_enable_hlb_wmark(struct lpa_drv *lpa, u32 wmark_ctrl,
u32 cpu_id)
{
u32 val;
val = LPA_REG_READL(lpa, LPA_OBUF_WMARK_HLB);
val &= ~LPA_OBUF_HLB_WMARK_CTRL_BMSK;
val &= ~LPA_OBUF_HLB_WMARK_MAP_BMSK;
val |= (wmark_ctrl << LPA_OBUF_HLB_WMARK_CTRL_SHFT) &
LPA_OBUF_HLB_WMARK_CTRL_BMSK;
val |= (cpu_id << LPA_OBUF_HLB_WMARK_MAP_SHFT) &
LPA_OBUF_HLB_WMARK_MAP_BMSK;
LPA_REG_WRITEL(lpa, val, LPA_OBUF_WMARK_HLB);
}
static void lpa_enable_utc(struct lpa_drv *lpa, bool enable, u32 cpu_id)
{
u32 val;
val = (cpu_id << LPA_OBUF_UTC_CONFIG_MAP_SHFT) &
LPA_OBUF_UTC_CONFIG_MAP_BMSK;
enable = (enable ? 1 : 0);
val = (enable << LPA_OBUF_UTC_CONFIG_EN_SHFT) &
LPA_OBUF_UTC_CONFIG_EN_BMSK;
LPA_REG_WRITEL(lpa, val, LPA_OBUF_UTC_CONFIG);
}
static void lpa_enable_mixing(struct lpa_drv *lpa, bool enable)
{
u32 val;
val = LPA_REG_READL(lpa, LPA_OBUF_CONTROL);
val = (enable ? val | LPA_OBUF_CONTROL_LLB_EN_BMSK :
val & ~LPA_OBUF_CONTROL_LLB_EN_BMSK);
LPA_REG_WRITEL(lpa, val, LPA_OBUF_CONTROL);
}
static void lpa_enable_mixer_saturation(struct lpa_drv *lpa, u32 buf_id,
bool enable)
{
u32 val;
val = LPA_REG_READL(lpa, LPA_OBUF_CONTROL);
switch (buf_id) {
case LPA_BUF_ID_LLB:
val = enable ? (val | LPA_OBUF_CONTROL_LLB_SAT_EN_BMSK) :
(val & ~LPA_OBUF_CONTROL_LLB_SAT_EN_BMSK);
break;
case LPA_BUF_ID_SB:
val = enable ? (val | LPA_OBUF_CONTROL_SB_SAT_EN_BMSK) :
(val & ~LPA_OBUF_CONTROL_SB_SAT_EN_BMSK);
break;
}
LPA_REG_WRITEL(lpa, val, LPA_OBUF_CONTROL);
}
static void lpa_enable_obuf(struct lpa_drv *lpa, u32 buf_id, bool enable)
{
u32 val;
val = LPA_REG_READL(lpa, LPA_OBUF_CONTROL);
switch (buf_id) {
case LPA_BUF_ID_HLB:
val = enable ? (val | LPA_OBUF_CONTROL_HLB_EN_BMSK) :
(val & ~LPA_OBUF_CONTROL_HLB_EN_BMSK);
break;
case LPA_BUF_ID_LLB:
val = enable ? (val | LPA_OBUF_CONTROL_LLB_EN_BMSK) :
(val & ~LPA_OBUF_CONTROL_LLB_EN_BMSK);
break;
case LPA_BUF_ID_SB:
val = enable ? (val | LPA_OBUF_CONTROL_SB_EN_BMSK) :
(val & ~LPA_OBUF_CONTROL_SB_EN_BMSK);
break;
}
LPA_REG_WRITEL(lpa, val, LPA_OBUF_CONTROL);
}
struct lpa_drv *lpa_get(void)
{
struct lpa_mem_bank_select mem_bank;
struct lpa_drv *ret_lpa = &the_lpa_state.lpa_drv;
mutex_lock(&the_lpa_state.lpa_lock);
if (the_lpa_state.assigned) {
MM_ERR("LPA HW accupied\n");
ret_lpa = ERR_PTR(-EBUSY);
goto error;
}
/* perform initialization */
lpa_reset(ret_lpa);
/* Config adec param */
/* Initialize LLB/SB min/max address */
lpa_switch_sb(ret_lpa);
/* Config HLB minx/max address */
lpa_config_hlb_addr(ret_lpa);
/* Power up all memory bank for now */
mem_bank.b0 = 1;
mem_bank.b1 = 1;
mem_bank.b2 = 1;
mem_bank.b3 = 1;
mem_bank.b4 = 1;
mem_bank.b5 = 1;
mem_bank.b6 = 1;
mem_bank.b7 = 1;
mem_bank.b8 = 1;
mem_bank.b9 = 1;
mem_bank.b10 = 1;
mem_bank.llb = 1;
lpa_powerup_mem_bank(ret_lpa, &mem_bank);
while
(lpa_req_wmark_id(ret_lpa) != LPA_OBUF_WMARK_ASSIGN_DONE);
lpa_enable_llb_wmark(ret_lpa, LPA_WMARK_CTL_DISABLED, 0,
ret_lpa->dsp_proc_id);
lpa_enable_llb_wmark(ret_lpa, LPA_WMARK_CTL_DISABLED, 1,
ret_lpa->dsp_proc_id);
lpa_enable_llb_wmark(ret_lpa, LPA_WMARK_CTL_DISABLED, 2,
ret_lpa->app_proc_id);
lpa_enable_llb_wmark(ret_lpa, LPA_WMARK_CTL_DISABLED, 3,
ret_lpa->app_proc_id);
lpa_enable_hlb_wmark(ret_lpa, LPA_WMARK_CTL_DISABLED,
ret_lpa->dsp_proc_id);
lpa_enable_sb_wmark(ret_lpa, LPA_WMARK_CTL_DISABLED,
ret_lpa->dsp_proc_id);
lpa_enable_utc(ret_lpa, 0, LPA_OBUF_UTC_CONFIG_NO_INTR);
lpa_enable_mixing(ret_lpa, 1);
lpa_enable_mixer_saturation(ret_lpa, LPA_BUF_ID_LLB, 1);
lpa_enable_obuf(ret_lpa, LPA_BUF_ID_HLB, 0);
lpa_enable_obuf(ret_lpa, LPA_BUF_ID_LLB, 1);
if (ret_lpa->status & LPA_STATUS_SBUF_EN) {
lpa_enable_mixer_saturation(ret_lpa, LPA_BUF_ID_SB, 1);
lpa_enable_obuf(ret_lpa, LPA_BUF_ID_SB, 1);
}
lpa_enable_interrupt(ret_lpa, ret_lpa->dsp_proc_id);
mb();
the_lpa_state.assigned++;
error:
mutex_unlock(&the_lpa_state.lpa_lock);
return ret_lpa;
}
EXPORT_SYMBOL(lpa_get);
void lpa_put(struct lpa_drv *lpa)
{
mutex_lock(&the_lpa_state.lpa_lock);
if (!lpa || &the_lpa_state.lpa_drv != lpa) {
MM_ERR("invalid arg\n");
goto error;
}
/* Deinitialize */
the_lpa_state.assigned--;
error:
mutex_unlock(&the_lpa_state.lpa_lock);
}
EXPORT_SYMBOL(lpa_put);
int lpa_cmd_codec_config(struct lpa_drv *lpa,
struct lpa_codec_config *config_ptr)
{
u32 sample_rate;
u32 num_channels;
u32 width;
u32 val = 0;
if (!lpa || !config_ptr) {
MM_ERR("invalid parameters\n");
return -EINVAL;
}
switch (config_ptr->num_channels) {
case 8:
num_channels = LPA_NUM_CHAN_7P1;
break;
case 6:
num_channels = LPA_NUM_CHAN_5P1;
break;
case 4:
num_channels = LPA_NUM_CHAN_4_CHANNEL;
break;
case 2:
num_channels = LPA_NUM_CHAN_STEREO;
break;
case 1:
num_channels = LPA_NUM_CHAN_MONO;
break;
default:
MM_ERR("unsupported number of channel\n");
goto error;
}
val |= (num_channels << LPA_OBUF_CODEC_NUM_CHAN_SHFT) &
LPA_OBUF_CODEC_NUM_CHAN_BMSK;
switch (config_ptr->sample_rate) {
case 96000:
sample_rate = LPA_SAMPLE_RATE_96KHZ;
break;
case 64000:
sample_rate = LPA_SAMPLE_RATE_64KHZ;
break;
case 48000:
sample_rate = LPA_SAMPLE_RATE_48KHZ;
break;
case 44100:
sample_rate = LPA_SAMPLE_RATE_44P1KHZ;
break;
case 32000:
sample_rate = LPA_SAMPLE_RATE_32KHZ;
break;
case 22050:
sample_rate = LPA_SAMPLE_RATE_22P05KHZ;
break;
case 16000:
sample_rate = LPA_SAMPLE_RATE_16KHZ;
break;
case 11025:
sample_rate = LPA_SAMPLE_RATE_11P025KHZ;
break;
case 8000:
sample_rate = LPA_SAMPLE_RATE_8KHZ;
break;
default:
MM_ERR("unsupported sample rate \n");
goto error;
}
val |= (sample_rate << LPA_OBUF_CODEC_SAMP_SHFT) &
LPA_OBUF_CODEC_SAMP_BMSK;
switch (config_ptr->sample_width) {
case 32:
width = LPA_BITS_PER_CHAN_32BITS;
break;
case 24:
width = LPA_BITS_PER_CHAN_24BITS;
break;
case 16:
width = LPA_BITS_PER_CHAN_16BITS;
break;
default:
MM_ERR("unsupported sample width \n");
goto error;
}
val |= (width << LPA_OBUF_CODEC_BITS_PER_CHAN_SHFT) &
LPA_OBUF_CODEC_BITS_PER_CHAN_BMSK;
val |= LPA_OBUF_CODEC_LOAD_BMSK;
val |= (config_ptr->output_interface << LPA_OBUF_CODEC_INTF_SHFT) &
LPA_OBUF_CODEC_INTF_BMSK;
LPA_REG_WRITEL(lpa, val, LPA_OBUF_CODEC);
mb();
return 0;
error:
return -EINVAL;
}
EXPORT_SYMBOL(lpa_cmd_codec_config);
static int lpa_check_llb_clear(struct lpa_drv *lpa)
{
u32 val;
val = LPA_REG_READL(lpa, LPA_OBUF_STATUS);
return !(val & LPA_OBUF_STATUS_LLB_CLR_BMSK);
}
static void lpa_clear_llb(struct lpa_drv *lpa)
{
u32 val;
val = LPA_REG_READL(lpa, LPA_OBUF_CONTROL);
LPA_REG_WRITEL(lpa, (val | LPA_OBUF_CONTROL_LLB_CLR_CMD_BMSK),
LPA_OBUF_CONTROL);
lpa_enable_obuf(lpa, LPA_BUF_ID_LLB, 0);
while (!lpa_check_llb_clear(lpa))
udelay(100);
LPA_REG_WRITEL(lpa, val, LPA_OBUF_CONTROL);
}
int lpa_cmd_enable_codec(struct lpa_drv *lpa, bool enable)
{
u32 val;
struct lpa_mem_bank_select mem_bank;
MM_DBG(" %s\n", (enable ? "enable" : "disable"));
if (!lpa)
return -EINVAL;
val = LPA_REG_READL(lpa, LPA_OBUF_CODEC);
if (enable) {
if (val & LPA_OBUF_CODEC_CODEC_INTF_EN_BMSK)
return -EBUSY;
/* Power up all memory bank for now */
mem_bank.b0 = 1;
mem_bank.b1 = 1;
mem_bank.b2 = 1;
mem_bank.b3 = 1;
mem_bank.b4 = 1;
mem_bank.b5 = 1;
mem_bank.b6 = 1;
mem_bank.b7 = 1;
mem_bank.b8 = 1;
mem_bank.b9 = 1;
mem_bank.b10 = 1;
mem_bank.llb = 1;
lpa_powerup_mem_bank(lpa, &mem_bank);
/*clear LLB*/
lpa_clear_llb(lpa);
lpa_enable_codec(lpa, 1);
MM_DBG("LPA codec is enabled\n");
} else {
if (val & LPA_OBUF_CODEC_CODEC_INTF_EN_BMSK) {
lpa_enable_codec(lpa, 0);
MM_DBG("LPA codec is disabled\n");
} else
MM_ERR("LPA codec is already disable\n");
}
mb();
return 0;
}
EXPORT_SYMBOL(lpa_cmd_enable_codec);
static int lpa_probe(struct platform_device *pdev)
{
int rc = 0;
struct resource *mem_src;
struct msm_lpa_platform_data *pdata;
MM_INFO("lpa probe\n");
if (!pdev || !pdev->dev.platform_data) {
MM_ERR("no plaform data\n");
rc = -ENODEV;
goto error;
}
mem_src = platform_get_resource_byname(pdev, IORESOURCE_MEM, "lpa");
if (!mem_src) {
MM_ERR("LPA base address undefined\n");
rc = -ENODEV;
goto error;
}
pdata = pdev->dev.platform_data;
the_lpa_state.lpa_drv.baseaddr = ioremap(mem_src->start,
(mem_src->end - mem_src->start) + 1);
if (!the_lpa_state.lpa_drv.baseaddr) {
rc = -ENOMEM;
goto error;
}
the_lpa_state.lpa_drv.obuf_hlb_size = pdata->obuf_hlb_size;
the_lpa_state.lpa_drv.dsp_proc_id = pdata->dsp_proc_id;
the_lpa_state.lpa_drv.app_proc_id = pdata->app_proc_id;
the_lpa_state.lpa_drv.nosb_config = pdata->nosb_config;
the_lpa_state.lpa_drv.sb_config = pdata->sb_config;
/* default to enable summing buffer */
the_lpa_state.lpa_drv.status = LPA_STATUS_SBUF_EN;
error:
return rc;
}
static int lpa_remove(struct platform_device *pdev)
{
iounmap(the_lpa_state.lpa_drv.baseaddr);
return 0;
}
static struct platform_driver lpa_driver = {
.probe = lpa_probe,
.remove = lpa_remove,
.driver = {
.name = "lpa",
.owner = THIS_MODULE,
},
};
static int __init lpa_init(void)
{
the_lpa_state.assigned = 0;
mutex_init(&the_lpa_state.lpa_lock);
return platform_driver_register(&lpa_driver);
}
static void __exit lpa_exit(void)
{
platform_driver_unregister(&lpa_driver);
}
module_init(lpa_init);
module_exit(lpa_exit);
MODULE_DESCRIPTION("MSM LPA driver");
MODULE_LICENSE("GPL v2");