X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=drivers%2Fserial%2Fsunsu.c;fp=drivers%2Fserial%2Fsunsu.c;h=4e453fa966ae23956a370ffc24dc19a2f241a0fe;hb=64ba3f394c830ec48a1c31b53dcae312c56f1604;hp=d3a5aeee73a34b33bb4736b751a2aacab8ec5e8a;hpb=be1e6109ac94a859551f8e1774eb9a8469fe055c;p=linux-2.6.git diff --git a/drivers/serial/sunsu.c b/drivers/serial/sunsu.c index d3a5aeee7..4e453fa96 100644 --- a/drivers/serial/sunsu.c +++ b/drivers/serial/sunsu.c @@ -12,9 +12,10 @@ * Theodore Ts'o , 2001-Oct-12 * * Converted to new 2.5.x UART layer. - * David S. Miller (davem@davemloft.net), 2002-Jul-29 + * David S. Miller (davem@redhat.com), 2002-Jul-29 */ +#include #include #include #include @@ -39,8 +40,11 @@ #include #include -#include -#include +#include +#include +#ifdef CONFIG_SPARC64 +#include +#endif #if defined(CONFIG_SERIAL_SUNSU_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ) #define SUPPORT_SYSRQ @@ -90,15 +94,17 @@ struct uart_sunsu_port { /* Probing information. */ enum su_type su_type; unsigned int type_probed; /* XXX Stupid */ - unsigned long reg_size; + int port_node; #ifdef CONFIG_SERIO - struct serio serio; + struct serio *serio; int serio_open; #endif }; -static unsigned int serial_in(struct uart_sunsu_port *up, int offset) +#define _INLINE_ + +static _INLINE_ unsigned int serial_in(struct uart_sunsu_port *up, int offset) { offset <<= up->port.regshift; @@ -115,7 +121,8 @@ static unsigned int serial_in(struct uart_sunsu_port *up, int offset) } } -static void serial_out(struct uart_sunsu_port *up, int offset, int value) +static _INLINE_ void +serial_out(struct uart_sunsu_port *up, int offset, int value) { #ifndef CONFIG_SPARC64 /* @@ -309,7 +316,7 @@ static void sunsu_enable_ms(struct uart_port *port) spin_unlock_irqrestore(&up->port.lock, flags); } -static struct tty_struct * +static _INLINE_ struct tty_struct * receive_chars(struct uart_sunsu_port *up, unsigned char *status, struct pt_regs *regs) { struct tty_struct *tty = up->port.info->tty; @@ -388,7 +395,7 @@ receive_chars(struct uart_sunsu_port *up, unsigned char *status, struct pt_regs return tty; } -static void transmit_chars(struct uart_sunsu_port *up) +static _INLINE_ void transmit_chars(struct uart_sunsu_port *up) { struct circ_buf *xmit = &up->port.info->xmit; int count; @@ -424,7 +431,7 @@ static void transmit_chars(struct uart_sunsu_port *up) __stop_tx(up); } -static void check_modem_status(struct uart_sunsu_port *up) +static _INLINE_ void check_modem_status(struct uart_sunsu_port *up) { int status; @@ -505,7 +512,7 @@ static void receive_kbd_ms_chars(struct uart_sunsu_port *up, struct pt_regs *reg /* Stop-A is handled by drivers/char/keyboard.c now. */ if (up->su_type == SU_PORT_KBD) { #ifdef CONFIG_SERIO - serio_interrupt(&up->serio, ch, 0, regs); + serio_interrupt(up->serio, ch, 0, regs); #endif } else if (up->su_type == SU_PORT_MS) { int ret = suncore_mouse_baud_detection(ch, is_break); @@ -519,7 +526,7 @@ static void receive_kbd_ms_chars(struct uart_sunsu_port *up, struct pt_regs *reg case 0: #ifdef CONFIG_SERIO - serio_interrupt(&up->serio, ch, 0, regs); + serio_interrupt(up->serio, ch, 0, regs); #endif break; }; @@ -637,7 +644,7 @@ static int sunsu_startup(struct uart_port *port) /* * Clear the FIFO buffers and disable them. - * (they will be reenabled in set_termios()) + * (they will be reeanbled in set_termios()) */ if (uart_config[up->port.type].flags & UART_CLEAR_FIFO) { serial_outp(up, UART_FCR, UART_FCR_ENABLE_FIFO); @@ -667,10 +674,10 @@ static int sunsu_startup(struct uart_port *port) if (up->su_type != SU_PORT_PORT) { retval = request_irq(up->port.irq, sunsu_kbd_ms_interrupt, - IRQF_SHARED, su_typev[up->su_type], up); + SA_SHIRQ, su_typev[up->su_type], up); } else { retval = request_irq(up->port.irq, sunsu_serial_interrupt, - IRQF_SHARED, su_typev[up->su_type], up); + SA_SHIRQ, su_typev[up->su_type], up); } if (retval) { printk("su: Cannot register IRQ %d\n", up->port.irq); @@ -1027,14 +1034,99 @@ static void sunsu_autoconfig(struct uart_sunsu_port *up) { unsigned char status1, status2, scratch, scratch2, scratch3; unsigned char save_lcr, save_mcr; + struct linux_ebus_device *dev = NULL; + struct linux_ebus *ebus; +#ifdef CONFIG_SPARC64 + struct sparc_isa_bridge *isa_br; + struct sparc_isa_device *isa_dev; +#endif +#ifndef CONFIG_SPARC64 + struct linux_prom_registers reg0; +#endif unsigned long flags; - if (up->su_type == SU_PORT_NONE) + if (!up->port_node || !up->su_type) return; up->type_probed = PORT_UNKNOWN; up->port.iotype = UPIO_MEM; + /* + * First we look for Ebus-bases su's + */ + for_each_ebus(ebus) { + for_each_ebusdev(dev, ebus) { + if (dev->prom_node == up->port_node) { + /* + * The EBus is broken on sparc; it delivers + * virtual addresses in resources. Oh well... + * This is correct on sparc64, though. + */ + up->port.membase = (char *) dev->resource[0].start; + /* + * This is correct on both architectures. + */ + up->port.mapbase = dev->resource[0].start; + up->port.irq = dev->irqs[0]; + goto ebus_done; + } + } + } + +#ifdef CONFIG_SPARC64 + for_each_isa(isa_br) { + for_each_isadev(isa_dev, isa_br) { + if (isa_dev->prom_node == up->port_node) { + /* Same on sparc64. Cool architecure... */ + up->port.membase = (char *) isa_dev->resource.start; + up->port.mapbase = isa_dev->resource.start; + up->port.irq = isa_dev->irq; + goto ebus_done; + } + } + } +#endif + +#ifdef CONFIG_SPARC64 + /* + * Not on Ebus, bailing. + */ + return; +#else + /* + * Not on Ebus, must be OBIO. + */ + if (prom_getproperty(up->port_node, "reg", + (char *)®0, sizeof(reg0)) == -1) { + prom_printf("sunsu: no \"reg\" property\n"); + return; + } + prom_apply_obio_ranges(®0, 1); + if (reg0.which_io != 0) { /* Just in case... */ + prom_printf("sunsu: bus number nonzero: 0x%x:%x\n", + reg0.which_io, reg0.phys_addr); + return; + } + up->port.mapbase = reg0.phys_addr; + if ((up->port.membase = ioremap(reg0.phys_addr, reg0.reg_size)) == 0) { + prom_printf("sunsu: Cannot map registers.\n"); + return; + } + + /* + * 0x20 is sun4m thing, Dave Redman heritage. + * See arch/sparc/kernel/irq.c. + */ +#define IRQ_4M(n) ((n)|0x20) + + /* + * There is no intr property on MrCoffee, so hardwire it. + */ + up->port.irq = IRQ_4M(13); +#endif + +ebus_done: + spin_lock_irqsave(&up->port.lock, flags); if (!(up->port.flags & UPF_BUGGY_UART)) { @@ -1176,17 +1268,22 @@ out: static struct uart_driver sunsu_reg = { .owner = THIS_MODULE, .driver_name = "serial", + .devfs_name = "tts/", .dev_name = "ttyS", .major = TTY_MAJOR, }; -static int __init sunsu_kbd_ms_init(struct uart_sunsu_port *up) +static int __init sunsu_kbd_ms_init(struct uart_sunsu_port *up, int channel) { int quot, baud; #ifdef CONFIG_SERIO struct serio *serio; #endif + up->port.line = channel; + up->port.type = PORT_UNKNOWN; + up->port.uartclk = (SU_BASE_BAUD * 16); + if (up->su_type == SU_PORT_KBD) { up->cflag = B1200 | CS8 | CLOCAL | CREAD; baud = 1200; @@ -1198,36 +1295,41 @@ static int __init sunsu_kbd_ms_init(struct uart_sunsu_port *up) sunsu_autoconfig(up); if (up->port.type == PORT_UNKNOWN) - return -ENODEV; + return -1; - printk("%s: %s port at %lx, irq %u\n", - to_of_device(up->port.dev)->node->full_name, - (up->su_type == SU_PORT_KBD) ? "Keyboard" : "Mouse", - up->port.mapbase, up->port.irq); + printk(KERN_INFO "su%d at 0x%p (irq = %s) is a %s\n", + channel, + up->port.membase, __irq_itoa(up->port.irq), + sunsu_type(&up->port)); #ifdef CONFIG_SERIO - serio = &up->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->su_type == SU_PORT_KBD) { - serio->id.proto = SERIO_SUNKBD; - strlcpy(serio->name, "sukbd", sizeof(serio->name)); - } else { - serio->id.proto = SERIO_SUN; - serio->id.extra = 1; - strlcpy(serio->name, "sums", sizeof(serio->name)); - } - strlcpy(serio->phys, - (!(up->port.line & 1) ? "su/serio0" : "su/serio1"), - sizeof(serio->phys)); + serio->port_data = up; - serio->write = sunsu_serio_write; - serio->open = sunsu_serio_open; - serio->close = sunsu_serio_close; - serio->dev.parent = up->port.dev; + serio->id.type = SERIO_RS232; + if (up->su_type == SU_PORT_KBD) { + serio->id.proto = SERIO_SUNKBD; + strlcpy(serio->name, "sukbd", sizeof(serio->name)); + } else { + serio->id.proto = SERIO_SUN; + serio->id.extra = 1; + strlcpy(serio->name, "sums", sizeof(serio->name)); + } + strlcpy(serio->phys, (channel == 0 ? "su/serio0" : "su/serio1"), + sizeof(serio->phys)); + + serio->write = sunsu_serio_write; + serio->open = sunsu_serio_open; + serio->close = sunsu_serio_close; - serio_register_port(serio); + serio_register_port(serio); + } else { + printk(KERN_WARNING "su%d: not enough memory for serio port\n", + channel); + } #endif sunsu_change_speed(&up->port, up->cflag, 0, quot); @@ -1274,14 +1376,6 @@ static __inline__ void wait_for_xmitr(struct uart_sunsu_port *up) } } -static void sunsu_console_putchar(struct uart_port *port, int ch) -{ - struct uart_sunsu_port *up = (struct uart_sunsu_port *)port; - - wait_for_xmitr(up); - serial_out(up, UART_TX, ch); -} - /* * Print a string to the serial port trying not to disturb * any possible real use of the port... @@ -1291,6 +1385,7 @@ static void sunsu_console_write(struct console *co, const char *s, { struct uart_sunsu_port *up = &sunsu_ports[co->index]; unsigned int ier; + int i; /* * First save the UER then disable the interrupts @@ -1298,7 +1393,22 @@ static void sunsu_console_write(struct console *co, const char *s, ier = serial_in(up, UART_IER); serial_out(up, UART_IER, 0); - uart_console_write(&up->port, s, count, sunsu_console_putchar); + /* + * Now, do each character + */ + for (i = 0; i < count; i++, s++) { + wait_for_xmitr(up); + + /* + * Send the character out. + * If a LF, also do CR... + */ + serial_out(up, UART_TX, *s); + if (*s == 10) { + wait_for_xmitr(up); + serial_out(up, UART_TX, 13); + } + } /* * Finally, wait for transmitter to become empty @@ -1354,229 +1464,276 @@ static struct console sunsu_cons = { .index = -1, .data = &sunsu_reg, }; +#define SUNSU_CONSOLE (&sunsu_cons) /* * Register console. */ -static inline struct console *SUNSU_CONSOLE(int num_uart) +static int __init sunsu_serial_console_init(void) { int i; if (con_is_present()) - return NULL; + return 0; - for (i = 0; i < num_uart; i++) { + for (i = 0; i < UART_NR; i++) { int this_minor = sunsu_reg.minor + i; if ((this_minor - 64) == (serial_console - 1)) break; } - if (i == num_uart) - return NULL; + if (i == UART_NR) + return 0; + if (sunsu_ports[i].port_node == 0) + return 0; sunsu_cons.index = i; - - return &sunsu_cons; + register_console(&sunsu_cons); + return 0; } #else -#define SUNSU_CONSOLE(num_uart) (NULL) +#define SUNSU_CONSOLE (NULL) #define sunsu_serial_console_init() do { } while (0) #endif -static enum su_type __devinit su_get_type(struct device_node *dp) +static int __init sunsu_serial_init(void) { - struct device_node *ap = of_find_node_by_path("/aliases"); + int instance, ret, i; - if (ap) { - char *keyb = of_get_property(ap, "keyboard", NULL); - char *ms = of_get_property(ap, "mouse", NULL); + /* How many instances do we need? */ + instance = 0; + for (i = 0; i < UART_NR; i++) { + struct uart_sunsu_port *up = &sunsu_ports[i]; - if (keyb) { - if (dp == of_find_node_by_path(keyb)) - return SU_PORT_KBD; - } - if (ms) { - if (dp == of_find_node_by_path(ms)) - return SU_PORT_MS; - } - } + if (up->su_type == SU_PORT_MS || + up->su_type == SU_PORT_KBD) + continue; - return SU_PORT_PORT; -} + up->port.flags |= UPF_BOOT_AUTOCONF; + up->port.type = PORT_UNKNOWN; + up->port.uartclk = (SU_BASE_BAUD * 16); -static int __devinit su_probe(struct of_device *op, const struct of_device_id *match) -{ - static int inst; - struct device_node *dp = op->node; - struct uart_sunsu_port *up; - struct resource *rp; - enum su_type type; - int err; - - type = su_get_type(dp); - if (type == SU_PORT_PORT) { - if (inst >= UART_NR) - return -EINVAL; - up = &sunsu_ports[inst]; - } else { - up = kzalloc(sizeof(*up), GFP_KERNEL); - if (!up) - return -ENOMEM; - } + sunsu_autoconfig(up); + if (up->port.type == PORT_UNKNOWN) + continue; - up->port.line = inst; - - spin_lock_init(&up->port.lock); + up->port.line = instance++; + up->port.ops = &sunsu_pops; + } - up->su_type = type; + sunsu_reg.minor = sunserial_current_minor; + sunserial_current_minor += instance; - rp = &op->resource[0]; - up->port.mapbase = rp->start; - up->reg_size = (rp->end - rp->start) + 1; - up->port.membase = of_ioremap(rp, 0, up->reg_size, "su"); - if (!up->port.membase) { - if (type != SU_PORT_PORT) - kfree(up); - return -ENOMEM; - } + sunsu_reg.nr = instance; + sunsu_reg.cons = SUNSU_CONSOLE; - up->port.irq = op->irqs[0]; + ret = uart_register_driver(&sunsu_reg); + if (ret < 0) + return ret; - up->port.dev = &op->dev; + sunsu_serial_console_init(); + for (i = 0; i < UART_NR; i++) { + struct uart_sunsu_port *up = &sunsu_ports[i]; - up->port.type = PORT_UNKNOWN; - up->port.uartclk = (SU_BASE_BAUD * 16); + /* Do not register Keyboard/Mouse lines with UART + * layer. + */ + if (up->su_type == SU_PORT_MS || + up->su_type == SU_PORT_KBD) + continue; - err = 0; - if (up->su_type == SU_PORT_KBD || up->su_type == SU_PORT_MS) { - err = sunsu_kbd_ms_init(up); - if (err) { - kfree(up); - goto out_unmap; - } - dev_set_drvdata(&op->dev, up); + if (up->port.type == PORT_UNKNOWN) + continue; - return 0; + uart_add_one_port(&sunsu_reg, &up->port); } - up->port.flags |= UPF_BOOT_AUTOCONF; - - sunsu_autoconfig(up); + return 0; +} - err = -ENODEV; - if (up->port.type == PORT_UNKNOWN) - goto out_unmap; +static int su_node_ok(int node, char *name, int namelen) +{ + if (strncmp(name, "su", namelen) == 0 || + strncmp(name, "su_pnp", namelen) == 0) + return 1; + + if (strncmp(name, "serial", namelen) == 0) { + char compat[32]; + int clen; + + /* Is it _really_ a 'su' device? */ + clen = prom_getproperty(node, "compatible", compat, sizeof(compat)); + if (clen > 0) { + if (strncmp(compat, "sab82532", 8) == 0) { + /* Nope, Siemens serial, not for us. */ + return 0; + } + } + return 1; + } - up->port.ops = &sunsu_pops; + return 0; +} - err = uart_add_one_port(&sunsu_reg, &up->port); - if (err) - goto out_unmap; +#define SU_PROPSIZE 128 - dev_set_drvdata(&op->dev, up); +/* + * Scan status structure. + * "prop" is a local variable but it eats stack to keep it in each + * stack frame of a recursive procedure. + */ +struct su_probe_scan { + int msnode, kbnode; /* PROM nodes for mouse and keyboard */ + int msx, kbx; /* minors for mouse and keyboard */ + int devices; /* scan index */ + char prop[SU_PROPSIZE]; +}; - inst++; +/* + * We have several platforms which present 'su' in different parts + * of the device tree. 'su' may be found under obio, ebus, isa and pci. + * We walk over the tree and find them wherever PROM hides them. + */ +static void __init su_probe_any(struct su_probe_scan *t, int sunode) +{ + struct uart_sunsu_port *up; + int len; - return 0; + if (t->devices >= UART_NR) + return; -out_unmap: - of_iounmap(up->port.membase, up->reg_size); - return err; + for (; sunode != 0; sunode = prom_getsibling(sunode)) { + len = prom_getproperty(sunode, "name", t->prop, SU_PROPSIZE); + if (len <= 1) + continue; /* Broken PROM node */ + + if (su_node_ok(sunode, t->prop, len)) { + up = &sunsu_ports[t->devices]; + if (t->kbnode != 0 && sunode == t->kbnode) { + t->kbx = t->devices; + up->su_type = SU_PORT_KBD; + } else if (t->msnode != 0 && sunode == t->msnode) { + t->msx = t->devices; + up->su_type = SU_PORT_MS; + } else { +#ifdef CONFIG_SPARC64 + /* + * Do not attempt to use the truncated + * keyboard/mouse ports as serial ports + * on Ultras with PC keyboard attached. + */ + if (prom_getbool(sunode, "mouse")) + continue; + if (prom_getbool(sunode, "keyboard")) + continue; +#endif + up->su_type = SU_PORT_PORT; + } + up->port_node = sunode; + ++t->devices; + } else { + su_probe_any(t, prom_getchild(sunode)); + } + } } -static int __devexit su_remove(struct of_device *dev) +static int __init sunsu_probe(void) { - struct uart_sunsu_port *up = dev_get_drvdata(&dev->dev);; + int node; + int len; + struct su_probe_scan scan; - if (up->su_type == SU_PORT_MS || - up->su_type == SU_PORT_KBD) { -#ifdef CONFIG_SERIO - serio_unregister_port(&up->serio); -#endif - kfree(up); - } else if (up->port.type != PORT_UNKNOWN) { - uart_remove_one_port(&sunsu_reg, &up->port); - } - - dev_set_drvdata(&dev->dev, NULL); + /* + * First, we scan the tree. + */ + scan.devices = 0; + scan.msx = -1; + scan.kbx = -1; + scan.kbnode = 0; + scan.msnode = 0; - return 0; -} + /* + * Get the nodes for keyboard and mouse from 'aliases'... + */ + node = prom_getchild(prom_root_node); + node = prom_searchsiblings(node, "aliases"); + if (node != 0) { + len = prom_getproperty(node, "keyboard", scan.prop, SU_PROPSIZE); + if (len > 0) { + scan.prop[len] = 0; + scan.kbnode = prom_finddevice(scan.prop); + } -static struct of_device_id su_match[] = { - { - .name = "su", - }, - { - .name = "su_pnp", - }, - { - .name = "serial", - .compatible = "su", - }, - {}, -}; -MODULE_DEVICE_TABLE(of, su_match); + len = prom_getproperty(node, "mouse", scan.prop, SU_PROPSIZE); + if (len > 0) { + scan.prop[len] = 0; + scan.msnode = prom_finddevice(scan.prop); + } + } -static struct of_platform_driver su_driver = { - .name = "su", - .match_table = su_match, - .probe = su_probe, - .remove = __devexit_p(su_remove), -}; + su_probe_any(&scan, prom_getchild(prom_root_node)); -static int num_uart; + /* + * Second, we process the special case of keyboard and mouse. + * + * Currently if we got keyboard and mouse hooked to "su" ports + * we do not use any possible remaining "su" as a serial port. + * Thus, we ignore values of .msx and .kbx, then compact ports. + */ + if (scan.msx != -1 && scan.kbx != -1) { + sunsu_ports[0].su_type = SU_PORT_MS; + sunsu_ports[0].port_node = scan.msnode; + sunsu_kbd_ms_init(&sunsu_ports[0], 0); -static int __init sunsu_init(void) -{ - struct device_node *dp; - int err; + sunsu_ports[1].su_type = SU_PORT_KBD; + sunsu_ports[1].port_node = scan.kbnode; + sunsu_kbd_ms_init(&sunsu_ports[1], 1); - num_uart = 0; - for_each_node_by_name(dp, "su") { - if (su_get_type(dp) == SU_PORT_PORT) - num_uart++; - } - for_each_node_by_name(dp, "su_pnp") { - if (su_get_type(dp) == SU_PORT_PORT) - num_uart++; - } - for_each_node_by_name(dp, "serial") { - if (of_device_is_compatible(dp, "su")) { - if (su_get_type(dp) == SU_PORT_PORT) - num_uart++; - } + return 0; } - if (num_uart) { - sunsu_reg.minor = sunserial_current_minor; - sunsu_reg.nr = num_uart; - err = uart_register_driver(&sunsu_reg); - if (err) - return err; - sunsu_reg.tty_driver->name_base = sunsu_reg.minor - 64; - sunserial_current_minor += num_uart; - sunsu_reg.cons = SUNSU_CONSOLE(num_uart); + if (scan.msx != -1 || scan.kbx != -1) { + printk("sunsu_probe: cannot match keyboard and mouse, confused\n"); + return -ENODEV; } - err = of_register_driver(&su_driver, &of_bus_type); - if (err && num_uart) - uart_unregister_driver(&sunsu_reg); + if (scan.devices == 0) + return -ENODEV; - return err; + /* + * Console must be initiated after the generic initialization. + */ + sunsu_serial_init(); + + return 0; } static void __exit sunsu_exit(void) { - if (num_uart) + int i, saw_uart; + + saw_uart = 0; + for (i = 0; i < UART_NR; i++) { + struct uart_sunsu_port *up = &sunsu_ports[i]; + + if (up->su_type == SU_PORT_MS || + up->su_type == SU_PORT_KBD) { +#ifdef CONFIG_SERIO + if (up->serio) { + serio_unregister_port(up->serio); + up->serio = NULL; + } +#endif + } else if (up->port.type != PORT_UNKNOWN) { + uart_remove_one_port(&sunsu_reg, &up->port); + saw_uart++; + } + } + + if (saw_uart) uart_unregister_driver(&sunsu_reg); } -module_init(sunsu_init); +module_init(sunsu_probe); module_exit(sunsu_exit); - -MODULE_AUTHOR("Eddie C. Dost, Peter Zaitcev, and David S. Miller"); -MODULE_DESCRIPTION("Sun SU serial port driver"); -MODULE_VERSION("2.0"); -MODULE_LICENSE("GPL");