745 lines
17 KiB
C
745 lines
17 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (C) STMicroelectronics 2018 - All Rights Reserved
|
|
* Author: Fabien Dessenne <fabien.dessenne@st.com> for STMicroelectronics.
|
|
*/
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/clk-provider.h>
|
|
#include <linux/component.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_irq.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/regulator/consumer.h>
|
|
#include <linux/remoteproc.h>
|
|
|
|
#include "rproc_srm_core.h"
|
|
|
|
struct rproc_srm_clk_info {
|
|
struct list_head list;
|
|
unsigned int index;
|
|
struct clk *clk;
|
|
const char *name;
|
|
bool parent_enabled;
|
|
};
|
|
|
|
struct rproc_srm_regu_info {
|
|
struct list_head list;
|
|
unsigned int index;
|
|
struct regulator *regu;
|
|
const char *name;
|
|
bool enabled;
|
|
};
|
|
|
|
struct rproc_srm_irq_info {
|
|
struct list_head list;
|
|
unsigned int index;
|
|
char *name;
|
|
int irq;
|
|
bool enabled;
|
|
};
|
|
|
|
struct rproc_srm_dev {
|
|
struct device *dev;
|
|
struct rproc_srm_core *core;
|
|
struct notifier_block nb;
|
|
bool early_boot;
|
|
|
|
struct list_head clk_list_head;
|
|
struct list_head regu_list_head;
|
|
struct list_head irq_list_head;
|
|
};
|
|
|
|
/* Irqs */
|
|
static void rproc_srm_dev_irqs_put(struct rproc_srm_dev *rproc_srm_dev)
|
|
{
|
|
struct device *dev = rproc_srm_dev->dev;
|
|
struct rproc_srm_irq_info *i, *tmp;
|
|
|
|
list_for_each_entry_safe(i, tmp, &rproc_srm_dev->irq_list_head, list) {
|
|
devm_free_irq(dev, i->irq, NULL);
|
|
dev_dbg(dev, "Put irq %d (%s)\n", i->irq, i->name);
|
|
list_del(&i->list);
|
|
}
|
|
}
|
|
|
|
static irqreturn_t rproc_srm_dev_irq_handler(int irq, void *dev)
|
|
{
|
|
dev_warn(dev, "Spurious interrupt\n");
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int rproc_srm_dev_irqs_get(struct rproc_srm_dev *rproc_srm_dev)
|
|
{
|
|
struct device *dev = rproc_srm_dev->dev;
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
struct device_node *np = dev->of_node;
|
|
struct rproc_srm_irq_info *info;
|
|
const char *name;
|
|
int nr, ret, irq;
|
|
unsigned int i;
|
|
|
|
if (!np)
|
|
return 0;
|
|
|
|
nr = platform_irq_count(pdev);
|
|
if (!nr)
|
|
return 0;
|
|
|
|
if (rproc_srm_dev->early_boot)
|
|
/*
|
|
* Do not overwrite the irq configuration.
|
|
* No need to parse irq from DT since the resource manager does
|
|
* not offer any service to update the irq config.
|
|
*/
|
|
return 0;
|
|
|
|
for (i = 0; i < nr; i++) {
|
|
info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
|
|
if (!info) {
|
|
ret = -ENOMEM;
|
|
goto err;
|
|
}
|
|
|
|
irq = platform_get_irq(pdev, i);
|
|
if (irq <= 0) {
|
|
ret = irq;
|
|
dev_err(dev, "Failed to get irq (%d)\n", ret);
|
|
goto err;
|
|
}
|
|
|
|
info->irq = irq;
|
|
|
|
/* Register a dummy irq handleras not used by Linux */
|
|
ret = devm_request_irq(dev, info->irq,
|
|
rproc_srm_dev_irq_handler, 0,
|
|
dev_name(dev), NULL);
|
|
if (ret) {
|
|
dev_err(dev, "Failed to request irq (%d)\n", ret);
|
|
goto err;
|
|
}
|
|
|
|
/*
|
|
* Disable IRQ. Since it is used by the remote processor we
|
|
* must not use the 'irq lazy disable' optimization
|
|
*/
|
|
irq_set_status_flags(info->irq, IRQ_DISABLE_UNLAZY);
|
|
disable_irq(info->irq);
|
|
|
|
/* Note: "interrupt-names" is optional */
|
|
if (!of_property_read_string_index(np, "interrupt-names", i,
|
|
&name))
|
|
info->name = devm_kstrdup(dev, name, GFP_KERNEL);
|
|
else
|
|
info->name = devm_kstrdup(dev, "", GFP_KERNEL);
|
|
|
|
info->index = i;
|
|
|
|
list_add_tail(&info->list, &rproc_srm_dev->irq_list_head);
|
|
dev_dbg(dev, "Got irq %d (%s)\n", info->irq, info->name);
|
|
}
|
|
|
|
return 0;
|
|
|
|
err:
|
|
rproc_srm_dev_irqs_put(rproc_srm_dev);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Clocks */
|
|
static void rproc_srm_dev_clocks_unsetup(struct rproc_srm_dev *rproc_srm_dev)
|
|
{
|
|
struct rproc_srm_clk_info *c;
|
|
|
|
list_for_each_entry(c, &rproc_srm_dev->clk_list_head, list) {
|
|
if (!c->parent_enabled)
|
|
continue;
|
|
|
|
clk_disable_unprepare(clk_get_parent(c->clk));
|
|
c->parent_enabled = false;
|
|
dev_dbg(rproc_srm_dev->dev, "clk %d (%s) unsetup\n",
|
|
c->index, c->name);
|
|
}
|
|
}
|
|
|
|
static int rproc_srm_dev_clocks_setup(struct rproc_srm_dev *rproc_srm_dev)
|
|
{
|
|
struct rproc_srm_clk_info *c;
|
|
int ret;
|
|
|
|
/*
|
|
* Prepare and enable the parent clocks.
|
|
* Since the clock tree is under the exclusive control of the master
|
|
* processor, we need to configure the clock tree of the targeted clock.
|
|
* We do not want to enable the clock itself, which is under the
|
|
* responsibility of the remote processor.
|
|
* Hence we prepare and enable the parent clock.
|
|
*/
|
|
|
|
list_for_each_entry(c, &rproc_srm_dev->clk_list_head, list) {
|
|
if (c->parent_enabled)
|
|
continue;
|
|
|
|
ret = clk_prepare_enable(clk_get_parent(c->clk));
|
|
if (ret) {
|
|
dev_err(rproc_srm_dev->dev,
|
|
"clk %d (%s) parent enable failed\n",
|
|
c->index, c->name);
|
|
rproc_srm_dev_clocks_unsetup(rproc_srm_dev);
|
|
return ret;
|
|
}
|
|
c->parent_enabled = true;
|
|
dev_dbg(rproc_srm_dev->dev, "clk %d (%s) parent enabled\n",
|
|
c->index, c->name);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct rproc_srm_clk_info
|
|
*rproc_srm_dev_clock_find(struct rproc_srm_dev *rproc_srm_dev,
|
|
struct clock_cfg *cfg)
|
|
{
|
|
struct rproc_srm_clk_info *ci;
|
|
|
|
/* Search by index (if valid value) otherwise search by name */
|
|
list_for_each_entry(ci, &rproc_srm_dev->clk_list_head, list) {
|
|
if (cfg->index != U32_MAX) {
|
|
if (ci->index == cfg->index)
|
|
return ci;
|
|
} else {
|
|
if (!strcmp(ci->name, cfg->name))
|
|
return ci;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int rproc_srm_dev_clock_set_cfg(struct rproc_srm_dev *rproc_srm_dev,
|
|
struct clock_cfg *cfg)
|
|
{
|
|
struct rproc_srm_clk_info *c;
|
|
struct device *dev = rproc_srm_dev->dev;
|
|
int ret;
|
|
|
|
c = rproc_srm_dev_clock_find(rproc_srm_dev, cfg);
|
|
|
|
if (!c) {
|
|
dev_err(dev, "unknown clock (id %d)\n", cfg->index);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (cfg->rate && clk_get_rate(c->clk) != cfg->rate) {
|
|
ret = clk_set_rate(c->clk, cfg->rate);
|
|
if (ret) {
|
|
dev_err(dev, "clk set rate failed\n");
|
|
return ret;
|
|
}
|
|
|
|
dev_dbg(dev, "clk %d (%s) rate = %d\n", c->index, c->name,
|
|
cfg->rate);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rproc_srm_dev_clock_get_cfg(struct rproc_srm_dev *rproc_srm_dev,
|
|
struct clock_cfg *cfg)
|
|
{
|
|
struct rproc_srm_clk_info *c;
|
|
|
|
c = rproc_srm_dev_clock_find(rproc_srm_dev, cfg);
|
|
if (!c) {
|
|
dev_err(rproc_srm_dev->dev, "unknown clock (%d)\n", cfg->index);
|
|
return -EINVAL;
|
|
}
|
|
|
|
strlcpy(cfg->name, c->name, sizeof(cfg->name));
|
|
cfg->index = c->index;
|
|
cfg->rate = (u32)clk_get_rate(c->clk);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void rproc_srm_dev_clocks_put(struct rproc_srm_dev *rproc_srm_dev)
|
|
{
|
|
struct device *dev = rproc_srm_dev->dev;
|
|
struct rproc_srm_clk_info *c, *tmp;
|
|
|
|
list_for_each_entry_safe(c, tmp, &rproc_srm_dev->clk_list_head, list) {
|
|
clk_put(c->clk);
|
|
dev_dbg(dev, "put clock %d (%s)\n", c->index, c->name);
|
|
list_del(&c->list);
|
|
}
|
|
}
|
|
|
|
static int rproc_srm_dev_clocks_get(struct rproc_srm_dev *rproc_srm_dev)
|
|
{
|
|
struct device *dev = rproc_srm_dev->dev;
|
|
struct device_node *np = dev->of_node;
|
|
struct rproc_srm_clk_info *c;
|
|
const char *name;
|
|
int nb_c, ret;
|
|
unsigned int i;
|
|
|
|
if (!np)
|
|
return 0;
|
|
|
|
nb_c = of_clk_get_parent_count(np);
|
|
if (!nb_c)
|
|
return 0;
|
|
|
|
for (i = 0; i < nb_c; i++) {
|
|
c = devm_kzalloc(dev, sizeof(*c), GFP_KERNEL);
|
|
if (!c) {
|
|
ret = -ENOMEM;
|
|
goto err;
|
|
}
|
|
|
|
c->clk = of_clk_get(np, i);
|
|
if (IS_ERR(c->clk)) {
|
|
dev_err(dev, "clock %d KO (%ld)\n", i,
|
|
PTR_ERR(c->clk));
|
|
ret = -ENOMEM;
|
|
goto err;
|
|
}
|
|
|
|
/* Note: "clock-names" is optional */
|
|
if (!of_property_read_string_index(np, "clock-names", i,
|
|
&name))
|
|
c->name = devm_kstrdup(dev, name, GFP_KERNEL);
|
|
else
|
|
c->name = devm_kstrdup(dev, "", GFP_KERNEL);
|
|
|
|
c->index = i;
|
|
|
|
list_add_tail(&c->list, &rproc_srm_dev->clk_list_head);
|
|
dev_dbg(dev, "got clock %d (%s)\n", c->index, c->name);
|
|
}
|
|
|
|
return 0;
|
|
|
|
err:
|
|
rproc_srm_dev_clocks_put(rproc_srm_dev);
|
|
return ret;
|
|
}
|
|
|
|
/* Regulators */
|
|
static void rproc_srm_dev_regus_unsetup(struct rproc_srm_dev *rproc_srm_dev)
|
|
{
|
|
struct rproc_srm_regu_info *r;
|
|
struct device *dev = rproc_srm_dev->dev;
|
|
|
|
list_for_each_entry(r, &rproc_srm_dev->regu_list_head, list) {
|
|
if (!r->enabled)
|
|
continue;
|
|
|
|
if (regulator_disable(r->regu)) {
|
|
dev_warn(dev, "regu %d disabled failed\n", r->index);
|
|
continue;
|
|
}
|
|
|
|
r->enabled = false;
|
|
dev_dbg(dev, "regu %d (%s) disabled\n", r->index, r->name);
|
|
}
|
|
}
|
|
|
|
static int rproc_srm_dev_regus_setup(struct rproc_srm_dev *rproc_srm_dev)
|
|
{
|
|
struct rproc_srm_regu_info *r;
|
|
int ret;
|
|
|
|
/* Enable all the regulators */
|
|
list_for_each_entry(r, &rproc_srm_dev->regu_list_head, list) {
|
|
if (r->enabled)
|
|
continue;
|
|
|
|
/* in early_boot mode sync on hw */
|
|
if (rproc_srm_dev->early_boot && !regulator_is_enabled(r->regu))
|
|
continue;
|
|
|
|
ret = regulator_enable(r->regu);
|
|
if (ret) {
|
|
dev_err(rproc_srm_dev->dev, "regu %d (%s) failed\n",
|
|
r->index, r->name);
|
|
rproc_srm_dev_regus_unsetup(rproc_srm_dev);
|
|
return ret;
|
|
}
|
|
r->enabled = true;
|
|
dev_dbg(rproc_srm_dev->dev, "regu %d (%s) enabled\n",
|
|
r->index, r->name);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct rproc_srm_regu_info
|
|
*rproc_srm_dev_regu_find(struct rproc_srm_dev *rproc_srm_dev,
|
|
struct regu_cfg *cfg)
|
|
{
|
|
struct rproc_srm_regu_info *ri;
|
|
|
|
list_for_each_entry(ri, &rproc_srm_dev->regu_list_head, list) {
|
|
if (cfg->index != U32_MAX) {
|
|
if (ri->index == cfg->index)
|
|
return ri;
|
|
} else {
|
|
if (!strcmp(ri->name, cfg->name))
|
|
return ri;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int rproc_srm_dev_regu_set_cfg(struct rproc_srm_dev *rproc_srm_dev,
|
|
struct regu_cfg *cfg)
|
|
{
|
|
struct rproc_srm_regu_info *r;
|
|
struct device *dev = rproc_srm_dev->dev;
|
|
int ret;
|
|
|
|
r = rproc_srm_dev_regu_find(rproc_srm_dev, cfg);
|
|
if (!r) {
|
|
dev_err(dev, "unknown regu (%d)\n", cfg->index);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!r->enabled && cfg->enable) {
|
|
ret = regulator_enable(r->regu);
|
|
if (ret) {
|
|
dev_err(dev, "regu %d enable failed\n", r->index);
|
|
return ret;
|
|
}
|
|
r->enabled = true;
|
|
dev_dbg(dev, "regu %d (%s) enabled\n", r->index, r->name);
|
|
} else if (r->enabled && !cfg->enable) {
|
|
ret = regulator_disable(r->regu);
|
|
if (ret) {
|
|
dev_err(dev, "regu %d disable failed\n", r->index);
|
|
return ret;
|
|
}
|
|
r->enabled = false;
|
|
dev_dbg(dev, "regu %d (%s) disabled\n", r->index, r->name);
|
|
}
|
|
|
|
if (cfg->min_voltage_mv || cfg->max_voltage_mv) {
|
|
ret = regulator_set_voltage(r->regu, cfg->min_voltage_mv * 1000,
|
|
cfg->max_voltage_mv * 1000);
|
|
if (ret) {
|
|
dev_err(dev, "regu %d set voltage failed\n", r->index);
|
|
return ret;
|
|
}
|
|
|
|
dev_dbg(dev, "regu %d (%s) voltage = [%d - %d] mv\n", r->index,
|
|
r->name, cfg->min_voltage_mv, cfg->max_voltage_mv);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rproc_srm_dev_regu_get_cfg(struct rproc_srm_dev *rproc_srm_dev,
|
|
struct regu_cfg *cfg)
|
|
{
|
|
struct rproc_srm_regu_info *r;
|
|
struct device *dev = rproc_srm_dev->dev;
|
|
int v;
|
|
|
|
r = rproc_srm_dev_regu_find(rproc_srm_dev, cfg);
|
|
if (!r) {
|
|
dev_err(dev, "unknown regu (%d)\n", cfg->index);
|
|
return -EINVAL;
|
|
}
|
|
|
|
strlcpy(cfg->name, r->name, sizeof(cfg->name));
|
|
cfg->index = r->index;
|
|
cfg->enable = r->enabled;
|
|
cfg->min_voltage_mv = 0;
|
|
cfg->max_voltage_mv = 0;
|
|
|
|
v = regulator_get_voltage(r->regu);
|
|
if (v < 0) {
|
|
dev_warn(dev, "cannot get %s voltage\n", r->name);
|
|
cfg->curr_voltage_mv = 0;
|
|
} else {
|
|
cfg->curr_voltage_mv = v / 1000;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void rproc_srm_dev_regus_put(struct rproc_srm_dev *rproc_srm_dev)
|
|
{
|
|
struct device *dev = rproc_srm_dev->dev;
|
|
struct rproc_srm_regu_info *r, *tmp;
|
|
|
|
list_for_each_entry_safe(r, tmp, &rproc_srm_dev->regu_list_head, list) {
|
|
devm_regulator_put(r->regu);
|
|
dev_dbg(dev, "put regu %d (%s)\n", r->index, r->name);
|
|
list_del(&r->list);
|
|
}
|
|
}
|
|
|
|
static int rproc_srm_dev_regus_get(struct rproc_srm_dev *rproc_srm_dev)
|
|
{
|
|
struct device *dev = rproc_srm_dev->dev;
|
|
struct device_node *np = dev->of_node;
|
|
struct property *p;
|
|
const char *n;
|
|
char *name;
|
|
struct rproc_srm_regu_info *r;
|
|
int ret, nb_s = 0;
|
|
|
|
if (!np)
|
|
return 0;
|
|
|
|
for_each_property_of_node(np, p) {
|
|
n = strstr(p->name, "-supply");
|
|
if (!n || n == p->name)
|
|
continue;
|
|
|
|
r = devm_kzalloc(dev, sizeof(*r), GFP_KERNEL);
|
|
if (!r) {
|
|
ret = -ENOMEM;
|
|
goto err_list;
|
|
}
|
|
|
|
name = devm_kstrdup(dev, p->name, GFP_KERNEL);
|
|
name[strlen(p->name) - strlen("-supply")] = '\0';
|
|
r->name = name;
|
|
|
|
r->regu = devm_regulator_get(dev, r->name);
|
|
if (IS_ERR(r->regu)) {
|
|
dev_err(dev, "cannot get regu %s\n", r->name);
|
|
ret = -EINVAL;
|
|
goto err_list;
|
|
}
|
|
|
|
r->index = nb_s++;
|
|
|
|
list_add_tail(&r->list, &rproc_srm_dev->regu_list_head);
|
|
dev_dbg(dev, "got regu %d (%s)\n", r->index, r->name);
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_list:
|
|
rproc_srm_dev_regus_put(rproc_srm_dev);
|
|
return ret;
|
|
}
|
|
|
|
/* Core */
|
|
static int rproc_srm_dev_notify_cb(struct notifier_block *nb, unsigned long evt,
|
|
void *data)
|
|
{
|
|
struct rproc_srm_dev *rproc_srm_dev =
|
|
container_of(nb, struct rproc_srm_dev, nb);
|
|
struct device *dev = rproc_srm_dev->dev;
|
|
struct rpmsg_srm_msg_desc *desc;
|
|
struct rpmsg_srm_msg *i, o;
|
|
int ret = 0;
|
|
|
|
dev_dbg(dev, "%s\n", __func__);
|
|
|
|
desc = (struct rpmsg_srm_msg_desc *)data;
|
|
i = desc->msg;
|
|
o = *i;
|
|
|
|
/* Check if 'device_id' (name / addr ) matches this device */
|
|
if (!strstr(dev_name(dev), i->device_id))
|
|
return NOTIFY_DONE;
|
|
|
|
switch (i->message_type) {
|
|
case RPROC_SRM_MSG_SETCONFIG:
|
|
switch (i->rsc_type) {
|
|
case RPROC_SRM_RSC_CLOCK:
|
|
ret = rproc_srm_dev_clock_set_cfg(rproc_srm_dev,
|
|
&i->clock_cfg);
|
|
if (!ret)
|
|
ret = rproc_srm_dev_clock_get_cfg(rproc_srm_dev,
|
|
&o.clock_cfg);
|
|
break;
|
|
case RPROC_SRM_RSC_REGU:
|
|
ret = rproc_srm_dev_regu_set_cfg(rproc_srm_dev,
|
|
&i->regu_cfg);
|
|
if (!ret)
|
|
ret = rproc_srm_dev_regu_get_cfg(rproc_srm_dev,
|
|
&o.regu_cfg);
|
|
break;
|
|
default:
|
|
dev_warn(dev, "bad rsc type (%d)\n", i->rsc_type);
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
break;
|
|
case RPROC_SRM_MSG_GETCONFIG:
|
|
switch (i->rsc_type) {
|
|
case RPROC_SRM_RSC_CLOCK:
|
|
ret = rproc_srm_dev_clock_get_cfg(rproc_srm_dev,
|
|
&o.clock_cfg);
|
|
break;
|
|
case RPROC_SRM_RSC_REGU:
|
|
ret = rproc_srm_dev_regu_get_cfg(rproc_srm_dev,
|
|
&o.regu_cfg);
|
|
break;
|
|
default:
|
|
dev_warn(dev, "bad rsc type (%d)\n", i->rsc_type);
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
dev_warn(dev, "bad msg type (%d)\n", i->message_type);
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
/* Send return msg */
|
|
if (ret)
|
|
o.message_type = RPROC_SRM_MSG_ERROR;
|
|
|
|
ret = rpmsg_srm_send(desc->ept, &o);
|
|
|
|
return ret ? NOTIFY_BAD : NOTIFY_STOP;
|
|
}
|
|
|
|
static void
|
|
rproc_srm_dev_unbind(struct device *dev, struct device *master, void *data)
|
|
{
|
|
struct rproc_srm_dev *rproc_srm_dev = dev_get_drvdata(dev);
|
|
|
|
dev_dbg(dev, "%s\n", __func__);
|
|
|
|
rproc_srm_dev_regus_unsetup(rproc_srm_dev);
|
|
rproc_srm_dev_clocks_unsetup(rproc_srm_dev);
|
|
|
|
/* For IRQs: nothing to unsetup */
|
|
}
|
|
|
|
static int
|
|
rproc_srm_dev_bind(struct device *dev, struct device *master, void *data)
|
|
{
|
|
struct rproc_srm_dev *rproc_srm_dev = dev_get_drvdata(dev);
|
|
int ret;
|
|
|
|
dev_dbg(dev, "%s\n", __func__);
|
|
|
|
ret = rproc_srm_dev_clocks_setup(rproc_srm_dev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = rproc_srm_dev_regus_setup(rproc_srm_dev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* For IRQs: nothing to setup */
|
|
return 0;
|
|
}
|
|
|
|
static const struct component_ops rproc_srm_dev_ops = {
|
|
.bind = rproc_srm_dev_bind,
|
|
.unbind = rproc_srm_dev_unbind,
|
|
};
|
|
|
|
static int rproc_srm_dev_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct rproc_srm_dev *rproc_srm_dev;
|
|
struct rproc *rproc;
|
|
int ret;
|
|
|
|
dev_dbg(dev, "%s for node %s\n", __func__, dev->of_node->name);
|
|
|
|
rproc_srm_dev = devm_kzalloc(dev, sizeof(struct rproc_srm_dev),
|
|
GFP_KERNEL);
|
|
if (!rproc_srm_dev)
|
|
return -ENOMEM;
|
|
|
|
rproc_srm_dev->dev = dev;
|
|
rproc = (struct rproc *)dev_get_drvdata(dev->parent->parent);
|
|
rproc_srm_dev->early_boot = rproc->early_boot;
|
|
rproc_srm_dev->core = dev_get_drvdata(dev->parent);
|
|
|
|
INIT_LIST_HEAD(&rproc_srm_dev->clk_list_head);
|
|
INIT_LIST_HEAD(&rproc_srm_dev->regu_list_head);
|
|
INIT_LIST_HEAD(&rproc_srm_dev->irq_list_head);
|
|
|
|
/* Get clocks, regu and irqs */
|
|
ret = rproc_srm_dev_clocks_get(rproc_srm_dev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = rproc_srm_dev_regus_get(rproc_srm_dev);
|
|
if (ret)
|
|
goto err_get;
|
|
|
|
ret = rproc_srm_dev_irqs_get(rproc_srm_dev);
|
|
if (ret)
|
|
goto err_get;
|
|
|
|
rproc_srm_dev->nb.notifier_call = rproc_srm_dev_notify_cb;
|
|
ret = rproc_srm_core_register_notifier(rproc_srm_dev->core,
|
|
&rproc_srm_dev->nb);
|
|
if (ret)
|
|
goto err_register;
|
|
|
|
dev_set_drvdata(dev, rproc_srm_dev);
|
|
|
|
return component_add(dev, &rproc_srm_dev_ops);
|
|
|
|
err_register:
|
|
rproc_srm_core_unregister_notifier(rproc_srm_dev->core,
|
|
&rproc_srm_dev->nb);
|
|
err_get:
|
|
rproc_srm_dev_irqs_put(rproc_srm_dev);
|
|
rproc_srm_dev_regus_put(rproc_srm_dev);
|
|
rproc_srm_dev_clocks_put(rproc_srm_dev);
|
|
return ret;
|
|
}
|
|
|
|
static int rproc_srm_dev_remove(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct rproc_srm_dev *rproc_srm_dev = dev_get_drvdata(dev);
|
|
|
|
dev_dbg(dev, "%s\n", __func__);
|
|
|
|
component_del(dev, &rproc_srm_dev_ops);
|
|
|
|
rproc_srm_core_unregister_notifier(rproc_srm_dev->core,
|
|
&rproc_srm_dev->nb);
|
|
|
|
rproc_srm_dev_irqs_put(rproc_srm_dev);
|
|
rproc_srm_dev_regus_put(rproc_srm_dev);
|
|
rproc_srm_dev_clocks_put(rproc_srm_dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id rproc_srm_dev_match[] = {
|
|
{ .compatible = "rproc-srm-dev", },
|
|
{},
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(of, rproc_srm_dev_match);
|
|
|
|
static struct platform_driver rproc_srm_dev_driver = {
|
|
.probe = rproc_srm_dev_probe,
|
|
.remove = rproc_srm_dev_remove,
|
|
.driver = {
|
|
.name = "rproc-srm-dev",
|
|
.of_match_table = of_match_ptr(rproc_srm_dev_match),
|
|
},
|
|
};
|
|
|
|
module_platform_driver(rproc_srm_dev_driver);
|
|
|
|
MODULE_AUTHOR("Fabien Dessenne <fabien.dessenne@st.com>");
|
|
MODULE_DESCRIPTION("Remoteproc System Resource Manager driver - dev");
|
|
MODULE_LICENSE("GPL v2");
|