diff options
Diffstat (limited to 'drivers/media/video/em28xx/em28xx-cards.c')
-rw-r--r-- | drivers/media/video/em28xx/em28xx-cards.c | 219 |
1 files changed, 140 insertions, 79 deletions
diff --git a/drivers/media/video/em28xx/em28xx-cards.c b/drivers/media/video/em28xx/em28xx-cards.c index 320f1f60276e..1c2e544eda73 100644 --- a/drivers/media/video/em28xx/em28xx-cards.c +++ b/drivers/media/video/em28xx/em28xx-cards.c | |||
@@ -218,7 +218,7 @@ static struct em28xx_reg_seq silvercrest_reg_seq[] = { | |||
218 | struct em28xx_board em28xx_boards[] = { | 218 | struct em28xx_board em28xx_boards[] = { |
219 | [EM2750_BOARD_UNKNOWN] = { | 219 | [EM2750_BOARD_UNKNOWN] = { |
220 | .name = "EM2710/EM2750/EM2751 webcam grabber", | 220 | .name = "EM2710/EM2750/EM2751 webcam grabber", |
221 | .xclk = EM28XX_XCLK_FREQUENCY_48MHZ, | 221 | .xclk = EM28XX_XCLK_FREQUENCY_20MHZ, |
222 | .tuner_type = TUNER_ABSENT, | 222 | .tuner_type = TUNER_ABSENT, |
223 | .is_webcam = 1, | 223 | .is_webcam = 1, |
224 | .input = { { | 224 | .input = { { |
@@ -622,22 +622,27 @@ struct em28xx_board em28xx_boards[] = { | |||
622 | }, | 622 | }, |
623 | [EM2861_BOARD_PLEXTOR_PX_TV100U] = { | 623 | [EM2861_BOARD_PLEXTOR_PX_TV100U] = { |
624 | .name = "Plextor ConvertX PX-TV100U", | 624 | .name = "Plextor ConvertX PX-TV100U", |
625 | .valid = EM28XX_BOARD_NOT_VALIDATED, | ||
626 | .tuner_type = TUNER_TNF_5335MF, | 625 | .tuner_type = TUNER_TNF_5335MF, |
626 | .xclk = EM28XX_XCLK_I2S_MSB_TIMING | | ||
627 | EM28XX_XCLK_FREQUENCY_12MHZ, | ||
627 | .tda9887_conf = TDA9887_PRESENT, | 628 | .tda9887_conf = TDA9887_PRESENT, |
628 | .decoder = EM28XX_TVP5150, | 629 | .decoder = EM28XX_TVP5150, |
630 | .has_msp34xx = 1, | ||
629 | .input = { { | 631 | .input = { { |
630 | .type = EM28XX_VMUX_TELEVISION, | 632 | .type = EM28XX_VMUX_TELEVISION, |
631 | .vmux = TVP5150_COMPOSITE0, | 633 | .vmux = TVP5150_COMPOSITE0, |
632 | .amux = EM28XX_AMUX_LINE_IN, | 634 | .amux = EM28XX_AMUX_LINE_IN, |
635 | .gpio = pinnacle_hybrid_pro_analog, | ||
633 | }, { | 636 | }, { |
634 | .type = EM28XX_VMUX_COMPOSITE1, | 637 | .type = EM28XX_VMUX_COMPOSITE1, |
635 | .vmux = TVP5150_COMPOSITE1, | 638 | .vmux = TVP5150_COMPOSITE1, |
636 | .amux = EM28XX_AMUX_LINE_IN, | 639 | .amux = EM28XX_AMUX_LINE_IN, |
640 | .gpio = pinnacle_hybrid_pro_analog, | ||
637 | }, { | 641 | }, { |
638 | .type = EM28XX_VMUX_SVIDEO, | 642 | .type = EM28XX_VMUX_SVIDEO, |
639 | .vmux = TVP5150_SVIDEO, | 643 | .vmux = TVP5150_SVIDEO, |
640 | .amux = EM28XX_AMUX_LINE_IN, | 644 | .amux = EM28XX_AMUX_LINE_IN, |
645 | .gpio = pinnacle_hybrid_pro_analog, | ||
641 | } }, | 646 | } }, |
642 | }, | 647 | }, |
643 | 648 | ||
@@ -1544,6 +1549,8 @@ struct usb_device_id em28xx_id_table[] = { | |||
1544 | .driver_info = EM2750_BOARD_UNKNOWN }, | 1549 | .driver_info = EM2750_BOARD_UNKNOWN }, |
1545 | { USB_DEVICE(0xeb1a, 0x2800), | 1550 | { USB_DEVICE(0xeb1a, 0x2800), |
1546 | .driver_info = EM2800_BOARD_UNKNOWN }, | 1551 | .driver_info = EM2800_BOARD_UNKNOWN }, |
1552 | { USB_DEVICE(0xeb1a, 0x2710), | ||
1553 | .driver_info = EM2820_BOARD_UNKNOWN }, | ||
1547 | { USB_DEVICE(0xeb1a, 0x2820), | 1554 | { USB_DEVICE(0xeb1a, 0x2820), |
1548 | .driver_info = EM2820_BOARD_UNKNOWN }, | 1555 | .driver_info = EM2820_BOARD_UNKNOWN }, |
1549 | { USB_DEVICE(0xeb1a, 0x2821), | 1556 | { USB_DEVICE(0xeb1a, 0x2821), |
@@ -1723,6 +1730,25 @@ static inline void em28xx_set_model(struct em28xx *dev) | |||
1723 | EM28XX_I2C_FREQ_100_KHZ; | 1730 | EM28XX_I2C_FREQ_100_KHZ; |
1724 | } | 1731 | } |
1725 | 1732 | ||
1733 | |||
1734 | /* FIXME: Should be replaced by a proper mt9m111 driver */ | ||
1735 | static int em28xx_initialize_mt9m111(struct em28xx *dev) | ||
1736 | { | ||
1737 | int i; | ||
1738 | unsigned char regs[][3] = { | ||
1739 | { 0x0d, 0x00, 0x01, }, /* reset and use defaults */ | ||
1740 | { 0x0d, 0x00, 0x00, }, | ||
1741 | { 0x0a, 0x00, 0x21, }, | ||
1742 | { 0x21, 0x04, 0x00, }, /* full readout speed, no row/col skipping */ | ||
1743 | }; | ||
1744 | |||
1745 | for (i = 0; i < ARRAY_SIZE(regs); i++) | ||
1746 | i2c_master_send(&dev->i2c_client, ®s[i][0], 3); | ||
1747 | |||
1748 | return 0; | ||
1749 | } | ||
1750 | |||
1751 | |||
1726 | /* FIXME: Should be replaced by a proper mt9m001 driver */ | 1752 | /* FIXME: Should be replaced by a proper mt9m001 driver */ |
1727 | static int em28xx_initialize_mt9m001(struct em28xx *dev) | 1753 | static int em28xx_initialize_mt9m001(struct em28xx *dev) |
1728 | { | 1754 | { |
@@ -1751,7 +1777,7 @@ static int em28xx_initialize_mt9m001(struct em28xx *dev) | |||
1751 | 1777 | ||
1752 | /* HINT method: webcam I2C chips | 1778 | /* HINT method: webcam I2C chips |
1753 | * | 1779 | * |
1754 | * This method work for webcams with Micron sensors | 1780 | * This method works for webcams with Micron sensors |
1755 | */ | 1781 | */ |
1756 | static int em28xx_hint_sensor(struct em28xx *dev) | 1782 | static int em28xx_hint_sensor(struct em28xx *dev) |
1757 | { | 1783 | { |
@@ -1761,6 +1787,7 @@ static int em28xx_hint_sensor(struct em28xx *dev) | |||
1761 | __be16 version_be; | 1787 | __be16 version_be; |
1762 | u16 version; | 1788 | u16 version; |
1763 | 1789 | ||
1790 | /* Micron sensor detection */ | ||
1764 | dev->i2c_client.addr = 0xba >> 1; | 1791 | dev->i2c_client.addr = 0xba >> 1; |
1765 | cmd = 0; | 1792 | cmd = 0; |
1766 | i2c_master_send(&dev->i2c_client, &cmd, 1); | 1793 | i2c_master_send(&dev->i2c_client, &cmd, 1); |
@@ -1769,23 +1796,54 @@ static int em28xx_hint_sensor(struct em28xx *dev) | |||
1769 | return -EINVAL; | 1796 | return -EINVAL; |
1770 | 1797 | ||
1771 | version = be16_to_cpu(version_be); | 1798 | version = be16_to_cpu(version_be); |
1772 | |||
1773 | switch (version) { | 1799 | switch (version) { |
1774 | case 0x8243: /* mt9v011 640x480 1.3 Mpix sensor */ | 1800 | case 0x8232: /* mt9v011 640x480 1.3 Mpix sensor */ |
1801 | case 0x8243: /* mt9v011 rev B 640x480 1.3 Mpix sensor */ | ||
1775 | dev->model = EM2820_BOARD_SILVERCREST_WEBCAM; | 1802 | dev->model = EM2820_BOARD_SILVERCREST_WEBCAM; |
1803 | em28xx_set_model(dev); | ||
1804 | |||
1776 | sensor_name = "mt9v011"; | 1805 | sensor_name = "mt9v011"; |
1777 | dev->em28xx_sensor = EM28XX_MT9V011; | 1806 | dev->em28xx_sensor = EM28XX_MT9V011; |
1778 | dev->sensor_xres = 640; | 1807 | dev->sensor_xres = 640; |
1779 | dev->sensor_yres = 480; | 1808 | dev->sensor_yres = 480; |
1780 | dev->sensor_xtal = 6300000; | 1809 | /* |
1810 | * FIXME: mt9v011 uses I2S speed as xtal clk - at least with | ||
1811 | * the Silvercrest cam I have here for testing - for higher | ||
1812 | * resolutions, a high clock cause horizontal artifacts, so we | ||
1813 | * need to use a lower xclk frequency. | ||
1814 | * Yet, it would be possible to adjust xclk depending on the | ||
1815 | * desired resolution, since this affects directly the | ||
1816 | * frame rate. | ||
1817 | */ | ||
1818 | dev->board.xclk = EM28XX_XCLK_FREQUENCY_4_3MHZ; | ||
1819 | dev->sensor_xtal = 4300000; | ||
1781 | 1820 | ||
1782 | /* probably means GRGB 16 bit bayer */ | 1821 | /* probably means GRGB 16 bit bayer */ |
1783 | dev->vinmode = 0x0d; | 1822 | dev->vinmode = 0x0d; |
1784 | dev->vinctl = 0x00; | 1823 | dev->vinctl = 0x00; |
1785 | 1824 | ||
1786 | break; | 1825 | break; |
1826 | |||
1827 | case 0x143a: /* MT9M111 as found in the ECS G200 */ | ||
1828 | dev->model = EM2750_BOARD_UNKNOWN; | ||
1829 | em28xx_set_model(dev); | ||
1830 | |||
1831 | sensor_name = "mt9m111"; | ||
1832 | dev->board.xclk = EM28XX_XCLK_FREQUENCY_48MHZ; | ||
1833 | dev->em28xx_sensor = EM28XX_MT9M111; | ||
1834 | em28xx_initialize_mt9m111(dev); | ||
1835 | dev->sensor_xres = 640; | ||
1836 | dev->sensor_yres = 512; | ||
1837 | |||
1838 | dev->vinmode = 0x0a; | ||
1839 | dev->vinctl = 0x00; | ||
1840 | |||
1841 | break; | ||
1842 | |||
1787 | case 0x8431: | 1843 | case 0x8431: |
1788 | dev->model = EM2750_BOARD_UNKNOWN; | 1844 | dev->model = EM2750_BOARD_UNKNOWN; |
1845 | em28xx_set_model(dev); | ||
1846 | |||
1789 | sensor_name = "mt9m001"; | 1847 | sensor_name = "mt9m001"; |
1790 | dev->em28xx_sensor = EM28XX_MT9M001; | 1848 | dev->em28xx_sensor = EM28XX_MT9M001; |
1791 | em28xx_initialize_mt9m001(dev); | 1849 | em28xx_initialize_mt9m001(dev); |
@@ -1798,10 +1856,13 @@ static int em28xx_hint_sensor(struct em28xx *dev) | |||
1798 | 1856 | ||
1799 | break; | 1857 | break; |
1800 | default: | 1858 | default: |
1801 | printk("Unknown Micron Sensor 0x%04x\n", be16_to_cpu(version)); | 1859 | printk("Unknown Micron Sensor 0x%04x\n", version); |
1802 | return -EINVAL; | 1860 | return -EINVAL; |
1803 | } | 1861 | } |
1804 | 1862 | ||
1863 | /* Setup webcam defaults */ | ||
1864 | em28xx_pre_card_setup(dev); | ||
1865 | |||
1805 | em28xx_errdev("Sensor is %s, using model %s entry.\n", | 1866 | em28xx_errdev("Sensor is %s, using model %s entry.\n", |
1806 | sensor_name, em28xx_boards[dev->model].name); | 1867 | sensor_name, em28xx_boards[dev->model].name); |
1807 | 1868 | ||
@@ -1813,60 +1874,6 @@ static int em28xx_hint_sensor(struct em28xx *dev) | |||
1813 | */ | 1874 | */ |
1814 | void em28xx_pre_card_setup(struct em28xx *dev) | 1875 | void em28xx_pre_card_setup(struct em28xx *dev) |
1815 | { | 1876 | { |
1816 | int rc; | ||
1817 | |||
1818 | em28xx_set_model(dev); | ||
1819 | |||
1820 | em28xx_info("Identified as %s (card=%d)\n", | ||
1821 | dev->board.name, dev->model); | ||
1822 | |||
1823 | /* Set the default GPO/GPIO for legacy devices */ | ||
1824 | dev->reg_gpo_num = EM2880_R04_GPO; | ||
1825 | dev->reg_gpio_num = EM28XX_R08_GPIO; | ||
1826 | |||
1827 | dev->wait_after_write = 5; | ||
1828 | |||
1829 | /* Based on the Chip ID, set the device configuration */ | ||
1830 | rc = em28xx_read_reg(dev, EM28XX_R0A_CHIPID); | ||
1831 | if (rc > 0) { | ||
1832 | dev->chip_id = rc; | ||
1833 | |||
1834 | switch (dev->chip_id) { | ||
1835 | case CHIP_ID_EM2750: | ||
1836 | em28xx_info("chip ID is em2750\n"); | ||
1837 | break; | ||
1838 | case CHIP_ID_EM2820: | ||
1839 | em28xx_info("chip ID is em2710 or em2820\n"); | ||
1840 | break; | ||
1841 | case CHIP_ID_EM2840: | ||
1842 | em28xx_info("chip ID is em2840\n"); | ||
1843 | break; | ||
1844 | case CHIP_ID_EM2860: | ||
1845 | em28xx_info("chip ID is em2860\n"); | ||
1846 | break; | ||
1847 | case CHIP_ID_EM2870: | ||
1848 | em28xx_info("chip ID is em2870\n"); | ||
1849 | dev->wait_after_write = 0; | ||
1850 | break; | ||
1851 | case CHIP_ID_EM2874: | ||
1852 | em28xx_info("chip ID is em2874\n"); | ||
1853 | dev->reg_gpio_num = EM2874_R80_GPIO; | ||
1854 | dev->wait_after_write = 0; | ||
1855 | break; | ||
1856 | case CHIP_ID_EM2883: | ||
1857 | em28xx_info("chip ID is em2882/em2883\n"); | ||
1858 | dev->wait_after_write = 0; | ||
1859 | break; | ||
1860 | default: | ||
1861 | em28xx_info("em28xx chip ID = %d\n", dev->chip_id); | ||
1862 | } | ||
1863 | } | ||
1864 | |||
1865 | /* Prepopulate cached GPO register content */ | ||
1866 | rc = em28xx_read_reg(dev, dev->reg_gpo_num); | ||
1867 | if (rc >= 0) | ||
1868 | dev->reg_gpo = rc; | ||
1869 | |||
1870 | /* Set the initial XCLK and I2C clock values based on the board | 1877 | /* Set the initial XCLK and I2C clock values based on the board |
1871 | definition */ | 1878 | definition */ |
1872 | em28xx_write_reg(dev, EM28XX_R0F_XCLK, dev->board.xclk & 0x7f); | 1879 | em28xx_write_reg(dev, EM28XX_R0F_XCLK, dev->board.xclk & 0x7f); |
@@ -1876,9 +1883,8 @@ void em28xx_pre_card_setup(struct em28xx *dev) | |||
1876 | /* request some modules */ | 1883 | /* request some modules */ |
1877 | switch (dev->model) { | 1884 | switch (dev->model) { |
1878 | case EM2861_BOARD_PLEXTOR_PX_TV100U: | 1885 | case EM2861_BOARD_PLEXTOR_PX_TV100U: |
1879 | /* FIXME guess */ | 1886 | /* Sets the msp34xx I2S speed */ |
1880 | /* Turn on analog audio output */ | 1887 | dev->i2s_speed = 2048000; |
1881 | em28xx_write_reg(dev, EM28XX_R08_GPIO, 0xfd); | ||
1882 | break; | 1888 | break; |
1883 | case EM2861_BOARD_KWORLD_PVRTV_300U: | 1889 | case EM2861_BOARD_KWORLD_PVRTV_300U: |
1884 | case EM2880_BOARD_KWORLD_DVB_305U: | 1890 | case EM2880_BOARD_KWORLD_DVB_305U: |
@@ -2216,7 +2222,20 @@ void em28xx_register_i2c_ir(struct em28xx *dev) | |||
2216 | 2222 | ||
2217 | void em28xx_card_setup(struct em28xx *dev) | 2223 | void em28xx_card_setup(struct em28xx *dev) |
2218 | { | 2224 | { |
2219 | em28xx_set_model(dev); | 2225 | /* |
2226 | * If the device can be a webcam, seek for a sensor. | ||
2227 | * If sensor is not found, then it isn't a webcam. | ||
2228 | */ | ||
2229 | if (dev->board.is_webcam) { | ||
2230 | if (em28xx_hint_sensor(dev) < 0) | ||
2231 | dev->board.is_webcam = 0; | ||
2232 | else | ||
2233 | dev->progressive = 1; | ||
2234 | } else | ||
2235 | em28xx_set_model(dev); | ||
2236 | |||
2237 | em28xx_info("Identified as %s (card=%d)\n", | ||
2238 | dev->board.name, dev->model); | ||
2220 | 2239 | ||
2221 | dev->tuner_type = em28xx_boards[dev->model].tuner_type; | 2240 | dev->tuner_type = em28xx_boards[dev->model].tuner_type; |
2222 | if (em28xx_boards[dev->model].tuner_addr) | 2241 | if (em28xx_boards[dev->model].tuner_addr) |
@@ -2290,10 +2309,6 @@ void em28xx_card_setup(struct em28xx *dev) | |||
2290 | em28xx_gpio_set(dev, dev->board.tuner_gpio); | 2309 | em28xx_gpio_set(dev, dev->board.tuner_gpio); |
2291 | em28xx_set_mode(dev, EM28XX_ANALOG_MODE); | 2310 | em28xx_set_mode(dev, EM28XX_ANALOG_MODE); |
2292 | break; | 2311 | break; |
2293 | case EM2820_BOARD_SILVERCREST_WEBCAM: | ||
2294 | /* FIXME: need to document the registers bellow */ | ||
2295 | em28xx_write_reg(dev, 0x0d, 0x42); | ||
2296 | em28xx_write_reg(dev, 0x13, 0x08); | ||
2297 | } | 2312 | } |
2298 | 2313 | ||
2299 | if (dev->board.has_snapshot_button) | 2314 | if (dev->board.has_snapshot_button) |
@@ -2367,7 +2382,9 @@ void em28xx_card_setup(struct em28xx *dev) | |||
2367 | } | 2382 | } |
2368 | 2383 | ||
2369 | em28xx_tuner_setup(dev); | 2384 | em28xx_tuner_setup(dev); |
2370 | em28xx_ir_init(dev); | 2385 | |
2386 | if(!disable_ir) | ||
2387 | em28xx_ir_init(dev); | ||
2371 | } | 2388 | } |
2372 | 2389 | ||
2373 | 2390 | ||
@@ -2433,7 +2450,7 @@ static int em28xx_init_dev(struct em28xx **devhandle, struct usb_device *udev, | |||
2433 | int minor) | 2450 | int minor) |
2434 | { | 2451 | { |
2435 | struct em28xx *dev = *devhandle; | 2452 | struct em28xx *dev = *devhandle; |
2436 | int retval = -ENOMEM; | 2453 | int retval; |
2437 | int errCode; | 2454 | int errCode; |
2438 | 2455 | ||
2439 | dev->udev = udev; | 2456 | dev->udev = udev; |
@@ -2450,6 +2467,58 @@ static int em28xx_init_dev(struct em28xx **devhandle, struct usb_device *udev, | |||
2450 | dev->em28xx_read_reg_req = em28xx_read_reg_req; | 2467 | dev->em28xx_read_reg_req = em28xx_read_reg_req; |
2451 | dev->board.is_em2800 = em28xx_boards[dev->model].is_em2800; | 2468 | dev->board.is_em2800 = em28xx_boards[dev->model].is_em2800; |
2452 | 2469 | ||
2470 | em28xx_set_model(dev); | ||
2471 | |||
2472 | /* Set the default GPO/GPIO for legacy devices */ | ||
2473 | dev->reg_gpo_num = EM2880_R04_GPO; | ||
2474 | dev->reg_gpio_num = EM28XX_R08_GPIO; | ||
2475 | |||
2476 | dev->wait_after_write = 5; | ||
2477 | |||
2478 | /* Based on the Chip ID, set the device configuration */ | ||
2479 | retval = em28xx_read_reg(dev, EM28XX_R0A_CHIPID); | ||
2480 | if (retval > 0) { | ||
2481 | dev->chip_id = retval; | ||
2482 | |||
2483 | switch (dev->chip_id) { | ||
2484 | case CHIP_ID_EM2710: | ||
2485 | em28xx_info("chip ID is em2710\n"); | ||
2486 | break; | ||
2487 | case CHIP_ID_EM2750: | ||
2488 | em28xx_info("chip ID is em2750\n"); | ||
2489 | break; | ||
2490 | case CHIP_ID_EM2820: | ||
2491 | em28xx_info("chip ID is em2820 (or em2710)\n"); | ||
2492 | break; | ||
2493 | case CHIP_ID_EM2840: | ||
2494 | em28xx_info("chip ID is em2840\n"); | ||
2495 | break; | ||
2496 | case CHIP_ID_EM2860: | ||
2497 | em28xx_info("chip ID is em2860\n"); | ||
2498 | break; | ||
2499 | case CHIP_ID_EM2870: | ||
2500 | em28xx_info("chip ID is em2870\n"); | ||
2501 | dev->wait_after_write = 0; | ||
2502 | break; | ||
2503 | case CHIP_ID_EM2874: | ||
2504 | em28xx_info("chip ID is em2874\n"); | ||
2505 | dev->reg_gpio_num = EM2874_R80_GPIO; | ||
2506 | dev->wait_after_write = 0; | ||
2507 | break; | ||
2508 | case CHIP_ID_EM2883: | ||
2509 | em28xx_info("chip ID is em2882/em2883\n"); | ||
2510 | dev->wait_after_write = 0; | ||
2511 | break; | ||
2512 | default: | ||
2513 | em28xx_info("em28xx chip ID = %d\n", dev->chip_id); | ||
2514 | } | ||
2515 | } | ||
2516 | |||
2517 | /* Prepopulate cached GPO register content */ | ||
2518 | retval = em28xx_read_reg(dev, dev->reg_gpo_num); | ||
2519 | if (retval >= 0) | ||
2520 | dev->reg_gpo = retval; | ||
2521 | |||
2453 | em28xx_pre_card_setup(dev); | 2522 | em28xx_pre_card_setup(dev); |
2454 | 2523 | ||
2455 | if (!dev->board.is_em2800) { | 2524 | if (!dev->board.is_em2800) { |
@@ -2484,14 +2553,6 @@ static int em28xx_init_dev(struct em28xx **devhandle, struct usb_device *udev, | |||
2484 | dev->vinmode = 0x10; | 2553 | dev->vinmode = 0x10; |
2485 | dev->vinctl = 0x11; | 2554 | dev->vinctl = 0x11; |
2486 | 2555 | ||
2487 | /* | ||
2488 | * If the device can be a webcam, seek for a sensor. | ||
2489 | * If sensor is not found, then it isn't a webcam. | ||
2490 | */ | ||
2491 | if (dev->board.is_webcam) | ||
2492 | if (em28xx_hint_sensor(dev) < 0) | ||
2493 | dev->board.is_webcam = 0; | ||
2494 | |||
2495 | /* Do board specific init and eeprom reading */ | 2556 | /* Do board specific init and eeprom reading */ |
2496 | em28xx_card_setup(dev); | 2557 | em28xx_card_setup(dev); |
2497 | 2558 | ||