|
19 | 19 | #include <linux/slab.h>
|
20 | 20 | #include <linux/spinlock.h>
|
21 | 21 | #include <linux/usb/pd.h>
|
| 22 | +#include <linux/usb/pd_ado.h> |
22 | 23 | #include <linux/usb/pd_bdo.h>
|
| 24 | +#include <linux/usb/pd_ext_sdb.h> |
23 | 25 | #include <linux/usb/pd_vdo.h>
|
24 | 26 | #include <linux/usb/role.h>
|
25 | 27 | #include <linux/usb/tcpm.h>
|
|
114 | 116 | S(SNK_TRYWAIT_VBUS), \
|
115 | 117 | S(BIST_RX), \
|
116 | 118 | \
|
| 119 | + S(GET_STATUS_SEND), \ |
| 120 | + S(GET_STATUS_SEND_TIMEOUT), \ |
| 121 | + S(GET_PPS_STATUS_SEND), \ |
| 122 | + S(GET_PPS_STATUS_SEND_TIMEOUT), \ |
| 123 | + \ |
117 | 124 | S(ERROR_RECOVERY), \
|
118 | 125 | S(PORT_RESET), \
|
119 | 126 | S(PORT_RESET_WAIT_OFF)
|
@@ -144,6 +151,7 @@ enum pd_msg_request {
|
144 | 151 | PD_MSG_NONE = 0,
|
145 | 152 | PD_MSG_CTRL_REJECT,
|
146 | 153 | PD_MSG_CTRL_WAIT,
|
| 154 | + PD_MSG_CTRL_NOT_SUPP, |
147 | 155 | PD_MSG_DATA_SINK_CAP,
|
148 | 156 | PD_MSG_DATA_SOURCE_CAP,
|
149 | 157 | };
|
@@ -1411,10 +1419,42 @@ static int tcpm_validate_caps(struct tcpm_port *port, const u32 *pdo,
|
1411 | 1419 | /*
|
1412 | 1420 | * PD (data, control) command handling functions
|
1413 | 1421 | */
|
| 1422 | +static inline enum tcpm_state ready_state(struct tcpm_port *port) |
| 1423 | +{ |
| 1424 | + if (port->pwr_role == TYPEC_SOURCE) |
| 1425 | + return SRC_READY; |
| 1426 | + else |
| 1427 | + return SNK_READY; |
| 1428 | +} |
1414 | 1429 |
|
1415 | 1430 | static int tcpm_pd_send_control(struct tcpm_port *port,
|
1416 | 1431 | enum pd_ctrl_msg_type type);
|
1417 | 1432 |
|
| 1433 | +static void tcpm_handle_alert(struct tcpm_port *port, const __le32 *payload, |
| 1434 | + int cnt) |
| 1435 | +{ |
| 1436 | + u32 p0 = le32_to_cpu(payload[0]); |
| 1437 | + unsigned int type = usb_pd_ado_type(p0); |
| 1438 | + |
| 1439 | + if (!type) { |
| 1440 | + tcpm_log(port, "Alert message received with no type"); |
| 1441 | + return; |
| 1442 | + } |
| 1443 | + |
| 1444 | + /* Just handling non-battery alerts for now */ |
| 1445 | + if (!(type & USB_PD_ADO_TYPE_BATT_STATUS_CHANGE)) { |
| 1446 | + switch (port->state) { |
| 1447 | + case SRC_READY: |
| 1448 | + case SNK_READY: |
| 1449 | + tcpm_set_state(port, GET_STATUS_SEND, 0); |
| 1450 | + break; |
| 1451 | + default: |
| 1452 | + tcpm_queue_message(port, PD_MSG_CTRL_WAIT); |
| 1453 | + break; |
| 1454 | + } |
| 1455 | + } |
| 1456 | +} |
| 1457 | + |
1418 | 1458 | static void tcpm_pd_data_request(struct tcpm_port *port,
|
1419 | 1459 | const struct pd_message *msg)
|
1420 | 1460 | {
|
@@ -1502,6 +1542,14 @@ static void tcpm_pd_data_request(struct tcpm_port *port,
|
1502 | 1542 | tcpm_set_state(port, BIST_RX, 0);
|
1503 | 1543 | }
|
1504 | 1544 | break;
|
| 1545 | + case PD_DATA_ALERT: |
| 1546 | + tcpm_handle_alert(port, msg->payload, cnt); |
| 1547 | + break; |
| 1548 | + case PD_DATA_BATT_STATUS: |
| 1549 | + case PD_DATA_GET_COUNTRY_INFO: |
| 1550 | + /* Currently unsupported */ |
| 1551 | + tcpm_queue_message(port, PD_MSG_CTRL_NOT_SUPP); |
| 1552 | + break; |
1505 | 1553 | default:
|
1506 | 1554 | tcpm_log(port, "Unhandled data message type %#x", type);
|
1507 | 1555 | break;
|
@@ -1584,6 +1632,7 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port,
|
1584 | 1632 | break;
|
1585 | 1633 | case PD_CTRL_REJECT:
|
1586 | 1634 | case PD_CTRL_WAIT:
|
| 1635 | + case PD_CTRL_NOT_SUPP: |
1587 | 1636 | switch (port->state) {
|
1588 | 1637 | case SNK_NEGOTIATE_CAPABILITIES:
|
1589 | 1638 | /* USB PD specification, Figure 8-43 */
|
@@ -1703,12 +1752,75 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port,
|
1703 | 1752 | break;
|
1704 | 1753 | }
|
1705 | 1754 | break;
|
| 1755 | + case PD_CTRL_GET_SOURCE_CAP_EXT: |
| 1756 | + case PD_CTRL_GET_STATUS: |
| 1757 | + case PD_CTRL_FR_SWAP: |
| 1758 | + case PD_CTRL_GET_PPS_STATUS: |
| 1759 | + case PD_CTRL_GET_COUNTRY_CODES: |
| 1760 | + /* Currently not supported */ |
| 1761 | + tcpm_queue_message(port, PD_MSG_CTRL_NOT_SUPP); |
| 1762 | + break; |
1706 | 1763 | default:
|
1707 | 1764 | tcpm_log(port, "Unhandled ctrl message type %#x", type);
|
1708 | 1765 | break;
|
1709 | 1766 | }
|
1710 | 1767 | }
|
1711 | 1768 |
|
| 1769 | +static void tcpm_pd_ext_msg_request(struct tcpm_port *port, |
| 1770 | + const struct pd_message *msg) |
| 1771 | +{ |
| 1772 | + enum pd_ext_msg_type type = pd_header_type_le(msg->header); |
| 1773 | + unsigned int data_size = pd_ext_header_data_size_le(msg->ext_msg.header); |
| 1774 | + |
| 1775 | + if (!(msg->ext_msg.header && PD_EXT_HDR_CHUNKED)) { |
| 1776 | + tcpm_log(port, "Unchunked extended messages unsupported"); |
| 1777 | + return; |
| 1778 | + } |
| 1779 | + |
| 1780 | + if (data_size > PD_EXT_MAX_CHUNK_DATA) { |
| 1781 | + tcpm_log(port, "Chunk handling not yet supported"); |
| 1782 | + return; |
| 1783 | + } |
| 1784 | + |
| 1785 | + switch (type) { |
| 1786 | + case PD_EXT_STATUS: |
| 1787 | + /* |
| 1788 | + * If PPS related events raised then get PPS status to clear |
| 1789 | + * (see USB PD 3.0 Spec, 6.5.2.4) |
| 1790 | + */ |
| 1791 | + if (msg->ext_msg.data[USB_PD_EXT_SDB_EVENT_FLAGS] & |
| 1792 | + USB_PD_EXT_SDB_PPS_EVENTS) |
| 1793 | + tcpm_set_state(port, GET_PPS_STATUS_SEND, 0); |
| 1794 | + else |
| 1795 | + tcpm_set_state(port, ready_state(port), 0); |
| 1796 | + break; |
| 1797 | + case PD_EXT_PPS_STATUS: |
| 1798 | + /* |
| 1799 | + * For now the PPS status message is used to clear events |
| 1800 | + * and nothing more. |
| 1801 | + */ |
| 1802 | + tcpm_set_state(port, ready_state(port), 0); |
| 1803 | + break; |
| 1804 | + case PD_EXT_SOURCE_CAP_EXT: |
| 1805 | + case PD_EXT_GET_BATT_CAP: |
| 1806 | + case PD_EXT_GET_BATT_STATUS: |
| 1807 | + case PD_EXT_BATT_CAP: |
| 1808 | + case PD_EXT_GET_MANUFACTURER_INFO: |
| 1809 | + case PD_EXT_MANUFACTURER_INFO: |
| 1810 | + case PD_EXT_SECURITY_REQUEST: |
| 1811 | + case PD_EXT_SECURITY_RESPONSE: |
| 1812 | + case PD_EXT_FW_UPDATE_REQUEST: |
| 1813 | + case PD_EXT_FW_UPDATE_RESPONSE: |
| 1814 | + case PD_EXT_COUNTRY_INFO: |
| 1815 | + case PD_EXT_COUNTRY_CODES: |
| 1816 | + tcpm_queue_message(port, PD_MSG_CTRL_NOT_SUPP); |
| 1817 | + break; |
| 1818 | + default: |
| 1819 | + tcpm_log(port, "Unhandled extended message type %#x", type); |
| 1820 | + break; |
| 1821 | + } |
| 1822 | +} |
| 1823 | + |
1712 | 1824 | static void tcpm_pd_rx_handler(struct work_struct *work)
|
1713 | 1825 | {
|
1714 | 1826 | struct pd_rx_event *event = container_of(work,
|
@@ -1749,7 +1861,9 @@ static void tcpm_pd_rx_handler(struct work_struct *work)
|
1749 | 1861 | "Data role mismatch, initiating error recovery");
|
1750 | 1862 | tcpm_set_state(port, ERROR_RECOVERY, 0);
|
1751 | 1863 | } else {
|
1752 |
| - if (cnt) |
| 1864 | + if (msg->header & PD_HEADER_EXT_HDR) |
| 1865 | + tcpm_pd_ext_msg_request(port, msg); |
| 1866 | + else if (cnt) |
1753 | 1867 | tcpm_pd_data_request(port, msg);
|
1754 | 1868 | else
|
1755 | 1869 | tcpm_pd_ctrl_request(port, msg);
|
@@ -1810,6 +1924,9 @@ static bool tcpm_send_queued_message(struct tcpm_port *port)
|
1810 | 1924 | case PD_MSG_CTRL_REJECT:
|
1811 | 1925 | tcpm_pd_send_control(port, PD_CTRL_REJECT);
|
1812 | 1926 | break;
|
| 1927 | + case PD_MSG_CTRL_NOT_SUPP: |
| 1928 | + tcpm_pd_send_control(port, PD_CTRL_NOT_SUPP); |
| 1929 | + break; |
1813 | 1930 | case PD_MSG_DATA_SINK_CAP:
|
1814 | 1931 | tcpm_pd_send_sink_caps(port);
|
1815 | 1932 | break;
|
@@ -2572,14 +2689,6 @@ static inline enum tcpm_state hard_reset_state(struct tcpm_port *port)
|
2572 | 2689 | return SNK_UNATTACHED;
|
2573 | 2690 | }
|
2574 | 2691 |
|
2575 |
| -static inline enum tcpm_state ready_state(struct tcpm_port *port) |
2576 |
| -{ |
2577 |
| - if (port->pwr_role == TYPEC_SOURCE) |
2578 |
| - return SRC_READY; |
2579 |
| - else |
2580 |
| - return SNK_READY; |
2581 |
| -} |
2582 |
| - |
2583 | 2692 | static inline enum tcpm_state unattached_state(struct tcpm_port *port)
|
2584 | 2693 | {
|
2585 | 2694 | if (port->port_type == TYPEC_PORT_DRP) {
|
@@ -3279,6 +3388,22 @@ static void run_state_machine(struct tcpm_port *port)
|
3279 | 3388 | /* Always switch to unattached state */
|
3280 | 3389 | tcpm_set_state(port, unattached_state(port), 0);
|
3281 | 3390 | break;
|
| 3391 | + case GET_STATUS_SEND: |
| 3392 | + tcpm_pd_send_control(port, PD_CTRL_GET_STATUS); |
| 3393 | + tcpm_set_state(port, GET_STATUS_SEND_TIMEOUT, |
| 3394 | + PD_T_SENDER_RESPONSE); |
| 3395 | + break; |
| 3396 | + case GET_STATUS_SEND_TIMEOUT: |
| 3397 | + tcpm_set_state(port, ready_state(port), 0); |
| 3398 | + break; |
| 3399 | + case GET_PPS_STATUS_SEND: |
| 3400 | + tcpm_pd_send_control(port, PD_CTRL_GET_PPS_STATUS); |
| 3401 | + tcpm_set_state(port, GET_PPS_STATUS_SEND_TIMEOUT, |
| 3402 | + PD_T_SENDER_RESPONSE); |
| 3403 | + break; |
| 3404 | + case GET_PPS_STATUS_SEND_TIMEOUT: |
| 3405 | + tcpm_set_state(port, ready_state(port), 0); |
| 3406 | + break; |
3282 | 3407 | case ERROR_RECOVERY:
|
3283 | 3408 | tcpm_swap_complete(port, -EPROTO);
|
3284 | 3409 | tcpm_pps_complete(port, -EPROTO);
|
|
0 commit comments