mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-21 16:29:59 +00:00
![Philipp Zabel](/assets/img/avatar_default.png)
Commit a53e35db70
("reset: Ensure drivers are explicit when requesting
reset lines") started to transition the reset control request API calls
to explicitly state whether the driver needs exclusive or shared reset
control behavior. Convert all drivers requesting exclusive resets to the
explicit API call so the temporary transition helpers can be removed.
No functional changes.
Cc: Patrice Chotard <patrice.chotard@st.com>
Cc: Ohad Ben-Cohen <ohad@wizery.com>
Cc: Bjorn Andersson <bjorn.andersson@linaro.org>
Cc: linux-remoteproc@vger.kernel.org
Signed-off-by: Philipp Zabel <p.zabel@pengutronix.de>
Signed-off-by: Bjorn Andersson <bjorn.andersson@linaro.org>
412 lines
9.9 KiB
C
412 lines
9.9 KiB
C
/*
|
|
* ST's Remote Processor Control Driver
|
|
*
|
|
* Copyright (C) 2015 STMicroelectronics - All Rights Reserved
|
|
*
|
|
* Author: Ludovic Barre <ludovic.barre@st.com>
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/err.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/mailbox_client.h>
|
|
#include <linux/mfd/syscon.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/of_reserved_mem.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/remoteproc.h>
|
|
#include <linux/reset.h>
|
|
|
|
#include "remoteproc_internal.h"
|
|
|
|
#define ST_RPROC_VQ0 0
|
|
#define ST_RPROC_VQ1 1
|
|
#define ST_RPROC_MAX_VRING 2
|
|
|
|
#define MBOX_RX 0
|
|
#define MBOX_TX 1
|
|
#define MBOX_MAX 2
|
|
|
|
struct st_rproc_config {
|
|
bool sw_reset;
|
|
bool pwr_reset;
|
|
unsigned long bootaddr_mask;
|
|
};
|
|
|
|
struct st_rproc {
|
|
struct st_rproc_config *config;
|
|
struct reset_control *sw_reset;
|
|
struct reset_control *pwr_reset;
|
|
struct clk *clk;
|
|
u32 clk_rate;
|
|
struct regmap *boot_base;
|
|
u32 boot_offset;
|
|
struct mbox_chan *mbox_chan[ST_RPROC_MAX_VRING * MBOX_MAX];
|
|
struct mbox_client mbox_client_vq0;
|
|
struct mbox_client mbox_client_vq1;
|
|
};
|
|
|
|
static void st_rproc_mbox_callback(struct device *dev, u32 msg)
|
|
{
|
|
struct rproc *rproc = dev_get_drvdata(dev);
|
|
|
|
if (rproc_vq_interrupt(rproc, msg) == IRQ_NONE)
|
|
dev_dbg(dev, "no message was found in vqid %d\n", msg);
|
|
}
|
|
|
|
static
|
|
void st_rproc_mbox_callback_vq0(struct mbox_client *mbox_client, void *data)
|
|
{
|
|
st_rproc_mbox_callback(mbox_client->dev, 0);
|
|
}
|
|
|
|
static
|
|
void st_rproc_mbox_callback_vq1(struct mbox_client *mbox_client, void *data)
|
|
{
|
|
st_rproc_mbox_callback(mbox_client->dev, 1);
|
|
}
|
|
|
|
static void st_rproc_kick(struct rproc *rproc, int vqid)
|
|
{
|
|
struct st_rproc *ddata = rproc->priv;
|
|
struct device *dev = rproc->dev.parent;
|
|
int ret;
|
|
|
|
/* send the index of the triggered virtqueue in the mailbox payload */
|
|
if (WARN_ON(vqid >= ST_RPROC_MAX_VRING))
|
|
return;
|
|
|
|
ret = mbox_send_message(ddata->mbox_chan[vqid * MBOX_MAX + MBOX_TX],
|
|
(void *)&vqid);
|
|
if (ret < 0)
|
|
dev_err(dev, "failed to send message via mbox: %d\n", ret);
|
|
}
|
|
|
|
static int st_rproc_start(struct rproc *rproc)
|
|
{
|
|
struct st_rproc *ddata = rproc->priv;
|
|
int err;
|
|
|
|
regmap_update_bits(ddata->boot_base, ddata->boot_offset,
|
|
ddata->config->bootaddr_mask, rproc->bootaddr);
|
|
|
|
err = clk_enable(ddata->clk);
|
|
if (err) {
|
|
dev_err(&rproc->dev, "Failed to enable clock\n");
|
|
return err;
|
|
}
|
|
|
|
if (ddata->config->sw_reset) {
|
|
err = reset_control_deassert(ddata->sw_reset);
|
|
if (err) {
|
|
dev_err(&rproc->dev, "Failed to deassert S/W Reset\n");
|
|
goto sw_reset_fail;
|
|
}
|
|
}
|
|
|
|
if (ddata->config->pwr_reset) {
|
|
err = reset_control_deassert(ddata->pwr_reset);
|
|
if (err) {
|
|
dev_err(&rproc->dev, "Failed to deassert Power Reset\n");
|
|
goto pwr_reset_fail;
|
|
}
|
|
}
|
|
|
|
dev_info(&rproc->dev, "Started from 0x%x\n", rproc->bootaddr);
|
|
|
|
return 0;
|
|
|
|
|
|
pwr_reset_fail:
|
|
if (ddata->config->pwr_reset)
|
|
reset_control_assert(ddata->sw_reset);
|
|
sw_reset_fail:
|
|
clk_disable(ddata->clk);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int st_rproc_stop(struct rproc *rproc)
|
|
{
|
|
struct st_rproc *ddata = rproc->priv;
|
|
int sw_err = 0, pwr_err = 0;
|
|
|
|
if (ddata->config->sw_reset) {
|
|
sw_err = reset_control_assert(ddata->sw_reset);
|
|
if (sw_err)
|
|
dev_err(&rproc->dev, "Failed to assert S/W Reset\n");
|
|
}
|
|
|
|
if (ddata->config->pwr_reset) {
|
|
pwr_err = reset_control_assert(ddata->pwr_reset);
|
|
if (pwr_err)
|
|
dev_err(&rproc->dev, "Failed to assert Power Reset\n");
|
|
}
|
|
|
|
clk_disable(ddata->clk);
|
|
|
|
return sw_err ?: pwr_err;
|
|
}
|
|
|
|
static const struct rproc_ops st_rproc_ops = {
|
|
.kick = st_rproc_kick,
|
|
.start = st_rproc_start,
|
|
.stop = st_rproc_stop,
|
|
};
|
|
|
|
/*
|
|
* Fetch state of the processor: 0 is off, 1 is on.
|
|
*/
|
|
static int st_rproc_state(struct platform_device *pdev)
|
|
{
|
|
struct rproc *rproc = platform_get_drvdata(pdev);
|
|
struct st_rproc *ddata = rproc->priv;
|
|
int reset_sw = 0, reset_pwr = 0;
|
|
|
|
if (ddata->config->sw_reset)
|
|
reset_sw = reset_control_status(ddata->sw_reset);
|
|
|
|
if (ddata->config->pwr_reset)
|
|
reset_pwr = reset_control_status(ddata->pwr_reset);
|
|
|
|
if (reset_sw < 0 || reset_pwr < 0)
|
|
return -EINVAL;
|
|
|
|
return !reset_sw && !reset_pwr;
|
|
}
|
|
|
|
static const struct st_rproc_config st40_rproc_cfg = {
|
|
.sw_reset = true,
|
|
.pwr_reset = true,
|
|
.bootaddr_mask = GENMASK(28, 1),
|
|
};
|
|
|
|
static const struct st_rproc_config st231_rproc_cfg = {
|
|
.sw_reset = true,
|
|
.pwr_reset = false,
|
|
.bootaddr_mask = GENMASK(31, 6),
|
|
};
|
|
|
|
static const struct of_device_id st_rproc_match[] = {
|
|
{ .compatible = "st,st40-rproc", .data = &st40_rproc_cfg },
|
|
{ .compatible = "st,st231-rproc", .data = &st231_rproc_cfg },
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, st_rproc_match);
|
|
|
|
static int st_rproc_parse_dt(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct rproc *rproc = platform_get_drvdata(pdev);
|
|
struct st_rproc *ddata = rproc->priv;
|
|
struct device_node *np = dev->of_node;
|
|
int err;
|
|
|
|
if (ddata->config->sw_reset) {
|
|
ddata->sw_reset = devm_reset_control_get_exclusive(dev,
|
|
"sw_reset");
|
|
if (IS_ERR(ddata->sw_reset)) {
|
|
dev_err(dev, "Failed to get S/W Reset\n");
|
|
return PTR_ERR(ddata->sw_reset);
|
|
}
|
|
}
|
|
|
|
if (ddata->config->pwr_reset) {
|
|
ddata->pwr_reset = devm_reset_control_get_exclusive(dev,
|
|
"pwr_reset");
|
|
if (IS_ERR(ddata->pwr_reset)) {
|
|
dev_err(dev, "Failed to get Power Reset\n");
|
|
return PTR_ERR(ddata->pwr_reset);
|
|
}
|
|
}
|
|
|
|
ddata->clk = devm_clk_get(dev, NULL);
|
|
if (IS_ERR(ddata->clk)) {
|
|
dev_err(dev, "Failed to get clock\n");
|
|
return PTR_ERR(ddata->clk);
|
|
}
|
|
|
|
err = of_property_read_u32(np, "clock-frequency", &ddata->clk_rate);
|
|
if (err) {
|
|
dev_err(dev, "failed to get clock frequency\n");
|
|
return err;
|
|
}
|
|
|
|
ddata->boot_base = syscon_regmap_lookup_by_phandle(np, "st,syscfg");
|
|
if (IS_ERR(ddata->boot_base)) {
|
|
dev_err(dev, "Boot base not found\n");
|
|
return PTR_ERR(ddata->boot_base);
|
|
}
|
|
|
|
err = of_property_read_u32_index(np, "st,syscfg", 1,
|
|
&ddata->boot_offset);
|
|
if (err) {
|
|
dev_err(dev, "Boot offset not found\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
err = of_reserved_mem_device_init(dev);
|
|
if (err) {
|
|
dev_err(dev, "Failed to obtain shared memory\n");
|
|
return err;
|
|
}
|
|
|
|
err = clk_prepare(ddata->clk);
|
|
if (err)
|
|
dev_err(dev, "failed to get clock\n");
|
|
|
|
return err;
|
|
}
|
|
|
|
static int st_rproc_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
const struct of_device_id *match;
|
|
struct st_rproc *ddata;
|
|
struct device_node *np = dev->of_node;
|
|
struct rproc *rproc;
|
|
struct mbox_chan *chan;
|
|
int enabled;
|
|
int ret, i;
|
|
|
|
match = of_match_device(st_rproc_match, dev);
|
|
if (!match || !match->data) {
|
|
dev_err(dev, "No device match found\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
rproc = rproc_alloc(dev, np->name, &st_rproc_ops, NULL, sizeof(*ddata));
|
|
if (!rproc)
|
|
return -ENOMEM;
|
|
|
|
rproc->has_iommu = false;
|
|
ddata = rproc->priv;
|
|
ddata->config = (struct st_rproc_config *)match->data;
|
|
|
|
platform_set_drvdata(pdev, rproc);
|
|
|
|
ret = st_rproc_parse_dt(pdev);
|
|
if (ret)
|
|
goto free_rproc;
|
|
|
|
enabled = st_rproc_state(pdev);
|
|
if (enabled < 0) {
|
|
ret = enabled;
|
|
goto free_clk;
|
|
}
|
|
|
|
if (enabled) {
|
|
atomic_inc(&rproc->power);
|
|
rproc->state = RPROC_RUNNING;
|
|
} else {
|
|
clk_set_rate(ddata->clk, ddata->clk_rate);
|
|
}
|
|
|
|
if (of_get_property(np, "mbox-names", NULL)) {
|
|
ddata->mbox_client_vq0.dev = dev;
|
|
ddata->mbox_client_vq0.tx_done = NULL;
|
|
ddata->mbox_client_vq0.tx_block = false;
|
|
ddata->mbox_client_vq0.knows_txdone = false;
|
|
ddata->mbox_client_vq0.rx_callback = st_rproc_mbox_callback_vq0;
|
|
|
|
ddata->mbox_client_vq1.dev = dev;
|
|
ddata->mbox_client_vq1.tx_done = NULL;
|
|
ddata->mbox_client_vq1.tx_block = false;
|
|
ddata->mbox_client_vq1.knows_txdone = false;
|
|
ddata->mbox_client_vq1.rx_callback = st_rproc_mbox_callback_vq1;
|
|
|
|
/*
|
|
* To control a co-processor without IPC mechanism.
|
|
* This driver can be used without mbox and rpmsg.
|
|
*/
|
|
chan = mbox_request_channel_byname(&ddata->mbox_client_vq0, "vq0_rx");
|
|
if (IS_ERR(chan)) {
|
|
dev_err(&rproc->dev, "failed to request mbox chan 0\n");
|
|
ret = PTR_ERR(chan);
|
|
goto free_clk;
|
|
}
|
|
ddata->mbox_chan[ST_RPROC_VQ0 * MBOX_MAX + MBOX_RX] = chan;
|
|
|
|
chan = mbox_request_channel_byname(&ddata->mbox_client_vq0, "vq0_tx");
|
|
if (IS_ERR(chan)) {
|
|
dev_err(&rproc->dev, "failed to request mbox chan 0\n");
|
|
ret = PTR_ERR(chan);
|
|
goto free_mbox;
|
|
}
|
|
ddata->mbox_chan[ST_RPROC_VQ0 * MBOX_MAX + MBOX_TX] = chan;
|
|
|
|
chan = mbox_request_channel_byname(&ddata->mbox_client_vq1, "vq1_rx");
|
|
if (IS_ERR(chan)) {
|
|
dev_err(&rproc->dev, "failed to request mbox chan 1\n");
|
|
ret = PTR_ERR(chan);
|
|
goto free_mbox;
|
|
}
|
|
ddata->mbox_chan[ST_RPROC_VQ1 * MBOX_MAX + MBOX_RX] = chan;
|
|
|
|
chan = mbox_request_channel_byname(&ddata->mbox_client_vq1, "vq1_tx");
|
|
if (IS_ERR(chan)) {
|
|
dev_err(&rproc->dev, "failed to request mbox chan 1\n");
|
|
ret = PTR_ERR(chan);
|
|
goto free_mbox;
|
|
}
|
|
ddata->mbox_chan[ST_RPROC_VQ1 * MBOX_MAX + MBOX_TX] = chan;
|
|
}
|
|
|
|
ret = rproc_add(rproc);
|
|
if (ret)
|
|
goto free_mbox;
|
|
|
|
return 0;
|
|
|
|
free_mbox:
|
|
for (i = 0; i < ST_RPROC_MAX_VRING * MBOX_MAX; i++)
|
|
mbox_free_channel(ddata->mbox_chan[i]);
|
|
free_clk:
|
|
clk_unprepare(ddata->clk);
|
|
free_rproc:
|
|
rproc_free(rproc);
|
|
return ret;
|
|
}
|
|
|
|
static int st_rproc_remove(struct platform_device *pdev)
|
|
{
|
|
struct rproc *rproc = platform_get_drvdata(pdev);
|
|
struct st_rproc *ddata = rproc->priv;
|
|
int i;
|
|
|
|
rproc_del(rproc);
|
|
|
|
clk_disable_unprepare(ddata->clk);
|
|
|
|
of_reserved_mem_device_release(&pdev->dev);
|
|
|
|
for (i = 0; i < ST_RPROC_MAX_VRING * MBOX_MAX; i++)
|
|
mbox_free_channel(ddata->mbox_chan[i]);
|
|
|
|
rproc_free(rproc);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver st_rproc_driver = {
|
|
.probe = st_rproc_probe,
|
|
.remove = st_rproc_remove,
|
|
.driver = {
|
|
.name = "st-rproc",
|
|
.of_match_table = of_match_ptr(st_rproc_match),
|
|
},
|
|
};
|
|
module_platform_driver(st_rproc_driver);
|
|
|
|
MODULE_DESCRIPTION("ST Remote Processor Control Driver");
|
|
MODULE_AUTHOR("Ludovic Barre <ludovic.barre@st.com>");
|
|
MODULE_LICENSE("GPL v2");
|