Loading arch/arm64/configs/vendor/lahaina_GKI.config +3 −0 Original line number Diff line number Diff line Loading @@ -21,6 +21,9 @@ CONFIG_USB_DWC3=m CONFIG_USB_DWC3_QCOM=m CONFIG_USB_DWC3_MSM=m CONFIG_USB_CONFIGFS_F_DIAG=m CONFIG_TYPEC=m CONFIG_TYPEC_UCSI=m CONFIG_UCSI_QTI_GLINK=m CONFIG_INTERCONNECT_QCOM=m CONFIG_INTERCONNECT_QCOM_LAHAINA=m CONFIG_PHY_QCOM_UFS=m Loading drivers/usb/typec/ucsi/Kconfig +12 −0 Original line number Diff line number Diff line Loading @@ -47,4 +47,16 @@ config UCSI_ACPI To compile the driver as a module, choose M here: the module will be called ucsi_acpi config UCSI_QTI_GLINK tristate "UCSI Interface Driver for QTI PMIC" depends on QTI_PMIC_GLINK help This driver enables UCSI support on Qualcomm Technologies, Inc. platforms that expose UCSI interface to communicate with PPM which is charger firmware running on the remote subsystem (e.g. DSP) over PMIC Glink. To compile the driver as a module, choose M here: the module will be called ucsi_glink. endif drivers/usb/typec/ucsi/Makefile +1 −0 Original line number Diff line number Diff line Loading @@ -13,3 +13,4 @@ endif obj-$(CONFIG_UCSI_ACPI) += ucsi_acpi.o obj-$(CONFIG_UCSI_CCG) += ucsi_ccg.o obj-$(CONFIG_UCSI_QTI_GLINK) += ucsi_glink.o drivers/usb/typec/ucsi/ucsi_glink.c 0 → 100644 +476 −0 Original line number Diff line number Diff line // SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2019, The Linux Foundation. All rights reserved. */ #define pr_fmt(fmt) "UCSI: %s: " fmt, __func__ #include <linux/device.h> #include <linux/ipc_logging.h> #include <linux/module.h> #include <linux/of_platform.h> #include <linux/platform_device.h> #include <linux/soc/qcom/pmic_glink.h> #include "ucsi.h" /* PPM specific definitions */ #define MSG_OWNER_UC 32779 #define MSG_TYPE_REQ_RESP 1 #define UCSI_BUF_SIZE 48 #define UC_NOTIFY_RECEIVER_UCSI 0x0 #define UC_UCSI_READ_BUF_REQ 0x11 #define UC_UCSI_WRITE_BUF_REQ 0x12 #define UC_UCSI_USBC_NOTIFY_IND 0x13 /* Generic definitions */ #define CMD_PENDING 1 #define UCSI_LOG_BUF_SIZE 256 #define NUM_LOG_PAGES 10 #define UCSI_WAIT_TIME_MS 5000 #define ucsi_dbg(fmt, ...) \ do { \ ipc_log_string(ucsi_ipc_log, "%s: " fmt, __func__, \ ##__VA_ARGS__); \ pr_debug(fmt, ##__VA_ARGS__); \ } while (0) struct ucsi_read_buf_req_msg { struct pmic_glink_hdr hdr; }; struct ucsi_read_buf_resp_msg { struct pmic_glink_hdr hdr; u8 buf[UCSI_BUF_SIZE]; u32 ret_code; }; struct ucsi_write_buf_req_msg { struct pmic_glink_hdr hdr; u8 buf[UCSI_BUF_SIZE]; u32 reserved; }; struct ucsi_write_buf_resp_msg { struct pmic_glink_hdr hdr; u32 ret_code; }; struct ucsi_notify_ind_msg { struct pmic_glink_hdr hdr; u32 notification; u32 receiver; u32 reserved; }; struct ucsi_dev { struct device *dev; struct ucsi *ucsi; struct pmic_glink_client *client; struct completion read_ack; struct completion write_ack; struct completion sync_write_ack; struct mutex read_lock; struct mutex write_lock; struct ucsi_read_buf_resp_msg rx_buf; unsigned long flags; atomic_t rx_valid; }; static void *ucsi_ipc_log; static char *offset_to_name(unsigned int offset) { char *type; switch (offset) { case UCSI_VERSION: type = "VER:"; break; case UCSI_CCI: type = "CCI:"; break; case UCSI_CONTROL: type = "CONTROL:"; break; case UCSI_MESSAGE_IN: type = "MSG_IN:"; break; case UCSI_MESSAGE_OUT: type = "MSG_OUT:"; break; default: type = "UNKNOWN:"; break; } return type; } static void ucsi_log(const char *prefix, unsigned int offset, u8 *buf, size_t len) { char str[UCSI_LOG_BUF_SIZE] = { 0 }; u32 i, pos = 0; for (i = 0; i < len && pos < sizeof(str) - 1; i++) pos += scnprintf(str + pos, sizeof(str) - pos, "%02x ", buf[i]); str[pos] = '\0'; ucsi_dbg("%s %s %s\n", prefix, offset_to_name(offset), str); } static int handle_ucsi_read_ack(struct ucsi_dev *udev, void *data, size_t len) { if (len != sizeof(udev->rx_buf)) { pr_err("Incorrect received length %zu expected %u\n", len, sizeof(udev->rx_buf)); atomic_set(&udev->rx_valid, 0); return -EINVAL; } memcpy(&udev->rx_buf, data, sizeof(udev->rx_buf)); if (udev->rx_buf.ret_code) { pr_err("ret_code: %u\n", udev->rx_buf.ret_code); return -EINVAL; } pr_debug("read ack\n"); atomic_set(&udev->rx_valid, 1); complete(&udev->read_ack); return 0; } static int handle_ucsi_write_ack(struct ucsi_dev *udev, void *data, size_t len) { struct ucsi_write_buf_resp_msg *msg_ptr; if (len != sizeof(*msg_ptr)) { pr_err("Incorrect received length %zu expected %u\n", len, sizeof(*msg_ptr)); return -EINVAL; } msg_ptr = data; if (msg_ptr->ret_code) { pr_err("ret_code: %u\n", msg_ptr->ret_code); return -EINVAL; } pr_debug("write ack\n"); complete(&udev->write_ack); return 0; } static int handle_ucsi_notify(struct ucsi_dev *udev, void *data, size_t len) { struct ucsi_notify_ind_msg *msg_ptr; struct ucsi_connector *con; u32 cci; u8 con_num; if (len != sizeof(*msg_ptr)) { pr_err("Incorrect received length %zu expected %u\n", len, sizeof(*msg_ptr)); return -EINVAL; } msg_ptr = data; cci = msg_ptr->notification; ucsi_log("", UCSI_CCI, (u8 *)&cci, sizeof(cci)); if (test_bit(CMD_PENDING, &udev->flags) && cci & (UCSI_CCI_ACK_COMPLETE | UCSI_CCI_COMMAND_COMPLETE)) { pr_debug("received ack\n"); complete(&udev->sync_write_ack); } con_num = UCSI_CCI_CONNECTOR(cci); pr_debug("con_num: %u num_connectors: %u\n", con_num, udev->ucsi->cap.num_connectors); if (con_num && con_num <= udev->ucsi->cap.num_connectors && udev->ucsi->connector) { con = &udev->ucsi->connector[con_num - 1]; if (con && con->ucsi) ucsi_connector_change(udev->ucsi, con_num); } return 0; } static int ucsi_callback(void *priv, void *data, size_t len) { struct pmic_glink_hdr *hdr = data; struct ucsi_dev *udev = priv; pr_debug("owner: %u type: %u opcode: %u len:%zu\n", hdr->owner, hdr->type, hdr->opcode, len); if (hdr->opcode == UC_UCSI_READ_BUF_REQ) handle_ucsi_read_ack(udev, data, len); else if (hdr->opcode == UC_UCSI_WRITE_BUF_REQ) handle_ucsi_write_ack(udev, data, len); else if (hdr->opcode == UC_UCSI_USBC_NOTIFY_IND) handle_ucsi_notify(udev, data, len); else pr_err("Unknown message opcode: %d\n", hdr->opcode); return 0; } static bool validate_ucsi_msg(unsigned int offset, size_t len) { pr_debug("offset %u len %u\n", offset, len); if (offset > UCSI_BUF_SIZE - 1 || len > UCSI_BUF_SIZE || offset + len > UCSI_BUF_SIZE) { pr_err("Incorrect length %zu or offset %u\n", len, offset); return false; } return true; } static int ucsi_qti_glink_write(struct ucsi_dev *udev, unsigned int offset, const void *val, size_t val_len, bool sync) { struct ucsi_write_buf_req_msg ucsi_buf = { { 0 } }; int rc; if (!validate_ucsi_msg(offset, val_len)) return -EINVAL; ucsi_buf.hdr.owner = MSG_OWNER_UC; ucsi_buf.hdr.type = MSG_TYPE_REQ_RESP; ucsi_buf.hdr.opcode = UC_UCSI_WRITE_BUF_REQ; memcpy(&ucsi_buf.buf[offset], val, val_len); mutex_lock(&udev->write_lock); pr_debug("%s write\n", sync ? "sync" : "async"); reinit_completion(&udev->write_ack); if (sync) { set_bit(CMD_PENDING, &udev->flags); reinit_completion(&udev->sync_write_ack); } rc = pmic_glink_write(udev->client, &ucsi_buf, sizeof(ucsi_buf)); if (rc < 0) { pr_err("Error in sending message rc=%d\n", rc); goto out; } rc = wait_for_completion_timeout(&udev->write_ack, msecs_to_jiffies(UCSI_WAIT_TIME_MS)); if (!rc) { pr_err("timed out\n"); rc = -ETIMEDOUT; goto out; } else { rc = 0; } if (sync) { rc = wait_for_completion_timeout(&udev->sync_write_ack, msecs_to_jiffies(UCSI_WAIT_TIME_MS)); if (!rc) { pr_err("timed out for sync_write_ack\n"); rc = -ETIMEDOUT; goto out; } else { rc = 0; } } ucsi_log(sync ? "sync_write:" : "async_write:", offset, (u8 *)val, val_len); out: if (sync) clear_bit(CMD_PENDING, &udev->flags); mutex_unlock(&udev->write_lock); return rc; } static int ucsi_qti_async_write(struct ucsi *ucsi, unsigned int offset, const void *val, size_t val_len) { struct ucsi_dev *udev = ucsi_get_drvdata(ucsi); return ucsi_qti_glink_write(udev, offset, val, val_len, false); } static int ucsi_qti_sync_write(struct ucsi *ucsi, unsigned int offset, const void *val, size_t val_len) { struct ucsi_dev *udev = ucsi_get_drvdata(ucsi); return ucsi_qti_glink_write(udev, offset, val, val_len, true); } static int ucsi_qti_read(struct ucsi *ucsi, unsigned int offset, void *val, size_t val_len) { struct ucsi_dev *udev = ucsi_get_drvdata(ucsi); struct ucsi_read_buf_req_msg ucsi_buf = { { 0 } }; int rc; if (!validate_ucsi_msg(offset, val_len)) return -EINVAL; ucsi_buf.hdr.owner = MSG_OWNER_UC; ucsi_buf.hdr.type = MSG_TYPE_REQ_RESP; ucsi_buf.hdr.opcode = UC_UCSI_READ_BUF_REQ; mutex_lock(&udev->read_lock); pr_debug("read offset %s len %u\n", offset_to_name(offset), val_len); reinit_completion(&udev->read_ack); rc = pmic_glink_write(udev->client, &ucsi_buf, sizeof(ucsi_buf)); if (rc < 0) { pr_err("Error in sending message rc=%d\n", rc); goto out; } rc = wait_for_completion_timeout(&udev->read_ack, msecs_to_jiffies(UCSI_WAIT_TIME_MS)); if (!rc) { pr_err("timed out\n"); rc = -ETIMEDOUT; goto out; } else { rc = 0; } if (!atomic_read(&udev->rx_valid)) { rc = -ENODATA; goto out; } memcpy((u8 *)val, &udev->rx_buf.buf[offset], val_len); atomic_set(&udev->rx_valid, 0); ucsi_log("read:", offset, (u8 *)val, val_len); out: mutex_unlock(&udev->read_lock); return rc; } static const struct ucsi_operations ucsi_qti_ops = { .read = ucsi_qti_read, .sync_write = ucsi_qti_sync_write, .async_write = ucsi_qti_async_write }; static int ucsi_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct pmic_glink_client_data client_data; struct ucsi_dev *udev; int rc; udev = devm_kzalloc(dev, sizeof(*udev), GFP_KERNEL); if (!udev) return -ENOMEM; mutex_init(&udev->read_lock); mutex_init(&udev->write_lock); init_completion(&udev->read_ack); init_completion(&udev->write_ack); init_completion(&udev->sync_write_ack); atomic_set(&udev->rx_valid, 0); client_data.id = MSG_OWNER_UC; client_data.name = "ucsi"; client_data.callback = ucsi_callback; client_data.priv = udev; udev->client = pmic_glink_register_client(dev, &client_data); if (IS_ERR(udev->client)) { rc = PTR_ERR(udev->client); if (rc != -EPROBE_DEFER) dev_err(dev, "Error in registering with pmic_glink rc=%d\n", rc); return rc; } platform_set_drvdata(pdev, udev); udev->ucsi = ucsi_create(dev, &ucsi_qti_ops); if (IS_ERR(udev->ucsi)) { rc = PTR_ERR(udev->ucsi); dev_err(dev, "ucsi_create failed rc=%d\n", rc); return rc; } ucsi_set_drvdata(udev->ucsi, udev); ucsi_ipc_log = ipc_log_context_create(NUM_LOG_PAGES, "ucsi", 0); if (!ucsi_ipc_log) dev_warn(dev, "Error in creating ipc_log_context\n"); rc = ucsi_register(udev->ucsi); if (rc) { dev_err(dev, "ucsi_register failed rc=%d\n", rc); goto out; } pr_debug("driver probed\n"); return 0; out: ipc_log_context_destroy(ucsi_ipc_log); ucsi_ipc_log = NULL; ucsi_destroy(udev->ucsi); pmic_glink_unregister_client(udev->client); return rc; } static int ucsi_remove(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct ucsi_dev *udev = dev_get_drvdata(dev); int rc; ucsi_unregister(udev->ucsi); ucsi_destroy(udev->ucsi); rc = pmic_glink_unregister_client(udev->client); if (rc < 0) dev_err(dev, "pmic_glink_unregister_client failed rc=%d\n", rc); ipc_log_context_destroy(ucsi_ipc_log); ucsi_ipc_log = NULL; return rc; } static const struct of_device_id ucsi_match_table[] = { {.compatible = "qcom,ucsi-glink"}, {}, }; static struct platform_driver ucsi_driver = { .driver = { .name = "ucsi_glink", .of_match_table = ucsi_match_table, }, .probe = ucsi_probe, .remove = ucsi_remove, }; module_platform_driver(ucsi_driver); MODULE_DESCRIPTION("QTI UCSI Glink driver"); MODULE_LICENSE("GPL v2"); Loading
arch/arm64/configs/vendor/lahaina_GKI.config +3 −0 Original line number Diff line number Diff line Loading @@ -21,6 +21,9 @@ CONFIG_USB_DWC3=m CONFIG_USB_DWC3_QCOM=m CONFIG_USB_DWC3_MSM=m CONFIG_USB_CONFIGFS_F_DIAG=m CONFIG_TYPEC=m CONFIG_TYPEC_UCSI=m CONFIG_UCSI_QTI_GLINK=m CONFIG_INTERCONNECT_QCOM=m CONFIG_INTERCONNECT_QCOM_LAHAINA=m CONFIG_PHY_QCOM_UFS=m Loading
drivers/usb/typec/ucsi/Kconfig +12 −0 Original line number Diff line number Diff line Loading @@ -47,4 +47,16 @@ config UCSI_ACPI To compile the driver as a module, choose M here: the module will be called ucsi_acpi config UCSI_QTI_GLINK tristate "UCSI Interface Driver for QTI PMIC" depends on QTI_PMIC_GLINK help This driver enables UCSI support on Qualcomm Technologies, Inc. platforms that expose UCSI interface to communicate with PPM which is charger firmware running on the remote subsystem (e.g. DSP) over PMIC Glink. To compile the driver as a module, choose M here: the module will be called ucsi_glink. endif
drivers/usb/typec/ucsi/Makefile +1 −0 Original line number Diff line number Diff line Loading @@ -13,3 +13,4 @@ endif obj-$(CONFIG_UCSI_ACPI) += ucsi_acpi.o obj-$(CONFIG_UCSI_CCG) += ucsi_ccg.o obj-$(CONFIG_UCSI_QTI_GLINK) += ucsi_glink.o
drivers/usb/typec/ucsi/ucsi_glink.c 0 → 100644 +476 −0 Original line number Diff line number Diff line // SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2019, The Linux Foundation. All rights reserved. */ #define pr_fmt(fmt) "UCSI: %s: " fmt, __func__ #include <linux/device.h> #include <linux/ipc_logging.h> #include <linux/module.h> #include <linux/of_platform.h> #include <linux/platform_device.h> #include <linux/soc/qcom/pmic_glink.h> #include "ucsi.h" /* PPM specific definitions */ #define MSG_OWNER_UC 32779 #define MSG_TYPE_REQ_RESP 1 #define UCSI_BUF_SIZE 48 #define UC_NOTIFY_RECEIVER_UCSI 0x0 #define UC_UCSI_READ_BUF_REQ 0x11 #define UC_UCSI_WRITE_BUF_REQ 0x12 #define UC_UCSI_USBC_NOTIFY_IND 0x13 /* Generic definitions */ #define CMD_PENDING 1 #define UCSI_LOG_BUF_SIZE 256 #define NUM_LOG_PAGES 10 #define UCSI_WAIT_TIME_MS 5000 #define ucsi_dbg(fmt, ...) \ do { \ ipc_log_string(ucsi_ipc_log, "%s: " fmt, __func__, \ ##__VA_ARGS__); \ pr_debug(fmt, ##__VA_ARGS__); \ } while (0) struct ucsi_read_buf_req_msg { struct pmic_glink_hdr hdr; }; struct ucsi_read_buf_resp_msg { struct pmic_glink_hdr hdr; u8 buf[UCSI_BUF_SIZE]; u32 ret_code; }; struct ucsi_write_buf_req_msg { struct pmic_glink_hdr hdr; u8 buf[UCSI_BUF_SIZE]; u32 reserved; }; struct ucsi_write_buf_resp_msg { struct pmic_glink_hdr hdr; u32 ret_code; }; struct ucsi_notify_ind_msg { struct pmic_glink_hdr hdr; u32 notification; u32 receiver; u32 reserved; }; struct ucsi_dev { struct device *dev; struct ucsi *ucsi; struct pmic_glink_client *client; struct completion read_ack; struct completion write_ack; struct completion sync_write_ack; struct mutex read_lock; struct mutex write_lock; struct ucsi_read_buf_resp_msg rx_buf; unsigned long flags; atomic_t rx_valid; }; static void *ucsi_ipc_log; static char *offset_to_name(unsigned int offset) { char *type; switch (offset) { case UCSI_VERSION: type = "VER:"; break; case UCSI_CCI: type = "CCI:"; break; case UCSI_CONTROL: type = "CONTROL:"; break; case UCSI_MESSAGE_IN: type = "MSG_IN:"; break; case UCSI_MESSAGE_OUT: type = "MSG_OUT:"; break; default: type = "UNKNOWN:"; break; } return type; } static void ucsi_log(const char *prefix, unsigned int offset, u8 *buf, size_t len) { char str[UCSI_LOG_BUF_SIZE] = { 0 }; u32 i, pos = 0; for (i = 0; i < len && pos < sizeof(str) - 1; i++) pos += scnprintf(str + pos, sizeof(str) - pos, "%02x ", buf[i]); str[pos] = '\0'; ucsi_dbg("%s %s %s\n", prefix, offset_to_name(offset), str); } static int handle_ucsi_read_ack(struct ucsi_dev *udev, void *data, size_t len) { if (len != sizeof(udev->rx_buf)) { pr_err("Incorrect received length %zu expected %u\n", len, sizeof(udev->rx_buf)); atomic_set(&udev->rx_valid, 0); return -EINVAL; } memcpy(&udev->rx_buf, data, sizeof(udev->rx_buf)); if (udev->rx_buf.ret_code) { pr_err("ret_code: %u\n", udev->rx_buf.ret_code); return -EINVAL; } pr_debug("read ack\n"); atomic_set(&udev->rx_valid, 1); complete(&udev->read_ack); return 0; } static int handle_ucsi_write_ack(struct ucsi_dev *udev, void *data, size_t len) { struct ucsi_write_buf_resp_msg *msg_ptr; if (len != sizeof(*msg_ptr)) { pr_err("Incorrect received length %zu expected %u\n", len, sizeof(*msg_ptr)); return -EINVAL; } msg_ptr = data; if (msg_ptr->ret_code) { pr_err("ret_code: %u\n", msg_ptr->ret_code); return -EINVAL; } pr_debug("write ack\n"); complete(&udev->write_ack); return 0; } static int handle_ucsi_notify(struct ucsi_dev *udev, void *data, size_t len) { struct ucsi_notify_ind_msg *msg_ptr; struct ucsi_connector *con; u32 cci; u8 con_num; if (len != sizeof(*msg_ptr)) { pr_err("Incorrect received length %zu expected %u\n", len, sizeof(*msg_ptr)); return -EINVAL; } msg_ptr = data; cci = msg_ptr->notification; ucsi_log("", UCSI_CCI, (u8 *)&cci, sizeof(cci)); if (test_bit(CMD_PENDING, &udev->flags) && cci & (UCSI_CCI_ACK_COMPLETE | UCSI_CCI_COMMAND_COMPLETE)) { pr_debug("received ack\n"); complete(&udev->sync_write_ack); } con_num = UCSI_CCI_CONNECTOR(cci); pr_debug("con_num: %u num_connectors: %u\n", con_num, udev->ucsi->cap.num_connectors); if (con_num && con_num <= udev->ucsi->cap.num_connectors && udev->ucsi->connector) { con = &udev->ucsi->connector[con_num - 1]; if (con && con->ucsi) ucsi_connector_change(udev->ucsi, con_num); } return 0; } static int ucsi_callback(void *priv, void *data, size_t len) { struct pmic_glink_hdr *hdr = data; struct ucsi_dev *udev = priv; pr_debug("owner: %u type: %u opcode: %u len:%zu\n", hdr->owner, hdr->type, hdr->opcode, len); if (hdr->opcode == UC_UCSI_READ_BUF_REQ) handle_ucsi_read_ack(udev, data, len); else if (hdr->opcode == UC_UCSI_WRITE_BUF_REQ) handle_ucsi_write_ack(udev, data, len); else if (hdr->opcode == UC_UCSI_USBC_NOTIFY_IND) handle_ucsi_notify(udev, data, len); else pr_err("Unknown message opcode: %d\n", hdr->opcode); return 0; } static bool validate_ucsi_msg(unsigned int offset, size_t len) { pr_debug("offset %u len %u\n", offset, len); if (offset > UCSI_BUF_SIZE - 1 || len > UCSI_BUF_SIZE || offset + len > UCSI_BUF_SIZE) { pr_err("Incorrect length %zu or offset %u\n", len, offset); return false; } return true; } static int ucsi_qti_glink_write(struct ucsi_dev *udev, unsigned int offset, const void *val, size_t val_len, bool sync) { struct ucsi_write_buf_req_msg ucsi_buf = { { 0 } }; int rc; if (!validate_ucsi_msg(offset, val_len)) return -EINVAL; ucsi_buf.hdr.owner = MSG_OWNER_UC; ucsi_buf.hdr.type = MSG_TYPE_REQ_RESP; ucsi_buf.hdr.opcode = UC_UCSI_WRITE_BUF_REQ; memcpy(&ucsi_buf.buf[offset], val, val_len); mutex_lock(&udev->write_lock); pr_debug("%s write\n", sync ? "sync" : "async"); reinit_completion(&udev->write_ack); if (sync) { set_bit(CMD_PENDING, &udev->flags); reinit_completion(&udev->sync_write_ack); } rc = pmic_glink_write(udev->client, &ucsi_buf, sizeof(ucsi_buf)); if (rc < 0) { pr_err("Error in sending message rc=%d\n", rc); goto out; } rc = wait_for_completion_timeout(&udev->write_ack, msecs_to_jiffies(UCSI_WAIT_TIME_MS)); if (!rc) { pr_err("timed out\n"); rc = -ETIMEDOUT; goto out; } else { rc = 0; } if (sync) { rc = wait_for_completion_timeout(&udev->sync_write_ack, msecs_to_jiffies(UCSI_WAIT_TIME_MS)); if (!rc) { pr_err("timed out for sync_write_ack\n"); rc = -ETIMEDOUT; goto out; } else { rc = 0; } } ucsi_log(sync ? "sync_write:" : "async_write:", offset, (u8 *)val, val_len); out: if (sync) clear_bit(CMD_PENDING, &udev->flags); mutex_unlock(&udev->write_lock); return rc; } static int ucsi_qti_async_write(struct ucsi *ucsi, unsigned int offset, const void *val, size_t val_len) { struct ucsi_dev *udev = ucsi_get_drvdata(ucsi); return ucsi_qti_glink_write(udev, offset, val, val_len, false); } static int ucsi_qti_sync_write(struct ucsi *ucsi, unsigned int offset, const void *val, size_t val_len) { struct ucsi_dev *udev = ucsi_get_drvdata(ucsi); return ucsi_qti_glink_write(udev, offset, val, val_len, true); } static int ucsi_qti_read(struct ucsi *ucsi, unsigned int offset, void *val, size_t val_len) { struct ucsi_dev *udev = ucsi_get_drvdata(ucsi); struct ucsi_read_buf_req_msg ucsi_buf = { { 0 } }; int rc; if (!validate_ucsi_msg(offset, val_len)) return -EINVAL; ucsi_buf.hdr.owner = MSG_OWNER_UC; ucsi_buf.hdr.type = MSG_TYPE_REQ_RESP; ucsi_buf.hdr.opcode = UC_UCSI_READ_BUF_REQ; mutex_lock(&udev->read_lock); pr_debug("read offset %s len %u\n", offset_to_name(offset), val_len); reinit_completion(&udev->read_ack); rc = pmic_glink_write(udev->client, &ucsi_buf, sizeof(ucsi_buf)); if (rc < 0) { pr_err("Error in sending message rc=%d\n", rc); goto out; } rc = wait_for_completion_timeout(&udev->read_ack, msecs_to_jiffies(UCSI_WAIT_TIME_MS)); if (!rc) { pr_err("timed out\n"); rc = -ETIMEDOUT; goto out; } else { rc = 0; } if (!atomic_read(&udev->rx_valid)) { rc = -ENODATA; goto out; } memcpy((u8 *)val, &udev->rx_buf.buf[offset], val_len); atomic_set(&udev->rx_valid, 0); ucsi_log("read:", offset, (u8 *)val, val_len); out: mutex_unlock(&udev->read_lock); return rc; } static const struct ucsi_operations ucsi_qti_ops = { .read = ucsi_qti_read, .sync_write = ucsi_qti_sync_write, .async_write = ucsi_qti_async_write }; static int ucsi_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct pmic_glink_client_data client_data; struct ucsi_dev *udev; int rc; udev = devm_kzalloc(dev, sizeof(*udev), GFP_KERNEL); if (!udev) return -ENOMEM; mutex_init(&udev->read_lock); mutex_init(&udev->write_lock); init_completion(&udev->read_ack); init_completion(&udev->write_ack); init_completion(&udev->sync_write_ack); atomic_set(&udev->rx_valid, 0); client_data.id = MSG_OWNER_UC; client_data.name = "ucsi"; client_data.callback = ucsi_callback; client_data.priv = udev; udev->client = pmic_glink_register_client(dev, &client_data); if (IS_ERR(udev->client)) { rc = PTR_ERR(udev->client); if (rc != -EPROBE_DEFER) dev_err(dev, "Error in registering with pmic_glink rc=%d\n", rc); return rc; } platform_set_drvdata(pdev, udev); udev->ucsi = ucsi_create(dev, &ucsi_qti_ops); if (IS_ERR(udev->ucsi)) { rc = PTR_ERR(udev->ucsi); dev_err(dev, "ucsi_create failed rc=%d\n", rc); return rc; } ucsi_set_drvdata(udev->ucsi, udev); ucsi_ipc_log = ipc_log_context_create(NUM_LOG_PAGES, "ucsi", 0); if (!ucsi_ipc_log) dev_warn(dev, "Error in creating ipc_log_context\n"); rc = ucsi_register(udev->ucsi); if (rc) { dev_err(dev, "ucsi_register failed rc=%d\n", rc); goto out; } pr_debug("driver probed\n"); return 0; out: ipc_log_context_destroy(ucsi_ipc_log); ucsi_ipc_log = NULL; ucsi_destroy(udev->ucsi); pmic_glink_unregister_client(udev->client); return rc; } static int ucsi_remove(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct ucsi_dev *udev = dev_get_drvdata(dev); int rc; ucsi_unregister(udev->ucsi); ucsi_destroy(udev->ucsi); rc = pmic_glink_unregister_client(udev->client); if (rc < 0) dev_err(dev, "pmic_glink_unregister_client failed rc=%d\n", rc); ipc_log_context_destroy(ucsi_ipc_log); ucsi_ipc_log = NULL; return rc; } static const struct of_device_id ucsi_match_table[] = { {.compatible = "qcom,ucsi-glink"}, {}, }; static struct platform_driver ucsi_driver = { .driver = { .name = "ucsi_glink", .of_match_table = ucsi_match_table, }, .probe = ucsi_probe, .remove = ucsi_remove, }; module_platform_driver(ucsi_driver); MODULE_DESCRIPTION("QTI UCSI Glink driver"); MODULE_LICENSE("GPL v2");