/* * Copyright (c) 2000-2006 Apple Computer, Inc. All rights reserved. * * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. The rights granted to you under the License * may not be used to create, or enable the creation or redistribution of, * unlawful or unlicensed copies of an Apple operating system, or to * circumvent, violate, or enable the circumvention or violation of, any * terms of an Apple operating system software license agreement. * * Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ */ /* * @OSF_COPYRIGHT@ */ /* * @APPLE_FREE_COPYRIGHT@ */ /* * Mach Operating System * Copyright (c) 1991,1990,1989 Carnegie Mellon University * All Rights Reserved. * * Permission to use, copy, modify and distribute this software and its * documentation is hereby granted, provided that both the copyright * notice and this permission notice appear in all copies of the * software, derivative works or modified versions, and any portions * thereof, and that both notices appear in supporting documentation. * * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. * * Carnegie Mellon requests users of this software to return to * * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU * School of Computer Science * Carnegie Mellon University * Pittsburgh PA 15213-3890 * * any improvements or extensions that they make and grant Carnegie Mellon * the rights to redistribute these changes. */ /* */ /* * File: scc_8530_hdw.c * Author: Alessandro Forin, Carnegie Mellon University * Date: 6/91 * * Hardware-level operations for the SCC Serial Line Driver */ #define NSCC 1 /* Number of serial chips, two ports per chip. */ #if NSCC > 0 #include #include #include #include #include #include #include #include #include #include #include #include #include #if MACH_KDB #include #endif /* MACH_KDB */ #define kdebug_state() (1) #define delay(x) { volatile int _d_; for (_d_ = 0; _d_ < (10000*x); _d_++) ; } #define NSCC_LINE 2 /* 2 ttys per chip */ #define SCC_DMA_TRANSFERS 0 struct scc_tty scc_tty[NSCC_LINE]; #define scc_tty_for(chan) (&scc_tty[chan]) /* #define scc_unit(dev_no) (dev_no) */ #define scc_dev_no(chan) ((chan)^0x01) #define scc_chan(dev_no) ((dev_no)^0x01) int serial_initted = 0; unsigned int scc_parm_done = 0; static struct scc_byte { unsigned char reg; unsigned char val; } scc_init_hw[] = { {9, 0x80}, {4, 0x44}, {3, 0xC0}, {5, 0xE2}, {2, 0x00}, {10, 0x00}, {11, 0x50}, {12, 0x0A}, {13, 0x00}, {3, 0xC1}, {5, 0xEA}, {14, 0x01}, {15, 0x00}, {0, 0x10}, {0, 0x10}, #if 0 {1, 0x12}, /* int or Rx, Tx int enable */ #else {1, 0x10}, /* int or Rx, no Tx int enable */ #endif {9, 0x0A} }; static int scc_init_hw_count = sizeof(scc_init_hw)/sizeof(scc_init_hw[0]); enum scc_error {SCC_ERR_NONE, SCC_ERR_PARITY, SCC_ERR_BREAK, SCC_ERR_OVERRUN}; /* * BRG formula is: * ClockFrequency (115200 for Power Mac) * BRGconstant = --------------------------- - 2 * BaudRate */ #define SERIAL_CLOCK_FREQUENCY (115200*2) /* Power Mac value */ #define convert_baud_rate(rate) ((((SERIAL_CLOCK_FREQUENCY) + (rate)) / (2 * (rate))) - 2) #define DEFAULT_SPEED 57600 #define DEFAULT_PORT0_SPEED 1200 #define DEFAULT_FLAGS (TF_LITOUT|TF_ECHO) int scc_param(struct scc_tty *tp); struct scc_softc scc_softc[NSCC]; caddr_t scc_std[NSCC] = { (caddr_t) 0}; #define SCC_RR1_ERRS (SCC_RR1_FRAME_ERR|SCC_RR1_RX_OVERRUN|SCC_RR1_PARITY_ERR) #define SCC_RR3_ALL (SCC_RR3_RX_IP_A|SCC_RR3_TX_IP_A|SCC_RR3_EXT_IP_A|\ SCC_RR3_RX_IP_B|SCC_RR3_TX_IP_B|SCC_RR3_EXT_IP_B) #define DEBUG_SCC #undef DEBUG_SCC #ifdef DEBUG_SCC static int total_chars, total_ints, total_overruns, total_errors, num_ints, max_chars; static int chars_received[8]; static int __SCC_STATS = 0; static int max_in_q = 0; static int max_out_q = 0; #endif DECL_FUNNEL(, scc_funnel) /* funnel to serialize the SCC driver */ boolean_t scc_funnel_initted = FALSE; #define SCC_FUNNEL scc_funnel #define SCC_FUNNEL_INITTED scc_funnel_initted /* * Adapt/Probe/Attach functions */ boolean_t scc_uses_modem_control = FALSE;/* patch this with adb */ decl_simple_lock_data(,scc_stomp) /* This is called VERY early on in the init and therefore has to have * hardcoded addresses of the serial hardware control registers. The * serial line may be needed for console and debugging output before * anything else takes place */ void initialize_serial( caddr_t scc_phys_base, int32_t serial_baud ) { int i, chan, bits; scc_regmap_t regs; DECL_FUNNEL_VARS assert( scc_phys_base ); if (!SCC_FUNNEL_INITTED) { FUNNEL_INIT(&SCC_FUNNEL, master_processor); SCC_FUNNEL_INITTED = TRUE; } FUNNEL_ENTER(&SCC_FUNNEL); if (serial_initted) { FUNNEL_EXIT(&SCC_FUNNEL); return; } simple_lock_init(&scc_stomp, FALSE); if (serial_baud == -1) serial_baud = DEFAULT_SPEED; scc_softc[0].full_modem = TRUE; scc_std[0] = scc_phys_base; regs = scc_softc[0].regs = (scc_regmap_t)scc_std[0]; for (chan = 0; chan < NSCC_LINE; chan++) { if (chan == 1) scc_init_hw[0].val = 0x80; for (i = 0; i < scc_init_hw_count; i++) { scc_write_reg(regs, chan, scc_init_hw[i].reg, scc_init_hw[i].val); } } /* Call probe so we are ready very early for remote gdb and for serial console output if appropriate. */ if (scc_probe(serial_baud)) { for (i = 0; i < NSCC_LINE; i++) { scc_softc[0].softr[i].wr5 = SCC_WR5_DTR | SCC_WR5_RTS; scc_param(scc_tty_for(i)); /* Enable SCC interrupts (how many interrupts are to this thing?!?) */ scc_write_reg(regs, i, 9, SCC_WR9_NV); scc_read_reg_zero(regs, 0, bits);/* Clear the status */ } scc_parm_done = 1; } serial_initted = TRUE; FUNNEL_EXIT(&SCC_FUNNEL); return; } int scc_probe(int32_t serial_baud) { scc_softc_t scc; int i; scc_regmap_t regs; spl_t s; DECL_FUNNEL_VARS if (!SCC_FUNNEL_INITTED) { FUNNEL_INIT(&SCC_FUNNEL, master_processor); SCC_FUNNEL_INITTED = TRUE; } FUNNEL_ENTER(&SCC_FUNNEL); /* Readjust the I/O address to handling * new memory mappings. */ regs = (scc_regmap_t)scc_std[0]; if (regs == (scc_regmap_t) 0) { FUNNEL_EXIT(&SCC_FUNNEL); return 0; } scc = &scc_softc[0]; scc->regs = regs; s = splhigh(); for (i = 0; i < NSCC_LINE; i++) { register struct scc_tty *tp; tp = scc_tty_for(i); tp->t_addr = (char*)(0x80000000L + (i&1)); /* Set default values. These will be overridden on open but are needed if the port will be used independently of the Mach interfaces, e.g., for gdb or for a serial console. */ if (i == 0) { tp->t_ispeed = DEFAULT_PORT0_SPEED; tp->t_ospeed = DEFAULT_PORT0_SPEED; } else { tp->t_ispeed = serial_baud; tp->t_ospeed = serial_baud; } tp->t_flags = DEFAULT_FLAGS; scc->softr[i].speed = -1; /* do min buffering */ tp->t_state |= TS_MIN; tp->t_dev = scc_dev_no(i); } splx(s); FUNNEL_EXIT(&SCC_FUNNEL); return 1; } /* * Get a char from a specific SCC line * [this is only used for console&screen purposes] * must be splhigh since it may be called from another routine under spl */ int scc_getc(__unused int unit, int line, boolean_t wait, __unused boolean_t raw) { scc_regmap_t regs; unsigned char c, value; int rcvalue; spl_t s = splhigh(); DECL_FUNNEL_VARS FUNNEL_ENTER(&SCC_FUNNEL); simple_lock(&scc_stomp); regs = scc_softc[0].regs; /* * wait till something available * */ again: rcvalue = 0; while (1) { scc_read_reg_zero(regs, line, value); if (value & SCC_RR0_RX_AVAIL) break; if (!wait) { simple_unlock(&scc_stomp); splx(s); FUNNEL_EXIT(&SCC_FUNNEL); return -1; } } /* * if nothing found return -1 */ scc_read_reg(regs, line, SCC_RR1, value); scc_read_data(regs, line, c); #if MACH_KDB if (console_is_serial() && c == ('_' & 0x1f)) { /* Drop into the debugger */ simple_unlock(&scc_stomp); Debugger("Serial Line Request"); simple_lock(&scc_stomp); scc_write_reg(regs, line, SCC_RR0, SCC_RESET_HIGHEST_IUS); if (wait) { goto again; } simple_unlock(&scc_stomp); splx(s); FUNNEL_EXIT(&SCC_FUNNEL); return -1; } #endif /* MACH_KDB */ /* * bad chars not ok */ if (value&(SCC_RR1_PARITY_ERR | SCC_RR1_RX_OVERRUN | SCC_RR1_FRAME_ERR)) { scc_write_reg(regs, line, SCC_RR0, SCC_RESET_ERROR); if (wait) { scc_write_reg(regs, line, SCC_RR0, SCC_RESET_HIGHEST_IUS); goto again; } } scc_write_reg(regs, line, SCC_RR0, SCC_RESET_HIGHEST_IUS); simple_unlock(&scc_stomp); splx(s); FUNNEL_EXIT(&SCC_FUNNEL); return c; } /* * This front-ends scc_getc to make some intel changes easier */ int _serial_getc(int unit, int line, boolean_t wait, boolean_t raw) { return(scc_getc(unit, line, wait, raw)); } /* * Put a char on a specific SCC line * use splhigh since we might be doing a printf in high spl'd code */ void scc_putc(__unused int unit, int line, int c) { scc_regmap_t regs; spl_t s; unsigned char value; DECL_FUNNEL_VARS if (disable_serial_output) return; s = splhigh(); FUNNEL_ENTER(&SCC_FUNNEL); simple_lock(&scc_stomp); regs = scc_softc[0].regs; do { scc_read_reg(regs, line, SCC_RR0, value); if (value & SCC_RR0_TX_EMPTY) break; delay(1); } while (1); scc_write_data(regs, line, c); /* wait for it to swallow the char ? */ do { scc_read_reg(regs, line, SCC_RR0, value); if (value & SCC_RR0_TX_EMPTY) break; } while (1); scc_write_reg(regs, line, SCC_RR0, SCC_RESET_HIGHEST_IUS); simple_unlock(&scc_stomp); splx(s); FUNNEL_EXIT(&SCC_FUNNEL); } void powermac_scc_set_datum(scc_regmap_t regs, unsigned int offset, unsigned char value) { volatile unsigned char *address = (unsigned char *) regs + offset; assert(FUNNEL_IN_USE(&SCC_FUNNEL)); *address = value; eieio(); assert(FUNNEL_IN_USE(&SCC_FUNNEL)); } unsigned char powermac_scc_get_datum(scc_regmap_t regs, unsigned int offset) { volatile unsigned char *address = (unsigned char *) regs + offset; unsigned char value; assert(FUNNEL_IN_USE(&SCC_FUNNEL)); value = *address; eieio(); return value; assert(FUNNEL_IN_USE(&SCC_FUNNEL)); } int scc_param(struct scc_tty *tp) { scc_regmap_t regs; unsigned char value; unsigned short speed_value; int bits, chan; spl_t s; struct scc_softreg *sr; scc_softc_t scc; assert(FUNNEL_IN_USE(&SCC_FUNNEL)); s = splhigh(); simple_lock(&scc_stomp); chan = scc_chan(tp->t_dev); scc = &scc_softc[0]; regs = scc->regs; sr = &scc->softr[chan]; /* Do a quick check to see if the hardware needs to change */ if ((sr->flags & (TF_ODDP|TF_EVENP)) == (tp->t_flags & (TF_ODDP|TF_EVENP)) && sr->speed == (unsigned long)tp->t_ispeed) { assert(FUNNEL_IN_USE(&SCC_FUNNEL)); simple_unlock(&scc_stomp); splx(s); return 0; } if(scc_parm_done) { scc_write_reg(regs, chan, 3, SCC_WR3_RX_8_BITS|SCC_WR3_RX_ENABLE); sr->wr1 = SCC_WR1_RXI_FIRST_CHAR | SCC_WR1_EXT_IE; scc_write_reg(regs, chan, 1, sr->wr1); scc_write_reg(regs, chan, 15, SCC_WR15_ENABLE_ESCC); scc_write_reg(regs, chan, 7, SCC_WR7P_RX_FIFO); scc_write_reg(regs, chan, 0, SCC_IE_NEXT_CHAR); scc_write_reg(regs, chan, 0, SCC_RESET_EXT_IP); scc_write_reg(regs, chan, 0, SCC_RESET_EXT_IP); scc_write_reg(regs, chan, 9, SCC_WR9_MASTER_IE|SCC_WR9_NV); scc_read_reg_zero(regs, 0, bits); sr->wr1 = SCC_WR1_RXI_FIRST_CHAR | SCC_WR1_EXT_IE; scc_write_reg(regs, chan, 1, sr->wr1); scc_write_reg(regs, chan, 0, SCC_IE_NEXT_CHAR); simple_unlock(&scc_stomp); splx(s); return 0; } sr->flags = tp->t_flags; sr->speed = tp->t_ispeed; if (tp->t_ispeed == 0) { sr->wr5 &= ~SCC_WR5_DTR; scc_write_reg(regs, chan, 5, sr->wr5); simple_unlock(&scc_stomp); splx(s); assert(FUNNEL_IN_USE(&SCC_FUNNEL)); return 0; } #if SCC_DMA_TRANSFERS if (scc->dma_initted & (1<dma_ops->scc_dma_reset_rx(chan); #endif value = SCC_WR4_1_STOP; /* * For 115K the clocking divide changes to 64.. to 230K will * start at the normal clock divide 16. * * However, both speeds will pull from a different clocking * source */ if (tp->t_ispeed == 115200) value |= SCC_WR4_CLK_x32; else value |= SCC_WR4_CLK_x16 ; /* .. and parity */ if ((tp->t_flags & (TF_ODDP | TF_EVENP)) == TF_EVENP) value |= (SCC_WR4_EVEN_PARITY | SCC_WR4_PARITY_ENABLE); else if ((tp->t_flags & (TF_ODDP | TF_EVENP)) == TF_ODDP) value |= SCC_WR4_PARITY_ENABLE; /* set it now, remember it must be first after reset */ sr->wr4 = value; /* Program Parity, and Stop bits */ scc_write_reg(regs, chan, 4, sr->wr4); /* Setup for 8 bits */ scc_write_reg(regs, chan, 3, SCC_WR3_RX_8_BITS); // Set DTR, RTS, and transmitter bits/character. sr->wr5 = SCC_WR5_TX_8_BITS | SCC_WR5_RTS | SCC_WR5_DTR; scc_write_reg(regs, chan, 5, sr->wr5); scc_write_reg(regs, chan, 14, 0); /* Disable baud rate */ /* Setup baud rate 57.6Kbps, 115K, 230K should all yeild * a converted baud rate of zero */ speed_value = convert_baud_rate(tp->t_ispeed); if (speed_value == 0xffff) speed_value = 0; scc_set_timing_base(regs, chan, speed_value); if (tp->t_ispeed == 115200 || tp->t_ispeed == 230400) { /* Special case here.. change the clock source*/ scc_write_reg(regs, chan, 11, 0); /* Baud rate generator is disabled.. */ } else { scc_write_reg(regs, chan, 11, SCC_WR11_RCLK_BAUDR|SCC_WR11_XTLK_BAUDR); /* Enable the baud rate generator */ scc_write_reg(regs, chan, 14, SCC_WR14_BAUDR_ENABLE); } scc_write_reg(regs, chan, 3, SCC_WR3_RX_8_BITS|SCC_WR3_RX_ENABLE); sr->wr1 = SCC_WR1_RXI_FIRST_CHAR | SCC_WR1_EXT_IE; scc_write_reg(regs, chan, 1, sr->wr1); scc_write_reg(regs, chan, 15, SCC_WR15_ENABLE_ESCC); scc_write_reg(regs, chan, 7, SCC_WR7P_RX_FIFO); scc_write_reg(regs, chan, 0, SCC_IE_NEXT_CHAR); /* Clear out any pending external or status interrupts */ scc_write_reg(regs, chan, 0, SCC_RESET_EXT_IP); scc_write_reg(regs, chan, 0, SCC_RESET_EXT_IP); //scc_write_reg(regs, chan, 0, SCC_RESET_ERROR); /* Enable SCC interrupts (how many interrupts are to this thing?!?) */ scc_write_reg(regs, chan, 9, SCC_WR9_MASTER_IE|SCC_WR9_NV); scc_read_reg_zero(regs, 0, bits);/* Clear the status */ #if SCC_DMA_TRANSFERS if (scc->dma_initted & (1<dma_ops->scc_dma_start_rx(chan); scc->dma_ops->scc_dma_setup_8530(chan); } else #endif { sr->wr1 = SCC_WR1_RXI_FIRST_CHAR | SCC_WR1_EXT_IE; scc_write_reg(regs, chan, 1, sr->wr1); scc_write_reg(regs, chan, 0, SCC_IE_NEXT_CHAR); } sr->wr5 |= SCC_WR5_TX_ENABLE; scc_write_reg(regs, chan, 5, sr->wr5); simple_unlock(&scc_stomp); splx(s); assert(FUNNEL_IN_USE(&SCC_FUNNEL)); return 0; } #endif /* NSCC > 0 */