From 270a493149da6a55c6673a42fbf524b89f2a9db8 Mon Sep 17 00:00:00 2001 From: Amandeep Singh Date: Fri, 7 Jun 2019 12:05:28 +0530 Subject: [PATCH] msm: qcn: Add QCN bridge client driver Add QCN bridge client driver to provide interface to QCN SDIO core function-1 driver. This driver currently provides interfacing for diag, IPC router and sahara application. Change-Id: Ie992aec91347bb68e46a01ee6f67a1d07ce40ecc Signed-off-by: Amandeep Singh --- .../devicetree/bindings/mmc/qcn-sdio-txt | 60 + drivers/char/qti_sdio_client.c | 1138 +++++++++++++++++ include/linux/qcn_sdio_al.h | 2 + 3 files changed, 1200 insertions(+) create mode 100644 drivers/char/qti_sdio_client.c diff --git a/Documentation/devicetree/bindings/mmc/qcn-sdio-txt b/Documentation/devicetree/bindings/mmc/qcn-sdio-txt index aaa4472aae76..15532b6ba4ac 100644 --- a/Documentation/devicetree/bindings/mmc/qcn-sdio-txt +++ b/Documentation/devicetree/bindings/mmc/qcn-sdio-txt @@ -28,3 +28,63 @@ soc { compatible = "qcom,qcn-sdio"; } }; + +QCN SDIO bridge driver +====================== + +The Q6-WCSS runs few services that need to be interfaced on the host device. The +QCN SDIO bridge driver provides the interfacing between the function-1 driver +and the services running on the host device, ther by maintaining a streamlined +communication between Q6-WCSS and host device. + +Example Services: Diag, IPC router, QMI + +QCN SDIO Bridge Node: +===================== +A QCN SDIO bridge driver node is used to represent the client driver instance. +It is needed as a child node to SOC parent node through which +it is accessible to host. + +Required properties: +-------------------- +- compatible Should be one of + "qcom,sdio-bridge" for MSM8996 SOCs + +- qcom,client-id Client ID, Should be on of + 1: TTY, 2: WLAN, 3: QMI, 4: DIAG; + +- qcom,client-name Client Name, Should be one of + "SDIO_AL_CLIENT_TTY", "SDIO_AL_CLIENT_WLAN" + "SDIO_AL_CLIENT_QMI", "SDIO_AL_CLIENT_DIAG" + +- qcom,ch-name "SDIO_AL_TTY_CH0", "SDIO_AL_WLAN_CH0", + "SDIO_AL_WLAN_CH1", "SDIO_AL_QMI_CH0", + +Example: +-------- +/* MSM8996 */ +soc { + sdio_bridge_tty: qcom,sdio-bridge@0 { + compatible = "qcom,sdio-bridge"; + qcom,client-id = <1>; + qcom,client-name = "SDIO_AL_CLIENT_TTY"; + qcom,ch-name = "SDIO_AL_TTY_CH0"; + status = "disabled"; + }; + + sdio_bridge_ipc: qcom,sdio-bridge@1 { + compatible = "qcom,sdio-bridge"; + qcom,client-id = <3>; + qcom,client-name = "SDIO_AL_CLIENT_QMI"; + qcom,ch-name = "SDIO_AL_QMI_CH0"; + status = "disabled"; + }; + + sdio_bridge_diag: qcom,sdio-bridge@3 { + compatible = "qcom,sdio-bridge"; + qcom,client-id = <4>; + qcom,client-name = "SDIO_AL_CLIENT_DIAG"; + qcom,ch-name = "SDIO_AL_DIAG_CH0"; + status = "disabled"; + }; +}; diff --git a/drivers/char/qti_sdio_client.c b/drivers/char/qti_sdio_client.c new file mode 100644 index 000000000000..9b13a2f91adb --- /dev/null +++ b/drivers/char/qti_sdio_client.c @@ -0,0 +1,1138 @@ +/* + * Copyright (c) 2019 The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* add additional information to our printk's */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DATA_ALIGNMENT 4 +#define MAX_CLIENTS 5 +#define TX_BUF_SIZE 0x4000 +#define RX_BUF_SIZE 0x4000 + +#define TTY_RX_BYTE_COUNT 0x21 +#define TTY_RX_BYTE_COUNT_TRANS 0x22 +#define TTY_RX_BYTE_TRANS_MODE 0x23 +#define TTY_RX_BYTE_TX_READY 0x24 + +#define TTY_SAHARA_DOORBELL_EVENT 0x20 +#define TTY_TX_BUF_SZ_EVENT 0x21 +#define TTY_TX_BUF_SZ_TRANS_EVENT 0x22 + +#define QMI_RX_BYTE_COUNT 0x61 +#define QMI_RX_BYTE_COUNT_TRANS 0x62 +#define QMI_RX_BYTE_TRANS_MODE 0x63 +#define QMI_RX_BYTE_TX_READY 0x64 + +#define QMI_DOORBELL_EVENT 0x60 +#define QMI_TX_BUF_SZ_EVENT 0x61 +#define QMI_TX_BUF_SZ_TRANS_EVENT 0x62 + +#define DIAG_RX_BYTE_COUNT 0x81 +#define DIAG_RX_BYTE_COUNT_TRANS 0x82 +#define DIAG_RX_BYTE_TRANS_MODE 0x83 +#define DIAG_RX_BYTE_TX_READY 0x84 + +#define DIAG_DOORBELL_EVENT 0x80 +#define DIAG_TX_BUF_SZ_EVENT 0x81 +#define DIAG_TX_BUF_SZ_TRANS_EVENT 0x82 + +#define QCN_IPC_LOG_PAGES 32 + +#define IPC_BRIDGE_MAX_READ_SZ (8 * 1024) +#define IPC_BRIDGE_MAX_WRITE_SZ (8 * 1024) + +static bool to_console; +module_param(to_console, bool, S_IRUGO | S_IWUSR | S_IWGRP); + +static bool ipc_log = 1; +module_param(ipc_log, bool, S_IRUGO | S_IWUSR | S_IWGRP); + + +static DEFINE_MUTEX(work_lock); +static spinlock_t list_lock; + +#define qlog(qsb, _msg, ...) do { \ + if (to_console) \ + pr_err("[%s] " _msg, __func__, ##__VA_ARGS__); \ + if (ipc_log) \ + ipc_log_string(qsb->ipc_log_ctxt, "[%s] " _msg, __func__, \ + ##__VA_ARGS__); \ +} while (0) + +enum qcn_sdio_cli_id { + QCN_SDIO_CLI_ID_INVALID = 0, + QCN_SDIO_CLI_ID_TTY, + QCN_SDIO_CLI_ID_WLAN, + QCN_SDIO_CLI_ID_QMI, + QCN_SDIO_CLI_ID_DIAG, + QCN_SDIO_CLI_ID_MAX +}; + +struct ipc_bridge_platform_data { + unsigned int max_read_size; + unsigned int max_write_size; + int (*open)(int id, void *ops); + int (*read)(int id, char *buf, size_t count); + int (*write)(int id, char *buf, size_t count); + int (*close)(int id); +}; + +struct diag_bridge_ops { + void *ctxt; + void (*read_complete_cb)(void *ctxt, char *buf, + int buf_size, int actual); + void (*write_complete_cb)(void *ctxt, char *buf, + int buf_size, int actual); + int (*suspend)(void *ctxt); + void (*resume)(void *ctxt); +}; + +struct qti_sdio_bridge { + const char *name; + const char *ch_name; + uint8_t id; + uint8_t mode; + struct sdio_al_channel_handle *channel_handle; + struct sdio_al_client_handle *client_handle; + wait_queue_head_t wait_q; + u8 *tx_dma_buf; + u8 *rx_dma_buf; + int data_avail; + int data_remain; + int blk_trans_mode; + int tx_ready; + struct diag_bridge_ops *ops; + void *ipc_log_ctxt; + void *priv_dev_info; + unsigned int mdata_count; + unsigned int tx_ready_count; + unsigned int data_avail_count; + +}; + +struct data_avail_node { + int id; + int data_avail; + u8 *rx_dma_buf; + struct list_head list; +}; + +struct tty_device { + struct device *qsb_device; + struct class *qsb_class; +}; + +static struct qti_sdio_bridge *qsbdev[MAX_CLIENTS]; + +static int kworker_refs_count; +struct kthread_work kwork; +struct kthread_worker kworker; +struct task_struct *task; +struct list_head data_avail_list; +static struct completion read_complete; + +void qti_client_queue_rx(int id, u8 *buf, unsigned int bytes) +{ + struct data_avail_node *data_node; + + if ((id < QCN_SDIO_CLI_ID_TTY) || (id > QCN_SDIO_CLI_ID_DIAG)) { + pr_err("%s invalid client ID %d\n", __func__, id); + return; + } + + data_node = kzalloc(sizeof(struct data_avail_node), GFP_ATOMIC); + if (!data_node) { + to_console = 1; + qlog(qsbdev[id], "client %d dnode allocation failed\n", id); + return; + } + + qlog(qsbdev[id], "%s Queuing to work %d %p\n", qsbdev[id]->name, bytes, + buf); + data_node->data_avail = bytes; + data_node->id = id; + data_node->rx_dma_buf = buf; + + spin_lock(&list_lock); + list_add_tail(&data_node->list, &data_avail_list); + spin_unlock(&list_lock); + + queue_kthread_work(&kworker, &kwork); +} + +void qti_client_ul_xfer_cb(struct sdio_al_channel_handle *ch_handle, + struct sdio_al_xfer_result *xfer, void *ctxt) +{ + struct qti_sdio_bridge *qsb = NULL; + struct completion *tx_complete = (struct completion *)ctxt; + struct sdio_al_client_data *cl_data = + ch_handle->channel_data->client_data; + + if (!xfer || xfer->xfer_status || !cl_data || + (cl_data->id < QCN_SDIO_CLI_ID_TTY) || + (cl_data->id > QCN_SDIO_CLI_ID_DIAG)) { + pr_err("%s invalid client ID\n", __func__); + return; + } + + qsb = qsbdev[cl_data->id]; + complete(tx_complete); +} + +void qti_client_dl_xfer_cb(struct sdio_al_channel_handle *ch_handle, + struct sdio_al_xfer_result *xfer, void *ctxt) +{ + struct qti_sdio_bridge *qsb = NULL; + struct sdio_al_client_data *cl_data = + ch_handle->channel_data->client_data; + + if (!xfer || xfer->xfer_status || !cl_data || + (cl_data->id < QCN_SDIO_CLI_ID_TTY) || + (cl_data->id > QCN_SDIO_CLI_ID_DIAG)) { + pr_err("%s invalid client ID\n", __func__); + return; + } + + qsb = qsbdev[cl_data->id]; + qti_client_queue_rx(cl_data->id, xfer->buf_addr, (int)(uintptr_t)ctxt); +} + +void qti_client_data_avail_cb(struct sdio_al_channel_handle *ch_handle, + unsigned int bytes) +{ + int ret = 0; + int padded_len = 0; + u8 *rx_dma_buf = NULL; + struct qti_sdio_bridge *qsb = NULL; + struct sdio_al_client_data *cl_data = + ch_handle->channel_data->client_data; + + if (!cl_data || (cl_data->id < QCN_SDIO_CLI_ID_TTY) || + (cl_data->id > QCN_SDIO_CLI_ID_DIAG)) { + pr_err("%s invalid client ID\n", __func__); + return; + } + + qsb = qsbdev[cl_data->id]; + qsb->data_avail_count++; + rx_dma_buf = kzalloc(RX_BUF_SIZE, GFP_ATOMIC); + if (!rx_dma_buf) { + to_console = 1; + qlog(qsb, "Unable to allocate rx_dma_buf\n"); + return; + } + + if (qsb->blk_trans_mode && + (bytes % qsb->client_handle->block_size)) { + padded_len = (((bytes / + qsb->client_handle->block_size) + 1) * + (qsb->client_handle->block_size)); + } else { + padded_len = bytes; + } + + if (qsb->mode) { + ret = sdio_al_queue_transfer_async(qsb->channel_handle, + SDIO_AL_RX, rx_dma_buf, + padded_len, 0, + (void *)(uintptr_t)bytes); + if (ret) { + to_console = 1; + qlog(qsb, "%s: data queueing failed %d\n", qsb->name, + ret); + return; + } + } else { + ret = sdio_al_queue_transfer(qsb->channel_handle, + SDIO_AL_RX, rx_dma_buf, padded_len, 0); + if (ret == 1) { + pr_err("TRACK: operating in async mode now\n"); + goto out; + } + if (ret) { + to_console = 1; + qlog(qsb, "%s: data transfer failed %d\n", qsb->name, + ret); + return; + } + qti_client_queue_rx(cl_data->id, rx_dma_buf, bytes); + } +out: + qlog(qsb, "%s: data %s success\n", qsb->name, + qsb->mode ? "queueing" : "transfer"); +} + +static void sdio_dl_meta_data_cb(struct sdio_al_channel_handle *ch_handle, + unsigned int data) +{ + u8 event = 0; + struct qti_sdio_bridge *qsb = NULL; + struct sdio_al_client_data *cl_data = + ch_handle->channel_data->client_data; + + if (!cl_data || (cl_data->id < QCN_SDIO_CLI_ID_TTY) || + (cl_data->id > QCN_SDIO_CLI_ID_DIAG)) { + pr_err("%s invalid client ID\n", __func__); + return; + } + + qsb = qsbdev[cl_data->id]; + + event = (u8)((data & 0xFF000000) >> 24); + + switch (event) { + case TTY_RX_BYTE_COUNT: + qlog(qsb, "client %s data_avail %d\n", qsb->name, + data & 0x00003FFF); + qti_client_data_avail_cb(ch_handle, (data & 0x00003FFF)); + break; + case QMI_RX_BYTE_COUNT: + case DIAG_RX_BYTE_COUNT: + qlog(qsb, "client %s meta_data %x\n", qsb->name, data); + break; + case TTY_RX_BYTE_COUNT_TRANS: + case QMI_RX_BYTE_COUNT_TRANS: + case DIAG_RX_BYTE_COUNT_TRANS: + break; + case TTY_RX_BYTE_TRANS_MODE: + case QMI_RX_BYTE_TRANS_MODE: + case DIAG_RX_BYTE_TRANS_MODE: + qsb->blk_trans_mode = (data & 0x00000001); + qlog(qsb, "client %s mode = %d data %x\n", qsb->name, + qsb->blk_trans_mode, data); + break; + case TTY_RX_BYTE_TX_READY: + case QMI_RX_BYTE_TX_READY: + case DIAG_RX_BYTE_TX_READY: + qsb->tx_ready = 1; + wake_up(&qsb->wait_q); + qlog(qsb, "client %s tx_ready data = %x\n", qsb->name, data); + qsb->tx_ready_count++; + break; + default: + to_console = 1; + qlog(qsb, "client %s INVALID_DATA\n", qsb->name); + } +} + +int qti_client_open(int id, void *ops) +{ + int ret = -ENODEV; + unsigned int mdata = 0; + unsigned int event = 0; + struct qti_sdio_bridge *qsb = NULL; + + switch (id) { + case QCN_SDIO_CLI_ID_TTY: + event = TTY_SAHARA_DOORBELL_EVENT; + break; + case QCN_SDIO_CLI_ID_QMI: + event = QMI_DOORBELL_EVENT; + break; + case QCN_SDIO_CLI_ID_DIAG: + event = DIAG_DOORBELL_EVENT; + break; + default: + to_console = 1; + qlog(qsb, "Invalid client\n"); + return ret; + } + + qsb = qsbdev[id]; + + qlog(qsb, "client %s\n", qsb->name); + + qsb->ops = (struct diag_bridge_ops *)ops; + + mdata = (event << 24); + ret = sdio_al_meta_transfer(qsb->channel_handle, mdata, 0); + + return ret; +} +EXPORT_SYMBOL(qti_client_open); + +int qti_client_close(int id) +{ + int ret = -ENODEV; + struct qti_sdio_bridge *qsb = NULL; + + if ((id < QCN_SDIO_CLI_ID_TTY) || (id > QCN_SDIO_CLI_ID_DIAG)) { + pr_err("%s invalid client ID %d\n", __func__, id); + return ret; + } + + qsb = qsbdev[id]; + + qlog(qsb, "client %s\n", qsb->name); + + qsb->ops = NULL; + + return 0; +} +EXPORT_SYMBOL(qti_client_close); + +int qti_client_read(int id, char *buf, size_t count) +{ + int ret = 0; + int bytes = 0; + struct qti_sdio_bridge *qsb = NULL; + + if ((id < QCN_SDIO_CLI_ID_TTY) || (id > QCN_SDIO_CLI_ID_DIAG)) { + pr_err("%s invalid client ID %d\n", __func__, id); + return ret; + } + + qsb = qsbdev[id]; + qlog(qsb, "client %s\n", qsb->name); + + if (id == QCN_SDIO_CLI_ID_DIAG && !qsb->ops) { + to_console = 1; + qlog(qsb, "%s: no diag operations assigned\n", qsb->name); + ret = -ENODEV; + goto out; + } + + wait_event(qsb->wait_q, qsb->data_avail); + + bytes = qsb->data_avail; + + if (!qsb->data_remain) { + if (count > bytes) + count = bytes; + + if (id == QCN_SDIO_CLI_ID_TTY) { + ret = copy_to_user(buf, qsb->rx_dma_buf, count); + if (ret) { + to_console = 1; + qlog(qsb, "%s: failed to copy to user buffer\n", + qsb->name); + return -EIO; + } + } else { + memcpy(buf, qsb->rx_dma_buf, count); + } + qsb->data_remain = bytes - count; + } else { + if (count > qsb->data_remain) + count = qsb->data_remain; + + if (id == QCN_SDIO_CLI_ID_TTY) { + ret = copy_to_user(buf, qsb->rx_dma_buf + + (bytes - qsb->data_remain), count); + if (ret) { + to_console = 1; + qlog(qsb, "%s: failed to copy to user buffer\n", + qsb->name); + return -EIO; + } + } else { + memcpy(buf, qsb->rx_dma_buf + + (bytes - qsb->data_remain), count); + } + qsb->data_remain -= count; + } +out: + if (id == QCN_SDIO_CLI_ID_DIAG && qsb->ops && + qsb->ops->read_complete_cb) { + qsb->ops->read_complete_cb((void *)(uintptr_t)0, buf, count, + count); + } + + if (!qsb->data_remain) { + qsb->data_avail = 0; + bytes = 0; + complete(&read_complete); + } + + return count; +} +EXPORT_SYMBOL(qti_client_read); + +int qti_client_write(int id, char *buf, size_t count) +{ + int ret = 0; + int remaining = 0; + int padded_len = 0; + int temp_count = 0; + u8 *buffer = NULL; + unsigned int mdata = 0; + unsigned int event = 0; + struct qti_sdio_bridge *qsb = NULL; + struct completion tx_complete; + + switch (id) { + case QCN_SDIO_CLI_ID_TTY: + event = TTY_TX_BUF_SZ_EVENT; + break; + case QCN_SDIO_CLI_ID_QMI: + event = QMI_TX_BUF_SZ_EVENT; + break; + case QCN_SDIO_CLI_ID_DIAG: + event = DIAG_TX_BUF_SZ_EVENT; + break; + default: + to_console = 1; + qlog(qsb, "Invalid client\n"); + return ret; + } + + qsb = qsbdev[id]; + qsb->tx_ready = 0; + + qlog(qsb, "client %s\n", qsb->name); + + if (id == QCN_SDIO_CLI_ID_DIAG && !qsb->ops) { + to_console = 1; + qlog(qsb, "%s: no diag operations assigned\n", qsb->name); + ret = -ENODEV; + return ret; + } + + if (id == QCN_SDIO_CLI_ID_TTY) { + ret = copy_from_user(qsb->tx_dma_buf, buf, count); + if (ret) { + qlog(qsb, "%s: failed to copy from user buffer\n", + qsb->name); + return ret; + } + } else { + memcpy(qsb->tx_dma_buf, buf, count); + } + + remaining = count; + temp_count = count; + buffer = qsb->tx_dma_buf; + + while (remaining) { + qsb->tx_ready = 0; + if (id != QCN_SDIO_CLI_ID_TTY && remaining > 1024) { + temp_count = remaining; + remaining = remaining - 1024; + temp_count = temp_count - remaining; + } else if (id == QCN_SDIO_CLI_ID_TTY) { + remaining = 0; + } else { + temp_count = remaining; + remaining = 0; + } + + if (qsb->blk_trans_mode && + (temp_count % qsb->client_handle->block_size)) + padded_len = + (((temp_count / qsb->client_handle->block_size) + + 1) * (qsb->client_handle->block_size)); + else + padded_len = temp_count; + + mdata = ((event << 24) | (temp_count & 0x3FFF)); + ret = sdio_al_meta_transfer(qsb->channel_handle, mdata, 0); + if (ret) { + to_console = 1; + qlog(qsb, "%s: meta data transfer failed %d\n", + qsb->name, ret); + return ret; + } + + qlog(qsb, "MDATA: %x\n", mdata); + qsb->mdata_count++; + + wait_event(qsb->wait_q, qsb->tx_ready); + + if (qsb->mode) { + init_completion(&tx_complete); + ret = sdio_al_queue_transfer_async(qsb->channel_handle, + SDIO_AL_TX, buffer, + padded_len, 0, + (void *)&tx_complete); + if (ret) { + to_console = 1; + qlog(qsb, "%s: data transfer failed %d\n", + qsb->name, ret); + return ret; + } + } else { + + ret = sdio_al_queue_transfer(qsb->channel_handle, + SDIO_AL_TX, buffer, padded_len, 0); + if (ret) { + to_console = 1; + qlog(qsb, "%s: data transfer failed %d\n", + qsb->name, ret); + return ret; + } + } + buffer = buffer + temp_count; + } + + if (id == QCN_SDIO_CLI_ID_DIAG && qsb->ops && + qsb->ops->write_complete_cb) { + qsb->ops->write_complete_cb((void *)(uintptr_t)0, buf, count, + count); + } + + if (qsb->mode) + wait_for_completion(&tx_complete); + + return count; +} +EXPORT_SYMBOL(qti_client_write); + +static int qsb_dev_open(struct inode *inode, struct file *file) +{ + if (atomic_read(&inode->i_count) != 1) + return -EBUSY; + + return qti_client_open(QCN_SDIO_CLI_ID_TTY, NULL); +} + +static ssize_t qsb_dev_read(struct file *file, char __user *buf, size_t count, + loff_t *ppos) +{ + return qti_client_read(QCN_SDIO_CLI_ID_TTY, (char *)buf, count); +} + +static ssize_t qsb_dev_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + return qti_client_write(QCN_SDIO_CLI_ID_TTY, (char *)buf, count); +} + +static int qsb_dev_release(struct inode *inode, struct file *file) +{ + return qti_client_close(QCN_SDIO_CLI_ID_TTY); +} + +static +unsigned int qsb_dev_poll(struct file *file, struct poll_table_struct *wait) +{ + int ret = 0; + struct qti_sdio_bridge *qsb = NULL; + + qsb = qsbdev[QCN_SDIO_CLI_ID_TTY]; + + if (!qsb->data_avail) + poll_wait(file, &qsb->wait_q, wait); + + if (qsb->data_avail) + ret = POLLIN | POLLRDNORM; + + return ret; +} + +static const struct ipc_bridge_platform_data ipc_bridge_pdata = { + .max_read_size = IPC_BRIDGE_MAX_READ_SZ, + .max_write_size = IPC_BRIDGE_MAX_WRITE_SZ, + .open = qti_client_open, + .read = qti_client_read, + .write = qti_client_write, + .close = qti_client_close, +}; + +static const struct file_operations qsb_dev_ops = { + .open = qsb_dev_open, + .read = qsb_dev_read, + .write = qsb_dev_write, + .release = qsb_dev_release, + .poll = qsb_dev_poll, +}; + +int qti_client_debug_init(int id) +{ + int ret = -EINVAL; + char name[32] = {0}; + struct qti_sdio_bridge *qsb = NULL; + + if ((id < QCN_SDIO_CLI_ID_TTY) || (id > QCN_SDIO_CLI_ID_DIAG)) { + pr_err("%s : invalid client ID %d\n", __func__, id); + return ret; + } + + qsb = qsbdev[id]; + + snprintf(name, sizeof(name), "%s_%s", "qcn_client", + (char *)(qsb->name + 15)); + + qsb->ipc_log_ctxt = ipc_log_context_create(QCN_IPC_LOG_PAGES, name, 0); + if (!qsb->ipc_log_ctxt) { + pr_err("failed to initialize ipc logging for client_%d", id); + goto out; + } + + return 0; +out: + return ret; +} + +void qti_client_debug_deinit(int id) +{ + struct qti_sdio_bridge *qsb = NULL; + + if ((id < QCN_SDIO_CLI_ID_TTY) || (id > QCN_SDIO_CLI_ID_DIAG)) { + pr_err("%s : invalid client ID %d\n", __func__, id); + return; + } + + qsb = qsbdev[id]; + + if (qsb && qsb->ipc_log_ctxt) { + ipc_log_context_destroy(qsb->ipc_log_ctxt); + qsb->ipc_log_ctxt = NULL; + qsb = NULL; + } +} + +static int qti_client_probe(struct sdio_al_client_handle *client_handle) +{ + int ret = -EINVAL; + int major_no = 0; + int diag_ch = QCN_SDIO_CLI_ID_DIAG; + struct tty_device *tty_dev = NULL; + struct platform_device *ipc_pdev = NULL; + struct platform_device *diag_pdev = NULL; + struct qti_sdio_bridge *qsb = NULL; + struct sdio_al_channel_handle *channel_handle = NULL; + struct sdio_al_channel_data *channel_data = NULL; + + if ((client_handle->id < QCN_SDIO_CLI_ID_TTY) || + (client_handle->id > QCN_SDIO_CLI_ID_DIAG)) { + pr_err("%s : invalid client ID %d\n", __func__, + client_handle->id); + goto err; + } + + qti_client_debug_init(client_handle->id); + + qsb = qsbdev[client_handle->id]; + + qlog(qsb, "probing client %s\n", qsb->name); + + channel_data = kzalloc(sizeof(struct sdio_al_channel_data), GFP_KERNEL); + if (!channel_data) { + to_console = 1; + qlog(qsb, "client %s failed to allocate channel_data\n", + qsb->name); + ret = -ENOMEM; + goto err; + } + + channel_data->name = kasprintf(GFP_KERNEL, qsb->ch_name); + channel_data->client_data = client_handle->client_data; + channel_data->dl_meta_data_cb = sdio_dl_meta_data_cb; + + if (client_handle->id != QCN_SDIO_CLI_ID_TTY) + channel_data->dl_data_avail_cb = qti_client_data_avail_cb; + else + channel_data->dl_data_avail_cb = NULL; + + channel_data->ul_xfer_cb = qti_client_ul_xfer_cb; + channel_data->dl_xfer_cb = qti_client_dl_xfer_cb; + + channel_handle = sdio_al_register_channel(client_handle, channel_data); + if (IS_ERR(channel_handle)) { + ret = PTR_ERR(channel_handle); + to_console = 1; + qlog(qsb, + "client %s failed to register channel_handle ret = %d\n", + qsb->name, ret); + goto channel_data_err; + } + + qsb->channel_handle = channel_handle; + + qsb->tx_dma_buf = kzalloc(TX_BUF_SIZE, GFP_KERNEL); + if (!qsb->tx_dma_buf) { + to_console = 1; + qlog(qsb, "client %s failed to allocate tx_buf\n", qsb->name); + ret = -ENOMEM; + goto channel_handle_err; + } + + init_waitqueue_head(&qsb->wait_q); + + if (client_handle->id == QCN_SDIO_CLI_ID_TTY) { + tty_dev = kmalloc(sizeof(struct tty_device), GFP_KERNEL); + if (!tty_dev) { + to_console = 1; + qlog(qsb, "unable to allocate platform device\n"); + ret = -ENOMEM; + goto tx_err; + } + + major_no = register_chrdev(UNNAMED_MAJOR, "QCN", &qsb_dev_ops); + if (major_no < 0) { + to_console = 1; + qlog(qsb, "client %s failed to allocate major_no\n", + qsb->name); + ret = major_no; + goto tx_err; + } + + tty_dev->qsb_class = class_create(THIS_MODULE, "qsahara"); + if (IS_ERR(tty_dev->qsb_class)) { + to_console = 1; + qlog(qsb, "client %s failed to create class\n", + qsb->name); + ret = PTR_ERR(tty_dev->qsb_class); + goto reg_err; + } + + tty_dev->qsb_device = device_create(tty_dev->qsb_class, NULL, + MKDEV(major_no, 0), NULL, "qcn_sdio"); + if (IS_ERR(tty_dev->qsb_device)) { + to_console = 1; + qlog(qsb, "client %s failed to create device node\n", + qsb->name); + ret = PTR_ERR(tty_dev->qsb_device); + + goto dev_err; + } + qsb->priv_dev_info = tty_dev; + } + + if (client_handle->id == QCN_SDIO_CLI_ID_QMI) { + ipc_pdev = platform_device_alloc("ipc_bridge_sdio", + QCN_SDIO_CLI_ID_QMI); + if (!ipc_pdev) { + to_console = 1; + qlog(qsb, "unable to allocate platform device\n"); + ret = -ENOMEM; + goto tx_err; + } + + ret = platform_device_add_data(ipc_pdev, &ipc_bridge_pdata, + sizeof(struct ipc_bridge_platform_data)); + if (ret) { + to_console = 1; + qlog(qsb, "failed to add pdata\n"); + goto put_pdev; + } + + ret = platform_device_add(ipc_pdev); + if (ret) { + to_console = 1; + qlog(qsb, "failed to add ipc_pdev\n"); + goto put_pdev; + } + qsb->priv_dev_info = ipc_pdev; + } + + if (client_handle->id == QCN_SDIO_CLI_ID_DIAG) { + diag_pdev = platform_device_register_data(NULL, + "diag_bridge_sdio", 0, &diag_ch, sizeof(int)); + if (IS_ERR(diag_pdev)) { + to_console = 1; + qlog(qsb, "%s: unable to allocate platform device\n", + __func__); + ret = PTR_ERR(diag_pdev); + goto put_pdev; + } + qsb->priv_dev_info = diag_pdev; + } + + qlog(qsb, "probed client %s\n", qsb->name); + return 0; + +put_pdev: + if (client_handle->id == QCN_SDIO_CLI_ID_QMI) + platform_device_put(ipc_pdev); + + if (client_handle->id == QCN_SDIO_CLI_ID_TTY) { +dev_err: + class_destroy(tty_dev->qsb_class); +reg_err: + unregister_chrdev(major_no, "qsahara"); + } +tx_err: + kfree(qsb->tx_dma_buf); +channel_handle_err: + sdio_al_deregister_channel(channel_handle); +channel_data_err: + kfree(channel_data); +err: + pr_err("probe failed for client %d\n", client_handle->id); + return ret; +} + +static int qti_client_remove(struct sdio_al_client_handle *client_handle) +{ + int ret = -EINVAL; + int minor_no = 0; + int major_no = 0; + struct tty_device *tty_dev = NULL; + struct qti_sdio_bridge *qsb = NULL; + + if ((client_handle->id < QCN_SDIO_CLI_ID_TTY) || + (client_handle->id > QCN_SDIO_CLI_ID_DIAG)) { + pr_err("%s : invalid client ID %d\n", __func__, + client_handle->id); + goto err; + } + + qsb = qsbdev[client_handle->id]; + tty_dev = (struct tty_device *)qsb->priv_dev_info; + if (tty_dev->qsb_device && client_handle->id == QCN_SDIO_CLI_ID_TTY) { + minor_no = MINOR(tty_dev->qsb_device->devt); + major_no = MAJOR(tty_dev->qsb_device->devt); + device_destroy(tty_dev->qsb_class, MKDEV(major_no, minor_no)); + class_destroy(tty_dev->qsb_class); + unregister_chrdev(major_no, "qsahara"); + tty_dev->qsb_class = NULL; + tty_dev->qsb_device = NULL; + major_no = 0; + } + + if (client_handle->id == QCN_SDIO_CLI_ID_QMI) + platform_device_unregister(qsb->priv_dev_info); + + if (client_handle->id == QCN_SDIO_CLI_ID_DIAG) + platform_device_unregister(qsb->priv_dev_info); + + qlog(qsb, "removed client %s\n", qsb->name); + kfree(qsb->tx_dma_buf); + qti_client_debug_deinit(client_handle->id); + return 0; + +err: + pr_err("%s : failed to removed client %d\n", __func__, + client_handle->id); + return ret; +} + +static void data_avail_worker(struct kthread_work *work) +{ + struct data_avail_node *data_node = NULL; + struct qti_sdio_bridge *qsb = NULL; + + mutex_lock(&work_lock); + spin_lock(&list_lock); + while (!list_empty(&data_avail_list)) { + reinit_completion(&read_complete); + data_node = list_first_entry(&data_avail_list, + struct data_avail_node, list); + list_del(&data_node->list); + spin_unlock(&list_lock); + + qsb = qsbdev[data_node->id]; + qsb->data_avail = data_node->data_avail; + + qsb->rx_dma_buf = data_node->rx_dma_buf; + + qlog(qsb, "%s Queuing to read %d %p\n", qsb->name, + qsb->data_avail, qsb->rx_dma_buf); + + if (qsb->data_avail) + wake_up(&qsb->wait_q); + + wait_for_completion(&read_complete); + kfree(data_node); + spin_lock(&list_lock); + } + spin_unlock(&list_lock); + mutex_unlock(&work_lock); +} + +static int qti_bridge_probe(struct platform_device *pdev) +{ + int id = 0; + int ret = -EPROBE_DEFER; + struct sdio_al_client_data *client_data = NULL; + struct sdio_al_client_handle *client_handle = NULL; + + ret = sdio_al_is_ready(); + if (ret) { + ret = -EPROBE_DEFER; + goto out; + } + + ret = of_property_read_u32(pdev->dev.of_node, "qcom,client-id", &id); + if (ret) { + pr_err("qcom,client-id not found\n"); + goto out; + } + + qsbdev[id] = kzalloc(sizeof(struct qti_sdio_bridge), GFP_KERNEL); + if (!qsbdev[id]) { + ret = -ENOMEM; + goto out; + } + + qsbdev[id]->id = id; + + ret = of_property_read_string(pdev->dev.of_node, "qcom,ch-name", + &(qsbdev[id]->ch_name)); + if (ret) { + pr_err("qcom,ch-name not found\n"); + goto out; + } + + client_data = kzalloc(sizeof(struct sdio_al_client_data), GFP_KERNEL); + if (!client_data) { + ret = -ENOMEM; + goto bridge_alloc_error; + } + + ret = of_property_read_string(pdev->dev.of_node, "qcom,client-name", + &client_data->name); + if (ret) { + pr_err("qcom,client-name not found\n"); + goto bridge_alloc_error; + } + + qsbdev[id]->name = kasprintf(GFP_KERNEL, client_data->name); + + ret = of_property_read_u32(pdev->dev.of_node, "qcom,client-mode", + &client_data->mode); + if (ret) + pr_err("qcom,client-mode not set, mode[async] as default\n"); + + client_data->probe = qti_client_probe; + client_data->remove = qti_client_remove; + client_data->id = id; + + qsbdev[id]->mode = client_data->mode; + + client_handle = sdio_al_register_client(client_data); + if (IS_ERR(client_handle)) { + ret = PTR_ERR(client_handle); + goto client_error; + } + + if (qsbdev[client_handle->id]->id != client_handle->id) { + pr_err("probed client %d doesn't match registered client %d\n", + qsbdev[client_handle->id]->id, client_handle->id); + goto client_reg_error; + } + + qsbdev[client_handle->id]->client_handle = client_handle; + + if (!kworker_refs_count) { + init_kthread_work(&kwork, data_avail_worker); + init_kthread_worker(&kworker); + init_completion(&read_complete); + + INIT_LIST_HEAD(&data_avail_list); + + task = kthread_run(kthread_worker_fn, &kworker, "qcn_worker"); + if (IS_ERR(task)) { + pr_err("Failed to run qcn_worker thread\n"); + goto client_reg_error; + } + qcn_sdio_client_probe_complete(client_handle->id); + spin_lock_init(&list_lock); + } + ++kworker_refs_count; + + return 0; + +client_reg_error: + sdio_al_deregister_client(client_handle); +client_error: + kfree(client_data); +bridge_alloc_error: + kfree(qsbdev[id]); +out: + return ret; +} + +static int qti_bridge_remove(struct platform_device *pdev) +{ + int ret = -EBUSY; + int id = 0; + struct qti_sdio_bridge *qsb = NULL; + + ret = of_property_read_u32(pdev->dev.of_node, "qcom,client-id", &id); + if (ret) { + pr_err("%s: qcom,client-id not found\n", __func__); + goto out; + } + + qsb = qsbdev[id]; + + --kworker_refs_count; + if (!kworker_refs_count) + kthread_stop(task); + + sdio_al_deregister_client(qsb->client_handle); + kfree(qsb); + +out: + return ret; +} + +static const struct of_device_id qti_sdio_bridge_of_match[] = { + {.compatible = "qcom,sdio-bridge"}, + {} +}; +MODULE_DEVICE_TABLE(of, qti_sdio_bridge_of_match); + +static struct platform_driver qti_sdio_bridge_driver = { + .probe = qti_bridge_probe, + .remove = qti_bridge_remove, + .driver = { + .name = "sdio_bridge", + .owner = THIS_MODULE, + .of_match_table = qti_sdio_bridge_of_match, + }, +}; + +static int __init qti_bridge_init(void) +{ + int ret = -EBUSY; + + ret = platform_driver_register(&qti_sdio_bridge_driver); + if (ret) { + printk(to_console ? KERN_ERR : KERN_DEBUG + "%s: platform_driver registeration failed\n", __func__); + goto out; + } + + return 0; +out: + return ret; +} + +static void __exit qti_bridge_exit(void) +{ + platform_driver_unregister(&qti_sdio_bridge_driver); +} + +module_init(qti_bridge_init); +module_exit(qti_bridge_exit); +MODULE_DESCRIPTION("Qualcomm Technologies, Inc."); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/qcn_sdio_al.h b/include/linux/qcn_sdio_al.h index b7e9e91c5382..97d8aa189735 100644 --- a/include/linux/qcn_sdio_al.h +++ b/include/linux/qcn_sdio_al.h @@ -97,6 +97,8 @@ struct sdio_al_client_data { int id; + int mode; + int (*probe)(struct sdio_al_client_handle *); int (*remove)(struct sdio_al_client_handle *);