445 lines
10 KiB
C
445 lines
10 KiB
C
/*
|
|
* 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");
|