/**********************************************************
 * 
 * USB Video config
 * (c) Bigscreen, Inc. 2024
 * Created by: David Miller
 *
 * Handles the control transactions for a USB Video Class
 * (UVC) device. 
 *
 *********************************************************/

module usb_video_config #(
    parameter VIDEO_CONTROL_INTERFACE = 8'h00,
    parameter VIDEO_STREAMING_INTERFACE = 8'h01
)
(
    input               PHY_CLKOUT, // clock
    input               RESET_IN, // reset (active high)
    input               setup_active_i, // true when receiving a setup packet
    input               usb_rxval_i, // data on rxdat is valid
    input               usb_rxact_i, // high whenever receiving an OUT packet
    input   [7:0]       usb_rxdat_i, // data received from USB host (OUT or SETUP)
    output              usb_rxrdy_o, // set true when ready to receive an OUT packet
    input               usb_txact_i, 
    input               usb_txpop_i,
    output  [11:0]      usb_txdat_len_o,
    output  [7:0]       usb_txdat_o,
    output              usb_txval_o // set true when sending a setup response
);

// local registers to get rid of "Procedural assignment to a non-register" errors
reg         my_rxrdy;
reg [11:0]  my_txdat_len;
reg [7:0]   my_txdat;
reg         my_txval;

assign usb_rxrdy_o = my_rxrdy;
assign usb_txdat_len_o = my_txdat_len;
assign usb_txdat_o = my_txdat;
assign usb_txval_o = my_txval;


/* 
***** Notes: *****
When receiving a SETUP packet, the setup_active_i signal
will be high. Otherwise it acts exactly the same as an
OUT packet. 
Receiving data (OUT or SETUP):
- rxdat should be latched in the cycle when rxval is high
- OUT only: rxact is high until the end of the packet
    rxrdy must be high to receive data even if it is
    part of a control transfer. The OUT packet of a control
    transfer is treated just like any other OUT packet, 
    except that rxpktval is not used (note that it's not present
    in our input list, above)
- SETUP only: setup_active is high for the duration of the
    setup packet. rxrdy is not used.
Sending data:
- Set txdat_len to the number of bytes to send
- Set the first byte on txdat
- Wait on txpop. Each cycle it is high, proceed to the
    next byte to send.
- For control transactions (started with a SETUP) set
    txval high for duration of all bytes transferred
*/

// UVC class requests:
localparam  RC_UNDEFINED    = 8'h00,
            SET_CUR         = 8'h01,
            SET_CUR_ALL     = 8'h11,
            GET_CUR         = 8'h81,
            GET_MIN         = 8'h82,
            GET_MAX         = 8'h83,
            GET_RES         = 8'h84,
            GET_LEN         = 8'h85,
            GET_INFO        = 8'h86,
            GET_DEF         = 8'h87,
            GET_CUR_ALL     = 8'h91,
            GET_MIN_ALL     = 8'h92,
            GET_MAX_ALL     = 8'h93,
            GET_RES_ALL     = 8'h94,
            GET_DEF_ALL     = 8'h97;
// UVC VideoControl interface control selectors
localparam  VC_CONTROL_UNDEFINED            = 8'h00,
            VC_VIDEO_MODE_POWER_CONTROL     = 8'h01,
            VC_REQUEST_ERROR_CODE_CONTROL   = 8'h02;
// UVC VideoStreaming interface control selectors
localparam  VS_CONTROL_UNDEFINED            = 8'h00,
            VS_PROBE_CONTROL                = 8'h01,
            VS_COMMIT_CONTROL               = 8'h02,
            VS_STILL_PROBE_CONTROL          = 8'h03,
            VS_STILL_COMMIT_CONTROL         = 8'h04,
            VS_STILL_IMAGE_TRIGGER_CONTROL  = 8'h05,
            VS_STREAM_ERROR_CODE_CONTROL    = 8'h06,
            VS_GENERATE_KEY_FRAME_CONTROL   = 8'h07,
            VS_UPDATE_FRAME_SEGMENT_CONTROL = 8'h08,
            VS_SYNCH_DELAY_CONTROL          = 8'h09;

/*
local variables:
- videocontrol error code (fixed to 0 for no error)
- video power mode (fixed to 0 for full power mode)
- videostreaming error code (fixed to 0 for no error)
- probe / commit values:
-- bmHint (fixed to 0, only set by host)
-- bFormatIndex (fixed to 1 for our single frame type)
-- bFrameIndex (fixed to 1 for the only resolution we offer)
// -- dwFrameInterval (fixed to 83333 for 120fps (100ns units))  0x00.01.45.85
// -- dwFrameInterval (fixed to 111111 for 90fps (100ns units))  0x00.01.B2.07
// -- dwFrameInterval (fixed to 142857 for 70fps (100ns units))  0x00.02.2E.09
// -- dwFrameInterval (fixed to 333333 for 30fps (100ns units))  0x00.05.16.15
-- wKeyFrameRate (unused, fixed to 0)
-- wPFrameRate (unused, fixed to 0)
-- wCompQuality (unused, fixed to 0)
-- wCompWindowSize (unused, fixed to 0)
-- wDelay (fixed to 0)
-- dwMaxVideoFrameSize (fixed to 1572864) ?? maybe this will work
-- dwMaxPayloadTransferSize (fixed to 512, one bulk packet is a max payload)
-- dwClockFrequency (fixed to 0)
-- bmFramingInfo (fixed to 0x03 to denote that frame ID and end of frame are used)
-- bPreferredVersion (fixed to 1 for payload format version)
-- bMinVersion (also fixed to 1)
-- bMaxVersion (you guessed it, also 1)
-- bUsage (fixed to 0, not used)
-- bBitDepthLuma (fixed to 8)
-- bmSettings (fixed to 0)
-- bMaxNumberOfRefRamesPlus1 (fixed to 0)
-- bmRateControlModes (fixed to 0)
-- bmLayoutPerStream (fixed to 0)

for now all of those values are constants.
*/

reg [7:0] probe_commit_control [47:0];
initial begin
    // bmHint, 2 bytes
    probe_commit_control[0] = 8'h00;
    probe_commit_control[1] = 8'h00;
    // bFormatIndex, 1 byte
    probe_commit_control[2] = 8'h01;
    // bFrameIndex, 1 byte
    probe_commit_control[3] = 8'h01;
    // dwFrameInterval, 4 bytes
    probe_commit_control[4] = 8'h09;
    probe_commit_control[5] = 8'h2E;
    probe_commit_control[6] = 8'h02;
    probe_commit_control[7] = 8'h00;
    // wKeyFrameRate, 2 bytes
    probe_commit_control[8] = 8'h00;
    probe_commit_control[9] = 8'h00;
    // wPFrameRate, 2 bytes
    probe_commit_control[10] = 8'h00;
    probe_commit_control[11] = 8'h00;
    // wCompQuality, 2 bytes
    probe_commit_control[12] = 8'h00;
    probe_commit_control[13] = 8'h00;
    // wCompWindow, 2 bytes
    probe_commit_control[14] = 8'h00;
    probe_commit_control[15] = 8'h00;
    // wDelay, 2 bytes
    probe_commit_control[16] = 8'h00;
    probe_commit_control[17] = 8'h00;
    // dwMaxVideoFrameSize, 4 bytes
    /* for 400x400
    probe_commit_control[18] = 8'h00;
    probe_commit_control[19] = 8'he2;
    probe_commit_control[20] = 8'h04;
    probe_commit_control[21] = 8'h00;
    */
    /* for 800x400 */
    probe_commit_control[18] = 8'h00;
    probe_commit_control[19] = 8'h00;
    probe_commit_control[20] = 8'h18;
    probe_commit_control[21] = 8'h00;
    // dwMaxPayloadTransferSize, 4 bytes
    probe_commit_control[22] = 8'h00;
    probe_commit_control[23] = 8'h02; // 512 bytes for bulk
//    probe_commit_control[23] = 8'h04; // 1024 bytes for isochronous
    probe_commit_control[24] = 8'h00;
    probe_commit_control[25] = 8'h00;
    // dwClockFrequency, 4 bytes
    probe_commit_control[26] = 8'h00;
    probe_commit_control[27] = 8'h00;
    probe_commit_control[28] = 8'h00;
    probe_commit_control[29] = 8'h00;
    // bmFramingInfo, 1 byte
    probe_commit_control[30] = 8'h03;
    // bPreferredVersion, 1 byte
    probe_commit_control[31] = 8'h01;
    // bMinVersion, 1 byte
    probe_commit_control[32] = 8'h01;
    // bMaxVersion, 1 byte
    probe_commit_control[33] = 8'h01;
    // bUsage, 1 byte
    probe_commit_control[34] = 8'h00;
    // bBitDepthLuma, 1 byte
    probe_commit_control[35] = 8'h08;
    // bmSettings, 1 byte
    probe_commit_control[36] = 8'h00;
    // bMaxNumberOfRefFramesPlus1, 1 byte
    probe_commit_control[37] = 8'h00;
    // bmRateControlModes, 2 bytes
    probe_commit_control[38] = 8'h00;
    probe_commit_control[39] = 8'h00;
    // bmLayoutPerStream, 8 bytes
    probe_commit_control[40] = 8'h00;
    probe_commit_control[41] = 8'h00;
    probe_commit_control[42] = 8'h00;
    probe_commit_control[43] = 8'h00;
    probe_commit_control[44] = 8'h00;
    probe_commit_control[45] = 8'h00;
    probe_commit_control[46] = 8'h00;
    probe_commit_control[47] = 8'h00;
end
localparam  PROBE_COMMIT_LENGTH     = 8'd48,
            ANY_INFO_LENGTH         = 8'd1,
            POWER_CONTROL_LENGTH    = 8'd1,
            ERROR_CODE_LENGTH       = 8'd1;



reg [7:0] videocontrol_error_code = 8'h00;
reg [7:0] videostreaming_error_code = 8'h00;
reg [7:0] videocontrol_power_mode = 8'h00;
reg [7:0] any_info_code = 8'h03; // get and set allowed, others disallowed

// state machine variables
reg [7:0] stage;
reg [7:0] sub_stage;

// data variables, for selecting the control request. this is the setup packet contents
reg [7:0] bmRequestType;
reg [7:0] bRequest;
reg [15:0] wValue;
reg [15:0] wIndex;
reg [15:0] wLength;

reg usb_rxact_d0;
wire usb_rxact_falling = usb_rxact_d0 & (~usb_rxact_i);

always @(posedge PHY_CLKOUT,posedge RESET_IN) begin
    if (RESET_IN) begin
        usb_rxact_d0 <= 1'b0;
    end else begin
        usb_rxact_d0 <= usb_rxact_i;
    end
end


always @(posedge PHY_CLKOUT,posedge RESET_IN) begin
    if (RESET_IN) begin
        stage <= 8'd0;
        sub_stage <= 8'd0;
        bmRequestType <= 8'd0;
        bRequest <= 8'd0;
        wValue <= 16'd0;
        wIndex <= 16'd0;
        wLength<= 16'd0;
        my_rxrdy <= 1'b0;
        my_txdat_len <= 12'd0; 
        // my_txdat <= 8'd0;
        // my_txval <= 1'b0;
    end
    else begin
        if (setup_active_i) begin
                // receiving the setup packet
            if (usb_rxval_i) begin
                // this is a valid byte on usb_rxdat_i
                case(stage)
                8'd0: begin
                    bmRequestType <= usb_rxdat_i;
                    stage <= stage + 8'd1;
                    sub_stage <= 8'd0;
                    // my_txval <= 1'b0;
                end
                8'd1: begin
                    bRequest <= usb_rxdat_i;
                    stage <= stage + 8'd1;
                end
                8'd2: begin
                    wValue[7:0] <= usb_rxdat_i;
                    stage <= stage + 8'd1;
                end
                8'd3: begin
                    wValue[15:8] <= usb_rxdat_i;
                    stage <= stage + 8'd1;
                end
                8'd4: begin
                    wIndex[7:0] <= usb_rxdat_i;
                    stage <= stage + 8'd1;
                end

                8'd5: begin
                    wIndex[15:8] <= usb_rxdat_i;
                    stage <= stage + 8'd1;
                end
                8'd6: begin
                    wLength[7:0] <= usb_rxdat_i;
                    stage <= stage + 8'd1;
                end
                8'd7: begin
                    wLength[15:8] <= usb_rxdat_i;
                    stage <= stage + 8'd1;
                end
                default: begin
                    stage <= stage;
                end
                endcase
            end
        end else begin
            stage <= 8'd0;
            if(wIndex[7:0] == VIDEO_CONTROL_INTERFACE) begin
                case(bRequest)
                GET_CUR: begin
                    // progression through this stage done by txpop
                    if(usb_txpop_i) 
                        sub_stage <= sub_stage + 8'd1;
                    // how to set the txval, txdat, and txdat_len outputs depends on the requested control selector
                    if(wValue[15:8] == VC_VIDEO_MODE_POWER_CONTROL) begin
                        my_txdat_len <= POWER_CONTROL_LENGTH;
                        if(sub_stage < POWER_CONTROL_LENGTH) begin
                            // my_txval <= 1'b1;
                            // my_txdat <= videocontrol_power_mode;
                        end else begin
                            // my_txval <= 1'b0;
                            // my_txdat <= 8'd0;
                        end
                        
                    end else if(wValue[15:8] == VC_REQUEST_ERROR_CODE_CONTROL) begin
                        my_txdat_len <= ERROR_CODE_LENGTH;
                        if(sub_stage < ERROR_CODE_LENGTH) begin
                            // my_txval <= 1'b1;
                            // my_txdat <= videocontrol_error_code;
                        end else begin
                            // my_txval <= 1'b0;
                            // my_txdat <= 8'd0;
                        end
                    end
                end 
                GET_INFO: begin
                    // progression through this stage done by txpop
                    if(usb_txpop_i) 
                        sub_stage <= sub_stage + 8'd1;
                    my_txdat_len <= ANY_INFO_LENGTH;
                    // doesn't matter which control selector, all the info responses are the same
                    if(sub_stage < ANY_INFO_LENGTH) begin
                        // my_txval <= 1'b1;
                        // my_txdat <= any_info_code;
                    end else begin
                        // my_txval <= 1'b0;
                        // my_txdat <= 8'd0;
                    end
                end 
                SET_CUR: begin
                    // we don't actually use any SET_CUR. it would only be valid for power control,
                    // and we don't use it for anything!
                    // but it's nice to see the data so we can set rxrdy to receive it
                    // progression now done by rxval
                    my_txdat_len <= 12'b0;
                    if(usb_rxact_falling)
                        my_rxrdy <= 1'b0;
                    else
                        my_rxrdy <= 1'b1;
                    if(usb_rxval_i) sub_stage <= sub_stage + 8'd1;
                end
                endcase
            end else if(wIndex[7:0] == VIDEO_STREAMING_INTERFACE) begin

                case(bRequest)
                GET_CUR, 
                GET_MIN,
                GET_MAX,
                GET_DEF,
                GET_RES: // probably not the correct values, but rarely asked for by the PC.
                begin
                    // progression through this stage done by txpop
                    if(usb_txpop_i) 
                        sub_stage <= sub_stage + 8'd1;
                    if(wValue[15:8] == VS_STREAM_ERROR_CODE_CONTROL) begin
                        my_txdat_len <= ERROR_CODE_LENGTH;
                        if(sub_stage < ERROR_CODE_LENGTH) begin
                            // my_txval <= 1'b1;
                            // my_txdat <= videostreaming_error_code;
                        end else begin
                            // my_txval <= 1'b0;
                            // my_txdat <= 8'd0;
                        end
                    end else if(wValue[15:8] == VS_PROBE_CONTROL) begin
                        my_txdat_len <= PROBE_COMMIT_LENGTH;
                        if(sub_stage < PROBE_COMMIT_LENGTH) begin
                            // my_txval <= 1'b1;
                            // my_txdat <= probe_commit_control[sub_stage];
                        end else begin
                            // my_txval <= 1'b0;
                            // my_txdat <= 8'd0;
                        end
                    end else if(wValue[15:8] == VS_COMMIT_CONTROL) begin
                        my_txdat_len <= PROBE_COMMIT_LENGTH;
                        if(sub_stage < PROBE_COMMIT_LENGTH) begin
                            // my_txval <= 1'b1;
                            // my_txdat <= probe_commit_control[sub_stage];
                        end else begin
                            // my_txval <= 1'b0;
                            // my_txdat <= 8'd0;
                        end
                    end
                end
                GET_LEN: begin
                    // progression through this stage done by txpop
                    if(usb_txpop_i) 
                        sub_stage <= sub_stage + 8'd1;
                    my_txdat_len <= 8'd1;
                    if(sub_stage < 8'd1) begin
                        // my_txval <= 1'b1;
                        if((wValue[15:8] == VS_PROBE_CONTROL) || (wValue[15:8] == VS_COMMIT_CONTROL)) begin
                            // my_txdat <= PROBE_COMMIT_LENGTH;
                        end else begin
                            // all others unsupported
                            // my_txdat <= 8'd0;
                        end
                    end else begin
                        // my_txval <= 1'b0;
                        // my_txdat <= 8'd0;
                    end
                end
                GET_INFO: begin
                    // progression through this stage done by txpop
                    if(usb_txpop_i) 
                        sub_stage <= sub_stage + 8'd1;
                    my_txdat_len <= ANY_INFO_LENGTH;
                    // doesn't matter which control selector, all the info responses are the same
                    if(sub_stage < ANY_INFO_LENGTH) begin
                        // my_txval <= 1'b1;
                        // my_txdat <= any_info_code;
                    end else begin
                        // my_txval <= 1'b0;
                        // my_txdat <= 8'd0;
                    end
                end
                SET_CUR: begin
                    // we don't actually use any SET_CUR. 
                    // maybe when we have more than one frame format / resolution.
                    // but it's nice to see the data so we can set rxrdy to receive it
                    // progression now done by rxval
                    my_txdat_len <= 12'b0;
                    if(usb_rxact_falling)
                        my_rxrdy <= 1'b0;
                    else
                        my_rxrdy <= 1'b1;
                    if(usb_rxval_i) sub_stage <= sub_stage + 8'd1;
                end
                endcase
            end // videostreaming interface
        end // not setup active
    end // not reset
end // always block

always @(*) begin
    if(RESET_IN) begin
        my_txval = 1'b0;
    end else begin
        if(setup_active_i) begin
            my_txval = 1'b0;
        end else begin
            if(sub_stage < my_txdat_len) begin
                my_txval = 1'b1;
            end else begin
                my_txval = 1'b0;
            end
        end
    end
end


always @(*) begin
    if(RESET_IN) begin
        my_txdat = 8'b0;
    end else begin
        case(wIndex[7:0])
        VIDEO_CONTROL_INTERFACE: begin
            case(bRequest)
            GET_CUR: begin
                if(wValue[15:8] == VC_VIDEO_MODE_POWER_CONTROL) begin
                    if(sub_stage < POWER_CONTROL_LENGTH)
                        my_txdat = videocontrol_power_mode;
                    else 
                        my_txdat = 8'b0;
                end else if(wValue[15:8] == VC_REQUEST_ERROR_CODE_CONTROL) begin
                    if(sub_stage < ERROR_CODE_LENGTH) 
                        my_txdat = videocontrol_error_code;
                    else
                        my_txdat = 8'b0;
                end else
                    my_txdat = 8'b0;
            end
            GET_INFO: begin
                if(sub_stage < ANY_INFO_LENGTH) begin
                    my_txdat = any_info_code;
                end else begin
                    my_txdat = 8'b0;
                end
            end 
            default: begin
                my_txdat = 8'b0;
            end
            endcase
        end
        VIDEO_STREAMING_INTERFACE: begin
            case(bRequest)
            GET_CUR, 
            GET_MIN,
            GET_MAX,
            GET_DEF,
            GET_RES: 
            begin
                if(wValue[15:8] == VS_STREAM_ERROR_CODE_CONTROL) begin
                    if(sub_stage < ERROR_CODE_LENGTH) begin
                        my_txdat = videostreaming_error_code;
                    end else begin
                        my_txdat = 8'b0;
                    end
                end else if(wValue[15:8] == VS_PROBE_CONTROL) begin
                    if(sub_stage < PROBE_COMMIT_LENGTH) begin
                        my_txdat = probe_commit_control[sub_stage];
                    end else begin
                        my_txdat = 8'b0;
                    end
                end else if(wValue[15:8] == VS_COMMIT_CONTROL) begin
                    if(sub_stage < PROBE_COMMIT_LENGTH) begin
                        my_txdat = probe_commit_control[sub_stage];
                    end else begin
                        my_txdat = 8'b0;
                    end
                end else
                    my_txdat = 8'b0;
            end
            GET_LEN: begin
                if(sub_stage < 8'd1) begin
                    if((wValue[15:8] == VS_PROBE_CONTROL) || (wValue[15:8] == VS_COMMIT_CONTROL)) begin
                        my_txdat = PROBE_COMMIT_LENGTH;
                    end else begin
                        // all others unsupported
                        my_txdat = 8'b0;
                    end
                end else begin
                    my_txdat = 8'b0;
                end
            end
            GET_INFO: begin
                if(sub_stage < ANY_INFO_LENGTH) begin
                    my_txdat = any_info_code;
                end else begin
                    my_txdat = 8'b0;
                end
            end
            default:
                my_txdat = 8'b0;

            endcase
        end
        default: begin
            my_txdat = 8'b0;
        end
        endcase
    end
end








endmodule
