xref: /illumos-gate/usr/src/uts/common/io/ipw/ipw2100_hw.c (revision 581cede61ac9c14d8d4ea452562a567189eead78)
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