Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

usb: typec: tipd: Handle mode transitions for CD321x

On Apple Silicon machines there is no control over which alt mode is
chosen. The CD321x' firmware negotiates the target mode on its own and
only lets the main CPU know after the mode has already been chosen.
Especially after plugging a new cable in this can result to quick mode
changes from e.g. power only -> USB3 only -> USB3+DisplayPort in a short
time. It is not possile to influence this in any way and we also do not
get direct access to the PDOs or VDOs exchanged via USB PD.

Additionally, mode changes must be tightly synchronized between DWC3 and
the Type C PHY and most mode changes require a full reset of DWC3 to
make the port work correctly.
This is all done synchronously from the role switch handler inside the
DWC3 glue driver on these machines to avoid tripping any failsafes or
watchdogs inside the Type-C PHY that may, in the worst case, reset the
entire SoC.

To be able to control all this we trigger the entire process in the
correct order directly from the TIPD driver and de-bounce any mode
changes to avoid tearing down and re-setting DWC3 back up multiple times
any time a new connection is made.

Signed-off-by: Hector Martin <marcan@marcan.st>
Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Reviewed-by: Neal Gompa <neal@gompa.dev>
Co-developed-by: Sven Peter <sven@kernel.org>
Signed-off-by: Sven Peter <sven@kernel.org>
Reviewed-by: Janne Grunau <j@jannau.net>
Link: https://lore.kernel.org/r/20250914-apple-usb3-tipd-v1-11-4e99c8649024@kernel.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Hector Martin and committed by
Greg Kroah-Hartman
82432bbf 04041fd7

+286 -4
+286 -4
drivers/usb/typec/tipd/core.c
··· 17 17 #include <linux/usb/typec.h> 18 18 #include <linux/usb/typec_altmode.h> 19 19 #include <linux/usb/typec_dp.h> 20 + #include <linux/usb/typec_mux.h> 20 21 #include <linux/usb/typec_tbt.h> 21 22 #include <linux/usb/role.h> 22 23 #include <linux/workqueue.h> ··· 121 120 #define TPS_TASK_TIMEOUT 1 122 121 #define TPS_TASK_REJECTED 3 123 122 123 + /* Debounce delay for mode changes, in milliseconds */ 124 + #define CD321X_DEBOUNCE_DELAY_MS 500 125 + 124 126 enum { 125 127 TPS_MODE_APP, 126 128 TPS_MODE_BOOT, ··· 149 145 irq_handler_t irq_handler; 150 146 u64 irq_mask1; 151 147 size_t tps_struct_size; 148 + void (*remove)(struct tps6598x *tps); 152 149 int (*register_port)(struct tps6598x *tps, struct fwnode_handle *node); 153 150 void (*unregister_port)(struct tps6598x *tps); 154 151 void (*trace_data_status)(u32 status); ··· 160 155 int (*switch_power_state)(struct tps6598x *tps, u8 target_state); 161 156 bool (*read_data_status)(struct tps6598x *tps); 162 157 int (*reset)(struct tps6598x *tps); 158 + int (*connect)(struct tps6598x *tps, u32 status); 163 159 }; 164 160 165 161 struct tps6598x { ··· 189 183 const struct tipd_data *data; 190 184 }; 191 185 186 + struct cd321x_status { 187 + u32 status; 188 + u32 pwr_status; 189 + u32 data_status; 190 + u32 status_changed; 191 + struct usb_pd_identity partner_identity; 192 + struct tps6598x_dp_sid_status_reg dp_sid_status; 193 + struct tps6598x_intel_vid_status_reg intel_vid_status; 194 + struct tps6598x_usb4_status_reg usb4_status; 195 + }; 196 + 192 197 struct cd321x { 193 198 struct tps6598x tps; 194 199 ··· 209 192 210 193 struct typec_altmode *port_altmode_dp; 211 194 struct typec_altmode *port_altmode_tbt; 195 + 196 + struct typec_mux *mux; 197 + struct typec_mux_state state; 198 + 199 + struct cd321x_status update_status; 200 + struct delayed_work update_work; 201 + struct usb_pd_identity cur_partner_identity; 212 202 }; 213 203 214 204 static enum power_supply_property tps6598x_psy_props[] = { ··· 637 613 } 638 614 } 639 615 616 + static void cd321x_typec_update_mode(struct tps6598x *tps, struct cd321x_status *st) 617 + { 618 + struct cd321x *cd321x = container_of(tps, struct cd321x, tps); 619 + 620 + if (!(st->data_status & TPS_DATA_STATUS_DATA_CONNECTION)) { 621 + if (cd321x->state.mode == TYPEC_STATE_SAFE) 622 + return; 623 + cd321x->state.alt = NULL; 624 + cd321x->state.mode = TYPEC_STATE_SAFE; 625 + cd321x->state.data = NULL; 626 + typec_mux_set(cd321x->mux, &cd321x->state); 627 + } else if (st->data_status & TPS_DATA_STATUS_DP_CONNECTION) { 628 + struct typec_displayport_data dp_data; 629 + unsigned long mode; 630 + 631 + switch (TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT(st->data_status)) { 632 + case TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_A: 633 + mode = TYPEC_DP_STATE_A; 634 + break; 635 + case TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_B: 636 + mode = TYPEC_DP_STATE_B; 637 + break; 638 + case TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_C: 639 + mode = TYPEC_DP_STATE_C; 640 + break; 641 + case TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_D: 642 + mode = TYPEC_DP_STATE_D; 643 + break; 644 + case TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_E: 645 + mode = TYPEC_DP_STATE_E; 646 + break; 647 + case TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_F: 648 + mode = TYPEC_DP_STATE_F; 649 + break; 650 + default: 651 + dev_err(tps->dev, "Invalid DP pin assignment\n"); 652 + return; 653 + } 654 + 655 + if (cd321x->state.alt == cd321x->port_altmode_dp && 656 + cd321x->state.mode == mode) { 657 + return; 658 + } 659 + 660 + dp_data.status = le32_to_cpu(st->dp_sid_status.status_rx); 661 + dp_data.conf = le32_to_cpu(st->dp_sid_status.configure); 662 + cd321x->state.alt = cd321x->port_altmode_dp; 663 + cd321x->state.data = &dp_data; 664 + cd321x->state.mode = mode; 665 + typec_mux_set(cd321x->mux, &cd321x->state); 666 + } else if (st->data_status & TPS_DATA_STATUS_TBT_CONNECTION) { 667 + struct typec_thunderbolt_data tbt_data; 668 + 669 + if (cd321x->state.alt == cd321x->port_altmode_tbt && 670 + cd321x->state.mode == TYPEC_TBT_MODE) 671 + return; 672 + 673 + tbt_data.cable_mode = le16_to_cpu(st->intel_vid_status.cable_mode); 674 + tbt_data.device_mode = le16_to_cpu(st->intel_vid_status.device_mode); 675 + tbt_data.enter_vdo = le16_to_cpu(st->intel_vid_status.enter_vdo); 676 + cd321x->state.alt = cd321x->port_altmode_tbt; 677 + cd321x->state.mode = TYPEC_TBT_MODE; 678 + cd321x->state.data = &tbt_data; 679 + typec_mux_set(cd321x->mux, &cd321x->state); 680 + } else if (st->data_status & CD321X_DATA_STATUS_USB4_CONNECTION) { 681 + struct enter_usb_data eusb_data; 682 + 683 + if (cd321x->state.alt == NULL && cd321x->state.mode == TYPEC_MODE_USB4) 684 + return; 685 + 686 + eusb_data.eudo = le32_to_cpu(st->usb4_status.eudo); 687 + eusb_data.active_link_training = 688 + !!(st->data_status & TPS_DATA_STATUS_ACTIVE_LINK_TRAIN); 689 + 690 + cd321x->state.alt = NULL; 691 + cd321x->state.data = &eusb_data; 692 + cd321x->state.mode = TYPEC_MODE_USB4; 693 + typec_mux_set(cd321x->mux, &cd321x->state); 694 + } else { 695 + if (cd321x->state.alt == NULL && cd321x->state.mode == TYPEC_STATE_USB) 696 + return; 697 + cd321x->state.alt = NULL; 698 + cd321x->state.mode = TYPEC_STATE_USB; 699 + cd321x->state.data = NULL; 700 + typec_mux_set(cd321x->mux, &cd321x->state); 701 + } 702 + 703 + /* Clear data since it's no longer used after typec_mux_set and points to the stack */ 704 + cd321x->state.data = NULL; 705 + } 706 + 707 + static void cd321x_update_work(struct work_struct *work) 708 + { 709 + struct cd321x *cd321x = container_of(to_delayed_work(work), 710 + struct cd321x, update_work); 711 + struct tps6598x *tps = &cd321x->tps; 712 + struct cd321x_status st; 713 + 714 + guard(mutex)(&tps->lock); 715 + 716 + st = cd321x->update_status; 717 + cd321x->update_status.status_changed = 0; 718 + 719 + bool old_connected = !!tps->partner; 720 + bool new_connected = st.status & TPS_STATUS_PLUG_PRESENT; 721 + bool was_disconnected = st.status_changed & TPS_STATUS_PLUG_PRESENT; 722 + 723 + bool usb_connection = st.data_status & 724 + (TPS_DATA_STATUS_USB2_CONNECTION | TPS_DATA_STATUS_USB3_CONNECTION); 725 + 726 + enum usb_role old_role = usb_role_switch_get_role(tps->role_sw); 727 + enum usb_role new_role = USB_ROLE_NONE; 728 + enum typec_pwr_opmode pwr_opmode = TYPEC_PWR_MODE_USB; 729 + enum typec_orientation orientation = TYPEC_ORIENTATION_NONE; 730 + 731 + if (usb_connection) { 732 + if (tps->data_status & TPS_DATA_STATUS_USB_DATA_ROLE) 733 + new_role = USB_ROLE_DEVICE; 734 + else 735 + new_role = USB_ROLE_HOST; 736 + } 737 + 738 + if (new_connected) { 739 + pwr_opmode = TPS_POWER_STATUS_PWROPMODE(st.pwr_status); 740 + orientation = TPS_STATUS_TO_UPSIDE_DOWN(st.status) ? 741 + TYPEC_ORIENTATION_REVERSE : TYPEC_ORIENTATION_NORMAL; 742 + } 743 + 744 + bool is_pd = pwr_opmode == TYPEC_PWR_MODE_PD; 745 + bool partner_changed = old_connected && new_connected && 746 + (was_disconnected || 747 + (is_pd && memcmp(&st.partner_identity, 748 + &cd321x->cur_partner_identity, sizeof(struct usb_pd_identity)))); 749 + 750 + /* If we are switching from an active role, transition to USB_ROLE_NONE first */ 751 + if (old_role != USB_ROLE_NONE && (new_role != old_role || was_disconnected)) 752 + usb_role_switch_set_role(tps->role_sw, USB_ROLE_NONE); 753 + 754 + /* Process partner disconnection or change */ 755 + if (!new_connected || partner_changed) { 756 + if (!IS_ERR(tps->partner)) 757 + typec_unregister_partner(tps->partner); 758 + tps->partner = NULL; 759 + } 760 + 761 + /* If there was a disconnection, set PHY to off */ 762 + if (!new_connected || was_disconnected) { 763 + cd321x->state.alt = NULL; 764 + cd321x->state.mode = TYPEC_STATE_SAFE; 765 + cd321x->state.data = NULL; 766 + typec_set_mode(tps->port, TYPEC_STATE_SAFE); 767 + } 768 + 769 + /* Update Type-C properties */ 770 + typec_set_pwr_opmode(tps->port, pwr_opmode); 771 + typec_set_pwr_role(tps->port, TPS_STATUS_TO_TYPEC_PORTROLE(st.status)); 772 + typec_set_vconn_role(tps->port, TPS_STATUS_TO_TYPEC_VCONN(st.status)); 773 + typec_set_orientation(tps->port, orientation); 774 + typec_set_data_role(tps->port, TPS_STATUS_TO_TYPEC_DATAROLE(st.status)); 775 + power_supply_changed(tps->psy); 776 + 777 + /* If the plug is disconnected, we are done */ 778 + if (!new_connected) 779 + return; 780 + 781 + /* Set up partner if we were previously disconnected (or changed). */ 782 + if (!tps->partner) { 783 + struct typec_partner_desc desc; 784 + 785 + desc.usb_pd = is_pd; 786 + desc.accessory = TYPEC_ACCESSORY_NONE; /* XXX: handle accessories */ 787 + desc.identity = NULL; 788 + 789 + if (desc.usb_pd) 790 + desc.identity = &st.partner_identity; 791 + 792 + tps->partner = typec_register_partner(tps->port, &desc); 793 + if (IS_ERR(tps->partner)) 794 + dev_warn(tps->dev, "%s: failed to register partnet\n", __func__); 795 + 796 + if (desc.identity) { 797 + typec_partner_set_identity(tps->partner); 798 + cd321x->cur_partner_identity = st.partner_identity; 799 + } 800 + } 801 + 802 + /* Update the TypeC MUX/PHY state */ 803 + cd321x_typec_update_mode(tps, &st); 804 + 805 + /* Launch the USB role switch */ 806 + usb_role_switch_set_role(tps->role_sw, new_role); 807 + 808 + power_supply_changed(tps->psy); 809 + } 810 + 811 + static void cd321x_queue_status(struct cd321x *cd321x) 812 + { 813 + cd321x->update_status.status_changed |= cd321x->update_status.status ^ cd321x->tps.status; 814 + 815 + cd321x->update_status.status = cd321x->tps.status; 816 + cd321x->update_status.pwr_status = cd321x->tps.pwr_status; 817 + cd321x->update_status.data_status = cd321x->tps.data_status; 818 + 819 + cd321x->update_status.partner_identity = cd321x->tps.partner_identity; 820 + cd321x->update_status.dp_sid_status = cd321x->dp_sid_status; 821 + cd321x->update_status.intel_vid_status = cd321x->intel_vid_status; 822 + cd321x->update_status.usb4_status = cd321x->usb4_status; 823 + } 824 + 825 + static int cd321x_connect(struct tps6598x *tps, u32 status) 826 + { 827 + struct cd321x *cd321x = container_of(tps, struct cd321x, tps); 828 + 829 + tps->status = status; 830 + cd321x_queue_status(cd321x); 831 + 832 + /* 833 + * Cancel pending work if not already running, then requeue after CD321X_DEBOUNCE_DELAY_MS 834 + * regardless since the work function will check for any plug or altmodes changes since 835 + * its last run anyway. 836 + */ 837 + cancel_delayed_work(&cd321x->update_work); 838 + schedule_delayed_work(&cd321x->update_work, msecs_to_jiffies(CD321X_DEBOUNCE_DELAY_MS)); 839 + 840 + return 0; 841 + } 842 + 640 843 static irqreturn_t cd321x_interrupt(int irq, void *data) 641 844 { 642 845 struct tps6598x *tps = data; ··· 903 652 if (!tps->data->read_data_status(tps)) 904 653 goto err_unlock; 905 654 906 - /* Handle plug insert or removal */ 907 - if (event & APPLE_CD_REG_INT_PLUG_EVENT) 908 - tps6598x_handle_plug_event(tps, status); 655 + /* Can be called uncondtionally since it will check for any changes itself */ 656 + cd321x_connect(tps, status); 909 657 910 658 err_unlock: 911 659 mutex_unlock(&tps->lock); ··· 1264 1014 struct cd321x *cd321x = container_of(tps, struct cd321x, tps); 1265 1015 int ret; 1266 1016 1017 + INIT_DELAYED_WORK(&cd321x->update_work, cd321x_update_work); 1018 + 1267 1019 ret = tps6598x_register_port(tps, fwnode); 1268 1020 if (ret) 1269 1021 return ret; ··· 1274 1022 if (ret) 1275 1023 goto err_unregister_port; 1276 1024 1025 + cd321x->mux = fwnode_typec_mux_get(fwnode); 1026 + if (IS_ERR(cd321x->mux)) { 1027 + ret = PTR_ERR(cd321x->mux); 1028 + goto err_unregister_altmodes; 1029 + } 1030 + 1031 + cd321x->state.alt = NULL; 1032 + cd321x->state.mode = TYPEC_STATE_SAFE; 1033 + cd321x->state.data = NULL; 1277 1034 typec_set_mode(tps->port, TYPEC_STATE_SAFE); 1278 1035 1279 1036 return 0; 1280 1037 1038 + err_unregister_altmodes: 1039 + typec_unregister_altmode(cd321x->port_altmode_dp); 1040 + typec_unregister_altmode(cd321x->port_altmode_tbt); 1041 + cd321x->port_altmode_dp = NULL; 1042 + cd321x->port_altmode_tbt = NULL; 1281 1043 err_unregister_port: 1282 1044 typec_unregister_port(tps->port); 1283 1045 return ret; ··· 1308 1042 { 1309 1043 struct cd321x *cd321x = container_of(tps, struct cd321x, tps); 1310 1044 1045 + typec_mux_put(cd321x->mux); 1046 + cd321x->mux = NULL; 1311 1047 typec_unregister_altmode(cd321x->port_altmode_dp); 1312 1048 cd321x->port_altmode_dp = NULL; 1313 1049 typec_unregister_altmode(cd321x->port_altmode_tbt); ··· 1722 1454 return 0; 1723 1455 } 1724 1456 1457 + static void cd321x_remove(struct tps6598x *tps) 1458 + { 1459 + struct cd321x *cd321x = container_of(tps, struct cd321x, tps); 1460 + 1461 + cancel_delayed_work_sync(&cd321x->update_work); 1462 + } 1463 + 1725 1464 static int tps6598x_probe(struct i2c_client *client) 1726 1465 { 1727 1466 const struct tipd_data *data; ··· 1830 1555 goto err_unregister_port; 1831 1556 if (!tps->data->read_data_status(tps)) 1832 1557 goto err_unregister_port; 1833 - ret = tps6598x_connect(tps, status); 1558 + ret = tps->data->connect(tps, status); 1834 1559 if (ret) 1835 1560 dev_err(&client->dev, "failed to register partner\n"); 1836 1561 } ··· 1886 1611 cancel_delayed_work_sync(&tps->wq_poll); 1887 1612 else 1888 1613 devm_free_irq(tps->dev, client->irq, tps); 1614 + 1615 + if (tps->data->remove) 1616 + tps->data->remove(tps); 1889 1617 1890 1618 tps6598x_disconnect(tps, 0); 1891 1619 tps->data->unregister_port(tps); ··· 1960 1682 APPLE_CD_REG_INT_DATA_STATUS_UPDATE | 1961 1683 APPLE_CD_REG_INT_PLUG_EVENT, 1962 1684 .tps_struct_size = sizeof(struct cd321x), 1685 + .remove = cd321x_remove, 1963 1686 .register_port = cd321x_register_port, 1964 1687 .unregister_port = cd321x_unregister_port, 1965 1688 .trace_data_status = trace_cd321x_data_status, ··· 1970 1691 .read_data_status = cd321x_read_data_status, 1971 1692 .reset = cd321x_reset, 1972 1693 .switch_power_state = cd321x_switch_power_state, 1694 + .connect = cd321x_connect, 1973 1695 }; 1974 1696 1975 1697 static const struct tipd_data tps6598x_data = { ··· 1988 1708 .init = tps6598x_init, 1989 1709 .read_data_status = tps6598x_read_data_status, 1990 1710 .reset = tps6598x_reset, 1711 + .connect = tps6598x_connect, 1991 1712 }; 1992 1713 1993 1714 static const struct tipd_data tps25750_data = { ··· 2006 1725 .init = tps25750_init, 2007 1726 .read_data_status = tps6598x_read_data_status, 2008 1727 .reset = tps25750_reset, 1728 + .connect = tps6598x_connect, 2009 1729 }; 2010 1730 2011 1731 static const struct of_device_id tps6598x_of_match[] = {