tmp_kernel_5.15/drivers/remoteproc/fmql_remoteproc.c
2023-07-25 20:43:55 +08:00

646 lines
16 KiB
C

/*
* FMQL Remote Processor driver
*
* Based on origin OMAP Remote Processor driver
*
* Copyright (C) 2011 Texas Instruments, Inc.
* Copyright (C) 2011 Google, Inc.
*
* 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.
*/
#define DEBUG
#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/interrupt.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/of_reserved_mem.h>
#include <linux/smp.h>
#include <linux/irqchip/arm-gic.h>
#include <linux/slab.h>
#include <linux/cpu.h>
#include <linux/genalloc.h>
#ifdef CONFIG_ARCH_FMSH
#include <../../arch/arm/mach-fmsh/common.h>
#endif
#include "remoteproc_internal.h"
#define MAX_NUM_VRINGS 2
#define NOTIFYID_ANY (-1)
/* Maximum on chip memories used by the driver*/
#define MAX_ON_CHIP_MEMS 32
#define MAX_REMOTE_CORE (3)
#ifdef CONFIG_ARCH_FMSH
#define NO_CPU_AUTO_HOTPLUG
#endif
/* Structure for storing IRQs */
struct irq_list {
int irq;
struct list_head list;
};
/* Structure for IPIs */
struct ipi_info {
u32 irq;
u32 notifyid;
bool pending;
};
/**
* struct fmql_mem_res - fmql memory resource for firmware memory
* @res: memory resource
* @node: list node
*/
struct fmql_mem_res {
struct resource res;
struct list_head node;
};
/**
* struct fmql_rproc_data - fmql rproc private data
* @irqs: inter processor soft IRQs
* @rproc: pointer to remoteproc instance
* @ipis: interrupt processor interrupts statistics
* @fw_mems: list of firmware memories
*/
struct fmql_rproc_pdata {
struct irq_list irqs;
struct rproc *rproc;
struct ipi_info ipis[MAX_NUM_VRINGS];
struct list_head fw_mems;
struct work_struct workqueue;
int core_id;
};
static bool autoboot __read_mostly;
static struct work_struct* workqueue_array[MAX_REMOTE_CORE];
static void handle_event(struct work_struct *work)
{
struct fmql_rproc_pdata *local = container_of(work,
struct fmql_rproc_pdata, workqueue);
struct rproc *rproc = local->rproc;
if (rproc_vq_interrupt(local->rproc, local->ipis[0].notifyid) ==
IRQ_NONE)
dev_dbg(rproc->dev.parent, "no message found in vqid 0\n");
}
static void ipi_kick(void)
{
int i;
pr_debug("Remoteproc KICK Linux because of pending message\n");
for (i = 0; i < MAX_REMOTE_CORE; i++) {
if (workqueue_array[i]) {
schedule_work(workqueue_array[i]);
}
}
}
static void kick_pending_ipi(struct rproc *rproc)
{
struct fmql_rproc_pdata *local = rproc->priv;
int i;
for (i = 0; i < MAX_NUM_VRINGS; i++) {
/* Send swirq to firmware */
if (local->ipis[i].pending == true) {
smp_kick_ipi(cpumask_of(local->core_id),
local->ipis[i].irq);
local->ipis[i].pending = false;
}
}
}
#ifdef CONFIG_ARCH_FMSH
static int cpu_onoff_raw(unsigned int cpu, unsigned long entry_point, bool on)
{
if (on) {
fmsh_cpun_start(entry_point, cpu);
} else {
// not support stop this cpu
}
return 0;
}
#elif CONFIG_ARCH_FMQLMP
static int cpu_onoff_raw(unsigned int cpu, unsigned long entry_point, bool on)
{
#define SLCR_REG_BASE_ADDR (0xe0026000)
#define APU_RST_CTRL (SLCR_REG_BASE_ADDR + 0x380)
#define APU_RVBAR_L_0 (SLCR_REG_BASE_ADDR + 0xA40)
#define APU_RVBAR_H_0 (SLCR_REG_BASE_ADDR + 0xA44)
#define APU_RVBAR_L_1 (SLCR_REG_BASE_ADDR + 0xA48)
#define APU_RVBAR_H_1 (SLCR_REG_BASE_ADDR + 0xA4C)
#define APU_RVBAR_L_2 (SLCR_REG_BASE_ADDR + 0xA50)
#define APU_RVBAR_H_2 (SLCR_REG_BASE_ADDR + 0xA54)
#define APU_RVBAR_L_3 (SLCR_REG_BASE_ADDR + 0xA58)
#define APU_RVBAR_H_3 (SLCR_REG_BASE_ADDR + 0xA5C)
#define APU0_RESET_BIT_MASK ((1<<10)|(1<<0))
#define APU1_RESET_BIT_MASK ((1<<11)|(1<<1))
#define APU2_RESET_BIT_MASK ((1<<12)|(1<<2))
#define APU3_RESET_BIT_MASK ((1<<13)|(1<<3))
static u8 __iomem *apu_crtl;
static u8 __iomem *base_addr;
static u32 apu_rst_control;
static u32 apu_rst_control_mask;
apu_crtl = ioremap(APU_RST_CTRL, 4);
base_addr = ioremap(APU_RVBAR_L_0 + (cpu * 8), 8);
pr_debug("remote cpu%d: %s, entry:0x%lx\n", cpu, on ? "on" : "off",
entry_point);
apu_rst_control = readl(apu_crtl);
apu_rst_control_mask = (1 << cpu) | (1 << (cpu + 10));
if (on) {
apu_rst_control &= ~apu_rst_control_mask;
} else {
apu_rst_control |= apu_rst_control_mask;
}
writel(entry_point, base_addr);
writel(apu_rst_control, apu_crtl);
smp_wmb();
iounmap(base_addr);
iounmap(apu_crtl);
sev();
return 0;
}
#else
static int cpu_onoff_raw(unsigned int cpu, unsigned long entry_point, bool on)
{
pr_info("The architecture does not support this feature.\n");
}
#endif
static int fmql_rproc_start(struct rproc *rproc)
{
struct device *dev = rproc->dev.parent;
struct fmql_rproc_pdata *local = rproc->priv;
int ret = 0;
dev_dbg(dev, "%s\n", __func__);
INIT_WORK(&local->workqueue, handle_event);
workqueue_array[local->core_id - 1] = &local->workqueue;
#ifndef NO_CPU_AUTO_HOTPLUG
// ret = cpu_down(local->core_id);
ret = cpu_device_down(dev);
/* EBUSY means CPU is already released */
if (ret && (ret != -EBUSY)) {
dev_err(dev, "Can't release cpu1\n");
return ret;
}
#endif
/* start up cpu */
// smp_cpu_start_raw(local->core_id, rproc->bootaddr);
cpu_onoff_raw(local->core_id, rproc->bootaddr, true);
/* Trigger pending kicks */
kick_pending_ipi(rproc);
return ret;
}
/* kick a firmware */
static void fmql_rproc_kick(struct rproc *rproc, int vqid)
{
struct device *dev = rproc->dev.parent;
struct fmql_rproc_pdata *local = rproc->priv;
struct rproc_vdev *rvdev, *rvtmp;
int i;
dev_dbg(dev, "KICK Firmware to start send messages vqid %d\n", vqid);
list_for_each_entry_safe(rvdev, rvtmp, &rproc->rvdevs, node) {
for (i = 0; i < MAX_NUM_VRINGS; i++) {
struct rproc_vring *rvring = &rvdev->vring[i];
/* Send swirq to firmware */
if (rvring->notifyid == vqid) {
local->ipis[i].notifyid = vqid;
/* As we do not turn off CPU1 until start,
* we delay firmware kick
*/
if (rproc->state == RPROC_RUNNING) {
smp_kick_ipi(cpumask_of(local->core_id),
local->ipis[i].irq);
} else {
local->ipis[i].pending = true;
}
}
}
}
}
/* power off the remote processor */
static int fmql_rproc_stop(struct rproc *rproc)
{
struct device *dev = rproc->dev.parent;
struct fmql_rproc_pdata *local = rproc->priv;
int ret = 0;
dev_dbg(dev, "%s\n", __func__);
/* stop cpu */
// smp_cpu_stop_raw(local->core_id);
cpu_onoff_raw(local->core_id, 0, false);
#ifndef NO_CPU_AUTO_HOTPLUG
/* Cpu can't be power on - for example in nosmp mode */
ret =cpu_device_up(dev);
if (ret)
dev_err(dev, "Can't power on cpu1 %d\n", ret);
#endif
return ret;
}
static int fmql_parse_fw(struct rproc *rproc, const struct firmware *fw)
{
int num_mems, i, ret;
struct device *dev = rproc->dev.parent;
struct device_node *np = dev->of_node;
struct rproc_mem_entry *mem;
num_mems = of_count_phandle_with_args(np, "memory-region", NULL);
if (num_mems <= 0)
return 0;
for (i = 0; i < num_mems; i++) {
struct device_node *node;
struct reserved_mem *rmem;
node = of_parse_phandle(np, "memory-region", i);
rmem = of_reserved_mem_lookup(node);
if (!rmem) {
dev_err(dev, "unable to acquire memory-region\n");
return -EINVAL;
}
if (strstr(node->name, "vdev") &&
strstr(node->name, "buffer")) {
/* Register DMA region */
mem = rproc_mem_entry_init(dev, NULL,
(dma_addr_t)rmem->base,
rmem->size, rmem->base,
NULL, NULL,
node->name);
if (!mem) {
dev_err(dev,
"unable to initialize memory-region %s \n",
node->name);
return -ENOMEM;
}
rproc_add_carveout(rproc, mem);
} else if (strstr(node->name, "vdev") &&
strstr(node->name, "vring")) {
/* Register vring */
mem = rproc_mem_entry_init(dev, NULL,
(dma_addr_t)rmem->base,
rmem->size, rmem->base,
NULL, NULL,
node->name);
mem->va = devm_ioremap_wc(dev, rmem->base, rmem->size);
if (!mem->va)
return -ENOMEM;
if (!mem) {
dev_err(dev,
"unable to initialize memory-region %s\n",
node->name);
return -ENOMEM;
}
rproc_add_carveout(rproc, mem);
} else {
mem = rproc_of_resm_mem_entry_init(dev, i,
rmem->size,
rmem->base,
node->name);
if (!mem) {
dev_err(dev,
"unable to initialize memory-region %s \n",
node->name);
return -ENOMEM;
}
mem->va = devm_ioremap_wc(dev, rmem->base, rmem->size);
if (!mem->va)
return -ENOMEM;
rproc_add_carveout(rproc, mem);
}
}
ret = rproc_elf_load_rsc_table(rproc, fw);
if (ret == -EINVAL)
ret = 0;
return ret;
}
#if 0
static void *fmql_rproc_da_to_va(struct rproc *rproc, u64 da, int len)
{
struct rproc_mem_entry *mem;
void *va = 0;
struct fmql_rproc_pdata *local = rproc->priv;
dev_dbg(rproc->dev.parent, "%s, da:0x%lx,len:%d\n", __func__, da, len);
list_for_each_entry(mem, &local->mems, node) {
int offset = da - mem->da;
/* try next carveout if da is too small */
if (offset < 0)
continue;
/* try next carveout if da is too large */
if (offset + len > mem->len)
continue;
va = mem->va + offset;
break;
}
return va;
}
#endif
static struct rproc_ops fmql_rproc_ops = {
.start = fmql_rproc_start,
.stop = fmql_rproc_stop,
.load = rproc_elf_load_segments,
.parse_fw = fmql_parse_fw,
.find_loaded_rsc_table = rproc_elf_find_loaded_rsc_table,
.get_boot_addr = rproc_elf_get_boot_addr,
.kick = fmql_rproc_kick,
};
/* Just to detect bug if interrupt forwarding is broken */
static irqreturn_t fmql_remoteproc_interrupt(int irq, void *dev_id)
{
struct device *dev = dev_id;
dev_err(dev, "GIC IRQ %d is not forwarded correctly\n", irq);
/*
* MS: Calling this function doesn't need to be BUG
* especially for cases where firmware doesn't disable
* interrupts. In next probing can be som interrupts pending.
* The next scenario is for cases when you want to monitor
* non frequent interrupt through Linux kernel. Interrupt happen
* and it is forwarded to Linux which update own statistic
* in (/proc/interrupt) and forward it to firmware.
*
* gic_set_cpu(1, irq); - setup cpu1 as destination cpu
* gic_raise_softirq(cpumask_of(1), irq); - forward irq to firmware
*/
// gic_set_cpu(1, irq);
return IRQ_HANDLED;
}
static void clear_irq(struct rproc *rproc)
{
struct list_head *pos, *q;
struct irq_list *tmp;
struct fmql_rproc_pdata *local = rproc->priv;
dev_info(rproc->dev.parent, "Deleting the irq_list\n");
list_for_each_safe(pos, q, &local->irqs.list) {
tmp = list_entry(pos, struct irq_list, list);
free_irq(tmp->irq, rproc->dev.parent);
// gic_set_cpu(0, tmp->irq);
list_del(pos);
kfree(tmp);
}
}
#if 0
static int fmql_rproc_add_mems(struct fmql_rproc_pdata *pdata)
{
struct mem_pool_st *mem_node;
size_t mem_size;
struct gen_pool *mem_pool;
struct rproc_mem_entry *mem;
dma_addr_t dma;
void *va;
struct device *dev = pdata->rproc->dev.parent;
list_for_each_entry(mem_node, &pdata->mem_pools, node) {
mem_pool = mem_node->pool;
mem_size = gen_pool_size(mem_pool);
mem = devm_kzalloc(dev, sizeof(struct rproc_mem_entry),
GFP_KERNEL);
if (!mem)
return -ENOMEM;
va = gen_pool_dma_alloc(mem_pool, mem_size, &dma);
if (!va) {
dev_err(dev, "Failed to allocate dma carveout mem.\n");
return -ENOMEM;
}
mem->priv = (void *)mem_pool;
mem->va = va;
mem->len = mem_size;
mem->dma = dma;
mem->da = dma;
dev_dbg(dev, "%s: va = %p, da = 0x%x dma = 0x%x\n",
__func__, va, mem->da, mem->dma);
list_add_tail(&mem->node, &pdata->mems);
}
return 0;
}
#endif
static int fmql_remoteproc_probe(struct platform_device *pdev)
{
int ret = 0;
struct irq_list *tmp;
int count = 0;
struct rproc *rproc;
struct fmql_rproc_pdata *local;
rproc = rproc_alloc(&pdev->dev, dev_name(&pdev->dev),
&fmql_rproc_ops, NULL,
sizeof(struct fmql_rproc_pdata));
if (!rproc) {
dev_err(&pdev->dev, "rproc allocation failed\n");
ret = -ENOMEM;
return ret;
}
local = rproc->priv;
local->rproc = rproc;
platform_set_drvdata(pdev, rproc);
ret = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32));
if (ret) {
dev_err(&pdev->dev, "dma_set_coherent_mask: %d\n", ret);
goto dma_mask_fault;
}
/* Set core id */
ret = of_property_read_u32(pdev->dev.of_node, "core-id",
&local->core_id);
if (ret < 0) {
dev_warn(&pdev->dev, "unable to read core-id property, use core1");
local->core_id = 1;
}
#if 1
/* Init list for IRQs - it can be long list */
INIT_LIST_HEAD(&local->irqs.list);
/* Alloc IRQ based on DTS to be sure that no other driver will use it */
while (1) {
int irq;
irq = platform_get_irq(pdev, count++);
if (irq == -ENXIO || irq == -EINVAL)
break;
tmp = kzalloc(sizeof(*tmp), GFP_KERNEL);
if (!tmp) {
ret = -ENOMEM;
goto irq_fault;
}
tmp->irq = irq;
dev_dbg(&pdev->dev, "%d: Alloc irq: %d\n", count, tmp->irq);
/* Allocating shared IRQs will ensure that any module will
* use these IRQs
*/
ret = request_irq(tmp->irq, fmql_remoteproc_interrupt, 0,
dev_name(&pdev->dev), &pdev->dev);
if (ret) {
dev_err(&pdev->dev, "IRQ %d already allocated\n",
tmp->irq);
goto irq_fault;
}
/*
* MS: Here is place for detecting problem with firmware
* which doesn't work correctly with interrupts
*
* MS: Comment if you want to count IRQs on Linux
*/
// gic_set_cpu(1, tmp->irq);
list_add(&tmp->list, &local->irqs.list);
}
#endif
/* Allocate free IPI number */
/* Read vring0 ipi number */
ret = of_property_read_u32(pdev->dev.of_node, "vring0",
&local->ipis[0].irq);
if (ret < 0) {
dev_err(&pdev->dev, "unable to read property");
goto irq_fault;
}
/* TODO */
ret = set_ipi_handler(local->ipis[0].irq, ipi_kick,
"Firmware kick");
if (ret) {
dev_err(&pdev->dev, "IPI handler already registered\n");
goto irq_fault;
}
/* Read vring1 ipi number */
ret = of_property_read_u32(pdev->dev.of_node, "vring1",
&local->ipis[1].irq);
if (ret < 0) {
dev_err(&pdev->dev, "unable to read property");
goto ipi_fault;
}
rproc->auto_boot = autoboot;
ret = rproc_add(local->rproc);
if (ret) {
dev_err(&pdev->dev, "rproc registration failed\n");
goto ipi_fault;
}
return 0;
ipi_fault:
clear_ipi_handler(local->ipis[0].irq);
irq_fault:
clear_irq(rproc);
dma_mask_fault:
rproc_free(rproc);
return ret;
}
static int fmql_remoteproc_remove(struct platform_device *pdev)
{
struct rproc *rproc = platform_get_drvdata(pdev);
struct fmql_rproc_pdata *local = rproc->priv;
dev_info(&pdev->dev, "%s\n", __func__);
rproc_del(rproc);
clear_ipi_handler(local->ipis[0].irq);
clear_irq(rproc);
of_reserved_mem_device_release(&pdev->dev);
rproc_free(rproc);
return 0;
}
/* Match table for OF platform binding */
static const struct of_device_id fmql_remoteproc_match[] = {
{ .compatible = "fmsh,fmql_remoteproc", },
{ /* end of list */ },
};
MODULE_DEVICE_TABLE(of, fmql_remoteproc_match);
static struct platform_driver fmql_remoteproc_driver = {
.probe = fmql_remoteproc_probe,
.remove = fmql_remoteproc_remove,
.driver = {
.name = "fmql_remoteproc",
.of_match_table = fmql_remoteproc_match,
},
};
module_platform_driver(fmql_remoteproc_driver);
module_param_named(autoboot, autoboot, bool, 0444);
MODULE_PARM_DESC(autoboot,
"enable | disable autoboot. (default: false)");
MODULE_AUTHOR("Liu Lei <liulei@fmsh.com.cn");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("FMQL remote processor control driver");