// transmit_functions.c

#include "1394.h"
#include "data_structures.h"
#include "phy_services.h"
#include "arb.h"
#include "shared.h"

int symbol_time(speedCode spd) {      // Legacy_wait units (2/BASE_RATE)
  return(spd == S400 ? 1: spd == S200 ? 2 : 4);
}

void tx_byte(byte data_byte) {          // Transmit a byte
  int i;
  non_null_packet = TRUE;            // Set data packet indicator as actual data payload is sent
  for (i = 0; i < NPORT; i++) {
    if (i != receive_port) {
      if (speed_OK[i]) {
        portT[i].speed = cur_speed;
        portT[i].data = data_byte;
        portT[i].tag = DATA;
      } 
      // if not OK, then already sending Data Prefix or Data Null
    }
  }
  wait_symbol_time(cur_speed);
}

void tx_quadlet(quadlet quad_data) {    // Send a quadlet...
  int i;
  byte data_byte;
  for (i = 0; i < 4; i++) {             // ...a byte at a time
    data_byte = quad_data >> 8*(3-i);
    PH_DATA_indication(PH_DATA_BYTE, 0, data_byte, 0); // Copy our own self_ID packet to the link
    tx_byte(data_byte);
  }
}

void portTarb_at_speed(int port_num, ArbState arb, speedCode speed) {
  if (active[port_num]) {
    portT[port_num].arb = arb;
    portT[port_num].speed = speed;
    portT[port_num].tag = ARB_STATE;
  }
}

void portTarb(int port_num, ArbState arb) { // suitable for both DS and Beta ports
  portTarb_at_speed(port_num, arb, DEFAULT);
}

void send_control(ArbState control, boolean out_of_packet, int not_to_port) {
  int i;
  for (i = 0; i < NPORT; i++) {         // send in parallel to all ports
    if (i != not_to_port) {
      if (Beta_mode[i])
        portTarb_at_speed(i, control, S100);
    }
  }
  wait_symbol_time(S100);
  for (i = 0; i < NPORT; i++)
    if (out_of_packet && i != not_to_port && Beta_mode[i]) 
      portTarb(i, IDLE);
} 

void portTspeed(int port_num, speedCode pkt_speed, pktType packet_format) {
  portT[port_num].pkt = packet_format;  // LEGACY or BETA
  portTarb_at_speed(port_num, SPEED, pkt_speed);
}

void portTspeed_raw(int port_num, speedCode speed) {
  if (!Beta_mode[port_num]) portTarb_at_speed(port_num, SPEED_RAW, speed);
} 

void start_tx_packet(speedCode pkt_speed, pktType pkt) { // Send data prefix and do speed signaling
  int signal_port = NPORT, i;
  boolean send_speed;
  int deletable_symbol_time;            // time for two symbols

  if (!DS_stuck) {  
    max_beta_timer = 0;                 // timer  only needs to be implemented in border capable nodes
    DS_stuck = TRUE;                    // and note that this will have to be released by sending
                                        // a Legacy format packet
  }
  cur_speed = pkt_speed;                // Remember speed and format of packet to transmit
  cur_format = pkt;
  non_null_packet = FALSE;              // Reset data payload indicator at beginning of each new packet

  for (i = 0; i < NPORT; i++) { 
    if (!active[i])                     // deal with inactive ports
      speed_OK[i] = FALSE;
    else if (disable_request[i] && !phy_response)
      portTarb(signal_port = i, DISABLE_NOTIFY);
    else if (suspend_request[i] && !phy_response)
      portTarb(signal_port = i, SUSPEND);
    else if (do_standby[i] && !phy_response)
      portTarb(signal_port = i, STANDBY);
    else                                // normal packet
      speed_OK[i] = (pkt_speed <= port_speed[i]) && (Beta_mode[i] || pkt == LEGACY);
  }
  
  if (signal_port != NPORT)             // send DATA_NULL on the other ports
    for (i = 0; i < NPORT; i++)
      if (!(disable_request[i] || suspend_request[i] || do_standby[i])) portTarb(i, DATA_NULL);
    
  // if granting a cycle start request, first send the appropriate cycle start token
  if (!immediate_request && !iso_cycle && cycle_start_request) {
    if (link == B_Link)           // Alert link
      PH_DATA_indication(odd_isoch_phase ? PH_ISOCH_ODD: PH_ISOCH_EVEN, 0, 0, 0); 
    for (i = 0; i < NPORT; i++)                   // Cycle start tokens are not sent on DS ports,
      if (!Beta_mode[i]) portTarb(i, DATA_NULL);  // send DATA_NULL instead to prevent idle gap
    send_control(odd_isoch_phase ? CYCLE_START_ODD: CYCLE_START_EVEN, FALSE, NPORT);
    iso_cycle = TRUE;
  }

  if (signal_port != NPORT)
    for (i = 0; i < 4; i++)                   // send four for robustness
      wait_symbol_time(min_operating_speed);

  for (i = 0; i < NPORT; i++) {
    if ((pkt == LEGACY) && (link == Legacy_Link) && (pkt_speed == S100))
      portTarb(i, DATA_PREFIX);     // suppress speedcode, send data prefix
    else if (speed_OK[i]) {         // send speed code
      portTspeed(i, pkt_speed, pkt);
    } else portTarb(i, DATA_NULL);
  }

  wait_symbol_time(pkt_speed); // Wait till speed-sig symbol completed.

  for (i = 0; i < NPORT; i++)
    // Now send the next symbol on the Beta mode ports.
    if (Beta_mode[i]) portTarb(i, speed_OK[i] ? DATA_PREFIX : DATA_NULL);

  // ensure timing for starting packet format, plus timing for deletable symbols

  // The requirement is that the originating node shall insert two symbols
  // (one "can delete" and one "must delete", and shall insert more symbols if 
  // necessary to ensure that there is (approx) 40ns of deletable symbol time.
 
  deletable_symbol_time = MIN_DELETABLE_SYMBOL_TIME;
  if ((pkt_speed < S800) && (symbol_time(pkt_speed) * 2 > MIN_DELETABLE_SYMBOL_TIME))
    deletable_symbol_time = symbol_time(pkt_speed) * 2;  // or two symbol times
    
  if (pkt == LEGACY) { // therefore pkt_speed < S800
    // Leave the speed signal for longer, send the next symbol on the DS ports.
    // waited 1, 2 or 4 Legacy_time units
    wait_Legacy_time(SPEED_SIGNAL_LENGTH - symbol_time(pkt_speed));
    for (i = 0; i < NPORT; i++)
      // Harmless writing this to the beta mode ports as well!
      portTarb(i, speed_OK[i] ? DATA_PREFIX : DATA_NULL);
    wait_Legacy_time(DATA_PREFIX_HOLD);  // finish Data_Prefix
    // for S100 packets, must round up to two S100 symbol times
    // not used if a longer value of DATA_PREFIX_HOLD is used for Legacy interoperability
    // (see text) i.e. if the time to wait is <= 0
    if (pkt_speed == S100) 
      wait_Legacy_time (2*symbol_time(S100) - (SPEED_SIGNAL_LENGTH+DATA_PREFIX_HOLD));
  } else wait_symbol_time(pkt_speed);
  wait_Legacy_time (deletable_symbol_time);
  signaled = (signal_port != NPORT);
}

void stop_tx_packet(ArbState ending_status, int port_to_grant) {
  int i, hold_time;
  ArbState tx_arb;
  switch (ending_status) {
    case DATA_PREFIX:      // current talker is holding onto
    case DATA_NULL:        // the bus to form a concatenation
      hold_time = CONCATENATION_PREFIX_TIME;
      tx_arb = ending_status;
      break;
    case GRANT:
    case GRANT_ISOCH:
      if (port_to_grant == senior_port) {
        // grant to senior releases bus
        hold_time = DATA_END_TIME;
        tx_arb = DATA_END;
        DS_stuck = (cur_format != LEGACY);
      } else if (receive_port == NPORT) {
        // grant to junior/link without fear of overwriting IDLE on receive port
        hold_time = CONCATENATION_PREFIX_TIME;
        tx_arb = DATA_NULL;
      } else {
        // grant to junior/link taking care not to overwrite IDLE on receive port
        hold_time = DATA_END_TIME;
        tx_arb = DATA_NULL;
      }
      break;
    case DATA_END:
      hold_time = DATA_END_TIME;
      tx_arb = DATA_END;
      DS_stuck = (cur_format != LEGACY);
      break;
    case IDLE:
    case ARB_CONTEXT:
      tx_arb = ending_status;
      DS_stuck = (cur_format != LEGACY);
      break;
    case BUS_RESET:
      tx_arb = BUS_RESET;
      DS_stuck = FALSE;
      break;
    default:
      break;
  }

  if ((tx_arb == BUS_RESET) || (tx_arb == IDLE))
    return;  // Immediate exit to R0 or A0

  for (i = 0; i < NPORT; i++) {
    if (Beta_mode[i] && port_to_grant == i) {
      portTarb(i, ending_status);
    } else if (i != receive_port) 
      // leave DATA_NULL on stuck DS or speed_filtered ports for Beta packet formats
      if (cur_format == LEGACY || (Beta_mode[i] && speed_OK[i]))
        portTarb(i, tx_arb);
  }
  
  if ((cur_format == LEGACY) && (tx_arb != ARB_CONTEXT)) {
    // For LEGACY format packets, ensure all Legacy timings.  ARB_CONTEXT indicates a 
    // sudden end of a packet prefix; consequently, Legacy timings are not preserved.
    if (non_null_packet)
      wait_Legacy_time(1);  // allow for 20 ns of dribble bit time on non null legacy format packets
    wait_Legacy_time(hold_time);
  } else if ((port_to_grant != NPORT) && (port_to_grant != NPORT+1) &&
             (cur_speed > port_speed[port_to_grant])) {
    // stretch the GRANT if its going to a slower external port
    wait_symbol_time(port_speed[port_to_grant]);
    wait_symbol_time(port_speed[port_to_grant]);        
  } else {
    wait_symbol_time(cur_speed);
    wait_symbol_time(cur_speed);
  }
}
