diff --git a/drivers/usb/typec/ucsi/ucsi_glink.c b/drivers/usb/typec/ucsi/ucsi_glink.c index 3f49e9d69051..2cff884324bc 100644 --- a/drivers/usb/typec/ucsi/ucsi_glink.c +++ b/drivers/usb/typec/ucsi/ucsi_glink.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2019-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2019-2021, The Linux Foundation. All rights reserved. */ #define pr_fmt(fmt) "UCSI: %s: " fmt, __func__ @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -67,6 +68,11 @@ struct ucsi_notify_ind_msg { u32 reserved; }; +struct constat_info_entry { + struct list_head node; + struct ucsi_glink_constat_info constat_info; +}; + struct ucsi_dev { struct device *dev; struct ucsi *ucsi; @@ -77,11 +83,12 @@ struct ucsi_dev { struct mutex read_lock; struct mutex write_lock; struct mutex notify_lock; + struct mutex state_lock; struct ucsi_read_buf_resp_msg rx_buf; unsigned long flags; atomic_t rx_valid; unsigned long cmd_requested_flags; - struct ucsi_glink_constat_info constat_info; + struct list_head constat_info_list; struct work_struct notify_work; struct work_struct setup_work; atomic_t state; @@ -201,6 +208,19 @@ static int handle_ucsi_notify(struct ucsi_dev *udev, void *data, size_t len) return -EINVAL; } + mutex_lock(&udev->state_lock); + if (atomic_read(&udev->state) == PMIC_GLINK_STATE_DOWN) { + pr_err("glink state is down\n"); + mutex_unlock(&udev->state_lock); + return -ENOTCONN; + } + + if (!udev->ucsi) { + pr_err("ucsi is NULL\n"); + mutex_unlock(&udev->state_lock); + return -ENODEV; + } + msg_ptr = data; cci = msg_ptr->notification; ucsi_log("notify:", UCSI_CCI, (u8 *)&cci, sizeof(cci)); @@ -221,6 +241,7 @@ static int handle_ucsi_notify(struct ucsi_dev *udev, void *data, size_t len) if (con && con->ucsi) ucsi_connector_change(udev->ucsi, con_num); } + mutex_unlock(&udev->state_lock); return 0; } @@ -285,6 +306,9 @@ static int ucsi_qti_glink_write(struct ucsi_dev *udev, unsigned int offset, reinit_completion(&udev->sync_write_ack); } + ucsi_log(sync ? "sync_write:" : "async_write:", offset, + (u8 *)val, val_len); + rc = pmic_glink_write(udev->client, &ucsi_buf, sizeof(ucsi_buf)); if (rc < 0) { @@ -314,9 +338,6 @@ static int ucsi_qti_glink_write(struct ucsi_dev *udev, unsigned int offset, } } - ucsi_log(sync ? "sync_write:" : "async_write:", offset, - (u8 *)val, val_len); - if (((u8 *)val)[0] == UCSI_GET_CONNECTOR_STATUS) { mutex_lock(&udev->notify_lock); set_bit(CONN_STAT_REQD, &udev->cmd_requested_flags); @@ -348,12 +369,43 @@ static int ucsi_qti_sync_write(struct ucsi *ucsi, unsigned int offset, return ucsi_qti_glink_write(udev, offset, val, val_len, true); } +static void ucsi_qti_clean_notification(struct ucsi_dev *udev) +{ + struct constat_info_entry *entry, *tmp; + + mutex_lock(&udev->notify_lock); + list_for_each_entry_safe(entry, tmp, &udev->constat_info_list, node) { + list_del(&entry->node); + kfree(entry); + } + INIT_LIST_HEAD(&udev->constat_info_list); + mutex_unlock(&udev->notify_lock); +} + static void ucsi_qti_notify_work(struct work_struct *work) { struct ucsi_dev *udev = container_of(work, struct ucsi_dev, notify_work); + struct constat_info_entry *entry; - raw_notifier_call_chain(&ucsi_glink_notifier, 0, &udev->constat_info); + mutex_lock(&udev->notify_lock); + while (!list_empty(&udev->constat_info_list)) { + entry = list_first_entry(&udev->constat_info_list, + struct constat_info_entry, node); + list_del(&entry->node); + mutex_unlock(&udev->notify_lock); + pr_debug("acc: %d usb: %d alt_mode: %d change: %d connect: %d\n", + entry->constat_info.acc, + entry->constat_info.partner_usb, + entry->constat_info.partner_alternate_mode, + entry->constat_info.partner_change, + entry->constat_info.connect); + raw_notifier_call_chain(&ucsi_glink_notifier, + 0, &entry->constat_info); + kfree(entry); + mutex_lock(&udev->notify_lock); + } + mutex_unlock(&udev->notify_lock); } static void ucsi_qti_notify(struct ucsi_dev *udev, unsigned int offset, @@ -361,6 +413,7 @@ static void ucsi_qti_notify(struct ucsi_dev *udev, unsigned int offset, { u8 conn_partner_type, conn_partner_flag; bool cmd_requested; + struct constat_info_entry *entry; if (len != sizeof(*status)) return; @@ -370,39 +423,49 @@ static void ucsi_qti_notify(struct ucsi_dev *udev, unsigned int offset, mutex_unlock(&udev->notify_lock); if (cmd_requested && offset == UCSI_MESSAGE_IN) { - cancel_work_sync(&udev->notify_work); + entry = kzalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) + return; - udev->constat_info.partner_usb = false; - udev->constat_info.partner_alternate_mode = false; + INIT_LIST_HEAD(&entry->node); + entry->constat_info.partner_usb = false; + entry->constat_info.partner_alternate_mode = false; + + entry->constat_info.partner_change = + status->change & UCSI_CONSTAT_PARTNER_CHANGE; + + entry->constat_info.connect = + status->flags & UCSI_CONSTAT_CONNECTED; conn_partner_type = UCSI_CONSTAT_PARTNER_TYPE(status->flags); switch (conn_partner_type) { case UCSI_CONSTAT_PARTNER_TYPE_AUDIO: - udev->constat_info.acc = TYPEC_ACCESSORY_AUDIO; + entry->constat_info.acc = TYPEC_ACCESSORY_AUDIO; break; case UCSI_CONSTAT_PARTNER_TYPE_DEBUG: - udev->constat_info.acc = TYPEC_ACCESSORY_DEBUG; + entry->constat_info.acc = TYPEC_ACCESSORY_DEBUG; break; case UCSI_CONSTAT_PARTNER_TYPE_UFP: case UCSI_CONSTAT_PARTNER_TYPE_CABLE: case UCSI_CONSTAT_PARTNER_TYPE_CABLE_AND_UFP: case UCSI_CONSTAT_PARTNER_TYPE_DFP: - udev->constat_info.partner_usb = true; + entry->constat_info.partner_usb = true; fallthrough; default: - udev->constat_info.acc = TYPEC_ACCESSORY_NONE; + entry->constat_info.acc = TYPEC_ACCESSORY_NONE; break; } conn_partner_flag = UCSI_CONSTAT_PARTNER_FLAGS(status->flags); if (conn_partner_flag & UCSI_CONSTAT_PARTNER_FLAG_USB) - udev->constat_info.partner_usb = true; + entry->constat_info.partner_usb = true; if (conn_partner_flag & UCSI_CONSTAT_PARTNER_FLAG_ALT_MODE) - udev->constat_info.partner_alternate_mode = true; + entry->constat_info.partner_alternate_mode = true; mutex_lock(&udev->notify_lock); + list_add_tail(&entry->node, &udev->constat_info_list); clear_bit(CONN_STAT_REQD, &udev->cmd_requested_flags); mutex_unlock(&udev->notify_lock); @@ -479,11 +542,13 @@ static int ucsi_setup(struct ucsi_dev *udev) return -EINVAL; } + mutex_lock(&udev->state_lock); udev->ucsi = ucsi_create(udev->dev, &ucsi_qti_ops); if (IS_ERR(udev->ucsi)) { rc = PTR_ERR(udev->ucsi); dev_err(udev->dev, "ucsi_create failed rc=%d\n", rc); udev->ucsi = NULL; + mutex_unlock(&udev->state_lock); return rc; } @@ -494,9 +559,11 @@ static int ucsi_setup(struct ucsi_dev *udev) dev_err(udev->dev, "ucsi_register failed rc=%d\n", rc); ucsi_destroy(udev->ucsi); udev->ucsi = NULL; + mutex_unlock(&udev->state_lock); return rc; } + mutex_unlock(&udev->state_lock); return 0; } @@ -514,15 +581,18 @@ static void ucsi_qti_state_cb(void *priv, enum pmic_glink_state state) dev_dbg(udev->dev, "state: %d\n", state); + mutex_lock(&udev->state_lock); atomic_set(&udev->state, state); switch (state) { case PMIC_GLINK_STATE_DOWN: if (!udev->ucsi) { - dev_err(udev->dev, "ucsi is NULL\n"); + dev_dbg(udev->dev, "ucsi is NULL\n"); + mutex_unlock(&udev->state_lock); return; } + ucsi_qti_clean_notification(udev); ucsi_unregister(udev->ucsi); ucsi_destroy(udev->ucsi); udev->ucsi = NULL; @@ -533,6 +603,7 @@ static void ucsi_qti_state_cb(void *priv, enum pmic_glink_state state) default: break; } + mutex_unlock(&udev->state_lock); } static int ucsi_probe(struct platform_device *pdev) @@ -546,11 +617,13 @@ static int ucsi_probe(struct platform_device *pdev) if (!udev) return -ENOMEM; + INIT_LIST_HEAD(&udev->constat_info_list); INIT_WORK(&udev->notify_work, ucsi_qti_notify_work); INIT_WORK(&udev->setup_work, ucsi_qti_setup_work); mutex_init(&udev->read_lock); mutex_init(&udev->write_lock); mutex_init(&udev->notify_lock); + mutex_init(&udev->state_lock); init_completion(&udev->read_ack); init_completion(&udev->write_ack); init_completion(&udev->sync_write_ack); @@ -595,6 +668,7 @@ static int ucsi_remove(struct platform_device *pdev) struct ucsi_dev *udev = dev_get_drvdata(dev); int rc; + ucsi_qti_clean_notification(udev); cancel_work_sync(&udev->notify_work); ucsi_unregister(udev->ucsi); ucsi_destroy(udev->ucsi); diff --git a/include/linux/usb/ucsi_glink.h b/include/linux/usb/ucsi_glink.h index 891ba957bb7b..bdd9d1d86412 100644 --- a/include/linux/usb/ucsi_glink.h +++ b/include/linux/usb/ucsi_glink.h @@ -13,6 +13,8 @@ struct ucsi_glink_constat_info { enum typec_accessory acc; bool partner_usb; bool partner_alternate_mode; + bool partner_change; + bool connect; }; struct notifier_block;