1 1.10 andvar /* $NetBSD: if_cs_mainbus.c,v 1.10 2025/02/03 22:30:15 andvar Exp $ */ 2 1.2 garbled 3 1.2 garbled /* 4 1.2 garbled * Copyright (c) 2002 The NetBSD Foundation, Inc. 5 1.2 garbled * All rights reserved. 6 1.2 garbled * 7 1.2 garbled * This code is derived from software contributed to The NetBSD Foundation 8 1.2 garbled * by Lennart Augustsson (lennart (at) augustsson.net) at Sandburst Corp. 9 1.2 garbled * 10 1.2 garbled * Redistribution and use in source and binary forms, with or without 11 1.2 garbled * modification, are permitted provided that the following conditions 12 1.2 garbled * are met: 13 1.2 garbled * 1. Redistributions of source code must retain the above copyright 14 1.2 garbled * notice, this list of conditions and the following disclaimer. 15 1.2 garbled * 2. Redistributions in binary form must reproduce the above copyright 16 1.2 garbled * notice, this list of conditions and the following disclaimer in the 17 1.2 garbled * documentation and/or other materials provided with the distribution. 18 1.2 garbled * 19 1.2 garbled * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 1.2 garbled * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 1.2 garbled * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 1.2 garbled * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 1.2 garbled * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 1.2 garbled * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 1.2 garbled * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 1.2 garbled * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 1.2 garbled * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 1.2 garbled * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 1.2 garbled * POSSIBILITY OF SUCH DAMAGE. 30 1.2 garbled */ 31 1.2 garbled 32 1.2 garbled #include <sys/cdefs.h> 33 1.10 andvar __KERNEL_RCSID(0, "$NetBSD: if_cs_mainbus.c,v 1.10 2025/02/03 22:30:15 andvar Exp $"); 34 1.2 garbled 35 1.2 garbled #include <sys/param.h> 36 1.2 garbled #include <sys/device.h> 37 1.2 garbled #include <sys/systm.h> 38 1.2 garbled #include <sys/socket.h> 39 1.6 matt #include <sys/bus.h> 40 1.2 garbled 41 1.2 garbled #include <net/if.h> 42 1.2 garbled #include <net/if_ether.h> 43 1.2 garbled #include <net/if_media.h> 44 1.2 garbled #ifdef INET 45 1.2 garbled #include <netinet/in.h> 46 1.2 garbled #include <netinet/if_inarp.h> 47 1.2 garbled #endif 48 1.2 garbled 49 1.6 matt #include <powerpc/psl.h> 50 1.6 matt 51 1.2 garbled #include <machine/pio.h> 52 1.2 garbled #include <machine/pmppc.h> 53 1.6 matt #include <evbppc/pmppc/dev/mainbus.h> 54 1.2 garbled 55 1.2 garbled #include <dev/ic/cs89x0reg.h> 56 1.2 garbled #include <dev/ic/cs89x0var.h> 57 1.2 garbled 58 1.2 garbled #include <sys/callout.h> 59 1.2 garbled 60 1.2 garbled #define ATSN_EEPROM_MAC_OFFSET 0x20 61 1.2 garbled 62 1.2 garbled 63 1.2 garbled static void cs_check_eeprom(struct cs_softc *sc); 64 1.2 garbled 65 1.5 matt static int cs_mainbus_match(device_t, cfdata_t, void *); 66 1.5 matt static void cs_mainbus_attach(device_t, device_t, void *); 67 1.2 garbled 68 1.4 tsutsui CFATTACH_DECL_NEW(cs_mainbus, sizeof(struct cs_softc), 69 1.2 garbled cs_mainbus_match, cs_mainbus_attach, NULL, NULL); 70 1.2 garbled 71 1.2 garbled int 72 1.4 tsutsui cs_mainbus_match(device_t parent, cfdata_t cf, void *aux) 73 1.2 garbled { 74 1.2 garbled struct mainbus_attach_args *maa = aux; 75 1.2 garbled 76 1.2 garbled return (strcmp(maa->mb_name, "cs") == 0); 77 1.2 garbled } 78 1.2 garbled 79 1.2 garbled #if 0 80 1.2 garbled static u_int64_t 81 1.2 garbled in64(uint a) 82 1.2 garbled { 83 1.2 garbled union { 84 1.2 garbled double d; 85 1.2 garbled u_int64_t i; 86 1.2 garbled } u; 87 1.2 garbled double save, *dp = (double *)a; 88 1.2 garbled u_int32_t msr, nmsr; 89 1.2 garbled 90 1.2 garbled __asm volatile("mfmsr %0" : "=r"(msr)); 91 1.2 garbled nmsr = (msr | PSL_FP) & ~(PSL_FE0 | PSL_FE1); 92 1.2 garbled __asm volatile("mtmsr %0" :: "r"(nmsr)); 93 1.2 garbled __asm volatile("mfmsr %0" : "=r"(nmsr)); /* some interlock nonsense */ 94 1.2 garbled __asm volatile( 95 1.2 garbled "stfd 0,%0\n\ 96 1.2 garbled lfd 0,%1\n\ 97 1.2 garbled stfd 0,%2\n\ 98 1.2 garbled lfd 0,%0" 99 1.2 garbled : "=m"(save), "=m"(*dp) 100 1.2 garbled : "m"(u.d) 101 1.2 garbled ); 102 1.9 riastrad __asm volatile("eieio; sync" ::: "memory"); 103 1.2 garbled __asm volatile("mtmsr %0" :: "r"(msr)); 104 1.2 garbled return (u.i); 105 1.2 garbled } 106 1.2 garbled #endif 107 1.2 garbled 108 1.2 garbled static void 109 1.2 garbled out64(uint a, u_int64_t v) 110 1.2 garbled { 111 1.2 garbled union { 112 1.2 garbled double d; 113 1.2 garbled u_int64_t i; 114 1.2 garbled } u; 115 1.2 garbled double save, *dp = (double *)a; 116 1.2 garbled u_int32_t msr, nmsr; 117 1.2 garbled int s; 118 1.2 garbled 119 1.2 garbled s = splhigh(); 120 1.2 garbled u.i = v; 121 1.2 garbled __asm volatile("mfmsr %0" : "=r"(msr)); 122 1.2 garbled nmsr = (msr | PSL_FP) & ~(PSL_FE0 | PSL_FE1); 123 1.2 garbled __asm volatile("mtmsr %0" :: "r"(nmsr)); 124 1.2 garbled __asm volatile("mfmsr %0" : "=r"(nmsr)); /* some interlock nonsense */ 125 1.2 garbled __asm volatile( 126 1.2 garbled "stfd 0,%0\n\ 127 1.2 garbled lfd 0,%2\n\ 128 1.2 garbled stfd 0,%1\n\ 129 1.2 garbled lfd 0,%0" 130 1.2 garbled : "=m"(save), "=m"(*dp) 131 1.2 garbled : "m"(u.d) 132 1.2 garbled ); 133 1.9 riastrad __asm volatile("eieio; sync" ::: "memory"); 134 1.2 garbled __asm volatile("mtmsr %0" :: "r"(msr)); 135 1.2 garbled splx(s); 136 1.2 garbled } 137 1.2 garbled 138 1.2 garbled static u_int8_t 139 1.2 garbled cs_io_read_1(struct cs_softc *sc, bus_size_t offs) 140 1.2 garbled { 141 1.2 garbled u_int32_t a, v; 142 1.2 garbled 143 1.2 garbled a = sc->sc_ioh + (offs << 2); 144 1.2 garbled v = in8(a); 145 1.2 garbled return v; 146 1.2 garbled } 147 1.2 garbled 148 1.2 garbled static u_int16_t 149 1.2 garbled cs_io_read_2(struct cs_softc *sc, bus_size_t offs) 150 1.2 garbled { 151 1.2 garbled u_int32_t a, v; 152 1.2 garbled 153 1.2 garbled a = sc->sc_ioh + (offs << 2); 154 1.2 garbled v = in16(a); 155 1.2 garbled return v; 156 1.2 garbled } 157 1.2 garbled 158 1.2 garbled static void 159 1.2 garbled cs_io_read_multi_2(struct cs_softc *sc, bus_size_t offs, u_int16_t *buf, 160 1.2 garbled bus_size_t cnt) 161 1.2 garbled { 162 1.2 garbled u_int32_t a, v; 163 1.2 garbled 164 1.2 garbled a = sc->sc_ioh + (offs << 2); 165 1.2 garbled while (cnt--) { 166 1.2 garbled v = in16(a); 167 1.2 garbled *buf++ = bswap16(v); 168 1.2 garbled } 169 1.2 garbled } 170 1.2 garbled 171 1.2 garbled static void 172 1.2 garbled cs_io_write_2(struct cs_softc *sc, bus_size_t offs, u_int16_t data) 173 1.2 garbled { 174 1.2 garbled u_int32_t a; 175 1.2 garbled u_int64_t v; 176 1.2 garbled 177 1.2 garbled a = sc->sc_ioh + (offs << 2); 178 1.2 garbled v = (u_int64_t)data << 48; 179 1.2 garbled out64(a, v); 180 1.2 garbled 181 1.2 garbled (void)in16(a); /* CPC700 write post bug */ 182 1.2 garbled } 183 1.2 garbled 184 1.2 garbled static void 185 1.2 garbled cs_io_write_multi_2(struct cs_softc *sc, bus_size_t offs, 186 1.2 garbled const u_int16_t *buf, bus_size_t cnt) 187 1.2 garbled { 188 1.2 garbled u_int16_t v; 189 1.2 garbled double save, *dp; 190 1.2 garbled union { 191 1.2 garbled double d; 192 1.2 garbled u_int64_t i; 193 1.2 garbled } u; 194 1.2 garbled u_int32_t msr, nmsr; 195 1.2 garbled int s; 196 1.2 garbled 197 1.2 garbled dp = (double *)(sc->sc_ioh + (offs << 2)); 198 1.2 garbled 199 1.2 garbled s = splhigh(); 200 1.2 garbled __asm volatile("mfmsr %0" : "=r"(msr)); 201 1.2 garbled nmsr = (msr | PSL_FP) & ~(PSL_FE0 | PSL_FE1); 202 1.2 garbled __asm volatile("mtmsr %0" :: "r"(nmsr)); 203 1.2 garbled __asm volatile("mfmsr %0" : "=r"(nmsr)); /* some interlock nonsense */ 204 1.2 garbled __asm volatile("stfd 0,%0" : "=m"(save)); 205 1.2 garbled 206 1.2 garbled while (cnt--) { 207 1.2 garbled v = *buf++; 208 1.2 garbled v = bswap16(v); 209 1.2 garbled u.i = (u_int64_t)v << 48; 210 1.2 garbled __asm volatile("lfd 0,%1\nstfd 0,%0" : "=m"(*dp) : "m"(u.d) ); 211 1.9 riastrad __asm volatile("eieio; sync" ::: "memory"); 212 1.2 garbled } 213 1.2 garbled __asm volatile("lfd 0,%0" :: "m"(save)); 214 1.2 garbled __asm volatile("mtmsr %0" :: "r"(msr)); 215 1.2 garbled splx(s); 216 1.2 garbled } 217 1.2 garbled 218 1.2 garbled static u_int16_t 219 1.2 garbled cs_mem_read_2(struct cs_softc *sc, bus_size_t offs) 220 1.2 garbled { 221 1.2 garbled panic("cs_mem_read_2"); 222 1.2 garbled } 223 1.2 garbled 224 1.2 garbled static void 225 1.2 garbled cs_mem_write_2(struct cs_softc *sc, bus_size_t offs, u_int16_t data) 226 1.2 garbled { 227 1.2 garbled panic("cs_mem_write_2"); 228 1.2 garbled } 229 1.2 garbled 230 1.2 garbled static void 231 1.2 garbled cs_mem_write_region_2(struct cs_softc *sc, bus_size_t offs, 232 1.2 garbled const u_int16_t *buf, bus_size_t cnt) 233 1.2 garbled { 234 1.2 garbled panic("cs_mem_write_region_2"); 235 1.2 garbled } 236 1.2 garbled 237 1.2 garbled void 238 1.4 tsutsui cs_mainbus_attach(device_t parent, device_t self, void *aux) 239 1.2 garbled { 240 1.4 tsutsui struct cs_softc *sc = device_private(self); 241 1.2 garbled struct mainbus_attach_args *maa = aux; 242 1.2 garbled int media[1] = { IFM_ETHER | IFM_10_T }; 243 1.2 garbled 244 1.2 garbled printf("\n"); 245 1.2 garbled 246 1.4 tsutsui sc->sc_dev = self; 247 1.2 garbled sc->sc_iot = maa->mb_bt; 248 1.2 garbled sc->sc_memt = maa->mb_bt; 249 1.2 garbled sc->sc_irq = maa->mb_irq; 250 1.2 garbled 251 1.2 garbled if (bus_space_map(sc->sc_iot, PMPPC_CS_IO, CS8900_IOSIZE*4, 252 1.2 garbled 0, &sc->sc_ioh)) { 253 1.4 tsutsui printf("%s: failed to map io\n", device_xname(self)); 254 1.2 garbled return; 255 1.2 garbled } 256 1.2 garbled 257 1.2 garbled cs_check_eeprom(sc); 258 1.2 garbled 259 1.2 garbled sc->sc_ih = intr_establish(sc->sc_irq, IST_LEVEL, IPL_NET, cs_intr, sc); 260 1.2 garbled if (!sc->sc_ih) { 261 1.2 garbled printf("%s: unable to establish interrupt\n", 262 1.4 tsutsui device_xname(self)); 263 1.2 garbled goto fail; 264 1.2 garbled } 265 1.2 garbled 266 1.2 garbled sc->sc_cfgflags = CFGFLG_NOT_EEPROM; 267 1.2 garbled 268 1.2 garbled sc->sc_io_read_1 = cs_io_read_1; 269 1.2 garbled sc->sc_io_read_2 = cs_io_read_2; 270 1.2 garbled sc->sc_io_read_multi_2 = cs_io_read_multi_2; 271 1.2 garbled sc->sc_io_write_2 = cs_io_write_2; 272 1.2 garbled sc->sc_io_write_multi_2 = cs_io_write_multi_2; 273 1.2 garbled sc->sc_mem_read_2 = cs_mem_read_2; 274 1.2 garbled sc->sc_mem_write_2 = cs_mem_write_2; 275 1.2 garbled sc->sc_mem_write_region_2 = cs_mem_write_region_2; 276 1.2 garbled 277 1.2 garbled /* 278 1.2 garbled * We need interrupt on INTRQ0 from the CS8900 (that's what wired 279 1.2 garbled * to the UIC). The MI driver subtracts 10 from the irq, so 280 1.2 garbled * use 10 as the irq. 281 1.2 garbled */ 282 1.2 garbled sc->sc_irq = 10; 283 1.2 garbled 284 1.2 garbled /* Use half duplex 10baseT. */ 285 1.2 garbled if (cs_attach(sc, NULL, media, 1, IFM_ETHER | IFM_10_T)) { 286 1.4 tsutsui printf("%s: unable to attach\n", device_xname(self)); 287 1.2 garbled goto fail; 288 1.2 garbled } 289 1.2 garbled 290 1.2 garbled return; 291 1.2 garbled 292 1.2 garbled fail: 293 1.2 garbled /* XXX disestablish, unmap */ 294 1.2 garbled return; 295 1.2 garbled } 296 1.2 garbled 297 1.2 garbled 298 1.2 garbled /* 299 1.2 garbled * EEPROM initialization code. 300 1.2 garbled */ 301 1.2 garbled 302 1.2 garbled static uint16_t default_eeprom_cfg[] = 303 1.2 garbled { 0xA100, 0x2020, 0x0300, 0x0000, 0x0000, 304 1.2 garbled 0x102C, 0x1000, 0x0008, 0x2158, 0x0000, 305 1.2 garbled 0x0000, 0x0000 }; 306 1.2 garbled 307 1.2 garbled static uint16_t 308 1.2 garbled cs_readreg(struct cs_softc *sc, uint pp_offset) 309 1.2 garbled { 310 1.2 garbled cs_io_write_2(sc, PORT_PKTPG_PTR, pp_offset); 311 1.2 garbled (void)cs_io_read_2(sc, PORT_PKTPG_PTR); 312 1.2 garbled return (cs_io_read_2(sc, PORT_PKTPG_DATA)); 313 1.2 garbled } 314 1.2 garbled 315 1.2 garbled static void 316 1.2 garbled cs_writereg(struct cs_softc *sc, uint pp_offset, uint16_t value) 317 1.2 garbled { 318 1.2 garbled cs_io_write_2(sc, PORT_PKTPG_PTR, pp_offset); 319 1.2 garbled (void)cs_io_read_2(sc, PORT_PKTPG_PTR); 320 1.2 garbled cs_io_write_2(sc, PORT_PKTPG_DATA, value); 321 1.2 garbled (void)cs_io_read_2(sc, PORT_PKTPG_DATA); 322 1.2 garbled } 323 1.2 garbled 324 1.2 garbled static int 325 1.2 garbled cs_wait_eeprom_ready(struct cs_softc *sc) 326 1.2 garbled { 327 1.2 garbled int ms; 328 1.2 garbled 329 1.2 garbled /* 330 1.2 garbled * Check to see if the EEPROM is ready, a timeout is used - 331 1.2 garbled * just in case EEPROM is ready when SI_BUSY in the 332 1.2 garbled * PP_SelfST is clear. 333 1.2 garbled */ 334 1.2 garbled ms = 0; 335 1.2 garbled while(cs_readreg(sc, PKTPG_SELF_ST) & SELF_ST_SI_BUSY) { 336 1.2 garbled delay(1000); 337 1.2 garbled if (ms++ > 20) 338 1.2 garbled return 0; 339 1.2 garbled } 340 1.2 garbled return 1; 341 1.2 garbled } 342 1.2 garbled 343 1.2 garbled static void 344 1.2 garbled cs_wr_eeprom(struct cs_softc *sc, uint16_t offset, uint16_t data) 345 1.2 garbled { 346 1.2 garbled 347 1.2 garbled /* Check to make sure EEPROM is ready. */ 348 1.2 garbled if (!cs_wait_eeprom_ready(sc)) { 349 1.4 tsutsui printf("%s: write EEPROM not ready\n", 350 1.4 tsutsui device_xname(sc->sc_dev)); 351 1.2 garbled return; 352 1.2 garbled } 353 1.2 garbled 354 1.2 garbled /* Enable writing. */ 355 1.2 garbled cs_writereg(sc, PKTPG_EEPROM_CMD, EEPROM_WRITE_ENABLE); 356 1.2 garbled 357 1.2 garbled /* Wait for WRITE_ENABLE command to complete. */ 358 1.2 garbled if (!cs_wait_eeprom_ready(sc)) { 359 1.4 tsutsui printf("%s: EEPROM WRITE_ENABLE timeout", 360 1.4 tsutsui device_xname(sc->sc_dev)); 361 1.2 garbled } else { 362 1.2 garbled /* Write data into EEPROM_DATA register. */ 363 1.2 garbled cs_writereg(sc, PKTPG_EEPROM_DATA, data); 364 1.2 garbled delay(1000); 365 1.2 garbled cs_writereg(sc, PKTPG_EEPROM_CMD, EEPROM_CMD_WRITE | offset); 366 1.2 garbled 367 1.2 garbled /* Wait for WRITE_REGISTER command to complete. */ 368 1.2 garbled if (!cs_wait_eeprom_ready(sc)) { 369 1.2 garbled printf("%s: EEPROM WRITE_REGISTER timeout\n", 370 1.4 tsutsui device_xname(sc->sc_dev)); 371 1.2 garbled } 372 1.2 garbled } 373 1.2 garbled 374 1.2 garbled /* Disable writing. */ 375 1.2 garbled cs_writereg(sc, PKTPG_EEPROM_CMD, EEPROM_WRITE_DISABLE); 376 1.2 garbled 377 1.2 garbled /* Wait for WRITE_DISABLE command to complete. */ 378 1.2 garbled if (!cs_wait_eeprom_ready(sc)) { 379 1.4 tsutsui printf("%s: WRITE_DISABLE timeout\n", device_xname(sc->sc_dev)); 380 1.2 garbled } 381 1.2 garbled } 382 1.2 garbled 383 1.2 garbled static uint16_t 384 1.2 garbled cs_rd_eeprom(struct cs_softc *sc, uint16_t offset) 385 1.2 garbled { 386 1.2 garbled 387 1.2 garbled if (!cs_wait_eeprom_ready(sc)) { 388 1.4 tsutsui printf("%s: read EEPROM not ready\n", device_xname(sc->sc_dev)); 389 1.2 garbled return 0; 390 1.2 garbled } 391 1.2 garbled cs_writereg(sc, PKTPG_EEPROM_CMD, EEPROM_CMD_READ | offset); 392 1.2 garbled 393 1.2 garbled if (!cs_wait_eeprom_ready(sc)) { 394 1.4 tsutsui printf("%s: EEPROM_READ timeout\n", device_xname(sc->sc_dev)); 395 1.2 garbled return 0; 396 1.2 garbled } 397 1.2 garbled return cs_readreg(sc, PKTPG_EEPROM_DATA); 398 1.2 garbled } 399 1.2 garbled 400 1.2 garbled static void 401 1.2 garbled cs_check_eeprom(struct cs_softc *sc) 402 1.2 garbled { 403 1.2 garbled uint8_t checksum; 404 1.2 garbled int i; 405 1.2 garbled uint16_t tmp; 406 1.2 garbled 407 1.2 garbled /* 408 1.2 garbled * If the SELFST[EEPROMOK] is set, then assume EEPROM configuration 409 1.2 garbled * is valid. 410 1.2 garbled */ 411 1.2 garbled if (cs_readreg(sc, PKTPG_SELF_ST) & SELF_ST_EEP_OK) { 412 1.2 garbled printf("%s: EEPROM OK, skipping initialization\n", 413 1.4 tsutsui device_xname(sc->sc_dev)); 414 1.2 garbled return; 415 1.2 garbled } 416 1.4 tsutsui printf("%s: updating EEPROM\n", device_xname(sc->sc_dev)); 417 1.2 garbled 418 1.2 garbled /* 419 1.2 garbled * Calculate the size (in bytes) of the default config array and write 420 1.2 garbled * it to the lower byte of the array itself. 421 1.2 garbled */ 422 1.2 garbled default_eeprom_cfg[0] |= sizeof(default_eeprom_cfg); 423 1.2 garbled 424 1.2 garbled /* 425 1.2 garbled * Read the MAC address from its Artesyn-specified offset in the EEPROM. 426 1.2 garbled */ 427 1.2 garbled for (i = 0; i < 3; i++) { 428 1.2 garbled tmp = cs_rd_eeprom(sc, ATSN_EEPROM_MAC_OFFSET + i); 429 1.2 garbled default_eeprom_cfg[EEPROM_MAC + i] = bswap16(tmp); 430 1.2 garbled } 431 1.2 garbled 432 1.2 garbled /* 433 1.2 garbled * Program the EEPROM with our default configuration, 434 1.2 garbled * calculating checksum as we proceed. 435 1.2 garbled */ 436 1.2 garbled checksum = 0; 437 1.2 garbled for (i = 0; i < sizeof(default_eeprom_cfg)/2 ; i++) { 438 1.2 garbled tmp = default_eeprom_cfg[i]; 439 1.2 garbled cs_wr_eeprom(sc, i, tmp); 440 1.2 garbled checksum += tmp >> 8; 441 1.2 garbled checksum += tmp & 0xff; 442 1.2 garbled } 443 1.2 garbled 444 1.2 garbled /* 445 1.2 garbled * The CS8900a datasheet calls for the two's complement of the checksum 446 1.10 andvar * to be programmed in the most significant byte of the last word of the 447 1.2 garbled * header. 448 1.2 garbled */ 449 1.2 garbled checksum = ~checksum + 1; 450 1.2 garbled cs_wr_eeprom(sc, i++, checksum << 8); 451 1.2 garbled /* write "end of data" flag */ 452 1.2 garbled cs_wr_eeprom(sc, i, 0xffff); 453 1.2 garbled } 454