blob: 398a0f5a166c7f22bbb7b69c5b541475fccc7341 [file] [log] [blame]
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2023, Google Inc
*
* MAX77779 BATTVIMON management
*/
#include <linux/debugfs.h>
#include <linux/device.h>
#include <linux/irq.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/regmap.h>
#include <linux/reboot.h>
#include "google_bms.h"
#include "max77779.h"
#include "max77779_vimon.h"
static inline int max77779_vimon_reg_read(struct max77779_vimon_data *data, unsigned int reg,
unsigned int *val)
{
return regmap_read(data->regmap, reg, val);
}
static inline int max77779_vimon_reg_write(struct max77779_vimon_data *data, unsigned int reg,
unsigned int val)
{
return regmap_write(data->regmap, reg, val);
}
static inline int max77779_vimon_reg_update(struct max77779_vimon_data *data, unsigned int reg,
unsigned int mask, unsigned int val)
{
return regmap_update_bits(data->regmap, reg, mask, val);
}
/* 0 not running, !=0 running, <0 error */
static int max77779_vimon_is_running(struct max77779_vimon_data *data)
{
unsigned int running;
int ret;
ret = max77779_vimon_reg_read(data, MAX77779_BVIM_CTRL, &running);
if (ret < 0)
return ret;
return !!(running & MAX77779_BVIM_CTRL_BVIMON_TRIG_MASK);
}
/* requires mutex_lock(&data->vimon_lock); */
static int vimon_is_running(struct max77779_vimon_data *data)
{
return data->state > MAX77779_VIMON_IDLE;
}
int max77779_external_vimon_reg_read(struct device *dev, uint16_t reg, void *val, int len)
{
struct max77779_vimon_data *data = dev_get_drvdata(dev);
if (!data || !data->regmap)
return -ENODEV;
return regmap_raw_read(data->regmap, reg, val, len);
}
EXPORT_SYMBOL_GPL(max77779_external_vimon_reg_read);
int max77779_external_vimon_reg_write(struct device *dev, uint16_t reg, const void *val, int len)
{
struct max77779_vimon_data *data = dev_get_drvdata(dev);
if (!data || !data->regmap)
return -ENODEV;
return regmap_raw_write(data->regmap, reg, val, len);
}
EXPORT_SYMBOL_GPL(max77779_external_vimon_reg_write);
int max77779_external_vimon_read_buffer(struct device *dev, uint16_t *buff, size_t *count,
size_t buff_max)
{
struct max77779_vimon_data *data = dev_get_drvdata(dev);
int ret = 0;
int copy_count;
if (!data)
return -ENODEV;
copy_count = data->buf_len;
if (buff_max < data->buf_len)
copy_count = buff_max;
memcpy(buff, data->buf, copy_count);
*count = copy_count;
return ret;
}
EXPORT_SYMBOL_GPL(max77779_external_vimon_read_buffer);
int max77779_external_vimon_enable(struct device *dev, bool enable)
{
struct max77779_vimon_data *data = dev_get_drvdata(dev);
int ret, reg;
if (!data)
return -ENODEV;
mutex_lock(&data->vimon_lock);
ret = max77779_vimon_reg_read(data, MAX77779_BVIM_CTRL, &reg);
if (ret < 0) {
mutex_unlock(&data->vimon_lock);
return -EIO;
}
reg = _max77779_bvim_ctrl_bvimon_trig_set(reg, enable);
ret = max77779_vimon_reg_write(data, MAX77779_BVIM_CTRL, reg);
if (reg < 0) {
mutex_unlock(&data->vimon_lock);
return -EIO;
}
ret = max77779_vimon_reg_read(data, MAX77779_BVIM_INT_STS, &reg);
if (ret < 0) {
mutex_unlock(&data->vimon_lock);
return -EIO;
}
reg = _max77779_bvim_int_sts_bvim_samples_rdy_set(reg, enable);
ret = max77779_vimon_reg_write(data, MAX77779_BVIM_INT_STS, reg);
if (ret < 0) {
mutex_unlock(&data->vimon_lock);
return -EIO;
}
data->state = enable ? MAX77779_VIMON_IDLE : MAX77779_VIMON_DISABLED;
mutex_unlock(&data->vimon_lock);
return ret;
}
EXPORT_SYMBOL_GPL(max77779_external_vimon_enable);
static int max77779_vimon_start(struct max77779_vimon_data *data, uint16_t config)
{
int ret;
mutex_lock(&data->vimon_lock);
ret = max77779_vimon_reg_update(data, MAX77779_BVIM_bvim_cfg, config, config);
if (ret)
goto vimon_start_exit;
ret = max77779_vimon_reg_write(data, MAX77779_BVIM_CTRL,
MAX77779_BVIM_CTRL_BVIMON_TRIG_MASK);
if (ret == 0)
data->state = MAX77779_VIMON_RUNNING;
vimon_start_exit:
mutex_unlock(&data->vimon_lock);
return ret;
}
static int max77779_vimon_stop(struct max77779_vimon_data *data)
{
return max77779_vimon_reg_write(data, MAX77779_BVIM_CTRL, 0);
}
static int max77779_vimon_set_config(struct max77779_vimon_data *data, uint16_t mask)
{
return max77779_vimon_reg_write(data, MAX77779_BVIM_bvim_cfg, mask);
}
static int max77779_vimon_clear_config(struct max77779_vimon_data *data, uint16_t mask)
{
return max77779_vimon_reg_write(data, MAX77779_BVIM_bvim_cfg, 0);
}
/*
* BattVIMon's Buffer: (1024-32) bytes
* -page[0:2] 256byts, page[3]:224(256-32)
* -ranges
* page0: [0x000:0x07F]
* page1: [0x080:0x0FF] ---> 0x80:0xFF
* page2: [0x100:0x17F]
* page3: [0x180:0x1EF]
*/
static ssize_t max77779_vimon_access_buffer(struct max77779_vimon_data *data, size_t offset,
size_t len, uint16_t *buffer, bool toread)
{
unsigned int target_addr;
int ret = -1;
size_t sz;
unsigned int page;
size_t start = offset;
const char* type = toread ? "read" : "write";
/* valid range: 0 - (1024-32) */
if (offset + len > 992) {
dev_err(data->dev, "Failed to %s BVIM's buffer: out of range\n", type);
return -EINVAL;
}
while (len > 0) {
/*
* page = offset / 128
* sz = 256 - (offset % 256)
* target_addr = 0x80 + (offset % 256)
*/
page = offset >> 7;
sz = MAX77779_VIMON_BUFFER_SIZE - (offset & 0x7F);
if (sz > len)
sz = len;
target_addr = MAX77779_VIMON_OFFSET_BASE + (offset & 0x7F);
ret = regmap_write(data->regmap, MAX77779_BVIM_PAGE_CTRL, page);
if (ret < 0) {
dev_err(data->dev, "page write failed: page: %i\n", page);
break;
}
if (toread)
ret = regmap_raw_read(data->regmap, target_addr, buffer, sz);
else
ret = regmap_raw_write(data->regmap, target_addr, buffer, sz);
if (ret < 0) {
dev_err(data->dev, "regmap_raw_read or write failed: %d\n", ret);
break;
}
offset += sz;
buffer += sz / MAX77779_VIMON_BYTES_PER_ENTRY;
len -= sz;
}
if (ret < 0)
return ret;
return offset - start;
}
static ssize_t bvim_cfg_show(struct device *dev, struct device_attribute *attr, char *buf)
{
int ret = -1;
unsigned int val=-1;
struct max77779_vimon_data *data = dev_get_drvdata(dev);
ret = max77779_vimon_reg_read(data, MAX77779_BVIM_bvim_cfg, &val);
if (ret <0)
return ret;
return scnprintf(buf, PAGE_SIZE, "%d\n", val);
}
static void max77779_vimon_handle_data(struct work_struct *work)
{
struct max77779_vimon_data *data = container_of(work, struct max77779_vimon_data,
read_data_work.work);
unsigned bvim_rfap, rsc, bvim_osc, smpl_start_add;
int ret;
int rd_bytes;
pm_stay_awake(data->dev);
mutex_lock(&data->vimon_lock);
if (data->state != MAX77779_VIMON_DATA_AVAILABLE) {
ret = -ENODATA;
goto vimon_handle_data_exit;
}
ret = max77779_vimon_reg_read(data, MAX77779_BVIM_bvim_rfap, &bvim_rfap);
if (ret)
goto vimon_handle_data_exit;
ret = max77779_vimon_reg_read(data, MAX77779_BVIM_bvim_rs, &rsc);
if (ret)
goto vimon_handle_data_exit;
rsc = _max77779_bvim_bvim_rs_rsc_get(rsc);
rd_bytes = rsc * MAX77779_VIMON_BYTES_PER_ENTRY * MAX77779_VIMON_ENTRIES_PER_VI_PAIR;
ret = max77779_vimon_stop(data);
if (ret)
goto vimon_handle_data_exit;
ret = max77779_vimon_access_buffer(data, bvim_rfap, rd_bytes, data->buf, true);
if (ret < 0)
goto vimon_handle_data_exit;
data->buf_len = ret;
ret = max77779_vimon_reg_read(data, MAX77779_BVIM_bvim_sts, &bvim_osc);
if (ret)
goto vimon_handle_data_exit;
bvim_osc = _max77779_bvim_bvim_sts_bvim_osc_get(bvim_osc);
ret = max77779_vimon_reg_read(data, MAX77779_BVIM_smpl_math, &smpl_start_add);
if (ret)
goto vimon_handle_data_exit;
smpl_start_add = _max77779_bvim_smpl_math_smpl_start_add_get(smpl_start_add);
vimon_handle_data_exit:
if (ret)
dev_dbg(data->dev, "Failed to handle data: (%d).\n", ret);
data->state = MAX77779_VIMON_IDLE;
ret = max77779_vimon_reg_write(data, MAX77779_BVIM_CTRL,
MAX77779_BVIM_CTRL_BVIMON_TRIG_MASK);
if (ret)
dev_err(data->dev, "Failed to rearm bvim_ctrl (%d).\n", ret);
ret = regmap_write(data->regmap, MAX77779_BVIM_INT_STS,
MAX77779_BVIM_INT_STS_BVIM_Samples_Rdy_MASK);
if (ret)
dev_err(data->dev, "Failed to clear INT_STS (%d).\n",
ret);
mutex_unlock(&data->vimon_lock);
pm_relax(data->dev);
}
static ssize_t bvim_cfg_store(struct device *dev, struct device_attribute *attr, const char* buf,
size_t count)
{
int ret = -1;
unsigned int val = -1;
struct max77779_vimon_data *data = dev_get_drvdata(dev);
ret = kstrtoint(buf, 0, &val);
if (ret < 0)
return ret;
ret = max77779_vimon_reg_write(data, MAX77779_BVIM_bvim_cfg, val);
return ret < 0 ? ret : count;
}
DEVICE_ATTR(bvim_cfg, 0660, bvim_cfg_show, bvim_cfg_store);
static ssize_t latest_buff_show(struct device *dev, struct device_attribute *attr, char *buf)
{
int idx;
ssize_t count = 0;
uint16_t rdback;
struct max77779_vimon_data *data = dev_get_drvdata(dev);
mutex_lock(&data->vimon_lock);
for (idx = 0; idx < data->buf_len / MAX77779_VIMON_BYTES_PER_ENTRY; idx++) {
rdback = data->buf[idx];
count += sysfs_emit_at(buf, count, "%#x\n", rdback);
}
mutex_unlock(&data->vimon_lock);
return count;
}
static DEVICE_ATTR_RO(latest_buff);
static struct attribute *max77779_vimon_attrs[] = {
&dev_attr_bvim_cfg.attr,
&dev_attr_latest_buff.attr,
NULL,
};
static const struct attribute_group max77779_vimon_attr_grp = {
.attrs = max77779_vimon_attrs,
};
/* -- debug --------------------------------------------------------------- */
static int max77779_vimon_debug_start(void *d, u64 *val)
{
struct max77779_vimon_data *data = d;
int ret;
ret = max77779_vimon_start(data, MAX77779_BVIM_bvim_cfg_cnt_run_MASK);
if (ret)
return ret;
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(debug_start_fops, max77779_vimon_debug_start, NULL, "%02llx\n");
static int max77779_vimon_debug_reg_read(void *d, u64 *val)
{
struct max77779_vimon_data *data = d;
int ret, reg;
ret = regmap_read(data->regmap, data->debug_reg_address, &reg);
if (ret == 0)
*val = reg & 0xffff;
return ret;
}
static int max77779_vimon_debug_reg_write(void *d, u64 val)
{
struct max77779_vimon_data *data = d;
return regmap_write(data->regmap, data->debug_reg_address, val & 0xffff);
}
DEFINE_SIMPLE_ATTRIBUTE(debug_reg_rw_fops, max77779_vimon_debug_reg_read,
max77779_vimon_debug_reg_write, "%04llx\n");
static ssize_t max77779_vimon_show_reg_all(struct file *filp, char __user *buf, size_t count,
loff_t *ppos)
{
struct max77779_vimon_data *data = filp->private_data;
u32 reg_address;
char *tmp;
int ret = 0, len = 0;
int regread;
if (*ppos)
return 0;
if (!data->regmap)
return -EIO;
tmp = kmalloc(PAGE_SIZE, GFP_KERNEL);
if (!tmp)
return -ENOMEM;
for (reg_address = 0; reg_address <= 0x7F; reg_address++) {
ret = regmap_read(data->regmap, reg_address, &regread);
if (ret < 0)
continue;
len += scnprintf(tmp + len, PAGE_SIZE - len, "%02x: %04x\n", reg_address,
regread & 0xffff);
}
if (len > 0)
len = simple_read_from_buffer(buf, count, ppos, tmp, len);
kfree(tmp);
return len;
}
BATTERY_DEBUG_ATTRIBUTE(debug_vimon_all_reg_fops, max77779_vimon_show_reg_all, NULL);
static ssize_t max77779_vimon_show_buff_all(struct file *filp, char __user *buf,
size_t count, loff_t *ppos)
{
struct max77779_vimon_data *data = filp->private_data;
char *tmp;
uint16_t *vals;
int ret;
int len = 0;
int i;
const size_t last_readback_size = MAX77779_VIMON_LAST_PAGE_SIZE *
MAX77779_VIMON_BYTES_PER_ENTRY;
const size_t readback_size = MAX77779_VIMON_PAGE_SIZE * MAX77779_VIMON_BYTES_PER_ENTRY;
int readback_cnt;
if (*ppos)
return 0;
if (!data->regmap)
return -EIO;
tmp = kmalloc(PAGE_SIZE, GFP_KERNEL);
if (!tmp)
return -ENOMEM;
vals = kcalloc(MAX77779_VIMON_PAGE_SIZE, sizeof(uint16_t), GFP_KERNEL);
mutex_lock(&data->vimon_lock);
ret = regmap_write(data->regmap, MAX77779_BVIM_PAGE_CTRL, data->debug_buffer_page);
if (ret < 0)
goto vimon_show_buff_exit;
if (data->debug_buffer_page < MAX77779_VIMON_PAGE_CNT - 1) {
ret = regmap_raw_read(data->regmap, MAX77779_VIMON_OFFSET_BASE, vals,
readback_size);
readback_cnt = MAX77779_VIMON_PAGE_SIZE;
} else {
ret = regmap_raw_read(data->regmap, MAX77779_VIMON_OFFSET_BASE, vals,
last_readback_size);
readback_cnt = MAX77779_VIMON_LAST_PAGE_SIZE;
}
if (ret < 0)
goto vimon_show_buff_exit;
for (i = 0; i < readback_cnt; i++)
len += scnprintf(tmp + len, PAGE_SIZE - len, "%02x: %04x\n",
data->debug_buffer_page * MAX77779_VIMON_PAGE_SIZE + i, vals[i]);
if (len > 0)
len = simple_read_from_buffer(buf, count, ppos, tmp, strlen(tmp));
ret = len;
vimon_show_buff_exit:
mutex_unlock(&data->vimon_lock);
kfree(tmp);
kfree(vals);
return ret;
}
BATTERY_DEBUG_ATTRIBUTE(debug_vimon_all_buff_fops, max77779_vimon_show_buff_all, NULL);
static int max77779_vimon_debug_buff_page_read(void *d, u64 *val)
{
struct max77779_vimon_data *data = d;
*val = data->debug_buffer_page;
return 0;
}
static int max77779_vimon_debug_buff_page_write(void *d, u64 val)
{
struct max77779_vimon_data *data = d;
if (val >= MAX77779_VIMON_PAGE_CNT)
return -EINVAL;
data->debug_buffer_page = (u8)val;
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(debug_buff_page_rw_fops, max77779_vimon_debug_buff_page_read,
max77779_vimon_debug_buff_page_write, "%llu\n");
bool max77779_vimon_is_reg(struct device *dev, unsigned int reg)
{
return reg >= 0 && reg <= MAX77779_VIMON_SIZE;
}
EXPORT_SYMBOL_GPL(max77779_vimon_is_reg);
static int max77779_vimon_init_fs(struct max77779_vimon_data *data)
{
int ret = -1;
ret = sysfs_create_group(&data->dev->kobj, &max77779_vimon_attr_grp);
if (ret < 0) {
dev_err(data->dev, "Failed to create sysfs group ret:%d\n", ret);
return ret;
}
data->de = debugfs_create_dir("max77779_vimon", 0);
if (IS_ERR_OR_NULL(data->de))
return -EINVAL;
debugfs_create_u32("address", 0600, data->de, &data->debug_reg_address);
debugfs_create_file("data", 0600, data->de, data, &debug_reg_rw_fops);
debugfs_create_file("registers", 0444, data->de, data, &debug_vimon_all_reg_fops);
debugfs_create_file("start", 0600, data->de, data, &debug_start_fops);
debugfs_create_file("buffer", 0444, data->de, data, &debug_vimon_all_buff_fops);
debugfs_create_file("buffer_page", 0600, data->de, data, &debug_buff_page_rw_fops);
debugfs_create_bool("run_in_offmode", 0644, data->de, &data->run_in_offmode);
return 0;
}
static int max77779_vimon_reboot_notifier(struct notifier_block *nb,
unsigned long val, void *v)
{
struct max77779_vimon_data *data =
container_of(nb, struct max77779_vimon_data, reboot_notifier);
int running;
running = max77779_vimon_is_running(data);
if (running < 0)
dev_err(data->dev, "cannot read VIMON HW state (%d)\n", running);
if (running || vimon_is_running(data))
dev_warn(data->dev, "vimon state HW=%d SW=%d\n",
running, data->state);
/* stop the HW, warn on inconsistency betwee HW and SW state */
if (!data->run_in_offmode && running) {
int ret;
ret = max77779_vimon_stop(data);
if (ret < 0)
dev_err(data->dev, "cannot stop vimon acquisition\n");
}
return NOTIFY_OK;
}
/* IRQ */
static irqreturn_t max77779_vimon_irq(int irq, void *ptr)
{
struct max77779_vimon_data *data = ptr;
int ret;
if (data->state <= MAX77779_VIMON_DISABLED)
return IRQ_HANDLED;
if (data->state >= MAX77779_VIMON_DATA_AVAILABLE)
goto vimon_rearm_interrupt;
data->state = MAX77779_VIMON_DATA_AVAILABLE;
schedule_delayed_work(&data->read_data_work,
msecs_to_jiffies(MAX77779_VIMON_DATA_RETRIEVE_DELAY));
vimon_rearm_interrupt:
ret = regmap_write(data->regmap, MAX77779_BVIM_INT_STS,
MAX77779_BVIM_INT_STS_BVIM_Samples_Rdy_MASK);
if (ret)
dev_err(data->dev, "Failed to clear INT_STS (%d).\n", ret);
return IRQ_HANDLED;
}
/*
* Initialization requirements
* struct max77779_vimon_data *data
* - dev
* - regmap
* - irq
*/
int max77779_vimon_init(struct max77779_vimon_data *data)
{
struct device *dev = data->dev;
unsigned int running;
uint16_t cfg_mask = 0;
uint16_t cfg_mask_lower_bits = 0;
int ret;
/* VIMON can be used to profile battery drain during reboot */
running = max77779_vimon_is_running(data);
if (running)
dev_warn(data->dev, "VIMON is already running (%d)\n", running);
mutex_init(&data->vimon_lock);
/* configure collected sample count with MAX77779_VIMON_SMPL_CNT */
cfg_mask = MAX77779_BVIM_bvim_cfg_vioaok_stop_MASK |
MAX77779_BVIM_bvim_cfg_top_fault_stop_MASK;
cfg_mask_lower_bits = _max77779_bvim_bvim_cfg_smpl_n_set(cfg_mask_lower_bits,
MAX77779_VIMON_SMPL_CNT);
cfg_mask |= cfg_mask_lower_bits;
ret = max77779_vimon_set_config(data, cfg_mask);
if (ret) {
dev_err(dev, "Failed to configure vimon\n");
return ret;
}
cfg_mask = MAX77779_BVIM_bvim_trig_oilo_stop_source_MASK |
MAX77779_BVIM_bvim_trig_batoilo1_tr_MASK |
MAX77779_BVIM_bvim_trig_batoilo2_tr_MASK |
MAX77779_BVIM_bvim_trig_sysuvlo1_tr_MASK |
MAX77779_BVIM_bvim_trig_sysuvlo2_tr_MASK;
ret = max77779_vimon_reg_write(data, MAX77779_BVIM_bvim_trig, cfg_mask);
if (ret) {
dev_err(dev, "Failed to configure vimon trig\n");
return ret;
}
ret = max77779_vimon_reg_write(data, MAX77779_BVIM_CTRL,
MAX77779_BVIM_CTRL_BVIMON_TRIG_MASK);
if (ret) {
dev_err(dev, "Failed to configure BVIM enable\n");
return ret;
}
ret = of_property_read_u32(dev->of_node, "max77779,max_cnt", &data->max_cnt);
if (ret)
data->max_cnt = MAX77779_VIMON_DEFAULT_MAX_CNT;
ret = of_property_read_u32(dev->of_node, "max77779,max_triggers", &data->max_cnt);
if (ret)
data->max_triggers = MAX77779_VIMON_DEFAULT_MAX_TRIGGERS;
data->buf_size = sizeof(*data->buf) * data->max_cnt * data->max_triggers * 2;
if (!data->buf_size) {
dev_err(dev, "max_cnt=%d, max_cnt=%d invalid buf_size\n",
data->max_cnt, data->max_triggers);
return -EINVAL;
}
data->buf = devm_kzalloc(dev, data->buf_size, GFP_KERNEL);
if (!data->buf)
return -ENOMEM;
INIT_DELAYED_WORK(&data->read_data_work, max77779_vimon_handle_data);
if (data->irq){
ret = devm_request_threaded_irq(data->dev, data->irq, NULL,
max77779_vimon_irq,
IRQF_TRIGGER_LOW | IRQF_SHARED | IRQF_ONESHOT,
"max77779_vimon", data);
if (ret < 0)
dev_warn(dev, "Failed to get irq thread.\n");
} else {
dev_warn(dev, "irq not setup\n");
}
ret = max77779_vimon_init_fs(data);
if (ret < 0)
dev_warn(dev, "Failed to initialize debug fs\n");
/* turn off vimon on reboot */
data->reboot_notifier.notifier_call = max77779_vimon_reboot_notifier;
ret = register_reboot_notifier(&data->reboot_notifier);
if (ret)
dev_err(data->dev, "failed to register reboot notifier\n");
ret = max77779_vimon_reg_write(data, MAX77779_BVIM_MASK, 0);
if (ret)
dev_err(data->dev, "Failed to unmask INT (%d).\n", ret);
data->state = MAX77779_VIMON_IDLE;
dev_info(data->dev, "buf_size=%lu\n", data->buf_size);
return 0;
}
EXPORT_SYMBOL_GPL(max77779_vimon_init);
void max77779_vimon_remove(struct max77779_vimon_data *data)
{
unsigned int running;
running = max77779_vimon_is_running(data);
if (running < 0)
dev_err(data->dev, "cannot read VIMON HW state (%d)\n", running);
if (running || vimon_is_running(data))
dev_warn(data->dev, "vimon state HW=%d SW=%d\n",
running, data->state);
if (data->de)
debugfs_remove(data->de);
if (data->irq)
free_irq(data->irq, data);
}
EXPORT_SYMBOL_GPL(max77779_vimon_remove);
MODULE_DESCRIPTION("max77779 VIMON Driver");
MODULE_AUTHOR("Daniel Okazaki <[email protected]>");
MODULE_AUTHOR("Chungro Lee <[email protected]>");
MODULE_AUTHOR("AleX Pelosi <[email protected]>");
MODULE_AUTHOR("Hiroshi Akiyama <[email protected]>");
MODULE_LICENSE("GPL");