18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * linux/drivers/net/wireless/libertas/if_sdio.c 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright 2007-2008 Pierre Ossman 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * Inspired by if_cs.c, Copyright 2007 Holger Schurig 88c2ecf20Sopenharmony_ci * 98c2ecf20Sopenharmony_ci * This hardware has more or less no CMD53 support, so all registers 108c2ecf20Sopenharmony_ci * must be accessed using sdio_readb()/sdio_writeb(). 118c2ecf20Sopenharmony_ci * 128c2ecf20Sopenharmony_ci * Transfers must be in one transaction or the firmware goes bonkers. 138c2ecf20Sopenharmony_ci * This means that the transfer must either be small enough to do a 148c2ecf20Sopenharmony_ci * byte based transfer or it must be padded to a multiple of the 158c2ecf20Sopenharmony_ci * current block size. 168c2ecf20Sopenharmony_ci * 178c2ecf20Sopenharmony_ci * As SDIO is still new to the kernel, it is unfortunately common with 188c2ecf20Sopenharmony_ci * bugs in the host controllers related to that. One such bug is that 198c2ecf20Sopenharmony_ci * controllers cannot do transfers that aren't a multiple of 4 bytes. 208c2ecf20Sopenharmony_ci * If you don't have time to fix the host controller driver, you can 218c2ecf20Sopenharmony_ci * work around the problem by modifying if_sdio_host_to_card() and 228c2ecf20Sopenharmony_ci * if_sdio_card_to_host() to pad the data. 238c2ecf20Sopenharmony_ci */ 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_ci#include <linux/kernel.h> 288c2ecf20Sopenharmony_ci#include <linux/module.h> 298c2ecf20Sopenharmony_ci#include <linux/slab.h> 308c2ecf20Sopenharmony_ci#include <linux/firmware.h> 318c2ecf20Sopenharmony_ci#include <linux/netdevice.h> 328c2ecf20Sopenharmony_ci#include <linux/delay.h> 338c2ecf20Sopenharmony_ci#include <linux/mmc/card.h> 348c2ecf20Sopenharmony_ci#include <linux/mmc/sdio_func.h> 358c2ecf20Sopenharmony_ci#include <linux/mmc/sdio_ids.h> 368c2ecf20Sopenharmony_ci#include <linux/mmc/sdio.h> 378c2ecf20Sopenharmony_ci#include <linux/mmc/host.h> 388c2ecf20Sopenharmony_ci#include <linux/pm_runtime.h> 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_ci#include "host.h" 418c2ecf20Sopenharmony_ci#include "decl.h" 428c2ecf20Sopenharmony_ci#include "defs.h" 438c2ecf20Sopenharmony_ci#include "dev.h" 448c2ecf20Sopenharmony_ci#include "cmd.h" 458c2ecf20Sopenharmony_ci#include "if_sdio.h" 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_cistatic void if_sdio_interrupt(struct sdio_func *func); 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci/* The if_sdio_remove() callback function is called when 508c2ecf20Sopenharmony_ci * user removes this module from kernel space or ejects 518c2ecf20Sopenharmony_ci * the card from the slot. The driver handles these 2 cases 528c2ecf20Sopenharmony_ci * differently for SD8688 combo chip. 538c2ecf20Sopenharmony_ci * If the user is removing the module, the FUNC_SHUTDOWN 548c2ecf20Sopenharmony_ci * command for SD8688 is sent to the firmware. 558c2ecf20Sopenharmony_ci * If the card is removed, there is no need to send this command. 568c2ecf20Sopenharmony_ci * 578c2ecf20Sopenharmony_ci * The variable 'user_rmmod' is used to distinguish these two 588c2ecf20Sopenharmony_ci * scenarios. This flag is initialized as FALSE in case the card 598c2ecf20Sopenharmony_ci * is removed, and will be set to TRUE for module removal when 608c2ecf20Sopenharmony_ci * module_exit function is called. 618c2ecf20Sopenharmony_ci */ 628c2ecf20Sopenharmony_cistatic u8 user_rmmod; 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_cistatic const struct sdio_device_id if_sdio_ids[] = { 658c2ecf20Sopenharmony_ci { SDIO_DEVICE(SDIO_VENDOR_ID_MARVELL, 668c2ecf20Sopenharmony_ci SDIO_DEVICE_ID_MARVELL_LIBERTAS) }, 678c2ecf20Sopenharmony_ci { SDIO_DEVICE(SDIO_VENDOR_ID_MARVELL, 688c2ecf20Sopenharmony_ci SDIO_DEVICE_ID_MARVELL_8688_WLAN) }, 698c2ecf20Sopenharmony_ci { /* end: all zeroes */ }, 708c2ecf20Sopenharmony_ci}; 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(sdio, if_sdio_ids); 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci#define MODEL_8385 0x04 758c2ecf20Sopenharmony_ci#define MODEL_8686 0x0b 768c2ecf20Sopenharmony_ci#define MODEL_8688 0x10 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_cistatic const struct lbs_fw_table fw_table[] = { 798c2ecf20Sopenharmony_ci { MODEL_8385, "libertas/sd8385_helper.bin", "libertas/sd8385.bin" }, 808c2ecf20Sopenharmony_ci { MODEL_8385, "sd8385_helper.bin", "sd8385.bin" }, 818c2ecf20Sopenharmony_ci { MODEL_8686, "libertas/sd8686_v9_helper.bin", "libertas/sd8686_v9.bin" }, 828c2ecf20Sopenharmony_ci { MODEL_8686, "libertas/sd8686_v8_helper.bin", "libertas/sd8686_v8.bin" }, 838c2ecf20Sopenharmony_ci { MODEL_8686, "sd8686_helper.bin", "sd8686.bin" }, 848c2ecf20Sopenharmony_ci { MODEL_8688, "libertas/sd8688_helper.bin", "libertas/sd8688.bin" }, 858c2ecf20Sopenharmony_ci { MODEL_8688, "sd8688_helper.bin", "sd8688.bin" }, 868c2ecf20Sopenharmony_ci { 0, NULL, NULL } 878c2ecf20Sopenharmony_ci}; 888c2ecf20Sopenharmony_ciMODULE_FIRMWARE("libertas/sd8385_helper.bin"); 898c2ecf20Sopenharmony_ciMODULE_FIRMWARE("libertas/sd8385.bin"); 908c2ecf20Sopenharmony_ciMODULE_FIRMWARE("sd8385_helper.bin"); 918c2ecf20Sopenharmony_ciMODULE_FIRMWARE("sd8385.bin"); 928c2ecf20Sopenharmony_ciMODULE_FIRMWARE("libertas/sd8686_v9_helper.bin"); 938c2ecf20Sopenharmony_ciMODULE_FIRMWARE("libertas/sd8686_v9.bin"); 948c2ecf20Sopenharmony_ciMODULE_FIRMWARE("libertas/sd8686_v8_helper.bin"); 958c2ecf20Sopenharmony_ciMODULE_FIRMWARE("libertas/sd8686_v8.bin"); 968c2ecf20Sopenharmony_ciMODULE_FIRMWARE("sd8686_helper.bin"); 978c2ecf20Sopenharmony_ciMODULE_FIRMWARE("sd8686.bin"); 988c2ecf20Sopenharmony_ciMODULE_FIRMWARE("libertas/sd8688_helper.bin"); 998c2ecf20Sopenharmony_ciMODULE_FIRMWARE("libertas/sd8688.bin"); 1008c2ecf20Sopenharmony_ciMODULE_FIRMWARE("sd8688_helper.bin"); 1018c2ecf20Sopenharmony_ciMODULE_FIRMWARE("sd8688.bin"); 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_cistruct if_sdio_packet { 1048c2ecf20Sopenharmony_ci struct if_sdio_packet *next; 1058c2ecf20Sopenharmony_ci u16 nb; 1068c2ecf20Sopenharmony_ci u8 buffer[] __aligned(4); 1078c2ecf20Sopenharmony_ci}; 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_cistruct if_sdio_card { 1108c2ecf20Sopenharmony_ci struct sdio_func *func; 1118c2ecf20Sopenharmony_ci struct lbs_private *priv; 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_ci int model; 1148c2ecf20Sopenharmony_ci unsigned long ioport; 1158c2ecf20Sopenharmony_ci unsigned int scratch_reg; 1168c2ecf20Sopenharmony_ci bool started; 1178c2ecf20Sopenharmony_ci wait_queue_head_t pwron_waitq; 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_ci u8 buffer[65536] __attribute__((aligned(4))); 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ci spinlock_t lock; 1228c2ecf20Sopenharmony_ci struct if_sdio_packet *packets; 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_ci struct workqueue_struct *workqueue; 1258c2ecf20Sopenharmony_ci struct work_struct packet_worker; 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_ci u8 rx_unit; 1288c2ecf20Sopenharmony_ci}; 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_cistatic void if_sdio_finish_power_on(struct if_sdio_card *card); 1318c2ecf20Sopenharmony_cistatic int if_sdio_power_off(struct if_sdio_card *card); 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci/********************************************************************/ 1348c2ecf20Sopenharmony_ci/* I/O */ 1358c2ecf20Sopenharmony_ci/********************************************************************/ 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_ci/* 1388c2ecf20Sopenharmony_ci * For SD8385/SD8686, this function reads firmware status after 1398c2ecf20Sopenharmony_ci * the image is downloaded, or reads RX packet length when 1408c2ecf20Sopenharmony_ci * interrupt (with IF_SDIO_H_INT_UPLD bit set) is received. 1418c2ecf20Sopenharmony_ci * For SD8688, this function reads firmware status only. 1428c2ecf20Sopenharmony_ci */ 1438c2ecf20Sopenharmony_cistatic u16 if_sdio_read_scratch(struct if_sdio_card *card, int *err) 1448c2ecf20Sopenharmony_ci{ 1458c2ecf20Sopenharmony_ci int ret; 1468c2ecf20Sopenharmony_ci u16 scratch; 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci scratch = sdio_readb(card->func, card->scratch_reg, &ret); 1498c2ecf20Sopenharmony_ci if (!ret) 1508c2ecf20Sopenharmony_ci scratch |= sdio_readb(card->func, card->scratch_reg + 1, 1518c2ecf20Sopenharmony_ci &ret) << 8; 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci if (err) 1548c2ecf20Sopenharmony_ci *err = ret; 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_ci if (ret) 1578c2ecf20Sopenharmony_ci return 0xffff; 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci return scratch; 1608c2ecf20Sopenharmony_ci} 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_cistatic u8 if_sdio_read_rx_unit(struct if_sdio_card *card) 1638c2ecf20Sopenharmony_ci{ 1648c2ecf20Sopenharmony_ci int ret; 1658c2ecf20Sopenharmony_ci u8 rx_unit; 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_ci rx_unit = sdio_readb(card->func, IF_SDIO_RX_UNIT, &ret); 1688c2ecf20Sopenharmony_ci 1698c2ecf20Sopenharmony_ci if (ret) 1708c2ecf20Sopenharmony_ci rx_unit = 0; 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_ci return rx_unit; 1738c2ecf20Sopenharmony_ci} 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_cistatic u16 if_sdio_read_rx_len(struct if_sdio_card *card, int *err) 1768c2ecf20Sopenharmony_ci{ 1778c2ecf20Sopenharmony_ci int ret; 1788c2ecf20Sopenharmony_ci u16 rx_len; 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_ci switch (card->model) { 1818c2ecf20Sopenharmony_ci case MODEL_8385: 1828c2ecf20Sopenharmony_ci case MODEL_8686: 1838c2ecf20Sopenharmony_ci rx_len = if_sdio_read_scratch(card, &ret); 1848c2ecf20Sopenharmony_ci break; 1858c2ecf20Sopenharmony_ci case MODEL_8688: 1868c2ecf20Sopenharmony_ci default: /* for newer chipsets */ 1878c2ecf20Sopenharmony_ci rx_len = sdio_readb(card->func, IF_SDIO_RX_LEN, &ret); 1888c2ecf20Sopenharmony_ci if (!ret) 1898c2ecf20Sopenharmony_ci rx_len <<= card->rx_unit; 1908c2ecf20Sopenharmony_ci else 1918c2ecf20Sopenharmony_ci rx_len = 0xffff; /* invalid length */ 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_ci break; 1948c2ecf20Sopenharmony_ci } 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_ci if (err) 1978c2ecf20Sopenharmony_ci *err = ret; 1988c2ecf20Sopenharmony_ci 1998c2ecf20Sopenharmony_ci return rx_len; 2008c2ecf20Sopenharmony_ci} 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_cistatic int if_sdio_handle_cmd(struct if_sdio_card *card, 2038c2ecf20Sopenharmony_ci u8 *buffer, unsigned size) 2048c2ecf20Sopenharmony_ci{ 2058c2ecf20Sopenharmony_ci struct lbs_private *priv = card->priv; 2068c2ecf20Sopenharmony_ci int ret; 2078c2ecf20Sopenharmony_ci unsigned long flags; 2088c2ecf20Sopenharmony_ci u8 i; 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci if (size > LBS_CMD_BUFFER_SIZE) { 2118c2ecf20Sopenharmony_ci lbs_deb_sdio("response packet too large (%d bytes)\n", 2128c2ecf20Sopenharmony_ci (int)size); 2138c2ecf20Sopenharmony_ci ret = -E2BIG; 2148c2ecf20Sopenharmony_ci goto out; 2158c2ecf20Sopenharmony_ci } 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_ci spin_lock_irqsave(&priv->driver_lock, flags); 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_ci i = (priv->resp_idx == 0) ? 1 : 0; 2208c2ecf20Sopenharmony_ci BUG_ON(priv->resp_len[i]); 2218c2ecf20Sopenharmony_ci priv->resp_len[i] = size; 2228c2ecf20Sopenharmony_ci memcpy(priv->resp_buf[i], buffer, size); 2238c2ecf20Sopenharmony_ci lbs_notify_command_response(priv, i); 2248c2ecf20Sopenharmony_ci 2258c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&priv->driver_lock, flags); 2268c2ecf20Sopenharmony_ci 2278c2ecf20Sopenharmony_ci ret = 0; 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_ciout: 2308c2ecf20Sopenharmony_ci return ret; 2318c2ecf20Sopenharmony_ci} 2328c2ecf20Sopenharmony_ci 2338c2ecf20Sopenharmony_cistatic int if_sdio_handle_data(struct if_sdio_card *card, 2348c2ecf20Sopenharmony_ci u8 *buffer, unsigned size) 2358c2ecf20Sopenharmony_ci{ 2368c2ecf20Sopenharmony_ci int ret; 2378c2ecf20Sopenharmony_ci struct sk_buff *skb; 2388c2ecf20Sopenharmony_ci 2398c2ecf20Sopenharmony_ci if (size > MRVDRV_ETH_RX_PACKET_BUFFER_SIZE) { 2408c2ecf20Sopenharmony_ci lbs_deb_sdio("response packet too large (%d bytes)\n", 2418c2ecf20Sopenharmony_ci (int)size); 2428c2ecf20Sopenharmony_ci ret = -E2BIG; 2438c2ecf20Sopenharmony_ci goto out; 2448c2ecf20Sopenharmony_ci } 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_ci skb = dev_alloc_skb(MRVDRV_ETH_RX_PACKET_BUFFER_SIZE + NET_IP_ALIGN); 2478c2ecf20Sopenharmony_ci if (!skb) { 2488c2ecf20Sopenharmony_ci ret = -ENOMEM; 2498c2ecf20Sopenharmony_ci goto out; 2508c2ecf20Sopenharmony_ci } 2518c2ecf20Sopenharmony_ci 2528c2ecf20Sopenharmony_ci skb_reserve(skb, NET_IP_ALIGN); 2538c2ecf20Sopenharmony_ci 2548c2ecf20Sopenharmony_ci skb_put_data(skb, buffer, size); 2558c2ecf20Sopenharmony_ci 2568c2ecf20Sopenharmony_ci lbs_process_rxed_packet(card->priv, skb); 2578c2ecf20Sopenharmony_ci 2588c2ecf20Sopenharmony_ci ret = 0; 2598c2ecf20Sopenharmony_ci 2608c2ecf20Sopenharmony_ciout: 2618c2ecf20Sopenharmony_ci return ret; 2628c2ecf20Sopenharmony_ci} 2638c2ecf20Sopenharmony_ci 2648c2ecf20Sopenharmony_cistatic int if_sdio_handle_event(struct if_sdio_card *card, 2658c2ecf20Sopenharmony_ci u8 *buffer, unsigned size) 2668c2ecf20Sopenharmony_ci{ 2678c2ecf20Sopenharmony_ci int ret; 2688c2ecf20Sopenharmony_ci u32 event; 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_ci if (card->model == MODEL_8385) { 2718c2ecf20Sopenharmony_ci event = sdio_readb(card->func, IF_SDIO_EVENT, &ret); 2728c2ecf20Sopenharmony_ci if (ret) 2738c2ecf20Sopenharmony_ci goto out; 2748c2ecf20Sopenharmony_ci 2758c2ecf20Sopenharmony_ci /* right shift 3 bits to get the event id */ 2768c2ecf20Sopenharmony_ci event >>= 3; 2778c2ecf20Sopenharmony_ci } else { 2788c2ecf20Sopenharmony_ci if (size < 4) { 2798c2ecf20Sopenharmony_ci lbs_deb_sdio("event packet too small (%d bytes)\n", 2808c2ecf20Sopenharmony_ci (int)size); 2818c2ecf20Sopenharmony_ci ret = -EINVAL; 2828c2ecf20Sopenharmony_ci goto out; 2838c2ecf20Sopenharmony_ci } 2848c2ecf20Sopenharmony_ci event = buffer[3] << 24; 2858c2ecf20Sopenharmony_ci event |= buffer[2] << 16; 2868c2ecf20Sopenharmony_ci event |= buffer[1] << 8; 2878c2ecf20Sopenharmony_ci event |= buffer[0] << 0; 2888c2ecf20Sopenharmony_ci } 2898c2ecf20Sopenharmony_ci 2908c2ecf20Sopenharmony_ci lbs_queue_event(card->priv, event & 0xFF); 2918c2ecf20Sopenharmony_ci ret = 0; 2928c2ecf20Sopenharmony_ci 2938c2ecf20Sopenharmony_ciout: 2948c2ecf20Sopenharmony_ci return ret; 2958c2ecf20Sopenharmony_ci} 2968c2ecf20Sopenharmony_ci 2978c2ecf20Sopenharmony_cistatic int if_sdio_wait_status(struct if_sdio_card *card, const u8 condition) 2988c2ecf20Sopenharmony_ci{ 2998c2ecf20Sopenharmony_ci u8 status; 3008c2ecf20Sopenharmony_ci unsigned long timeout; 3018c2ecf20Sopenharmony_ci int ret = 0; 3028c2ecf20Sopenharmony_ci 3038c2ecf20Sopenharmony_ci timeout = jiffies + HZ; 3048c2ecf20Sopenharmony_ci while (1) { 3058c2ecf20Sopenharmony_ci status = sdio_readb(card->func, IF_SDIO_STATUS, &ret); 3068c2ecf20Sopenharmony_ci if (ret) 3078c2ecf20Sopenharmony_ci return ret; 3088c2ecf20Sopenharmony_ci if ((status & condition) == condition) 3098c2ecf20Sopenharmony_ci break; 3108c2ecf20Sopenharmony_ci if (time_after(jiffies, timeout)) 3118c2ecf20Sopenharmony_ci return -ETIMEDOUT; 3128c2ecf20Sopenharmony_ci mdelay(1); 3138c2ecf20Sopenharmony_ci } 3148c2ecf20Sopenharmony_ci return ret; 3158c2ecf20Sopenharmony_ci} 3168c2ecf20Sopenharmony_ci 3178c2ecf20Sopenharmony_cistatic int if_sdio_card_to_host(struct if_sdio_card *card) 3188c2ecf20Sopenharmony_ci{ 3198c2ecf20Sopenharmony_ci int ret; 3208c2ecf20Sopenharmony_ci u16 size, type, chunk; 3218c2ecf20Sopenharmony_ci 3228c2ecf20Sopenharmony_ci size = if_sdio_read_rx_len(card, &ret); 3238c2ecf20Sopenharmony_ci if (ret) 3248c2ecf20Sopenharmony_ci goto out; 3258c2ecf20Sopenharmony_ci 3268c2ecf20Sopenharmony_ci if (size < 4) { 3278c2ecf20Sopenharmony_ci lbs_deb_sdio("invalid packet size (%d bytes) from firmware\n", 3288c2ecf20Sopenharmony_ci (int)size); 3298c2ecf20Sopenharmony_ci ret = -EINVAL; 3308c2ecf20Sopenharmony_ci goto out; 3318c2ecf20Sopenharmony_ci } 3328c2ecf20Sopenharmony_ci 3338c2ecf20Sopenharmony_ci ret = if_sdio_wait_status(card, IF_SDIO_IO_RDY); 3348c2ecf20Sopenharmony_ci if (ret) 3358c2ecf20Sopenharmony_ci goto out; 3368c2ecf20Sopenharmony_ci 3378c2ecf20Sopenharmony_ci /* 3388c2ecf20Sopenharmony_ci * The transfer must be in one transaction or the firmware 3398c2ecf20Sopenharmony_ci * goes suicidal. There's no way to guarantee that for all 3408c2ecf20Sopenharmony_ci * controllers, but we can at least try. 3418c2ecf20Sopenharmony_ci */ 3428c2ecf20Sopenharmony_ci chunk = sdio_align_size(card->func, size); 3438c2ecf20Sopenharmony_ci 3448c2ecf20Sopenharmony_ci ret = sdio_readsb(card->func, card->buffer, card->ioport, chunk); 3458c2ecf20Sopenharmony_ci if (ret) 3468c2ecf20Sopenharmony_ci goto out; 3478c2ecf20Sopenharmony_ci 3488c2ecf20Sopenharmony_ci chunk = card->buffer[0] | (card->buffer[1] << 8); 3498c2ecf20Sopenharmony_ci type = card->buffer[2] | (card->buffer[3] << 8); 3508c2ecf20Sopenharmony_ci 3518c2ecf20Sopenharmony_ci lbs_deb_sdio("packet of type %d and size %d bytes\n", 3528c2ecf20Sopenharmony_ci (int)type, (int)chunk); 3538c2ecf20Sopenharmony_ci 3548c2ecf20Sopenharmony_ci if (chunk > size) { 3558c2ecf20Sopenharmony_ci lbs_deb_sdio("packet fragment (%d > %d)\n", 3568c2ecf20Sopenharmony_ci (int)chunk, (int)size); 3578c2ecf20Sopenharmony_ci ret = -EINVAL; 3588c2ecf20Sopenharmony_ci goto out; 3598c2ecf20Sopenharmony_ci } 3608c2ecf20Sopenharmony_ci 3618c2ecf20Sopenharmony_ci if (chunk < size) { 3628c2ecf20Sopenharmony_ci lbs_deb_sdio("packet fragment (%d < %d)\n", 3638c2ecf20Sopenharmony_ci (int)chunk, (int)size); 3648c2ecf20Sopenharmony_ci } 3658c2ecf20Sopenharmony_ci 3668c2ecf20Sopenharmony_ci switch (type) { 3678c2ecf20Sopenharmony_ci case MVMS_CMD: 3688c2ecf20Sopenharmony_ci ret = if_sdio_handle_cmd(card, card->buffer + 4, chunk - 4); 3698c2ecf20Sopenharmony_ci if (ret) 3708c2ecf20Sopenharmony_ci goto out; 3718c2ecf20Sopenharmony_ci break; 3728c2ecf20Sopenharmony_ci case MVMS_DAT: 3738c2ecf20Sopenharmony_ci ret = if_sdio_handle_data(card, card->buffer + 4, chunk - 4); 3748c2ecf20Sopenharmony_ci if (ret) 3758c2ecf20Sopenharmony_ci goto out; 3768c2ecf20Sopenharmony_ci break; 3778c2ecf20Sopenharmony_ci case MVMS_EVENT: 3788c2ecf20Sopenharmony_ci ret = if_sdio_handle_event(card, card->buffer + 4, chunk - 4); 3798c2ecf20Sopenharmony_ci if (ret) 3808c2ecf20Sopenharmony_ci goto out; 3818c2ecf20Sopenharmony_ci break; 3828c2ecf20Sopenharmony_ci default: 3838c2ecf20Sopenharmony_ci lbs_deb_sdio("invalid type (%d) from firmware\n", 3848c2ecf20Sopenharmony_ci (int)type); 3858c2ecf20Sopenharmony_ci ret = -EINVAL; 3868c2ecf20Sopenharmony_ci goto out; 3878c2ecf20Sopenharmony_ci } 3888c2ecf20Sopenharmony_ci 3898c2ecf20Sopenharmony_ciout: 3908c2ecf20Sopenharmony_ci if (ret) 3918c2ecf20Sopenharmony_ci pr_err("problem fetching packet from firmware\n"); 3928c2ecf20Sopenharmony_ci 3938c2ecf20Sopenharmony_ci return ret; 3948c2ecf20Sopenharmony_ci} 3958c2ecf20Sopenharmony_ci 3968c2ecf20Sopenharmony_cistatic void if_sdio_host_to_card_worker(struct work_struct *work) 3978c2ecf20Sopenharmony_ci{ 3988c2ecf20Sopenharmony_ci struct if_sdio_card *card; 3998c2ecf20Sopenharmony_ci struct if_sdio_packet *packet; 4008c2ecf20Sopenharmony_ci int ret; 4018c2ecf20Sopenharmony_ci unsigned long flags; 4028c2ecf20Sopenharmony_ci 4038c2ecf20Sopenharmony_ci card = container_of(work, struct if_sdio_card, packet_worker); 4048c2ecf20Sopenharmony_ci 4058c2ecf20Sopenharmony_ci while (1) { 4068c2ecf20Sopenharmony_ci spin_lock_irqsave(&card->lock, flags); 4078c2ecf20Sopenharmony_ci packet = card->packets; 4088c2ecf20Sopenharmony_ci if (packet) 4098c2ecf20Sopenharmony_ci card->packets = packet->next; 4108c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&card->lock, flags); 4118c2ecf20Sopenharmony_ci 4128c2ecf20Sopenharmony_ci if (!packet) 4138c2ecf20Sopenharmony_ci break; 4148c2ecf20Sopenharmony_ci 4158c2ecf20Sopenharmony_ci sdio_claim_host(card->func); 4168c2ecf20Sopenharmony_ci 4178c2ecf20Sopenharmony_ci ret = if_sdio_wait_status(card, IF_SDIO_IO_RDY); 4188c2ecf20Sopenharmony_ci if (ret == 0) { 4198c2ecf20Sopenharmony_ci ret = sdio_writesb(card->func, card->ioport, 4208c2ecf20Sopenharmony_ci packet->buffer, packet->nb); 4218c2ecf20Sopenharmony_ci } 4228c2ecf20Sopenharmony_ci 4238c2ecf20Sopenharmony_ci if (ret) 4248c2ecf20Sopenharmony_ci pr_err("error %d sending packet to firmware\n", ret); 4258c2ecf20Sopenharmony_ci 4268c2ecf20Sopenharmony_ci sdio_release_host(card->func); 4278c2ecf20Sopenharmony_ci 4288c2ecf20Sopenharmony_ci kfree(packet); 4298c2ecf20Sopenharmony_ci } 4308c2ecf20Sopenharmony_ci} 4318c2ecf20Sopenharmony_ci 4328c2ecf20Sopenharmony_ci/********************************************************************/ 4338c2ecf20Sopenharmony_ci/* Firmware */ 4348c2ecf20Sopenharmony_ci/********************************************************************/ 4358c2ecf20Sopenharmony_ci 4368c2ecf20Sopenharmony_ci#define FW_DL_READY_STATUS (IF_SDIO_IO_RDY | IF_SDIO_DL_RDY) 4378c2ecf20Sopenharmony_ci 4388c2ecf20Sopenharmony_cistatic int if_sdio_prog_helper(struct if_sdio_card *card, 4398c2ecf20Sopenharmony_ci const struct firmware *fw) 4408c2ecf20Sopenharmony_ci{ 4418c2ecf20Sopenharmony_ci int ret; 4428c2ecf20Sopenharmony_ci unsigned long timeout; 4438c2ecf20Sopenharmony_ci u8 *chunk_buffer; 4448c2ecf20Sopenharmony_ci u32 chunk_size; 4458c2ecf20Sopenharmony_ci const u8 *firmware; 4468c2ecf20Sopenharmony_ci size_t size; 4478c2ecf20Sopenharmony_ci 4488c2ecf20Sopenharmony_ci chunk_buffer = kzalloc(64, GFP_KERNEL); 4498c2ecf20Sopenharmony_ci if (!chunk_buffer) { 4508c2ecf20Sopenharmony_ci ret = -ENOMEM; 4518c2ecf20Sopenharmony_ci goto out; 4528c2ecf20Sopenharmony_ci } 4538c2ecf20Sopenharmony_ci 4548c2ecf20Sopenharmony_ci sdio_claim_host(card->func); 4558c2ecf20Sopenharmony_ci 4568c2ecf20Sopenharmony_ci ret = sdio_set_block_size(card->func, 32); 4578c2ecf20Sopenharmony_ci if (ret) 4588c2ecf20Sopenharmony_ci goto release; 4598c2ecf20Sopenharmony_ci 4608c2ecf20Sopenharmony_ci firmware = fw->data; 4618c2ecf20Sopenharmony_ci size = fw->size; 4628c2ecf20Sopenharmony_ci 4638c2ecf20Sopenharmony_ci while (size) { 4648c2ecf20Sopenharmony_ci ret = if_sdio_wait_status(card, FW_DL_READY_STATUS); 4658c2ecf20Sopenharmony_ci if (ret) 4668c2ecf20Sopenharmony_ci goto release; 4678c2ecf20Sopenharmony_ci 4688c2ecf20Sopenharmony_ci /* On some platforms (like Davinci) the chip needs more time 4698c2ecf20Sopenharmony_ci * between helper blocks. 4708c2ecf20Sopenharmony_ci */ 4718c2ecf20Sopenharmony_ci mdelay(2); 4728c2ecf20Sopenharmony_ci 4738c2ecf20Sopenharmony_ci chunk_size = min_t(size_t, size, 60); 4748c2ecf20Sopenharmony_ci 4758c2ecf20Sopenharmony_ci *((__le32*)chunk_buffer) = cpu_to_le32(chunk_size); 4768c2ecf20Sopenharmony_ci memcpy(chunk_buffer + 4, firmware, chunk_size); 4778c2ecf20Sopenharmony_ci/* 4788c2ecf20Sopenharmony_ci lbs_deb_sdio("sending %d bytes chunk\n", chunk_size); 4798c2ecf20Sopenharmony_ci*/ 4808c2ecf20Sopenharmony_ci ret = sdio_writesb(card->func, card->ioport, 4818c2ecf20Sopenharmony_ci chunk_buffer, 64); 4828c2ecf20Sopenharmony_ci if (ret) 4838c2ecf20Sopenharmony_ci goto release; 4848c2ecf20Sopenharmony_ci 4858c2ecf20Sopenharmony_ci firmware += chunk_size; 4868c2ecf20Sopenharmony_ci size -= chunk_size; 4878c2ecf20Sopenharmony_ci } 4888c2ecf20Sopenharmony_ci 4898c2ecf20Sopenharmony_ci /* an empty block marks the end of the transfer */ 4908c2ecf20Sopenharmony_ci memset(chunk_buffer, 0, 4); 4918c2ecf20Sopenharmony_ci ret = sdio_writesb(card->func, card->ioport, chunk_buffer, 64); 4928c2ecf20Sopenharmony_ci if (ret) 4938c2ecf20Sopenharmony_ci goto release; 4948c2ecf20Sopenharmony_ci 4958c2ecf20Sopenharmony_ci lbs_deb_sdio("waiting for helper to boot...\n"); 4968c2ecf20Sopenharmony_ci 4978c2ecf20Sopenharmony_ci /* wait for the helper to boot by looking at the size register */ 4988c2ecf20Sopenharmony_ci timeout = jiffies + HZ; 4998c2ecf20Sopenharmony_ci while (1) { 5008c2ecf20Sopenharmony_ci u16 req_size; 5018c2ecf20Sopenharmony_ci 5028c2ecf20Sopenharmony_ci req_size = sdio_readb(card->func, IF_SDIO_RD_BASE, &ret); 5038c2ecf20Sopenharmony_ci if (ret) 5048c2ecf20Sopenharmony_ci goto release; 5058c2ecf20Sopenharmony_ci 5068c2ecf20Sopenharmony_ci req_size |= sdio_readb(card->func, IF_SDIO_RD_BASE + 1, &ret) << 8; 5078c2ecf20Sopenharmony_ci if (ret) 5088c2ecf20Sopenharmony_ci goto release; 5098c2ecf20Sopenharmony_ci 5108c2ecf20Sopenharmony_ci if (req_size != 0) 5118c2ecf20Sopenharmony_ci break; 5128c2ecf20Sopenharmony_ci 5138c2ecf20Sopenharmony_ci if (time_after(jiffies, timeout)) { 5148c2ecf20Sopenharmony_ci ret = -ETIMEDOUT; 5158c2ecf20Sopenharmony_ci goto release; 5168c2ecf20Sopenharmony_ci } 5178c2ecf20Sopenharmony_ci 5188c2ecf20Sopenharmony_ci msleep(10); 5198c2ecf20Sopenharmony_ci } 5208c2ecf20Sopenharmony_ci 5218c2ecf20Sopenharmony_ci ret = 0; 5228c2ecf20Sopenharmony_ci 5238c2ecf20Sopenharmony_cirelease: 5248c2ecf20Sopenharmony_ci sdio_release_host(card->func); 5258c2ecf20Sopenharmony_ci kfree(chunk_buffer); 5268c2ecf20Sopenharmony_ci 5278c2ecf20Sopenharmony_ciout: 5288c2ecf20Sopenharmony_ci if (ret) 5298c2ecf20Sopenharmony_ci pr_err("failed to load helper firmware\n"); 5308c2ecf20Sopenharmony_ci 5318c2ecf20Sopenharmony_ci return ret; 5328c2ecf20Sopenharmony_ci} 5338c2ecf20Sopenharmony_ci 5348c2ecf20Sopenharmony_cistatic int if_sdio_prog_real(struct if_sdio_card *card, 5358c2ecf20Sopenharmony_ci const struct firmware *fw) 5368c2ecf20Sopenharmony_ci{ 5378c2ecf20Sopenharmony_ci int ret; 5388c2ecf20Sopenharmony_ci unsigned long timeout; 5398c2ecf20Sopenharmony_ci u8 *chunk_buffer; 5408c2ecf20Sopenharmony_ci u32 chunk_size; 5418c2ecf20Sopenharmony_ci const u8 *firmware; 5428c2ecf20Sopenharmony_ci size_t size, req_size; 5438c2ecf20Sopenharmony_ci 5448c2ecf20Sopenharmony_ci chunk_buffer = kzalloc(512, GFP_KERNEL); 5458c2ecf20Sopenharmony_ci if (!chunk_buffer) { 5468c2ecf20Sopenharmony_ci ret = -ENOMEM; 5478c2ecf20Sopenharmony_ci goto out; 5488c2ecf20Sopenharmony_ci } 5498c2ecf20Sopenharmony_ci 5508c2ecf20Sopenharmony_ci sdio_claim_host(card->func); 5518c2ecf20Sopenharmony_ci 5528c2ecf20Sopenharmony_ci ret = sdio_set_block_size(card->func, 32); 5538c2ecf20Sopenharmony_ci if (ret) 5548c2ecf20Sopenharmony_ci goto release; 5558c2ecf20Sopenharmony_ci 5568c2ecf20Sopenharmony_ci firmware = fw->data; 5578c2ecf20Sopenharmony_ci size = fw->size; 5588c2ecf20Sopenharmony_ci 5598c2ecf20Sopenharmony_ci while (size) { 5608c2ecf20Sopenharmony_ci timeout = jiffies + HZ; 5618c2ecf20Sopenharmony_ci while (1) { 5628c2ecf20Sopenharmony_ci ret = if_sdio_wait_status(card, FW_DL_READY_STATUS); 5638c2ecf20Sopenharmony_ci if (ret) 5648c2ecf20Sopenharmony_ci goto release; 5658c2ecf20Sopenharmony_ci 5668c2ecf20Sopenharmony_ci req_size = sdio_readb(card->func, IF_SDIO_RD_BASE, 5678c2ecf20Sopenharmony_ci &ret); 5688c2ecf20Sopenharmony_ci if (ret) 5698c2ecf20Sopenharmony_ci goto release; 5708c2ecf20Sopenharmony_ci 5718c2ecf20Sopenharmony_ci req_size |= sdio_readb(card->func, IF_SDIO_RD_BASE + 1, 5728c2ecf20Sopenharmony_ci &ret) << 8; 5738c2ecf20Sopenharmony_ci if (ret) 5748c2ecf20Sopenharmony_ci goto release; 5758c2ecf20Sopenharmony_ci 5768c2ecf20Sopenharmony_ci /* 5778c2ecf20Sopenharmony_ci * For SD8688 wait until the length is not 0, 1 or 2 5788c2ecf20Sopenharmony_ci * before downloading the first FW block, 5798c2ecf20Sopenharmony_ci * since BOOT code writes the register to indicate the 5808c2ecf20Sopenharmony_ci * helper/FW download winner, 5818c2ecf20Sopenharmony_ci * the value could be 1 or 2 (Func1 or Func2). 5828c2ecf20Sopenharmony_ci */ 5838c2ecf20Sopenharmony_ci if ((size != fw->size) || (req_size > 2)) 5848c2ecf20Sopenharmony_ci break; 5858c2ecf20Sopenharmony_ci if (time_after(jiffies, timeout)) { 5868c2ecf20Sopenharmony_ci ret = -ETIMEDOUT; 5878c2ecf20Sopenharmony_ci goto release; 5888c2ecf20Sopenharmony_ci } 5898c2ecf20Sopenharmony_ci mdelay(1); 5908c2ecf20Sopenharmony_ci } 5918c2ecf20Sopenharmony_ci 5928c2ecf20Sopenharmony_ci/* 5938c2ecf20Sopenharmony_ci lbs_deb_sdio("firmware wants %d bytes\n", (int)req_size); 5948c2ecf20Sopenharmony_ci*/ 5958c2ecf20Sopenharmony_ci if (req_size == 0) { 5968c2ecf20Sopenharmony_ci lbs_deb_sdio("firmware helper gave up early\n"); 5978c2ecf20Sopenharmony_ci ret = -EIO; 5988c2ecf20Sopenharmony_ci goto release; 5998c2ecf20Sopenharmony_ci } 6008c2ecf20Sopenharmony_ci 6018c2ecf20Sopenharmony_ci if (req_size & 0x01) { 6028c2ecf20Sopenharmony_ci lbs_deb_sdio("firmware helper signalled error\n"); 6038c2ecf20Sopenharmony_ci ret = -EIO; 6048c2ecf20Sopenharmony_ci goto release; 6058c2ecf20Sopenharmony_ci } 6068c2ecf20Sopenharmony_ci 6078c2ecf20Sopenharmony_ci if (req_size > size) 6088c2ecf20Sopenharmony_ci req_size = size; 6098c2ecf20Sopenharmony_ci 6108c2ecf20Sopenharmony_ci while (req_size) { 6118c2ecf20Sopenharmony_ci chunk_size = min_t(size_t, req_size, 512); 6128c2ecf20Sopenharmony_ci 6138c2ecf20Sopenharmony_ci memcpy(chunk_buffer, firmware, chunk_size); 6148c2ecf20Sopenharmony_ci/* 6158c2ecf20Sopenharmony_ci lbs_deb_sdio("sending %d bytes (%d bytes) chunk\n", 6168c2ecf20Sopenharmony_ci chunk_size, (chunk_size + 31) / 32 * 32); 6178c2ecf20Sopenharmony_ci*/ 6188c2ecf20Sopenharmony_ci ret = sdio_writesb(card->func, card->ioport, 6198c2ecf20Sopenharmony_ci chunk_buffer, roundup(chunk_size, 32)); 6208c2ecf20Sopenharmony_ci if (ret) 6218c2ecf20Sopenharmony_ci goto release; 6228c2ecf20Sopenharmony_ci 6238c2ecf20Sopenharmony_ci firmware += chunk_size; 6248c2ecf20Sopenharmony_ci size -= chunk_size; 6258c2ecf20Sopenharmony_ci req_size -= chunk_size; 6268c2ecf20Sopenharmony_ci } 6278c2ecf20Sopenharmony_ci } 6288c2ecf20Sopenharmony_ci 6298c2ecf20Sopenharmony_ci ret = 0; 6308c2ecf20Sopenharmony_ci 6318c2ecf20Sopenharmony_ci lbs_deb_sdio("waiting for firmware to boot...\n"); 6328c2ecf20Sopenharmony_ci 6338c2ecf20Sopenharmony_ci /* wait for the firmware to boot */ 6348c2ecf20Sopenharmony_ci timeout = jiffies + HZ; 6358c2ecf20Sopenharmony_ci while (1) { 6368c2ecf20Sopenharmony_ci u16 scratch; 6378c2ecf20Sopenharmony_ci 6388c2ecf20Sopenharmony_ci scratch = if_sdio_read_scratch(card, &ret); 6398c2ecf20Sopenharmony_ci if (ret) 6408c2ecf20Sopenharmony_ci goto release; 6418c2ecf20Sopenharmony_ci 6428c2ecf20Sopenharmony_ci if (scratch == IF_SDIO_FIRMWARE_OK) 6438c2ecf20Sopenharmony_ci break; 6448c2ecf20Sopenharmony_ci 6458c2ecf20Sopenharmony_ci if (time_after(jiffies, timeout)) { 6468c2ecf20Sopenharmony_ci ret = -ETIMEDOUT; 6478c2ecf20Sopenharmony_ci goto release; 6488c2ecf20Sopenharmony_ci } 6498c2ecf20Sopenharmony_ci 6508c2ecf20Sopenharmony_ci msleep(10); 6518c2ecf20Sopenharmony_ci } 6528c2ecf20Sopenharmony_ci 6538c2ecf20Sopenharmony_ci ret = 0; 6548c2ecf20Sopenharmony_ci 6558c2ecf20Sopenharmony_cirelease: 6568c2ecf20Sopenharmony_ci sdio_release_host(card->func); 6578c2ecf20Sopenharmony_ci kfree(chunk_buffer); 6588c2ecf20Sopenharmony_ci 6598c2ecf20Sopenharmony_ciout: 6608c2ecf20Sopenharmony_ci if (ret) 6618c2ecf20Sopenharmony_ci pr_err("failed to load firmware\n"); 6628c2ecf20Sopenharmony_ci 6638c2ecf20Sopenharmony_ci return ret; 6648c2ecf20Sopenharmony_ci} 6658c2ecf20Sopenharmony_ci 6668c2ecf20Sopenharmony_cistatic void if_sdio_do_prog_firmware(struct lbs_private *priv, int ret, 6678c2ecf20Sopenharmony_ci const struct firmware *helper, 6688c2ecf20Sopenharmony_ci const struct firmware *mainfw) 6698c2ecf20Sopenharmony_ci{ 6708c2ecf20Sopenharmony_ci struct if_sdio_card *card = priv->card; 6718c2ecf20Sopenharmony_ci 6728c2ecf20Sopenharmony_ci if (ret) { 6738c2ecf20Sopenharmony_ci pr_err("failed to find firmware (%d)\n", ret); 6748c2ecf20Sopenharmony_ci return; 6758c2ecf20Sopenharmony_ci } 6768c2ecf20Sopenharmony_ci 6778c2ecf20Sopenharmony_ci ret = if_sdio_prog_helper(card, helper); 6788c2ecf20Sopenharmony_ci if (ret) 6798c2ecf20Sopenharmony_ci return; 6808c2ecf20Sopenharmony_ci 6818c2ecf20Sopenharmony_ci lbs_deb_sdio("Helper firmware loaded\n"); 6828c2ecf20Sopenharmony_ci 6838c2ecf20Sopenharmony_ci ret = if_sdio_prog_real(card, mainfw); 6848c2ecf20Sopenharmony_ci if (ret) 6858c2ecf20Sopenharmony_ci return; 6868c2ecf20Sopenharmony_ci 6878c2ecf20Sopenharmony_ci lbs_deb_sdio("Firmware loaded\n"); 6888c2ecf20Sopenharmony_ci if_sdio_finish_power_on(card); 6898c2ecf20Sopenharmony_ci} 6908c2ecf20Sopenharmony_ci 6918c2ecf20Sopenharmony_cistatic int if_sdio_prog_firmware(struct if_sdio_card *card) 6928c2ecf20Sopenharmony_ci{ 6938c2ecf20Sopenharmony_ci int ret; 6948c2ecf20Sopenharmony_ci u16 scratch; 6958c2ecf20Sopenharmony_ci 6968c2ecf20Sopenharmony_ci /* 6978c2ecf20Sopenharmony_ci * Disable interrupts 6988c2ecf20Sopenharmony_ci */ 6998c2ecf20Sopenharmony_ci sdio_claim_host(card->func); 7008c2ecf20Sopenharmony_ci sdio_writeb(card->func, 0x00, IF_SDIO_H_INT_MASK, &ret); 7018c2ecf20Sopenharmony_ci sdio_release_host(card->func); 7028c2ecf20Sopenharmony_ci 7038c2ecf20Sopenharmony_ci sdio_claim_host(card->func); 7048c2ecf20Sopenharmony_ci scratch = if_sdio_read_scratch(card, &ret); 7058c2ecf20Sopenharmony_ci sdio_release_host(card->func); 7068c2ecf20Sopenharmony_ci 7078c2ecf20Sopenharmony_ci lbs_deb_sdio("firmware status = %#x\n", scratch); 7088c2ecf20Sopenharmony_ci lbs_deb_sdio("scratch ret = %d\n", ret); 7098c2ecf20Sopenharmony_ci 7108c2ecf20Sopenharmony_ci if (ret) 7118c2ecf20Sopenharmony_ci goto out; 7128c2ecf20Sopenharmony_ci 7138c2ecf20Sopenharmony_ci 7148c2ecf20Sopenharmony_ci /* 7158c2ecf20Sopenharmony_ci * The manual clearly describes that FEDC is the right code to use 7168c2ecf20Sopenharmony_ci * to detect firmware presence, but for SD8686 it is not that simple. 7178c2ecf20Sopenharmony_ci * Scratch is also used to store the RX packet length, so we lose 7188c2ecf20Sopenharmony_ci * the FEDC value early on. So we use a non-zero check in order 7198c2ecf20Sopenharmony_ci * to validate firmware presence. 7208c2ecf20Sopenharmony_ci * Additionally, the SD8686 in the Gumstix always has the high scratch 7218c2ecf20Sopenharmony_ci * bit set, even when the firmware is not loaded. So we have to 7228c2ecf20Sopenharmony_ci * exclude that from the test. 7238c2ecf20Sopenharmony_ci */ 7248c2ecf20Sopenharmony_ci if (scratch == IF_SDIO_FIRMWARE_OK) { 7258c2ecf20Sopenharmony_ci lbs_deb_sdio("firmware already loaded\n"); 7268c2ecf20Sopenharmony_ci if_sdio_finish_power_on(card); 7278c2ecf20Sopenharmony_ci return 0; 7288c2ecf20Sopenharmony_ci } else if ((card->model == MODEL_8686) && (scratch & 0x7fff)) { 7298c2ecf20Sopenharmony_ci lbs_deb_sdio("firmware may be running\n"); 7308c2ecf20Sopenharmony_ci if_sdio_finish_power_on(card); 7318c2ecf20Sopenharmony_ci return 0; 7328c2ecf20Sopenharmony_ci } 7338c2ecf20Sopenharmony_ci 7348c2ecf20Sopenharmony_ci ret = lbs_get_firmware_async(card->priv, &card->func->dev, card->model, 7358c2ecf20Sopenharmony_ci fw_table, if_sdio_do_prog_firmware); 7368c2ecf20Sopenharmony_ci 7378c2ecf20Sopenharmony_ciout: 7388c2ecf20Sopenharmony_ci return ret; 7398c2ecf20Sopenharmony_ci} 7408c2ecf20Sopenharmony_ci 7418c2ecf20Sopenharmony_ci/********************************************************************/ 7428c2ecf20Sopenharmony_ci/* Power management */ 7438c2ecf20Sopenharmony_ci/********************************************************************/ 7448c2ecf20Sopenharmony_ci 7458c2ecf20Sopenharmony_ci/* Finish power on sequence (after firmware is loaded) */ 7468c2ecf20Sopenharmony_cistatic void if_sdio_finish_power_on(struct if_sdio_card *card) 7478c2ecf20Sopenharmony_ci{ 7488c2ecf20Sopenharmony_ci struct sdio_func *func = card->func; 7498c2ecf20Sopenharmony_ci struct lbs_private *priv = card->priv; 7508c2ecf20Sopenharmony_ci int ret; 7518c2ecf20Sopenharmony_ci 7528c2ecf20Sopenharmony_ci sdio_claim_host(func); 7538c2ecf20Sopenharmony_ci sdio_set_block_size(card->func, IF_SDIO_BLOCK_SIZE); 7548c2ecf20Sopenharmony_ci 7558c2ecf20Sopenharmony_ci /* 7568c2ecf20Sopenharmony_ci * Get rx_unit if the chip is SD8688 or newer. 7578c2ecf20Sopenharmony_ci * SD8385 & SD8686 do not have rx_unit. 7588c2ecf20Sopenharmony_ci */ 7598c2ecf20Sopenharmony_ci if ((card->model != MODEL_8385) 7608c2ecf20Sopenharmony_ci && (card->model != MODEL_8686)) 7618c2ecf20Sopenharmony_ci card->rx_unit = if_sdio_read_rx_unit(card); 7628c2ecf20Sopenharmony_ci else 7638c2ecf20Sopenharmony_ci card->rx_unit = 0; 7648c2ecf20Sopenharmony_ci 7658c2ecf20Sopenharmony_ci /* 7668c2ecf20Sopenharmony_ci * Set up the interrupt handler late. 7678c2ecf20Sopenharmony_ci * 7688c2ecf20Sopenharmony_ci * If we set it up earlier, the (buggy) hardware generates a spurious 7698c2ecf20Sopenharmony_ci * interrupt, even before the interrupt has been enabled, with 7708c2ecf20Sopenharmony_ci * CCCR_INTx = 0. 7718c2ecf20Sopenharmony_ci * 7728c2ecf20Sopenharmony_ci * We register the interrupt handler late so that we can handle any 7738c2ecf20Sopenharmony_ci * spurious interrupts, and also to avoid generation of that known 7748c2ecf20Sopenharmony_ci * spurious interrupt in the first place. 7758c2ecf20Sopenharmony_ci */ 7768c2ecf20Sopenharmony_ci ret = sdio_claim_irq(func, if_sdio_interrupt); 7778c2ecf20Sopenharmony_ci if (ret) 7788c2ecf20Sopenharmony_ci goto release; 7798c2ecf20Sopenharmony_ci 7808c2ecf20Sopenharmony_ci /* 7818c2ecf20Sopenharmony_ci * Enable interrupts now that everything is set up 7828c2ecf20Sopenharmony_ci */ 7838c2ecf20Sopenharmony_ci sdio_writeb(func, 0x0f, IF_SDIO_H_INT_MASK, &ret); 7848c2ecf20Sopenharmony_ci if (ret) 7858c2ecf20Sopenharmony_ci goto release_irq; 7868c2ecf20Sopenharmony_ci 7878c2ecf20Sopenharmony_ci sdio_release_host(func); 7888c2ecf20Sopenharmony_ci 7898c2ecf20Sopenharmony_ci /* Set fw_ready before queuing any commands so that 7908c2ecf20Sopenharmony_ci * lbs_thread won't block from sending them to firmware. 7918c2ecf20Sopenharmony_ci */ 7928c2ecf20Sopenharmony_ci priv->fw_ready = 1; 7938c2ecf20Sopenharmony_ci 7948c2ecf20Sopenharmony_ci /* 7958c2ecf20Sopenharmony_ci * FUNC_INIT is required for SD8688 WLAN/BT multiple functions 7968c2ecf20Sopenharmony_ci */ 7978c2ecf20Sopenharmony_ci if (card->model == MODEL_8688) { 7988c2ecf20Sopenharmony_ci struct cmd_header cmd; 7998c2ecf20Sopenharmony_ci 8008c2ecf20Sopenharmony_ci memset(&cmd, 0, sizeof(cmd)); 8018c2ecf20Sopenharmony_ci 8028c2ecf20Sopenharmony_ci lbs_deb_sdio("send function INIT command\n"); 8038c2ecf20Sopenharmony_ci if (__lbs_cmd(priv, CMD_FUNC_INIT, &cmd, sizeof(cmd), 8048c2ecf20Sopenharmony_ci lbs_cmd_copyback, (unsigned long) &cmd)) 8058c2ecf20Sopenharmony_ci netdev_alert(priv->dev, "CMD_FUNC_INIT cmd failed\n"); 8068c2ecf20Sopenharmony_ci } 8078c2ecf20Sopenharmony_ci 8088c2ecf20Sopenharmony_ci wake_up(&card->pwron_waitq); 8098c2ecf20Sopenharmony_ci 8108c2ecf20Sopenharmony_ci if (!card->started) { 8118c2ecf20Sopenharmony_ci ret = lbs_start_card(priv); 8128c2ecf20Sopenharmony_ci if_sdio_power_off(card); 8138c2ecf20Sopenharmony_ci if (ret == 0) { 8148c2ecf20Sopenharmony_ci card->started = true; 8158c2ecf20Sopenharmony_ci /* Tell PM core that we don't need the card to be 8168c2ecf20Sopenharmony_ci * powered now */ 8178c2ecf20Sopenharmony_ci pm_runtime_put(&func->dev); 8188c2ecf20Sopenharmony_ci } 8198c2ecf20Sopenharmony_ci } 8208c2ecf20Sopenharmony_ci 8218c2ecf20Sopenharmony_ci return; 8228c2ecf20Sopenharmony_ci 8238c2ecf20Sopenharmony_cirelease_irq: 8248c2ecf20Sopenharmony_ci sdio_release_irq(func); 8258c2ecf20Sopenharmony_cirelease: 8268c2ecf20Sopenharmony_ci sdio_release_host(func); 8278c2ecf20Sopenharmony_ci} 8288c2ecf20Sopenharmony_ci 8298c2ecf20Sopenharmony_cistatic int if_sdio_power_on(struct if_sdio_card *card) 8308c2ecf20Sopenharmony_ci{ 8318c2ecf20Sopenharmony_ci struct sdio_func *func = card->func; 8328c2ecf20Sopenharmony_ci struct mmc_host *host = func->card->host; 8338c2ecf20Sopenharmony_ci int ret; 8348c2ecf20Sopenharmony_ci 8358c2ecf20Sopenharmony_ci sdio_claim_host(func); 8368c2ecf20Sopenharmony_ci 8378c2ecf20Sopenharmony_ci ret = sdio_enable_func(func); 8388c2ecf20Sopenharmony_ci if (ret) 8398c2ecf20Sopenharmony_ci goto release; 8408c2ecf20Sopenharmony_ci 8418c2ecf20Sopenharmony_ci /* For 1-bit transfers to the 8686 model, we need to enable the 8428c2ecf20Sopenharmony_ci * interrupt flag in the CCCR register. Set the MMC_QUIRK_LENIENT_FN0 8438c2ecf20Sopenharmony_ci * bit to allow access to non-vendor registers. */ 8448c2ecf20Sopenharmony_ci if ((card->model == MODEL_8686) && 8458c2ecf20Sopenharmony_ci (host->caps & MMC_CAP_SDIO_IRQ) && 8468c2ecf20Sopenharmony_ci (host->ios.bus_width == MMC_BUS_WIDTH_1)) { 8478c2ecf20Sopenharmony_ci u8 reg; 8488c2ecf20Sopenharmony_ci 8498c2ecf20Sopenharmony_ci func->card->quirks |= MMC_QUIRK_LENIENT_FN0; 8508c2ecf20Sopenharmony_ci reg = sdio_f0_readb(func, SDIO_CCCR_IF, &ret); 8518c2ecf20Sopenharmony_ci if (ret) 8528c2ecf20Sopenharmony_ci goto disable; 8538c2ecf20Sopenharmony_ci 8548c2ecf20Sopenharmony_ci reg |= SDIO_BUS_ECSI; 8558c2ecf20Sopenharmony_ci sdio_f0_writeb(func, reg, SDIO_CCCR_IF, &ret); 8568c2ecf20Sopenharmony_ci if (ret) 8578c2ecf20Sopenharmony_ci goto disable; 8588c2ecf20Sopenharmony_ci } 8598c2ecf20Sopenharmony_ci 8608c2ecf20Sopenharmony_ci card->ioport = sdio_readb(func, IF_SDIO_IOPORT, &ret); 8618c2ecf20Sopenharmony_ci if (ret) 8628c2ecf20Sopenharmony_ci goto disable; 8638c2ecf20Sopenharmony_ci 8648c2ecf20Sopenharmony_ci card->ioport |= sdio_readb(func, IF_SDIO_IOPORT + 1, &ret) << 8; 8658c2ecf20Sopenharmony_ci if (ret) 8668c2ecf20Sopenharmony_ci goto disable; 8678c2ecf20Sopenharmony_ci 8688c2ecf20Sopenharmony_ci card->ioport |= sdio_readb(func, IF_SDIO_IOPORT + 2, &ret) << 16; 8698c2ecf20Sopenharmony_ci if (ret) 8708c2ecf20Sopenharmony_ci goto disable; 8718c2ecf20Sopenharmony_ci 8728c2ecf20Sopenharmony_ci sdio_release_host(func); 8738c2ecf20Sopenharmony_ci ret = if_sdio_prog_firmware(card); 8748c2ecf20Sopenharmony_ci if (ret) { 8758c2ecf20Sopenharmony_ci sdio_claim_host(func); 8768c2ecf20Sopenharmony_ci goto disable; 8778c2ecf20Sopenharmony_ci } 8788c2ecf20Sopenharmony_ci 8798c2ecf20Sopenharmony_ci return 0; 8808c2ecf20Sopenharmony_ci 8818c2ecf20Sopenharmony_cidisable: 8828c2ecf20Sopenharmony_ci sdio_disable_func(func); 8838c2ecf20Sopenharmony_cirelease: 8848c2ecf20Sopenharmony_ci sdio_release_host(func); 8858c2ecf20Sopenharmony_ci return ret; 8868c2ecf20Sopenharmony_ci} 8878c2ecf20Sopenharmony_ci 8888c2ecf20Sopenharmony_cistatic int if_sdio_power_off(struct if_sdio_card *card) 8898c2ecf20Sopenharmony_ci{ 8908c2ecf20Sopenharmony_ci struct sdio_func *func = card->func; 8918c2ecf20Sopenharmony_ci struct lbs_private *priv = card->priv; 8928c2ecf20Sopenharmony_ci 8938c2ecf20Sopenharmony_ci priv->fw_ready = 0; 8948c2ecf20Sopenharmony_ci 8958c2ecf20Sopenharmony_ci sdio_claim_host(func); 8968c2ecf20Sopenharmony_ci sdio_release_irq(func); 8978c2ecf20Sopenharmony_ci sdio_disable_func(func); 8988c2ecf20Sopenharmony_ci sdio_release_host(func); 8998c2ecf20Sopenharmony_ci return 0; 9008c2ecf20Sopenharmony_ci} 9018c2ecf20Sopenharmony_ci 9028c2ecf20Sopenharmony_ci 9038c2ecf20Sopenharmony_ci/*******************************************************************/ 9048c2ecf20Sopenharmony_ci/* Libertas callbacks */ 9058c2ecf20Sopenharmony_ci/*******************************************************************/ 9068c2ecf20Sopenharmony_ci 9078c2ecf20Sopenharmony_cistatic int if_sdio_host_to_card(struct lbs_private *priv, 9088c2ecf20Sopenharmony_ci u8 type, u8 *buf, u16 nb) 9098c2ecf20Sopenharmony_ci{ 9108c2ecf20Sopenharmony_ci int ret; 9118c2ecf20Sopenharmony_ci struct if_sdio_card *card; 9128c2ecf20Sopenharmony_ci struct if_sdio_packet *packet, *cur; 9138c2ecf20Sopenharmony_ci u16 size; 9148c2ecf20Sopenharmony_ci unsigned long flags; 9158c2ecf20Sopenharmony_ci 9168c2ecf20Sopenharmony_ci card = priv->card; 9178c2ecf20Sopenharmony_ci 9188c2ecf20Sopenharmony_ci if (nb > (65536 - sizeof(struct if_sdio_packet) - 4)) { 9198c2ecf20Sopenharmony_ci ret = -EINVAL; 9208c2ecf20Sopenharmony_ci goto out; 9218c2ecf20Sopenharmony_ci } 9228c2ecf20Sopenharmony_ci 9238c2ecf20Sopenharmony_ci /* 9248c2ecf20Sopenharmony_ci * The transfer must be in one transaction or the firmware 9258c2ecf20Sopenharmony_ci * goes suicidal. There's no way to guarantee that for all 9268c2ecf20Sopenharmony_ci * controllers, but we can at least try. 9278c2ecf20Sopenharmony_ci */ 9288c2ecf20Sopenharmony_ci size = sdio_align_size(card->func, nb + 4); 9298c2ecf20Sopenharmony_ci 9308c2ecf20Sopenharmony_ci packet = kzalloc(sizeof(struct if_sdio_packet) + size, 9318c2ecf20Sopenharmony_ci GFP_ATOMIC); 9328c2ecf20Sopenharmony_ci if (!packet) { 9338c2ecf20Sopenharmony_ci ret = -ENOMEM; 9348c2ecf20Sopenharmony_ci goto out; 9358c2ecf20Sopenharmony_ci } 9368c2ecf20Sopenharmony_ci 9378c2ecf20Sopenharmony_ci packet->next = NULL; 9388c2ecf20Sopenharmony_ci packet->nb = size; 9398c2ecf20Sopenharmony_ci 9408c2ecf20Sopenharmony_ci /* 9418c2ecf20Sopenharmony_ci * SDIO specific header. 9428c2ecf20Sopenharmony_ci */ 9438c2ecf20Sopenharmony_ci packet->buffer[0] = (nb + 4) & 0xff; 9448c2ecf20Sopenharmony_ci packet->buffer[1] = ((nb + 4) >> 8) & 0xff; 9458c2ecf20Sopenharmony_ci packet->buffer[2] = type; 9468c2ecf20Sopenharmony_ci packet->buffer[3] = 0; 9478c2ecf20Sopenharmony_ci 9488c2ecf20Sopenharmony_ci memcpy(packet->buffer + 4, buf, nb); 9498c2ecf20Sopenharmony_ci 9508c2ecf20Sopenharmony_ci spin_lock_irqsave(&card->lock, flags); 9518c2ecf20Sopenharmony_ci 9528c2ecf20Sopenharmony_ci if (!card->packets) 9538c2ecf20Sopenharmony_ci card->packets = packet; 9548c2ecf20Sopenharmony_ci else { 9558c2ecf20Sopenharmony_ci cur = card->packets; 9568c2ecf20Sopenharmony_ci while (cur->next) 9578c2ecf20Sopenharmony_ci cur = cur->next; 9588c2ecf20Sopenharmony_ci cur->next = packet; 9598c2ecf20Sopenharmony_ci } 9608c2ecf20Sopenharmony_ci 9618c2ecf20Sopenharmony_ci switch (type) { 9628c2ecf20Sopenharmony_ci case MVMS_CMD: 9638c2ecf20Sopenharmony_ci priv->dnld_sent = DNLD_CMD_SENT; 9648c2ecf20Sopenharmony_ci break; 9658c2ecf20Sopenharmony_ci case MVMS_DAT: 9668c2ecf20Sopenharmony_ci priv->dnld_sent = DNLD_DATA_SENT; 9678c2ecf20Sopenharmony_ci break; 9688c2ecf20Sopenharmony_ci default: 9698c2ecf20Sopenharmony_ci lbs_deb_sdio("unknown packet type %d\n", (int)type); 9708c2ecf20Sopenharmony_ci } 9718c2ecf20Sopenharmony_ci 9728c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&card->lock, flags); 9738c2ecf20Sopenharmony_ci 9748c2ecf20Sopenharmony_ci queue_work(card->workqueue, &card->packet_worker); 9758c2ecf20Sopenharmony_ci 9768c2ecf20Sopenharmony_ci ret = 0; 9778c2ecf20Sopenharmony_ci 9788c2ecf20Sopenharmony_ciout: 9798c2ecf20Sopenharmony_ci return ret; 9808c2ecf20Sopenharmony_ci} 9818c2ecf20Sopenharmony_ci 9828c2ecf20Sopenharmony_cistatic int if_sdio_enter_deep_sleep(struct lbs_private *priv) 9838c2ecf20Sopenharmony_ci{ 9848c2ecf20Sopenharmony_ci int ret = -1; 9858c2ecf20Sopenharmony_ci struct cmd_header cmd; 9868c2ecf20Sopenharmony_ci 9878c2ecf20Sopenharmony_ci memset(&cmd, 0, sizeof(cmd)); 9888c2ecf20Sopenharmony_ci 9898c2ecf20Sopenharmony_ci lbs_deb_sdio("send DEEP_SLEEP command\n"); 9908c2ecf20Sopenharmony_ci ret = __lbs_cmd(priv, CMD_802_11_DEEP_SLEEP, &cmd, sizeof(cmd), 9918c2ecf20Sopenharmony_ci lbs_cmd_copyback, (unsigned long) &cmd); 9928c2ecf20Sopenharmony_ci if (ret) 9938c2ecf20Sopenharmony_ci netdev_err(priv->dev, "DEEP_SLEEP cmd failed\n"); 9948c2ecf20Sopenharmony_ci 9958c2ecf20Sopenharmony_ci mdelay(200); 9968c2ecf20Sopenharmony_ci return ret; 9978c2ecf20Sopenharmony_ci} 9988c2ecf20Sopenharmony_ci 9998c2ecf20Sopenharmony_cistatic int if_sdio_exit_deep_sleep(struct lbs_private *priv) 10008c2ecf20Sopenharmony_ci{ 10018c2ecf20Sopenharmony_ci struct if_sdio_card *card = priv->card; 10028c2ecf20Sopenharmony_ci int ret = -1; 10038c2ecf20Sopenharmony_ci 10048c2ecf20Sopenharmony_ci sdio_claim_host(card->func); 10058c2ecf20Sopenharmony_ci 10068c2ecf20Sopenharmony_ci sdio_writeb(card->func, HOST_POWER_UP, CONFIGURATION_REG, &ret); 10078c2ecf20Sopenharmony_ci if (ret) 10088c2ecf20Sopenharmony_ci netdev_err(priv->dev, "sdio_writeb failed!\n"); 10098c2ecf20Sopenharmony_ci 10108c2ecf20Sopenharmony_ci sdio_release_host(card->func); 10118c2ecf20Sopenharmony_ci 10128c2ecf20Sopenharmony_ci return ret; 10138c2ecf20Sopenharmony_ci} 10148c2ecf20Sopenharmony_ci 10158c2ecf20Sopenharmony_cistatic int if_sdio_reset_deep_sleep_wakeup(struct lbs_private *priv) 10168c2ecf20Sopenharmony_ci{ 10178c2ecf20Sopenharmony_ci struct if_sdio_card *card = priv->card; 10188c2ecf20Sopenharmony_ci int ret = -1; 10198c2ecf20Sopenharmony_ci 10208c2ecf20Sopenharmony_ci sdio_claim_host(card->func); 10218c2ecf20Sopenharmony_ci 10228c2ecf20Sopenharmony_ci sdio_writeb(card->func, 0, CONFIGURATION_REG, &ret); 10238c2ecf20Sopenharmony_ci if (ret) 10248c2ecf20Sopenharmony_ci netdev_err(priv->dev, "sdio_writeb failed!\n"); 10258c2ecf20Sopenharmony_ci 10268c2ecf20Sopenharmony_ci sdio_release_host(card->func); 10278c2ecf20Sopenharmony_ci 10288c2ecf20Sopenharmony_ci return ret; 10298c2ecf20Sopenharmony_ci 10308c2ecf20Sopenharmony_ci} 10318c2ecf20Sopenharmony_ci 10328c2ecf20Sopenharmony_cistatic struct mmc_host *reset_host; 10338c2ecf20Sopenharmony_ci 10348c2ecf20Sopenharmony_cistatic void if_sdio_reset_card_worker(struct work_struct *work) 10358c2ecf20Sopenharmony_ci{ 10368c2ecf20Sopenharmony_ci /* 10378c2ecf20Sopenharmony_ci * The actual reset operation must be run outside of lbs_thread. This 10388c2ecf20Sopenharmony_ci * is because mmc_remove_host() will cause the device to be instantly 10398c2ecf20Sopenharmony_ci * destroyed, and the libertas driver then needs to end lbs_thread, 10408c2ecf20Sopenharmony_ci * leading to a deadlock. 10418c2ecf20Sopenharmony_ci * 10428c2ecf20Sopenharmony_ci * We run it in a workqueue totally independent from the if_sdio_card 10438c2ecf20Sopenharmony_ci * instance for that reason. 10448c2ecf20Sopenharmony_ci */ 10458c2ecf20Sopenharmony_ci 10468c2ecf20Sopenharmony_ci pr_info("Resetting card..."); 10478c2ecf20Sopenharmony_ci mmc_remove_host(reset_host); 10488c2ecf20Sopenharmony_ci mmc_add_host(reset_host); 10498c2ecf20Sopenharmony_ci} 10508c2ecf20Sopenharmony_cistatic DECLARE_WORK(card_reset_work, if_sdio_reset_card_worker); 10518c2ecf20Sopenharmony_ci 10528c2ecf20Sopenharmony_cistatic void if_sdio_reset_card(struct lbs_private *priv) 10538c2ecf20Sopenharmony_ci{ 10548c2ecf20Sopenharmony_ci struct if_sdio_card *card = priv->card; 10558c2ecf20Sopenharmony_ci 10568c2ecf20Sopenharmony_ci if (work_pending(&card_reset_work)) 10578c2ecf20Sopenharmony_ci return; 10588c2ecf20Sopenharmony_ci 10598c2ecf20Sopenharmony_ci reset_host = card->func->card->host; 10608c2ecf20Sopenharmony_ci schedule_work(&card_reset_work); 10618c2ecf20Sopenharmony_ci} 10628c2ecf20Sopenharmony_ci 10638c2ecf20Sopenharmony_cistatic int if_sdio_power_save(struct lbs_private *priv) 10648c2ecf20Sopenharmony_ci{ 10658c2ecf20Sopenharmony_ci struct if_sdio_card *card = priv->card; 10668c2ecf20Sopenharmony_ci int ret; 10678c2ecf20Sopenharmony_ci 10688c2ecf20Sopenharmony_ci flush_workqueue(card->workqueue); 10698c2ecf20Sopenharmony_ci 10708c2ecf20Sopenharmony_ci ret = if_sdio_power_off(card); 10718c2ecf20Sopenharmony_ci 10728c2ecf20Sopenharmony_ci /* Let runtime PM know the card is powered off */ 10738c2ecf20Sopenharmony_ci pm_runtime_put_sync(&card->func->dev); 10748c2ecf20Sopenharmony_ci 10758c2ecf20Sopenharmony_ci return ret; 10768c2ecf20Sopenharmony_ci} 10778c2ecf20Sopenharmony_ci 10788c2ecf20Sopenharmony_cistatic int if_sdio_power_restore(struct lbs_private *priv) 10798c2ecf20Sopenharmony_ci{ 10808c2ecf20Sopenharmony_ci struct if_sdio_card *card = priv->card; 10818c2ecf20Sopenharmony_ci int r; 10828c2ecf20Sopenharmony_ci 10838c2ecf20Sopenharmony_ci /* Make sure the card will not be powered off by runtime PM */ 10848c2ecf20Sopenharmony_ci pm_runtime_get_sync(&card->func->dev); 10858c2ecf20Sopenharmony_ci 10868c2ecf20Sopenharmony_ci r = if_sdio_power_on(card); 10878c2ecf20Sopenharmony_ci if (r) 10888c2ecf20Sopenharmony_ci return r; 10898c2ecf20Sopenharmony_ci 10908c2ecf20Sopenharmony_ci wait_event(card->pwron_waitq, priv->fw_ready); 10918c2ecf20Sopenharmony_ci return 0; 10928c2ecf20Sopenharmony_ci} 10938c2ecf20Sopenharmony_ci 10948c2ecf20Sopenharmony_ci 10958c2ecf20Sopenharmony_ci/*******************************************************************/ 10968c2ecf20Sopenharmony_ci/* SDIO callbacks */ 10978c2ecf20Sopenharmony_ci/*******************************************************************/ 10988c2ecf20Sopenharmony_ci 10998c2ecf20Sopenharmony_cistatic void if_sdio_interrupt(struct sdio_func *func) 11008c2ecf20Sopenharmony_ci{ 11018c2ecf20Sopenharmony_ci int ret; 11028c2ecf20Sopenharmony_ci struct if_sdio_card *card; 11038c2ecf20Sopenharmony_ci u8 cause; 11048c2ecf20Sopenharmony_ci 11058c2ecf20Sopenharmony_ci card = sdio_get_drvdata(func); 11068c2ecf20Sopenharmony_ci 11078c2ecf20Sopenharmony_ci cause = sdio_readb(card->func, IF_SDIO_H_INT_STATUS, &ret); 11088c2ecf20Sopenharmony_ci if (ret || !cause) 11098c2ecf20Sopenharmony_ci return; 11108c2ecf20Sopenharmony_ci 11118c2ecf20Sopenharmony_ci lbs_deb_sdio("interrupt: 0x%X\n", (unsigned)cause); 11128c2ecf20Sopenharmony_ci 11138c2ecf20Sopenharmony_ci sdio_writeb(card->func, ~cause, IF_SDIO_H_INT_STATUS, &ret); 11148c2ecf20Sopenharmony_ci if (ret) 11158c2ecf20Sopenharmony_ci return; 11168c2ecf20Sopenharmony_ci 11178c2ecf20Sopenharmony_ci /* 11188c2ecf20Sopenharmony_ci * Ignore the define name, this really means the card has 11198c2ecf20Sopenharmony_ci * successfully received the command. 11208c2ecf20Sopenharmony_ci */ 11218c2ecf20Sopenharmony_ci card->priv->is_activity_detected = 1; 11228c2ecf20Sopenharmony_ci if (cause & IF_SDIO_H_INT_DNLD) 11238c2ecf20Sopenharmony_ci lbs_host_to_card_done(card->priv); 11248c2ecf20Sopenharmony_ci 11258c2ecf20Sopenharmony_ci 11268c2ecf20Sopenharmony_ci if (cause & IF_SDIO_H_INT_UPLD) { 11278c2ecf20Sopenharmony_ci ret = if_sdio_card_to_host(card); 11288c2ecf20Sopenharmony_ci if (ret) 11298c2ecf20Sopenharmony_ci return; 11308c2ecf20Sopenharmony_ci } 11318c2ecf20Sopenharmony_ci} 11328c2ecf20Sopenharmony_ci 11338c2ecf20Sopenharmony_cistatic int if_sdio_probe(struct sdio_func *func, 11348c2ecf20Sopenharmony_ci const struct sdio_device_id *id) 11358c2ecf20Sopenharmony_ci{ 11368c2ecf20Sopenharmony_ci struct if_sdio_card *card; 11378c2ecf20Sopenharmony_ci struct lbs_private *priv; 11388c2ecf20Sopenharmony_ci int ret, i; 11398c2ecf20Sopenharmony_ci unsigned int model; 11408c2ecf20Sopenharmony_ci struct if_sdio_packet *packet; 11418c2ecf20Sopenharmony_ci 11428c2ecf20Sopenharmony_ci for (i = 0;i < func->card->num_info;i++) { 11438c2ecf20Sopenharmony_ci if (sscanf(func->card->info[i], 11448c2ecf20Sopenharmony_ci "802.11 SDIO ID: %x", &model) == 1) 11458c2ecf20Sopenharmony_ci break; 11468c2ecf20Sopenharmony_ci if (sscanf(func->card->info[i], 11478c2ecf20Sopenharmony_ci "ID: %x", &model) == 1) 11488c2ecf20Sopenharmony_ci break; 11498c2ecf20Sopenharmony_ci if (!strcmp(func->card->info[i], "IBIS Wireless SDIO Card")) { 11508c2ecf20Sopenharmony_ci model = MODEL_8385; 11518c2ecf20Sopenharmony_ci break; 11528c2ecf20Sopenharmony_ci } 11538c2ecf20Sopenharmony_ci } 11548c2ecf20Sopenharmony_ci 11558c2ecf20Sopenharmony_ci if (i == func->card->num_info) { 11568c2ecf20Sopenharmony_ci pr_err("unable to identify card model\n"); 11578c2ecf20Sopenharmony_ci return -ENODEV; 11588c2ecf20Sopenharmony_ci } 11598c2ecf20Sopenharmony_ci 11608c2ecf20Sopenharmony_ci card = kzalloc(sizeof(struct if_sdio_card), GFP_KERNEL); 11618c2ecf20Sopenharmony_ci if (!card) 11628c2ecf20Sopenharmony_ci return -ENOMEM; 11638c2ecf20Sopenharmony_ci 11648c2ecf20Sopenharmony_ci card->func = func; 11658c2ecf20Sopenharmony_ci card->model = model; 11668c2ecf20Sopenharmony_ci 11678c2ecf20Sopenharmony_ci switch (card->model) { 11688c2ecf20Sopenharmony_ci case MODEL_8385: 11698c2ecf20Sopenharmony_ci card->scratch_reg = IF_SDIO_SCRATCH_OLD; 11708c2ecf20Sopenharmony_ci break; 11718c2ecf20Sopenharmony_ci case MODEL_8686: 11728c2ecf20Sopenharmony_ci card->scratch_reg = IF_SDIO_SCRATCH; 11738c2ecf20Sopenharmony_ci break; 11748c2ecf20Sopenharmony_ci case MODEL_8688: 11758c2ecf20Sopenharmony_ci default: /* for newer chipsets */ 11768c2ecf20Sopenharmony_ci card->scratch_reg = IF_SDIO_FW_STATUS; 11778c2ecf20Sopenharmony_ci break; 11788c2ecf20Sopenharmony_ci } 11798c2ecf20Sopenharmony_ci 11808c2ecf20Sopenharmony_ci spin_lock_init(&card->lock); 11818c2ecf20Sopenharmony_ci card->workqueue = alloc_workqueue("libertas_sdio", WQ_MEM_RECLAIM, 0); 11828c2ecf20Sopenharmony_ci if (unlikely(!card->workqueue)) { 11838c2ecf20Sopenharmony_ci ret = -ENOMEM; 11848c2ecf20Sopenharmony_ci goto err_queue; 11858c2ecf20Sopenharmony_ci } 11868c2ecf20Sopenharmony_ci INIT_WORK(&card->packet_worker, if_sdio_host_to_card_worker); 11878c2ecf20Sopenharmony_ci init_waitqueue_head(&card->pwron_waitq); 11888c2ecf20Sopenharmony_ci 11898c2ecf20Sopenharmony_ci /* Check if we support this card */ 11908c2ecf20Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(fw_table); i++) { 11918c2ecf20Sopenharmony_ci if (card->model == fw_table[i].model) 11928c2ecf20Sopenharmony_ci break; 11938c2ecf20Sopenharmony_ci } 11948c2ecf20Sopenharmony_ci if (i == ARRAY_SIZE(fw_table)) { 11958c2ecf20Sopenharmony_ci pr_err("unknown card model 0x%x\n", card->model); 11968c2ecf20Sopenharmony_ci ret = -ENODEV; 11978c2ecf20Sopenharmony_ci goto free; 11988c2ecf20Sopenharmony_ci } 11998c2ecf20Sopenharmony_ci 12008c2ecf20Sopenharmony_ci sdio_set_drvdata(func, card); 12018c2ecf20Sopenharmony_ci 12028c2ecf20Sopenharmony_ci lbs_deb_sdio("class = 0x%X, vendor = 0x%X, " 12038c2ecf20Sopenharmony_ci "device = 0x%X, model = 0x%X, ioport = 0x%X\n", 12048c2ecf20Sopenharmony_ci func->class, func->vendor, func->device, 12058c2ecf20Sopenharmony_ci model, (unsigned)card->ioport); 12068c2ecf20Sopenharmony_ci 12078c2ecf20Sopenharmony_ci 12088c2ecf20Sopenharmony_ci priv = lbs_add_card(card, &func->dev); 12098c2ecf20Sopenharmony_ci if (IS_ERR(priv)) { 12108c2ecf20Sopenharmony_ci ret = PTR_ERR(priv); 12118c2ecf20Sopenharmony_ci goto free; 12128c2ecf20Sopenharmony_ci } 12138c2ecf20Sopenharmony_ci 12148c2ecf20Sopenharmony_ci card->priv = priv; 12158c2ecf20Sopenharmony_ci 12168c2ecf20Sopenharmony_ci priv->card = card; 12178c2ecf20Sopenharmony_ci priv->hw_host_to_card = if_sdio_host_to_card; 12188c2ecf20Sopenharmony_ci priv->enter_deep_sleep = if_sdio_enter_deep_sleep; 12198c2ecf20Sopenharmony_ci priv->exit_deep_sleep = if_sdio_exit_deep_sleep; 12208c2ecf20Sopenharmony_ci priv->reset_deep_sleep_wakeup = if_sdio_reset_deep_sleep_wakeup; 12218c2ecf20Sopenharmony_ci priv->reset_card = if_sdio_reset_card; 12228c2ecf20Sopenharmony_ci priv->power_save = if_sdio_power_save; 12238c2ecf20Sopenharmony_ci priv->power_restore = if_sdio_power_restore; 12248c2ecf20Sopenharmony_ci priv->is_polling = !(func->card->host->caps & MMC_CAP_SDIO_IRQ); 12258c2ecf20Sopenharmony_ci ret = if_sdio_power_on(card); 12268c2ecf20Sopenharmony_ci if (ret) 12278c2ecf20Sopenharmony_ci goto err_activate_card; 12288c2ecf20Sopenharmony_ci 12298c2ecf20Sopenharmony_ciout: 12308c2ecf20Sopenharmony_ci return ret; 12318c2ecf20Sopenharmony_ci 12328c2ecf20Sopenharmony_cierr_activate_card: 12338c2ecf20Sopenharmony_ci flush_workqueue(card->workqueue); 12348c2ecf20Sopenharmony_ci lbs_remove_card(priv); 12358c2ecf20Sopenharmony_cifree: 12368c2ecf20Sopenharmony_ci destroy_workqueue(card->workqueue); 12378c2ecf20Sopenharmony_cierr_queue: 12388c2ecf20Sopenharmony_ci while (card->packets) { 12398c2ecf20Sopenharmony_ci packet = card->packets; 12408c2ecf20Sopenharmony_ci card->packets = card->packets->next; 12418c2ecf20Sopenharmony_ci kfree(packet); 12428c2ecf20Sopenharmony_ci } 12438c2ecf20Sopenharmony_ci 12448c2ecf20Sopenharmony_ci kfree(card); 12458c2ecf20Sopenharmony_ci 12468c2ecf20Sopenharmony_ci goto out; 12478c2ecf20Sopenharmony_ci} 12488c2ecf20Sopenharmony_ci 12498c2ecf20Sopenharmony_cistatic void if_sdio_remove(struct sdio_func *func) 12508c2ecf20Sopenharmony_ci{ 12518c2ecf20Sopenharmony_ci struct if_sdio_card *card; 12528c2ecf20Sopenharmony_ci struct if_sdio_packet *packet; 12538c2ecf20Sopenharmony_ci 12548c2ecf20Sopenharmony_ci card = sdio_get_drvdata(func); 12558c2ecf20Sopenharmony_ci 12568c2ecf20Sopenharmony_ci /* Undo decrement done above in if_sdio_probe */ 12578c2ecf20Sopenharmony_ci pm_runtime_get_noresume(&func->dev); 12588c2ecf20Sopenharmony_ci 12598c2ecf20Sopenharmony_ci if (user_rmmod && (card->model == MODEL_8688)) { 12608c2ecf20Sopenharmony_ci /* 12618c2ecf20Sopenharmony_ci * FUNC_SHUTDOWN is required for SD8688 WLAN/BT 12628c2ecf20Sopenharmony_ci * multiple functions 12638c2ecf20Sopenharmony_ci */ 12648c2ecf20Sopenharmony_ci struct cmd_header cmd; 12658c2ecf20Sopenharmony_ci 12668c2ecf20Sopenharmony_ci memset(&cmd, 0, sizeof(cmd)); 12678c2ecf20Sopenharmony_ci 12688c2ecf20Sopenharmony_ci lbs_deb_sdio("send function SHUTDOWN command\n"); 12698c2ecf20Sopenharmony_ci if (__lbs_cmd(card->priv, CMD_FUNC_SHUTDOWN, 12708c2ecf20Sopenharmony_ci &cmd, sizeof(cmd), lbs_cmd_copyback, 12718c2ecf20Sopenharmony_ci (unsigned long) &cmd)) 12728c2ecf20Sopenharmony_ci pr_alert("CMD_FUNC_SHUTDOWN cmd failed\n"); 12738c2ecf20Sopenharmony_ci } 12748c2ecf20Sopenharmony_ci 12758c2ecf20Sopenharmony_ci 12768c2ecf20Sopenharmony_ci lbs_deb_sdio("call remove card\n"); 12778c2ecf20Sopenharmony_ci lbs_stop_card(card->priv); 12788c2ecf20Sopenharmony_ci lbs_remove_card(card->priv); 12798c2ecf20Sopenharmony_ci 12808c2ecf20Sopenharmony_ci destroy_workqueue(card->workqueue); 12818c2ecf20Sopenharmony_ci 12828c2ecf20Sopenharmony_ci while (card->packets) { 12838c2ecf20Sopenharmony_ci packet = card->packets; 12848c2ecf20Sopenharmony_ci card->packets = card->packets->next; 12858c2ecf20Sopenharmony_ci kfree(packet); 12868c2ecf20Sopenharmony_ci } 12878c2ecf20Sopenharmony_ci 12888c2ecf20Sopenharmony_ci kfree(card); 12898c2ecf20Sopenharmony_ci} 12908c2ecf20Sopenharmony_ci 12918c2ecf20Sopenharmony_cistatic int if_sdio_suspend(struct device *dev) 12928c2ecf20Sopenharmony_ci{ 12938c2ecf20Sopenharmony_ci struct sdio_func *func = dev_to_sdio_func(dev); 12948c2ecf20Sopenharmony_ci struct if_sdio_card *card = sdio_get_drvdata(func); 12958c2ecf20Sopenharmony_ci struct lbs_private *priv = card->priv; 12968c2ecf20Sopenharmony_ci int ret; 12978c2ecf20Sopenharmony_ci 12988c2ecf20Sopenharmony_ci mmc_pm_flag_t flags = sdio_get_host_pm_caps(func); 12998c2ecf20Sopenharmony_ci priv->power_up_on_resume = false; 13008c2ecf20Sopenharmony_ci 13018c2ecf20Sopenharmony_ci /* If we're powered off anyway, just let the mmc layer remove the 13028c2ecf20Sopenharmony_ci * card. */ 13038c2ecf20Sopenharmony_ci if (!lbs_iface_active(priv)) { 13048c2ecf20Sopenharmony_ci if (priv->fw_ready) { 13058c2ecf20Sopenharmony_ci priv->power_up_on_resume = true; 13068c2ecf20Sopenharmony_ci if_sdio_power_off(card); 13078c2ecf20Sopenharmony_ci } 13088c2ecf20Sopenharmony_ci 13098c2ecf20Sopenharmony_ci return 0; 13108c2ecf20Sopenharmony_ci } 13118c2ecf20Sopenharmony_ci 13128c2ecf20Sopenharmony_ci dev_info(dev, "%s: suspend: PM flags = 0x%x\n", 13138c2ecf20Sopenharmony_ci sdio_func_id(func), flags); 13148c2ecf20Sopenharmony_ci 13158c2ecf20Sopenharmony_ci /* If we aren't being asked to wake on anything, we should bail out 13168c2ecf20Sopenharmony_ci * and let the SD stack power down the card. 13178c2ecf20Sopenharmony_ci */ 13188c2ecf20Sopenharmony_ci if (priv->wol_criteria == EHS_REMOVE_WAKEUP) { 13198c2ecf20Sopenharmony_ci dev_info(dev, "Suspend without wake params -- powering down card\n"); 13208c2ecf20Sopenharmony_ci if (priv->fw_ready) { 13218c2ecf20Sopenharmony_ci ret = lbs_suspend(priv); 13228c2ecf20Sopenharmony_ci if (ret) 13238c2ecf20Sopenharmony_ci return ret; 13248c2ecf20Sopenharmony_ci 13258c2ecf20Sopenharmony_ci priv->power_up_on_resume = true; 13268c2ecf20Sopenharmony_ci if_sdio_power_off(card); 13278c2ecf20Sopenharmony_ci } 13288c2ecf20Sopenharmony_ci 13298c2ecf20Sopenharmony_ci return 0; 13308c2ecf20Sopenharmony_ci } 13318c2ecf20Sopenharmony_ci 13328c2ecf20Sopenharmony_ci if (!(flags & MMC_PM_KEEP_POWER)) { 13338c2ecf20Sopenharmony_ci dev_err(dev, "%s: cannot remain alive while host is suspended\n", 13348c2ecf20Sopenharmony_ci sdio_func_id(func)); 13358c2ecf20Sopenharmony_ci return -ENOSYS; 13368c2ecf20Sopenharmony_ci } 13378c2ecf20Sopenharmony_ci 13388c2ecf20Sopenharmony_ci ret = sdio_set_host_pm_flags(func, MMC_PM_KEEP_POWER); 13398c2ecf20Sopenharmony_ci if (ret) 13408c2ecf20Sopenharmony_ci return ret; 13418c2ecf20Sopenharmony_ci 13428c2ecf20Sopenharmony_ci ret = lbs_suspend(priv); 13438c2ecf20Sopenharmony_ci if (ret) 13448c2ecf20Sopenharmony_ci return ret; 13458c2ecf20Sopenharmony_ci 13468c2ecf20Sopenharmony_ci return sdio_set_host_pm_flags(func, MMC_PM_WAKE_SDIO_IRQ); 13478c2ecf20Sopenharmony_ci} 13488c2ecf20Sopenharmony_ci 13498c2ecf20Sopenharmony_cistatic int if_sdio_resume(struct device *dev) 13508c2ecf20Sopenharmony_ci{ 13518c2ecf20Sopenharmony_ci struct sdio_func *func = dev_to_sdio_func(dev); 13528c2ecf20Sopenharmony_ci struct if_sdio_card *card = sdio_get_drvdata(func); 13538c2ecf20Sopenharmony_ci int ret; 13548c2ecf20Sopenharmony_ci 13558c2ecf20Sopenharmony_ci dev_info(dev, "%s: resume: we're back\n", sdio_func_id(func)); 13568c2ecf20Sopenharmony_ci 13578c2ecf20Sopenharmony_ci if (card->priv->power_up_on_resume) { 13588c2ecf20Sopenharmony_ci if_sdio_power_on(card); 13598c2ecf20Sopenharmony_ci wait_event(card->pwron_waitq, card->priv->fw_ready); 13608c2ecf20Sopenharmony_ci } 13618c2ecf20Sopenharmony_ci 13628c2ecf20Sopenharmony_ci ret = lbs_resume(card->priv); 13638c2ecf20Sopenharmony_ci 13648c2ecf20Sopenharmony_ci return ret; 13658c2ecf20Sopenharmony_ci} 13668c2ecf20Sopenharmony_ci 13678c2ecf20Sopenharmony_cistatic const struct dev_pm_ops if_sdio_pm_ops = { 13688c2ecf20Sopenharmony_ci .suspend = if_sdio_suspend, 13698c2ecf20Sopenharmony_ci .resume = if_sdio_resume, 13708c2ecf20Sopenharmony_ci}; 13718c2ecf20Sopenharmony_ci 13728c2ecf20Sopenharmony_cistatic struct sdio_driver if_sdio_driver = { 13738c2ecf20Sopenharmony_ci .name = "libertas_sdio", 13748c2ecf20Sopenharmony_ci .id_table = if_sdio_ids, 13758c2ecf20Sopenharmony_ci .probe = if_sdio_probe, 13768c2ecf20Sopenharmony_ci .remove = if_sdio_remove, 13778c2ecf20Sopenharmony_ci .drv = { 13788c2ecf20Sopenharmony_ci .pm = &if_sdio_pm_ops, 13798c2ecf20Sopenharmony_ci }, 13808c2ecf20Sopenharmony_ci}; 13818c2ecf20Sopenharmony_ci 13828c2ecf20Sopenharmony_ci/*******************************************************************/ 13838c2ecf20Sopenharmony_ci/* Module functions */ 13848c2ecf20Sopenharmony_ci/*******************************************************************/ 13858c2ecf20Sopenharmony_ci 13868c2ecf20Sopenharmony_cistatic int __init if_sdio_init_module(void) 13878c2ecf20Sopenharmony_ci{ 13888c2ecf20Sopenharmony_ci int ret = 0; 13898c2ecf20Sopenharmony_ci 13908c2ecf20Sopenharmony_ci printk(KERN_INFO "libertas_sdio: Libertas SDIO driver\n"); 13918c2ecf20Sopenharmony_ci printk(KERN_INFO "libertas_sdio: Copyright Pierre Ossman\n"); 13928c2ecf20Sopenharmony_ci 13938c2ecf20Sopenharmony_ci ret = sdio_register_driver(&if_sdio_driver); 13948c2ecf20Sopenharmony_ci 13958c2ecf20Sopenharmony_ci /* Clear the flag in case user removes the card. */ 13968c2ecf20Sopenharmony_ci user_rmmod = 0; 13978c2ecf20Sopenharmony_ci 13988c2ecf20Sopenharmony_ci return ret; 13998c2ecf20Sopenharmony_ci} 14008c2ecf20Sopenharmony_ci 14018c2ecf20Sopenharmony_cistatic void __exit if_sdio_exit_module(void) 14028c2ecf20Sopenharmony_ci{ 14038c2ecf20Sopenharmony_ci /* Set the flag as user is removing this module. */ 14048c2ecf20Sopenharmony_ci user_rmmod = 1; 14058c2ecf20Sopenharmony_ci 14068c2ecf20Sopenharmony_ci cancel_work_sync(&card_reset_work); 14078c2ecf20Sopenharmony_ci 14088c2ecf20Sopenharmony_ci sdio_unregister_driver(&if_sdio_driver); 14098c2ecf20Sopenharmony_ci} 14108c2ecf20Sopenharmony_ci 14118c2ecf20Sopenharmony_cimodule_init(if_sdio_init_module); 14128c2ecf20Sopenharmony_cimodule_exit(if_sdio_exit_module); 14138c2ecf20Sopenharmony_ci 14148c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Libertas SDIO WLAN Driver"); 14158c2ecf20Sopenharmony_ciMODULE_AUTHOR("Pierre Ossman"); 14168c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 1417