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