X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=drivers%2Fserial%2Fsunsu.c;h=2b4f96541b8e6a10f25a717c8909a742a0576515;hb=9464c7cf61b9433057924c36e6e02f303a00e768;hp=d3a5aeee73a34b33bb4736b751a2aacab8ec5e8a;hpb=41689045f6a3cbe0550e1d34e9cc20d2e8c432ba;p=linux-2.6.git diff --git a/drivers/serial/sunsu.c b/drivers/serial/sunsu.c index d3a5aeee7..2b4f96541 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,10 +94,10 @@ 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 }; @@ -505,7 +509,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 +523,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; }; @@ -667,10 +671,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 +1031,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 +1265,23 @@ 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 + spin_lock_init(&up->port.lock); + 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 +1293,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->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->dev.parent = up->port.dev; + 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); @@ -1359,20 +1459,22 @@ static struct console sunsu_cons = { * Register console. */ -static inline struct console *SUNSU_CONSOLE(int num_uart) +static inline struct console *SUNSU_CONSOLE(void) { int i; if (con_is_present()) return NULL; - 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) + if (i == UART_NR) + return NULL; + if (sunsu_ports[i].port_node == 0) return NULL; sunsu_cons.index = i; @@ -1380,203 +1482,252 @@ static inline struct console *SUNSU_CONSOLE(int num_uart) return &sunsu_cons; } #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; -} + spin_lock_init(&up->port.lock); + 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; - } - - up->port.line = inst; - - spin_lock_init(&up->port.lock); - - up->su_type = type; + sunsu_autoconfig(up); + if (up->port.type == PORT_UNKNOWN) + continue; - 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; + up->port.line = instance++; + up->port.ops = &sunsu_pops; } - up->port.irq = op->irqs[0]; + sunsu_reg.minor = sunserial_current_minor; - up->port.dev = &op->dev; + sunsu_reg.nr = instance; - up->port.type = PORT_UNKNOWN; - up->port.uartclk = (SU_BASE_BAUD * 16); + ret = uart_register_driver(&sunsu_reg); + if (ret < 0) + return ret; - 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); + sunsu_reg.tty_driver->name_base = sunsu_reg.minor - 64; - return 0; - } + sunserial_current_minor += instance; - up->port.flags |= UPF_BOOT_AUTOCONF; + sunsu_reg.cons = SUNSU_CONSOLE(); - sunsu_autoconfig(up); + for (i = 0; i < UART_NR; i++) { + struct uart_sunsu_port *up = &sunsu_ports[i]; - err = -ENODEV; - if (up->port.type == PORT_UNKNOWN) - goto out_unmap; - - up->port.ops = &sunsu_pops; - - err = uart_add_one_port(&sunsu_reg, &up->port); - if (err) - goto out_unmap; + /* Do not register Keyboard/Mouse lines with UART + * layer. + */ + if (up->su_type == SU_PORT_MS || + up->su_type == SU_PORT_KBD) + continue; - dev_set_drvdata(&op->dev, up); + if (up->port.type == PORT_UNKNOWN) + continue; - inst++; + uart_add_one_port(&sunsu_reg, &up->port); + } return 0; - -out_unmap: - of_iounmap(up->port.membase, up->reg_size); - return err; } -static int __devexit su_remove(struct of_device *dev) +static int su_node_ok(int node, char *name, int namelen) { - struct uart_sunsu_port *up = dev_get_drvdata(&dev->dev);; - - 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); + 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; } - dev_set_drvdata(&dev->dev, NULL); - return 0; } -static struct of_device_id su_match[] = { - { - .name = "su", - }, - { - .name = "su_pnp", - }, - { - .name = "serial", - .compatible = "su", - }, - {}, -}; -MODULE_DEVICE_TABLE(of, su_match); +#define SU_PROPSIZE 128 -static struct of_platform_driver su_driver = { - .name = "su", - .match_table = su_match, - .probe = su_probe, - .remove = __devexit_p(su_remove), +/* + * 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]; }; -static int num_uart; - -static int __init sunsu_init(void) +/* + * 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 device_node *dp; - int err; + struct uart_sunsu_port *up; + int len; - 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++; + if (t->devices >= UART_NR) + return; + + 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)); + } } - for_each_node_by_name(dp, "serial") { - if (of_device_is_compatible(dp, "su")) { - if (su_get_type(dp) == SU_PORT_PORT) - num_uart++; +} + +static int __init sunsu_probe(void) +{ + int node; + int len; + struct su_probe_scan scan; + + /* + * First, we scan the tree. + */ + scan.devices = 0; + scan.msx = -1; + scan.kbx = -1; + scan.kbnode = 0; + scan.msnode = 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); + } + + len = prom_getproperty(node, "mouse", scan.prop, SU_PROPSIZE); + if (len > 0) { + scan.prop[len] = 0; + scan.msnode = prom_finddevice(scan.prop); } } - 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); + su_probe_any(&scan, prom_getchild(prom_root_node)); + + /* + * 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); + + sunsu_ports[1].su_type = SU_PORT_KBD; + sunsu_ports[1].port_node = scan.kbnode; + sunsu_kbd_ms_init(&sunsu_ports[1], 1); + + return 0; } - err = of_register_driver(&su_driver, &of_bus_type); - if (err && num_uart) - uart_unregister_driver(&sunsu_reg); + if (scan.msx != -1 || scan.kbx != -1) { + printk("sunsu_probe: cannot match keyboard and mouse, confused\n"); + return -ENODEV; + } + + if (scan.devices == 0) + return -ENODEV; + + /* + * Console must be initiated after the generic initialization. + */ + sunsu_serial_init(); - return err; + 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");