-/* 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.
* 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 <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <asm/io.h>
#include <asm/irq.h>
-#include <asm/prom.h>
-#include <asm/of_device.h>
+#ifdef CONFIG_SPARC64
+#include <asm/fhc.h>
+#endif
+#include <asm/sbus.h>
#if defined(CONFIG_SERIAL_SUNZILOG_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ)
#define SUPPORT_SYSRQ
#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. */
unsigned char prev_status;
#ifdef CONFIG_SERIO
- struct serio serio;
+ struct serio *serio;
int serio_open;
#endif
};
{
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;
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();
}
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);
}
udelay(100);
}
- writeb(ERR_RES, &channel->control);
+ sbus_writeb(ERR_RES, &channel->control);
ZSDELAY();
ZS_WSYNC(channel);
/* 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);
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;
};
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
if (!(ch & Rx_CH_AV))
break;
- ch = readb(&channel->data);
+ ch = sbus_readb(&channel->data);
ZSDELAY();
ch &= up->parity_mask;
{
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);
* confusing the PROM.
*/
while (1) {
- status = readb(&channel->control);
+ status = sbus_readb(&channel->control);
ZSDELAY();
if (!(status & BRK_ABRT))
break;
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.
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);
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);
return;
ack_tx_int:
- writeb(RES_Tx_P, &channel->control);
+ sbus_writeb(RES_Tx_P, &channel->control);
ZSDELAY();
ZS_WSYNC(channel);
}
/* 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);
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);
unsigned char status;
channel = ZILOG_CHANNEL_FROM_PORT(port);
- status = readb(&channel->control);
+ status = sbus_readb(&channel->control);
ZSDELAY();
return status;
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. */
* IRQ sending engine.
*/
if (port->x_char) {
- writeb(port->x_char, &channel->data);
+ sbus_writeb(port->x_char, &channel->data);
ZSDELAY();
ZS_WSYNC(channel);
} 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);
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;
static const char *sunzilog_type(struct uart_port *port)
{
- return "zs";
+ return "SunZilog";
}
/* We do not request/release mappings of the registers here, this
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;
+}
- spin_lock_init(&up->port.lock);
+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();
+ }
+}
- if (i == 0)
- sunzilog_irq_chain = up;
+#ifdef CONFIG_SPARC64
- if (i < NUM_CHANNELS - 1)
- up->next = up + 1;
- else
- up->next = NULL;
+/* 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 (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 */
-static void sunzilog_putchar(struct uart_port *port, int ch)
+static void sunzilog_put_char(struct zilog_channel __iomem *channel, unsigned char ch)
{
- struct zilog_channel *channel = ZILOG_CHANNEL_FROM_PORT(port);
int loops = ZS_PUT_CHAR_MAX_DELAY;
/* This is a timed polling loop so do not switch the explicit
* 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;
udelay(5);
} while (--loops);
- writeb(ch, &channel->data);
+ sbus_writeb(ch, &channel->data);
ZSDELAY();
ZS_WSYNC(channel);
}
spin_lock_irqsave(&sunzilog_serio_lock, flags);
- sunzilog_putchar(&up->port, ch);
+ sunzilog_put_char(ZILOG_CHANNEL_FROM_PORT(&up->port), ch);
spin_unlock_irqrestore(&sunzilog_serio_lock, flags);
sunzilog_console_write(struct console *con, const char *s, unsigned int count)
{
struct uart_sunzilog_port *up = &sunzilog_port_table[con->index];
+ struct zilog_channel *channel = ZILOG_CHANNEL_FROM_PORT(&up->port);
unsigned long flags;
+ int i;
spin_lock_irqsave(&up->port.lock, flags);
- uart_console_write(&up->port, s, count, sunzilog_putchar);
+ for (i = 0; i < count; i++, s++) {
+ sunzilog_put_char(channel, *s);
+ if (*s == 10)
+ sunzilog_put_char(channel, 13);
+ }
udelay(2);
spin_unlock_irqrestore(&up->port.lock, flags);
}
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);
.index = -1,
.data = &sunzilog_reg,
};
+#define SUNZILOG_CONSOLE (&sunzilog_console)
-static inline struct console *SUNZILOG_CONSOLE(void)
+static int __init sunzilog_console_init(void)
{
int i;
if (con_is_present())
- return NULL;
+ return 0;
for (i = 0; i < NUM_CHANNELS; i++) {
int this_minor = sunzilog_reg.minor + i;
break;
}
if (i == NUM_CHANNELS)
- return NULL;
+ return 0;
sunzilog_console.index = i;
sunzilog_port_table[i].flags |= SUNZILOG_FLAG_IS_CONS;
-
- return &sunzilog_console;
+ register_console(&sunzilog_console);
+ return 0;
}
-
#else
-#define SUNZILOG_CONSOLE() (NULL)
+#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);
}
#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();
- zs_remove_one(&up[0]);
- zs_remove_one(&up[1]);
+ /* 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];
- regs = sunzilog_chip_regs[up[0].port.line / 2];
- of_iounmap(regs, sizeof(struct zilog_layout));
+ if (ZS_IS_KEYB(up) || ZS_IS_MOUSE(up))
+ continue;
- dev_set_drvdata(&dev->dev, NULL);
+ uart_count++;
+ }
+
+ sunzilog_reg.nr = uart_count;
+ sunzilog_reg.cons = SUNZILOG_CONSOLE;
- return 0;
-}
+ sunzilog_reg.minor = sunserial_current_minor;
+ sunserial_current_minor += uart_count;
-static struct of_device_id zs_match[] = {
- {
- .name = "zs",
- },
- {},
-};
-MODULE_DEVICE_TABLE(of, zs_match);
+ ret = uart_register_driver(&sunzilog_reg);
+ if (ret == 0) {
+ sunzilog_console_init();
+ 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);
MODULE_AUTHOR("David S. Miller");
MODULE_DESCRIPTION("Sun Zilog serial port driver");
-MODULE_VERSION("2.0");
MODULE_LICENSE("GPL");