summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorLinux Build Service Account <lnxbuild@localhost>2019-09-03 14:02:19 -0700
committerGerrit - the friendly Code Review server <code-review@localhost>2019-09-03 14:02:19 -0700
commit81a9e55e0863fb3e14be5e6b996e5c0bb795bdfb (patch)
tree876fb15a9c254462f71c9dbde3803ad4f4b9bf5e /drivers
parenta7499d6acea5c581b1f56fc663010d3b90a3c9d8 (diff)
parentf9d70693889be5024d0e423e5924018300b902af (diff)
Merge "diag: Add diag over sdio support"
Diffstat (limited to 'drivers')
-rw-r--r--drivers/char/diag/diagfwd_sdio.c536
-rw-r--r--drivers/char/diag/diagfwd_sdio.h57
-rw-r--r--drivers/char/qti_sdio_client.c1138
-rw-r--r--drivers/platform/msm/qcn/Kconfig8
-rw-r--r--drivers/platform/msm/qcn/Makefile1
-rw-r--r--drivers/platform/msm/qcn/qcn_sdio.c1122
-rw-r--r--drivers/platform/msm/qcn/qcn_sdio.h175
-rw-r--r--drivers/platform/msm/qcn/qcn_sdio_hwio.h296
8 files changed, 3333 insertions, 0 deletions
diff --git a/drivers/char/diag/diagfwd_sdio.c b/drivers/char/diag/diagfwd_sdio.c
new file mode 100644
index 000000000000..27441ff31763
--- /dev/null
+++ b/drivers/char/diag/diagfwd_sdio.c
@@ -0,0 +1,536 @@
+/* Copyright (c) 2012-2014,2016,2018 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.
+ */
+
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/uaccess.h>
+#include <linux/diagchar.h>
+#include <linux/sched.h>
+#include <linux/err.h>
+#include <linux/ratelimit.h>
+#include <linux/workqueue.h>
+#include <linux/pm_runtime.h>
+#include <linux/platform_device.h>
+#include <asm/current.h>
+#include "diagmem.h"
+#include "diagfwd_bridge.h"
+#include "diagfwd_hsic.h"
+
+#define DIAG_HSIC_STRING_SZ 11
+
+struct diag_hsic_info diag_hsic[NUM_HSIC_DEV] = {
+ {
+ .id = HSIC_1,
+ .dev_id = DIAGFWD_MDM,
+ .name = "MDM",
+ .mempool = POOL_TYPE_MDM,
+ .opened = 0,
+ .enabled = 0,
+ .suspended = 0,
+ .hsic_wq = NULL
+ },
+ {
+ .id = HSIC_2,
+ .dev_id = DIAGFWD_MDM_DCI,
+ .name = "MDM_DCI",
+ .mempool = POOL_TYPE_MDM_DCI,
+ .opened = 0,
+ .enabled = 0,
+ .suspended = 0,
+ .hsic_wq = NULL
+ }
+};
+
+static int hsic_buf_tbl_push(struct diag_hsic_info *ch, void *buf, int len)
+{
+ unsigned long flags;
+ struct diag_hsic_buf_tbl_t *item;
+
+ if (!ch || !buf || len < 0)
+ return -EINVAL;
+
+ item = kzalloc(sizeof(struct diag_hsic_buf_tbl_t), GFP_ATOMIC);
+ if (!item)
+ return -ENOMEM;
+ kmemleak_not_leak(item);
+
+ spin_lock_irqsave(&ch->lock, flags);
+ item->buf = buf;
+ item->len = len;
+ list_add_tail(&item->link, &ch->buf_tbl);
+ spin_unlock_irqrestore(&ch->lock, flags);
+
+ return 0;
+}
+
+static struct diag_hsic_buf_tbl_t *hsic_buf_tbl_pop(struct diag_hsic_info *ch)
+{
+ unsigned long flags;
+ struct diag_hsic_buf_tbl_t *item = NULL;
+
+ if (!ch || list_empty(&ch->buf_tbl))
+ return NULL;
+
+ spin_lock_irqsave(&ch->lock, flags);
+ item = list_first_entry(&ch->buf_tbl, struct diag_hsic_buf_tbl_t, link);
+ list_del(&item->link);
+ spin_unlock_irqrestore(&ch->lock, flags);
+
+ return item;
+}
+
+static void hsic_buf_tbl_clear(struct diag_hsic_info *ch)
+{
+ unsigned long flags;
+ struct list_head *start, *temp;
+ struct diag_hsic_buf_tbl_t *item = NULL;
+
+ if (!ch)
+ return;
+
+ /* At this point, the channel should already by closed */
+ spin_lock_irqsave(&ch->lock, flags);
+ list_for_each_safe(start, temp, &ch->buf_tbl) {
+ item = list_entry(start, struct diag_hsic_buf_tbl_t,
+ link);
+ list_del(&item->link);
+ kfree(item);
+
+ }
+ spin_unlock_irqrestore(&ch->lock, flags);
+}
+
+static void diag_hsic_read_complete(void *ctxt, char *buf, int len,
+ int actual_size)
+{
+ int index = (int)(uintptr_t)ctxt;
+ struct diag_hsic_info *ch = NULL;
+
+ if (index < 0 || index >= NUM_HSIC_DEV) {
+ pr_err_ratelimited("diag: In %s, invalid HSIC index %d\n",
+ __func__, index);
+ return;
+ }
+ ch = &diag_hsic[index];
+
+ /*
+ * Don't pass on the buffer if the channel is closed when a pending read
+ * completes. Also, actual size can be negative error codes - do not
+ * pass on the buffer.
+ */
+ if (!ch->opened || actual_size <= 0)
+ goto fail;
+ hsic_buf_tbl_push(ch, buf, actual_size);
+ queue_work(ch->hsic_wq, &ch->read_complete_work);
+ return;
+
+fail:
+ diagmem_free(driver, buf, ch->mempool);
+ queue_work(ch->hsic_wq, &ch->read_work);
+}
+
+static void diag_hsic_write_complete(void *ctxt, char *buf, int len,
+ int actual_size)
+{
+ int index = (int)(uintptr_t)ctxt;
+ struct diag_hsic_info *ch = NULL;
+
+ if (index < 0 || index >= NUM_HSIC_DEV) {
+ pr_err_ratelimited("diag: In %s, invalid HSIC index %d\n",
+ __func__, index);
+ return;
+ }
+
+ ch = &diag_hsic[index];
+ diag_remote_dev_write_done(ch->dev_id, buf, actual_size, ch->id);
+}
+
+static int diag_hsic_suspend(void *ctxt)
+{
+ int index = (int)(uintptr_t)ctxt;
+ unsigned long flags;
+ struct diag_hsic_info *ch = NULL;
+
+ if (index < 0 || index >= NUM_HSIC_DEV) {
+ pr_err_ratelimited("diag: In %s, invalid HSIC index %d\n",
+ __func__, index);
+ return -EINVAL;
+ }
+
+ ch = &diag_hsic[index];
+ spin_lock_irqsave(&ch->lock, flags);
+ ch->suspended = 1;
+ spin_unlock_irqrestore(&ch->lock, flags);
+ return 0;
+}
+
+static void diag_hsic_resume(void *ctxt)
+{
+ int index = (int)(uintptr_t)ctxt;
+ unsigned long flags;
+ struct diag_hsic_info *ch = NULL;
+
+ if (index < 0 || index >= NUM_HSIC_DEV) {
+ pr_err_ratelimited("diag: In %s, invalid HSIC index %d\n",
+ __func__, index);
+ return;
+ }
+ ch = &diag_hsic[index];
+ spin_lock_irqsave(&ch->lock, flags);
+ ch->suspended = 0;
+ spin_unlock_irqrestore(&ch->lock, flags);
+ queue_work(ch->hsic_wq, &(ch->read_work));
+}
+
+static struct diag_bridge_ops diag_hsic_ops[NUM_HSIC_DEV] = {
+ {
+ .ctxt = (void *)HSIC_1,
+ .read_complete_cb = diag_hsic_read_complete,
+ .write_complete_cb = diag_hsic_write_complete,
+ .suspend = diag_hsic_suspend,
+ .resume = diag_hsic_resume,
+ },
+ {
+ .ctxt = (void *)HSIC_2,
+ .read_complete_cb = diag_hsic_read_complete,
+ .write_complete_cb = diag_hsic_write_complete,
+ .suspend = diag_hsic_suspend,
+ .resume = diag_hsic_resume,
+ }
+};
+
+static int hsic_open(int id)
+{
+ int err = 0;
+ unsigned long flags;
+ struct diag_hsic_info *ch = NULL;
+
+ if (id < 0 || id >= NUM_HSIC_DEV) {
+ pr_err("diag: Invalid index %d in %s\n", id, __func__);
+ return -EINVAL;
+ }
+
+ ch = &diag_hsic[id];
+ if (!ch->enabled)
+ return -ENODEV;
+
+ if (ch->opened) {
+ pr_debug("diag: HSIC channel %d is already opened\n", ch->id);
+ return -ENODEV;
+ }
+
+ err = diag_bridge_open(ch->id, &diag_hsic_ops[ch->id]);
+ if (err) {
+ pr_err("diag: Unable to open HSIC channel %d, err: %d",
+ ch->id, err);
+ return err;
+ }
+ spin_lock_irqsave(&ch->lock, flags);
+ ch->opened = 1;
+ spin_unlock_irqrestore(&ch->lock, flags);
+ diagmem_init(driver, ch->mempool);
+ /* Notify the bridge that the channel is open */
+ diag_remote_dev_open(ch->dev_id);
+ INIT_LIST_HEAD(&ch->buf_tbl);
+ queue_work(ch->hsic_wq, &(ch->read_work));
+ return 0;
+}
+
+static void hsic_open_work_fn(struct work_struct *work)
+{
+ struct diag_hsic_info *ch = container_of(work, struct diag_hsic_info,
+ open_work);
+ if (ch)
+ hsic_open(ch->id);
+}
+
+static int hsic_close(int id)
+{
+ unsigned long flags;
+ struct diag_hsic_info *ch = NULL;
+
+ if (id < 0 || id >= NUM_HSIC_DEV) {
+ pr_err("diag: Invalid index %d in %s\n", id, __func__);
+ return -EINVAL;
+ }
+
+ ch = &diag_hsic[id];
+ if (!ch->enabled)
+ return -ENODEV;
+
+ if (!ch->opened) {
+ pr_debug("diag: HSIC channel %d is already closed\n", ch->id);
+ return -ENODEV;
+ }
+
+ spin_lock_irqsave(&ch->lock, flags);
+ ch->opened = 0;
+ spin_unlock_irqrestore(&ch->lock, flags);
+ diag_bridge_close(ch->id);
+ diagmem_exit(driver, ch->mempool);
+ diag_remote_dev_close(ch->dev_id);
+ hsic_buf_tbl_clear(ch);
+ return 0;
+}
+
+static void hsic_close_work_fn(struct work_struct *work)
+{
+ struct diag_hsic_info *ch = container_of(work, struct diag_hsic_info,
+ close_work);
+ if (ch)
+ hsic_close(ch->id);
+}
+
+static void hsic_read_work_fn(struct work_struct *work)
+{
+ int err = 0;
+ unsigned char *buf = NULL;
+ struct diag_hsic_info *ch = container_of(work, struct diag_hsic_info,
+ read_work);
+ if (!ch || !ch->enabled || !ch->opened)
+ return;
+
+ do {
+ buf = diagmem_alloc(driver, DIAG_MDM_BUF_SIZE, ch->mempool);
+ if (!buf) {
+ err = -ENOMEM;
+ break;
+ }
+
+ err = diag_bridge_read(ch->id, buf, DIAG_MDM_BUF_SIZE);
+ if (err) {
+ diagmem_free(driver, buf, ch->mempool);
+ pr_err_ratelimited("diag: Unable to read from HSIC channel %d, err: %d\n",
+ ch->id, err);
+ break;
+ }
+ } while (buf);
+
+ /* Read from the HSIC channel continuously if the channel is present */
+ if (!err)
+ queue_work(ch->hsic_wq, &ch->read_work);
+}
+
+static void hsic_read_complete_work_fn(struct work_struct *work)
+{
+ struct diag_hsic_info *ch = container_of(work, struct diag_hsic_info,
+ read_complete_work);
+ struct diag_hsic_buf_tbl_t *item;
+
+ do {
+ item = hsic_buf_tbl_pop(ch);
+ if (item) {
+ if (diag_remote_dev_read_done(ch->dev_id,
+ item->buf, item->len))
+ goto fail;
+ kfree(item);
+ }
+ } while (item);
+
+ return;
+
+fail:
+ diagmem_free(driver, item->buf, ch->mempool);
+ queue_work(ch->hsic_wq, &ch->read_work);
+ kfree(item);
+}
+
+static int diag_hsic_probe(struct platform_device *pdev)
+{
+ unsigned long flags;
+ struct diag_hsic_info *ch = NULL;
+
+ if (!pdev)
+ return -EIO;
+
+ pr_debug("diag: hsic probe pdev: %d\n", pdev->id);
+ if (pdev->id >= NUM_HSIC_DEV) {
+ pr_err("diag: No support for HSIC device %d\n", pdev->id);
+ return -EIO;
+ }
+
+ ch = &diag_hsic[pdev->id];
+ if (!ch->enabled) {
+ spin_lock_irqsave(&ch->lock, flags);
+ ch->enabled = 1;
+ spin_unlock_irqrestore(&ch->lock, flags);
+ }
+ queue_work(ch->hsic_wq, &(ch->open_work));
+ return 0;
+}
+
+static int diag_hsic_remove(struct platform_device *pdev)
+{
+ struct diag_hsic_info *ch = NULL;
+
+ if (!pdev)
+ return -EIO;
+
+ pr_debug("diag: hsic close pdev: %d\n", pdev->id);
+ if (pdev->id >= NUM_HSIC_DEV) {
+ pr_err("diag: No support for HSIC device %d\n", pdev->id);
+ return -EIO;
+ }
+
+ ch = &diag_hsic[pdev->id];
+ queue_work(ch->hsic_wq, &(ch->close_work));
+ return 0;
+}
+
+static int diagfwd_hsic_runtime_suspend(struct device *dev)
+{
+ dev_dbg(dev, "pm_runtime: suspending...\n");
+ return 0;
+}
+
+static int diagfwd_hsic_runtime_resume(struct device *dev)
+{
+ dev_dbg(dev, "pm_runtime: resuming...\n");
+ return 0;
+}
+
+static const struct dev_pm_ops diagfwd_hsic_dev_pm_ops = {
+ .runtime_suspend = diagfwd_hsic_runtime_suspend,
+ .runtime_resume = diagfwd_hsic_runtime_resume,
+};
+
+static struct platform_driver msm_hsic_ch_driver = {
+ .probe = diag_hsic_probe,
+ .remove = diag_hsic_remove,
+ .driver = {
+ .name = "diag_bridge",
+ .owner = THIS_MODULE,
+ .pm = &diagfwd_hsic_dev_pm_ops,
+ },
+};
+
+static int hsic_queue_read(int id)
+{
+ if (id < 0 || id >= NUM_HSIC_DEV) {
+ pr_err_ratelimited("diag: In %s, invalid index %d\n",
+ __func__, id);
+ return -EINVAL;
+ }
+ queue_work(diag_hsic[id].hsic_wq, &(diag_hsic[id].read_work));
+ return 0;
+}
+
+static int hsic_write(int id, unsigned char *buf, int len, int ctxt)
+{
+ int err = 0;
+ struct diag_hsic_info *ch = NULL;
+
+ if (id < 0 || id >= NUM_HSIC_DEV) {
+ pr_err_ratelimited("diag: In %s, invalid index %d\n",
+ __func__, id);
+ return -EINVAL;
+ }
+ if (!buf || len <= 0) {
+ pr_err_ratelimited("diag: In %s, ch %d, invalid buf %pK len %d\n",
+ __func__, id, buf, len);
+ return -EINVAL;
+ }
+
+ ch = &diag_hsic[id];
+ if (!ch->opened || !ch->enabled) {
+ pr_debug_ratelimited("diag: In %s, ch %d is disabled. opened %d enabled: %d\n",
+ __func__, id, ch->opened, ch->enabled);
+ return -EIO;
+ }
+
+ err = diag_bridge_write(ch->id, buf, len);
+ if (err) {
+ pr_err_ratelimited("diag: cannot write to HSIC ch %d, err: %d\n",
+ ch->id, err);
+ }
+ return err;
+}
+
+static int hsic_fwd_complete(int id, unsigned char *buf, int len, int ctxt)
+{
+ if (id < 0 || id >= NUM_HSIC_DEV) {
+ pr_err_ratelimited("diag: In %s, invalid index %d\n",
+ __func__, id);
+ return -EINVAL;
+ }
+ if (!buf)
+ return -EIO;
+ diagmem_free(driver, buf, diag_hsic[id].mempool);
+ queue_work(diag_hsic[id].hsic_wq, &(diag_hsic[id].read_work));
+ return 0;
+}
+
+static struct diag_remote_dev_ops diag_hsic_fwd_ops = {
+ .open = hsic_open,
+ .close = hsic_close,
+ .queue_read = hsic_queue_read,
+ .write = hsic_write,
+ .fwd_complete = hsic_fwd_complete,
+};
+
+int diag_hsic_init(void)
+{
+ int i;
+ int err = 0;
+ struct diag_hsic_info *ch = NULL;
+ char wq_name[DIAG_HSIC_NAME_SZ + DIAG_HSIC_STRING_SZ];
+
+ for (i = 0; i < NUM_HSIC_DEV; i++) {
+ ch = &diag_hsic[i];
+ spin_lock_init(&ch->lock);
+ INIT_WORK(&(ch->read_work), hsic_read_work_fn);
+ INIT_WORK(&(ch->read_complete_work),
+ hsic_read_complete_work_fn);
+ INIT_WORK(&(ch->open_work), hsic_open_work_fn);
+ INIT_WORK(&(ch->close_work), hsic_close_work_fn);
+ strlcpy(wq_name, "DIAG_HSIC_", DIAG_HSIC_STRING_SZ);
+ strlcat(wq_name, ch->name, sizeof(ch->name));
+ ch->hsic_wq = create_singlethread_workqueue(wq_name);
+ if (!ch->hsic_wq)
+ goto fail;
+ err = diagfwd_bridge_register(ch->dev_id, ch->id,
+ &diag_hsic_fwd_ops);
+ if (err) {
+ pr_err("diag: Unable to register HSIC channel %d with bridge, err: %d\n",
+ i, err);
+ goto fail;
+ }
+ }
+
+ err = platform_driver_register(&msm_hsic_ch_driver);
+ if (err) {
+ pr_err("diag: could not register HSIC device, err: %d\n", err);
+ goto fail;
+ }
+
+ return 0;
+fail:
+ diag_hsic_exit();
+ return -ENOMEM;
+}
+
+void diag_hsic_exit(void)
+{
+ int i;
+ struct diag_hsic_info *ch = NULL;
+
+ for (i = 0; i < NUM_HSIC_DEV; i++) {
+ ch = &diag_hsic[i];
+ ch->enabled = 0;
+ ch->opened = 0;
+ ch->suspended = 0;
+ if (ch->hsic_wq)
+ destroy_workqueue(ch->hsic_wq);
+ }
+ platform_driver_unregister(&msm_hsic_ch_driver);
+}
+
diff --git a/drivers/char/diag/diagfwd_sdio.h b/drivers/char/diag/diagfwd_sdio.h
new file mode 100644
index 000000000000..618bc4e1939d
--- /dev/null
+++ b/drivers/char/diag/diagfwd_sdio.h
@@ -0,0 +1,57 @@
+/* Copyright (c) 2012-2014, 2018 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.
+ */
+
+#ifndef DIAGFWD_HSIC_H
+#define DIAGFWD_HSIC_H
+
+#include <linux/list.h>
+#ifdef CONFIG_DIAG_OVER_USB
+#include <linux/usb/usbdiag.h>
+#endif
+#include <linux/usb/diag_bridge.h>
+
+#define HSIC_1 0
+#define HSIC_2 1
+#define NUM_HSIC_DEV 2
+
+#define DIAG_HSIC_NAME_SZ 24
+
+struct diag_hsic_buf_tbl_t {
+ struct list_head link;
+ unsigned char *buf;
+ int len;
+};
+
+struct diag_hsic_info {
+ int id;
+ int dev_id;
+ int mempool;
+ uint8_t opened;
+ uint8_t enabled;
+ uint8_t suspended;
+ char name[DIAG_HSIC_NAME_SZ];
+ struct work_struct read_work;
+ struct work_struct read_complete_work;
+ struct work_struct open_work;
+ struct work_struct close_work;
+ struct workqueue_struct *hsic_wq;
+ spinlock_t lock;
+ struct list_head buf_tbl;
+};
+
+extern struct diag_hsic_info diag_hsic[NUM_HSIC_DEV];
+
+int diag_hsic_init(void);
+void diag_hsic_exit(void);
+
+#endif
+
diff --git a/drivers/char/qti_sdio_client.c b/drivers/char/qti_sdio_client.c
new file mode 100644
index 000000000000..4904c1be83d0
--- /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 <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/kref.h>
+#include <linux/platform_device.h>
+#include <linux/ratelimit.h>
+#include <linux/uaccess.h>
+#include <linux/usb.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/cdev.h>
+#include <linux/list.h>
+#include <linux/wait.h>
+#include <linux/poll.h>
+#include <linux/cache.h>
+#include <linux/qcn_sdio_al.h>
+#include <linux/of.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/types.h>
+#include <uapi/linux/major.h>
+#include <linux/ipc_logging.h>
+#include <linux/kthread.h>
+#include <linux/completion.h>
+
+#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;
+ DECLARE_COMPLETION_ONSTACK(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) {
+ reinit_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;
+ }
+
+ if (qsb->mode)
+ wait_for_completion(&tx_complete);
+ } 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);
+ }
+
+ 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/drivers/platform/msm/qcn/Kconfig b/drivers/platform/msm/qcn/Kconfig
new file mode 100644
index 000000000000..50f798fa1645
--- /dev/null
+++ b/drivers/platform/msm/qcn/Kconfig
@@ -0,0 +1,8 @@
+config SDIO_QCN
+ tristate "Qualcomm Technologies, Inc SDIO Function 1 Driver"
+ depends on MMC
+ default n
+ help
+ This module adds the support for SDIO based Wi-Fi devices with QCN7605
+ chipsets. This module interfaces the diag, IPC router, Qsahara and
+ WLAN driver with the external sdio based soc.
diff --git a/drivers/platform/msm/qcn/Makefile b/drivers/platform/msm/qcn/Makefile
new file mode 100644
index 000000000000..0e57bd91e799
--- /dev/null
+++ b/drivers/platform/msm/qcn/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_SDIO_QCN) += qcn_sdio.o
diff --git a/drivers/platform/msm/qcn/qcn_sdio.c b/drivers/platform/msm/qcn/qcn_sdio.c
new file mode 100644
index 000000000000..71787acd6940
--- /dev/null
+++ b/drivers/platform/msm/qcn/qcn_sdio.c
@@ -0,0 +1,1122 @@
+/* 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.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/mmc.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/sdio_func.h>
+#include <linux/mmc/sdio_ids.h>
+#include <linux/mmc/sdio.h>
+#include <linux/mmc/sd.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include "qcn_sdio.h"
+
+static bool tx_dump;
+module_param(tx_dump, bool, S_IRUGO | S_IWUSR | S_IWGRP);
+
+static bool rx_dump;
+module_param(rx_dump, bool, S_IRUGO | S_IWUSR | S_IWGRP);
+
+static int dump_len = 32;
+module_param(dump_len, int, S_IRUGO | S_IWUSR | S_IWGRP);
+
+#define HEX_DUMP(mode, buf, len) \
+ print_hex_dump(KERN_ERR, mode, 2, 32, 4, buf, \
+ dump_len > len ? len : dump_len, 0)
+
+struct qcn_sdio {
+ enum qcn_sdio_sw_mode curr_sw_mode;
+ struct sdio_func *func;
+ const struct sdio_device_id *id;
+ struct qcn_sdio_ch_info *ch[QCN_SDIO_CH_MAX];
+ atomic_t ch_status[QCN_SDIO_CH_MAX];
+ spinlock_t lock_free_q;
+ spinlock_t lock_wait_q;
+ u32 rx_addr_base;
+ u32 tx_addr_base;
+ u8 rx_cnum_base;
+ u8 tx_cnum_base;
+ struct qcn_sdio_rw_info rw_req_info[QCN_SDIO_RW_REQ_MAX];
+ struct list_head rw_free_q;
+ struct list_head rw_wait_q;
+ atomic_t free_list_count;
+ atomic_t wait_list_count;
+ struct workqueue_struct *qcn_sdio_wq;
+ struct work_struct sdio_rw_w;
+};
+
+static struct qcn_sdio *sdio_ctxt;
+struct completion client_probe_complete;
+static struct mutex lock;
+static struct list_head cinfo_head;
+static atomic_t status;
+static spinlock_t async_lock;
+
+#if (QCN_SDIO_META_VER_0)
+#define META_INFO(event, data) \
+ ((u32)((u32)data << QCN_SDIO_HMETA_DATA_SHFT) | \
+ (u32)(((u32)event << QCN_SDIO_HMETA_EVENT_SHFT) & \
+ QCN_SDIO_HMETA_EVENT_BMSK) | (u32)(((u32)(sdio_ctxt->curr_sw_mode)\
+ << QCN_SDIO_HMETA_SW_SHFT) & QCN_SDIO_HMETA_SW_BMSK) | \
+ (u32)(QCN_SDIO_HMETA_FMT_VER & QCN_SDIO_HMETA_VER_BMSK))
+#elif (QCN_SDIO_META_VER_1)
+#define META_INFO(even, data) \
+ ((u32)(((u32)event << QCN_SDIO_HMETA_EVENT_SHFT) & \
+ QCN_SDIO_HMETA_EVENT_BMSK) | (u32)(((u32)data << \
+ QCN_SDIO_HMETA_DATA_SHFT) & QCN_SDIO_HMETA_DATA_BMSK))
+#endif
+
+#define SDIO_RW_OFFSET 31
+#define SDIO_RW_MASK 1
+#define SDIO_FUNCTION_OFFSET 28
+#define SDIO_FUNCTION_MASK 7
+#define SDIO_MODE_OFFSET 27
+#define SDIO_MODE_MASK 1
+#define SDIO_OPCODE_OFFSET 26
+#define SDIO_OPCODE_MASK 1
+#define SDIO_ADDRESS_OFFSET 9
+#define SDIO_ADDRESS_MASK 0x1FFFF
+#define SDIO_RAW_OFFSET 27
+#define SDIO_RAW_MASK 1
+#define SDIO_STUFF_OFFSET1 26
+#define SDIO_STUFF_OFFSET2 8
+#define SDIO_STUFF_MASK 1
+#define SDIO_BLOCKSZ_MASK 0x1FF
+#define SDIO_DATA_MASK 0xFF
+
+static inline
+void qcn_sdio_set_cmd53_arg(u32 *arg, u8 rw, u8 func, u8 mode, u8 opcode,
+ u32 addr, u16 blksz)
+{
+ *arg = (((rw & SDIO_RW_MASK) << SDIO_RW_OFFSET) |
+ ((func & SDIO_FUNCTION_MASK) << SDIO_FUNCTION_OFFSET) |
+ ((mode & SDIO_MODE_MASK) << SDIO_MODE_OFFSET) |
+ ((opcode & SDIO_OPCODE_MASK) << SDIO_OPCODE_OFFSET) |
+ ((addr & SDIO_ADDRESS_MASK) << SDIO_ADDRESS_OFFSET) |
+ (blksz & SDIO_BLOCKSZ_MASK));
+}
+
+static inline
+void qcn_sdio_set_cmd52_arg(u32 *arg, u8 rw, u8 func, u8 raw, u32 addr, u8 val)
+{
+ *arg = ((rw & SDIO_RW_MASK) << SDIO_RW_OFFSET) |
+ ((func & SDIO_FUNCTION_MASK) << SDIO_FUNCTION_OFFSET) |
+ ((raw & SDIO_RAW_MASK) << SDIO_RAW_OFFSET) |
+ (SDIO_STUFF_MASK << SDIO_STUFF_OFFSET1) |
+ ((addr & SDIO_ADDRESS_MASK) << SDIO_ADDRESS_OFFSET) |
+ (SDIO_STUFF_MASK << SDIO_STUFF_OFFSET2) |
+ (val & SDIO_DATA_MASK);
+}
+
+static void qcn_sdio_free_rw_req(struct qcn_sdio_rw_info *rw_req)
+{
+ spin_lock(&sdio_ctxt->lock_free_q);
+ list_add_tail(&rw_req->list, &sdio_ctxt->rw_free_q);
+ atomic_inc(&sdio_ctxt->free_list_count);
+ spin_unlock(&sdio_ctxt->lock_free_q);
+}
+
+void qcn_sdio_client_probe_complete(int id)
+{
+ complete(&client_probe_complete);
+}
+EXPORT_SYMBOL(qcn_sdio_client_probe_complete);
+
+static struct qcn_sdio_rw_info *qcn_sdio_alloc_rw_req(void)
+{
+ struct qcn_sdio_rw_info *rw_req = NULL;
+
+ spin_lock(&sdio_ctxt->lock_free_q);
+ if (list_empty(&sdio_ctxt->rw_free_q)) {
+ spin_unlock(&sdio_ctxt->lock_free_q);
+ return rw_req;
+ }
+
+ rw_req = list_first_entry(&sdio_ctxt->rw_free_q,
+ struct qcn_sdio_rw_info, list);
+ list_del(&rw_req->list);
+ atomic_dec(&sdio_ctxt->free_list_count);
+ spin_unlock(&sdio_ctxt->lock_free_q);
+
+ return rw_req;
+}
+
+static void qcn_sdio_add_rw_req(struct qcn_sdio_rw_info *rw_req)
+{
+ spin_lock(&sdio_ctxt->lock_wait_q);
+ list_add_tail(&rw_req->list, &sdio_ctxt->rw_wait_q);
+ atomic_inc(&sdio_ctxt->wait_list_count);
+ spin_unlock(&sdio_ctxt->lock_wait_q);
+}
+
+static int qcn_enable_async_irq(bool enable)
+{
+ unsigned int num = 0;
+ int ret = 0;
+ u32 data = 0;
+
+ num = sdio_ctxt->func->num;
+ sdio_claim_host(sdio_ctxt->func);
+ sdio_ctxt->func->num = 0;
+ data = sdio_readb(sdio_ctxt->func, SDIO_CCCR_INTERRUPT_EXTENSION, NULL);
+ if (enable)
+ data |= SDIO_ENABLE_ASYNC_INTR;
+ else
+ data &= ~SDIO_ENABLE_ASYNC_INTR;
+ sdio_writeb(sdio_ctxt->func, data, SDIO_CCCR_INTERRUPT_EXTENSION, &ret);
+ sdio_ctxt->func->num = num;
+ sdio_release_host(sdio_ctxt->func);
+
+ return ret;
+}
+
+static int qcn_send_io_abort(void)
+{
+ unsigned int num = 0;
+ int ret = 0;
+
+ num = sdio_ctxt->func->num;
+ sdio_claim_host(sdio_ctxt->func);
+ sdio_ctxt->func->num = 0;
+ sdio_writeb(sdio_ctxt->func, 0x1, SDIO_CCCR_ABORT, &ret);
+ sdio_ctxt->func->num = num;
+ sdio_release_host(sdio_ctxt->func);
+
+ return ret;
+}
+
+static int qcn_send_meta_info(u8 event, u32 data)
+{
+ int ret = 0;
+ u32 i = 0;
+ u32 value = 0;
+ u8 temp = 0;
+
+ value = META_INFO(event, data);
+
+ sdio_claim_host(sdio_ctxt->func);
+ if (sdio_ctxt->curr_sw_mode < QCN_SDIO_SW_SBL) {
+ for (i = 0; i < 4; i++) {
+ temp = (u8)((value >> (i * 8)) & 0x000000FF);
+ sdio_writeb(sdio_ctxt->func, temp,
+ (SDIO_QCN_HRQ_PUSH + i), &ret);
+ }
+ } else {
+ sdio_writel(sdio_ctxt->func, value, SDIO_QCN_HRQ_PUSH, &ret);
+ }
+
+ sdio_release_host(sdio_ctxt->func);
+
+ return ret;
+}
+
+static int qcn_read_crq_info(void)
+{
+ int ret = 0;
+ u32 i = 0;
+ u32 temp = 0;
+ u32 data = 0;
+ u32 len = 0;
+ u8 cid = 0;
+
+ struct sdio_al_channel_handle *ch_handle = NULL;
+
+ sdio_claim_host(sdio_ctxt->func);
+ if (sdio_ctxt->curr_sw_mode < QCN_SDIO_SW_SBL) {
+ for (i = 0; i < 4; i++) {
+ temp = sdio_readb(sdio_ctxt->func,
+ (SDIO_QCN_CRQ_PULL + i), &ret);
+ temp = temp << (i * 8);
+ data |= temp;
+ }
+ } else {
+ data = sdio_readl(sdio_ctxt->func, SDIO_QCN_CRQ_PULL, &ret);
+ }
+
+ sdio_release_host(sdio_ctxt->func);
+ if (ret)
+ return ret;
+
+ if (data & SDIO_QCN_CRQ_PULL_TRANS_MASK) {
+ cid = (u8)(data & SDIO_QCN_CRQ_PULL_CH_NUM_MASK);
+ cid -= sdio_ctxt->rx_cnum_base;
+ len = (data & SDIO_QCN_CRQ_PULL_BLK_CNT_MASK) >>
+ SDIO_QCN_CRQ_PULL_BLK_CNT_SHIFT;
+
+ if (data & SDIO_QCN_CRQ_PULL_BLK_MASK)
+ len *= sdio_ctxt->func->cur_blksize;
+ temp = (data & SDIO_QCN_CRQ_PULL_UD_MASK) >>
+ SDIO_QCN_CRQ_PULL_UD_SHIFT;
+
+ if (!sdio_ctxt->ch[cid]) {
+ pr_err("Client Id not initialized\n");
+ return -EINVAL;
+ }
+ switch (temp) {
+ case QCN_SDIO_CRQ_START:
+ sdio_ctxt->ch[cid]->crq_len = len;
+ return ret;
+ case QCN_SDIO_CRQ_END:
+ sdio_ctxt->ch[cid]->crq_len += len;
+ break;
+ default:
+ sdio_ctxt->ch[cid]->crq_len = len;
+ }
+
+ ch_handle = &(sdio_ctxt->ch[cid]->ch_handle);
+ if (sdio_ctxt->ch[cid]->ch_data.dl_data_avail_cb)
+ sdio_ctxt->ch[cid]->ch_data.dl_data_avail_cb(ch_handle,
+ sdio_ctxt->ch[cid]->crq_len);
+ }
+
+ return ret;
+}
+
+static int qcn_sdio_config(struct qcn_sdio_client_info *cinfo)
+{
+ int ret = 0;
+ u32 data = 0;
+
+ sdio_claim_host(sdio_ctxt->func);
+ ret = sdio_set_block_size(sdio_ctxt->func,
+ cinfo->cli_handle.block_size);
+
+ if (ret) {
+ sdio_release_host(sdio_ctxt->func);
+ goto err;
+ }
+
+ data = SDIO_QCN_CONFIG_QE_MASK;
+
+ sdio_writeb(sdio_ctxt->func, (u8)data, SDIO_QCN_CONFIG, &ret);
+ if (ret) {
+ sdio_release_host(sdio_ctxt->func);
+ goto err;
+ }
+
+ data = (SDIO_QCN_IRQ_EN_LOCAL_MASK |
+ SDIO_QCN_IRQ_EN_SYS_ERR_MASK |
+ SDIO_QCN_IRQ_UNDERFLOW_MASK |
+ SDIO_QCN_IRQ_OVERFLOW_MASK |
+ SDIO_QCN_IRQ_CH_MISMATCH_MASK |
+ SDIO_QCN_IRQ_CRQ_READY_MASK);
+
+ sdio_writeb(sdio_ctxt->func, (u8)data, SDIO_QCN_IRQ_EN, &ret);
+ sdio_release_host(sdio_ctxt->func);
+ if (ret) {
+ pr_err("%s: failed write config\n", __func__);
+ goto err;
+ }
+
+ sdio_ctxt->rx_addr_base = SDIO_QCN_MC_DMA0_RX_CH0;
+ sdio_ctxt->rx_cnum_base = QCN_SDIO_DMA0_RX_CNUM;
+ sdio_ctxt->tx_addr_base = SDIO_QCN_MC_DMA1_TX_CH0;
+ sdio_ctxt->tx_cnum_base = QCN_SDIO_DMA1_TX_CNUM;
+
+#if (QCN_SDIO_META_VER_0)
+ data = ((cinfo->cli_handle.block_size / 8) - 1);
+#elif (QCN_SDIO_META_VER_1)
+ data = cinfo->cli_handle.block_size;
+#endif
+ ret = qcn_send_meta_info(QCN_SDIO_BLK_SZ_HEVENT, data);
+err:
+ return ret;
+}
+
+
+int qcn_sw_mode_change(enum qcn_sdio_sw_mode mode)
+{
+ struct qcn_sdio_client_info *cinfo = NULL;
+ struct qcn_sdio_ch_info *chinfo = NULL;
+ int ret = 0;
+
+ if (!(mode) && !(mode < QCN_SDIO_SW_MAX))
+ return -EINVAL;
+
+ if (sdio_ctxt->curr_sw_mode == mode)
+ return 0;
+
+ if ((sdio_ctxt->curr_sw_mode == QCN_SDIO_SW_PBL) &&
+ (mode == QCN_SDIO_SW_SBL)) {
+ sdio_ctxt->curr_sw_mode = QCN_SDIO_SW_SBL;
+ qcn_send_meta_info(QCN_SDIO_BLK_SZ_HEVENT,
+ sdio_ctxt->func->cur_blksize);
+ qcn_send_meta_info(QCN_SDIO_DOORBELL_HEVENT, (u32)0);
+ return 0;
+ }
+
+ switch (sdio_ctxt->curr_sw_mode) {
+ case QCN_SDIO_SW_PBL:
+ case QCN_SDIO_SW_SBL:
+ case QCN_SDIO_SW_RDDM:
+ mutex_lock(&lock);
+ list_for_each_entry(cinfo, &cinfo_head, cli_list) {
+ while (!list_empty(&cinfo->ch_head)) {
+ chinfo = list_first_entry(&cinfo->ch_head,
+ struct qcn_sdio_ch_info, ch_list);
+ sdio_al_deregister_channel(&chinfo->ch_handle);
+ }
+ cinfo->cli_handle.func = NULL;
+
+ if (cinfo->is_probed) {
+ cinfo->cli_data.remove(&cinfo->cli_handle);
+ cinfo->is_probed = 0;
+ }
+ if (((cinfo->cli_handle.id == QCN_SDIO_CLI_ID_WLAN) ||
+ (cinfo->cli_handle.id == QCN_SDIO_CLI_ID_QMI) ||
+ (cinfo->cli_handle.id == QCN_SDIO_CLI_ID_DIAG)) &&
+ (mode == QCN_SDIO_SW_MROM)) {
+ qcn_send_meta_info((u8)QCN_SDIO_SW_MODE_HEVENT,
+ (u32)(mode | QCN_SDIO_MAJOR_VER
+ | QCN_SDIO_MINOR_VER));
+ cinfo->cli_handle.block_size =
+ QCN_SDIO_MROM_BLK_SZ;
+ cinfo->cli_handle.func = sdio_ctxt->func;
+ qcn_sdio_config(cinfo);
+ cinfo->is_probed = !cinfo->cli_data.probe(
+ &cinfo->cli_handle);
+ qcn_send_meta_info(QCN_SDIO_DOORBELL_HEVENT,
+ (u32)0);
+ }
+ }
+ mutex_unlock(&lock);
+ break;
+ case QCN_SDIO_SW_RESET:
+ case QCN_SDIO_SW_MROM:
+ ret = wait_for_completion_timeout(&client_probe_complete,
+ msecs_to_jiffies(3000));
+ if (!ret)
+ pr_err("Timeout waiting for clients\n");
+
+ mutex_lock(&lock);
+ list_for_each_entry(cinfo, &cinfo_head, cli_list) {
+ while (!list_empty(&cinfo->ch_head)) {
+ chinfo = list_first_entry(&cinfo->ch_head,
+ struct qcn_sdio_ch_info, ch_list);
+ sdio_al_deregister_channel(&chinfo->ch_handle);
+ }
+ cinfo->cli_handle.func = NULL;
+
+
+ if (cinfo->is_probed) {
+ cinfo->cli_data.remove(&cinfo->cli_handle);
+ cinfo->is_probed = 0;
+ }
+
+ if ((cinfo->cli_handle.id == QCN_SDIO_CLI_ID_TTY) &&
+ (mode <= QCN_SDIO_SW_MROM)) {
+ qcn_send_meta_info((u8)QCN_SDIO_SW_MODE_HEVENT,
+ (u32)(mode | QCN_SDIO_MAJOR_VER
+ | QCN_SDIO_MINOR_VER));
+ cinfo->cli_handle.block_size =
+ QCN_SDIO_TTY_BLK_SZ;
+ cinfo->cli_handle.func = sdio_ctxt->func;
+ qcn_sdio_config(cinfo);
+ cinfo->is_probed = !cinfo->cli_data.probe(
+ &cinfo->cli_handle);
+ qcn_send_meta_info(QCN_SDIO_DOORBELL_HEVENT,
+ (u32)0);
+ }
+ }
+ mutex_unlock(&lock);
+ break;
+ default:
+ pr_err("Invalid mode\n");
+ }
+
+ sdio_ctxt->curr_sw_mode = mode;
+ return 0;
+}
+
+static int qcn_read_meta_info(void)
+{
+ int ret = 0;
+ u32 i = 0;
+ u32 data = 0;
+ u32 temp = 0;
+
+ sdio_claim_host(sdio_ctxt->func);
+
+ if (sdio_ctxt->curr_sw_mode < QCN_SDIO_SW_SBL) {
+ for (i = 0; i < 4; i++) {
+ temp = sdio_readb(sdio_ctxt->func,
+ (SDIO_QCN_LOCAL_INFO + i), &ret);
+ temp = temp << (i * 8);
+ data |= temp;
+ }
+ } else {
+ data = sdio_readl(sdio_ctxt->func, SDIO_QCN_LOCAL_INFO, &ret);
+ }
+
+ sdio_writeb(sdio_ctxt->func, (u8)SDIO_QCN_IRQ_CLR_LOCAL_MASK,
+ SDIO_QCN_IRQ_CLR, NULL);
+
+ sdio_release_host(sdio_ctxt->func);
+
+ if (ret)
+ return ret;
+
+ temp = (data & QCN_SDIO_LMETA_EVENT_BMSK) >> QCN_SDIO_LMETA_EVENT_SHFT;
+ switch (temp) {
+ case QCN_SDIO_SW_MODE_LEVENT:
+ temp = (data & QCN_SDIO_LMETA_SW_BMSK) >>
+ QCN_SDIO_LMETA_SW_SHFT;
+ qcn_sw_mode_change((enum qcn_sdio_sw_mode)temp);
+ break;
+ default:
+ if ((temp >= QCN_SDIO_META_START_CH0) &&
+ (temp < QCN_SDIO_META_START_CH1)) {
+ if (sdio_ctxt->ch[0] &&
+ sdio_ctxt->ch[0]->ch_data.dl_meta_data_cb)
+ sdio_ctxt->ch[0]->ch_data.dl_meta_data_cb(
+ &(sdio_ctxt->ch[0]->ch_handle), data);
+ } else if ((temp >= QCN_SDIO_META_START_CH1) &&
+ (temp < QCN_SDIO_META_START_CH2)) {
+ if (sdio_ctxt->ch[1] &&
+ sdio_ctxt->ch[1]->ch_data.dl_meta_data_cb)
+ sdio_ctxt->ch[1]->ch_data.dl_meta_data_cb(
+ &(sdio_ctxt->ch[1]->ch_handle), data);
+ } else if ((temp >= QCN_SDIO_META_START_CH2) &&
+ (temp < QCN_SDIO_META_START_CH3)) {
+ if (sdio_ctxt->ch[2] &&
+ sdio_ctxt->ch[2]->ch_data.dl_meta_data_cb)
+ sdio_ctxt->ch[2]->ch_data.dl_meta_data_cb(
+ &(sdio_ctxt->ch[2]->ch_handle), data);
+ } else if ((temp >= QCN_SDIO_META_START_CH3) &&
+ (temp < QCN_SDIO_META_END)) {
+ if (sdio_ctxt->ch[3] &&
+ sdio_ctxt->ch[3]->ch_data.dl_meta_data_cb)
+ sdio_ctxt->ch[3]->ch_data.dl_meta_data_cb(
+ &(sdio_ctxt->ch[3]->ch_handle), data);
+ } else {
+ ret = -EINVAL;
+ }
+ }
+
+ return ret;
+}
+
+static void qcn_sdio_irq_handler(struct sdio_func *func)
+{
+ u8 data = 0;
+
+
+ sdio_claim_host(sdio_ctxt->func);
+ data = sdio_readb(sdio_ctxt->func, SDIO_QCN_IRQ_STATUS, NULL);
+ sdio_release_host(sdio_ctxt->func);
+
+ if (data & SDIO_QCN_IRQ_CRQ_READY_MASK) {
+ qcn_read_crq_info();
+ } else if (data & SDIO_QCN_IRQ_LOCAL_MASK) {
+ qcn_read_meta_info();
+ } else if (data & SDIO_QCN_IRQ_EN_SYS_ERR_MASK) {
+ sdio_claim_host(sdio_ctxt->func);
+ sdio_writeb(sdio_ctxt->func, (u8)SDIO_QCN_IRQ_CLR_SYS_ERR_MASK,
+ SDIO_QCN_IRQ_CLR, NULL);
+ sdio_release_host(sdio_ctxt->func);
+ pr_err("%s: sys_err interrupt triggered\n", __func__);
+ } else if (data & SDIO_QCN_IRQ_EN_UNDERFLOW_MASK) {
+ sdio_claim_host(sdio_ctxt->func);
+ sdio_writeb(sdio_ctxt->func,
+ (u8)SDIO_QCN_IRQ_CLR_UNDERFLOW_MASK,
+ SDIO_QCN_IRQ_CLR, NULL);
+ sdio_release_host(sdio_ctxt->func);
+ pr_err("%s: underflow interrupt triggered\n", __func__);
+ } else if (data & SDIO_QCN_IRQ_EN_OVERFLOW_MASK) {
+ sdio_claim_host(sdio_ctxt->func);
+ sdio_writeb(sdio_ctxt->func, (u8)SDIO_QCN_IRQ_CLR_OVERFLOW_MASK,
+ SDIO_QCN_IRQ_CLR, NULL);
+ sdio_release_host(sdio_ctxt->func);
+ pr_err("%s: overflow interrupt triggered\n", __func__);
+ } else if (data & SDIO_QCN_IRQ_EN_CH_MISMATCH_MASK) {
+ sdio_claim_host(sdio_ctxt->func);
+ sdio_writeb(sdio_ctxt->func,
+ (u8)SDIO_QCN_IRQ_CLR_CH_MISMATCH_MASK,
+ SDIO_QCN_IRQ_CLR, NULL);
+ sdio_release_host(sdio_ctxt->func);
+ pr_err("%s: channel mismatch interrupt triggered\n", __func__);
+ } else {
+ sdio_claim_host(sdio_ctxt->func);
+ sdio_writeb(sdio_ctxt->func, (u8)data, SDIO_QCN_IRQ_CLR, NULL);
+ sdio_release_host(sdio_ctxt->func);
+ }
+}
+
+static int qcn_sdio_send_buff(u32 cid, void *buff, size_t len)
+{
+ int ret = 0;
+
+ sdio_claim_host(sdio_ctxt->func);
+ ret = sdio_writesb(sdio_ctxt->func,
+ (sdio_ctxt->tx_addr_base + (cid * (u32)4)), buff, len);
+
+ if (ret)
+ qcn_send_io_abort();
+
+ sdio_release_host(sdio_ctxt->func);
+
+ return ret;
+}
+
+static int qcn_sdio_recv_buff(u32 cid, void *buff, size_t len)
+{
+ int ret = 0;
+
+ sdio_claim_host(sdio_ctxt->func);
+ ret = sdio_readsb(sdio_ctxt->func, buff,
+ (sdio_ctxt->rx_addr_base + (cid * (u32)4)), len);
+
+ if (ret)
+ qcn_send_io_abort();
+
+ sdio_release_host(sdio_ctxt->func);
+
+ return ret;
+}
+
+static void qcn_sdio_rw_work(struct work_struct *work)
+{
+ int ret = 0;
+ struct qcn_sdio_rw_info *rw_req = NULL;
+ struct sdio_al_xfer_result *result = NULL;
+ struct sdio_al_channel_handle *ch_handle = NULL;
+
+ while (1) {
+ spin_lock(&sdio_ctxt->lock_wait_q);
+ if (list_empty(&sdio_ctxt->rw_wait_q)) {
+ spin_unlock(&sdio_ctxt->lock_wait_q);
+ break;
+ }
+ rw_req = list_first_entry(&sdio_ctxt->rw_wait_q,
+ struct qcn_sdio_rw_info, list);
+ list_del(&rw_req->list);
+ spin_unlock(&sdio_ctxt->lock_wait_q);
+
+ if (rw_req->dir) {
+ ret = qcn_sdio_recv_buff(rw_req->cid, rw_req->buf,
+ rw_req->len);
+ if (rx_dump)
+ HEX_DUMP("ASYNC_RECV: ", rw_req->buf,
+ rw_req->len);
+ } else {
+ ret = qcn_sdio_send_buff(rw_req->cid, rw_req->buf,
+ rw_req->len);
+ if (tx_dump)
+ HEX_DUMP("ASYNC_SEND: ", rw_req->buf,
+ rw_req->len);
+ }
+
+ ch_handle = &sdio_ctxt->ch[rw_req->cid]->ch_handle;
+ result = &sdio_ctxt->ch[rw_req->cid]->result;
+ result->xfer_status = ret;
+ result->buf_addr = rw_req->buf;
+ result->xfer_len = rw_req->len;
+ if (rw_req->dir)
+ sdio_ctxt->ch[rw_req->cid]->ch_data.dl_xfer_cb(
+ ch_handle, result, rw_req->ctxt);
+ else
+ sdio_ctxt->ch[rw_req->cid]->ch_data.ul_xfer_cb(
+ ch_handle, result, rw_req->ctxt);
+ atomic_set(&sdio_ctxt->ch_status[rw_req->cid], 0);
+ qcn_sdio_free_rw_req(rw_req);
+ atomic_dec(&sdio_ctxt->wait_list_count);
+ }
+}
+
+static
+int qcn_sdio_probe(struct sdio_func *func, const struct sdio_device_id *id)
+{
+ int ret = 0;
+
+ sdio_ctxt = kzalloc(sizeof(struct qcn_sdio), GFP_KERNEL);
+ if (!sdio_ctxt)
+ return -ENOMEM;
+
+ sdio_ctxt->func = func;
+ sdio_ctxt->id = id;
+ sdio_set_drvdata(func, sdio_ctxt);
+ sdio_ctxt->qcn_sdio_wq = create_singlethread_workqueue("qcn_sdio");
+ if (!sdio_ctxt->qcn_sdio_wq) {
+ pr_err("%s: Error: SDIO create wq\n", __func__);
+ goto err;
+ }
+
+ for (ret = 0; ret < QCN_SDIO_CH_MAX; ret++) {
+ sdio_ctxt->ch[ret] = NULL;
+ atomic_set(&sdio_ctxt->ch_status[ret], -1);
+ }
+
+ spin_lock_init(&sdio_ctxt->lock_free_q);
+ spin_lock_init(&sdio_ctxt->lock_wait_q);
+ spin_lock_init(&async_lock);
+ INIT_WORK(&sdio_ctxt->sdio_rw_w, qcn_sdio_rw_work);
+ INIT_LIST_HEAD(&sdio_ctxt->rw_free_q);
+ INIT_LIST_HEAD(&sdio_ctxt->rw_wait_q);
+
+ for (ret = 0; ret < QCN_SDIO_RW_REQ_MAX; ret++)
+ qcn_sdio_free_rw_req(&sdio_ctxt->rw_req_info[ret]);
+
+ sdio_claim_host(sdio_ctxt->func);
+ ret = sdio_enable_func(sdio_ctxt->func);
+ if (ret) {
+ pr_err("%s: Error:%d SDIO enable func\n", __func__, ret);
+ sdio_release_host(sdio_ctxt->func);
+ goto err;
+ }
+ ret = sdio_claim_irq(sdio_ctxt->func, qcn_sdio_irq_handler);
+ if (ret) {
+ pr_err("%s: Error:%d SDIO claim irq\n", __func__, ret);
+ sdio_release_host(sdio_ctxt->func);
+ goto err;
+ }
+
+ qcn_enable_async_irq(true);
+ sdio_release_host(sdio_ctxt->func);
+ if (qcn_read_meta_info()) {
+ pr_err("%s: Error: SDIO Config\n", __func__);
+ qcn_send_meta_info((u8)QCN_SDIO_SW_MODE_HEVENT, (u32)0);
+ }
+
+ return 0;
+err:
+ kfree(sdio_ctxt);
+ sdio_ctxt = NULL;
+ return ret;
+}
+
+static void qcn_sdio_remove(struct sdio_func *func)
+{
+ struct qcn_sdio_client_info *cinfo = NULL;
+ struct qcn_sdio_ch_info *ch_info = NULL;
+
+ qcn_enable_async_irq(false);
+ mutex_lock(&lock);
+ list_for_each_entry(cinfo, &cinfo_head, cli_list) {
+ while (!list_empty(&cinfo->ch_head)) {
+ ch_info = list_first_entry(&cinfo->ch_head,
+ struct qcn_sdio_ch_info, ch_list);
+ sdio_al_deregister_channel(&ch_info->ch_handle);
+ }
+ mutex_unlock(&lock);
+ cinfo->cli_data.remove(&cinfo->cli_handle);
+ mutex_lock(&lock);
+ }
+ mutex_unlock(&lock);
+ destroy_workqueue(sdio_ctxt->qcn_sdio_wq);
+ kfree(sdio_ctxt);
+ sdio_ctxt = NULL;
+}
+
+static const struct sdio_device_id qcn_sdio_devices[] = {
+ {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCN_BASE | 0x0))},
+ {},
+};
+
+MODULE_DEVICE_TABLE(sdio, qcn_sdio_devices);
+
+static struct sdio_driver qcn_sdio_driver = {
+ .name = "qcn_sdio",
+ .id_table = qcn_sdio_devices,
+ .probe = qcn_sdio_probe,
+ .remove = qcn_sdio_remove,
+};
+
+static int qcn_sdio_plat_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+
+ mutex_init(&lock);
+ INIT_LIST_HEAD(&cinfo_head);
+ atomic_set(&status, 1);
+
+ ret = sdio_register_driver(&qcn_sdio_driver);
+ if (ret) {
+ pr_err("%s: SDIO driver registration failed: %d\n", __func__,
+ ret);
+ mutex_destroy(&lock);
+ atomic_set(&status, 0);
+ }
+
+ init_completion(&client_probe_complete);
+
+ return ret;
+}
+
+static int qcn_sdio_plat_remove(struct platform_device *pdev)
+{
+ struct qcn_sdio_client_info *cinfo = NULL;
+
+ mutex_lock(&lock);
+ while (!list_empty(&cinfo_head)) {
+ cinfo = list_first_entry(&cinfo_head, struct
+ qcn_sdio_client_info, cli_list);
+ mutex_unlock(&lock);
+ sdio_al_deregister_client(&cinfo->cli_handle);
+ mutex_lock(&lock);
+ list_del(&cinfo->cli_list);
+ }
+ mutex_unlock(&lock);
+ mutex_destroy(&lock);
+ if (sdio_ctxt) {
+ destroy_workqueue(sdio_ctxt->qcn_sdio_wq);
+ sdio_release_irq(sdio_ctxt->func);
+ kfree(sdio_ctxt);
+ sdio_ctxt = NULL;
+ }
+ sdio_unregister_driver(&qcn_sdio_driver);
+ atomic_set(&status, 0);
+
+ return 0;
+}
+
+static const struct of_device_id qcn_sdio_dt_match[] = {
+ {.compatible = "qcom,qcn-sdio"},
+ {}
+};
+MODULE_DEVICE_TABLE(of, qcn_sdio_dt_match);
+
+static struct platform_driver qcn_sdio_plat_driver = {
+ .probe = qcn_sdio_plat_probe,
+ .remove = qcn_sdio_plat_remove,
+ .driver = {
+ .name = "qcn-sdio",
+ .owner = THIS_MODULE,
+ .of_match_table = qcn_sdio_dt_match,
+ },
+};
+
+static int __init qcn_sdio_init(void)
+{
+ return platform_driver_register(&qcn_sdio_plat_driver);
+}
+
+static void __exit qcn_sdio_exit(void)
+{
+ platform_driver_unregister(&qcn_sdio_plat_driver);
+}
+
+module_init(qcn_sdio_init);
+module_exit(qcn_sdio_exit);
+
+int sdio_al_is_ready(void)
+{
+ if (atomic_read(&status))
+ return 0;
+ else
+ return -EBUSY;
+}
+EXPORT_SYMBOL(sdio_al_is_ready);
+
+struct sdio_al_client_handle *sdio_al_register_client(
+ struct sdio_al_client_data *client_data)
+{
+ struct qcn_sdio_client_info *client_info = NULL;
+
+ if (!((client_data) && (client_data->name) &&
+ (client_data->probe) && (client_data->remove))) {
+ pr_err("%s: SDIO: Invalid param\n", __func__);
+ return ERR_PTR(-EINVAL);
+ }
+
+ client_info = (struct qcn_sdio_client_info *)
+ kzalloc(sizeof(struct qcn_sdio_client_info), GFP_KERNEL);
+ if (!client_info)
+ return ERR_PTR(-ENOMEM);
+
+ memcpy(&client_info->cli_data, client_data,
+ sizeof(struct sdio_al_client_data));
+
+ if (!strcmp(client_data->name, "SDIO_AL_CLIENT_TTY")) {
+ client_info->cli_handle.id = QCN_SDIO_CLI_ID_TTY;
+ client_info->cli_handle.block_size = QCN_SDIO_TTY_BLK_SZ;
+ } else if (!strcmp(client_data->name, "SDIO_AL_CLIENT_WLAN")) {
+ client_info->cli_handle.id = QCN_SDIO_CLI_ID_WLAN;
+ client_info->cli_handle.block_size = QCN_SDIO_MROM_BLK_SZ;
+ } else if (!strcmp(client_data->name, "SDIO_AL_CLIENT_QMI")) {
+ client_info->cli_handle.id = QCN_SDIO_CLI_ID_QMI;
+ client_info->cli_handle.block_size = QCN_SDIO_MROM_BLK_SZ;
+ } else if (!strcmp(client_data->name, "SDIO_AL_CLIENT_DIAG")) {
+ client_info->cli_handle.id = QCN_SDIO_CLI_ID_DIAG;
+ client_info->cli_handle.block_size = QCN_SDIO_MROM_BLK_SZ;
+ } else {
+ pr_err("%s: SDIO: Invalid name\n", __func__);
+ kfree(client_info);
+ return ERR_PTR(-EINVAL);
+ }
+ client_info->cli_handle.client_data = &client_info->cli_data;
+
+ INIT_LIST_HEAD(&client_info->ch_head);
+ mutex_lock(&lock);
+ list_add_tail(&client_info->cli_list, &cinfo_head);
+ mutex_unlock(&lock);
+
+ client_info->is_probed = 0;
+ if ((sdio_ctxt) && (sdio_ctxt->curr_sw_mode)) {
+ if ((sdio_ctxt->curr_sw_mode == QCN_SDIO_SW_MROM) &&
+ (client_info->cli_handle.id > QCN_SDIO_CLI_ID_TTY)) {
+ qcn_sdio_config(client_info);
+ client_info->is_probed = !client_data->probe(
+ &client_info->cli_handle);
+ qcn_send_meta_info(QCN_SDIO_DOORBELL_HEVENT, (u32)0);
+ }
+ }
+
+ return &client_info->cli_handle;
+}
+EXPORT_SYMBOL(sdio_al_register_client);
+
+void sdio_al_deregister_client(struct sdio_al_client_handle *handle)
+{
+ struct qcn_sdio_ch_info *ch_info = NULL;
+ struct qcn_sdio_client_info *client_info = NULL;
+
+ if (!handle) {
+ pr_err("%s: SDIO: Invalid param\n", __func__);
+ return;
+ }
+
+ client_info = container_of(handle, struct qcn_sdio_client_info,
+ cli_handle);
+
+ while (!list_empty(&client_info->ch_head)) {
+ ch_info = list_first_entry(&client_info->ch_head,
+ struct qcn_sdio_ch_info, ch_list);
+ sdio_al_deregister_channel(&ch_info->ch_handle);
+ }
+ mutex_lock(&lock);
+ list_del(&client_info->cli_list);
+ kfree(client_info);
+ mutex_unlock(&lock);
+}
+EXPORT_SYMBOL(sdio_al_deregister_client);
+
+struct sdio_al_channel_handle *sdio_al_register_channel(
+ struct sdio_al_client_handle *client_handle,
+ struct sdio_al_channel_data *channel_data)
+{
+ struct qcn_sdio_ch_info *ch_info = NULL;
+ struct qcn_sdio_client_info *client_info = NULL;
+
+ if (!((channel_data) && (channel_data->name) && (client_handle) &&
+ (channel_data->client_data))) {
+ pr_err("%s: SDIO: Invalid param\n", __func__);
+ return ERR_PTR(-EINVAL);
+ }
+
+ ch_info = kzalloc(sizeof(struct qcn_sdio_client_info), GFP_KERNEL);
+ if (!ch_info)
+ return ERR_PTR(-ENOMEM);
+
+ memcpy(&ch_info->ch_data, channel_data,
+ sizeof(struct sdio_al_channel_data));
+
+ if ((!strcmp(channel_data->name, "SDIO_AL_WLAN_CH0")) ||
+ (!strcmp(channel_data->name, "SDIO_AL_TTY_CH0"))) {
+ if (atomic_read(&sdio_ctxt->ch_status[QCN_SDIO_CH_0]) < 0)
+ ch_info->ch_handle.channel_id = QCN_SDIO_CH_0;
+ } else if (!strcmp(channel_data->name, "SDIO_AL_WLAN_CH1")) {
+ if (atomic_read(&sdio_ctxt->ch_status[QCN_SDIO_CH_1]) < 0)
+ ch_info->ch_handle.channel_id = QCN_SDIO_CH_1;
+ } else if (!strcmp(channel_data->name, "SDIO_AL_QMI_CH0")) {
+ if (atomic_read(&sdio_ctxt->ch_status[QCN_SDIO_CH_2]) < 0)
+ ch_info->ch_handle.channel_id = QCN_SDIO_CH_2;
+ } else if (!strcmp(channel_data->name, "SDIO_AL_DIAG_CH0")) {
+ if (atomic_read(&sdio_ctxt->ch_status[QCN_SDIO_CH_3]) < 0)
+ ch_info->ch_handle.channel_id = QCN_SDIO_CH_3;
+ } else {
+ pr_err("%s: SDIO: Invalid CH name: %s\n", __func__,
+ channel_data->name);
+ kfree(ch_info);
+ return ERR_PTR(-EINVAL);
+ }
+
+ client_info = container_of(client_handle, struct qcn_sdio_client_info,
+ cli_handle);
+ ch_info->ch_handle.channel_data = &ch_info->ch_data;
+ ch_info->chandle = &client_info->cli_handle;
+ list_add_tail(&ch_info->ch_list, &client_info->ch_head);
+ sdio_ctxt->ch[ch_info->ch_handle.channel_id] = ch_info;
+ atomic_set(&sdio_ctxt->ch_status[ch_info->ch_handle.channel_id], 0);
+
+ return &ch_info->ch_handle;
+}
+EXPORT_SYMBOL(sdio_al_register_channel);
+
+void sdio_al_deregister_channel(struct sdio_al_channel_handle *ch_handle)
+{
+ int ret = 0;
+ struct qcn_sdio_ch_info *ch_info = NULL;
+
+ if (!ch_handle) {
+ pr_err("%s: Error: Invalid Param\n", __func__);
+ return;
+ }
+
+ do {
+ ret = atomic_cmpxchg(
+ &sdio_ctxt->ch_status[ch_handle->channel_id], 0, 1);
+ if (ret) {
+ if (ret == -1)
+ return;
+
+ usleep_range(1000, 1500);
+ }
+ } while (ret);
+
+ ch_info = sdio_ctxt->ch[ch_handle->channel_id];
+ if (ch_info) {
+ list_del(&ch_info->ch_list);
+ sdio_ctxt->ch[ch_handle->channel_id] = NULL;
+ atomic_set(&sdio_ctxt->ch_status[ch_handle->channel_id], -1);
+ kfree(ch_info);
+ }
+}
+EXPORT_SYMBOL(sdio_al_deregister_channel);
+
+int sdio_al_queue_transfer_async(struct sdio_al_channel_handle *handle,
+ enum sdio_al_dma_direction dir,
+ void *buf, size_t len, int priority, void *ctxt)
+{
+ struct qcn_sdio_rw_info *rw_req = NULL;
+ u32 cid = QCN_SDIO_CH_MAX;
+
+ if (!handle) {
+ pr_err("%s: Error: Invalid Param\n", __func__);
+ return -EINVAL;
+ }
+
+ cid = handle->channel_id;
+
+ if (!(cid < QCN_SDIO_CH_MAX) &&
+ (atomic_read(&sdio_ctxt->ch_status[cid]) < 0))
+ return -EINVAL;
+
+ if (dir == SDIO_AL_TX && atomic_read(&sdio_ctxt->free_list_count) <= 8)
+ return -ENOMEM;
+
+ rw_req = qcn_sdio_alloc_rw_req();
+ if (!rw_req)
+ return -ENOMEM;
+
+ rw_req->cid = cid;
+ rw_req->dir = dir;
+ rw_req->buf = buf;
+ rw_req->len = len;
+ rw_req->ctxt = ctxt;
+
+ if (dir == SDIO_AL_RX)
+ spin_lock(&async_lock);
+
+ qcn_sdio_add_rw_req(rw_req);
+ queue_work(sdio_ctxt->qcn_sdio_wq, &sdio_ctxt->sdio_rw_w);
+
+ if (dir == SDIO_AL_RX)
+ spin_unlock(&async_lock);
+
+ return 0;
+}
+EXPORT_SYMBOL(sdio_al_queue_transfer_async);
+
+int sdio_al_queue_transfer(struct sdio_al_channel_handle *ch_handle,
+ enum sdio_al_dma_direction dir,
+ void *buf, size_t len, int priority)
+{
+ int ret = 0;
+ u32 cid = QCN_SDIO_CH_MAX;
+
+ if (!ch_handle) {
+ pr_err("%s: SDIO: Invalid Param\n", __func__);
+ return -EINVAL;
+ }
+
+ if (dir == SDIO_AL_RX && !list_empty(&sdio_ctxt->rw_wait_q) &&
+ !atomic_read(&sdio_ctxt->wait_list_count)) {
+ sdio_al_queue_transfer_async(ch_handle, dir, buf, len, true,
+ (void *)(uintptr_t)len);
+ pr_info("%s: switching to async\n", __func__);
+ ret = 1;
+ } else {
+ cid = ch_handle->channel_id;
+
+ if (!(cid < QCN_SDIO_CH_MAX))
+ return -EINVAL;
+
+ if (dir == SDIO_AL_RX) {
+ if (!atomic_read(&sdio_ctxt->wait_list_count))
+ ret = qcn_sdio_recv_buff(cid, buf, len);
+ else {
+ sdio_al_queue_transfer_async(ch_handle, dir,
+ buf, len, true, (void *)(uintptr_t)len);
+ pr_info("%s switching to async\n", __func__);
+ ret = 1;
+ }
+
+ if (rx_dump)
+ HEX_DUMP("SYNC_RECV: ", buf, len);
+ } else if (dir == SDIO_AL_TX) {
+ ret = qcn_sdio_send_buff(cid, buf, len);
+ if (tx_dump)
+ HEX_DUMP("SYNC_SEND: ", buf, len);
+ } else
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL(sdio_al_queue_transfer);
+
+int sdio_al_meta_transfer(struct sdio_al_channel_handle *handle,
+ unsigned int data, unsigned int trans)
+{
+ u32 cid = QCN_SDIO_CH_MAX;
+ u8 event = 0;
+
+ if (!handle)
+ return -EINVAL;
+
+ cid = handle->channel_id;
+
+ if (!(cid < QCN_SDIO_CH_MAX))
+ return -EINVAL;
+
+ event = (u8)((data & QCN_SDIO_HMETA_EVENT_BMSK) >>
+ QCN_SDIO_HMETA_EVENT_SHFT);
+
+ if (cid == QCN_SDIO_CH_0) {
+ if ((event < QCN_SDIO_META_START_CH0) &&
+ (event >= QCN_SDIO_META_START_CH1)) {
+ return -EINVAL;
+ }
+ } else if (cid == QCN_SDIO_CH_1) {
+ if ((event < QCN_SDIO_META_START_CH1) &&
+ (event >= QCN_SDIO_META_START_CH2)) {
+ return -EINVAL;
+ }
+ } else if (cid == QCN_SDIO_CH_2) {
+ if ((event < QCN_SDIO_META_START_CH2) &&
+ (event >= QCN_SDIO_META_START_CH3)) {
+ return -EINVAL;
+ }
+ } else if (cid == QCN_SDIO_CH_3) {
+ if ((event < QCN_SDIO_META_START_CH3) &&
+ (event >= QCN_SDIO_META_END)) {
+ return -EINVAL;
+ }
+ }
+
+ return qcn_send_meta_info(event, data);
+}
+EXPORT_SYMBOL(sdio_al_meta_transfer);
diff --git a/drivers/platform/msm/qcn/qcn_sdio.h b/drivers/platform/msm/qcn/qcn_sdio.h
new file mode 100644
index 000000000000..1a63e8e08e0c
--- /dev/null
+++ b/drivers/platform/msm/qcn/qcn_sdio.h
@@ -0,0 +1,175 @@
+/* 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.
+ */
+
+#ifndef _QCN_SDIO_H_
+#define _QCN_SDIO_H_
+
+#include <linux/qcn_sdio_al.h>
+#include "qcn_sdio_hwio.h"
+
+
+#define MANUFACTURER_CODE (0x70)
+#define MANUFACTURER_ID_QCN_BASE (0x400B)
+#define QCN_SDIO_TTY_BLK_SZ (512)
+#define QCN_SDIO_MROM_BLK_SZ (512)
+#define QCN_SDIO_RW_REQ_MAX (128)
+
+#define QCN_SDIO_DMA0_RX_CNUM (0x4)
+#define QCN_SDIO_DMA0_TX_CNUM (0xC)
+#define QCN_SDIO_DMA1_RX_CNUM (0x14)
+#define QCN_SDIO_DMA1_TX_CNUM (0x1C)
+
+#define QCN_SDIO_CRQ_START (0x1)
+#define QCN_SDIO_CRQ_END (0x2)
+
+#define QCN_SDIO_META_VER_0 (0)
+#define QCN_SDIO_META_VER_1 (1)
+
+#if (QCN_SDIO_META_VER_0)
+#define QCN_SDIO_LMETA_FMT_VER (0)
+#define QCN_SDIO_LMETA_VER_BMSK (0x0000000F)
+#define QCN_SDIO_LMETA_VER_SHFT (0)
+#define QCN_SDIO_LMETA_SW_BMSK (0x000000F0)
+#define QCN_SDIO_LMETA_SW_SHFT (4)
+#define QCN_SDIO_LMETA_EVENT_BMSK (0x0000FF00)
+#define QCN_SDIO_LMETA_EVENT_SHFT (8)
+#define QCN_SDIO_LMETA_DATA_BMSK (0xFFFF0000)
+#define QCN_SDIO_LMETA_DATA_SHFT (16)
+#define QCN_SDIO_LMETA_TREG_BMSK (0x00F00000)
+#define QCN_SDIO_LMETA_TREG_SHFT (20)
+#define QCN_SDIO_LMETA_TLEN_BMSK (0x000F0000)
+#define QCN_SDIO_LMETA_TLEN_SHFT (16)
+
+#define QCN_SDIO_HMETA_FMT_VER (0)
+#define QCN_SDIO_HMETA_VER_BMSK (0x0000000F)
+#define QCN_SDIO_HMETA_VER_SHFT (0)
+#define QCN_SDIO_HMETA_SW_BMSK (0x000000F0)
+#define QCN_SDIO_HMETA_SW_SHFT (4)
+#define QCN_SDIO_HMETA_EVENT_BMSK (0x00003F00)
+#define QCN_SDIO_HMETA_EVENT_SHFT (8)
+#define QCN_SDIO_HMETA_TRANS_BMSK (0x00400000)
+#define QCN_SDIO_HMETA_TRANS_SHFT (22)
+#define QCN_SDIO_HMETA_DATA_BMSK (0xFF000000)
+#define QCN_SDIO_HMETA_DATA_SHFT (24)
+#define QCN_SDIO_HMETA_TREG_BMSK (0xF0000000)
+#define QCN_SDIO_HMETA_TREG_SHFT (28)
+#define QCN_SDIO_HMETA_TLEN_BMSK (0x0F000000)
+#define QCN_SDIO_HMETA_TLEN_SHFT (24)
+
+#elif (QCN_SDIO_META_VER_1)
+
+#define QCN_SDIO_MAJOR_VER (0x00000000)
+#define QCN_SDIO_MINOR_VER (0x00000010)
+
+#define QCN_SDIO_LMETA_FMT_VER (1)
+#define QCN_SDIO_LMETA_EVENT_BMSK (0xFF000000)
+#define QCN_SDIO_LMETA_EVENT_SHFT (24)
+#define QCN_SDIO_LMETA_DATA_BMSK (0x00003FFF)
+#define QCN_SDIO_LMETA_DATA_SHFT (0)
+#define QCN_SDIO_LMETA_SW_BMSK (0x0000000F)
+#define QCN_SDIO_LMETA_SW_SHFT (0)
+#define QCN_SDIO_LMETA_VER_MAJ_BMSK (0x000000F0)
+#define QCN_SDIO_LMETA_VER_MAJ_SHFT (4)
+#define QCN_SDIO_LMETA_VER_MIN_BMSK (0x00000F00)
+#define QCN_SDIO_LMETA_VER_MIN_SHFT (8)
+
+#define QCN_SDIO_HMETA_FMT_VER (1)
+#define QCN_SDIO_HMETA_EVENT_BMSK (0xFF000000)
+#define QCN_SDIO_HMETA_EVENT_SHFT (24)
+#define QCN_SDIO_HMETA_DATA_BMSK (0x00003FFF)
+#define QCN_SDIO_HMETA_DATA_SHFT (0)
+#define QCN_SDIO_HMETA_TRANS_BMSK (0x00400000)
+#define QCN_SDIO_HMETA_TRANS_SHFT (22)
+#define QCN_SDIO_HMETA_SW_BMSK (0x0000000F)
+#define QCN_SDIO_HMETA_SW_SHFT (0)
+#define QCN_SDIO_HMETA_VER_MAJ_BMSK (0x000000F0)
+#define QCN_SDIO_HMETA_VER_MAJ_SHFT (4)
+#define QCN_SDIO_HMETA_VER_MIN_BMSK (0x00000F00)
+#define QCN_SDIO_HMETA_VER_MIN_SHFT (8)
+
+#define QCN_SDIO_META_START_CH0 (0x20)
+#define QCN_SDIO_META_START_CH1 (0x40)
+#define QCN_SDIO_META_START_CH2 (0x60)
+#define QCN_SDIO_META_START_CH3 (0x80)
+#define QCN_SDIO_META_END (0xA0)
+
+#endif /* QCN_SDIO_META_VER */
+
+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
+};
+
+enum qcn_sdio_ch_id {
+ QCN_SDIO_CH_0 = 0,
+ QCN_SDIO_CH_1,
+ QCN_SDIO_CH_2,
+ QCN_SDIO_CH_3,
+ QCN_SDIO_CH_MAX,
+};
+
+enum qcn_sdio_sw_mode {
+ QCN_SDIO_SW_RESET = 0,
+ QCN_SDIO_SW_PBL,
+ QCN_SDIO_SW_SBL,
+ QCN_SDIO_SW_RDDM,
+ QCN_SDIO_SW_MROM,
+ QCN_SDIO_SW_MAX,
+};
+
+enum qcn_sdio_host_event {
+ QCN_SDIO_INVALID_HEVENT = 0,
+ QCN_SDIO_SW_MODE_HEVENT,
+ QCN_SDIO_BLK_SZ_HEVENT,
+ QCN_SDIO_DOORBELL_HEVENT,
+ QCN_SDIO_MAX_HEVENT = 63,
+};
+
+enum qcn_sdio_local_event {
+ QCN_SDIO_INVALID_LEVENT = 0,
+ QCN_SDIO_SW_MODE_LEVENT,
+ QCN_SDIO_MAX_LEVENT = 255,
+};
+
+
+struct qcn_sdio_client_info {
+ int is_probed;
+ struct sdio_al_client_data cli_data;
+ struct sdio_al_client_handle cli_handle;
+ struct list_head cli_list;
+ struct list_head ch_head;
+};
+
+struct qcn_sdio_ch_info {
+ struct sdio_al_xfer_result result;
+ struct sdio_al_client_handle *chandle;
+ struct sdio_al_channel_data ch_data;
+ struct sdio_al_channel_handle ch_handle;
+ struct list_head ch_list;
+ u32 crq_len;
+};
+
+struct qcn_sdio_rw_info {
+ struct list_head list;
+ u32 cid;
+ enum sdio_al_dma_direction dir;
+ void *buf;
+ size_t len;
+ void *ctxt;
+};
+
+#endif /* _QCN_SDIO_H_ */
+
diff --git a/drivers/platform/msm/qcn/qcn_sdio_hwio.h b/drivers/platform/msm/qcn/qcn_sdio_hwio.h
new file mode 100644
index 000000000000..9ea9b6a666bf
--- /dev/null
+++ b/drivers/platform/msm/qcn/qcn_sdio_hwio.h
@@ -0,0 +1,296 @@
+/* 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.
+ */
+
+#ifndef _QCN_SDIO_HWIO_
+#define _QCN_SDIO_HWIO_
+
+#define SDIO_QCN_DMA0_RX (0x0)
+#define SDIO_QCN_DMA0_RX_MASK (0xffffffff)
+#define SDIO_QCN_DMA0_RX_SHIFT (0)
+
+#define SDIO_QCN_DMA1_RX (0x4)
+#define SDIO_QCN_DMA1_RX_MASK (0xffffffff)
+#define SDIO_QCN_DMA1_RX_SHIFT (0)
+
+#define SDIO_QCN_DMA0_TX (0x8)
+#define SDIO_QCN_DMA0_TX_MASK (0xffffffff)
+#define SDIO_QCN_DMA0_TX_SHIFT (0)
+
+#define SDIO_QCN_DMA1_TX (0xC)
+#define SDIO_QCN_DMA1_TX_MASK (0xffffffff)
+#define SDIO_QCN_DMA1_TX_SHIFT (0)
+
+
+#define SDIO_QCN_MC_DMA0_RX_CH0 (0x10)
+#define SDIO_QCN_MC_DMA0_RX_CH0_MASK (0xffffffff)
+#define SDIO_QCN_MC_DMA0_RX_CH0_SHIFT (0)
+
+#define SDIO_QCN_MC_DMA0_RX_CH1 (0x14)
+#define SDIO_QCN_MC_DMA0_RX_CH1_MASK (0xffffffff)
+#define SDIO_QCN_MC_DMA0_RX_CH1_SHIFT (0)
+
+#define SDIO_QCN_MC_DMA0_RX_CH2 (0x18)
+#define SDIO_QCN_MC_DMA0_RX_CH2_MASK (0xffffffff)
+#define SDIO_QCN_MC_DMA0_RX_CH2_SHIFT (0)
+
+#define SDIO_QCN_MC_DMA0_RX_CH3 (0x1C)
+#define SDIO_QCN_MC_DMA0_RX_CH3_MASK (0xffffffff)
+#define SDIO_QCN_MC_DMA0_RX_CH3_SHIFT (0)
+
+#define SDIO_QCN_MC_DMA0_TX_CH0 (0x30)
+#define SDIO_QCN_MC_DMA0_TX_CH0_MASK (0xffffffff)
+#define SDIO_QCN_MC_DMA0_TX_CH0_SHIFT (0)
+
+#define SDIO_QCN_MC_DMA0_TX_CH1 (0x34)
+#define SDIO_QCN_MC_DMA0_TX_CH1_MASK (0xffffffff)
+#define SDIO_QCN_MC_DMA0_TX_CH1_SHIFT (0)
+
+#define SDIO_QCN_MC_DMA0_TX_CH2 (0x38)
+#define SDIO_QCN_MC_DMA0_TX_CH2_MASK (0xffffffff)
+#define SDIO_QCN_MC_DMA0_TX_CH2_SHIFT (0)
+
+#define SDIO_QCN_MC_DMA0_TX_CH3 (0x3C)
+#define SDIO_QCN_MC_DMA0_TX_CH3_MASK (0xffffffff)
+#define SDIO_QCN_MC_DMA0_TX_CH3_SHIFT (0)
+
+
+#define SDIO_QCN_MC_DMA1_RX_CH0 (0x50)
+#define SDIO_QCN_MC_DMA1_RX_CH0_MASK (0xffffffff)
+#define SDIO_QCN_MC_DMA1_RX_CH0_SHIFT (0)
+
+#define SDIO_QCN_MC_DMA1_RX_CH1 (0x54)
+#define SDIO_QCN_MC_DMA1_RX_CH1_MASK (0xffffffff)
+#define SDIO_QCN_MC_DMA1_RX_CH1_SHIFT (0)
+
+#define SDIO_QCN_MC_DMA1_RX_CH2 (0x58)
+#define SDIO_QCN_MC_DMA1_RX_CH2_MASK (0xffffffff)
+#define SDIO_QCN_MC_DMA1_RX_CH2_SHIFT (0)
+
+#define SDIO_QCN_MC_DMA1_RX_CH3 (0x5C)
+#define SDIO_QCN_MC_DMA1_RX_CH3_MASK (0xffffffff)
+#define SDIO_QCN_MC_DMA1_RX_CH3_SHIFT (0)
+
+#define SDIO_QCN_MC_DMA1_TX_CH0 (0x70)
+#define SDIO_QCN_MC_DMA1_TX_CH0_MASK (0xffffffff)
+#define SDIO_QCN_MC_DMA1_TX_CH0_SHIFT (0)
+
+#define SDIO_QCN_MC_DMA1_TX_CH1 (0x74)
+#define SDIO_QCN_MC_DMA1_TX_CH1_MASK (0xffffffff)
+#define SDIO_QCN_MC_DMA1_TX_CH1_SHIFT (0)
+
+#define SDIO_QCN_MC_DMA1_TX_CH2 (0x78)
+#define SDIO_QCN_MC_DMA1_TX_CH2_MASK (0xffffffff)
+#define SDIO_QCN_MC_DMA1_TX_CH2_SHIFT (0)
+
+#define SDIO_QCN_MC_DMA1_TX_CH3 (0x7C)
+#define SDIO_QCN_MC_DMA1_TX_CH3_MASK (0xffffffff)
+#define SDIO_QCN_MC_DMA1_TX_CH3_SHIFT (0)
+
+
+#define SDIO_QCN_CONFIG (0x100)
+#define SDIO_QCN_CONFIG_OOB_MASK (0x00000002)
+#define SDIO_QCN_CONFIG_OOB_SHIFT (1)
+#define SDIO_QCN_CONFIG_QE_MASK (0x00000001)
+#define SDIO_QCN_CONFIG_QE_SHIFT (0)
+
+
+#define SDIO_QCN_HRQ_PUSH (0x104)
+#define SDIO_QCN_HRQ_PUSH_UD_MASK (0xff000000)
+#define SDIO_QCN_HRQ_PUSH_UD_SHIFT (24)
+#define SDIO_QCN_HRQ_PUSH_BLK_MASK (0x00800000)
+#define SDIO_QCN_HRQ_PUSH_BLK_SHIFT (23)
+#define SDIO_QCN_HRQ_PUSH_TRANS_MASK (0x00400000)
+#define SDIO_QCN_HRQ_PUSH_TRANS_SHIFT (22)
+#define SDIO_QCN_HRQ_PUSH_BLK_CNT_MASK (0x00003ffe)
+#define SDIO_QCN_HRQ_PUSH_BLK_CNT_SHIFT (1)
+#define SDIO_QCN_HRQ_PUSH_DIR_MASK (0x00000001)
+#define SDIO_QCN_HRQ_PUSH_DIR_SHIFT (0)
+#define SDIO_QCN_HRQ_PUSH_UD1_MASK (0xffc00000)
+#define SDIO_QCN_HRQ_PUSH_UD1_SHIFT (22)
+#define SDIO_QCN_HRQ_PUSH_UD0_MASK (0x00003fff)
+#define SDIO_QCN_HRQ_PUSH_UD0_SHIFT (0)
+
+
+#define SDIO_QCN_CRQ_TX_PULL (0x108)
+#define SDIO_QCN_CRQ_TX_PULL_REQ_ID_MASK (0x3c)
+#define SDIO_QCN_CRQ_TX_PULL_REQ_ID_SHIFT (2)
+#define SDIO_QCN_CRQ_TX_PULL_CH_NUM_MASK (0x3)
+#define SDIO_QCN_CRQ_TX_PULL_CH_NUM_SHIFT (0)
+
+#define SDIO_QCN_CRQ_RX_PULL (0x108)
+#define SDIO_QCN_CRQ_RX_PULL_REQ_ID_MASK (0x7ffc)
+#define SDIO_QCN_CRQ_RX_PULL_REQ_ID_SHIFT (2)
+#define SDIO_QCN_CRQ_RX_PULL_CH_NUM_MASK (0x3)
+#define SDIO_QCN_CRQ_RX_PULL_CH_NUM_SHIFT (0)
+
+
+#define SDIO_QCN_IRQ_STATUS (0x10C)
+#define SDIO_QCN_IRQ_LOCAL_MASK (0x80)
+#define SDIO_QCN_IRQ_LOCAL_SHIFT (7)
+#define SDIO_QCN_IRQ_DAM_WACCESS_MASK (0x40)
+#define SDIO_QCN_IRQ_DAM_WACCESS_SHIFT (6)
+#define SDIO_QCN_IRQ_DAM_MISMATCH_MASK (0x20)
+#define SDIO_QCN_IRQ_DAM_MISMATCH_SHIFT (5)
+#define SDIO_QCN_IRQ_SYS_ERR_MASK (0x10)
+#define SDIO_QCN_IRQ_SYS_ERR_SHIFT (4)
+#define SDIO_QCN_IRQ_UNDERFLOW_MASK (0x8)
+#define SDIO_QCN_IRQ_UNDERFLOW_SHIFT (3)
+#define SDIO_QCN_IRQ_OVERFLOW_MASK (0x4)
+#define SDIO_QCN_IRQ_OVERFLOW_SHIFT (2)
+#define SDIO_QCN_IRQ_CH_MISMATCH_MASK (0x2)
+#define SDIO_QCN_IRQ_CH_MISMATCH_SHIFT (1)
+#define SDIO_QCN_IRQ_CRQ_READY_MASK (0x1)
+#define SDIO_QCN_IRQ_CRQ_READY_SHIFT (0)
+
+
+#define SDIO_QCN_IRQ_EN (0x110)
+#define SDIO_QCN_IRQ_EN_LOCAL_MASK (0x80)
+#define SDIO_QCN_IRQ_EN_LOCAL_SHIFT (7)
+#define SDIO_QCN_IRQ_EN_DAM_WACCESS_MASK (0x40)
+#define SDIO_QCN_IRQ_EN_DAM_WACCESS_SHIFT (6)
+#define SDIO_QCN_IRQ_EN_DAM_MISMATCH_MASK (0x20)
+#define SDIO_QCN_IRQ_EN_DAM_MISMATCH_SHIFT (5)
+#define SDIO_QCN_IRQ_EN_SYS_ERR_MASK (0x10)
+#define SDIO_QCN_IRQ_EN_SYS_ERR_SHIFT (4)
+#define SDIO_QCN_IRQ_EN_UNDERFLOW_MASK (0x8)
+#define SDIO_QCN_IRQ_EN_UNDERFLOW_SHIFT (3)
+#define SDIO_QCN_IRQ_EN_OVERFLOW_MASK (0x4)
+#define SDIO_QCN_IRQ_EN_OVERFLOW_SHIFT (2)
+#define SDIO_QCN_IRQ_EN_CH_MISMATCH_MASK (0x2)
+#define SDIO_QCN_IRQ_EN_CH_MISMATCH_SHIFT (1)
+#define SDIO_QCN_IRQ_EN_CRQ_READY_MASK (0x1)
+#define SDIO_QCN_IRQ_EN_CRQ_READY_SHIFT (0)
+
+
+#define SDIO_QCN_IRQ_CLR (0x114)
+#define SDIO_QCN_IRQ_CLR_LOCAL_MASK (0x80)
+#define SDIO_QCN_IRQ_CLR_LOCAL_SHIFT (7)
+#define SDIO_QCN_IRQ_CLR_DAM_WACCESS_MASK (0x40)
+#define SDIO_QCN_IRQ_CLR_DAM_WACCESS_SHIFT (6)
+#define SDIO_QCN_IRQ_CLR_DAM_MISMATCH_MASK (0x20)
+#define SDIO_QCN_IRQ_CLR_DAM_MISMATCH_SHIFT (5)
+#define SDIO_QCN_IRQ_CLR_SYS_ERR_MASK (0x10)
+#define SDIO_QCN_IRQ_CLR_SYS_ERR_SHIFT (4)
+#define SDIO_QCN_IRQ_CLR_UNDERFLOW_MASK (0x8)
+#define SDIO_QCN_IRQ_CLR_UNDERFLOW_SHIFT (3)
+#define SDIO_QCN_IRQ_CLR_OVERFLOW_MASK (0x4)
+#define SDIO_QCN_IRQ_CLR_OVERFLOW_SHIFT (2)
+#define SDIO_QCN_IRQ_CLR_CH_MISMATCH_MASK (0x2)
+#define SDIO_QCN_IRQ_CLR_CH_MISMATCH_SHIFT (1)
+#define SDIO_QCN_IRQ_CLR_CRQ_READY_MASK (0x1)
+#define SDIO_QCN_IRQ_CLR_CRQ_READY_SHIFT (0)
+
+
+#define SDIO_QCN_IRQ_FRC (0x11c)
+#define SDIO_QCN_IRQ_FRC_LOCAL_MASK (0x80)
+#define SDIO_QCN_IRQ_FRC_LOCAL_SHIFT (7)
+#define SDIO_QCN_IRQ_FRC_DAM_WACCESS_MASK (0x40)
+#define SDIO_QCN_IRQ_FRC_DAM_WACCESS_SHIFT (6)
+#define SDIO_QCN_IRQ_FRC_DAM_MISMATCH_MASK (0x20)
+#define SDIO_QCN_IRQ_FRC_DAM_MISMATCH_SHIFT (5)
+#define SDIO_QCN_IRQ_FRC_SYS_ERR_MASK (0x10)
+#define SDIO_QCN_IRQ_FRC_SYS_ERR_SHIFT (4)
+#define SDIO_QCN_IRQ_FRC_UNDERFLOW_MASK (0x8)
+#define SDIO_QCN_IRQ_FRC_UNDERFLOW_SHIFT (3)
+#define SDIO_QCN_IRQ_FRC_OVERFLOW_MASK (0x4)
+#define SDIO_QCN_IRQ_FRC_OVERFLOW_SHIFT (2)
+#define SDIO_QCN_IRQ_FRC_CH_MISMATCH_MASK (0x2)
+#define SDIO_QCN_IRQ_FRC_CH_MISMATCH_SHIFT (1)
+#define SDIO_QCN_IRQ_FRC_CRQ_READY_MASK (0x1)
+#define SDIO_QCN_IRQ_FRC_CRQ_READY_SHIFT (0)
+
+
+#define SDIO_QCN_HRQ_ACK_PULL (0x118)
+#define SDIO_QCN_HRQ_ACK_PULL_REQ_ID_MASK (0x3c0)
+#define SDIO_QCN_HRQ_ACK_PULL_REQ_ID_SHIFT (6)
+#define SDIO_QCN_HRQ_ACK_PULL_CH_NUM_MASK (0x3f)
+#define SDIO_QCN_HRQ_ACK_PULL_CH_NUM_SHIFT (0)
+
+
+#define SDIO_QCN_CRQ_PULL (0x118)
+#define SDIO_QCN_CRQ_PULL_UD_MASK (0xff000000)
+#define SDIO_QCN_CRQ_PULL_UD_SHIFT (24)
+#define SDIO_QCN_CRQ_PULL_BLK_MASK (0x00800000)
+#define SDIO_QCN_CRQ_PULL_BLK_SHIFT (23)
+#define SDIO_QCN_CRQ_PULL_TRANS_MASK (0x00400000)
+#define SDIO_QCN_CRQ_PULL_TRANS_SHIFT (22)
+#define SDIO_QCN_CRQ_PULL_BLK_CNT_MASK (0x0007ffc0)
+#define SDIO_QCN_CRQ_PULL_BLK_CNT_SHIFT (6)
+#define SDIO_QCN_CRQ_PULL_CH_NUM_MASK (0x3f)
+#define SDIO_QCN_CRQ_PULL_CH_NUM_SHIFT (0)
+
+
+#define SDIO_QCN_LOW_PWR (0x120)
+#define SDIO_QCN_LOW_PWR_GO_MASK (0x1)
+#define SDIO_QCN_LOW_PWR_GO_SHIFT (0)
+
+
+#define SDIO_QCN_HOST_TRANS_REG0 (0x124)
+#define SDIO_QCN_HOST_TRANS_REG0_MASK (0xffffffff)
+#define SDIO_QCN_HOST_TRANS_REG0_SHIFT (0)
+#define SDIO_QCN_HOST_TRANS_REG1 (0x128)
+#define SDIO_QCN_HOST_TRANS_REG1_MASK (0xffffffff)
+#define SDIO_QCN_HOST_TRANS_REG1_SHIFT (0)
+#define SDIO_QCN_HOST_TRANS_REG2 (0x12C)
+#define SDIO_QCN_HOST_TRANS_REG2_MASK (0xffffffff)
+#define SDIO_QCN_HOST_TRANS_REG2_SHIFT (0)
+#define SDIO_QCN_HOST_TRANS_REG3 (0x130)
+#define SDIO_QCN_HOST_TRANS_REG3_MASK (0xffffffff)
+#define SDIO_QCN_HOST_TRANS_REG3_SHIFT (0)
+
+
+#define SDIO_QCN_CLIENT_TRANS_REG0 (0x144)
+#define SDIO_QCN_CLIENT_TRANS_REG0_MASK (0xffffffff)
+#define SDIO_QCN_CLIENT_TRANS_REG0_SHIFT (0)
+#define SDIO_QCN_CLIENT_TRANS_REG1 (0x148)
+#define SDIO_QCN_CLIENT_TRANS_REG1_MASK (0xffffffff)
+#define SDIO_QCN_CLIENT_TRANS_REG1_SHIFT (0)
+#define SDIO_QCN_CLIENT_TRANS_REG2 (0x14C)
+#define SDIO_QCN_CLIENT_TRANS_REG2_MASK (0xffffffff)
+#define SDIO_QCN_CLIENT_TRANS_REG2_SHIFT (0)
+#define SDIO_QCN_CLIENT_TRANS_REG3 (0x150)
+#define SDIO_QCN_CLIENT_TRANS_REG3_MASK (0xffffffff)
+#define SDIO_QCN_CLIENT_TRANS_REG3_SHIFT (0)
+
+
+#define SDIO_QCN_LOCAL_INFO (0x00000164)
+#define SDIO_QCN_LOCAL_INFO_MASK (0xffffffff)
+#define SDIO_QCN_LOCAL_INFO_SHIFT (0)
+
+#define SDIO_QCN_SDIOC_CONFIG (0x200)
+#define SDIO_QCN_SDIOC_CONFIG_MC_DMA1_DIR_BMSK (0x40000)
+#define SDIO_QCN_SDIOC_CONFIG_MC_DMA1_DIR_SHFT (0x12)
+#define SDIO_QCN_SDIOC_CONFIG_MC_DMA0_DIR_BMSK (0x20000)
+#define SDIO_QCN_SDIOC_CONFIG_MC_DMA0_DIR_SHFT (0x11)
+#define SDIO_QCN_SDIOC_CONFIG_MC_DMA_EN_BMSK (0x10000)
+#define SDIO_QCN_SDIOC_CONFIG_MC_DMA_EN_SHFT (0x10)
+#define SDIO_QCN_SDIOC_CONFIG_SW_CLK_EN_BMSK (0x80)
+#define SDIO_QCN_SDIOC_CONFIG_SW_CLK_EN_SHFT (0x7)
+#define SDIO_QCN_SDIOC_CONFIG_WR_HRQ_MEM_BMSK (0x40)
+#define SDIO_QCN_SDIOC_CONFIG_WR_HRQ_MEM_SHFT (0x6)
+#define SDIO_QCN_SDIOC_CONFIG_CLR_CRQ_PUSH_BMSK (0x20)
+#define SDIO_QCN_SDIOC_CONFIG_CLR_CRQ_PUSH_SHFT (0x5)
+#define SDIO_QCN_SDIOC_CONFIG_DIS_LEN_CHK_BMSK (0x10)
+#define SDIO_QCN_SDIOC_CONFIG_DIS_LEN_CHK_SHFT (0x4)
+#define SDIO_QCN_SDIOC_CONFIG_WRAP_ERROR_BMSK (0x8)
+#define SDIO_QCN_SDIOC_CONFIG_WRAP_ERROR_SHFT (0x3)
+#define SDIO_QCN_SDIOC_CONFIG_ADMA_64BIT_BMSK (0x4)
+#define SDIO_QCN_SDIOC_CONFIG_ADMA_64BIT_SHFT (0x2)
+#define SDIO_QCN_SDIOC_CONFIG_ADMA_INT_BMSK (0x2)
+#define SDIO_QCN_SDIOC_CONFIG_ADMA_INT_SHFT (0x1)
+#define SDIO_QCN_SDIOC_CONFIG_DMA_ENABLE_BMSK (0x1)
+#define SDIO_QCN_SDIOC_CONFIG_DMA_ENABLE_SHFT (0x0)
+
+
+
+#endif /* _QCN_SDIO_HWIO_ */
+