Luciano Coelho | 9a1a699 | 2012-05-10 12:13:06 +0300 | [diff] [blame] | 1 | /* |
| 2 | * This file is part of wl18xx |
| 3 | * |
| 4 | * Copyright (C) 2011 Texas Instruments |
| 5 | * |
| 6 | * This program is free software; you can redistribute it and/or |
| 7 | * modify it under the terms of the GNU General Public License |
| 8 | * version 2 as published by the Free Software Foundation. |
| 9 | * |
| 10 | * This program is distributed in the hope that it will be useful, but |
| 11 | * WITHOUT ANY WARRANTY; without even the implied warranty of |
| 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 13 | * General Public License for more details. |
| 14 | * |
| 15 | * You should have received a copy of the GNU General Public License |
| 16 | * along with this program; if not, write to the Free Software |
| 17 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA |
| 18 | * 02110-1301 USA |
| 19 | * |
| 20 | */ |
| 21 | |
| 22 | #include <linux/module.h> |
| 23 | #include <linux/platform_device.h> |
| 24 | |
| 25 | #include "../wlcore/wlcore.h" |
| 26 | #include "../wlcore/debug.h" |
Luciano Coelho | 46a1d51 | 2012-05-10 12:13:12 +0300 | [diff] [blame] | 27 | #include "../wlcore/io.h" |
| 28 | #include "../wlcore/acx.h" |
| 29 | #include "../wlcore/boot.h" |
Luciano Coelho | 9a1a699 | 2012-05-10 12:13:06 +0300 | [diff] [blame] | 30 | |
Luciano Coelho | 5d4a9fa | 2012-05-10 12:13:10 +0300 | [diff] [blame] | 31 | #include "reg.h" |
Luciano Coelho | 46a1d51 | 2012-05-10 12:13:12 +0300 | [diff] [blame] | 32 | #include "conf.h" |
Luciano Coelho | 274c66c | 2012-05-10 12:13:13 +0300 | [diff] [blame^] | 33 | #include "wl18xx.h" |
Luciano Coelho | 46a1d51 | 2012-05-10 12:13:12 +0300 | [diff] [blame] | 34 | |
| 35 | static struct wl18xx_conf wl18xx_default_conf = { |
| 36 | .phy = { |
| 37 | .phy_standalone = 0x00, |
| 38 | .primary_clock_setting_time = 0x05, |
| 39 | .clock_valid_on_wake_up = 0x00, |
| 40 | .secondary_clock_setting_time = 0x05, |
| 41 | .rdl = 0x01, |
| 42 | .auto_detect = 0x00, |
| 43 | .dedicated_fem = FEM_NONE, |
| 44 | .low_band_component = COMPONENT_2_WAY_SWITCH, |
| 45 | .low_band_component_type = 0x05, |
| 46 | .high_band_component = COMPONENT_2_WAY_SWITCH, |
| 47 | .high_band_component_type = 0x09, |
| 48 | .number_of_assembled_ant2_4 = 0x01, |
| 49 | .number_of_assembled_ant5 = 0x01, |
| 50 | .external_pa_dc2dc = 0x00, |
| 51 | .tcxo_ldo_voltage = 0x00, |
| 52 | .xtal_itrim_val = 0x04, |
| 53 | .srf_state = 0x00, |
| 54 | .io_configuration = 0x01, |
| 55 | .sdio_configuration = 0x00, |
| 56 | .settings = 0x00, |
| 57 | .enable_clpc = 0x00, |
| 58 | .enable_tx_low_pwr_on_siso_rdl = 0x00, |
| 59 | .rx_profile = 0x00, |
| 60 | }, |
| 61 | }; |
Luciano Coelho | 5d4a9fa | 2012-05-10 12:13:10 +0300 | [diff] [blame] | 62 | |
Luciano Coelho | 82b890c | 2012-05-10 12:13:09 +0300 | [diff] [blame] | 63 | static const struct wlcore_partition_set wl18xx_ptable[PART_TABLE_LEN] = { |
| 64 | [PART_TOP_PRCM_ELP_SOC] = { |
| 65 | .mem = { .start = 0x00A02000, .size = 0x00010000 }, |
| 66 | .reg = { .start = 0x00807000, .size = 0x00005000 }, |
| 67 | .mem2 = { .start = 0x00800000, .size = 0x0000B000 }, |
| 68 | .mem3 = { .start = 0x00000000, .size = 0x00000000 }, |
| 69 | }, |
| 70 | [PART_DOWN] = { |
| 71 | .mem = { .start = 0x00000000, .size = 0x00014000 }, |
| 72 | .reg = { .start = 0x00810000, .size = 0x0000BFFF }, |
| 73 | .mem2 = { .start = 0x00000000, .size = 0x00000000 }, |
| 74 | .mem3 = { .start = 0x00000000, .size = 0x00000000 }, |
| 75 | }, |
| 76 | [PART_BOOT] = { |
| 77 | .mem = { .start = 0x00700000, .size = 0x0000030c }, |
| 78 | .reg = { .start = 0x00802000, .size = 0x00014578 }, |
| 79 | .mem2 = { .start = 0x00B00404, .size = 0x00001000 }, |
| 80 | .mem3 = { .start = 0x00C00000, .size = 0x00000400 }, |
| 81 | }, |
| 82 | [PART_WORK] = { |
| 83 | .mem = { .start = 0x00800000, .size = 0x000050FC }, |
| 84 | .reg = { .start = 0x00B00404, .size = 0x00001000 }, |
| 85 | .mem2 = { .start = 0x00C00000, .size = 0x00000400 }, |
| 86 | .mem3 = { .start = 0x00000000, .size = 0x00000000 }, |
| 87 | }, |
| 88 | [PART_PHY_INIT] = { |
| 89 | /* TODO: use the phy_conf struct size here */ |
| 90 | .mem = { .start = 0x80926000, .size = 252 }, |
| 91 | .reg = { .start = 0x00000000, .size = 0x00000000 }, |
| 92 | .mem2 = { .start = 0x00000000, .size = 0x00000000 }, |
| 93 | .mem3 = { .start = 0x00000000, .size = 0x00000000 }, |
| 94 | }, |
| 95 | }; |
| 96 | |
Luciano Coelho | 5d4a9fa | 2012-05-10 12:13:10 +0300 | [diff] [blame] | 97 | static const int wl18xx_rtable[REG_TABLE_LEN] = { |
| 98 | [REG_ECPU_CONTROL] = WL18XX_REG_ECPU_CONTROL, |
| 99 | [REG_INTERRUPT_NO_CLEAR] = WL18XX_REG_INTERRUPT_NO_CLEAR, |
| 100 | [REG_INTERRUPT_ACK] = WL18XX_REG_INTERRUPT_ACK, |
| 101 | [REG_COMMAND_MAILBOX_PTR] = WL18XX_REG_COMMAND_MAILBOX_PTR, |
| 102 | [REG_EVENT_MAILBOX_PTR] = WL18XX_REG_EVENT_MAILBOX_PTR, |
| 103 | [REG_INTERRUPT_TRIG] = WL18XX_REG_INTERRUPT_TRIG_H, |
| 104 | [REG_INTERRUPT_MASK] = WL18XX_REG_INTERRUPT_MASK, |
| 105 | [REG_PC_ON_RECOVERY] = 0, /* TODO: where is the PC? */ |
| 106 | [REG_CHIP_ID_B] = WL18XX_REG_CHIP_ID_B, |
| 107 | [REG_CMD_MBOX_ADDRESS] = WL18XX_CMD_MBOX_ADDRESS, |
| 108 | |
| 109 | /* data access memory addresses, used with partition translation */ |
| 110 | [REG_SLV_MEM_DATA] = WL18XX_SLV_MEM_DATA, |
| 111 | [REG_SLV_REG_DATA] = WL18XX_SLV_REG_DATA, |
| 112 | |
| 113 | /* raw data access memory addresses */ |
| 114 | [REG_RAW_FW_STATUS_ADDR] = WL18XX_FW_STATUS_ADDR, |
| 115 | }; |
| 116 | |
Luciano Coelho | 0cd6543 | 2012-05-10 12:13:11 +0300 | [diff] [blame] | 117 | /* TODO: maybe move to a new header file? */ |
| 118 | #define WL18XX_FW_NAME "ti-connectivity/wl18xx-fw.bin" |
| 119 | |
| 120 | static int wl18xx_identify_chip(struct wl1271 *wl) |
| 121 | { |
| 122 | int ret = 0; |
| 123 | |
| 124 | switch (wl->chip.id) { |
| 125 | case CHIP_ID_185x_PG10: |
| 126 | wl1271_debug(DEBUG_BOOT, "chip id 0x%x (185x PG10)", |
| 127 | wl->chip.id); |
| 128 | wl->sr_fw_name = WL18XX_FW_NAME; |
| 129 | wl->quirks |= WLCORE_QUIRK_NO_ELP; |
| 130 | |
| 131 | /* TODO: need to blocksize alignment for RX/TX separately? */ |
| 132 | break; |
| 133 | default: |
| 134 | wl1271_warning("unsupported chip id: 0x%x", wl->chip.id); |
| 135 | ret = -ENODEV; |
| 136 | goto out; |
| 137 | } |
| 138 | |
| 139 | out: |
| 140 | return ret; |
| 141 | } |
| 142 | |
Luciano Coelho | 46a1d51 | 2012-05-10 12:13:12 +0300 | [diff] [blame] | 143 | static void wl18xx_set_clk(struct wl1271 *wl) |
| 144 | { |
| 145 | /* |
| 146 | * TODO: this is hardcoded just for DVP/EVB, fix according to |
| 147 | * new unified_drv. |
| 148 | */ |
| 149 | wl1271_write32(wl, WL18XX_SCR_PAD2, 0xB3); |
| 150 | |
| 151 | wlcore_set_partition(wl, &wl->ptable[PART_TOP_PRCM_ELP_SOC]); |
| 152 | wl1271_write32(wl, 0x00A02360, 0xD0078); |
| 153 | wl1271_write32(wl, 0x00A0236c, 0x12); |
| 154 | wl1271_write32(wl, 0x00A02390, 0x20118); |
| 155 | } |
| 156 | |
| 157 | static void wl18xx_boot_soft_reset(struct wl1271 *wl) |
| 158 | { |
| 159 | /* disable Rx/Tx */ |
| 160 | wl1271_write32(wl, WL18XX_ENABLE, 0x0); |
| 161 | |
| 162 | /* disable auto calibration on start*/ |
| 163 | wl1271_write32(wl, WL18XX_SPARE_A2, 0xffff); |
| 164 | } |
| 165 | |
| 166 | static int wl18xx_pre_boot(struct wl1271 *wl) |
| 167 | { |
| 168 | /* TODO: add hw_pg_ver reading */ |
| 169 | |
| 170 | wl18xx_set_clk(wl); |
| 171 | |
| 172 | /* Continue the ELP wake up sequence */ |
| 173 | wl1271_write32(wl, WL18XX_WELP_ARM_COMMAND, WELP_ARM_COMMAND_VAL); |
| 174 | udelay(500); |
| 175 | |
| 176 | wlcore_set_partition(wl, &wl->ptable[PART_BOOT]); |
| 177 | |
| 178 | /* Disable interrupts */ |
| 179 | wlcore_write_reg(wl, REG_INTERRUPT_MASK, WL1271_ACX_INTR_ALL); |
| 180 | |
| 181 | wl18xx_boot_soft_reset(wl); |
| 182 | |
| 183 | return 0; |
| 184 | } |
| 185 | |
| 186 | static void wl18xx_pre_upload(struct wl1271 *wl) |
| 187 | { |
| 188 | u32 tmp; |
| 189 | |
| 190 | wlcore_set_partition(wl, &wl->ptable[PART_BOOT]); |
| 191 | |
| 192 | /* TODO: check if this is all needed */ |
| 193 | wl1271_write32(wl, WL18XX_EEPROMLESS_IND, WL18XX_EEPROMLESS_IND); |
| 194 | |
| 195 | tmp = wlcore_read_reg(wl, REG_CHIP_ID_B); |
| 196 | |
| 197 | wl1271_debug(DEBUG_BOOT, "chip id 0x%x", tmp); |
| 198 | |
| 199 | tmp = wl1271_read32(wl, WL18XX_SCR_PAD2); |
| 200 | } |
| 201 | |
| 202 | static void wl18xx_set_mac_and_phy(struct wl1271 *wl) |
| 203 | { |
| 204 | struct wl18xx_mac_and_phy_params params; |
| 205 | |
| 206 | memset(¶ms, 0, sizeof(params)); |
| 207 | |
| 208 | params.phy_standalone = wl18xx_default_conf.phy.phy_standalone; |
| 209 | params.rdl = wl18xx_default_conf.phy.rdl; |
| 210 | params.enable_clpc = wl18xx_default_conf.phy.enable_clpc; |
| 211 | params.enable_tx_low_pwr_on_siso_rdl = |
| 212 | wl18xx_default_conf.phy.enable_tx_low_pwr_on_siso_rdl; |
| 213 | params.auto_detect = wl18xx_default_conf.phy.auto_detect; |
| 214 | params.dedicated_fem = wl18xx_default_conf.phy.dedicated_fem; |
| 215 | params.low_band_component = wl18xx_default_conf.phy.low_band_component; |
| 216 | params.low_band_component_type = |
| 217 | wl18xx_default_conf.phy.low_band_component_type; |
| 218 | params.high_band_component = |
| 219 | wl18xx_default_conf.phy.high_band_component; |
| 220 | params.high_band_component_type = |
| 221 | wl18xx_default_conf.phy.high_band_component_type; |
| 222 | params.number_of_assembled_ant2_4 = |
| 223 | wl18xx_default_conf.phy.number_of_assembled_ant2_4; |
| 224 | params.number_of_assembled_ant5 = |
| 225 | wl18xx_default_conf.phy.number_of_assembled_ant5; |
| 226 | params.external_pa_dc2dc = wl18xx_default_conf.phy.external_pa_dc2dc; |
| 227 | params.tcxo_ldo_voltage = wl18xx_default_conf.phy.tcxo_ldo_voltage; |
| 228 | params.xtal_itrim_val = wl18xx_default_conf.phy.xtal_itrim_val; |
| 229 | params.srf_state = wl18xx_default_conf.phy.srf_state; |
| 230 | params.io_configuration = wl18xx_default_conf.phy.io_configuration; |
| 231 | params.sdio_configuration = wl18xx_default_conf.phy.sdio_configuration; |
| 232 | params.settings = wl18xx_default_conf.phy.settings; |
| 233 | params.rx_profile = wl18xx_default_conf.phy.rx_profile; |
| 234 | params.primary_clock_setting_time = |
| 235 | wl18xx_default_conf.phy.primary_clock_setting_time; |
| 236 | params.clock_valid_on_wake_up = |
| 237 | wl18xx_default_conf.phy.clock_valid_on_wake_up; |
| 238 | params.secondary_clock_setting_time = |
| 239 | wl18xx_default_conf.phy.secondary_clock_setting_time; |
| 240 | |
| 241 | /* TODO: hardcoded for now */ |
| 242 | params.board_type = BOARD_TYPE_DVP_EVB_18XX; |
| 243 | |
| 244 | wlcore_set_partition(wl, &wl->ptable[PART_PHY_INIT]); |
| 245 | wl1271_write(wl, WL18XX_PHY_INIT_MEM_ADDR, (u8 *)¶ms, |
| 246 | sizeof(params), false); |
| 247 | } |
| 248 | |
| 249 | static void wl18xx_enable_interrupts(struct wl1271 *wl) |
| 250 | { |
| 251 | wlcore_write_reg(wl, REG_INTERRUPT_MASK, WL1271_ACX_ALL_EVENTS_VECTOR); |
| 252 | |
| 253 | wlcore_enable_interrupts(wl); |
| 254 | wlcore_write_reg(wl, REG_INTERRUPT_MASK, |
| 255 | WL1271_ACX_INTR_ALL & ~(WL1271_INTR_MASK)); |
| 256 | } |
| 257 | |
| 258 | static int wl18xx_boot(struct wl1271 *wl) |
| 259 | { |
| 260 | int ret; |
| 261 | |
| 262 | ret = wl18xx_pre_boot(wl); |
| 263 | if (ret < 0) |
| 264 | goto out; |
| 265 | |
| 266 | ret = wlcore_boot_upload_nvs(wl); |
| 267 | if (ret < 0) |
| 268 | goto out; |
| 269 | |
| 270 | wl18xx_pre_upload(wl); |
| 271 | |
| 272 | ret = wlcore_boot_upload_firmware(wl); |
| 273 | if (ret < 0) |
| 274 | goto out; |
| 275 | |
| 276 | wl18xx_set_mac_and_phy(wl); |
| 277 | |
| 278 | ret = wlcore_boot_run_firmware(wl); |
| 279 | if (ret < 0) |
| 280 | goto out; |
| 281 | |
| 282 | wl18xx_enable_interrupts(wl); |
| 283 | |
| 284 | out: |
| 285 | return ret; |
| 286 | } |
| 287 | |
Luciano Coelho | 274c66c | 2012-05-10 12:13:13 +0300 | [diff] [blame^] | 288 | static void wl18xx_trigger_cmd(struct wl1271 *wl, int cmd_box_addr, |
| 289 | void *buf, size_t len) |
| 290 | { |
| 291 | struct wl18xx_priv *priv = wl->priv; |
| 292 | |
| 293 | memcpy(priv->cmd_buf, buf, len); |
| 294 | memset(priv->cmd_buf + len, 0, WL18XX_CMD_MAX_SIZE - len); |
| 295 | |
| 296 | wl1271_write(wl, cmd_box_addr, priv->cmd_buf, WL18XX_CMD_MAX_SIZE, |
| 297 | false); |
| 298 | } |
| 299 | |
| 300 | static void wl18xx_ack_event(struct wl1271 *wl) |
| 301 | { |
| 302 | wlcore_write_reg(wl, REG_INTERRUPT_TRIG, WL18XX_INTR_TRIG_EVENT_ACK); |
| 303 | } |
| 304 | |
Luciano Coelho | 0cd6543 | 2012-05-10 12:13:11 +0300 | [diff] [blame] | 305 | static struct wlcore_ops wl18xx_ops = { |
Luciano Coelho | 46a1d51 | 2012-05-10 12:13:12 +0300 | [diff] [blame] | 306 | .identify_chip = wl18xx_identify_chip, |
| 307 | .boot = wl18xx_boot, |
Luciano Coelho | 274c66c | 2012-05-10 12:13:13 +0300 | [diff] [blame^] | 308 | .trigger_cmd = wl18xx_trigger_cmd, |
| 309 | .ack_event = wl18xx_ack_event, |
Luciano Coelho | 0cd6543 | 2012-05-10 12:13:11 +0300 | [diff] [blame] | 310 | }; |
| 311 | |
Luciano Coelho | 9a1a699 | 2012-05-10 12:13:06 +0300 | [diff] [blame] | 312 | int __devinit wl18xx_probe(struct platform_device *pdev) |
| 313 | { |
| 314 | struct wl1271 *wl; |
| 315 | struct ieee80211_hw *hw; |
| 316 | |
| 317 | hw = wlcore_alloc_hw(0); |
| 318 | if (IS_ERR(hw)) { |
| 319 | wl1271_error("can't allocate hw"); |
| 320 | return PTR_ERR(hw); |
| 321 | } |
| 322 | |
| 323 | wl = hw->priv; |
Luciano Coelho | 554c36b | 2012-05-10 12:13:08 +0300 | [diff] [blame] | 324 | wl->ops = &wl18xx_ops; |
Luciano Coelho | 82b890c | 2012-05-10 12:13:09 +0300 | [diff] [blame] | 325 | wl->ptable = wl18xx_ptable; |
Luciano Coelho | 5d4a9fa | 2012-05-10 12:13:10 +0300 | [diff] [blame] | 326 | wl->rtable = wl18xx_rtable; |
Luciano Coelho | 9a1a699 | 2012-05-10 12:13:06 +0300 | [diff] [blame] | 327 | |
| 328 | return wlcore_probe(wl, pdev); |
| 329 | } |
| 330 | |
| 331 | static const struct platform_device_id wl18xx_id_table[] __devinitconst = { |
| 332 | { "wl18xx", 0 }, |
| 333 | { } /* Terminating Entry */ |
| 334 | }; |
| 335 | MODULE_DEVICE_TABLE(platform, wl18xx_id_table); |
| 336 | |
| 337 | static struct platform_driver wl18xx_driver = { |
| 338 | .probe = wl18xx_probe, |
| 339 | .remove = __devexit_p(wlcore_remove), |
| 340 | .id_table = wl18xx_id_table, |
| 341 | .driver = { |
| 342 | .name = "wl18xx_driver", |
| 343 | .owner = THIS_MODULE, |
| 344 | } |
| 345 | }; |
| 346 | |
| 347 | static int __init wl18xx_init(void) |
| 348 | { |
| 349 | return platform_driver_register(&wl18xx_driver); |
| 350 | } |
| 351 | module_init(wl18xx_init); |
| 352 | |
| 353 | static void __exit wl18xx_exit(void) |
| 354 | { |
| 355 | platform_driver_unregister(&wl18xx_driver); |
| 356 | } |
| 357 | module_exit(wl18xx_exit); |
| 358 | |
| 359 | MODULE_LICENSE("GPL v2"); |
| 360 | MODULE_AUTHOR("Luciano Coelho <coelho@ti.com>"); |
Luciano Coelho | 0cd6543 | 2012-05-10 12:13:11 +0300 | [diff] [blame] | 361 | MODULE_FIRMWARE(WL18XX_FW_NAME); |