diff options
Diffstat (limited to 'drivers/usb/atm/ueagle-atm.c')
| -rw-r--r-- | drivers/usb/atm/ueagle-atm.c | 96 |
1 files changed, 55 insertions, 41 deletions
diff --git a/drivers/usb/atm/ueagle-atm.c b/drivers/usb/atm/ueagle-atm.c index 7d2a679989ed..830d2c982670 100644 --- a/drivers/usb/atm/ueagle-atm.c +++ b/drivers/usb/atm/ueagle-atm.c | |||
| @@ -63,11 +63,12 @@ | |||
| 63 | #include <linux/ctype.h> | 63 | #include <linux/ctype.h> |
| 64 | #include <linux/kthread.h> | 64 | #include <linux/kthread.h> |
| 65 | #include <linux/version.h> | 65 | #include <linux/version.h> |
| 66 | #include <linux/mutex.h> | ||
| 66 | #include <asm/unaligned.h> | 67 | #include <asm/unaligned.h> |
| 67 | 68 | ||
| 68 | #include "usbatm.h" | 69 | #include "usbatm.h" |
| 69 | 70 | ||
| 70 | #define EAGLEUSBVERSION "ueagle 1.1" | 71 | #define EAGLEUSBVERSION "ueagle 1.2" |
| 71 | 72 | ||
| 72 | 73 | ||
| 73 | /* | 74 | /* |
| @@ -358,16 +359,19 @@ struct intr_pkt { | |||
| 358 | #define INTR_PKT_SIZE 28 | 359 | #define INTR_PKT_SIZE 28 |
| 359 | 360 | ||
| 360 | static struct usb_driver uea_driver; | 361 | static struct usb_driver uea_driver; |
| 361 | static DECLARE_MUTEX(uea_semaphore); | 362 | static DEFINE_MUTEX(uea_mutex); |
| 362 | static const char *chip_name[] = {"ADI930", "Eagle I", "Eagle II", "Eagle III"}; | 363 | static const char *chip_name[] = {"ADI930", "Eagle I", "Eagle II", "Eagle III"}; |
| 363 | 364 | ||
| 364 | static int modem_index; | 365 | static int modem_index; |
| 365 | static unsigned int debug; | 366 | static unsigned int debug; |
| 367 | static int use_iso[NB_MODEM] = {[0 ... (NB_MODEM - 1)] = 1}; | ||
| 366 | static int sync_wait[NB_MODEM]; | 368 | static int sync_wait[NB_MODEM]; |
| 367 | static char *cmv_file[NB_MODEM]; | 369 | static char *cmv_file[NB_MODEM]; |
| 368 | 370 | ||
| 369 | module_param(debug, uint, 0644); | 371 | module_param(debug, uint, 0644); |
| 370 | MODULE_PARM_DESC(debug, "module debug level (0=off,1=on,2=verbose)"); | 372 | MODULE_PARM_DESC(debug, "module debug level (0=off,1=on,2=verbose)"); |
| 373 | module_param_array(use_iso, bool, NULL, 0644); | ||
| 374 | MODULE_PARM_DESC(use_iso, "use isochronous usb pipe for incoming traffic"); | ||
| 371 | module_param_array(sync_wait, bool, NULL, 0644); | 375 | module_param_array(sync_wait, bool, NULL, 0644); |
| 372 | MODULE_PARM_DESC(sync_wait, "wait the synchronisation before starting ATM"); | 376 | MODULE_PARM_DESC(sync_wait, "wait the synchronisation before starting ATM"); |
| 373 | module_param_array(cmv_file, charp, NULL, 0644); | 377 | module_param_array(cmv_file, charp, NULL, 0644); |
| @@ -628,8 +632,7 @@ static int request_dsp(struct uea_softc *sc) | |||
| 628 | dsp_name = FW_DIR "DSPep.bin"; | 632 | dsp_name = FW_DIR "DSPep.bin"; |
| 629 | } | 633 | } |
| 630 | 634 | ||
| 631 | ret = request_firmware(&sc->dsp_firm, | 635 | ret = request_firmware(&sc->dsp_firm, dsp_name, &sc->usb_dev->dev); |
| 632 | dsp_name, &sc->usb_dev->dev); | ||
| 633 | if (ret < 0) { | 636 | if (ret < 0) { |
| 634 | uea_err(INS_TO_USBDEV(sc), | 637 | uea_err(INS_TO_USBDEV(sc), |
| 635 | "requesting firmware %s failed with error %d\n", | 638 | "requesting firmware %s failed with error %d\n", |
| @@ -744,7 +747,6 @@ static inline int wait_cmv_ack(struct uea_softc *sc) | |||
| 744 | return ret; | 747 | return ret; |
| 745 | 748 | ||
| 746 | return (ret == 0) ? -ETIMEDOUT : 0; | 749 | return (ret == 0) ? -ETIMEDOUT : 0; |
| 747 | |||
| 748 | } | 750 | } |
| 749 | 751 | ||
| 750 | #define UCDC_SEND_ENCAPSULATED_COMMAND 0x00 | 752 | #define UCDC_SEND_ENCAPSULATED_COMMAND 0x00 |
| @@ -935,6 +937,7 @@ static int uea_stat(struct uea_softc *sc) | |||
| 935 | * ADI930 don't support it (-EPIPE error). | 937 | * ADI930 don't support it (-EPIPE error). |
| 936 | */ | 938 | */ |
| 937 | if (UEA_CHIP_VERSION(sc) != ADI930 | 939 | if (UEA_CHIP_VERSION(sc) != ADI930 |
| 940 | && !use_iso[sc->modem_index] | ||
| 938 | && sc->stats.phy.dsrate != (data >> 16) * 32) { | 941 | && sc->stats.phy.dsrate != (data >> 16) * 32) { |
| 939 | /* Original timming from ADI(used in windows driver) | 942 | /* Original timming from ADI(used in windows driver) |
| 940 | * 0x20ffff>>16 * 32 = 32 * 32 = 1Mbits | 943 | * 0x20ffff>>16 * 32 = 32 * 32 = 1Mbits |
| @@ -1010,7 +1013,7 @@ static int request_cmvs(struct uea_softc *sc, | |||
| 1010 | int ret, size; | 1013 | int ret, size; |
| 1011 | u8 *data; | 1014 | u8 *data; |
| 1012 | char *file; | 1015 | char *file; |
| 1013 | static char cmv_name[256] = FW_DIR; | 1016 | char cmv_name[FIRMWARE_NAME_MAX]; /* 30 bytes stack variable */ |
| 1014 | 1017 | ||
| 1015 | if (cmv_file[sc->modem_index] == NULL) { | 1018 | if (cmv_file[sc->modem_index] == NULL) { |
| 1016 | if (UEA_CHIP_VERSION(sc) == ADI930) | 1019 | if (UEA_CHIP_VERSION(sc) == ADI930) |
| @@ -1184,8 +1187,7 @@ static int load_XILINX_firmware(struct uea_softc *sc) | |||
| 1184 | } | 1187 | } |
| 1185 | } | 1188 | } |
| 1186 | 1189 | ||
| 1187 | /* finish to send the fpga | 1190 | /* finish to send the fpga */ |
| 1188 | */ | ||
| 1189 | ret = uea_request(sc, 0xe, 1, 0, NULL); | 1191 | ret = uea_request(sc, 0xe, 1, 0, NULL); |
| 1190 | if (ret < 0) { | 1192 | if (ret < 0) { |
| 1191 | uea_err(INS_TO_USBDEV(sc), | 1193 | uea_err(INS_TO_USBDEV(sc), |
| @@ -1193,9 +1195,7 @@ static int load_XILINX_firmware(struct uea_softc *sc) | |||
| 1193 | goto err1; | 1195 | goto err1; |
| 1194 | } | 1196 | } |
| 1195 | 1197 | ||
| 1196 | /* | 1198 | /* Tell the modem we finish : de-assert reset */ |
| 1197 | * Tell the modem we finish : de-assert reset | ||
| 1198 | */ | ||
| 1199 | value = 0; | 1199 | value = 0; |
| 1200 | ret = uea_send_modem_cmd(sc->usb_dev, 0xe, 1, &value); | 1200 | ret = uea_send_modem_cmd(sc->usb_dev, 0xe, 1, &value); |
| 1201 | if (ret < 0) | 1201 | if (ret < 0) |
| @@ -1209,6 +1209,7 @@ err0: | |||
| 1209 | return ret; | 1209 | return ret; |
| 1210 | } | 1210 | } |
| 1211 | 1211 | ||
| 1212 | /* The modem send us an ack. First with check if it right */ | ||
| 1212 | static void uea_dispatch_cmv(struct uea_softc *sc, struct cmv* cmv) | 1213 | static void uea_dispatch_cmv(struct uea_softc *sc, struct cmv* cmv) |
| 1213 | { | 1214 | { |
| 1214 | uea_enters(INS_TO_USBDEV(sc)); | 1215 | uea_enters(INS_TO_USBDEV(sc)); |
| @@ -1268,23 +1269,19 @@ bad1: | |||
| 1268 | */ | 1269 | */ |
| 1269 | static void uea_intr(struct urb *urb, struct pt_regs *regs) | 1270 | static void uea_intr(struct urb *urb, struct pt_regs *regs) |
| 1270 | { | 1271 | { |
| 1271 | struct uea_softc *sc = (struct uea_softc *)urb->context; | 1272 | struct uea_softc *sc = urb->context; |
| 1272 | struct intr_pkt *intr; | 1273 | struct intr_pkt *intr = urb->transfer_buffer; |
| 1273 | uea_enters(INS_TO_USBDEV(sc)); | 1274 | uea_enters(INS_TO_USBDEV(sc)); |
| 1274 | 1275 | ||
| 1275 | if (urb->status < 0) { | 1276 | if (unlikely(urb->status < 0)) { |
| 1276 | uea_err(INS_TO_USBDEV(sc), "uea_intr() failed with %d\n", | 1277 | uea_err(INS_TO_USBDEV(sc), "uea_intr() failed with %d\n", |
| 1277 | urb->status); | 1278 | urb->status); |
| 1278 | return; | 1279 | return; |
| 1279 | } | 1280 | } |
| 1280 | 1281 | ||
| 1281 | intr = (struct intr_pkt *) urb->transfer_buffer; | ||
| 1282 | |||
| 1283 | /* device-to-host interrupt */ | 1282 | /* device-to-host interrupt */ |
| 1284 | if (intr->bType != 0x08 || sc->booting) { | 1283 | if (intr->bType != 0x08 || sc->booting) { |
| 1285 | uea_err(INS_TO_USBDEV(sc), "wrong intr\n"); | 1284 | uea_err(INS_TO_USBDEV(sc), "wrong interrupt\n"); |
| 1286 | // rebooting ? | ||
| 1287 | // sc->reset = 1; | ||
| 1288 | goto resubmit; | 1285 | goto resubmit; |
| 1289 | } | 1286 | } |
| 1290 | 1287 | ||
| @@ -1300,7 +1297,7 @@ static void uea_intr(struct urb *urb, struct pt_regs *regs) | |||
| 1300 | break; | 1297 | break; |
| 1301 | 1298 | ||
| 1302 | default: | 1299 | default: |
| 1303 | uea_err(INS_TO_USBDEV(sc), "unknown intr %u\n", | 1300 | uea_err(INS_TO_USBDEV(sc), "unknown interrupt %u\n", |
| 1304 | le16_to_cpu(intr->wInterrupt)); | 1301 | le16_to_cpu(intr->wInterrupt)); |
| 1305 | } | 1302 | } |
| 1306 | 1303 | ||
| @@ -1379,7 +1376,7 @@ static void uea_stop(struct uea_softc *sc) | |||
| 1379 | int ret; | 1376 | int ret; |
| 1380 | uea_enters(INS_TO_USBDEV(sc)); | 1377 | uea_enters(INS_TO_USBDEV(sc)); |
| 1381 | ret = kthread_stop(sc->kthread); | 1378 | ret = kthread_stop(sc->kthread); |
| 1382 | uea_info(INS_TO_USBDEV(sc), "kthread finish with status %d\n", ret); | 1379 | uea_dbg(INS_TO_USBDEV(sc), "kthread finish with status %d\n", ret); |
| 1383 | 1380 | ||
| 1384 | /* stop any pending boot process */ | 1381 | /* stop any pending boot process */ |
| 1385 | flush_scheduled_work(); | 1382 | flush_scheduled_work(); |
| @@ -1418,13 +1415,13 @@ static ssize_t read_status(struct device *dev, struct device_attribute *attr, | |||
| 1418 | int ret = -ENODEV; | 1415 | int ret = -ENODEV; |
| 1419 | struct uea_softc *sc; | 1416 | struct uea_softc *sc; |
| 1420 | 1417 | ||
| 1421 | down(&uea_semaphore); | 1418 | mutex_lock(&uea_mutex); |
| 1422 | sc = dev_to_uea(dev); | 1419 | sc = dev_to_uea(dev); |
| 1423 | if (!sc) | 1420 | if (!sc) |
| 1424 | goto out; | 1421 | goto out; |
| 1425 | ret = snprintf(buf, 10, "%08x\n", sc->stats.phy.state); | 1422 | ret = snprintf(buf, 10, "%08x\n", sc->stats.phy.state); |
| 1426 | out: | 1423 | out: |
| 1427 | up(&uea_semaphore); | 1424 | mutex_unlock(&uea_mutex); |
| 1428 | return ret; | 1425 | return ret; |
| 1429 | } | 1426 | } |
| 1430 | 1427 | ||
| @@ -1434,14 +1431,14 @@ static ssize_t reboot(struct device *dev, struct device_attribute *attr, | |||
| 1434 | int ret = -ENODEV; | 1431 | int ret = -ENODEV; |
| 1435 | struct uea_softc *sc; | 1432 | struct uea_softc *sc; |
| 1436 | 1433 | ||
| 1437 | down(&uea_semaphore); | 1434 | mutex_lock(&uea_mutex); |
| 1438 | sc = dev_to_uea(dev); | 1435 | sc = dev_to_uea(dev); |
| 1439 | if (!sc) | 1436 | if (!sc) |
| 1440 | goto out; | 1437 | goto out; |
| 1441 | sc->reset = 1; | 1438 | sc->reset = 1; |
| 1442 | ret = count; | 1439 | ret = count; |
| 1443 | out: | 1440 | out: |
| 1444 | up(&uea_semaphore); | 1441 | mutex_unlock(&uea_mutex); |
| 1445 | return ret; | 1442 | return ret; |
| 1446 | } | 1443 | } |
| 1447 | 1444 | ||
| @@ -1453,7 +1450,7 @@ static ssize_t read_human_status(struct device *dev, struct device_attribute *at | |||
| 1453 | int ret = -ENODEV; | 1450 | int ret = -ENODEV; |
| 1454 | struct uea_softc *sc; | 1451 | struct uea_softc *sc; |
| 1455 | 1452 | ||
| 1456 | down(&uea_semaphore); | 1453 | mutex_lock(&uea_mutex); |
| 1457 | sc = dev_to_uea(dev); | 1454 | sc = dev_to_uea(dev); |
| 1458 | if (!sc) | 1455 | if (!sc) |
| 1459 | goto out; | 1456 | goto out; |
| @@ -1473,7 +1470,7 @@ static ssize_t read_human_status(struct device *dev, struct device_attribute *at | |||
| 1473 | break; | 1470 | break; |
| 1474 | } | 1471 | } |
| 1475 | out: | 1472 | out: |
| 1476 | up(&uea_semaphore); | 1473 | mutex_unlock(&uea_mutex); |
| 1477 | return ret; | 1474 | return ret; |
| 1478 | } | 1475 | } |
| 1479 | 1476 | ||
| @@ -1485,7 +1482,7 @@ static ssize_t read_delin(struct device *dev, struct device_attribute *attr, | |||
| 1485 | int ret = -ENODEV; | 1482 | int ret = -ENODEV; |
| 1486 | struct uea_softc *sc; | 1483 | struct uea_softc *sc; |
| 1487 | 1484 | ||
| 1488 | down(&uea_semaphore); | 1485 | mutex_lock(&uea_mutex); |
| 1489 | sc = dev_to_uea(dev); | 1486 | sc = dev_to_uea(dev); |
| 1490 | if (!sc) | 1487 | if (!sc) |
| 1491 | goto out; | 1488 | goto out; |
| @@ -1497,7 +1494,7 @@ static ssize_t read_delin(struct device *dev, struct device_attribute *attr, | |||
| 1497 | else | 1494 | else |
| 1498 | ret = sprintf(buf, "GOOD\n"); | 1495 | ret = sprintf(buf, "GOOD\n"); |
| 1499 | out: | 1496 | out: |
| 1500 | up(&uea_semaphore); | 1497 | mutex_unlock(&uea_mutex); |
| 1501 | return ret; | 1498 | return ret; |
| 1502 | } | 1499 | } |
| 1503 | 1500 | ||
| @@ -1511,7 +1508,7 @@ static ssize_t read_##name(struct device *dev, \ | |||
| 1511 | int ret = -ENODEV; \ | 1508 | int ret = -ENODEV; \ |
| 1512 | struct uea_softc *sc; \ | 1509 | struct uea_softc *sc; \ |
| 1513 | \ | 1510 | \ |
| 1514 | down(&uea_semaphore); \ | 1511 | mutex_lock(&uea_mutex); \ |
| 1515 | sc = dev_to_uea(dev); \ | 1512 | sc = dev_to_uea(dev); \ |
| 1516 | if (!sc) \ | 1513 | if (!sc) \ |
| 1517 | goto out; \ | 1514 | goto out; \ |
| @@ -1519,7 +1516,7 @@ static ssize_t read_##name(struct device *dev, \ | |||
| 1519 | if (reset) \ | 1516 | if (reset) \ |
| 1520 | sc->stats.phy.name = 0; \ | 1517 | sc->stats.phy.name = 0; \ |
| 1521 | out: \ | 1518 | out: \ |
| 1522 | up(&uea_semaphore); \ | 1519 | mutex_unlock(&uea_mutex); \ |
| 1523 | return ret; \ | 1520 | return ret; \ |
| 1524 | } \ | 1521 | } \ |
| 1525 | \ | 1522 | \ |
| @@ -1617,7 +1614,7 @@ static void create_fs_entries(struct uea_softc *sc, struct usb_interface *intf) | |||
| 1617 | } | 1614 | } |
| 1618 | 1615 | ||
| 1619 | static int uea_bind(struct usbatm_data *usbatm, struct usb_interface *intf, | 1616 | static int uea_bind(struct usbatm_data *usbatm, struct usb_interface *intf, |
| 1620 | const struct usb_device_id *id, int *heavy) | 1617 | const struct usb_device_id *id) |
| 1621 | { | 1618 | { |
| 1622 | struct usb_device *usb = interface_to_usbdev(intf); | 1619 | struct usb_device *usb = interface_to_usbdev(intf); |
| 1623 | struct uea_softc *sc; | 1620 | struct uea_softc *sc; |
| @@ -1629,16 +1626,14 @@ static int uea_bind(struct usbatm_data *usbatm, struct usb_interface *intf, | |||
| 1629 | if (ifnum != UEA_INTR_IFACE_NO) | 1626 | if (ifnum != UEA_INTR_IFACE_NO) |
| 1630 | return -ENODEV; | 1627 | return -ENODEV; |
| 1631 | 1628 | ||
| 1632 | *heavy = sync_wait[modem_index]; | 1629 | usbatm->flags = (sync_wait[modem_index] ? 0 : UDSL_SKIP_HEAVY_INIT); |
| 1633 | 1630 | ||
| 1634 | /* interface 1 is for outbound traffic */ | 1631 | /* interface 1 is for outbound traffic */ |
| 1635 | ret = claim_interface(usb, usbatm, UEA_US_IFACE_NO); | 1632 | ret = claim_interface(usb, usbatm, UEA_US_IFACE_NO); |
| 1636 | if (ret < 0) | 1633 | if (ret < 0) |
| 1637 | return ret; | 1634 | return ret; |
| 1638 | 1635 | ||
| 1639 | /* ADI930 has only 2 interfaces and inbound traffic | 1636 | /* ADI930 has only 2 interfaces and inbound traffic is on interface 1 */ |
| 1640 | * is on interface 1 | ||
| 1641 | */ | ||
| 1642 | if (UEA_CHIP_VERSION(id) != ADI930) { | 1637 | if (UEA_CHIP_VERSION(id) != ADI930) { |
| 1643 | /* interface 2 is for inbound traffic */ | 1638 | /* interface 2 is for inbound traffic */ |
| 1644 | ret = claim_interface(usb, usbatm, UEA_DS_IFACE_NO); | 1639 | ret = claim_interface(usb, usbatm, UEA_DS_IFACE_NO); |
| @@ -1658,6 +1653,25 @@ static int uea_bind(struct usbatm_data *usbatm, struct usb_interface *intf, | |||
| 1658 | sc->modem_index = (modem_index < NB_MODEM) ? modem_index++ : 0; | 1653 | sc->modem_index = (modem_index < NB_MODEM) ? modem_index++ : 0; |
| 1659 | sc->driver_info = id->driver_info; | 1654 | sc->driver_info = id->driver_info; |
| 1660 | 1655 | ||
| 1656 | /* ADI930 don't support iso */ | ||
| 1657 | if (UEA_CHIP_VERSION(id) != ADI930 && use_iso[sc->modem_index]) { | ||
| 1658 | int i; | ||
| 1659 | |||
| 1660 | /* try set fastest alternate for inbound traffic interface */ | ||
| 1661 | for (i = FASTEST_ISO_INTF; i > 0; i--) | ||
| 1662 | if (usb_set_interface(usb, UEA_DS_IFACE_NO, i) == 0) | ||
| 1663 | break; | ||
| 1664 | |||
| 1665 | if (i > 0) { | ||
| 1666 | uea_dbg(usb, "set alternate %d for 2 interface\n", i); | ||
| 1667 | uea_info(usb, "using iso mode\n"); | ||
| 1668 | usbatm->flags |= UDSL_USE_ISOC | UDSL_IGNORE_EILSEQ; | ||
| 1669 | } else { | ||
| 1670 | uea_err(usb, "setting any alternate failed for " | ||
| 1671 | "2 interface, using bulk mode\n"); | ||
| 1672 | } | ||
| 1673 | } | ||
| 1674 | |||
| 1661 | ret = uea_boot(sc); | 1675 | ret = uea_boot(sc); |
| 1662 | if (ret < 0) { | 1676 | if (ret < 0) { |
| 1663 | kfree(sc); | 1677 | kfree(sc); |
| @@ -1701,13 +1715,13 @@ static void uea_unbind(struct usbatm_data *usbatm, struct usb_interface *intf) | |||
| 1701 | 1715 | ||
| 1702 | static struct usbatm_driver uea_usbatm_driver = { | 1716 | static struct usbatm_driver uea_usbatm_driver = { |
| 1703 | .driver_name = "ueagle-atm", | 1717 | .driver_name = "ueagle-atm", |
| 1704 | .owner = THIS_MODULE, | ||
| 1705 | .bind = uea_bind, | 1718 | .bind = uea_bind, |
| 1706 | .atm_start = uea_atm_open, | 1719 | .atm_start = uea_atm_open, |
| 1707 | .unbind = uea_unbind, | 1720 | .unbind = uea_unbind, |
| 1708 | .heavy_init = uea_heavy, | 1721 | .heavy_init = uea_heavy, |
| 1709 | .in = UEA_BULK_DATA_PIPE, | 1722 | .bulk_in = UEA_BULK_DATA_PIPE, |
| 1710 | .out = UEA_BULK_DATA_PIPE, | 1723 | .bulk_out = UEA_BULK_DATA_PIPE, |
| 1724 | .isoc_in = UEA_ISO_DATA_PIPE, | ||
| 1711 | }; | 1725 | }; |
| 1712 | 1726 | ||
| 1713 | static int uea_probe(struct usb_interface *intf, const struct usb_device_id *id) | 1727 | static int uea_probe(struct usb_interface *intf, const struct usb_device_id *id) |
| @@ -1738,9 +1752,9 @@ static void uea_disconnect(struct usb_interface *intf) | |||
| 1738 | * Pre-firmware device has one interface | 1752 | * Pre-firmware device has one interface |
| 1739 | */ | 1753 | */ |
| 1740 | if (usb->config->desc.bNumInterfaces != 1 && ifnum == 0) { | 1754 | if (usb->config->desc.bNumInterfaces != 1 && ifnum == 0) { |
| 1741 | down(&uea_semaphore); | 1755 | mutex_lock(&uea_mutex); |
| 1742 | usbatm_usb_disconnect(intf); | 1756 | usbatm_usb_disconnect(intf); |
| 1743 | up(&uea_semaphore); | 1757 | mutex_unlock(&uea_mutex); |
| 1744 | uea_info(usb, "ADSL device removed\n"); | 1758 | uea_info(usb, "ADSL device removed\n"); |
| 1745 | } | 1759 | } |
| 1746 | 1760 | ||
