module usb_serial_ctrl_ep #(
  parameter MAX_IN_PACKET_SIZE = 32,
  parameter MAX_OUT_PACKET_SIZE = 32
) (
  input clk,
  input reset,
  output [6:0] dev_addr,

  ////////////////////
  // out endpoint interface
  ////////////////////
  output out_ep_req,
  input out_ep_grant,
  input out_ep_data_avail,
  input out_ep_setup,
  output out_ep_data_get,
  input [7:0] out_ep_data,
  output out_ep_stall,
  input out_ep_acked,


  ////////////////////
  // in endpoint interface
  ////////////////////
  output in_ep_req,
  input in_ep_grant,
  input in_ep_data_free,
  output in_ep_data_put,
  output reg [7:0] in_ep_data = 0,
  output in_ep_data_done,
  output reg in_ep_stall,
  input in_ep_acked
);


  localparam IDLE = 0;
  localparam SETUP = 1;
  localparam DATA_IN = 2;
  localparam DATA_OUT = 3;
  localparam STATUS_IN = 4;
  localparam STATUS_OUT = 5;

  reg [5:0] ctrl_xfr_state = IDLE;
  reg [5:0] ctrl_xfr_state_next;



  reg setup_stage_end = 0;
  reg data_stage_end = 0;
  reg status_stage_end = 0;
  reg send_zero_length_data_pkt = 0;



  // the default control endpoint gets assigned the device address
  reg [6:0] dev_addr_i = 0;
  assign dev_addr = dev_addr_i;

  assign out_ep_req = out_ep_data_avail;
  assign out_ep_data_get = out_ep_data_avail;
  reg out_ep_data_valid = 0;
  always @(posedge clk) out_ep_data_valid <= out_ep_data_avail && out_ep_grant;

  // need to record the setup data
  reg [3:0] setup_data_addr = 0;
  reg [9:0] raw_setup_data [7:0];

  wire [7:0] bmRequestType = raw_setup_data[0];
  wire [7:0] bRequest = raw_setup_data[1];
  wire [15:0] wValue = {raw_setup_data[3][7:0], raw_setup_data[2][7:0]};
  wire [15:0] wIndex = {raw_setup_data[5][7:0], raw_setup_data[4][7:0]};
  wire [15:0] wLength = {raw_setup_data[7][7:0], raw_setup_data[6][7:0]};

  // keep track of new out data start and end
  wire pkt_start;
  wire pkt_end;

  rising_edge_detector detect_pkt_start (
    .clk(clk),
    .in(out_ep_data_avail),
    .out(pkt_start)
  );

  falling_edge_detector detect_pkt_end (
    .clk(clk),
    .in(out_ep_data_avail),
    .out(pkt_end)
  );

  assign out_ep_stall = 1'b0;

  wire setup_pkt_start = pkt_start && out_ep_setup;

  // wire has_data_stage = wLength != 16'b0000000000000000; // this version for some reason causes a 16b carry which is slow
  wire has_data_stage = |wLength;

  wire out_data_stage;
  assign out_data_stage = has_data_stage && !bmRequestType[7];

  wire in_data_stage;
  assign in_data_stage = has_data_stage && bmRequestType[7];

  reg [7:0] bytes_sent = 0;
  reg [6:0] rom_length = 0;

  wire wLength_is_large = |wLength[15:7]; // 15-7 bits because rom_length is only 7 bits wide

  wire all_data_sent =
    (bytes_sent >= rom_length) ||
    (!wLength_is_large && bytes_sent >= wLength[7:0]); // if the requested wLength is large, we only send rom_length bytes

  wire more_data_to_send =
    !all_data_sent;

  wire in_data_transfer_done;

  rising_edge_detector detect_in_data_transfer_done (
    .clk(clk),
    .in(all_data_sent),
    .out(in_data_transfer_done)
  );

  assign in_ep_data_done = (in_data_transfer_done && ctrl_xfr_state == DATA_IN) || send_zero_length_data_pkt;

  assign in_ep_req = ctrl_xfr_state == DATA_IN && more_data_to_send;
  assign in_ep_data_put = ctrl_xfr_state == DATA_IN && more_data_to_send && in_ep_data_free;


  reg [6:0] rom_addr = 0;

  reg save_dev_addr = 0;
  reg [6:0] new_dev_addr = 0;

  ////////////////////////////////////////////////////////////////////////////////
  // control transfer state machine
  ////////////////////////////////////////////////////////////////////////////////


  always @* begin
    setup_stage_end <= 0;
    data_stage_end <= 0;
    status_stage_end <= 0;
    send_zero_length_data_pkt <= 0;

    case (ctrl_xfr_state)
      IDLE : begin
        if (setup_pkt_start) begin
          ctrl_xfr_state_next <= SETUP;
        end else begin
          ctrl_xfr_state_next <= IDLE;
        end
      end

      SETUP : begin
        if (pkt_end) begin
          setup_stage_end <= 1;

          if (in_data_stage) begin
            ctrl_xfr_state_next <= DATA_IN;

          end else if (out_data_stage) begin
            ctrl_xfr_state_next <= DATA_OUT;

          end else begin
            ctrl_xfr_state_next <= STATUS_IN;
            send_zero_length_data_pkt <= 1;
          end

        end else begin
          ctrl_xfr_state_next <= SETUP;
        end
      end

      DATA_IN : begin
	if (in_ep_stall) begin
          ctrl_xfr_state_next <= IDLE;
          data_stage_end <= 1;
          status_stage_end <= 1;

	end else if (in_ep_acked && all_data_sent) begin
          ctrl_xfr_state_next <= STATUS_OUT;
          data_stage_end <= 1;

        end else begin
          ctrl_xfr_state_next <= DATA_IN;
        end
      end

      DATA_OUT : begin
        if (out_ep_acked) begin
          ctrl_xfr_state_next <= STATUS_IN;
          send_zero_length_data_pkt <= 1;
          data_stage_end <= 1;

        end else begin
          ctrl_xfr_state_next <= DATA_OUT;
        end
      end

      STATUS_IN : begin
        if (in_ep_acked) begin
          ctrl_xfr_state_next <= IDLE;
          status_stage_end <= 1;

        end else begin
          ctrl_xfr_state_next <= STATUS_IN;
        end
      end

      STATUS_OUT: begin
        if (out_ep_acked) begin
          ctrl_xfr_state_next <= IDLE;
          status_stage_end <= 1;

        end else begin
          ctrl_xfr_state_next <= STATUS_OUT;
        end
      end

      default begin
        ctrl_xfr_state_next <= IDLE;
      end
    endcase
  end

  always @(posedge clk) begin
    if (reset) begin
      ctrl_xfr_state <= IDLE;
    end else begin
      ctrl_xfr_state <= ctrl_xfr_state_next;
    end
  end

  always @(posedge clk) begin
    in_ep_stall <= 0;

    if (out_ep_setup && out_ep_data_valid) begin
      raw_setup_data[setup_data_addr] <= out_ep_data;
      setup_data_addr <= setup_data_addr + 1;
    end

    if (setup_stage_end) begin
      case (bRequest)
        'h06 : begin
          // GET_DESCRIPTOR
          case (wValue[15:8])
            1 : begin
              // DEVICE
              rom_addr    <= 'h00;
              rom_length  <= 'h12;
            end

            2 : begin
              // CONFIGURATION
              rom_addr    <= 'h12;
              rom_length  <= 'h43;
            end

            3 : begin
              // STRING
              in_ep_stall <= 1;
              rom_addr    <= 'h00;
              rom_length  <= 'h00;
            end

            6 : begin
              // DEVICE_QUALIFIER
              in_ep_stall <= 1;
              rom_addr   <= 'h00;
              rom_length <= 'h00;
            end
            
            default : begin
              // DEVICE_QUALIFIER
              in_ep_stall <= 1;
              rom_addr   <= 'h00;
              rom_length <= 'h00;
            end

          endcase
        end

        'h05 : begin
          // SET_ADDRESS
          rom_addr   <= 'h00;
          rom_length <= 'h00;

          // we need to save the address after the status stage ends
          // this is because the status stage token will still be using
          // the old device address
          save_dev_addr <= 1;
          new_dev_addr <= wValue[6:0];
        end

        'h09 : begin
          // SET_CONFIGURATION
          rom_addr   <= 'h00;
          rom_length <= 'h00;
        end

        'h20 : begin
          // SET_LINE_CODING
          rom_addr   <= 'h00;
          rom_length <= 'h00;
        end

        'h21 : begin
          // GET_LINE_CODING
          rom_addr   <= 'h55;
          rom_length <= 'h07;
        end

        'h22 : begin
          // SET_CONTROL_LINE_STATE
          rom_addr   <= 'h00;
          rom_length <= 'h00;
        end

        'h23 : begin
          // SEND_BREAK
          rom_addr   <= 'h00;
          rom_length <= 'h00;
        end

        default begin
          rom_addr   <= 'h00;
          rom_length <= 'h00;
        end
      endcase
    end

    if (ctrl_xfr_state == DATA_IN && more_data_to_send && in_ep_grant && in_ep_data_free) begin
      rom_addr <= rom_addr + 1;
      bytes_sent <= bytes_sent + 1;
    end

    if (status_stage_end) begin
      setup_data_addr <= 0;
      bytes_sent <= 0;
      rom_addr <= 0;
      rom_length <= 0;

      if (save_dev_addr) begin
        save_dev_addr <= 0;
        dev_addr_i <= new_dev_addr;
      end
    end

    if (reset) begin
      dev_addr_i <= 0;
      setup_data_addr <= 0;
      save_dev_addr <= 0;
    end
  end


  `define CDC_ACM_ENDPOINT 2
  `define CDC_RX_ENDPOINT 1
  `define CDC_TX_ENDPOINT 1


  always @* begin
    case (rom_addr)
      // device descriptor
      'h000 : in_ep_data <= 18; // bLength
      'h001 : in_ep_data <= 1; // bDescriptorType
      'h002 : in_ep_data <= 'h00; // bcdUSB[0]
      'h003 : in_ep_data <= 'h02; // bcdUSB[1]
      'h004 : in_ep_data <= 'h02; // bDeviceClass (Communications Device Class)
      'h005 : in_ep_data <= 'h00; // bDeviceSubClass (Abstract Control Model)
      'h006 : in_ep_data <= 'h00; // bDeviceProtocol (No class specific protocol required)
      'h007 : in_ep_data <= MAX_IN_PACKET_SIZE; // bMaxPacketSize0

      'h008 : in_ep_data <= 'h50; // idVendor[0] http://wiki.openmoko.org/wiki/USB_Product_IDs
      'h009 : in_ep_data <= 'h1d; // idVendor[1]
      'h00A : in_ep_data <= 'h30; // idProduct[0]
      'h00B : in_ep_data <= 'h61; // idProduct[1]

      'h00C : in_ep_data <= 0; // bcdDevice[0]
      'h00D : in_ep_data <= 0; // bcdDevice[1]
      'h00E : in_ep_data <= 0; // iManufacturer
      'h00F : in_ep_data <= 0; // iProduct
      'h010 : in_ep_data <= 0; // iSerialNumber
      'h011 : in_ep_data <= 1; // bNumConfigurations

      // configuration descriptor
      'h012 : in_ep_data <= 9; // bLength
      'h013 : in_ep_data <= 2; // bDescriptorType
      'h014 : in_ep_data <= (9+9+5+5+4+5+7+9+7+7); // wTotalLength[0]
      'h015 : in_ep_data <= 0; // wTotalLength[1]
      'h016 : in_ep_data <= 2; // bNumInterfaces
      'h017 : in_ep_data <= 1; // bConfigurationValue
      'h018 : in_ep_data <= 0; // iConfiguration
      'h019 : in_ep_data <= 'hC0; // bmAttributes
      'h01A : in_ep_data <= 50; // bMaxPower

      // interface descriptor, USB spec 9.6.5, page 267-269, Table 9-12
      'h01B : in_ep_data <= 9; // bLength
      'h01C : in_ep_data <= 4; // bDescriptorType
      'h01D : in_ep_data <= 0; // bInterfaceNumber
      'h01E : in_ep_data <= 0; // bAlternateSetting
      'h01F : in_ep_data <= 1; // bNumEndpoints
      'h020 : in_ep_data <= 2; // bInterfaceClass (Communications Device Class)
      'h021 : in_ep_data <= 2; // bInterfaceSubClass (Abstract Control Model)
      'h022 : in_ep_data <= 0; // bInterfaceProtocol (0 = ?, 1 = AT Commands: V.250 etc)
      'h023 : in_ep_data <= 0; // iInterface

      // CDC Header Functional Descriptor, CDC Spec 5.2.3.1, Table 26
      'h024 : in_ep_data <= 5;					// bFunctionLength
	    'h025 : in_ep_data <= 'h24;					// bDescriptorType
	    'h026 : in_ep_data <= 'h00;					// bDescriptorSubtype
	    'h027 : in_ep_data <= 'h10;
      'h028 : in_ep_data <= 'h01;				// bcdCDC

	    // Call Management Functional Descriptor, CDC Spec 5.2.3.2, Table 27
	    'h029 : in_ep_data <= 5;					// bFunctionLength
	    'h02A : in_ep_data <= 'h24;					// bDescriptorType
	    'h02B : in_ep_data <= 'h01;					// bDescriptorSubtype
	    'h02C : in_ep_data <= 'h00;					// bmCapabilities
	    'h02D : in_ep_data <= 1;					// bDataInterface

	    // Abstract Control Management Functional Descriptor, CDC Spec 5.2.3.3, Table 28
	    'h02E : in_ep_data <= 4;					// bFunctionLength
	    'h02F : in_ep_data <= 'h24;					// bDescriptorType
	    'h030 : in_ep_data <= 'h02;					// bDescriptorSubtype
	    'h031 : in_ep_data <= 'h06;					// bmCapabilities

	    // Union Functional Descriptor, CDC Spec 5.2.3.8, Table 33
    	'h032 : in_ep_data <= 5;					// bFunctionLength
    	'h033 : in_ep_data <= 'h24;					// bDescriptorType
    	'h034 : in_ep_data <= 'h06;					// bDescriptorSubtype
    	'h035 : in_ep_data <= 0;					// bMasterInterface
    	'h036 : in_ep_data <= 1;					// bSlaveInterface0

    	// endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13
    	'h037 : in_ep_data <= 7;					// bLength
    	'h038 : in_ep_data <= 5;					// bDescriptorType
    	'h039 : in_ep_data <= `CDC_ACM_ENDPOINT | 'h80;		// bEndpointAddress
    	'h03A : in_ep_data <= 'h03;					// bmAttributes (0x03=intr)
    	'h03B : in_ep_data <= 8;     // wMaxPacketSize[0]
      'h03C : in_ep_data <= 0;			// wMaxPacketSize[1]
    	'h03D : in_ep_data <= 64;					// bInterval

    	// interface descriptor, USB spec 9.6.5, page 267-269, Table 9-12
    	'h03E : in_ep_data <= 9;					// bLength
    	'h03F : in_ep_data <= 4;					// bDescriptorType
    	'h040 : in_ep_data <= 1;					// bInterfaceNumber
    	'h041 : in_ep_data <= 0;					// bAlternateSetting
    	'h042 : in_ep_data <= 2;					// bNumEndpoints
    	'h043 : in_ep_data <= 'h0A;					// bInterfaceClass
    	'h044 : in_ep_data <= 'h00;					// bInterfaceSubClass
    	'h045 : in_ep_data <= 'h00;					// bInterfaceProtocol
    	'h046 : in_ep_data <= 0;					// iInterface

    	// endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13
    	'h047 : in_ep_data <= 7;					// bLength
    	'h048 : in_ep_data <= 5;					// bDescriptorType
    	'h049 : in_ep_data <= `CDC_RX_ENDPOINT;			// bEndpointAddress
    	'h04A : in_ep_data <= 'h02;					// bmAttributes (0x02=bulk)
    	'h04B : in_ep_data <= MAX_IN_PACKET_SIZE; // wMaxPacketSize[0]
      'h04C : in_ep_data <= 0;				// wMaxPacketSize[1]
    	'h04D : in_ep_data <= 0;					// bInterval

    	// endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13
    	'h04E : in_ep_data <= 7;					// bLength
    	'h04F : in_ep_data <= 5;					// bDescriptorType
    	'h050 : in_ep_data <= `CDC_TX_ENDPOINT | 'h80;			// bEndpointAddress
    	'h051 : in_ep_data <= 'h02;					// bmAttributes (0x02=bulk)

    	'h052 : in_ep_data <= MAX_OUT_PACKET_SIZE; // wMaxPacketSize[0]
      'h053 : in_ep_data <= 0;				// wMaxPacketSize[1]
    	'h054 : in_ep_data <= 0;				// bInterval

      // LINE_CODING
      'h055 : in_ep_data <= 'h80; // dwDTERate[0]
      'h056 : in_ep_data <= 'h25; // dwDTERate[1]
      'h057 : in_ep_data <= 'h00; // dwDTERate[2]
      'h058 : in_ep_data <= 'h00; // dwDTERate[3]
      'h059 : in_ep_data <= 1; // bCharFormat (1 stop bit)
      'h05A : in_ep_data <= 0; // bParityType (None)
      'h05B : in_ep_data <= 8; // bDataBits (8 bits)

      default begin
        in_ep_data <= 0;
      end
    endcase
  end

endmodule