ucsi: ucsi_glink: Notify upon change in connector status

Add APIs to allow clients to register for (and unregister from)
receiving notifications whenever a response is received for a
GET_CONNECTOR_STATUS request. Currently the notifications contain only a
single field indicating whether or not a Debug Accessory or an Audio
Adapter Accessory is attached (parsed from the received response).

Change-Id: If1ef46f5f9e4b4fa93f32bfd06fd50c7e16f7874
Signed-off-by: Guru Das Srinagesh <gurus@codeaurora.org>
This commit is contained in:
Guru Das Srinagesh 2020-01-17 11:21:54 -08:00 committed by Subbaraman Narayanamurthy
parent eba564a773
commit 37e61f7dbc
2 changed files with 110 additions and 1 deletions

View File

@ -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 <linux/device.h>
#include <linux/ipc_logging.h>
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/soc/qcom/pmic_glink.h>
#include <linux/usb/typec.h>
#include <linux/usb/ucsi_glink.h>
#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);

View File

@ -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 <linux/errno.h>
#include <linux/usb/typec.h>
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