|
@@ -0,0 +1,2705 @@
|
|
|
+/*
|
|
|
+ * Copyright (c) 2013-2016 The Linux Foundation. All rights reserved.
|
|
|
+ *
|
|
|
+ * Previously licensed under the ISC license by Qualcomm Atheros, Inc.
|
|
|
+ *
|
|
|
+ *
|
|
|
+ * Permission to use, copy, modify, and/or distribute this software for
|
|
|
+ * any purpose with or without fee is hereby granted, provided that the
|
|
|
+ * above copyright notice and this permission notice appear in all
|
|
|
+ * copies.
|
|
|
+ *
|
|
|
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
|
|
+ * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
|
|
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
|
|
+ * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
|
|
|
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
|
|
|
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
|
|
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
|
+ * PERFORMANCE OF THIS SOFTWARE.
|
|
|
+ */
|
|
|
+
|
|
|
+/*
|
|
|
+ * This file was originally distributed by Qualcomm Atheros, Inc.
|
|
|
+ * under proprietary terms before Copyright ownership was assigned
|
|
|
+ * to the Linux Foundation.
|
|
|
+ */
|
|
|
+
|
|
|
+#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/kthread.h>
|
|
|
+#include <linux/version.h>
|
|
|
+#include <linux/module.h>
|
|
|
+#include <qdf_atomic.h>
|
|
|
+#include <cds_utils.h>
|
|
|
+#include <qdf_timer.h>
|
|
|
+#include <cds_api.h>
|
|
|
+#include <qdf_time.h>
|
|
|
+#include "hif_sdio_dev.h"
|
|
|
+#include "if_sdio.h"
|
|
|
+#include "regtable_sdio.h"
|
|
|
+#include "wma_api.h"
|
|
|
+#include "hif_internal.h"
|
|
|
+
|
|
|
+/* by default setup a bounce buffer for the data packets,
|
|
|
+ * if the underlying host controller driver
|
|
|
+ * does not use DMA you may be able to skip this step
|
|
|
+ * and save the memory allocation and transfer time */
|
|
|
+#define HIF_USE_DMA_BOUNCE_BUFFER 1
|
|
|
+#define ATH_MODULE_NAME hif
|
|
|
+#include "a_debug.h"
|
|
|
+
|
|
|
+#if HIF_USE_DMA_BOUNCE_BUFFER
|
|
|
+/* macro to check if DMA buffer is WORD-aligned and DMA-able.
|
|
|
+ * Most host controllers assume the
|
|
|
+ * buffer is DMA'able and will bug-check otherwise (i.e. buffers on the stack).
|
|
|
+ * virt_addr_valid check fails on stack memory.
|
|
|
+ */
|
|
|
+#define BUFFER_NEEDS_BOUNCE(buffer) (((unsigned long)(buffer) & 0x3) || \
|
|
|
+ !virt_addr_valid((buffer)))
|
|
|
+#else
|
|
|
+#define BUFFER_NEEDS_BOUNCE(buffer) (false)
|
|
|
+#endif
|
|
|
+#define MAX_HIF_DEVICES 2
|
|
|
+#ifdef HIF_MBOX_SLEEP_WAR
|
|
|
+#define HIF_MIN_SLEEP_INACTIVITY_TIME_MS 50
|
|
|
+#define HIF_SLEEP_DISABLE_UPDATE_DELAY 1
|
|
|
+#define HIF_IS_WRITE_REQUEST_MBOX1_TO_3(request) \
|
|
|
+ ((request->request & HIF_SDIO_WRITE) && \
|
|
|
+ (request->address >= 0x1000 && \
|
|
|
+ request->address < 0x1FFFF))
|
|
|
+#endif
|
|
|
+
|
|
|
+extern struct hif_sdio_softc *sc;
|
|
|
+
|
|
|
+unsigned int mmcbuswidth = 0;
|
|
|
+module_param(mmcbuswidth, uint, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
|
|
+MODULE_PARM_DESC(mmcbuswidth,
|
|
|
+ "Set MMC driver Bus Width: 1-1Bit, 4-4Bit, 8-8Bit");
|
|
|
+
|
|
|
+unsigned int mmcclock = 0;
|
|
|
+module_param(mmcclock, uint, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
|
|
+MODULE_PARM_DESC(mmcclock, "Set MMC driver Clock value");
|
|
|
+
|
|
|
+unsigned int brokenirq = 0;
|
|
|
+module_param(brokenirq, uint, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
|
|
+MODULE_PARM_DESC(brokenirq,
|
|
|
+ "Set as 1 to use polling method instead of interrupt mode");
|
|
|
+
|
|
|
+unsigned int forcesleepmode = 0;
|
|
|
+module_param(forcesleepmode, uint, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
|
|
+MODULE_PARM_DESC(forcesleepmode,
|
|
|
+ "Set sleep mode: 0-host capbility, "
|
|
|
+ "1-force WOW, 2-force DeepSleep, 3-force CutPower");
|
|
|
+
|
|
|
+#ifdef CONFIG_X86
|
|
|
+unsigned int asyncintdelay = 2;
|
|
|
+module_param(asyncintdelay, uint, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
|
|
+MODULE_PARM_DESC(asyncintdelay,
|
|
|
+ "Delay clock count for aysnc interrupt, "
|
|
|
+ "2 is default, vaild values are 1 and 2");
|
|
|
+#else
|
|
|
+unsigned int asyncintdelay = 0;
|
|
|
+module_param(asyncintdelay, uint, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
|
|
+MODULE_PARM_DESC(asyncintdelay,
|
|
|
+ "Delay clock count for aysnc interrupt, "
|
|
|
+ "0 is default, vaild values are 1 and 2");
|
|
|
+#endif
|
|
|
+
|
|
|
+unsigned int forcecard = 0;
|
|
|
+module_param(forcecard, uint, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
|
|
+MODULE_PARM_DESC(forcecard,
|
|
|
+ "Ignore card capabilities information to switch bus mode");
|
|
|
+
|
|
|
+unsigned int debugcccr = 1;
|
|
|
+module_param(debugcccr, uint, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
|
|
+MODULE_PARM_DESC(debugcccr, "Output this cccr values");
|
|
|
+
|
|
|
+unsigned int writecccr1 = 0;
|
|
|
+module_param(writecccr1, uint, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
|
|
+unsigned int writecccr1value = 0;
|
|
|
+module_param(writecccr1value, uint, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
|
|
+
|
|
|
+unsigned int writecccr2 = 0;
|
|
|
+module_param(writecccr2, uint, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
|
|
+unsigned int writecccr2value = 0;
|
|
|
+module_param(writecccr2value, uint, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
|
|
+
|
|
|
+unsigned int writecccr3 = 0;
|
|
|
+module_param(writecccr3, uint, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
|
|
+unsigned int writecccr3value = 0;
|
|
|
+module_param(writecccr3value, uint, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
|
|
+
|
|
|
+unsigned int writecccr4 = 0;
|
|
|
+module_param(writecccr4, uint, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
|
|
+
|
|
|
+unsigned int writecccr4value = 0;
|
|
|
+module_param(writecccr4value, uint, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
|
|
+
|
|
|
+unsigned int modstrength = 0;
|
|
|
+module_param(modstrength, uint, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
|
|
+MODULE_PARM_DESC(modstrength, "Adjust internal driver strength");
|
|
|
+
|
|
|
+#define dev_to_sdio_func(d) container_of(d, struct sdio_func, dev)
|
|
|
+#define to_sdio_driver(d) container_of(d, struct sdio_driver, drv)
|
|
|
+static int hif_device_inserted(struct sdio_func *func,
|
|
|
+ const struct sdio_device_id *id);
|
|
|
+static void hif_device_removed(struct sdio_func *func);
|
|
|
+static struct hif_sdio_dev *add_hif_device(struct sdio_func *func);
|
|
|
+static struct hif_sdio_dev *get_hif_device(struct sdio_func *func);
|
|
|
+static void del_hif_device(struct hif_sdio_dev *device);
|
|
|
+static int func0_cmd52_write_byte(struct mmc_card *card, unsigned int address,
|
|
|
+ unsigned char byte);
|
|
|
+static int func0_cmd52_read_byte(struct mmc_card *card, unsigned int address,
|
|
|
+ unsigned char *byte);
|
|
|
+
|
|
|
+int reset_sdio_on_unload = 0;
|
|
|
+module_param(reset_sdio_on_unload, int, 0644);
|
|
|
+
|
|
|
+uint32_t nohifscattersupport = 1;
|
|
|
+
|
|
|
+uint32_t forcedriverstrength = 1; /* force driver strength to type D */
|
|
|
+
|
|
|
+/* ------ Static Variables ------ */
|
|
|
+static const struct sdio_device_id ar6k_id_table[] = {
|
|
|
+#ifdef AR6002_HEADERS_DEF
|
|
|
+ {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6002_BASE | 0x0))},
|
|
|
+ {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6002_BASE | 0x1))},
|
|
|
+#endif
|
|
|
+#ifdef AR6003_HEADERS_DEF
|
|
|
+ {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6003_BASE | 0x0))},
|
|
|
+ {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6003_BASE | 0x1))},
|
|
|
+#endif
|
|
|
+#ifdef AR6004_HEADERS_DEF
|
|
|
+ {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6004_BASE | 0x0))},
|
|
|
+ {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6004_BASE | 0x1))},
|
|
|
+#endif
|
|
|
+#ifdef AR6320_HEADERS_DEF
|
|
|
+ {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0x0))},
|
|
|
+ {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0x1))},
|
|
|
+ {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0x2))},
|
|
|
+ {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0x3))},
|
|
|
+ {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0x4))},
|
|
|
+ {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0x5))},
|
|
|
+ {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0x6))},
|
|
|
+ {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0x7))},
|
|
|
+ {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0x8))},
|
|
|
+ {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0x9))},
|
|
|
+ {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0xA))},
|
|
|
+ {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0xB))},
|
|
|
+ {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0xC))},
|
|
|
+ {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0xD))},
|
|
|
+ {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0xE))},
|
|
|
+ {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0xF))},
|
|
|
+ {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0x0))},
|
|
|
+ {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0x1))},
|
|
|
+ {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0x2))},
|
|
|
+ {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0x3))},
|
|
|
+ {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0x4))},
|
|
|
+ {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0x5))},
|
|
|
+ {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0x6))},
|
|
|
+ {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0x7))},
|
|
|
+ {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0x8))},
|
|
|
+ {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0x9))},
|
|
|
+ {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0xA))},
|
|
|
+ {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0xB))},
|
|
|
+ {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0xC))},
|
|
|
+ {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0xD))},
|
|
|
+ {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0xE))},
|
|
|
+ {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0xF))},
|
|
|
+ {SDIO_DEVICE(MANUFACTURER_CODE, (0 | 0x0))},
|
|
|
+ {SDIO_DEVICE(MANUFACTURER_CODE, (0 | 0x1))},
|
|
|
+#endif
|
|
|
+ { /* null */ },
|
|
|
+};
|
|
|
+
|
|
|
+#ifndef CONFIG_CNSS_SDIO
|
|
|
+MODULE_DEVICE_TABLE(sdio, ar6k_id_table);
|
|
|
+
|
|
|
+static struct sdio_driver ar6k_driver = {
|
|
|
+ .name = "ar6k_wlan",
|
|
|
+ .id_table = ar6k_id_table,
|
|
|
+ .probe = hif_device_inserted,
|
|
|
+ .remove = hif_device_removed,
|
|
|
+};
|
|
|
+
|
|
|
+static const struct dev_pm_ops ar6k_device_pm_ops = {
|
|
|
+ .suspend = hif_device_suspend,
|
|
|
+ .resume = hif_device_resume,
|
|
|
+};
|
|
|
+
|
|
|
+#endif
|
|
|
+
|
|
|
+/* make sure we only unregister when registered. */
|
|
|
+static int registered;
|
|
|
+
|
|
|
+struct osdrv_callbacks osdrv_callbacks;
|
|
|
+uint32_t onebitmode;
|
|
|
+uint32_t busspeedlow;
|
|
|
+uint32_t debughif;
|
|
|
+
|
|
|
+static struct hif_sdio_dev *hif_devices[MAX_HIF_DEVICES];
|
|
|
+
|
|
|
+static void reset_all_cards(void);
|
|
|
+static QDF_STATUS hif_disable_func(struct hif_sdio_dev *device,
|
|
|
+ struct sdio_func *func);
|
|
|
+static QDF_STATUS hif_enable_func(struct hif_sdio_dev *device,
|
|
|
+ struct sdio_func *func);
|
|
|
+
|
|
|
+#ifdef DEBUG
|
|
|
+ATH_DEBUG_INSTANTIATE_MODULE_VAR(hif,
|
|
|
+ "hif",
|
|
|
+ "(Linux MMC) Host Interconnect Framework",
|
|
|
+ ATH_DEBUG_MASK_DEFAULTS, 0, NULL);
|
|
|
+#endif
|
|
|
+
|
|
|
+static int hif_sdio_init_callbacks(struct osdrv_callbacks *callbacks)
|
|
|
+{
|
|
|
+ int status = 0;
|
|
|
+ /* store the callback handlers */
|
|
|
+ osdrv_callbacks = *callbacks;
|
|
|
+
|
|
|
+ /* Register with bus driver core is done from HDD */
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_TRACE, ("%s: HIFInit registering\n",
|
|
|
+ __func__));
|
|
|
+ registered = 1;
|
|
|
+
|
|
|
+ return status;
|
|
|
+}
|
|
|
+static void hif_sdio_remove_callbacks(void)
|
|
|
+{
|
|
|
+ qdf_mem_zero(&osdrv_callbacks, sizeof(osdrv_callbacks));
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * hif_init() - Initializes the driver callbacks
|
|
|
+ * @callbacks: pointer to driver callback structure
|
|
|
+ *
|
|
|
+ * Return: 0 on success, error number otherwise.
|
|
|
+ */
|
|
|
+QDF_STATUS hif_init(struct osdrv_callbacks *callbacks)
|
|
|
+{
|
|
|
+ int status;
|
|
|
+
|
|
|
+ AR_DEBUG_ASSERT(callbacks != NULL);
|
|
|
+ A_REGISTER_MODULE_DEBUG_INFO(hif);
|
|
|
+
|
|
|
+ HIF_ENTER();
|
|
|
+
|
|
|
+ status = hif_sdio_init_callbacks(callbacks);
|
|
|
+ AR_DEBUG_ASSERT(status == 0);
|
|
|
+
|
|
|
+ if (status != 0) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERROR,
|
|
|
+ ("%s sdio_register_driver failed!", __func__));
|
|
|
+ return QDF_STATUS_E_FAILURE;
|
|
|
+ } else {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_TRACE,
|
|
|
+ ("%s sdio_register_driver successful", __func__));
|
|
|
+ }
|
|
|
+
|
|
|
+ return QDF_STATUS_SUCCESS;
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * __hif_read_write() - sdio read/write wrapper
|
|
|
+ * @device: pointer to hif device structure
|
|
|
+ * @address: address to read
|
|
|
+ * @buffer: buffer to hold read/write data
|
|
|
+ * @length: length to read/write
|
|
|
+ * @request: read/write/sync/async request
|
|
|
+ * @context: pointer to hold calling context
|
|
|
+ *
|
|
|
+ * Return: 0 on success, error number otherwise.
|
|
|
+ */
|
|
|
+static QDF_STATUS
|
|
|
+__hif_read_write(struct hif_sdio_dev *device,
|
|
|
+ uint32_t address,
|
|
|
+ char *buffer,
|
|
|
+ uint32_t length, uint32_t request, void *context)
|
|
|
+{
|
|
|
+ uint8_t opcode;
|
|
|
+ QDF_STATUS status = QDF_STATUS_SUCCESS;
|
|
|
+ int ret;
|
|
|
+ uint8_t *tbuffer;
|
|
|
+ bool bounced = false;
|
|
|
+
|
|
|
+ AR_DEBUG_ASSERT(device != NULL);
|
|
|
+ AR_DEBUG_ASSERT(device->func != NULL);
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_TRACE,
|
|
|
+ ("__hif_read_write, addr:0X%06X, len:%08d, %s, %s\n",
|
|
|
+ address, length,
|
|
|
+ request & HIF_SDIO_READ ? "Read " : "Write",
|
|
|
+ request & HIF_ASYNCHRONOUS ? "Async" : "Sync "));
|
|
|
+
|
|
|
+ do {
|
|
|
+ if (request & HIF_EXTENDED_IO) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_TRACE,
|
|
|
+ ("%s: Command type: CMD53\n", __func__));
|
|
|
+ } else {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERROR,
|
|
|
+ ("%s: Invalid command type: 0x%08x\n",
|
|
|
+ __func__, request));
|
|
|
+ status = QDF_STATUS_E_INVAL;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (request & HIF_BLOCK_BASIS) {
|
|
|
+ /* round to whole block length size */
|
|
|
+ length =
|
|
|
+ (length / HIF_MBOX_BLOCK_SIZE) *
|
|
|
+ HIF_MBOX_BLOCK_SIZE;
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_TRACE,
|
|
|
+ ("%s: Block mode (BlockLen: %d)\n",
|
|
|
+ __func__, length));
|
|
|
+ } else if (request & HIF_BYTE_BASIS) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_TRACE,
|
|
|
+ ("%s: Byte mode (BlockLen: %d)\n",
|
|
|
+ __func__, length));
|
|
|
+ } else {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERROR,
|
|
|
+ ("%s: Invalid data mode: 0x%08x\n",
|
|
|
+ __func__, request));
|
|
|
+ status = QDF_STATUS_E_INVAL;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ if (request & HIF_SDIO_WRITE) {
|
|
|
+ struct hif_device_mbox_info MailBoxInfo;
|
|
|
+ unsigned int mboxLength = 0;
|
|
|
+ hif_configure_device(device,
|
|
|
+ HIF_DEVICE_GET_MBOX_ADDR,
|
|
|
+ &MailBoxInfo, sizeof(MailBoxInfo));
|
|
|
+ if (address >= 0x800 && address < 0xC00) {
|
|
|
+ /* Host control register and CIS Window */
|
|
|
+ mboxLength = 0;
|
|
|
+ } else if (address == MailBoxInfo.mbox_addresses[0]
|
|
|
+ || address == MailBoxInfo.mbox_addresses[1]
|
|
|
+ || address == MailBoxInfo.mbox_addresses[2]
|
|
|
+ || address ==
|
|
|
+ MailBoxInfo.mbox_addresses[3]) {
|
|
|
+ mboxLength = HIF_MBOX_WIDTH;
|
|
|
+ } else if (address ==
|
|
|
+ MailBoxInfo.mbox_prop[0].extended_address) {
|
|
|
+ mboxLength =
|
|
|
+ MailBoxInfo.mbox_prop[0].extended_size;
|
|
|
+ } else if (address ==
|
|
|
+ MailBoxInfo.mbox_prop[1].extended_address) {
|
|
|
+ mboxLength =
|
|
|
+ MailBoxInfo.mbox_prop[1].extended_size;
|
|
|
+ } else {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERROR,
|
|
|
+ ("Invalid written address: 0x%08x\n",
|
|
|
+ address));
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_TRACE,
|
|
|
+ ("address:%08X, Length:0x%08X, Dummy:0x%04X, "
|
|
|
+ "Final:0x%08X\n",
|
|
|
+ address, length,
|
|
|
+ (request & HIF_DUMMY_SPACE_MASK) >> 16,
|
|
|
+ mboxLength ==
|
|
|
+ 0 ? address : address + (mboxLength -
|
|
|
+ length)));
|
|
|
+ if (mboxLength != 0) {
|
|
|
+ if (length > mboxLength) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERROR,
|
|
|
+ ("%s: written length(0x%08X) "
|
|
|
+ "larger than mbox len(0x%08x)\n",
|
|
|
+ __func__, length, mboxLength));
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ address += (mboxLength - length);
|
|
|
+ /*
|
|
|
+ * plus dummy byte count
|
|
|
+ */
|
|
|
+ address += ((request &
|
|
|
+ HIF_DUMMY_SPACE_MASK) >> 16);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (request & HIF_FIXED_ADDRESS) {
|
|
|
+ opcode = CMD53_FIXED_ADDRESS;
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_TRACE,
|
|
|
+ ("%s: Address mode: Fixed 0x%X\n",
|
|
|
+ __func__, address));
|
|
|
+ } else if (request & HIF_INCREMENTAL_ADDRESS) {
|
|
|
+ opcode = CMD53_INCR_ADDRESS;
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_TRACE,
|
|
|
+ ("%s: Address mode: Incremental 0x%X\n",
|
|
|
+ __func__, address));
|
|
|
+ } else {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERROR,
|
|
|
+ ("%s: Invalid address mode: 0x%08x\n",
|
|
|
+ __func__, request));
|
|
|
+ status = QDF_STATUS_E_INVAL;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (request & HIF_SDIO_WRITE) {
|
|
|
+#if HIF_USE_DMA_BOUNCE_BUFFER
|
|
|
+ if (BUFFER_NEEDS_BOUNCE(buffer)) {
|
|
|
+ AR_DEBUG_ASSERT(device->dma_buffer != NULL);
|
|
|
+ tbuffer = device->dma_buffer;
|
|
|
+ /* copy the write data to the dma buffer */
|
|
|
+ AR_DEBUG_ASSERT(length <= HIF_DMA_BUFFER_SIZE);
|
|
|
+ if (length > HIF_DMA_BUFFER_SIZE) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERROR,
|
|
|
+ ("%s: Invalid write length:"
|
|
|
+ "%d\n", __func__, length));
|
|
|
+ status = QDF_STATUS_E_INVAL;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ memcpy(tbuffer, buffer, length);
|
|
|
+ bounced = true;
|
|
|
+ } else {
|
|
|
+ tbuffer = buffer;
|
|
|
+ }
|
|
|
+#else
|
|
|
+ tbuffer = buffer;
|
|
|
+#endif
|
|
|
+ if (opcode == CMD53_FIXED_ADDRESS && tbuffer != NULL) {
|
|
|
+ ret =
|
|
|
+ sdio_writesb(device->func, address,
|
|
|
+ tbuffer,
|
|
|
+ length);
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_TRACE,
|
|
|
+ ("%s: writesb ret=%d address:"
|
|
|
+ " 0x%X, len: %d, 0x%X\n",
|
|
|
+ __func__, ret, address, length,
|
|
|
+ *(int *)tbuffer));
|
|
|
+ } else {
|
|
|
+ ret =
|
|
|
+ sdio_memcpy_toio(device->func, address,
|
|
|
+ tbuffer, length);
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_TRACE,
|
|
|
+ ("%s: writeio ret=%d address: "
|
|
|
+ " 0x%X, len: %d, 0x%X\n",
|
|
|
+ __func__, ret, address, length,
|
|
|
+ *(int *)tbuffer));
|
|
|
+ }
|
|
|
+ } else if (request & HIF_SDIO_READ) {
|
|
|
+#if HIF_USE_DMA_BOUNCE_BUFFER
|
|
|
+ if (BUFFER_NEEDS_BOUNCE(buffer)) {
|
|
|
+ AR_DEBUG_ASSERT(device->dma_buffer != NULL);
|
|
|
+ AR_DEBUG_ASSERT(length <= HIF_DMA_BUFFER_SIZE);
|
|
|
+ if (length > HIF_DMA_BUFFER_SIZE) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERROR,
|
|
|
+ ("%s: Invalid read length: %d\n",
|
|
|
+ __func__, length));
|
|
|
+ status = QDF_STATUS_E_INVAL;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ tbuffer = device->dma_buffer;
|
|
|
+ bounced = true;
|
|
|
+ } else {
|
|
|
+ tbuffer = buffer;
|
|
|
+ }
|
|
|
+#else
|
|
|
+ tbuffer = buffer;
|
|
|
+#endif
|
|
|
+ if (opcode == CMD53_FIXED_ADDRESS && tbuffer != NULL) {
|
|
|
+ ret =
|
|
|
+ sdio_readsb(device->func, tbuffer,
|
|
|
+ address,
|
|
|
+ length);
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_TRACE,
|
|
|
+ ("%s: readsb ret=%d address:"
|
|
|
+ " 0x%X, len: %d, 0x%X\n",
|
|
|
+ __func__, ret, address, length,
|
|
|
+ *(int *)tbuffer));
|
|
|
+ } else {
|
|
|
+ ret =
|
|
|
+ sdio_memcpy_fromio(device->func,
|
|
|
+ tbuffer,
|
|
|
+ address, length);
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_TRACE,
|
|
|
+ ("%s: readio ret=%d address: 0x%X,"
|
|
|
+ " len: %d, 0x%X\n",
|
|
|
+ __func__, ret, address, length,
|
|
|
+ *(int *)tbuffer));
|
|
|
+ }
|
|
|
+#if HIF_USE_DMA_BOUNCE_BUFFER
|
|
|
+ if (bounced)
|
|
|
+ memcpy(buffer, tbuffer, length);
|
|
|
+#endif
|
|
|
+ } else {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERROR,
|
|
|
+ ("%s: Invalid direction: 0x%08x\n",
|
|
|
+ __func__, request));
|
|
|
+ status = QDF_STATUS_E_INVAL;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (ret) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERROR,
|
|
|
+ ("%s: SDIO bus operation failed! "
|
|
|
+ "MMC stack returned : %d\n",
|
|
|
+ __func__, ret));
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERROR,
|
|
|
+ ("__hif_read_write, addr:0X%06X, "
|
|
|
+ "len:%08d, %s, %s\n",
|
|
|
+ address, length,
|
|
|
+ request & HIF_SDIO_READ ? "Read " : "Write",
|
|
|
+ request & HIF_ASYNCHRONOUS ? "Async" :
|
|
|
+ "Sync "));
|
|
|
+ status = QDF_STATUS_E_FAILURE;
|
|
|
+ }
|
|
|
+ } while (false);
|
|
|
+
|
|
|
+ return status;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * add_to_async_list() - add bus reqest to async task list
|
|
|
+ * @device: pointer to hif device
|
|
|
+ * @busrequest: pointer to type of bus request
|
|
|
+ *
|
|
|
+ * Return: None.
|
|
|
+ */
|
|
|
+void add_to_async_list(struct hif_sdio_dev *device,
|
|
|
+ struct bus_request *busrequest)
|
|
|
+{
|
|
|
+ struct bus_request *async;
|
|
|
+ struct bus_request *active;
|
|
|
+
|
|
|
+ qdf_spin_lock_irqsave(&device->asynclock);
|
|
|
+ active = device->asyncreq;
|
|
|
+ if (active == NULL) {
|
|
|
+ device->asyncreq = busrequest;
|
|
|
+ device->asyncreq->inusenext = NULL;
|
|
|
+ } else {
|
|
|
+ for (async = device->asyncreq;
|
|
|
+ async != NULL; async = async->inusenext) {
|
|
|
+ active = async;
|
|
|
+ }
|
|
|
+ active->inusenext = busrequest;
|
|
|
+ busrequest->inusenext = NULL;
|
|
|
+ }
|
|
|
+ qdf_spin_unlock_irqrestore(&device->asynclock);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * hif_read_write() - queue a read/write request
|
|
|
+ * @device: pointer to hif device structure
|
|
|
+ * @address: address to read
|
|
|
+ * @buffer: buffer to hold read/write data
|
|
|
+ * @length: length to read/write
|
|
|
+ * @request: read/write/sync/async request
|
|
|
+ * @context: pointer to hold calling context
|
|
|
+ *
|
|
|
+ * Return: 0 on success, error number otherwise.
|
|
|
+ */
|
|
|
+QDF_STATUS
|
|
|
+hif_read_write(struct hif_sdio_dev *device,
|
|
|
+ uint32_t address,
|
|
|
+ char *buffer, uint32_t length,
|
|
|
+ uint32_t request, void *context)
|
|
|
+{
|
|
|
+ QDF_STATUS status = QDF_STATUS_SUCCESS;
|
|
|
+ struct bus_request *busrequest;
|
|
|
+
|
|
|
+ AR_DEBUG_ASSERT(device != NULL);
|
|
|
+ AR_DEBUG_ASSERT(device->func != NULL);
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_TRACE,
|
|
|
+ ("%s: device 0x%p addr 0x%X buffer 0x%p "
|
|
|
+ "len %d req 0x%X context 0x%p",
|
|
|
+ __func__, device, address, buffer,
|
|
|
+ length, request, context));
|
|
|
+
|
|
|
+ /*sdio r/w action is not needed when suspend, so just return */
|
|
|
+ if ((device->is_suspend == true)
|
|
|
+ && (device->power_config == HIF_DEVICE_POWER_CUT)) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_TRACE, ("skip io when suspending\n"));
|
|
|
+ return QDF_STATUS_SUCCESS;
|
|
|
+ }
|
|
|
+ do {
|
|
|
+ if ((request & HIF_ASYNCHRONOUS) ||
|
|
|
+ (request & HIF_SYNCHRONOUS)) {
|
|
|
+ /* serialize all requests through the async thread */
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_TRACE,
|
|
|
+ ("%s: Execution mode: %s\n", __func__,
|
|
|
+ (request & HIF_ASYNCHRONOUS) ? "Async"
|
|
|
+ : "Synch"));
|
|
|
+ busrequest = hif_allocate_bus_request(device);
|
|
|
+ if (busrequest == NULL) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERROR,
|
|
|
+ ("no async bus requests "
|
|
|
+ "available (%s, addr:0x%X, len:%d)\n",
|
|
|
+ request & HIF_SDIO_READ ? "READ" :
|
|
|
+ "WRITE", address, length));
|
|
|
+ return QDF_STATUS_E_FAILURE;
|
|
|
+ }
|
|
|
+ busrequest->address = address;
|
|
|
+ busrequest->buffer = buffer;
|
|
|
+ busrequest->length = length;
|
|
|
+ busrequest->request = request;
|
|
|
+ busrequest->context = context;
|
|
|
+
|
|
|
+ add_to_async_list(device, busrequest);
|
|
|
+
|
|
|
+ if (request & HIF_SYNCHRONOUS) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_TRACE,
|
|
|
+ ("%s: queued sync req: 0x%lX\n",
|
|
|
+ __func__, (unsigned long)busrequest));
|
|
|
+
|
|
|
+ /* wait for completion */
|
|
|
+ up(&device->sem_async);
|
|
|
+ if (down_interruptible(&busrequest->sem_req) !=
|
|
|
+ 0) {
|
|
|
+ /* interrupted, exit */
|
|
|
+ return QDF_STATUS_E_FAILURE;
|
|
|
+ } else {
|
|
|
+ QDF_STATUS status = busrequest->status;
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_TRACE,
|
|
|
+ ("%s: sync return freeing 0x%lX: 0x%X\n",
|
|
|
+ __func__,
|
|
|
+ (unsigned long)
|
|
|
+ busrequest,
|
|
|
+ busrequest->status));
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_TRACE,
|
|
|
+ ("%s: freeing req: 0x%X\n",
|
|
|
+ __func__,
|
|
|
+ (unsigned int)
|
|
|
+ request));
|
|
|
+ hif_free_bus_request(device,
|
|
|
+ busrequest);
|
|
|
+ return status;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_TRACE,
|
|
|
+ ("%s: queued async req: 0x%lX\n",
|
|
|
+ __func__,
|
|
|
+ (unsigned long)busrequest));
|
|
|
+ up(&device->sem_async);
|
|
|
+ return QDF_STATUS_E_PENDING;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERROR,
|
|
|
+ ("%s: Invalid execution mode: 0x%08x\n",
|
|
|
+ __func__,
|
|
|
+ (unsigned int)request));
|
|
|
+ status = QDF_STATUS_E_INVAL;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ } while (0);
|
|
|
+
|
|
|
+ return status;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * async_task() - thread function to serialize all bus requests
|
|
|
+ * @param: pointer to hif device
|
|
|
+ *
|
|
|
+ * thread function to serialize all requests, both sync and async
|
|
|
+ * Return: 0 on success, error number otherwise.
|
|
|
+ */
|
|
|
+static int async_task(void *param)
|
|
|
+{
|
|
|
+ struct hif_sdio_dev *device;
|
|
|
+ struct bus_request *request;
|
|
|
+ QDF_STATUS status;
|
|
|
+
|
|
|
+ device = (struct hif_sdio_dev *) param;
|
|
|
+ set_current_state(TASK_INTERRUPTIBLE);
|
|
|
+ while (!device->async_shutdown) {
|
|
|
+ /* wait for work */
|
|
|
+ if (down_interruptible(&device->sem_async) != 0) {
|
|
|
+ /* interrupted, exit */
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_TRACE,
|
|
|
+ ("%s: async task interrupted\n",
|
|
|
+ __func__));
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ if (device->async_shutdown) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_TRACE,
|
|
|
+ ("%s: async task stopping\n",
|
|
|
+ __func__));
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ /* we want to hold the host over multiple cmds
|
|
|
+ * if possible, but holding the host blocks
|
|
|
+ * card interrupts */
|
|
|
+ sdio_claim_host(device->func);
|
|
|
+ qdf_spin_lock_irqsave(&device->asynclock);
|
|
|
+ /* pull the request to work on */
|
|
|
+ while (device->asyncreq != NULL) {
|
|
|
+ request = device->asyncreq;
|
|
|
+ if (request->inusenext != NULL)
|
|
|
+ device->asyncreq = request->inusenext;
|
|
|
+ else
|
|
|
+ device->asyncreq = NULL;
|
|
|
+ qdf_spin_unlock_irqrestore(&device->asynclock);
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_TRACE,
|
|
|
+ ("%s: async_task processing req: 0x%lX\n",
|
|
|
+ __func__, (unsigned long)request));
|
|
|
+
|
|
|
+ if (request->scatter_req != NULL) {
|
|
|
+ A_ASSERT(device->scatter_enabled);
|
|
|
+ /* pass the request to scatter routine which
|
|
|
+ * executes it synchronously, note, no need
|
|
|
+ * to free the request since scatter requests
|
|
|
+ * are maintained on a separate list */
|
|
|
+ status = do_hif_read_write_scatter(device,
|
|
|
+ request);
|
|
|
+ } else {
|
|
|
+ /* call hif_read_write in sync mode */
|
|
|
+ status =
|
|
|
+ __hif_read_write(device,
|
|
|
+ request->address,
|
|
|
+ request->buffer,
|
|
|
+ request->length,
|
|
|
+ request->
|
|
|
+ request &
|
|
|
+ ~HIF_SYNCHRONOUS,
|
|
|
+ NULL);
|
|
|
+ if (request->request & HIF_ASYNCHRONOUS) {
|
|
|
+ void *context = request->context;
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_TRACE,
|
|
|
+ ("%s: freeing req: 0x%lX\n",
|
|
|
+ __func__, (unsigned long)
|
|
|
+ request));
|
|
|
+ hif_free_bus_request(device, request);
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_TRACE,
|
|
|
+ ("%s: async_task completion req 0x%lX\n",
|
|
|
+ __func__, (unsigned long)
|
|
|
+ request));
|
|
|
+ device->htc_callbacks.
|
|
|
+ rwCompletionHandler(context,
|
|
|
+ status);
|
|
|
+ } else {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_TRACE,
|
|
|
+ ("%s: async_task upping req: 0x%lX\n",
|
|
|
+ __func__, (unsigned long)
|
|
|
+ request));
|
|
|
+ request->status = status;
|
|
|
+ up(&request->sem_req);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ qdf_spin_lock_irqsave(&device->asynclock);
|
|
|
+ }
|
|
|
+ qdf_spin_unlock_irqrestore(&device->asynclock);
|
|
|
+ sdio_release_host(device->func);
|
|
|
+ }
|
|
|
+
|
|
|
+ complete_and_exit(&device->async_completion, 0);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 16, 0))
|
|
|
+/**
|
|
|
+ * sdio_card_highspeed() - check if high speed supported
|
|
|
+ * @card: pointer to mmc card struct
|
|
|
+ *
|
|
|
+ * Return: non zero if card supports high speed.
|
|
|
+ */
|
|
|
+static inline int sdio_card_highspeed(struct mmc_card *card)
|
|
|
+{
|
|
|
+ return mmc_card_highspeed(card);
|
|
|
+}
|
|
|
+#else
|
|
|
+static inline int sdio_card_highspeed(struct mmc_card *card)
|
|
|
+{
|
|
|
+ return mmc_card_hs(card);
|
|
|
+}
|
|
|
+#endif
|
|
|
+
|
|
|
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 16, 0))
|
|
|
+/**
|
|
|
+ * sdio_card_set_highspeed() - set high speed
|
|
|
+ * @card: pointer to mmc card struct
|
|
|
+ *
|
|
|
+ * Return: none.
|
|
|
+ */
|
|
|
+static inline void sdio_card_set_highspeed(struct mmc_card *card)
|
|
|
+{
|
|
|
+ mmc_card_set_highspeed(card);
|
|
|
+}
|
|
|
+#else
|
|
|
+static inline void sdio_card_set_highspeed(struct mmc_card *card)
|
|
|
+{
|
|
|
+}
|
|
|
+#endif
|
|
|
+
|
|
|
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 16, 0))
|
|
|
+/**
|
|
|
+ * sdio_card_state() - set card state
|
|
|
+ * @card: pointer to mmc card struct
|
|
|
+ *
|
|
|
+ * Return: none.
|
|
|
+ */
|
|
|
+static inline void sdio_card_state(struct mmc_card *card)
|
|
|
+{
|
|
|
+ card->state &= ~MMC_STATE_HIGHSPEED;
|
|
|
+}
|
|
|
+#else
|
|
|
+static inline void sdio_card_state(struct mmc_card *card)
|
|
|
+{
|
|
|
+}
|
|
|
+#endif
|
|
|
+
|
|
|
+/**
|
|
|
+ * reinit_sdio() - re-initialize sdio bus
|
|
|
+ * @param: pointer to hif device
|
|
|
+ *
|
|
|
+ * Return: 0 on success, error number otherwise.
|
|
|
+ */
|
|
|
+QDF_STATUS reinit_sdio(struct hif_sdio_dev *device)
|
|
|
+{
|
|
|
+ int32_t err = 0;
|
|
|
+ struct mmc_host *host;
|
|
|
+ struct mmc_card *card;
|
|
|
+ struct sdio_func *func;
|
|
|
+ uint8_t cmd52_resp;
|
|
|
+ uint32_t clock;
|
|
|
+
|
|
|
+ func = device->func;
|
|
|
+ card = func->card;
|
|
|
+ host = card->host;
|
|
|
+
|
|
|
+ sdio_claim_host(func);
|
|
|
+
|
|
|
+ do {
|
|
|
+ /* Enable high speed */
|
|
|
+ if (card->host->caps & MMC_CAP_SD_HIGHSPEED) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_TRACE,
|
|
|
+ ("%s: Set high speed mode\n",
|
|
|
+ __func__));
|
|
|
+ err = func0_cmd52_read_byte(card, SDIO_CCCR_SPEED,
|
|
|
+ &cmd52_resp);
|
|
|
+ if (err) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERR,
|
|
|
+ ("%s: CMD52 read to CCCR speed "
|
|
|
+ "register failed : %d\n",
|
|
|
+ __func__, err));
|
|
|
+ sdio_card_state(card);
|
|
|
+ /* no need to break */
|
|
|
+ } else {
|
|
|
+ err = func0_cmd52_write_byte(card,
|
|
|
+ SDIO_CCCR_SPEED,
|
|
|
+ (cmd52_resp | SDIO_SPEED_EHS));
|
|
|
+ if (err) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERR,
|
|
|
+ ("%s: CMD52 write to CCCR speed"
|
|
|
+ " register failed : %d\n",
|
|
|
+ __func__, err));
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ sdio_card_set_highspeed(card);
|
|
|
+ host->ios.timing = MMC_TIMING_SD_HS;
|
|
|
+ host->ops->set_ios(host, &host->ios);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Set clock */
|
|
|
+ if (sdio_card_highspeed(card))
|
|
|
+ clock = 50000000;
|
|
|
+ else
|
|
|
+ clock = card->cis.max_dtr;
|
|
|
+
|
|
|
+ if (clock > host->f_max)
|
|
|
+ clock = host->f_max;
|
|
|
+ /*
|
|
|
+ * In fpga mode the clk should be set to 12500000,
|
|
|
+ * or will result in scan channel setting timeout error.
|
|
|
+ * So in fpga mode, please set module parameter mmcclock
|
|
|
+ * to 12500000.
|
|
|
+ */
|
|
|
+ if (mmcclock > 0)
|
|
|
+ clock = mmcclock;
|
|
|
+ host->ios.clock = clock;
|
|
|
+ host->ops->set_ios(host, &host->ios);
|
|
|
+
|
|
|
+
|
|
|
+ if (card->host->caps & MMC_CAP_4_BIT_DATA) {
|
|
|
+ /* CMD52: Set bus width & disable card detect resistor */
|
|
|
+ err = func0_cmd52_write_byte(card, SDIO_CCCR_IF,
|
|
|
+ SDIO_BUS_CD_DISABLE | SDIO_BUS_WIDTH_4BIT);
|
|
|
+ if (err) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERR,
|
|
|
+ ("%s: CMD52 to set bus mode "
|
|
|
+ "failed : %d\n",
|
|
|
+ __func__, err));
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ host->ios.bus_width = MMC_BUS_WIDTH_4;
|
|
|
+ host->ops->set_ios(host, &host->ios);
|
|
|
+ }
|
|
|
+ } while (0);
|
|
|
+
|
|
|
+ sdio_release_host(func);
|
|
|
+
|
|
|
+ return (err) ? QDF_STATUS_E_FAILURE : QDF_STATUS_SUCCESS;
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Setup IRQ mode for deep sleep and WoW
|
|
|
+ * Switch back to 1 bits mode when we suspend for
|
|
|
+ * WoW in order to detect SDIO irq without clock.
|
|
|
+ * Re-enable async 4-bit irq mode for some host controllers
|
|
|
+ * after resume.
|
|
|
+ */
|
|
|
+static int sdio_enable4bits(struct hif_sdio_dev *device, int enable)
|
|
|
+{
|
|
|
+ int ret = 0;
|
|
|
+ struct sdio_func *func = device->func;
|
|
|
+ struct mmc_card *card = func->card;
|
|
|
+ struct mmc_host *host = card->host;
|
|
|
+
|
|
|
+ if (!(host->caps & (MMC_CAP_4_BIT_DATA)))
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ if (card->cccr.low_speed && !card->cccr.wide_bus)
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ sdio_claim_host(func);
|
|
|
+ do {
|
|
|
+ int setAsyncIRQ = 0;
|
|
|
+ __u16 manufacturer_id =
|
|
|
+ device->id->device & MANUFACTURER_ID_AR6K_BASE_MASK;
|
|
|
+
|
|
|
+ /* Re-enable 4-bit ASYNC interrupt on AR6003x
|
|
|
+ * after system resume for some host controller */
|
|
|
+ if (manufacturer_id == MANUFACTURER_ID_AR6003_BASE) {
|
|
|
+ setAsyncIRQ = 1;
|
|
|
+ ret =
|
|
|
+ func0_cmd52_write_byte(func->card,
|
|
|
+ CCCR_SDIO_IRQ_MODE_REG_AR6003,
|
|
|
+ enable ?
|
|
|
+ SDIO_IRQ_MODE_ASYNC_4BIT_IRQ_AR6003
|
|
|
+ : 0);
|
|
|
+ } else if (manufacturer_id == MANUFACTURER_ID_AR6320_BASE
|
|
|
+ || manufacturer_id == MANUFACTURER_ID_QCA9377_BASE) {
|
|
|
+ unsigned char data = 0;
|
|
|
+ setAsyncIRQ = 1;
|
|
|
+ ret =
|
|
|
+ func0_cmd52_read_byte(func->card,
|
|
|
+ CCCR_SDIO_IRQ_MODE_REG_AR6320,
|
|
|
+ &data);
|
|
|
+ if (ret) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERR,
|
|
|
+ ("%s: failed to read interrupt "
|
|
|
+ "extension register %d\n",
|
|
|
+ __func__, ret));
|
|
|
+ sdio_release_host(func);
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+ if (enable)
|
|
|
+ data |= SDIO_IRQ_MODE_ASYNC_4BIT_IRQ_AR6320;
|
|
|
+ else
|
|
|
+ data &= ~SDIO_IRQ_MODE_ASYNC_4BIT_IRQ_AR6320;
|
|
|
+ ret =
|
|
|
+ func0_cmd52_write_byte(func->card,
|
|
|
+ CCCR_SDIO_IRQ_MODE_REG_AR6320,
|
|
|
+ data);
|
|
|
+ }
|
|
|
+ if (setAsyncIRQ) {
|
|
|
+ if (ret) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERR,
|
|
|
+ ("%s: failed to setup 4-bit "
|
|
|
+ "ASYNC IRQ mode into %d err %d\n",
|
|
|
+ __func__, enable, ret));
|
|
|
+ } else {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_INFO,
|
|
|
+ ("%s: Setup 4-bit ASYNC "
|
|
|
+ "IRQ mode into %d successfully\n",
|
|
|
+ __func__, enable));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } while (0);
|
|
|
+ sdio_release_host(func);
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * power_state_change_notify() - SDIO bus power notification handler
|
|
|
+ * @config: hif device power change type
|
|
|
+ *
|
|
|
+ * Return: 0 on success, error number otherwise.
|
|
|
+ */
|
|
|
+QDF_STATUS
|
|
|
+power_state_change_notify(struct hif_sdio_dev *device,
|
|
|
+ HIF_DEVICE_POWER_CHANGE_TYPE config)
|
|
|
+{
|
|
|
+ QDF_STATUS status = QDF_STATUS_SUCCESS;
|
|
|
+ struct sdio_func *func = device->func;
|
|
|
+ int old_reset_val;
|
|
|
+
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_TRACE,
|
|
|
+ ("%s: config type %d\n",
|
|
|
+ __func__, config));
|
|
|
+ switch (config) {
|
|
|
+ case HIF_DEVICE_POWER_DOWN:
|
|
|
+ /* Disable 4bits to allow SDIO bus to detect
|
|
|
+ * DAT1 as interrupt source */
|
|
|
+ sdio_enable4bits(device, 0);
|
|
|
+ break;
|
|
|
+ case HIF_DEVICE_POWER_CUT:
|
|
|
+ old_reset_val = reset_sdio_on_unload;
|
|
|
+ reset_sdio_on_unload = 1;
|
|
|
+ status = hif_disable_func(device, func);
|
|
|
+ reset_sdio_on_unload = old_reset_val;
|
|
|
+ if (!device->is_suspend) {
|
|
|
+ device->power_config = config;
|
|
|
+ mmc_detect_change(device->host, HZ / 3);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case HIF_DEVICE_POWER_UP:
|
|
|
+ if (device->power_config == HIF_DEVICE_POWER_CUT) {
|
|
|
+ if (device->is_suspend) {
|
|
|
+ status = reinit_sdio(device);
|
|
|
+ /* set power_config before EnableFunc to
|
|
|
+ * passthrough sdio r/w action when resuming
|
|
|
+ * from cut power */
|
|
|
+ device->power_config = config;
|
|
|
+ if (status == QDF_STATUS_SUCCESS)
|
|
|
+ status = hif_enable_func(device, func);
|
|
|
+ } else {
|
|
|
+ /* device->func is bad pointer at this time */
|
|
|
+ mmc_detect_change(device->host, 0);
|
|
|
+ return QDF_STATUS_E_PENDING;
|
|
|
+ }
|
|
|
+ } else if (device->power_config == HIF_DEVICE_POWER_DOWN) {
|
|
|
+ int ret = sdio_enable4bits(device, 1);
|
|
|
+ status = (ret == 0) ? QDF_STATUS_SUCCESS :
|
|
|
+ QDF_STATUS_E_FAILURE;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ device->power_config = config;
|
|
|
+
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_TRACE,
|
|
|
+ ("%s:\n", __func__));
|
|
|
+
|
|
|
+ return status;
|
|
|
+}
|
|
|
+
|
|
|
+#ifdef SDIO_3_0
|
|
|
+/**
|
|
|
+ * set_extended_mbox_size() - set extended MBOX size
|
|
|
+ * @pinfo: sdio mailbox info
|
|
|
+ *
|
|
|
+ * Return: none.
|
|
|
+ */
|
|
|
+static void set_extended_mbox_size(struct hif_device_mbox_info *pinfo)
|
|
|
+{
|
|
|
+ pinfo->mbox_prop[0].extended_size =
|
|
|
+ HIF_MBOX0_EXTENDED_WIDTH_AR6320_ROME_2_0;
|
|
|
+ pinfo->mbox_prop[1].extended_size =
|
|
|
+ HIF_MBOX1_EXTENDED_WIDTH_AR6320;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * set_extended_mbox_address() - set extended MBOX address
|
|
|
+ * @pinfo: sdio mailbox info
|
|
|
+ *
|
|
|
+ * Return: none.
|
|
|
+ */
|
|
|
+static void set_extended_mbox_address(struct hif_device_mbox_info *pinfo)
|
|
|
+{
|
|
|
+ pinfo->mbox_prop[1].extended_address =
|
|
|
+ pinfo->mbox_prop[0].extended_address +
|
|
|
+ pinfo->mbox_prop[0].extended_size +
|
|
|
+ HIF_MBOX_DUMMY_SPACE_SIZE_AR6320;
|
|
|
+}
|
|
|
+#else
|
|
|
+static void set_extended_mbox_size(struct hif_device_mbox_info *pinfo)
|
|
|
+{
|
|
|
+ pinfo->mbox_prop[0].extended_size =
|
|
|
+ HIF_MBOX0_EXTENDED_WIDTH_AR6320;
|
|
|
+}
|
|
|
+static inline void
|
|
|
+set_extended_mbox_address(struct hif_device_mbox_info *pinfo)
|
|
|
+{
|
|
|
+
|
|
|
+}
|
|
|
+#endif
|
|
|
+
|
|
|
+/**
|
|
|
+ * set_extended_mbox_window_info() - set extended MBOX window
|
|
|
+ * information for SDIO interconnects
|
|
|
+ * @manf_id: manufacturer id
|
|
|
+ * @pinfo: sdio mailbox info
|
|
|
+ *
|
|
|
+ * Return: none.
|
|
|
+ */
|
|
|
+static void set_extended_mbox_window_info(uint16_t manf_id,
|
|
|
+ struct hif_device_mbox_info *pinfo)
|
|
|
+{
|
|
|
+ switch (manf_id & MANUFACTURER_ID_AR6K_BASE_MASK) {
|
|
|
+ case MANUFACTURER_ID_AR6002_BASE:
|
|
|
+ /* MBOX 0 has an extended range */
|
|
|
+
|
|
|
+ pinfo->mbox_prop[0].extended_address =
|
|
|
+ HIF_MBOX0_EXTENDED_BASE_ADDR_AR6003_V1;
|
|
|
+ pinfo->mbox_prop[0].extended_size =
|
|
|
+ HIF_MBOX0_EXTENDED_WIDTH_AR6003_V1;
|
|
|
+
|
|
|
+ pinfo->mbox_prop[0].extended_address =
|
|
|
+ HIF_MBOX0_EXTENDED_BASE_ADDR_AR6003_V1;
|
|
|
+ pinfo->mbox_prop[0].extended_size =
|
|
|
+ HIF_MBOX0_EXTENDED_WIDTH_AR6003_V1;
|
|
|
+
|
|
|
+ pinfo->mbox_prop[0].extended_address =
|
|
|
+ HIF_MBOX0_EXTENDED_BASE_ADDR_AR6004;
|
|
|
+ pinfo->mbox_prop[0].extended_size =
|
|
|
+ HIF_MBOX0_EXTENDED_WIDTH_AR6004;
|
|
|
+
|
|
|
+ break;
|
|
|
+ case MANUFACTURER_ID_AR6003_BASE:
|
|
|
+ /* MBOX 0 has an extended range */
|
|
|
+ pinfo->mbox_prop[0].extended_address =
|
|
|
+ HIF_MBOX0_EXTENDED_BASE_ADDR_AR6003_V1;
|
|
|
+ pinfo->mbox_prop[0].extended_size =
|
|
|
+ HIF_MBOX0_EXTENDED_WIDTH_AR6003_V1;
|
|
|
+ pinfo->gmbox_address = HIF_GMBOX_BASE_ADDR;
|
|
|
+ pinfo->gmbox_size = HIF_GMBOX_WIDTH;
|
|
|
+ break;
|
|
|
+ case MANUFACTURER_ID_AR6004_BASE:
|
|
|
+ pinfo->mbox_prop[0].extended_address =
|
|
|
+ HIF_MBOX0_EXTENDED_BASE_ADDR_AR6004;
|
|
|
+ pinfo->mbox_prop[0].extended_size =
|
|
|
+ HIF_MBOX0_EXTENDED_WIDTH_AR6004;
|
|
|
+ pinfo->gmbox_address = HIF_GMBOX_BASE_ADDR;
|
|
|
+ pinfo->gmbox_size = HIF_GMBOX_WIDTH;
|
|
|
+ break;
|
|
|
+ case MANUFACTURER_ID_AR6320_BASE: {
|
|
|
+ uint16_t ManuRevID =
|
|
|
+ manf_id & MANUFACTURER_ID_AR6K_REV_MASK;
|
|
|
+ pinfo->mbox_prop[0].extended_address =
|
|
|
+ HIF_MBOX0_EXTENDED_BASE_ADDR_AR6320;
|
|
|
+ if (ManuRevID < 4) {
|
|
|
+ pinfo->mbox_prop[0].extended_size =
|
|
|
+ HIF_MBOX0_EXTENDED_WIDTH_AR6320;
|
|
|
+ } else {
|
|
|
+ /* from rome 2.0(0x504), the width has been extended to 56K */
|
|
|
+ set_extended_mbox_size(pinfo);
|
|
|
+ }
|
|
|
+ set_extended_mbox_address(pinfo);
|
|
|
+ pinfo->gmbox_address = HIF_GMBOX_BASE_ADDR;
|
|
|
+ pinfo->gmbox_size = HIF_GMBOX_WIDTH;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ case MANUFACTURER_ID_QCA9377_BASE:
|
|
|
+ pinfo->mbox_prop[0].extended_address =
|
|
|
+ HIF_MBOX0_EXTENDED_BASE_ADDR_AR6320;
|
|
|
+ pinfo->mbox_prop[0].extended_size =
|
|
|
+ HIF_MBOX0_EXTENDED_WIDTH_AR6320_ROME_2_0;
|
|
|
+ pinfo->mbox_prop[1].extended_address =
|
|
|
+ pinfo->mbox_prop[0].extended_address +
|
|
|
+ pinfo->mbox_prop[0].extended_size +
|
|
|
+ HIF_MBOX_DUMMY_SPACE_SIZE_AR6320;
|
|
|
+ pinfo->mbox_prop[1].extended_size =
|
|
|
+ HIF_MBOX1_EXTENDED_WIDTH_AR6320;
|
|
|
+ pinfo->gmbox_address = HIF_GMBOX_BASE_ADDR;
|
|
|
+ pinfo->gmbox_size = HIF_GMBOX_WIDTH;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ A_ASSERT(false);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * hif_configure_device() - configure sdio device
|
|
|
+ * @device: pointer to hif device structure
|
|
|
+ * @opcode: configuration type
|
|
|
+ * @config: configuration value to set
|
|
|
+ * @configLen: configuration length
|
|
|
+ *
|
|
|
+ * Return: 0 on success, error number otherwise.
|
|
|
+ */
|
|
|
+QDF_STATUS
|
|
|
+hif_configure_device(struct hif_sdio_dev *device,
|
|
|
+ enum hif_device_config_opcode opcode,
|
|
|
+ void *config, uint32_t config_len)
|
|
|
+{
|
|
|
+ uint32_t count;
|
|
|
+ QDF_STATUS status = QDF_STATUS_SUCCESS;
|
|
|
+
|
|
|
+ switch (opcode) {
|
|
|
+ case HIF_DEVICE_GET_MBOX_BLOCK_SIZE:
|
|
|
+ ((uint32_t *) config)[0] = HIF_MBOX0_BLOCK_SIZE;
|
|
|
+ ((uint32_t *) config)[1] = HIF_MBOX1_BLOCK_SIZE;
|
|
|
+ ((uint32_t *) config)[2] = HIF_MBOX2_BLOCK_SIZE;
|
|
|
+ ((uint32_t *) config)[3] = HIF_MBOX3_BLOCK_SIZE;
|
|
|
+ break;
|
|
|
+
|
|
|
+ case HIF_DEVICE_GET_MBOX_ADDR:
|
|
|
+ for (count = 0; count < 4; count++) {
|
|
|
+ ((uint32_t *) config)[count] =
|
|
|
+ HIF_MBOX_START_ADDR(count);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (config_len >= sizeof(struct hif_device_mbox_info)) {
|
|
|
+ set_extended_mbox_window_info((uint16_t) device->func->
|
|
|
+ device,
|
|
|
+ (struct hif_device_mbox_info *)
|
|
|
+ config);
|
|
|
+ }
|
|
|
+
|
|
|
+ break;
|
|
|
+ case HIF_DEVICE_GET_PENDING_EVENTS_FUNC:
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_WARN,
|
|
|
+ ("%s: configuration opcode %d\n",
|
|
|
+ __func__, opcode));
|
|
|
+ status = QDF_STATUS_E_FAILURE;
|
|
|
+ break;
|
|
|
+ case HIF_DEVICE_GET_IRQ_PROC_MODE:
|
|
|
+ *((enum hif_device_irq_mode *) config) =
|
|
|
+ HIF_DEVICE_IRQ_SYNC_ONLY;
|
|
|
+ break;
|
|
|
+ case HIF_DEVICE_GET_RECV_EVENT_MASK_UNMASK_FUNC:
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_WARN,
|
|
|
+ ("%s: configuration opcode %d\n",
|
|
|
+ __func__, opcode));
|
|
|
+ status = QDF_STATUS_E_FAILURE;
|
|
|
+ break;
|
|
|
+ case HIF_CONFIGURE_QUERY_SCATTER_REQUEST_SUPPORT:
|
|
|
+ if (!device->scatter_enabled)
|
|
|
+ return QDF_STATUS_E_NOSUPPORT;
|
|
|
+ status =
|
|
|
+ setup_hif_scatter_support(device,
|
|
|
+ (struct HIF_DEVICE_SCATTER_SUPPORT_INFO *)
|
|
|
+ config);
|
|
|
+ if (QDF_IS_STATUS_ERROR(status))
|
|
|
+ device->scatter_enabled = false;
|
|
|
+ break;
|
|
|
+ case HIF_DEVICE_GET_OS_DEVICE:
|
|
|
+ /* pass back a pointer to the SDIO function's "dev" struct */
|
|
|
+ ((struct HIF_DEVICE_OS_DEVICE_INFO *) config)->os_dev =
|
|
|
+ &device->func->dev;
|
|
|
+ break;
|
|
|
+ case HIF_DEVICE_POWER_STATE_CHANGE:
|
|
|
+ status =
|
|
|
+ power_state_change_notify(device,
|
|
|
+ *(HIF_DEVICE_POWER_CHANGE_TYPE *)
|
|
|
+ config);
|
|
|
+ break;
|
|
|
+ case HIF_DEVICE_GET_IRQ_YIELD_PARAMS:
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_WARN,
|
|
|
+ ("%s: configuration opcode %d\n",
|
|
|
+ __func__, opcode));
|
|
|
+ status = QDF_STATUS_E_FAILURE;
|
|
|
+ break;
|
|
|
+ case HIF_DEVICE_SET_HTC_CONTEXT:
|
|
|
+ device->htc_context = config;
|
|
|
+ break;
|
|
|
+ case HIF_DEVICE_GET_HTC_CONTEXT:
|
|
|
+ if (config == NULL) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_WARN,
|
|
|
+ ("%s: htc context is NULL\n",
|
|
|
+ __func__));
|
|
|
+ return QDF_STATUS_E_FAILURE;
|
|
|
+ }
|
|
|
+ *(void **)config = device->htc_context;
|
|
|
+ break;
|
|
|
+ case HIF_BMI_DONE:
|
|
|
+ {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERROR,
|
|
|
+ ("%s: BMI_DONE\n", __func__));
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ default:
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_WARN,
|
|
|
+ ("%s: Unsupported configuration opcode: %d\n",
|
|
|
+ __func__, opcode));
|
|
|
+ status = QDF_STATUS_E_FAILURE;
|
|
|
+ }
|
|
|
+
|
|
|
+ return status;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * hif_sdio_shutdown() - hif-sdio shutdown routine
|
|
|
+ * @hif_ctx: pointer to hif_softc structore
|
|
|
+ *
|
|
|
+ * Return: None.
|
|
|
+ */
|
|
|
+void hif_sdio_shutdown(struct hif_softc *hif_ctx)
|
|
|
+{
|
|
|
+ struct hif_sdio_softc *scn = HIF_GET_SDIO_SOFTC(hif_ctx);
|
|
|
+ struct hif_sdio_dev *hif_device = scn->hif_handle;
|
|
|
+
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_TRACE,
|
|
|
+ ("%s: Enter\n", __func__));
|
|
|
+ if (hif_device != NULL) {
|
|
|
+ AR_DEBUG_ASSERT(hif_device->power_config == HIF_DEVICE_POWER_CUT
|
|
|
+ || hif_device->func != NULL);
|
|
|
+ } else {
|
|
|
+ int i;
|
|
|
+ /* since we are unloading the driver anyways,
|
|
|
+ * reset all cards in case the SDIO card is
|
|
|
+ * externally powered and we are unloading the SDIO
|
|
|
+ * stack. This avoids the problem when the SDIO stack
|
|
|
+ * is reloaded and attempts are made to re-enumerate
|
|
|
+ * a card that is already enumerated */
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_TRACE,
|
|
|
+ ("%s: hif_shut_down_device, resetting\n",
|
|
|
+ __func__));
|
|
|
+ reset_all_cards();
|
|
|
+
|
|
|
+ /* Unregister with bus driver core */
|
|
|
+ if (registered) {
|
|
|
+ registered = 0;
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERROR,
|
|
|
+ ("%s: Unregistering with the bus driver\n",
|
|
|
+ __func__));
|
|
|
+ hif_sdio_remove_callbacks();
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERROR,
|
|
|
+ ("%s: Unregistered!",
|
|
|
+ __func__));
|
|
|
+ }
|
|
|
+
|
|
|
+ for (i = 0; i < MAX_HIF_DEVICES; ++i) {
|
|
|
+ if (hif_devices[i] && hif_devices[i]->func == NULL) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_TRACE,
|
|
|
+ ("%s: Remove pending hif_device %p\n",
|
|
|
+ __func__, hif_devices[i]));
|
|
|
+ del_hif_device(hif_devices[i]);
|
|
|
+ hif_devices[i] = NULL;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_TRACE,
|
|
|
+ ("%s: Exit\n", __func__));
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * hif_irq_handler() - hif-sdio interrupt handler
|
|
|
+ * @func: pointer to sdio_func
|
|
|
+ *
|
|
|
+ * Return: None.
|
|
|
+ */
|
|
|
+static void hif_irq_handler(struct sdio_func *func)
|
|
|
+{
|
|
|
+ QDF_STATUS status;
|
|
|
+ struct hif_sdio_dev *device;
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_TRACE,
|
|
|
+ ("%s: Enter\n", __func__));
|
|
|
+
|
|
|
+ device = get_hif_device(func);
|
|
|
+ atomic_set(&device->irq_handling, 1);
|
|
|
+ /* release the host during intr so we can use
|
|
|
+ * it when we process cmds */
|
|
|
+ sdio_release_host(device->func);
|
|
|
+ status = device->htc_callbacks.dsrHandler(device->htc_callbacks
|
|
|
+ .context);
|
|
|
+ sdio_claim_host(device->func);
|
|
|
+ atomic_set(&device->irq_handling, 0);
|
|
|
+ AR_DEBUG_ASSERT(status == QDF_STATUS_SUCCESS ||
|
|
|
+ status == QDF_STATUS_E_CANCELED);
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_TRACE,
|
|
|
+ ("%s: Exit\n", __func__));
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * startup_task() - startup task to fill ol_softc
|
|
|
+ * @param: pointer to struct hif_sdio_dev
|
|
|
+ *
|
|
|
+ * Return: 0 on success, error number otherwise.
|
|
|
+ */
|
|
|
+static int startup_task(void *param)
|
|
|
+{
|
|
|
+ struct hif_sdio_dev *device;
|
|
|
+
|
|
|
+ device = (struct hif_sdio_dev *) param;
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_TRACE,
|
|
|
+ ("%s: call HTC from startup_task\n",
|
|
|
+ __func__));
|
|
|
+ /* start up inform DRV layer */
|
|
|
+ if ((osdrv_callbacks.
|
|
|
+ device_inserted_handler(osdrv_callbacks.context,
|
|
|
+ device)) != QDF_STATUS_SUCCESS) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_TRACE,
|
|
|
+ ("%s: Device rejected\n", __func__));
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int enable_task(void *param)
|
|
|
+{
|
|
|
+ struct hif_sdio_dev *device;
|
|
|
+ device = (struct hif_sdio_dev *) param;
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_TRACE,
|
|
|
+ ("%s: call from resume_task\n",
|
|
|
+ __func__));
|
|
|
+
|
|
|
+ /* start up inform DRV layer */
|
|
|
+ if (device &&
|
|
|
+ device->claimed_ctx &&
|
|
|
+ osdrv_callbacks.device_power_change_handler &&
|
|
|
+ osdrv_callbacks.device_power_change_handler(device->claimed_ctx,
|
|
|
+ HIF_DEVICE_POWER_UP) !=
|
|
|
+ QDF_STATUS_SUCCESS) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_TRACE,
|
|
|
+ ("%s: Device rejected\n",
|
|
|
+ __func__));
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * foce_drive_strength() - Set sdio drive strength
|
|
|
+ * @func: pointer to sdio_func
|
|
|
+ *
|
|
|
+ * Return: none.
|
|
|
+ */
|
|
|
+static void foce_drive_strength(struct sdio_func *func)
|
|
|
+{
|
|
|
+ unsigned int addr = SDIO_CCCR_DRIVE_STRENGTH;
|
|
|
+ unsigned char value = 0;
|
|
|
+
|
|
|
+ uint32_t err = func0_cmd52_read_byte(func->card,
|
|
|
+ addr, &value);
|
|
|
+ if (err) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERROR,
|
|
|
+ ("%s: Read CCCR 0x%02X failed: %d\n",
|
|
|
+ __func__,
|
|
|
+ (unsigned int) addr,
|
|
|
+ (unsigned int) err));
|
|
|
+ } else {
|
|
|
+ value = (value &
|
|
|
+ (~(SDIO_DRIVE_DTSx_MASK <<
|
|
|
+ SDIO_DRIVE_DTSx_SHIFT))) |
|
|
|
+ SDIO_DTSx_SET_TYPE_D;
|
|
|
+ err = func0_cmd52_write_byte(func->card, addr,
|
|
|
+ value);
|
|
|
+ if (err) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERROR,
|
|
|
+ ("%s: Write CCCR 0x%02X to 0x%02X failed: %d\n",
|
|
|
+ __func__,
|
|
|
+ (unsigned int) addr,
|
|
|
+ (unsigned int) value,
|
|
|
+ (unsigned int) err));
|
|
|
+ } else {
|
|
|
+ addr = CCCR_SDIO_DRIVER_STRENGTH_ENABLE_ADDR;
|
|
|
+ value = 0;
|
|
|
+ err = func0_cmd52_read_byte(func->card,
|
|
|
+ addr, &value);
|
|
|
+ if (err) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERROR,
|
|
|
+ ("Read CCCR 0x%02X failed: %d\n",
|
|
|
+ (unsigned int) addr,
|
|
|
+ (unsigned int) err));
|
|
|
+ } else {
|
|
|
+ value = (value &
|
|
|
+ (~CCCR_SDIO_DRIVER_STRENGTH_ENABLE_MASK))
|
|
|
+ | CCCR_SDIO_DRIVER_STRENGTH_ENABLE_A
|
|
|
+ | CCCR_SDIO_DRIVER_STRENGTH_ENABLE_C
|
|
|
+ | CCCR_SDIO_DRIVER_STRENGTH_ENABLE_D;
|
|
|
+ err = func0_cmd52_write_byte(func->card,
|
|
|
+ addr, value);
|
|
|
+ if (err) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERROR,
|
|
|
+ ("Write CCCR 0x%02X to 0x%02X failed: %d\n",
|
|
|
+ (unsigned int) addr,
|
|
|
+ (unsigned int) value,
|
|
|
+ (unsigned int) err));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * write_cccr() - write CCCR
|
|
|
+ * @func: pointer to sdio_func
|
|
|
+ *
|
|
|
+ * Return: none.
|
|
|
+ */
|
|
|
+static void write_cccr(struct sdio_func *func)
|
|
|
+{
|
|
|
+ if (writecccr1) {
|
|
|
+ uint32_t err = func0_cmd52_write_byte(func->card,
|
|
|
+ writecccr1,
|
|
|
+ writecccr1value);
|
|
|
+ if (err) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERROR,
|
|
|
+ ("Write CCCR 0x%02X to 0x%02X failed: %d\n",
|
|
|
+ (unsigned int)writecccr1,
|
|
|
+ (unsigned int)writecccr1value,
|
|
|
+ (unsigned int)err));
|
|
|
+ } else {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERROR,
|
|
|
+ ("Write CCCR 0x%02X to 0x%02X OK\n",
|
|
|
+ (unsigned int)writecccr1,
|
|
|
+ (unsigned int)writecccr1value));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (writecccr2) {
|
|
|
+ uint32_t err = func0_cmd52_write_byte(func->card,
|
|
|
+ writecccr2,
|
|
|
+ writecccr2value);
|
|
|
+ if (err) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERROR,
|
|
|
+ ("Write CCCR 0x%02X to 0x%02X failed: %d\n",
|
|
|
+ (unsigned int)writecccr2,
|
|
|
+ (unsigned int)writecccr2value,
|
|
|
+ (unsigned int)err));
|
|
|
+ } else {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERROR,
|
|
|
+ ("Write CCCR 0x%02X to 0x%02X OK\n",
|
|
|
+ (unsigned int)writecccr2,
|
|
|
+ (unsigned int)writecccr2value));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (writecccr3) {
|
|
|
+ uint32_t err = func0_cmd52_write_byte(func->card,
|
|
|
+ writecccr3,
|
|
|
+ writecccr3value);
|
|
|
+ if (err) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERROR,
|
|
|
+ ("Write CCCR 0x%02X to 0x%02X failed: %d\n",
|
|
|
+ (unsigned int)writecccr3,
|
|
|
+ (unsigned int)writecccr3value,
|
|
|
+ (unsigned int)err));
|
|
|
+ } else {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERROR,
|
|
|
+ ("Write CCCR 0x%02X to 0x%02X OK\n",
|
|
|
+ (unsigned int)writecccr3,
|
|
|
+ (unsigned int)writecccr3value));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (writecccr4) {
|
|
|
+ uint32_t err = func0_cmd52_write_byte(func->card,
|
|
|
+ writecccr4,
|
|
|
+ writecccr4value);
|
|
|
+ if (err)
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERROR,
|
|
|
+ ("Write CCCR 0x%02X to 0x%02X failed: %d\n",
|
|
|
+ (unsigned int)writecccr4,
|
|
|
+ (unsigned int)writecccr4value,
|
|
|
+ (unsigned int)err));
|
|
|
+ else
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERROR,
|
|
|
+ ("Write CCCR 0x%02X to 0x%02X OK\n",
|
|
|
+ (unsigned int)writecccr4,
|
|
|
+ (unsigned int)writecccr4value));
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * hif_device_inserted() - hif-sdio driver probe handler
|
|
|
+ * @func: pointer to sdio_func
|
|
|
+ * @id: pointer to sdio_device_id
|
|
|
+ *
|
|
|
+ * Return: 0 on success, error number otherwise.
|
|
|
+ */
|
|
|
+static int hif_device_inserted(struct sdio_func *func,
|
|
|
+ const struct sdio_device_id *id)
|
|
|
+{
|
|
|
+ int i;
|
|
|
+ int ret;
|
|
|
+ struct hif_sdio_dev *device = NULL;
|
|
|
+ int count;
|
|
|
+ uint32_t clock, clock_set = 12500000;
|
|
|
+
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_TRACE,
|
|
|
+ ("%s: Function: "
|
|
|
+ "0x%X, Vendor ID: 0x%X, Device ID: 0x%X, "
|
|
|
+ "block size: 0x%X/0x%X\n",
|
|
|
+ __func__,
|
|
|
+ func->num, func->vendor, id->device,
|
|
|
+ func->max_blksize,
|
|
|
+ func->cur_blksize));
|
|
|
+ /* dma_mask should not be NULL, otherwise dma_map_single
|
|
|
+ * will crash. TODO: check why dma_mask is NULL here */
|
|
|
+ if (func->dev.dma_mask == NULL) {
|
|
|
+ static u64 dma_mask = 0xFFFFFFFF;
|
|
|
+ func->dev.dma_mask = &dma_mask;
|
|
|
+ }
|
|
|
+ for (i = 0; i < MAX_HIF_DEVICES; ++i) {
|
|
|
+ struct hif_sdio_dev *hifdevice = hif_devices[i];
|
|
|
+ if (hifdevice && hifdevice->power_config == HIF_DEVICE_POWER_CUT
|
|
|
+ && hifdevice->host == func->card->host) {
|
|
|
+ hifdevice->func = func;
|
|
|
+ hifdevice->power_config = HIF_DEVICE_POWER_UP;
|
|
|
+ sdio_set_drvdata(func, hifdevice);
|
|
|
+ device = get_hif_device(func);
|
|
|
+
|
|
|
+ if (device->is_suspend) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_TRACE,
|
|
|
+ ("%s: Resume from suspend",
|
|
|
+ __func__));
|
|
|
+ ret = reinit_sdio(device);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (device == NULL) {
|
|
|
+ if (add_hif_device(func) == NULL)
|
|
|
+ return QDF_STATUS_E_FAILURE;
|
|
|
+ device = get_hif_device(func);
|
|
|
+
|
|
|
+ for (i = 0; i < MAX_HIF_DEVICES; ++i) {
|
|
|
+ if (hif_devices[i] == NULL) {
|
|
|
+ hif_devices[i] = device;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (i == MAX_HIF_DEVICES) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERROR,
|
|
|
+ ("%s: No more hif_devices[] slot for %p",
|
|
|
+ __func__, device));
|
|
|
+ }
|
|
|
+
|
|
|
+ device->id = id;
|
|
|
+ device->host = func->card->host;
|
|
|
+ device->is_disabled = true;
|
|
|
+/* TODO: MMC SDIO3.0 Setting should also be modified in ReInit()
|
|
|
+ * function when Power Manage work. */
|
|
|
+ sdio_claim_host(func);
|
|
|
+ /* force driver strength to type D */
|
|
|
+ if (forcedriverstrength == 1)
|
|
|
+ foce_drive_strength(func);
|
|
|
+ write_cccr(func);
|
|
|
+ /* Set MMC Clock */
|
|
|
+ if (mmcclock > 0)
|
|
|
+ clock_set = mmcclock;
|
|
|
+ if (sdio_card_highspeed(func->card))
|
|
|
+ clock = 50000000;
|
|
|
+ else
|
|
|
+ clock = func->card->cis.max_dtr;
|
|
|
+ if (clock > device->host->f_max)
|
|
|
+ clock = device->host->f_max;
|
|
|
+
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERROR,
|
|
|
+ ("%s: Dumping clocks (%d,%d)\n",
|
|
|
+ __func__, func->card->cis.max_dtr,
|
|
|
+ device->host->f_max));
|
|
|
+
|
|
|
+ /* only when mmcclock module parameter is specified,
|
|
|
+ * set the clock explicitly
|
|
|
+ */
|
|
|
+ if (mmcclock > 0) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERR,
|
|
|
+ ("Decrease host clock from %d to %d(%d,%d)\n",
|
|
|
+ clock, clock_set,
|
|
|
+ func->card->cis.max_dtr,
|
|
|
+ device->host->f_max));
|
|
|
+ device->host->ios.clock = clock_set;
|
|
|
+ device->host->ops->set_ios(device->host,
|
|
|
+ &device->host->ios);
|
|
|
+ }
|
|
|
+ /* Set SDIO3.0 */
|
|
|
+ /* Set MMC Bus Width: 1-1Bit, 4-4Bit, 8-8Bit */
|
|
|
+ if (mmcbuswidth > 0) {
|
|
|
+ if (mmcbuswidth == 1) {
|
|
|
+ ret =
|
|
|
+ func0_cmd52_write_byte(func->card,
|
|
|
+ SDIO_CCCR_IF,
|
|
|
+ SDIO_BUS_CD_DISABLE
|
|
|
+ |
|
|
|
+ SDIO_BUS_WIDTH_1BIT);
|
|
|
+ if (ret) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERR,
|
|
|
+ ("%s: CMD52 to set bus width failed: %d\n",
|
|
|
+ __func__,
|
|
|
+ ret));
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+ device->host->ios.bus_width =
|
|
|
+ MMC_BUS_WIDTH_1;
|
|
|
+ device->host->ops->set_ios(device->host,
|
|
|
+ &device->
|
|
|
+ host->ios);
|
|
|
+ } else if (mmcbuswidth == 4
|
|
|
+ && (device->host->
|
|
|
+ caps & MMC_CAP_4_BIT_DATA)) {
|
|
|
+ ret =
|
|
|
+ func0_cmd52_write_byte(func->card,
|
|
|
+ SDIO_CCCR_IF,
|
|
|
+ SDIO_BUS_CD_DISABLE
|
|
|
+ |
|
|
|
+ SDIO_BUS_WIDTH_4BIT);
|
|
|
+ if (ret) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERR,
|
|
|
+ ("%s: CMD52 to bus width failed: %d\n",
|
|
|
+ __func__,
|
|
|
+ ret));
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+ device->host->ios.bus_width =
|
|
|
+ MMC_BUS_WIDTH_4;
|
|
|
+ device->host->ops->set_ios(device->host,
|
|
|
+ &device->
|
|
|
+ host->ios);
|
|
|
+ } else if (mmcbuswidth == 8
|
|
|
+ && (device->host->
|
|
|
+ caps & MMC_CAP_8_BIT_DATA)) {
|
|
|
+ ret =
|
|
|
+ func0_cmd52_write_byte(func->card,
|
|
|
+ SDIO_CCCR_IF,
|
|
|
+ SDIO_BUS_CD_DISABLE
|
|
|
+ |
|
|
|
+ SDIO_BUS_WIDTH_8BIT);
|
|
|
+ if (ret) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERR,
|
|
|
+ ("%s: CMD52 to bus width failed: %d\n",
|
|
|
+ __func__,
|
|
|
+ ret));
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+ device->host->ios.bus_width =
|
|
|
+ MMC_BUS_WIDTH_8;
|
|
|
+ device->host->ops->set_ios(device->host,
|
|
|
+ &device->
|
|
|
+ host->ios);
|
|
|
+ } else {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERR,
|
|
|
+ ("%s: MMC bus width %d is not supported.\n",
|
|
|
+ __func__,
|
|
|
+ mmcbuswidth));
|
|
|
+ return ret = QDF_STATUS_E_FAILURE;
|
|
|
+ }
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ANY,
|
|
|
+ ("%s: Set MMC bus width to %dBit.\n",
|
|
|
+ __func__, mmcbuswidth));
|
|
|
+ }
|
|
|
+ if (debugcccr)
|
|
|
+ hif_dump_cccr(device);
|
|
|
+
|
|
|
+ sdio_release_host(func);
|
|
|
+ }
|
|
|
+
|
|
|
+ qdf_spinlock_create(&device->lock);
|
|
|
+
|
|
|
+ qdf_spinlock_create(&device->asynclock);
|
|
|
+
|
|
|
+ DL_LIST_INIT(&device->scatter_req_head);
|
|
|
+
|
|
|
+ if (!nohifscattersupport) {
|
|
|
+ /* try to allow scatter operation on all instances,
|
|
|
+ * unless globally overridden */
|
|
|
+ device->scatter_enabled = true;
|
|
|
+ } else
|
|
|
+ device->scatter_enabled = false;
|
|
|
+
|
|
|
+ /* Initialize the bus requests to be used later */
|
|
|
+ qdf_mem_zero(device->bus_request, sizeof(device->bus_request));
|
|
|
+ for (count = 0; count < BUS_REQUEST_MAX_NUM; count++) {
|
|
|
+ sema_init(&device->bus_request[count].sem_req, 0);
|
|
|
+ hif_free_bus_request(device, &device->bus_request[count]);
|
|
|
+ }
|
|
|
+ sema_init(&device->sem_async, 0);
|
|
|
+
|
|
|
+ ret = hif_enable_func(device, func);
|
|
|
+
|
|
|
+ return (ret == QDF_STATUS_SUCCESS || ret == QDF_STATUS_E_PENDING)
|
|
|
+ ? 0 : QDF_STATUS_E_FAILURE;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * hif_ack_interrupt() - Acknowledge hif device irq
|
|
|
+ * @device: pointer to struct hif_sdio_dev
|
|
|
+ *
|
|
|
+ * This should translate to an acknowledgment to the bus driver indicating that
|
|
|
+ * the previous interrupt request has been serviced and the all the relevant
|
|
|
+ * sources have been cleared. HTC is ready to process more interrupts.
|
|
|
+ * This should prevent the bus driver from raising an interrupt unless the
|
|
|
+ * previous one has been serviced and acknowledged using the previous API.
|
|
|
+ *
|
|
|
+ * Return: None.
|
|
|
+ */
|
|
|
+void hif_ack_interrupt(struct hif_sdio_dev *device)
|
|
|
+{
|
|
|
+ AR_DEBUG_ASSERT(device != NULL);
|
|
|
+
|
|
|
+ /* Acknowledge our function IRQ */
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * hif_un_mask_interrupt() - Re-enable hif device irq
|
|
|
+ * @device: pointer to struct hif_sdio_dev
|
|
|
+ *
|
|
|
+ *
|
|
|
+ * Return: None.
|
|
|
+ */
|
|
|
+void hif_un_mask_interrupt(struct hif_sdio_dev *device)
|
|
|
+{
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ AR_DEBUG_ASSERT(device != NULL);
|
|
|
+ AR_DEBUG_ASSERT(device->func != NULL);
|
|
|
+
|
|
|
+ HIF_ENTER();
|
|
|
+ /*
|
|
|
+ * On HP Elitebook 8460P, interrupt mode is not stable
|
|
|
+ * in high throughput, so polling method should be used
|
|
|
+ * instead of interrupt mode. */
|
|
|
+ if (brokenirq) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_INFO,
|
|
|
+ ("%s: Using broken IRQ mode\n",
|
|
|
+ __func__));
|
|
|
+ /* disable IRQ support even the capability exists */
|
|
|
+ device->func->card->host->caps &= ~MMC_CAP_SDIO_IRQ;
|
|
|
+ }
|
|
|
+ /* Register the IRQ Handler */
|
|
|
+ sdio_claim_host(device->func);
|
|
|
+ ret = sdio_claim_irq(device->func, hif_irq_handler);
|
|
|
+ sdio_release_host(device->func);
|
|
|
+ AR_DEBUG_ASSERT(ret == 0);
|
|
|
+ HIF_EXIT();
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * hif_mask_interrupt() - Disable hif device irq
|
|
|
+ * @device: pointer to struct hif_sdio_dev
|
|
|
+ *
|
|
|
+ *
|
|
|
+ * Return: None.
|
|
|
+ */
|
|
|
+void hif_mask_interrupt(struct hif_sdio_dev *device)
|
|
|
+{
|
|
|
+ int ret;
|
|
|
+ AR_DEBUG_ASSERT(device != NULL);
|
|
|
+ AR_DEBUG_ASSERT(device->func != NULL);
|
|
|
+
|
|
|
+ HIF_ENTER();
|
|
|
+
|
|
|
+ /* Mask our function IRQ */
|
|
|
+ sdio_claim_host(device->func);
|
|
|
+ while (atomic_read(&device->irq_handling)) {
|
|
|
+ sdio_release_host(device->func);
|
|
|
+ schedule_timeout_interruptible(HZ / 10);
|
|
|
+ sdio_claim_host(device->func);
|
|
|
+ }
|
|
|
+ ret = sdio_release_irq(device->func);
|
|
|
+ sdio_release_host(device->func);
|
|
|
+ if (ret) {
|
|
|
+ if (ret == -ETIMEDOUT) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_WARN,
|
|
|
+ ("%s: Timeout to mask interrupt\n",
|
|
|
+ __func__));
|
|
|
+ } else {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERROR,
|
|
|
+ ("%s: Unable to mask interrupt %d\n",
|
|
|
+ __func__, ret));
|
|
|
+ AR_DEBUG_ASSERT(ret == 0);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ HIF_EXIT();
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * hif_allocate_bus_request() - Allocate hif bus request
|
|
|
+ * @device: pointer to struct hif_sdio_dev
|
|
|
+ *
|
|
|
+ *
|
|
|
+ * Return: pointer to struct bus_request structure.
|
|
|
+ */
|
|
|
+struct bus_request *hif_allocate_bus_request(struct hif_sdio_dev *device)
|
|
|
+{
|
|
|
+ struct bus_request *busrequest;
|
|
|
+
|
|
|
+ qdf_spin_lock_irqsave(&device->lock);
|
|
|
+ busrequest = device->bus_request_free_queue;
|
|
|
+ /* Remove first in list */
|
|
|
+ if (busrequest != NULL)
|
|
|
+ device->bus_request_free_queue = busrequest->next;
|
|
|
+
|
|
|
+ /* Release lock */
|
|
|
+ qdf_spin_unlock_irqrestore(&device->lock);
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_TRACE,
|
|
|
+ ("%s: hif_allocate_bus_request: 0x%p\n",
|
|
|
+ __func__, busrequest));
|
|
|
+
|
|
|
+ return busrequest;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * hif_free_bus_request() - Free hif bus request
|
|
|
+ * @device: pointer to struct hif_sdio_dev
|
|
|
+ *
|
|
|
+ *
|
|
|
+ * Return: None.
|
|
|
+ */
|
|
|
+void hif_free_bus_request(struct hif_sdio_dev *device,
|
|
|
+ struct bus_request *busrequest)
|
|
|
+{
|
|
|
+ AR_DEBUG_ASSERT(busrequest != NULL);
|
|
|
+ /* Acquire lock */
|
|
|
+ qdf_spin_lock_irqsave(&device->lock);
|
|
|
+
|
|
|
+ /* Insert first in list */
|
|
|
+ busrequest->next = device->bus_request_free_queue;
|
|
|
+ busrequest->inusenext = NULL;
|
|
|
+ device->bus_request_free_queue = busrequest;
|
|
|
+
|
|
|
+ /* Release lock */
|
|
|
+ qdf_spin_unlock_irqrestore(&device->lock);
|
|
|
+}
|
|
|
+
|
|
|
+static QDF_STATUS hif_disable_func(struct hif_sdio_dev *device,
|
|
|
+ struct sdio_func *func)
|
|
|
+{
|
|
|
+ int ret;
|
|
|
+ QDF_STATUS status = QDF_STATUS_SUCCESS;
|
|
|
+
|
|
|
+ HIF_ENTER();
|
|
|
+ device = get_hif_device(func);
|
|
|
+ if (!IS_ERR(device->async_task)) {
|
|
|
+ init_completion(&device->async_completion);
|
|
|
+ device->async_shutdown = 1;
|
|
|
+ up(&device->sem_async);
|
|
|
+ wait_for_completion(&device->async_completion);
|
|
|
+ device->async_task = NULL;
|
|
|
+ sema_init(&device->sem_async, 0);
|
|
|
+ }
|
|
|
+ /* Disable the card */
|
|
|
+ sdio_claim_host(device->func);
|
|
|
+ ret = sdio_disable_func(device->func);
|
|
|
+ if (ret)
|
|
|
+ status = QDF_STATUS_E_FAILURE;
|
|
|
+
|
|
|
+ if (reset_sdio_on_unload && status == QDF_STATUS_SUCCESS) {
|
|
|
+ /* reset the SDIO interface. It's useful in automated testing
|
|
|
+ * where the card does not need to be removed at the end
|
|
|
+ * of the test. It is expected that the user will also
|
|
|
+ * un/reload the host controller driver to force the bus
|
|
|
+ * driver to re-enumerate the slot */
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_WARN,
|
|
|
+ ("%s: reseting SDIO card",
|
|
|
+ __func__));
|
|
|
+
|
|
|
+ /* sdio_f0_writeb() cannot be used here, this allows access
|
|
|
+ * to undefined registers in the range of: 0xF0-0xFF */
|
|
|
+
|
|
|
+ ret =
|
|
|
+ func0_cmd52_write_byte(device->func->card,
|
|
|
+ SDIO_CCCR_ABORT,
|
|
|
+ (1 << 3));
|
|
|
+ if (ret) {
|
|
|
+ status = QDF_STATUS_E_FAILURE;
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERR,
|
|
|
+ ("%s: reset failed : %d\n",
|
|
|
+ __func__, ret));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ sdio_release_host(device->func);
|
|
|
+
|
|
|
+ if (status == QDF_STATUS_SUCCESS)
|
|
|
+ device->is_disabled = true;
|
|
|
+ cleanup_hif_scatter_resources(device);
|
|
|
+
|
|
|
+ HIF_EXIT();
|
|
|
+
|
|
|
+ return status;
|
|
|
+}
|
|
|
+
|
|
|
+static QDF_STATUS hif_enable_func(struct hif_sdio_dev *device,
|
|
|
+ struct sdio_func *func)
|
|
|
+{
|
|
|
+ struct task_struct *task;
|
|
|
+ const char *task_name = NULL;
|
|
|
+ int (*taskFunc)(void *) = NULL;
|
|
|
+ int ret = QDF_STATUS_SUCCESS;
|
|
|
+
|
|
|
+ HIF_ENTER("sdio_func 0x%p", func);
|
|
|
+
|
|
|
+ device = get_hif_device(func);
|
|
|
+
|
|
|
+ if (device->is_disabled) {
|
|
|
+ int setAsyncIRQ = 0;
|
|
|
+ __u16 manufacturer_id =
|
|
|
+ device->id->device & MANUFACTURER_ID_AR6K_BASE_MASK;
|
|
|
+ /* enable the SDIO function */
|
|
|
+ sdio_claim_host(func);
|
|
|
+ /* enable 4-bit ASYNC interrupt on AR6003x or later devices */
|
|
|
+ if (manufacturer_id == MANUFACTURER_ID_AR6003_BASE) {
|
|
|
+ setAsyncIRQ = 1;
|
|
|
+ ret =
|
|
|
+ func0_cmd52_write_byte(func->card,
|
|
|
+ CCCR_SDIO_IRQ_MODE_REG_AR6003,
|
|
|
+ SDIO_IRQ_MODE_ASYNC_4BIT_IRQ_AR6003);
|
|
|
+ } else if (manufacturer_id == MANUFACTURER_ID_AR6320_BASE
|
|
|
+ || manufacturer_id == MANUFACTURER_ID_QCA9377_BASE) {
|
|
|
+ unsigned char data = 0;
|
|
|
+ setAsyncIRQ = 1;
|
|
|
+ ret =
|
|
|
+ func0_cmd52_read_byte(func->card,
|
|
|
+ CCCR_SDIO_IRQ_MODE_REG_AR6320,
|
|
|
+ &data);
|
|
|
+ if (ret) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERR,
|
|
|
+ ("%s: failed to read irq reg %d\n",
|
|
|
+ __func__, ret));
|
|
|
+ sdio_release_host(func);
|
|
|
+ return QDF_STATUS_E_FAILURE;
|
|
|
+ }
|
|
|
+ data |= SDIO_IRQ_MODE_ASYNC_4BIT_IRQ_AR6320;
|
|
|
+ ret =
|
|
|
+ func0_cmd52_write_byte(func->card,
|
|
|
+ CCCR_SDIO_IRQ_MODE_REG_AR6320,
|
|
|
+ data);
|
|
|
+ }
|
|
|
+ if (setAsyncIRQ) {
|
|
|
+ if (ret) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERR,
|
|
|
+ ("%s: failed to enable ASYNC IRQ mode %d\n",
|
|
|
+ __func__, ret));
|
|
|
+ sdio_release_host(func);
|
|
|
+ return QDF_STATUS_E_FAILURE;
|
|
|
+ }
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_TRACE,
|
|
|
+ ("%s: 4-bit ASYNC IRQ mode enabled\n",
|
|
|
+ __func__));
|
|
|
+ }
|
|
|
+
|
|
|
+ /* set CCCR 0xF0[7:6] to increase async interrupt delay clock to
|
|
|
+ * fix interrupt missing issue on dell 8460p */
|
|
|
+ if (asyncintdelay != 0) {
|
|
|
+ unsigned char data = 0;
|
|
|
+ ret =
|
|
|
+ func0_cmd52_read_byte(func->card,
|
|
|
+ CCCR_SDIO_ASYNC_INT_DELAY_ADDRESS,
|
|
|
+ &data);
|
|
|
+ if (ret) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERR,
|
|
|
+ ("%s: failed to read CCCR %d, val is %d\n",
|
|
|
+ __func__,
|
|
|
+ CCCR_SDIO_ASYNC_INT_DELAY_ADDRESS,
|
|
|
+ ret));
|
|
|
+ sdio_release_host(func);
|
|
|
+ return QDF_STATUS_E_FAILURE;
|
|
|
+ }
|
|
|
+ data = (data & ~CCCR_SDIO_ASYNC_INT_DELAY_MASK) |
|
|
|
+ ((asyncintdelay <<
|
|
|
+ CCCR_SDIO_ASYNC_INT_DELAY_LSB) &
|
|
|
+ CCCR_SDIO_ASYNC_INT_DELAY_MASK);
|
|
|
+ ret =
|
|
|
+ func0_cmd52_write_byte(func->card,
|
|
|
+ CCCR_SDIO_ASYNC_INT_DELAY_ADDRESS,
|
|
|
+ data);
|
|
|
+ if (ret) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERR,
|
|
|
+ ("%s: failed to write CCCR %d, val is %d\n",
|
|
|
+ __func__,
|
|
|
+ CCCR_SDIO_ASYNC_INT_DELAY_ADDRESS,
|
|
|
+ ret));
|
|
|
+ sdio_release_host(func);
|
|
|
+ return QDF_STATUS_E_FAILURE;
|
|
|
+ }
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERROR,
|
|
|
+ ("%s: Set async interrupt delay clock as %d.\n",
|
|
|
+ __func__,
|
|
|
+ asyncintdelay));
|
|
|
+ }
|
|
|
+ /* give us some time to enable, in ms */
|
|
|
+ func->enable_timeout = 100;
|
|
|
+ ret = sdio_enable_func(func);
|
|
|
+ if (ret) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERROR,
|
|
|
+ ("%s: Unable to enable AR6K: 0x%X\n",
|
|
|
+ __func__, ret));
|
|
|
+ sdio_release_host(func);
|
|
|
+ return QDF_STATUS_E_FAILURE;
|
|
|
+ }
|
|
|
+ ret = sdio_set_block_size(func, HIF_MBOX_BLOCK_SIZE);
|
|
|
+
|
|
|
+ if (modstrength) {
|
|
|
+ unsigned int address = WINDOW_DATA_ADDRESS;
|
|
|
+ unsigned int value = 0x0FFF;
|
|
|
+ ret =
|
|
|
+ sdio_memcpy_toio(device->func, address,
|
|
|
+ &value, 4);
|
|
|
+ if (ret) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_INFO,
|
|
|
+ ("memcpy_toio 0x%x 0x%x error:%d\n",
|
|
|
+ address, value, ret));
|
|
|
+ } else {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_INFO,
|
|
|
+ ("memcpy_toio, 0x%x 0x%x OK\n", address,
|
|
|
+ value));
|
|
|
+ address = WINDOW_WRITE_ADDR_ADDRESS;
|
|
|
+ value = 0x50F8;
|
|
|
+ ret =
|
|
|
+ sdio_memcpy_toio(device->func, address,
|
|
|
+ &value, 4);
|
|
|
+ if (ret)
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_INFO,
|
|
|
+ ("memcpy_toio 0x%x 0x%x error:%d\n",
|
|
|
+ address, value, ret));
|
|
|
+ else
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_INFO,
|
|
|
+ ("memcpy_toio, 0x%x 0x%x OK\n",
|
|
|
+ address, value));
|
|
|
+ }
|
|
|
+ };
|
|
|
+ sdio_release_host(func);
|
|
|
+ if (ret) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERROR,
|
|
|
+ ("%s: can't set block size 0x%x AR6K: 0x%X\n",
|
|
|
+ __func__, HIF_MBOX_BLOCK_SIZE,
|
|
|
+ ret));
|
|
|
+ return QDF_STATUS_E_FAILURE;
|
|
|
+ }
|
|
|
+ device->is_disabled = false;
|
|
|
+ /* create async I/O thread */
|
|
|
+ if (!device->async_task) {
|
|
|
+ device->async_shutdown = 0;
|
|
|
+ device->async_task = kthread_create(async_task,
|
|
|
+ (void *)device,
|
|
|
+ "AR6K Async");
|
|
|
+ if (IS_ERR(device->async_task)) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERROR,
|
|
|
+ ("%s: to create async task\n",
|
|
|
+ __func__));
|
|
|
+ return QDF_STATUS_E_FAILURE;
|
|
|
+ }
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_TRACE,
|
|
|
+ ("%s: start async task\n",
|
|
|
+ __func__));
|
|
|
+ wake_up_process(device->async_task);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!device->claimed_ctx) {
|
|
|
+ taskFunc = startup_task;
|
|
|
+ task_name = "AR6K startup";
|
|
|
+ ret = QDF_STATUS_SUCCESS;
|
|
|
+ } else {
|
|
|
+ taskFunc = enable_task;
|
|
|
+ task_name = "AR6K enable";
|
|
|
+ ret = QDF_STATUS_E_PENDING;
|
|
|
+ }
|
|
|
+ /* create resume thread */
|
|
|
+ task = kthread_create(taskFunc, (void *)device, task_name);
|
|
|
+ if (IS_ERR(task)) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERROR,
|
|
|
+ ("%s: to create enabel task\n",
|
|
|
+ __func__));
|
|
|
+ return QDF_STATUS_E_FAILURE;
|
|
|
+ }
|
|
|
+ wake_up_process(task);
|
|
|
+
|
|
|
+ /* task will call the enable func, indicate pending */
|
|
|
+ HIF_EXIT();
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+int hif_device_suspend(struct device *dev)
|
|
|
+{
|
|
|
+ struct sdio_func *func = dev_to_sdio_func(dev);
|
|
|
+ QDF_STATUS status = QDF_STATUS_SUCCESS;
|
|
|
+ int ret = QDF_STATUS_SUCCESS;
|
|
|
+#if defined(MMC_PM_KEEP_POWER)
|
|
|
+ mmc_pm_flag_t pm_flag = 0;
|
|
|
+ HIF_DEVICE_POWER_CHANGE_TYPE config;
|
|
|
+ struct mmc_host *host = NULL;
|
|
|
+#endif
|
|
|
+
|
|
|
+ struct hif_sdio_dev *device = get_hif_device(func);
|
|
|
+
|
|
|
+#if defined(MMC_PM_KEEP_POWER)
|
|
|
+ if (device && device->func)
|
|
|
+ host = device->func->card->host;
|
|
|
+#endif
|
|
|
+
|
|
|
+ HIF_ENTER();
|
|
|
+ if (device && device->claimed_ctx
|
|
|
+ && osdrv_callbacks.device_suspend_handler) {
|
|
|
+ device->is_suspend = true;
|
|
|
+ status =
|
|
|
+ osdrv_callbacks.device_suspend_handler(device->claimed_ctx);
|
|
|
+
|
|
|
+#if defined(MMC_PM_KEEP_POWER)
|
|
|
+ switch (forcesleepmode) {
|
|
|
+ case 0: /* depend on sdio host pm capbility */
|
|
|
+ pm_flag = sdio_get_host_pm_caps(func);
|
|
|
+ break;
|
|
|
+ case 1: /* force WOW */
|
|
|
+ pm_flag |= MMC_PM_KEEP_POWER | MMC_PM_WAKE_SDIO_IRQ;
|
|
|
+ break;
|
|
|
+ case 2: /* force DeepSleep */
|
|
|
+ pm_flag &= ~MMC_PM_WAKE_SDIO_IRQ;
|
|
|
+ pm_flag |= MMC_PM_KEEP_POWER;
|
|
|
+ break;
|
|
|
+ case 3: /* force CutPower */
|
|
|
+ pm_flag &=
|
|
|
+ ~(MMC_PM_WAKE_SDIO_IRQ | MMC_PM_WAKE_SDIO_IRQ);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ if (!(pm_flag & MMC_PM_KEEP_POWER)) {
|
|
|
+ /* cut power support */
|
|
|
+ /* setting power_config before hif_configure_device to
|
|
|
+ * skip sdio r/w when suspending with cut power */
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_INFO,
|
|
|
+ ("hif_device_suspend: cut power enter\n"));
|
|
|
+ config = HIF_DEVICE_POWER_CUT;
|
|
|
+ device->power_config = config;
|
|
|
+ if ((device->claimed_ctx != NULL)
|
|
|
+ && osdrv_callbacks.device_removed_handler) {
|
|
|
+ status = osdrv_callbacks.
|
|
|
+ device_removed_handler(device->
|
|
|
+ claimed_ctx,
|
|
|
+ device);
|
|
|
+ }
|
|
|
+ ret = hif_configure_device(device,
|
|
|
+ HIF_DEVICE_POWER_STATE_CHANGE,
|
|
|
+ &config,
|
|
|
+ sizeof
|
|
|
+ (HIF_DEVICE_POWER_CHANGE_TYPE));
|
|
|
+ if (ret) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERROR,
|
|
|
+ ("%s: hif config device failed: %d\n",
|
|
|
+ __func__, ret));
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ hif_mask_interrupt(device);
|
|
|
+ device->device_state = HIF_DEVICE_STATE_CUTPOWER;
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_INFO,
|
|
|
+ ("hif_device_suspend: cut power success\n"));
|
|
|
+ return ret;
|
|
|
+ } else {
|
|
|
+ ret = sdio_set_host_pm_flags(func, MMC_PM_KEEP_POWER);
|
|
|
+ if (ret) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERROR,
|
|
|
+ ("%s: set sdio pm flags failed %d\n",
|
|
|
+ __func__, ret));
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* TODO:WOW support */
|
|
|
+ if (pm_flag & MMC_PM_WAKE_SDIO_IRQ) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_INFO,
|
|
|
+ ("hif_device_suspend: wow enter\n"));
|
|
|
+ config = HIF_DEVICE_POWER_DOWN;
|
|
|
+ ret = hif_configure_device(device,
|
|
|
+ HIF_DEVICE_POWER_STATE_CHANGE,
|
|
|
+ &config,
|
|
|
+ sizeof
|
|
|
+ (HIF_DEVICE_POWER_CHANGE_TYPE));
|
|
|
+
|
|
|
+ if (ret) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERROR,
|
|
|
+ ("%s: hif config dev failed: %d\n",
|
|
|
+ __func__, ret));
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+ ret =
|
|
|
+ sdio_set_host_pm_flags(func,
|
|
|
+ MMC_PM_WAKE_SDIO_IRQ);
|
|
|
+ if (ret) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERROR,
|
|
|
+ ("%s: set sdio pm flags %d\n",
|
|
|
+ __func__, ret));
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+ hif_mask_interrupt(device);
|
|
|
+ device->device_state = HIF_DEVICE_STATE_WOW;
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_INFO,
|
|
|
+ ("hif_device_suspend: wow success\n"));
|
|
|
+ return ret;
|
|
|
+ } else {
|
|
|
+ /* deep sleep support */
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_INFO,
|
|
|
+ ("%s: deep sleep enter\n",
|
|
|
+ __func__));
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Wait for some async clean handler finished.
|
|
|
+ * These handlers are part of vdev disconnect.
|
|
|
+ * As handlers are async,sleep is not suggested,
|
|
|
+ * some blocking method may be a good choice.
|
|
|
+ * But before adding callback function to these
|
|
|
+ * handler, sleep wait is a simple method.
|
|
|
+ */
|
|
|
+ msleep(100);
|
|
|
+ hif_mask_interrupt(device);
|
|
|
+ device->device_state =
|
|
|
+ HIF_DEVICE_STATE_DEEPSLEEP;
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_INFO,
|
|
|
+ ("%s: deep sleep done\n",
|
|
|
+ __func__));
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+ }
|
|
|
+#endif
|
|
|
+ }
|
|
|
+
|
|
|
+ HIF_EXIT();
|
|
|
+
|
|
|
+ switch (status) {
|
|
|
+ case QDF_STATUS_SUCCESS:
|
|
|
+#if defined(MMC_PM_KEEP_POWER)
|
|
|
+ if (host) {
|
|
|
+ host->pm_flags &=
|
|
|
+ ~(MMC_PM_KEEP_POWER | MMC_PM_WAKE_SDIO_IRQ);
|
|
|
+ }
|
|
|
+#endif
|
|
|
+ return 0;
|
|
|
+ case QDF_STATUS_E_BUSY:
|
|
|
+#if defined(MMC_PM_KEEP_POWER)
|
|
|
+ if (host) {
|
|
|
+ /* WAKE_SDIO_IRQ in order to wake up by DAT1 */
|
|
|
+ host->pm_flags |=
|
|
|
+ (MMC_PM_KEEP_POWER | MMC_PM_WAKE_SDIO_IRQ);
|
|
|
+ host->pm_flags &= host->pm_caps;
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+#else
|
|
|
+ return -EBUSY; /* Hack to support deep sleep and wow */
|
|
|
+#endif
|
|
|
+ default:
|
|
|
+ device->is_suspend = false;
|
|
|
+
|
|
|
+ return QDF_STATUS_E_FAILURE;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+int hif_device_resume(struct device *dev)
|
|
|
+{
|
|
|
+ struct sdio_func *func = dev_to_sdio_func(dev);
|
|
|
+ QDF_STATUS status = QDF_STATUS_SUCCESS;
|
|
|
+ HIF_DEVICE_POWER_CHANGE_TYPE config;
|
|
|
+ struct hif_sdio_dev *device;
|
|
|
+
|
|
|
+ device = get_hif_device(func);
|
|
|
+
|
|
|
+ if (device->device_state == HIF_DEVICE_STATE_CUTPOWER) {
|
|
|
+ config = HIF_DEVICE_POWER_UP;
|
|
|
+ status = hif_configure_device(device,
|
|
|
+ HIF_DEVICE_POWER_STATE_CHANGE,
|
|
|
+ &config,
|
|
|
+ sizeof
|
|
|
+ (HIF_DEVICE_POWER_CHANGE_TYPE));
|
|
|
+ if (status) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERROR,
|
|
|
+ ("%s: hif_configure_device failed\n",
|
|
|
+ __func__));
|
|
|
+ return status;
|
|
|
+ }
|
|
|
+ } else if (device->device_state == HIF_DEVICE_STATE_DEEPSLEEP) {
|
|
|
+ hif_un_mask_interrupt(device);
|
|
|
+ } else if (device->device_state == HIF_DEVICE_STATE_WOW) {
|
|
|
+ /*TODO:WOW support */
|
|
|
+ hif_un_mask_interrupt(device);
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * device_resume_handler do nothing now. If some operation
|
|
|
+ * should be added to this handler in power cut
|
|
|
+ * resume flow, do make sure those operation is not
|
|
|
+ * depent on what startup_task has done,or the resume
|
|
|
+ * flow will block.
|
|
|
+ */
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_TRACE,
|
|
|
+ ("%s: +hif_device_resume\n",
|
|
|
+ __func__));
|
|
|
+ if (device && device->claimed_ctx
|
|
|
+ && osdrv_callbacks.device_suspend_handler) {
|
|
|
+ status =
|
|
|
+ osdrv_callbacks.device_resume_handler(device->claimed_ctx);
|
|
|
+ device->is_suspend = false;
|
|
|
+ }
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_TRACE,
|
|
|
+ ("%s: -hif_device_resume\n",
|
|
|
+ __func__));
|
|
|
+ device->device_state = HIF_DEVICE_STATE_ON;
|
|
|
+
|
|
|
+ return QDF_IS_STATUS_SUCCESS(status) ? 0 : status;
|
|
|
+}
|
|
|
+
|
|
|
+static void hif_device_removed(struct sdio_func *func)
|
|
|
+{
|
|
|
+ QDF_STATUS status = QDF_STATUS_SUCCESS;
|
|
|
+ struct hif_sdio_dev *device;
|
|
|
+ AR_DEBUG_ASSERT(func != NULL);
|
|
|
+
|
|
|
+ HIF_ENTER();
|
|
|
+
|
|
|
+ device = get_hif_device(func);
|
|
|
+
|
|
|
+ if (device->power_config == HIF_DEVICE_POWER_CUT) {
|
|
|
+ device->func = NULL; /* func will be free by mmc stack */
|
|
|
+ return; /* Just return for cut-off mode */
|
|
|
+ } else {
|
|
|
+ int i;
|
|
|
+ for (i = 0; i < MAX_HIF_DEVICES; ++i) {
|
|
|
+ if (hif_devices[i] == device)
|
|
|
+ hif_devices[i] = NULL;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (device->claimed_ctx != NULL)
|
|
|
+ status =
|
|
|
+ osdrv_callbacks.device_removed_handler(device->claimed_ctx,
|
|
|
+ device);
|
|
|
+
|
|
|
+ hif_mask_interrupt(device);
|
|
|
+
|
|
|
+ if (device->is_disabled)
|
|
|
+ device->is_disabled = false;
|
|
|
+ else
|
|
|
+ status = hif_disable_func(device, func);
|
|
|
+
|
|
|
+
|
|
|
+ del_hif_device(device);
|
|
|
+ if (status != QDF_STATUS_SUCCESS)
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_WARN,
|
|
|
+ ("%s: Unable to disable sdio func\n",
|
|
|
+ __func__));
|
|
|
+
|
|
|
+ HIF_EXIT();
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * This should be moved to AR6K HTC layer.
|
|
|
+ */
|
|
|
+QDF_STATUS hif_wait_for_pending_recv(struct hif_sdio_dev *device)
|
|
|
+{
|
|
|
+ int32_t cnt = 10;
|
|
|
+ uint8_t host_int_status;
|
|
|
+ QDF_STATUS status = QDF_STATUS_SUCCESS;
|
|
|
+
|
|
|
+ do {
|
|
|
+ while (atomic_read(&device->irq_handling)) {
|
|
|
+ /* wait until irq handler finished all the jobs */
|
|
|
+ schedule_timeout_interruptible(HZ / 10);
|
|
|
+ }
|
|
|
+ /* check if there is any pending irq due to force done */
|
|
|
+ host_int_status = 0;
|
|
|
+ status = hif_read_write(device, HOST_INT_STATUS_ADDRESS,
|
|
|
+ (uint8_t *) &host_int_status,
|
|
|
+ sizeof(host_int_status),
|
|
|
+ HIF_RD_SYNC_BYTE_INC, NULL);
|
|
|
+ host_int_status =
|
|
|
+ QDF_IS_STATUS_SUCCESS(status) ?
|
|
|
+ (host_int_status & (1 << 0)) : 0;
|
|
|
+ if (host_int_status)
|
|
|
+ /* wait until irq handler finishs its job */
|
|
|
+ schedule_timeout_interruptible(1);
|
|
|
+ } while (host_int_status && --cnt > 0);
|
|
|
+
|
|
|
+ if (host_int_status && cnt == 0)
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERROR,
|
|
|
+ ("%s: Unable clear up pending IRQ\n",
|
|
|
+ __func__));
|
|
|
+
|
|
|
+ return QDF_STATUS_SUCCESS;
|
|
|
+}
|
|
|
+
|
|
|
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 15, 0)) && \
|
|
|
+ !defined(WITH_BACKPORTS)
|
|
|
+/**
|
|
|
+ * hif_sdio_set_drvdata() - set driver data
|
|
|
+ * @func: pointer to sdio function
|
|
|
+ * @hifdevice: pointer to hif device
|
|
|
+ *
|
|
|
+ * Return: non zero for success.
|
|
|
+ */
|
|
|
+static inline int hif_sdio_set_drvdata(struct sdio_func *func,
|
|
|
+ struct hif_sdio_dev *hifdevice)
|
|
|
+{
|
|
|
+ return sdio_set_drvdata(func, hifdevice);
|
|
|
+}
|
|
|
+#else
|
|
|
+static inline int hif_sdio_set_drvdata(struct sdio_func *func,
|
|
|
+ struct hif_sdio_dev *hifdevice)
|
|
|
+{
|
|
|
+ sdio_set_drvdata(func, hifdevice);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+#endif
|
|
|
+
|
|
|
+static struct hif_sdio_dev *add_hif_device(struct sdio_func *func)
|
|
|
+{
|
|
|
+ struct hif_sdio_dev *hifdevice = NULL;
|
|
|
+ int ret = 0;
|
|
|
+
|
|
|
+ HIF_ENTER();
|
|
|
+ AR_DEBUG_ASSERT(func != NULL);
|
|
|
+ hifdevice = (struct hif_sdio_dev *) qdf_mem_malloc(sizeof(
|
|
|
+ struct hif_sdio_dev));
|
|
|
+ AR_DEBUG_ASSERT(hifdevice != NULL);
|
|
|
+ qdf_mem_zero(hifdevice, sizeof(*hifdevice));
|
|
|
+#if HIF_USE_DMA_BOUNCE_BUFFER
|
|
|
+ hifdevice->dma_buffer = qdf_mem_malloc(HIF_DMA_BUFFER_SIZE);
|
|
|
+ AR_DEBUG_ASSERT(hifdevice->dma_buffer != NULL);
|
|
|
+#endif
|
|
|
+ hifdevice->func = func;
|
|
|
+ hifdevice->power_config = HIF_DEVICE_POWER_UP;
|
|
|
+ hifdevice->device_state = HIF_DEVICE_STATE_ON;
|
|
|
+ ret = hif_sdio_set_drvdata(func, hifdevice);
|
|
|
+ HIF_EXIT("status %d", ret);
|
|
|
+
|
|
|
+ return hifdevice;
|
|
|
+}
|
|
|
+
|
|
|
+static struct hif_sdio_dev *get_hif_device(struct sdio_func *func)
|
|
|
+{
|
|
|
+ AR_DEBUG_ASSERT(func != NULL);
|
|
|
+
|
|
|
+ return (struct hif_sdio_dev *) sdio_get_drvdata(func);
|
|
|
+}
|
|
|
+
|
|
|
+static void del_hif_device(struct hif_sdio_dev *device)
|
|
|
+{
|
|
|
+ AR_DEBUG_ASSERT(device != NULL);
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_TRACE,
|
|
|
+ ("%s: deleting hif device 0x%p\n",
|
|
|
+ __func__, device));
|
|
|
+ if (device->dma_buffer != NULL)
|
|
|
+ qdf_mem_free(device->dma_buffer);
|
|
|
+
|
|
|
+ qdf_mem_free(device);
|
|
|
+}
|
|
|
+
|
|
|
+static void reset_all_cards(void)
|
|
|
+{
|
|
|
+}
|
|
|
+
|
|
|
+void hif_release_device(struct hif_opaque_softc *hif_ctx)
|
|
|
+{
|
|
|
+ struct hif_sdio_softc *scn = HIF_GET_SDIO_SOFTC(hif_ctx);
|
|
|
+ struct hif_sdio_dev *hif_device = scn->hif_handle;
|
|
|
+
|
|
|
+ hif_device->claimed_ctx = NULL;
|
|
|
+}
|
|
|
+
|
|
|
+QDF_STATUS hif_attach_htc(struct hif_sdio_dev *device,
|
|
|
+ HTC_CALLBACKS *callbacks)
|
|
|
+{
|
|
|
+ if (device->htc_callbacks.context != NULL)
|
|
|
+ /* already in use! */
|
|
|
+ return QDF_STATUS_E_FAILURE;
|
|
|
+ device->htc_callbacks = *callbacks;
|
|
|
+
|
|
|
+ return QDF_STATUS_SUCCESS;
|
|
|
+}
|
|
|
+
|
|
|
+void hif_detach_htc(struct hif_opaque_softc *hif_ctx)
|
|
|
+{
|
|
|
+ struct hif_sdio_softc *scn = HIF_GET_SDIO_SOFTC(hif_ctx);
|
|
|
+ struct hif_sdio_dev *hif_device = scn->hif_handle;
|
|
|
+
|
|
|
+ qdf_mem_zero(&hif_device->htc_callbacks,
|
|
|
+ sizeof(hif_device->htc_callbacks));
|
|
|
+}
|
|
|
+
|
|
|
+#define SDIO_SET_CMD52_ARG(arg, rw, func, raw, address, writedata) \
|
|
|
+ ((arg) = (((rw) & 1) << 31) | \
|
|
|
+ ((func & 0x7) << 28) | \
|
|
|
+ (((raw) & 1) << 27) | \
|
|
|
+ (1 << 26) | \
|
|
|
+ (((address) & 0x1FFFF) << 9) | \
|
|
|
+ (1 << 8) | \
|
|
|
+ ((writedata) & 0xFF))
|
|
|
+
|
|
|
+#define SDIO_SET_CMD52_READ_ARG(arg, func, address) \
|
|
|
+ SDIO_SET_CMD52_ARG(arg, 0, (func), 0, address, 0x00)
|
|
|
+#define SDIO_SET_CMD52_WRITE_ARG(arg, func, address, value) \
|
|
|
+ SDIO_SET_CMD52_ARG(arg, 1, (func), 0, address, value)
|
|
|
+
|
|
|
+static int func0_cmd52_write_byte(struct mmc_card *card,
|
|
|
+ unsigned int address,
|
|
|
+ unsigned char byte)
|
|
|
+{
|
|
|
+ struct mmc_command io_cmd;
|
|
|
+ unsigned long arg;
|
|
|
+ int status = 0;
|
|
|
+
|
|
|
+ memset(&io_cmd, 0, sizeof(io_cmd));
|
|
|
+ SDIO_SET_CMD52_WRITE_ARG(arg, 0, address, byte);
|
|
|
+ io_cmd.opcode = SD_IO_RW_DIRECT;
|
|
|
+ io_cmd.arg = arg;
|
|
|
+ io_cmd.flags = MMC_RSP_R5 | MMC_CMD_AC;
|
|
|
+ status = mmc_wait_for_cmd(card->host, &io_cmd, 0);
|
|
|
+
|
|
|
+ if (status)
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERROR,
|
|
|
+ ("%s: mmc_wait_for_cmd returned %d\n",
|
|
|
+ __func__, status));
|
|
|
+
|
|
|
+ return status;
|
|
|
+}
|
|
|
+
|
|
|
+static int func0_cmd52_read_byte(struct mmc_card *card,
|
|
|
+ unsigned int address,
|
|
|
+ unsigned char *byte)
|
|
|
+{
|
|
|
+ struct mmc_command io_cmd;
|
|
|
+ unsigned long arg;
|
|
|
+ int32_t err;
|
|
|
+
|
|
|
+ memset(&io_cmd, 0, sizeof(io_cmd));
|
|
|
+ SDIO_SET_CMD52_READ_ARG(arg, 0, address);
|
|
|
+ io_cmd.opcode = SD_IO_RW_DIRECT;
|
|
|
+ io_cmd.arg = arg;
|
|
|
+ io_cmd.flags = MMC_RSP_R5 | MMC_CMD_AC;
|
|
|
+
|
|
|
+ err = mmc_wait_for_cmd(card->host, &io_cmd, 0);
|
|
|
+
|
|
|
+ if ((!err) && (byte))
|
|
|
+ *byte = io_cmd.resp[0] & 0xFF;
|
|
|
+
|
|
|
+ if (err)
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERROR,
|
|
|
+ ("%s: mmc_wait_for_cmd returned %d\n",
|
|
|
+ __func__, err));
|
|
|
+
|
|
|
+ return err;
|
|
|
+}
|
|
|
+
|
|
|
+void hif_dump_cccr(struct hif_sdio_dev *hif_device)
|
|
|
+{
|
|
|
+ int i;
|
|
|
+ uint8_t cccr_val;
|
|
|
+ uint32_t err;
|
|
|
+
|
|
|
+ if (!hif_device || !hif_device->func ||
|
|
|
+ !hif_device->func->card) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERROR,
|
|
|
+ ("hif_dump_cccr incorrect input arguments\n"));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERROR, ("hif_dump_cccr "));
|
|
|
+ for (i = 0; i <= 0x16; i++) {
|
|
|
+ err = func0_cmd52_read_byte(hif_device->func->card,
|
|
|
+ i, &cccr_val);
|
|
|
+ if (err) {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERROR,
|
|
|
+ ("Reading CCCR 0x%02X failed: %d\n",
|
|
|
+ (unsigned int)i, (unsigned int)err));
|
|
|
+ } else {
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERROR,
|
|
|
+ ("%X(%X) ", (unsigned int)i,
|
|
|
+ (unsigned int)cccr_val));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ AR_DEBUG_PRINTF(ATH_DEBUG_ERROR, ("\n"));
|
|
|
+}
|
|
|
+
|
|
|
+#ifdef CONFIG_CNSS_SDIO
|
|
|
+int hif_sdio_device_inserted(struct device *dev,
|
|
|
+ const struct sdio_device_id *id)
|
|
|
+{
|
|
|
+ struct sdio_func *func = dev_to_sdio_func(dev);
|
|
|
+
|
|
|
+ return hif_device_inserted(func, id);
|
|
|
+}
|
|
|
+
|
|
|
+void hif_sdio_device_removed(struct sdio_func *func)
|
|
|
+{
|
|
|
+ hif_device_removed(func);
|
|
|
+}
|
|
|
+#endif
|