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