android_kernel_modules_leno.../drivers/platform/x86/intel_mid_osip.c

782 lines
20 KiB
C

/*
* OSIP driver for Medfield.
*
* Copyright (C) 2011 Intel Corp
*
* 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; version 2 of the License.
*
* 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.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/pci.h>
#include <linux/reboot.h>
#include <linux/pagemap.h>
#include <linux/blkdev.h>
#include <linux/debugfs.h>
#include <linux/genhd.h>
#include <linux/seq_file.h>
#include <linux/rpmsg.h>
#include <linux/version.h>
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 1))
#include <linux/platform_data/intel_mid_remoteproc.h>
#else
#include <asm/intel_mid_remoteproc.h>
#endif
#include <linux/delay.h>
#include <asm/intel_scu_ipcutil.h>
#include <asm/intel_mid_rpmsg.h>
#include <asm/intel-mid.h>
#include <asm/intel_scu_pmic.h>
#include "reboot_target.h"
/* change to "loop0" and use losetup for safe testing */
#define EMMC_OSIP_BLKDEVICE "mmcblk0"
#define HDD_OSIP_BLKDEVICE "sda"
#include <asm/intel_scu_ipc.h>
#include <linux/power_supply.h>
/* OSIP backup will be stored with this offset in the first sector */
#define OSIP_BACKUP_OFFSET 0xE0
#define MAX_OSII (7)
#define VALLEYVIEW2_FAMILY 0x30670
#define CPUID_MASK 0xffff0
#define DRV_VERSION "1.00"
struct OSII { /* os image identifier */
uint16_t os_rev_minor;
uint16_t os_rev_major;
uint32_t logical_start_block;
uint32_t ddr_load_address;
uint32_t entry_point;
uint32_t size_of_os_image;
uint8_t attribute;
uint8_t reserved[3];
};
struct OSIP_header { /* os image profile */
uint32_t sig;
uint8_t intel_reserved;
uint8_t header_rev_minor;
uint8_t header_rev_major;
uint8_t header_checksum;
uint8_t num_pointers;
uint8_t num_images;
uint16_t header_size;
uint32_t reserved[5];
struct OSII desc[MAX_OSII];
};
#ifdef CONFIG_INTEL_SCU_IPC
/* A boolean variable, that is set, when wants to make the platform
force shuts down */
static int force_shutdown_occured;
module_param(force_shutdown_occured, int, 0644);
MODULE_PARM_DESC(force_shutdown_occured,
"Variable to be set by user space"
" when a force shudown condition occurs, to allow"
" system shut down even with charger connected");
int get_force_shutdown_occured(void)
{
pr_info("%s, force_shutdown_occured=%d\n",
__func__, force_shutdown_occured);
return force_shutdown_occured;
}
EXPORT_SYMBOL(get_force_shutdown_occured);
#endif
int emmc_match(struct device *dev, const void *data)
{
if (strcmp(dev_name(dev), EMMC_OSIP_BLKDEVICE) == 0)
return 1;
return 0;
}
int hdd_match(struct device *dev, const void *data)
{
if (strcmp(dev_name(dev), HDD_OSIP_BLKDEVICE) == 0)
return 1;
return 0;
}
static struct block_device *get_bdev(void)
{
struct block_device *bdev;
struct device *block_disk;
block_disk = class_find_device(&block_class, NULL, NULL,
emmc_match);
if (block_disk == 0) {
block_disk = class_find_device(&block_class, NULL, NULL,
hdd_match);
if (block_disk == 0) {
pr_err("block disk not found!\n");
return NULL;
}
}
/* partition 0 means raw disk */
bdev = bdget_disk(dev_to_disk(block_disk), 0);
if (bdev == NULL) {
dev_err(block_disk, "unable to get disk\n");
return NULL;
}
/* Note: this bdev ref will be freed after first
bdev_get/bdev_put cycle */
return bdev;
}
static uint8_t calc_checksum(void *_buf, int size)
{
int i;
uint8_t checksum = 0;
uint8_t *buf = (uint8_t *)_buf;
for (i = 0; i < size; i++)
checksum = checksum ^ (buf[i]);
return checksum;
}
/*
Allows to access the osip image. Callback is passed for user to
implement actual usage.
This function takes care of the blkdev housekeeping
how to do proper block access is got from:
fs/partitions/check.c
mtd/devices/block2mtd.c
*/
/* callbacks returns whether the OSIP was modified */
typedef int (*osip_callback_t)(struct OSIP_header *osip, void *data);
static int access_osip_record(osip_callback_t callback, void *cb_data)
{
Sector sect;
struct block_device *bdev;
char *buffer;
struct OSIP_header *osip;
struct OSIP_header *osip_backup;
int ret = 0;
int dirty = 0;
bdev = get_bdev();
if (bdev == NULL) {
pr_err("%s: get_bdev failed!\n", __func__);
return -ENODEV;
}
/* make sure the block device is open rw */
ret = blkdev_get(bdev, FMODE_READ|FMODE_WRITE, NULL);
if (ret < 0) {
pr_err("%s: blk_dev_get failed!\n", __func__);
return -ret;
}
/* get memmap of the OSIP header */
buffer = read_dev_sector(bdev, 0, &sect);
if (buffer == NULL) {
ret = -ENODEV;
goto bd_put;
}
osip = (struct OSIP_header *) buffer;
/* some sanity checks */
if (osip->header_size <= 0 || osip->header_size > PAGE_SIZE) {
pr_err("%s: corrupted osip!\n", __func__);
ret = -EINVAL;
goto put_sector;
}
if (calc_checksum(osip, osip->header_size) != 0) {
pr_err("%s: corrupted osip!\n", __func__);
ret = -EINVAL;
goto put_sector;
}
/* store the OSIP backup which will be used to recover in PrOS */
osip_backup = kmalloc(sizeof(struct OSIP_header), GFP_KERNEL);
if (osip_backup == NULL)
goto put_sector;
memcpy(osip_backup, osip, sizeof(struct OSIP_header));
lock_page(sect.v);
dirty = callback(osip, cb_data);
if (dirty) {
memcpy(buffer + OSIP_BACKUP_OFFSET, osip_backup,
sizeof(struct OSIP_header));
osip->header_checksum = 0;
osip->header_checksum = calc_checksum(osip, osip->header_size);
set_page_dirty(sect.v);
}
unlock_page(sect.v);
sync_blockdev(bdev);
kfree(osip_backup);
put_sector:
put_dev_sector(sect);
bd_put:
blkdev_put(bdev, FMODE_READ|FMODE_WRITE);
return 0;
}
/* find OSII index for a given OS attribute */
static int get_osii_index_cb(struct OSIP_header *osip, void *data)
{
int attr = *(int *)data;
int i;
*(int *)data = 0;
for (i = 0; i < osip->num_pointers; i++) {
if ((osip->desc[i].attribute & ~0x1) == (attr & ~0x1) && i < MAX_OSII) {
*(int *)data = i;
break;
}
}
return 0;
}
static int get_osii_index(int attribute)
{
int data = attribute;
access_osip_record(get_osii_index_cb, (void *)(&data));
return data;
}
#ifdef CONFIG_INTEL_SCU_IPC
/*
OSHOB - OS Handoff Buffer
OSNIB - OS No Init Buffer
This buffer contains the 32-byte value that is persists across cold and warm
resets only, but loses context on a cold boot.
More info about OSHOB, OSNIB could be found in FAS Section 2.8.
We use the first byte in OSNIB to store and pass the Reboot/boot Reason.
The attribute of OS image is selected for Reboot/boot reason.
*/
static int osip_invalidate(struct OSIP_header *osip, void *data)
{
unsigned int id = (unsigned int)(uintptr_t)data;
osip->desc[id].ddr_load_address = 0;
osip->desc[id].entry_point = 0;
return 1;
}
static int osip_restore(struct OSIP_header *osip, void *data)
{
unsigned int id = (unsigned int)(uintptr_t)data;
/* hardcoding addresses. According to the FAS, this is how
the OS image blob has to be loaded, and where is the
bootstub entry point.
*/
osip->desc[id].ddr_load_address = 0x1100000;
osip->desc[id].entry_point = 0x1101000;
return 1;
}
/* Cold off sequence is initiated 4 sec after power button long press starts. */
/* In case of force shutdown, we delay cold off IPC sending by 5 seconds to make */
/* sure PMIC fault timer has a chance to elapsed after power button is held */
/* down for 8 seconds */
#define FORCE_SHUTDOWN_DELAY_IN_MSEC 5000
#define PMIC_USBIDCTRL_ADDR 0x19
static int osip_shutdown_notifier_call(struct notifier_block *notifier,
unsigned long what, void *data)
{
int ret = NOTIFY_DONE;
char *cmd = (char *)data;
if (what == SYS_HALT || what == SYS_POWER_OFF) {
pr_info("%s: notified [%s] command\n", __func__, cmd);
pr_info("%s(): sys power off ...\n", __func__);
if (get_force_shutdown_occured()) {
/* BZ 174011 software workaround: clear USBIDCTRL to avoid reboot */
ret = intel_scu_ipc_iowrite8(PMIC_USBIDCTRL_ADDR, 0x00);
if (ret)
pr_err("%s(): PMIC USBIDCTRL clear failed\n", __func__);
pr_warn("[SHTDWN] %s: Force shutdown occured, delaying ...\n",
__func__);
mdelay(FORCE_SHUTDOWN_DELAY_IN_MSEC);
}
else
pr_warn("[SHTDWN] %s, Not in force shutdown\n",
__func__);
/*
* PNW and CLVP depend on watchdog driver to
* send COLD OFF message to SCU.
* TNG and ANN use COLD_OFF IPC message to shut
* down the system.
*/
if ((intel_mid_identify_cpu() == INTEL_MID_CPU_CHIP_TANGIER) ||
(intel_mid_identify_cpu() == INTEL_MID_CPU_CHIP_ANNIEDALE)) {
pr_err("[SHTDWN] %s, executing COLD_OFF...\n", __func__);
ret = rpmsg_send_generic_simple_command(RP_COLD_OFF, 0);
if (ret)
pr_err("%s(): COLD_OFF ipc failed\n", __func__);
}
}
/* Reboot actions will be handled by osip_reboot_target_call */
return NOTIFY_DONE;
}
static int osip_reboot_target_call(const char *target, int id)
{
int ret_ipc;
#ifdef DEBUG
u8 rbt_reason;
#endif
pr_info("%s: notified [%s] target\n", __func__, target);
pr_warn("[REBOOT] %s, rebooting into %s\n", __func__, target);
#ifdef DEBUG
if (id == SIGNED_RECOVERY_ATTR)
intel_scu_ipc_read_osnib_rr(&rbt_reason);
#endif
ret_ipc = intel_scu_ipc_write_osnib_rr(id);
if (ret_ipc < 0)
pr_err("%s cannot write %s reboot reason in OSNIB\n",
__func__, target);
if (id == SIGNED_MOS_ATTR || id == SIGNED_POS_ATTR) {
/* If device is already in RECOVERY we must be able */
/* to reboot in MOS if given target is MOS or POS. */
pr_warn("[REBOOT] %s, restoring OSIP\n", __func__);
access_osip_record(osip_restore, (void *)(get_osii_index(SIGNED_MOS_ATTR)));
}
if (id == SIGNED_RECOVERY_ATTR && ret_ipc >= 0) {
pr_warn("[REBOOT] %s, invalidating osip\n", __func__);
access_osip_record(osip_invalidate, (void *)(get_osii_index(SIGNED_MOS_ATTR)));
}
return NOTIFY_DONE;
}
static struct notifier_block osip_shutdown_notifier = {
.notifier_call = osip_shutdown_notifier_call,
};
static struct reboot_target osip_reboot_target = {
.set_reboot_target = osip_reboot_target_call,
};
#endif
/* useful for engineering, not for product */
#ifdef CONFIG_INTEL_MID_OSIP_DEBUG_FS
/* show and edit boot.bin's cmdline */
#define OSIP_MAX_CMDLINE_SIZE 0x400
/* number N of sectors (512bytes) needed for the cmdline, N+1 needed */
#define OSIP_MAX_CMDLINE_SECTOR ((OSIP_MAX_CMDLINE_SIZE >> 9) + 1)
/* Size used by signature is not the same for valleyview */
#define OSIP_SIGNATURE_SIZE_MDFLD 0x1E0
#define OSIP_SIGNATURE_SIZE 0x2D8
#define OSIP_VALLEYVIEW_SIGNATURE_SIZE 0x400
struct cmdline_priv {
Sector sect[OSIP_MAX_CMDLINE_SECTOR];
struct block_device *bdev;
int lba;
char *cmdline;
unsigned int osip_id;
uint8_t attribute;
};
static int osip_find_cmdline(struct OSIP_header *osip, void *data)
{
struct cmdline_priv *p = (struct cmdline_priv *) data;
if (p->osip_id < MAX_OSII) {
p->attribute = osip->desc[p->osip_id].attribute;
p->lba = osip->desc[p->osip_id].logical_start_block;
}
return 0;
}
int open_cmdline(struct inode *i, struct file *f)
{
struct cmdline_priv *p;
int ret, j;
p = kzalloc(sizeof(struct cmdline_priv), GFP_KERNEL);
if (!p) {
pr_err("%s: unable to allocate p!\n", __func__);
ret = -ENOMEM;
goto end;
}
if (i->i_private)
p->osip_id = (unsigned int)(uintptr_t) i->i_private;
f->private_data = 0;
access_osip_record(osip_find_cmdline, (void *)p);
if (!p->lba) {
pr_err("%s: osip_find_cmdline failed!\n", __func__);
ret = -ENODEV;
goto free;
}
/* need to open it again */
p->bdev = get_bdev();
if (!p->bdev) {
pr_err("%s: access_osip_record failed!\n", __func__);
ret = -ENODEV;
goto free;
}
ret = blkdev_get(p->bdev, f->f_mode, NULL);
if (ret < 0) {
pr_err("%s: blk_dev_get failed!\n", __func__);
ret = -ENODEV;
goto free;
}
if (p->lba >= get_capacity(p->bdev->bd_disk)) {
pr_err("%s: %d out of disk bound!\n", __func__, p->lba);
ret = -EINVAL;
goto put;
}
for (j = (OSIP_MAX_CMDLINE_SECTOR - 1); j >= 0; j--) {
p->cmdline = read_dev_sector(p->bdev,
p->lba + j,
&p->sect[j]);
}
if (!p->cmdline) {
pr_err("%s: read_dev_sector failed!\n", __func__);
ret = -ENODEV;
goto put;
}
if (!(p->attribute & 1))
/* even number: signed add size of signature header. */
#ifdef CONFIG_INTEL_SCU_IPC
#ifdef CONFIG_X86_MDFLD
p->cmdline += OSIP_SIGNATURE_SIZE_MDFLD;
#else
p->cmdline += OSIP_SIGNATURE_SIZE;
#endif
#else
p->cmdline += OSIP_VALLEYVIEW_SIGNATURE_SIZE;
#endif
f->private_data = p;
return 0;
put:
blkdev_put(p->bdev, f->f_mode);
free:
kfree(p);
end:
return ret;
}
static ssize_t read_cmdline(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
struct cmdline_priv *p =
(struct cmdline_priv *)file->private_data;
if (!p)
return -ENODEV;
return simple_read_from_buffer(buf, count, ppos,
p->cmdline, strnlen(p->cmdline, OSIP_MAX_CMDLINE_SIZE));
}
static ssize_t write_cmdline(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
int ret, i;
struct cmdline_priv *p;
if (!file)
return -ENODEV;
p = (struct cmdline_priv *)file->private_data;
if (!p)
return -ENODEV;
/* @todo detect if image is signed, and prevent write */
lock_page(p->sect[0].v);
for (i = 1; i < OSIP_MAX_CMDLINE_SECTOR; i++) {
if (p->sect[i-1].v != p->sect[i].v)
lock_page(p->sect[i].v);
}
ret = simple_write_to_buffer(p->cmdline, OSIP_MAX_CMDLINE_SIZE-1,
ppos,
buf, count);
if (ret < 0)
goto unlock;
/* make sure we zero terminate the cmdline */
if (file->f_pos + count < OSIP_MAX_CMDLINE_SIZE)
p->cmdline[file->f_pos + count] = '\0';
unlock:
set_page_dirty(p->sect[0].v);
unlock_page(p->sect[0].v);
for (i = 1; i < OSIP_MAX_CMDLINE_SECTOR; i++) {
if (p->sect[i-1].v != p->sect[i].v) {
set_page_dirty(p->sect[i].v);
unlock_page(p->sect[i].v);
}
}
return ret;
}
int release_cmdline(struct inode *i, struct file *f)
{
int j;
struct cmdline_priv *p =
(struct cmdline_priv *)f->private_data;
if (!p)
return -ENOMEM;
put_dev_sector(p->sect[0]);
for (j = 1; j < OSIP_MAX_CMDLINE_SECTOR; j++)
if (p->sect[j-1].v != p->sect[j].v)
put_dev_sector(p->sect[j]);
blkdev_put(p->bdev, f->f_mode);
kfree(p);
return 0;
}
int fsync_cmdline(struct file *f, loff_t start, loff_t end, int datasync)
{
struct cmdline_priv *p =
(struct cmdline_priv *)f->private_data;
if (!p)
return -ENOMEM;
sync_blockdev(p->bdev);
return 0;
}
static const struct file_operations osip_cmdline_fops = {
.open = open_cmdline,
.read = read_cmdline,
.write = write_cmdline,
.release = release_cmdline,
.fsync = fsync_cmdline
};
/* decode the osip */
static int decode_show_cb(struct OSIP_header *osip, void *data)
{
struct seq_file *s = (struct seq_file *) data;
int i;
seq_printf(s, "HEADER:\n"
"\tsig = 0x%x\n"
"\theader_size = 0x%hx\n"
"\theader_rev_minor = 0x%hhx\n"
"\theader_rev_major = 0x%hhx\n"
"\theader_checksum = 0x%hhx\n"
"\tnum_pointers = 0x%hhx\n"
"\tnum_images = 0x%hhx\n",
osip->sig,
osip->header_size,
osip->header_rev_minor,
osip->header_rev_major,
osip->header_checksum,
osip->num_pointers,
osip->num_images);
for (i = 0; i < osip->num_pointers; i++)
seq_printf(s, "image%d\n"
"\tos_rev = 0x%0hx\n"
"\tos_rev = 0x%hx\n"
"\tlogical_start_block = 0x%x\n"
"\tddr_load_address = 0x%0x\n"
"\tentry_point = 0x%0x\n"
"\tsize_of_os_image = 0x%x\n"
"\tattribute = 0x%02x\n"
"\treserved = %02x%02x%02x\n",
i,
osip->desc[i].os_rev_minor,
osip->desc[i].os_rev_major,
osip->desc[i].logical_start_block,
osip->desc[i].ddr_load_address,
osip->desc[i].entry_point,
osip->desc[i].size_of_os_image,
osip->desc[i].attribute,
osip->desc[i].reserved[0],
osip->desc[i].reserved[1],
osip->desc[i].reserved[2]);
return 0;
}
static int decode_show(struct seq_file *s, void *unused)
{
access_osip_record(decode_show_cb, (void *)s);
return 0;
}
static int decode_open(struct inode *inode, struct file *file)
{
return single_open(file, decode_show, NULL);
}
static const struct file_operations osip_decode_fops = {
.open = decode_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static struct dentry *osip_dir;
static void create_debugfs_files(void)
{
/* /sys/kernel/debug/osip */
osip_dir = debugfs_create_dir("osip", NULL);
/* /sys/kernel/debug/osip/cmdline */
(void) debugfs_create_file("cmdline",
S_IFREG | S_IRUGO | S_IWUSR | S_IWGRP,
osip_dir, (void *)(get_osii_index(SIGNED_MOS_ATTR)), &osip_cmdline_fops);
/* /sys/kernel/debug/osip/cmdline_ros */
(void) debugfs_create_file("cmdline_ros",
S_IFREG | S_IRUGO | S_IWUSR | S_IWGRP,
osip_dir, (void *)(get_osii_index(SIGNED_RECOVERY_ATTR)), &osip_cmdline_fops);
/* /sys/kernel/debug/osip/cmdline_pos */
(void) debugfs_create_file("cmdline_pos",
S_IFREG | S_IRUGO | S_IWUSR | S_IWGRP,
osip_dir, (void *)(get_osii_index(SIGNED_POS_ATTR)), &osip_cmdline_fops);
/* /sys/kernel/debug/osip/decode */
(void) debugfs_create_file("decode",
S_IFREG | S_IRUGO,
osip_dir, NULL, &osip_decode_fops);
}
static void remove_debugfs_files(void)
{
debugfs_remove_recursive(osip_dir);
}
#else /* defined(CONFIG_INTEL_MID_OSIP_DEBUG_FS) */
static void create_debugfs_files(void)
{
}
static void remove_debugfs_files(void)
{
}
#endif
static int osip_init(void)
{
#ifdef CONFIG_INTEL_SCU_IPC
pr_info("%s: shutdown_notifier registered\n", __func__);
if (register_reboot_notifier(&osip_shutdown_notifier))
pr_warning("osip: unable to register shutdown notifier");
pr_info("%s: reboot_target registered\n", __func__);
if (reboot_target_register(&osip_reboot_target))
pr_warning("osip: unable to register reboot notifier");
#endif
create_debugfs_files();
return 0;
}
static void osip_exit(void)
{
#ifdef CONFIG_INTEL_SCU_IPC
pr_info("%s: shutdown_notifier unregistered\n", __func__);
unregister_reboot_notifier(&osip_shutdown_notifier);
pr_info("%s: reboot_target unregistered\n", __func__);
reboot_target_unregister(&osip_reboot_target);
#endif
remove_debugfs_files();
}
#ifdef CONFIG_INTEL_SCU_IPC
static int osip_rpmsg_probe(struct rpmsg_channel *rpdev)
{
if (rpdev == NULL) {
pr_err("rpmsg channel not created\n");
return -ENODEV;
}
dev_info(&rpdev->dev, "Probed OSIP rpmsg device\n");
return osip_init();
}
static void osip_rpmsg_cb(struct rpmsg_channel *rpdev, void *data,
int len, void *priv, u32 src)
{
dev_warn(&rpdev->dev, "unexpected, message\n");
print_hex_dump(KERN_DEBUG, __func__, DUMP_PREFIX_NONE, 16, 1,
data, len, true);
}
static void osip_rpmsg_remove(struct rpmsg_channel *rpdev)
{
osip_exit();
dev_info(&rpdev->dev, "Removed OSIP rpmsg device\n");
}
static struct rpmsg_device_id osip_rpmsg_id_table[] = {
{ .name = "rpmsg_osip" },
{ },
};
MODULE_DEVICE_TABLE(rpmsg, osip_rpmsg_id_table);
static struct rpmsg_driver osip_rpmsg_driver = {
.drv.name = KBUILD_MODNAME,
.drv.owner = THIS_MODULE,
.id_table = osip_rpmsg_id_table,
.probe = osip_rpmsg_probe,
.callback = osip_rpmsg_cb,
.remove = osip_rpmsg_remove,
};
static int __init osip_rpmsg_init(void)
{
return register_rpmsg_driver(&osip_rpmsg_driver);
}
module_init(osip_rpmsg_init);
static void __exit osip_rpmsg_exit(void)
{
return unregister_rpmsg_driver(&osip_rpmsg_driver);
}
module_exit(osip_rpmsg_exit);
#else /* ! defined(CONFIG_INTEL_SCU_IPC) */
static int __init osip_probe(struct platform_device *dev)
{
return osip_init();
}
static int osip_remove(struct platform_device *dev)
{
osip_exit();
return 0;
}
static struct platform_driver osip_driver = {
.remove = osip_remove,
.driver = {
.owner = THIS_MODULE,
.name = KBUILD_MODNAME,
},
};
static int __init osip_init_module(void)
{
int err=0;
pr_info("Intel OSIP Driver v%s\n", DRV_VERSION);
platform_device_register_simple(KBUILD_MODNAME, -1, NULL, 0);
err = platform_driver_probe(&osip_driver,osip_probe);
return err;
}
static void __exit osip_cleanup_module(void)
{
platform_driver_unregister(&osip_driver);
pr_info("OSIP Module Unloaded\n");
}
module_init(osip_init_module);
module_exit(osip_cleanup_module);
#endif
MODULE_AUTHOR("Pierre Tardy <pierre.tardy@intel.com>");
MODULE_AUTHOR("Xiaokang Qin <xiaokang.qin@intel.com>");
MODULE_DESCRIPTION("Intel Medfield OSIP Driver");
MODULE_LICENSE("GPL v2");