// © 2017 - 2020 Raptor Engineering, LLC
//
// Released under the terms of the AGPL v3
// See the LICENSE file for full details
//
// SPI mode 3 transfer
// Single data rate, standard unidirectional transfer
module spi_master_interface(
		input wire platform_clock,
		input wire reset,
		input wire [7:0] tx_data,
		output reg [7:0] rx_data,
		input wire hold_ss_active,
		input wire cycle_start,
		output reg transaction_complete,

		output reg spi_clock,
		output reg spi_mosi,
		input wire spi_miso,
		output reg spi_ss_n,
		output reg spi_hold_n,
		output reg spi_wp_n
	);

	reg [3:0] transfer_state = 0;
	reg [7:0] data_shift_out = 0;

	reg [7:0] state_iteration = 0;
	reg ss_state_at_idle = 1'b1;

	reg cycle_start_reg = 0;
	reg hold_ss_active_reg = 0;
	reg [7:0] tx_data_reg = 0;

	always @(posedge platform_clock) begin
		if (reset) begin
			transfer_state <= 0;
			state_iteration <= 0;
			transaction_complete <= 1;
			spi_clock <= 1'b1;
			spi_mosi <= 1'b0;
			spi_ss_n <= 1'b1;
			spi_hold_n <= 1'b1;
			spi_wp_n <= 1'b1;
			ss_state_at_idle <= 1'b1;
		end else begin
			case (transfer_state)
				0: begin
					// Idle state
					spi_clock <= 1'b1;
					spi_mosi <= 1'b0;
					spi_ss_n <= ss_state_at_idle;
					transaction_complete <= 0;
					state_iteration <= 0;
					if (cycle_start_reg) begin
						// Set up transfer
						rx_data <= 0;
						data_shift_out <= tx_data_reg;

						// Drive frame start
						spi_clock <= 1'b1;
						spi_ss_n <= 1'b0;

						transfer_state <= 1;
					end else begin
						if (!hold_ss_active_reg) begin
							ss_state_at_idle <= 1'b1;
						end
						transfer_state <= 0;
					end
				end
				1: begin
					// Shift out TX byte / toggle clock
					spi_clock <= 1'b0;
					spi_ss_n <= 1'b0;
					spi_mosi <= data_shift_out[7];
					data_shift_out <= data_shift_out << 1;
					transfer_state <= 2;
				end
				2: begin
					// Shift in RX byte / toggle clock
					spi_clock <= 1'b1;
					spi_ss_n <= 1'b0;
					rx_data <= {rx_data[6:0], spi_miso};
					state_iteration <= state_iteration + 1;
					if (state_iteration >= 7) begin
						if (hold_ss_active_reg) begin
							ss_state_at_idle <= 1'b0;
						end else begin
							ss_state_at_idle <= 1'b1;
						end
						transaction_complete <= 1;
						transfer_state <= 3;
					end else begin
						transfer_state <= 1;
					end
				end
				3: begin
					// Wait for host to deassert transaction request
					if (!cycle_start_reg) begin
						transaction_complete <= 0;
						transfer_state <= 0;
					end
					spi_clock <= 1'b1;
					spi_mosi <= 1'b0;
					spi_ss_n <= ss_state_at_idle;
				end
				default: begin
					transfer_state <= 0;
				end
			endcase
		end

		// Avoid glitches from signals crossing clock domains
		cycle_start_reg <= cycle_start;
		hold_ss_active_reg = hold_ss_active;
		tx_data_reg <= tx_data;
	end

endmodule

// SPI mode 3 transfer
// Single data rate, quad transfer
// This module assumes it is on the same clock domain as the external control logic
module spi_master_interface_quad(
		input wire platform_clock,
		input wire reset,
		input wire [31:0] tx_data,
		output reg [31:0] rx_data,
		input wire [7:0] dummy_cycle_count,
		input wire hold_ss_active,
		input wire qspi_mode_active,
		input wire qspi_transfer_mode,		// 0 == byte transfer, 1 == word transfer
		input wire qspi_transfer_direction,	// 0 == read (input), 1 == write (output)
		input wire cycle_start,
		output reg transaction_complete,

		output reg spi_clock,
		output reg spi_d0_out,
		input wire spi_d0_in,
		output reg spi_d1_out,
		input wire spi_d1_in,
		output reg spi_d2_out,
		input wire spi_d2_in,
		output reg spi_d3_out,
		input wire spi_d3_in,
		output reg spi_ss_n,
		output reg spi_data_direction,		// 0 == tristate (input), 1 == driven (output)
		output reg spi_quad_mode_pin_enable
	);

	reg [3:0] transfer_state = 0;
	reg [31:0] data_shift_out = 0;

	reg [7:0] state_iteration = 0;
	reg ss_state_at_idle = 1'b1;

	reg [7:0] dummy_cycle_count_reg = 0;
	reg [7:0] dummy_cycle_ctr = 0;

	reg qspi_transfer_mode_reg = 0;
	reg [3:0] qspi_transfer_cycle_stop_value = 1;

	always @(posedge platform_clock) begin
		if (reset) begin
			transfer_state <= 0;
			state_iteration <= 0;
			transaction_complete <= 1;
			dummy_cycle_count_reg <= 0;
			spi_clock <= 1'b1;
			spi_d0_out <= 1'b0;
			spi_d1_out <= 1'b1;
			spi_d2_out <= 1'b1;
			spi_d3_out <= 1'b1;
			spi_ss_n <= 1'b1;
			ss_state_at_idle <= 1'b1;
			spi_data_direction <= 1'b0;
			spi_quad_mode_pin_enable <= 1'b0;
		end else begin
			case (transfer_state)
				0: begin
					// Idle state
					spi_clock <= 1'b1;
					spi_d0_out <= 1'b0;
					spi_ss_n <= ss_state_at_idle;
					transaction_complete <= 0;
					state_iteration <= 0;
					if (cycle_start) begin
						// Set up transfer
						rx_data <= 0;
						dummy_cycle_count_reg <= dummy_cycle_count;
						data_shift_out <= tx_data;
						spi_quad_mode_pin_enable <= qspi_mode_active;
						spi_data_direction <= qspi_transfer_direction;
						qspi_transfer_mode_reg <= qspi_transfer_mode;
						if (qspi_transfer_mode == 0) begin
							// Byte transfer (2 nibbles per word)
							qspi_transfer_cycle_stop_value <= 1;
						end else begin
							// Word transfer (4 bytes / 8 nibbles per word)
							qspi_transfer_cycle_stop_value <= 7;
						end

						// Drive frame start
						spi_clock <= 1'b1;
						spi_ss_n <= 1'b0;

						transfer_state <= 1;
					end else begin
						if (!hold_ss_active) begin
							ss_state_at_idle <= 1'b1;
						end
						spi_quad_mode_pin_enable <= 0;
						transfer_state <= 0;
					end
				end
				1: begin
					// Shift out TX byte / toggle clock
					spi_clock <= 1'b0;
					spi_ss_n <= 1'b0;
					if (spi_quad_mode_pin_enable) begin
						if (qspi_transfer_mode_reg) begin
							spi_d3_out <= data_shift_out[31];
							spi_d2_out <= data_shift_out[30];
							spi_d1_out <= data_shift_out[29];
							spi_d0_out <= data_shift_out[28];
						end else begin
							spi_d3_out <= data_shift_out[7];
							spi_d2_out <= data_shift_out[6];
							spi_d1_out <= data_shift_out[5];
							spi_d0_out <= data_shift_out[4];
						end
						data_shift_out <= data_shift_out << 4;
					end else begin
						spi_d0_out <= data_shift_out[7];
						data_shift_out <= data_shift_out << 1;
					end
					transfer_state <= 2;
				end
				2: begin
					// Shift in RX byte / toggle clock
					spi_clock <= 1'b1;
					spi_ss_n <= 1'b0;
					state_iteration <= state_iteration + 1;
					if (spi_quad_mode_pin_enable) begin
						if (qspi_transfer_mode_reg) begin
							rx_data <= {rx_data[27:0], spi_d3_in, spi_d2_in, spi_d1_in, spi_d0_in};
						end else begin
							rx_data <= {rx_data[3:0], spi_d3_in, spi_d2_in, spi_d1_in, spi_d0_in};
						end
						if (state_iteration >= qspi_transfer_cycle_stop_value) begin
							if (hold_ss_active) begin
								ss_state_at_idle <= 1'b0;
							end else begin
								ss_state_at_idle <= 1'b1;
							end
							if (dummy_cycle_count_reg == 0) begin
								transaction_complete <= 1;
								transfer_state <= 3;
							end else begin
								dummy_cycle_ctr <= 0;
								transfer_state <= 4;
							end
						end else begin
							transfer_state <= 1;
						end
					end else begin
						rx_data <= {rx_data[6:0], spi_d1_in};
						if (state_iteration >= 7) begin
							if (hold_ss_active) begin
								ss_state_at_idle <= 1'b0;
							end else begin
								ss_state_at_idle <= 1'b1;
							end
							transaction_complete <= 1;
							if (dummy_cycle_count_reg == 0) begin
								transfer_state <= 3;
							end else begin
								dummy_cycle_ctr <= 0;
								transfer_state <= 4;
							end
						end else begin
							transfer_state <= 1;
						end
					end
				end
				3: begin
					// Wait for host to deassert transaction request
					if (!cycle_start) begin
						transaction_complete <= 0;
						transfer_state <= 0;
					end
					spi_clock <= 1'b1;
					spi_d0_out <= 1'b0;
					spi_d1_out <= 1'b1;
					spi_d2_out <= 1'b1;
					spi_d3_out <= 1'b1;
					spi_ss_n <= ss_state_at_idle;
					spi_data_direction <= 0;
					spi_quad_mode_pin_enable <= 0;
				end
				4: begin
					// Increment counter / toggle clock
					spi_clock <= 1'b0;
					dummy_cycle_ctr <= dummy_cycle_ctr + 1;

					transfer_state <= 5;
				end
				5: begin
					if (dummy_cycle_ctr < dummy_cycle_count_reg) begin
						transfer_state <= 4;
					end else begin
						transaction_complete <= 1;
						transfer_state <= 3;
					end

					// Toggle clock
					spi_clock <= 1'b1;
				end
				default: begin
					transfer_state <= 0;
				end
			endcase
		end
	end

endmodule