/*
* Copyright (c) 2011 Synaptics Incorporated
* Copyright (c) 2011 Unixphere
*
* 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.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <linux/kernel.h>
#include <linux/rmi.h>
#include <linux/slab.h>
#include <linux/version.h>
#include "rmi_driver.h"
/* define fn $34 commands */
#define WRITE_FW_BLOCK 0x2
#define ERASE_ALL 0x3
#define READ_CONFIG_BLOCK 0x5
#define WRITE_CONFIG_BLOCK 0x6
#define ERASE_CONFIG 0x7
#define ENABLE_FLASH_PROG 0xf
#define STATUS_IN_PROGRESS 0xff
#define STATUS_IDLE 0x80
#define PDT_START_SCAN_LOCATION 0x00e9
#define PDT_END_SCAN_LOCATION 0x0005
#define BLK_SZ_OFF 3
#define IMG_BLK_CNT_OFF 5
#define CFG_BLK_CNT_OFF 7
#define BLK_NUM_OFF 2
#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 32)
#define KERNEL_VERSION_ABOVE_2_6_32 1
#endif
/* data specific to fn $34 that needs to be kept around */
struct rmi_fn_34_data {
unsigned char status;
unsigned char cmd;
unsigned short bootloaderid;
unsigned short blocksize;
unsigned short imageblockcount;
unsigned short configblockcount;
unsigned short blocknum;
bool inflashprogmode;
};
static ssize_t rmi_fn_34_status_show(struct device *dev,
struct device_attribute *attr, char *buf);
static ssize_t rmi_fn_34_cmd_show(struct device *dev,
struct device_attribute *attr, char *buf);
static ssize_t rmi_fn_34_cmd_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count);
#ifdef KERNEL_VERSION_ABOVE_2_6_32
static ssize_t rmi_fn_34_data_read(struct file *data_file, struct kobject *kobj,
struct bin_attribute *attributes,
char *buf, loff_t pos, size_t count);
static ssize_t rmi_fn_34_data_write(struct file *data_file,
struct kobject *kobj,
struct bin_attribute *attributes, char *buf,
loff_t pos, size_t count);
#else
static ssize_t rmi_fn_34_data_read(struct kobject *kobj,
struct bin_attribute *attributes,
char *buf, loff_t pos, size_t count);
static ssize_t rmi_fn_34_data_write(struct kobject *kobj,
struct bin_attribute *attributes, char *buf,
loff_t pos, size_t count);
#endif
static ssize_t rmi_fn_34_bootloaderid_show(struct device *dev,
struct device_attribute *attr,
char *buf);
static ssize_t rmi_fn_34_bootloaderid_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count);
static ssize_t rmi_fn_34_blocksize_show(struct device *dev,
struct device_attribute *attr,
char *buf);
static ssize_t rmi_fn_34_imageblockcount_show(struct device *dev,
struct device_attribute *attr,
char *buf);
static ssize_t rmi_fn_34_configblockcount_show(struct device *dev,
struct device_attribute *attr,
char *buf);
static ssize_t rmi_fn_34_blocknum_show(struct device *dev,
struct device_attribute *attr,
char *buf);
static ssize_t rmi_fn_34_blocknum_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count);
static ssize_t rmi_fn_34_rescanPDT_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count);
static struct device_attribute attrs[] = {
__ATTR(status, RMI_RO_ATTR,
rmi_fn_34_status_show, rmi_store_error),
/* Also, sysfs will need to have a file set up to distinguish
* between commands - like Config write/read, Image write/verify. */
__ATTR(cmd, RMI_RW_ATTR,
rmi_fn_34_cmd_show, rmi_fn_34_cmd_store),
__ATTR(bootloaderid, RMI_RW_ATTR,
rmi_fn_34_bootloaderid_show, rmi_fn_34_bootloaderid_store),
__ATTR(blocksize, RMI_RO_ATTR,
rmi_fn_34_blocksize_show, rmi_store_error),
__ATTR(imageblockcount, RMI_RO_ATTR,
rmi_fn_34_imageblockcount_show, rmi_store_error),
__ATTR(configblockcount, RMI_RO_ATTR,
rmi_fn_34_configblockcount_show, rmi_store_error),
__ATTR(blocknum, RMI_RW_ATTR,
rmi_fn_34_blocknum_show, rmi_fn_34_blocknum_store),
__ATTR(rescanPDT, RMI_WO_ATTR,
rmi_show_error, rmi_fn_34_rescanPDT_store)
};
struct bin_attribute dev_attr_data = {
.attr = {
.name = "data",
.mode = 0666},
.size = 0,
.read = rmi_fn_34_data_read,
.write = rmi_fn_34_data_write,
};
static int rmi_f34_init(struct rmi_function_container *fc)
{
int retval = 0;
int attr_count = 0;
struct rmi_fn_34_data *f34;
u16 query_base_addr;
u16 control_base_addr;
unsigned char buf[2];
dev_info(&fc->dev, "Intializing f34 values.");
/* init instance data, fill in values and create any sysfs files */
f34 = kzalloc(sizeof(struct rmi_fn_34_data), GFP_KERNEL);
if (!f34) {
dev_err(&fc->dev, "Failed to allocate rmi_fn_34_data.\n");
return -ENOMEM;
}
fc->data = f34;
/* get the Bootloader ID and Block Size. */
query_base_addr = fc->fd.query_base_addr;
control_base_addr = fc->fd.control_base_addr;
retval = rmi_read_block(fc->rmi_dev, query_base_addr, buf,
ARRAY_SIZE(buf));
if (retval < 0) {
dev_err(&fc->dev, "Could not read bootloaderid from 0x%04x.\n",
query_base_addr);
goto exit_free_data;
}
batohs(&f34->bootloaderid, buf);
retval = rmi_read_block(fc->rmi_dev, query_base_addr + BLK_SZ_OFF, buf,
ARRAY_SIZE(buf));
if (retval < 0) {
dev_err(&fc->dev, "Could not read block size from 0x%04x, "
"error=%d.\n", query_base_addr + BLK_SZ_OFF, retval);
goto exit_free_data;
}
batohs(&f34->blocksize, buf);
/* Get firmware image block count and store it in the instance data */
retval = rmi_read_block(fc->rmi_dev, query_base_addr + IMG_BLK_CNT_OFF,
buf, ARRAY_SIZE(buf));
if (retval < 0) {
dev_err(&fc->dev, "Couldn't read image block count from 0x%x, "
"error=%d.\n", query_base_addr + IMG_BLK_CNT_OFF,
retval);
goto exit_free_data;
}
batohs(&f34->imageblockcount, buf);
/* Get config block count and store it in the instance data */
retval = rmi_read_block(fc->rmi_dev, query_base_addr + 7, buf,
ARRAY_SIZE(buf));
if (retval < 0) {
dev_err(&fc->dev, "Couldn't read config block count from 0x%x, "
"error=%d.\n", query_base_addr + CFG_BLK_CNT_OFF,
retval);
goto exit_free_data;
}
batohs(&f34->configblockcount, buf);
/* We need a sysfs file for the image/config block to write or read.
* Set up sysfs bin file for binary data block. Since the image is
* already in our format there is no need to convert the data for
* endianess. */
retval = sysfs_create_bin_file(&fc->dev.kobj,
&dev_attr_data);
if (retval < 0) {
dev_err(&fc->dev, "Failed to create sysfs file for F34 data "
"(error = %d).\n", retval);
retval = -ENODEV;
goto exit_free_data;
}
dev_dbg(&fc->dev, "Creating sysfs files.\n");
for (attr_count = 0; attr_count < ARRAY_SIZE(attrs); attr_count++) {
if (sysfs_create_file
(&fc->dev.kobj, &attrs[attr_count].attr) < 0) {
dev_err(&fc->dev, "Failed to create sysfs file for %s.",
attrs[attr_count].attr.name);
retval = -ENODEV;
goto exit_free_attrs;
}
}
return retval;
exit_free_attrs:
for (attr_count--; attr_count >= 0; attr_count--)
sysfs_remove_file(&fc->dev.kobj,
&attrs[attr_count].attr);
exit_free_data:
kfree(f34);
return retval;
}
static int f34_read_status(struct rmi_function_container *fc)
{
struct rmi_fn_34_data *instance_data = fc->data;
u16 data_base_addr = fc->fd.data_base_addr;
u8 status;
int retval;
/* Read the Fn $34 status from F34_Flash_Data3 to see the previous
* commands status. F34_Flash_Data3 will be the address after the
* 2 block number registers plus blocksize Data registers.
* inform user space - through a sysfs param. */
retval = rmi_read(fc->rmi_dev,
data_base_addr + instance_data->blocksize +
BLK_NUM_OFF, &status);
if (retval < 0) {
dev_err(&fc->dev, "Could not read status from 0x%x\n",
data_base_addr + instance_data->blocksize + BLK_NUM_OFF);
status = 0xff; /* failure */
}
/* set a sysfs value that the user mode can read - only
* upper 4 bits are the status. successful is $80, anything
* else is failure */
instance_data->status = status & 0xf0;
/* put mode into Flash Prog Mode when we successfully do
* an Enable Flash Prog cmd. */
if ((instance_data->status == STATUS_IDLE) &&
(instance_data->cmd == ENABLE_FLASH_PROG))
instance_data->inflashprogmode = true;
return retval;
}
int rmi_f34_attention(struct rmi_function_container *fc, u8 *irq_bits)
{
return f34_read_status(fc);
}
static struct rmi_function_handler function_handler = {
.func = 0x34,
.init = rmi_f34_init,
.attention = rmi_f34_attention
};
static ssize_t rmi_fn_34_bootloaderid_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct rmi_function_container *fc;
struct rmi_fn_34_data *instance_data;
fc = to_rmi_function_container(dev);
instance_data = fc->data;
return snprintf(buf, PAGE_SIZE, "%u\n", instance_data->bootloaderid);
}
static ssize_t rmi_fn_34_bootloaderid_store(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t count)
{
int error;
unsigned long val;
unsigned char data[2];
struct rmi_function_container *fc;
struct rmi_fn_34_data *instance_data;
u16 data_base_addr;
fc = to_rmi_function_container(dev);
instance_data = fc->data;
/* need to convert the string data to an actual value */
error = strict_strtoul(buf, 10, &val);
if (error)
return error;
instance_data->bootloaderid = val;
/* Write the Bootloader ID key data back to the first two Block
* Data registers (F34_Flash_Data2.0 and F34_Flash_Data2.1). */
hstoba(data, (unsigned short)val);
data_base_addr = fc->fd.data_base_addr;
error = rmi_write_block(fc->rmi_dev,
data_base_addr + BLK_NUM_OFF,
data,
ARRAY_SIZE(data));
if (error < 0) {
dev_err(dev, "%s : Could not write bootloader id to 0x%x\n",
__func__, data_base_addr + BLK_NUM_OFF);
return error;
}
return count;
}
static ssize_t rmi_fn_34_blocksize_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct rmi_function_container *fc;
struct rmi_fn_34_data *instance_data;
fc = to_rmi_function_container(dev);
instance_data = fc->data;
return snprintf(buf, PAGE_SIZE, "%u\n", instance_data->blocksize);
}
static ssize_t rmi_fn_34_imageblockcount_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct rmi_function_container *fc;
struct rmi_fn_34_data *instance_data;
fc = to_rmi_function_container(dev);
instance_data = fc->data;
return snprintf(buf, PAGE_SIZE, "%u\n",
instance_data->imageblockcount);
}
static ssize_t rmi_fn_34_configblockcount_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct rmi_function_container *fc;
struct rmi_fn_34_data *instance_data;
fc = to_rmi_function_container(dev);
instance_data = fc->data;
return snprintf(buf, PAGE_SIZE, "%u\n",
instance_data->configblockcount);
}
static ssize_t rmi_fn_34_status_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct rmi_function_container *fc;
struct rmi_fn_34_data *instance_data;
int retval;
fc = to_rmi_function_container(dev);
instance_data = fc->data;
retval = f34_read_status(fc);
return snprintf(buf, PAGE_SIZE, "%u\n", instance_data->status);
}
static ssize_t rmi_fn_34_cmd_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct rmi_function_container *fc;
struct rmi_fn_34_data *instance_data;
fc = to_rmi_function_container(dev);
instance_data = fc->data;
return snprintf(buf, PAGE_SIZE, "%u\n", instance_data->cmd);
}
static ssize_t rmi_fn_34_cmd_store(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t count)
{
struct rmi_function_container *fc;
struct rmi_fn_34_data *instance_data;
unsigned long val;
u16 data_base_addr;
int error;
fc = to_rmi_function_container(dev);
instance_data = fc->data;
data_base_addr = fc->fd.data_base_addr;
/* need to convert the string data to an actual value */
error = strict_strtoul(buf, 10, &val);
if (error)
return error;
/* make sure we are in Flash Prog mode for all cmds except the
* Enable Flash Programming cmd - otherwise we are in error */
if ((val != ENABLE_FLASH_PROG) && !instance_data->inflashprogmode) {
dev_err(dev, "%s: CANNOT SEND CMD %d TO SENSOR - "
"NOT IN FLASH PROG MODE\n"
, __func__, data_base_addr);
return -EINVAL;
}
instance_data->cmd = val;
/* Validate command value and (if necessary) write it to the command
* register.
*/
switch (instance_data->cmd) {
case ENABLE_FLASH_PROG:
case ERASE_ALL:
case ERASE_CONFIG:
case WRITE_FW_BLOCK:
case READ_CONFIG_BLOCK:
case WRITE_CONFIG_BLOCK:
/* Reset the status to indicate we are in progress on a cmd. */
/* The status will change when the ATTN interrupt happens
and the status of the cmd that was issued is read from
the F34_Flash_Data3 register - result should be 0x80 for
success - any other value indicates an error */
/* Issue the command to the device. */
error = rmi_write(fc->rmi_dev,
data_base_addr + instance_data->blocksize +
BLK_NUM_OFF, instance_data->cmd);
if (error < 0) {
dev_err(dev, "%s: Could not write command 0x%02x "
"to 0x%04x\n", __func__, instance_data->cmd,
data_base_addr + instance_data->blocksize +
BLK_NUM_OFF);
return error;
}
if (instance_data->cmd == ENABLE_FLASH_PROG)
instance_data->inflashprogmode = true;
/* set status to indicate we are in progress */
instance_data->status = STATUS_IN_PROGRESS;
break;
default:
dev_dbg(dev, "%s: RMI4 function $34 - "
"unknown command 0x%02lx.\n", __func__, val);
count = -EINVAL;
break;
}
return count;
}
static ssize_t rmi_fn_34_blocknum_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct rmi_function_container *fc;
struct rmi_fn_34_data *instance_data;
fc = to_rmi_function_container(dev);
instance_data = fc->data;
return snprintf(buf, PAGE_SIZE, "%u\n", instance_data->blocknum);
}
static ssize_t rmi_fn_34_blocknum_store(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t count)
{
int error;
unsigned long val;
unsigned char data[2];
struct rmi_function_container *fc;
struct rmi_fn_34_data *instance_data;
u16 data_base_addr;
fc = to_rmi_function_container(dev);
instance_data = fc->data;
data_base_addr = fc->fd.data_base_addr;
/* need to convert the string data to an actual value */
error = strict_strtoul(buf, 10, &val);
if (error)
return error;
instance_data->blocknum = val;
/* Write the Block Number data back to the first two Block
* Data registers (F34_Flash_Data_0 and F34_Flash_Data_1). */
hstoba(data, (unsigned short)val);
error = rmi_write_block(fc->rmi_dev,
data_base_addr,
data,
ARRAY_SIZE(data));
if (error < 0) {
dev_err(dev, "%s : Could not write block number %u to 0x%x\n",
__func__, instance_data->blocknum, data_base_addr);
return error;
}
return count;
}
static ssize_t rmi_fn_34_rescanPDT_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct rmi_function_container *fc;
struct rmi_fn_34_data *instance_data;
struct rmi_device *rmi_dev;
struct rmi_driver_data *driver_data;
struct pdt_entry pdt_entry;
bool fn01found = false;
bool fn34found = false;
unsigned int rescan;
int irq_count = 0;
int retval = 0;
int i;
/* Rescan of the PDT is needed since issuing the Flash Enable cmd
* the device registers for Fn$01 and Fn$34 moving around because
* of the change from Bootloader mode to Flash Programming mode
* may change to a different PDT with only Fn$01 and Fn$34 that
* could have addresses for query, control, data, command registers
* that differ from the PDT scan done at device initialization. */
fc = to_rmi_function_container(dev);
instance_data = fc->data;
rmi_dev = fc->rmi_dev;
driver_data = rmi_get_driverdata(rmi_dev);
/* Make sure we are only in Flash Programming mode - DON'T
* ALLOW THIS IN UI MODE. */
if (instance_data->cmd != ENABLE_FLASH_PROG) {
dev_err(dev, "%s: NOT IN FLASH PROG MODE - CAN'T RESCAN PDT.\n"
, __func__);
return -EINVAL;
}
/* The only good value to write to this is 1, we allow 0, but with
* no effect (this is consistent with the way the command bit works. */
if (sscanf(buf, "%u", &rescan) != 1)
return -EINVAL;
if (rescan < 0 || rescan > 1)
return -EINVAL;
/* 0 has no effect, so we skip it entirely. */
if (rescan) {
/* rescan the PDT - filling in Fn01 and Fn34 addresses -
* this is only temporary - the device will need to be reset
* to return the PDT to the normal values. */
/* mini-parse the PDT - we only have to get Fn$01 and Fn$34 and
since we are Flash Programming mode we only have page 0. */
for (i = PDT_START_SCAN_LOCATION; i >= PDT_END_SCAN_LOCATION;
i -= sizeof(pdt_entry)) {
retval = rmi_read_block(rmi_dev, i, (u8 *)&pdt_entry,
sizeof(pdt_entry));
if (retval != sizeof(pdt_entry)) {
dev_err(dev, "%s: err frm rmi_read_block pdt "
"entry data from PDT, "
"error = %d.", __func__, retval);
return retval;
}
if ((pdt_entry.function_number == 0x00) ||
(pdt_entry.function_number == 0xff))
break;
dev_dbg(dev, "%s: Found F%.2X\n",
__func__, pdt_entry.function_number);
/* f01 found - just fill in the new addresses in
* the existing fc. */
if (pdt_entry.function_number == 0x01) {
struct rmi_function_container *f01_fc =
driver_data->f01_container;
fn01found = true;
f01_fc->fd.query_base_addr =
pdt_entry.query_base_addr;
f01_fc->fd.command_base_addr =
pdt_entry.command_base_addr;
f01_fc->fd.control_base_addr =
pdt_entry.control_base_addr;
f01_fc->fd.data_base_addr =
pdt_entry.data_base_addr;
f01_fc->fd.function_number =
pdt_entry.function_number;
f01_fc->fd.interrupt_source_count =
pdt_entry.interrupt_source_count;
f01_fc->num_of_irqs =
pdt_entry.interrupt_source_count;
f01_fc->irq_pos = irq_count;
irq_count += f01_fc->num_of_irqs;
if (fn34found)
break;
}
/* f34 found - just fill in the new addresses in
* the existing fc. */
if (pdt_entry.function_number == 0x34) {
fn34found = true;
fc->fd.query_base_addr =
pdt_entry.query_base_addr;
fc->fd.command_base_addr =
pdt_entry.command_base_addr;
fc->fd.control_base_addr =
pdt_entry.control_base_addr;
fc->fd.data_base_addr =
pdt_entry.data_base_addr;
fc->fd.function_number =
pdt_entry.function_number;
fc->fd.interrupt_source_count =
pdt_entry.interrupt_source_count;
fc->num_of_irqs =
pdt_entry.interrupt_source_count;
fc->irq_pos = irq_count;
irq_count += fc->num_of_irqs;
if (fn01found)
break;
}
}
if (!fn01found || !fn34found) {
dev_err(dev, "%s: failed to find fn$01 or fn$34 trying "
"to do rescan PDT.\n"
, __func__);
return -EINVAL;
}
}
return count;
}
#ifdef KERNEL_VERSION_ABOVE_2_6_32
static ssize_t rmi_fn_34_data_read(struct file *data_file,
struct kobject *kobj,
struct bin_attribute *attributes,
char *buf,
loff_t pos,
size_t count)
#else
static ssize_t rmi_fn_34_data_read(struct kobject *kobj,
struct bin_attribute *attributes,
char *buf,
loff_t pos,
size_t count)
#endif
{
struct device *dev = container_of(kobj, struct device, kobj);
struct rmi_function_container *fc;
struct rmi_fn_34_data *instance_data;
u16 data_base_addr;
int error;
fc = to_rmi_function_container(dev);
instance_data = fc->data;
data_base_addr = fc->fd.data_base_addr;
if (count != instance_data->blocksize) {
dev_err(dev,
"%s : Incorrect F34 block size %d. "
"Expected size %d.\n",
__func__, count, instance_data->blocksize);
return -EINVAL;
}
/* Read the data from flash into buf. The app layer will be blocked
* at reading from the sysfs file. When we return the count (or
* error if we fail) the app will resume. */
error = rmi_read_block(fc->rmi_dev, data_base_addr + BLK_NUM_OFF,
(unsigned char *)buf, count);
if (error < 0) {
dev_err(dev, "%s : Could not read data from 0x%04x\n",
__func__, data_base_addr + BLK_NUM_OFF);
return error;
}
return count;
}
#ifdef KERNEL_VERSION_ABOVE_2_6_32
static ssize_t rmi_fn_34_data_write(struct file *data_file,
struct kobject *kobj,
struct bin_attribute *attributes,
char *buf,
loff_t pos,
size_t count)
#else
static ssize_t rmi_fn_34_data_write(struct kobject *kobj,
struct bin_attribute *attributes,
char *buf,
loff_t pos,
size_t count)
#endif
{
struct device *dev = container_of(kobj, struct device, kobj);
struct rmi_function_container *fc;
struct rmi_fn_34_data *instance_data;
u16 data_base_addr;
int error;
fc = to_rmi_function_container(dev);
instance_data = fc->data;
data_base_addr = fc->fd.data_base_addr;
/* Write the data from buf to flash. The app layer will be
* blocked at writing to the sysfs file. When we return the
* count (or error if we fail) the app will resume. */
if (count != instance_data->blocksize) {
dev_err(dev,
"%s : Incorrect F34 block size %d. "
"Expected size %d.\n",
__func__, count, instance_data->blocksize);
return -EINVAL;
}
/* Write the data block - only if the count is non-zero */
if (count) {
error = rmi_write_block(fc->rmi_dev,
data_base_addr + BLK_NUM_OFF,
(unsigned char *)buf,
count);
if (error < 0) {
dev_err(dev, "%s : Could not write block data "
"to 0x%x\n", __func__,
data_base_addr + BLK_NUM_OFF);
return error;
}
}
return count;
}
static int __init rmi_f34_module_init(void)
{
int error;
error = rmi_register_function_driver(&function_handler);
if (error < 0) {
pr_err("%s : register failed !\n", __func__);
return error;
}
return 0;
}
static void rmi_f34_module_exit(void)
{
rmi_unregister_function_driver(&function_handler);
}
module_init(rmi_f34_module_init);
module_exit(rmi_f34_module_exit);
MODULE_AUTHOR("Eric Andersson <eric.andersson@unixphere.com>");
MODULE_DESCRIPTION("RMI f34 module");
MODULE_LICENSE("GPL");