| /* | |
| * linux/drivers/serial/acorn.c | |
| * | |
| * Copyright (C) 1996-2003 Russell King. | |
| * | |
| * This program is free software; you can redistribute it and/or modify | |
| * it under the terms of the GNU General Public License version 2 as | |
| * published by the Free Software Foundation. | |
| */ | |
| #include <linux/module.h> | |
| #include <linux/types.h> | |
| #include <linux/tty.h> | |
| #include <linux/serial_core.h> | |
| #include <linux/errno.h> | |
| #include <linux/ioport.h> | |
| #include <linux/slab.h> | |
| #include <linux/device.h> | |
| #include <linux/init.h> | |
| #include <asm/io.h> | |
| #include <asm/ecard.h> | |
| #include <asm/string.h> | |
| #include "8250.h" | |
| #define MAX_PORTS 3 | |
| struct serial_card_type { | |
| unsigned int num_ports; | |
| unsigned int uartclk; | |
| unsigned int type; | |
| unsigned int offset[MAX_PORTS]; | |
| }; | |
| struct serial_card_info { | |
| unsigned int num_ports; | |
| int ports[MAX_PORTS]; | |
| void __iomem *vaddr; | |
| }; | |
| static int __devinit | |
| serial_card_probe(struct expansion_card *ec, const struct ecard_id *id) | |
| { | |
| struct serial_card_info *info; | |
| struct serial_card_type *type = id->data; | |
| struct uart_port port; | |
| unsigned long bus_addr; | |
| unsigned int i; | |
| info = kmalloc(sizeof(struct serial_card_info), GFP_KERNEL); | |
| if (!info) | |
| return -ENOMEM; | |
| memset(info, 0, sizeof(struct serial_card_info)); | |
| info->num_ports = type->num_ports; | |
| bus_addr = ecard_resource_start(ec, type->type); | |
| info->vaddr = ioremap(bus_addr, ecard_resource_len(ec, type->type)); | |
| if (!info->vaddr) { | |
| kfree(info); | |
| return -ENOMEM; | |
| } | |
| ecard_set_drvdata(ec, info); | |
| memset(&port, 0, sizeof(struct uart_port)); | |
| port.irq = ec->irq; | |
| port.flags = UPF_BOOT_AUTOCONF | UPF_SHARE_IRQ; | |
| port.uartclk = type->uartclk; | |
| port.iotype = UPIO_MEM; | |
| port.regshift = 2; | |
| port.dev = &ec->dev; | |
| for (i = 0; i < info->num_ports; i ++) { | |
| port.membase = info->vaddr + type->offset[i]; | |
| port.mapbase = bus_addr + type->offset[i]; | |
| info->ports[i] = serial8250_register_port(&port); | |
| } | |
| return 0; | |
| } | |
| static void __devexit serial_card_remove(struct expansion_card *ec) | |
| { | |
| struct serial_card_info *info = ecard_get_drvdata(ec); | |
| int i; | |
| ecard_set_drvdata(ec, NULL); | |
| for (i = 0; i < info->num_ports; i++) | |
| if (info->ports[i] > 0) | |
| serial8250_unregister_port(info->ports[i]); | |
| iounmap(info->vaddr); | |
| kfree(info); | |
| } | |
| static struct serial_card_type atomwide_type = { | |
| .num_ports = 3, | |
| .uartclk = 7372800, | |
| .type = ECARD_RES_IOCSLOW, | |
| .offset = { 0x2800, 0x2400, 0x2000 }, | |
| }; | |
| static struct serial_card_type serport_type = { | |
| .num_ports = 2, | |
| .uartclk = 3686400, | |
| .type = ECARD_RES_IOCSLOW, | |
| .offset = { 0x2000, 0x2020 }, | |
| }; | |
| static const struct ecard_id serial_cids[] = { | |
| { MANU_ATOMWIDE, PROD_ATOMWIDE_3PSERIAL, &atomwide_type }, | |
| { MANU_SERPORT, PROD_SERPORT_DSPORT, &serport_type }, | |
| { 0xffff, 0xffff } | |
| }; | |
| static struct ecard_driver serial_card_driver = { | |
| .probe = serial_card_probe, | |
| .remove = __devexit_p(serial_card_remove), | |
| .id_table = serial_cids, | |
| .drv = { | |
| .name = "8250_acorn", | |
| }, | |
| }; | |
| static int __init serial_card_init(void) | |
| { | |
| return ecard_register_driver(&serial_card_driver); | |
| } | |
| static void __exit serial_card_exit(void) | |
| { | |
| ecard_remove_driver(&serial_card_driver); | |
| } | |
| MODULE_AUTHOR("Russell King"); | |
| MODULE_DESCRIPTION("Acorn 8250-compatible serial port expansion card driver"); | |
| MODULE_LICENSE("GPL"); | |
| module_init(serial_card_init); | |
| module_exit(serial_card_exit); |