X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=drivers%2Fserial%2Fsunzilog.c;h=cd49ebbf4a45e46ad4cf91b945814985200d884e;hb=9464c7cf61b9433057924c36e6e02f303a00e768;hp=d34f336d53d80db4fdebd4016e8f9dedb077c770;hpb=41689045f6a3cbe0550e1d34e9cc20d2e8c432ba;p=linux-2.6.git diff --git a/drivers/serial/sunzilog.c b/drivers/serial/sunzilog.c index d34f336d5..cd49ebbf4 100644 --- a/drivers/serial/sunzilog.c +++ b/drivers/serial/sunzilog.c @@ -1,4 +1,5 @@ -/* sunzilog.c: Zilog serial driver for Sparc systems. +/* + * sunzilog.c * * Driver for Zilog serial chips found on Sun workstations and * servers. This driver could actually be made more generic. @@ -9,9 +10,10 @@ * C. Dost, Pete Zaitcev, Ted Ts'o and Alex Buell for their * work there. * - * Copyright (C) 2002, 2006 David S. Miller (davem@davemloft.net) + * Copyright (C) 2002 David S. Miller (davem@redhat.com) */ +#include #include #include #include @@ -36,8 +38,10 @@ #include #include -#include -#include +#ifdef CONFIG_SPARC64 +#include +#endif +#include #if defined(CONFIG_SERIAL_SUNZILOG_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ) #define SUPPORT_SYSRQ @@ -61,13 +65,16 @@ #define ZSDELAY() #define ZSDELAY_LONG() #define ZS_WSYNC(__channel) \ - readb(&((__channel)->control)) + sbus_readb(&((__channel)->control)) #endif static int num_sunzilog; #define NUM_SUNZILOG num_sunzilog #define NUM_CHANNELS (NUM_SUNZILOG * 2) +#define KEYBOARD_LINE 0x2 +#define MOUSE_LINE 0x3 + #define ZS_CLOCK 4915200 /* Zilog input clock rate. */ #define ZS_CLOCK_DIVISOR 16 /* Divisor this driver uses. */ @@ -100,7 +107,7 @@ struct uart_sunzilog_port { unsigned char prev_status; #ifdef CONFIG_SERIO - struct serio serio; + struct serio *serio; int serio_open; #endif }; @@ -131,9 +138,9 @@ static unsigned char read_zsreg(struct zilog_channel __iomem *channel, { unsigned char retval; - writeb(reg, &channel->control); + sbus_writeb(reg, &channel->control); ZSDELAY(); - retval = readb(&channel->control); + retval = sbus_readb(&channel->control); ZSDELAY(); return retval; @@ -142,9 +149,9 @@ static unsigned char read_zsreg(struct zilog_channel __iomem *channel, static void write_zsreg(struct zilog_channel __iomem *channel, unsigned char reg, unsigned char value) { - writeb(reg, &channel->control); + sbus_writeb(reg, &channel->control); ZSDELAY(); - writeb(value, &channel->control); + sbus_writeb(value, &channel->control); ZSDELAY(); } @@ -155,17 +162,17 @@ static void sunzilog_clear_fifo(struct zilog_channel __iomem *channel) for (i = 0; i < 32; i++) { unsigned char regval; - regval = readb(&channel->control); + regval = sbus_readb(&channel->control); ZSDELAY(); if (regval & Rx_CH_AV) break; regval = read_zsreg(channel, R1); - readb(&channel->data); + sbus_readb(&channel->data); ZSDELAY(); if (regval & (PAR_ERR | Rx_OVR | CRC_ERR)) { - writeb(ERR_RES, &channel->control); + sbus_writeb(ERR_RES, &channel->control); ZSDELAY(); ZS_WSYNC(channel); } @@ -187,7 +194,7 @@ static void __load_zsregs(struct zilog_channel __iomem *channel, unsigned char * udelay(100); } - writeb(ERR_RES, &channel->control); + sbus_writeb(ERR_RES, &channel->control); ZSDELAY(); ZS_WSYNC(channel); @@ -284,7 +291,7 @@ static void sunzilog_kbdms_receive_chars(struct uart_sunzilog_port *up, /* Stop-A is handled by drivers/char/keyboard.c now. */ #ifdef CONFIG_SERIO if (up->serio_open) - serio_interrupt(&up->serio, ch, 0, regs); + serio_interrupt(up->serio, ch, 0, regs); #endif } else if (ZS_IS_MOUSE(up)) { int ret = suncore_mouse_baud_detection(ch, is_break); @@ -299,7 +306,7 @@ static void sunzilog_kbdms_receive_chars(struct uart_sunzilog_port *up, case 0: #ifdef CONFIG_SERIO if (up->serio_open) - serio_interrupt(&up->serio, ch, 0, regs); + serio_interrupt(up->serio, ch, 0, regs); #endif break; }; @@ -323,12 +330,12 @@ sunzilog_receive_chars(struct uart_sunzilog_port *up, r1 = read_zsreg(channel, R1); if (r1 & (PAR_ERR | Rx_OVR | CRC_ERR)) { - writeb(ERR_RES, &channel->control); + sbus_writeb(ERR_RES, &channel->control); ZSDELAY(); ZS_WSYNC(channel); } - ch = readb(&channel->control); + ch = sbus_readb(&channel->control); ZSDELAY(); /* This funny hack depends upon BRK_ABRT not interfering @@ -340,7 +347,7 @@ sunzilog_receive_chars(struct uart_sunzilog_port *up, if (!(ch & Rx_CH_AV)) break; - ch = readb(&channel->data); + ch = sbus_readb(&channel->data); ZSDELAY(); ch &= up->parity_mask; @@ -399,10 +406,10 @@ static void sunzilog_status_handle(struct uart_sunzilog_port *up, { unsigned char status; - status = readb(&channel->control); + status = sbus_readb(&channel->control); ZSDELAY(); - writeb(RES_EXT_INT, &channel->control); + sbus_writeb(RES_EXT_INT, &channel->control); ZSDELAY(); ZS_WSYNC(channel); @@ -414,7 +421,7 @@ static void sunzilog_status_handle(struct uart_sunzilog_port *up, * confusing the PROM. */ while (1) { - status = readb(&channel->control); + status = sbus_readb(&channel->control); ZSDELAY(); if (!(status & BRK_ABRT)) break; @@ -451,7 +458,7 @@ static void sunzilog_transmit_chars(struct uart_sunzilog_port *up, struct circ_buf *xmit; if (ZS_IS_CONS(up)) { - unsigned char status = readb(&channel->control); + unsigned char status = sbus_readb(&channel->control); ZSDELAY(); /* TX still busy? Just wait for the next TX done interrupt. @@ -480,7 +487,7 @@ static void sunzilog_transmit_chars(struct uart_sunzilog_port *up, if (up->port.x_char) { up->flags |= SUNZILOG_FLAG_TX_ACTIVE; - writeb(up->port.x_char, &channel->data); + sbus_writeb(up->port.x_char, &channel->data); ZSDELAY(); ZS_WSYNC(channel); @@ -499,7 +506,7 @@ static void sunzilog_transmit_chars(struct uart_sunzilog_port *up, goto ack_tx_int; up->flags |= SUNZILOG_FLAG_TX_ACTIVE; - writeb(xmit->buf[xmit->tail], &channel->data); + sbus_writeb(xmit->buf[xmit->tail], &channel->data); ZSDELAY(); ZS_WSYNC(channel); @@ -512,7 +519,7 @@ static void sunzilog_transmit_chars(struct uart_sunzilog_port *up, return; ack_tx_int: - writeb(RES_Tx_P, &channel->control); + sbus_writeb(RES_Tx_P, &channel->control); ZSDELAY(); ZS_WSYNC(channel); } @@ -533,7 +540,7 @@ static irqreturn_t sunzilog_interrupt(int irq, void *dev_id, struct pt_regs *reg /* Channel A */ tty = NULL; if (r3 & (CHAEXT | CHATxIP | CHARxIP)) { - writeb(RES_H_IUS, &channel->control); + sbus_writeb(RES_H_IUS, &channel->control); ZSDELAY(); ZS_WSYNC(channel); @@ -556,7 +563,7 @@ static irqreturn_t sunzilog_interrupt(int irq, void *dev_id, struct pt_regs *reg spin_lock(&up->port.lock); tty = NULL; if (r3 & (CHBEXT | CHBTxIP | CHBRxIP)) { - writeb(RES_H_IUS, &channel->control); + sbus_writeb(RES_H_IUS, &channel->control); ZSDELAY(); ZS_WSYNC(channel); @@ -587,7 +594,7 @@ static __inline__ unsigned char sunzilog_read_channel_status(struct uart_port *p unsigned char status; channel = ZILOG_CHANNEL_FROM_PORT(port); - status = readb(&channel->control); + status = sbus_readb(&channel->control); ZSDELAY(); return status; @@ -675,7 +682,7 @@ static void sunzilog_start_tx(struct uart_port *port) up->flags |= SUNZILOG_FLAG_TX_ACTIVE; up->flags &= ~SUNZILOG_FLAG_TX_STOPPED; - status = readb(&channel->control); + status = sbus_readb(&channel->control); ZSDELAY(); /* TX busy? Just wait for the TX done interrupt. */ @@ -686,7 +693,7 @@ static void sunzilog_start_tx(struct uart_port *port) * IRQ sending engine. */ if (port->x_char) { - writeb(port->x_char, &channel->data); + sbus_writeb(port->x_char, &channel->data); ZSDELAY(); ZS_WSYNC(channel); @@ -695,7 +702,7 @@ static void sunzilog_start_tx(struct uart_port *port) } else { struct circ_buf *xmit = &port->info->xmit; - writeb(xmit->buf[xmit->tail], &channel->data); + sbus_writeb(xmit->buf[xmit->tail], &channel->data); ZSDELAY(); ZS_WSYNC(channel); @@ -772,7 +779,7 @@ static void __sunzilog_startup(struct uart_sunzilog_port *up) struct zilog_channel __iomem *channel; channel = ZILOG_CHANNEL_FROM_PORT(&up->port); - up->prev_status = readb(&channel->control); + up->prev_status = sbus_readb(&channel->control); /* Enable receiver and transmitter. */ up->curregs[R3] |= RxENAB; @@ -956,7 +963,7 @@ sunzilog_set_termios(struct uart_port *port, struct termios *termios, static const char *sunzilog_type(struct uart_port *port) { - return "zs"; + return "SunZilog"; } /* We do not request/release mappings of the registers here, this @@ -1005,55 +1012,242 @@ static struct uart_sunzilog_port *sunzilog_port_table; static struct zilog_layout __iomem **sunzilog_chip_regs; static struct uart_sunzilog_port *sunzilog_irq_chain; +static int zilog_irq = -1; static struct uart_driver sunzilog_reg = { .owner = THIS_MODULE, .driver_name = "ttyS", + .devfs_name = "tts/", .dev_name = "ttyS", .major = TTY_MAJOR, }; -static int __init sunzilog_alloc_tables(void) +static void * __init alloc_one_table(unsigned long size) { - struct uart_sunzilog_port *up; - unsigned long size; - int i; + void *ret; - size = NUM_CHANNELS * sizeof(struct uart_sunzilog_port); - sunzilog_port_table = kzalloc(size, GFP_KERNEL); - if (!sunzilog_port_table) - return -ENOMEM; + ret = kmalloc(size, GFP_KERNEL); + if (ret != NULL) + memset(ret, 0, size); - for (i = 0; i < NUM_CHANNELS; i++) { - up = &sunzilog_port_table[i]; + return ret; +} + +static void __init sunzilog_alloc_tables(void) +{ + sunzilog_port_table = + alloc_one_table(NUM_CHANNELS * sizeof(struct uart_sunzilog_port)); + sunzilog_chip_regs = + alloc_one_table(NUM_SUNZILOG * sizeof(struct zilog_layout __iomem *)); + + if (sunzilog_port_table == NULL || sunzilog_chip_regs == NULL) { + prom_printf("SunZilog: Cannot allocate tables.\n"); + prom_halt(); + } +} - spin_lock_init(&up->port.lock); +#ifdef CONFIG_SPARC64 - if (i == 0) - sunzilog_irq_chain = up; +/* We used to attempt to use the address property of the Zilog device node + * but that totally is not necessary on sparc64. + */ +static struct zilog_layout __iomem * __init get_zs_sun4u(int chip, int zsnode) +{ + void __iomem *mapped_addr; + unsigned int sun4u_ino; + struct sbus_bus *sbus = NULL; + struct sbus_dev *sdev = NULL; + int err; - if (i < NUM_CHANNELS - 1) - up->next = up + 1; - else - up->next = NULL; + if (central_bus == NULL) { + for_each_sbus(sbus) { + for_each_sbusdev(sdev, sbus) { + if (sdev->prom_node == zsnode) + goto found; + } + } + } + found: + if (sdev == NULL && central_bus == NULL) { + prom_printf("SunZilog: sdev&¢ral == NULL for " + "Zilog %d in get_zs_sun4u.\n", chip); + prom_halt(); } + if (central_bus == NULL) { + mapped_addr = + sbus_ioremap(&sdev->resource[0], 0, + PAGE_SIZE, + "Zilog Registers"); + } else { + struct linux_prom_registers zsregs[1]; + + err = prom_getproperty(zsnode, "reg", + (char *) &zsregs[0], + sizeof(zsregs)); + if (err == -1) { + prom_printf("SunZilog: Cannot map " + "Zilog %d regs on " + "central bus.\n", chip); + prom_halt(); + } + apply_fhc_ranges(central_bus->child, + &zsregs[0], 1); + apply_central_ranges(central_bus, &zsregs[0], 1); + mapped_addr = (void __iomem *) + ((((u64)zsregs[0].which_io)<<32UL) | + ((u64)zsregs[0].phys_addr)); + } + + if (zilog_irq == -1) { + if (central_bus) { + unsigned long iclr, imap; - size = NUM_SUNZILOG * sizeof(struct zilog_layout __iomem *); - sunzilog_chip_regs = kzalloc(size, GFP_KERNEL); - if (!sunzilog_chip_regs) { - kfree(sunzilog_port_table); - sunzilog_irq_chain = NULL; - return -ENOMEM; + iclr = central_bus->child->fhc_regs.uregs + + FHC_UREGS_ICLR; + imap = central_bus->child->fhc_regs.uregs + + FHC_UREGS_IMAP; + zilog_irq = build_irq(12, 0, iclr, imap); + } else { + err = prom_getproperty(zsnode, "interrupts", + (char *) &sun4u_ino, + sizeof(sun4u_ino)); + zilog_irq = sbus_build_irq(sbus_root, sun4u_ino); + } } - return 0; + return (struct zilog_layout __iomem *) mapped_addr; +} +#else /* CONFIG_SPARC64 */ + +/* + * XXX The sun4d case is utterly screwed: it tries to re-walk the tree + * (for the 3rd time) in order to find bootbus and cpu. Streamline it. + */ +static struct zilog_layout __iomem * __init get_zs_sun4cmd(int chip, int node) +{ + struct linux_prom_irqs irq_info[2]; + void __iomem *mapped_addr = NULL; + int zsnode, cpunode, bbnode; + struct linux_prom_registers zsreg[4]; + struct resource res; + + if (sparc_cpu_model == sun4d) { + int walk; + + zsnode = 0; + bbnode = 0; + cpunode = 0; + for (walk = prom_getchild(prom_root_node); + (walk = prom_searchsiblings(walk, "cpu-unit")) != 0; + walk = prom_getsibling(walk)) { + bbnode = prom_getchild(walk); + if (bbnode && + (bbnode = prom_searchsiblings(bbnode, "bootbus"))) { + if ((zsnode = prom_getchild(bbnode)) == node) { + cpunode = walk; + break; + } + } + } + if (!walk) { + prom_printf("SunZilog: Cannot find the %d'th bootbus on sun4d.\n", + (chip / 2)); + prom_halt(); + } + + if (prom_getproperty(zsnode, "reg", + (char *) zsreg, sizeof(zsreg)) == -1) { + prom_printf("SunZilog: Cannot map Zilog %d\n", chip); + prom_halt(); + } + /* XXX Looks like an off by one? */ + prom_apply_generic_ranges(bbnode, cpunode, zsreg, 1); + res.start = zsreg[0].phys_addr; + res.end = res.start + (8 - 1); + res.flags = zsreg[0].which_io | IORESOURCE_IO; + mapped_addr = sbus_ioremap(&res, 0, 8, "Zilog Serial"); + + } else { + zsnode = node; + +#if 0 /* XXX When was this used? */ + if (prom_getintdefault(zsnode, "slave", -1) != chipid) { + zsnode = prom_getsibling(zsnode); + continue; + } +#endif + + /* + * "address" is only present on ports that OBP opened + * (from Mitch Bradley's "Hitchhiker's Guide to OBP"). + * We do not use it. + */ + + if (prom_getproperty(zsnode, "reg", + (char *) zsreg, sizeof(zsreg)) == -1) { + prom_printf("SunZilog: Cannot map Zilog %d\n", chip); + prom_halt(); + } + if (sparc_cpu_model == sun4m) /* Crude. Pass parent. XXX */ + prom_apply_obio_ranges(zsreg, 1); + res.start = zsreg[0].phys_addr; + res.end = res.start + (8 - 1); + res.flags = zsreg[0].which_io | IORESOURCE_IO; + mapped_addr = sbus_ioremap(&res, 0, 8, "Zilog Serial"); + } + + if (prom_getproperty(zsnode, "intr", + (char *) irq_info, sizeof(irq_info)) + % sizeof(struct linux_prom_irqs)) { + prom_printf("SunZilog: Cannot get IRQ property for Zilog %d.\n", + chip); + prom_halt(); + } + if (zilog_irq == -1) { + zilog_irq = irq_info[0].pri; + } else if (zilog_irq != irq_info[0].pri) { + /* XXX. Dumb. Should handle per-chip IRQ, for add-ons. */ + prom_printf("SunZilog: Inconsistent IRQ layout for Zilog %d.\n", + chip); + prom_halt(); + } + + return (struct zilog_layout __iomem *) mapped_addr; } +#endif /* !(CONFIG_SPARC64) */ -static void sunzilog_free_tables(void) +/* Get the address of the registers for SunZilog instance CHIP. */ +static struct zilog_layout __iomem * __init get_zs(int chip, int node) { - kfree(sunzilog_port_table); - sunzilog_irq_chain = NULL; - kfree(sunzilog_chip_regs); + if (chip < 0 || chip >= NUM_SUNZILOG) { + prom_printf("SunZilog: Illegal chip number %d in get_zs.\n", chip); + prom_halt(); + } + +#ifdef CONFIG_SPARC64 + return get_zs_sun4u(chip, node); +#else + + if (sparc_cpu_model == sun4) { + struct resource res; + + /* Not probe-able, hard code it. */ + switch (chip) { + case 0: + res.start = 0xf1000000; + break; + case 1: + res.start = 0xf0000000; + break; + }; + zilog_irq = 12; + res.end = (res.start + (8 - 1)); + res.flags = IORESOURCE_IO; + return sbus_ioremap(&res, 0, 8, "SunZilog"); + } + + return get_zs_sun4cmd(chip, node); +#endif } #define ZS_PUT_CHAR_MAX_DELAY 2000 /* 10 ms */ @@ -1067,7 +1261,7 @@ static void sunzilog_putchar(struct uart_port *port, int ch) * udelay with ZSDELAY as that is a NOP on some platforms. -DaveM */ do { - unsigned char val = readb(&channel->control); + unsigned char val = sbus_readb(&channel->control); if (val & Tx_BUF_EMP) { ZSDELAY(); break; @@ -1075,7 +1269,7 @@ static void sunzilog_putchar(struct uart_port *port, int ch) udelay(5); } while (--loops); - writeb(ch, &channel->data); + sbus_writeb(ch, &channel->data); ZSDELAY(); ZS_WSYNC(channel); } @@ -1146,9 +1340,6 @@ static int __init sunzilog_console_setup(struct console *con, char *options) unsigned long flags; int baud, brg; - if (up->port.type != PORT_SUNZILOG) - return -1; - printk(KERN_INFO "Console: ttyS%d (SunZilog zs%d)\n", (sunzilog_reg.minor - 64) + con->index, con->index); @@ -1195,6 +1386,28 @@ static struct console sunzilog_console = { .data = &sunzilog_reg, }; +static int __init sunzilog_console_init(void) +{ + int i; + + if (con_is_present()) + return 0; + + for (i = 0; i < NUM_CHANNELS; i++) { + int this_minor = sunzilog_reg.minor + i; + + if ((this_minor - 64) == (serial_console - 1)) + break; + } + if (i == NUM_CHANNELS) + return 0; + + sunzilog_console.index = i; + sunzilog_port_table[i].flags |= SUNZILOG_FLAG_IS_CONS; + register_console(&sunzilog_console); + return 0; +} + static inline struct console *SUNZILOG_CONSOLE(void) { int i; @@ -1219,19 +1432,116 @@ static inline struct console *SUNZILOG_CONSOLE(void) #else #define SUNZILOG_CONSOLE() (NULL) +#define sunzilog_console_init() do { } while (0) #endif +/* + * We scan the PROM tree recursively. This is the most reliable way + * to find Zilog nodes on various platforms. However, we face an extreme + * shortage of kernel stack, so we must be very careful. To that end, + * we scan only to a certain depth, and we use a common property buffer + * in the scan structure. + */ +#define ZS_PROPSIZE 128 +#define ZS_SCAN_DEPTH 5 + +struct zs_probe_scan { + int depth; + void (*scanner)(struct zs_probe_scan *t, int node); + + int devices; + char prop[ZS_PROPSIZE]; +}; + +static int __inline__ sunzilog_node_ok(int node, const char *name, int len) +{ + if (strncmp(name, "zs", len) == 0) + return 1; + /* Don't fold this procedure just yet. Compare to su_node_ok(). */ + return 0; +} + +static void __init sunzilog_scan(struct zs_probe_scan *t, int node) +{ + int len; + + for (; node != 0; node = prom_getsibling(node)) { + len = prom_getproperty(node, "name", t->prop, ZS_PROPSIZE); + if (len <= 1) + continue; /* Broken PROM node */ + if (sunzilog_node_ok(node, t->prop, len)) { + (*t->scanner)(t, node); + } else { + if (t->depth < ZS_SCAN_DEPTH) { + t->depth++; + sunzilog_scan(t, prom_getchild(node)); + --t->depth; + } + } + } +} + +static void __init sunzilog_prepare(void) +{ + struct uart_sunzilog_port *up; + struct zilog_layout __iomem *rp; + int channel, chip; + + /* + * Temporary fix. + */ + for (channel = 0; channel < NUM_CHANNELS; channel++) + spin_lock_init(&sunzilog_port_table[channel].port.lock); + + sunzilog_irq_chain = up = &sunzilog_port_table[0]; + for (channel = 0; channel < NUM_CHANNELS - 1; channel++) + up[channel].next = &up[channel + 1]; + up[channel].next = NULL; + + for (chip = 0; chip < NUM_SUNZILOG; chip++) { + rp = sunzilog_chip_regs[chip]; + up[(chip * 2) + 0].port.membase = (void __iomem *)&rp->channelA; + up[(chip * 2) + 1].port.membase = (void __iomem *)&rp->channelB; + + /* Channel A */ + up[(chip * 2) + 0].port.iotype = UPIO_MEM; + up[(chip * 2) + 0].port.irq = zilog_irq; + up[(chip * 2) + 0].port.uartclk = ZS_CLOCK; + up[(chip * 2) + 0].port.fifosize = 1; + up[(chip * 2) + 0].port.ops = &sunzilog_pops; + up[(chip * 2) + 0].port.type = PORT_SUNZILOG; + up[(chip * 2) + 0].port.flags = 0; + up[(chip * 2) + 0].port.line = (chip * 2) + 0; + up[(chip * 2) + 0].flags |= SUNZILOG_FLAG_IS_CHANNEL_A; + + /* Channel B */ + up[(chip * 2) + 1].port.iotype = UPIO_MEM; + up[(chip * 2) + 1].port.irq = zilog_irq; + up[(chip * 2) + 1].port.uartclk = ZS_CLOCK; + up[(chip * 2) + 1].port.fifosize = 1; + up[(chip * 2) + 1].port.ops = &sunzilog_pops; + up[(chip * 2) + 1].port.type = PORT_SUNZILOG; + up[(chip * 2) + 1].port.flags = 0; + up[(chip * 2) + 1].port.line = (chip * 2) + 1; + up[(chip * 2) + 1].flags |= 0; + } +} + static void __init sunzilog_init_kbdms(struct uart_sunzilog_port *up, int channel) { int baud, brg; - if (up->flags & SUNZILOG_FLAG_CONS_KEYB) { + if (channel == KEYBOARD_LINE) { + up->flags |= SUNZILOG_FLAG_CONS_KEYB; up->cflag = B1200 | CS8 | CLOCAL | CREAD; baud = 1200; } else { + up->flags |= SUNZILOG_FLAG_CONS_MOUSE; up->cflag = B4800 | CS8 | CLOCAL | CREAD; baud = 4800; } + printk(KERN_INFO "zs%d at 0x%p (irq = %s) is a SunZilog\n", + channel, up->port.membase, __irq_itoa(zilog_irq)); up->curregs[R15] = BRKIE; brg = BPS_TO_BRG(baud, ZS_CLOCK / ZS_CLOCK_DIVISOR); @@ -1241,290 +1551,218 @@ static void __init sunzilog_init_kbdms(struct uart_sunzilog_port *up, int channe } #ifdef CONFIG_SERIO -static void __init sunzilog_register_serio(struct uart_sunzilog_port *up) +static void __init sunzilog_register_serio(struct uart_sunzilog_port *up, int channel) { - struct serio *serio = &up->serio; + struct serio *serio; - serio->port_data = up; + up->serio = serio = kmalloc(sizeof(struct serio), GFP_KERNEL); + if (serio) { + memset(serio, 0, sizeof(*serio)); - serio->id.type = SERIO_RS232; - if (up->flags & SUNZILOG_FLAG_CONS_KEYB) { - serio->id.proto = SERIO_SUNKBD; - strlcpy(serio->name, "zskbd", sizeof(serio->name)); - } else { - serio->id.proto = SERIO_SUN; - serio->id.extra = 1; - strlcpy(serio->name, "zsms", sizeof(serio->name)); - } - strlcpy(serio->phys, - ((up->flags & SUNZILOG_FLAG_CONS_KEYB) ? - "zs/serio0" : "zs/serio1"), - sizeof(serio->phys)); + serio->port_data = up; + + serio->id.type = SERIO_RS232; + if (channel == KEYBOARD_LINE) { + serio->id.proto = SERIO_SUNKBD; + strlcpy(serio->name, "zskbd", sizeof(serio->name)); + } else { + serio->id.proto = SERIO_SUN; + serio->id.extra = 1; + strlcpy(serio->name, "zsms", sizeof(serio->name)); + } + strlcpy(serio->phys, + (channel == KEYBOARD_LINE ? "zs/serio0" : "zs/serio1"), + sizeof(serio->phys)); - serio->write = sunzilog_serio_write; - serio->open = sunzilog_serio_open; - serio->close = sunzilog_serio_close; - serio->dev.parent = up->port.dev; + serio->write = sunzilog_serio_write; + serio->open = sunzilog_serio_open; + serio->close = sunzilog_serio_close; - serio_register_port(serio); + serio_register_port(serio); + } else { + printk(KERN_WARNING "zs%d: not enough memory for serio port\n", + channel); + } } #endif -static void __init sunzilog_init_hw(struct uart_sunzilog_port *up) +static void __init sunzilog_init_hw(void) { - struct zilog_channel __iomem *channel; - unsigned long flags; - int baud, brg; + int i; - channel = ZILOG_CHANNEL_FROM_PORT(&up->port); + for (i = 0; i < NUM_CHANNELS; i++) { + struct uart_sunzilog_port *up = &sunzilog_port_table[i]; + struct zilog_channel __iomem *channel = ZILOG_CHANNEL_FROM_PORT(&up->port); + unsigned long flags; + int baud, brg; - spin_lock_irqsave(&up->port.lock, flags); - if (ZS_IS_CHANNEL_A(up)) { - write_zsreg(channel, R9, FHWRES); - ZSDELAY_LONG(); - (void) read_zsreg(channel, R0); - } + spin_lock_irqsave(&up->port.lock, flags); - if (up->flags & (SUNZILOG_FLAG_CONS_KEYB | - SUNZILOG_FLAG_CONS_MOUSE)) { - sunzilog_init_kbdms(up, up->port.line); - up->curregs[R9] |= (NV | MIE); - write_zsreg(channel, R9, up->curregs[R9]); - } else { - /* Normal serial TTY. */ - up->parity_mask = 0xff; - up->curregs[R1] = EXT_INT_ENAB | INT_ALL_Rx | TxINT_ENAB; - up->curregs[R4] = PAR_EVEN | X16CLK | SB1; - up->curregs[R3] = RxENAB | Rx8; - up->curregs[R5] = TxENAB | Tx8; - up->curregs[R9] = NV | MIE; - up->curregs[R10] = NRZ; - up->curregs[R11] = TCBR | RCBR; - baud = 9600; - brg = BPS_TO_BRG(baud, ZS_CLOCK / ZS_CLOCK_DIVISOR); - up->curregs[R12] = (brg & 0xff); - up->curregs[R13] = (brg >> 8) & 0xff; - up->curregs[R14] = BRSRC | BRENAB; - __load_zsregs(channel, up->curregs); - write_zsreg(channel, R9, up->curregs[R9]); - } + if (ZS_IS_CHANNEL_A(up)) { + write_zsreg(channel, R9, FHWRES); + ZSDELAY_LONG(); + (void) read_zsreg(channel, R0); + } - spin_unlock_irqrestore(&up->port.lock, flags); + if (i == KEYBOARD_LINE || i == MOUSE_LINE) { + sunzilog_init_kbdms(up, i); + up->curregs[R9] |= (NV | MIE); + write_zsreg(channel, R9, up->curregs[R9]); + } else { + /* Normal serial TTY. */ + up->parity_mask = 0xff; + up->curregs[R1] = EXT_INT_ENAB | INT_ALL_Rx | TxINT_ENAB; + up->curregs[R4] = PAR_EVEN | X16CLK | SB1; + up->curregs[R3] = RxENAB | Rx8; + up->curregs[R5] = TxENAB | Tx8; + up->curregs[R9] = NV | MIE; + up->curregs[R10] = NRZ; + up->curregs[R11] = TCBR | RCBR; + baud = 9600; + brg = BPS_TO_BRG(baud, ZS_CLOCK / ZS_CLOCK_DIVISOR); + up->curregs[R12] = (brg & 0xff); + up->curregs[R13] = (brg >> 8) & 0xff; + up->curregs[R14] = BRSRC | BRENAB; + __load_zsregs(channel, up->curregs); + write_zsreg(channel, R9, up->curregs[R9]); + } + + spin_unlock_irqrestore(&up->port.lock, flags); #ifdef CONFIG_SERIO - if (up->flags & (SUNZILOG_FLAG_CONS_KEYB | - SUNZILOG_FLAG_CONS_MOUSE)) - sunzilog_register_serio(up); + if (i == KEYBOARD_LINE || i == MOUSE_LINE) + sunzilog_register_serio(up, i); #endif + } } -static int zilog_irq = -1; +static struct zilog_layout __iomem * __init get_zs(int chip, int node); -static int __devinit zs_probe(struct of_device *op, const struct of_device_id *match) +static void __init sunzilog_scan_probe(struct zs_probe_scan *t, int node) { - static int inst; - struct uart_sunzilog_port *up; - struct zilog_layout __iomem *rp; - int keyboard_mouse; - int err; + sunzilog_chip_regs[t->devices] = get_zs(t->devices, node); + t->devices++; +} - keyboard_mouse = 0; - if (of_find_property(op->node, "keyboard", NULL)) - keyboard_mouse = 1; - - sunzilog_chip_regs[inst] = of_ioremap(&op->resource[0], 0, - sizeof(struct zilog_layout), - "zs"); - if (!sunzilog_chip_regs[inst]) - return -ENOMEM; - - rp = sunzilog_chip_regs[inst]; - - if (zilog_irq == -1) - zilog_irq = op->irqs[0]; - - up = &sunzilog_port_table[inst * 2]; - - /* Channel A */ - up[0].port.mapbase = op->resource[0].start + 0x00; - up[0].port.membase = (void __iomem *) &rp->channelA; - up[0].port.iotype = UPIO_MEM; - up[0].port.irq = op->irqs[0]; - up[0].port.uartclk = ZS_CLOCK; - up[0].port.fifosize = 1; - up[0].port.ops = &sunzilog_pops; - up[0].port.type = PORT_SUNZILOG; - up[0].port.flags = 0; - up[0].port.line = (inst * 2) + 0; - up[0].port.dev = &op->dev; - up[0].flags |= SUNZILOG_FLAG_IS_CHANNEL_A; - if (keyboard_mouse) - up[0].flags |= SUNZILOG_FLAG_CONS_KEYB; - sunzilog_init_hw(&up[0]); - - /* Channel B */ - up[1].port.mapbase = op->resource[0].start + 0x04; - up[1].port.membase = (void __iomem *) &rp->channelB; - up[1].port.iotype = UPIO_MEM; - up[1].port.irq = op->irqs[0]; - up[1].port.uartclk = ZS_CLOCK; - up[1].port.fifosize = 1; - up[1].port.ops = &sunzilog_pops; - up[1].port.type = PORT_SUNZILOG; - up[1].port.flags = 0; - up[1].port.line = (inst * 2) + 1; - up[1].port.dev = &op->dev; - up[1].flags |= 0; - if (keyboard_mouse) - up[1].flags |= SUNZILOG_FLAG_CONS_MOUSE; - sunzilog_init_hw(&up[1]); - - if (!keyboard_mouse) { - err = uart_add_one_port(&sunzilog_reg, &up[0].port); - if (err) { - of_iounmap(rp, sizeof(struct zilog_layout)); - return err; - } - err = uart_add_one_port(&sunzilog_reg, &up[1].port); - if (err) { - uart_remove_one_port(&sunzilog_reg, &up[0].port); - of_iounmap(rp, sizeof(struct zilog_layout)); - return err; - } - } else { - printk(KERN_INFO "%s: Keyboard at MMIO %lx (irq = %d) " - "is a zs\n", - op->dev.bus_id, up[0].port.mapbase, op->irqs[0]); - printk(KERN_INFO "%s: Mouse at MMIO %lx (irq = %d) " - "is a zs\n", - op->dev.bus_id, up[1].port.mapbase, op->irqs[0]); - } +static int __init sunzilog_ports_init(void) +{ + struct zs_probe_scan scan; + int ret; + int uart_count; + int i; - dev_set_drvdata(&op->dev, &up[0]); + printk(KERN_DEBUG "SunZilog: %d chips.\n", NUM_SUNZILOG); - inst++; + scan.scanner = sunzilog_scan_probe; + scan.depth = 0; + scan.devices = 0; + sunzilog_scan(&scan, prom_getchild(prom_root_node)); - return 0; -} + sunzilog_prepare(); -static void __devexit zs_remove_one(struct uart_sunzilog_port *up) -{ - if (ZS_IS_KEYB(up) || ZS_IS_MOUSE(up)) { -#ifdef CONFIG_SERIO - serio_unregister_port(&up->serio); -#endif - } else - uart_remove_one_port(&sunzilog_reg, &up->port); -} + if (request_irq(zilog_irq, sunzilog_interrupt, SA_SHIRQ, + "SunZilog", sunzilog_irq_chain)) { + prom_printf("SunZilog: Unable to register zs interrupt handler.\n"); + prom_halt(); + } -static int __devexit zs_remove(struct of_device *dev) -{ - struct uart_sunzilog_port *up = dev_get_drvdata(&dev->dev); - struct zilog_layout __iomem *regs; + sunzilog_init_hw(); + + /* We can only init this once we have probed the Zilogs + * in the system. Do not count channels assigned to keyboards + * or mice when we are deciding how many ports to register. + */ + uart_count = 0; + for (i = 0; i < NUM_CHANNELS; i++) { + struct uart_sunzilog_port *up = &sunzilog_port_table[i]; - zs_remove_one(&up[0]); - zs_remove_one(&up[1]); + if (ZS_IS_KEYB(up) || ZS_IS_MOUSE(up)) + continue; - regs = sunzilog_chip_regs[up[0].port.line / 2]; - of_iounmap(regs, sizeof(struct zilog_layout)); + uart_count++; + } + + sunzilog_reg.nr = uart_count; + sunzilog_reg.minor = sunserial_current_minor; - dev_set_drvdata(&dev->dev, NULL); + ret = uart_register_driver(&sunzilog_reg); + if (ret == 0) { + sunzilog_reg.tty_driver->name_base = sunzilog_reg.minor - 64; + sunzilog_reg.cons = SUNZILOG_CONSOLE(); - return 0; -} + sunserial_current_minor += uart_count; -static struct of_device_id zs_match[] = { - { - .name = "zs", - }, - {}, -}; -MODULE_DEVICE_TABLE(of, zs_match); + for (i = 0; i < NUM_CHANNELS; i++) { + struct uart_sunzilog_port *up = &sunzilog_port_table[i]; -static struct of_platform_driver zs_driver = { - .name = "zs", - .match_table = zs_match, - .probe = zs_probe, - .remove = __devexit_p(zs_remove), -}; + if (ZS_IS_KEYB(up) || ZS_IS_MOUSE(up)) + continue; -static int __init sunzilog_init(void) -{ - struct device_node *dp; - int err, uart_count; - int num_keybms; - - NUM_SUNZILOG = 0; - num_keybms = 0; - for_each_node_by_name(dp, "zs") { - NUM_SUNZILOG++; - if (of_find_property(dp, "keyboard", NULL)) - num_keybms++; + if (uart_add_one_port(&sunzilog_reg, &up->port)) { + printk(KERN_ERR + "SunZilog: failed to add port zs%d\n", i); + } + } } - uart_count = 0; - if (NUM_SUNZILOG) { - int uart_count; + return ret; +} - err = sunzilog_alloc_tables(); - if (err) - goto out; +static void __init sunzilog_scan_count(struct zs_probe_scan *t, int node) +{ + t->devices++; +} - uart_count = (NUM_SUNZILOG * 2) - (2 * num_keybms); +static int __init sunzilog_ports_count(void) +{ + struct zs_probe_scan scan; - sunzilog_reg.nr = uart_count; - sunzilog_reg.minor = sunserial_current_minor; - err = uart_register_driver(&sunzilog_reg); - if (err) - goto out_free_tables; + /* Sun4 Zilog setup is hard coded, no probing to do. */ + if (sparc_cpu_model == sun4) + return 2; - sunzilog_reg.tty_driver->name_base = sunzilog_reg.minor - 64; - sunzilog_reg.cons = SUNZILOG_CONSOLE(); + scan.scanner = sunzilog_scan_count; + scan.depth = 0; + scan.devices = 0; - sunserial_current_minor += uart_count; - } + sunzilog_scan(&scan, prom_getchild(prom_root_node)); - err = of_register_driver(&zs_driver, &of_bus_type); - if (err) - goto out_unregister_uart; + return scan.devices; +} - if (zilog_irq != -1) { - err = request_irq(zilog_irq, sunzilog_interrupt, IRQF_SHARED, - "zs", sunzilog_irq_chain); - if (err) - goto out_unregister_driver; - } +static int __init sunzilog_init(void) +{ -out: - return err; + NUM_SUNZILOG = sunzilog_ports_count(); + if (NUM_SUNZILOG == 0) + return -ENODEV; -out_unregister_driver: - of_unregister_driver(&zs_driver); + sunzilog_alloc_tables(); -out_unregister_uart: - if (NUM_SUNZILOG) { - uart_unregister_driver(&sunzilog_reg); - sunzilog_reg.cons = NULL; - } + sunzilog_ports_init(); -out_free_tables: - sunzilog_free_tables(); - goto out; + return 0; } static void __exit sunzilog_exit(void) { - of_unregister_driver(&zs_driver); + int i; - if (zilog_irq != -1) { - free_irq(zilog_irq, sunzilog_irq_chain); - zilog_irq = -1; - } + for (i = 0; i < NUM_CHANNELS; i++) { + struct uart_sunzilog_port *up = &sunzilog_port_table[i]; - if (NUM_SUNZILOG) { - uart_unregister_driver(&sunzilog_reg); - sunzilog_free_tables(); + if (ZS_IS_KEYB(up) || ZS_IS_MOUSE(up)) { +#ifdef CONFIG_SERIO + if (up->serio) { + serio_unregister_port(up->serio); + up->serio = NULL; + } +#endif + } else + uart_remove_one_port(&sunzilog_reg, &up->port); } + + uart_unregister_driver(&sunzilog_reg); } module_init(sunzilog_init); @@ -1532,5 +1770,4 @@ module_exit(sunzilog_exit); MODULE_AUTHOR("David S. Miller"); MODULE_DESCRIPTION("Sun Zilog serial port driver"); -MODULE_VERSION("2.0"); MODULE_LICENSE("GPL");