blob: 9520d01f854f3440c4b059485ceb804939b05015 [file] [log] [blame]
/**
* system/src/rpclnt/rpclnt.h
*
* History:
* 2012/08/15 - [Tzu-Jung Lee] created file
*
* Copyright 2008-2015 Ambarella Inc. 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
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* 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/kthread.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <plat/remoteproc_cfg.h>
#include <plat/rpdev.h>
#include <plat/rct.h>
#include "rpclnt.h"
#include "vq.h"
extern void rpdev_svq_cb(struct vq *vq);
extern void rpdev_rvq_cb(struct vq *vq);
DECLARE_COMPLETION(rpclnt_comp);
static struct rpclnt G_rproc_ca9_b = {
.name = "rpclnt_ca9_a_and_b",
.inited = 0,
.svq_tx_irq = VRING_CA9_B_TO_A_HOST_IRQ,
.svq_rx_irq = VRING_CA9_B_TO_A_CLNT_IRQ,
.svq_num_bufs = RPMSG_NUM_BUFS >> 1,
.svq_buf_phys = VRING_BUF_CA9_A_AND_B,
.svq_vring_phys = VRING_CA9_B_TO_A,
.svq_vring_algn = RPMSG_VRING_ALIGN,
.rvq_tx_irq = VRING_CA9_A_TO_B_HOST_IRQ,
.rvq_rx_irq = VRING_CA9_A_TO_B_CLNT_IRQ,
.rvq_num_bufs = RPMSG_NUM_BUFS >> 1,
.rvq_buf_phys = VRING_BUF_CA9_A_AND_B + (RPMSG_TOTAL_BUF_SPACE >> 1),
.rvq_vring_phys = VRING_CA9_A_TO_B,
.rvq_vring_algn = RPMSG_VRING_ALIGN,
};
struct rpclnt *rpclnt_sync(const char *bus_name)
{
struct rpclnt *rpclnt = &G_rproc_ca9_b;
if (strcmp(bus_name, "ca9_a_and_b"))
return NULL;
if (!rpclnt->inited)
wait_for_completion(&rpclnt_comp);
return &G_rproc_ca9_b;
}
static void rpclnt_complete_registration(struct rpclnt *rpclnt)
{
u32 buf = rpclnt->rvq_buf_phys;
vq_init_unused_bufs(rpclnt->rvq, (void*)buf, RPMSG_BUF_SIZE);
rpclnt->inited = 1;
complete_all(&rpclnt_comp);
}
static irqreturn_t rpclnt_isr(int irq, void *dev_id)
{
struct rpclnt *rpclnt = dev_id;
if (!rpclnt->inited && irq == rpclnt->svq_rx_irq) {
rpclnt_complete_registration(rpclnt);
amba_writel(AHB_SCRATCHPAD_REG(0x14),
0x1 << (irq - AXI_SOFT_IRQ(0)));
return IRQ_HANDLED;
}
/*
* Before scheduling the bottom half for processing the messages,
* we tell the remote host not to notify us (generate interrupts) when
* subsequent messages are enqueued. The bottom half will pull out
* this message and the pending ones. Once it processed all the messages
* in the queue, it will re-enable remote host for the notification.
*/
if (irq == rpclnt->rvq_rx_irq) {
vq_disable_used_notify(rpclnt->rvq);
schedule_work(&rpclnt->rvq_work);
} else if (irq == rpclnt->svq_rx_irq) {
vq_disable_used_notify(rpclnt->svq);
schedule_work(&rpclnt->svq_work);
}
amba_writel(AHB_SCRATCHPAD_REG(0x14),
0x1 << (irq - AXI_SOFT_IRQ(0)));
return IRQ_HANDLED;
}
static void rpclnt_rvq_worker(struct work_struct *work)
{
struct rpclnt *rpclnt = container_of(work, struct rpclnt, rvq_work);
struct vq *vq = rpclnt->rvq;
if (vq->cb)
vq->cb(vq);
}
static void rpclnt_svq_worker(struct work_struct *work)
{
struct rpclnt *rpclnt = container_of(work, struct rpclnt, svq_work);
struct vq *vq = rpclnt->svq;
if (vq->cb)
vq->cb(vq);
}
static void rpclnt_kick_rvq(struct vq *vq)
{
/*
* Honor the flag set by the remote host.
*
* Most of the time, the remote host want to supress their
* tx-complete interrupts. In this case, we don't bother it.
*/
if (vq_kick_prepare(vq)) {
amba_writel(AHB_SCRATCHPAD_REG(0x10),
0x1 << (107 - AXI_SOFT_IRQ(0)));
}
}
static void rpclnt_kick_svq(struct vq *vq)
{
/*
* Honor the flag set by the remote host.
*
* When the remote host is already busy enough processing the
* messages, it might suppress the interrupt and work in polling
* mode.
*/
if (vq_kick_prepare(vq)) {
amba_writel(AHB_SCRATCHPAD_REG(0x10),
0x1 << (109 - AXI_SOFT_IRQ(0)));
}
}
static int rpclnt_drv_init(void)
{
int ret;
struct rpclnt *rpclnt = &G_rproc_ca9_b;
rpclnt->svq = vq_create(rpdev_svq_cb,
rpclnt_kick_svq,
rpclnt->svq_num_bufs,
ambarella_phys_to_virt(rpclnt->svq_vring_phys),
rpclnt->svq_vring_algn);
rpclnt->rvq = vq_create(rpdev_rvq_cb,
rpclnt_kick_rvq,
rpclnt->rvq_num_bufs,
ambarella_phys_to_virt(rpclnt->rvq_vring_phys),
rpclnt->rvq_vring_algn);
INIT_WORK(&rpclnt->svq_work, rpclnt_svq_worker);
INIT_WORK(&rpclnt->rvq_work, rpclnt_rvq_worker);
ret = request_irq(rpclnt->svq_rx_irq, rpclnt_isr,
IRQF_SHARED, "rpclnt_svq_rx", rpclnt);
if (ret)
printk("Error: failed to request svq_rx_irq: %d, err: %d\n",
rpclnt->svq_rx_irq, ret);
ret = request_irq(rpclnt->rvq_rx_irq, rpclnt_isr,
IRQF_SHARED, "rpclnt_rvq_rx", rpclnt);
if (ret)
printk("Error: failed to request rvq_rx_irq: %d, err: %d\n",
rpclnt->rvq_rx_irq, ret);
return 0;
}
module_init(rpclnt_drv_init);