/* * Copyright (C) 2011 Google, Inc. * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "hsmmc.h" #include "control.h" #include "mux.h" #include "board-tuna.h" #define GPIO_WLAN_PMENA 104 #define GPIO_WLAN_IRQ 2 #define ATAG_TUNA_MAC 0x57464d41 /* #define ATAG_TUNA_MAC_DEBUG */ #ifdef CONFIG_DHD_USE_STATIC_BUF #define PREALLOC_WLAN_NUMBER_OF_SECTIONS 4 #define PREALLOC_WLAN_NUMBER_OF_BUFFERS 160 #define PREALLOC_WLAN_SECTION_HEADER 24 #define WLAN_SECTION_SIZE_0 (PREALLOC_WLAN_NUMBER_OF_BUFFERS * 128) #define WLAN_SECTION_SIZE_1 (PREALLOC_WLAN_NUMBER_OF_BUFFERS * 128) #define WLAN_SECTION_SIZE_2 (PREALLOC_WLAN_NUMBER_OF_BUFFERS * 512) #define WLAN_SECTION_SIZE_3 (PREALLOC_WLAN_NUMBER_OF_BUFFERS * 1024) #define WLAN_SKB_BUF_NUM 16 static struct sk_buff *wlan_static_skb[WLAN_SKB_BUF_NUM]; typedef struct wifi_mem_prealloc_struct { void *mem_ptr; unsigned long size; } wifi_mem_prealloc_t; static wifi_mem_prealloc_t wifi_mem_array[PREALLOC_WLAN_NUMBER_OF_SECTIONS] = { { NULL, (WLAN_SECTION_SIZE_0 + PREALLOC_WLAN_SECTION_HEADER) }, { NULL, (WLAN_SECTION_SIZE_1 + PREALLOC_WLAN_SECTION_HEADER) }, { NULL, (WLAN_SECTION_SIZE_2 + PREALLOC_WLAN_SECTION_HEADER) }, { NULL, (WLAN_SECTION_SIZE_3 + PREALLOC_WLAN_SECTION_HEADER) } }; static void *tuna_wifi_mem_prealloc(int section, unsigned long size) { if (section == PREALLOC_WLAN_NUMBER_OF_SECTIONS) return wlan_static_skb; if ((section < 0) || (section > PREALLOC_WLAN_NUMBER_OF_SECTIONS)) return NULL; if (wifi_mem_array[section].size < size) return NULL; return wifi_mem_array[section].mem_ptr; } #endif int __init tuna_init_wifi_mem(void) { #ifdef CONFIG_DHD_USE_STATIC_BUF int i; for(i=0;( i < WLAN_SKB_BUF_NUM );i++) { if (i < (WLAN_SKB_BUF_NUM/2)) wlan_static_skb[i] = dev_alloc_skb(4096); else wlan_static_skb[i] = dev_alloc_skb(8192); } for(i=0;( i < PREALLOC_WLAN_NUMBER_OF_SECTIONS );i++) { wifi_mem_array[i].mem_ptr = kmalloc(wifi_mem_array[i].size, GFP_KERNEL); if (wifi_mem_array[i].mem_ptr == NULL) return -ENOMEM; } #endif return 0; } static struct resource tuna_wifi_resources[] = { [0] = { .name = "bcmdhd_wlan_irq", .start = OMAP_GPIO_IRQ(GPIO_WLAN_IRQ), .end = OMAP_GPIO_IRQ(GPIO_WLAN_IRQ), .flags = IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHLEVEL | IORESOURCE_IRQ_SHAREABLE, }, }; #if 0 /* BCM4329 returns wrong sdio_vsn(1) when we read cccr, * we use predefined value (sdio_vsn=2) here to initial sdio driver well */ static struct embedded_sdio_data tuna_wifi_emb_data = { .cccr = { .sdio_vsn = 2, .multi_block = 1, .low_speed = 0, .wide_bus = 0, .high_power = 1, .high_speed = 1, }, }; #endif static int tuna_wifi_cd = 0; /* WIFI virtual 'card detect' status */ static void (*wifi_status_cb)(int card_present, void *dev_id); static void *wifi_status_cb_devid; static struct regulator *clk32kaudio_reg; static int tuna_wifi_status_register( void (*callback)(int card_present, void *dev_id), void *dev_id) { if (wifi_status_cb) return -EAGAIN; wifi_status_cb = callback; wifi_status_cb_devid = dev_id; return 0; } static unsigned int tuna_wifi_status(struct device *dev) { return tuna_wifi_cd; } struct mmc_platform_data tuna_wifi_data = { .ocr_mask = MMC_VDD_165_195 | MMC_VDD_20_21, .built_in = 1, .status = tuna_wifi_status, .card_present = 0, .register_status_notify = tuna_wifi_status_register, }; static int tuna_wifi_set_carddetect(int val) { pr_debug("%s: %d\n", __func__, val); tuna_wifi_cd = val; if (wifi_status_cb) { wifi_status_cb(val, wifi_status_cb_devid); } else pr_warning("%s: Nobody to notify\n", __func__); return 0; } static int tuna_wifi_power_state; struct fixed_voltage_data { struct regulator_desc desc; struct regulator_dev *dev; int microvolts; int gpio; unsigned startup_delay; bool enable_high; bool is_enabled; }; static struct regulator_consumer_supply tuna_vmmc5_supply = { .supply = "vmmc", .dev_name = "omap_hsmmc.4", }; static struct regulator_init_data tuna_vmmc5 = { .constraints = { .valid_ops_mask = REGULATOR_CHANGE_STATUS, }, .num_consumer_supplies = 1, .consumer_supplies = &tuna_vmmc5_supply, }; static struct fixed_voltage_config tuna_vwlan = { .supply_name = "vwl1271", .microvolts = 2000000, /* 2.0V */ .gpio = GPIO_WLAN_PMENA, .startup_delay = 70000, /* 70msec */ .enable_high = 1, .enabled_at_boot = 0, .init_data = &tuna_vmmc5, }; static struct platform_device omap_vwlan_device = { .name = "reg-fixed-voltage", .id = 1, .dev = { .platform_data = &tuna_vwlan, }, }; static int tuna_wifi_power(int on) { int ret = 0; pr_debug("%s: %d\n", __func__, on); if (!clk32kaudio_reg) { clk32kaudio_reg = regulator_get(0, "clk32kaudio"); if (IS_ERR(clk32kaudio_reg)) { pr_err("%s: clk32kaudio reg not found!\n", __func__); clk32kaudio_reg = NULL; } } if (clk32kaudio_reg && on && !tuna_wifi_power_state) { ret = regulator_enable(clk32kaudio_reg); if (ret) pr_err("%s: regulator_enable failed: %d\n", __func__, ret); } msleep(300); gpio_set_value(GPIO_WLAN_PMENA, on); msleep(200); if (clk32kaudio_reg && !on && tuna_wifi_power_state) { ret = regulator_disable(clk32kaudio_reg); if (ret) pr_err("%s: regulator_disable failed: %d\n", __func__, ret); } tuna_wifi_power_state = on; return ret; } static int tuna_wifi_reset_state; static int tuna_wifi_reset(int on) { pr_debug("%s: do nothing\n", __func__); tuna_wifi_reset_state = on; return 0; } static unsigned char tuna_mac_addr[IFHWADDRLEN] = { 0,0x90,0x4c,0,0,0 }; static int __init tuna_mac_addr_setup(char *str) { char macstr[IFHWADDRLEN*3]; char *macptr = macstr; char *token; int i = 0; if (!str) return 0; pr_debug("wlan MAC = %s\n", str); if (strlen(str) >= sizeof(macstr)) return 0; strcpy(macstr, str); while ((token = strsep(&macptr, ":")) != NULL) { unsigned long val; int res; if (i >= IFHWADDRLEN) break; res = strict_strtoul(token, 0x10, &val); if (res < 0) return 0; tuna_mac_addr[i++] = (u8)val; } return 1; } __setup("androidboot.macaddr=", tuna_mac_addr_setup); static int tuna_wifi_get_mac_addr(unsigned char *buf) { int type = omap4_tuna_get_type(); uint rand_mac; if (type != TUNA_TYPE_TORO) return -EINVAL; if (!buf) return -EFAULT; if ((tuna_mac_addr[4] == 0) && (tuna_mac_addr[5] == 0)) { srandom32((uint)jiffies); rand_mac = random32(); tuna_mac_addr[3] = (unsigned char)rand_mac; tuna_mac_addr[4] = (unsigned char)(rand_mac >> 8); tuna_mac_addr[5] = (unsigned char)(rand_mac >> 16); } memcpy(buf, tuna_mac_addr, IFHWADDRLEN); return 0; } /* Customized Locale table : OPTIONAL feature */ #define WLC_CNTRY_BUF_SZ 4 typedef struct cntry_locales_custom { char iso_abbrev[WLC_CNTRY_BUF_SZ]; char custom_locale[WLC_CNTRY_BUF_SZ]; int custom_locale_rev; } cntry_locales_custom_t; static cntry_locales_custom_t tuna_wifi_translate_custom_table[] = { /* Table should be filled out based on custom platform regulatory requirement */ {"", "XY", 4}, /* universal */ {"US", "US", 69}, /* input ISO "US" to : US regrev 69 */ {"CA", "US", 69}, /* input ISO "CA" to : US regrev 69 */ {"EU", "EU", 5}, /* European union countries */ {"AT", "EU", 5}, {"BE", "EU", 5}, {"BG", "EU", 5}, {"CY", "EU", 5}, {"CZ", "EU", 5}, {"DK", "EU", 5}, {"EE", "EU", 5}, {"FI", "EU", 5}, {"FR", "EU", 5}, {"DE", "EU", 5}, {"GR", "EU", 5}, {"HU", "EU", 5}, {"IE", "EU", 5}, {"IT", "EU", 5}, {"LV", "EU", 5}, {"LI", "EU", 5}, {"LT", "EU", 5}, {"LU", "EU", 5}, {"MT", "EU", 5}, {"NL", "EU", 5}, {"PL", "EU", 5}, {"PT", "EU", 5}, {"RO", "EU", 5}, {"SK", "EU", 5}, {"SI", "EU", 5}, {"ES", "EU", 5}, {"SE", "EU", 5}, {"GB", "EU", 5}, /* input ISO "GB" to : EU regrev 05 */ {"IL", "IL", 0}, {"CH", "CH", 0}, {"TR", "TR", 0}, {"NO", "NO", 0}, {"KR", "KR", 25}, {"AU", "XY", 3}, {"CN", "CN", 0}, {"TW", "XY", 3}, {"AR", "XY", 3}, {"MX", "XY", 3}, {"JP", "EU", 0}, {"BR", "KR", 25} }; static void *tuna_wifi_get_country_code(char *ccode) { int size = ARRAY_SIZE(tuna_wifi_translate_custom_table); int i; if (!ccode) return NULL; for (i = 0; i < size; i++) if (strcmp(ccode, tuna_wifi_translate_custom_table[i].iso_abbrev) == 0) return &tuna_wifi_translate_custom_table[i]; return &tuna_wifi_translate_custom_table[0]; } static struct wifi_platform_data tuna_wifi_control = { .set_power = tuna_wifi_power, .set_reset = tuna_wifi_reset, .set_carddetect = tuna_wifi_set_carddetect, #ifdef CONFIG_DHD_USE_STATIC_BUF .mem_prealloc = tuna_wifi_mem_prealloc, #else .mem_prealloc = NULL, #endif .get_mac_addr = tuna_wifi_get_mac_addr, .get_country_code = tuna_wifi_get_country_code, }; static struct platform_device tuna_wifi_device = { .name = "bcmdhd_wlan", .id = 1, .num_resources = ARRAY_SIZE(tuna_wifi_resources), .resource = tuna_wifi_resources, .dev = { .platform_data = &tuna_wifi_control, }, }; static void __init tuna_wlan_gpio(void) { pr_debug("%s: start\n", __func__); /* WLAN SDIO: MMC5 CMD */ omap_mux_init_signal("sdmmc5_cmd", OMAP_PIN_INPUT_PULLUP); /* WLAN SDIO: MMC5 CLK */ omap_mux_init_signal("sdmmc5_clk", OMAP_PIN_INPUT_PULLUP); /* WLAN SDIO: MMC5 DAT[0-3] */ omap_mux_init_signal("sdmmc5_dat0", OMAP_PIN_INPUT_PULLUP); omap_mux_init_signal("sdmmc5_dat1", OMAP_PIN_INPUT_PULLUP); omap_mux_init_signal("sdmmc5_dat2", OMAP_PIN_INPUT_PULLUP); omap_mux_init_signal("sdmmc5_dat3", OMAP_PIN_INPUT_PULLUP); /* WLAN OOB - BCM4330 - GPIO 16 or GPIO 2 */ omap_mux_init_signal("sim_reset.gpio_wk2", OMAP_PIN_INPUT); omap_mux_init_signal("kpd_row1.safe_mode", 0); /* WLAN PMENA - GPIO 104 */ omap_mux_init_signal("gpmc_ncs7.gpio_104", OMAP_PIN_OUTPUT); /* Enable power to gpio_wk0-gpio_wk2 */ omap4_ctrl_wk_pad_writel(0xb0000000, OMAP4_CTRL_MODULE_PAD_WKUP_CONTROL_USIMIO); /* gpio_enable(GPIO_WLAN_IRQ); */ gpio_request(GPIO_WLAN_IRQ, "wlan_irq"); gpio_direction_input(GPIO_WLAN_IRQ); } int __init tuna_wlan_init(void) { pr_debug("%s: start\n", __func__); tuna_wlan_gpio(); tuna_init_wifi_mem(); platform_device_register(&omap_vwlan_device); return platform_device_register(&tuna_wifi_device); }