aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/hid
diff options
context:
space:
mode:
authorBruno Prémont <bonbons@linux-vserver.org>2010-03-30 16:38:09 -0400
committerJiri Kosina <jkosina@suse.cz>2010-03-31 05:32:31 -0400
commit9bbf2b98ba11d00bd73e3254e15cfe17ccaff6ba (patch)
treecffe1c088a62e943fde7a954177e530bc780d2c3 /drivers/hid
parent467d6523065187d4c081b078755da4103d7ffacb (diff)
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>
Diffstat (limited to 'drivers/hid')
-rw-r--r--drivers/hid/Kconfig2
-rw-r--r--drivers/hid/hid-picolcd.c439
2 files changed, 423 insertions, 18 deletions
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 34f6593ea3b6..a2ecd83bfe89 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -281,9 +281,9 @@ config HID_PICOLCD
281 - Backlight control (needs CONFIG_BACKLIGHT_CLASS_DEVICE) 281 - Backlight control (needs CONFIG_BACKLIGHT_CLASS_DEVICE)
282 - Contrast control (needs CONFIG_LCD_CLASS_DEVICE) 282 - Contrast control (needs CONFIG_LCD_CLASS_DEVICE)
283 - General purpose outputs (needs CONFIG_LEDS_CLASS) 283 - General purpose outputs (needs CONFIG_LEDS_CLASS)
284 - EEProm / Flash access (via debugfs)
284 Features that are not (yet) supported: 285 Features that are not (yet) supported:
285 - IR 286 - IR
286 - EEProm / Flash access
287 287
288config HID_QUANTA 288config HID_QUANTA
289 tristate "Quanta Optical Touch" 289 tristate "Quanta Optical Touch"
diff --git a/drivers/hid/hid-picolcd.c b/drivers/hid/hid-picolcd.c
index 517677305ef9..66f9cfd41abe 100644
--- a/drivers/hid/hid-picolcd.c
+++ b/drivers/hid/hid-picolcd.c
@@ -169,6 +169,10 @@ struct picolcd_pending {
169struct picolcd_data { 169struct picolcd_data {
170 struct hid_device *hdev; 170 struct hid_device *hdev;
171#ifdef CONFIG_DEBUG_FS 171#ifdef CONFIG_DEBUG_FS
172 struct dentry *debug_reset;
173 struct dentry *debug_eeprom;
174 struct dentry *debug_flash;
175 struct mutex mutex_flash;
172 int addr_sz; 176 int addr_sz;
173#endif 177#endif
174 u8 version[2]; 178 u8 version[2];
@@ -1314,6 +1318,357 @@ static DEVICE_ATTR(operation_mode, 0644, picolcd_operation_mode_show,
1314 1318
1315#ifdef CONFIG_DEBUG_FS 1319#ifdef CONFIG_DEBUG_FS
1316/* 1320/*
1321 * The "reset" file
1322 */
1323static int picolcd_debug_reset_show(struct seq_file *f, void *p)
1324{
1325 if (picolcd_fbinfo((struct picolcd_data *)f->private))
1326 seq_printf(f, "all fb\n");
1327 else
1328 seq_printf(f, "all\n");
1329 return 0;
1330}
1331
1332static int picolcd_debug_reset_open(struct inode *inode, struct file *f)
1333{
1334 return single_open(f, picolcd_debug_reset_show, inode->i_private);
1335}
1336
1337static ssize_t picolcd_debug_reset_write(struct file *f, const char __user *user_buf,
1338 size_t count, loff_t *ppos)
1339{
1340 struct picolcd_data *data = ((struct seq_file *)f->private_data)->private;
1341 char buf[32];
1342 size_t cnt = min(count, sizeof(buf)-1);
1343 if (copy_from_user(buf, user_buf, cnt))
1344 return -EFAULT;
1345
1346 while (cnt > 0 && (buf[cnt-1] == ' ' || buf[cnt-1] == '\n'))
1347 cnt--;
1348 buf[cnt] = '\0';
1349 if (strcmp(buf, "all") == 0) {
1350 picolcd_reset(data->hdev);
1351 picolcd_fb_reset(data, 1);
1352 } else if (strcmp(buf, "fb") == 0) {
1353 picolcd_fb_reset(data, 1);
1354 } else {
1355 return -EINVAL;
1356 }
1357 return count;
1358}
1359
1360static const struct file_operations picolcd_debug_reset_fops = {
1361 .owner = THIS_MODULE,
1362 .open = picolcd_debug_reset_open,
1363 .read = seq_read,
1364 .llseek = seq_lseek,
1365 .write = picolcd_debug_reset_write,
1366 .release = single_release,
1367};
1368
1369/*
1370 * The "eeprom" file
1371 */
1372static int picolcd_debug_eeprom_open(struct inode *i, struct file *f)
1373{
1374 f->private_data = i->i_private;
1375 return 0;
1376}
1377
1378static ssize_t picolcd_debug_eeprom_read(struct file *f, char __user *u,
1379 size_t s, loff_t *off)
1380{
1381 struct picolcd_data *data = f->private_data;
1382 struct picolcd_pending *resp;
1383 u8 raw_data[3];
1384 ssize_t ret = -EIO;
1385
1386 if (s == 0)
1387 return -EINVAL;
1388 if (*off > 0x0ff)
1389 return 0;
1390
1391 /* prepare buffer with info about what we want to read (addr & len) */
1392 raw_data[0] = *off & 0xff;
1393 raw_data[1] = (*off >> 8) && 0xff;
1394 raw_data[2] = s < 20 ? s : 20;
1395 if (*off + raw_data[2] > 0xff)
1396 raw_data[2] = 0x100 - *off;
1397 resp = picolcd_send_and_wait(data->hdev, REPORT_EE_READ, raw_data,
1398 sizeof(raw_data));
1399 if (!resp)
1400 return -EIO;
1401
1402 if (resp->in_report && resp->in_report->id == REPORT_EE_DATA) {
1403 /* successful read :) */
1404 ret = resp->raw_data[2];
1405 if (ret > s)
1406 ret = s;
1407 if (copy_to_user(u, resp->raw_data+3, ret))
1408 ret = -EFAULT;
1409 else
1410 *off += ret;
1411 } /* anything else is some kind of IO error */
1412
1413 kfree(resp);
1414 return ret;
1415}
1416
1417static ssize_t picolcd_debug_eeprom_write(struct file *f, const char __user *u,
1418 size_t s, loff_t *off)
1419{
1420 struct picolcd_data *data = f->private_data;
1421 struct picolcd_pending *resp;
1422 ssize_t ret = -EIO;
1423 u8 raw_data[23];
1424
1425 if (s == 0)
1426 return -EINVAL;
1427 if (*off > 0x0ff)
1428 return -ENOSPC;
1429
1430 memset(raw_data, 0, sizeof(raw_data));
1431 raw_data[0] = *off & 0xff;
1432 raw_data[1] = (*off >> 8) && 0xff;
1433 raw_data[2] = s < 20 ? s : 20;
1434 if (*off + raw_data[2] > 0xff)
1435 raw_data[2] = 0x100 - *off;
1436
1437 if (copy_from_user(raw_data+3, u, raw_data[2]))
1438 return -EFAULT;
1439 resp = picolcd_send_and_wait(data->hdev, REPORT_EE_WRITE, raw_data,
1440 sizeof(raw_data));
1441
1442 if (!resp)
1443 return -EIO;
1444
1445 if (resp->in_report && resp->in_report->id == REPORT_EE_DATA) {
1446 /* check if written data matches */
1447 if (memcmp(raw_data, resp->raw_data, 3+raw_data[2]) == 0) {
1448 *off += raw_data[2];
1449 ret = raw_data[2];
1450 }
1451 }
1452 kfree(resp);
1453 return ret;
1454}
1455
1456/*
1457 * Notes:
1458 * - read/write happens in chunks of at most 20 bytes, it's up to userspace
1459 * to loop in order to get more data.
1460 * - on write errors on otherwise correct write request the bytes
1461 * that should have been written are in undefined state.
1462 */
1463static const struct file_operations picolcd_debug_eeprom_fops = {
1464 .owner = THIS_MODULE,
1465 .open = picolcd_debug_eeprom_open,
1466 .read = picolcd_debug_eeprom_read,
1467 .write = picolcd_debug_eeprom_write,
1468 .llseek = generic_file_llseek,
1469};
1470
1471/*
1472 * The "flash" file
1473 */
1474static int picolcd_debug_flash_open(struct inode *i, struct file *f)
1475{
1476 f->private_data = i->i_private;
1477 return 0;
1478}
1479
1480/* record a flash address to buf (bounds check to be done by caller) */
1481static int _picolcd_flash_setaddr(struct picolcd_data *data, u8 *buf, long off)
1482{
1483 buf[0] = off & 0xff;
1484 buf[1] = (off >> 8) & 0xff;
1485 if (data->addr_sz == 3)
1486 buf[2] = (off >> 16) & 0xff;
1487 return data->addr_sz == 2 ? 2 : 3;
1488}
1489
1490/* read a given size of data (bounds check to be done by caller) */
1491static ssize_t _picolcd_flash_read(struct picolcd_data *data, int report_id,
1492 char __user *u, size_t s, loff_t *off)
1493{
1494 struct picolcd_pending *resp;
1495 u8 raw_data[4];
1496 ssize_t ret = 0;
1497 int len_off, err = -EIO;
1498
1499 while (s > 0) {
1500 err = -EIO;
1501 len_off = _picolcd_flash_setaddr(data, raw_data, *off);
1502 raw_data[len_off] = s > 32 ? 32 : s;
1503 resp = picolcd_send_and_wait(data->hdev, report_id, raw_data, len_off+1);
1504 if (!resp || !resp->in_report)
1505 goto skip;
1506 if (resp->in_report->id == REPORT_MEMORY ||
1507 resp->in_report->id == REPORT_BL_READ_MEMORY) {
1508 if (memcmp(raw_data, resp->raw_data, len_off+1) != 0)
1509 goto skip;
1510 if (copy_to_user(u+ret, resp->raw_data+len_off+1, raw_data[len_off])) {
1511 err = -EFAULT;
1512 goto skip;
1513 }
1514 *off += raw_data[len_off];
1515 s -= raw_data[len_off];
1516 ret += raw_data[len_off];
1517 err = 0;
1518 }
1519skip:
1520 kfree(resp);
1521 if (err)
1522 return ret > 0 ? ret : err;
1523 }
1524 return ret;
1525}
1526
1527static ssize_t picolcd_debug_flash_read(struct file *f, char __user *u,
1528 size_t s, loff_t *off)
1529{
1530 struct picolcd_data *data = f->private_data;
1531
1532 if (s == 0)
1533 return -EINVAL;
1534 if (*off > 0x05fff)
1535 return 0;
1536 if (*off + s > 0x05fff)
1537 s = 0x06000 - *off;
1538
1539 if (data->status & PICOLCD_BOOTLOADER)
1540 return _picolcd_flash_read(data, REPORT_BL_READ_MEMORY, u, s, off);
1541 else
1542 return _picolcd_flash_read(data, REPORT_READ_MEMORY, u, s, off);
1543}
1544
1545/* erase block aligned to 64bytes boundary */
1546static ssize_t _picolcd_flash_erase64(struct picolcd_data *data, int report_id,
1547 loff_t *off)
1548{
1549 struct picolcd_pending *resp;
1550 u8 raw_data[3];
1551 int len_off;
1552 ssize_t ret = -EIO;
1553
1554 if (*off & 0x3f)
1555 return -EINVAL;
1556
1557 len_off = _picolcd_flash_setaddr(data, raw_data, *off);
1558 resp = picolcd_send_and_wait(data->hdev, report_id, raw_data, len_off);
1559 if (!resp || !resp->in_report)
1560 goto skip;
1561 if (resp->in_report->id == REPORT_MEMORY ||
1562 resp->in_report->id == REPORT_BL_ERASE_MEMORY) {
1563 if (memcmp(raw_data, resp->raw_data, len_off) != 0)
1564 goto skip;
1565 ret = 0;
1566 }
1567skip:
1568 kfree(resp);
1569 return ret;
1570}
1571
1572/* write a given size of data (bounds check to be done by caller) */
1573static ssize_t _picolcd_flash_write(struct picolcd_data *data, int report_id,
1574 const char __user *u, size_t s, loff_t *off)
1575{
1576 struct picolcd_pending *resp;
1577 u8 raw_data[36];
1578 ssize_t ret = 0;
1579 int len_off, err = -EIO;
1580
1581 while (s > 0) {
1582 err = -EIO;
1583 len_off = _picolcd_flash_setaddr(data, raw_data, *off);
1584 raw_data[len_off] = s > 32 ? 32 : s;
1585 if (copy_from_user(raw_data+len_off+1, u, raw_data[len_off])) {
1586 err = -EFAULT;
1587 goto skip;
1588 }
1589 resp = picolcd_send_and_wait(data->hdev, report_id, raw_data,
1590 len_off+1+raw_data[len_off]);
1591 if (!resp || !resp->in_report)
1592 goto skip;
1593 if (resp->in_report->id == REPORT_MEMORY ||
1594 resp->in_report->id == REPORT_BL_WRITE_MEMORY) {
1595 if (memcmp(raw_data, resp->raw_data, len_off+1+raw_data[len_off]) != 0)
1596 goto skip;
1597 *off += raw_data[len_off];
1598 s -= raw_data[len_off];
1599 ret += raw_data[len_off];
1600 err = 0;
1601 }
1602skip:
1603 kfree(resp);
1604 if (err)
1605 break;
1606 }
1607 return ret > 0 ? ret : err;
1608}
1609
1610static ssize_t picolcd_debug_flash_write(struct file *f, const char __user *u,
1611 size_t s, loff_t *off)
1612{
1613 struct picolcd_data *data = f->private_data;
1614 ssize_t err, ret = 0;
1615 int report_erase, report_write;
1616
1617 if (s == 0)
1618 return -EINVAL;
1619 if (*off > 0x5fff)
1620 return -ENOSPC;
1621 if (s & 0x3f)
1622 return -EINVAL;
1623 if (*off & 0x3f)
1624 return -EINVAL;
1625
1626 if (data->status & PICOLCD_BOOTLOADER) {
1627 report_erase = REPORT_BL_ERASE_MEMORY;
1628 report_write = REPORT_BL_WRITE_MEMORY;
1629 } else {
1630 report_erase = REPORT_ERASE_MEMORY;
1631 report_write = REPORT_WRITE_MEMORY;
1632 }
1633 mutex_lock(&data->mutex_flash);
1634 while (s > 0) {
1635 err = _picolcd_flash_erase64(data, report_erase, off);
1636 if (err)
1637 break;
1638 err = _picolcd_flash_write(data, report_write, u, 64, off);
1639 if (err < 0)
1640 break;
1641 ret += err;
1642 *off += err;
1643 s -= err;
1644 if (err != 64)
1645 break;
1646 }
1647 mutex_unlock(&data->mutex_flash);
1648 return ret > 0 ? ret : err;
1649}
1650
1651/*
1652 * Notes:
1653 * - concurrent writing is prevented by mutex and all writes must be
1654 * n*64 bytes and 64-byte aligned, each write being preceeded by an
1655 * ERASE which erases a 64byte block.
1656 * If less than requested was written or an error is returned for an
1657 * otherwise correct write request the next 64-byte block which should
1658 * have been written is in undefined state (mostly: original, erased,
1659 * (half-)written with write error)
1660 * - reading can happend without special restriction
1661 */
1662static const struct file_operations picolcd_debug_flash_fops = {
1663 .owner = THIS_MODULE,
1664 .open = picolcd_debug_flash_open,
1665 .read = picolcd_debug_flash_read,
1666 .write = picolcd_debug_flash_write,
1667 .llseek = generic_file_llseek,
1668};
1669
1670
1671/*
1317 * Helper code for HID report level dumping/debugging 1672 * Helper code for HID report level dumping/debugging
1318 */ 1673 */
1319static const char *error_codes[] = { 1674static const char *error_codes[] = {
@@ -1788,9 +2143,66 @@ static void picolcd_debug_raw_event(struct picolcd_data *data,
1788 wake_up_interruptible(&hdev->debug_wait); 2143 wake_up_interruptible(&hdev->debug_wait);
1789 kfree(buff); 2144 kfree(buff);
1790} 2145}
2146
2147static void picolcd_init_devfs(struct picolcd_data *data,
2148 struct hid_report *eeprom_r, struct hid_report *eeprom_w,
2149 struct hid_report *flash_r, struct hid_report *flash_w,
2150 struct hid_report *reset)
2151{
2152 struct hid_device *hdev = data->hdev;
2153
2154 mutex_init(&data->mutex_flash);
2155
2156 /* reset */
2157 if (reset)
2158 data->debug_reset = debugfs_create_file("reset", 0600,
2159 hdev->debug_dir, data, &picolcd_debug_reset_fops);
2160
2161 /* eeprom */
2162 if (eeprom_r || eeprom_w)
2163 data->debug_eeprom = debugfs_create_file("eeprom",
2164 (eeprom_w ? S_IWUSR : 0) | (eeprom_r ? S_IRUSR : 0),
2165 hdev->debug_dir, data, &picolcd_debug_eeprom_fops);
2166
2167 /* flash */
2168 if (flash_r && flash_r->maxfield == 1 && flash_r->field[0]->report_size == 8)
2169 data->addr_sz = flash_r->field[0]->report_count - 1;
2170 else
2171 data->addr_sz = -1;
2172 if (data->addr_sz == 2 || data->addr_sz == 3) {
2173 data->debug_flash = debugfs_create_file("flash",
2174 (flash_w ? S_IWUSR : 0) | (flash_r ? S_IRUSR : 0),
2175 hdev->debug_dir, data, &picolcd_debug_flash_fops);
2176 } else if (flash_r || flash_w)
2177 dev_warn(&hdev->dev, "Unexpected FLASH access reports, "
2178 "please submit rdesc for review\n");
2179}
2180
2181static void picolcd_exit_devfs(struct picolcd_data *data)
2182{
2183 struct dentry *dent;
2184
2185 dent = data->debug_reset;
2186 data->debug_reset = NULL;
2187 if (dent)
2188 debugfs_remove(dent);
2189 dent = data->debug_eeprom;
2190 data->debug_eeprom = NULL;
2191 if (dent)
2192 debugfs_remove(dent);
2193 dent = data->debug_flash;
2194 data->debug_flash = NULL;
2195 if (dent)
2196 debugfs_remove(dent);
2197 mutex_destroy(&data->mutex_flash);
2198}
1791#else 2199#else
1792#define picolcd_debug_raw_event(data, hdev, report, raw_data, size) 2200#define picolcd_debug_raw_event(data, hdev, report, raw_data, size)
1793#endif 2201#define picolcd_init_devfs(data, eeprom_r, eeprom_w, flash_r, flash_w, reset)
2202static void picolcd_exit_devfs(struct picolcd_data *data)
2203{
2204}
2205#endif /* CONFIG_DEBUG_FS */
1794 2206
1795/* 2207/*
1796 * Handle raw report as sent by device 2208 * Handle raw report as sent by device
@@ -1900,7 +2312,6 @@ static inline void picolcd_exit_cir(struct picolcd_data *data)
1900 2312
1901static int picolcd_probe_lcd(struct hid_device *hdev, struct picolcd_data *data) 2313static int picolcd_probe_lcd(struct hid_device *hdev, struct picolcd_data *data)
1902{ 2314{
1903 struct hid_report *report;
1904 int error; 2315 int error;
1905 2316
1906 error = picolcd_check_version(hdev); 2317 error = picolcd_check_version(hdev);
@@ -1942,13 +2353,11 @@ static int picolcd_probe_lcd(struct hid_device *hdev, struct picolcd_data *data)
1942 if (error) 2353 if (error)
1943 goto err; 2354 goto err;
1944 2355
1945#ifdef CONFIG_DEBUG_FS 2356 picolcd_init_devfs(data, picolcd_out_report(REPORT_EE_READ, hdev),
1946 report = picolcd_out_report(REPORT_READ_MEMORY, hdev); 2357 picolcd_out_report(REPORT_EE_WRITE, hdev),
1947 if (report && report->maxfield == 1 && report->field[0]->report_size == 8) 2358 picolcd_out_report(REPORT_READ_MEMORY, hdev),
1948 data->addr_sz = report->field[0]->report_count - 1; 2359 picolcd_out_report(REPORT_WRITE_MEMORY, hdev),
1949 else 2360 picolcd_out_report(REPORT_RESET, hdev));
1950 data->addr_sz = -1;
1951#endif
1952 return 0; 2361 return 0;
1953err: 2362err:
1954 picolcd_exit_leds(data); 2363 picolcd_exit_leds(data);
@@ -1962,7 +2371,6 @@ err:
1962 2371
1963static int picolcd_probe_bootloader(struct hid_device *hdev, struct picolcd_data *data) 2372static int picolcd_probe_bootloader(struct hid_device *hdev, struct picolcd_data *data)
1964{ 2373{
1965 struct hid_report *report;
1966 int error; 2374 int error;
1967 2375
1968 error = picolcd_check_version(hdev); 2376 error = picolcd_check_version(hdev);
@@ -1974,13 +2382,9 @@ static int picolcd_probe_bootloader(struct hid_device *hdev, struct picolcd_data
1974 "please submit /sys/kernel/debug/hid/%s/rdesc for this device.\n", 2382 "please submit /sys/kernel/debug/hid/%s/rdesc for this device.\n",
1975 dev_name(&hdev->dev)); 2383 dev_name(&hdev->dev));
1976 2384
1977#ifdef CONFIG_DEBUG_FS 2385 picolcd_init_devfs(data, NULL, NULL,
1978 report = picolcd_out_report(REPORT_BL_READ_MEMORY, hdev); 2386 picolcd_out_report(REPORT_BL_READ_MEMORY, hdev),
1979 if (report && report->maxfield == 1 && report->field[0]->report_size == 8) 2387 picolcd_out_report(REPORT_BL_WRITE_MEMORY, hdev), NULL);
1980 data->addr_sz = report->field[0]->report_count - 1;
1981 else
1982 data->addr_sz = -1;
1983#endif
1984 return 0; 2388 return 0;
1985} 2389}
1986 2390
@@ -2073,6 +2477,7 @@ static void picolcd_remove(struct hid_device *hdev)
2073 data->status |= PICOLCD_FAILED; 2477 data->status |= PICOLCD_FAILED;
2074 spin_unlock_irqrestore(&data->lock, flags); 2478 spin_unlock_irqrestore(&data->lock, flags);
2075 2479
2480 picolcd_exit_devfs(data);
2076 device_remove_file(&hdev->dev, &dev_attr_operation_mode); 2481 device_remove_file(&hdev->dev, &dev_attr_operation_mode);
2077 hdev->ll_driver->close(hdev); 2482 hdev->ll_driver->close(hdev);
2078 hid_hw_stop(hdev); 2483 hid_hw_stop(hdev);