mirror of
https://github.com/followmsi/android_kernel_google_msm.git
synced 2024-11-06 23:17:41 +00:00
3fad359855
As part of LPAII, the power profiling showed that having 100msec timeout is letting the AXI switch to low frequency as soon as the APROC collapses. Having this timeout as 1 sec is pushing the AXI to stay high for longer time hence causing more power drain. Hence with this change this time out is updated to 100msec. Change-Id: I4aaf8e1ba5c177e4a77291f13a8ecd0fbc6e6130 Signed-off-by: Krishna Vanka <kvanka@codeaurora.org>
774 lines
21 KiB
C
774 lines
21 KiB
C
/* linux/arch/arm/mach-msm/dma.c
|
|
*
|
|
* Copyright (C) 2007 Google, Inc.
|
|
* Copyright (c) 2008-2010, 2012 Code Aurora Forum. All rights reserved.
|
|
*
|
|
* This software is licensed under the terms of the GNU General Public
|
|
* License version 2, as published by the Free Software Foundation, and
|
|
* may be copied, distributed, and modified under those terms.
|
|
*
|
|
* 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/clk.h>
|
|
#include <linux/err.h>
|
|
#include <linux/io.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/module.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <mach/dma.h>
|
|
|
|
#define MODULE_NAME "msm_dmov"
|
|
|
|
#define MSM_DMOV_CHANNEL_COUNT 16
|
|
#define MSM_DMOV_CRCI_COUNT 16
|
|
|
|
enum {
|
|
CLK_DIS,
|
|
CLK_TO_BE_DIS,
|
|
CLK_EN
|
|
};
|
|
|
|
struct msm_dmov_ci_conf {
|
|
int start;
|
|
int end;
|
|
int burst;
|
|
};
|
|
|
|
struct msm_dmov_crci_conf {
|
|
int sd;
|
|
int blk_size;
|
|
};
|
|
|
|
struct msm_dmov_chan_conf {
|
|
int sd;
|
|
int block;
|
|
int priority;
|
|
};
|
|
|
|
struct msm_dmov_conf {
|
|
void *base;
|
|
struct msm_dmov_crci_conf *crci_conf;
|
|
struct msm_dmov_chan_conf *chan_conf;
|
|
int channel_active;
|
|
int sd;
|
|
size_t sd_size;
|
|
struct list_head staged_commands[MSM_DMOV_CHANNEL_COUNT];
|
|
struct list_head ready_commands[MSM_DMOV_CHANNEL_COUNT];
|
|
struct list_head active_commands[MSM_DMOV_CHANNEL_COUNT];
|
|
struct mutex lock;
|
|
spinlock_t list_lock;
|
|
unsigned int irq;
|
|
struct clk *clk;
|
|
struct clk *pclk;
|
|
struct clk *ebiclk;
|
|
unsigned int clk_ctl;
|
|
struct delayed_work work;
|
|
struct workqueue_struct *cmd_wq;
|
|
};
|
|
|
|
static void msm_dmov_clock_work(struct work_struct *);
|
|
|
|
#ifdef CONFIG_ARCH_MSM8X60
|
|
|
|
#define DMOV_CHANNEL_DEFAULT_CONF { .sd = 1, .block = 0, .priority = 0 }
|
|
#define DMOV_CHANNEL_MODEM_CONF { .sd = 3, .block = 0, .priority = 0 }
|
|
#define DMOV_CHANNEL_CONF(secd, blk, pri) \
|
|
{ .sd = secd, .block = blk, .priority = pri }
|
|
|
|
static struct msm_dmov_chan_conf adm0_chan_conf[] = {
|
|
DMOV_CHANNEL_DEFAULT_CONF,
|
|
DMOV_CHANNEL_DEFAULT_CONF,
|
|
DMOV_CHANNEL_DEFAULT_CONF,
|
|
DMOV_CHANNEL_DEFAULT_CONF,
|
|
DMOV_CHANNEL_DEFAULT_CONF,
|
|
DMOV_CHANNEL_DEFAULT_CONF,
|
|
DMOV_CHANNEL_DEFAULT_CONF,
|
|
DMOV_CHANNEL_DEFAULT_CONF,
|
|
DMOV_CHANNEL_DEFAULT_CONF,
|
|
DMOV_CHANNEL_DEFAULT_CONF,
|
|
DMOV_CHANNEL_MODEM_CONF,
|
|
DMOV_CHANNEL_MODEM_CONF,
|
|
DMOV_CHANNEL_MODEM_CONF,
|
|
DMOV_CHANNEL_MODEM_CONF,
|
|
DMOV_CHANNEL_MODEM_CONF,
|
|
DMOV_CHANNEL_DEFAULT_CONF,
|
|
};
|
|
|
|
static struct msm_dmov_chan_conf adm1_chan_conf[] = {
|
|
DMOV_CHANNEL_DEFAULT_CONF,
|
|
DMOV_CHANNEL_DEFAULT_CONF,
|
|
DMOV_CHANNEL_DEFAULT_CONF,
|
|
DMOV_CHANNEL_DEFAULT_CONF,
|
|
DMOV_CHANNEL_DEFAULT_CONF,
|
|
DMOV_CHANNEL_DEFAULT_CONF,
|
|
DMOV_CHANNEL_DEFAULT_CONF,
|
|
DMOV_CHANNEL_DEFAULT_CONF,
|
|
DMOV_CHANNEL_DEFAULT_CONF,
|
|
DMOV_CHANNEL_DEFAULT_CONF,
|
|
DMOV_CHANNEL_MODEM_CONF,
|
|
DMOV_CHANNEL_MODEM_CONF,
|
|
DMOV_CHANNEL_MODEM_CONF,
|
|
DMOV_CHANNEL_MODEM_CONF,
|
|
DMOV_CHANNEL_MODEM_CONF,
|
|
DMOV_CHANNEL_MODEM_CONF,
|
|
};
|
|
|
|
#define DMOV_CRCI_DEFAULT_CONF { .sd = 1, .blk_size = 0 }
|
|
#define DMOV_CRCI_CONF(secd, blk) { .sd = secd, .blk_size = blk }
|
|
|
|
static struct msm_dmov_crci_conf adm0_crci_conf[] = {
|
|
DMOV_CRCI_DEFAULT_CONF,
|
|
DMOV_CRCI_DEFAULT_CONF,
|
|
DMOV_CRCI_DEFAULT_CONF,
|
|
DMOV_CRCI_DEFAULT_CONF,
|
|
DMOV_CRCI_DEFAULT_CONF,
|
|
DMOV_CRCI_DEFAULT_CONF,
|
|
DMOV_CRCI_CONF(1, 4),
|
|
DMOV_CRCI_DEFAULT_CONF,
|
|
DMOV_CRCI_DEFAULT_CONF,
|
|
DMOV_CRCI_DEFAULT_CONF,
|
|
DMOV_CRCI_DEFAULT_CONF,
|
|
DMOV_CRCI_DEFAULT_CONF,
|
|
DMOV_CRCI_DEFAULT_CONF,
|
|
DMOV_CRCI_DEFAULT_CONF,
|
|
DMOV_CRCI_DEFAULT_CONF,
|
|
DMOV_CRCI_DEFAULT_CONF,
|
|
};
|
|
|
|
static struct msm_dmov_crci_conf adm1_crci_conf[] = {
|
|
DMOV_CRCI_DEFAULT_CONF,
|
|
DMOV_CRCI_CONF(1, 1),
|
|
DMOV_CRCI_CONF(1, 1),
|
|
DMOV_CRCI_DEFAULT_CONF,
|
|
DMOV_CRCI_CONF(1, 1),
|
|
DMOV_CRCI_CONF(1, 1),
|
|
DMOV_CRCI_DEFAULT_CONF,
|
|
DMOV_CRCI_DEFAULT_CONF,
|
|
DMOV_CRCI_DEFAULT_CONF,
|
|
DMOV_CRCI_DEFAULT_CONF,
|
|
DMOV_CRCI_DEFAULT_CONF,
|
|
DMOV_CRCI_DEFAULT_CONF,
|
|
DMOV_CRCI_DEFAULT_CONF,
|
|
DMOV_CRCI_DEFAULT_CONF,
|
|
DMOV_CRCI_CONF(1, 1),
|
|
DMOV_CRCI_DEFAULT_CONF,
|
|
};
|
|
|
|
static struct msm_dmov_conf dmov_conf[] = {
|
|
{
|
|
.crci_conf = adm0_crci_conf,
|
|
.chan_conf = adm0_chan_conf,
|
|
.lock = __MUTEX_INITIALIZER(dmov_conf[0].lock),
|
|
.list_lock = __SPIN_LOCK_UNLOCKED(dmov_list_lock),
|
|
.clk_ctl = CLK_DIS,
|
|
.work = __DELAYED_WORK_INITIALIZER(dmov_conf[0].work,
|
|
msm_dmov_clock_work),
|
|
}, {
|
|
.crci_conf = adm1_crci_conf,
|
|
.chan_conf = adm1_chan_conf,
|
|
.lock = __MUTEX_INITIALIZER(dmov_conf[1].lock),
|
|
.list_lock = __SPIN_LOCK_UNLOCKED(dmov_list_lock),
|
|
.clk_ctl = CLK_DIS,
|
|
.work = __DELAYED_WORK_INITIALIZER(dmov_conf[1].work,
|
|
msm_dmov_clock_work),
|
|
}
|
|
};
|
|
#else
|
|
static struct msm_dmov_conf dmov_conf[] = {
|
|
{
|
|
.crci_conf = NULL,
|
|
.chan_conf = NULL,
|
|
.lock = __MUTEX_INITIALIZER(dmov_conf[0].lock),
|
|
.list_lock = __SPIN_LOCK_UNLOCKED(dmov_list_lock),
|
|
.clk_ctl = CLK_DIS,
|
|
.work = __DELAYED_WORK_INITIALIZER(dmov_conf[0].work,
|
|
msm_dmov_clock_work),
|
|
}
|
|
};
|
|
#endif
|
|
|
|
#define MSM_DMOV_ID_COUNT (MSM_DMOV_CHANNEL_COUNT * ARRAY_SIZE(dmov_conf))
|
|
#define DMOV_REG(name, adm) ((name) + (dmov_conf[adm].base) +\
|
|
(dmov_conf[adm].sd * dmov_conf[adm].sd_size))
|
|
#define DMOV_ID_TO_ADM(id) ((id) / MSM_DMOV_CHANNEL_COUNT)
|
|
#define DMOV_ID_TO_CHAN(id) ((id) % MSM_DMOV_CHANNEL_COUNT)
|
|
#define DMOV_CHAN_ADM_TO_ID(ch, adm) ((ch) + (adm) * MSM_DMOV_CHANNEL_COUNT)
|
|
|
|
#ifdef CONFIG_MSM_ADM3
|
|
#define DMOV_IRQ_TO_ADM(irq) \
|
|
({ \
|
|
typeof(irq) _irq = irq; \
|
|
((_irq == INT_ADM1_MASTER) || (_irq == INT_ADM1_AARM)); \
|
|
})
|
|
#else
|
|
#define DMOV_IRQ_TO_ADM(irq) 0
|
|
#endif
|
|
|
|
enum {
|
|
MSM_DMOV_PRINT_ERRORS = 1,
|
|
MSM_DMOV_PRINT_IO = 2,
|
|
MSM_DMOV_PRINT_FLOW = 4
|
|
};
|
|
|
|
unsigned int msm_dmov_print_mask = MSM_DMOV_PRINT_ERRORS;
|
|
|
|
#define MSM_DMOV_DPRINTF(mask, format, args...) \
|
|
do { \
|
|
if ((mask) & msm_dmov_print_mask) \
|
|
printk(KERN_ERR format, args); \
|
|
} while (0)
|
|
#define PRINT_ERROR(format, args...) \
|
|
MSM_DMOV_DPRINTF(MSM_DMOV_PRINT_ERRORS, format, args);
|
|
#define PRINT_IO(format, args...) \
|
|
MSM_DMOV_DPRINTF(MSM_DMOV_PRINT_IO, format, args);
|
|
#define PRINT_FLOW(format, args...) \
|
|
MSM_DMOV_DPRINTF(MSM_DMOV_PRINT_FLOW, format, args);
|
|
|
|
static int msm_dmov_clk_on(int adm)
|
|
{
|
|
int ret;
|
|
|
|
ret = clk_prepare_enable(dmov_conf[adm].clk);
|
|
if (ret)
|
|
return ret;
|
|
if (dmov_conf[adm].pclk) {
|
|
ret = clk_prepare_enable(dmov_conf[adm].pclk);
|
|
if (ret) {
|
|
clk_disable_unprepare(dmov_conf[adm].clk);
|
|
return ret;
|
|
}
|
|
}
|
|
if (dmov_conf[adm].ebiclk) {
|
|
ret = clk_prepare_enable(dmov_conf[adm].ebiclk);
|
|
if (ret) {
|
|
if (dmov_conf[adm].pclk)
|
|
clk_disable_unprepare(dmov_conf[adm].pclk);
|
|
clk_disable_unprepare(dmov_conf[adm].clk);
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static void msm_dmov_clk_off(int adm)
|
|
{
|
|
if (dmov_conf[adm].ebiclk)
|
|
clk_disable_unprepare(dmov_conf[adm].ebiclk);
|
|
if (dmov_conf[adm].pclk)
|
|
clk_disable_unprepare(dmov_conf[adm].pclk);
|
|
clk_disable_unprepare(dmov_conf[adm].clk);
|
|
}
|
|
|
|
static void msm_dmov_clock_work(struct work_struct *work)
|
|
{
|
|
struct msm_dmov_conf *conf =
|
|
container_of(to_delayed_work(work), struct msm_dmov_conf, work);
|
|
int adm = DMOV_IRQ_TO_ADM(conf->irq);
|
|
mutex_lock(&conf->lock);
|
|
if (conf->clk_ctl == CLK_TO_BE_DIS) {
|
|
BUG_ON(conf->channel_active);
|
|
msm_dmov_clk_off(adm);
|
|
conf->clk_ctl = CLK_DIS;
|
|
}
|
|
mutex_unlock(&conf->lock);
|
|
}
|
|
|
|
enum {
|
|
NOFLUSH = 0,
|
|
GRACEFUL,
|
|
NONGRACEFUL,
|
|
};
|
|
|
|
/* Caller must hold the list lock */
|
|
static struct msm_dmov_cmd *start_ready_cmd(unsigned ch, int adm)
|
|
{
|
|
struct msm_dmov_cmd *cmd;
|
|
|
|
if (list_empty(&dmov_conf[adm].ready_commands[ch]))
|
|
return NULL;
|
|
|
|
cmd = list_entry(dmov_conf[adm].ready_commands[ch].next, typeof(*cmd),
|
|
list);
|
|
list_del(&cmd->list);
|
|
if (cmd->exec_func)
|
|
cmd->exec_func(cmd);
|
|
list_add_tail(&cmd->list, &dmov_conf[adm].active_commands[ch]);
|
|
if (!dmov_conf[adm].channel_active)
|
|
enable_irq(dmov_conf[adm].irq);
|
|
dmov_conf[adm].channel_active |= BIT(ch);
|
|
PRINT_IO("msm dmov enqueue command, %x, ch %d\n", cmd->cmdptr, ch);
|
|
writel_relaxed(cmd->cmdptr, DMOV_REG(DMOV_CMD_PTR(ch), adm));
|
|
|
|
return cmd;
|
|
}
|
|
|
|
static void msm_dmov_enqueue_cmd_ext_work(struct work_struct *work)
|
|
{
|
|
struct msm_dmov_cmd *cmd =
|
|
container_of(work, struct msm_dmov_cmd, work);
|
|
unsigned id = cmd->id;
|
|
unsigned status;
|
|
unsigned long flags;
|
|
int adm = DMOV_ID_TO_ADM(id);
|
|
int ch = DMOV_ID_TO_CHAN(id);
|
|
|
|
mutex_lock(&dmov_conf[adm].lock);
|
|
if (dmov_conf[adm].clk_ctl == CLK_DIS) {
|
|
status = msm_dmov_clk_on(adm);
|
|
if (status != 0)
|
|
goto error;
|
|
}
|
|
dmov_conf[adm].clk_ctl = CLK_EN;
|
|
|
|
spin_lock_irqsave(&dmov_conf[adm].list_lock, flags);
|
|
|
|
cmd = list_entry(dmov_conf[adm].staged_commands[ch].next, typeof(*cmd),
|
|
list);
|
|
list_del(&cmd->list);
|
|
list_add_tail(&cmd->list, &dmov_conf[adm].ready_commands[ch]);
|
|
status = readl_relaxed(DMOV_REG(DMOV_STATUS(ch), adm));
|
|
if (status & DMOV_STATUS_CMD_PTR_RDY) {
|
|
PRINT_IO("msm_dmov_enqueue_cmd(%d), start command, status %x\n",
|
|
id, status);
|
|
cmd = start_ready_cmd(ch, adm);
|
|
/*
|
|
* We added something to the ready list, and still hold the
|
|
* list lock. Thus, no need to check for cmd == NULL
|
|
*/
|
|
if (cmd->toflush) {
|
|
int flush = (cmd->toflush == GRACEFUL) ? 1 << 31 : 0;
|
|
writel_relaxed(flush, DMOV_REG(DMOV_FLUSH0(ch), adm));
|
|
}
|
|
} else {
|
|
cmd->toflush = 0;
|
|
if (list_empty(&dmov_conf[adm].active_commands[ch]) &&
|
|
!list_empty(&dmov_conf[adm].ready_commands[ch]))
|
|
PRINT_ERROR("msm_dmov_enqueue_cmd_ext(%d), stalled, "
|
|
"status %x\n", id, status);
|
|
PRINT_IO("msm_dmov_enqueue_cmd(%d), enqueue command, status "
|
|
"%x\n", id, status);
|
|
}
|
|
if (!dmov_conf[adm].channel_active) {
|
|
dmov_conf[adm].clk_ctl = CLK_TO_BE_DIS;
|
|
schedule_delayed_work(&dmov_conf[adm].work, (HZ/10));
|
|
}
|
|
spin_unlock_irqrestore(&dmov_conf[adm].list_lock, flags);
|
|
error:
|
|
mutex_unlock(&dmov_conf[adm].lock);
|
|
}
|
|
|
|
static void __msm_dmov_enqueue_cmd_ext(unsigned id, struct msm_dmov_cmd *cmd)
|
|
{
|
|
int adm = DMOV_ID_TO_ADM(id);
|
|
int ch = DMOV_ID_TO_CHAN(id);
|
|
unsigned long flags;
|
|
cmd->id = id;
|
|
cmd->toflush = 0;
|
|
|
|
spin_lock_irqsave(&dmov_conf[adm].list_lock, flags);
|
|
list_add_tail(&cmd->list, &dmov_conf[adm].staged_commands[ch]);
|
|
spin_unlock_irqrestore(&dmov_conf[adm].list_lock, flags);
|
|
|
|
queue_work(dmov_conf[adm].cmd_wq, &cmd->work);
|
|
}
|
|
|
|
void msm_dmov_enqueue_cmd_ext(unsigned id, struct msm_dmov_cmd *cmd)
|
|
{
|
|
INIT_WORK(&cmd->work, msm_dmov_enqueue_cmd_ext_work);
|
|
__msm_dmov_enqueue_cmd_ext(id, cmd);
|
|
}
|
|
EXPORT_SYMBOL(msm_dmov_enqueue_cmd_ext);
|
|
|
|
void msm_dmov_enqueue_cmd(unsigned id, struct msm_dmov_cmd *cmd)
|
|
{
|
|
/* Disable callback function (for backwards compatibility) */
|
|
cmd->exec_func = NULL;
|
|
INIT_WORK(&cmd->work, msm_dmov_enqueue_cmd_ext_work);
|
|
__msm_dmov_enqueue_cmd_ext(id, cmd);
|
|
}
|
|
EXPORT_SYMBOL(msm_dmov_enqueue_cmd);
|
|
|
|
void msm_dmov_flush(unsigned int id, int graceful)
|
|
{
|
|
unsigned long irq_flags;
|
|
int ch = DMOV_ID_TO_CHAN(id);
|
|
int adm = DMOV_ID_TO_ADM(id);
|
|
int flush = graceful ? DMOV_FLUSH_TYPE : 0;
|
|
struct msm_dmov_cmd *cmd;
|
|
|
|
spin_lock_irqsave(&dmov_conf[adm].list_lock, irq_flags);
|
|
/* XXX not checking if flush cmd sent already */
|
|
if (!list_empty(&dmov_conf[adm].active_commands[ch])) {
|
|
PRINT_IO("msm_dmov_flush(%d), send flush cmd\n", id);
|
|
writel_relaxed(flush, DMOV_REG(DMOV_FLUSH0(ch), adm));
|
|
}
|
|
list_for_each_entry(cmd, &dmov_conf[adm].staged_commands[ch], list)
|
|
cmd->toflush = graceful ? GRACEFUL : NONGRACEFUL;
|
|
/* spin_unlock_irqrestore has the necessary barrier */
|
|
spin_unlock_irqrestore(&dmov_conf[adm].list_lock, irq_flags);
|
|
}
|
|
EXPORT_SYMBOL(msm_dmov_flush);
|
|
|
|
struct msm_dmov_exec_cmdptr_cmd {
|
|
struct msm_dmov_cmd dmov_cmd;
|
|
struct completion complete;
|
|
unsigned id;
|
|
unsigned int result;
|
|
struct msm_dmov_errdata err;
|
|
};
|
|
|
|
static void
|
|
dmov_exec_cmdptr_complete_func(struct msm_dmov_cmd *_cmd,
|
|
unsigned int result,
|
|
struct msm_dmov_errdata *err)
|
|
{
|
|
struct msm_dmov_exec_cmdptr_cmd *cmd = container_of(_cmd, struct msm_dmov_exec_cmdptr_cmd, dmov_cmd);
|
|
cmd->result = result;
|
|
if (result != 0x80000002 && err)
|
|
memcpy(&cmd->err, err, sizeof(struct msm_dmov_errdata));
|
|
|
|
complete(&cmd->complete);
|
|
}
|
|
|
|
int msm_dmov_exec_cmd(unsigned id, unsigned int cmdptr)
|
|
{
|
|
struct msm_dmov_exec_cmdptr_cmd cmd;
|
|
|
|
PRINT_FLOW("dmov_exec_cmdptr(%d, %x)\n", id, cmdptr);
|
|
|
|
cmd.dmov_cmd.cmdptr = cmdptr;
|
|
cmd.dmov_cmd.complete_func = dmov_exec_cmdptr_complete_func;
|
|
cmd.dmov_cmd.exec_func = NULL;
|
|
cmd.id = id;
|
|
cmd.result = 0;
|
|
INIT_WORK_ONSTACK(&cmd.dmov_cmd.work, msm_dmov_enqueue_cmd_ext_work);
|
|
init_completion(&cmd.complete);
|
|
|
|
__msm_dmov_enqueue_cmd_ext(id, &cmd.dmov_cmd);
|
|
wait_for_completion_io(&cmd.complete);
|
|
|
|
if (cmd.result != 0x80000002) {
|
|
PRINT_ERROR("dmov_exec_cmdptr(%d): ERROR, result: %x\n", id, cmd.result);
|
|
PRINT_ERROR("dmov_exec_cmdptr(%d): flush: %x %x %x %x\n",
|
|
id, cmd.err.flush[0], cmd.err.flush[1], cmd.err.flush[2], cmd.err.flush[3]);
|
|
return -EIO;
|
|
}
|
|
PRINT_FLOW("dmov_exec_cmdptr(%d, %x) done\n", id, cmdptr);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(msm_dmov_exec_cmd);
|
|
|
|
static void fill_errdata(struct msm_dmov_errdata *errdata, int ch, int adm)
|
|
{
|
|
errdata->flush[0] = readl_relaxed(DMOV_REG(DMOV_FLUSH0(ch), adm));
|
|
errdata->flush[1] = readl_relaxed(DMOV_REG(DMOV_FLUSH1(ch), adm));
|
|
errdata->flush[2] = 0;
|
|
errdata->flush[3] = readl_relaxed(DMOV_REG(DMOV_FLUSH3(ch), adm));
|
|
errdata->flush[4] = readl_relaxed(DMOV_REG(DMOV_FLUSH4(ch), adm));
|
|
errdata->flush[5] = readl_relaxed(DMOV_REG(DMOV_FLUSH5(ch), adm));
|
|
}
|
|
|
|
static irqreturn_t msm_dmov_isr(int irq, void *dev_id)
|
|
{
|
|
unsigned int int_status;
|
|
unsigned int mask;
|
|
unsigned int id;
|
|
unsigned int ch;
|
|
unsigned long irq_flags;
|
|
unsigned int ch_status;
|
|
unsigned int ch_result;
|
|
unsigned int valid = 0;
|
|
struct msm_dmov_cmd *cmd;
|
|
int adm = DMOV_IRQ_TO_ADM(irq);
|
|
|
|
mutex_lock(&dmov_conf[adm].lock);
|
|
/* read and clear isr */
|
|
int_status = readl_relaxed(DMOV_REG(DMOV_ISR, adm));
|
|
PRINT_FLOW("msm_datamover_irq_handler: DMOV_ISR %x\n", int_status);
|
|
|
|
spin_lock_irqsave(&dmov_conf[adm].list_lock, irq_flags);
|
|
while (int_status) {
|
|
mask = int_status & -int_status;
|
|
ch = fls(mask) - 1;
|
|
id = DMOV_CHAN_ADM_TO_ID(ch, adm);
|
|
PRINT_FLOW("msm_datamover_irq_handler %08x %08x id %d\n", int_status, mask, id);
|
|
int_status &= ~mask;
|
|
ch_status = readl_relaxed(DMOV_REG(DMOV_STATUS(ch), adm));
|
|
if (!(ch_status & DMOV_STATUS_RSLT_VALID)) {
|
|
PRINT_FLOW("msm_datamover_irq_handler id %d, "
|
|
"result not valid %x\n", id, ch_status);
|
|
continue;
|
|
}
|
|
do {
|
|
valid = 1;
|
|
ch_result = readl_relaxed(DMOV_REG(DMOV_RSLT(ch), adm));
|
|
if (list_empty(&dmov_conf[adm].active_commands[ch])) {
|
|
PRINT_ERROR("msm_datamover_irq_handler id %d, got result "
|
|
"with no active command, status %x, result %x\n",
|
|
id, ch_status, ch_result);
|
|
cmd = NULL;
|
|
} else {
|
|
cmd = list_entry(dmov_conf[adm].
|
|
active_commands[ch].next, typeof(*cmd),
|
|
list);
|
|
}
|
|
PRINT_FLOW("msm_datamover_irq_handler id %d, status %x, result %x\n", id, ch_status, ch_result);
|
|
if (ch_result & DMOV_RSLT_DONE) {
|
|
PRINT_FLOW("msm_datamover_irq_handler id %d, status %x\n",
|
|
id, ch_status);
|
|
PRINT_IO("msm_datamover_irq_handler id %d, got result "
|
|
"for %p, result %x\n", id, cmd, ch_result);
|
|
if (cmd) {
|
|
list_del(&cmd->list);
|
|
cmd->complete_func(cmd, ch_result, NULL);
|
|
}
|
|
}
|
|
if (ch_result & DMOV_RSLT_FLUSH) {
|
|
struct msm_dmov_errdata errdata;
|
|
|
|
fill_errdata(&errdata, ch, adm);
|
|
PRINT_FLOW("msm_datamover_irq_handler id %d, status %x\n", id, ch_status);
|
|
PRINT_FLOW("msm_datamover_irq_handler id %d, flush, result %x, flush0 %x\n", id, ch_result, errdata.flush[0]);
|
|
if (cmd) {
|
|
list_del(&cmd->list);
|
|
cmd->complete_func(cmd, ch_result, &errdata);
|
|
}
|
|
}
|
|
if (ch_result & DMOV_RSLT_ERROR) {
|
|
struct msm_dmov_errdata errdata;
|
|
|
|
fill_errdata(&errdata, ch, adm);
|
|
|
|
PRINT_ERROR("msm_datamover_irq_handler id %d, status %x\n", id, ch_status);
|
|
PRINT_ERROR("msm_datamover_irq_handler id %d, error, result %x, flush0 %x\n", id, ch_result, errdata.flush[0]);
|
|
if (cmd) {
|
|
list_del(&cmd->list);
|
|
cmd->complete_func(cmd, ch_result, &errdata);
|
|
}
|
|
/* this does not seem to work, once we get an error */
|
|
/* the datamover will no longer accept commands */
|
|
writel_relaxed(0, DMOV_REG(DMOV_FLUSH0(ch),
|
|
adm));
|
|
}
|
|
rmb();
|
|
ch_status = readl_relaxed(DMOV_REG(DMOV_STATUS(ch),
|
|
adm));
|
|
PRINT_FLOW("msm_datamover_irq_handler id %d, status %x\n", id, ch_status);
|
|
if (ch_status & DMOV_STATUS_CMD_PTR_RDY)
|
|
start_ready_cmd(ch, adm);
|
|
} while (ch_status & DMOV_STATUS_RSLT_VALID);
|
|
if (list_empty(&dmov_conf[adm].active_commands[ch]) &&
|
|
list_empty(&dmov_conf[adm].ready_commands[ch]))
|
|
dmov_conf[adm].channel_active &= ~(1U << ch);
|
|
PRINT_FLOW("msm_datamover_irq_handler id %d, status %x\n", id, ch_status);
|
|
}
|
|
spin_unlock_irqrestore(&dmov_conf[adm].list_lock, irq_flags);
|
|
|
|
if (!dmov_conf[adm].channel_active && valid) {
|
|
disable_irq_nosync(dmov_conf[adm].irq);
|
|
dmov_conf[adm].clk_ctl = CLK_TO_BE_DIS;
|
|
schedule_delayed_work(&dmov_conf[adm].work, (HZ/10));
|
|
}
|
|
|
|
mutex_unlock(&dmov_conf[adm].lock);
|
|
return valid ? IRQ_HANDLED : IRQ_NONE;
|
|
}
|
|
|
|
static int msm_dmov_suspend_late(struct device *dev)
|
|
{
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
int adm = (pdev->id >= 0) ? pdev->id : 0;
|
|
mutex_lock(&dmov_conf[adm].lock);
|
|
if (dmov_conf[adm].clk_ctl == CLK_TO_BE_DIS) {
|
|
BUG_ON(dmov_conf[adm].channel_active);
|
|
msm_dmov_clk_off(adm);
|
|
dmov_conf[adm].clk_ctl = CLK_DIS;
|
|
}
|
|
mutex_unlock(&dmov_conf[adm].lock);
|
|
return 0;
|
|
}
|
|
|
|
static int msm_dmov_runtime_suspend(struct device *dev)
|
|
{
|
|
dev_dbg(dev, "pm_runtime: suspending...\n");
|
|
return 0;
|
|
}
|
|
|
|
static int msm_dmov_runtime_resume(struct device *dev)
|
|
{
|
|
dev_dbg(dev, "pm_runtime: resuming...\n");
|
|
return 0;
|
|
}
|
|
|
|
static int msm_dmov_runtime_idle(struct device *dev)
|
|
{
|
|
dev_dbg(dev, "pm_runtime: idling...\n");
|
|
return 0;
|
|
}
|
|
|
|
static struct dev_pm_ops msm_dmov_dev_pm_ops = {
|
|
.runtime_suspend = msm_dmov_runtime_suspend,
|
|
.runtime_resume = msm_dmov_runtime_resume,
|
|
.runtime_idle = msm_dmov_runtime_idle,
|
|
.suspend = msm_dmov_suspend_late,
|
|
};
|
|
|
|
static int msm_dmov_init_clocks(struct platform_device *pdev)
|
|
{
|
|
int adm = (pdev->id >= 0) ? pdev->id : 0;
|
|
int ret;
|
|
|
|
dmov_conf[adm].clk = clk_get(&pdev->dev, "core_clk");
|
|
if (IS_ERR(dmov_conf[adm].clk)) {
|
|
printk(KERN_ERR "%s: Error getting adm_clk\n", __func__);
|
|
dmov_conf[adm].clk = NULL;
|
|
return -ENOENT;
|
|
}
|
|
|
|
dmov_conf[adm].pclk = clk_get(&pdev->dev, "iface_clk");
|
|
if (IS_ERR(dmov_conf[adm].pclk)) {
|
|
dmov_conf[adm].pclk = NULL;
|
|
/* pclk not present on all SoCs, don't bail on failure */
|
|
}
|
|
|
|
dmov_conf[adm].ebiclk = clk_get(&pdev->dev, "mem_clk");
|
|
if (IS_ERR(dmov_conf[adm].ebiclk)) {
|
|
dmov_conf[adm].ebiclk = NULL;
|
|
/* ebiclk not present on all SoCs, don't bail on failure */
|
|
} else {
|
|
ret = clk_set_rate(dmov_conf[adm].ebiclk, 27000000);
|
|
if (ret)
|
|
return -ENOENT;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void config_datamover(int adm)
|
|
{
|
|
#ifdef CONFIG_MSM_ADM3
|
|
int i;
|
|
for (i = 0; i < MSM_DMOV_CHANNEL_COUNT; i++) {
|
|
struct msm_dmov_chan_conf *chan_conf =
|
|
dmov_conf[adm].chan_conf;
|
|
unsigned conf;
|
|
/* Only configure scorpion channels */
|
|
if (chan_conf[i].sd <= 1) {
|
|
conf = readl_relaxed(DMOV_REG(DMOV_CONF(i), adm));
|
|
conf &= ~DMOV_CONF_SD(7);
|
|
conf |= DMOV_CONF_SD(chan_conf[i].sd);
|
|
writel_relaxed(conf | DMOV_CONF_SHADOW_EN,
|
|
DMOV_REG(DMOV_CONF(i), adm));
|
|
}
|
|
}
|
|
for (i = 0; i < MSM_DMOV_CRCI_COUNT; i++) {
|
|
struct msm_dmov_crci_conf *crci_conf =
|
|
dmov_conf[adm].crci_conf;
|
|
|
|
writel_relaxed(DMOV_CRCI_CTL_BLK_SZ(crci_conf[i].blk_size),
|
|
DMOV_REG(DMOV_CRCI_CTL(i), adm));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static int msm_dmov_probe(struct platform_device *pdev)
|
|
{
|
|
int adm = (pdev->id >= 0) ? pdev->id : 0;
|
|
int i;
|
|
int ret;
|
|
struct msm_dmov_pdata *pdata = pdev->dev.platform_data;
|
|
struct resource *irqres =
|
|
platform_get_resource(pdev, IORESOURCE_IRQ, 0);
|
|
struct resource *mres =
|
|
platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
|
|
if (pdata) {
|
|
dmov_conf[adm].sd = pdata->sd;
|
|
dmov_conf[adm].sd_size = pdata->sd_size;
|
|
}
|
|
if (!dmov_conf[adm].sd_size)
|
|
return -ENXIO;
|
|
|
|
if (!irqres || !irqres->start)
|
|
return -ENXIO;
|
|
dmov_conf[adm].irq = irqres->start;
|
|
|
|
if (!mres || !mres->start)
|
|
return -ENXIO;
|
|
dmov_conf[adm].base = ioremap_nocache(mres->start, resource_size(mres));
|
|
if (!dmov_conf[adm].base)
|
|
return -ENOMEM;
|
|
|
|
dmov_conf[adm].cmd_wq = alloc_ordered_workqueue("dmov%d_wq", 0, adm);
|
|
if (!dmov_conf[adm].cmd_wq) {
|
|
PRINT_ERROR("Couldn't allocate ADM%d workqueue.\n", adm);
|
|
ret = -ENOMEM;
|
|
goto out_map;
|
|
}
|
|
|
|
ret = request_threaded_irq(dmov_conf[adm].irq, NULL, msm_dmov_isr,
|
|
IRQF_ONESHOT, "msmdatamover", NULL);
|
|
if (ret) {
|
|
PRINT_ERROR("Requesting ADM%d irq %d failed\n", adm,
|
|
dmov_conf[adm].irq);
|
|
goto out_wq;
|
|
}
|
|
disable_irq(dmov_conf[adm].irq);
|
|
ret = msm_dmov_init_clocks(pdev);
|
|
if (ret) {
|
|
PRINT_ERROR("Requesting ADM%d clocks failed\n", adm);
|
|
goto out_irq;
|
|
}
|
|
ret = msm_dmov_clk_on(adm);
|
|
if (ret) {
|
|
PRINT_ERROR("Enabling ADM%d clocks failed\n", adm);
|
|
goto out_irq;
|
|
}
|
|
|
|
config_datamover(adm);
|
|
for (i = 0; i < MSM_DMOV_CHANNEL_COUNT; i++) {
|
|
INIT_LIST_HEAD(&dmov_conf[adm].staged_commands[i]);
|
|
INIT_LIST_HEAD(&dmov_conf[adm].ready_commands[i]);
|
|
INIT_LIST_HEAD(&dmov_conf[adm].active_commands[i]);
|
|
|
|
writel_relaxed(DMOV_RSLT_CONF_IRQ_EN
|
|
| DMOV_RSLT_CONF_FORCE_FLUSH_RSLT,
|
|
DMOV_REG(DMOV_RSLT_CONF(i), adm));
|
|
}
|
|
wmb();
|
|
msm_dmov_clk_off(adm);
|
|
return ret;
|
|
out_irq:
|
|
free_irq(dmov_conf[adm].irq, NULL);
|
|
out_wq:
|
|
destroy_workqueue(dmov_conf[adm].cmd_wq);
|
|
out_map:
|
|
iounmap(dmov_conf[adm].base);
|
|
return ret;
|
|
}
|
|
|
|
static struct platform_driver msm_dmov_driver = {
|
|
.probe = msm_dmov_probe,
|
|
.driver = {
|
|
.name = MODULE_NAME,
|
|
.owner = THIS_MODULE,
|
|
.pm = &msm_dmov_dev_pm_ops,
|
|
},
|
|
};
|
|
|
|
/* static int __init */
|
|
static int __init msm_init_datamover(void)
|
|
{
|
|
int ret;
|
|
ret = platform_driver_register(&msm_dmov_driver);
|
|
if (ret)
|
|
return ret;
|
|
return 0;
|
|
}
|
|
arch_initcall(msm_init_datamover);
|