| /* Copyright (c) 2010-2011, 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. |
| */ |
| |
| /* |
| * SDIO-Abstraction-Layer Module. |
| * |
| * To be used with Qualcomm's SDIO-Client connected to this host. |
| */ |
| #include "sdio_al_private.h" |
| |
| #include <linux/module.h> |
| #include <linux/scatterlist.h> |
| #include <linux/workqueue.h> |
| #include <linux/wait.h> |
| #include <linux/delay.h> |
| #include <linux/fs.h> |
| #include <linux/slab.h> |
| #include <linux/wakelock.h> |
| #include <linux/mmc/core.h> |
| #include <linux/mmc/card.h> |
| #include <linux/mmc/host.h> |
| #include <linux/mmc/mmc.h> |
| #include <linux/mmc/sdio.h> |
| #include <linux/mmc/sdio_func.h> |
| #include <linux/mmc/sdio_ids.h> |
| #include <linux/gpio.h> |
| #include <linux/dma-mapping.h> |
| #include <linux/earlysuspend.h> |
| #include <linux/debugfs.h> |
| #include <linux/uaccess.h> |
| #include <linux/syscalls.h> |
| #include <linux/time.h> |
| #include <linux/spinlock.h> |
| |
| #include <mach/dma.h> |
| #include <mach/gpio.h> |
| #include <mach/subsystem_notif.h> |
| |
| #include "../../../drivers/mmc/host/msm_sdcc.h" |
| |
| /** |
| * Func#0 has SDIO standard registers |
| * Func#1 is for Mailbox. |
| * Functions 2..7 are for channels. |
| * Currently only functions 2..5 are active due to SDIO-Client |
| * number of pipes. |
| * |
| */ |
| #define SDIO_AL_MAX_CHANNELS 6 |
| |
| /** Func 1..5 */ |
| #define SDIO_AL_MAX_FUNCS (SDIO_AL_MAX_CHANNELS+1) |
| #define SDIO_AL_WAKEUP_FUNC 6 |
| |
| /** Number of SDIO-Client pipes */ |
| #define SDIO_AL_MAX_PIPES 16 |
| #define SDIO_AL_ACTIVE_PIPES 8 |
| |
| /** CMD53/CMD54 Block size */ |
| #define SDIO_AL_BLOCK_SIZE 256 |
| |
| /** Func#1 hardware Mailbox base address */ |
| #define HW_MAILBOX_ADDR 0x1000 |
| |
| /** Func#1 peer sdioc software version. |
| * The header is duplicated also to the mailbox of the other |
| * functions. It can be used before other functions are enabled. */ |
| #define SDIOC_SW_HEADER_ADDR 0x0400 |
| |
| /** Func#2..7 software Mailbox base address at 16K */ |
| #define SDIOC_SW_MAILBOX_ADDR 0x4000 |
| |
| /** Some Mailbox registers address, written by host for |
| control */ |
| #define PIPES_THRESHOLD_ADDR 0x01000 |
| |
| #define PIPES_0_7_IRQ_MASK_ADDR 0x01048 |
| |
| #define PIPES_8_15_IRQ_MASK_ADDR 0x0104C |
| |
| #define FUNC_1_4_MASK_IRQ_ADDR 0x01040 |
| #define FUNC_5_7_MASK_IRQ_ADDR 0x01044 |
| #define FUNC_1_4_USER_IRQ_ADDR 0x01050 |
| #define FUNC_5_7_USER_IRQ_ADDR 0x01054 |
| |
| #define EOT_PIPES_ENABLE 0x00 |
| |
| /** Maximum read/write data available is SDIO-Client limitation */ |
| #define MAX_DATA_AVAILABLE (16*1024) |
| #define INVALID_DATA_AVAILABLE (0x8000) |
| |
| /** SDIO-Client HW threshold to generate interrupt to the |
| * SDIO-Host on write available bytes. |
| */ |
| #define DEFAULT_WRITE_THRESHOLD (1024) |
| |
| /** SDIO-Client HW threshold to generate interrupt to the |
| * SDIO-Host on read available bytes, for streaming (non |
| * packet) rx data. |
| */ |
| #define DEFAULT_READ_THRESHOLD (1024) |
| #define LOW_LATENCY_THRESHOLD (1) |
| |
| /* Extra bytes to ensure getting the rx threshold interrupt on stream channels |
| when restoring the threshold after sleep */ |
| #define THRESHOLD_CHANGE_EXTRA_BYTES (100) |
| |
| /** SW threshold to trigger reading the mailbox. */ |
| #define DEFAULT_MIN_WRITE_THRESHOLD (1024) |
| #define DEFAULT_MIN_WRITE_THRESHOLD_STREAMING (1600) |
| |
| #define THRESHOLD_DISABLE_VAL (0xFFFFFFFF) |
| |
| /** Mailbox polling time for packet channels */ |
| #define DEFAULT_POLL_DELAY_MSEC 10 |
| /** Mailbox polling time for streaming channels */ |
| #define DEFAULT_POLL_DELAY_NOPACKET_MSEC 30 |
| |
| /** The SDIO-Client prepares N buffers of size X per Tx pipe. |
| * Even when the transfer fills a partial buffer, |
| * that buffer becomes unusable for the next transfer. */ |
| #define DEFAULT_PEER_TX_BUF_SIZE (128) |
| |
| #define ROUND_UP(x, n) (((x + n - 1) / n) * n) |
| |
| /** Func#2..7 FIFOs are r/w via |
| sdio_readsb() & sdio_writesb(),when inc_addr=0 */ |
| #define PIPE_RX_FIFO_ADDR 0x00 |
| #define PIPE_TX_FIFO_ADDR 0x00 |
| |
| /** Inactivity time to go to sleep in mseconds */ |
| #define INACTIVITY_TIME_MSEC 30 |
| #define INITIAL_INACTIVITY_TIME_MSEC 5000 |
| |
| /** Context validity check */ |
| #define SDIO_AL_SIGNATURE 0xAABBCCDD |
| |
| /* Vendor Specific Command */ |
| #define SD_IO_RW_EXTENDED_QCOM 54 |
| |
| #define TIME_TO_WAIT_US 500 |
| #define SDIO_CLOSE_FLUSH_TIMEOUT_MSEC (10000) |
| #define RX_FLUSH_BUFFER_SIZE (16*1024) |
| |
| #define SDIO_TEST_POSTFIX "_TEST" |
| |
| #define DATA_DEBUG(x, y...) \ |
| do { \ |
| if (sdio_al->debug.debug_data_on) \ |
| pr_info(y); \ |
| sdio_al_log(x, y); \ |
| } while (0) |
| |
| #define LPM_DEBUG(x, y...) \ |
| do { \ |
| if (sdio_al->debug.debug_lpm_on) \ |
| pr_info(y); \ |
| sdio_al_log(x, y); \ |
| } while (0) |
| |
| #define sdio_al_loge(x, y...) \ |
| do { \ |
| pr_err(y); \ |
| sdio_al_log(x, y); \ |
| } while (0) |
| |
| #define sdio_al_logi(x, y...) \ |
| do { \ |
| pr_info(y); \ |
| sdio_al_log(x, y); \ |
| } while (0) |
| |
| #define CLOSE_DEBUG(x, y...) \ |
| do { \ |
| if (sdio_al->debug.debug_close_on) \ |
| pr_info(y); \ |
| sdio_al_log(x, y); \ |
| } while (0) |
| |
| /* The index of the SDIO card used for the sdio_al_dloader */ |
| #define SDIO_BOOTLOADER_CARD_INDEX 1 |
| |
| |
| /* SDIO card state machine */ |
| enum sdio_al_device_state { |
| CARD_INSERTED, |
| CARD_REMOVED, |
| MODEM_RESTART |
| }; |
| |
| struct sdio_al_debug { |
| u8 debug_lpm_on; |
| u8 debug_data_on; |
| u8 debug_close_on; |
| struct dentry *sdio_al_debug_root; |
| struct dentry *sdio_al_debug_lpm_on; |
| struct dentry *sdio_al_debug_data_on; |
| struct dentry *sdio_al_debug_close_on; |
| struct dentry *sdio_al_debug_info; |
| struct dentry *sdio_al_debug_log_buffers[MAX_NUM_OF_SDIO_DEVICES + 1]; |
| }; |
| |
| /* Polling time for the inactivity timer for devices that doesn't have |
| * a streaming channel |
| */ |
| #define SDIO_AL_POLL_TIME_NO_STREAMING 30 |
| |
| #define CHAN_TO_FUNC(x) ((x) + 2 - 1) |
| |
| /** |
| * Mailbox structure. |
| * The Mailbox is located on the SDIO-Client Function#1. |
| * The mailbox size is 128 bytes, which is one block. |
| * The mailbox allows the host ton: |
| * 1. Get the number of available bytes on the pipes. |
| * 2. Enable/Disable SDIO-Client interrupt, related to pipes. |
| * 3. Set the Threshold for generating interrupt. |
| * |
| */ |
| struct sdio_mailbox { |
| u32 pipe_bytes_threshold[SDIO_AL_MAX_PIPES]; /* Addr 0x1000 */ |
| |
| /* Mask USER interrupts generated towards host - Addr 0x1040 */ |
| u32 mask_irq_func_1:8; /* LSB */ |
| u32 mask_irq_func_2:8; |
| u32 mask_irq_func_3:8; |
| u32 mask_irq_func_4:8; |
| |
| u32 mask_irq_func_5:8; |
| u32 mask_irq_func_6:8; |
| u32 mask_irq_func_7:8; |
| u32 mask_mutex_irq:8; |
| |
| /* Mask PIPE interrupts generated towards host - Addr 0x1048 */ |
| u32 mask_eot_pipe_0_7:8; |
| u32 mask_thresh_above_limit_pipe_0_7:8; |
| u32 mask_overflow_pipe_0_7:8; |
| u32 mask_underflow_pipe_0_7:8; |
| |
| u32 mask_eot_pipe_8_15:8; |
| u32 mask_thresh_above_limit_pipe_8_15:8; |
| u32 mask_overflow_pipe_8_15:8; |
| u32 mask_underflow_pipe_8_15:8; |
| |
| /* Status of User interrupts generated towards host - Addr 0x1050 */ |
| u32 user_irq_func_1:8; |
| u32 user_irq_func_2:8; |
| u32 user_irq_func_3:8; |
| u32 user_irq_func_4:8; |
| |
| u32 user_irq_func_5:8; |
| u32 user_irq_func_6:8; |
| u32 user_irq_func_7:8; |
| u32 user_mutex_irq:8; |
| |
| /* Status of PIPE interrupts generated towards host */ |
| /* Note: All sources are cleared once they read. - Addr 0x1058 */ |
| u32 eot_pipe_0_7:8; |
| u32 thresh_above_limit_pipe_0_7:8; |
| u32 overflow_pipe_0_7:8; |
| u32 underflow_pipe_0_7:8; |
| |
| u32 eot_pipe_8_15:8; |
| u32 thresh_above_limit_pipe_8_15:8; |
| u32 overflow_pipe_8_15:8; |
| u32 underflow_pipe_8_15:8; |
| |
| u16 pipe_bytes_avail[SDIO_AL_MAX_PIPES]; |
| }; |
| |
| /** Track pending Rx Packet size */ |
| struct rx_packet_size { |
| u32 size; /* in bytes */ |
| struct list_head list; |
| }; |
| |
| #define PEER_SDIOC_SW_MAILBOX_SIGNATURE 0xFACECAFE |
| #define PEER_SDIOC_SW_MAILBOX_UT_SIGNATURE 0x5D107E57 |
| #define PEER_SDIOC_SW_MAILBOX_BOOT_SIGNATURE 0xDEADBEEF |
| |
| /* Allow support in old sdio version */ |
| #define PEER_SDIOC_OLD_VERSION_MAJOR 0x0002 |
| #define INVALID_SDIO_CHAN 0xFF |
| |
| /** |
| * Peer SDIO-Client software header. |
| */ |
| struct peer_sdioc_sw_header { |
| u32 signature; |
| u32 version; |
| u32 max_channels; |
| char channel_names[SDIO_AL_MAX_CHANNELS][PEER_CHANNEL_NAME_SIZE]; |
| u32 reserved[23]; |
| }; |
| |
| struct peer_sdioc_boot_sw_header { |
| u32 signature; |
| u32 version; |
| u32 boot_ch_num; |
| u32 reserved[29]; /* 32 - previous fields */ |
| }; |
| |
| /** |
| * Peer SDIO-Client software mailbox. |
| */ |
| struct peer_sdioc_sw_mailbox { |
| struct peer_sdioc_sw_header sw_header; |
| struct peer_sdioc_channel_config ch_config[SDIO_AL_MAX_CHANNELS]; |
| }; |
| |
| #define SDIO_AL_DEBUG_LOG_SIZE 3000 |
| struct sdio_al_local_log { |
| char buffer[SDIO_AL_DEBUG_LOG_SIZE]; |
| unsigned int buf_cur_pos; |
| spinlock_t log_lock; |
| }; |
| |
| #define SDIO_AL_DEBUG_TMP_LOG_SIZE 250 |
| static int sdio_al_log(struct sdio_al_local_log *, const char *fmt, ...); |
| |
| /** |
| * SDIO Abstraction Layer driver context. |
| * |
| * @pdata - |
| * @debug - |
| * @devices - an array of the the devices claimed by sdio_al |
| * @unittest_mode - a flag to indicate if sdio_al is in |
| * unittest mode |
| * @bootloader_dev - the device which is used for the |
| * bootloader |
| * @subsys_notif_handle - handle for modem restart |
| * notifications |
| * |
| */ |
| struct sdio_al { |
| struct sdio_al_local_log gen_log; |
| struct sdio_al_local_log device_log[MAX_NUM_OF_SDIO_DEVICES]; |
| struct sdio_al_platform_data *pdata; |
| struct sdio_al_debug debug; |
| struct sdio_al_device *devices[MAX_NUM_OF_SDIO_DEVICES]; |
| int unittest_mode; |
| struct sdio_al_device *bootloader_dev; |
| void *subsys_notif_handle; |
| int sdioc_major; |
| int skip_print_info; |
| }; |
| |
| struct sdio_al_work { |
| struct work_struct work; |
| struct sdio_al_device *sdio_al_dev; |
| }; |
| |
| |
| /** |
| * SDIO Abstraction Layer device context. |
| * |
| * @card - card claimed. |
| * |
| * @mailbox - A shadow of the SDIO-Client mailbox. |
| * |
| * @channel - Channels context. |
| * |
| * @workqueue - workqueue to read the mailbox and handle |
| * pending requests. Reading the mailbox should not happen |
| * in interrupt context. |
| * |
| * @work - work to submit to workqueue. |
| * |
| * @is_ready - driver is ready. |
| * |
| * @ask_mbox - Flag to request reading the mailbox, |
| * for different reasons. |
| * |
| * @wake_lock - Lock when can't sleep. |
| * |
| * @lpm_chan - Channel to use for LPM (low power mode) |
| * communication. |
| * |
| * @is_ok_to_sleep - Mark if driver is OK with going to sleep |
| * (no pending transactions). |
| * |
| * @inactivity_time - time allowed to be in inactivity before |
| * going to sleep |
| * |
| * @timer - timer to use for polling the mailbox. |
| * |
| * @poll_delay_msec - timer delay for polling the mailbox. |
| * |
| * @is_err - error detected. |
| * |
| * @signature - Context Validity Check. |
| * |
| * @flashless_boot_on - flag to indicate if sdio_al is in |
| * flshless boot mode |
| * |
| */ |
| struct sdio_al_device { |
| struct sdio_al_local_log *dev_log; |
| struct mmc_card *card; |
| struct mmc_host *host; |
| struct sdio_mailbox *mailbox; |
| struct sdio_channel channel[SDIO_AL_MAX_CHANNELS]; |
| |
| struct peer_sdioc_sw_header *sdioc_sw_header; |
| struct peer_sdioc_boot_sw_header *sdioc_boot_sw_header; |
| |
| struct workqueue_struct *workqueue; |
| struct sdio_al_work sdio_al_work; |
| struct sdio_al_work boot_work; |
| |
| int is_ready; |
| |
| wait_queue_head_t wait_mbox; |
| int ask_mbox; |
| int bootloader_done; |
| |
| struct wake_lock wake_lock; |
| int lpm_chan; |
| int is_ok_to_sleep; |
| unsigned long inactivity_time; |
| |
| struct timer_list timer; |
| u32 poll_delay_msec; |
| int is_timer_initialized; |
| |
| int is_err; |
| |
| u32 signature; |
| |
| unsigned int is_suspended; |
| |
| int flashless_boot_on; |
| int ch_close_supported; |
| int state; |
| int (*lpm_callback)(void *, int); |
| |
| int print_after_interrupt; |
| |
| u8 *rx_flush_buf; |
| }; |
| |
| /* |
| * Host operation: |
| * lower 16bits are operation code |
| * upper 16bits are operation state |
| */ |
| #define PEER_OPERATION(op_code , op_state) ((op_code) | ((op_state) << 16)) |
| #define GET_PEER_OPERATION_CODE(op) ((op) & 0xffff) |
| #define GET_PEER_OPERATION_STATE(op) ((op) >> 16) |
| |
| enum peer_op_code { |
| PEER_OP_CODE_CLOSE = 1 |
| }; |
| |
| enum peer_op_state { |
| PEER_OP_STATE_INIT = 0, |
| PEER_OP_STATE_START = 1 |
| }; |
| |
| |
| /* |
| * On the kernel command line specify |
| * sdio_al.debug_lpm_on=1 to enable the LPM debug messages |
| * By default the LPM debug messages are turned off |
| */ |
| static int debug_lpm_on; |
| module_param(debug_lpm_on, int, 0); |
| |
| /* |
| * On the kernel command line specify |
| * sdio_al.debug_data_on=1 to enable the DATA debug messages |
| * By default the DATA debug messages are turned off |
| */ |
| static int debug_data_on; |
| module_param(debug_data_on, int, 0); |
| |
| /* |
| * Enables / disables open close debug messages |
| */ |
| static int debug_close_on = 1; |
| module_param(debug_close_on, int, 0); |
| |
| /** The driver context */ |
| static struct sdio_al *sdio_al; |
| |
| /* Static functions declaration */ |
| static int enable_eot_interrupt(struct sdio_al_device *sdio_al_dev, |
| int pipe_index, int enable); |
| static int enable_threshold_interrupt(struct sdio_al_device *sdio_al_dev, |
| int pipe_index, int enable); |
| static void sdio_func_irq(struct sdio_func *func); |
| static void sdio_al_timer_handler(unsigned long data); |
| static int get_min_poll_time_msec(struct sdio_al_device *sdio_al_dev); |
| static u32 check_pending_rx_packet(struct sdio_channel *ch, u32 eot); |
| static u32 remove_handled_rx_packet(struct sdio_channel *ch); |
| static int set_pipe_threshold(struct sdio_al_device *sdio_al_dev, |
| int pipe_index, int threshold); |
| static int sdio_al_wake_up(struct sdio_al_device *sdio_al_dev, |
| u32 not_from_int, struct sdio_channel *ch); |
| static int sdio_al_client_setup(struct sdio_al_device *sdio_al_dev); |
| static int enable_mask_irq(struct sdio_al_device *sdio_al_dev, |
| int func_num, int enable, u8 bit_offset); |
| static int sdio_al_enable_func_retry(struct sdio_func *func, const char *name); |
| static void sdio_al_print_info(void); |
| static int sdio_read_internal(struct sdio_channel *ch, void *data, int len); |
| static int sdio_read_from_closed_ch(struct sdio_channel *ch, int len); |
| static void stop_and_del_timer(struct sdio_al_device *sdio_al_dev); |
| |
| #define SDIO_AL_ERR(func) \ |
| do { \ |
| printk_once(KERN_ERR MODULE_NAME \ |
| ":In Error state, ignore %s\n", \ |
| func); \ |
| sdio_al_print_info(); \ |
| } while (0) |
| |
| #ifdef CONFIG_DEBUG_FS |
| static int debug_info_open(struct inode *inode, struct file *file) |
| { |
| file->private_data = inode->i_private; |
| return 0; |
| } |
| |
| static ssize_t debug_info_write(struct file *file, |
| const char __user *buf, size_t count, loff_t *ppos) |
| { |
| sdio_al_print_info(); |
| return 1; |
| } |
| |
| const struct file_operations debug_info_ops = { |
| .open = debug_info_open, |
| .write = debug_info_write, |
| }; |
| |
| struct debugfs_blob_wrapper sdio_al_dbgfs_log[MAX_NUM_OF_SDIO_DEVICES + 1]; |
| |
| /* |
| * |
| * Trigger on/off for debug messages |
| * for trigger off the data messages debug level use: |
| * echo 0 > /sys/kernel/debugfs/sdio_al/debug_data_on |
| * for trigger on the data messages debug level use: |
| * echo 1 > /sys/kernel/debugfs/sdio_al/debug_data_on |
| * for trigger off the lpm messages debug level use: |
| * echo 0 > /sys/kernel/debugfs/sdio_al/debug_lpm_on |
| * for trigger on the lpm messages debug level use: |
| * echo 1 > /sys/kernel/debugfs/sdio_al/debug_lpm_on |
| */ |
| static int sdio_al_debugfs_init(void) |
| { |
| int i, blob_errs = 0; |
| |
| sdio_al->debug.sdio_al_debug_root = debugfs_create_dir("sdio_al", NULL); |
| if (!sdio_al->debug.sdio_al_debug_root) |
| return -ENOENT; |
| |
| sdio_al->debug.sdio_al_debug_lpm_on = debugfs_create_u8("debug_lpm_on", |
| S_IRUGO | S_IWUGO, |
| sdio_al->debug.sdio_al_debug_root, |
| &sdio_al->debug.debug_lpm_on); |
| |
| sdio_al->debug.sdio_al_debug_data_on = debugfs_create_u8( |
| "debug_data_on", |
| S_IRUGO | S_IWUGO, |
| sdio_al->debug.sdio_al_debug_root, |
| &sdio_al->debug.debug_data_on); |
| |
| sdio_al->debug.sdio_al_debug_close_on = debugfs_create_u8( |
| "debug_close_on", |
| S_IRUGO | S_IWUGO, |
| sdio_al->debug.sdio_al_debug_root, |
| &sdio_al->debug.debug_close_on); |
| |
| sdio_al->debug.sdio_al_debug_info = debugfs_create_file( |
| "sdio_debug_info", |
| S_IRUGO | S_IWUGO, |
| sdio_al->debug.sdio_al_debug_root, |
| NULL, |
| &debug_info_ops); |
| |
| for (i = 0; i < MAX_NUM_OF_SDIO_DEVICES; ++i) { |
| char temp[18]; |
| |
| scnprintf(temp, 18, "sdio_al_log_dev_%d", i + 1); |
| sdio_al->debug.sdio_al_debug_log_buffers[i] = |
| debugfs_create_blob(temp, |
| S_IRUGO | S_IWUGO, |
| sdio_al->debug.sdio_al_debug_root, |
| &sdio_al_dbgfs_log[i]); |
| } |
| |
| sdio_al->debug.sdio_al_debug_log_buffers[MAX_NUM_OF_SDIO_DEVICES] = |
| debugfs_create_blob("sdio_al_gen_log", |
| S_IRUGO | S_IWUGO, |
| sdio_al->debug.sdio_al_debug_root, |
| &sdio_al_dbgfs_log[MAX_NUM_OF_SDIO_DEVICES]); |
| |
| for (i = 0; i < (MAX_NUM_OF_SDIO_DEVICES + 1); ++i) { |
| if (!sdio_al->debug.sdio_al_debug_log_buffers[i]) { |
| pr_err(MODULE_NAME ": Failed to create debugfs buffer" |
| " entry for " |
| "sdio_al->debug.sdio_al_debug_log_buffers[%d]", |
| i); |
| blob_errs = 1; |
| } |
| } |
| |
| if (blob_errs) { |
| for (i = 0; i < (MAX_NUM_OF_SDIO_DEVICES + 1); ++i) |
| if (sdio_al->debug.sdio_al_debug_log_buffers[i]) |
| debugfs_remove( |
| sdio_al-> |
| debug.sdio_al_debug_log_buffers[i]); |
| } |
| |
| |
| if ((!sdio_al->debug.sdio_al_debug_data_on) && |
| (!sdio_al->debug.sdio_al_debug_lpm_on) && |
| (!sdio_al->debug.sdio_al_debug_close_on) && |
| (!sdio_al->debug.sdio_al_debug_info) && |
| blob_errs) { |
| debugfs_remove(sdio_al->debug.sdio_al_debug_root); |
| sdio_al->debug.sdio_al_debug_root = NULL; |
| return -ENOENT; |
| } |
| |
| sdio_al_dbgfs_log[MAX_NUM_OF_SDIO_DEVICES].data = |
| sdio_al->gen_log.buffer; |
| sdio_al_dbgfs_log[MAX_NUM_OF_SDIO_DEVICES].size = |
| SDIO_AL_DEBUG_LOG_SIZE; |
| |
| return 0; |
| } |
| |
| static void sdio_al_debugfs_cleanup(void) |
| { |
| int i; |
| |
| debugfs_remove(sdio_al->debug.sdio_al_debug_lpm_on); |
| debugfs_remove(sdio_al->debug.sdio_al_debug_data_on); |
| debugfs_remove(sdio_al->debug.sdio_al_debug_close_on); |
| debugfs_remove(sdio_al->debug.sdio_al_debug_info); |
| |
| for (i = 0; i < (MAX_NUM_OF_SDIO_DEVICES + 1); ++i) |
| debugfs_remove(sdio_al->debug.sdio_al_debug_log_buffers[i]); |
| |
| debugfs_remove(sdio_al->debug.sdio_al_debug_root); |
| } |
| #endif |
| |
| static int sdio_al_log(struct sdio_al_local_log *log, const char *fmt, ...) |
| { |
| va_list args; |
| int r; |
| char *tp, *log_buf; |
| unsigned int *log_cur_pos; |
| struct timeval kt; |
| unsigned long flags; |
| static char sdio_al_log_tmp[SDIO_AL_DEBUG_TMP_LOG_SIZE]; |
| |
| spin_lock_irqsave(&log->log_lock, flags); |
| |
| kt = ktime_to_timeval(ktime_get()); |
| r = scnprintf(sdio_al_log_tmp, SDIO_AL_DEBUG_TMP_LOG_SIZE, |
| "[%8ld.%6ld] ", kt.tv_sec, kt.tv_usec); |
| |
| va_start(args, fmt); |
| r += vscnprintf(&sdio_al_log_tmp[r], (SDIO_AL_DEBUG_TMP_LOG_SIZE - r), |
| fmt, args); |
| va_end(args); |
| |
| log_buf = log->buffer; |
| log_cur_pos = &(log->buf_cur_pos); |
| |
| for (tp = sdio_al_log_tmp; tp < (sdio_al_log_tmp + r); tp++) { |
| log_buf[(*log_cur_pos)++] = *tp; |
| if ((*log_cur_pos) == SDIO_AL_DEBUG_LOG_SIZE) |
| *log_cur_pos = 0; |
| } |
| |
| spin_unlock_irqrestore(&log->log_lock, flags); |
| |
| return r; |
| } |
| |
| static int sdio_al_verify_func1(struct sdio_al_device *sdio_al_dev, |
| char const *func) |
| { |
| if (sdio_al_dev == NULL) { |
| sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ": %s: NULL " |
| "sdio_al_dev\n", func); |
| return -ENODEV; |
| } |
| |
| if (sdio_al_dev->signature != SDIO_AL_SIGNATURE) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ": %s: Invalid " |
| "signature\n", func); |
| return -ENODEV; |
| } |
| |
| if (!sdio_al_dev->card) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ": %s: NULL " |
| "card\n", func); |
| return -ENODEV; |
| } |
| if (!sdio_al_dev->card->sdio_func[0]) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ": %s: NULL " |
| "func1\n", func); |
| return -ENODEV; |
| } |
| return 0; |
| } |
| |
| static int sdio_al_claim_mutex(struct sdio_al_device *sdio_al_dev, |
| char const *func) |
| { |
| if (!sdio_al_dev) { |
| sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ": %s: NULL " |
| "device\n", func); |
| return -ENODEV; |
| } |
| |
| if (sdio_al_dev->signature != SDIO_AL_SIGNATURE) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ": %s: Invalid " |
| "device signature\n", func); |
| return -ENODEV; |
| } |
| |
| if (!sdio_al_dev->host) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ": %s: NULL " |
| "host\n", func); |
| return -ENODEV; |
| } |
| |
| mmc_claim_host(sdio_al_dev->host); |
| |
| return 0; |
| } |
| |
| static int sdio_al_release_mutex(struct sdio_al_device *sdio_al_dev, |
| char const *func) |
| { |
| if (!sdio_al_dev) { |
| sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ": %s: NULL " |
| "device\n", func); |
| return -ENODEV; |
| } |
| |
| if (sdio_al_dev->signature != SDIO_AL_SIGNATURE) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ": %s: Invalid " |
| "device signature\n", func); |
| return -ENODEV; |
| } |
| |
| if (!sdio_al_dev->host) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ": %s: NULL " |
| "host\n", func); |
| return -ENODEV; |
| } |
| |
| mmc_release_host(sdio_al_dev->host); |
| |
| return 0; |
| } |
| |
| static int sdio_al_claim_mutex_and_verify_dev( |
| struct sdio_al_device *sdio_al_dev, |
| char const *func) |
| { |
| if (sdio_al_claim_mutex(sdio_al_dev, __func__)) |
| return -ENODEV; |
| |
| if (sdio_al_dev->state != CARD_INSERTED) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ": %s: Invalid " |
| "device state %d\n", func, sdio_al_dev->state); |
| sdio_al_release_mutex(sdio_al_dev, __func__); |
| return -ENODEV; |
| } |
| |
| return 0; |
| } |
| |
| static void sdio_al_get_into_err_state(struct sdio_al_device *sdio_al_dev) |
| { |
| if ((!sdio_al) || (!sdio_al_dev)) |
| return; |
| |
| sdio_al_dev->is_err = true; |
| sdio_al->debug.debug_data_on = 0; |
| sdio_al->debug.debug_lpm_on = 0; |
| sdio_al_print_info(); |
| } |
| |
| void sdio_al_register_lpm_cb(void *device_handle, |
| int(*lpm_callback)(void *, int)) |
| { |
| struct sdio_al_device *sdio_al_dev = |
| (struct sdio_al_device *) device_handle; |
| |
| if (!sdio_al_dev) { |
| sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ": %s - " |
| "device_handle is NULL\n", __func__); |
| return; |
| } |
| |
| if (lpm_callback) { |
| sdio_al_dev->lpm_callback = lpm_callback; |
| lpm_callback((void *)sdio_al_dev, |
| sdio_al_dev->is_ok_to_sleep); |
| } |
| |
| LPM_DEBUG(sdio_al_dev->dev_log, MODULE_NAME ": %s - device %d " |
| "registered for wakeup callback\n", __func__, |
| sdio_al_dev->host->index); |
| } |
| |
| void sdio_al_unregister_lpm_cb(void *device_handle) |
| { |
| struct sdio_al_device *sdio_al_dev = |
| (struct sdio_al_device *) device_handle; |
| |
| if (!sdio_al_dev) { |
| sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ": %s - " |
| "device_handle is NULL\n", __func__); |
| return; |
| } |
| |
| sdio_al_dev->lpm_callback = NULL; |
| LPM_DEBUG(sdio_al_dev->dev_log, MODULE_NAME ": %s - device %d " |
| "unregister for wakeup callback\n", __func__, |
| sdio_al_dev->host->index); |
| } |
| |
| static void sdio_al_vote_for_sleep(struct sdio_al_device *sdio_al_dev, |
| int is_vote_for_sleep) |
| { |
| pr_debug(MODULE_NAME ": %s()", __func__); |
| |
| if (!sdio_al_dev) { |
| sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ": %s - sdio_al_dev" |
| " is NULL\n", __func__); |
| return; |
| } |
| |
| if (is_vote_for_sleep) { |
| pr_debug(MODULE_NAME ": %s - sdio vote for Sleep", __func__); |
| wake_unlock(&sdio_al_dev->wake_lock); |
| } else { |
| pr_debug(MODULE_NAME ": %s - sdio vote against sleep", |
| __func__); |
| wake_lock(&sdio_al_dev->wake_lock); |
| } |
| |
| if (sdio_al_dev->lpm_callback != NULL) { |
| LPM_DEBUG(sdio_al_dev->dev_log, MODULE_NAME ": %s - " |
| "is_vote_for_sleep=%d for card#%d, " |
| "calling callback...", __func__, |
| is_vote_for_sleep, |
| sdio_al_dev->host->index); |
| sdio_al_dev->lpm_callback((void *)sdio_al_dev, |
| is_vote_for_sleep); |
| } |
| } |
| |
| /** |
| * Write SDIO-Client lpm information |
| * Should only be called with host claimed. |
| */ |
| static int write_lpm_info(struct sdio_al_device *sdio_al_dev) |
| { |
| struct sdio_func *lpm_func = NULL; |
| int offset = offsetof(struct peer_sdioc_sw_mailbox, ch_config)+ |
| sizeof(struct peer_sdioc_channel_config) * |
| sdio_al_dev->lpm_chan+ |
| offsetof(struct peer_sdioc_channel_config, is_host_ok_to_sleep); |
| int ret; |
| |
| if (sdio_al_dev->lpm_chan == INVALID_SDIO_CHAN) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":Invalid " |
| "lpm_chan for card %d\n", |
| sdio_al_dev->host->index); |
| return -EINVAL; |
| } |
| |
| if (!sdio_al_dev->card || |
| !sdio_al_dev->card->sdio_func[sdio_al_dev->lpm_chan+1]) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME |
| ": NULL card or lpm_func\n"); |
| return -ENODEV; |
| } |
| lpm_func = sdio_al_dev->card->sdio_func[sdio_al_dev->lpm_chan+1]; |
| |
| pr_debug(MODULE_NAME ":write_lpm_info is_ok_to_sleep=%d, device %d\n", |
| sdio_al_dev->is_ok_to_sleep, |
| sdio_al_dev->host->index); |
| |
| ret = sdio_memcpy_toio(lpm_func, SDIOC_SW_MAILBOX_ADDR+offset, |
| &sdio_al_dev->is_ok_to_sleep, sizeof(u32)); |
| if (ret) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":failed to " |
| "write lpm info for card %d\n", |
| sdio_al_dev->host->index); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| /* Set inactivity counter to intial value to allow clients come up */ |
| static inline void start_inactive_time(struct sdio_al_device *sdio_al_dev) |
| { |
| sdio_al_dev->inactivity_time = jiffies + |
| msecs_to_jiffies(INITIAL_INACTIVITY_TIME_MSEC); |
| } |
| |
| static inline void restart_inactive_time(struct sdio_al_device *sdio_al_dev) |
| { |
| sdio_al_dev->inactivity_time = jiffies + |
| msecs_to_jiffies(INACTIVITY_TIME_MSEC); |
| } |
| |
| static inline int is_inactive_time_expired(struct sdio_al_device *sdio_al_dev) |
| { |
| return time_after(jiffies, sdio_al_dev->inactivity_time); |
| } |
| |
| |
| static int is_user_irq_enabled(struct sdio_al_device *sdio_al_dev, |
| int func_num) |
| { |
| int ret = 0; |
| struct sdio_func *func1; |
| u32 user_irq = 0; |
| u32 addr = 0; |
| u32 offset = 0; |
| u32 masked_user_irq = 0; |
| |
| if (sdio_al_verify_func1(sdio_al_dev, __func__)) |
| return 0; |
| func1 = sdio_al_dev->card->sdio_func[0]; |
| |
| if (func_num < 4) { |
| addr = FUNC_1_4_USER_IRQ_ADDR; |
| offset = func_num * 8; |
| } else { |
| addr = FUNC_5_7_USER_IRQ_ADDR; |
| offset = (func_num - 4) * 8; |
| } |
| |
| user_irq = sdio_readl(func1, addr, &ret); |
| if (ret) { |
| pr_debug(MODULE_NAME ":read_user_irq fail\n"); |
| return 0; |
| } |
| |
| masked_user_irq = (user_irq >> offset) && 0xFF; |
| if (masked_user_irq == 0x1) { |
| sdio_al_logi(sdio_al_dev->dev_log, MODULE_NAME ":user_irq " |
| "enabled\n"); |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| static void sdio_al_sleep(struct sdio_al_device *sdio_al_dev, |
| struct mmc_host *host) |
| { |
| int i; |
| |
| /* Go to sleep */ |
| pr_debug(MODULE_NAME ":Inactivity timer expired." |
| " Going to sleep\n"); |
| /* Stop mailbox timer */ |
| stop_and_del_timer(sdio_al_dev); |
| /* Make sure we get interrupt for non-packet-mode right away */ |
| for (i = 0; i < SDIO_AL_MAX_CHANNELS; i++) { |
| struct sdio_channel *ch = &sdio_al_dev->channel[i]; |
| if ((ch->state != SDIO_CHANNEL_STATE_OPEN) && |
| (ch->state != SDIO_CHANNEL_STATE_CLOSED)) { |
| pr_debug(MODULE_NAME ":continue for channel %s in" |
| " state %d\n", ch->name, ch->state); |
| continue; |
| } |
| if (ch->is_packet_mode == false) { |
| ch->read_threshold = LOW_LATENCY_THRESHOLD; |
| set_pipe_threshold(sdio_al_dev, |
| ch->rx_pipe_index, |
| ch->read_threshold); |
| } |
| } |
| /* Prevent modem to go to sleep until we get the PROG_DONE on |
| the dummy CMD52 */ |
| msmsdcc_set_pwrsave(sdio_al_dev->host, 0); |
| /* Mark HOST_OK_TOSLEEP */ |
| sdio_al_dev->is_ok_to_sleep = 1; |
| write_lpm_info(sdio_al_dev); |
| |
| msmsdcc_lpm_enable(host); |
| LPM_DEBUG(sdio_al_dev->dev_log, MODULE_NAME ":Finished sleep sequence" |
| " for card %d. Sleep now.\n", |
| sdio_al_dev->host->index); |
| /* Release wakelock */ |
| sdio_al_vote_for_sleep(sdio_al_dev, 1); |
| } |
| |
| |
| /** |
| * Read SDIO-Client Mailbox from Function#1.thresh_pipe |
| * |
| * The mailbox contain the bytes available per pipe, |
| * and the End-Of-Transfer indication per pipe (if available). |
| * |
| * WARNING: Each time the Mailbox is read from the client, the |
| * read_bytes_avail is incremented with another pending |
| * transfer. Therefore, a pending rx-packet should be added to a |
| * list before the next read of the mailbox. |
| * |
| * This function should run from a workqueue context since it |
| * notifies the clients. |
| * |
| * This function assumes that sdio_al_claim_mutex was called before |
| * calling it. |
| * |
| */ |
| static int read_mailbox(struct sdio_al_device *sdio_al_dev, int from_isr) |
| { |
| int ret; |
| struct sdio_func *func1 = NULL; |
| struct sdio_mailbox *mailbox = sdio_al_dev->mailbox; |
| struct mmc_host *host = sdio_al_dev->host; |
| u32 new_write_avail = 0; |
| u32 old_write_avail = 0; |
| u32 any_read_avail = 0; |
| u32 any_write_pending = 0; |
| int i; |
| u32 rx_notify_bitmask = 0; |
| u32 tx_notify_bitmask = 0; |
| u32 eot_pipe = 0; |
| u32 thresh_pipe = 0; |
| u32 overflow_pipe = 0; |
| u32 underflow_pipe = 0; |
| u32 thresh_intr_mask = 0; |
| int is_closing = 0; |
| |
| if (sdio_al_dev->is_err) { |
| SDIO_AL_ERR(__func__); |
| return 0; |
| } |
| |
| if (sdio_al_verify_func1(sdio_al_dev, __func__)) |
| return -ENODEV; |
| func1 = sdio_al_dev->card->sdio_func[0]; |
| |
| pr_debug(MODULE_NAME ":start %s from_isr = %d for card %d.\n" |
| , __func__, from_isr, sdio_al_dev->host->index); |
| |
| pr_debug(MODULE_NAME ":before sdio_memcpy_fromio.\n"); |
| memset(mailbox, 0, sizeof(struct sdio_mailbox)); |
| ret = sdio_memcpy_fromio(func1, mailbox, |
| HW_MAILBOX_ADDR, sizeof(*mailbox)); |
| pr_debug(MODULE_NAME ":after sdio_memcpy_fromio.\n"); |
| if (ret) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":Fail to read " |
| "Mailbox for card %d, goto error state\n", |
| sdio_al_dev->host->index); |
| sdio_al_get_into_err_state(sdio_al_dev); |
| goto exit_err; |
| } |
| |
| eot_pipe = (mailbox->eot_pipe_0_7) | |
| (mailbox->eot_pipe_8_15<<8); |
| thresh_pipe = (mailbox->thresh_above_limit_pipe_0_7) | |
| (mailbox->thresh_above_limit_pipe_8_15<<8); |
| |
| overflow_pipe = (mailbox->overflow_pipe_0_7) | |
| (mailbox->overflow_pipe_8_15<<8); |
| underflow_pipe = mailbox->underflow_pipe_0_7 | |
| (mailbox->underflow_pipe_8_15<<8); |
| thresh_intr_mask = |
| (mailbox->mask_thresh_above_limit_pipe_0_7) | |
| (mailbox->mask_thresh_above_limit_pipe_8_15<<8); |
| |
| if (overflow_pipe || underflow_pipe) |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":Mailbox ERROR " |
| "overflow=0x%x, underflow=0x%x\n", |
| overflow_pipe, underflow_pipe); |
| |
| /* In case of modem reset we would like to read the daya from the modem |
| to clear the interrupts but do not process it */ |
| if (sdio_al_dev->state != CARD_INSERTED) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":sdio_al_device" |
| " (card %d) is in invalid state %d\n", |
| sdio_al_dev->host->index, |
| sdio_al_dev->state); |
| return -ENODEV; |
| } |
| |
| pr_debug(MODULE_NAME ":card %d: eot=0x%x, thresh=0x%x\n", |
| sdio_al_dev->host->index, |
| eot_pipe, thresh_pipe); |
| |
| /* Scan for Rx Packets available and update read available bytes */ |
| for (i = 0; i < SDIO_AL_MAX_CHANNELS; i++) { |
| struct sdio_channel *ch = &sdio_al_dev->channel[i]; |
| u32 old_read_avail; |
| u32 read_avail; |
| u32 new_packet_size = 0; |
| |
| if (ch->state == SDIO_CHANNEL_STATE_CLOSING) |
| is_closing = true; /* used to prevent sleep */ |
| |
| old_read_avail = ch->read_avail; |
| read_avail = mailbox->pipe_bytes_avail[ch->rx_pipe_index]; |
| |
| if ((ch->state == SDIO_CHANNEL_STATE_CLOSED) && |
| (read_avail > 0)) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME |
| ":%s: Invalid read_avail 0x%x, for CLOSED ch %s\n", |
| __func__, read_avail, ch->name); |
| sdio_read_from_closed_ch(ch, read_avail); |
| } |
| if ((ch->state != SDIO_CHANNEL_STATE_OPEN) && |
| (ch->state != SDIO_CHANNEL_STATE_CLOSING)) |
| continue; |
| |
| if (read_avail > INVALID_DATA_AVAILABLE) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME |
| ":Invalid read_avail 0x%x for pipe %d\n", |
| read_avail, ch->rx_pipe_index); |
| continue; |
| } |
| any_read_avail |= read_avail | old_read_avail; |
| ch->statistics.last_any_read_avail = any_read_avail; |
| ch->statistics.last_read_avail = read_avail; |
| ch->statistics.last_old_read_avail = old_read_avail; |
| |
| if (ch->is_packet_mode) { |
| if ((eot_pipe & (1<<ch->rx_pipe_index)) && |
| sdio_al_dev->print_after_interrupt) { |
| LPM_DEBUG(sdio_al_dev->dev_log, MODULE_NAME |
| ":Interrupt on ch %s, " |
| "card %d", ch->name, |
| sdio_al_dev->host->index); |
| } |
| new_packet_size = check_pending_rx_packet(ch, eot_pipe); |
| } else { |
| if ((thresh_pipe & (1<<ch->rx_pipe_index)) && |
| sdio_al_dev->print_after_interrupt) { |
| LPM_DEBUG(sdio_al_dev->dev_log, MODULE_NAME |
| ":Interrupt on ch %s, " |
| "card %d", ch->name, |
| sdio_al_dev->host->index); |
| } |
| ch->read_avail = read_avail; |
| |
| /* |
| * Restore default thresh for non packet channels. |
| * in case it IS low latency channel then read_threshold |
| * and def_read_threshold are both |
| * LOW_LATENCY_THRESHOLD |
| */ |
| if ((ch->read_threshold != ch->def_read_threshold) && |
| (read_avail >= ch->threshold_change_cnt)) { |
| if (!ch->is_low_latency_ch) { |
| ch->read_threshold = |
| ch->def_read_threshold; |
| set_pipe_threshold(sdio_al_dev, |
| ch->rx_pipe_index, |
| ch->read_threshold); |
| } |
| } |
| } |
| |
| if ((ch->is_packet_mode) && (new_packet_size > 0)) { |
| rx_notify_bitmask |= (1<<ch->num); |
| ch->statistics.total_notifs++; |
| } |
| |
| if ((!ch->is_packet_mode) && (ch->read_avail > 0) && |
| (old_read_avail == 0)) { |
| rx_notify_bitmask |= (1<<ch->num); |
| ch->statistics.total_notifs++; |
| } |
| } |
| sdio_al_dev->print_after_interrupt = 0; |
| |
| /* Update Write available */ |
| for (i = 0; i < SDIO_AL_MAX_CHANNELS; i++) { |
| struct sdio_channel *ch = &sdio_al_dev->channel[i]; |
| |
| if ((ch->state != SDIO_CHANNEL_STATE_OPEN) && |
| (ch->state != SDIO_CHANNEL_STATE_CLOSING)) |
| continue; |
| |
| new_write_avail = mailbox->pipe_bytes_avail[ch->tx_pipe_index]; |
| |
| if (new_write_avail > INVALID_DATA_AVAILABLE) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME |
| ":Invalid write_avail 0x%x for pipe %d\n", |
| new_write_avail, ch->tx_pipe_index); |
| continue; |
| } |
| |
| old_write_avail = ch->write_avail; |
| ch->write_avail = new_write_avail; |
| |
| if ((old_write_avail <= ch->min_write_avail) && |
| (new_write_avail >= ch->min_write_avail)) |
| tx_notify_bitmask |= (1<<ch->num); |
| |
| /* There is not enough write avail for this channel. |
| We need to keep reading mailbox to wait for the appropriate |
| write avail and cannot sleep. Ignore SMEM channel that has |
| only one direction. */ |
| if (strncmp(ch->name, "SDIO_SMEM", CHANNEL_NAME_SIZE)) |
| any_write_pending |= |
| (new_write_avail < ch->ch_config.max_tx_threshold); |
| } |
| /* notify clients */ |
| for (i = 0; i < SDIO_AL_MAX_CHANNELS; i++) { |
| struct sdio_channel *ch = &sdio_al_dev->channel[i]; |
| |
| if ((ch->state != SDIO_CHANNEL_STATE_OPEN) || |
| (ch->notify == NULL)) |
| continue; |
| |
| if (rx_notify_bitmask & (1<<ch->num)) |
| ch->notify(ch->priv, |
| SDIO_EVENT_DATA_READ_AVAIL); |
| |
| if (tx_notify_bitmask & (1<<ch->num)) |
| ch->notify(ch->priv, |
| SDIO_EVENT_DATA_WRITE_AVAIL); |
| } |
| |
| |
| if ((rx_notify_bitmask == 0) && (tx_notify_bitmask == 0) && |
| !any_read_avail && !any_write_pending) { |
| DATA_DEBUG(sdio_al_dev->dev_log, MODULE_NAME ":Nothing to " |
| "Notify for card %d, is_closing=%d\n", |
| sdio_al_dev->host->index, is_closing); |
| if (is_closing) |
| restart_inactive_time(sdio_al_dev); |
| else if (is_inactive_time_expired(sdio_al_dev)) |
| sdio_al_sleep(sdio_al_dev, host); |
| } else { |
| DATA_DEBUG(sdio_al_dev->dev_log, MODULE_NAME ":Notify bitmask" |
| " for card %d rx=0x%x, tx=0x%x.\n", |
| sdio_al_dev->host->index, |
| rx_notify_bitmask, tx_notify_bitmask); |
| /* Restart inactivity timer if any activity on the channel */ |
| restart_inactive_time(sdio_al_dev); |
| } |
| |
| pr_debug(MODULE_NAME ":end %s.\n", __func__); |
| |
| exit_err: |
| return ret; |
| } |
| |
| /** |
| * Check pending rx packet when reading the mailbox. |
| */ |
| static u32 check_pending_rx_packet(struct sdio_channel *ch, u32 eot) |
| { |
| u32 rx_pending; |
| u32 rx_avail; |
| u32 new_packet_size = 0; |
| struct sdio_al_device *sdio_al_dev = ch->sdio_al_dev; |
| |
| |
| if (sdio_al_dev == NULL) { |
| sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ": NULL sdio_al_dev" |
| " for channel %s\n", ch->name); |
| return -EINVAL; |
| } |
| |
| mutex_lock(&ch->ch_lock); |
| |
| rx_pending = ch->rx_pending_bytes; |
| rx_avail = sdio_al_dev->mailbox->pipe_bytes_avail[ch->rx_pipe_index]; |
| |
| pr_debug(MODULE_NAME ":pipe %d of card %d rx_avail=0x%x, " |
| "rx_pending=0x%x\n", |
| ch->rx_pipe_index, sdio_al_dev->host->index, rx_avail, |
| rx_pending); |
| |
| |
| /* new packet detected */ |
| if (eot & (1<<ch->rx_pipe_index)) { |
| struct rx_packet_size *p = NULL; |
| new_packet_size = rx_avail - rx_pending; |
| |
| if ((rx_avail <= rx_pending)) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME |
| ": Invalid new packet size." |
| " rx_avail=%d.\n", rx_avail); |
| new_packet_size = 0; |
| goto exit_err; |
| } |
| |
| p = kzalloc(sizeof(*p), GFP_KERNEL); |
| if (p == NULL) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME |
| ": failed to allocate item for " |
| "rx_pending list. rx_avail=%d, " |
| "rx_pending=%d.\n", |
| rx_avail, rx_pending); |
| new_packet_size = 0; |
| goto exit_err; |
| } |
| p->size = new_packet_size; |
| /* Add new packet as last */ |
| list_add_tail(&p->list, &ch->rx_size_list_head); |
| ch->rx_pending_bytes += new_packet_size; |
| |
| if (ch->read_avail == 0) |
| ch->read_avail = new_packet_size; |
| } |
| |
| exit_err: |
| mutex_unlock(&ch->ch_lock); |
| |
| return new_packet_size; |
| } |
| |
| |
| |
| /** |
| * Remove first pending packet from the list. |
| */ |
| static u32 remove_handled_rx_packet(struct sdio_channel *ch) |
| { |
| struct rx_packet_size *p = NULL; |
| |
| mutex_lock(&ch->ch_lock); |
| |
| ch->rx_pending_bytes -= ch->read_avail; |
| |
| if (!list_empty(&ch->rx_size_list_head)) { |
| p = list_first_entry(&ch->rx_size_list_head, |
| struct rx_packet_size, list); |
| list_del(&p->list); |
| kfree(p); |
| } else { |
| sdio_al_loge(ch->sdio_al_dev->dev_log, MODULE_NAME ":%s: ch " |
| "%s: unexpected empty list!!\n", |
| __func__, ch->name); |
| } |
| |
| if (list_empty(&ch->rx_size_list_head)) { |
| ch->read_avail = 0; |
| } else { |
| p = list_first_entry(&ch->rx_size_list_head, |
| struct rx_packet_size, list); |
| ch->read_avail = p->size; |
| } |
| |
| mutex_unlock(&ch->ch_lock); |
| |
| return ch->read_avail; |
| } |
| |
| |
| /** |
| * Bootloader worker function. |
| * |
| * @note: clear the bootloader_done flag only after reading the |
| * mailbox, to ignore more requests while reading the mailbox. |
| */ |
| static void boot_worker(struct work_struct *work) |
| { |
| int ret = 0; |
| int func_num = 0; |
| int i; |
| struct sdio_al_device *sdio_al_dev = NULL; |
| struct sdio_al_work *sdio_al_work = container_of(work, |
| struct sdio_al_work, |
| work); |
| |
| if (sdio_al_work == NULL) { |
| sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ": %s: NULL " |
| "sdio_al_work\n", __func__); |
| return; |
| } |
| |
| sdio_al_dev = sdio_al_work->sdio_al_dev; |
| if (sdio_al_dev == NULL) { |
| sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ": %s: NULL " |
| "sdio_al_dev\n", __func__); |
| return; |
| } |
| sdio_al_logi(&sdio_al->gen_log, MODULE_NAME ":Bootloader Worker Started" |
| ", wait for bootloader_done event..\n"); |
| wait_event(sdio_al_dev->wait_mbox, |
| sdio_al_dev->bootloader_done); |
| sdio_al_logi(&sdio_al->gen_log, MODULE_NAME ":Got bootloader_done " |
| "event..\n"); |
| /* Do polling until MDM is up */ |
| for (i = 0; i < 5000; ++i) { |
| if (sdio_al_claim_mutex_and_verify_dev(sdio_al_dev, __func__)) |
| return; |
| if (is_user_irq_enabled(sdio_al_dev, func_num)) { |
| sdio_al_release_mutex(sdio_al_dev, __func__); |
| sdio_al_dev->bootloader_done = 0; |
| ret = sdio_al_client_setup(sdio_al_dev); |
| if (ret) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME |
| ": sdio_al_client_setup failed, " |
| "for card %d ret=%d\n", |
| sdio_al_dev->host->index, ret); |
| sdio_al_get_into_err_state(sdio_al_dev); |
| } |
| goto done; |
| } |
| sdio_al_release_mutex(sdio_al_dev, __func__); |
| msleep(100); |
| } |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":Timeout waiting for " |
| "user_irq for card %d\n", |
| sdio_al_dev->host->index); |
| sdio_al_get_into_err_state(sdio_al_dev); |
| |
| done: |
| pr_debug(MODULE_NAME ":Boot Worker for card %d Exit!\n", |
| sdio_al_dev->host->index); |
| } |
| |
| /** |
| * Worker function. |
| * |
| * @note: clear the ask_mbox flag only after |
| * reading the mailbox, to ignore more requests while |
| * reading the mailbox. |
| */ |
| static void worker(struct work_struct *work) |
| { |
| int ret = 0; |
| struct sdio_al_device *sdio_al_dev = NULL; |
| struct sdio_al_work *sdio_al_work = container_of(work, |
| struct sdio_al_work, |
| work); |
| if (sdio_al_work == NULL) { |
| sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ": worker: NULL " |
| "sdio_al_work\n"); |
| return; |
| } |
| |
| sdio_al_dev = sdio_al_work->sdio_al_dev; |
| if (sdio_al_dev == NULL) { |
| sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ": worker: NULL " |
| "sdio_al_dev\n"); |
| return; |
| } |
| pr_debug(MODULE_NAME ":Worker Started..\n"); |
| while ((sdio_al_dev->is_ready) && (ret == 0)) { |
| pr_debug(MODULE_NAME ":Wait for read mailbox request..\n"); |
| wait_event(sdio_al_dev->wait_mbox, sdio_al_dev->ask_mbox); |
| if (!sdio_al_dev->is_ready) |
| break; |
| if (sdio_al_claim_mutex_and_verify_dev(sdio_al_dev, __func__)) |
| break; |
| if (sdio_al_dev->is_ok_to_sleep) { |
| ret = sdio_al_wake_up(sdio_al_dev, 1, NULL); |
| if (ret) { |
| sdio_al_release_mutex(sdio_al_dev, __func__); |
| return; |
| } |
| } |
| ret = read_mailbox(sdio_al_dev, false); |
| sdio_al_release_mutex(sdio_al_dev, __func__); |
| sdio_al_dev->ask_mbox = false; |
| } |
| |
| pr_debug(MODULE_NAME ":Worker Exit!\n"); |
| } |
| |
| /** |
| * Write command using CMD54 rather than CMD53. |
| * Writing with CMD54 generate EOT interrupt at the |
| * SDIO-Client. |
| * Based on mmc_io_rw_extended() |
| */ |
| static int sdio_write_cmd54(struct mmc_card *card, unsigned fn, |
| unsigned addr, const u8 *buf, |
| unsigned blocks, unsigned blksz) |
| { |
| struct mmc_request mrq; |
| struct mmc_command cmd; |
| struct mmc_data data; |
| struct scatterlist sg; |
| int incr_addr = 1; /* MUST */ |
| int write = 1; |
| |
| BUG_ON(!card); |
| BUG_ON(fn > 7); |
| BUG_ON(blocks == 1 && blksz > 512); |
| WARN_ON(blocks == 0); |
| WARN_ON(blksz == 0); |
| |
| write = true; |
| pr_debug(MODULE_NAME ":sdio_write_cmd54()" |
| "fn=%d,buf=0x%x,blocks=%d,blksz=%d\n", |
| fn, (u32) buf, blocks, blksz); |
| |
| memset(&mrq, 0, sizeof(struct mmc_request)); |
| memset(&cmd, 0, sizeof(struct mmc_command)); |
| memset(&data, 0, sizeof(struct mmc_data)); |
| |
| mrq.cmd = &cmd; |
| mrq.data = &data; |
| |
| cmd.opcode = SD_IO_RW_EXTENDED_QCOM; |
| |
| cmd.arg = write ? 0x80000000 : 0x00000000; |
| cmd.arg |= fn << 28; |
| cmd.arg |= incr_addr ? 0x04000000 : 0x00000000; |
| cmd.arg |= addr << 9; |
| if (blocks == 1 && blksz <= 512) |
| cmd.arg |= (blksz == 512) ? 0 : blksz; /* byte mode */ |
| else |
| cmd.arg |= 0x08000000 | blocks; /* block mode */ |
| cmd.flags = MMC_RSP_SPI_R5 | MMC_RSP_R5 | MMC_CMD_ADTC; |
| |
| data.blksz = blksz; |
| data.blocks = blocks; |
| data.flags = write ? MMC_DATA_WRITE : MMC_DATA_READ; |
| data.sg = &sg; |
| data.sg_len = 1; |
| |
| sg_init_one(&sg, buf, blksz * blocks); |
| |
| mmc_set_data_timeout(&data, card); |
| |
| mmc_wait_for_req(card->host, &mrq); |
| |
| if (cmd.error) |
| return cmd.error; |
| if (data.error) |
| return data.error; |
| |
| if (mmc_host_is_spi(card->host)) { |
| /* host driver already reported errors */ |
| } else { |
| if (cmd.resp[0] & R5_ERROR) { |
| sdio_al_loge(&sdio_al->gen_log, MODULE_NAME |
| ":%s: R5_ERROR for card %d", |
| __func__, card->host->index); |
| return -EIO; |
| } |
| if (cmd.resp[0] & R5_FUNCTION_NUMBER) { |
| sdio_al_loge(&sdio_al->gen_log, MODULE_NAME |
| ":%s: R5_FUNCTION_NUMBER for card %d", |
| __func__, card->host->index); |
| return -EINVAL; |
| } |
| if (cmd.resp[0] & R5_OUT_OF_RANGE) { |
| sdio_al_loge(&sdio_al->gen_log, MODULE_NAME |
| ":%s: R5_OUT_OF_RANGE for card %d", |
| __func__, card->host->index); |
| return -ERANGE; |
| } |
| } |
| |
| return 0; |
| } |
| |
| |
| /** |
| * Write data to channel. |
| * Handle different data size types. |
| * |
| */ |
| static int sdio_ch_write(struct sdio_channel *ch, const u8 *buf, u32 len) |
| { |
| int ret = 0; |
| unsigned blksz = ch->func->cur_blksize; |
| int blocks = len / blksz; |
| int remain_bytes = len % blksz; |
| struct mmc_card *card = NULL; |
| u32 fn = ch->func->num; |
| |
| if (!ch) { |
| sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ": %s: NULL " |
| "channel\n", __func__); |
| return -ENODEV; |
| } |
| |
| if (!ch->sdio_al_dev) { |
| sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ": %s: NULL " |
| "sdio_al_dev\n", __func__); |
| return -ENODEV; |
| } |
| |
| if (len == 0) { |
| sdio_al_loge(ch->sdio_al_dev->dev_log, MODULE_NAME ":channel " |
| "%s trying to write 0 bytes\n", ch->name); |
| return -EINVAL; |
| } |
| |
| card = ch->func->card; |
| |
| if (remain_bytes) { |
| /* CMD53 */ |
| if (blocks) { |
| ret = sdio_memcpy_toio(ch->func, PIPE_TX_FIFO_ADDR, |
| (void *) buf, blocks*blksz); |
| if (ret != 0) { |
| sdio_al_loge(ch->sdio_al_dev->dev_log, |
| MODULE_NAME ":%s: sdio_memcpy_toio " |
| "failed for channel %s\n", |
| __func__, ch->name); |
| sdio_al_get_into_err_state(ch->sdio_al_dev); |
| return ret; |
| } |
| } |
| |
| buf += (blocks*blksz); |
| |
| ret = sdio_write_cmd54(card, fn, PIPE_TX_FIFO_ADDR, |
| buf, 1, remain_bytes); |
| } else { |
| ret = sdio_write_cmd54(card, fn, PIPE_TX_FIFO_ADDR, |
| buf, blocks, blksz); |
| } |
| |
| if (ret != 0) { |
| sdio_al_loge(ch->sdio_al_dev->dev_log, MODULE_NAME ":%s: " |
| "sdio_write_cmd54 failed for channel %s\n", |
| __func__, ch->name); |
| ch->sdio_al_dev->is_err = true; |
| return ret; |
| } |
| |
| return ret; |
| } |
| |
| static int sdio_al_bootloader_completed(void) |
| { |
| int i; |
| |
| pr_debug(MODULE_NAME ":sdio_al_bootloader_completed was called\n"); |
| |
| for (i = 0; i < MAX_NUM_OF_SDIO_DEVICES; ++i) { |
| struct sdio_al_device *dev = NULL; |
| if (sdio_al->devices[i] == NULL) |
| continue; |
| dev = sdio_al->devices[i]; |
| dev->bootloader_done = 1; |
| wake_up(&dev->wait_mbox); |
| } |
| |
| return 0; |
| } |
| |
| static int sdio_al_wait_for_bootloader_comp(struct sdio_al_device *sdio_al_dev) |
| { |
| int ret = 0; |
| |
| if (sdio_al_claim_mutex_and_verify_dev(sdio_al_dev, __func__)) |
| return -ENODEV; |
| |
| /* |
| * Enable function 0 interrupt mask to allow 9k to raise this interrupt |
| * in power-up. When sdio_downloader will notify its completion |
| * we will poll on this interrupt to wait for 9k power-up |
| */ |
| ret = enable_mask_irq(sdio_al_dev, 0, 1, 0); |
| if (ret) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME |
| ": Enable_mask_irq for card %d failed, " |
| "ret=%d\n", |
| sdio_al_dev->host->index, ret); |
| sdio_al_release_mutex(sdio_al_dev, __func__); |
| return ret; |
| } |
| |
| sdio_al_release_mutex(sdio_al_dev, __func__); |
| |
| /* |
| * Start bootloader worker that will wait for the bootloader |
| * completion |
| */ |
| sdio_al_dev->boot_work.sdio_al_dev = sdio_al_dev; |
| INIT_WORK(&sdio_al_dev->boot_work.work, boot_worker); |
| sdio_al_dev->bootloader_done = 0; |
| queue_work(sdio_al_dev->workqueue, &sdio_al_dev->boot_work.work); |
| |
| return 0; |
| } |
| |
| static int sdio_al_bootloader_setup(void) |
| { |
| int ret = 0; |
| struct sdio_al_device *bootloader_dev = sdio_al->bootloader_dev; |
| struct sdio_func *func1 = NULL; |
| |
| if (sdio_al_claim_mutex_and_verify_dev(bootloader_dev, __func__)) |
| return -ENODEV; |
| |
| if (bootloader_dev->flashless_boot_on) { |
| sdio_al_loge(bootloader_dev->dev_log, MODULE_NAME ":Already " |
| "in boot process.\n"); |
| sdio_al_release_mutex(bootloader_dev, __func__); |
| return 0; |
| } |
| |
| bootloader_dev->sdioc_boot_sw_header |
| = kzalloc(sizeof(*bootloader_dev->sdioc_boot_sw_header), |
| GFP_KERNEL); |
| if (bootloader_dev->sdioc_boot_sw_header == NULL) { |
| sdio_al_loge(bootloader_dev->dev_log, MODULE_NAME ":fail to " |
| "allocate sdioc boot sw header.\n"); |
| sdio_al_release_mutex(bootloader_dev, __func__); |
| return -ENOMEM; |
| } |
| |
| if (sdio_al_verify_func1(bootloader_dev, __func__)) { |
| sdio_al_release_mutex(bootloader_dev, __func__); |
| goto exit_err; |
| } |
| func1 = bootloader_dev->card->sdio_func[0]; |
| |
| ret = sdio_memcpy_fromio(func1, |
| bootloader_dev->sdioc_boot_sw_header, |
| SDIOC_SW_HEADER_ADDR, |
| sizeof(struct peer_sdioc_boot_sw_header)); |
| if (ret) { |
| sdio_al_loge(bootloader_dev->dev_log, MODULE_NAME ":fail to " |
| "read sdioc boot sw header.\n"); |
| sdio_al_release_mutex(bootloader_dev, __func__); |
| goto exit_err; |
| } |
| |
| if (bootloader_dev->sdioc_boot_sw_header->signature != |
| (u32) PEER_SDIOC_SW_MAILBOX_BOOT_SIGNATURE) { |
| sdio_al_loge(bootloader_dev->dev_log, MODULE_NAME ":invalid " |
| "mailbox signature 0x%x.\n", |
| bootloader_dev->sdioc_boot_sw_header->signature); |
| sdio_al_release_mutex(bootloader_dev, __func__); |
| ret = -EINVAL; |
| goto exit_err; |
| } |
| |
| /* Upper byte has to be equal - no backward compatibility for unequal */ |
| if ((bootloader_dev->sdioc_boot_sw_header->version >> 16) != |
| (sdio_al->pdata->peer_sdioc_boot_version_major)) { |
| sdio_al_loge(bootloader_dev->dev_log, MODULE_NAME ": HOST(0x%x)" |
| " and CLIENT(0x%x) SDIO_AL BOOT VERSION don't match\n", |
| ((sdio_al->pdata->peer_sdioc_boot_version_major<<16)+ |
| sdio_al->pdata->peer_sdioc_boot_version_minor), |
| bootloader_dev->sdioc_boot_sw_header->version); |
| sdio_al_release_mutex(bootloader_dev, __func__); |
| ret = -EIO; |
| goto exit_err; |
| } |
| |
| sdio_al_logi(bootloader_dev->dev_log, MODULE_NAME ": SDIOC BOOT SW " |
| "version 0x%x\n", |
| bootloader_dev->sdioc_boot_sw_header->version); |
| |
| bootloader_dev->flashless_boot_on = true; |
| |
| sdio_al_release_mutex(bootloader_dev, __func__); |
| |
| ret = sdio_al_wait_for_bootloader_comp(bootloader_dev); |
| if (ret) { |
| sdio_al_loge(bootloader_dev->dev_log, MODULE_NAME |
| ": sdio_al_wait_for_bootloader_comp failed, " |
| "err=%d\n", ret); |
| goto exit_err; |
| } |
| |
| ret = sdio_downloader_setup(bootloader_dev->card, 1, |
| bootloader_dev->sdioc_boot_sw_header->boot_ch_num, |
| sdio_al_bootloader_completed); |
| |
| if (ret) { |
| sdio_al_loge(bootloader_dev->dev_log, MODULE_NAME |
| ": sdio_downloader_setup failed, err=%d\n", ret); |
| goto exit_err; |
| } |
| |
| sdio_al_logi(bootloader_dev->dev_log, MODULE_NAME ":In Flashless boot," |
| " waiting for its completion\n"); |
| |
| |
| exit_err: |
| sdio_al_logi(&sdio_al->gen_log, MODULE_NAME ":free " |
| "sdioc_boot_sw_header.\n"); |
| kfree(bootloader_dev->sdioc_boot_sw_header); |
| bootloader_dev->sdioc_boot_sw_header = NULL; |
| bootloader_dev = NULL; |
| |
| return ret; |
| } |
| |
| |
| /** |
| * Read SDIO-Client software header |
| * |
| */ |
| static int read_sdioc_software_header(struct sdio_al_device *sdio_al_dev, |
| struct peer_sdioc_sw_header *header) |
| { |
| int ret; |
| int i; |
| int test_version = 0; |
| int sdioc_test_version = 0; |
| struct sdio_func *func1 = NULL; |
| |
| pr_debug(MODULE_NAME ":reading sdioc sw header.\n"); |
| |
| if (sdio_al_verify_func1(sdio_al_dev, __func__)) |
| return -ENODEV; |
| |
| func1 = sdio_al_dev->card->sdio_func[0]; |
| |
| ret = sdio_memcpy_fromio(func1, header, |
| SDIOC_SW_HEADER_ADDR, sizeof(*header)); |
| if (ret) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":fail to read " |
| "sdioc sw header.\n"); |
| goto exit_err; |
| } |
| |
| if (header->signature == (u32)PEER_SDIOC_SW_MAILBOX_UT_SIGNATURE) { |
| sdio_al_logi(sdio_al_dev->dev_log, MODULE_NAME ":SDIOC SW " |
| "unittest signature. 0x%x\n", |
| header->signature); |
| sdio_al->unittest_mode = true; |
| /* Verify test code compatibility with the modem */ |
| sdioc_test_version = (header->version & 0xFF00) >> 8; |
| test_version = sdio_al->pdata->peer_sdioc_version_minor >> 8; |
| if (test_version != sdioc_test_version) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME |
| ": HOST(0x%x) and CLIENT(0x%x) " |
| "testing VERSION don't match\n", |
| test_version, |
| sdioc_test_version); |
| msleep(500); |
| BUG(); |
| } |
| } |
| |
| if ((header->signature != (u32) PEER_SDIOC_SW_MAILBOX_SIGNATURE) && |
| (header->signature != (u32) PEER_SDIOC_SW_MAILBOX_UT_SIGNATURE)) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":SDIOC SW " |
| "invalid signature. 0x%x\n", header->signature); |
| goto exit_err; |
| } |
| /* Upper byte has to be equal - no backward compatibility for unequal */ |
| sdio_al->sdioc_major = header->version >> 16; |
| if (sdio_al->pdata->allow_sdioc_version_major_2) { |
| if ((sdio_al->sdioc_major != |
| sdio_al->pdata->peer_sdioc_version_major) && |
| (sdio_al->sdioc_major != PEER_SDIOC_OLD_VERSION_MAJOR)) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME |
| ": HOST(0x%x) and CLIENT(0x%x) " |
| "SDIO_AL VERSION don't match\n", |
| ((sdio_al->pdata->peer_sdioc_version_major<<16)+ |
| sdio_al->pdata->peer_sdioc_version_minor), |
| header->version); |
| goto exit_err; |
| } |
| } else { |
| if (sdio_al->sdioc_major != |
| sdio_al->pdata->peer_sdioc_version_major) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME |
| ": HOST(0x%x) and CLIENT(0x%x) " |
| "SDIO_AL VERSION don't match\n", |
| ((sdio_al->pdata->peer_sdioc_version_major<<16)+ |
| sdio_al->pdata->peer_sdioc_version_minor), |
| header->version); |
| goto exit_err; |
| } |
| } |
| sdio_al_dev->ch_close_supported = (header->version & 0x000F) >= |
| (sdio_al->pdata->peer_sdioc_version_minor & 0xF); |
| |
| sdio_al_logi(sdio_al_dev->dev_log, MODULE_NAME ":SDIOC SW version 0x%x," |
| " sdio_al major 0x%x minor 0x%x\n", header->version, |
| sdio_al->sdioc_major, |
| sdio_al->pdata->peer_sdioc_version_minor); |
| |
| sdio_al_dev->flashless_boot_on = false; |
| for (i = 0; i < SDIO_AL_MAX_CHANNELS; i++) { |
| struct sdio_channel *ch = &sdio_al_dev->channel[i]; |
| |
| /* Set default values */ |
| ch->read_threshold = DEFAULT_READ_THRESHOLD; |
| ch->write_threshold = DEFAULT_WRITE_THRESHOLD; |
| ch->min_write_avail = DEFAULT_MIN_WRITE_THRESHOLD; |
| ch->is_packet_mode = true; |
| ch->peer_tx_buf_size = DEFAULT_PEER_TX_BUF_SIZE; |
| ch->poll_delay_msec = 0; |
| |
| ch->num = i; |
| ch->func = NULL; |
| ch->rx_pipe_index = ch->num*2; |
| ch->tx_pipe_index = ch->num*2+1; |
| |
| memset(ch->name, 0, sizeof(ch->name)); |
| |
| if (header->channel_names[i][0]) { |
| memcpy(ch->name, SDIO_PREFIX, |
| strlen(SDIO_PREFIX)); |
| memcpy(ch->name + strlen(SDIO_PREFIX), |
| header->channel_names[i], |
| PEER_CHANNEL_NAME_SIZE); |
| |
| ch->state = SDIO_CHANNEL_STATE_IDLE; |
| ch->sdio_al_dev = sdio_al_dev; |
| if (sdio_al_dev->card->sdio_func[ch->num+1]) { |
| ch->func = |
| sdio_al_dev->card->sdio_func[ch->num+1]; |
| } else { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME |
| ": NULL func for channel %s\n", |
| ch->name); |
| goto exit_err; |
| } |
| } else { |
| ch->state = SDIO_CHANNEL_STATE_INVALID; |
| } |
| |
| sdio_al_logi(sdio_al_dev->dev_log, MODULE_NAME ":Channel=%s, " |
| "state=%d\n", ch->name, ch->state); |
| } |
| |
| return 0; |
| |
| exit_err: |
| sdio_al_get_into_err_state(sdio_al_dev); |
| memset(header, 0, sizeof(*header)); |
| |
| return -EIO; |
| } |
| |
| /** |
| * Read SDIO-Client channel configuration |
| * |
| */ |
| static int read_sdioc_channel_config(struct sdio_channel *ch) |
| { |
| int ret; |
| struct peer_sdioc_sw_mailbox *sw_mailbox = NULL; |
| struct peer_sdioc_channel_config *ch_config = NULL; |
| struct sdio_al_device *sdio_al_dev = ch->sdio_al_dev; |
| |
| if (sdio_al_dev == NULL) { |
| sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ": NULL sdio_al_dev" |
| " for channel %s\n", ch->name); |
| return -EINVAL; |
| } |
| |
| if (sdio_al_dev->sdioc_sw_header->version == 0) |
| return -1; |
| |
| pr_debug(MODULE_NAME ":reading sw mailbox %s channel.\n", ch->name); |
| |
| sw_mailbox = kzalloc(sizeof(*sw_mailbox), GFP_KERNEL); |
| if (sw_mailbox == NULL) |
| return -ENOMEM; |
| |
| ret = sdio_memcpy_fromio(ch->func, sw_mailbox, |
| SDIOC_SW_MAILBOX_ADDR, sizeof(*sw_mailbox)); |
| if (ret) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":fail to read " |
| "sw mailbox.\n"); |
| goto exit_err; |
| } |
| |
| ch_config = &sw_mailbox->ch_config[ch->num]; |
| memcpy(&ch->ch_config, ch_config, |
| sizeof(struct peer_sdioc_channel_config)); |
| |
| if (!ch_config->is_ready) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":sw mailbox " |
| "channel not ready.\n"); |
| goto exit_err; |
| } |
| |
| ch->read_threshold = LOW_LATENCY_THRESHOLD; |
| ch->is_low_latency_ch = ch_config->is_low_latency_ch; |
| /* Threshold on 50% of the maximum size , sdioc uses double-buffer */ |
| ch->write_threshold = (ch_config->max_tx_threshold * 5) / 10; |
| ch->threshold_change_cnt = ch->ch_config.max_rx_threshold - |
| ch->read_threshold + THRESHOLD_CHANGE_EXTRA_BYTES; |
| |
| if (ch->is_low_latency_ch) |
| ch->def_read_threshold = LOW_LATENCY_THRESHOLD; |
| else /* Aggregation up to 90% of the maximum size */ |
| ch->def_read_threshold = (ch_config->max_rx_threshold * 9) / 10; |
| |
| ch->is_packet_mode = ch_config->is_packet_mode; |
| if (!ch->is_packet_mode) { |
| ch->poll_delay_msec = DEFAULT_POLL_DELAY_NOPACKET_MSEC; |
| ch->min_write_avail = DEFAULT_MIN_WRITE_THRESHOLD_STREAMING; |
| } |
| /* The max_packet_size is set by the modem in version 3 and on */ |
| if (sdio_al->sdioc_major > PEER_SDIOC_OLD_VERSION_MAJOR) |
| ch->min_write_avail = ch_config->max_packet_size; |
| |
| if (ch->min_write_avail > ch->write_threshold) |
| ch->min_write_avail = ch->write_threshold; |
| |
| CLOSE_DEBUG(sdio_al_dev->dev_log, MODULE_NAME ":ch %s " |
| "read_threshold=%d, write_threshold=%d," |
| " min_write_avail=%d, max_rx_threshold=%d," |
| " max_tx_threshold=%d\n", ch->name, ch->read_threshold, |
| ch->write_threshold, ch->min_write_avail, |
| ch_config->max_rx_threshold, |
| ch_config->max_tx_threshold); |
| |
| ch->peer_tx_buf_size = ch_config->tx_buf_size; |
| |
| kfree(sw_mailbox); |
| |
| return 0; |
| |
| exit_err: |
| sdio_al_logi(sdio_al_dev->dev_log, MODULE_NAME ":Reading SW Mailbox " |
| "error.\n"); |
| kfree(sw_mailbox); |
| |
| return -1; |
| } |
| |
| |
| /** |
| * Enable/Disable EOT interrupt of a pipe. |
| * |
| */ |
| static int enable_eot_interrupt(struct sdio_al_device *sdio_al_dev, |
| int pipe_index, int enable) |
| { |
| int ret = 0; |
| struct sdio_func *func1; |
| u32 mask; |
| u32 pipe_mask; |
| u32 addr; |
| |
| if (sdio_al_verify_func1(sdio_al_dev, __func__)) |
| return -ENODEV; |
| func1 = sdio_al_dev->card->sdio_func[0]; |
| |
| if (pipe_index < 8) { |
| addr = PIPES_0_7_IRQ_MASK_ADDR; |
| pipe_mask = (1<<pipe_index); |
| } else { |
| addr = PIPES_8_15_IRQ_MASK_ADDR; |
| pipe_mask = (1<<(pipe_index-8)); |
| } |
| |
| mask = sdio_readl(func1, addr, &ret); |
| if (ret) { |
| pr_debug(MODULE_NAME ":enable_eot_interrupt fail\n"); |
| goto exit_err; |
| } |
| |
| if (enable) |
| mask &= (~pipe_mask); /* 0 = enable */ |
| else |
| mask |= (pipe_mask); /* 1 = disable */ |
| |
| sdio_writel(func1, mask, addr, &ret); |
| |
| exit_err: |
| return ret; |
| } |
| |
| |
| /** |
| * Enable/Disable mask interrupt of a function. |
| * |
| */ |
| static int enable_mask_irq(struct sdio_al_device *sdio_al_dev, |
| int func_num, int enable, u8 bit_offset) |
| { |
| int ret = 0; |
| struct sdio_func *func1 = NULL; |
| u32 mask = 0; |
| u32 func_mask = 0; |
| u32 addr = 0; |
| u32 offset = 0; |
| |
| if (sdio_al_verify_func1(sdio_al_dev, __func__)) |
| return -ENODEV; |
| func1 = sdio_al_dev->card->sdio_func[0]; |
| |
| if (func_num < 4) { |
| addr = FUNC_1_4_MASK_IRQ_ADDR; |
| offset = func_num * 8 + bit_offset; |
| } else { |
| addr = FUNC_5_7_MASK_IRQ_ADDR; |
| offset = (func_num - 4) * 8 + bit_offset; |
| } |
| |
| func_mask = 1<<offset; |
| |
| mask = sdio_readl(func1, addr, &ret); |
| if (ret) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ": " |
| "enable_mask_irq fail\n"); |
| goto exit_err; |
| } |
| |
| if (enable) |
| mask &= (~func_mask); /* 0 = enable */ |
| else |
| mask |= (func_mask); /* 1 = disable */ |
| |
| pr_debug(MODULE_NAME ":enable_mask_irq, writing mask = 0x%x\n", mask); |
| |
| sdio_writel(func1, mask, addr, &ret); |
| |
| exit_err: |
| return ret; |
| } |
| |
| /** |
| * Enable/Disable Threshold interrupt of a pipe. |
| * |
| */ |
| static int enable_threshold_interrupt(struct sdio_al_device *sdio_al_dev, |
| int pipe_index, int enable) |
| { |
| int ret = 0; |
| struct sdio_func *func1; |
| u32 mask; |
| u32 pipe_mask; |
| u32 addr; |
| |
| if (sdio_al_verify_func1(sdio_al_dev, __func__)) |
| return -ENODEV; |
| func1 = sdio_al_dev->card->sdio_func[0]; |
| |
| if (pipe_index < 8) { |
| addr = PIPES_0_7_IRQ_MASK_ADDR; |
| pipe_mask = (1<<pipe_index); |
| } else { |
| addr = PIPES_8_15_IRQ_MASK_ADDR; |
| pipe_mask = (1<<(pipe_index-8)); |
| } |
| |
| mask = sdio_readl(func1, addr, &ret); |
| if (ret) { |
| pr_debug(MODULE_NAME ":enable_threshold_interrupt fail\n"); |
| goto exit_err; |
| } |
| |
| pipe_mask = pipe_mask<<8; /* Threshold bits 8..15 */ |
| if (enable) |
| mask &= (~pipe_mask); /* 0 = enable */ |
| else |
| mask |= (pipe_mask); /* 1 = disable */ |
| |
| sdio_writel(func1, mask, addr, &ret); |
| |
| exit_err: |
| return ret; |
| } |
| |
| /** |
| * Set the threshold to trigger interrupt from SDIO-Card on |
| * pipe available bytes. |
| * |
| */ |
| static int set_pipe_threshold(struct sdio_al_device *sdio_al_dev, |
| int pipe_index, int threshold) |
| { |
| int ret = 0; |
| struct sdio_func *func1; |
| |
| if (sdio_al_verify_func1(sdio_al_dev, __func__)) |
| return -ENODEV; |
| func1 = sdio_al_dev->card->sdio_func[0]; |
| |
| sdio_writel(func1, threshold, |
| PIPES_THRESHOLD_ADDR+pipe_index*4, &ret); |
| if (ret) |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ": " |
| "set_pipe_threshold err=%d\n", -ret); |
| |
| return ret; |
| } |
| |
| /** |
| * Enable func w/ retries |
| * |
| */ |
| static int sdio_al_enable_func_retry(struct sdio_func *func, const char *name) |
| { |
| int ret, i; |
| for (i = 0; i < 200; i++) { |
| ret = sdio_enable_func(func); |
| if (ret) { |
| pr_debug(MODULE_NAME ":retry enable %s func#%d " |
| "ret=%d\n", |
| name, func->num, ret); |
| msleep(10); |
| } else |
| break; |
| } |
| |
| return ret; |
| } |
| |
| /** |
| * Open Channel |
| * |
| * 1. Init Channel Context. |
| * 2. Init the Channel SDIO-Function. |
| * 3. Init the Channel Pipes on Mailbox. |
| */ |
| static int open_channel(struct sdio_channel *ch) |
| { |
| int ret = 0; |
| struct sdio_al_device *sdio_al_dev = ch->sdio_al_dev; |
| |
| if (sdio_al_dev == NULL) { |
| sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ": NULL " |
| "sdio_al_dev for channel %s\n", ch->name); |
| return -EINVAL; |
| } |
| |
| /* Init channel Context */ |
| /** Func#1 is reserved for mailbox */ |
| ch->func = sdio_al_dev->card->sdio_func[ch->num+1]; |
| ch->rx_pipe_index = ch->num*2; |
| ch->tx_pipe_index = ch->num*2+1; |
| ch->signature = SDIO_AL_SIGNATURE; |
| |
| ch->total_rx_bytes = 0; |
| ch->total_tx_bytes = 0; |
| |
| ch->write_avail = 0; |
| ch->read_avail = 0; |
| ch->rx_pending_bytes = 0; |
| |
| mutex_init(&ch->ch_lock); |
| |
| pr_debug(MODULE_NAME ":open_channel %s func#%d\n", |
| ch->name, ch->func->num); |
| |
| INIT_LIST_HEAD(&(ch->rx_size_list_head)); |
| |
| /* Init SDIO Function */ |
| ret = sdio_al_enable_func_retry(ch->func, ch->name); |
| if (ret) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ": " |
| "sdio_enable_func() err=%d\n", -ret); |
| goto exit_err; |
| } |
| |
| /* Note: Patch Func CIS tuple issue */ |
| ret = sdio_set_block_size(ch->func, SDIO_AL_BLOCK_SIZE); |
| if (ret) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ": " |
| "sdio_set_block_size()failed, err=%d\n", -ret); |
| goto exit_err; |
| } |
| |
| ch->func->max_blksize = SDIO_AL_BLOCK_SIZE; |
| |
| sdio_set_drvdata(ch->func, ch); |
| |
| /* Get channel parameters from the peer SDIO-Client */ |
| read_sdioc_channel_config(ch); |
| |
| /* Set Pipes Threshold on Mailbox */ |
| ret = set_pipe_threshold(sdio_al_dev, |
| ch->rx_pipe_index, ch->read_threshold); |
| if (ret) |
| goto exit_err; |
| ret = set_pipe_threshold(sdio_al_dev, |
| ch->tx_pipe_index, ch->write_threshold); |
| if (ret) |
| goto exit_err; |
| |
| /* Set flag before interrupts are enabled to allow notify */ |
| ch->state = SDIO_CHANNEL_STATE_OPEN; |
| pr_debug(MODULE_NAME ":channel %s is in OPEN state now\n", ch->name); |
| |
| sdio_al_dev->poll_delay_msec = get_min_poll_time_msec(sdio_al_dev); |
| |
| /* lpm mechanism lives under the assumption there is always a timer */ |
| /* Check if need to start the timer */ |
| if ((sdio_al_dev->poll_delay_msec) && |
| (sdio_al_dev->is_timer_initialized == false)) { |
| |
| init_timer(&sdio_al_dev->timer); |
| sdio_al_dev->timer.data = (unsigned long) sdio_al_dev; |
| sdio_al_dev->timer.function = sdio_al_timer_handler; |
| sdio_al_dev->timer.expires = jiffies + |
| msecs_to_jiffies(sdio_al_dev->poll_delay_msec); |
| add_timer(&sdio_al_dev->timer); |
| sdio_al_dev->is_timer_initialized = true; |
| } |
| |
| /* Enable Pipes Interrupts */ |
| enable_eot_interrupt(sdio_al_dev, ch->rx_pipe_index, true); |
| enable_eot_interrupt(sdio_al_dev, ch->tx_pipe_index, true); |
| |
| enable_threshold_interrupt(sdio_al_dev, ch->rx_pipe_index, true); |
| enable_threshold_interrupt(sdio_al_dev, ch->tx_pipe_index, true); |
| |
| exit_err: |
| |
| return ret; |
| } |
| |
| /** |
| * Ask the worker to read the mailbox. |
| */ |
| static void ask_reading_mailbox(struct sdio_al_device *sdio_al_dev) |
| { |
| if (!sdio_al_dev->ask_mbox) { |
| pr_debug(MODULE_NAME ":ask_reading_mailbox for card %d\n", |
| sdio_al_dev->host->index); |
| sdio_al_dev->ask_mbox = true; |
| wake_up(&sdio_al_dev->wait_mbox); |
| } |
| } |
| |
| /** |
| * Start the timer |
| */ |
| static void start_timer(struct sdio_al_device *sdio_al_dev) |
| { |
| if ((sdio_al_dev->poll_delay_msec) && |
| (sdio_al_dev->state == CARD_INSERTED)) { |
| sdio_al_dev->timer.expires = jiffies + |
| msecs_to_jiffies(sdio_al_dev->poll_delay_msec); |
| add_timer(&sdio_al_dev->timer); |
| } |
| } |
| |
| /** |
| * Restart(postpone) the already working timer |
| */ |
| static void restart_timer(struct sdio_al_device *sdio_al_dev) |
| { |
| if ((sdio_al_dev->poll_delay_msec) && |
| (sdio_al_dev->state == CARD_INSERTED)) { |
| ulong expires = jiffies + |
| msecs_to_jiffies(sdio_al_dev->poll_delay_msec); |
| mod_timer(&sdio_al_dev->timer, expires); |
| } |
| } |
| |
| /** |
| * Stop and delete the timer |
| */ |
| static void stop_and_del_timer(struct sdio_al_device *sdio_al_dev) |
| { |
| if (sdio_al_dev->is_timer_initialized) { |
| sdio_al_dev->poll_delay_msec = 0; |
| del_timer_sync(&sdio_al_dev->timer); |
| } |
| } |
| |
| /** |
| * Do the wakup sequence. |
| * This function should be called after claiming the host! |
| * The caller is responsible for releasing the host. |
| * |
| * Wake up sequence |
| * 1. Get lock |
| * 2. Enable wake up function if needed |
| * 3. Mark NOT OK to sleep and write it |
| * 4. Restore default thresholds |
| * 5. Start the mailbox and inactivity timer again |
| */ |
| static int sdio_al_wake_up(struct sdio_al_device *sdio_al_dev, |
| u32 not_from_int, struct sdio_channel *ch) |
| { |
| int ret = 0; |
| struct sdio_func *wk_func = NULL; |
| unsigned long time_to_wait; |
| struct mmc_host *host = sdio_al_dev->host; |
| |
| if (sdio_al_dev->is_err) { |
| SDIO_AL_ERR(__func__); |
| return -ENODEV; |
| } |
| |
| if (!sdio_al_dev->is_ok_to_sleep) { |
| LPM_DEBUG(sdio_al_dev->dev_log, MODULE_NAME ":card %d " |
| "already awake, no need to wake up\n", |
| sdio_al_dev->host->index); |
| return 0; |
| } |
| |
| /* Wake up sequence */ |
| if (not_from_int) { |
| if (ch) { |
| LPM_DEBUG(sdio_al_dev->dev_log, MODULE_NAME ": Wake up" |
| " card %d (not by interrupt), ch %s", |
| sdio_al_dev->host->index, |
| ch->name); |
| } else { |
| LPM_DEBUG(sdio_al_dev->dev_log, MODULE_NAME ": Wake up" |
| " card %d (not by interrupt)", |
| sdio_al_dev->host->index); |
| } |
| } else { |
| LPM_DEBUG(sdio_al_dev->dev_log, MODULE_NAME ": Wake up card " |
| "%d by interrupt", |
| sdio_al_dev->host->index); |
| sdio_al_dev->print_after_interrupt = 1; |
| } |
| |
| sdio_al_vote_for_sleep(sdio_al_dev, 0); |
| |
| msmsdcc_lpm_disable(host); |
| msmsdcc_set_pwrsave(host, 0); |
| /* Poll the GPIO */ |
| time_to_wait = jiffies + msecs_to_jiffies(1000); |
| while (time_before(jiffies, time_to_wait)) { |
| if (sdio_al->pdata->get_mdm2ap_status()) |
| break; |
| udelay(TIME_TO_WAIT_US); |
| } |
| |
| pr_debug(MODULE_NAME ":GPIO mdm2ap_status=%d\n", |
| sdio_al->pdata->get_mdm2ap_status()); |
| |
| /* Here get_mdm2ap_status() returning 0 is not an error condition */ |
| if (sdio_al->pdata->get_mdm2ap_status() == 0) |
| LPM_DEBUG(sdio_al_dev->dev_log, MODULE_NAME ": " |
| "get_mdm2ap_status() is 0\n"); |
| |
| /* Enable Wake up Function */ |
| if (!sdio_al_dev->card || |
| !sdio_al_dev->card->sdio_func[SDIO_AL_WAKEUP_FUNC-1]) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME |
| ": NULL card or wk_func\n"); |
| return -ENODEV; |
| } |
| wk_func = sdio_al_dev->card->sdio_func[SDIO_AL_WAKEUP_FUNC-1]; |
| ret = sdio_al_enable_func_retry(wk_func, "wakeup func"); |
| if (ret) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ": " |
| "sdio_enable_func() err=%d\n", -ret); |
| goto error_exit; |
| } |
| /* Mark NOT OK_TOSLEEP */ |
| sdio_al_dev->is_ok_to_sleep = 0; |
| ret = write_lpm_info(sdio_al_dev); |
| if (ret) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ": " |
| "write_lpm_info() failed, err=%d\n", -ret); |
| sdio_al_dev->is_ok_to_sleep = 1; |
| sdio_disable_func(wk_func); |
| goto error_exit; |
| } |
| sdio_disable_func(wk_func); |
| |
| /* Start the timer again*/ |
| restart_inactive_time(sdio_al_dev); |
| sdio_al_dev->poll_delay_msec = get_min_poll_time_msec(sdio_al_dev); |
| start_timer(sdio_al_dev); |
| |
| LPM_DEBUG(sdio_al_dev->dev_log, MODULE_NAME "Finished Wake up sequence" |
| " for card %d", sdio_al_dev->host->index); |
| |
| msmsdcc_set_pwrsave(host, 1); |
| pr_debug(MODULE_NAME ":Turn clock off\n"); |
| |
| return ret; |
| error_exit: |
| sdio_al_vote_for_sleep(sdio_al_dev, 1); |
| msmsdcc_set_pwrsave(host, 1); |
| WARN_ON(ret); |
| sdio_al_get_into_err_state(sdio_al_dev); |
| return ret; |
| } |
| |
| |
| /** |
| * SDIO Function Interrupt handler. |
| * |
| * Interrupt shall be triggered by SDIO-Client when: |
| * 1. End-Of-Transfer (EOT) detected in packet mode. |
| * 2. Bytes-available reached the threshold. |
| * |
| * Reading the mailbox clears the EOT/Threshold interrupt |
| * source. |
| * The interrupt source should be cleared before this ISR |
| * returns. This ISR is called from IRQ Thread and not |
| * interrupt, so it may sleep. |
| * |
| */ |
| static void sdio_func_irq(struct sdio_func *func) |
| { |
| struct sdio_al_device *sdio_al_dev = sdio_get_drvdata(func); |
| |
| pr_debug(MODULE_NAME ":start %s.\n", __func__); |
| |
| if (sdio_al_dev == NULL) { |
| sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ": NULL device"); |
| return; |
| } |
| |
| if (sdio_al_dev->is_ok_to_sleep) |
| sdio_al_wake_up(sdio_al_dev, 0, NULL); |
| else |
| restart_timer(sdio_al_dev); |
| |
| read_mailbox(sdio_al_dev, true); |
| |
| pr_debug(MODULE_NAME ":end %s.\n", __func__); |
| } |
| |
| /** |
| * Timer Expire Handler |
| * |
| */ |
| static void sdio_al_timer_handler(unsigned long data) |
| { |
| struct sdio_al_device *sdio_al_dev = (struct sdio_al_device *)data; |
| if (sdio_al_dev == NULL) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ": NULL " |
| "sdio_al_dev for data %lu\n", data); |
| return; |
| } |
| if (sdio_al_dev->state != CARD_INSERTED) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ": sdio_al_dev " |
| "is in invalid state %d\n", sdio_al_dev->state); |
| return; |
| } |
| pr_debug(MODULE_NAME " Timer Expired\n"); |
| |
| ask_reading_mailbox(sdio_al_dev); |
| |
| restart_timer(sdio_al_dev); |
| } |
| |
| /** |
| * Driver Setup. |
| * |
| */ |
| static int sdio_al_setup(struct sdio_al_device *sdio_al_dev) |
| { |
| int ret = 0; |
| struct mmc_card *card = sdio_al_dev->card; |
| struct sdio_func *func1 = NULL; |
| int i = 0; |
| int fn = 0; |
| |
| if (sdio_al_verify_func1(sdio_al_dev, __func__)) |
| return -ENODEV; |
| func1 = card->sdio_func[0]; |
| |
| sdio_al_logi(sdio_al_dev->dev_log, MODULE_NAME ":sdio_al_setup for " |
| "card %d\n", sdio_al_dev->host->index); |
| |
| ret = sdio_al->pdata->config_mdm2ap_status(1); |
| if (ret) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME "Could not " |
| "request GPIO\n"); |
| return ret; |
| } |
| |
| INIT_WORK(&sdio_al_dev->sdio_al_work.work, worker); |
| /* disable all pipes interrupts before claim irq. |
| since all are enabled by default. */ |
| for (i = 0 ; i < SDIO_AL_MAX_PIPES; i++) { |
| enable_eot_interrupt(sdio_al_dev, i, false); |
| enable_threshold_interrupt(sdio_al_dev, i, false); |
| } |
| |
| /* Disable all SDIO Functions before claim irq. */ |
| for (fn = 1 ; fn <= card->sdio_funcs; fn++) |
| sdio_disable_func(card->sdio_func[fn-1]); |
| |
| sdio_set_drvdata(func1, sdio_al_dev); |
| sdio_al_logi(sdio_al_dev->dev_log, MODULE_NAME ":claim IRQ for card " |
| "%d\n", sdio_al_dev->host->index); |
| |
| ret = sdio_claim_irq(func1, sdio_func_irq); |
| if (ret) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":Fail to claim" |
| " IRQ for card %d\n", |
| sdio_al_dev->host->index); |
| return ret; |
| } |
| |
| sdio_al_dev->is_ready = true; |
| |
| /* Start worker before interrupt might happen */ |
| queue_work(sdio_al_dev->workqueue, &sdio_al_dev->sdio_al_work.work); |
| |
| start_inactive_time(sdio_al_dev); |
| |
| pr_debug(MODULE_NAME ":Ready.\n"); |
| |
| return 0; |
| } |
| |
| /** |
| * Driver Tear-Down. |
| * |
| */ |
| static void sdio_al_tear_down(void) |
| { |
| int i, j; |
| struct sdio_al_device *sdio_al_dev = NULL; |
| struct sdio_func *func1; |
| |
| for (i = 0; i < MAX_NUM_OF_SDIO_DEVICES; ++i) { |
| if (sdio_al->devices[i] == NULL) |
| continue; |
| sdio_al_dev = sdio_al->devices[i]; |
| |
| if (sdio_al_dev->is_ready) { |
| sdio_al_dev->is_ready = false; /* Flag worker to exit */ |
| sdio_al_dev->ask_mbox = false; |
| ask_reading_mailbox(sdio_al_dev); /* Wakeup worker */ |
| /* allow gracefully exit of the worker thread */ |
| msleep(100); |
| |
| flush_workqueue(sdio_al_dev->workqueue); |
| destroy_workqueue(sdio_al_dev->workqueue); |
| |
| sdio_al_vote_for_sleep(sdio_al_dev, 1); |
| |
| if (!sdio_al_claim_mutex_and_verify_dev(sdio_al_dev, |
| __func__)) { |
| if (!sdio_al_dev->card || |
| !sdio_al_dev->card->sdio_func[0]) { |
| sdio_al_loge(sdio_al_dev->dev_log, |
| MODULE_NAME |
| ": %s: Invalid func1", |
| __func__); |
| return; |
| } |
| func1 = sdio_al_dev->card->sdio_func[0]; |
| sdio_release_irq(func1); |
| sdio_disable_func(func1); |
| sdio_al_release_mutex(sdio_al_dev, __func__); |
| } |
| } |
| |
| for (j = 0; j < SDIO_AL_MAX_CHANNELS; j++) |
| sdio_al_dev->channel[j].signature = 0x0; |
| sdio_al_dev->signature = 0; |
| |
| kfree(sdio_al_dev->sdioc_sw_header); |
| kfree(sdio_al_dev->mailbox); |
| kfree(sdio_al_dev->rx_flush_buf); |
| kfree(sdio_al_dev); |
| } |
| |
| sdio_al->pdata->config_mdm2ap_status(0); |
| } |
| |
| /** |
| * Find channel by name. |
| * |
| */ |
| static struct sdio_channel *find_channel_by_name(const char *name) |
| { |
| struct sdio_channel *ch = NULL; |
| int i, j; |
| struct sdio_al_device *sdio_al_dev = NULL; |
| |
| for (j = 0; j < MAX_NUM_OF_SDIO_DEVICES; ++j) { |
| if (sdio_al->devices[j] == NULL) |
| continue; |
| sdio_al_dev = sdio_al->devices[j]; |
| for (i = 0; i < SDIO_AL_MAX_CHANNELS; i++) { |
| if (sdio_al_dev->channel[i].state == |
| SDIO_CHANNEL_STATE_INVALID) |
| continue; |
| if (strncmp(sdio_al_dev->channel[i].name, name, |
| CHANNEL_NAME_SIZE) == 0) { |
| ch = &sdio_al_dev->channel[i]; |
| break; |
| } |
| } |
| if (ch != NULL) |
| break; |
| } |
| |
| return ch; |
| } |
| |
| /** |
| * Find the minimal poll time. |
| * |
| */ |
| static int get_min_poll_time_msec(struct sdio_al_device *sdio_sl_dev) |
| { |
| int i; |
| int poll_delay_msec = 0x0FFFFFFF; |
| |
| for (i = 0; i < SDIO_AL_MAX_CHANNELS; i++) |
| if ((sdio_sl_dev->channel[i].state == |
| SDIO_CHANNEL_STATE_OPEN) && |
| (sdio_sl_dev->channel[i].poll_delay_msec > 0) && |
| (sdio_sl_dev->channel[i].poll_delay_msec < poll_delay_msec)) |
| poll_delay_msec = |
| sdio_sl_dev->channel[i].poll_delay_msec; |
| |
| if (poll_delay_msec == 0x0FFFFFFF) |
| poll_delay_msec = SDIO_AL_POLL_TIME_NO_STREAMING; |
| |
| pr_debug(MODULE_NAME ":poll delay time is %d msec\n", poll_delay_msec); |
| |
| return poll_delay_msec; |
| } |
| |
| /** |
| * Open SDIO Channel. |
| * |
| * Enable the channel. |
| * Set the channel context. |
| * Trigger reading the mailbox to check available bytes. |
| * |
| */ |
| int sdio_open(const char *name, struct sdio_channel **ret_ch, void *priv, |
| void (*notify)(void *priv, unsigned ch_event)) |
| { |
| int ret = 0; |
| struct sdio_channel *ch = NULL; |
| struct sdio_al_device *sdio_al_dev = NULL; |
| |
| *ret_ch = NULL; /* default */ |
| |
| ch = find_channel_by_name(name); |
| if (ch == NULL) { |
| sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ":Can't find " |
| "channel name %s\n", name); |
| return -EINVAL; |
| } |
| |
| sdio_al_dev = ch->sdio_al_dev; |
| if (sdio_al_claim_mutex_and_verify_dev(sdio_al_dev, __func__)) |
| return -ENODEV; |
| |
| if ((ch->state != SDIO_CHANNEL_STATE_IDLE) && |
| (ch->state != SDIO_CHANNEL_STATE_CLOSED)) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":Wrong ch %s " |
| "state %d\n", name, ch->state); |
| ret = -EPERM; |
| goto exit_err; |
| } |
| |
| if (sdio_al_dev->is_err) { |
| SDIO_AL_ERR(__func__); |
| ret = -ENODEV; |
| goto exit_err; |
| } |
| |
| ret = sdio_al_wake_up(sdio_al_dev, 1, ch); |
| if (ret) |
| goto exit_err; |
| |
| ch->notify = notify; |
| ch->priv = priv; |
| |
| /* Note: Set caller returned context before interrupts are enabled */ |
| *ret_ch = ch; |
| |
| ret = open_channel(ch); |
| if (ret) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":sdio_open %s " |
| "err=%d\n", name, -ret); |
| goto exit_err; |
| } |
| |
| CLOSE_DEBUG(sdio_al_dev->dev_log, MODULE_NAME ":sdio_open %s " |
| "completed OK\n", name); |
| if (sdio_al_dev->lpm_chan == INVALID_SDIO_CHAN) { |
| if (sdio_al->sdioc_major == PEER_SDIOC_OLD_VERSION_MAJOR) { |
| if (!ch->is_packet_mode) { |
| sdio_al_logi(sdio_al_dev->dev_log, MODULE_NAME |
| ":setting channel %s as " |
| "lpm_chan\n", name); |
| sdio_al_dev->lpm_chan = ch->num; |
| } |
| } else { |
| sdio_al_logi(sdio_al_dev->dev_log, MODULE_NAME ": " |
| "setting channel %s as lpm_chan\n", |
| name); |
| sdio_al_dev->lpm_chan = ch->num; |
| } |
| } |
| |
| exit_err: |
| sdio_al_release_mutex(sdio_al_dev, __func__); |
| return ret; |
| } |
| EXPORT_SYMBOL(sdio_open); |
| |
| /** |
| * Request peer operation |
| * note: sanity checks of parameters done by caller |
| * called under bus locked |
| */ |
| static int peer_set_operation(u32 opcode, |
| struct sdio_al_device *sdio_al_dev, |
| struct sdio_channel *ch) |
| { |
| int ret; |
| int offset; |
| struct sdio_func *wk_func = NULL; |
| u32 peer_operation; |
| int loop_count = 0; |
| |
| if (!sdio_al_dev->card || |
| !sdio_al_dev->card->sdio_func[SDIO_AL_WAKEUP_FUNC-1]) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME |
| ": NULL card or wk_func\n"); |
| ret = -ENODEV; |
| goto exit; |
| } |
| wk_func = sdio_al_dev->card->sdio_func[SDIO_AL_WAKEUP_FUNC-1]; |
| |
| /* calculate offset of peer_operation field in sw mailbox struct */ |
| offset = offsetof(struct peer_sdioc_sw_mailbox, ch_config) + |
| sizeof(struct peer_sdioc_channel_config) * ch->num + |
| offsetof(struct peer_sdioc_channel_config, peer_operation); |
| |
| ret = sdio_al_wake_up(sdio_al_dev, 1, ch); |
| if (ret) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":Fail to " |
| "wake up\n"); |
| goto exit; |
| } |
| /* request operation from MDM peer */ |
| peer_operation = PEER_OPERATION(opcode, PEER_OP_STATE_INIT); |
| ret = sdio_memcpy_toio(ch->func, SDIOC_SW_MAILBOX_ADDR+offset, |
| &peer_operation, sizeof(u32)); |
| if (ret) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":failed to " |
| "request close operation\n"); |
| goto exit; |
| } |
| ret = sdio_al_enable_func_retry(wk_func, "wk_func"); |
| if (ret) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":Fail to enable" |
| " Func#%d\n", wk_func->num); |
| goto exit; |
| } |
| pr_debug(MODULE_NAME ":%s: wk_func enabled on ch %s\n", |
| __func__, ch->name); |
| /* send "start" operation to MDM */ |
| peer_operation = PEER_OPERATION(opcode, PEER_OP_STATE_START); |
| ret = sdio_memcpy_toio(ch->func, SDIOC_SW_MAILBOX_ADDR+offset, |
| &peer_operation, sizeof(u32)); |
| if (ret) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":failed to " |
| "send start close operation\n"); |
| goto exit; |
| } |
| ret = sdio_disable_func(wk_func); |
| if (ret) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":Fail to " |
| "disable Func#%d\n", wk_func->num); |
| goto exit; |
| } |
| /* poll for peer operation ack */ |
| while (peer_operation != 0) { |
| ret = sdio_memcpy_fromio(ch->func, |
| &peer_operation, |
| SDIOC_SW_MAILBOX_ADDR+offset, |
| sizeof(u32)); |
| if (ret) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME |
| ":failed to request ack on close" |
| " operation, loop_count = %d\n", |
| loop_count); |
| goto exit; |
| } |
| loop_count++; |
| if (loop_count > 10) { |
| sdio_al_logi(sdio_al_dev->dev_log, MODULE_NAME ":%s: " |
| "peer_operation=0x%x wait loop" |
| " %d on ch %s\n", __func__, |
| peer_operation, loop_count, ch->name); |
| } |
| } |
| exit: |
| return ret; |
| } |
| |
| static int channel_close(struct sdio_channel *ch, int flush_flag) |
| { |
| int ret; |
| struct sdio_al_device *sdio_al_dev = NULL; |
| int flush_len; |
| ulong flush_expires; |
| |
| if (!ch) { |
| sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ":%s: NULL " |
| "channel\n", __func__); |
| return -ENODEV; |
| } |
| |
| if (!ch->func) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":%s: NULL func" |
| " on channel:%d\n", __func__, ch->num); |
| return -ENODEV; |
| } |
| |
| sdio_al_dev = ch->sdio_al_dev; |
| if (sdio_al_claim_mutex_and_verify_dev(sdio_al_dev, __func__)) |
| return -ENODEV; |
| |
| if (!sdio_al_dev->ch_close_supported) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":%s: Not " |
| "supported by mdm, ch %s\n", |
| __func__, ch->name); |
| ret = -ENOTSUPP; |
| goto error_exit; |
| } |
| |
| if (sdio_al_dev->is_err) { |
| SDIO_AL_ERR(__func__); |
| ret = -ENODEV; |
| goto error_exit; |
| } |
| if (ch->state != SDIO_CHANNEL_STATE_OPEN) { |
| sdio_al_loge(sdio_al_dev->dev_log, |
| MODULE_NAME ":%s: ch %s is not in " |
| "open state (%d)\n", |
| __func__, ch->name, ch->state); |
| ret = -ENODEV; |
| goto error_exit; |
| } |
| ch->state = SDIO_CHANNEL_STATE_CLOSING; |
| ret = peer_set_operation(PEER_OP_CODE_CLOSE, sdio_al_dev, ch); |
| if (ret) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":%s: " |
| "peer_set_operation() failed: %d\n", |
| __func__, ret); |
| ret = -ENODEV; |
| goto error_exit; |
| } |
| /* udate poll time for opened channels */ |
| if (ch->poll_delay_msec > 0) { |
| sdio_al_dev->poll_delay_msec = |
| get_min_poll_time_msec(sdio_al_dev); |
| } |
| sdio_al_release_mutex(ch->sdio_al_dev, __func__); |
| |
| flush_expires = jiffies + |
| msecs_to_jiffies(SDIO_CLOSE_FLUSH_TIMEOUT_MSEC); |
| /* flush rx packets of the channel */ |
| if (flush_flag) { |
| do { |
| while (ch->read_avail > 0) { |
| flush_len = ch->read_avail; |
| ret = sdio_read_internal(ch, |
| sdio_al_dev->rx_flush_buf, |
| flush_len); |
| if (ret) { |
| sdio_al_loge(&sdio_al->gen_log, |
| MODULE_NAME ":%s sdio_read" |
| " failed: %d, ch %s\n", |
| __func__, ret, |
| ch->name); |
| return ret; |
| } |
| |
| if (time_after(jiffies, flush_expires) != 0) { |
| sdio_al_loge(&sdio_al->gen_log, |
| MODULE_NAME ":%s flush rx " |
| "packets timeout: ch %s\n", |
| __func__, ch->name); |
| sdio_al_get_into_err_state(sdio_al_dev); |
| return -EBUSY; |
| } |
| } |
| msleep(100); |
| if (ch->signature != SDIO_AL_SIGNATURE) { |
| sdio_al_loge(&sdio_al->gen_log, |
| MODULE_NAME ":%s: after sleep," |
| " invalid signature" |
| " 0x%x\n", __func__, |
| ch->signature); |
| return -ENODEV; |
| } |
| if (sdio_al_claim_mutex_and_verify_dev(ch->sdio_al_dev, |
| __func__)) |
| return -ENODEV; |
| |
| ret = read_mailbox(sdio_al_dev, false); |
| if (ret) { |
| sdio_al_loge(&sdio_al->gen_log, |
| MODULE_NAME ":%s: failed to" |
| " read mailbox", __func__); |
| goto error_exit; |
| } |
| sdio_al_release_mutex(ch->sdio_al_dev, __func__); |
| } while (ch->read_avail > 0); |
| } |
| if (sdio_al_claim_mutex_and_verify_dev(ch->sdio_al_dev, |
| __func__)) |
| return -ENODEV; |
| /* disable function to be able to open the channel again */ |
| ret = sdio_disable_func(ch->func); |
| if (ret) { |
| sdio_al_loge(&sdio_al->gen_log, |
| MODULE_NAME ":Fail to disable Func#%d\n", |
| ch->func->num); |
| goto error_exit; |
| } |
| ch->state = SDIO_CHANNEL_STATE_CLOSED; |
| CLOSE_DEBUG(sdio_al_dev->dev_log, MODULE_NAME ":%s: Ch %s closed " |
| "successfully\n", __func__, ch->name); |
| |
| error_exit: |
| sdio_al_release_mutex(ch->sdio_al_dev, __func__); |
| |
| return ret; |
| } |
| |
| /** |
| * Close SDIO Channel. |
| * |
| */ |
| int sdio_close(struct sdio_channel *ch) |
| { |
| return channel_close(ch, true); |
| } |
| EXPORT_SYMBOL(sdio_close); |
| |
| /** |
| * Get the number of available bytes to write. |
| * |
| */ |
| int sdio_write_avail(struct sdio_channel *ch) |
| { |
| if (!ch) { |
| sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ":%s: NULL " |
| "channel\n", __func__); |
| return -ENODEV; |
| } |
| if (ch->signature != SDIO_AL_SIGNATURE) { |
| sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ":%s: " |
| "Invalid signature 0x%x\n", __func__, |
| ch->signature); |
| return -ENODEV; |
| } |
| if (ch->state != SDIO_CHANNEL_STATE_OPEN) { |
| sdio_al_loge(ch->sdio_al_dev->dev_log, MODULE_NAME ":%s: " |
| "channel %s state is not open (%d)\n", |
| __func__, ch->name, ch->state); |
| return -ENODEV; |
| } |
| pr_debug(MODULE_NAME ":sdio_write_avail %s 0x%x\n", |
| ch->name, ch->write_avail); |
| |
| return ch->write_avail; |
| } |
| EXPORT_SYMBOL(sdio_write_avail); |
| |
| /** |
| * Get the number of available bytes to read. |
| * |
| */ |
| int sdio_read_avail(struct sdio_channel *ch) |
| { |
| if (!ch) { |
| sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ":%s: NULL " |
| "channel\n", __func__); |
| return -ENODEV; |
| } |
| if (ch->signature != SDIO_AL_SIGNATURE) { |
| sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ":%s: " |
| "Invalid signature 0x%x\n", __func__, |
| ch->signature); |
| return -ENODEV; |
| } |
| if (ch->state != SDIO_CHANNEL_STATE_OPEN) { |
| sdio_al_loge(ch->sdio_al_dev->dev_log, MODULE_NAME ":%s: " |
| "channel %s state is not open (%d)\n", |
| __func__, ch->name, ch->state); |
| return -ENODEV; |
| } |
| pr_debug(MODULE_NAME ":sdio_read_avail %s 0x%x\n", |
| ch->name, ch->read_avail); |
| |
| return ch->read_avail; |
| } |
| EXPORT_SYMBOL(sdio_read_avail); |
| |
| static int sdio_read_from_closed_ch(struct sdio_channel *ch, int len) |
| { |
| int ret = 0; |
| struct sdio_al_device *sdio_al_dev = NULL; |
| |
| if (!ch) { |
| sdio_al_loge(ch->sdio_al_dev->dev_log, |
| MODULE_NAME ":%s: NULL channel\n", __func__); |
| return -ENODEV; |
| } |
| |
| sdio_al_dev = ch->sdio_al_dev; |
| if (sdio_al_claim_mutex_and_verify_dev(sdio_al_dev, __func__)) |
| return -ENODEV; |
| |
| ret = sdio_memcpy_fromio(ch->func, sdio_al_dev->rx_flush_buf, |
| PIPE_RX_FIFO_ADDR, len); |
| |
| if (ret) { |
| sdio_al_loge(ch->sdio_al_dev->dev_log, |
| MODULE_NAME ":ch %s: %s err=%d, len=%d\n", |
| ch->name, __func__, -ret, len); |
| sdio_al_dev->is_err = true; |
| sdio_al_release_mutex(sdio_al_dev, __func__); |
| return ret; |
| } |
| |
| restart_inactive_time(sdio_al_dev); |
| |
| sdio_al_release_mutex(sdio_al_dev, __func__); |
| |
| return 0; |
| } |
| |
| /** |
| * Internal read from SDIO Channel. |
| * |
| * Reading from the pipe will trigger interrupt if there are |
| * other pending packets on the SDIO-Client. |
| * |
| */ |
| static int sdio_read_internal(struct sdio_channel *ch, void *data, int len) |
| { |
| int ret = 0; |
| struct sdio_al_device *sdio_al_dev = NULL; |
| |
| if (!ch) { |
| sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ":%s: NULL " |
| "channel\n", __func__); |
| return -ENODEV; |
| } |
| if (!data) { |
| sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ":%s: NULL data\n", |
| __func__); |
| return -ENODEV; |
| } |
| if (len == 0) { |
| sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ":channel %s trying" |
| " to read 0 bytes\n", ch->name); |
| return -EINVAL; |
| } |
| |
| if (ch->signature != SDIO_AL_SIGNATURE) { |
| sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ":%s: Invalid " |
| "signature 0x%x\n", __func__, ch->signature); |
| return -ENODEV; |
| } |
| |
| sdio_al_dev = ch->sdio_al_dev; |
| if (sdio_al_claim_mutex_and_verify_dev(sdio_al_dev, __func__)) |
| return -ENODEV; |
| |
| if (sdio_al_dev->is_err) { |
| SDIO_AL_ERR(__func__); |
| ret = -ENODEV; |
| goto exit; |
| } |
| |
| /* lpm policy says we can't go to sleep when we have pending rx data, |
| so either we had rx interrupt and woken up, or we never went to |
| sleep */ |
| if (sdio_al_dev->is_ok_to_sleep) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":%s: called " |
| "when is_ok_to_sleep is set for ch %s, len=%d," |
| " last_any_read_avail=%d, last_read_avail=%d, " |
| "last_old_read_avail=%d", __func__, ch->name, |
| len, ch->statistics.last_any_read_avail, |
| ch->statistics.last_read_avail, |
| ch->statistics.last_old_read_avail); |
| } |
| BUG_ON(sdio_al_dev->is_ok_to_sleep); |
| |
| if ((ch->state != SDIO_CHANNEL_STATE_OPEN) && |
| (ch->state != SDIO_CHANNEL_STATE_CLOSING)) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":%s wrong " |
| "channel %s state %d\n", |
| __func__, ch->name, ch->state); |
| ret = -EINVAL; |
| goto exit; |
| } |
| |
| DATA_DEBUG(sdio_al_dev->dev_log, MODULE_NAME ":start ch %s read %d " |
| "avail %d.\n", ch->name, len, ch->read_avail); |
| |
| restart_inactive_time(sdio_al_dev); |
| |
| if ((ch->is_packet_mode) && (len != ch->read_avail)) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":sdio_read ch " |
| "%s len != read_avail\n", ch->name); |
| ret = -EINVAL; |
| goto exit; |
| } |
| |
| if (len > ch->read_avail) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":ERR ch %s: " |
| "reading more bytes (%d) than the avail(%d).\n", |
| ch->name, len, ch->read_avail); |
| ret = -ENOMEM; |
| goto exit; |
| } |
| |
| ret = sdio_memcpy_fromio(ch->func, data, PIPE_RX_FIFO_ADDR, len); |
| |
| if (ret) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":ch %s: " |
| "sdio_read err=%d, len=%d, read_avail=%d, " |
| "last_read_avail=%d, last_old_read_avail=%d\n", |
| ch->name, -ret, len, ch->read_avail, |
| ch->statistics.last_read_avail, |
| ch->statistics.last_old_read_avail); |
| sdio_al_get_into_err_state(sdio_al_dev); |
| goto exit; |
| } |
| |
| ch->statistics.total_read_times++; |
| |
| /* Remove handled packet from the list regardless if ret is ok */ |
| if (ch->is_packet_mode) |
| remove_handled_rx_packet(ch); |
| else |
| ch->read_avail -= len; |
| |
| ch->total_rx_bytes += len; |
| DATA_DEBUG(sdio_al_dev->dev_log, MODULE_NAME ":end ch %s read %d " |
| "avail %d total %d.\n", ch->name, len, |
| ch->read_avail, ch->total_rx_bytes); |
| |
| if ((ch->read_avail == 0) && !(ch->is_packet_mode)) |
| ask_reading_mailbox(sdio_al_dev); |
| |
| exit: |
| sdio_al_release_mutex(sdio_al_dev, __func__); |
| |
| return ret; |
| } |
| |
| /** |
| * Read from SDIO Channel. |
| * |
| * Reading from the pipe will trigger interrupt if there are |
| * other pending packets on the SDIO-Client. |
| * |
| */ |
| int sdio_read(struct sdio_channel *ch, void *data, int len) |
| { |
| if (!ch) { |
| sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ":%s: NULL " |
| "channel\n", __func__); |
| return -ENODEV; |
| } |
| if (ch->signature != SDIO_AL_SIGNATURE) { |
| sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ":%s: " |
| "Invalid signature 0x%x\n", __func__, ch->signature); |
| return -ENODEV; |
| } |
| if (ch->state == SDIO_CHANNEL_STATE_OPEN) { |
| return sdio_read_internal(ch, data, len); |
| } else { |
| sdio_al_loge(ch->sdio_al_dev->dev_log, MODULE_NAME |
| ":%s: Invalid channel %s state %d\n", |
| __func__, ch->name, ch->state); |
| } |
| return -ENODEV; |
| } |
| EXPORT_SYMBOL(sdio_read); |
| |
| /** |
| * Write to SDIO Channel. |
| * |
| */ |
| int sdio_write(struct sdio_channel *ch, const void *data, int len) |
| { |
| int ret = 0; |
| struct sdio_al_device *sdio_al_dev = NULL; |
| |
| if (!ch) { |
| sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ":%s: NULL " |
| "channel\n", __func__); |
| return -ENODEV; |
| } |
| if (!data) { |
| sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ":%s: NULL data\n", |
| __func__); |
| return -ENODEV; |
| } |
| if (len == 0) { |
| sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ":channel %s trying" |
| " to write 0 bytes\n", ch->name); |
| return -EINVAL; |
| } |
| |
| if (ch->signature != SDIO_AL_SIGNATURE) { |
| sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ":%s: Invalid " |
| "signature 0x%x\n", __func__, ch->signature); |
| return -ENODEV; |
| } |
| |
| sdio_al_dev = ch->sdio_al_dev; |
| if (sdio_al_claim_mutex_and_verify_dev(sdio_al_dev, __func__)) |
| return -ENODEV; |
| |
| WARN_ON(len > ch->write_avail); |
| |
| if (sdio_al_dev->is_err) { |
| SDIO_AL_ERR(__func__); |
| ret = -ENODEV; |
| goto exit; |
| } |
| |
| if (ch->state != SDIO_CHANNEL_STATE_OPEN) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":writing to " |
| "closed channel %s\n", ch->name); |
| ret = -EINVAL; |
| goto exit; |
| } |
| |
| if (sdio_al_dev->is_ok_to_sleep) { |
| ret = sdio_al_wake_up(sdio_al_dev, 1, ch); |
| if (ret) |
| goto exit; |
| } else { |
| restart_inactive_time(sdio_al_dev); |
| } |
| |
| DATA_DEBUG(sdio_al_dev->dev_log, MODULE_NAME ":start ch %s write %d " |
| "avail %d.\n", ch->name, len, ch->write_avail); |
| |
| if (len > ch->write_avail) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":ERR ch %s: " |
| "write more bytes (%d) than available %d.\n", |
| ch->name, len, ch->write_avail); |
| ret = -ENOMEM; |
| goto exit; |
| } |
| |
| ret = sdio_ch_write(ch, data, len); |
| if (ret) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":sdio_write " |
| "on channel %s err=%d\n", ch->name, -ret); |
| goto exit; |
| } |
| |
| ch->total_tx_bytes += len; |
| DATA_DEBUG(sdio_al_dev->dev_log, MODULE_NAME ":end ch %s write %d " |
| "avail %d total %d.\n", ch->name, len, |
| ch->write_avail, ch->total_tx_bytes); |
| |
| /* Round up to whole buffer size */ |
| len = ROUND_UP(len, ch->peer_tx_buf_size); |
| /* Protect from wraparound */ |
| len = min(len, (int) ch->write_avail); |
| ch->write_avail -= len; |
| |
| if (ch->write_avail < ch->min_write_avail) |
| ask_reading_mailbox(sdio_al_dev); |
| |
| exit: |
| sdio_al_release_mutex(sdio_al_dev, __func__); |
| |
| return ret; |
| } |
| EXPORT_SYMBOL(sdio_write); |
| |
| static int __devinit msm_sdio_al_probe(struct platform_device *pdev) |
| { |
| if (!sdio_al) { |
| pr_err(MODULE_NAME ": %s: NULL sdio_al\n", __func__); |
| return -ENODEV; |
| } |
| |
| sdio_al->pdata = pdev->dev.platform_data; |
| return 0; |
| } |
| |
| static int __devexit msm_sdio_al_remove(struct platform_device *pdev) |
| { |
| return 0; |
| } |
| |
| static void sdio_al_close_all_channels(struct sdio_al_device *sdio_al_dev) |
| { |
| int j; |
| int ret; |
| struct sdio_channel *ch = NULL; |
| |
| sdio_al_logi(&sdio_al->gen_log, MODULE_NAME ": %s", __func__); |
| |
| if (!sdio_al_dev) { |
| sdio_al_loge(sdio_al_dev->dev_log, |
| MODULE_NAME ": %s: NULL device", __func__); |
| return; |
| } |
| for (j = 0; j < SDIO_AL_MAX_CHANNELS; j++) { |
| ch = &sdio_al_dev->channel[j]; |
| |
| if (ch->state == SDIO_CHANNEL_STATE_OPEN) { |
| sdio_al_loge(sdio_al_dev->dev_log, |
| MODULE_NAME ": %s: Call to sdio_close() for" |
| " ch %s\n", __func__, ch->name); |
| ret = channel_close(ch, false); |
| if (ret) { |
| sdio_al_loge(sdio_al_dev->dev_log, |
| MODULE_NAME ": %s: failed sdio_close()" |
| " for ch %s (%d)\n", |
| __func__, ch->name, ret); |
| } |
| } else { |
| pr_debug(MODULE_NAME ": %s: skip sdio_close() ch %s" |
| " (state=%d)\n", __func__, |
| ch->name, ch->state); |
| } |
| } |
| } |
| |
| static void sdio_al_invalidate_sdio_clients(struct sdio_al_device *sdio_al_dev, |
| struct platform_device **pdev_arr) |
| { |
| int j; |
| |
| pr_debug(MODULE_NAME ": %s: Notifying SDIO clients for card %d", |
| __func__, sdio_al_dev->host->index); |
| for (j = 0; j < SDIO_AL_MAX_CHANNELS; ++j) { |
| if (sdio_al_dev->channel[j].state == |
| SDIO_CHANNEL_STATE_INVALID) |
| continue; |
| pdev_arr[j] = sdio_al_dev->channel[j].pdev; |
| sdio_al_dev->channel[j].signature = 0x0; |
| sdio_al_dev->channel[j].state = |
| SDIO_CHANNEL_STATE_INVALID; |
| } |
| } |
| |
| static void sdio_al_modem_reset_operations(struct sdio_al_device |
| *sdio_al_dev) |
| { |
| int ret = 0; |
| struct platform_device *pdev_arr[SDIO_AL_MAX_CHANNELS]; |
| int j; |
| |
| sdio_al_logi(&sdio_al->gen_log, MODULE_NAME ": %s", __func__); |
| |
| if (sdio_al_claim_mutex_and_verify_dev(sdio_al_dev, __func__)) |
| return; |
| |
| if (sdio_al_dev->state == CARD_REMOVED) { |
| sdio_al_logi(&sdio_al->gen_log, MODULE_NAME ": %s: " |
| "card %d is already removed", __func__, |
| sdio_al_dev->host->index); |
| goto exit_err; |
| } |
| |
| if (sdio_al_dev->state == MODEM_RESTART) { |
| sdio_al_logi(sdio_al_dev->dev_log, MODULE_NAME ": %s: " |
| "card %d was already notified for modem reset", |
| __func__, sdio_al_dev->host->index); |
| goto exit_err; |
| } |
| |
| sdio_al_logi(sdio_al_dev->dev_log, MODULE_NAME ": %s: Set the " |
| "state to MODEM_RESTART for card %d", |
| __func__, sdio_al_dev->host->index); |
| sdio_al_dev->state = MODEM_RESTART; |
| sdio_al_dev->is_ready = false; |
| |
| /* Stop mailbox timer */ |
| stop_and_del_timer(sdio_al_dev); |
| |
| if ((sdio_al_dev->is_ok_to_sleep) && |
| (!sdio_al_dev->is_err)) { |
| pr_debug(MODULE_NAME ": %s: wakeup modem for " |
| "card %d", __func__, |
| sdio_al_dev->host->index); |
| ret = sdio_al_wake_up(sdio_al_dev, 1, NULL); |
| } |
| |
| if (!ret && (!sdio_al_dev->is_err) && sdio_al_dev->card && |
| sdio_al_dev->card->sdio_func[0]) { |
| sdio_al_logi(sdio_al_dev->dev_log, MODULE_NAME |
| ": %s: sdio_release_irq for card %d", |
| __func__, |
| sdio_al_dev->host->index); |
| sdio_release_irq(sdio_al_dev->card->sdio_func[0]); |
| } |
| |
| memset(pdev_arr, 0, sizeof(pdev_arr)); |
| sdio_al_invalidate_sdio_clients(sdio_al_dev, pdev_arr); |
| |
| sdio_al_release_mutex(sdio_al_dev, __func__); |
| |
| sdio_al_logi(&sdio_al->gen_log, MODULE_NAME ": %s: Notifying SDIO " |
| "clients for card %d", |
| __func__, sdio_al_dev->host->index); |
| for (j = 0; j < SDIO_AL_MAX_CHANNELS; j++) { |
| if (!pdev_arr[j]) |
| continue; |
| platform_device_unregister(pdev_arr[j]); |
| } |
| sdio_al_logi(&sdio_al->gen_log, MODULE_NAME ": %s: Finished Notifying " |
| "SDIO clients for card %d", |
| __func__, sdio_al_dev->host->index); |
| |
| return; |
| |
| exit_err: |
| sdio_al_release_mutex(sdio_al_dev, __func__); |
| return; |
| } |
| |
| #ifdef CONFIG_MSM_SUBSYSTEM_RESTART |
| static void sdio_al_reset(void) |
| { |
| int i; |
| struct sdio_al_device *sdio_al_dev; |
| |
| sdio_al_logi(&sdio_al->gen_log, MODULE_NAME ": %s", __func__); |
| |
| for (i = 0; i < MAX_NUM_OF_SDIO_DEVICES; i++) { |
| if (sdio_al->devices[i] == NULL) { |
| pr_debug(MODULE_NAME ": %s: NULL device in index %d", |
| __func__, i); |
| continue; |
| } |
| sdio_al_dev = sdio_al->devices[i]; |
| sdio_al_modem_reset_operations(sdio_al->devices[i]); |
| } |
| |
| sdio_al_logi(&sdio_al->gen_log, MODULE_NAME ": %s completed", __func__); |
| } |
| #endif |
| |
| static void msm_sdio_al_shutdown(struct platform_device *pdev) |
| { |
| int i; |
| struct sdio_al_device *sdio_al_dev; |
| |
| sdio_al_logi(&sdio_al->gen_log, MODULE_NAME |
| "Initiating msm_sdio_al_shutdown..."); |
| |
| for (i = 0; i < MAX_NUM_OF_SDIO_DEVICES; i++) { |
| if (sdio_al->devices[i] == NULL) { |
| pr_debug(MODULE_NAME ": %s: NULL device in index %d", |
| __func__, i); |
| continue; |
| } |
| sdio_al_dev = sdio_al->devices[i]; |
| |
| if (sdio_al_claim_mutex_and_verify_dev(sdio_al_dev, __func__)) |
| return; |
| |
| if (sdio_al_dev->ch_close_supported) |
| sdio_al_close_all_channels(sdio_al_dev); |
| |
| sdio_al_release_mutex(sdio_al_dev, __func__); |
| |
| sdio_al_modem_reset_operations(sdio_al_dev); |
| } |
| sdio_al_logi(&sdio_al->gen_log, MODULE_NAME ": %s: " |
| "msm_sdio_al_shutdown complete.", __func__); |
| } |
| |
| static struct platform_driver msm_sdio_al_driver = { |
| .probe = msm_sdio_al_probe, |
| .remove = __exit_p(msm_sdio_al_remove), |
| .shutdown = msm_sdio_al_shutdown, |
| .driver = { |
| .name = "msm_sdio_al", |
| }, |
| }; |
| |
| /** |
| * Initialize SDIO_AL channels. |
| * |
| */ |
| static int init_channels(struct sdio_al_device *sdio_al_dev) |
| { |
| int ret = 0; |
| int i; |
| |
| if (sdio_al_claim_mutex_and_verify_dev(sdio_al_dev, __func__)) |
| return -ENODEV; |
| |
| ret = read_sdioc_software_header(sdio_al_dev, |
| sdio_al_dev->sdioc_sw_header); |
| if (ret) |
| goto exit; |
| |
| ret = sdio_al_setup(sdio_al_dev); |
| if (ret) |
| goto exit; |
| |
| for (i = 0; i < SDIO_AL_MAX_CHANNELS; i++) { |
| int ch_name_size; |
| if (sdio_al_dev->channel[i].state == SDIO_CHANNEL_STATE_INVALID) |
| continue; |
| if (sdio_al->unittest_mode) { |
| memset(sdio_al_dev->channel[i].ch_test_name, 0, |
| sizeof(sdio_al_dev->channel[i].ch_test_name)); |
| ch_name_size = strnlen(sdio_al_dev->channel[i].name, |
| CHANNEL_NAME_SIZE); |
| strncpy(sdio_al_dev->channel[i].ch_test_name, |
| sdio_al_dev->channel[i].name, |
| ch_name_size); |
| strncat(sdio_al_dev->channel[i].ch_test_name + |
| ch_name_size, |
| SDIO_TEST_POSTFIX, |
| SDIO_TEST_POSTFIX_SIZE); |
| pr_debug(MODULE_NAME ":pdev.name = %s\n", |
| sdio_al_dev->channel[i].ch_test_name); |
| sdio_al_dev->channel[i].pdev = platform_device_alloc( |
| sdio_al_dev->channel[i].ch_test_name, -1); |
| } else { |
| pr_debug(MODULE_NAME ":pdev.name = %s\n", |
| sdio_al_dev->channel[i].name); |
| sdio_al_dev->channel[i].pdev = platform_device_alloc( |
| sdio_al_dev->channel[i].name, -1); |
| } |
| if (!sdio_al_dev->channel[i].pdev) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME |
| ":NULL platform device for ch %s", |
| sdio_al_dev->channel[i].name); |
| sdio_al_dev->channel[i].state = |
| SDIO_CHANNEL_STATE_INVALID; |
| continue; |
| } |
| ret = platform_device_add(sdio_al_dev->channel[i].pdev); |
| if (ret) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME |
| ":platform_device_add failed, " |
| "ret=%d\n", ret); |
| sdio_al_dev->channel[i].state = |
| SDIO_CHANNEL_STATE_INVALID; |
| } |
| } |
| |
| exit: |
| sdio_al_release_mutex(sdio_al_dev, __func__); |
| return ret; |
| } |
| |
| /** |
| * Initialize SDIO_AL channels according to the client setup. |
| * This function also check if the client is in boot mode and |
| * flashless boot is required to be activated or the client is |
| * up and running. |
| * |
| */ |
| static int sdio_al_client_setup(struct sdio_al_device *sdio_al_dev) |
| { |
| int ret = 0; |
| struct sdio_func *func1; |
| int signature = 0; |
| |
| if (sdio_al_claim_mutex_and_verify_dev(sdio_al_dev, __func__)) |
| return -ENODEV; |
| |
| if (!sdio_al_dev->card || !sdio_al_dev->card->sdio_func[0]) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":NULL card or " |
| "func1\n"); |
| sdio_al_release_mutex(sdio_al_dev, __func__); |
| return -ENODEV; |
| } |
| func1 = sdio_al_dev->card->sdio_func[0]; |
| |
| /* Read the header signature to determine the status of the MDM |
| * SDIO Client |
| */ |
| signature = sdio_readl(func1, SDIOC_SW_HEADER_ADDR, &ret); |
| sdio_al_release_mutex(sdio_al_dev, __func__); |
| if (ret) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":fail to read " |
| "signature from sw header.\n"); |
| return ret; |
| } |
| |
| switch (signature) { |
| case PEER_SDIOC_SW_MAILBOX_BOOT_SIGNATURE: |
| if (sdio_al_dev == sdio_al->bootloader_dev) { |
| sdio_al_logi(sdio_al_dev->dev_log, MODULE_NAME ":setup " |
| "bootloader on card %d\n", |
| sdio_al_dev->host->index); |
| return sdio_al_bootloader_setup(); |
| } else { |
| sdio_al_logi(sdio_al_dev->dev_log, MODULE_NAME ":wait " |
| "for bootloader completion " |
| "on card %d\n", |
| sdio_al_dev->host->index); |
| return sdio_al_wait_for_bootloader_comp(sdio_al_dev); |
| } |
| case PEER_SDIOC_SW_MAILBOX_SIGNATURE: |
| case PEER_SDIOC_SW_MAILBOX_UT_SIGNATURE: |
| return init_channels(sdio_al_dev); |
| default: |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":Invalid " |
| "signature 0x%x\n", signature); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static void clean_sdio_al_device_data(struct sdio_al_device *sdio_al_dev) |
| { |
| sdio_al_dev->is_ready = 0; |
| sdio_al_dev->bootloader_done = 0; |
| sdio_al_dev->lpm_chan = 0; |
| sdio_al_dev->is_ok_to_sleep = 0; |
| sdio_al_dev->inactivity_time = 0; |
| sdio_al_dev->poll_delay_msec = 0; |
| sdio_al_dev->is_timer_initialized = 0; |
| sdio_al_dev->is_err = 0; |
| sdio_al_dev->is_suspended = 0; |
| sdio_al_dev->flashless_boot_on = 0; |
| sdio_al_dev->ch_close_supported = 0; |
| sdio_al_dev->print_after_interrupt = 0; |
| memset(sdio_al_dev->sdioc_sw_header, 0, |
| sizeof(*sdio_al_dev->sdioc_sw_header)); |
| memset(sdio_al_dev->mailbox, 0, sizeof(*sdio_al_dev->mailbox)); |
| memset(sdio_al_dev->rx_flush_buf, 0, |
| sizeof(*sdio_al_dev->rx_flush_buf)); |
| } |
| |
| /* |
| * SDIO driver functions |
| */ |
| static int sdio_al_sdio_probe(struct sdio_func *func, |
| const struct sdio_device_id *sdio_dev_id) |
| { |
| int ret = 0; |
| struct sdio_al_device *sdio_al_dev = NULL; |
| int i; |
| struct mmc_card *card = NULL; |
| |
| if (!func) { |
| sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ": %s: NULL func\n", |
| __func__); |
| return -ENODEV; |
| } |
| card = func->card; |
| |
| if (!card) { |
| sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ": %s: NULL card\n", |
| __func__); |
| return -ENODEV; |
| } |
| |
| if (!card->sdio_func[0]) { |
| sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ": %s: NULL " |
| "func1\n", |
| __func__); |
| return -ENODEV; |
| } |
| |
| if (card->sdio_funcs < SDIO_AL_MAX_FUNCS) { |
| dev_info(&card->dev, |
| "SDIO-functions# %d less than expected.\n", |
| card->sdio_funcs); |
| return -ENODEV; |
| } |
| |
| /* Check if there is already a device for this card */ |
| for (i = 0; i < MAX_NUM_OF_SDIO_DEVICES; ++i) { |
| if (sdio_al->devices[i] == NULL) |
| continue; |
| if (sdio_al->devices[i]->host == card->host) { |
| sdio_al_dev = sdio_al->devices[i]; |
| if (sdio_al_dev->state == CARD_INSERTED) |
| return 0; |
| clean_sdio_al_device_data(sdio_al_dev); |
| break; |
| } |
| } |
| |
| if (!sdio_al_dev) { |
| sdio_al_dev = kzalloc(sizeof(struct sdio_al_device), |
| GFP_KERNEL); |
| if (sdio_al_dev == NULL) |
| return -ENOMEM; |
| |
| for (i = 0; i < MAX_NUM_OF_SDIO_DEVICES ; ++i) |
| if (sdio_al->devices[i] == NULL) { |
| sdio_al->devices[i] = sdio_al_dev; |
| sdio_al_dev->dev_log = &sdio_al->device_log[i]; |
| spin_lock_init(&sdio_al_dev->dev_log->log_lock); |
| #ifdef CONFIG_DEBUG_FS |
| sdio_al_dbgfs_log[i].data = |
| sdio_al_dev->dev_log->buffer; |
| sdio_al_dbgfs_log[i].size = |
| SDIO_AL_DEBUG_LOG_SIZE; |
| #endif |
| break; |
| } |
| if (i == MAX_NUM_OF_SDIO_DEVICES) { |
| sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ":No space " |
| "in devices array for the device\n"); |
| return -ENOMEM; |
| } |
| } |
| |
| dev_info(&card->dev, "SDIO Card claimed.\n"); |
| sdio_al->skip_print_info = 0; |
| |
| sdio_al_dev->state = CARD_INSERTED; |
| |
| if (card->host->index == SDIO_BOOTLOADER_CARD_INDEX) |
| sdio_al->bootloader_dev = sdio_al_dev; |
| |
| sdio_al_dev->is_ready = false; |
| |
| sdio_al_dev->signature = SDIO_AL_SIGNATURE; |
| |
| sdio_al_dev->is_suspended = 0; |
| sdio_al_dev->is_timer_initialized = false; |
| |
| sdio_al_dev->lpm_chan = INVALID_SDIO_CHAN; |
| |
| sdio_al_dev->card = card; |
| sdio_al_dev->host = card->host; |
| |
| if (!sdio_al_dev->mailbox) { |
| sdio_al_dev->mailbox = kzalloc(sizeof(struct sdio_mailbox), |
| GFP_KERNEL); |
| if (sdio_al_dev->mailbox == NULL) |
| return -ENOMEM; |
| } |
| |
| if (!sdio_al_dev->sdioc_sw_header) { |
| sdio_al_dev->sdioc_sw_header |
| = kzalloc(sizeof(*sdio_al_dev->sdioc_sw_header), |
| GFP_KERNEL); |
| if (sdio_al_dev->sdioc_sw_header == NULL) |
| return -ENOMEM; |
| } |
| |
| if (!sdio_al_dev->rx_flush_buf) { |
| sdio_al_dev->rx_flush_buf = kzalloc(RX_FLUSH_BUFFER_SIZE, |
| GFP_KERNEL); |
| if (sdio_al_dev->rx_flush_buf == NULL) { |
| sdio_al_loge(&sdio_al->gen_log, |
| MODULE_NAME ":Fail to allocate " |
| "rx_flush_buf for card %d\n", |
| card->host->index); |
| return -ENOMEM; |
| } |
| } |
| |
| sdio_al_dev->timer.data = (unsigned long)sdio_al_dev; |
| |
| wake_lock_init(&sdio_al_dev->wake_lock, WAKE_LOCK_SUSPEND, MODULE_NAME); |
| /* Don't allow sleep until all required clients register */ |
| sdio_al_vote_for_sleep(sdio_al_dev, 0); |
| |
| if (sdio_al_claim_mutex_and_verify_dev(sdio_al_dev, __func__)) |
| return -ENODEV; |
| |
| /* Init Func#1 */ |
| ret = sdio_al_enable_func_retry(card->sdio_func[0], "Init Func#1"); |
| if (ret) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":Fail to " |
| "enable Func#%d\n", card->sdio_func[0]->num); |
| goto exit; |
| } |
| |
| /* Patch Func CIS tuple issue */ |
| ret = sdio_set_block_size(card->sdio_func[0], SDIO_AL_BLOCK_SIZE); |
| if (ret) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ":Fail to set " |
| "block size, Func#%d\n", card->sdio_func[0]->num); |
| goto exit; |
| } |
| sdio_al_dev->card->sdio_func[0]->max_blksize = SDIO_AL_BLOCK_SIZE; |
| |
| sdio_al_dev->workqueue = create_singlethread_workqueue("sdio_al_wq"); |
| sdio_al_dev->sdio_al_work.sdio_al_dev = sdio_al_dev; |
| init_waitqueue_head(&sdio_al_dev->wait_mbox); |
| |
| ret = sdio_al_client_setup(sdio_al_dev); |
| |
| exit: |
| sdio_al_release_mutex(sdio_al_dev, __func__); |
| return ret; |
| } |
| |
| static void sdio_al_sdio_remove(struct sdio_func *func) |
| { |
| struct sdio_al_device *sdio_al_dev = NULL; |
| int i; |
| struct mmc_card *card = NULL; |
| struct platform_device *pdev_arr[SDIO_AL_MAX_CHANNELS]; |
| |
| if (!func) { |
| sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ": %s: NULL func\n", |
| __func__); |
| return; |
| } |
| card = func->card; |
| |
| if (!card) { |
| sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ": %s: NULL card\n", |
| __func__); |
| return; |
| } |
| |
| /* Find the sdio_al_device of this card */ |
| for (i = 0; i < MAX_NUM_OF_SDIO_DEVICES; ++i) { |
| if (sdio_al->devices[i] == NULL) |
| continue; |
| if (sdio_al->devices[i]->card == card) { |
| sdio_al_dev = sdio_al->devices[i]; |
| break; |
| } |
| } |
| if (sdio_al_dev == NULL) { |
| pr_debug(MODULE_NAME ":%s :NULL sdio_al_dev for card %d\n", |
| __func__, card->host->index); |
| return; |
| } |
| |
| if (sdio_al_claim_mutex(sdio_al_dev, __func__)) |
| return; |
| |
| if (sdio_al_dev->state == CARD_REMOVED) { |
| sdio_al_release_mutex(sdio_al_dev, __func__); |
| return; |
| } |
| |
| if (!card->sdio_func[0]) { |
| sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ": %s: NULL " |
| "func1\n", __func__); |
| sdio_al_release_mutex(sdio_al_dev, __func__); |
| return; |
| } |
| |
| sdio_al_logi(&sdio_al->gen_log, MODULE_NAME ":%s for card %d\n", |
| __func__, card->host->index); |
| |
| sdio_al_dev->state = CARD_REMOVED; |
| |
| memset(pdev_arr, 0, sizeof(pdev_arr)); |
| sdio_al_invalidate_sdio_clients(sdio_al_dev, pdev_arr); |
| |
| sdio_al_logi(&sdio_al->gen_log, MODULE_NAME ":%s: ask_reading_mailbox " |
| "for card %d\n", __func__, card->host->index); |
| sdio_al_dev->is_ready = false; /* Flag worker to exit */ |
| sdio_al_dev->ask_mbox = false; |
| ask_reading_mailbox(sdio_al_dev); /* Wakeup worker */ |
| |
| stop_and_del_timer(sdio_al_dev); |
| |
| sdio_al_release_mutex(sdio_al_dev, __func__); |
| |
| sdio_al_logi(&sdio_al->gen_log, MODULE_NAME ": %s: Notifying SDIO " |
| "clients for card %d", |
| __func__, sdio_al_dev->host->index); |
| for (i = 0; i < SDIO_AL_MAX_CHANNELS; i++) { |
| if (!pdev_arr[i]) |
| continue; |
| platform_device_unregister(pdev_arr[i]); |
| } |
| sdio_al_logi(&sdio_al->gen_log, MODULE_NAME ": %s: Finished Notifying " |
| "SDIO clients for card %d", |
| __func__, sdio_al_dev->host->index); |
| |
| sdio_al_logi(&sdio_al->gen_log, MODULE_NAME ":%s: vote for sleep for " |
| "card %d\n", __func__, card->host->index); |
| sdio_al_vote_for_sleep(sdio_al_dev, 1); |
| |
| sdio_al_logi(&sdio_al->gen_log, MODULE_NAME ":%s: flush_workqueue for " |
| "card %d\n", __func__, card->host->index); |
| flush_workqueue(sdio_al_dev->workqueue); |
| destroy_workqueue(sdio_al_dev->workqueue); |
| wake_lock_destroy(&sdio_al_dev->wake_lock); |
| |
| sdio_al_logi(&sdio_al->gen_log, MODULE_NAME ":%s: sdio card %d removed." |
| "\n", __func__, card->host->index); |
| } |
| |
| static void sdio_print_mailbox(char *prefix_str, struct sdio_mailbox *mailbox) |
| { |
| int k = 0; |
| char buf[256]; |
| char buf1[10]; |
| |
| if (!mailbox) { |
| sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ": mailbox is " |
| "NULL\n"); |
| return; |
| } |
| |
| sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ": %s: pipes 0_7: eot=0x%x," |
| " thresh=0x%x, overflow=0x%x, " |
| "underflow=0x%x, mask_thresh=0x%x\n", |
| prefix_str, mailbox->eot_pipe_0_7, |
| mailbox->thresh_above_limit_pipe_0_7, |
| mailbox->overflow_pipe_0_7, |
| mailbox->underflow_pipe_0_7, |
| mailbox->mask_thresh_above_limit_pipe_0_7); |
| |
| memset(buf, 0, sizeof(buf)); |
| strncat(buf, ": bytes_avail:", sizeof(buf)); |
| |
| for (k = 0 ; k < SDIO_AL_ACTIVE_PIPES ; ++k) { |
| snprintf(buf1, sizeof(buf1), "%d, ", |
| mailbox->pipe_bytes_avail[k]); |
| strncat(buf, buf1, sizeof(buf)); |
| } |
| |
| sdio_al_loge(&sdio_al->gen_log, MODULE_NAME "%s", buf); |
| } |
| |
| static void sdio_al_print_info(void) |
| { |
| int i = 0; |
| int j = 0; |
| int ret = 0; |
| struct sdio_mailbox *mailbox = NULL; |
| struct sdio_mailbox *hw_mailbox = NULL; |
| struct peer_sdioc_channel_config *ch_config = NULL; |
| struct sdio_func *func1 = NULL; |
| struct sdio_func *lpm_func = NULL; |
| int offset = 0; |
| int is_ok_to_sleep = 0; |
| char buf[50]; |
| |
| if (sdio_al->skip_print_info == 1) |
| return; |
| |
| sdio_al->skip_print_info = 1; |
| |
| sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ": %s - SDIO DEBUG INFO\n", |
| __func__); |
| |
| if (!sdio_al) { |
| sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ": %s - ERROR - " |
| "sdio_al is NULL\n", __func__); |
| return; |
| } |
| |
| sdio_al_loge(&sdio_al->gen_log, MODULE_NAME ": GPIO mdm2ap_status=%d\n", |
| sdio_al->pdata->get_mdm2ap_status()); |
| |
| for (j = 0 ; j < MAX_NUM_OF_SDIO_DEVICES ; ++j) { |
| struct sdio_al_device *sdio_al_dev = sdio_al->devices[j]; |
| |
| if (sdio_al_dev == NULL) { |
| continue; |
| } |
| |
| if (!sdio_al_dev->host) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ": Host" |
| " is NULL\n);"); |
| continue; |
| } |
| |
| snprintf(buf, sizeof(buf), "Card#%d: Shadow HW MB", |
| sdio_al_dev->host->index); |
| |
| /* printing Shadowing HW Mailbox*/ |
| mailbox = sdio_al_dev->mailbox; |
| sdio_print_mailbox(buf, mailbox); |
| |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ": Card#%d: " |
| "is_ok_to_sleep=%d\n", |
| sdio_al_dev->host->index, |
| sdio_al_dev->is_ok_to_sleep); |
| |
| |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME ": Card#%d: " |
| "Shadow channels SW MB:", |
| sdio_al_dev->host->index); |
| |
| /* printing Shadowing SW Mailbox per channel*/ |
| for (i = 0 ; i < SDIO_AL_MAX_CHANNELS ; ++i) { |
| struct sdio_channel *ch = &sdio_al_dev->channel[i]; |
| |
| if (ch == NULL) { |
| continue; |
| } |
| |
| if (ch->state == SDIO_CHANNEL_STATE_INVALID) |
| continue; |
| |
| ch_config = &sdio_al_dev->channel[i].ch_config; |
| |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME |
| ": Ch %s: max_rx_thres=0x%x, " |
| "max_tx_thres=0x%x, tx_buf=0x%x, " |
| "is_packet_mode=%d, " |
| "max_packet=0x%x, min_write=0x%x", |
| ch->name, ch_config->max_rx_threshold, |
| ch_config->max_tx_threshold, |
| ch_config->tx_buf_size, |
| ch_config->is_packet_mode, |
| ch_config->max_packet_size, |
| ch->min_write_avail); |
| |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME |
| ": total_rx=0x%x, total_tx=0x%x, " |
| "read_avail=0x%x, write_avail=0x%x, " |
| "rx_pending=0x%x, num_reads=0x%x, " |
| "num_notifs=0x%x", ch->total_rx_bytes, |
| ch->total_tx_bytes, ch->read_avail, |
| ch->write_avail, ch->rx_pending_bytes, |
| ch->statistics.total_read_times, |
| ch->statistics.total_notifs); |
| } /* end loop over all channels */ |
| |
| } /* end loop over all devices */ |
| |
| /* reading from client and printing is_host_ok_to_sleep per device */ |
| for (j = 0 ; j < MAX_NUM_OF_SDIO_DEVICES ; ++j) { |
| struct sdio_al_device *sdio_al_dev = sdio_al->devices[j]; |
| |
| if (sdio_al_verify_func1(sdio_al_dev, __func__)) |
| continue; |
| |
| if (!sdio_al_dev->host) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME |
| ": Host is NULL"); |
| continue; |
| } |
| |
| if (sdio_al_dev->lpm_chan == INVALID_SDIO_CHAN) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME |
| ": %s - for Card#%d, is lpm_chan==" |
| "INVALID_SDIO_CHAN. continuing...", |
| __func__, sdio_al_dev->host->index); |
| continue; |
| } |
| |
| offset = offsetof(struct peer_sdioc_sw_mailbox, ch_config)+ |
| sizeof(struct peer_sdioc_channel_config) * |
| sdio_al_dev->lpm_chan+ |
| offsetof(struct peer_sdioc_channel_config, is_host_ok_to_sleep); |
| |
| lpm_func = sdio_al_dev->card->sdio_func[sdio_al_dev-> |
| lpm_chan+1]; |
| if (!lpm_func) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME |
| ": %s - lpm_func is NULL for card#%d" |
| " continuing...\n", __func__, |
| sdio_al_dev->host->index); |
| continue; |
| } |
| |
| if (sdio_al_claim_mutex_and_verify_dev(sdio_al_dev, __func__)) |
| return; |
| ret = sdio_memcpy_fromio(lpm_func, |
| &is_ok_to_sleep, |
| SDIOC_SW_MAILBOX_ADDR+offset, |
| sizeof(int)); |
| sdio_al_release_mutex(sdio_al_dev, __func__); |
| |
| if (ret) |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME |
| ": %s - fail to read " |
| "is_HOST_ok_to_sleep from mailbox for card %d", |
| __func__, sdio_al_dev->host->index); |
| else |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME |
| ": Card#%d: " |
| "is_HOST_ok_to_sleep=%d\n", |
| sdio_al_dev->host->index, |
| is_ok_to_sleep); |
| } |
| |
| for (j = 0 ; j < MAX_NUM_OF_SDIO_DEVICES ; ++j) { |
| struct sdio_al_device *sdio_al_dev = sdio_al->devices[j]; |
| |
| if (!sdio_al_dev) |
| continue; |
| |
| /* Reading HW Mailbox */ |
| hw_mailbox = sdio_al_dev->mailbox; |
| |
| if (sdio_al_claim_mutex_and_verify_dev(sdio_al_dev, __func__)) |
| return; |
| |
| if (!sdio_al_dev->card || !sdio_al_dev->card->sdio_func[0]) { |
| sdio_al_release_mutex(sdio_al_dev, __func__); |
| return; |
| } |
| func1 = sdio_al_dev->card->sdio_func[0]; |
| ret = sdio_memcpy_fromio(func1, hw_mailbox, |
| HW_MAILBOX_ADDR, sizeof(*hw_mailbox)); |
| sdio_al_release_mutex(sdio_al_dev, __func__); |
| |
| if (ret) { |
| sdio_al_loge(sdio_al_dev->dev_log, MODULE_NAME |
| ": fail to read " |
| "mailbox for card#%d. " |
| "continuing...\n", |
| sdio_al_dev->host->index); |
| continue; |
| } |
| |
| snprintf(buf, sizeof(buf), "Card#%d: Current HW MB", |
| sdio_al_dev->host->index); |
| |
| /* Printing HW Mailbox */ |
| sdio_print_mailbox(buf, hw_mailbox); |
| } |
| } |
| |
| static struct sdio_device_id sdio_al_sdioid[] = { |
| {.class = 0, .vendor = 0x70, .device = 0x2460}, |
| {.class = 0, .vendor = 0x70, .device = 0x0460}, |
| {.class = 0, .vendor = 0x70, .device = 0x23F1}, |
| {.class = 0, .vendor = 0x70, .device = 0x23F0}, |
| {} |
| }; |
| |
| static struct sdio_driver sdio_al_sdiofn_driver = { |
| .name = "sdio_al_sdiofn", |
| .id_table = sdio_al_sdioid, |
| .probe = sdio_al_sdio_probe, |
| .remove = sdio_al_sdio_remove, |
| }; |
| |
| #ifdef CONFIG_MSM_SUBSYSTEM_RESTART |
| /* |
| * Callback for notifications from restart mudule. |
| * This function handles only the BEFORE_RESTART notification. |
| * Stop all the activity on the card and notify our clients. |
| */ |
| static int sdio_al_subsys_notifier_cb(struct notifier_block *this, |
| unsigned long notif_type, |
| void *data) |
| { |
| if (notif_type != SUBSYS_BEFORE_SHUTDOWN) { |
| sdio_al_logi(&sdio_al->gen_log, MODULE_NAME ": %s: got " |
| "notification %ld", __func__, notif_type); |
| return NOTIFY_DONE; |
| } |
| |
| sdio_al_reset(); |
| return NOTIFY_OK; |
| } |
| |
| static struct notifier_block sdio_al_nb = { |
| .notifier_call = sdio_al_subsys_notifier_cb, |
| }; |
| #endif |
| |
| /** |
| * Module Init. |
| * |
| * @warn: allocate sdio_al context before registering driver. |
| * |
| */ |
| static int __init sdio_al_init(void) |
| { |
| int ret = 0; |
| int i; |
| |
| pr_debug(MODULE_NAME ":sdio_al_init\n"); |
| |
| pr_info(MODULE_NAME ":SDIO-AL SW version %s\n", |
| DRV_VERSION); |
| |
| sdio_al = kzalloc(sizeof(struct sdio_al), GFP_KERNEL); |
| if (sdio_al == NULL) |
| return -ENOMEM; |
| |
| for (i = 0; i < MAX_NUM_OF_SDIO_DEVICES ; ++i) |
| sdio_al->devices[i] = NULL; |
| |
| sdio_al->unittest_mode = false; |
| |
| sdio_al->debug.debug_lpm_on = debug_lpm_on; |
| sdio_al->debug.debug_data_on = debug_data_on; |
| sdio_al->debug.debug_close_on = debug_close_on; |
| |
| #ifdef CONFIG_DEBUG_FS |
| sdio_al_debugfs_init(); |
| #endif |
| |
| |
| #ifdef CONFIG_MSM_SUBSYSTEM_RESTART |
| sdio_al->subsys_notif_handle = subsys_notif_register_notifier( |
| "external_modem", &sdio_al_nb); |
| #endif |
| |
| ret = platform_driver_register(&msm_sdio_al_driver); |
| if (ret) { |
| pr_err(MODULE_NAME ": platform_driver_register failed: %d\n", |
| ret); |
| goto exit; |
| } |
| |
| sdio_register_driver(&sdio_al_sdiofn_driver); |
| |
| spin_lock_init(&sdio_al->gen_log.log_lock); |
| |
| exit: |
| if (ret) |
| kfree(sdio_al); |
| return ret; |
| } |
| |
| /** |
| * Module Exit. |
| * |
| * Free allocated memory. |
| * Disable SDIO-Card. |
| * Unregister driver. |
| * |
| */ |
| static void __exit sdio_al_exit(void) |
| { |
| if (sdio_al == NULL) |
| return; |
| |
| pr_debug(MODULE_NAME ":sdio_al_exit\n"); |
| |
| #ifdef CONFIG_MSM_SUBSYSTEM_RESTART |
| subsys_notif_unregister_notifier( |
| sdio_al->subsys_notif_handle, &sdio_al_nb); |
| #endif |
| |
| sdio_al_tear_down(); |
| |
| sdio_unregister_driver(&sdio_al_sdiofn_driver); |
| |
| kfree(sdio_al); |
| |
| #ifdef CONFIG_DEBUG_FS |
| sdio_al_debugfs_cleanup(); |
| #endif |
| |
| platform_driver_unregister(&msm_sdio_al_driver); |
| |
| pr_debug(MODULE_NAME ":sdio_al_exit complete\n"); |
| } |
| |
| module_init(sdio_al_init); |
| module_exit(sdio_al_exit); |
| |
| MODULE_LICENSE("GPL v2"); |
| MODULE_DESCRIPTION("SDIO Abstraction Layer"); |
| MODULE_AUTHOR("Amir Samuelov <amirs@codeaurora.org>"); |
| MODULE_VERSION(DRV_VERSION); |
| |