blob: 2706283a7e1775555162643fbba864578476d391 [file] [log] [blame]
/*
* INTEL MID Remote Processor - SCU driver
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* 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/kernel.h>
#include <linux/module.h>
#include <linux/err.h>
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/remoteproc.h>
#include <linux/delay.h>
#include <linux/rpmsg.h>
#include <linux/slab.h>
#include <linux/virtio_ring.h>
#include <linux/virtio_ids.h>
#include <linux/platform_data/intel_mid_remoteproc.h>
#include <asm/intel_scu_ipc.h>
#include <asm/scu_ipc_rpmsg.h>
#include <asm/intel-mid.h>
#include "intel_mid_rproc_core.h"
#include "remoteproc_internal.h"
static struct rpmsg_ns_list *nslist;
static int scu_ipc_command(void *tx_buf)
{
struct tx_ipc_msg *tx_msg;
int ret = 0;
tx_msg = (struct tx_ipc_msg *)tx_buf;
ret = intel_scu_ipc_command(tx_msg->cmd, tx_msg->sub,
tx_msg->in, tx_msg->inlen,
tx_msg->out, tx_msg->outlen);
return ret;
}
static int scu_ipc_raw_command(void *tx_buf)
{
struct tx_ipc_msg *tx_msg;
int ret = 0;
tx_msg = (struct tx_ipc_msg *)tx_buf;
intel_scu_ipc_lock();
ret = intel_scu_ipc_raw_cmd(tx_msg->cmd, tx_msg->sub,
tx_msg->in, tx_msg->inlen,
tx_msg->out, tx_msg->outlen,
tx_msg->dptr, tx_msg->sptr);
intel_scu_ipc_unlock();
return ret;
}
static int scu_ipc_simple_command(void *tx_buf)
{
struct tx_ipc_msg *tx_msg;
int ret = 0;
tx_msg = (struct tx_ipc_msg *)tx_buf;
ret = intel_scu_ipc_simple_command(tx_msg->cmd, tx_msg->sub);
return ret;
}
static void scu_ipc_send_command(void *tx_buf)
{
struct tx_ipc_msg *tx_msg;
tx_msg = (struct tx_ipc_msg *)tx_buf;
intel_scu_ipc_send_command(tx_msg->sub << 12 | tx_msg->cmd);
}
static int scu_ipc_fw_command(void *tx_buf)
{
struct tx_ipc_msg *tx_msg;
int ret = 0;
tx_msg = (struct tx_ipc_msg *)tx_buf;
switch (tx_msg->cmd) {
case RP_GET_FW_REVISION:
ret = scu_ipc_command(tx_buf);
break;
case RP_FW_UPDATE:
/* Only scu_ipc_send_command works for fw update */
scu_ipc_send_command(tx_buf);
break;
default:
pr_info("Command %x not supported\n", tx_msg->cmd);
break;
};
return ret;
}
static int scu_ipc_util_command(void *tx_buf)
{
struct tx_ipc_msg *tx_msg;
int ret = 0;
tx_msg = (struct tx_ipc_msg *)tx_buf;
switch (tx_msg->cmd) {
case RP_GET_FW_REVISION:
case RP_GET_HOBADDR:
case RP_OSC_CLK_CTRL:
ret = scu_ipc_command(tx_buf);
break;
case RP_S0IX_COUNTER:
ret = scu_ipc_simple_command(tx_buf);
break;
case RP_WRITE_OSNIB:
ret = scu_ipc_raw_command(tx_buf);
break;
default:
pr_info("Command %x not supported\n", tx_msg->cmd);
break;
};
return ret;
}
static int scu_ipc_vrtc_command(void *tx_buf)
{
struct tx_ipc_msg *tx_msg;
int ret = 0;
tx_msg = (struct tx_ipc_msg *)tx_buf;
switch (tx_msg->cmd) {
case RP_GET_HOBADDR:
ret = scu_ipc_command(tx_buf);
break;
case RP_VRTC:
ret = scu_ipc_simple_command(tx_buf);
break;
default:
pr_info("Command %x not supported\n", tx_msg->cmd);
break;
};
return ret;
}
static int scu_ipc_fw_logging_command(void *tx_buf)
{
struct tx_ipc_msg *tx_msg;
int ret = 0;
tx_msg = (struct tx_ipc_msg *)tx_buf;
switch (tx_msg->cmd) {
case RP_GET_HOBADDR:
case RP_SCULOG_TRACE:
ret = scu_ipc_command(tx_buf);
break;
case RP_CLEAR_FABERROR:
case RP_SCULOG_CTRL:
ret = scu_ipc_simple_command(tx_buf);
break;
default:
pr_info("Command %x not supported\n", tx_msg->cmd);
break;
};
return ret;
}
/**
* scu_ipc_rpmsg_handle() - scu rproc specified ipc rpmsg handle
* @rx_buf: rx buffer to be add
* @tx_buf: tx buffer to be get
* @r_len: rx buffer length
* @s_len: tx buffer length
*/
int scu_ipc_rpmsg_handle(void *rx_buf, void *tx_buf, u32 *r_len, u32 *s_len)
{
struct rpmsg_hdr *tx_hdr, *tmp_hdr;
struct tx_ipc_msg *tx_msg;
struct rx_ipc_msg *tmp_msg;
int ret = 0;
*r_len = sizeof(struct rpmsg_hdr) + sizeof(struct rx_ipc_msg);
*s_len = sizeof(struct rpmsg_hdr) + sizeof(struct tx_ipc_msg);
/* get tx_msg and send scu ipc command */
tx_hdr = (struct rpmsg_hdr *)tx_buf;
tx_msg = (struct tx_ipc_msg *)(tx_buf + sizeof(*tx_hdr));
tmp_hdr = (struct rpmsg_hdr *)rx_buf;
tmp_msg = (struct rx_ipc_msg *)tmp_hdr->data;
switch (tx_hdr->dst) {
case RP_PMIC_ACCESS:
case RP_FLIS_ACCESS:
case RP_IPC_COMMAND:
tmp_msg->status = scu_ipc_command(tx_msg);
break;
case RP_SET_WATCHDOG:
if ((intel_mid_identify_cpu() == INTEL_MID_CPU_CHIP_TANGIER) ||
(intel_mid_identify_cpu() == INTEL_MID_CPU_CHIP_ANNIEDALE))
tmp_msg->status = scu_ipc_raw_command(tx_msg);
else
tmp_msg->status = scu_ipc_command(tx_msg);
break;
case RP_MIP_ACCESS:
case RP_IPC_RAW_COMMAND:
tmp_msg->status = scu_ipc_raw_command(tx_msg);
break;
case RP_IPC_SIMPLE_COMMAND:
tmp_msg->status = scu_ipc_simple_command(tx_msg);
break;
case RP_IPC_UTIL:
tmp_msg->status = scu_ipc_util_command(tx_msg);
break;
case RP_FW_ACCESS:
tmp_msg->status = scu_ipc_fw_command(tx_msg);
break;
case RP_VRTC:
tmp_msg->status = scu_ipc_vrtc_command(tx_msg);
break;
case RP_FW_LOGGING:
tmp_msg->status = scu_ipc_fw_logging_command(tx_msg);
break;
default:
tmp_msg->status = 0;
pr_info("Command %x not supported yet\n", tx_hdr->dst);
break;
};
/* prepare rx buffer, switch src and dst */
tmp_hdr->src = tx_hdr->dst;
tmp_hdr->dst = tx_hdr->src;
tmp_hdr->flags = tx_hdr->flags;
tmp_hdr->len = sizeof(struct rx_ipc_msg);
return ret;
}
/* kick a virtqueue */
static void intel_rproc_scu_kick(struct rproc *rproc, int vqid)
{
int idx;
int ret;
struct intel_mid_rproc *iproc;
struct rproc_vdev *rvdev;
struct device *dev = rproc->dev.parent;
static unsigned long ns_info_all_received;
iproc = (struct intel_mid_rproc *)rproc->priv;
/*
* Remote processor virtqueue being kicked.
* This part simulates remote processor handling messages.
*/
idx = find_vring_index(rproc, vqid, VIRTIO_ID_RPMSG);
switch (idx) {
case RX_VRING:
if (iproc->ns_enabled && !ns_info_all_received) {
/* push messages with ns_info for ALL available
name services in the list (nslist) into
rx buffers. */
list_for_each_entry_continue(iproc->ns_info,
&nslist->list, node) {
ret = intel_mid_rproc_ns_handle(iproc,
iproc->ns_info);
if (ret) {
dev_err(dev, "ns handle error\n");
return;
}
}
ns_info_all_received = 1;
intel_mid_rproc_vq_interrupt(rproc, vqid);
}
break;
case TX_VRING:
dev_dbg(dev, "remote processor got the message ...\n");
intel_mid_rproc_msg_handle(iproc);
intel_mid_rproc_vq_interrupt(rproc, vqid);
/*
* After remoteproc handles the message, it calls
* the receive callback.
* TODO: replace this part with real remote processor
* operation.
*/
rvdev = find_rvdev(rproc, VIRTIO_ID_RPMSG);
if (rvdev)
intel_mid_rproc_vq_interrupt(rproc,
rvdev->vring[RX_VRING].notifyid);
else
WARN(1, "%s: can't find given rproc state\n", __func__);
break;
default:
dev_err(dev, "invalid vring index\n");
break;
}
}
/* power up the remote processor */
static int intel_rproc_scu_start(struct rproc *rproc)
{
struct intel_mid_rproc *iproc;
pr_info("Started intel scu remote processor\n");
iproc = (struct intel_mid_rproc *)rproc->priv;
intel_mid_rproc_vring_init(rproc, &iproc->rx_vring, RX_VRING);
intel_mid_rproc_vring_init(rproc, &iproc->tx_vring, TX_VRING);
return 0;
}
/* power off the remote processor */
static int intel_rproc_scu_stop(struct rproc *rproc)
{
pr_info("Stopped intel scu remote processor\n");
return 0;
}
static struct rproc_ops intel_rproc_scu_ops = {
.start = intel_rproc_scu_start,
.stop = intel_rproc_scu_stop,
.kick = intel_rproc_scu_kick,
};
static int intel_rproc_scu_probe(struct platform_device *pdev)
{
struct intel_mid_rproc_pdata *pdata = pdev->dev.platform_data;
struct intel_mid_rproc *iproc;
struct rproc *rproc;
int ret;
ret = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32));
if (ret) {
dev_err(pdev->dev.parent, "dma_set_coherent_mask: %d\n", ret);
return ret;
}
rproc = rproc_alloc(&pdev->dev, pdata->name, &intel_rproc_scu_ops,
pdata->firmware, sizeof(*iproc));
if (!rproc)
return -ENOMEM;
iproc = rproc->priv;
iproc->rproc = rproc;
nslist = pdata->nslist;
platform_set_drvdata(pdev, rproc);
ret = rproc_add(rproc);
if (ret)
goto free_rproc;
/*
* Temporarily follow the rproc framework to load firmware
* TODO: modify remoteproc code according to X86 architecture
*/
if (0 == wait_for_completion_timeout(&rproc->firmware_loading_complete,
RPROC_FW_LOADING_TIMEOUT)) {
dev_err(pdev->dev.parent, "fw loading not complete\n");
goto free_rproc;
}
/* Initialize intel_rproc_scu private data */
strncpy(iproc->name, pdev->id_entry->name, sizeof(iproc->name) - 1);
iproc->type = pdev->id_entry->driver_data;
iproc->r_vring_last_used = 0;
iproc->s_vring_last_used = 0;
iproc->ns_enabled = true;
iproc->rproc_rpmsg_handle = scu_ipc_rpmsg_handle;
iproc->ns_info = list_entry(&nslist->list,
struct rpmsg_ns_info, node);
return 0;
free_rproc:
rproc_put(rproc);
return ret;
}
static int intel_rproc_scu_remove(struct platform_device *pdev)
{
struct rproc *rproc = platform_get_drvdata(pdev);
if (nslist)
rpmsg_ns_del_list(nslist);
rproc_del(rproc);
rproc_put(rproc);
return 0;
}
static const struct platform_device_id intel_rproc_scu_id_table[] = {
{ "intel_rproc_scu", RPROC_SCU },
{ },
};
static struct platform_driver intel_rproc_scu_driver = {
.probe = intel_rproc_scu_probe,
.remove = intel_rproc_scu_remove,
.driver = {
.name = "intel_rproc_scu",
.owner = THIS_MODULE,
},
.id_table = intel_rproc_scu_id_table,
};
static int __init intel_rproc_scu_init(void)
{
return platform_driver_register(&intel_rproc_scu_driver);
}
static void __exit intel_rproc_scu_exit(void)
{
platform_driver_unregister(&intel_rproc_scu_driver);
}
subsys_initcall(intel_rproc_scu_init);
module_exit(intel_rproc_scu_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Ning Li<ning.li@intel.com>");
MODULE_DESCRIPTION("INTEL MID Remoteproc Core driver");