11 changed files with 2965 additions and 0 deletions
@ -0,0 +1,609 @@
|
||||
/*
|
||||
* dspdl core driver |
||||
* |
||||
* Copyright (C) 2008 Lyrtech <www.lyrtech.com> |
||||
* |
||||
* Based on code found in book "Linux Device Drivers" by |
||||
* Alessandro Rubini and Jonathan Corbet, published by O'Reilly & Associates. |
||||
* |
||||
* 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/module.h> |
||||
#include <linux/moduleparam.h> |
||||
#include <linux/init.h> |
||||
#include <linux/device.h> |
||||
#include <linux/platform_device.h> |
||||
#include <linux/miscdevice.h> |
||||
#include <linux/string.h> |
||||
#include <linux/err.h> |
||||
#include <linux/fs.h> |
||||
#include <linux/slab.h> |
||||
#include <linux/vmalloc.h> |
||||
#include <linux/firmware.h> |
||||
#include <linux/delay.h> |
||||
#include <linux/dspdl.h> |
||||
#include <linux/dspdl-coff.h> |
||||
|
||||
#include <asm/uaccess.h> |
||||
|
||||
#define MODULE_NAME "dspdl" |
||||
|
||||
/* Define this to enable verbose debug messages */ |
||||
#define DSPDL_DEBUG 1 |
||||
|
||||
static const char dspdl_driver_version[] = "v1.0"; |
||||
|
||||
/* Module parameters */ |
||||
static unsigned int dspdl_debug; |
||||
module_param_named(debug, dspdl_debug, int, 0644); |
||||
|
||||
#ifdef DSPDL_DEBUG |
||||
#define INFOMSG(fmt, args...) \ |
||||
do { \
|
||||
printk(KERN_INFO "%s: "fmt"\n", MODULE_NAME, ## args); \
|
||||
} while (0) |
||||
#define DBGMSG(fmt, args...) \ |
||||
do { \
|
||||
if (dspdl_debug > 0) \
|
||||
printk(KERN_DEBUG "%s: "fmt"\n", \
|
||||
MODULE_NAME, ## args); \
|
||||
} while (0) |
||||
#define DBGMSG_ENTER() \ |
||||
DBGMSG("%s() enter", __func__); |
||||
#define DBGMSG_LEAVE() \ |
||||
DBGMSG("%s() leave", __func__); |
||||
#else |
||||
#define INFOMSG(fmt, args...) do {} while (0) |
||||
#define DBGMSG(fmt, args...) do {} while (0) |
||||
#define DBGMSG_ENTER() do {} while (0) |
||||
#define DBGMSG_LEAVE() do {} while (0) |
||||
#endif |
||||
|
||||
#define FAILMSG(fmt, args...) \ |
||||
do { \
|
||||
printk(KERN_ERR "%s: "fmt"\n", MODULE_NAME, ## args); \
|
||||
} while (0) |
||||
|
||||
#define DSPDL_FW_MAX_SIZE 0x800000 |
||||
|
||||
#define MAX_DSPDL_DEV 4 |
||||
|
||||
static unsigned fw_max_size = DSPDL_FW_MAX_SIZE; |
||||
static int dspdl_dev_count; |
||||
static struct dspdl_device *dspdl_dev_array[MAX_DSPDL_DEV]; |
||||
|
||||
/* Respond to hotplug events. */ |
||||
static int dspdl_uevent(struct device *dev, struct kobj_uevent_env *env) |
||||
{ |
||||
DBGMSG_ENTER(); |
||||
|
||||
if (add_uevent_var(env, "DSPDL_BUS_VERSION=%s", dspdl_driver_version)) |
||||
return -ENOMEM; |
||||
return 0; |
||||
}; |
||||
|
||||
static int dspdl_fw_load(struct dspdl_device *dspdl_dev, |
||||
const u8 *data, size_t size) |
||||
{ |
||||
int i; |
||||
struct file_hdr_t *file_hdr; |
||||
struct opt_hdr_t *opt_hdr; |
||||
struct section_hdr_t *section_hdr; |
||||
int ret; |
||||
|
||||
if (size == 0) { |
||||
return 0; |
||||
} |
||||
|
||||
dspdl_dev->fw_loaded = 0; |
||||
|
||||
DBGMSG("DSP %d: Starting programming", dspdl_dev->id); |
||||
|
||||
if (dspdl_dev->cb_start) { |
||||
ret = dspdl_dev->cb_start(dspdl_dev); |
||||
if (ret < 0) |
||||
return ret; |
||||
} |
||||
|
||||
/* Extract header infos. */ |
||||
file_hdr = (struct file_hdr_t *) data; |
||||
if (file_hdr->magic != 0xC2) { |
||||
FAILMSG("Invalid magic number in header (0x%02X)", |
||||
file_hdr->magic); |
||||
return -EFAULT; |
||||
} |
||||
if (file_hdr->target_id != 0x0099) { |
||||
FAILMSG("Invalid target ID in header (0x%04X)", |
||||
file_hdr->target_id); |
||||
return -EFAULT; |
||||
} |
||||
|
||||
/* Make sure file is a valid COFF. */ |
||||
if (!(file_hdr->flags & F_EXEC)) { |
||||
FAILMSG("Not an executable"); |
||||
return -ENOEXEC; |
||||
} |
||||
|
||||
/* The DSPBOOTADDR contains the upper 22 bits of the DSP reset vector.
|
||||
* This driver creates a reset vector that is always located at the |
||||
* very beginning of the DDR2 memory region occupied by the DSP. |
||||
* Therefore, the DSP application must not use the first 12 bytes of |
||||
* DDR2 memory. */ |
||||
opt_hdr = (struct opt_hdr_t *) (data + sizeof(struct file_hdr_t)); |
||||
DBGMSG(" Entry point: $%08X", opt_hdr->entrypt); |
||||
|
||||
/* Points to first Section */ |
||||
section_hdr = (struct section_hdr_t *) |
||||
((void *) opt_hdr + file_hdr->opthdr); |
||||
|
||||
/* Scan each section and copies their data into memory if their flag is
|
||||
* set to STYP_COPY */ |
||||
for (i = 0; i < file_hdr->nscns; i++) { |
||||
if ((section_hdr->s_size != 0) && |
||||
(section_hdr->s_scnptr != 0) && |
||||
!(section_hdr->s_flags & STYP_COPY)) { |
||||
|
||||
DBGMSG(" Section %02d, name = \"%s\"", i, |
||||
section_hdr->s_name); |
||||
DBGMSG(" addr = $%08X, size = $%08X", |
||||
section_hdr->s_paddr, section_hdr->s_size); |
||||
|
||||
ret = dspdl_dev->cb_write_section( |
||||
dspdl_dev, section_hdr->s_paddr, |
||||
(u8 *) (data + section_hdr->s_scnptr), |
||||
section_hdr->s_size); |
||||
if (ret < 0) |
||||
return ret; |
||||
} |
||||
section_hdr++; |
||||
} |
||||
|
||||
if (dspdl_dev->cb_finish) { |
||||
ret = dspdl_dev->cb_finish(dspdl_dev, opt_hdr->entrypt); |
||||
if (ret < 0) |
||||
return ret; |
||||
} |
||||
|
||||
INFOMSG("DSP %d: Firmware loaded", dspdl_dev->id); |
||||
|
||||
dspdl_dev->fw_loaded = 1; |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
/* Open method. */ |
||||
static int dspdl_open(struct inode *inode, struct file *filp) |
||||
{ |
||||
int k; |
||||
int found = 0; |
||||
struct dspdl_device *dspdl_dev; |
||||
|
||||
DBGMSG_ENTER(); |
||||
DBGMSG(" Opening device minor %d", MINOR(inode->i_rdev)); |
||||
|
||||
for (k = 0; k < dspdl_dev_count; k++) { |
||||
dspdl_dev = dspdl_dev_array[k]; |
||||
if (dspdl_dev) { |
||||
if (dspdl_dev->miscdev.minor == MINOR(inode->i_rdev)) { |
||||
found = 1; |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
if (!found) { |
||||
FAILMSG(" Invalid minor device"); |
||||
return -ENOMEM; |
||||
} |
||||
|
||||
filp->private_data = dspdl_dev; |
||||
|
||||
dspdl_dev->fw_length = 0; |
||||
|
||||
#ifdef USE_KMALLOC // Use kmaloc
|
||||
fw_max_size = DSPDL_FW_MAX_SIZE; |
||||
dspdl_dev->fw_data = kmalloc(fw_max_size, |
||||
GFP_KERNEL); |
||||
if (!dspdl_dev->fw_data) { |
||||
fw_max_size -= 0x200000; |
||||
dspdl_dev->fw_data = kmalloc(fw_max_size, |
||||
GFP_KERNEL); |
||||
if (!dspdl_dev->fw_data) { |
||||
fw_max_size -= 0x200000; |
||||
dspdl_dev->fw_data = kmalloc(fw_max_size, |
||||
GFP_KERNEL); |
||||
if (!dspdl_dev->fw_data) { |
||||
FAILMSG("Failed to allocate memory for firmware"); |
||||
return -ENOMEM; |
||||
} |
||||
} |
||||
}a |
||||
#else // Use vmalloc
|
||||
fw_max_size = DSPDL_FW_MAX_SIZE; |
||||
dspdl_dev->fw_data = vmalloc(fw_max_size); |
||||
if (!dspdl_dev->fw_data) { |
||||
FAILMSG("Failed to allocate memory for firmware"); |
||||
return -ENOMEM; |
||||
} |
||||
#endif |
||||
|
||||
dspdl_dev->fw_buffer_allocated = 1; |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
/* Write method. Fill buffer with bitstream data. */ |
||||
static ssize_t dspdl_write(struct file *filp, const char __user *buff, |
||||
size_t count, loff_t *offp) |
||||
{ |
||||
struct dspdl_device *dspdl_dev = filp->private_data; |
||||
|
||||
if ((dspdl_dev->fw_length + count) >= fw_max_size) { |
||||
FAILMSG("Bitstream buffer size exceeded"); |
||||
return -EFBIG; |
||||
} |
||||
|
||||
if (copy_from_user(dspdl_dev->fw_data + |
||||
dspdl_dev->fw_length, |
||||
(void __user *) buff, count)) |
||||
return -EFAULT; |
||||
|
||||
dspdl_dev->fw_length += count; |
||||
|
||||
return count; |
||||
} |
||||
|
||||
/* Release method. This will initiate the DSP programming. */ |
||||
static int dspdl_release(struct inode *inode, struct file *filp) |
||||
{ |
||||
int retval; |
||||
struct dspdl_device *dspdl_dev = filp->private_data; |
||||
|
||||
if (!dspdl_dev->fw_data) |
||||
return -EFAULT; |
||||
|
||||
retval = dspdl_fw_load(dspdl_dev, |
||||
dspdl_dev->fw_data, |
||||
dspdl_dev->fw_length); |
||||
|
||||
#ifdef USE_KMALLOC // Use kmaloc
|
||||
kfree(dspdl_dev->fw_data); |
||||
#else // Use vmalloc
|
||||
vfree(dspdl_dev->fw_data); |
||||
#endif |
||||
dspdl_dev->fw_buffer_allocated = 0; |
||||
|
||||
return retval; |
||||
} |
||||
|
||||
static long dspdl_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) |
||||
{ |
||||
int retval; |
||||
void __user *argp = (void __user *)arg; |
||||
struct dspdl_device *dspdl_dev = filp->private_data; |
||||
|
||||
switch ( cmd ) |
||||
{ |
||||
// 32-bit read
|
||||
case 0: |
||||
{ |
||||
u32 addr, data; |
||||
|
||||
if (copy_from_user(&addr, argp, sizeof(u32))) |
||||
return -EFAULT; |
||||
|
||||
if (dspdl_dev->cb_read_mem) { |
||||
retval = dspdl_dev->cb_read_mem(dspdl_dev, addr, &data); |
||||
if (retval < 0) |
||||
return retval; |
||||
|
||||
if (copy_to_user(argp, &data, sizeof(u32))) |
||||
return -EFAULT; |
||||
|
||||
break; |
||||
} |
||||
return -EFAULT; |
||||
} |
||||
// 32-bit write
|
||||
case 1: |
||||
{ |
||||
u32 addr, data; |
||||
|
||||
if (copy_from_user(&addr, argp, sizeof(u32))) |
||||
return -EFAULT; |
||||
if (copy_from_user(&data, argp + sizeof(u32), sizeof(u32))) |
||||
return -EFAULT; |
||||
|
||||
if (dspdl_dev->cb_write_mem) { |
||||
retval = dspdl_dev->cb_write_mem(dspdl_dev, addr, data); |
||||
if (retval < 0) |
||||
return retval; |
||||
break; |
||||
} |
||||
return -EFAULT; |
||||
} |
||||
default: |
||||
{ |
||||
return -EINVAL; |
||||
} |
||||
} |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
|
||||
|
||||
static struct file_operations fops_dspdl = { |
||||
.owner = THIS_MODULE, |
||||
.open = dspdl_open, |
||||
.write = dspdl_write, |
||||
.unlocked_ioctl = dspdl_ioctl, |
||||
.release = dspdl_release, |
||||
}; |
||||
|
||||
/* Match dspdl devices to drivers. Just do a simple name test. */ |
||||
static int dspdl_device_match(struct device *dev, |
||||
struct device_driver *drv) |
||||
{ |
||||
DBGMSG_ENTER(); |
||||
return !strncmp(dev_name(dev), drv->name, strlen(drv->name)); |
||||
} |
||||
|
||||
static ssize_t show_version(struct device_driver *driver, char *buf) |
||||
{ |
||||
struct dspdl_driver *dspdldriver = to_dspdl_driver(driver); |
||||
|
||||
sprintf(buf, "%s\n", dspdldriver->version); |
||||
return strlen(buf); |
||||
} |
||||
|
||||
int dspdl_register_driver(struct dspdl_driver *drv) |
||||
{ |
||||
int res; |
||||
|
||||
DBGMSG_ENTER(); |
||||
|
||||
/* Initialize common driver fields */ |
||||
drv->driver.bus = &dspdl_bus_type; |
||||
|
||||
/* Register with core */ |
||||
res = driver_register(&drv->driver); |
||||
if (res) |
||||
FAILMSG(" driver_register() failed"); |
||||
|
||||
drv->version_attr.attr.name = "version"; |
||||
drv->version_attr.attr.mode = S_IRUGO; |
||||
drv->version_attr.show = show_version; |
||||
drv->version_attr.store = NULL; |
||||
res = driver_create_file(&drv->driver, &drv->version_attr); |
||||
|
||||
return res; |
||||
} |
||||
EXPORT_SYMBOL(dspdl_register_driver); |
||||
|
||||
void dspdl_unregister_driver(struct dspdl_driver *drv) |
||||
{ |
||||
DBGMSG_ENTER(); |
||||
driver_unregister(&drv->driver); |
||||
} |
||||
EXPORT_SYMBOL(dspdl_unregister_driver); |
||||
|
||||
/* The dspdl bus device. */ |
||||
static void dspdl_bus_release(struct device *dev) |
||||
{ |
||||
DBGMSG_ENTER(); |
||||
} |
||||
|
||||
struct device dspdl_bus = { |
||||
.init_name = "dspdl0", |
||||
.release = dspdl_bus_release |
||||
}; |
||||
|
||||
struct bus_type dspdl_bus_type = { |
||||
.name = "dspdl", |
||||
.match = dspdl_device_match, |
||||
.uevent = dspdl_uevent, |
||||
}; |
||||
EXPORT_SYMBOL(dspdl_bus_type); |
||||
|
||||
/* Export a simple sysfs attribute. */ |
||||
static ssize_t show_bus_version(struct bus_type *bus, char *buf) |
||||
{ |
||||
return snprintf(buf, PAGE_SIZE, "%s\n", dspdl_driver_version); |
||||
} |
||||
|
||||
static BUS_ATTR(version, S_IRUGO, show_bus_version, NULL); |
||||
|
||||
/*
|
||||
* dspdl devices. |
||||
* For now, no references to dspdlbus devices go out which are not |
||||
* tracked via the module reference count, so we use a no-op |
||||
* release function. |
||||
*/ |
||||
static void dspdl_dev_release(struct device *dev) |
||||
{ |
||||
DBGMSG_ENTER(); |
||||
} |
||||
|
||||
static void dspdl_cleanup(struct dspdl_device *dspdl_dev) |
||||
{ |
||||
DBGMSG_ENTER(); |
||||
|
||||
if (!dspdl_dev) |
||||
return; |
||||
|
||||
dspdl_dev_array[dspdl_dev->id] = NULL; |
||||
|
||||
/* Get rid of any allocated buffer, not freed */ |
||||
if (dspdl_dev->fw_buffer_allocated) |
||||
#ifdef USE_KMALLOC // Use kmaloc
|
||||
kfree(dspdl_dev->fw_data); |
||||
#else // Use vmalloc
|
||||
vfree(dspdl_dev->fw_data); |
||||
#endif |
||||
|
||||
switch (dspdl_dev->state) { |
||||
case DSPDL_DEV_STATE_CHAR_DEV_REGISTERED: |
||||
misc_deregister(&dspdl_dev->miscdev); |
||||
case DSPDL_DEV_STATE_DEVICE_REGISTERED: |
||||
device_unregister(&dspdl_dev->dev); |
||||
case DSPDL_DEV_STATE_START: |
||||
break; |
||||
} |
||||
} |
||||
|
||||
int dspdl_register_device(struct dspdl_device *dspdl_dev) |
||||
{ |
||||
int res; |
||||
const struct firmware *fw_entry; |
||||
|
||||
DBGMSG_ENTER(); |
||||
|
||||
dspdl_dev->state = DSPDL_DEV_STATE_START; |
||||
|
||||
/* Sanity checks. */ |
||||
if (!dspdl_dev->name) { |
||||
FAILMSG(" Error, missing device name"); |
||||
res = -EFAULT; |
||||
goto error; |
||||
} |
||||
|
||||
if (!dspdl_dev->cb_write_section) { |
||||
FAILMSG(" Error, missing cb_write_section() callback"); |
||||
res = -ENOMEM; |
||||
goto error; |
||||
} |
||||
|
||||
if (dspdl_dev_count == MAX_DSPDL_DEV) { |
||||
FAILMSG("Maximum number of devices reached (%d)", |
||||
dspdl_dev_count); |
||||
res = -ENODEV; |
||||
goto error; |
||||
} |
||||
|
||||
DBGMSG(" device %d", dspdl_dev_count); |
||||
|
||||
/* Set some default values. */ |
||||
dspdl_dev->fw_loaded = 0; |
||||
dspdl_dev->fw_buffer_allocated = 0; |
||||
|
||||
dspdl_dev->dev.bus = &dspdl_bus_type; |
||||
dspdl_dev->dev.parent = &dspdl_bus; |
||||
dspdl_dev->dev.release = dspdl_dev_release; |
||||
dev_set_name(&dspdl_dev->dev, dspdl_dev->name); |
||||
res = device_register(&dspdl_dev->dev); |
||||
if (res) { |
||||
FAILMSG(" device_register() failed"); |
||||
goto error; |
||||
} |
||||
dspdl_dev->state = DSPDL_DEV_STATE_DEVICE_REGISTERED; |
||||
|
||||
dspdl_dev->miscdev.name = dspdl_dev->name; |
||||
dspdl_dev->miscdev.minor = MISC_DYNAMIC_MINOR; |
||||
dspdl_dev->miscdev.fops = &fops_dspdl; |
||||
res = misc_register(&dspdl_dev->miscdev); |
||||
if (res < 0) { |
||||
FAILMSG("Error registering misc driver"); |
||||
goto error; |
||||
} |
||||
DBGMSG(" MINOR = %d", dspdl_dev->miscdev.minor); |
||||
dspdl_dev->state = DSPDL_DEV_STATE_CHAR_DEV_REGISTERED; |
||||
|
||||
/* Try to load firmware through hotplug if available. */ |
||||
if (dspdl_dev->fw_name) { |
||||
res = request_firmware(&fw_entry, dspdl_dev->fw_name, |
||||
&dspdl_dev->dev); |
||||
if (res < 0) { |
||||
/* Not a critical error. */ |
||||
res = 0; |
||||
DBGMSG("firmware <%s> not available", dspdl_dev->fw_name); |
||||
} else { |
||||
res = dspdl_fw_load(dspdl_dev, fw_entry->data, |
||||
fw_entry->size); |
||||
release_firmware(fw_entry); |
||||
} |
||||
} |
||||
|
||||
dspdl_dev->id = dspdl_dev_count; |
||||
dspdl_dev_array[dspdl_dev_count] = dspdl_dev; |
||||
dspdl_dev_count++; |
||||
|
||||
return 0; |
||||
|
||||
error: |
||||
dspdl_cleanup(dspdl_dev); |
||||
return res; |
||||
} |
||||
EXPORT_SYMBOL(dspdl_register_device); |
||||
|
||||
void dspdl_unregister_device(struct dspdl_device *dspdl_dev) |
||||
{ |
||||
DBGMSG_ENTER(); |
||||
|
||||
/* Warning: fix this, potential bug if removing a device in-between. */ |
||||
dspdl_dev_array[dspdl_dev->id] = NULL; |
||||
dspdl_dev_count--; |
||||
|
||||
dspdl_cleanup(dspdl_dev); |
||||
} |
||||
EXPORT_SYMBOL(dspdl_unregister_device); |
||||
|
||||
static int __init dspdl_init(void) |
||||
{ |
||||
int res; |
||||
|
||||
DBGMSG_ENTER(); |
||||
INFOMSG("DSP firmware loader %s", dspdl_driver_version); |
||||
|
||||
res = bus_register(&dspdl_bus_type); |
||||
if (res) { |
||||
FAILMSG(" bus_register() failed"); |
||||
goto fail_bus; |
||||
} |
||||
|
||||
if (bus_create_file(&dspdl_bus_type, &bus_attr_version)) { |
||||
FAILMSG("Unable to create version attribute"); |
||||
goto fail_create_file; |
||||
} |
||||
|
||||
res = device_register(&dspdl_bus); |
||||
if (res) { |
||||
FAILMSG(" failed registering %s", dspdl_bus.init_name); |
||||
goto fail_dev_reg; |
||||
} |
||||
|
||||
return 0; |
||||
|
||||
fail_dev_reg: |
||||
fail_create_file: |
||||
bus_unregister(&dspdl_bus_type); |
||||
fail_bus: |
||||
return res; |
||||
} |
||||
module_init(dspdl_init); |
||||
|
||||
static void __exit dspdl_exit(void) |
||||
{ |
||||
DBGMSG_ENTER(); |
||||
device_unregister(&dspdl_bus); |
||||
bus_unregister(&dspdl_bus_type); |
||||
} |
||||
module_exit(dspdl_exit); |
||||
|
||||
MODULE_AUTHOR("Hugo Villeneuve <hvilleneuve@lyrtech.com>"); |
||||
MODULE_DESCRIPTION("DSP loader driver"); |
||||
MODULE_LICENSE("GPL"); |
@ -0,0 +1,457 @@
|
||||
/*
|
||||
* dspdl_dm644x.c - DM644X DSP loader driver |
||||
* |
||||
* Copyright (C) 2008 Lyrtech <www.lyrtech.com> |
||||
* |
||||
* 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/module.h> |
||||
#include <linux/moduleparam.h> |
||||
#include <linux/init.h> |
||||
#include <linux/device.h> |
||||
#include <linux/platform_device.h> |
||||
#include <linux/string.h> |
||||
#include <linux/err.h> |
||||
#include <linux/fs.h> |
||||
#include <linux/slab.h> |
||||
#include <linux/delay.h> |
||||
#include <linux/dspdl.h> |
||||
#include <linux/dma-mapping.h> |
||||
|
||||
#include <asm/gpio.h> /* For ioremap() */ |
||||
#include <asm/io.h> |
||||
|
||||
#include <mach/hardware.h> |
||||
#include <../../arch/arm/mach-davinci/psc.h> |
||||
|
||||
/* including davinci.h is difficult */ |
||||
#define DAVINCI_SYSTEM_MODULE_BASE 0x01c40000 |
||||
|
||||
#define MODULE_NAME "dspdl_dm644x" |
||||
#define MODULE_VERSION_STR "v1.1" |
||||
|
||||
/* Define this to enable verbose debug messages */ |
||||
#define DSPDL_DM644X_DEBUG 1 |
||||
|
||||
/* Module parameters */ |
||||
static unsigned int dspdl_dm644x_debug; |
||||
module_param_named(debug, dspdl_dm644x_debug, int, 0644); |
||||
|
||||
static char *dspdl_dm644x_fw_name = NULL; |
||||
module_param_named(fw_name, dspdl_dm644x_fw_name, charp, 0644); |
||||
|
||||
#ifdef DSPDL_DM644X_DEBUG |
||||
#define INFOMSG(fmt, args...) \ |
||||
do { \
|
||||
printk(KERN_INFO "%s: "fmt"\n", MODULE_NAME, ## args); } while (0) |
||||
#define DBGMSG(fmt, args...) \ |
||||
do { if (dspdl_dm644x_debug > 0) \
|
||||
printk(KERN_DEBUG "%s: "fmt"\n", MODULE_NAME, ## args); } while (0) |
||||
#define DBGMSG_ENTER() \ |
||||
DBGMSG("%s() enter", __func__); |
||||
#define DBGMSG_LEAVE() \ |
||||
DBGMSG("%s() leave", __func__); |
||||
#else |
||||
#define INFOMSG(fmt, args...) do {} while (0) |
||||
#define DBGMSG(fmt, args...) do {} while (0) |
||||
#define DBGMSG_ENTER() do {} while (0) |
||||
#define DBGMSG_LEAVE() do {} while (0) |
||||
#endif |
||||
|
||||
#define FAILMSG(fmt, args...) \ |
||||
do { \
|
||||
printk(KERN_ERR "%s: "fmt"\n", MODULE_NAME, ## args); } while (0) |
||||
|
||||
/*
|
||||
* PSC register offsets |
||||
* already defined in arch/arm/mach-davinci/psc.c |
||||
*/ |
||||
|
||||
#define DAVINCI_PWR_SLEEP_CNTRL_BASE 0x01C41000 |
||||
|
||||
#define EPCPR 0x070 |
||||
#define PTCMD 0x120 |
||||
#define PTSTAT 0x128 |
||||
#define PDSTAT 0x200 |
||||
#define PDCTL1 0x304 |
||||
#define MDSTAT 0x800 |
||||
#define MDCTL 0xA00 |
||||
|
||||
#define PSC_PID (DAVINCI_PWR_SLEEP_CNTRL_BASE + 0) |
||||
#define PSC_EPCPR (DAVINCI_PWR_SLEEP_CNTRL_BASE + EPCPR) |
||||
#define PSC_PTCMD (DAVINCI_PWR_SLEEP_CNTRL_BASE + PTCMD) |
||||
#define PSC_PTSTAT (DAVINCI_PWR_SLEEP_CNTRL_BASE + PTSTAT) |
||||
#define PSC_PDCTL1 (DAVINCI_PWR_SLEEP_CNTRL_BASE + PDCTL1) |
||||
#define PSC_MDSTAT_DSP (DAVINCI_PWR_SLEEP_CNTRL_BASE + MDSTAT + \ |
||||
(4 * DAVINCI_LPSC_GEM)) |
||||
#define PSC_MDCTL_DSP (DAVINCI_PWR_SLEEP_CNTRL_BASE + MDCTL + \ |
||||
(4 * DAVINCI_LPSC_GEM)) |
||||
#define PSC_MDSTAT_IMCOP (DAVINCI_PWR_SLEEP_CNTRL_BASE + MDSTAT + \ |
||||
(4 * DAVINCI_LPSC_IMCOP)) |
||||
#define PSC_MDCTL_IMCOP (DAVINCI_PWR_SLEEP_CNTRL_BASE + MDCTL + \ |
||||
(4 * DAVINCI_LPSC_IMCOP)) |
||||
|
||||
#define DSPBOOTADDR (DAVINCI_SYSTEM_MODULE_BASE + 8) |
||||
|
||||
/*
|
||||
* 0 = Assert local reset |
||||
* 1 = De-assert local reset |
||||
*/ |
||||
#define PSC_MDCTL_LRST_BIT (1 << 8) |
||||
|
||||
struct dspdl_dm644x_dev_t { |
||||
char devname[32]; |
||||
enum { |
||||
DSPDL_DM644X_DEV_STATE_STRUCT_ALLOCATED, |
||||
DSPDL_DM644X_DEV_STATE_HAVE_IOREMAP_L1D, |
||||
DSPDL_DM644X_DEV_STATE_HAVE_IOREMAP_L1P, |
||||
DSPDL_DM644X_DEV_STATE_HAVE_IOREMAP_L2, |
||||
DSPDL_DM644X_DEV_STATE_HAVE_IOREMAP_DDR, |
||||
DSPDL_DM644X_DEV_STATE_DSPDL_DEV_REGISTERED, |
||||
} state; |
||||
void *l1d_mmio; |
||||
struct resource *l1d_mmio_res; |
||||
void *l1p_mmio; |
||||
struct resource *l1p_mmio_res; |
||||
void *l2_mmio; |
||||
struct resource *l2_mmio_res; |
||||
void *ddr_mmio; |
||||
struct resource *ddr_mmio_res; |
||||
struct dspdl_device dspdl_dev; |
||||
}; |
||||
|
||||
#define MAX_DSPDL_DM644X_DEV 5 |
||||
|
||||
static int dspdl_dm644x_dev_count; |
||||
|
||||
static void dsp_reset(int active) |
||||
{ |
||||
u32 mdctl; |
||||
void __iomem *psc_base = IO_ADDRESS(DAVINCI_PWR_SLEEP_CNTRL_BASE); |
||||
|
||||
mdctl = __raw_readl(psc_base + MDCTL + 4 * DAVINCI_LPSC_GEM); |
||||
|
||||
if (active) |
||||
mdctl &= ~PSC_MDCTL_LRST_BIT; |
||||
else |
||||
mdctl |= PSC_MDCTL_LRST_BIT; |
||||
|
||||
__raw_writel(mdctl, psc_base + MDCTL + 4 * DAVINCI_LPSC_GEM); |
||||
} |
||||
|
||||
static int dm644x_start(struct dspdl_device *dspdl_dev) |
||||
{ |
||||
DBGMSG_ENTER(); |
||||
|
||||
/* Put DSP in reset */ |
||||
dsp_reset(1); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static int dm644x_finish(struct dspdl_device *dspdl_dev, u32 entry_point) |
||||
{ |
||||
void __iomem *dspbootaddr_reg = IO_ADDRESS(DSPBOOTADDR); |
||||
|
||||
DBGMSG_ENTER(); |
||||
|
||||
/* Verify if the DSP boot address first 10 bits are zero. */ |
||||
if ((entry_point & 0x000003FF) != 0x000) { |
||||
FAILMSG("Application entry point not aligned on a 10 bits boundary."); |
||||
return -EFAULT; |
||||
} |
||||
__raw_writel(entry_point, dspbootaddr_reg); |
||||
|
||||
DBGMSG(" Taking DSP out of reset"); |
||||
dsp_reset(0); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static int dm644x_write_section(struct dspdl_device *dspdl_dev, u32 start_addr, |
||||
u8 *data, ssize_t size) |
||||
{ |
||||
u32 offset; |
||||
struct dspdl_dm644x_dev_t *dspdl_dm644x_dev = |
||||
(struct dspdl_dm644x_dev_t *) dspdl_dev->devdata; |
||||
|
||||
DBGMSG_ENTER(); |
||||
|
||||
if ( (dspdl_dm644x_dev->l1d_mmio_res) && |
||||
(start_addr >= dspdl_dm644x_dev->l1d_mmio_res->start) && |
||||
(start_addr <= dspdl_dm644x_dev->l1d_mmio_res->end)) { |
||||
if ((start_addr+size) > dspdl_dm644x_dev->l1d_mmio_res->end) { |
||||
FAILMSG("Section exceeds L1D memory region."); |
||||
return -EFAULT; |
||||
} |
||||
offset = start_addr - dspdl_dm644x_dev->l1d_mmio_res->start; |
||||
memcpy(dspdl_dm644x_dev->l1d_mmio + offset, data, size); |
||||
|
||||
if (memcmp(dspdl_dm644x_dev->l1d_mmio + offset, data, size)) { |
||||
FAILMSG("Failed to write into L1D memory region."); |
||||
return -EFAULT; |
||||
} |
||||
} |
||||
else if ( (dspdl_dm644x_dev->l1p_mmio_res) && |
||||
(start_addr >= dspdl_dm644x_dev->l1p_mmio_res->start) && |
||||
(start_addr <= dspdl_dm644x_dev->l1p_mmio_res->end)) { |
||||
if ((start_addr+size) > dspdl_dm644x_dev->l1p_mmio_res->end) { |
||||
FAILMSG("Section exceeds L1P memory region."); |
||||
return -EFAULT; |
||||
} |
||||
offset = start_addr - dspdl_dm644x_dev->l1p_mmio_res->start; |
||||
memcpy(dspdl_dm644x_dev->l1p_mmio + offset, data, size); |
||||
|
||||
if (memcmp(dspdl_dm644x_dev->l1p_mmio + offset, data, size)) { |
||||
FAILMSG("Failed to write into L1P memory region."); |
||||
return -EFAULT; |
||||
} |
||||
} |
||||
else if ( (dspdl_dm644x_dev->l2_mmio_res) && |
||||
(start_addr >= dspdl_dm644x_dev->l2_mmio_res->start) && |
||||
(start_addr <= dspdl_dm644x_dev->l2_mmio_res->end)) { |
||||
if ((start_addr+size) > dspdl_dm644x_dev->l2_mmio_res->end) { |
||||
FAILMSG("Section exceeds L2 memory region."); |
||||
return -EFAULT; |
||||
} |
||||
offset = start_addr - dspdl_dm644x_dev->l2_mmio_res->start; |
||||
memcpy(dspdl_dm644x_dev->l2_mmio + offset, data, size); |
||||
|
||||
if (memcmp(dspdl_dm644x_dev->l2_mmio + offset, data, size)) { |
||||
FAILMSG("Failed to write into L2 memory region."); |
||||
return -EFAULT; |
||||
} |
||||
} |
||||
else if ( (dspdl_dm644x_dev->ddr_mmio_res) && |
||||
(start_addr >= dspdl_dm644x_dev->ddr_mmio_res->start) && |
||||
(start_addr <= dspdl_dm644x_dev->ddr_mmio_res->end)) { |
||||
if ((start_addr+size) > dspdl_dm644x_dev->ddr_mmio_res->end) { |
||||
FAILMSG("Section exceeds DDR memory region."); |
||||
return -EFAULT; |
||||
} |
||||
offset = start_addr - dspdl_dm644x_dev->ddr_mmio_res->start; |
||||
memcpy(dspdl_dm644x_dev->ddr_mmio + offset, data, size); |
||||
|
||||
if (memcmp(dspdl_dm644x_dev->ddr_mmio + offset, data, size)) { |
||||
FAILMSG("Failed to write into DDR memory region."); |
||||
return -EFAULT; |
||||
} |
||||
} |
||||
else { |
||||
FAILMSG("Invalid destination address."); |
||||
return -EFAULT; |
||||
} |
||||
return 0; |
||||
} |
||||
|
||||
static void dspdl_dm644x_cleanup(struct dspdl_dm644x_dev_t *dev) |
||||
{ |
||||
DBGMSG_ENTER(); |
||||
|
||||
if (!dev) |
||||
return; |
||||
|
||||
switch (dev->state) { |
||||
case DSPDL_DM644X_DEV_STATE_DSPDL_DEV_REGISTERED: |
||||
dspdl_unregister_device(&dev->dspdl_dev); |
||||
case DSPDL_DM644X_DEV_STATE_HAVE_IOREMAP_DDR: |
||||
iounmap(dev->ddr_mmio); |
||||
case DSPDL_DM644X_DEV_STATE_HAVE_IOREMAP_L2: |
||||
iounmap(dev->l2_mmio); |
||||
case DSPDL_DM644X_DEV_STATE_HAVE_IOREMAP_L1P: |
||||
iounmap(dev->l1p_mmio); |
||||
case DSPDL_DM644X_DEV_STATE_HAVE_IOREMAP_L1D: |
||||
iounmap(dev->l1d_mmio); |
||||
case DSPDL_DM644X_DEV_STATE_STRUCT_ALLOCATED: |
||||
kfree(dev); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
static int dspdl_dm644x_probe(struct platform_device *pdev) |
||||
{ |
||||
int len; |
||||
int res; |
||||
struct dspdl_dm644x_dev_t *dev = NULL; |
||||
|
||||
DBGMSG_ENTER(); |
||||
|
||||
if (dspdl_dm644x_dev_count == MAX_DSPDL_DM644X_DEV) { |
||||
FAILMSG("Maximum number of devices reached (%d)", |
||||
dspdl_dm644x_dev_count); |
||||
res = -ENODEV; |
||||
goto error; |
||||
} |
||||
|
||||
DBGMSG(" device %d", dspdl_dm644x_dev_count); |
||||
|
||||
dev = kzalloc(sizeof(*dev), GFP_KERNEL); |
||||
if (!dev) { |
||||
FAILMSG("Failed to allocate device structure"); |
||||
res = -ENOMEM; |
||||
goto error; |
||||
} |
||||
dev->state = DSPDL_DM644X_DEV_STATE_STRUCT_ALLOCATED; |
||||
|
||||
//pdev->dev.driver_data = dev; /* Private driver data */
|
||||
|
||||
/* Assign virtual addresses to L1D memory regions. */ |
||||
dev->l1d_mmio_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, |
||||
"l1d"); |
||||
if (dev->l1d_mmio_res) { |
||||
len = dev->l1d_mmio_res->end - dev->l1d_mmio_res->start; |
||||
dev->l1d_mmio = ioremap(dev->l1d_mmio_res->start, len); |
||||
if (!dev->l1d_mmio) { |
||||
FAILMSG("Can't remap l1d_mmio register"); |
||||
res = -ENXIO; |
||||
goto error; |
||||
} |
||||
} |
||||
dev->state = DSPDL_DM644X_DEV_STATE_HAVE_IOREMAP_L1D; |
||||
|
||||
/* Assign virtual addresses to L1P memory regions. */ |
||||
dev->l1p_mmio_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, |
||||
"l1p"); |
||||
if (dev->l1p_mmio_res) { |
||||
len = dev->l1p_mmio_res->end - dev->l1p_mmio_res->start; |
||||
dev->l1p_mmio = ioremap(dev->l1p_mmio_res->start, len); |
||||
if (!dev->l1p_mmio) { |
||||
FAILMSG("Can't remap l1p_mmio register"); |
||||
res = -ENXIO; |
||||
goto error; |
||||
} |
||||
} |
||||
dev->state = DSPDL_DM644X_DEV_STATE_HAVE_IOREMAP_L1P; |
||||
|
||||
/* Assign virtual addresses to L2 memory regions. */ |
||||
dev->l2_mmio_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, |
||||
"l2"); |
||||
if (dev->l2_mmio_res) { |
||||
len = dev->l2_mmio_res->end - dev->l2_mmio_res->start; |
||||
dev->l2_mmio = ioremap(dev->l2_mmio_res->start, len); |
||||
if (!dev->l2_mmio) { |
||||
FAILMSG("Can't remap l2_mmio register"); |
||||
res = -ENXIO; |
||||
goto error; |
||||
} |
||||
} |
||||
dev->state = DSPDL_DM644X_DEV_STATE_HAVE_IOREMAP_L2; |
||||
|
||||
/* Assign virtual addresses to DDR memory regions. */ |
||||
dev->ddr_mmio_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, |
||||
"ddr"); |
||||
if (dev->ddr_mmio_res) { |
||||
len = dev->ddr_mmio_res->end - dev->ddr_mmio_res->start; |
||||
dev->ddr_mmio = ioremap(dev->ddr_mmio_res->start, len); |
||||
if (!dev->ddr_mmio) { |
||||
FAILMSG("Can't remap ddr_mmio register"); |
||||
res = -ENXIO; |
||||
goto error; |
||||
} |
||||
} |
||||
dev->state = DSPDL_DM644X_DEV_STATE_HAVE_IOREMAP_DDR; |
||||
|
||||
if (dspdl_dm644x_fw_name) |
||||
dev->dspdl_dev.fw_name = dspdl_dm644x_fw_name; |
||||
dev->dspdl_dev.cb_start = dm644x_start; |
||||
dev->dspdl_dev.cb_finish = dm644x_finish; |
||||
dev->dspdl_dev.cb_write_section = dm644x_write_section; |
||||
sprintf(dev->devname, "%s_%d", MODULE_NAME, dspdl_dm644x_dev_count); |
||||
DBGMSG(" NAME = %s", dev->devname); |
||||
dev->dspdl_dev.name = dev->devname; |
||||
dev->dspdl_dev.devdata = dev; /* For our callbacks */ |
||||
res = dspdl_register_device(&dev->dspdl_dev); |
||||
if (res < 0) { |
||||
FAILMSG("Error registering dspdl_dm644x device"); |
||||
goto error; |
||||
} |
||||
dev->state = DSPDL_DM644X_DEV_STATE_DSPDL_DEV_REGISTERED; |
||||
|
||||
dspdl_dm644x_dev_count++; |
||||
|
||||
return 0; |
||||
|
||||
error: |
||||
dspdl_dm644x_cleanup(dev); |
||||
return res; |
||||
} |
||||
|
||||
static int dspdl_dm644x_remove(struct platform_device *pdev) |
||||
{ |
||||
struct dspdl_dm644x_dev_t *dev = platform_get_drvdata(pdev); |
||||
|
||||
DBGMSG_ENTER(); |
||||
|
||||
dspdl_dm644x_cleanup(dev); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static struct dspdl_driver dspdl_dm644x_driver = { |
||||
.version = MODULE_VERSION_STR, |
||||
.module = THIS_MODULE, |
||||
.driver = { |
||||
.name = "dspdl_dm644x", |
||||
}, |
||||
}; |
||||
|
||||
static struct platform_driver dspdl_platform_driver = { |
||||
.driver = { |
||||
.name = MODULE_NAME, |
||||
.owner = THIS_MODULE, |
||||
}, |
||||
.remove = dspdl_dm644x_remove, |
||||
}; |
||||
|
||||
static int dspdl_dm644x_init(void) |
||||
{ |
||||
int res; |
||||
|
||||
DBGMSG_ENTER(); |
||||
INFOMSG("DM644X DSP firmware loader %s", MODULE_VERSION_STR); |
||||
|
||||
/* Register with the driver core. */ |
||||
res = dspdl_register_driver(&dspdl_dm644x_driver); |
||||
if (res) { |
||||
FAILMSG("Can't register dspdl DM644X driver"); |
||||
return res; |
||||
} |
||||
|
||||
/* The probe function will be called for each platform device declared
|
||||
* in board setup code. */ |
||||
res = platform_driver_probe(&dspdl_platform_driver, |
||||
dspdl_dm644x_probe); |
||||
if (res) { |
||||
FAILMSG("platform_driver_probe() failed"); |
||||
return res; |
||||
} |
||||
|
||||
return 0; |
||||
} |
||||
module_init(dspdl_dm644x_init); |
||||
|
||||
static void __exit dspdl_dm644x_exit(void) |
||||
{ |
||||
DBGMSG_ENTER(); |
||||
platform_driver_unregister(&dspdl_platform_driver); |
||||
dspdl_unregister_driver(&dspdl_dm644x_driver); |
||||
} |
||||
module_exit(dspdl_dm644x_exit); |
||||
|
||||
MODULE_AUTHOR("Hugo Villeneuve <hvilleneuve@lyrtech.com>"); |
||||
MODULE_DESCRIPTION("DM644X DSP loader driver"); |
||||
MODULE_LICENSE("GPL"); |
@ -0,0 +1,272 @@
|
||||
#include <linux/device.h> |
||||
#include <linux/kernel.h> |
||||
#include <linux/module.h> |
||||
#include <linux/interrupt.h> |
||||
#include <linux/firmware.h> |
||||
#include <linux/platform_device.h> |
||||
#include <linux/miscdevice.h> |
||||
#include <linux/delay.h> |
||||
#include <linux/completion.h> |
||||
#include <linux/uaccess.h> |
||||
#include <linux/io.h> |
||||
#include <linux/slab.h> |
||||
|
||||
#include <mach/irqs.h> |
||||
#include <mach/hardware.h> |
||||
#include <mach/common.h> |
||||
#include <mach/mux.h> |
||||
|
||||
#include <asm/mach-types.h> |
||||
#include <asm/gpio.h> |
||||
#include <asm/io.h> |
||||
|
||||
#ifdef CONFIG_PROC_FS |
||||
#include <linux/proc_fs.h> |
||||
#include <linux/seq_file.h> |
||||
#include <asm/uaccess.h> |
||||
#endif /* CONFIG_PROC_FS */ |
||||
|
||||
#define MODULE_NAME "factoryreset" |
||||
#define FACTORY_RESET_GPIO GPIO(38) |
||||
|
||||
static const char factoryreset_driver_version[] = "v1.0"; |
||||
|
||||
static int factoryReset = 0; |
||||
struct miscdevice *mdev = NULL; |
||||
|
||||
#ifdef CONFIG_PROC_FS |
||||
static void *factoryreset_seq_start(struct seq_file *m, loff_t *pos) |
||||
{ |
||||
return *pos < 1 ? (void *)1 : NULL; |
||||
} |
||||
|
||||
static void *factoryreset_seq_next(struct seq_file *m, void *v, loff_t *pos) |
||||
{ |
||||
++*pos; |
||||
return NULL; |
||||
} |
||||
|
||||
static void factoryreset_seq_stop(struct seq_file *m, void *v) |
||||
{ |
||||
} |
||||
|
||||
static int factoryreset_proc_show(struct seq_file *m, void *v) |
||||
{ |
||||
seq_printf(m, "%d", factoryReset);
|
||||
return 0; |
||||
} |
||||
|
||||
static const struct seq_operations factoryreset_proc_op = { |
||||
.start = factoryreset_seq_start, |
||||
.next = factoryreset_seq_next, |
||||
.stop = factoryreset_seq_stop, |
||||
.show = factoryreset_proc_show |
||||
}; |
||||
|
||||
static int factoryreset_proc_open(struct inode *inode, struct file *file) |
||||
{ |
||||
int ret; |
||||
struct seq_file *m; |
||||
|
||||
ret = seq_open(file, &factoryreset_proc_op); |
||||
if (ret < 0) |
||||
return ret; |
||||
|
||||
m = file->private_data; |
||||
m->private = mdev; |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static ssize_t factoryreset_proc_write(struct file *file, const char __user *buf, |
||||
size_t size, loff_t *ppos) |
||||
{ |
||||
char *kbuf; |
||||
int ret = 0; |
||||
|
||||
if (size <= 1 || size >= PAGE_SIZE) |
||||
return -EINVAL; |
||||
|
||||
kbuf = kmalloc(size + 1, GFP_KERNEL); |
||||
if (!kbuf) |
||||
return -ENOMEM; |
||||
|
||||
if (copy_from_user(kbuf, buf, size) != 0) { |
||||
kfree(kbuf); |
||||
return -EFAULT; |
||||
} |
||||
kbuf[size] = 0; |
||||
factoryReset = simple_strtoul(kbuf, 0, 0); |
||||
|
||||
ret = size; |
||||
kfree(kbuf); |
||||
return ret; |
||||
} |
||||
|
||||
static const struct file_operations factoryreset_proc_fops = { |
||||
.open = factoryreset_proc_open, |
||||
.write = factoryreset_proc_write, |
||||
.read = seq_read, |
||||
.llseek = seq_lseek, |
||||
.release = seq_release, |
||||
.owner = THIS_MODULE, |
||||
}; |
||||
|
||||
#endif /* CONFIG_PROC_FS */ |
||||
|
||||
static int factoryreset_open(struct inode *inode, struct file *filp) |
||||
{ |
||||
return 0; |
||||
} |
||||
|
||||
static int factoryreset_release(struct inode *inode, struct file *filp) |
||||
{ |
||||
return 0; |
||||
} |
||||
|
||||
static int factoryreset_read(struct file *filp, char *buf, size_t count, loff_t *ppos) |
||||
{ |
||||
int ret; |
||||
ret = copy_to_user(buf, (void *)&factoryReset, 1); |
||||
return 1; |
||||
} |
||||
|
||||
static int factoryreset_write(struct file *filp, const char *buf,
|
||||
size_t count, loff_t *ppos) |
||||
{ |
||||
if (copy_from_user((void *)&factoryReset, buf, 1) != 0) {
|
||||
return -EFAULT;
|
||||
} |
||||
return 1; |
||||
} |
||||
|
||||
static const struct file_operations factoryreset_fops = { |
||||
.open = factoryreset_open, |
||||
.release = factoryreset_release, |
||||
.read = factoryreset_read, |
||||
.write = factoryreset_write, |
||||
.owner = THIS_MODULE, |
||||
}; |
||||
|
||||
static int factoryreset_probe(struct platform_device *pdev) |
||||
{ |
||||
int ret = -ENODEV; |
||||
struct proc_dir_entry *ent; |
||||
|
||||
ret = gpio_request(FACTORY_RESET_GPIO, "factory_reset"); |
||||
if (ret != 0) { |
||||
goto error; |
||||
} |
||||
ret = gpio_direction_input(FACTORY_RESET_GPIO); |
||||
if (ret != 0) { |
||||
goto error; |
||||
} |
||||
factoryReset = (gpio_get_value(FACTORY_RESET_GPIO) == 0); |
||||
|
||||
/* Alloc memory for the misc device */ |
||||
mdev = kzalloc(sizeof(struct miscdevice), GFP_KERNEL); |
||||
if (!mdev) { |
||||
printk(KERN_ERR "factoryreset: Failed to allocate misc device structure\n"); |
||||
ret = -ENOMEM; |
||||
goto error; |
||||
} |
||||
|
||||
mdev->name = MODULE_NAME; |
||||
mdev->minor = MISC_DYNAMIC_MINOR; |
||||
mdev->fops = &factoryreset_fops; |
||||
ret = misc_register(mdev); |
||||
if (ret < 0) { |
||||
printk(KERN_ERR "factoryreset: Error registering misc driver\n"); |
||||
goto error; |
||||
} |
||||
|
||||
/* Create PROC Entry */ |
||||
#ifdef CONFIG_PROC_FS |
||||
{ |
||||
ent = proc_create(MODULE_NAME, 0, NULL, &factoryreset_proc_fops); |
||||
if (!ent) { |
||||
printk(KERN_ERR "factoryreset: failed to create proc entry\n"); |
||||
} |
||||
}
|
||||
#endif |
||||
|
||||
printk(KERN_INFO "factoryreset: probed\n"); |
||||
return 0; |
||||
|
||||
error: |
||||
printk(KERN_ERR "factoryreset: probe failed\n"); |
||||
return ret; |
||||
} |
||||
|
||||
static int factoryreset_remove(struct platform_device *pdev) |
||||
{ |
||||
#ifdef CONFIG_PROC_FS |
||||
{ |
||||
remove_proc_entry(MODULE_NAME, NULL); |
||||
} |
||||
#endif |
||||
|
||||
if (mdev) { |
||||
misc_deregister(mdev); |
||||
kfree(mdev); |
||||
mdev = NULL; |
||||
} |
||||
|
||||
gpio_free(FACTORY_RESET_GPIO); |
||||
return 0; |
||||
} |
||||
|
||||
static void factoryreset_platform_release(struct device *device) |
||||
{ |
||||
} |
||||
|
||||
static struct platform_driver factoryreset_driver = { |
||||
.probe = factoryreset_probe, |
||||
.remove = factoryreset_remove, |
||||
.driver = { |
||||
.name = MODULE_NAME, |
||||
.owner = THIS_MODULE, |
||||
}, |
||||
}; |
||||
|
||||
static struct platform_device factoryreset_pdevice = { |
||||
.name = MODULE_NAME, |
||||
.id = 1, |
||||
.dev = { |
||||
.release = factoryreset_platform_release, |
||||
} |
||||
}; |
||||
|
||||
static int __init factoryreset_init(void) |
||||
{ |
||||
int err = 0; |
||||
|
||||
err = platform_driver_register(&factoryreset_driver); |
||||
if ( err < 0 ) { |
||||
printk( KERN_ERR "Failed to register factoryreset driver\n" ); |
||||
return err; |
||||
} |
||||
|
||||
err = platform_device_register(&factoryreset_pdevice); |
||||
if ( err < 0 ) { |
||||
platform_driver_unregister( &factoryreset_driver ); |
||||
printk( KERN_ERR "Failed to register factoryreset driver\n" ); |
||||
return err; |
||||
} |
||||
return err; |
||||
} |
||||
|
||||
static void __exit factoryreset_cleanup(void) |
||||
{ |
||||
platform_device_unregister(&factoryreset_pdevice); |
||||
platform_driver_unregister(&factoryreset_driver); |
||||
} |
||||
|
||||
module_init(factoryreset_init); |
||||
module_exit(factoryreset_cleanup); |
||||
|
||||
MODULE_DESCRIPTION("Factory Reset Driver"); |
||||
MODULE_AUTHOR("Lyrtech RD Inc. <www.lyrtech.com>"); |
||||
MODULE_LICENSE("GPL"); |
||||
MODULE_ALIAS("platform:" MODULE_NAME); |
||||
|
@ -0,0 +1,816 @@
|
||||
/*
|
||||
* fpgadl core driver |
||||
* |
||||
* Copyright (C) 2008 Lyrtech <www.lyrtech.com> |
||||
* |
||||
* Based on code found in book "Linux Device Drivers" by |
||||
* Alessandro Rubini and Jonathan Corbet, published by O'Reilly & Associates. |
||||
* |
||||
* 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/module.h> |
||||
#include <linux/moduleparam.h> |
||||
#include <linux/init.h> |
||||
#include <linux/device.h> |
||||
#include <linux/platform_device.h> |
||||
#include <linux/miscdevice.h> |
||||
#include <linux/string.h> |
||||
#include <linux/err.h> |
||||
#include <linux/firmware.h> |
||||
#include <linux/delay.h> |
||||
#include <linux/slab.h> |
||||
#include <linux/vmalloc.h> |
||||
#include <linux/fs.h> |
||||
#include <linux/fpgadl.h> |
||||
|
||||