| /* $Id: z8530.c,v 1.20 2010/06/05 16:07:17 fredette Exp $ */ |
| |
| /* ic/z8530.c - implementation of Zilog 8530 emulation: */ |
| |
| /* |
| * Copyright (c) 2003 Matt Fredette |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 3. All advertising materials mentioning features or use of this software |
| * must display the following acknowledgement: |
| * This product includes software developed by Matt Fredette. |
| * 4. The name of the author may not be used to endorse or promote products |
| * derived from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
| * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, |
| * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
| * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
| * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
| * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| * POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include <tme/common.h> |
| _TME_RCSID("$Id: z8530.c,v 1.20 2010/06/05 16:07:17 fredette Exp $"); |
| |
| /* includes: */ |
| #include <tme/generic/bus-device.h> |
| #include <tme/generic/serial.h> |
| #include <tme/ic/z8530.h> |
| #include "z8530reg.h" |
| |
| /* macros: */ |
| |
| /* buffer sizes: */ |
| #define TME_Z8530_BUFFER_SIZE_TX (16) |
| #define TME_Z8530_BUFFER_SIZE_RX (128) |
| |
| /* channel callout flags: */ |
| #define TME_Z8530_CALLOUT_CHECK (0) |
| #define TME_Z8530_CALLOUT_RUNNING TME_BIT(0) |
| #define TME_Z8530_CALLOUTS_MASK (-2) |
| #define TME_Z8530_CALLOUT_CTRL TME_BIT(1) |
| #define TME_Z8530_CALLOUT_CONFIG TME_BIT(2) |
| #define TME_Z8530_CALLOUT_READ TME_BIT(3) |
| #define TME_Z8530_CALLOUT_INT TME_BIT(4) |
| |
| /* interrupt types. note that the nonnegative values are chosen to |
| correspond to the interrupt vector modification values for the |
| given interrupt. the nonnegative values correspond to the |
| "status high/status low = 0" (TME_Z8530_WR9_INTVEC_STATUS = 0) |
| case given in Table 5-6 in my SCC manual: */ |
| #define TME_Z8530_INT_UNDEF (-1) |
| #define TME_Z8530_INT_CHAN_TX (0) |
| #define TME_Z8530_INT_CHAN_STATUS (1) |
| #define TME_Z8530_INT_CHAN_RX (2) |
| #define TME_Z8530_INT_CHAN_RX_SPCL (3) |
| #define TME_Z8530_INT_CHAN_A (4) |
| #define TME_Z8530_INT_CHAN_B (0) |
| |
| #define TME_Z8530_LOG_HANDLE(z) (&(z)->tme_z8530_element->tme_element_log_handle) |
| |
| /* structures: */ |
| |
| /* one channel: */ |
| struct tme_z8530_chan { |
| |
| /* the 16 write registers: */ |
| tme_uint8_t tme_z8530_chan_wrreg[16]; |
| |
| /* the 16 read registers: */ |
| tme_uint8_t tme_z8530_chan_rdreg[16]; |
| |
| /* the unlatched RR0 status bits and diff mask: */ |
| tme_uint8_t tme_z8530_chan_rr0_status_raw; |
| tme_uint8_t tme_z8530_chan_rr0_status_diff_mask; |
| |
| /* the serial connected to this channel: */ |
| struct tme_serial_connection *tme_z8530_chan_connection; |
| |
| /* our transmitter buffer: */ |
| struct tme_serial_buffer tme_z8530_chan_buffer_tx; |
| |
| /* our receiver buffer: */ |
| struct tme_serial_buffer tme_z8530_chan_buffer_rx; |
| |
| /* the flags on this channel: */ |
| unsigned int tme_z8530_chan_flags; |
| |
| /* the callout flags on this channel: */ |
| int tme_z8530_chan_callout_flags; |
| |
| #ifndef TME_NO_LOG |
| tme_uint8_t tme_z8530_chan_last_read_reg; |
| tme_uint8_t tme_z8530_chan_last_read_value; |
| #endif /* !TME_NO_LOG */ |
| }; |
| |
| /* a channel connection: */ |
| struct tme_z8530_connection { |
| |
| /* the generic serial connection: */ |
| struct tme_serial_connection tme_z8530_connection; |
| |
| /* the channel this connection is to: */ |
| struct tme_z8530_chan *tme_z8530_connection_chan; |
| }; |
| |
| /* the whole chip: */ |
| struct tme_z8530 { |
| |
| /* our simple bus device header: */ |
| struct tme_bus_device tme_z8530_device; |
| #define tme_z8530_element tme_z8530_device.tme_bus_device_element |
| |
| /* our socket: */ |
| struct tme_z8530_socket tme_z8530_socket; |
| #define tme_z8530_address_chan_a tme_z8530_socket.tme_z8530_socket_address_chan_a |
| #define tme_z8530_address_chan_b tme_z8530_socket.tme_z8530_socket_address_chan_b |
| #define tme_z8530_offset_csr tme_z8530_socket.tme_z8530_socket_offset_csr |
| #define tme_z8530_offset_data tme_z8530_socket.tme_z8530_socket_offset_data |
| #define tme_z8530_port_least_lane tme_z8530_socket.tme_z8530_socket_port_least_lane |
| #define tme_z8530_pclk tme_z8530_socket.tme_z8530_socket_pclk |
| |
| /* the mutex protecting the chip: */ |
| tme_mutex_t tme_z8530_mutex; |
| |
| /* the two channels, and some common shared registers: */ |
| struct tme_z8530_chan tme_z8530_chan_a; |
| struct tme_z8530_chan tme_z8530_chan_b; |
| #define tme_z8530_wr9 tme_z8530_chan_a.tme_z8530_chan_wrreg[9] |
| #define tme_z8530_rr3 tme_z8530_chan_a.tme_z8530_chan_rdreg[3] |
| |
| /* the IUS bits, which correspond exactly to bits in RR3: */ |
| tme_uint8_t tme_z8530_ius; |
| |
| /* the register pointer: */ |
| tme_uint8_t tme_z8530_register_pointer; |
| |
| /* if our interrupt line is currently asserted: */ |
| int tme_z8530_int_asserted; |
| }; |
| |
| /* the z8530 bus router: */ |
| static const tme_bus_lane_t tme_z8530_router[TME_BUS_ROUTER_SIZE(TME_BUS8_LOG2)] = { |
| |
| /* [gen] initiator port size: 8 bits |
| [gen] initiator port least lane: 0: */ |
| /* D7-D0 */ TME_BUS_LANE_ROUTE(0), |
| }; |
| |
| /* this resets one channel of a z8530: */ |
| static void |
| _tme_z8530_channel_reset(struct tme_z8530 *z8530, |
| struct tme_z8530_chan *chan, |
| int hardware_reset) |
| { |
| |
| /* these reset values are taken from the SCC User's Manual: */ |
| chan->tme_z8530_chan_wrreg[0] = 0x00; |
| chan->tme_z8530_chan_wrreg[1] = 0x00; |
| chan->tme_z8530_chan_wrreg[2] = 0x00; |
| chan->tme_z8530_chan_wrreg[3] = 0x00; |
| chan->tme_z8530_chan_wrreg[4] = 0x04; |
| chan->tme_z8530_chan_wrreg[5] = 0x00; |
| chan->tme_z8530_chan_wrreg[6] = 0x00; |
| chan->tme_z8530_chan_wrreg[7] = 0x00; |
| chan->tme_z8530_chan_wrreg[8] = 0x00; |
| z8530->tme_z8530_wr9 = (hardware_reset ? 0xc0 : 0x00); |
| chan->tme_z8530_chan_wrreg[10] = 0x00; |
| chan->tme_z8530_chan_wrreg[11] = 0x08; |
| chan->tme_z8530_chan_wrreg[12] = 0x00; |
| chan->tme_z8530_chan_wrreg[13] = 0x00; |
| chan->tme_z8530_chan_wrreg[14] = (hardware_reset ? 0x30 : 0x20); |
| chan->tme_z8530_chan_wrreg[15] = 0xf8; |
| chan->tme_z8530_chan_rdreg[0] = 0x44; |
| chan->tme_z8530_chan_rdreg[1] = 0x07; |
| z8530->tme_z8530_rr3 = 0x00; |
| chan->tme_z8530_chan_rdreg[10] = 0x00; |
| |
| /* the raw (unlatched) RR0 status bits: */ |
| chan->tme_z8530_chan_rr0_status_raw = 0; |
| chan->tme_z8530_chan_rr0_status_diff_mask = 0; |
| |
| /* if this is a hardware reset: */ |
| if (hardware_reset) { |
| |
| /* clear the IUS bits: */ |
| z8530->tme_z8530_ius = 0; |
| |
| /* set the modified interrupt vector stored in RR2 of channel B: */ |
| z8530->tme_z8530_chan_b.tme_z8530_chan_rdreg[2] = (TME_Z8530_INT_CHAN_RX_SPCL << 1); |
| } |
| } |
| |
| /* this initializes one channel of a z8530: */ |
| static void |
| _tme_z8530_channel_init(struct tme_z8530 *z8530, |
| struct tme_z8530_chan *chan) |
| { |
| |
| /* allocate the Tx and Rx FIFOs: */ |
| tme_serial_buffer_init(&chan->tme_z8530_chan_buffer_tx, |
| TME_Z8530_BUFFER_SIZE_TX); |
| tme_serial_buffer_init(&chan->tme_z8530_chan_buffer_rx, |
| TME_Z8530_BUFFER_SIZE_RX); |
| |
| /* reset the channel: */ |
| _tme_z8530_channel_reset(z8530, chan, TRUE); |
| } |
| |
| /* this returns TRUE iff the data at the top of the Rx FIFO has a |
| Special Condition. Special Conditions are: receiver overrun, |
| framing error, end of frame, or, (if selected) parity error: */ |
| static int |
| _tme_z8530_rx_fifo_special(struct tme_z8530_chan *chan) |
| { |
| return ((chan->tme_z8530_chan_rdreg[0] & TME_Z8530_RR0_RX_AVAIL) |
| && (chan->tme_z8530_chan_rdreg[1] |
| & (TME_Z8530_RR1_END_OF_FRAME |
| | TME_Z8530_RR1_ERROR_FRAME |
| | TME_Z8530_RR1_ERROR_RX_OVERRUN |
| | ((chan->tme_z8530_chan_wrreg[1] |
| & TME_Z8530_WR1_PARITY_SPCL) |
| ? TME_Z8530_RR1_ERROR_PARITY |
| : 0)))); |
| } |
| |
| /* iff the data at the top of the Rx FIFO has a Special Condition |
| exists, and the receive interrupt mode is either |
| TME_Z8530_WR1_RX_INT_1ST_SPCL or TME_Z8530_WR1_RX_INT_SPCL, the |
| data is held in the receive FIFO until an Error Reset command is |
| issued: */ |
| static int |
| _tme_z8530_rx_fifo_hold(struct tme_z8530_chan *chan) |
| { |
| tme_uint8_t wr1; |
| |
| /* if a special condition exists and needs to be held in the |
| receive FIFO: */ |
| wr1 = chan->tme_z8530_chan_wrreg[1]; |
| return (_tme_z8530_rx_fifo_special(chan) |
| && (((wr1 & TME_Z8530_WR1_RX_INT_MASK) |
| == TME_Z8530_WR1_RX_INT_1ST_SPCL) |
| || ((wr1 & TME_Z8530_WR1_RX_INT_MASK) |
| == TME_Z8530_WR1_RX_INT_SPCL))); |
| } |
| |
| /* this attempts to refill the receive FIFO: */ |
| static int |
| _tme_z8530_rx_fifo_refill(struct tme_z8530 *z8530, |
| struct tme_z8530_chan *chan) |
| { |
| tme_uint8_t byte_buffer, byte; |
| tme_serial_data_flags_t data_flags_buffer, data_flags; |
| tme_uint8_t rr1, rr3_ip_rx; |
| int ip_rx_set; |
| int new_callouts; |
| unsigned int rc; |
| |
| /* get the IP_RX bit for this channel: */ |
| rr3_ip_rx = ((chan == &z8530->tme_z8530_chan_a) |
| ? TME_Z8530_RR3_CHAN_A_IP_RX |
| : TME_Z8530_RR3_CHAN_B_IP_RX); |
| |
| /* if the data at the top of the Rx FIFO needs to be held, |
| there's nothing we can do: */ |
| if (_tme_z8530_rx_fifo_hold(chan)) { |
| return (0); |
| } |
| |
| /* assume we won't need any new callouts, and that the receive |
| Interrupt Pending bit for this channel is not supposed to be set: */ |
| new_callouts = 0; |
| ip_rx_set = FALSE; |
| |
| /* if the Rx buffer is currently full, after we copy out the |
| next character we want to call out to read more data: */ |
| if (tme_serial_buffer_is_full(&chan->tme_z8530_chan_buffer_rx)) { |
| new_callouts |= TME_Z8530_CALLOUT_READ; |
| } |
| |
| /* get the next byte from our Rx buffer: */ |
| rc = tme_serial_buffer_copyout(&chan->tme_z8530_chan_buffer_rx, |
| &byte_buffer, 1, |
| &data_flags_buffer, |
| TME_SERIAL_COPY_NORMAL); |
| |
| /* if the Rx buffer was empty, the Rx FIFO is now empty: */ |
| if (rc == 0) { |
| chan->tme_z8530_chan_rdreg[0] &= ~TME_Z8530_RR0_RX_AVAIL; |
| } |
| |
| /* otherwise we have another byte for the Rx FIFO: */ |
| else { |
| byte = byte_buffer; |
| data_flags = data_flags_buffer; |
| |
| /* put the byte into RR8: */ |
| chan->tme_z8530_chan_rdreg[8] = byte; |
| |
| /* update RR1: */ |
| rr1 = chan->tme_z8530_chan_rdreg[1]; |
| if (data_flags & TME_SERIAL_DATA_BAD_FRAME) { |
| rr1 |= TME_Z8530_RR1_ERROR_FRAME; |
| } |
| else { |
| rr1 &= ~TME_Z8530_RR1_ERROR_FRAME; |
| } |
| if (data_flags & TME_SERIAL_DATA_OVERRUN) { |
| rr1 |= TME_Z8530_RR1_ERROR_RX_OVERRUN; |
| } |
| if (data_flags & TME_SERIAL_DATA_BAD_PARITY) { |
| rr1 |= TME_Z8530_RR1_ERROR_PARITY; |
| } |
| chan->tme_z8530_chan_rdreg[1] = rr1; |
| |
| /* see if the receive Interrupt Pending bit is supposed to be set: */ |
| switch (chan->tme_z8530_chan_wrreg[1] & TME_Z8530_WR1_RX_INT_MASK) { |
| case TME_Z8530_WR1_RX_INT_DISABLE: |
| break; |
| case TME_Z8530_WR1_RX_INT_1ST_SPCL: |
| ip_rx_set = (!(chan->tme_z8530_chan_rdreg[0] & TME_Z8530_RR0_RX_AVAIL) |
| || _tme_z8530_rx_fifo_special(chan)); |
| break; |
| case TME_Z8530_WR1_RX_INT_ALL_SPCL: |
| ip_rx_set = TRUE; |
| break; |
| case TME_Z8530_WR1_RX_INT_SPCL: |
| ip_rx_set = _tme_z8530_rx_fifo_special(chan); |
| break; |
| } |
| |
| /* there is now a receive character available: */ |
| chan->tme_z8530_chan_rdreg[0] |= TME_Z8530_RR0_RX_AVAIL; |
| } |
| |
| /* if the receive Interrupt Pending bit for this channel doesn't |
| have the proper value, update it and callout an int: */ |
| if (!ip_rx_set != !(z8530->tme_z8530_rr3 |
| & rr3_ip_rx)) { |
| z8530->tme_z8530_rr3 ^= rr3_ip_rx; |
| new_callouts |= TME_Z8530_CALLOUT_INT; |
| } |
| |
| /* done: */ |
| return (new_callouts); |
| } |
| |
| /* if an External/Status interrupt is not pending on this channel, |
| this updates the status bits in RR0 with the current raw status, |
| and requests an External/Status interrupt if one is needed: */ |
| static int |
| _tme_z8530_rr0_update(struct tme_z8530 *z8530, |
| struct tme_z8530_chan *chan) |
| { |
| tme_uint8_t rr0_status_new; |
| tme_uint8_t rr0_status_diff_mask; |
| tme_uint8_t wr15; |
| tme_uint8_t rr3_ip_status; |
| int need_ip_status; |
| |
| /* get the IP_STATUS bit for this channel: */ |
| rr3_ip_status = ((chan == &z8530->tme_z8530_chan_a) |
| ? TME_Z8530_RR3_CHAN_A_IP_STATUS |
| : TME_Z8530_RR3_CHAN_B_IP_STATUS); |
| |
| /* if an External/Status interrupt is already pending on this |
| channel, return, and we need no new callouts: */ |
| if (z8530->tme_z8530_rr3 & rr3_ip_status) { |
| return (0); |
| } |
| |
| /* assume no IP_STATUS needed: */ |
| need_ip_status = FALSE; |
| |
| /* get the raw status and its interrupt mask: */ |
| rr0_status_new = chan->tme_z8530_chan_rr0_status_raw; |
| rr0_status_diff_mask = chan->tme_z8530_chan_rr0_status_diff_mask; |
| |
| /* if interrupts are enabled: */ |
| if (chan->tme_z8530_chan_wrreg[1] & TME_Z8530_WR1_STATUS_INT_ENABLE) { |
| wr15 = chan->tme_z8530_chan_wrreg[15]; |
| |
| /* if CTS interrupts are enabled, and CTS has changed, we need a |
| status interrupt. similarly for DCD: */ |
| if (((wr15 & TME_Z8530_WR15_CTS_IE) |
| && (rr0_status_diff_mask & TME_Z8530_RR0_CTS)) |
| || ((wr15 & TME_Z8530_WR15_DCD_IE) |
| && (rr0_status_diff_mask & TME_Z8530_RR0_DCD))) { |
| need_ip_status = TRUE; |
| } |
| |
| /* if break interrupts are enabled, and one or more break |
| changes have happened: */ |
| if ((wr15 & TME_Z8530_WR15_BREAK_IE) |
| && (rr0_status_diff_mask & TME_Z8530_RR0_BREAK)) { |
| |
| /* we need a status interrupt: */ |
| need_ip_status = TRUE; |
| |
| /* make sure we interrupt for multiple transitions if multiple |
| transitions have happened. i.e., if both the latched RR0 and |
| the raw status have the same value for BREAK, at least two |
| transitions happened "in between", so interrupt for the other |
| value first, and leave break set in the diff mask so another |
| interrupt for the actual current value will happen later: */ |
| if (((rr0_status_new |
| ^ chan->tme_z8530_chan_rdreg[0]) |
| & TME_Z8530_RR0_BREAK) == 0) { |
| rr0_status_new ^= TME_Z8530_RR0_BREAK; |
| } |
| } |
| } |
| |
| /* update RR0: */ |
| chan->tme_z8530_chan_rdreg[0] = |
| ((chan->tme_z8530_chan_rdreg[0] |
| & ~rr0_status_diff_mask) |
| | (rr0_status_new |
| & rr0_status_diff_mask)); |
| |
| /* update the status diff mask: */ |
| chan->tme_z8530_chan_rr0_status_diff_mask = |
| (rr0_status_new |
| ^ chan->tme_z8530_chan_rr0_status_raw); |
| |
| /* if this channel needs an External/Status interrupt: */ |
| if (need_ip_status) { |
| z8530->tme_z8530_rr3 |= rr3_ip_status; |
| return (TME_Z8530_CALLOUT_INT); |
| } |
| |
| /* we don't need any callouts: */ |
| return (0); |
| } |
| |
| /* this updates the modified interrupt vector stored in RR2 of |
| channel B: */ |
| static int |
| _tme_z8530_rr2_update(struct tme_z8530 *z8530, |
| tme_uint8_t rr3_ip) |
| { |
| struct tme_z8530_chan *chan; |
| int int_type; |
| int vector; |
| |
| /* there can be at most one RR3 IP bit: */ |
| assert ((rr3_ip & (rr3_ip - 1)) == 0); |
| |
| /* get which channel this IP bit is for, and make sure the IP bit is |
| shifted down to a TME_Z8530_RR3_CHAN_B_IP_ value: */ |
| if (rr3_ip <= TME_Z8530_RR3_CHAN_B_IP_RX) { |
| chan = &z8530->tme_z8530_chan_b; |
| rr3_ip /= TME_Z8530_RR3_CHAN_B_IP_STATUS; |
| int_type = TME_Z8530_INT_CHAN_B; |
| } |
| else { |
| chan = &z8530->tme_z8530_chan_a; |
| rr3_ip /= TME_Z8530_RR3_CHAN_A_IP_STATUS; |
| int_type = TME_Z8530_INT_CHAN_A; |
| } |
| |
| /* map that bit into an interrupt type: */ |
| switch (rr3_ip) { |
| case TME_Z8530_RR3_CHAN_B_IP_RX: |
| int_type |
| |= (_tme_z8530_rx_fifo_special(chan) |
| ? TME_Z8530_INT_CHAN_RX_SPCL |
| : TME_Z8530_INT_CHAN_RX); |
| break; |
| case TME_Z8530_RR3_CHAN_B_IP_TX: |
| int_type |
| |= TME_Z8530_INT_CHAN_TX; |
| break; |
| case TME_Z8530_RR3_CHAN_B_IP_STATUS: |
| int_type |
| |= TME_Z8530_INT_CHAN_STATUS; |
| break; |
| default: |
| /* "If no interrupts are pending, the status is V3,V2,V1 -011, or |
| V6,V5,V4-110." */ |
| int_type = TME_Z8530_INT_CHAN_RX_SPCL; |
| break; |
| } |
| |
| /* come up with the modified interrupt vector. iff |
| TME_Z8530_WR9_INTVEC_STATUS, bits 4, 5, and 6 respectively take |
| bits 2, 1, and 0 of the interrupt type, otherwise bits 1, 2, and |
| 3 respectively take bits 0, 1, and 2 of the interrupt type. this |
| strangness is found in Table 5-6 of my SCC manual: */ |
| vector = z8530->tme_z8530_chan_a.tme_z8530_chan_wrreg[2]; |
| if (z8530->tme_z8530_wr9 & TME_Z8530_WR9_INTVEC_STATUS) { |
| vector |
| = ((vector & 0x8f) |
| | ((vector << 6) & 0x40) |
| | ((vector << 4) & 0x20) |
| | ((vector << 3) & 0x10)); |
| } |
| else { |
| vector = (vector & 0xf1) | (int_type << 1); |
| } |
| |
| /* save this modified interrupt vector in the channel B RR2: */ |
| z8530->tme_z8530_chan_b.tme_z8530_chan_rdreg[2] = vector; |
| |
| /* return the vector: */ |
| return (vector); |
| } |
| |
| /* this updates RR2, and returns any pending interrupt with higher |
| priority than the highest-priority interrupt currently under |
| service, or zero if there is no such interrupt (or if interrupts |
| are disabled): */ |
| static int |
| _tme_z8530_int_pending(struct tme_z8530 *z8530) |
| { |
| tme_uint8_t rr3; |
| |
| /* get the highest priority IP bit: */ |
| rr3 = z8530->tme_z8530_rr3; |
| for (; (rr3 & (rr3 - 1)) != 0; ) { |
| rr3 &= (rr3 - 1); |
| } |
| assert(rr3 <= TME_Z8530_RR3_CHAN_A_IP_RX); |
| |
| /* if the highest priority IP bit is currently under service: */ |
| if (rr3 <= z8530->tme_z8530_ius) { |
| |
| /* there is no interrupt pending: */ |
| rr3 = 0; |
| } |
| |
| /* if interrupts are disabled: */ |
| if ((z8530->tme_z8530_wr9 & TME_Z8530_WR9_MIE) == 0) { |
| |
| /* there is no interrupt pending: */ |
| rr3 = 0; |
| } |
| |
| /* update RR2: */ |
| _tme_z8530_rr2_update(z8530, |
| rr3); |
| |
| return (rr3); |
| } |
| |
| /* this does an interrupt acknowledge. it returns TME_OK iff an |
| interrupt was acknowledged, else it returns some error. iff |
| _vector is non-NULL this is a hardware interrupt acknowledge: */ |
| static int |
| _tme_z8530_intack(struct tme_z8530 *z8530, int *_vector) |
| { |
| tme_uint8_t rr3, wr9; |
| int vector; |
| |
| /* if there is no pending interrupt with a higher priority than the |
| highest-priority interrupt currently under service, or if the |
| Master Interrupt Enable isn't set, the interrupt signal should |
| not have been asserted: */ |
| rr3 = _tme_z8530_int_pending(z8530); |
| if (rr3 == 0) { |
| return (ENOENT); |
| } |
| |
| /* if this is a hard interrupt acknowledge: */ |
| if (_vector != NULL) { |
| |
| /* if the chip's IEI pin is tied low: */ |
| if (z8530->tme_z8530_socket.tme_z8530_socket_flags |
| & TME_Z8530_SOCKET_FLAG_IEI_TIED_LOW) { |
| |
| /* when IEI is low, we return without error, but we also don't |
| recognize the hard interrupt acknowledge cycle as selecting us |
| and we don't drive any vector: */ |
| *_vector = TME_BUS_INTERRUPT_VECTOR_UNDEF; |
| return (TME_OK); |
| } |
| |
| /* otherwise, we assume that the IEI pin is currently high, which |
| means we always recognize the hard interrupt acknowledge cycle |
| as selecting us. this isn't a problem, even if there are |
| multiple devices that can interrupt at our level, because we're |
| not really driving a bus here, we're responding to a directed |
| request for an interrupt vector. we assume that if there |
| really is an important priority system within our interrupt |
| level, that it's enforced by the bus implementation: */ |
| } |
| |
| /* set the corresponding IUS bit: */ |
| z8530->tme_z8530_ius |= rr3; |
| |
| /* get the vector from the updated RR2 in channel B: */ |
| vector = z8530->tme_z8530_chan_b.tme_z8530_chan_rdreg[2]; |
| |
| /* get the current WR9 value: */ |
| wr9 = z8530->tme_z8530_wr9; |
| |
| /* if this is a hard interrupt acknowledge: */ |
| if (_vector != NULL) { |
| |
| /* if we're not supposed to acknowledge hard interrupts with any |
| vector, return no vector: */ |
| if (wr9 & TME_Z8530_WR9_NV) { |
| *_vector = TME_BUS_INTERRUPT_VECTOR_UNDEF; |
| } |
| |
| /* if we're supposed to acknowledge hard interrupts with the |
| modified vector, return the modified vector: */ |
| else if (wr9 & TME_Z8530_WR9_VIS) { |
| *_vector = vector; |
| } |
| |
| /* otherwise, return the plain interrupt vector: */ |
| else { |
| *_vector = z8530->tme_z8530_chan_a.tme_z8530_chan_wrreg[2]; |
| } |
| } |
| |
| /* done: */ |
| return (TME_OK); |
| } |
| |
| /* the z8530 callout function. it must be called with the mutex locked: */ |
| static void |
| _tme_z8530_callout(struct tme_z8530 *z8530, struct tme_z8530_chan *chan, int new_callouts) |
| { |
| struct tme_serial_connection *conn_serial; |
| struct tme_bus_connection *conn_bus; |
| unsigned int ctrl; |
| int int_asserted; |
| tme_uint8_t buffer_input[32]; |
| unsigned int buffer_input_size; |
| tme_serial_data_flags_t data_flags; |
| struct tme_serial_config config; |
| unsigned int chan_flags; |
| int callouts; |
| int later_callouts_a, later_callouts_b, *_later_callouts; |
| int rc; |
| |
| /* add in any new callouts: */ |
| chan->tme_z8530_chan_callout_flags |= new_callouts; |
| |
| /* if this function is already running in another thread, return |
| now. the other thread will do our work: */ |
| if ((z8530->tme_z8530_chan_a.tme_z8530_chan_callout_flags |
| | z8530->tme_z8530_chan_b.tme_z8530_chan_callout_flags) |
| & TME_Z8530_CALLOUT_RUNNING) { |
| return; |
| } |
| |
| /* callouts are now running: */ |
| z8530->tme_z8530_chan_a.tme_z8530_chan_callout_flags |= TME_Z8530_CALLOUT_RUNNING; |
| z8530->tme_z8530_chan_b.tme_z8530_chan_callout_flags |= TME_Z8530_CALLOUT_RUNNING; |
| |
| /* assume that we won't need any later callouts: */ |
| later_callouts_a = later_callouts_b = 0; |
| |
| /* we assume we will not change our interrupt vector: */ |
| int_asserted = -1; |
| |
| /* loop while callouts are needed: */ |
| for (;;) { |
| |
| /* stop only when both channels need no callouts: */ |
| if ((callouts |
| = z8530->tme_z8530_chan_a.tme_z8530_chan_callout_flags) |
| & TME_Z8530_CALLOUTS_MASK) { |
| chan = &z8530->tme_z8530_chan_a; |
| _later_callouts = &later_callouts_a; |
| } |
| else if ((callouts |
| = z8530->tme_z8530_chan_b.tme_z8530_chan_callout_flags) |
| & TME_Z8530_CALLOUTS_MASK) { |
| chan = &z8530->tme_z8530_chan_b; |
| _later_callouts = &later_callouts_b; |
| } |
| else { |
| break; |
| } |
| |
| /* clear the needed callouts: */ |
| chan->tme_z8530_chan_callout_flags = callouts & ~TME_Z8530_CALLOUTS_MASK; |
| |
| /* get this channel's connection: */ |
| conn_serial = chan->tme_z8530_chan_connection; |
| |
| /* get this channel's flags: */ |
| chan_flags = chan->tme_z8530_chan_flags; |
| |
| /* if we need to call out new control information: */ |
| if (callouts & TME_Z8530_CALLOUT_CTRL) { |
| |
| /* form the new ctrl: */ |
| ctrl = 0; |
| if (chan->tme_z8530_chan_wrreg[5] & TME_Z8530_WR5_DTR) { |
| ctrl |= TME_SERIAL_CTRL_DTR; |
| } |
| if (chan->tme_z8530_chan_wrreg[5] & TME_Z8530_WR5_RTS) { |
| ctrl |= TME_SERIAL_CTRL_RTS; |
| } |
| if (chan->tme_z8530_chan_wrreg[5] & TME_Z8530_WR5_BREAK) { |
| ctrl |= TME_SERIAL_CTRL_BREAK; |
| } |
| if (!tme_serial_buffer_is_empty(&chan->tme_z8530_chan_buffer_tx)) { |
| ctrl |= TME_SERIAL_CTRL_OK_READ; |
| } |
| |
| /* unlock the mutex: */ |
| tme_mutex_unlock(&z8530->tme_z8530_mutex); |
| |
| /* do the callout: */ |
| rc = (conn_serial != NULL |
| ? ((*conn_serial->tme_serial_connection_ctrl) |
| (conn_serial, |
| ctrl)) |
| : TME_OK); |
| |
| /* lock the mutex: */ |
| tme_mutex_lock(&z8530->tme_z8530_mutex); |
| |
| /* if the callout was unsuccessful, remember that at some |
| later time this callout should be attempted again: */ |
| if (rc != TME_OK) { |
| *_later_callouts |= TME_Z8530_CALLOUT_CTRL; |
| } |
| } |
| |
| /* if we need to call out new config information: */ |
| if (callouts & TME_Z8530_CALLOUT_CONFIG) { |
| |
| /* form the new config: */ |
| memset(&config, 0, sizeof(config)); |
| |
| /* the number of data bits per character. note that we ignore |
| the transmit character size and only pay attention to the |
| receive character size: */ |
| switch (chan->tme_z8530_chan_wrreg[3] & TME_Z8530_WR3_RX_CSIZE_MASK) { |
| case TME_Z8530_WR3_RX_CSIZE_5: |
| config.tme_serial_config_bits_data = 5; |
| break; |
| case TME_Z8530_WR3_RX_CSIZE_7: |
| config.tme_serial_config_bits_data = 7; |
| break; |
| case TME_Z8530_WR3_RX_CSIZE_6: |
| config.tme_serial_config_bits_data = 6; |
| break; |
| case TME_Z8530_WR3_RX_CSIZE_8: |
| config.tme_serial_config_bits_data = 8; |
| break; |
| } |
| |
| /* the number of stop bits: */ |
| switch (chan->tme_z8530_chan_wrreg[4] & TME_Z8530_WR4_STOP_MASK) { |
| case TME_Z8530_WR4_STOP_SYNC: |
| case TME_Z8530_WR4_STOP_1_5BITS: |
| abort(); |
| case TME_Z8530_WR4_STOP_1BIT: |
| config.tme_serial_config_bits_stop = 1; |
| break; |
| case TME_Z8530_WR4_STOP_2BITS: |
| config.tme_serial_config_bits_stop = 2; |
| break; |
| } |
| |
| /* the parity: */ |
| config.tme_serial_config_parity = |
| ((chan->tme_z8530_chan_wrreg[4] & TME_Z8530_WR4_PARITY_ENABLE) |
| ? (chan->tme_z8530_chan_wrreg[4] & TME_Z8530_WR4_PARITY_EVEN |
| ? TME_SERIAL_PARITY_EVEN |
| : TME_SERIAL_PARITY_ODD) |
| : TME_SERIAL_PARITY_NONE); |
| |
| /* the baud rate: */ |
| /* XXX TBD */ |
| config.tme_serial_config_baud = 9600; |
| |
| /* flags: */ |
| /* XXX TBD: */ |
| config.tme_serial_config_flags = 0; |
| |
| /* unlock the mutex: */ |
| tme_mutex_unlock(&z8530->tme_z8530_mutex); |
| |
| /* do the callout: */ |
| rc = (conn_serial != NULL |
| ? ((*conn_serial->tme_serial_connection_config) |
| (conn_serial, |
| &config)) |
| : TME_OK); |
| |
| /* lock the mutex: */ |
| tme_mutex_lock(&z8530->tme_z8530_mutex); |
| |
| /* if the callout was unsuccessful, remember that at some |
| later time this callout should be attempted again: */ |
| if (rc != TME_OK) { |
| *_later_callouts |= TME_Z8530_CALLOUT_CONFIG; |
| } |
| } |
| |
| /* if this channel's connection is readable: */ |
| if (callouts & TME_Z8530_CALLOUT_READ) { |
| |
| /* if the receive buffer is full, remember that at some later |
| time this callout should be attempted again: */ |
| if (tme_serial_buffer_is_full(&chan->tme_z8530_chan_buffer_rx)) { |
| *_later_callouts |= TME_Z8530_CALLOUT_READ; |
| } |
| |
| /* otherwise, continue to do the read: */ |
| else { |
| |
| /* get the minimum of the free space in the receive buffer and |
| the size of our stack buffer: */ |
| buffer_input_size = tme_serial_buffer_space_free(&chan->tme_z8530_chan_buffer_rx); |
| buffer_input_size = TME_MIN(buffer_input_size, sizeof(buffer_input)); |
| |
| /* unlock the mutex: */ |
| tme_mutex_unlock(&z8530->tme_z8530_mutex); |
| |
| /* do the read: */ |
| rc = (conn_serial != NULL |
| ? ((*conn_serial->tme_serial_connection_read) |
| (conn_serial, |
| buffer_input, |
| buffer_input_size, |
| &data_flags)) |
| : 0); |
| |
| /* lock the mutex: */ |
| tme_mutex_lock(&z8530->tme_z8530_mutex); |
| |
| /* if the read was successful: */ |
| if (rc > 0) { |
| |
| /* put the the characters into our Rx buffer: */ |
| (void) tme_serial_buffer_copyin(&chan->tme_z8530_chan_buffer_rx, |
| buffer_input, |
| rc, |
| data_flags, |
| TME_SERIAL_COPY_NORMAL); |
| |
| /* if the Rx FIFO is empty, refill it: */ |
| if (!(chan->tme_z8530_chan_rdreg[0] & TME_Z8530_RR0_RX_AVAIL)) { |
| chan->tme_z8530_chan_callout_flags |= |
| _tme_z8530_rx_fifo_refill(z8530, chan); |
| } |
| |
| /* mark that we need to loop to callout to read more data: */ |
| chan->tme_z8530_chan_callout_flags |= TME_Z8530_CALLOUT_READ; |
| } |
| |
| /* otherwise, the read failed. convention dictates that we |
| forget that the connection was readable, which we already |
| have done by clearing the CALLOUT_READ flag: */ |
| } |
| } |
| |
| /* if we need to call out a possible change to our interrupt signal: */ |
| if (callouts & TME_Z8530_CALLOUT_INT) { |
| |
| /* if we thought we weren't checking the interrupt signal, we |
| are now. assume that we are not supposed to be asserting an |
| interrupt: */ |
| if (int_asserted == -1) { |
| int_asserted = FALSE; |
| } |
| |
| /* if any pending interrupt has a higher priority than |
| the highest-priority interrupt currently under service, |
| and the Master Interrupt Enable is set, |
| we are supposed to be asserting an interrupt: */ |
| if (_tme_z8530_int_pending(z8530)) { |
| int_asserted = TRUE; |
| } |
| } |
| } |
| |
| /* if we checked the interrupt signal and we need to call out a |
| change: */ |
| if (int_asserted != -1 |
| && !int_asserted != !z8530->tme_z8530_int_asserted) { |
| |
| /* unlock our mutex: */ |
| tme_mutex_unlock(&z8530->tme_z8530_mutex); |
| |
| /* get our bus connection: */ |
| conn_bus = tme_memory_atomic_pointer_read(struct tme_bus_connection *, |
| z8530->tme_z8530_device.tme_bus_device_connection, |
| &z8530->tme_z8530_device.tme_bus_device_connection_rwlock); |
| |
| /* call out the bus interrupt signal edge: */ |
| rc = (*conn_bus->tme_bus_signal) |
| (conn_bus, |
| TME_BUS_SIGNAL_INT_UNSPEC |
| | TME_BUS_SIGNAL_EDGE |
| | (int_asserted |
| ? TME_BUS_SIGNAL_LEVEL_ASSERTED |
| : TME_BUS_SIGNAL_LEVEL_NEGATED)); |
| |
| /* lock our mutex: */ |
| tme_mutex_lock(&z8530->tme_z8530_mutex); |
| |
| /* if this callout was successful, note the new state of the |
| interrupt signal: */ |
| if (rc == TME_OK) { |
| z8530->tme_z8530_int_asserted = int_asserted; |
| } |
| |
| /* otherwise, remember that at some later time this callout |
| should be attempted again: */ |
| else { |
| later_callouts_a |= TME_Z8530_CALLOUT_INT; |
| later_callouts_b |= TME_Z8530_CALLOUT_INT; |
| } |
| } |
| |
| /* put in any later callouts, and clear that callouts are running: */ |
| z8530->tme_z8530_chan_a.tme_z8530_chan_callout_flags = later_callouts_a; |
| z8530->tme_z8530_chan_b.tme_z8530_chan_callout_flags = later_callouts_b; |
| } |
| |
| /* the z8530 bus cycle handler: */ |
| static int |
| _tme_z8530_bus_cycle(void *_z8530, struct tme_bus_cycle *cycle_init) |
| { |
| struct tme_z8530 *z8530; |
| struct tme_z8530_chan *chan; |
| tme_bus_addr32_t address, z8530_address_last; |
| int is_csr; |
| tme_uint8_t buffer, value; |
| struct tme_bus_cycle cycle_resp; |
| unsigned int register_pointer; |
| int new_callouts; |
| unsigned int rr3_chan_shift; |
| tme_uint8_t rr3_ip_tx; |
| tme_uint8_t rr3_ip_rx; |
| tme_uint8_t ius; |
| |
| /* recover our data structure: */ |
| z8530 = (struct tme_z8530 *) _z8530; |
| |
| /* the requested cycle must be within range: */ |
| z8530_address_last = z8530->tme_z8530_device.tme_bus_device_address_last; |
| assert(cycle_init->tme_bus_cycle_address <= z8530_address_last); |
| assert(cycle_init->tme_bus_cycle_size <= (z8530_address_last - cycle_init->tme_bus_cycle_address) + 1); |
| |
| /* see if this is channel A or channel B: */ |
| address = cycle_init->tme_bus_cycle_address; |
| if ((z8530->tme_z8530_address_chan_b |
| > z8530->tme_z8530_address_chan_a) |
| ? (address >= z8530->tme_z8530_address_chan_b) |
| : (address < z8530->tme_z8530_address_chan_a)) { |
| chan = &z8530->tme_z8530_chan_b; |
| address -= z8530->tme_z8530_address_chan_b; |
| rr3_chan_shift = 0; |
| } |
| else { |
| chan = &z8530->tme_z8530_chan_a; |
| address -= z8530->tme_z8530_address_chan_a; |
| rr3_chan_shift = 3; |
| } |
| |
| /* see if this is a data or csr access: */ |
| is_csr = ((z8530->tme_z8530_offset_csr |
| > z8530->tme_z8530_offset_data) |
| ? (address >= z8530->tme_z8530_offset_csr) |
| : (address < z8530->tme_z8530_offset_data)); |
| |
| /* lock the mutex: */ |
| tme_mutex_lock(&z8530->tme_z8530_mutex); |
| |
| /* if this is a csr access, use the current register pointer and |
| reset it to zero, otherwise use 8 as the register pointer |
| (corresponding to WR8 and RR8) and don't touch the current |
| register pointer: */ |
| if (is_csr) { |
| register_pointer = z8530->tme_z8530_register_pointer; |
| z8530->tme_z8530_register_pointer = 0; |
| } |
| else { |
| register_pointer = 8; |
| } |
| |
| /* assume we won't need any new callouts: */ |
| new_callouts = 0; |
| |
| /* if this is a write: */ |
| if (cycle_init->tme_bus_cycle_type == TME_BUS_CYCLE_WRITE) { |
| |
| /* run the bus cycle: */ |
| cycle_resp.tme_bus_cycle_buffer = &buffer; |
| cycle_resp.tme_bus_cycle_lane_routing = tme_z8530_router; |
| cycle_resp.tme_bus_cycle_address = 0; |
| cycle_resp.tme_bus_cycle_buffer_increment = 1; |
| cycle_resp.tme_bus_cycle_type = TME_BUS_CYCLE_READ; |
| cycle_resp.tme_bus_cycle_size = sizeof(buffer); |
| cycle_resp.tme_bus_cycle_port = |
| TME_BUS_CYCLE_PORT(z8530->tme_z8530_port_least_lane, |
| TME_BUS8_LOG2); |
| tme_bus_cycle_xfer(cycle_init, &cycle_resp); |
| value = buffer; |
| |
| /* log this write: */ |
| tme_log(TME_Z8530_LOG_HANDLE(z8530), 100000, TME_OK, |
| (TME_Z8530_LOG_HANDLE(z8530), |
| "channel %c 0x%02x -> WR%d", |
| 'A' + (chan == &z8530->tme_z8530_chan_b), |
| value, |
| register_pointer)); |
| |
| /* dispatch on the register pointer: */ |
| switch (register_pointer) { |
| |
| /* WR0: */ |
| case 0: |
| |
| /* get the next register pointer: */ |
| z8530->tme_z8530_register_pointer = (value & TME_Z8530_WR0_REGISTER_POINTER); |
| |
| /* dispatch on the CRC reset code: */ |
| switch (value & TME_Z8530_WR0_CRC_RESET_MASK) { |
| case TME_Z8530_WR0_CRC_RESET_NULL: |
| case TME_Z8530_WR0_CRC_RESET_RX: |
| case TME_Z8530_WR0_CRC_RESET_TX: |
| case TME_Z8530_WR0_CRC_RESET_TX_EOM: |
| break; |
| } |
| |
| /* dispatch on the command code: */ |
| switch (value & TME_Z8530_WR0_CMD_MASK) { |
| |
| case TME_Z8530_WR0_CMD_NULL: |
| break; |
| |
| case TME_Z8530_WR0_CMD_POINT_HI: |
| z8530->tme_z8530_register_pointer |= 0x08; |
| break; |
| |
| case TME_Z8530_WR0_CMD_RESET_STATUS: |
| |
| /* clear any Status interrupt and update RR0: */ |
| if (z8530->tme_z8530_rr3 |
| & (TME_Z8530_RR3_CHAN_B_IP_STATUS |
| << rr3_chan_shift)) { |
| z8530->tme_z8530_rr3 |
| &= ~(TME_Z8530_RR3_CHAN_B_IP_STATUS |
| << rr3_chan_shift); |
| new_callouts |= TME_Z8530_CALLOUT_INT; |
| } |
| new_callouts |= _tme_z8530_rr0_update(z8530, chan); |
| break; |
| |
| case TME_Z8530_WR0_CMD_RESET_ERROR: |
| |
| /* clear any errors and refill the Rx FIFO: */ |
| chan->tme_z8530_chan_rdreg[1] &= |
| ~(TME_Z8530_RR1_END_OF_FRAME |
| | TME_Z8530_RR1_ERROR_FRAME |
| | TME_Z8530_RR1_ERROR_RX_OVERRUN |
| | TME_Z8530_RR1_ERROR_PARITY); |
| new_callouts |= _tme_z8530_rx_fifo_refill(z8530, chan); |
| break; |
| |
| case TME_Z8530_WR0_CMD_RESET_IUS: |
| |
| /* reset the highest IUS bit: */ |
| for (ius = z8530->tme_z8530_ius; |
| ius & (ius - 1); |
| ius &= (ius - 1)); |
| z8530->tme_z8530_ius ^= ius; |
| |
| /* always check for an interrupt callout: */ |
| new_callouts |= TME_Z8530_CALLOUT_INT; |
| break; |
| |
| case TME_Z8530_WR0_CMD_RESET_TX: |
| |
| /* get the IP_TX bit for this channel: */ |
| rr3_ip_tx = ((chan == &z8530->tme_z8530_chan_a) |
| ? TME_Z8530_RR3_CHAN_A_IP_TX |
| : TME_Z8530_RR3_CHAN_B_IP_TX); |
| |
| /* if the IP_TX bit is set for this channel, clear it: */ |
| if (z8530->tme_z8530_rr3 & rr3_ip_tx) { |
| z8530->tme_z8530_rr3 &= ~rr3_ip_tx; |
| new_callouts |= TME_Z8530_CALLOUT_INT; |
| } |
| break; |
| |
| case TME_Z8530_WR0_CMD_INT_NEXT_RX: |
| |
| /* if the receive FIFO is not empty: */ |
| if (chan->tme_z8530_chan_rdreg[0] |
| & TME_Z8530_RR0_RX_AVAIL) { |
| |
| /* get the IP_RX bit for this channel: */ |
| rr3_ip_rx = ((chan == &z8530->tme_z8530_chan_a) |
| ? TME_Z8530_RR3_CHAN_A_IP_RX |
| : TME_Z8530_RR3_CHAN_B_IP_RX); |
| |
| /* call out another receive interrupt: */ |
| z8530->tme_z8530_rr3 |= rr3_ip_rx; |
| new_callouts |= TME_Z8530_CALLOUT_INT; |
| } |
| break; |
| |
| case TME_Z8530_WR0_CMD_SEND_ABORT: |
| /* XXX TBD: */ |
| abort(); |
| } |
| break; |
| |
| /* WR1: */ |
| case 1: |
| chan->tme_z8530_chan_wrreg[1] = value; |
| |
| /* XXX since we fixed the RR3 initialization bug present through |
| revision 1.9, this may now be unnecessary, and might even |
| be incorrect for non-NetBSD systems: */ |
| /* NB: some serial drivers, like NetBSD's z8530tty.c (at least |
| as recently as rev 1.79), never issue |
| TME_Z8530_WR0_CMD_RESET_TX when they have no more data to |
| send in response to a Tx Empty interrupt. instead, these |
| drivers simply clear TME_Z8530_WR1_TX_INT_ENABLE and expect |
| that to disable all further Tx Empty interrupts. |
| |
| I can't find anything in the SCC manual that says that the |
| transmit Interrupt Pending bit works this way. is the SCC |
| manual wrong? in any case, if TME_Z8530_WR1_TX_INT_ENABLE is |
| clear, clear the IP_TX bit for this channel: */ |
| if (!(value & TME_Z8530_WR1_TX_INT_ENABLE)) { |
| |
| /* get the IP_TX bit for this channel: */ |
| rr3_ip_tx = ((chan == &z8530->tme_z8530_chan_a) |
| ? TME_Z8530_RR3_CHAN_A_IP_TX |
| : TME_Z8530_RR3_CHAN_B_IP_TX); |
| |
| /* if the IP_TX bit is set for this channel, clear it: */ |
| if (z8530->tme_z8530_rr3 & rr3_ip_tx) { |
| z8530->tme_z8530_rr3 &= ~rr3_ip_tx; |
| new_callouts |= TME_Z8530_CALLOUT_INT; |
| } |
| } |
| |
| break; |
| |
| /* WR2: */ |
| case 2: |
| z8530->tme_z8530_chan_a.tme_z8530_chan_wrreg[2] = value; |
| break; |
| |
| /* WR3: */ |
| /* WR4: */ |
| /* WR5: */ |
| case 5: |
| new_callouts |= TME_Z8530_CALLOUT_CTRL; |
| case 3: |
| case 4: |
| chan->tme_z8530_chan_wrreg[register_pointer] = value; |
| new_callouts |= TME_Z8530_CALLOUT_CONFIG; |
| break; |
| |
| /* WR8: */ |
| case 8: |
| |
| /* if the transmit buffer was previously empty, it won't be now, |
| so clear any transmitter interrupt and call out the new |
| control: */ |
| if (tme_serial_buffer_is_empty(&chan->tme_z8530_chan_buffer_tx)) { |
| if (z8530->tme_z8530_rr3 |
| & (TME_Z8530_RR3_CHAN_B_IP_TX |
| << rr3_chan_shift)) { |
| z8530->tme_z8530_rr3 |
| &= ~(TME_Z8530_RR3_CHAN_B_IP_TX |
| << rr3_chan_shift); |
| new_callouts |= TME_Z8530_CALLOUT_INT; |
| } |
| new_callouts |= TME_Z8530_CALLOUT_CTRL; |
| } |
| |
| /* copy in the new character: */ |
| tme_serial_buffer_copyin(&chan->tme_z8530_chan_buffer_tx, |
| &buffer, 1, |
| TME_SERIAL_DATA_NORMAL, |
| TME_SERIAL_COPY_NORMAL); |
| |
| /* update RR0: */ |
| chan->tme_z8530_chan_rdreg[0] &= ~TME_Z8530_RR0_TX_EMPTY; |
| |
| /* update RR1: */ |
| chan->tme_z8530_chan_rdreg[1] &= ~TME_Z8530_RR1_ALL_SENT; |
| break; |
| |
| /* WR9: */ |
| case 9: |
| |
| /* dispatch on the reset command: */ |
| switch (value & TME_Z8530_WR9_RESET_MASK) { |
| case TME_Z8530_WR9_RESET_NULL: |
| break; |
| case TME_Z8530_WR9_RESET_CHAN_B: |
| _tme_z8530_channel_reset(z8530, &z8530->tme_z8530_chan_b, FALSE); |
| break; |
| case TME_Z8530_WR9_RESET_CHAN_A: |
| _tme_z8530_channel_reset(z8530, &z8530->tme_z8530_chan_a, FALSE); |
| break; |
| case TME_Z8530_WR9_RESET_CHIP: |
| _tme_z8530_channel_reset(z8530, &z8530->tme_z8530_chan_a, TRUE); |
| _tme_z8530_channel_reset(z8530, &z8530->tme_z8530_chan_b, TRUE); |
| break; |
| } |
| |
| /* we can't handle these bits yet: */ |
| if (value & (TME_Z8530_WR9_SOFT_INTACK |
| | TME_Z8530_WR9_DLC)) { |
| abort(); |
| } |
| |
| /* save the register value: */ |
| z8530->tme_z8530_wr9 = value; |
| |
| /* this may have enabled interrupts: */ |
| new_callouts |= TME_Z8530_CALLOUT_INT; |
| break; |
| |
| /* WR11: */ |
| /* WR12: */ |
| /* WR13: */ |
| /* WR14: */ |
| case 11: |
| case 12: |
| case 13: |
| case 14: |
| chan->tme_z8530_chan_wrreg[register_pointer] = value; |
| new_callouts |= TME_Z8530_CALLOUT_CONFIG; |
| break; |
| |
| /* WR6: */ |
| /* WR7: */ |
| /* WR10: */ |
| /* WR15: */ |
| case 6: |
| case 7: |
| case 10: |
| case 15: |
| chan->tme_z8530_chan_wrreg[register_pointer] = value; |
| break; |
| |
| /* everything else: */ |
| default: |
| abort(); |
| } |
| } |
| |
| /* otherwise, this is a read: */ |
| else { |
| assert(cycle_init->tme_bus_cycle_type == TME_BUS_CYCLE_READ); |
| |
| /* dispatch on the register pointer: */ |
| switch (register_pointer) { |
| |
| /* RR0: */ |
| /* RR4 (an image of RR0 on the 8530): */ |
| case 0: |
| case 4: |
| value = chan->tme_z8530_chan_rdreg[0]; |
| break; |
| |
| /* RR1: */ |
| /* RR5 (an image of RR1 on the 8530): */ |
| case 1: |
| case 5: |
| value = chan->tme_z8530_chan_rdreg[1]; |
| break; |
| |
| /* RR2: */ |
| case 2: |
| if (chan == &z8530->tme_z8530_chan_a) { |
| value = chan->tme_z8530_chan_wrreg[2]; |
| } |
| else { |
| value = chan->tme_z8530_chan_rdreg[2]; |
| } |
| break; |
| |
| /* RR3: */ |
| case 3: |
| value = ((chan == &z8530->tme_z8530_chan_a) |
| ? chan->tme_z8530_chan_rdreg[register_pointer] |
| : 0); |
| break; |
| |
| /* RR8: */ |
| case 8: |
| |
| /* return whatever's in the Rx FIFO right now and |
| refill it: */ |
| value = chan->tme_z8530_chan_rdreg[8]; |
| new_callouts |= _tme_z8530_rx_fifo_refill(z8530, chan); |
| break; |
| |
| /* RR10: */ |
| /* RR14 (an image of RR10 on the 8530): */ |
| case 10: |
| case 14: |
| value = 0; |
| break; |
| |
| /* RR12: */ |
| case 12: |
| /* these return the corresponding WR: */ |
| value = chan->tme_z8530_chan_wrreg[register_pointer]; |
| break; |
| |
| /* RR9 (an image of RR13 on the 8530): */ |
| /* RR13: */ |
| case 9: |
| case 13: |
| value = chan->tme_z8530_chan_wrreg[13]; |
| break; |
| |
| /* RR11 (an image of RR15 on the 8530): */ |
| /* RR15: */ |
| case 11: |
| case 15: |
| value = chan->tme_z8530_chan_wrreg[15]; |
| break; |
| |
| /* everything else: */ |
| default: |
| abort(); |
| } |
| |
| #ifndef TME_NO_LOG |
| /* log this read: */ |
| if (chan->tme_z8530_chan_last_read_reg != register_pointer |
| || chan->tme_z8530_chan_last_read_value != value) { |
| chan->tme_z8530_chan_last_read_reg = register_pointer; |
| chan->tme_z8530_chan_last_read_value = value; |
| tme_log(TME_Z8530_LOG_HANDLE(z8530), 100000, TME_OK, |
| (TME_Z8530_LOG_HANDLE(z8530), |
| "channel %c RR%d -> 0x%02x", |
| 'A' + (chan == &z8530->tme_z8530_chan_b), |
| register_pointer, |
| value)); |
| } |
| #endif /* !TME_NO_LOG */ |
| |
| /* run the bus cycle: */ |
| buffer = value; |
| cycle_resp.tme_bus_cycle_buffer = &buffer; |
| cycle_resp.tme_bus_cycle_lane_routing = tme_z8530_router; |
| cycle_resp.tme_bus_cycle_address = 0; |
| cycle_resp.tme_bus_cycle_buffer_increment = 1; |
| cycle_resp.tme_bus_cycle_type = TME_BUS_CYCLE_WRITE; |
| cycle_resp.tme_bus_cycle_size = sizeof(buffer); |
| cycle_resp.tme_bus_cycle_port = |
| TME_BUS_CYCLE_PORT(z8530->tme_z8530_port_least_lane, |
| TME_BUS8_LOG2); |
| tme_bus_cycle_xfer(cycle_init, &cycle_resp); |
| } |
| |
| /* make any needed callouts: */ |
| _tme_z8530_callout(z8530, chan, new_callouts); |
| |
| /* unlock the mutex: */ |
| tme_mutex_unlock(&z8530->tme_z8530_mutex); |
| |
| /* no faults: */ |
| return (TME_OK); |
| } |
| |
| /* this is called when the serial configuration changes: */ |
| static int |
| _tme_z8530_config(struct tme_serial_connection *conn_serial, |
| struct tme_serial_config *config) |
| { |
| /* do nothing: */ |
| return (TME_OK); |
| } |
| |
| /* this is called when control lines change: */ |
| static int |
| _tme_z8530_ctrl(struct tme_serial_connection *conn_serial, |
| unsigned int ctrl) |
| { |
| struct tme_z8530 *z8530; |
| struct tme_z8530_chan *chan; |
| tme_uint8_t rr0_status_new; |
| int new_callouts; |
| |
| /* recover our data structures: */ |
| z8530 = conn_serial->tme_serial_connection.tme_connection_element->tme_element_private; |
| chan = ((struct tme_z8530_connection *) conn_serial)->tme_z8530_connection_chan; |
| |
| /* lock the mutex: */ |
| tme_mutex_lock(&z8530->tme_z8530_mutex); |
| |
| /* assume that we won't need to make any callouts: */ |
| new_callouts = 0; |
| |
| /* make new raw RR0 status bits, and update the mask of bits that |
| have changed from the RR0 at the time of the last External/Status |
| interrupt, with the exception of TME_Z8530_RR0_BREAK - break |
| transitions are not aggregated so they are not missed while the |
| RR0 status bits are latched: */ |
| rr0_status_new = 0; |
| if (ctrl & TME_SERIAL_CTRL_DCD) { |
| rr0_status_new |= TME_Z8530_RR0_DCD; |
| } |
| if (ctrl & TME_SERIAL_CTRL_CTS) { |
| rr0_status_new |= TME_Z8530_RR0_CTS; |
| } |
| if (ctrl & TME_SERIAL_CTRL_BREAK) { |
| rr0_status_new |= TME_Z8530_RR0_BREAK; |
| } |
| chan->tme_z8530_chan_rr0_status_raw = rr0_status_new; |
| chan->tme_z8530_chan_rr0_status_diff_mask = |
| (((chan->tme_z8530_chan_rdreg[0] |
| ^ rr0_status_new) |
| & (TME_Z8530_RR0_DCD |
| | TME_Z8530_RR0_CTS |
| | TME_Z8530_RR0_BREAK)) |
| | (chan->tme_z8530_chan_rr0_status_diff_mask |
| & TME_Z8530_RR0_BREAK)); |
| |
| /* try to update RR0. (if an External/Status interrupt is pending, |
| RR0 is latched): */ |
| new_callouts |= _tme_z8530_rr0_update(z8530, chan); |
| |
| /* if this channel is readable, call out to read data: */ |
| if (ctrl & TME_SERIAL_CTRL_OK_READ) { |
| new_callouts |= TME_Z8530_CALLOUT_READ; |
| } |
| |
| /* if needed, make callouts: */ |
| _tme_z8530_callout(z8530, chan, new_callouts); |
| |
| /* unlock the mutex: */ |
| tme_mutex_unlock(&z8530->tme_z8530_mutex); |
| |
| return (TME_OK); |
| } |
| |
| /* this is called to read serial data (from the z8530 perspective, to transmit it): */ |
| static int |
| _tme_z8530_read(struct tme_serial_connection *conn_serial, |
| tme_uint8_t *data, unsigned int count, |
| tme_serial_data_flags_t *_data_flags) |
| { |
| struct tme_z8530 *z8530; |
| struct tme_z8530_chan *chan; |
| tme_uint8_t rr3_ip_tx; |
| int new_callouts; |
| int rc; |
| |
| /* assume that we won't need to make any callouts: */ |
| new_callouts = 0; |
| |
| /* recover our data structures: */ |
| z8530 = conn_serial->tme_serial_connection.tme_connection_element->tme_element_private; |
| chan = ((struct tme_z8530_connection *) conn_serial)->tme_z8530_connection_chan; |
| |
| /* lock our mutex: */ |
| tme_mutex_lock(&z8530->tme_z8530_mutex); |
| |
| /* copy out data from the Tx FIFO: */ |
| rc = tme_serial_buffer_copyout(&chan->tme_z8530_chan_buffer_tx, |
| data, count, |
| _data_flags, |
| TME_SERIAL_COPY_NORMAL); |
| |
| /* if the Tx buffer is now empty: */ |
| if (tme_serial_buffer_is_empty(&chan->tme_z8530_chan_buffer_tx)) { |
| |
| /* update RR0: */ |
| chan->tme_z8530_chan_rdreg[0] |= TME_Z8530_RR0_TX_EMPTY; |
| |
| /* update RR1: */ |
| chan->tme_z8530_chan_rdreg[1] |= TME_Z8530_RR1_ALL_SENT; |
| |
| /* if Tx interrupts are enabled: */ |
| if (chan->tme_z8530_chan_wrreg[1] & TME_Z8530_WR1_TX_INT_ENABLE) { |
| |
| /* get the IP_TX bit for this channel: */ |
| rr3_ip_tx = ((chan == &z8530->tme_z8530_chan_a) |
| ? TME_Z8530_RR3_CHAN_A_IP_TX |
| : TME_Z8530_RR3_CHAN_B_IP_TX); |
| |
| /* if the Tx buffer is newly empty, flag this channel for a Tx |
| interrupt: */ |
| if (!(z8530->tme_z8530_rr3 & rr3_ip_tx)) { |
| z8530->tme_z8530_rr3 |= rr3_ip_tx; |
| new_callouts |= TME_Z8530_CALLOUT_INT; |
| } |
| } |
| |
| /* call out a new control to clear OK_READ: */ |
| new_callouts |= TME_Z8530_CALLOUT_CTRL; |
| } |
| |
| /* make any needed callouts: */ |
| _tme_z8530_callout(z8530, chan, new_callouts); |
| |
| /* unlock our mutex: */ |
| tme_mutex_unlock(&z8530->tme_z8530_mutex); |
| |
| /* done: */ |
| return (rc); |
| } |
| |
| /* the z8530 TLB filler: */ |
| static int |
| _tme_z8530_tlb_fill(void *_z8530, struct tme_bus_tlb *tlb, |
| tme_bus_addr_t address, unsigned int cycles) |
| { |
| struct tme_z8530 *z8530; |
| tme_bus_addr32_t z8530_address_last; |
| |
| /* recover our data structure: */ |
| z8530 = (struct tme_z8530 *) _z8530; |
| |
| /* the address must be within range: */ |
| z8530_address_last = z8530->tme_z8530_device.tme_bus_device_address_last; |
| assert(address <= z8530_address_last); |
| |
| /* initialize the TLB entry: */ |
| tme_bus_tlb_initialize(tlb); |
| |
| /* this TLB entry can cover the whole device: */ |
| tlb->tme_bus_tlb_addr_first = 0; |
| tlb->tme_bus_tlb_addr_last = z8530_address_last; |
| |
| /* allow reading and writing: */ |
| tlb->tme_bus_tlb_cycles_ok = TME_BUS_CYCLE_READ | TME_BUS_CYCLE_WRITE; |
| |
| /* our bus cycle handler: */ |
| tlb->tme_bus_tlb_cycle_private = z8530; |
| tlb->tme_bus_tlb_cycle = _tme_z8530_bus_cycle; |
| |
| return (TME_OK); |
| } |
| |
| /* this handles a hardware interrupt acknowledge: */ |
| static int |
| _tme_z8530_hard_intack(void *_z8530, unsigned int ipl, int *_vector) |
| { |
| struct tme_z8530 *z8530; |
| int rc; |
| |
| /* recover our data structure: */ |
| z8530 = (struct tme_z8530 *) _z8530; |
| |
| /* lock our mutex: */ |
| tme_mutex_lock(&z8530->tme_z8530_mutex); |
| |
| /* acknowledge the interrupt: */ |
| rc = _tme_z8530_intack(z8530, _vector); |
| |
| /* we always call out an interrupt check: */ |
| _tme_z8530_callout(z8530, &z8530->tme_z8530_chan_a, TME_Z8530_CALLOUT_INT); |
| |
| /* unlock our mutex: */ |
| tme_mutex_unlock(&z8530->tme_z8530_mutex); |
| |
| /* done: */ |
| return (rc); |
| } |
| |
| /* this scores a serial connection: */ |
| static int |
| _tme_z8530_connection_score(struct tme_connection *conn, unsigned int *_score) |
| { |
| struct tme_z8530 *z8530; |
| struct tme_z8530_connection *conn_z8530; |
| struct tme_serial_connection *conn_serial_other; |
| |
| /* recover our data structures: */ |
| z8530 = conn->tme_connection_element->tme_element_private; |
| conn_z8530 = (struct tme_z8530_connection *) conn; |
| conn_serial_other = (struct tme_serial_connection *) conn->tme_connection_other; |
| |
| /* both sides must be serial connections: */ |
| assert(conn->tme_connection_type == TME_CONNECTION_SERIAL); |
| assert(conn->tme_connection_other->tme_connection_type == TME_CONNECTION_SERIAL); |
| |
| /* this channel must be free: */ |
| assert(conn_z8530->tme_z8530_connection_chan->tme_z8530_chan_connection == NULL); |
| |
| /* we're lax on checking the members of the serial connection, |
| and just assume this connection is fine: */ |
| *_score = 1; |
| return (TME_OK); |
| } |
| |
| /* this makes a new serial connection: */ |
| static int |
| _tme_z8530_connection_make(struct tme_connection *conn, unsigned int state) |
| { |
| struct tme_z8530 *z8530; |
| struct tme_z8530_connection *conn_z8530; |
| struct tme_serial_connection *conn_serial_other; |
| |
| /* recover our data structures: */ |
| z8530 = conn->tme_connection_element->tme_element_private; |
| conn_z8530 = (struct tme_z8530_connection *) conn; |
| conn_serial_other = (struct tme_serial_connection *) conn->tme_connection_other; |
| |
| /* both sides must be serial connections: */ |
| assert(conn->tme_connection_type == TME_CONNECTION_SERIAL); |
| assert(conn->tme_connection_other->tme_connection_type == TME_CONNECTION_SERIAL); |
| |
| /* we're always set up to answer calls across the connection, so we |
| only have to do work when the connection has gone full, namely |
| taking the other side of the connection: */ |
| if (state == TME_CONNECTION_FULL) { |
| |
| /* save our connection: */ |
| conn_z8530->tme_z8530_connection_chan->tme_z8530_chan_connection = conn_serial_other; |
| } |
| |
| return (TME_OK); |
| } |
| |
| /* this breaks a connection: */ |
| static int |
| _tme_z8530_connection_break(struct tme_connection *conn, unsigned int state) |
| { |
| abort(); |
| } |
| |
| /* this makes a new connection side for a z8530: */ |
| static int |
| _tme_z8530_connections_new(struct tme_element *element, |
| const char * const *args, |
| struct tme_connection **_conns, |
| char **_output) |
| { |
| struct tme_z8530 *z8530; |
| struct tme_z8530_chan *chan; |
| struct tme_z8530_connection *conn_z8530; |
| struct tme_serial_connection *conn_serial; |
| struct tme_connection *conn; |
| |
| /* recover our data structure: */ |
| z8530 = (struct tme_z8530 *) element->tme_element_private; |
| |
| /* if the args are "channel A" or "channel B", they're for us: */ |
| if (TME_ARG_IS(args[1], "channel")) { |
| |
| /* if this is for channel A, but it already has a connection, |
| we can't offer another connection. similarly for channel B: */ |
| if (TME_ARG_IS(args[2], "A")) { |
| chan = &z8530->tme_z8530_chan_a; |
| } |
| else if (TME_ARG_IS(args[2], "B")) { |
| chan = &z8530->tme_z8530_chan_b; |
| } |
| else { |
| tme_output_append_error(_output, |
| "%s channel '%s', %s %s channel { A | B }", |
| _("bad"), |
| args[2], |
| _("usage:"), |
| args[0]); |
| return (EINVAL); |
| } |
| if (chan->tme_z8530_chan_connection != NULL) { |
| tme_output_append_error(_output, |
| "%s %s", |
| _("channel"), |
| args[2]); |
| return (EISCONN); |
| } |
| |
| /* allocate the new serial connection: */ |
| conn_z8530 = tme_new0(struct tme_z8530_connection, 1); |
| conn_serial = &conn_z8530->tme_z8530_connection; |
| conn = &conn_serial->tme_serial_connection; |
| |
| /* fill in the generic connection: */ |
| conn->tme_connection_next = *_conns; |
| conn->tme_connection_type = TME_CONNECTION_SERIAL; |
| conn->tme_connection_score = _tme_z8530_connection_score; |
| conn->tme_connection_make = _tme_z8530_connection_make; |
| conn->tme_connection_break = _tme_z8530_connection_break; |
| |
| /* fill in the serial connection: */ |
| conn_serial->tme_serial_connection_config = _tme_z8530_config; |
| conn_serial->tme_serial_connection_ctrl = _tme_z8530_ctrl; |
| conn_serial->tme_serial_connection_read = _tme_z8530_read; |
| |
| /* fill in the z8530 connection: */ |
| conn_z8530->tme_z8530_connection_chan = chan; |
| |
| /* return the connection side possibility: */ |
| *_conns = conn; |
| return (TME_OK); |
| } |
| |
| /* otherwise, make the generic bus device connection side: */ |
| return (tme_bus_device_connections_new(element, args, _conns, _output)); |
| } |
| |
| /* the new z8530 function: */ |
| TME_ELEMENT_NEW_DECL(tme_ic_z8530) { |
| const struct tme_z8530_socket *socket; |
| struct tme_z8530 *z8530; |
| struct tme_z8530_socket socket_real; |
| tme_bus_addr_t address_mask; |
| |
| /* dispatch on our socket version: */ |
| socket = (const struct tme_z8530_socket *) extra; |
| if (socket == NULL) { |
| tme_output_append_error(_output, _("need an ic socket")); |
| return (ENXIO); |
| } |
| switch (socket->tme_z8530_socket_version) { |
| case TME_Z8530_SOCKET_0: |
| socket_real = *socket; |
| break; |
| default: |
| tme_output_append_error(_output, _("socket type")); |
| return (EOPNOTSUPP); |
| } |
| |
| /* we take no arguments: */ |
| if (args[1] != NULL) { |
| tme_output_append_error(_output, |
| "%s %s, %s %s", |
| args[1], |
| _("unexpected"), |
| _("usage:"), |
| args[0]); |
| return (EINVAL); |
| } |
| |
| /* start the z8530 structure: */ |
| z8530 = tme_new0(struct tme_z8530, 1); |
| z8530->tme_z8530_socket = socket_real; |
| tme_mutex_init(&z8530->tme_z8530_mutex); |
| _tme_z8530_channel_init(z8530, &z8530->tme_z8530_chan_a); |
| _tme_z8530_channel_init(z8530, &z8530->tme_z8530_chan_b); |
| |
| /* figure our address mask, up to the nearest power of two: */ |
| address_mask = TME_MAX(z8530->tme_z8530_address_chan_a, |
| z8530->tme_z8530_address_chan_b); |
| address_mask += TME_MAX(z8530->tme_z8530_offset_csr, |
| z8530->tme_z8530_offset_data); |
| if (address_mask & (address_mask - 1)) { |
| for (; address_mask & (address_mask - 1); address_mask &= (address_mask - 1)); |
| address_mask <<= 1; |
| } |
| address_mask -= 1; |
| |
| /* initialize our simple bus device descriptor: */ |
| z8530->tme_z8530_device.tme_bus_device_element = element; |
| z8530->tme_z8530_device.tme_bus_device_tlb_fill = _tme_z8530_tlb_fill; |
| z8530->tme_z8530_device.tme_bus_device_intack = _tme_z8530_hard_intack; |
| z8530->tme_z8530_device.tme_bus_device_address_last = address_mask; |
| |
| /* fill the element: */ |
| element->tme_element_private = z8530; |
| element->tme_element_connections_new = _tme_z8530_connections_new; |
| |
| return (TME_OK); |
| } |