| #include "config.h" |
| #include "libopenbios/bindings.h" |
| #include "libc/byteorder.h" |
| #include "libc/vsprintf.h" |
| #include "drivers/drivers.h" |
| #include "libopenbios/ofmem.h" |
| |
| #include "escc.h" |
| |
| /* ****************************************************************** |
| * serial console functions |
| * ****************************************************************** */ |
| |
| static volatile unsigned char *escc_serial_dev; |
| |
| #define CTRL(addr) (*(volatile unsigned char *)(uintptr_t)(addr)) |
| #ifdef CONFIG_DRIVER_ESCC_SUN |
| #define DATA(addr) (*(volatile unsigned char *)(uintptr_t)(addr + 2)) |
| #else |
| #define DATA(addr) (*(volatile unsigned char *)(uintptr_t)(addr + 16)) |
| #endif |
| |
| /* Conversion routines to/from brg time constants from/to bits |
| * per second. |
| */ |
| #define BPS_TO_BRG(bps, freq) ((((freq) + (bps)) / (2 * (bps))) - 2) |
| |
| #ifdef CONFIG_DRIVER_ESCC_SUN |
| #define ESCC_CLOCK 4915200 /* Zilog input clock rate. */ |
| #else |
| #define ESCC_CLOCK 3686400 |
| #endif |
| #define ESCC_CLOCK_DIVISOR 16 /* Divisor this driver uses. */ |
| |
| /* Write Register 3 */ |
| #define RxENAB 0x1 /* Rx Enable */ |
| #define Rx8 0xc0 /* Rx 8 Bits/Character */ |
| |
| /* Write Register 4 */ |
| #define SB1 0x4 /* 1 stop bit/char */ |
| #define X16CLK 0x40 /* x16 clock mode */ |
| |
| /* Write Register 5 */ |
| #define RTS 0x2 /* RTS */ |
| #define TxENAB 0x8 /* Tx Enable */ |
| #define Tx8 0x60 /* Tx 8 bits/character */ |
| #define DTR 0x80 /* DTR */ |
| |
| /* Write Register 9 */ |
| #define SW_CHAN_RESET_B 0x40 /* Software reset channel B */ |
| |
| /* Write Register 14 (Misc control bits) */ |
| #define BRENAB 1 /* Baud rate generator enable */ |
| #define BRSRC 2 /* Baud rate generator source */ |
| |
| /* Read Register 0 */ |
| #define Rx_CH_AV 0x1 /* Rx Character Available */ |
| #define Tx_BUF_EMP 0x4 /* Tx Buffer empty */ |
| |
| int escc_uart_charav(uintptr_t port) |
| { |
| return (CTRL(port) & Rx_CH_AV) != 0; |
| } |
| |
| char escc_uart_getchar(uintptr_t port) |
| { |
| while (!escc_uart_charav(port)) |
| ; |
| return DATA(port) & 0177; |
| } |
| |
| static void escc_uart_port_putchar(uintptr_t port, unsigned char c) |
| { |
| if (!escc_serial_dev) |
| return; |
| |
| if (c == '\n') |
| escc_uart_port_putchar(port, '\r'); |
| while (!(CTRL(port) & Tx_BUF_EMP)) |
| ; |
| DATA(port) = c; |
| } |
| |
| static void uart_init_line(volatile unsigned char *port, unsigned long baud, int index) |
| { |
| CTRL(port) = 9; // reg 9 |
| CTRL(port) = SW_CHAN_RESET_B << index; |
| |
| CTRL(port) = 4; // reg 4 |
| CTRL(port) = SB1 | X16CLK; // no parity, async, 1 stop bit, 16x |
| // clock |
| |
| baud = BPS_TO_BRG(baud, ESCC_CLOCK / ESCC_CLOCK_DIVISOR); |
| |
| CTRL(port) = 12; // reg 12 |
| CTRL(port) = baud & 0xff; |
| CTRL(port) = 13; // reg 13 |
| CTRL(port) = (baud >> 8) & 0xff; |
| CTRL(port) = 14; // reg 14 |
| CTRL(port) = BRSRC | BRENAB; |
| |
| CTRL(port) = 3; // reg 3 |
| CTRL(port) = RxENAB | Rx8; // enable rx, 8 bits/char |
| |
| CTRL(port) = 5; // reg 5 |
| CTRL(port) = RTS | TxENAB | Tx8 | DTR; // enable tx, 8 bits/char, |
| // set RTS & DTR |
| |
| } |
| |
| int escc_uart_init(phys_addr_t port, unsigned long speed) |
| { |
| #ifdef CONFIG_DRIVER_ESCC_SUN |
| escc_serial_dev = (unsigned char *)ofmem_map_io(port & ~7ULL, ZS_REGS); |
| escc_serial_dev += port & 7ULL; |
| #else |
| escc_serial_dev = (unsigned char *)(uintptr_t)port; |
| #endif |
| uart_init_line(escc_serial_dev, speed, 1); |
| return -1; |
| } |
| |
| void escc_uart_putchar(int c) |
| { |
| escc_uart_port_putchar((uintptr_t)escc_serial_dev, (unsigned char) (c & 0xff)); |
| } |
| |
| void serial_cls(void) |
| { |
| escc_uart_putchar(27); |
| escc_uart_putchar('['); |
| escc_uart_putchar('H'); |
| escc_uart_putchar(27); |
| escc_uart_putchar('['); |
| escc_uart_putchar('J'); |
| } |
| |
| /* ( addr len -- actual ) */ |
| static void |
| escc_port_read(ucell *address) |
| { |
| char *addr; |
| int len; |
| |
| len = POP(); |
| addr = (char *)cell2pointer(POP()); |
| |
| if (len < 1) |
| printk("escc_read: bad len, addr %p len %x\n", addr, len); |
| |
| if (escc_uart_charav(*address)) { |
| *addr = (char)escc_uart_getchar(*address); |
| PUSH(1); |
| } else { |
| PUSH(0); |
| } |
| } |
| |
| /* ( addr len -- actual ) */ |
| static void |
| escc_port_write(ucell *address) |
| { |
| unsigned char *addr; |
| int i, len; |
| |
| len = POP(); |
| addr = (unsigned char *)cell2pointer(POP()); |
| |
| for (i = 0; i < len; i++) { |
| escc_uart_port_putchar(*address, addr[i]); |
| } |
| PUSH(len); |
| } |
| |
| static void |
| escc_port_close(void) |
| { |
| } |
| |
| #ifdef CONFIG_DRIVER_ESCC_SUN |
| static void |
| escc_port_open(ucell *address) |
| { |
| |
| int len; |
| phandle_t ph; |
| unsigned long *prop; |
| char *args; |
| |
| fword("my-self"); |
| fword("ihandle>phandle"); |
| ph = (phandle_t)POP(); |
| prop = (unsigned long *)get_property(ph, "address", &len); |
| *address = *prop; |
| fword("my-args"); |
| args = pop_fstr_copy(); |
| if (args) { |
| if (args[0] == 'a') |
| *address += 4; |
| //printk("escc_open: address %lx, args %s\n", *address, args); |
| free(args); |
| } |
| |
| RET ( -1 ); |
| } |
| |
| #else |
| |
| static void |
| escc_port_open(ucell *address) |
| { |
| *address = (unsigned long)escc_serial_dev; // XXX |
| RET(-1); |
| } |
| |
| static void |
| escc_open(int *idx) |
| { |
| RET(-1); |
| } |
| |
| static void |
| escc_close(int *idx) |
| { |
| } |
| |
| DECLARE_UNNAMED_NODE(escc, 0, sizeof(int *)); |
| |
| NODE_METHODS(escc) = { |
| { "open", escc_open }, |
| { "close", escc_close }, |
| }; |
| |
| #endif |
| |
| DECLARE_UNNAMED_NODE(escc_port, 0, sizeof(ucell)); |
| |
| NODE_METHODS(escc_port) = { |
| { "open", escc_port_open }, |
| { "close", escc_port_close }, |
| { "read", escc_port_read }, |
| { "write", escc_port_write }, |
| }; |
| |
| #ifdef CONFIG_DRIVER_ESCC_SUN |
| static volatile unsigned char *kbd_dev; |
| |
| void kbd_init(phys_addr_t base) |
| { |
| kbd_dev = (unsigned char *)ofmem_map_io(base, 2 * 4); |
| kbd_dev += 4; |
| } |
| |
| static const unsigned char sunkbd_keycode[128] = { |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', 0, 8, |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, |
| 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', '\\', 13, |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| ' ', |
| }; |
| |
| static const unsigned char sunkbd_keycode_shifted[128] = { |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', 0, 8, |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, |
| 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '"', '|', 13, |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?', |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| ' ', |
| }; |
| |
| static int shiftstate; |
| |
| int |
| keyboard_dataready(void) |
| { |
| return ((kbd_dev[0] & 1) == 1); |
| } |
| |
| unsigned char |
| keyboard_readdata(void) |
| { |
| volatile unsigned char ch; |
| |
| while (!keyboard_dataready()) { } |
| |
| do { |
| ch = kbd_dev[2] & 0xff; |
| if (ch == 99) |
| shiftstate |= 1; |
| else if (ch == 110) |
| shiftstate |= 2; |
| else if (ch == 227) |
| shiftstate &= ~1; |
| else if (ch == 238) |
| shiftstate &= ~2; |
| //printk("getch: %d\n", ch); |
| } // If release, wait for key press |
| while ((ch & 0x80) == 0x80 || ch == 238 || ch == 227); |
| //printk("getch rel: %d\n", ch); |
| ch &= 0x7f; |
| if (shiftstate) |
| ch = sunkbd_keycode_shifted[ch]; |
| else |
| ch = sunkbd_keycode[ch]; |
| //printk("getch xlate: %d\n", ch); |
| |
| return ch; |
| } |
| |
| /* ( addr len -- actual ) */ |
| static void |
| escc_read_keyboard(void) |
| { |
| unsigned char *addr; |
| int len; |
| |
| len = POP(); |
| addr = (unsigned char *)POP(); |
| |
| if (len < 1) |
| printk("escc_read: bad len, addr %p len %x\n", addr, len); |
| |
| if (keyboard_dataready()) { |
| *addr = keyboard_readdata(); |
| PUSH(1); |
| } else { |
| PUSH(0); |
| } |
| } |
| |
| DECLARE_UNNAMED_NODE(escc_keyboard, 0, sizeof(ucell)); |
| |
| NODE_METHODS(escc_keyboard) = { |
| { "open", escc_port_open }, |
| { "close", escc_port_close }, |
| { "read", escc_read_keyboard }, |
| }; |
| |
| void |
| ob_zs_init(phys_addr_t base, uint64_t offset, int intr, int slave, int keyboard) |
| { |
| char nodebuff[256]; |
| phandle_t aliases; |
| |
| ob_new_obio_device("zs", "serial"); |
| |
| ob_reg(base, offset, ZS_REGS, 1); |
| |
| PUSH(slave); |
| fword("encode-int"); |
| push_str("slave"); |
| fword("property"); |
| |
| if (keyboard) { |
| PUSH(0); |
| PUSH(0); |
| push_str("keyboard"); |
| fword("property"); |
| |
| PUSH(0); |
| PUSH(0); |
| push_str("mouse"); |
| fword("property"); |
| } |
| |
| ob_intr(intr); |
| |
| PUSH(0); |
| PUSH(0); |
| push_str("port-a-ignore-cd"); |
| fword("property"); |
| |
| PUSH(0); |
| PUSH(0); |
| push_str("port-b-ignore-cd"); |
| fword("property"); |
| |
| if (keyboard) { |
| BIND_NODE_METHODS(get_cur_dev(), escc_keyboard); |
| } else { |
| BIND_NODE_METHODS(get_cur_dev(), escc_port); |
| } |
| |
| fword("finish-device"); |
| |
| aliases = find_dev("/aliases"); |
| if (keyboard) { |
| snprintf(nodebuff, sizeof(nodebuff), "/obio/zs@0,%x", |
| (int)offset & 0xffffffff); |
| set_property(aliases, "keyboard", nodebuff, strlen(nodebuff) + 1); |
| } else { |
| snprintf(nodebuff, sizeof(nodebuff), "/obio/zs@0,%x:a", |
| (int)offset & 0xffffffff); |
| set_property(aliases, "ttya", nodebuff, strlen(nodebuff) + 1); |
| |
| snprintf(nodebuff, sizeof(nodebuff), "/obio/zs@0,%x:b", |
| (int)offset & 0xffffffff); |
| set_property(aliases, "ttyb", nodebuff, strlen(nodebuff) + 1); |
| |
| } |
| } |
| |
| #else |
| |
| static void |
| escc_add_channel(const char *path, const char *node, phys_addr_t addr, |
| int esnum) |
| { |
| char buf[64], tty[32]; |
| phandle_t dnode, aliases; |
| |
| cell props[10]; |
| ucell offset; |
| int index; |
| int legacy; |
| |
| int dbdma_offsets[2][2] = { |
| /* ch-b */ |
| { 0x6, 0x7 }, |
| /* ch-a */ |
| { 0x4, 0x5 } |
| }; |
| |
| int reg_offsets[2][2][3] = { |
| { |
| /* ch-b */ |
| { 0x00, 0x10, 0x40 }, |
| /* ch-a */ |
| { 0x20, 0x30, 0x50 } |
| },{ |
| /* legacy ch-b */ |
| { 0x0, 0x4, 0x8 }, |
| /* legacy ch-a */ |
| { 0x2, 0x6, 0xa } |
| } |
| }; |
| |
| switch (esnum) { |
| case 2: index = 1; legacy = 0; break; |
| case 3: index = 0; legacy = 0; break; |
| case 4: index = 1; legacy = 1; break; |
| case 5: index = 0; legacy = 1; break; |
| default: return; |
| } |
| |
| /* add device */ |
| |
| fword("new-device"); |
| |
| snprintf(buf, sizeof(buf), "ch-%s", node); |
| push_str(buf); |
| fword("device-name"); |
| |
| BIND_NODE_METHODS(get_cur_dev(), escc_port); |
| |
| /* add aliases */ |
| |
| if (!legacy) { |
| aliases = find_dev("/aliases"); |
| |
| snprintf(buf, sizeof(buf), "%s/ch-%s", path, node); |
| OLDWORLD(snprintf(tty, sizeof(tty), "tty%s", node)); |
| OLDWORLD(set_property(aliases, tty, buf, strlen(buf) + 1)); |
| snprintf(tty, sizeof(tty), "scc%s", node); |
| set_property(aliases, tty, buf, strlen(buf) + 1); |
| } |
| /* add properties */ |
| |
| dnode = get_cur_dev(); |
| set_property(dnode, "device_type", "serial", |
| strlen("serial") + 1); |
| |
| snprintf(buf, sizeof(buf), "chrp,es%d", esnum); |
| set_property(dnode, "compatible", buf, 9); |
| |
| if (legacy) { |
| offset = IO_ESCC_LEGACY_OFFSET; |
| } else { |
| offset = IO_ESCC_OFFSET; |
| } |
| |
| props[0] = offset + reg_offsets[legacy][index][0]; |
| props[1] = 0x1; |
| props[2] = offset + reg_offsets[legacy][index][1]; |
| props[3] = 0x1; |
| props[4] = offset + reg_offsets[legacy][index][2]; |
| props[5] = 0x1; |
| props[6] = 0x8000 + dbdma_offsets[index][0] * 0x100; |
| props[7] = 0x100; |
| props[8] = 0x8000 + dbdma_offsets[index][1] * 0x100; |
| props[9] = 0x100; |
| set_property(dnode, "reg", (char *)&props, 10 * sizeof(cell)); |
| |
| props[0] = addr + offset + reg_offsets[legacy][index][0]; |
| OLDWORLD(set_property(dnode, "AAPL,address", |
| (char *)&props, 1 * sizeof(cell))); |
| |
| props[0] = 0x10 - index; |
| OLDWORLD(set_property(dnode, "AAPL,interrupts", |
| (char *)&props, 1 * sizeof(cell))); |
| |
| props[0] = (0x24) + index; |
| props[1] = 0x1; |
| props[2] = dbdma_offsets[index][0]; |
| props[3] = 0x0; |
| props[4] = dbdma_offsets[index][1]; |
| props[5] = 0x0; |
| NEWWORLD(set_property(dnode, "interrupts", |
| (char *)&props, 6 * sizeof(cell))); |
| |
| set_int_property(dnode, "slot-names", 0); |
| |
| fword("finish-device"); |
| |
| uart_init_line((unsigned char*)addr + offset + reg_offsets[legacy][index][0], |
| CONFIG_SERIAL_SPEED, index); |
| } |
| |
| void |
| escc_init(const char *path, phys_addr_t addr) |
| { |
| char buf[64]; |
| int props[2]; |
| phandle_t dnode; |
| |
| fword("new-device"); |
| |
| push_str("escc"); |
| fword("device-name"); |
| |
| dnode = get_cur_dev(); |
| set_int_property(dnode, "#address-cells", 1); |
| props[0] = __cpu_to_be32(IO_ESCC_OFFSET); |
| props[1] = __cpu_to_be32(IO_ESCC_SIZE); |
| set_property(dnode, "reg", (char *)&props, sizeof(props)); |
| set_property(dnode, "device_type", "escc", |
| strlen("escc") + 1); |
| set_property(dnode, "compatible", "escc\0CHRP,es0", 14); |
| set_property(dnode, "ranges", "", 0); |
| |
| snprintf(buf, sizeof(buf), "%s/escc", path); |
| escc_add_channel(buf, "a", addr, 2); |
| escc_add_channel(buf, "b", addr, 3); |
| |
| BIND_NODE_METHODS(dnode, escc); |
| fword("finish-device"); |
| |
| escc_serial_dev = (unsigned char *)addr + IO_ESCC_OFFSET + |
| (CONFIG_SERIAL_PORT ? 0 : 0x20); |
| |
| fword("new-device"); |
| |
| push_str("escc-legacy"); |
| fword("device-name"); |
| |
| dnode = get_cur_dev(); |
| set_int_property(dnode, "#address-cells", 1); |
| props[0] = __cpu_to_be32(IO_ESCC_LEGACY_OFFSET); |
| props[1] = __cpu_to_be32(IO_ESCC_LEGACY_SIZE); |
| set_property(dnode, "reg", (char *)&props, sizeof(props)); |
| set_property(dnode, "device_type", "escc-legacy", |
| strlen("escc-legacy") + 1); |
| set_property(dnode, "compatible", "chrp,es1", 9); |
| set_property(dnode, "ranges", "", 0); |
| |
| snprintf(buf, sizeof(buf), "%s/escc-legacy", path); |
| escc_add_channel(buf, "a", addr, 4); |
| escc_add_channel(buf, "b", addr, 5); |
| |
| BIND_NODE_METHODS(dnode, escc); |
| fword("finish-device"); |
| } |
| #endif |