// ========== Copyright Header Begin ==========================================
// OpenSPARC T2 Processor File: serial.cc
// Copyright (c) 2006 Sun Microsystems, Inc. All Rights Reserved.
// DO NOT ALTER OR REMOVE COPYRIGHT NOTICES.
// The above named program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public
// License version 2 as published by the Free Software Foundation.
// The above named program is distributed in the hope that it will be
// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
// You should have received a copy of the GNU General Public
// License along with this work; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
// ========== Copyright Header End ============================================
* Copyright (C) 1991, 2001 Sun Microsystems, Inc.
#pragma ident "@(#)1.11 04/08/04 serial.cc"
#include "blaze_globals.h"
typedef void* TM_OPAQUE_DATA
;
extern Serial
*systemConsole
;;
* This function is to allocate a serial object
serialT
*Serial::allocation_obj()
msp
= (serialT
*) calloc(1, sizeof(serialT
));
debug_err("%s: malloc serial object failed\n", HERE
);
* This routine is called to receive requests to read and write the serial's
* registers. Return a status of 1 if error
Serial::reg_access(char *buf
, uint64_t paddr
, bool wr
)
uint8_t *reg_value
= (uint8_t *) (buf
+ BYTE_OFFSET
); /* value of register read */
uint32_t addr
= (uint32_t) paddr
& SERIAL_PORT_ADDR_MASK
;
*reg_value
= rd_control(PORT_A
);
*reg_value
= rd_control(PORT_B
);
*reg_value
= rd_port(PORT_A
);
*reg_value
= rd_port(PORT_B
);
printf("%s: read invalid address 0x%lx\n", HERE
, addr
);
debug_err("%s: read invalid address 0x%lx\n", HERE
, addr
);
*(uint8_t *) (buf
+ BYTE_OFFSET
));
*(uint8_t *) (buf
+ BYTE_OFFSET
));
*(uint8_t *) (buf
+ BYTE_OFFSET
));
*(uint8_t *) (buf
+ BYTE_OFFSET
));
printf("%s: write invalid address 0x%lx\n", HERE
,
debug_err("%s: write invalid address 0x%lx\n", HERE
,
* This routine is called to receive characters as they arrive. It queues
* them up in the serial's internal queue.
Serial::char_input(uint8_t c
, int portnum
)
rcv_regs
= &(msp
->scc
.port
[portnum
]);
/* turn on the "ready to receive" flag */
rcv_regs
->rr
[0] |= RX_CHAR_AVAILABLE
;
if (INTERRUPTS_ENABLED(msp
, portnum
) && !RCV_INT_PENDING(msp
, portnum
)
&& RCV_INT_ENABLED(msp
, portnum
) && rcv_regs
->qcnt
) {
* Update status reg - Interrupt Pending Send interrupt
channel_regs
*channelA
= &(msp
->scc
.port
[PORT_A
]);
channel_regs
*channelB
= &(msp
->scc
.port
[PORT_B
]);
* if we're generating interrupts in Vector Include Status mode,
* the interrupt vector on channelB includes status in the lower
* nibble which is characteristic of the event causing the
if (rcv_regs
->wr
[9] & VIS
) {
* if no interrupts are pending, clear out
* vector include status bits before or'ing in new state
* (channelA.rr3) is the interrupt pending register
* (channelB.rr2) is the interrupt vector (VIS) register
/* clearing idle "special rcv condition" bits */
channelB
->rr
[2] &= ~VIS_LO_MASK
;
/* add "Rx ready" to status vector */
SET_VIS_RX_CHAR_AVAIL(msp
, portnum
);
SET_RCV_INT_PENDING(msp
, portnum
);
//dev_interrupt_in(TRUE,device, 0);
pciMaster_set_int(0,true);
* Add character 'c' to end of port receive queue.
Serial::enq(channel_regs
* rcv_regs
, char c
)
if ((q
= (squeue
*) calloc(1, sizeof(squeue
))) == NULL
) {
/* fatal("serial: enq: Unable to malloc.\n"); */
mutex_lock(&rcv_regs
->serial_lock
);
mutex_unlock(&rcv_regs
->serial_lock
);
* Remove character from front of queue and return it. If no char, return
Serial::deq(channel_regs
* rcv_regs
)
char c
= '\0'; /* still return 0 if nothing here */
mutex_lock(&rcv_regs
->serial_lock
);
rcv_regs
->q
.next
= cursor
->next
;
/* fatal("serial: deq: qcnt inconsistent"); */
mutex_unlock(&rcv_regs
->serial_lock
);
Serial::rd_port(int portnum
)
rcv_regs
= &(msp
->scc
.port
[portnum
]);
/* turn off "ready to receive" flag if we're out of characters */
if (rcv_regs
->qcnt
== 0) {
rcv_regs
->rr
[0] &= ~RX_CHAR_AVAILABLE
;
if (RCV_INT_PENDING(msp
, portnum
) && !(rcv_regs
->qcnt
)) {
CLEAR_RCV_INT_PENDING(msp
, portnum
);
//dev_interrupt_in(FALSE, device, 0);
pciMaster_set_int(0,false);
Serial::wr_port(int portnum
, uint8_t c
)
channel_regs
*xmit_regs
= &(msp
->scc
.port
[portnum
]);
xmit_regs
->rr
[0] &= ~TX_READY
;
xmit_regs
->rr
[1] &= ~ALL_SENT
;
* Note! the transmit & subsequent status posting operation
* may need to be delayed an indeterminate number of cycles
* for kernel/zs driver purposes.
* ...leaving it as a synchronous function call for now.
Serial::rd_control(int portnum
)
AB_Regs_t
*sccp
= &msp
->scc
;
/*--------------------------------------------------------
* 001 0 0 0 data (not implemented in this func)
*------------------------------------------------------
read_reg
= (sccp
->port
[portnum
].wr
[0] & REG_NUMBER
);
if ((sccp
->port
[portnum
].wr
[0] & WR0_BITS3_5
) != POINT_HIGH
) {
else if (read_reg
& REG_3
)
else if (read_reg
== 1) {
debug_err("%s: rd_control: POINT_HIGH and WR0 D2-D0 is 1.\nRequest ignored.\n", HERE
);
* If just read a non-zero register, clear D2-D0 so it points to WR0
sccp
->port
[portnum
].wr
[0] &= WR0_BITS0_4
;
return (sccp
->port
[portnum
].rr
[read_reg
]);
Serial::wr_control(int portnum
, unsigned char buf
)
AB_Regs_t
*sccp
= &msp
->scc
;
/*--------------------------------------------------------
* NOT 001 0 0 0 0 (comand register)
* 001 0 0 0 data (DATA request - not in this func)
*------------------------------------------------------
write_reg
= (sccp
->port
[portnum
].wr
[0] & REG_NUMBER
);
if ((sccp
->port
[portnum
].wr
[0] & WR0_BITS3_5
) == POINT_HIGH
) {
/* should never get here */
debug_err("%s: wr_control: POINT_HIGH and WR0 D2-D0 is 0. Request Ignored.\n", HERE
);
sccp
->port
[portnum
].wr
[write_reg
] = buf
;
* WR0 - If a channel command is being requested,
* perform necessary house cleaning
if (sccp
->port
[portnum
].wr
[0] & WR0_BITS4_5
)
* WR2 and WR9 are shared by the two channels.
if (write_reg
== 2 || write_reg
== 9) {
} else if (portnum
== PORT_B
) {
sccp
->port
[otherport
].wr
[write_reg
] = buf
;
/* WR5 transmit parameter & control */
sccp
->port
[portnum
].rr
[0] |= DCD
;
/* if RTS than set CTS */
sccp
->port
[portnum
].rr
[0] |= CTS
;
/* for status update purposes ... */
/* RR2,RR12,RR13,RR15 reflect the values in the write registers */
if ((write_reg
== 2) || (write_reg
== 12) || (write_reg
== 13) ||
sccp
->port
[portnum
].rr
[write_reg
] = buf
;
* If just written a non-zero register, clear D2-D0 so it points to
sccp
->port
[portnum
].wr
[0] &= WR0_BITS0_4
;
for (port
= 0; port
< NUM_PORTS
; port
++) {
/* Clear out all registers for port. */
msp
->scc
.port
[port
].qcnt
= 0;
msp
->scc
.port
[port
].q
.next
= NULL
;
for (i
= 0; i
< SERIAL_REGS
; i
++) {
msp
->scc
.port
[port
].wr
[i
] = 0;
msp
->scc
.port
[port
].rr
[i
] = 0;
/* set "ready to transmit" */
msp
->scc
.port
[port
].rr
[0] = TX_READY
;
msp
->scc
.port
[port
].rr
[1] = ALL_SENT
;
* set to the "special receive condition" whose need
* was determined by the kernel zs bringup efforts.
msp
->scc
.port
[port
].rr
[2] = SRC
;
mutex_init(&(msp
->scc
.port
[port
].serial_lock
), USYNC_THREAD
,
* Set interrrupts enabled for port A if there is an interrupt
* interface, otherwise leave them disabled.
* The state of WR1 after reset is all interrrupts disabled.
* The state of WR1 after reset is bitwise: <00x00x00>
* see pg 7-4; in the AMD Z85C30/SCC tech manual
* SET_INTERRUPTS_ENABLE(msp, PORT_A);
* SET_RCV_INT_ENABLE(msp, PORT_A);
Serial::transmit(int portnum
)
channel_regs
*xmit_regs
= &(msp
->scc
.port
[portnum
]);
/* transmit the data character */
term_write(&xmit_regs
->wr
[8],portHandle
[portnum
]);
//(msp == console_msp) ? portnum:portnum+2);
xmit_regs
->rr
[0] |= TX_READY
;
xmit_regs
->rr
[1] |= ALL_SENT
;
if (TX_INT_ENABLED(msp
, portnum
)) {
channel_regs
*channelA
= &(msp
->scc
.port
[PORT_A
]);
channel_regs
*channelB
= &(msp
->scc
.port
[PORT_B
]);
* if generating interrupts in Vector Include Status mode,
* the interrupt vector on channelB includes status in the
* lower nibble which is characteristic of the event
if (xmit_regs
->wr
[9] & VIS
) {
* if no interrupts are pending, clear out
* vector include status bits before or'ing in new state
* (channelA.rr3) is the interrupt pending register
* (channelB.rr2) is the interrupt vector (VIS) register
/* clearing idle "special rcv condition" bits */
channelB
->rr
[2] &= ~VIS_LO_MASK
;
/* add "Tx ready" to status vector */
SET_VIS_TX_BUF_EMPTY(msp
, portnum
);
/* assert serial interrupt */
SET_TX_INT_PENDING(msp
, portnum
);
//dev_interrupt_in(TRUE, device, 0);
pciMaster_set_int(0,true);
* Channel Command Register (WR0)
* - executes various channel/port commands
Serial::channel_cmd(int portnum
)
channel_regs
*channelp
= &(msp
->scc
.port
[portnum
]);
channel_regs
*channelA
= &(msp
->scc
.port
[PORT_A
]);
channel_regs
*channelB
= &(msp
->scc
.port
[PORT_B
]);
} else if (portnum
== PORT_B
) {
* individual reset functions used by
* reset external/status interrupts (0x10)
* reset TxINT pending (0x28)
* reset highest Interrupt Under Service (0x38)
switch (channelp
->wr
[0] & CMDCODE_MASK
) {
* enable int on next Rx, used for 1st receive interrupt
* reset external/status interrupts (0x10)
* (channelB RR2) is the interrupt vector (VIS) register
* (channelA RR3) is the interrupt pending register
/* reset ext/status interrrupt */
CLEAR_XS_INT_PENDING(msp
, portnum
);
* if no XS interrupts pending for other channel,
* clear Ext/Status Vector Include Status bit
if (!XS_INT_PENDING(msp
, otherport
))
CLEAR_VIS_XS_CHANGE(msp
);
* Reset TxINT Pending (0x28)
* (channelB RR2) is the interrupt vector (VIS) register
* (channelA RR3) is the interrupt pending register
/* reset transmit interrrupt */
CLEAR_TX_INT_PENDING(msp
, portnum
);
* Reset Highest Interrupt Under Service command (0x38),
* clear respective bits in interrupt vector (RR2),
* and the interrupt pending register (RR3).
* Note, only channelB contains VIS bits, and
* only channelA contains the IP bits.
/* clear the highest pending interrupt */
if (RCV_INT_PENDING(msp
, portnum
)) {
CLEAR_RCV_INT_PENDING(msp
, portnum
);
* if no Rx interrupts pending for other channel,
* clear Rx Vector Include Status bit
if (!RCV_INT_PENDING(msp
, otherport
))
CLEAR_VIS_RX_CHAR_AVAIL(msp
);
} else if (TX_INT_PENDING(msp
, portnum
)) {
CLEAR_TX_INT_PENDING(msp
, portnum
);
* no VIS bits to clear for Tx interrupts
} else if (XS_INT_PENDING(msp
, portnum
)) {
CLEAR_XS_INT_PENDING(msp
, portnum
);
* if no XS interrupts pending for other channel,
* clear Ext/Status Vector Include Status bit
if (!XS_INT_PENDING(msp
, otherport
))
CLEAR_VIS_XS_CHANGE(msp
);
debug_err("%s: blaze warning: unsupported SCC/wr[0] command 0x%x\n", HERE
,
* if no more interrupts pending for channelA,
* clear the Vector Include Status channel bit
if (!(channelA
->rr
[3] & A_IP_MASK
))
channelB
->rr
[2] &= ~CHAN_A
;
* if no interrupts are pending in RR3, de-assert the serial interrupt.
* if WR9 (master interrupt contorl) is in VIS mode,
* force the VIS bits to be: V3,V2,V1 = 011 "special receive condition"
* (...we currently *ONLY* support status low bits)
* (channelB RR2) is the interrupt vector (VIS) register
* (channelA RR3) is the interrupt pending register
if (channelA
->rr
[3] == 0) {
if (channelp
->wr
[9] & VIS
)
channelB
->rr
[2] = SRC
; /* special receive condition */
/* Deasserting the interrupt */
//dev_interrupt_in(FALSE,device, 0);
pciMaster_set_int(0,false);
* After a reset request has been performed,
* clear the WR0 (command register)
* (including special resets, point high, and register indexes, ...)
Serial::status(int portnum
)
channel_regs
*channelp
= &(msp
->scc
.port
[portnum
]);
/* if External/Status Master Interrupt Enable'd */
if (channelp
->wr
[1] & XS_INT_ENABLED
) {
channel_regs
*channelA
= &(msp
->scc
.port
[PORT_A
]);
channel_regs
*channelB
= &(msp
->scc
.port
[PORT_B
]);
* if we're generating interrupts in Vector Include Status
* mode, the interrupt vector on channelB includes status
* in the lower nibble which is characteristic of the
* event causing the interrupt.
if (channelp
->wr
[9] & VIS
) {
* if no interrupts are pending, clear out vector
* include status bits before or'ing in new state
* (channelA.rr3) is the interrupt pending register
* (channelB.rr2) is the interrupt vector (VIS) register
/* clearing idle "special rcv condition" bits */
channelB
->rr
[2] &= ~VIS_LO_MASK
;
/* add "external/status change" to status vector */
SET_VIS_XS_CHANGE(msp
, portnum
);
/* assert "external/status change" in rr3 */
SET_XS_INT_PENDING(msp
, portnum
);
/* assert serial interrupt */
//dev_interrupt_in(TRUE, device, 0);
pciMaster_set_int(0,true);
// Insert the following string into the incomming stream for PORTA, the
Serial::chars_send(char *str
, int portHandle
)
int len
= (int)strlen(str
);
int portnum
= mapHandle2Port(portHandle
);
for (i
= 0; i
< len
; i
++) {
/* Jam them one at a time into the input buffer */
char_input(str
[i
], portnum
);
struct channel_regs
*rcv_regs
;
debug_err("%s: dump\n", HERE
);
if (fwrite((char*)msp
, sizeof(serial_structT
), 1, fp
) != 1) {
debug_err("%s: serial_dump: fwrite failed\n", HERE
);
/* Dump queue of chars waiting on each port. */
for (portnum
= 0 ; portnum
< NUM_PORTS
; portnum
++) {
rcv_regs
= &(msp
->scc
.port
[portnum
]);
return genericPciDev::dump(DR_get_dir(),getName());
Serial::restore(FILE* fp
)
struct channel_regs
*rcv_regs
;
if (fread((char*)msp
, sizeof(serial_structT
), 1, fp
) != 1) {
debug_err("%s: serial_restore: fread failed\n", HERE
);
/* Restore queue of chars waiting on each port. */
for (portnum
= 0 ; portnum
< NUM_PORTS
; portnum
++) {
rcv_regs
= &(msp
->scc
.port
[portnum
]);
* # chars to load is in qcnt of state. Load them in but
* first set queue to empty state since the characters
* really aren't there yet.
count
= rcv_regs
->qcnt
; /* save count */
return genericPciDev::restore(DR_get_dir(),getName());