530 lines
13 KiB
C
530 lines
13 KiB
C
|
/* SPDX-License-Identifier: GPL-2.0 */
|
|||
|
/*
|
|||
|
* sunxi's Remote Processor Share Interrupt Driver
|
|||
|
*
|
|||
|
* Copyright (C) 2022 Allwinnertech - All Rights Reserved
|
|||
|
*
|
|||
|
* Author: lijiajian <lijiajian@allwinnertech.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/init.h>
|
|||
|
#include <linux/kernel.h>
|
|||
|
#include <linux/module.h>
|
|||
|
#include <linux/slab.h>
|
|||
|
#include <linux/irq.h>
|
|||
|
#include <linux/of.h>
|
|||
|
#include <linux/of_device.h>
|
|||
|
#include <linux/of_address.h>
|
|||
|
#include <linux/of_reserved_mem.h>
|
|||
|
#include <linux/platform_device.h>
|
|||
|
#include <linux/regmap.h>
|
|||
|
#include <linux/interrupt.h>
|
|||
|
#include <linux/debugfs.h>
|
|||
|
|
|||
|
#include "sunxi_share_interrupt.h"
|
|||
|
|
|||
|
#define irm_debug(fmt, ...) pr_debug("Sunxi IRM %s:%d " fmt,\
|
|||
|
__func__, __LINE__, ##__VA_ARGS__)
|
|||
|
#define irm_warn(fmt, ...) pr_warn("Sunxi IRM %s:%d " fmt,\
|
|||
|
__func__, __LINE__, ##__VA_ARGS__)
|
|||
|
#define irm_err(fmt, ...) pr_err("Sunxi IRM %s:%d " fmt,\
|
|||
|
__func__, __LINE__, ##__VA_ARGS__)
|
|||
|
|
|||
|
#define IRQ_NP_NAME "reserved-irq"
|
|||
|
#define CPU_BANK_NUM 16
|
|||
|
#define READY_TAG (('R' << 24) | ('E' << 16) | ('D' << 8) | 'Y')
|
|||
|
#define USED_TAG (('U' << 24) | ('S' << 16) | ('E' << 8) | 'D')
|
|||
|
|
|||
|
#define GET_INFO_MAJOR(info, i) (info[i * 3 + 0])
|
|||
|
#define GET_INFO_TYPE(info, i) (info[i * 3 + 1])
|
|||
|
#define GET_INFO_FLAGS(info, i) (info[i * 3 + 2])
|
|||
|
|
|||
|
#define IS_GPIO_TYPE(type) (!!(type))
|
|||
|
#define GET_GPIO_INDEX(type) ((type) - 1)
|
|||
|
|
|||
|
struct _table_head {
|
|||
|
uint32_t tag;
|
|||
|
uint32_t len;
|
|||
|
uint32_t banks_off;
|
|||
|
uint32_t banks_num;
|
|||
|
} __packed;
|
|||
|
|
|||
|
struct _table_entry {
|
|||
|
uint32_t status;
|
|||
|
uint32_t type;
|
|||
|
uint32_t flags;
|
|||
|
uint32_t arch_irq;
|
|||
|
} __packed;
|
|||
|
|
|||
|
struct arch_irq {
|
|||
|
const char *name;
|
|||
|
bool visable;
|
|||
|
/*
|
|||
|
* info format
|
|||
|
* major, type, flags
|
|||
|
*/
|
|||
|
uint32_t *info;
|
|||
|
uint32_t entris;
|
|||
|
/*
|
|||
|
* Table Format:
|
|||
|
* The first 16 byte is reserved for _table_head
|
|||
|
* The last CPU_BANK_NUM word is resource for gpio mask
|
|||
|
*
|
|||
|
* ----------------
|
|||
|
* | _table_head |
|
|||
|
* ----------------
|
|||
|
* | _table_entry |
|
|||
|
* ----------------
|
|||
|
* | ... |
|
|||
|
* ----------------
|
|||
|
* | GPIOA Mask |
|
|||
|
* ----------------
|
|||
|
* | ... |
|
|||
|
* ---------------
|
|||
|
*/
|
|||
|
void __iomem *table_addr;
|
|||
|
uint32_t len;
|
|||
|
/*
|
|||
|
* record Interrupt Status
|
|||
|
* 0 : Disable
|
|||
|
* 1 : Enable
|
|||
|
*/
|
|||
|
uint8_t *status;
|
|||
|
/*
|
|||
|
* record GPIO Alloc Status
|
|||
|
* 0 : Host
|
|||
|
* 1 : Arch
|
|||
|
*/
|
|||
|
uint32_t gpio_banks[CPU_BANK_NUM];
|
|||
|
/*
|
|||
|
* record GPIO IRQ number mapping with bank number
|
|||
|
* [banks][0] = hwirq
|
|||
|
* [banks][1] = softirq
|
|||
|
*/
|
|||
|
uint32_t gpio_irq_map[CPU_BANK_NUM][2];
|
|||
|
};
|
|||
|
|
|||
|
static uint32_t g_arch_number;
|
|||
|
static struct arch_irq *g_arch_array;
|
|||
|
|
|||
|
static int sunxi_init_arch_info(struct device_node *child, struct arch_irq *arch)
|
|||
|
{
|
|||
|
const char *name;
|
|||
|
struct device_node *mem_np;
|
|||
|
struct resource r;
|
|||
|
void __iomem *va;
|
|||
|
int ret;
|
|||
|
|
|||
|
arch->visable = false;
|
|||
|
|
|||
|
ret = of_property_read_string(child, "arch-name", &name);
|
|||
|
if (ret < 0) {
|
|||
|
irm_err("Can't find arch-name property.\n");
|
|||
|
return -EINVAL;
|
|||
|
}
|
|||
|
arch->name = name;
|
|||
|
|
|||
|
mem_np = of_parse_phandle(child, "memory-region", 0);
|
|||
|
if (IS_ERR_OR_NULL(mem_np)) {
|
|||
|
irm_err("Can't find memory-region property.\n");
|
|||
|
return -EINVAL;
|
|||
|
}
|
|||
|
ret = of_address_to_resource(mem_np, 0, &r);
|
|||
|
if (ret) {
|
|||
|
irm_err("Failed to get memory-region address range,ret=%d.\n", ret);
|
|||
|
return -EFAULT;
|
|||
|
}
|
|||
|
va = ioremap_wc(r.start, resource_size(&r));
|
|||
|
if (IS_ERR_OR_NULL(va)) {
|
|||
|
irm_err("Fialed to remap memory-region\n");
|
|||
|
return -ENOMEM;
|
|||
|
}
|
|||
|
arch->table_addr = va;
|
|||
|
arch->len = resource_size(&r);
|
|||
|
irm_debug("map table from 0x%zx to 0x%p\n", r.start, va);
|
|||
|
|
|||
|
ret = of_property_count_elems_of_size(child, "share-irq", 3 * sizeof(u32));
|
|||
|
if (ret < 0) {
|
|||
|
irm_err("fail to get share-irq\n");
|
|||
|
return -ENXIO;
|
|||
|
}
|
|||
|
arch->entris = ret;
|
|||
|
arch->info = kzalloc(ret * 3 * sizeof(u32), GFP_KERNEL);
|
|||
|
if (IS_ERR_OR_NULL(arch->info)) {
|
|||
|
irm_warn("Failed to alloc info array,ret=%d.\n", ret);
|
|||
|
return -ENOMEM;
|
|||
|
}
|
|||
|
ret = of_property_read_u32_array(child, "share-irq", arch->info, arch->entris * 3);
|
|||
|
if (ret < 0) {
|
|||
|
irm_err("Failed to read share-irq property,ret=%d\n", ret);
|
|||
|
return -EFAULT;
|
|||
|
}
|
|||
|
|
|||
|
arch->status = kzalloc(ARRAY_SIZE(share_irq_table), GFP_KERNEL);
|
|||
|
if (IS_ERR_OR_NULL(arch->status)) {
|
|||
|
irm_warn("Failed to alloc info status array,ret=%d.\n", ret);
|
|||
|
kfree(arch->info);
|
|||
|
return -ENOMEM;
|
|||
|
}
|
|||
|
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
static void sunxi_update_gpio_mask(struct arch_irq *arch)
|
|||
|
{
|
|||
|
int i, j;
|
|||
|
uint32_t type, index, major, mask;
|
|||
|
|
|||
|
irm_debug("%s gpio irq map:\n", arch->name);
|
|||
|
/* update gpio softirq info */
|
|||
|
for (i = 0; i < arch->entris; i++) {
|
|||
|
type = GET_INFO_TYPE(arch->info, i);
|
|||
|
major = GET_INFO_MAJOR(arch->info, i);
|
|||
|
index = GET_GPIO_INDEX(type);
|
|||
|
|
|||
|
if (!IS_GPIO_TYPE(type))
|
|||
|
continue;
|
|||
|
|
|||
|
for (j = 0; j < ARRAY_SIZE(share_irq_table); j++) {
|
|||
|
if (share_irq_table[j].major == major) {
|
|||
|
arch->gpio_irq_map[index][0] = share_irq_table[j].hwirq;
|
|||
|
arch->gpio_irq_map[index][1] = share_irq_table[j].softirq;
|
|||
|
irm_debug("\tGPIO%c -> %d\n", index, arch->gpio_irq_map[index][1]);
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
irm_debug("\n");
|
|||
|
|
|||
|
irm_debug("%s info:\n", arch->name);
|
|||
|
/* update info to globle gpio banks */
|
|||
|
for (i = 0; i < arch->entris; i++) {
|
|||
|
type = GET_INFO_TYPE(arch->info, i);
|
|||
|
mask = GET_INFO_FLAGS(arch->info, i);
|
|||
|
index = GET_GPIO_INDEX(type);
|
|||
|
|
|||
|
irm_debug("\t0x%x 0x%x 0x%08x\n", GET_INFO_MAJOR(arch->info, i), type, mask);
|
|||
|
|
|||
|
if (!IS_GPIO_TYPE(type))
|
|||
|
continue;
|
|||
|
if (arch->gpio_banks[index] & mask)
|
|||
|
irm_warn("Incompatible GPIO PIN:0x%x.\n", (arch->gpio_banks[index] & mask));
|
|||
|
arch->gpio_banks[index] |= mask;
|
|||
|
}
|
|||
|
irm_debug("\n");
|
|||
|
}
|
|||
|
|
|||
|
static void sunxi_update_table(struct arch_irq *arch)
|
|||
|
{
|
|||
|
int i, j;
|
|||
|
uint32_t major, offset, type, hwirq, flags;
|
|||
|
struct _table_entry *ptable;
|
|||
|
struct _table_head *phead;
|
|||
|
uint32_t *gpio_mask;
|
|||
|
|
|||
|
phead = arch->table_addr;
|
|||
|
ptable = arch->table_addr + sizeof(*phead);
|
|||
|
|
|||
|
irm_debug("%s info:\n", arch->name);
|
|||
|
irm_debug("\t head address %p:\n", phead);
|
|||
|
irm_debug("\t Entry address %p:\n", ptable);
|
|||
|
|
|||
|
if (phead->tag != USED_TAG)
|
|||
|
memset(arch->table_addr, 0, arch->len);
|
|||
|
/* update info to globle interrupt table */
|
|||
|
for (i = 0; i < arch->entris; i++) {
|
|||
|
major = GET_INFO_MAJOR(arch->info, i);
|
|||
|
type = GET_INFO_TYPE(arch->info, i);
|
|||
|
flags = GET_INFO_FLAGS(arch->info, i);
|
|||
|
|
|||
|
for (j = 0; j < ARRAY_SIZE(share_irq_table); j++) {
|
|||
|
hwirq = share_irq_table[j].hwirq;
|
|||
|
if (share_irq_table[j].major != major)
|
|||
|
continue;
|
|||
|
irm_debug("\tUpdate Entry %d irq to %s.\n", hwirq, arch->name);
|
|||
|
irm_debug("\t\tEntry status = 1\n");
|
|||
|
irm_debug("\t\tEntry type = 0x%x\n", type);
|
|||
|
irm_debug("\t\tEntry flags = 0x%x\n", flags);
|
|||
|
irm_debug("\t\tEntry IRQ = %d\n", share_irq_table[j].arch_irq);
|
|||
|
ptable[hwirq].status = 1;
|
|||
|
ptable[hwirq].type = type;
|
|||
|
ptable[hwirq].flags = flags;
|
|||
|
ptable[hwirq].arch_irq = share_irq_table[j].arch_irq;
|
|||
|
}
|
|||
|
}
|
|||
|
/* update gpio banks info to table */
|
|||
|
gpio_mask = arch->table_addr + arch->len - CPU_BANK_NUM * sizeof(uint32_t);
|
|||
|
irm_debug("Update GPIO Banks Table from 0x%p to 0x%p.\n", arch->gpio_banks, gpio_mask);
|
|||
|
memcpy(gpio_mask, arch->gpio_banks, CPU_BANK_NUM * sizeof(uint32_t));
|
|||
|
/* mark globle interrupt table ready */
|
|||
|
offset = (uint32_t)((unsigned long)gpio_mask - (unsigned long)(arch->table_addr));
|
|||
|
phead->tag = READY_TAG;
|
|||
|
phead->len = arch->len;
|
|||
|
phead->banks_off = offset;
|
|||
|
phead->banks_num = CPU_BANK_NUM;
|
|||
|
|
|||
|
irm_debug("%s Table Ready.\n", arch->name);
|
|||
|
irm_debug("\n");
|
|||
|
}
|
|||
|
|
|||
|
static struct arch_irq *sunxi_find_arch_by_name(const char *name)
|
|||
|
{
|
|||
|
int i;
|
|||
|
struct arch_irq *arch;
|
|||
|
|
|||
|
for (i = 0; i < g_arch_number; i++) {
|
|||
|
arch = &g_arch_array[i];
|
|||
|
if (strcmp(name, arch->name))
|
|||
|
continue;
|
|||
|
return arch;
|
|||
|
}
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
int sunxi_arch_interrupt_save(const char *name)
|
|||
|
{
|
|||
|
int i, j;
|
|||
|
struct arch_irq *arch;
|
|||
|
uint32_t type, mask, major;
|
|||
|
|
|||
|
arch = sunxi_find_arch_by_name(name);
|
|||
|
if (!arch) {
|
|||
|
irm_warn("No such %s arch name.\n", name);
|
|||
|
return -ENODEV;
|
|||
|
}
|
|||
|
|
|||
|
sunxi_update_table(arch);
|
|||
|
/* save normal interrupt */
|
|||
|
for (i = 0; i < arch->entris; i++) {
|
|||
|
type = GET_INFO_TYPE(arch->info, i);
|
|||
|
major = GET_INFO_MAJOR(arch->info, i);
|
|||
|
|
|||
|
if (IS_GPIO_TYPE(type))
|
|||
|
continue;
|
|||
|
|
|||
|
for (j = 0; j < ARRAY_SIZE(share_irq_table); j++) {
|
|||
|
if (share_irq_table[j].major != major)
|
|||
|
continue;
|
|||
|
/* TODO: host is enable this tnterrupt? */
|
|||
|
if (!can_request_irq(share_irq_table[j].softirq, 0))
|
|||
|
arch->status[j] = 1;
|
|||
|
arch->status[j] = 0;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/* save gpio interrupt */
|
|||
|
for (i = 0; i < arch->entris; i++) {
|
|||
|
major = GET_INFO_MAJOR(arch->info, i);
|
|||
|
type = GET_INFO_TYPE(arch->info, i);
|
|||
|
mask = GET_INFO_FLAGS(arch->info, i);
|
|||
|
|
|||
|
if (!IS_GPIO_TYPE(type))
|
|||
|
continue;
|
|||
|
|
|||
|
if (mask != 0xffffffff)
|
|||
|
continue;
|
|||
|
|
|||
|
for (j = 0; j < ARRAY_SIZE(share_irq_table); j++) {
|
|||
|
if (share_irq_table[j].major == major) {
|
|||
|
arch->status[j] = 1;
|
|||
|
disable_irq(share_irq_table[j].softirq);
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
arch->visable = true;
|
|||
|
|
|||
|
return 0;
|
|||
|
}
|
|||
|
EXPORT_SYMBOL(sunxi_arch_interrupt_save);
|
|||
|
|
|||
|
int sunxi_arch_interrupt_restore(const char *name)
|
|||
|
{
|
|||
|
int i;
|
|||
|
struct arch_irq *arch;
|
|||
|
uint32_t type, major;
|
|||
|
struct _table_head *phead;
|
|||
|
|
|||
|
arch = sunxi_find_arch_by_name(name);
|
|||
|
if (!arch) {
|
|||
|
irm_warn("No such %s arch name.\n", name);
|
|||
|
return -ENODEV;
|
|||
|
}
|
|||
|
|
|||
|
phead = arch->table_addr;
|
|||
|
/* restore all interrupt */
|
|||
|
for (i = 0; i < ARRAY_SIZE(share_irq_table); i++) {
|
|||
|
type = GET_INFO_TYPE(arch->info, i);
|
|||
|
major = GET_INFO_MAJOR(arch->info, i);
|
|||
|
|
|||
|
if (!arch->status[i])
|
|||
|
continue;
|
|||
|
enable_irq(share_irq_table[i].softirq);
|
|||
|
}
|
|||
|
memset(arch->status, 0, ARRAY_SIZE(share_irq_table));
|
|||
|
arch->visable = false;
|
|||
|
phead->tag = 0;
|
|||
|
|
|||
|
return 0;
|
|||
|
}
|
|||
|
EXPORT_SYMBOL(sunxi_arch_interrupt_restore);
|
|||
|
|
|||
|
uint32_t sunxi_rproc_get_gpio_mask(int softirq)
|
|||
|
{
|
|||
|
int i, j;
|
|||
|
uint32_t mask = 0;
|
|||
|
struct arch_irq *arch;
|
|||
|
|
|||
|
for (i = 0; i < g_arch_number; i++) {
|
|||
|
arch = &g_arch_array[i];
|
|||
|
if (!arch->visable)
|
|||
|
continue;
|
|||
|
|
|||
|
/* trans softirq to banks */
|
|||
|
for (j = 0; j < CPU_BANK_NUM; j++) {
|
|||
|
if (arch->gpio_irq_map[j][1] == softirq) {
|
|||
|
mask |= arch->gpio_banks[j];
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return mask;
|
|||
|
}
|
|||
|
EXPORT_SYMBOL(sunxi_rproc_get_gpio_mask);
|
|||
|
|
|||
|
uint32_t sunxi_rproc_get_gpio_mask_by_hwirq(int hwirq)
|
|||
|
{
|
|||
|
int i, j;
|
|||
|
uint32_t mask = 0;
|
|||
|
struct arch_irq *arch;
|
|||
|
|
|||
|
for (i = 0; i < g_arch_number; i++) {
|
|||
|
arch = &g_arch_array[i];
|
|||
|
if (!arch->visable)
|
|||
|
continue;
|
|||
|
|
|||
|
/* trans softirq to banks */
|
|||
|
for (j = 0; j < CPU_BANK_NUM; j++) {
|
|||
|
if (arch->gpio_irq_map[j][0] == hwirq) {
|
|||
|
mask |= arch->gpio_banks[j];
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return mask;
|
|||
|
}
|
|||
|
EXPORT_SYMBOL(sunxi_rproc_get_gpio_mask_by_hwirq);
|
|||
|
|
|||
|
static ssize_t sunxi_arch_irq_info_read(struct file *filp, char __user *userbuf,
|
|||
|
size_t count, loff_t *ppos)
|
|||
|
{
|
|||
|
struct arch_irq *arch = filp->private_data;
|
|||
|
/* need room for the name, a newline and a terminating null */
|
|||
|
char buf[1024];
|
|||
|
int i, j;
|
|||
|
|
|||
|
i = scnprintf(buf, sizeof(buf), "Status: %s\n",
|
|||
|
arch->visable ? "Active" : "Inactive");
|
|||
|
for (j = 0; j < CPU_BANK_NUM; j++) {
|
|||
|
i += scnprintf(buf + i, sizeof(buf) - i, "\tGPIO%c: 0x%08x\n",
|
|||
|
'A' + j, arch->gpio_banks[j]);
|
|||
|
}
|
|||
|
|
|||
|
return simple_read_from_buffer(userbuf, count, ppos, buf, i);
|
|||
|
}
|
|||
|
|
|||
|
static const struct file_operations sunxi_irq_info_ops = {
|
|||
|
.read = sunxi_arch_irq_info_read,
|
|||
|
.open = simple_open,
|
|||
|
.llseek = generic_file_llseek,
|
|||
|
};
|
|||
|
|
|||
|
int sunxi_arch_create_debug_dir(struct dentry *dir, const char *name)
|
|||
|
{
|
|||
|
struct arch_irq *arch;
|
|||
|
|
|||
|
if (!dir) {
|
|||
|
irm_warn("Invalid debug dir.\n");
|
|||
|
return -EINVAL;
|
|||
|
}
|
|||
|
arch = sunxi_find_arch_by_name(name);
|
|||
|
if (!arch) {
|
|||
|
irm_warn("No such %s arch name.\n", name);
|
|||
|
return -ENODEV;
|
|||
|
}
|
|||
|
|
|||
|
debugfs_create_file("share-interrupt", 0400, dir,
|
|||
|
arch, &sunxi_irq_info_ops);
|
|||
|
|
|||
|
return 0;
|
|||
|
}
|
|||
|
EXPORT_SYMBOL(sunxi_arch_create_debug_dir);
|
|||
|
|
|||
|
static int __init sunxi_share_resource_init(void)
|
|||
|
{
|
|||
|
struct device_node *np, *child;
|
|||
|
int cnt, i;
|
|||
|
|
|||
|
if (g_arch_array) {
|
|||
|
irm_warn("Sunxi Interrupt Resource Already Init.\n");
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
/* mapping hardware interrupt */
|
|||
|
for (i = 0; i < ARRAY_SIZE(share_irq_table); i++) {
|
|||
|
share_irq_table[i].softirq = irqnum_trans_from_hwirq(share_irq_table[i].hwirq);
|
|||
|
irm_err("Trans IRQ: %d -> %d\n", share_irq_table[i].hwirq,
|
|||
|
share_irq_table[i].softirq);
|
|||
|
}
|
|||
|
|
|||
|
np = of_find_node_by_name(NULL, IRQ_NP_NAME);
|
|||
|
if (!np) {
|
|||
|
irm_err("Can't Find reserved-irq node in dts.\n");
|
|||
|
return -ENOMEM;
|
|||
|
}
|
|||
|
|
|||
|
cnt = of_get_child_count(np);
|
|||
|
irm_debug("Child Cnt=%d\n", cnt);
|
|||
|
g_arch_number = cnt;
|
|||
|
|
|||
|
g_arch_array = kzalloc(cnt * sizeof(*g_arch_array), GFP_KERNEL);
|
|||
|
if (IS_ERR_OR_NULL(g_arch_array)) {
|
|||
|
irm_err("Alloc Resource Table Failed,ret=%ld.\n",
|
|||
|
PTR_ERR(g_arch_array));
|
|||
|
g_arch_array = NULL;
|
|||
|
return -ENOMEM;
|
|||
|
}
|
|||
|
|
|||
|
cnt = 0;
|
|||
|
for_each_child_of_node(np, child) {
|
|||
|
if (sunxi_init_arch_info(child, &g_arch_array[cnt]))
|
|||
|
goto out;
|
|||
|
sunxi_update_gpio_mask(&g_arch_array[cnt]);
|
|||
|
sunxi_update_table(&g_arch_array[cnt]);
|
|||
|
cnt++;
|
|||
|
}
|
|||
|
|
|||
|
return 0;
|
|||
|
out:
|
|||
|
for (i = 0; i < cnt; i++) {
|
|||
|
g_arch_array[i].name = NULL;
|
|||
|
if (g_arch_array[i].info)
|
|||
|
kfree(g_arch_array[i].info);
|
|||
|
if (g_arch_array[i].table_addr)
|
|||
|
iounmap(g_arch_array[i].table_addr);
|
|||
|
}
|
|||
|
|
|||
|
return -EINVAL;
|
|||
|
}
|
|||
|
early_initcall(sunxi_share_resource_init);
|
|||
|
|
|||
|
MODULE_DESCRIPTION("SUNXI Remote Processor Share IRQ Driver");
|
|||
|
MODULE_AUTHOR("lijiajian <lijiajian@allwinnertech.com>");
|
|||
|
MODULE_LICENSE("GPL v2");
|