/* * Copyright (c) 2002. All Rights Reserved. * * Author: Nazim Aliev, alnaz@mail.ru */ /* * Direction Finder "Platan" * UNIO-48 card driver for FreeBSD Embedded * Version 1.01 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define MAJOR 199 #define MAXDEVS 6 #define FPGA1 0xA000 #define FPGA2 0xA400 #define EVENT_MASK_DM 0x01 #define STROBE_MASK_DM 0xef #define EVENT_MASK_KBD 0x0F #define CIRCLES 32 #define DIPOLS 16 #define SIZE_DM 68 #define SIZE_BUF SIZE_DM*DIPOLS*CIRCLES + 1 #define STROBE_MASK_DIV 0xfe #define PULSE_MASK_DIV 0xfb #define DATA_MASK_DIV 0xfd #define MPC_MASK_DIV 0xf7 #define SCK_MASK_PROG 0xfd #define RESET_MASK_PROG 0xfe #define MOSI_1_MASK_PROG 0xfb #define MOSI_2_MASK_PROG 0xf7 #define MOSI_3_MASK_PROG 0xef #define MOSI_4_MASK_PROG 0xdf #define MISO_1_MASK_PROG 0xfe #define MISO_2_MASK_PROG 0xfd #define MISO_3_MASK_PROG 0xfb #define MISO_4_MASK_PROG 0xf7 #define RD_MASK_DISPLAY 0xfb #define RESET_MASK_DISPLAY 0xef #define WR_MASK_DISPLAY 0xdf #define CS_MASK_DISPLAY 0xbf #define A0_MASK_DISPLAY 0x7f #define BUSY_MASK_DISPLAY 0x01 #define WDT_ADDR 0x20c struct unio_softc { int BA; int isopen; int isready; int iswait; struct resource *res_port; struct resource *res_irq; int cookie_port; int cookie_irq; void *i_handler; int dev_port; int dev_flags; int dev_irq; int dev; unsigned char prog[4]; unsigned short int Input_Counter; unsigned char int0; unsigned char kbd_code_1, kbd_code_2, kbd_char; }; static void unio_intr_dm(void *); static void unio_intr_kbd(void *); static devclass_t unio_devclass; static unsigned char buf[SIZE_BUF]; static d_open_t unio_open; static d_close_t unio_close; static d_read_t unio_read; static d_write_t unio_write; static int unio_poll(dev_t, int, struct proc *) ; static struct selinfo unio_selevent; static struct cdevsw unio_cdevsw = { unio_open, /* open */ unio_close, /* close */ unio_read, /* read */ unio_write, /* write */ noioctl, /* ioctl */ unio_poll, /* poll */ nommap, /* mmap */ nostrategy, /* strategy */ "unio", /* name */ MAJOR, /* major */ nodump, /* dump */ nopsize, /* psize */ 0, /* flags */ -1 /* bmaj */ }; /* devices: card 1: BA1=120h unio0 - demodulator, FPGA1, irq10 unio1 - frequency divider, FPGA2 unio2 - programmator, FPGA2 card 2: BA2=130h unio3 - keyboard, FPGA1, irq5 unio4 - display, FPGA2 */ /*********************** PROBE() ***********************/ static int unio_probe(device_t dev) { int unit = device_get_unit(dev); if (unit >= MAXDEVS) return ENXIO; if (bus_get_resource_start(dev, SYS_RES_IOPORT, 0) == 0) return ENXIO; /* IRQ processing only for unio0 */ if(unit == 0 || unit==3) if (bus_get_resource_start(dev, SYS_RES_IRQ, 0) == 0) return ENXIO; device_set_desc(dev, "UNIO48-5 card driver v.1.02.1"); return 0; } /*********************** ATTACH() ***********************/ static int unio_attach(device_t dev) { struct unio_softc *sc; int id_d; char id_c; int error = 0; int unit = device_get_unit(dev); if (unit >= MAXDEVS) return ENXIO; sc = (struct unio_softc *) device_get_softc(dev); sc->cookie_port = 0; sc->cookie_irq = 0; /* alloc port resource */ sc->res_port = bus_alloc_resource(dev, SYS_RES_IOPORT, &sc->cookie_port, 0, ~0, 1, RF_ACTIVE | RF_SHAREABLE); if (sc->res_port == NULL) return ENXIO; /* alloc IRQ resource - only for unio0 & unio3 */ if(unit == 0 || unit == 3) { sc->res_irq = bus_alloc_resource(dev, SYS_RES_IRQ, &sc->cookie_irq, 0, ~0, 1, RF_ACTIVE); if (sc->res_irq == NULL) return ENXIO; /* setup interrupt handler */ if(unit == 0) { error = bus_setup_intr(dev, sc->res_irq, INTR_TYPE_TTY, unio_intr_dm, sc, &sc->i_handler); } else { if(unit == 3) { error = bus_setup_intr(dev, sc->res_irq, INTR_TYPE_TTY , unio_intr_kbd, sc, &sc->i_handler); } } if(error){ device_printf(dev, "cannot setup interrupt handler\n"); return error; } } /* if(unit) */ /* get numbers of port, irq, flags */ sc->dev_port = rman_get_start(sc->res_port); device_printf(dev, " attach port=%x ", sc->dev_port); sc->dev_flags = device_get_flags(dev); printf(" flags=%x ", sc->dev_flags); if(unit == 0 || unit == 3) { sc->dev_irq = rman_get_start(sc->res_irq); printf(" irq=%d", sc->dev_irq); } printf("\n"); /* set base address BAx */ sc->BA = sc->dev_port; if((unit == 0) || (unit == 3)) sc->BA += FPGA1; else sc->BA += FPGA2; /* test card */ outb(sc->dev_port+0x5401, 0); /* enhanced address mode - default */ if (sc->dev_irq > 2 && sc->dev_irq < 8) { outb(sc->dev_port+0xA00D, 0x0 | sc->dev_irq); /* irq only for FPGA1 */ } else { if (sc->dev_irq > 7) { outb(sc->dev_port+0xA00D, 0x0 | (sc->dev_irq-7)); /* irq only for FPGA1 */ } } /* read ID info */ id_c = inb(sc->BA+14); if(id_c<'a' || id_c>'z') { device_printf(dev, "attach error: unknown ID=%x\n", id_c); return ENXIO; } id_d = inb(sc->BA+15); device_printf(dev, "\tFPGA code=%c%d\n", id_c, id_d); switch(unit) { case 0: /* ports A & C - input B - output */ outb(sc->BA+3,0x19); /* 100 ns, falling edge on PortC */ outb(sc->BA+4,0x80); /* set unio answer for DM */ outb(sc->BA+1,0x10); /* reset event registers */ outb(sc->BA+6,0xff); outb(sc->BA+7,0xff); outb(sc->BA+8,0xff); /* disable interrupts */ outb(sc->BA+5,0x00); break; case 1: case 2: /* PortA, PortB - output, PortC - input */ outb(sc->BA+3,0x09); /* no events */ outb(sc->BA+4,0x00); /* 0x01 at PortA */ outb(sc->BA+0,~RESET_MASK_PROG); /* All 1s at PortB */ outb(sc->BA+1,0xff); /* reset event registers */ outb(sc->BA+6,0xff); outb(sc->BA+7,0xff); outb(sc->BA+8,0xff); /* disable interrupts */ outb(sc->BA+5,0x00); break; case 3: /* PortC [7:4] - output, PortC [3:0] - input */ outb(sc->BA+3,0x13); /* PortC events with 120ms anti-dr */ outb(sc->BA+4,0x83); /* reset event registers */ outb(sc->BA+6,0xff); outb(sc->BA+7,0xff); outb(sc->BA+8,0xff); /* disable interrupts */ outb(sc->BA+5,0x00); /* EFGH = 0 */ outb(sc->BA+2,0x0f); break; case 4: /* port A - input */ outb(sc->BA+3,0x10); /* PortB = 0 */ outb(sc->BA+1,0x00); /* PortC = 0xff */ outb(sc->BA+1,0xff); /* no events */ outb(sc->BA+4,0x00); /* reset event registers */ outb(sc->BA+6,0xff); outb(sc->BA+7,0xff); outb(sc->BA+8,0xff); /* disable interrupts */ outb(sc->BA+5,0x00); break; default: return ENXIO; } sc->isopen = 0; sc->isready = 0; sc->iswait = 0; make_dev(&unio_cdevsw, unit, UID_ROOT, GID_WHEEL, 0644, "unio%r", unit); return 0; } /*********************** OPEN() ***********************/ static int unio_open(dev_t dev, int flags, int fmt, struct proc *p) { struct unio_softc *sc; unsigned char tmp; int unit = minor(dev); if (unit >= MAXDEVS) return ENXIO; sc = devclass_get_softc(unio_devclass, unit); if(sc == NULL) return ENXIO; if(sc->isopen) return EBUSY; if (unit == 2) /* init programmer RESET = 0 & SCK = 0 */ { outb(sc->BA+0, RESET_MASK_PROG & SCK_MASK_PROG); DELAY(30000); outb(sc->BA+0, (RESET_MASK_PROG | 0x01) & SCK_MASK_PROG); DELAY(7); outb(sc->BA+0, RESET_MASK_PROG & SCK_MASK_PROG); DELAY(30000); } if (unit == 4) { outb(sc->BA+2, RESET_MASK_DISPLAY); DELAY(20000); outb(sc->BA+2, ~RESET_MASK_DISPLAY | RESET_MASK_DISPLAY); } /* reset event registers */ outb(sc->BA+6, 0xff); outb(sc->BA+7, 0xff); outb(sc->BA+8, 0xff); sc->Input_Counter = 1; sc->isready = 0; sc->iswait = 0; /* setup mask interrupt register BAx+5 */ if(unit == 0 || unit == 3) tmp = 0x04; /* enable interrupt for port C */ else tmp = 0x0; if (unit == 5) outb(WDT_ADDR,1); /* WDT is ON */ outb(sc->BA+5, tmp); sc->isopen = 1; return 0; } /*********************** POLL () ***********************/ static int unio_poll (dev_t dev, int events , struct proc *p) { int revents, mask; struct unio_softc * sc; int unit = minor(dev); if (unit != 3) return; sc = devclass_get_softc(unio_devclass, unit); revents = 0; mask = POLLRDNORM; if ( (events & mask) && sc->isready) revents = mask; if (revents == 0 && (events & mask)) selrecord(p,&unio_selevent); return revents; } /*********************** READ() ***********************/ static int unio_read(dev_t dev, struct uio *uio, int flag) { struct unio_softc *sc; int s, err, i; unsigned char Reg_D, Reg_U, tmp; unsigned char uch, mask_row; int unit = minor(dev); if (unit >= MAXDEVS) return ENXIO; err = 0; sc = devclass_get_softc(unio_devclass, unit); if (sc == NULL) return ENXIO; if(!sc->isopen) return 0; switch(unit) { case 0: /* ************************* * read from demodulator * ************************* */ s = spltty(); /* uprintf("we are now in read, isready=%d\n",sc->isready);*/ if(!sc->isready) { /* data from intr not ready, must sleep */ sc->iswait = 1; err = tsleep((caddr_t)sc, PWAIT | PCATCH, "unio0w", 2*hz); sc->iswait = 0; } /* data from intr ready or timeout */ if(err == EWOULDBLOCK) { sc->isready = 0; sc->Input_Counter = 1; splx(s); return err; } buf[0] = sc->isready; sc->int0 = 0x00; err = uiomove((caddr_t)buf, sc->Input_Counter, uio); sc->Input_Counter = 1; sc->isready = 0; splx(s); break; case 2: /* ************************** * read from programmator * ************************** */ Reg_U = 0x00; for (i=0;i<4;i++) sc->prog[i] = 0x00; for (i=0;i<8;i++) { sc->prog[0] = sc->prog[0] << 1; sc->prog[1] = sc->prog[1] << 1; sc->prog[2] = sc->prog[2] << 1; sc->prog[3] = sc->prog[3] << 1; Reg_D = inb(sc->BA+2); outb(sc->BA+0,Reg_U | ~SCK_MASK_PROG); Reg_D |= 0xf0; if (Reg_D & ~MISO_1_MASK_PROG) sc->prog[0] |= 0x01; else sc->prog[0] &= 0xfe; if (Reg_D & ~MISO_2_MASK_PROG) sc->prog[1] |= 0x01; else sc->prog[1] &= 0xfe; if (Reg_D & ~MISO_3_MASK_PROG) sc->prog[2] |= 0x01; else sc->prog[2] &= 0xfe; if (Reg_D & ~MISO_4_MASK_PROG) sc->prog[3] |= 0x01; else sc->prog[3] &= 0xfe; DELAY(5); outb(sc->BA+0,Reg_U & SCK_MASK_PROG); DELAY(5); } err = uiomove((caddr_t)sc->prog, 4, uio); break; case 3: /********************** * read from keyboard * **********************/ s = spltty(); if(!sc->isready) { /* data from intr not ready, must sleep */ sc->iswait = 1; err = tsleep((caddr_t)sc, PWAIT | PCATCH, "unio3w", 5*hz); } sc->isready = 0; sc->iswait = 0; /* data from intr ready or timeout */ if(err == EWOULDBLOCK) { /* uprintf("read timeout error\n"); */ splx(s); return err; } /* scan keyboard 4x4 matrix */ outb(sc->BA+4,0x80); mask_row = 0x7f; for (i=0;i<4;i++) { outb(sc->BA+2,mask_row); DELAY(10); tmp = inb(sc->BA+2) & 0x0f; if (tmp != 0x0f) { sc->kbd_code_2 = i; break; } mask_row = mask_row >> 1; } switch(sc->kbd_code_1) { case 1: i = 3; break; case 2: i = 1; break; case 4: i = 2; break; case 8: i = 0; break; } switch(sc->kbd_code_2) { case 0: /* G */ sc->kbd_char = 0x08 + i; break; case 1: /* E */ sc->kbd_char = i; break; case 2: /* H */ sc->kbd_char = 0x0c + i; break; case 3: /* F */ sc->kbd_char = 0x04 + i; break; default: break; } splx(s); outb(sc->BA+2,0x0f); outb(sc->BA+4,0x83); outb(sc->BA+8, 0xff); /* restore event register */ /* enable interrupts */ outb(sc->BA+5, 0x04); err = uiomove((caddr_t)&sc->kbd_char, 1, uio); break; case 1: /* ************************ * no read from divider * ************************ */ err = 0; break; case 4: Reg_U = 0xff; outb(sc->BA+3,0x12); /* Data port on input */ Reg_U &= A0_MASK_DISPLAY; outb(sc->BA+2,Reg_U); Reg_U &= CS_MASK_DISPLAY; Reg_U &= RD_MASK_DISPLAY; outb(sc->BA+2,Reg_U); uch = inb(sc->BA+1); Reg_U |= ~RD_MASK_DISPLAY; Reg_U |= ~CS_MASK_DISPLAY; outb(sc->BA+2,Reg_U); outb(sc->BA+3,0x10); /* Data port on output */ err = uiomove(&uch, 1, uio); break; case 5: uch = inb(WDT_ADDR); break; default: err = ENXIO; } return err; } /*********************** WRITE() ***********************/ static int unio_write(dev_t dev, struct uio *uio, int flag) { struct unio_softc *sc; unsigned char uch[5],Reg_D; int err,i,j,timeout; unsigned char Reg_U = 0xff; int unit = minor(dev); err = 0; if (unit >= MAXDEVS) return ENXIO; sc = devclass_get_softc(unio_devclass, unit); if (sc == NULL) return ENXIO; if(!sc->isopen) return 0; switch(unit) { case 0: /************************ * write to demodulator * ************************/ err = uiomove(uch, 1, uio); Reg_D = inb(sc->BA+1); Reg_D |= uch[0]; outb(sc->BA+1,Reg_D); break; case 1: /* ******************** * write to divider * ******************** */ err = uiomove(uch, 3, uio); uch[3] = uch[2]; uch[2] = uch[1]; uch[1] = uch[0]; uch[0]=0x17; uch[4]=0x71; Reg_U &= STROBE_MASK_DIV; /* strobe low */ outb(sc->BA+1,Reg_U); for (j=0;j<5;j++) { Reg_D = uch[j]; for (i=0;i<8;i++) { if( Reg_D & 0x80 ) Reg_U |= ~DATA_MASK_DIV; else Reg_U &= DATA_MASK_DIV; outb(sc->BA+1, Reg_U & PULSE_MASK_DIV); /* falling edge */ DELAY(15); outb(sc->BA+1, Reg_U | ~PULSE_MASK_DIV); /* rising edge */ DELAY(15); Reg_D=Reg_D<<1; } } Reg_U |= ~STROBE_MASK_DIV; /* strobe high */ outb(sc->BA+1, Reg_U); break; case 2: /* ************************* * write to programmator * ************************* */ Reg_U = 0xfc; err = uiomove((caddr_t)sc->prog, 4, uio); for (i=0;i<8;i++) { if (sc->prog[0] & 0x80) Reg_U |= ~MOSI_1_MASK_PROG; else Reg_U &= MOSI_1_MASK_PROG; if (sc->prog[1] & 0x80) Reg_U |= ~MOSI_2_MASK_PROG; else Reg_U &= MOSI_2_MASK_PROG; if (sc->prog[2] & 0x80) Reg_U |= ~MOSI_3_MASK_PROG; else Reg_U &= MOSI_3_MASK_PROG; if (sc->prog[3] & 0x80) Reg_U |= ~MOSI_4_MASK_PROG; else Reg_U &= MOSI_4_MASK_PROG; outb(sc->BA+0,Reg_U); /* data */ Reg_U |= ~SCK_MASK_PROG; outb(sc->BA+0,Reg_U); /* rise SCK */ DELAY(5); Reg_U &= SCK_MASK_PROG; /* fall SCK */ outb(sc->BA+0,Reg_U); DELAY(4); sc->prog[0] = sc->prog[0] << 1; sc->prog[1] = sc->prog[1] << 1; sc->prog[2] = sc->prog[2] << 1; sc->prog[3] = sc->prog[3] << 1; } break; case 4: /* ******************** * write to display * ******************** */ err = uiomove(uch, 2, uio); timeout = 3; do { Reg_D = inb(sc->BA+0) & BUSY_MASK_DISPLAY; if (!--timeout) { return EBUSY; } } while (Reg_D); if (uch[1]) /* must be 0 or 1 */ Reg_U |= ~A0_MASK_DISPLAY; else Reg_U &= A0_MASK_DISPLAY; outb(sc->BA+1,uch[0]); /* data */ Reg_U &= CS_MASK_DISPLAY; outb(sc->BA+2,Reg_U); /* possible no */ Reg_U &= WR_MASK_DISPLAY; outb(sc->BA+2,Reg_U); Reg_U |= ~WR_MASK_DISPLAY; Reg_U |= ~CS_MASK_DISPLAY; outb(sc->BA+2,Reg_U); break; case 3: /* ***************************************** * no write to keyboard * ***************************************** */ err = 0; break; default: err = ENXIO; } return err; } /*********************** INTR_DM() ***********************/ static void unio_intr_dm(void *arg) { struct unio_softc *sc; unsigned char BT, BT_R; unsigned char i; unsigned short int j; sc = (struct unio_softc*) arg; BT = inb(sc->BA+8); /* compare with mask */ if( !(BT & EVENT_MASK_DM) ) { outb(sc->BA+8, 0xff); return; } if (sc->Input_Counter < SIZE_BUF) { BT_R = inb(sc->BA+1); for (i=0;i<17;i++) { for (j=0;j<30;j++) {}; buf[(sc->Input_Counter)++] = inb(sc->BA+0); BT_R &= STROBE_MASK_DM; outb(sc->BA+1,BT_R); for (j=0;j<30;j++) {}; buf[(sc->Input_Counter)++] = inb(sc->BA+0); BT_R |= ~STROBE_MASK_DM; outb(sc->BA+1,BT_R); } DELAY(5); for (i=0;i<17;i++) { for (j=0;j<30;j++) {}; buf[(sc->Input_Counter)++] = inb(sc->BA+0); BT_R &= STROBE_MASK_DM; outb(sc->BA+1,BT_R); for (j=0;j<30;j++) {}; buf[(sc->Input_Counter)++] = inb(sc->BA+0); BT_R |= ~STROBE_MASK_DM; outb(sc->BA+1,BT_R); } sc->isready++; } else { sc->int0 |= 0x80; } /* read PortB from unio1 */ BT = inb(sc->dev_port+FPGA2+1); /* DELAY(5); outb(sc->dev_port+FPGA2+1,BT);*/ /* reset event register */ outb(sc->BA+8, 0xff); if(sc->iswait) { /* read is waiting */ wakeup((caddr_t)sc); BT &= MPC_MASK_DIV; } else { /* read is not waiting */ sc->int0 |= 0x40; BT |= ~MPC_MASK_DIV; } outb(sc->dev_port+FPGA2+1,BT); return; } /*********************** INTR_KBD() ***********************/ static void unio_intr_kbd(void *arg) { struct unio_softc *sc; sc = (struct unio_softc*) arg; sc->kbd_code_1 = inb(sc->BA+8); if(!(sc->kbd_code_1 & EVENT_MASK_KBD)) { /* uprintf("unio: unknown interrupt for KBD\n"); */ return; } /* reset event register */ outb(sc->BA+8, 0xff); /* disable interrupts */ outb(sc->BA+5,0x00); sc->isready = 1; if(sc->iswait) { /* read is waiting */ wakeup((caddr_t)sc); /*selwakeup(&unio_selevent);*/ } else { /* read is not waiting */ selwakeup(&unio_selevent); } return; } /*********************** CLOSE() ***********************/ static int unio_close(dev_t dev, int flags, int fmt, struct proc *p) { struct unio_softc *sc; int unit = minor(dev); if (unit >= MAXDEVS) return ENXIO; sc = devclass_get_softc(unio_devclass, unit); if(sc == NULL) return ENXIO; if (unit == 2) outb(sc->BA+0,~RESET_MASK_PROG); /* reset event registers */ outb(sc->BA+6, 0xff); outb(sc->BA+7, 0xff); outb(sc->BA+8, 0xff); /* disable interrupts */ outb(sc->BA+5,0x00); sc->isopen = 0; return 0; } /*********************** DETACH() ***********************/ static int unio_detach(device_t dev) { struct unio_softc *sc; int unit = device_get_unit(dev); sc = (struct unio_softc *) device_get_softc(dev); bus_release_resource(dev, SYS_RES_IOPORT, sc->cookie_port, sc->res_port); if(unit == 0 || unit == 3) { bus_teardown_intr(dev, sc->res_irq, sc->i_handler); bus_release_resource(dev, SYS_RES_IRQ, sc->cookie_irq, sc->res_irq); } return 0; } static device_method_t unio_methods[] = { /* Device interface */ DEVMETHOD(device_probe, unio_probe), DEVMETHOD(device_attach, unio_attach), DEVMETHOD(device_detach, unio_detach), { 0, 0 } }; static driver_t unio_driver = { "unio", unio_methods, sizeof(struct unio_softc), }; DRIVER_MODULE(unio, isa, unio_driver, unio_devclass, 0, 0);