diff options
| author | Linux Build Service Account <lnxbuild@localhost> | 2019-09-03 14:02:19 -0700 |
|---|---|---|
| committer | Gerrit - the friendly Code Review server <code-review@localhost> | 2019-09-03 14:02:19 -0700 |
| commit | 81a9e55e0863fb3e14be5e6b996e5c0bb795bdfb (patch) | |
| tree | 876fb15a9c254462f71c9dbde3803ad4f4b9bf5e /drivers | |
| parent | a7499d6acea5c581b1f56fc663010d3b90a3c9d8 (diff) | |
| parent | f9d70693889be5024d0e423e5924018300b902af (diff) | |
Merge "diag: Add diag over sdio support"
Diffstat (limited to 'drivers')
| -rw-r--r-- | drivers/char/diag/diagfwd_sdio.c | 536 | ||||
| -rw-r--r-- | drivers/char/diag/diagfwd_sdio.h | 57 | ||||
| -rw-r--r-- | drivers/char/qti_sdio_client.c | 1138 | ||||
| -rw-r--r-- | drivers/platform/msm/qcn/Kconfig | 8 | ||||
| -rw-r--r-- | drivers/platform/msm/qcn/Makefile | 1 | ||||
| -rw-r--r-- | drivers/platform/msm/qcn/qcn_sdio.c | 1122 | ||||
| -rw-r--r-- | drivers/platform/msm/qcn/qcn_sdio.h | 175 | ||||
| -rw-r--r-- | drivers/platform/msm/qcn/qcn_sdio_hwio.h | 296 |
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_ */ + |
