Fixed MTP to work with TWRP

This commit is contained in:
awab228 2018-06-19 23:16:04 +02:00
commit f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions

View file

@ -0,0 +1,26 @@
#
# MCU_IPC Device Driver
#
config MCU_IPC
bool "MCU IPC Support"
default n
help
This enables MCU_IPC driver to control the MCU_IPC Device.
MCU_IPC is the Mailbox which has 16 interrupts for TX/RX each
and 256 bytes memory for communicating messages.
AP and CP can share the messages through this device.
config MCU_IPC_TEST
bool "MCU_IPC driver test"
depends on MCU_IPC
help
This enables MCU_IPC_TEST for checking mailbox at probe time.
config SHM_IPC
bool "Shared Memory for IPC support"
default n
help
This enables SHM_IPC driver to control the Shared memory
for AP-CP Interface.

View file

@ -0,0 +1,6 @@
#
# MCU_IPC Device Driver
#
obj-$(CONFIG_MCU_IPC) += mcu_ipc.o
obj-$(CONFIG_SHM_IPC) += shm_ipc.o

View file

@ -0,0 +1,353 @@
/*
* Copyright (C) 2014 Samsung Electronics Co.Ltd
* http://www.samsung.com
*
* MCU IPC driver
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/miscdevice.h>
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/dma-mapping.h>
#include <linux/delay.h>
#include "regs-mcu_ipc.h"
#include "mcu_ipc.h"
static irqreturn_t mcu_ipc_handler(int irq, void *data)
{
u32 irq_stat, i;
u32 id;
id = ((struct mcu_ipc_drv_data *)data)->id;
irq_stat = mcu_ipc_readl(id, EXYNOS_MCU_IPC_INTSR0) & 0xFFFF0000;
/* Interrupt Clear */
mcu_ipc_writel(id, irq_stat, EXYNOS_MCU_IPC_INTCR0);
for (i = 0; i < 16; i++) {
if (irq_stat & (1 << (i + 16))) {
if ((1 << (i + 16)) & mcu_dat[id].registered_irq)
mcu_dat[id].hd[i].handler(mcu_dat[id].hd[i].data);
else
dev_err(mcu_dat[id].mcu_ipc_dev,
"Unregistered INT received.\n");
irq_stat &= ~(1 << (i + 16));
}
if (!irq_stat)
break;
}
return IRQ_HANDLED;
}
int mbox_request_irq(enum mcu_ipc_region id, u32 int_num,
void (*handler)(void *), void *data)
{
if ((!handler) || (int_num > 15))
return -EINVAL;
mcu_dat[id].hd[int_num].data = data;
mcu_dat[id].hd[int_num].handler = handler;
mcu_dat[id].registered_irq |= 1 << (int_num + 16);
return 0;
}
EXPORT_SYMBOL(mbox_request_irq);
int mcu_ipc_unregister_handler(enum mcu_ipc_region id, u32 int_num,
void (*handler)(void *))
{
if (!handler || (mcu_dat[id].hd[int_num].handler != handler))
return -EINVAL;
mcu_dat[id].hd[int_num].data = NULL;
mcu_dat[id].hd[int_num].handler = NULL;
mcu_dat[id].registered_irq &= ~(1 << (int_num + 16));
return 0;
}
EXPORT_SYMBOL(mcu_ipc_unregister_handler);
void mbox_set_interrupt(enum mcu_ipc_region id, u32 int_num)
{
/* generate interrupt */
if (int_num < 16)
mcu_ipc_writel(id, 0x1 << int_num, EXYNOS_MCU_IPC_INTGR1);
}
EXPORT_SYMBOL(mbox_set_interrupt);
void mcu_ipc_send_command(enum mcu_ipc_region id, u32 int_num, u16 cmd)
{
/* write command */
if (int_num < 16)
mcu_ipc_writel(id, cmd, EXYNOS_MCU_IPC_ISSR0 + (8 * int_num));
/* generate interrupt */
mbox_set_interrupt(id, int_num);
}
EXPORT_SYMBOL(mcu_ipc_send_command);
u32 mbox_get_value(enum mcu_ipc_region id, u32 mbx_num)
{
if (mbx_num < 64)
return mcu_ipc_readl(id, EXYNOS_MCU_IPC_ISSR0 + (4 * mbx_num));
else
return 0;
}
EXPORT_SYMBOL(mbox_get_value);
void mbox_set_value(enum mcu_ipc_region id, u32 mbx_num, u32 msg)
{
if (mbx_num < 64)
mcu_ipc_writel(id, msg, EXYNOS_MCU_IPC_ISSR0 + (4 * mbx_num));
}
EXPORT_SYMBOL(mbox_set_value);
u32 mbox_extract_value(enum mcu_ipc_region id, u32 mbx_num, u32 mask, u32 pos)
{
if (mbx_num < 64)
return (mbox_get_value(id, mbx_num) >> pos) & mask;
else
return 0;
}
EXPORT_SYMBOL(mbox_update_value);
void mbox_update_value(enum mcu_ipc_region id, u32 mbx_num,
u32 msg, u32 mask, u32 pos)
{
u32 val;
unsigned long flags;
spin_lock_irqsave(&mcu_dat[id].lock, flags);
if (mbx_num < 64) {
val = mbox_get_value(id, mbx_num);
val &= ~(mask << pos);
val |= (msg & mask) << pos;
mbox_set_value(id, mbx_num, val);
}
spin_unlock_irqrestore(&mcu_dat[id].lock, flags);
}
EXPORT_SYMBOL(mbox_update_value);
void mbox_sw_reset(enum mcu_ipc_region id)
{
u32 reg_val;
printk("Reset Mailbox registers\n");
reg_val = mcu_ipc_readl(id, EXYNOS_MCU_IPC_MCUCTLR);
reg_val |= (0x1 << MCU_IPC_MCUCTLR_MSWRST);
mcu_ipc_writel(id, reg_val, EXYNOS_MCU_IPC_MCUCTLR) ;
udelay(5);
}
EXPORT_SYMBOL(mbox_sw_reset);
static void mcu_ipc_clear_all_interrupt(enum mcu_ipc_region id)
{
mcu_ipc_writel(id, 0xFFFF, EXYNOS_MCU_IPC_INTCR1);
}
#ifdef CONFIG_ARGOS
static int mcu_ipc_set_affinity(enum mcu_ipc_region id, struct device *dev, int irq)
{
struct device_node *np = dev->of_node;
u32 irq_affinity_mask = 0;
if (!np) {
dev_err(dev, "non-DT project, can't set irq affinity\n");
return -ENODEV;
}
if (of_property_read_u32(np, "mcu,irq_affinity_mask",
&irq_affinity_mask)) {
dev_err(dev, "Failed to get affinity mask\n");
return -ENODEV;
}
dev_info(dev, "irq_affinity_mask = 0x%x\n", irq_affinity_mask);
if (!zalloc_cpumask_var(&mcu_dat[id].dmask, GFP_KERNEL))
return -ENOMEM;
if (!zalloc_cpumask_var(&mcu_dat[id].imask, GFP_KERNEL))
return -ENOMEM;
cpumask_or(mcu_dat[id].imask, mcu_dat[id].imask, cpumask_of(irq_affinity_mask));
cpumask_copy(mcu_dat[id].dmask, get_default_cpu_mask());
return argos_irq_affinity_setup_label(irq, "IPC", mcu_dat[id].imask,
mcu_dat[id].dmask);
}
#else
static int mcu_ipc_set_affinity(enum mcu_ipc_region id, struct device *dev, int irq)
{
return 0;
}
#endif
#ifdef CONFIG_MCU_IPC_TEST
static void test_without_dev(enum mcu_ipc_region id)
{
int i;
char *region;
switch(id) {
case MCU_CP:
region = "CP";
break;
case MCU_GNSS:
region = "GNSS";
break;
default:
region = NULL;
}
for (i = 0; i < 64; i++) {
mbox_set_value(id, i, 64 - i);
mdelay(50);
dev_err(mcu_dat[id].mcu_ipc_dev,
"Test without %s(%d): Read mbox value[%d]: %d\n",
region, id, i, mbox_get_value(i));
}
}
#endif
static int mcu_ipc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct resource *res = NULL;
int mcu_ipc_irq;
int err = 0;
u32 id;
dev_err(&pdev->dev, "%s: mcu_ipc probe start.\n", __func__);
err = of_property_read_u32(dev->of_node, "mcu,id", &id);
if (err) {
dev_err(&pdev->dev, "MCU IPC parse error! [id]\n");
return err;
}
if (id >= MCU_MAX) {
dev_err(&pdev->dev, "MCU IPC Invalid ID [%d]\n", id);
return -EINVAL;
}
mcu_dat[id].id = id;
mcu_dat[id].mcu_ipc_dev = &pdev->dev;
if (!pdev->dev.dma_mask)
pdev->dev.dma_mask = &pdev->dev.coherent_dma_mask;
if (!pdev->dev.coherent_dma_mask)
pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32);
if (dev->of_node) {
mcu_dt_read_string(dev->of_node, "mcu,name", mcu_dat[id].name);
if (IS_ERR(&mcu_dat[id])) {
dev_err(&pdev->dev, "MCU IPC parse error!\n");
return PTR_ERR(&mcu_dat[id]);
}
}
/* resource for mcu_ipc SFR region */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
mcu_dat[id].ioaddr = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(mcu_dat[id].ioaddr)) {
dev_err(&pdev->dev, "failded to request memory resource\n");
return PTR_ERR(mcu_dat[id].ioaddr);
}
/* Request IRQ */
mcu_ipc_irq = platform_get_irq(pdev, 0);
err = devm_request_irq(&pdev->dev, mcu_ipc_irq, mcu_ipc_handler, 0,
pdev->name, &mcu_dat[id]);
if (err) {
dev_err(&pdev->dev, "Can't request MCU_IPC IRQ\n");
return err;
}
mcu_ipc_clear_all_interrupt(id);
/* set argos irq affinity */
err = mcu_ipc_set_affinity(id, dev, mcu_ipc_irq);
if (err)
dev_err(dev, "Can't set IRQ affinity with(%d)\n", err);
#ifdef CONFIG_MCU_IPC_TEST
test_without_dev(id);
#endif
spin_lock_init(&mcu_dat[id].lock);
dev_err(&pdev->dev, "%s: mcu_ipc probe done.\n", __func__);
return 0;
}
static int __exit mcu_ipc_remove(struct platform_device *pdev)
{
/* TODO */
return 0;
}
#ifdef CONFIG_PM
static int mcu_ipc_suspend(struct device *dev)
{
/* TODO */
return 0;
}
static int mcu_ipc_resume(struct device *dev)
{
/* TODO */
return 0;
}
#else
#define mcu_ipc_suspend NULL
#define mcu_ipc_resume NULL
#endif
static const struct dev_pm_ops mcu_ipc_pm_ops = {
.suspend = mcu_ipc_suspend,
.resume = mcu_ipc_resume,
};
static const struct of_device_id exynos_mcu_ipc_dt_match[] = {
{ .compatible = "samsung,exynos7580-mailbox", },
{ .compatible = "samsung,exynos7890-mailbox", },
{ .compatible = "samsung,exynos8890-mailbox", },
{ .compatible = "samsung,exynos7870-mailbox", },
{ .compatible = "samsung,exynos-shd-ipc-mailbox", },
{},
};
MODULE_DEVICE_TABLE(of, exynos_mcu_ipc_dt_match);
static struct platform_driver mcu_ipc_driver = {
.probe = mcu_ipc_probe,
.remove = mcu_ipc_remove,
.driver = {
.name = "mcu_ipc",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(exynos_mcu_ipc_dt_match),
.pm = &mcu_ipc_pm_ops,
},
};
module_platform_driver(mcu_ipc_driver);
MODULE_DESCRIPTION("MCU IPC driver");
MODULE_AUTHOR("<hy50.seo@samsung.com>");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,117 @@
/*
* Copyright (C) 2014 Samsung Electronics Co.Ltd
* http://www.samsung.com
*
* MCU IPC driver
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#ifndef MCU_IPC_H
#define MCU_IPC_H
/* FIXME: will be removed */
/* Shared register with 64 * 32 words */
#define MAX_MBOX_NUM 64
enum mcu_ipc_region {
MCU_CP,
MCU_GNSS,
MCU_MAX,
};
struct mcu_ipc_ipc_handler {
void *data;
void (*handler)(void *);
};
struct mcu_ipc_drv_data {
char *name;
u32 id;
void __iomem *ioaddr;
u32 registered_irq;
/**
* irq affinity cpu mask
*/
cpumask_var_t dmask; /* default cpu mask */
cpumask_var_t imask; /* irq affinity cpu mask */
struct device *mcu_ipc_dev;
struct mcu_ipc_ipc_handler hd[16];
spinlock_t lock;
};
static struct mcu_ipc_drv_data mcu_dat[MCU_MAX];
static inline void mcu_ipc_writel(enum mcu_ipc_region id, u32 val, long reg)
{
writel(val, mcu_dat[id].ioaddr + reg);
}
static inline u32 mcu_ipc_readl(enum mcu_ipc_region id, long reg)
{
return readl(mcu_dat[id].ioaddr + reg);
}
#ifdef CONFIG_ARGOS
/* kernel team needs to provide argos header file. !!!
* As of now, there's nothing to use. */
#ifdef CONFIG_SCHED_HMP
extern struct cpumask hmp_slow_cpu_mask;
extern struct cpumask hmp_fast_cpu_mask;
static inline struct cpumask *get_default_cpu_mask(void)
{
return &hmp_slow_cpu_mask;
}
#else
static inline struct cpumask *get_default_cpu_mask(void)
{
return cpu_all_mask;
}
#endif
int argos_irq_affinity_setup_label(unsigned int irq, const char *label,
struct cpumask *affinity_cpu_mask,
struct cpumask *default_cpu_mask);
int argos_task_affinity_setup_label(struct task_struct *p, const char *label,
struct cpumask *affinity_cpu_mask,
struct cpumask *default_cpu_mask);
#endif
#define mcu_dt_read_enum(np, prop, dest) \
do { \
u32 val; \
if (of_property_read_u32(np, prop, &val)) \
return -EINVAL; \
dest = (__typeof__(dest))(val); \
} while (0)
#define mcu_dt_read_bool(np, prop, dest) \
do { \
u32 val; \
if (of_property_read_u32(np, prop, &val)) \
return -EINVAL; \
dest = val ? true : false; \
} while (0)
#define mcu_dt_read_string(np, prop, dest) \
do { \
if (of_property_read_string(np, prop, \
(const char **)&dest)) \
return -EINVAL; \
} while (0)
#define mcu_dt_read_u32(np, prop, dest) \
do { \
u32 val; \
if (of_property_read_u32(np, prop, &val)) \
return -EINVAL; \
dest = val; \
} while (0)
#endif

View file

@ -0,0 +1,57 @@
/*
* Copyright (c) 2014 Samsung Electronics Co., Ltd.
* http://www.samsung.com/
*
* Register definition file for Samsung MCU_IPC
*
* 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.
*/
#ifndef __ASM_ARCH_REGS_MCU_IPC_H
#define __ASM_ARCH_REGS_MCU_IPC_H
/***************************************************************/
/* MCU_IPC Registers part */
/***************************************************************/
#define EXYNOS_MCU_IPC_MCUCTLR 0x0
#define EXYNOS_MCU_IPC_INTGR0 0x8
#define EXYNOS_MCU_IPC_INTCR0 0xc
#define EXYNOS_MCU_IPC_INTMR0 0x10
#define EXYNOS_MCU_IPC_INTSR0 0x14
#define EXYNOS_MCU_IPC_INTMSR0 0x18
#define EXYNOS_MCU_IPC_INTGR1 0x1c
#define EXYNOS_MCU_IPC_INTCR1 0x20
#define EXYNOS_MCU_IPC_INTMR1 0x24
#define EXYNOS_MCU_IPC_INTSR1 0x28
#define EXYNOS_MCU_IPC_INTMSR1 0x2c
#define EXYNOS_MCU_IPC_ISSR0 0x80
#define EXYNOS_MCU_IPC_ISSR1 0x84
#define EXYNOS_MCU_IPC_ISSR2 0x88
#define EXYNOS_MCU_IPC_ISSR3 0x8c
/***************************************************************/
/* MCU_IPC Bit definition part */
/***************************************************************/
/* SYSREG Bit definition */
#define MCU_IPC_MCUCTLR_MSWRST (0) /* MCUCTRL S/W Reset */
#define MCU_IPC_RX_INT0 (1 << 16)
#define MCU_IPC_RX_INT1 (1 << 17)
#define MCU_IPC_RX_INT2 (1 << 18)
#define MCU_IPC_RX_INT3 (1 << 19)
#define MCU_IPC_RX_INT4 (1 << 20)
#define MCU_IPC_RX_INT5 (1 << 21)
#define MCU_IPC_RX_INT6 (1 << 22)
#define MCU_IPC_RX_INT7 (1 << 23)
#define MCU_IPC_RX_INT8 (1 << 24)
#define MCU_IPC_RX_INT9 (1 << 25)
#define MCU_IPC_RX_INT10 (1 << 26)
#define MCU_IPC_RX_INT11 (1 << 27)
#define MCU_IPC_RX_INT12 (1 << 28)
#define MCU_IPC_RX_INT13 (1 << 29)
#define MCU_IPC_RX_INT14 (1 << 30)
#define MCU_IPC_RX_INT15 (1 << 31)
#endif /* __ASM_ARCH_REGS_MCU_IPC_H */

View file

@ -0,0 +1,242 @@
/*
* Copyright (C) 2014 Samsung Electronics Co.Ltd
* http://www.samsung.com
*
* Shared memory driver
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/dma-mapping.h>
#include <linux/slab.h>
#include <linux/shm_ipc.h>
#include <linux/of_reserved_mem.h>
struct shm_plat_data {
unsigned long p_addr;
unsigned t_size;
unsigned ipc_off;
unsigned ipc_size;
unsigned long p_sysram_addr;
unsigned t_sysram_size;
} pdata;
unsigned long shm_get_phys_base(void)
{
return pdata.p_addr;
}
unsigned shm_get_phys_size(void)
{
return pdata.t_size;
}
unsigned long shm_get_sysram_base(void)
{
return pdata.p_sysram_addr;
}
unsigned shm_get_sysram_size(void)
{
return pdata.t_sysram_size;
}
unsigned shm_get_boot_size(void)
{
return pdata.ipc_off;
}
unsigned shm_get_ipc_rgn_offset(void)
{
return pdata.ipc_off;
}
unsigned shm_get_ipc_rgn_size(void)
{
return pdata.ipc_size;
}
unsigned long shm_get_security_param3(unsigned long mode, u32 main_size)
{
unsigned long ret;
switch (mode) {
case 0: /* CP_BOOT_MODE_NORMAL */
ret = main_size;
break;
case 1: /* CP_BOOT_MODE_DUMP */
#ifdef CP_NONSECURE_BOOT
ret = pdata.p_addr;
#else
ret = pdata.p_addr + pdata.ipc_off;
#endif
break;
case 2: /* CP_BOOT_RE_INIT */
ret = 0;
break;
default:
pr_info("%s: Invalid sec_mode(%lu)\n", __func__, mode);
ret = 0;
break;
}
return ret;
}
unsigned long shm_get_security_param2(unsigned long mode, u32 bl_size)
{
unsigned long ret;
switch (mode) {
case 0: /* CP_BOOT_MODE_NORMAL */
case 1: /* CP_BOOT_MODE_DUMP */
ret = bl_size;
break;
case 2: /* CP_BOOT_RE_INIT */
ret = 0;
break;
default:
pr_info("%s: Invalid sec_mode(%lu)\n", __func__, mode);
ret = 0;
break;
}
return ret;
}
void __iomem *shm_request_region(unsigned long sh_addr, unsigned size)
{
int i;
unsigned int num_pages = (size >> PAGE_SHIFT);
pgprot_t prot = pgprot_writecombine(PAGE_KERNEL);
struct page **pages;
void *v_addr;
if (!sh_addr)
return NULL;
pages = kmalloc(sizeof(struct page *) * num_pages, GFP_ATOMIC);
for (i = 0; i < (num_pages); i++) {
pages[i] = phys_to_page(sh_addr);
sh_addr += PAGE_SIZE;
}
v_addr = vmap(pages, num_pages, VM_MAP, prot);
kfree(pages);
return (void __iomem *)v_addr;
}
void __iomem *shm_get_boot_region(void)
{
return shm_request_region(pdata.p_addr, pdata.ipc_off);
}
void __iomem *shm_get_ipc_region(void)
{
return shm_request_region(pdata.p_addr + pdata.ipc_off,
pdata.t_size - pdata.ipc_off);
}
void shm_release_region(void *v_addr)
{
vunmap(v_addr);
}
#ifdef CONFIG_OF_RESERVED_MEM
static int __init modem_if_reserved_mem_setup(struct reserved_mem *remem)
{
pdata.p_addr = remem->base;
pdata.t_size = remem->size;
pr_err("%s: memory reserved: paddr=%lu, t_size=%u\n",
__func__, pdata.p_addr, pdata.t_size);
return 0;
}
RESERVEDMEM_OF_DECLARE(modem_if, "exynos,modem_if", modem_if_reserved_mem_setup);
#if !defined (CONFIG_SOC_EXYNOS7570)
static int __init deliver_cp_reserved_mem_setup(struct reserved_mem *remem)
{
pdata.p_sysram_addr = remem->base;
pdata.t_sysram_size = remem->size;
pr_err("%s: memory reserved: paddr=%u, t_size=%u\n",
__func__, (u32)remem->base, (u32)remem->size);
return 0;
}
RESERVEDMEM_OF_DECLARE(deliver_cp, "exynos,deliver_cp", deliver_cp_reserved_mem_setup);
#endif
#endif
static int shm_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
int ret;
dev_err(dev, "%s: shmem driver init\n", __func__);
if (dev->of_node) {
ret = of_property_read_u32(dev->of_node, "shmem,ipc_offset",
&pdata.ipc_off);
if (ret) {
dev_err(dev, "failed to get property, ipc_offset\n");
return -EINVAL;
}
ret = of_property_read_u32(dev->of_node, "shmem,ipc_size",
&pdata.ipc_size);
if (ret) {
dev_err(dev, "failed to get property, ipc_size\n");
return -EINVAL;
}
} else {
/* To do: In case of non-DT */
}
dev_err(dev, "paddr=%lu, t_size=%u, ipc_off=%u, ipc_size=%u\n",
pdata.p_addr, pdata.t_size, pdata.ipc_off, pdata.ipc_size);
return 0;
}
static int shm_remove(struct platform_device *pdev)
{
return 0;
}
static const struct of_device_id exynos_shm_dt_match[] = {
{ .compatible = "samsung,exynos7580-shm_ipc", },
{ .compatible = "samsung,exynos8890-shm_ipc", },
{ .compatible = "samsung,exynos7870-shm_ipc", },
{ .compatible = "samsung,exynos-shm_ipc", },
{},
};
MODULE_DEVICE_TABLE(of, exynos_shm_dt_match);
static struct platform_driver shmem_driver = {
.probe = shm_probe,
.remove = shm_remove,
.driver = {
.name = "shm_ipc",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(exynos_shm_dt_match),
},
};
module_platform_driver(shmem_driver);
MODULE_DESCRIPTION("");
MODULE_AUTHOR("");
MODULE_LICENSE("GPL");