mirror of
https://github.com/followmsi/android_kernel_google_msm.git
synced 2024-11-06 23:17:41 +00:00
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:
parent
467d652306
commit
9bbf2b98ba
2 changed files with 423 additions and 18 deletions
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue