| /* |
| * Atari TT/Falcon Am8530 SCC serial ports implementation. |
| * |
| * Copyright 2005 Michael Schmitz |
| * |
| * Based on: |
| * drivers/char/vme_scc.c: MVME147, MVME162, BVME6000 SCC serial ports |
| * implementation. |
| * Copyright 1999 Richard Hirst <richard@sleepie.demon.co.uk> |
| * |
| * which, in turn, was |
| * |
| * Based on atari_SCC.c which was |
| * Copyright 1994-95 Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de> |
| * Partially based on PC-Linux serial.c by Linus Torvalds and Theodore Ts'o |
| * |
| * This file is subject to the terms and conditions of the GNU General Public |
| * License. See the file COPYING in the main directory of this archive |
| * for more details. |
| * |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/kdev_t.h> |
| #include <linux/io.h> |
| #include <linux/kernel.h> |
| #include <linux/sched.h> |
| #include <linux/ioport.h> |
| #include <linux/interrupt.h> |
| #include <linux/errno.h> |
| #include <linux/tty.h> |
| #include <linux/tty_flip.h> |
| #include <linux/mm.h> |
| #include <linux/serial.h> |
| #include <linux/fcntl.h> |
| #include <linux/major.h> |
| #include <linux/delay.h> |
| #include <linux/slab.h> |
| #include <linux/miscdevice.h> |
| #include <linux/console.h> |
| #include <linux/init.h> |
| #include <linux/uaccess.h> |
| #include <linux/generic_serial.h> |
| |
| #include <asm/setup.h> |
| #include <asm/bootinfo.h> |
| #include <asm/atarihw.h> |
| #include <asm/atariints.h> |
| |
| #include "scc.h" |
| |
| #define SUPPORT_TT_SCC 1 |
| #define SUPPORT_FALCON_SCC 1 |
| #define SUPPORT_ST_SCC 1 |
| |
| #define CHANNEL_A 0 |
| #define CHANNEL_B 1 |
| |
| #define SCC_MINOR_BASE 64 |
| |
| /* Shadows for all SCC write registers */ |
| static unsigned char scc_shadow[2][16]; |
| |
| /* Location to access for SCC register access delay */ |
| static volatile unsigned char *scc_del; |
| |
| /* To keep track of STATUS_REG state for detection of Ext/Status int source */ |
| static unsigned char scc_last_status_reg[2]; |
| |
| /***************************** Prototypes *****************************/ |
| |
| /* Function prototypes */ |
| static void scc_disable_tx_interrupts(void *ptr); |
| static void scc_enable_tx_interrupts(void *ptr); |
| static void scc_disable_rx_interrupts(void *ptr); |
| static void scc_enable_rx_interrupts(void *ptr); |
| static int scc_carrier_raised(struct tty_port *port); |
| static void scc_shutdown_port(void *ptr); |
| static int scc_set_real_termios(void *ptr); |
| static void scc_hungup(void *ptr); |
| static void scc_close(void *ptr); |
| static int scc_chars_in_buffer(void *ptr); |
| static int scc_open(struct tty_struct *tty, struct file *filp); |
| static int scc_ioctl(struct tty_struct *tty, struct file *filp, |
| unsigned int cmd, unsigned long arg); |
| static void scc_throttle(struct tty_struct *tty); |
| static void scc_unthrottle(struct tty_struct *tty); |
| static irqreturn_t scc_tx_int(int irq, void *data); |
| static irqreturn_t scc_rx_int(int irq, void *data); |
| static irqreturn_t scc_stat_int(int irq, void *data); |
| static irqreturn_t scc_spcond_int(int irq, void *data); |
| static void scc_setsignals(struct scc_port *port, int dtr, int rts); |
| static int scc_break_ctl(struct tty_struct *tty, int break_state); |
| |
| static struct tty_driver *scc_driver; |
| |
| static struct scc_port scc_ports[2]; |
| |
| |
| /*--------------------------------------------------------------------------- |
| * Interface from generic_serial.c back here |
| *--------------------------------------------------------------------------*/ |
| |
| static struct real_driver scc_real_driver = { |
| .disable_tx_interrupts = scc_disable_tx_interrupts, |
| .enable_tx_interrupts = scc_enable_tx_interrupts, |
| .disable_rx_interrupts = scc_disable_rx_interrupts, |
| .enable_rx_interrupts = scc_enable_rx_interrupts, |
| .shutdown_port = scc_shutdown_port, |
| .set_real_termios = scc_set_real_termios, |
| .chars_in_buffer = scc_chars_in_buffer, |
| .close = scc_close, |
| .hungup = scc_hungup, |
| }; |
| |
| static struct tty_operations scc_ops = { |
| .open = scc_open, |
| .close = gs_close, |
| .write = gs_write, |
| .put_char = gs_put_char, |
| .flush_chars = gs_flush_chars, |
| .write_room = gs_write_room, |
| .chars_in_buffer = gs_chars_in_buffer, |
| .flush_buffer = gs_flush_buffer, |
| .ioctl = scc_ioctl, |
| .throttle = scc_throttle, |
| .unthrottle = scc_unthrottle, |
| .set_termios = gs_set_termios, |
| .stop = gs_stop, |
| .start = gs_start, |
| .hangup = gs_hangup, |
| .break_ctl = scc_break_ctl, |
| }; |
| |
| static const struct tty_port_operations scc_port_ops = { |
| .carrier_raised = scc_carrier_raised, |
| }; |
| |
| /* BRG values for the standard speeds and the various clock sources */ |
| |
| struct baud_entry { |
| unsigned clksrc; /* clock source to use or -1 for not possible */ |
| unsigned div; /* divisor: 1, 2 and 4 correspond to |
| * direct 1:16, 1:32 and 1:64 modes, |
| * divisors >= 4 yield a BRG value of |
| * div/2-2 (in 1:16 mode) |
| */ |
| }; |
| |
| /* A pointer for each channel to the current baud table */ |
| static struct baud_entry *scc_baud_table[2]; |
| |
| /* Baud table format: |
| * |
| * Each entry consists of the clock source (CLK_RTxC, CLK_TRxC or |
| * CLK_PCLK) and a divisor. The following rules apply to the divisor: |
| * |
| * - CLK_RTxC: 1 or even (1, 2 and 4 are the direct modes, > 4 use |
| * the BRG) |
| * |
| * - CLK_TRxC: 1, 2 or 4 (no BRG, only direct modes possible) |
| * |
| * - CLK_PCLK: >= 4 and even (no direct modes, only BRG) |
| * |
| */ |
| |
| /* This table is used if RTxC = 3.672 MHz. This is the case for TT's |
| * channel A and for both channels on the Mega STE/Falcon. (TRxC is unused) |
| */ |
| |
| static struct baud_entry bdtab_norm[20] = { |
| /* B0 */ { 0, 0 }, |
| /* B50 */ { CLK_RTxC, 4590 }, |
| /* B75 */ { CLK_RTxC, 3060 }, |
| /* B110 */ { CLK_PCLK, 4576 }, |
| /* B134 */ { CLK_PCLK, 3756 }, |
| /* B150 */ { CLK_RTxC, 1530 }, |
| /* B200 */ { CLK_PCLK, 2516 }, |
| /* B300 */ { CLK_PCLK, 1678 }, |
| /* B600 */ { CLK_PCLK, 838 }, |
| /* B1200 */ { CLK_PCLK, 420 }, |
| /* B1800 */ { CLK_PCLK, 280 }, |
| /* B2400 */ { CLK_PCLK, 210 }, |
| /* B4800 */ { CLK_RTxC, 48 }, |
| /* B9600 */ { CLK_RTxC, 24 }, |
| /* B19200 */ { CLK_RTxC, 12 }, |
| /* B38400 */ { CLK_RTxC, 6 }, /* #15 spd_extra */ |
| /* B57600 */ { CLK_RTxC, 4 }, /* #16 spd_hi */ |
| /* B115200 */ { CLK_RTxC, 2 }, /* #17 spd_vhi */ |
| /* B230400 */ { CLK_RTxC, 1 }, /* #18 spd_shi */ |
| /* B460800 */ { 0, 0 } /* #19 spd_warp: Impossible */ |
| }; |
| |
| /* This is a special table for the TT channel B with 307.2 kHz at RTxC |
| * and 2.4576 MHz at TRxC |
| */ |
| static struct baud_entry bdtab_TTChB[20] = { |
| /* B0 */ { 0, 0 }, |
| /* B50 */ { CLK_RTxC, 384 }, |
| /* B75 */ { CLK_RTxC, 256 }, |
| /* B110 */ { CLK_PCLK, 4576 }, |
| /* B134 */ { CLK_PCLK, 3756 }, |
| /* B150 */ { CLK_RTxC, 128 }, |
| /* B200 */ { CLK_RTxC, 96 }, |
| /* B300 */ { CLK_RTxC, 64 }, |
| /* B600 */ { CLK_RTxC, 32 }, |
| /* B1200 */ { CLK_RTxC, 16 }, |
| /* B1800 */ { CLK_PCLK, 280 }, |
| /* B2400 */ { CLK_RTxC, 8 }, |
| /* B4800 */ { CLK_RTxC, 4 }, |
| /* B9600 */ { CLK_RTxC, 2 }, |
| /* B19200 */ { CLK_RTxC, 1 }, |
| /* B38400 */ { CLK_TRxC, 4 }, |
| /* 57600 is not possible, use 76800 instead */ |
| /* B57600 */ { CLK_TRxC, 2 }, |
| /* 115200 is not possible, use 153600 instead */ |
| /* B115200 */ { CLK_TRxC, 1 }, |
| /* B230400 */ { 0, 0 }, /* #18 spd_shi: Impossible */ |
| /* B460800 */ { 0, 0 } /* #19 spd_warp: Impossible */ |
| }; |
| |
| |
| /*---------------------------------------------------------------------------- |
| * atari_scc_init() and support functions |
| *---------------------------------------------------------------------------*/ |
| |
| static int scc_init_drivers(void) |
| { |
| int error; |
| |
| scc_driver = alloc_tty_driver(2); |
| if (!scc_driver) |
| return -ENOMEM; |
| scc_driver->owner = THIS_MODULE; |
| scc_driver->driver_name = "scc"; |
| scc_driver->name = "ttyS"; |
| scc_driver->major = TTY_MAJOR; |
| scc_driver->minor_start = SCC_MINOR_BASE; |
| scc_driver->type = TTY_DRIVER_TYPE_SERIAL; |
| scc_driver->subtype = SERIAL_TYPE_NORMAL; |
| scc_driver->init_termios = tty_std_termios; |
| scc_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL; |
| scc_driver->flags = TTY_DRIVER_REAL_RAW; |
| |
| tty_set_operations(scc_driver, &scc_ops); |
| |
| error = tty_register_driver(scc_driver); |
| if (error) { |
| pr_err("scc: Couldn't register scc driver, error = %d\n", |
| error); |
| put_tty_driver(scc_driver); |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| |
| /* ports[] array is indexed by line no (i.e. [0] for ttyS0, [1] for ttyS1). |
| */ |
| |
| static void scc_init_portstructs(void) |
| { |
| struct scc_port *port; |
| int i; |
| |
| for (i = 0; i < 2; i++) { |
| port = scc_ports + i; |
| tty_port_init(&port->gs.port); |
| port->gs.port.ops = &scc_port_ops; |
| port->gs.magic = SCC_MAGIC; |
| port->gs.close_delay = HZ/2; |
| port->gs.closing_wait = 30 * HZ; |
| port->gs.rd = &scc_real_driver; |
| #ifdef NEW_WRITE_LOCKING |
| port->gs.port_write_sem = MUTEX; |
| #endif |
| init_waitqueue_head(&port->gs.port.open_wait); |
| init_waitqueue_head(&port->gs.port.close_wait); |
| } |
| } |
| |
| |
| static int atari_scc_a_request_irqs(struct scc_port *port) |
| { |
| int error; |
| |
| error = request_irq(IRQ_SCCA_TX, scc_tx_int, IRQ_TYPE_PRIO, "SCC-A TX", |
| port); |
| if (error) |
| goto fail; |
| |
| error = request_irq(IRQ_SCCA_STAT, scc_stat_int, IRQ_TYPE_PRIO, |
| "SCC-A status", port); |
| if (error) |
| goto fail_free_a_tx; |
| |
| error = request_irq(IRQ_SCCA_RX, scc_rx_int, IRQ_TYPE_PRIO, "SCC-A RX", |
| port); |
| if (error) |
| goto fail_free_a_stat; |
| |
| error = request_irq(IRQ_SCCA_SPCOND, scc_spcond_int, IRQ_TYPE_PRIO, |
| "SCC-A special cond", port); |
| if (error) |
| goto fail_free_a_rx; |
| |
| return 0; |
| |
| fail_free_a_rx: |
| free_irq(IRQ_SCCA_RX, port); |
| fail_free_a_stat: |
| free_irq(IRQ_SCCA_STAT, port); |
| fail_free_a_tx: |
| free_irq(IRQ_SCCA_TX, port); |
| fail: |
| return error; |
| } |
| |
| static void atari_scc_a_free_irqs(struct scc_port *port) |
| { |
| free_irq(IRQ_SCCA_TX, port); |
| free_irq(IRQ_SCCA_STAT, port); |
| free_irq(IRQ_SCCA_RX, port); |
| free_irq(IRQ_SCCA_SPCOND, port); |
| } |
| |
| static int atari_scc_b_request_irqs(struct scc_port *port) |
| { |
| int error; |
| |
| error = request_irq(IRQ_SCCB_TX, scc_tx_int, IRQ_TYPE_PRIO, "SCC-B TX", |
| port); |
| if (error) |
| goto fail; |
| |
| error = request_irq(IRQ_SCCB_STAT, scc_stat_int, IRQ_TYPE_PRIO, |
| "SCC-B status", port); |
| if (error) |
| goto fail_free_b_tx; |
| |
| error = request_irq(IRQ_SCCB_RX, scc_rx_int, IRQ_TYPE_PRIO, "SCC-B RX", |
| port); |
| if (error) |
| goto fail_free_b_stat; |
| |
| error = request_irq(IRQ_SCCB_SPCOND, scc_spcond_int, IRQ_TYPE_PRIO, |
| "SCC-B special cond", port); |
| if (error) |
| goto fail_free_b_rx; |
| |
| return 0; |
| |
| fail_free_b_rx: |
| free_irq(IRQ_SCCB_RX, port); |
| fail_free_b_stat: |
| free_irq(IRQ_SCCB_STAT, port); |
| fail_free_b_tx: |
| free_irq(IRQ_SCCB_TX, port); |
| fail: |
| return error; |
| } |
| |
| static void atari_scc_b_free_irqs(struct scc_port *port) |
| { |
| free_irq(IRQ_SCCB_TX, port); |
| free_irq(IRQ_SCCB_STAT, port); |
| free_irq(IRQ_SCCB_RX, port); |
| free_irq(IRQ_SCCB_SPCOND, port); |
| } |
| |
| #ifdef SUPPORT_TT_SCC |
| static int atari_tt_scc_init(void) |
| { |
| struct scc_port *port; |
| int error; |
| |
| pr_info("SCC: Atari TT Serial Driver\n"); |
| /* FIXME channel A may be switchable between modem and LAN port */ |
| /* Init channel A */ |
| if (atari_SCC_init_done) |
| pr_warning("SCC: already initialized, expect trouble!\n"); |
| pr_debug("SCC: init channel A\n"); |
| port = &scc_ports[0]; |
| port->channel = CHANNEL_A; |
| port->ctrlp = (volatile unsigned char *)&scc.cha_a_ctrl; |
| port->datap = port->ctrlp + 1; |
| port->port_a = &scc_ports[0]; |
| port->port_b = &scc_ports[1]; |
| pr_debug("SCC: request channel A irqs, port = %p\n", port); |
| error = atari_scc_a_request_irqs(port); |
| if (error) |
| return error; |
| |
| { |
| SCC_ACCESS_INIT(port); |
| pr_debug("SCC: read SCC status\n"); |
| /* |
| * on the first access, read status register to reset internal |
| * pointers |
| */ |
| SCCread(STATUS_REG); |
| pr_debug("SCC: reset SCC\n"); |
| /* FIXME: master reset, once only */ |
| SCCwrite(MASTER_INT_CTRL, MIC_HARD_RESET); |
| udelay(40); |
| |
| /* disable interrupts for this channel */ |
| SCCwrite(INT_AND_DMA_REG, 0); |
| /* Set the interrupt vector ; 0x60 for all Atari models */ |
| SCCwrite(INT_VECTOR_REG, 0x60); |
| /* Interrupt parameters: vector includes status, status low */ |
| SCCwrite(MASTER_INT_CTRL, MIC_VEC_INCL_STAT); |
| SCCmod(MASTER_INT_CTRL, 0xff, MIC_MASTER_INT_ENAB); |
| |
| /* disable interrupts for this channel */ |
| SCCwrite(INT_AND_DMA_REG, 0); |
| } |
| |
| if (!atari_SCC_init_done) { |
| /* Init channel B */ |
| pr_debug("SCC: init channel B\n"); |
| port = &scc_ports[1]; |
| port->channel = CHANNEL_B; |
| port->ctrlp = (volatile unsigned char *)&scc.cha_b_ctrl; |
| port->datap = port->ctrlp + 1; |
| port->port_a = &scc_ports[0]; |
| port->port_b = &scc_ports[1]; |
| pr_debug("SCC: request channel B irqs, port = %p\n", port); |
| error = atari_scc_b_request_irqs(port); |
| if (error) { |
| atari_scc_a_free_irqs(port); |
| return error; |
| } |
| |
| { |
| SCC_ACCESS_INIT(port); |
| |
| /* disable interrupts for this channel */ |
| SCCwrite(INT_AND_DMA_REG, 0); |
| } |
| /* not implemented yet */ |
| #if 0 |
| error = request_irq(IRQ_TT_MFP_RI, scc_ri_int, IRQ_TYPE_SLOW, |
| "TT-MFP ring indicator (modem 2)", port); |
| if (error) { |
| atari_scc_b_free_irqs(port); |
| atari_scc_a_free_irqs(port); |
| return error; |
| } |
| #endif |
| |
| } |
| |
| /* once only: initalize MFP timer C for RTxC */ |
| tt_mfp.tim_ct_cd = (tt_mfp.tim_ct_cd & ~0x70) | 0x10; |
| tt_mfp.tim_dt_c = 1; |
| atari_turnoff_irq(IRQ_TT_MFP_TIMC); |
| |
| /* set baud tables */ |
| scc_baud_table[CHANNEL_A] = bdtab_norm; |
| scc_baud_table[CHANNEL_B] = bdtab_TTChB; |
| |
| /* Initialise the tty driver structures and register */ |
| pr_debug("SCC: scc_init_portstructs()\n"); |
| scc_init_portstructs(); |
| pr_debug("SCC: scc_init_drivers()\n"); |
| scc_init_drivers(); |
| |
| return 0; |
| } |
| #else /* !SUPPORT_TT_SCC */ |
| static inline int atari_tt_scc_init(void) |
| { |
| return -ENODEV; |
| } |
| #endif /* !SUPPORT_TT_SCC */ |
| |
| |
| #ifdef SUPPORT_FALCON_SCC |
| static int atari_falcon_scc_init(void) |
| { |
| struct scc_port *port; |
| int error; |
| |
| pr_info("SCC: Atari Falcon Serial Driver\n"); |
| if (atari_SCC_init_done) |
| pr_warning("SCC: already initialized, expect trouble!\n"); |
| |
| /* Init channel A */ |
| port = &scc_ports[0]; |
| port->channel = CHANNEL_A; |
| port->ctrlp = (volatile unsigned char *)&scc.cha_a_ctrl; |
| port->datap = port->ctrlp + 2; |
| port->port_a = &scc_ports[0]; |
| port->port_b = &scc_ports[1]; |
| error = atari_scc_a_request_irqs(port); |
| if (error) |
| return error; |
| |
| { |
| SCC_ACCESS_INIT(port); |
| |
| /* |
| * on the first access, read status register to reset internal |
| * pointers |
| */ |
| SCCread(STATUS_REG); |
| |
| /* FIXME: master reset, once only */ |
| SCCwrite(MASTER_INT_CTRL, MIC_HARD_RESET); |
| udelay(40); |
| |
| /* disable interrupts for this channel */ |
| SCCwrite(INT_AND_DMA_REG, 0); |
| /* Set the interrupt vector */ |
| SCCwrite(INT_VECTOR_REG, 0x60); |
| /* Interrupt parameters: vector includes status, status low */ |
| SCCwrite(MASTER_INT_CTRL, MIC_VEC_INCL_STAT); |
| SCCmod(MASTER_INT_CTRL, 0xff, MIC_MASTER_INT_ENAB); |
| } |
| |
| /* conditionalize if port in use by console ?? */ |
| /* Init channel B */ |
| port = &scc_ports[1]; |
| port->channel = CHANNEL_B; |
| port->ctrlp = (volatile unsigned char *)&scc.cha_b_ctrl; |
| port->datap = port->ctrlp + 2; |
| port->port_a = &scc_ports[0]; |
| port->port_b = &scc_ports[1]; |
| error = atari_scc_b_request_irqs(port); |
| if (error) { |
| atari_scc_a_free_irqs(port); |
| return error; |
| } |
| |
| { |
| SCC_ACCESS_INIT(port); /* Either channel will do */ |
| |
| /* disable interrupts for this channel */ |
| SCCwrite(INT_AND_DMA_REG, 0); |
| } |
| |
| /* set baud tables */ |
| scc_baud_table[CHANNEL_A] = bdtab_norm; |
| scc_baud_table[CHANNEL_B] = bdtab_norm; |
| |
| /* Initialise the tty driver structures and register */ |
| scc_init_portstructs(); |
| scc_init_drivers(); |
| |
| return 0; |
| } |
| #else /* !SUPPORT_FALCON_SCC */ |
| static inline int atari_falcon_scc_init(void) |
| { |
| return -ENODEV; |
| } |
| #endif /* !SUPPORT_FALCON_SCC */ |
| |
| |
| #ifdef SUPPORT_ST_SCC |
| static int atari_st_scc_init(void) |
| { |
| struct scc_port *port; |
| int escc = ATARIHW_PRESENT(ST_ESCC); |
| int error; |
| |
| pr_info("SCC: Atari MegaST/E Serial Driver\n"); |
| /* FIXME: ports reversed logic */ |
| /* Init channel A */ |
| port = &scc_ports[1]; |
| port->channel = CHANNEL_A; |
| port->ctrlp = (volatile unsigned char *)(escc ? &st_escc.cha_a_ctrl |
| : &scc.cha_a_ctrl); |
| port->datap = port->ctrlp + 4; |
| port->port_a = &scc_ports[1]; |
| port->port_b = &scc_ports[0]; |
| error = atari_scc_a_request_irqs(port); |
| if (error) |
| return error; |
| |
| { |
| SCC_ACCESS_INIT(port); |
| |
| /* |
| * on the first access, read status register to reset internal |
| * pointers |
| */ |
| SCCread(STATUS_REG); |
| |
| /* FIXME: master reset, once only */ |
| SCCwrite(MASTER_INT_CTRL, MIC_HARD_RESET); |
| udelay(40); |
| |
| /* disable interrupts for this channel */ |
| SCCwrite(INT_AND_DMA_REG, 0); |
| /* Set the interrupt vector */ |
| SCCwrite(INT_VECTOR_REG, 0x60); |
| /* Interrupt parameters: vector includes status, status low */ |
| SCCwrite(MASTER_INT_CTRL, MIC_VEC_INCL_STAT); |
| SCCmod(MASTER_INT_CTRL, 0xff, MIC_MASTER_INT_ENAB); |
| } |
| |
| /* Init channel B */ |
| port = &scc_ports[0]; |
| port->channel = CHANNEL_B; |
| port->ctrlp = (volatile unsigned char *)(escc ? &st_escc.cha_b_ctrl |
| : &scc.cha_b_ctrl); |
| port->datap = port->ctrlp + 4; |
| port->port_a = &scc_ports[0]; |
| port->port_b = &scc_ports[1]; |
| error = atari_scc_b_request_irqs(port); |
| if (error) { |
| atari_scc_a_free_irqs(port); |
| return error; |
| } |
| |
| { |
| SCC_ACCESS_INIT(port); /* Either channel will do */ |
| |
| /* disable interrupts for this channel */ |
| SCCwrite(INT_AND_DMA_REG, 0); |
| } |
| |
| /* set baud tables */ |
| scc_baud_table[CHANNEL_A] = bdtab_norm; |
| scc_baud_table[CHANNEL_B] = bdtab_norm; |
| |
| /* Initialise the tty driver structures and register */ |
| scc_init_portstructs(); |
| scc_init_drivers(); |
| |
| return 0; |
| } |
| #else /* !SUPPORT_ST_SCC */ |
| static inline int atari_st_scc_init(void) |
| { |
| return -ENODEV; |
| } |
| #endif /* !SUPPORT_ST_SCC */ |
| |
| |
| int atari_scc_init(void) |
| { |
| int res = -ENODEV; |
| static int called; |
| |
| if (called) |
| return res; |
| called = 1; |
| |
| if (!(ATARIHW_PRESENT(SCC) || ATARIHW_PRESENT(ST_ESCC))) |
| return -ENODEV; |
| |
| scc_del = &st_mfp.par_dt_reg; |
| |
| if (MACH_IS_TT) |
| res = atari_tt_scc_init(); |
| if (MACH_IS_FALCON) |
| res = atari_falcon_scc_init(); |
| if (MACH_IS_ST) |
| res = atari_st_scc_init(); |
| return res; |
| } |
| |
| void atari_scc_cleanup(void) |
| { |
| struct scc_port *port; |
| |
| tty_unregister_driver(scc_driver); |
| port = &scc_ports[0]; |
| pr_debug("SCC: free channel A irqs, port = %p\n", port); |
| atari_scc_a_free_irqs(port); |
| |
| port = &scc_ports[1]; |
| pr_debug("SCC: free channel A irqs, port = %p\n", port); |
| atari_scc_b_free_irqs(port); |
| } |
| |
| module_init(atari_scc_init); |
| module_exit(atari_scc_cleanup); |
| |
| /*--------------------------------------------------------------------------- |
| * Interrupt handlers |
| *--------------------------------------------------------------------------*/ |
| |
| static irqreturn_t scc_rx_int(int irq, void *data) |
| { |
| unsigned char ch; |
| struct scc_port *port = data; |
| struct tty_struct *tty = port->gs.port.tty; |
| |
| SCC_ACCESS_INIT(port); |
| pr_debug("SCC: rx_int ...\n"); |
| ch = SCCread_NB(RX_DATA_REG); |
| if (!tty) { |
| pr_warning("scc_rx_int with NULL tty!\n"); |
| SCCwrite_NB(COMMAND_REG, CR_HIGHEST_IUS_RESET); |
| return IRQ_HANDLED; |
| } |
| tty_insert_flip_char(tty, ch, 0); |
| #if 0 |
| if (tty->flip.count < TTY_FLIPBUF_SIZE) { |
| *tty->flip.char_buf_ptr = ch; |
| *tty->flip.flag_buf_ptr = 0; |
| tty->flip.flag_buf_ptr++; |
| tty->flip.char_buf_ptr++; |
| tty->flip.count++; |
| } |
| #endif |
| /* |
| * Check if another character is already ready; in that case, the |
| * spcond_int() function must be used, because this character may have |
| * an * error condition that isn't signalled by the interrupt vector |
| * used! |
| */ |
| if (SCCread(INT_PENDING_REG) & |
| (port->channel == CHANNEL_A ? IPR_A_RX : IPR_B_RX)) { |
| scc_spcond_int(irq, data); |
| return IRQ_HANDLED; |
| } |
| |
| SCCwrite_NB(COMMAND_REG, CR_HIGHEST_IUS_RESET); |
| |
| tty_flip_buffer_push(tty); |
| pr_debug("SCC: rx_int done\n"); |
| return IRQ_HANDLED; |
| } |
| |
| |
| static irqreturn_t scc_spcond_int(int irq, void *data) |
| { |
| struct scc_port *port = data; |
| struct tty_struct *tty = port->gs.port.tty; |
| unsigned char stat, ch, err; |
| int int_pending_mask = port->channel == CHANNEL_A ? IPR_A_RX : IPR_B_RX; |
| |
| SCC_ACCESS_INIT(port); |
| pr_debug("SCC: spcond_int ...\n"); |
| if (!tty) { |
| pr_warning("scc_spcond_int with NULL tty!\n"); |
| SCCwrite(COMMAND_REG, CR_ERROR_RESET); |
| SCCwrite_NB(COMMAND_REG, CR_HIGHEST_IUS_RESET); |
| return IRQ_HANDLED; |
| } |
| do { |
| stat = SCCread(SPCOND_STATUS_REG); |
| ch = SCCread_NB(RX_DATA_REG); |
| |
| if (stat & SCSR_RX_OVERRUN) |
| err = TTY_OVERRUN; |
| else if (stat & SCSR_PARITY_ERR) |
| err = TTY_PARITY; |
| else if (stat & SCSR_CRC_FRAME_ERR) |
| err = TTY_FRAME; |
| else |
| err = 0; |
| |
| tty_insert_flip_char(tty, ch, err); |
| #if 0 |
| if (tty->flip.count < TTY_FLIPBUF_SIZE) { |
| *tty->flip.char_buf_ptr = ch; |
| *tty->flip.flag_buf_ptr = err; |
| tty->flip.flag_buf_ptr++; |
| tty->flip.char_buf_ptr++; |
| tty->flip.count++; |
| } |
| #endif |
| /* ++TeSche: *All* errors have to be cleared manually, |
| * else the condition persists for the next chars |
| */ |
| if (err) |
| SCCwrite(COMMAND_REG, CR_ERROR_RESET); |
| |
| } while (SCCread(INT_PENDING_REG) & int_pending_mask); |
| |
| SCCwrite_NB(COMMAND_REG, CR_HIGHEST_IUS_RESET); |
| |
| tty_flip_buffer_push(tty); |
| pr_debug("SCC: spcond_int done\n"); |
| return IRQ_HANDLED; |
| } |
| |
| /* not implemented yet */ |
| #if 0 |
| static void scc_ri_int(int irq, void *data) |
| { |
| struct scc_port *port = data; |
| /* update input line counter */ |
| port->icount.rng++; |
| wake_up_interruptible(&port->delta_msr_wait); |
| } |
| #endif |
| |
| static irqreturn_t scc_tx_int(int irq, void *data) |
| { |
| struct scc_port *port = data; |
| |
| SCC_ACCESS_INIT(port); |
| pr_debug("SCC: tx_int irq %d port %p ...\n", irq, data); |
| if (!port->gs.port.tty) { |
| pr_warning("scc_tx_int with NULL tty!\n"); |
| SCCmod(INT_AND_DMA_REG, ~IDR_TX_INT_ENAB, 0); |
| SCCwrite(COMMAND_REG, CR_TX_PENDING_RESET); |
| SCCwrite_NB(COMMAND_REG, CR_HIGHEST_IUS_RESET); |
| return IRQ_HANDLED; |
| } |
| while ((SCCread_NB(STATUS_REG) & SR_TX_BUF_EMPTY)) { |
| if (port->x_char) { |
| pr_debug("SCC: tx_int writing char %c\n", port->x_char); |
| SCCwrite(TX_DATA_REG, port->x_char); |
| port->x_char = 0; |
| } else if ((port->gs.xmit_cnt <= 0) || |
| port->gs.port.tty->stopped || |
| port->gs.port.tty->hw_stopped) { |
| pr_debug("SCC: nothing to do!\n"); |
| break; |
| } else { |
| pr_debug("SCC: tx_int writing buf %c\n", |
| port->gs.xmit_buf[port->gs.xmit_tail]); |
| SCCwrite(TX_DATA_REG, |
| port->gs.xmit_buf[port->gs.xmit_tail++]); |
| port->gs.xmit_tail = port->gs.xmit_tail & |
| (SERIAL_XMIT_SIZE-1); |
| if (--port->gs.xmit_cnt <= 0) |
| break; |
| } |
| } |
| if ((port->gs.xmit_cnt <= 0) || port->gs.port.tty->stopped || |
| port->gs.port.tty->hw_stopped) { |
| pr_debug("SCC: nothing to do, disabling int\n"); |
| /* disable tx interrupts */ |
| SCCmod(INT_AND_DMA_REG, ~IDR_TX_INT_ENAB, 0); |
| /* disable tx_int on next tx underrun? */ |
| SCCwrite(COMMAND_REG, CR_TX_PENDING_RESET); |
| port->gs.port.flags &= ~GS_TX_INTEN; |
| } |
| if (port->gs.port.tty && port->gs.xmit_cnt <= port->gs.wakeup_chars) { |
| pr_debug("SCC: waking up tty!\n"); |
| tty_wakeup(port->gs.port.tty); |
| } |
| |
| SCCwrite_NB(COMMAND_REG, CR_HIGHEST_IUS_RESET); |
| pr_debug("SCC: tx_int done\n"); |
| return IRQ_HANDLED; |
| } |
| |
| |
| static irqreturn_t scc_stat_int(int irq, void *data) |
| { |
| struct scc_port *port = data; |
| unsigned channel = port->channel; |
| unsigned char last_sr, sr, changed; |
| |
| SCC_ACCESS_INIT(port); |
| pr_debug("SCC: stat_int ...\n"); |
| last_sr = scc_last_status_reg[channel]; |
| sr = scc_last_status_reg[channel] = SCCread_NB(STATUS_REG); |
| changed = last_sr ^ sr; |
| |
| if (changed & SR_DCD) { |
| port->c_dcd = !!(sr & SR_DCD); |
| if (!(port->gs.port.flags & ASYNC_CHECK_CD)) |
| ; /* Don't report DCD changes */ |
| else if (port->c_dcd) { |
| /* Are we blocking in open? */ |
| wake_up_interruptible(&port->gs.port.open_wait); |
| } else { |
| if (port->gs.port.tty) |
| tty_hangup(port->gs.port.tty); |
| } |
| } |
| |
| /* FIXME: CTS and DSR status changes? */ |
| |
| SCCwrite(COMMAND_REG, CR_EXTSTAT_RESET); |
| SCCwrite_NB(COMMAND_REG, CR_HIGHEST_IUS_RESET); |
| pr_debug("SCC: stat_int done\n"); |
| return IRQ_HANDLED; |
| } |
| |
| |
| /*--------------------------------------------------------------------------- |
| * generic_serial.c callback funtions |
| *--------------------------------------------------------------------------*/ |
| |
| static void scc_disable_tx_interrupts(void *ptr) |
| { |
| struct scc_port *port = ptr; |
| unsigned long flags; |
| |
| SCC_ACCESS_INIT(port); |
| pr_debug("SCC: disable_tx_int ...\n"); |
| local_irq_save(flags); |
| SCCmod(INT_AND_DMA_REG, ~IDR_TX_INT_ENAB, 0); |
| port->gs.port.flags &= ~GS_TX_INTEN; |
| local_irq_restore(flags); |
| pr_debug("SCC: disable_tx_int done!\n"); |
| } |
| |
| |
| static void scc_enable_tx_interrupts(void *ptr) |
| { |
| struct scc_port *port = ptr; |
| unsigned long flags; |
| |
| SCC_ACCESS_INIT(port); |
| pr_debug("SCC: enable_tx_int ...\n"); |
| local_irq_save(flags); |
| SCCmod(INT_AND_DMA_REG, 0xff, IDR_TX_INT_ENAB); |
| /* restart the transmitter */ |
| scc_tx_int(0, port); |
| local_irq_restore(flags); |
| pr_debug("SCC: enable_tx_int done!\n"); |
| } |
| |
| |
| static void scc_disable_rx_interrupts(void *ptr) |
| { |
| struct scc_port *port = ptr; |
| unsigned long flags; |
| |
| SCC_ACCESS_INIT(port); |
| pr_debug("SCC: disable_rx_int ...\n"); |
| local_irq_save(flags); |
| SCCmod(INT_AND_DMA_REG, |
| ~(IDR_RX_INT_MASK|IDR_PARERR_AS_SPCOND|IDR_EXTSTAT_INT_ENAB), 0); |
| local_irq_restore(flags); |
| pr_debug("SCC: disable_rx_int done!\n"); |
| } |
| |
| |
| static void scc_enable_rx_interrupts(void *ptr) |
| { |
| struct scc_port *port = ptr; |
| unsigned long flags; |
| |
| SCC_ACCESS_INIT(port); |
| pr_debug("SCC: enable_rx_int ...\n"); |
| local_irq_save(flags); |
| SCCmod(INT_AND_DMA_REG, 0xff, |
| IDR_EXTSTAT_INT_ENAB|IDR_PARERR_AS_SPCOND|IDR_RX_INT_ALL); |
| local_irq_restore(flags); |
| pr_debug("SCC: enable_rx_int done!\n"); |
| } |
| |
| |
| static int scc_carrier_raised(struct tty_port *port) |
| { |
| struct scc_port *sc = container_of(port, struct scc_port, gs.port); |
| unsigned channel = sc->channel; |
| |
| pr_debug("SCC: get_CD!\n"); |
| return !!(scc_last_status_reg[channel] & SR_DCD); |
| } |
| |
| |
| static void scc_shutdown_port(void *ptr) |
| { |
| struct scc_port *port = ptr; |
| |
| pr_debug("SCC: shutdown_port ...\n"); |
| port->gs.port.flags &= ~GS_ACTIVE; |
| if (port->gs.port.tty && port->gs.port.tty->termios->c_cflag & HUPCL) |
| scc_setsignals(port, 0, 0); |
| pr_debug("SCC: shutdown_port done!\n"); |
| } |
| |
| |
| static int scc_set_real_termios(void *ptr) |
| { |
| /* the SCC has char sizes 5,7,6,8 in that order! */ |
| static int chsize_map[4] = { 0, 2, 1, 3 }; |
| unsigned int cflag, baud, baudbits, baudidx, brgmode; |
| unsigned int clkmode, clksrc, div, chsize, channel, brgval = 0; |
| unsigned long flags; |
| struct scc_port *port = ptr; |
| |
| SCC_ACCESS_INIT(port); |
| |
| if (!port->gs.port.tty || !port->gs.port.tty->termios) |
| return 0; |
| |
| channel = port->channel; |
| pr_debug("SCC: termios for channel %u\n", channel); |
| cflag = port->gs.port.tty->termios->c_cflag; |
| baud = port->gs.baud; |
| baudbits = cflag & CBAUD; |
| chsize = (cflag & CSIZE) >> 4; |
| |
| if (baud == 0) { |
| /* speed == 0 -> drop DTR */ |
| local_irq_save(flags); |
| SCCmod(TX_CTRL_REG, ~TCR_DTR, 0); |
| local_irq_restore(flags); |
| return 0; |
| } else if ((MACH_IS_TT && (baud < 50 || baud > 115200)) || |
| (MACH_IS_FALCON && (baud < 50 || baud > 230400))) { |
| pr_debug("SCC: Bad speed requested, %d\n", baud); |
| return 0; |
| } |
| |
| if (cflag & CLOCAL) |
| port->gs.port.flags &= ~ASYNC_CHECK_CD; |
| else |
| port->gs.port.flags |= ASYNC_CHECK_CD; |
| |
| /* calculate brgval for Atari; enable direct modes! */ |
| |
| /* |
| * convert baud rate from gs.baud to table index, set custom divisor |
| * eventually |
| */ |
| div = 0; |
| clksrc = 0; |
| baudidx = 0; |
| |
| switch (baud) { |
| case 50: |
| baudidx = 1; |
| break; |
| case 75: |
| baudidx = 2; |
| break; |
| case 110: |
| baudidx = 3; |
| break; |
| case 134: |
| baudidx = 4; |
| break; |
| case 150: |
| baudidx = 5; |
| break; |
| case 200: |
| baudidx = 6; |
| break; |
| case 300: |
| baudidx = 7; |
| break; |
| case 600: |
| baudidx = 8; |
| break; |
| case 1200: |
| baudidx = 9; |
| break; |
| case 1800: |
| baudidx = 10; |
| break; |
| case 2400: |
| baudidx = 11; |
| break; |
| case 4800: |
| baudidx = 12; |
| break; |
| case 9600: |
| baudidx = 13; |
| break; |
| case 19200: |
| baudidx = 14; |
| break; |
| case 38400: |
| baudidx = 15; |
| break; |
| case 57600: |
| baudidx = 16; |
| break; |
| case 115200: |
| baudidx = 17; |
| break; |
| case 230400: |
| baudidx = 18; |
| break; |
| default: |
| baudidx = 15; |
| break; |
| } |
| |
| /* do we have a custom divisor ?? */ |
| if (!div) { |
| if (baudidx > 19) |
| baudidx = 19; |
| clksrc = scc_baud_table[channel][baudidx].clksrc; |
| div = scc_baud_table[channel][baudidx].div; |
| if (!div) { |
| pr_debug("SCC_change_speed: divisor = 0 !!!\n"); |
| return 0; |
| } |
| } |
| pr_debug("SCC: termios baud %d baudbits %d baudidx %d \n clksrc %d " |
| "div %d\n", baud, baudbits, baudidx, clksrc, div); |
| /* compute the SCC's clock source, clock mode, BRG mode and BRG |
| * value from clksrc and div |
| */ |
| if (div <= 4) { |
| clkmode = (div == 1 ? A1CR_CLKMODE_x16 : |
| div == 2 ? A1CR_CLKMODE_x32 : |
| A1CR_CLKMODE_x64); |
| clksrc = (clksrc == CLK_RTxC |
| ? CCR_TXCLK_RTxC | CCR_RXCLK_RTxC |
| : CCR_TXCLK_TRxC | CCR_RXCLK_TRxC); |
| brgmode = 0; /* off */ |
| brgval = 0; |
| } else { |
| brgval = div/2 - 2; |
| brgmode = (DCR_BRG_ENAB | |
| (clksrc == CLK_PCLK ? DCR_BRG_USE_PCLK : 0)); |
| clkmode = A1CR_CLKMODE_x16; |
| clksrc = CCR_TXCLK_BRG | CCR_RXCLK_BRG; |
| } |
| |
| /* |
| * pr_info("SCC: termios baud %d baudbits %d baudidx %d \n clksrc %d " |
| * "clkmode %d div %d brgval %d brgmode %d\n", baud, baudbits, |
| * baudidx, clksrc, clkmode, div, brgval, brgmode); |
| */ |
| |
| /* Now we have all parameters and can go to set them: */ |
| local_irq_save(flags); |
| |
| pr_debug(" brgval=%d brgmode=%02x clkmode=%02x clksrc=%02x\n", brgval, |
| brgmode, clkmode, clksrc); |
| /* receiver's character size and auto-enables */ |
| #if 0 /* auto-enable considered harmful ... */ |
| SCCmod(RX_CTRL_REG, ~(RCR_CHSIZE_MASK|RCR_AUTO_ENAB_MODE), |
| (chsize_map[chsize] << 6) | |
| ((cflag & CRTSCTS) ? RCR_AUTO_ENAB_MODE : 0)); |
| #else |
| /* receiver's character size */ |
| SCCmod(RX_CTRL_REG, ~RCR_CHSIZE_MASK, chsize_map[chsize] << 6); |
| #endif |
| pr_debug(" RX_CTRL_REG <- %02x\n", SCCread(RX_CTRL_REG)); |
| |
| /* clock mode changes depending on baud rate */ |
| /* parity and stop bits (both, Tx and Rx) and clock mode */ |
| SCCmod(AUX1_CTRL_REG, |
| ~(A1CR_PARITY_MASK | A1CR_MODE_MASK | A1CR_CLKMODE_MASK), |
| ((cflag & PARENB |
| ? (cflag & PARODD ? A1CR_PARITY_ODD : A1CR_PARITY_EVEN) |
| : A1CR_PARITY_NONE) |
| | (cflag & CSTOPB ? A1CR_MODE_ASYNC_2 : A1CR_MODE_ASYNC_1) |
| | clkmode)); |
| |
| pr_debug(" AUX1_CTRL_REG <- %02x\n", SCCread(AUX1_CTRL_REG)); |
| /* sender's character size, set DTR for valid baud rate */ |
| SCCmod(TX_CTRL_REG, ~TCR_CHSIZE_MASK, |
| chsize_map[chsize] << 5 | TCR_DTR); |
| pr_debug(" TX_CTRL_REG <- %02x\n", SCCread(TX_CTRL_REG)); |
| |
| /* clock sources change for TT !! */ |
| /* clock sources never change */ |
| /* clock sources */ |
| SCCmod(CLK_CTRL_REG, ~(CCR_TXCLK_MASK | CCR_RXCLK_MASK), clksrc); |
| pr_debug(" CLK_CTRL_REG <- %02x\n", SCCread(CLK_CTRL_REG)); |
| |
| /* disable BRG before changing the value */ |
| SCCmod(DPLL_CTRL_REG, ~DCR_BRG_ENAB, 0); |
| /* BRG value */ |
| SCCwrite(TIMER_LOW_REG, brgval & 0xff); |
| SCCwrite(TIMER_HIGH_REG, (brgval >> 8) & 0xff); |
| /* BRG enable, and clock source never changes */ |
| /* SCCmod(DPLL_CTRL_REG, 0xff, DCR_BRG_ENAB); */ |
| SCCmod(DPLL_CTRL_REG, ~(DCR_BRG_ENAB | DCR_BRG_USE_PCLK), brgmode); |
| pr_debug(" TIMER_LOW_REG <- %02x\n", SCCread(TIMER_LOW_REG)); |
| pr_debug(" TIMER_HIGH_REG <- %02x\n", SCCread(TIMER_HIGH_REG)); |
| pr_debug(" DPLL_CTRL_REG <- %02x\n", SCCread(DPLL_CTRL_REG)); |
| |
| local_irq_restore(flags); |
| pr_debug("SCC: done termios for channel %d\n", channel); |
| return 0; |
| } |
| |
| |
| static int scc_chars_in_buffer(void *ptr) |
| { |
| struct scc_port *port = ptr; |
| #ifdef DEBUG |
| int rv; |
| #endif |
| |
| SCC_ACCESS_INIT(port); |
| #ifdef DEBUG |
| rv = (SCCread(SPCOND_STATUS_REG) & SCSR_ALL_SENT) ? 0 : 1; |
| pr_debug("SCC: chars_in_buffer: %d\n", rv); |
| return rv; |
| #else |
| return (SCCread(SPCOND_STATUS_REG) & SCSR_ALL_SENT) ? 0 : 1; |
| #endif |
| } |
| |
| |
| /* Comment taken from sx.c (2.4.0): |
| I haven't the foggiest why the decrement use count has to happen |
| here. The whole linux serial drivers stuff needs to be redesigned. |
| My guess is that this is a hack to minimize the impact of a bug |
| elsewhere. Thinking about it some more. (try it sometime) Try |
| running minicom on a serial port that is driven by a modularized |
| driver. Have the modem hangup. Then remove the driver module. Then |
| exit minicom. I expect an "oops". -- REW */ |
| |
| static void scc_hungup(void *ptr) |
| { |
| pr_debug("SCC: hungup ...\n"); |
| scc_disable_tx_interrupts(ptr); |
| scc_disable_rx_interrupts(ptr); |
| pr_debug("SCC: hungup done\n"); |
| } |
| |
| |
| static void scc_close(void *ptr) |
| { |
| pr_debug("SCC: close ...\n"); |
| scc_disable_tx_interrupts(ptr); |
| scc_disable_rx_interrupts(ptr); |
| pr_debug("SCC: close done\n"); |
| } |
| |
| |
| /*--------------------------------------------------------------------------- |
| * Internal support functions |
| *--------------------------------------------------------------------------*/ |
| |
| static void scc_setsignals(struct scc_port *port, int dtr, int rts) |
| { |
| unsigned long flags; |
| unsigned char t; |
| |
| SCC_ACCESS_INIT(port); |
| pr_debug("SCC: setsignals dtr %d rts %d...\n", dtr, rts); |
| local_irq_save(flags); |
| t = SCCread(TX_CTRL_REG); |
| if (dtr >= 0) |
| t = dtr? (t | TCR_DTR): (t & ~TCR_DTR); |
| if (rts >= 0) |
| t = rts? (t | TCR_RTS): (t & ~TCR_RTS); |
| SCCwrite(TX_CTRL_REG, t); |
| local_irq_restore(flags); |
| pr_debug("SCC: setsignals done\n"); |
| } |
| |
| |
| static void scc_send_xchar(struct tty_struct *tty, char ch) |
| { |
| struct scc_port *port = tty->driver_data; |
| |
| pr_debug("SCC: send_xchar ...\n"); |
| port->x_char = ch; |
| if (ch) |
| scc_enable_tx_interrupts(port); |
| pr_debug("SCC: send_xchar done\n"); |
| } |
| |
| |
| /*--------------------------------------------------------------------------- |
| * Driver entrypoints referenced from above |
| *--------------------------------------------------------------------------*/ |
| |
| static int scc_open(struct tty_struct *tty, struct file *filp) |
| { |
| int line = tty->index; |
| int retval; |
| struct scc_port *port = &scc_ports[line]; |
| int i, channel = port->channel; |
| unsigned long flags; |
| SCC_ACCESS_INIT(port); |
| |
| static const struct { |
| unsigned reg, val; |
| } scc_init_tab[] = { |
| /* no parity, 1 stop bit, async, 1:16 */ |
| { AUX1_CTRL_REG, |
| A1CR_PARITY_NONE|A1CR_MODE_ASYNC_1|A1CR_CLKMODE_x64 }, |
| /* parity error is special cond, ints disabled, no DMA */ |
| { INT_AND_DMA_REG, IDR_PARERR_AS_SPCOND | IDR_RX_INT_DISAB }, |
| /* Rx 8 bits/char, no auto enable, Rx off */ |
| { RX_CTRL_REG, RCR_CHSIZE_8 }, |
| /* DTR off, Tx 8 bits/char, RTS off, Tx off */ |
| { TX_CTRL_REG, TCR_CHSIZE_8 }, |
| /* special features off */ |
| { AUX2_CTRL_REG, 0 }, |
| /* RTxC is XTAL, TRxC is input, both clocks = RTxC */ |
| { CLK_CTRL_REG, |
| CCR_TRxCOUT_XTAL | CCR_TXCLK_RTxC | CCR_RXCLK_RTxC }, |
| { DPLL_CTRL_REG, 0 }, |
| /* Start Rx */ |
| { RX_CTRL_REG, RCR_RX_ENAB | RCR_CHSIZE_8 }, |
| /* Start Tx */ |
| { TX_CTRL_REG, TCR_TX_ENAB | TCR_RTS | TCR_DTR | TCR_CHSIZE_8 }, |
| /* Ext/Stat ints: CTS, DCD, SYNC (DSR) */ |
| { INT_CTRL_REG, |
| ICR_ENAB_DCD_INT | ICR_ENAB_CTS_INT | ICR_ENAB_SYNC_INT }, |
| /* Reset Ext/Stat ints */ |
| { COMMAND_REG, CR_EXTSTAT_RESET }, |
| /* ...again */ |
| { COMMAND_REG, CR_EXTSTAT_RESET }, |
| /* Rx int always, TX int off, Ext/Stat int on */ |
| { INT_AND_DMA_REG, IDR_EXTSTAT_INT_ENAB | |
| IDR_PARERR_AS_SPCOND | IDR_RX_INT_ALL } |
| }; |
| |
| if (atari_SCC_init_done && line == 1) |
| return -ENODEV; |
| |
| pr_debug("SCC: open port ...\n"); |
| if (!(port->gs.port.flags & ASYNC_INITIALIZED)) { |
| pr_debug("SCC: init port ...\n"); |
| local_irq_save(flags); |
| |
| SCCmod(MASTER_INT_CTRL, 0x3f, |
| channel == 0 ? MIC_CH_A_RESET : MIC_CH_B_RESET); |
| udelay(40); /* extra delay after a reset */ |
| |
| for (i = 0; i < ARRAY_SIZE(scc_init_tab); ++i) |
| SCCwrite(scc_init_tab[i].reg, scc_init_tab[i].val); |
| |
| |
| /* |
| * remember status register for detection of DCD and CTS |
| * changes |
| */ |
| scc_last_status_reg[channel] = SCCread(STATUS_REG); |
| |
| port->c_dcd = 0; /* Prevent initial 1->0 interrupt */ |
| scc_setsignals(port, 1, 1); |
| local_irq_restore(flags); |
| pr_debug("SCC: init port done!\n"); |
| } |
| |
| tty->driver_data = port; |
| port->gs.port.tty = tty; |
| port->gs.port.count++; |
| pr_debug(KERN_WARNING "SCC: gs init port ...\n"); |
| retval = gs_init_port(&port->gs); |
| if (retval) { |
| port->gs.port.count--; |
| return retval; |
| } |
| pr_debug(KERN_WARNING "SCC: gs init port done!\n"); |
| port->gs.port.flags |= GS_ACTIVE; |
| |
| pr_debug(KERN_WARNING "SCC: gs wait ready ...\n"); |
| retval = gs_block_til_ready(port, filp); |
| pr_debug(KERN_WARNING "SCC: gs wait ready done!\n"); |
| if (retval) { |
| port->gs.port.count--; |
| return retval; |
| } |
| |
| port->c_dcd = tty_port_carrier_raised(&port->gs.port); |
| |
| pr_debug(KERN_WARNING "SCC: enable rx ints ...\n"); |
| scc_enable_rx_interrupts(port); |
| pr_debug(KERN_WARNING "SCC: enable rx ints done!\n"); |
| pr_info("SCC: open port done!\n"); |
| return 0; |
| } |
| |
| |
| static void scc_throttle(struct tty_struct *tty) |
| { |
| struct scc_port *port = tty->driver_data; |
| unsigned long flags; |
| |
| SCC_ACCESS_INIT(port); |
| pr_debug("SCC: throttle ...\n"); |
| if (tty->termios->c_cflag & CRTSCTS) { |
| local_irq_save(flags); |
| SCCmod(TX_CTRL_REG, ~TCR_RTS, 0); |
| local_irq_restore(flags); |
| } |
| if (I_IXOFF(tty)) |
| scc_send_xchar(tty, STOP_CHAR(tty)); |
| pr_debug("SCC: throttle done!\n"); |
| } |
| |
| |
| static void scc_unthrottle(struct tty_struct *tty) |
| { |
| struct scc_port *port = tty->driver_data; |
| unsigned long flags; |
| |
| SCC_ACCESS_INIT(port); |
| pr_debug("SCC: unthrottle ...\n"); |
| if (tty->termios->c_cflag & CRTSCTS) { |
| local_irq_save(flags); |
| SCCmod(TX_CTRL_REG, 0xff, TCR_RTS); |
| local_irq_restore(flags); |
| } |
| if (I_IXOFF(tty)) |
| scc_send_xchar(tty, START_CHAR(tty)); |
| pr_debug("SCC: unthrottle done!\n"); |
| } |
| |
| |
| static int scc_ioctl(struct tty_struct *tty, struct file *file, |
| unsigned int cmd, unsigned long arg) |
| { |
| struct scc_port *port = tty->driver_data; |
| int retval; |
| |
| pr_debug("SCC: ioctl! cmd %d, arg %lu\n", cmd, arg); |
| /* if (serial_paranoia_check(info, tty->device, "zs_ioctl")) */ |
| /* return -ENODEV; */ |
| |
| if ((cmd != TIOCGSERIAL) && (cmd != TIOCSSERIAL) && |
| (cmd != TIOCSERCONFIG) && (cmd != TIOCSERGWILD) && |
| (cmd != TIOCSERSWILD) && (cmd != TIOCSERGSTRUCT)) { |
| if (tty->flags & (1 << TTY_IO_ERROR)) |
| return -EIO; |
| } |
| |
| switch (cmd) { |
| case TCSBRK: /* SVID version: non-zero arg --> no break */ |
| pr_debug("SCC: ioctl TCSBRK\n"); |
| retval = tty_check_change(tty); |
| if (retval) |
| return retval; |
| tty_wait_until_sent(tty, 0); |
| /* if (!arg) */ |
| /* send_break(info, HZ/4); */ |
| return 0; |
| case TCSBRKP: /* support for POSIX tcsendbreak() */ |
| pr_debug("SCC: ioctl TCSBRKP\n"); |
| retval = tty_check_change(tty); |
| if (retval) |
| return retval; |
| tty_wait_until_sent(tty, 0); |
| /* send_break(info, arg ? arg*(HZ/10) : HZ/4); */ |
| return 0; |
| case TIOCGSOFTCAR: |
| pr_debug("SCC: ioctl TIOCGSOFTCAR\n"); |
| if (put_user(C_CLOCAL(tty) ? 1 : 0, (unsigned long *) arg)) |
| return -EFAULT; |
| return 0; |
| case TIOCSSOFTCAR: |
| pr_debug("SCC: ioctl TIOCSSOFTCAR\n"); |
| if (get_user(arg, (unsigned long *)arg)) |
| return -EFAULT; |
| tty->termios->c_cflag = |
| ((tty->termios->c_cflag & ~CLOCAL) | |
| (arg ? CLOCAL : 0)); |
| return 0; |
| case TIOCMGET: |
| pr_debug("SCC: ioctl TIOCMGET\n"); |
| /* return get_modem_info(info, (unsigned int *)arg); */ |
| return 0; |
| case TIOCMBIS: |
| case TIOCMBIC: |
| case TIOCMSET: |
| pr_debug("SCC: ioctl TIOCMSET\n"); |
| /* return set_modem_info(info, cmd, (unsigned int *)arg); */ |
| return 0; |
| case TIOCGSERIAL: |
| pr_debug("SCC: ioctl TIOCGSERIAL\n"); |
| return 0; |
| /* return get_serial_info(info, (struct serial_struct *)arg); */ |
| case TIOCSSERIAL: |
| pr_debug("SCC: ioctl TIOCSSERIAL\n"); |
| return 0; |
| /* return set_serial_info(info, (struct serial_struct *)arg); */ |
| case TIOCSERGETLSR: /* Get line status register */ |
| pr_debug("SCC: ioctl TIOCSERGETLSR\n"); |
| return 0; |
| /* return get_lsr_info(info, (unsigned int *)arg); */ |
| |
| case TIOCSERGSTRUCT: |
| pr_debug("SCC: ioctl TIOCSERGSTRUCT\n"); |
| return 0; |
| if (copy_to_user((struct scc_port *)arg, port, |
| sizeof(struct scc_port))) |
| return -EFAULT; |
| return 0; |
| |
| default: |
| pr_debug("SCC: ioctl default\n"); |
| return -ENOIOCTLCMD; |
| } |
| return 0; |
| } |
| |
| |
| static int scc_break_ctl(struct tty_struct *tty, int break_state) |
| { |
| struct scc_port *port = tty->driver_data; |
| unsigned long flags; |
| |
| SCC_ACCESS_INIT(port); |
| pr_debug("SCC: break ctl ...\n"); |
| local_irq_save(flags); |
| SCCmod(TX_CTRL_REG, ~TCR_SEND_BREAK, break_state ? TCR_SEND_BREAK : 0); |
| local_irq_restore(flags); |
| pr_debug("SCC: break ctl done!\n"); |
| |
| return 0; |
| } |
| |
| |
| #if defined(CONFIG_SERIAL_CONSOLE) && !defined(MODULE) |
| |
| /*--------------------------------------------------------------------------- |
| * Serial console stuff... |
| *--------------------------------------------------------------------------*/ |
| |
| #define scc_delay() \ |
| asm volatile ("tstb %0" : : "m" (*scc_del) : "cc") |
| |
| #define SCC_WRITE(reg,val) \ |
| do { \ |
| scc.cha_b_ctrl = (reg); \ |
| scc_delay(); \ |
| scc.cha_b_ctrl = (val); \ |
| scc_delay(); \ |
| } while (0) |
| |
| /* |
| * loops_per_jiffy isn't initialized yet, so we can't use udelay(). |
| * This does a delay of ~ 60us. |
| */ |
| #define LONG_DELAY() \ |
| do { \ |
| int i; \ |
| for (i = 100; i > 0; i--) \ |
| scc_delay(); \ |
| } while (0) |
| |
| static void atari_init_scc_port(int cflag) |
| { |
| static int clksrc_table[9] = |
| /* reg 11: 0x50 = BRG, 0x00 = RTxC, 0x28 = TRxC */ |
| { 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x00, 0x00 }; |
| static int brgsrc_table[9] = |
| /* reg 14: 0 = RTxC, 2 = PCLK */ |
| { 2, 2, 2, 2, 2, 2, 0, 2, 2 }; |
| static int clkmode_table[9] = |
| /* reg 4: 0x40 = x16, 0x80 = x32, 0xc0 = x64 */ |
| { 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0xc0, 0x80 }; |
| static int div_table[9] = |
| /* reg12 (BRG low) */ |
| { 208, 138, 103, 50, 24, 11, 1, 0, 0 }; |
| |
| int baud = cflag & CBAUD; |
| int clksrc, clkmode, div, reg3, reg5; |
| |
| scc_del = &st_mfp.par_dt_reg; |
| |
| if (cflag & CBAUDEX) |
| baud += B38400; |
| if (baud < B1200 || baud > B38400+2) { |
| /* use default 9600bps for non-implemented rates */ |
| baud = B9600; |
| } |
| baud -= B1200; /* tables starts at 1200bps */ |
| |
| clksrc = clksrc_table[baud]; |
| clkmode = clkmode_table[baud]; |
| div = div_table[baud]; |
| if (ATARIHW_PRESENT(TT_MFP) && baud >= 6) { |
| /* |
| * special treatment for TT, where rates >= 38400 are done via |
| * TRxC |
| */ |
| clksrc = 0x28; /* TRxC */ |
| clkmode = baud == 6 ? 0xc0 : |
| baud == 7 ? 0x80 : /* really 76800bps */ |
| 0x40; /* really 153600bps */ |
| div = 0; |
| } |
| |
| reg3 = (cflag & CSIZE) == CS8 ? 0xc0 : 0x40; |
| reg5 = (cflag & CSIZE) == CS8 ? 0x60 : 0x20 | 0x82 /* assert DTR/RTS */; |
| |
| (void)scc.cha_b_ctrl; /* reset reg pointer */ |
| SCC_WRITE(9, 0xc0); /* reset */ |
| LONG_DELAY(); /* extra delay after WR9 access */ |
| SCC_WRITE(4, (cflag & PARENB) ? ((cflag & PARODD) ? 0x01 : 0x03) : 0 | |
| 0x04 /* 1 stopbit */ | |
| clkmode); |
| SCC_WRITE(3, reg3); |
| SCC_WRITE(5, reg5); |
| SCC_WRITE(9, 0); /* no interrupts */ |
| LONG_DELAY(); /* extra delay after WR9 access */ |
| SCC_WRITE(10, 0); /* NRZ mode */ |
| SCC_WRITE(11, clksrc); /* main clock source */ |
| SCC_WRITE(12, div); /* BRG value */ |
| SCC_WRITE(13, 0); /* BRG high byte */ |
| SCC_WRITE(14, brgsrc_table[baud]); |
| SCC_WRITE(14, brgsrc_table[baud] | (div ? 1 : 0)); |
| SCC_WRITE(3, reg3 | 1); |
| SCC_WRITE(5, reg5 | 8); |
| |
| atari_SCC_reset_done = 1; |
| atari_SCC_init_done = 1; |
| } |
| |
| static void scc_ch_write(char ch) |
| { |
| volatile char *p = NULL; |
| |
| if (MACH_IS_TT || MACH_IS_FALCON) |
| p = (volatile char *)&scc.cha_b_ctrl; |
| |
| if (MACH_IS_ST) |
| p = (volatile char *)&scc.cha_b_ctrl; |
| |
| if (MACH_IS_STE) |
| p = (volatile char *)&st_escc.cha_b_ctrl; |
| |
| do { |
| scc_delay(); |
| } while (!(*p & 4)); |
| /* scc_delay(); */ |
| /* *p = 8; */ |
| scc_delay(); |
| *(p+1) = ch; |
| } |
| |
| /* The console must be locked when we get here. */ |
| |
| static void scc_console_write(struct console *co, const char *str, |
| unsigned count) |
| { |
| unsigned long flags; |
| |
| /* printk("scc_console_write: %s\n", str); */ |
| local_irq_save(flags); |
| |
| while (count--) { |
| if (*str == '\n') |
| scc_ch_write('\r'); |
| scc_ch_write(*str++); |
| } |
| local_irq_restore(flags); |
| /* printk("scc_console_write done!\n"); */ |
| } |
| |
| static struct tty_driver *scc_console_device(struct console *c, int *index) |
| { |
| *index = c->index; |
| return scc_driver; |
| } |
| |
| |
| static int __init scc_console_setup(struct console *co, char *options) |
| { |
| pr_debug("scc_console_setup: initializing SCC port B\n"); |
| atari_init_scc_port(B9600|CS8); |
| pr_debug("scc_console_setup: done!\n"); |
| return 0; |
| } |
| |
| |
| static struct console sercons = { |
| .name = "ttyS", |
| .write = scc_console_write, |
| .device = scc_console_device, |
| .setup = scc_console_setup, |
| .flags = CON_PRINTBUFFER, |
| .index = -1, |
| }; |
| |
| |
| static int __init atari_scc_console_init(void) |
| { |
| if (MACH_IS_TT || MACH_IS_ST || MACH_IS_FALCON) |
| register_console(&sercons); |
| return 0; |
| } |
| |
| console_initcall(atari_scc_console_init); |
| |
| #endif /* CONFIG_SERIAL_CONSOLE && !MODULE */ |
| |
| /***************************** End of Functions *********************/ |
| |
| MODULE_AUTHOR("Michael Schmitz"); |
| MODULE_DESCRIPTION("Atari Amd8350 SCC serial driver"); |
| MODULE_LICENSE("GPL"); |