dect
/
linux-2.6
Archived
13
0
Fork 0

HID: add experimental access to PicoLCD device's EEPROM and FLASH

The PicoLCD device has a small amount of EEPROM and also provides access
to its FLASH where firmware and splash image are saved.
In flasher mode FLASH access is the only active feature.

Give read/write access to both via debugfs files.

NOTE: EEPROM and FLASH access should be switched to better suited API,
      until then the will reside in debugfs

Signed-off-by: Bruno Prémont <bonbons@linux-vserver.org>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
This commit is contained in:
Bruno Prémont 2010-03-30 22:38:09 +02:00 committed by Jiri Kosina
parent 467d652306
commit 9bbf2b98ba
2 changed files with 423 additions and 18 deletions

View File

@ -281,9 +281,9 @@ config HID_PICOLCD
- Backlight control (needs CONFIG_BACKLIGHT_CLASS_DEVICE)
- Contrast control (needs CONFIG_LCD_CLASS_DEVICE)
- General purpose outputs (needs CONFIG_LEDS_CLASS)
- EEProm / Flash access (via debugfs)
Features that are not (yet) supported:
- IR
- EEProm / Flash access
config HID_QUANTA
tristate "Quanta Optical Touch"

View File

@ -169,6 +169,10 @@ struct picolcd_pending {
struct picolcd_data {
struct hid_device *hdev;
#ifdef CONFIG_DEBUG_FS
struct dentry *debug_reset;
struct dentry *debug_eeprom;
struct dentry *debug_flash;
struct mutex mutex_flash;
int addr_sz;
#endif
u8 version[2];
@ -1313,6 +1317,357 @@ static DEVICE_ATTR(operation_mode, 0644, picolcd_operation_mode_show,
#ifdef CONFIG_DEBUG_FS
/*
* The "reset" file
*/
static int picolcd_debug_reset_show(struct seq_file *f, void *p)
{
if (picolcd_fbinfo((struct picolcd_data *)f->private))
seq_printf(f, "all fb\n");
else
seq_printf(f, "all\n");
return 0;
}
static int picolcd_debug_reset_open(struct inode *inode, struct file *f)
{
return single_open(f, picolcd_debug_reset_show, inode->i_private);
}
static ssize_t picolcd_debug_reset_write(struct file *f, const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct picolcd_data *data = ((struct seq_file *)f->private_data)->private;
char buf[32];
size_t cnt = min(count, sizeof(buf)-1);
if (copy_from_user(buf, user_buf, cnt))
return -EFAULT;
while (cnt > 0 && (buf[cnt-1] == ' ' || buf[cnt-1] == '\n'))
cnt--;
buf[cnt] = '\0';
if (strcmp(buf, "all") == 0) {
picolcd_reset(data->hdev);
picolcd_fb_reset(data, 1);
} else if (strcmp(buf, "fb") == 0) {
picolcd_fb_reset(data, 1);
} else {
return -EINVAL;
}
return count;
}
static const struct file_operations picolcd_debug_reset_fops = {
.owner = THIS_MODULE,
.open = picolcd_debug_reset_open,
.read = seq_read,
.llseek = seq_lseek,
.write = picolcd_debug_reset_write,
.release = single_release,
};
/*
* The "eeprom" file
*/
static int picolcd_debug_eeprom_open(struct inode *i, struct file *f)
{
f->private_data = i->i_private;
return 0;
}
static ssize_t picolcd_debug_eeprom_read(struct file *f, char __user *u,
size_t s, loff_t *off)
{
struct picolcd_data *data = f->private_data;
struct picolcd_pending *resp;
u8 raw_data[3];
ssize_t ret = -EIO;
if (s == 0)
return -EINVAL;
if (*off > 0x0ff)
return 0;
/* prepare buffer with info about what we want to read (addr & len) */
raw_data[0] = *off & 0xff;
raw_data[1] = (*off >> 8) && 0xff;
raw_data[2] = s < 20 ? s : 20;
if (*off + raw_data[2] > 0xff)
raw_data[2] = 0x100 - *off;
resp = picolcd_send_and_wait(data->hdev, REPORT_EE_READ, raw_data,
sizeof(raw_data));
if (!resp)
return -EIO;
if (resp->in_report && resp->in_report->id == REPORT_EE_DATA) {
/* successful read :) */
ret = resp->raw_data[2];
if (ret > s)
ret = s;
if (copy_to_user(u, resp->raw_data+3, ret))
ret = -EFAULT;
else
*off += ret;
} /* anything else is some kind of IO error */
kfree(resp);
return ret;
}
static ssize_t picolcd_debug_eeprom_write(struct file *f, const char __user *u,
size_t s, loff_t *off)
{
struct picolcd_data *data = f->private_data;
struct picolcd_pending *resp;
ssize_t ret = -EIO;
u8 raw_data[23];
if (s == 0)
return -EINVAL;
if (*off > 0x0ff)
return -ENOSPC;
memset(raw_data, 0, sizeof(raw_data));
raw_data[0] = *off & 0xff;
raw_data[1] = (*off >> 8) && 0xff;
raw_data[2] = s < 20 ? s : 20;
if (*off + raw_data[2] > 0xff)
raw_data[2] = 0x100 - *off;
if (copy_from_user(raw_data+3, u, raw_data[2]))
return -EFAULT;
resp = picolcd_send_and_wait(data->hdev, REPORT_EE_WRITE, raw_data,
sizeof(raw_data));
if (!resp)
return -EIO;
if (resp->in_report && resp->in_report->id == REPORT_EE_DATA) {
/* check if written data matches */
if (memcmp(raw_data, resp->raw_data, 3+raw_data[2]) == 0) {
*off += raw_data[2];
ret = raw_data[2];
}
}
kfree(resp);
return ret;
}
/*
* Notes:
* - read/write happens in chunks of at most 20 bytes, it's up to userspace
* to loop in order to get more data.
* - on write errors on otherwise correct write request the bytes
* that should have been written are in undefined state.
*/
static const struct file_operations picolcd_debug_eeprom_fops = {
.owner = THIS_MODULE,
.open = picolcd_debug_eeprom_open,
.read = picolcd_debug_eeprom_read,
.write = picolcd_debug_eeprom_write,
.llseek = generic_file_llseek,
};
/*
* The "flash" file
*/
static int picolcd_debug_flash_open(struct inode *i, struct file *f)
{
f->private_data = i->i_private;
return 0;
}
/* record a flash address to buf (bounds check to be done by caller) */
static int _picolcd_flash_setaddr(struct picolcd_data *data, u8 *buf, long off)
{
buf[0] = off & 0xff;
buf[1] = (off >> 8) & 0xff;
if (data->addr_sz == 3)
buf[2] = (off >> 16) & 0xff;
return data->addr_sz == 2 ? 2 : 3;
}
/* read a given size of data (bounds check to be done by caller) */
static ssize_t _picolcd_flash_read(struct picolcd_data *data, int report_id,
char __user *u, size_t s, loff_t *off)
{
struct picolcd_pending *resp;
u8 raw_data[4];
ssize_t ret = 0;
int len_off, err = -EIO;
while (s > 0) {
err = -EIO;
len_off = _picolcd_flash_setaddr(data, raw_data, *off);
raw_data[len_off] = s > 32 ? 32 : s;
resp = picolcd_send_and_wait(data->hdev, report_id, raw_data, len_off+1);
if (!resp || !resp->in_report)
goto skip;
if (resp->in_report->id == REPORT_MEMORY ||
resp->in_report->id == REPORT_BL_READ_MEMORY) {
if (memcmp(raw_data, resp->raw_data, len_off+1) != 0)
goto skip;
if (copy_to_user(u+ret, resp->raw_data+len_off+1, raw_data[len_off])) {
err = -EFAULT;
goto skip;
}
*off += raw_data[len_off];
s -= raw_data[len_off];
ret += raw_data[len_off];
err = 0;
}
skip:
kfree(resp);
if (err)
return ret > 0 ? ret : err;
}
return ret;
}
static ssize_t picolcd_debug_flash_read(struct file *f, char __user *u,
size_t s, loff_t *off)
{
struct picolcd_data *data = f->private_data;
if (s == 0)
return -EINVAL;
if (*off > 0x05fff)
return 0;
if (*off + s > 0x05fff)
s = 0x06000 - *off;
if (data->status & PICOLCD_BOOTLOADER)
return _picolcd_flash_read(data, REPORT_BL_READ_MEMORY, u, s, off);
else
return _picolcd_flash_read(data, REPORT_READ_MEMORY, u, s, off);
}
/* erase block aligned to 64bytes boundary */
static ssize_t _picolcd_flash_erase64(struct picolcd_data *data, int report_id,
loff_t *off)
{
struct picolcd_pending *resp;
u8 raw_data[3];
int len_off;
ssize_t ret = -EIO;
if (*off & 0x3f)
return -EINVAL;
len_off = _picolcd_flash_setaddr(data, raw_data, *off);
resp = picolcd_send_and_wait(data->hdev, report_id, raw_data, len_off);
if (!resp || !resp->in_report)
goto skip;
if (resp->in_report->id == REPORT_MEMORY ||
resp->in_report->id == REPORT_BL_ERASE_MEMORY) {
if (memcmp(raw_data, resp->raw_data, len_off) != 0)
goto skip;
ret = 0;
}
skip:
kfree(resp);
return ret;
}
/* write a given size of data (bounds check to be done by caller) */
static ssize_t _picolcd_flash_write(struct picolcd_data *data, int report_id,
const char __user *u, size_t s, loff_t *off)
{
struct picolcd_pending *resp;
u8 raw_data[36];
ssize_t ret = 0;
int len_off, err = -EIO;
while (s > 0) {
err = -EIO;
len_off = _picolcd_flash_setaddr(data, raw_data, *off);
raw_data[len_off] = s > 32 ? 32 : s;
if (copy_from_user(raw_data+len_off+1, u, raw_data[len_off])) {
err = -EFAULT;
goto skip;
}
resp = picolcd_send_and_wait(data->hdev, report_id, raw_data,
len_off+1+raw_data[len_off]);
if (!resp || !resp->in_report)
goto skip;
if (resp->in_report->id == REPORT_MEMORY ||
resp->in_report->id == REPORT_BL_WRITE_MEMORY) {
if (memcmp(raw_data, resp->raw_data, len_off+1+raw_data[len_off]) != 0)
goto skip;
*off += raw_data[len_off];
s -= raw_data[len_off];
ret += raw_data[len_off];
err = 0;
}
skip:
kfree(resp);
if (err)
break;
}
return ret > 0 ? ret : err;
}
static ssize_t picolcd_debug_flash_write(struct file *f, const char __user *u,
size_t s, loff_t *off)
{
struct picolcd_data *data = f->private_data;
ssize_t err, ret = 0;
int report_erase, report_write;
if (s == 0)
return -EINVAL;
if (*off > 0x5fff)
return -ENOSPC;
if (s & 0x3f)
return -EINVAL;
if (*off & 0x3f)
return -EINVAL;
if (data->status & PICOLCD_BOOTLOADER) {
report_erase = REPORT_BL_ERASE_MEMORY;
report_write = REPORT_BL_WRITE_MEMORY;
} else {
report_erase = REPORT_ERASE_MEMORY;
report_write = REPORT_WRITE_MEMORY;
}
mutex_lock(&data->mutex_flash);
while (s > 0) {
err = _picolcd_flash_erase64(data, report_erase, off);
if (err)
break;
err = _picolcd_flash_write(data, report_write, u, 64, off);
if (err < 0)
break;
ret += err;
*off += err;
s -= err;
if (err != 64)
break;
}
mutex_unlock(&data->mutex_flash);
return ret > 0 ? ret : err;
}
/*
* Notes:
* - concurrent writing is prevented by mutex and all writes must be
* n*64 bytes and 64-byte aligned, each write being preceeded by an
* ERASE which erases a 64byte block.
* If less than requested was written or an error is returned for an
* otherwise correct write request the next 64-byte block which should
* have been written is in undefined state (mostly: original, erased,
* (half-)written with write error)
* - reading can happend without special restriction
*/
static const struct file_operations picolcd_debug_flash_fops = {
.owner = THIS_MODULE,
.open = picolcd_debug_flash_open,
.read = picolcd_debug_flash_read,
.write = picolcd_debug_flash_write,
.llseek = generic_file_llseek,
};
/*
* Helper code for HID report level dumping/debugging
*/
@ -1788,9 +2143,66 @@ static void picolcd_debug_raw_event(struct picolcd_data *data,
wake_up_interruptible(&hdev->debug_wait);
kfree(buff);
}
static void picolcd_init_devfs(struct picolcd_data *data,
struct hid_report *eeprom_r, struct hid_report *eeprom_w,
struct hid_report *flash_r, struct hid_report *flash_w,
struct hid_report *reset)
{
struct hid_device *hdev = data->hdev;
mutex_init(&data->mutex_flash);
/* reset */
if (reset)
data->debug_reset = debugfs_create_file("reset", 0600,
hdev->debug_dir, data, &picolcd_debug_reset_fops);
/* eeprom */
if (eeprom_r || eeprom_w)
data->debug_eeprom = debugfs_create_file("eeprom",
(eeprom_w ? S_IWUSR : 0) | (eeprom_r ? S_IRUSR : 0),
hdev->debug_dir, data, &picolcd_debug_eeprom_fops);
/* flash */
if (flash_r && flash_r->maxfield == 1 && flash_r->field[0]->report_size == 8)
data->addr_sz = flash_r->field[0]->report_count - 1;
else
data->addr_sz = -1;
if (data->addr_sz == 2 || data->addr_sz == 3) {
data->debug_flash = debugfs_create_file("flash",
(flash_w ? S_IWUSR : 0) | (flash_r ? S_IRUSR : 0),
hdev->debug_dir, data, &picolcd_debug_flash_fops);
} else if (flash_r || flash_w)
dev_warn(&hdev->dev, "Unexpected FLASH access reports, "
"please submit rdesc for review\n");
}
static void picolcd_exit_devfs(struct picolcd_data *data)
{
struct dentry *dent;
dent = data->debug_reset;
data->debug_reset = NULL;
if (dent)
debugfs_remove(dent);
dent = data->debug_eeprom;
data->debug_eeprom = NULL;
if (dent)
debugfs_remove(dent);
dent = data->debug_flash;
data->debug_flash = NULL;
if (dent)
debugfs_remove(dent);
mutex_destroy(&data->mutex_flash);
}
#else
#define picolcd_debug_raw_event(data, hdev, report, raw_data, size)
#endif
#define picolcd_init_devfs(data, eeprom_r, eeprom_w, flash_r, flash_w, reset)
static void picolcd_exit_devfs(struct picolcd_data *data)
{
}
#endif /* CONFIG_DEBUG_FS */
/*
* Handle raw report as sent by device
@ -1900,7 +2312,6 @@ static inline void picolcd_exit_cir(struct picolcd_data *data)
static int picolcd_probe_lcd(struct hid_device *hdev, struct picolcd_data *data)
{
struct hid_report *report;
int error;
error = picolcd_check_version(hdev);
@ -1942,13 +2353,11 @@ static int picolcd_probe_lcd(struct hid_device *hdev, struct picolcd_data *data)
if (error)
goto err;
#ifdef CONFIG_DEBUG_FS
report = picolcd_out_report(REPORT_READ_MEMORY, hdev);
if (report && report->maxfield == 1 && report->field[0]->report_size == 8)
data->addr_sz = report->field[0]->report_count - 1;
else
data->addr_sz = -1;
#endif
picolcd_init_devfs(data, picolcd_out_report(REPORT_EE_READ, hdev),
picolcd_out_report(REPORT_EE_WRITE, hdev),
picolcd_out_report(REPORT_READ_MEMORY, hdev),
picolcd_out_report(REPORT_WRITE_MEMORY, hdev),
picolcd_out_report(REPORT_RESET, hdev));
return 0;
err:
picolcd_exit_leds(data);
@ -1962,7 +2371,6 @@ err:
static int picolcd_probe_bootloader(struct hid_device *hdev, struct picolcd_data *data)
{
struct hid_report *report;
int error;
error = picolcd_check_version(hdev);
@ -1974,13 +2382,9 @@ static int picolcd_probe_bootloader(struct hid_device *hdev, struct picolcd_data
"please submit /sys/kernel/debug/hid/%s/rdesc for this device.\n",
dev_name(&hdev->dev));
#ifdef CONFIG_DEBUG_FS
report = picolcd_out_report(REPORT_BL_READ_MEMORY, hdev);
if (report && report->maxfield == 1 && report->field[0]->report_size == 8)
data->addr_sz = report->field[0]->report_count - 1;
else
data->addr_sz = -1;
#endif
picolcd_init_devfs(data, NULL, NULL,
picolcd_out_report(REPORT_BL_READ_MEMORY, hdev),
picolcd_out_report(REPORT_BL_WRITE_MEMORY, hdev), NULL);
return 0;
}
@ -2073,6 +2477,7 @@ static void picolcd_remove(struct hid_device *hdev)
data->status |= PICOLCD_FAILED;
spin_unlock_irqrestore(&data->lock, flags);
picolcd_exit_devfs(data);
device_remove_file(&hdev->dev, &dev_attr_operation_mode);
hdev->ll_driver->close(hdev);
hid_hw_stop(hdev);