diff options
Diffstat (limited to 'drivers/usb/typec/ucsi/ucsi.c')
-rw-r--r-- | drivers/usb/typec/ucsi/ucsi.c | 125 |
1 files changed, 105 insertions, 20 deletions
diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c index 51a570d40a42..f02958927cbd 100644 --- a/drivers/usb/typec/ucsi/ucsi.c +++ b/drivers/usb/typec/ucsi/ucsi.c | |||
@@ -53,7 +53,7 @@ static int ucsi_acknowledge_connector_change(struct ucsi *ucsi) | |||
53 | ctrl = UCSI_ACK_CC_CI; | 53 | ctrl = UCSI_ACK_CC_CI; |
54 | ctrl |= UCSI_ACK_CONNECTOR_CHANGE; | 54 | ctrl |= UCSI_ACK_CONNECTOR_CHANGE; |
55 | 55 | ||
56 | return ucsi->ops->async_write(ucsi, UCSI_CONTROL, &ctrl, sizeof(ctrl)); | 56 | return ucsi->ops->sync_write(ucsi, UCSI_CONTROL, &ctrl, sizeof(ctrl)); |
57 | } | 57 | } |
58 | 58 | ||
59 | static int ucsi_exec_command(struct ucsi *ucsi, u64 command); | 59 | static int ucsi_exec_command(struct ucsi *ucsi, u64 command); |
@@ -625,21 +625,113 @@ static void ucsi_handle_connector_change(struct work_struct *work) | |||
625 | struct ucsi_connector *con = container_of(work, struct ucsi_connector, | 625 | struct ucsi_connector *con = container_of(work, struct ucsi_connector, |
626 | work); | 626 | work); |
627 | struct ucsi *ucsi = con->ucsi; | 627 | struct ucsi *ucsi = con->ucsi; |
628 | struct ucsi_connector_status pre_ack_status; | ||
629 | struct ucsi_connector_status post_ack_status; | ||
628 | enum typec_role role; | 630 | enum typec_role role; |
631 | u16 inferred_changes; | ||
632 | u16 changed_flags; | ||
629 | u64 command; | 633 | u64 command; |
630 | int ret; | 634 | int ret; |
631 | 635 | ||
632 | mutex_lock(&con->lock); | 636 | mutex_lock(&con->lock); |
633 | 637 | ||
638 | /* | ||
639 | * Some/many PPMs have an issue where all fields in the change bitfield | ||
640 | * are cleared when an ACK is send. This will causes any change | ||
641 | * between GET_CONNECTOR_STATUS and ACK to be lost. | ||
642 | * | ||
643 | * We work around this by re-fetching the connector status afterwards. | ||
644 | * We then infer any changes that we see have happened but that may not | ||
645 | * be represented in the change bitfield. | ||
646 | * | ||
647 | * Also, even though we don't need to know the currently supported alt | ||
648 | * modes, we run the GET_CAM_SUPPORTED command to ensure the PPM does | ||
649 | * not get stuck in case it assumes we do. | ||
650 | * Always do this, rather than relying on UCSI_CONSTAT_CAM_CHANGE to be | ||
651 | * set in the change bitfield. | ||
652 | * | ||
653 | * We end up with the following actions: | ||
654 | * 1. UCSI_GET_CONNECTOR_STATUS, store result, update unprocessed_changes | ||
655 | * 2. UCSI_GET_CAM_SUPPORTED, discard result | ||
656 | * 3. ACK connector change | ||
657 | * 4. UCSI_GET_CONNECTOR_STATUS, store result | ||
658 | * 5. Infere lost changes by comparing UCSI_GET_CONNECTOR_STATUS results | ||
659 | * 6. If PPM reported a new change, then restart in order to ACK | ||
660 | * 7. Process everything as usual. | ||
661 | * | ||
662 | * We may end up seeing a change twice, but we can only miss extremely | ||
663 | * short transitional changes. | ||
664 | */ | ||
665 | |||
666 | /* 1. First UCSI_GET_CONNECTOR_STATUS */ | ||
667 | command = UCSI_GET_CONNECTOR_STATUS | UCSI_CONNECTOR_NUMBER(con->num); | ||
668 | ret = ucsi_send_command(ucsi, command, &pre_ack_status, | ||
669 | sizeof(pre_ack_status)); | ||
670 | if (ret < 0) { | ||
671 | dev_err(ucsi->dev, "%s: GET_CONNECTOR_STATUS failed (%d)\n", | ||
672 | __func__, ret); | ||
673 | goto out_unlock; | ||
674 | } | ||
675 | con->unprocessed_changes |= pre_ack_status.change; | ||
676 | |||
677 | /* 2. Run UCSI_GET_CAM_SUPPORTED and discard the result. */ | ||
678 | command = UCSI_GET_CAM_SUPPORTED; | ||
679 | command |= UCSI_CONNECTOR_NUMBER(con->num); | ||
680 | ucsi_send_command(con->ucsi, command, NULL, 0); | ||
681 | |||
682 | /* 3. ACK connector change */ | ||
683 | clear_bit(EVENT_PENDING, &ucsi->flags); | ||
684 | ret = ucsi_acknowledge_connector_change(ucsi); | ||
685 | if (ret) { | ||
686 | dev_err(ucsi->dev, "%s: ACK failed (%d)", __func__, ret); | ||
687 | goto out_unlock; | ||
688 | } | ||
689 | |||
690 | /* 4. Second UCSI_GET_CONNECTOR_STATUS */ | ||
634 | command = UCSI_GET_CONNECTOR_STATUS | UCSI_CONNECTOR_NUMBER(con->num); | 691 | command = UCSI_GET_CONNECTOR_STATUS | UCSI_CONNECTOR_NUMBER(con->num); |
635 | ret = ucsi_send_command(ucsi, command, &con->status, | 692 | ret = ucsi_send_command(ucsi, command, &post_ack_status, |
636 | sizeof(con->status)); | 693 | sizeof(post_ack_status)); |
637 | if (ret < 0) { | 694 | if (ret < 0) { |
638 | dev_err(ucsi->dev, "%s: GET_CONNECTOR_STATUS failed (%d)\n", | 695 | dev_err(ucsi->dev, "%s: GET_CONNECTOR_STATUS failed (%d)\n", |
639 | __func__, ret); | 696 | __func__, ret); |
640 | goto out_unlock; | 697 | goto out_unlock; |
641 | } | 698 | } |
642 | 699 | ||
700 | /* 5. Inferre any missing changes */ | ||
701 | changed_flags = pre_ack_status.flags ^ post_ack_status.flags; | ||
702 | inferred_changes = 0; | ||
703 | if (UCSI_CONSTAT_PWR_OPMODE(changed_flags) != 0) | ||
704 | inferred_changes |= UCSI_CONSTAT_POWER_OPMODE_CHANGE; | ||
705 | |||
706 | if (changed_flags & UCSI_CONSTAT_CONNECTED) | ||
707 | inferred_changes |= UCSI_CONSTAT_CONNECT_CHANGE; | ||
708 | |||
709 | if (changed_flags & UCSI_CONSTAT_PWR_DIR) | ||
710 | inferred_changes |= UCSI_CONSTAT_POWER_DIR_CHANGE; | ||
711 | |||
712 | if (UCSI_CONSTAT_PARTNER_FLAGS(changed_flags) != 0) | ||
713 | inferred_changes |= UCSI_CONSTAT_PARTNER_CHANGE; | ||
714 | |||
715 | if (UCSI_CONSTAT_PARTNER_TYPE(changed_flags) != 0) | ||
716 | inferred_changes |= UCSI_CONSTAT_PARTNER_CHANGE; | ||
717 | |||
718 | /* Mask out anything that was correctly notified in the later call. */ | ||
719 | inferred_changes &= ~post_ack_status.change; | ||
720 | if (inferred_changes) | ||
721 | dev_dbg(ucsi->dev, "%s: Inferred changes that would have been lost: 0x%04x\n", | ||
722 | __func__, inferred_changes); | ||
723 | |||
724 | con->unprocessed_changes |= inferred_changes; | ||
725 | |||
726 | /* 6. If PPM reported a new change, then restart in order to ACK */ | ||
727 | if (post_ack_status.change) | ||
728 | goto out_unlock; | ||
729 | |||
730 | /* 7. Continue as if nothing happened */ | ||
731 | con->status = post_ack_status; | ||
732 | con->status.change = con->unprocessed_changes; | ||
733 | con->unprocessed_changes = 0; | ||
734 | |||
643 | role = !!(con->status.flags & UCSI_CONSTAT_PWR_DIR); | 735 | role = !!(con->status.flags & UCSI_CONSTAT_PWR_DIR); |
644 | 736 | ||
645 | if (con->status.change & UCSI_CONSTAT_POWER_OPMODE_CHANGE || | 737 | if (con->status.change & UCSI_CONSTAT_POWER_OPMODE_CHANGE || |
@@ -680,28 +772,19 @@ static void ucsi_handle_connector_change(struct work_struct *work) | |||
680 | ucsi_port_psy_changed(con); | 772 | ucsi_port_psy_changed(con); |
681 | } | 773 | } |
682 | 774 | ||
683 | if (con->status.change & UCSI_CONSTAT_CAM_CHANGE) { | ||
684 | /* | ||
685 | * We don't need to know the currently supported alt modes here. | ||
686 | * Running GET_CAM_SUPPORTED command just to make sure the PPM | ||
687 | * does not get stuck in case it assumes we do so. | ||
688 | */ | ||
689 | command = UCSI_GET_CAM_SUPPORTED; | ||
690 | command |= UCSI_CONNECTOR_NUMBER(con->num); | ||
691 | ucsi_send_command(con->ucsi, command, NULL, 0); | ||
692 | } | ||
693 | |||
694 | if (con->status.change & UCSI_CONSTAT_PARTNER_CHANGE) | 775 | if (con->status.change & UCSI_CONSTAT_PARTNER_CHANGE) |
695 | ucsi_partner_change(con); | 776 | ucsi_partner_change(con); |
696 | 777 | ||
697 | ret = ucsi_acknowledge_connector_change(ucsi); | ||
698 | if (ret) | ||
699 | dev_err(ucsi->dev, "%s: ACK failed (%d)", __func__, ret); | ||
700 | |||
701 | trace_ucsi_connector_change(con->num, &con->status); | 778 | trace_ucsi_connector_change(con->num, &con->status); |
702 | 779 | ||
703 | out_unlock: | 780 | out_unlock: |
704 | clear_bit(EVENT_PENDING, &ucsi->flags); | 781 | if (test_and_clear_bit(EVENT_PENDING, &ucsi->flags)) { |
782 | schedule_work(&con->work); | ||
783 | mutex_unlock(&con->lock); | ||
784 | return; | ||
785 | } | ||
786 | |||
787 | clear_bit(EVENT_PROCESSING, &ucsi->flags); | ||
705 | mutex_unlock(&con->lock); | 788 | mutex_unlock(&con->lock); |
706 | } | 789 | } |
707 | 790 | ||
@@ -719,7 +802,9 @@ void ucsi_connector_change(struct ucsi *ucsi, u8 num) | |||
719 | return; | 802 | return; |
720 | } | 803 | } |
721 | 804 | ||
722 | if (!test_and_set_bit(EVENT_PENDING, &ucsi->flags)) | 805 | set_bit(EVENT_PENDING, &ucsi->flags); |
806 | |||
807 | if (!test_and_set_bit(EVENT_PROCESSING, &ucsi->flags)) | ||
723 | schedule_work(&con->work); | 808 | schedule_work(&con->work); |
724 | } | 809 | } |
725 | EXPORT_SYMBOL_GPL(ucsi_connector_change); | 810 | EXPORT_SYMBOL_GPL(ucsi_connector_change); |