1 /* 2 * Copyright 2008 Sun Microsystems, Inc. All rights reserved. 3 * Use is subject to license terms. 4 */ 5 6 /* 7 * Copyright (c) 2004 8 * Damien Bergamini <damien.bergamini@free.fr>. All rights reserved. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice unmodified, this list of conditions, and the following 15 * disclaimer. 16 * 2. Redistributions in binary form must reproduce the above copyright 17 * notice, this list of conditions and the following disclaimer in the 18 * documentation and/or other materials provided with the distribution. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 * SUCH DAMAGE. 31 */ 32 33 #pragma ident "%Z%%M% %I% %E% SMI" 34 35 /* 36 * Intel Wireless PRO/2100 mini-PCI adapter driver 37 * ipw2100_hw.c is used to handle hardware operation and firmware operations. 38 */ 39 #include <sys/types.h> 40 #include <sys/byteorder.h> 41 #include <sys/ddi.h> 42 #include <sys/sunddi.h> 43 #include <sys/note.h> 44 #include <sys/stream.h> 45 #include <sys/strsun.h> 46 47 #include "ipw2100.h" 48 #include "ipw2100_impl.h" 49 50 /* 51 * Hardware related operations 52 */ 53 #define IPW2100_EEPROM_SHIFT_D (2) 54 #define IPW2100_EEPROM_SHIFT_Q (4) 55 56 #define IPW2100_EEPROM_C (1 << 0) 57 #define IPW2100_EEPROM_S (1 << 1) 58 #define IPW2100_EEPROM_D (1 << IPW2100_EEPROM_SHIFT_D) 59 #define IPW2100_EEPROM_Q (1 << IPW2100_EEPROM_SHIFT_Q) 60 61 uint8_t 62 ipw2100_csr_get8(struct ipw2100_softc *sc, uint32_t off) 63 { 64 return (ddi_get8(sc->sc_ioh, (uint8_t *)(sc->sc_regs + off))); 65 } 66 67 uint16_t 68 ipw2100_csr_get16(struct ipw2100_softc *sc, uint32_t off) 69 { 70 return (ddi_get16(sc->sc_ioh, 71 (uint16_t *)((uintptr_t)sc->sc_regs + off))); 72 } 73 74 uint32_t 75 ipw2100_csr_get32(struct ipw2100_softc *sc, uint32_t off) 76 { 77 return (ddi_get32(sc->sc_ioh, 78 (uint32_t *)((uintptr_t)sc->sc_regs + off))); 79 } 80 81 void 82 ipw2100_csr_rep_get16(struct ipw2100_softc *sc, 83 uint32_t off, uint16_t *buf, size_t cnt) 84 { 85 ddi_rep_get16(sc->sc_ioh, buf, 86 (uint16_t *)((uintptr_t)sc->sc_regs + off), 87 cnt, DDI_DEV_NO_AUTOINCR); 88 } 89 90 void 91 ipw2100_csr_put8(struct ipw2100_softc *sc, uint32_t off, uint8_t val) 92 { 93 ddi_put8(sc->sc_ioh, (uint8_t *)(sc->sc_regs + off), val); 94 } 95 96 void 97 ipw2100_csr_put16(struct ipw2100_softc *sc, uint32_t off, uint16_t val) 98 { 99 ddi_put16(sc->sc_ioh, 100 (uint16_t *)((uintptr_t)sc->sc_regs + off), val); 101 } 102 103 void 104 ipw2100_csr_put32(struct ipw2100_softc *sc, uint32_t off, uint32_t val) 105 { 106 ddi_put32(sc->sc_ioh, 107 (uint32_t *)((uintptr_t)sc->sc_regs + off), val); 108 } 109 110 void 111 ipw2100_csr_rep_put8(struct ipw2100_softc *sc, 112 uint32_t off, uint8_t *buf, size_t cnt) 113 { 114 ddi_rep_put8(sc->sc_ioh, buf, (uint8_t *)(sc->sc_regs + off), 115 cnt, DDI_DEV_NO_AUTOINCR); 116 } 117 118 uint8_t 119 ipw2100_imem_get8(struct ipw2100_softc *sc, int32_t addr) 120 { 121 ipw2100_csr_put32(sc, IPW2100_CSR_INDIRECT_ADDR, addr); 122 123 return (ipw2100_csr_get8(sc, IPW2100_CSR_INDIRECT_DATA)); 124 } 125 126 uint16_t 127 ipw2100_imem_get16(struct ipw2100_softc *sc, uint32_t addr) 128 { 129 ipw2100_csr_put32(sc, IPW2100_CSR_INDIRECT_ADDR, addr); 130 131 return (ipw2100_csr_get16(sc, IPW2100_CSR_INDIRECT_DATA)); 132 } 133 134 uint32_t 135 ipw2100_imem_get32(struct ipw2100_softc *sc, uint32_t addr) 136 { 137 ipw2100_csr_put32(sc, IPW2100_CSR_INDIRECT_ADDR, addr); 138 139 return (ipw2100_csr_get32(sc, IPW2100_CSR_INDIRECT_DATA)); 140 } 141 142 void 143 ipw2100_imem_rep_get16(struct ipw2100_softc *sc, 144 uint32_t addr, uint16_t *buf, size_t cnt) 145 { 146 ipw2100_csr_put32(sc, IPW2100_CSR_INDIRECT_ADDR, addr); 147 ipw2100_csr_rep_get16(sc, IPW2100_CSR_INDIRECT_DATA, buf, cnt); 148 } 149 150 void 151 ipw2100_imem_put8(struct ipw2100_softc *sc, uint32_t addr, uint8_t val) 152 { 153 ipw2100_csr_put32(sc, IPW2100_CSR_INDIRECT_ADDR, addr); 154 ipw2100_csr_put8(sc, IPW2100_CSR_INDIRECT_DATA, val); 155 } 156 157 void 158 ipw2100_imem_put16(struct ipw2100_softc *sc, uint32_t addr, uint16_t val) 159 { 160 ipw2100_csr_put32(sc, IPW2100_CSR_INDIRECT_ADDR, addr); 161 ipw2100_csr_put16(sc, IPW2100_CSR_INDIRECT_DATA, val); 162 } 163 164 void 165 ipw2100_imem_put32(struct ipw2100_softc *sc, uint32_t addr, uint32_t val) 166 { 167 ipw2100_csr_put32(sc, IPW2100_CSR_INDIRECT_ADDR, addr); 168 ipw2100_csr_put32(sc, IPW2100_CSR_INDIRECT_DATA, val); 169 } 170 171 void 172 ipw2100_imem_rep_put8(struct ipw2100_softc *sc, 173 uint32_t addr, uint8_t *buf, size_t cnt) 174 { 175 ipw2100_csr_put32(sc, IPW2100_CSR_INDIRECT_ADDR, addr); 176 ipw2100_csr_rep_put8(sc, IPW2100_CSR_INDIRECT_DATA, buf, cnt); 177 } 178 179 void 180 ipw2100_imem_getbuf(struct ipw2100_softc *sc, 181 uint32_t addr, uint8_t *buf, size_t cnt) 182 { 183 for (; cnt > 0; addr++, buf++, cnt--) { 184 ipw2100_csr_put32(sc, IPW2100_CSR_INDIRECT_ADDR, addr & ~3); 185 *buf = ipw2100_csr_get8(sc, 186 IPW2100_CSR_INDIRECT_DATA +(addr & 3)); 187 } 188 } 189 190 void 191 ipw2100_imem_putbuf(struct ipw2100_softc *sc, 192 uint32_t addr, uint8_t *buf, size_t cnt) 193 { 194 for (; cnt > 0; addr++, buf++, cnt--) { 195 ipw2100_csr_put32(sc, IPW2100_CSR_INDIRECT_ADDR, addr & ~3); 196 ipw2100_csr_put8(sc, 197 IPW2100_CSR_INDIRECT_DATA +(addr & 3), *buf); 198 } 199 } 200 201 void 202 ipw2100_rom_control(struct ipw2100_softc *sc, uint32_t val) 203 { 204 ipw2100_imem_put32(sc, IPW2100_IMEM_EEPROM_CTL, val); 205 drv_usecwait(IPW2100_EEPROM_DELAY); 206 } 207 208 209 uint8_t 210 ipw2100_table1_get8(struct ipw2100_softc *sc, uint32_t off) 211 { 212 uint32_t addr = ipw2100_imem_get32(sc, sc->sc_table1_base + off); 213 return (ipw2100_imem_get8(sc, addr)); 214 } 215 216 uint32_t 217 ipw2100_table1_get32(struct ipw2100_softc *sc, uint32_t off) 218 { 219 uint32_t addr = ipw2100_imem_get32(sc, sc->sc_table1_base + off); 220 return (ipw2100_imem_get32(sc, addr)); 221 } 222 223 void 224 ipw2100_table1_put32(struct ipw2100_softc *sc, uint32_t off, uint32_t val) 225 { 226 uint32_t addr = ipw2100_imem_get32(sc, sc->sc_table1_base + off); 227 ipw2100_imem_put32(sc, addr, val); 228 } 229 230 int 231 ipw2100_table2_getbuf(struct ipw2100_softc *sc, 232 uint32_t off, uint8_t *buf, uint32_t *len) 233 { 234 uint32_t addr, info; 235 uint16_t cnt, size; 236 uint32_t total; 237 238 addr = ipw2100_imem_get32(sc, sc->sc_table2_base + off); 239 info = ipw2100_imem_get32(sc, 240 sc->sc_table2_base + off + sizeof (uint32_t)); 241 242 cnt = info >> 16; 243 size = info & 0xffff; 244 total = cnt * size; 245 246 if (total > *len) { 247 IPW2100_WARN((sc->sc_dip, CE_WARN, 248 "ipw2100_table2_getbuf(): invalid table offset = 0x%08x\n", 249 off)); 250 return (DDI_FAILURE); 251 } 252 253 *len = total; 254 ipw2100_imem_getbuf(sc, addr, buf, total); 255 256 return (DDI_SUCCESS); 257 } 258 259 uint16_t 260 ipw2100_rom_get16(struct ipw2100_softc *sc, uint8_t addr) 261 { 262 uint32_t tmp; 263 uint16_t val; 264 int n; 265 266 /* 267 * According to i2c bus protocol to set them. 268 */ 269 /* clock */ 270 ipw2100_rom_control(sc, 0); 271 ipw2100_rom_control(sc, IPW2100_EEPROM_S); 272 ipw2100_rom_control(sc, IPW2100_EEPROM_S | IPW2100_EEPROM_C); 273 ipw2100_rom_control(sc, IPW2100_EEPROM_S); 274 /* start bit */ 275 ipw2100_rom_control(sc, IPW2100_EEPROM_S | IPW2100_EEPROM_D); 276 ipw2100_rom_control(sc, IPW2100_EEPROM_S 277 | IPW2100_EEPROM_D | IPW2100_EEPROM_C); 278 /* read opcode */ 279 ipw2100_rom_control(sc, IPW2100_EEPROM_S | IPW2100_EEPROM_D); 280 ipw2100_rom_control(sc, IPW2100_EEPROM_S 281 | IPW2100_EEPROM_D | IPW2100_EEPROM_C); 282 ipw2100_rom_control(sc, IPW2100_EEPROM_S); 283 ipw2100_rom_control(sc, IPW2100_EEPROM_S | IPW2100_EEPROM_C); 284 /* 285 * address, totally 8 bits, defined by hardware, push from MSB to LSB 286 */ 287 for (n = 7; n >= 0; n--) { 288 ipw2100_rom_control(sc, IPW2100_EEPROM_S 289 |(((addr >> n) & 1) << IPW2100_EEPROM_SHIFT_D)); 290 ipw2100_rom_control(sc, IPW2100_EEPROM_S 291 |(((addr >> n) & 1) << IPW2100_EEPROM_SHIFT_D) 292 | IPW2100_EEPROM_C); 293 } 294 295 ipw2100_rom_control(sc, IPW2100_EEPROM_S); 296 297 /* 298 * data, totally 16 bits, defined by hardware, push from MSB to LSB 299 */ 300 val = 0; 301 for (n = 15; n >= 0; n--) { 302 ipw2100_rom_control(sc, IPW2100_EEPROM_S | IPW2100_EEPROM_C); 303 ipw2100_rom_control(sc, IPW2100_EEPROM_S); 304 tmp = ipw2100_imem_get32(sc, IPW2100_IMEM_EEPROM_CTL); 305 val |= ((tmp & IPW2100_EEPROM_Q) 306 >> IPW2100_EEPROM_SHIFT_Q) << n; 307 } 308 309 ipw2100_rom_control(sc, 0); 310 311 /* clear chip select and clock */ 312 ipw2100_rom_control(sc, IPW2100_EEPROM_S); 313 ipw2100_rom_control(sc, 0); 314 ipw2100_rom_control(sc, IPW2100_EEPROM_C); 315 316 return (LE_16(val)); 317 } 318 319 320 /* 321 * Firmware related operations 322 */ 323 #define IPW2100_FW_MAJOR_VERSION (1) 324 #define IPW2100_FW_MINOR_VERSION (3) 325 326 #define IPW2100_FW_MAJOR(x)((x) & 0xff) 327 #define IPW2100_FW_MINOR(x)(((x) & 0xff) >> 8) 328 329 /* 330 * The firware was issued by Intel as binary which need to be loaded 331 * to hardware when card is initiated, or when fatal error happened, 332 * or when the chip need be reset. 333 */ 334 static uint8_t ipw2100_firmware_bin [] = { 335 #include "fw-ipw2100/ipw2100-1.3.fw.hex" 336 }; 337 338 int 339 ipw2100_cache_firmware(struct ipw2100_softc *sc) 340 { 341 uint8_t *bin = ipw2100_firmware_bin; 342 struct ipw2100_firmware_hdr *h = (struct ipw2100_firmware_hdr *)bin; 343 344 IPW2100_DBG(IPW2100_DBG_FW, (sc->sc_dip, CE_CONT, 345 "ipw2100_cache_firmwares(): enter\n")); 346 347 sc->sc_fw.bin_base = bin; 348 sc->sc_fw.bin_size = sizeof (ipw2100_firmware_bin); 349 350 if (IPW2100_FW_MAJOR(h->version) != IPW2100_FW_MAJOR_VERSION) { 351 IPW2100_WARN((sc->sc_dip, CE_WARN, 352 "ipw2100_cache_firmware(): image not compatible, %u\n", 353 h->version)); 354 return (DDI_FAILURE); 355 } 356 357 sc->sc_fw.fw_base = bin + sizeof (struct ipw2100_firmware_hdr); 358 sc->sc_fw.fw_size = LE_32(h->fw_size); 359 sc->sc_fw.uc_base = sc->sc_fw.fw_base + sc->sc_fw.fw_size; 360 sc->sc_fw.uc_size = LE_32(h->uc_size); 361 362 sc->sc_flags |= IPW2100_FLAG_FW_CACHED; 363 364 IPW2100_DBG(IPW2100_DBG_FW, (sc->sc_dip, CE_CONT, 365 "ipw2100_cache_firmware(): exit\n")); 366 367 return (DDI_SUCCESS); 368 } 369 370 /* 371 * If user-land firmware loading is supported, this routine 372 * free kmemory if sc->sc_fw.bin_base & sc->sc_fw.bin_size are 373 * not empty. 374 */ 375 int 376 ipw2100_free_firmware(struct ipw2100_softc *sc) 377 { 378 sc->sc_flags &= ~IPW2100_FLAG_FW_CACHED; 379 380 return (DDI_SUCCESS); 381 } 382 383 /* 384 * the following routines load code onto ipw2100 hardware 385 */ 386 int 387 ipw2100_load_uc(struct ipw2100_softc *sc) 388 { 389 int ntries; 390 391 ipw2100_imem_put32(sc, 0x3000e0, 0x80000000); 392 ipw2100_csr_put32(sc, IPW2100_CSR_RST, 0); 393 394 ipw2100_imem_put16(sc, 0x220000, 0x0703); 395 ipw2100_imem_put16(sc, 0x220000, 0x0707); 396 397 ipw2100_imem_put8(sc, 0x210014, 0x72); 398 ipw2100_imem_put8(sc, 0x210014, 0x72); 399 400 ipw2100_imem_put8(sc, 0x210000, 0x40); 401 ipw2100_imem_put8(sc, 0x210000, 0x00); 402 ipw2100_imem_put8(sc, 0x210000, 0x40); 403 404 ipw2100_imem_rep_put8(sc, 0x210010, 405 sc->sc_fw.uc_base, sc->sc_fw.uc_size); 406 407 ipw2100_imem_put8(sc, 0x210000, 0x00); 408 ipw2100_imem_put8(sc, 0x210000, 0x00); 409 ipw2100_imem_put8(sc, 0x210000, 0x80); 410 411 ipw2100_imem_put16(sc, 0x220000, 0x0703); 412 ipw2100_imem_put16(sc, 0x220000, 0x0707); 413 414 ipw2100_imem_put8(sc, 0x210014, 0x72); 415 ipw2100_imem_put8(sc, 0x210014, 0x72); 416 417 ipw2100_imem_put8(sc, 0x210000, 0x00); 418 ipw2100_imem_put8(sc, 0x210000, 0x80); 419 420 /* try many times */ 421 for (ntries = 0; ntries < 5000; ntries++) { 422 if (ipw2100_imem_get8(sc, 0x210000) & 1) 423 break; 424 drv_usecwait(1000); /* wait for a while */ 425 } 426 if (ntries == 5000) 427 return (DDI_FAILURE); 428 429 ipw2100_imem_put32(sc, 0x3000e0, 0); 430 431 return (DDI_SUCCESS); 432 } 433 434 int 435 ipw2100_load_fw(struct ipw2100_softc *sc) 436 { 437 uint8_t *p, *e; 438 uint32_t dst; 439 uint16_t len; 440 clock_t clk; 441 442 IPW2100_DBG(IPW2100_DBG_FW, (sc->sc_dip, CE_CONT, 443 "ipw2100_load_fw(): enter\n")); 444 445 p = sc->sc_fw.fw_base; 446 e = sc->sc_fw.fw_base + sc->sc_fw.fw_size; 447 while (p < e) { 448 /* 449 * each block is organized as <DST,LEN,DATA> 450 */ 451 if ((p + sizeof (dst) + sizeof (len)) > e) { 452 IPW2100_WARN((sc->sc_dip, CE_CONT, 453 "ipw2100_load_fw(): invalid firmware image\n")); 454 return (DDI_FAILURE); 455 } 456 dst = LE_32(*((uint32_t *)(uintptr_t)p)); p += sizeof (dst); 457 len = LE_16(*((uint16_t *)(uintptr_t)p)); p += sizeof (len); 458 if ((p + len) > e) { 459 IPW2100_WARN((sc->sc_dip, CE_CONT, 460 "ipw2100_load_fw(): invalid firmware image\n")); 461 return (DDI_FAILURE); 462 } 463 464 ipw2100_imem_putbuf(sc, dst, p, len); 465 p += len; 466 } 467 468 ipw2100_csr_put32(sc, IPW2100_CSR_IO, 469 IPW2100_IO_GPIO1_ENABLE | IPW2100_IO_GPIO3_MASK | 470 IPW2100_IO_LED_OFF); 471 472 mutex_enter(&sc->sc_ilock); 473 474 /* 475 * enable all interrupts 476 */ 477 ipw2100_csr_put32(sc, IPW2100_CSR_INTR_MASK, IPW2100_INTR_MASK_ALL); 478 479 ipw2100_csr_put32(sc, IPW2100_CSR_RST, 0); 480 ipw2100_csr_put32(sc, IPW2100_CSR_CTL, 481 ipw2100_csr_get32(sc, IPW2100_CSR_CTL) | IPW2100_CTL_ALLOW_STANDBY); 482 483 /* 484 * wait for interrupt to notify fw initialization is done 485 */ 486 while (!(sc->sc_flags & IPW2100_FLAG_FW_INITED)) { 487 /* 488 * wait longer for the fw initialized 489 */ 490 clk = ddi_get_lbolt() + drv_usectohz(5000000); /* 5 second */ 491 if (cv_timedwait(&sc->sc_fw_cond, &sc->sc_ilock, clk) < 0) 492 break; 493 } 494 mutex_exit(&sc->sc_ilock); 495 496 ipw2100_csr_put32(sc, IPW2100_CSR_IO, 497 ipw2100_csr_get32(sc, IPW2100_CSR_IO) | 498 IPW2100_IO_GPIO1_MASK | IPW2100_IO_GPIO3_MASK); 499 500 if (!(sc->sc_flags & IPW2100_FLAG_FW_INITED)) { 501 IPW2100_DBG(IPW2100_DBG_FW, (sc->sc_dip, CE_CONT, 502 "ipw2100_load_fw(): exit, init failed\n")); 503 return (DDI_FAILURE); 504 } 505 506 IPW2100_DBG(IPW2100_DBG_FW, (sc->sc_dip, CE_CONT, 507 "ipw2100_load_fw(): exit\n")); 508 return (DDI_SUCCESS); 509 } 510