# SPDX-License-Identifier: BSD-3-Clause
#
# This file is part of Torii-USB.
#
# Copyright (c) 2020 Great Scott Gadgets <info@greatscottgadgets.com>

''' Utilities for building USB3 descriptors into gateware. '''

from torii.hdl                          import DomainRenamer, Elaboratable, Module, Signal

from usb_construct.emitters.descriptors import DeviceDescriptorCollection

from ....stream.generator               import ConstantStreamGenerator
from ...stream                          import SuperSpeedStreamInterface

class GetDescriptorHandler(Elaboratable):
	''' Gateware that handles responding to GetDescriptor requests.

	Currently does not support descriptors in multiple languages.

	Attributes
	----------
	value: Signal(16), input
		The value field associated with the Get Descriptor request. Contains the descriptor type and index.
	length: Signal(16), input
		The length field associated with the Get Descriptor request.
		Determines the maximum amount allowed in a response.

	start: Signal(), input
		Strobe that indicates when a descriptor should be transmitted.

	tx: stream_type(), output stream
		Stream that carries our output descriptor data.
	tx_length: Signal(16), output
		The actual length of the descriptor to be sent. At SuperSpeed, used for header generation.

	stall: Signal(), output
		Strobe; pulsed if a STALL handshake should be generated, instead of a response.

	Parameters
	----------
	domain: string
		The clock domain this generator should belong to. Defaults to ''.
	stream_type: StreamInterface, or subclass
		The type of stream we'll be multiplexing.
	'''

	def __init__(self, descriptor_collection: DeviceDescriptorCollection, *,
		usb_domain = 'ss', stream_type = SuperSpeedStreamInterface):
		'''
		Parameters
		----------
		descriptor_collection: DeviceDescriptorCollection
			The DeviceDescriptorCollection containing the descriptors to use for this device.
		usb_domain: string
			The name of the domain to use for USB data exchange.
		stream_type: StreamInterface subclass
			The type of the stream to be used to carry descriptor data.
		'''
		self._domain      = usb_domain
		self._descriptors = descriptor_collection
		self._stream_type = stream_type

		#
		# I/O port
		#
		self.value     = Signal(16)
		self.length    = Signal(16)

		self.start     = Signal()

		self.tx        = stream_type()
		self.tx_length = Signal(16)
		self.stall     = Signal()

	def elaborate(self, platform):
		m = Module()

		# Collection that will store each of our descriptor-generation submodules.
		descriptor_generators = {}

		#
		# Create our constant-stream generators for each of our descriptors.
		#
		for type_number, index, raw_descriptor in self._descriptors:

			# Create the generator...
			generator = ConstantStreamGenerator(raw_descriptor,
				domain = self._domain, stream_type = self._stream_type, max_length_width = 16)
			descriptor_generators[(type_number, index)] = generator

			# ... and attach it to this module.
			m.submodules += generator

		#
		# Connect up each of our generators.
		#

		with m.Switch(self.value):

			# Generate a conditional interconnect for each of our items.
			for (type_number, index), generator in descriptor_generators.items():

				# If the value matches the given type number...
				with m.Case(type_number << 8 | index):

					# ... connect the relevant generator to our output.
					m.d.comb += [
						generator.start.eq(self.start),
						generator.max_length.eq(self.length),
					]

					# Buffer the output stream to improve timings.
					with m.If(~self.tx.valid.any() | self.tx.ready):
						m.d.sync += [
							self.tx.stream_eq(generator.stream, omit = {'ready'}),
							self.tx_length.eq(generator.output_length)
						]
						m.d.comb += [
							generator.stream.ready.eq(1),
						]

			# If none of our descriptors match, stall any request that comes in.
			with m.Default():
				m.d.comb += self.stall.eq(self.start)

		# Convert our sync domain to the domain requested by the user, if necessary.
		if self._domain != 'sync':
			m = DomainRenamer(sync = self._domain)(m)

		return m
