diff --git a/drivers/usb/typec/ucsi/ucsi_glink.c b/drivers/usb/typec/ucsi/ucsi_glink.c index f5e1fb524c38..aa5302d1eb84 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, The Linux Foundation. All rights reserved. + * Copyright (c) 2019-2020, The Linux Foundation. All rights reserved. */ #define pr_fmt(fmt) "UCSI: %s: " fmt, __func__ @@ -8,9 +8,12 @@ #include #include #include +#include #include #include #include +#include +#include #include "ucsi.h" @@ -74,12 +77,29 @@ struct ucsi_dev { struct completion sync_write_ack; struct mutex read_lock; struct mutex write_lock; + struct mutex notify_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 work_struct notify_work; }; static void *ucsi_ipc_log; +static RAW_NOTIFIER_HEAD(ucsi_glink_notifier); + +int register_ucsi_glink_notifier(struct notifier_block *nb) +{ + return raw_notifier_chain_register(&ucsi_glink_notifier, nb); +} +EXPORT_SYMBOL(register_ucsi_glink_notifier); + +int unregister_ucsi_glink_notifier(struct notifier_block *nb) +{ + return raw_notifier_chain_unregister(&ucsi_glink_notifier, nb); +} +EXPORT_SYMBOL(unregister_ucsi_glink_notifier); static char *offset_to_name(unsigned int offset) { @@ -237,6 +257,7 @@ static bool validate_ucsi_msg(unsigned int offset, size_t len) return true; } +#define CONN_STAT_REQD 1 static int ucsi_qti_glink_write(struct ucsi_dev *udev, unsigned int offset, const void *val, size_t val_len, bool sync) { @@ -291,6 +312,13 @@ 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); + mutex_unlock(&udev->notify_lock); + } + out: if (sync) clear_bit(CMD_PENDING, &udev->flags); @@ -316,6 +344,47 @@ 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_notify_work(struct work_struct *work) +{ + struct ucsi_dev *udev = container_of(work, struct ucsi_dev, + notify_work); + + raw_notifier_call_chain(&ucsi_glink_notifier, 0, &udev->constat_info); + mutex_lock(&udev->notify_lock); + clear_bit(CONN_STAT_REQD, &udev->cmd_requested_flags); + mutex_unlock(&udev->notify_lock); +} + +static void ucsi_qti_notify(struct ucsi_dev *udev, unsigned int offset, + struct ucsi_connector_status *status) +{ + u8 conn_partner_type; + bool cmd_requested; + + mutex_lock(&udev->notify_lock); + cmd_requested = test_bit(CONN_STAT_REQD, &udev->cmd_requested_flags); + mutex_unlock(&udev->notify_lock); + + if (cmd_requested && offset == UCSI_MESSAGE_IN) { + cancel_work_sync(&udev->notify_work); + 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; + break; + case UCSI_CONSTAT_PARTNER_TYPE_DEBUG: + udev->constat_info.acc = TYPEC_ACCESSORY_DEBUG; + break; + default: + udev->constat_info.acc = TYPEC_ACCESSORY_NONE; + break; + } + + schedule_work(&udev->notify_work); + } +} + static int ucsi_qti_read(struct ucsi *ucsi, unsigned int offset, void *val, size_t val_len) { @@ -359,6 +428,7 @@ static int ucsi_qti_read(struct ucsi *ucsi, unsigned int offset, memcpy((u8 *)val, &udev->rx_buf.buf[offset], val_len); atomic_set(&udev->rx_valid, 0); ucsi_log("read:", offset, (u8 *)val, val_len); + ucsi_qti_notify(udev, offset, val); out: mutex_unlock(&udev->read_lock); @@ -383,8 +453,10 @@ static int ucsi_probe(struct platform_device *pdev) if (!udev) return -ENOMEM; + INIT_WORK(&udev->notify_work, ucsi_qti_notify_work); mutex_init(&udev->read_lock); mutex_init(&udev->write_lock); + mutex_init(&udev->notify_lock); init_completion(&udev->read_ack); init_completion(&udev->write_ack); init_completion(&udev->sync_write_ack); @@ -442,6 +514,7 @@ static int ucsi_remove(struct platform_device *pdev) struct ucsi_dev *udev = dev_get_drvdata(dev); int rc; + 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 new file mode 100644 index 000000000000..67d2c8afd6c2 --- /dev/null +++ b/include/linux/usb/ucsi_glink.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +#ifndef __UCSI_GLINK_H__ +#define __UCSI_GLINK_H__ + +#include +#include + +struct ucsi_glink_constat_info { + enum typec_accessory acc; +}; + +struct notifier_block; + +#if IS_ENABLED(CONFIG_UCSI_QTI_GLINK) + +int register_ucsi_glink_notifier(struct notifier_block *nb); +int unregister_ucsi_glink_notifier(struct notifier_block *nb); + +#else + +static inline int register_ucsi_glink_notifier(struct notifier_block *nb) +{ + return -ENODEV; +} + +static inline int unregister_ucsi_glink_notifier(struct notifier_block *nb) +{ + return -ENODEV; +} + +#endif +#endif