/****************************************************************************
 *
 *   Copyright (c) 2020 PX4 Development Team. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name PX4 nor the names of its contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************/

#pragma once

#include <stdint.h>
#include <board_config.h>

#if defined(CONFIG_SPI)

/*
 * Helper macros to handle device ID's. They are used to match drivers against SPI buses and chip-select signals.
 * They match with corresponding definitions in NuttX.
 * 'type' is typically PX4_SPI_DEVICE_ID for PX4 drivers, and 'index' is used for the driver (DRV_*) or chip-select index.
 */
#define PX4_SPIDEV_ID(type, index)  ((((type) & 0xffff) << 16) | ((index) & 0xffff))
#define PX4_SPI_DEVICE_ID         (1 << 12)
#define PX4_SPI_DEV_ID(devid)     ((devid) & 0xffff)
#define PX4_SPIDEVID_TYPE(devid) (((uint32_t)(devid) >> 16) & 0xffff)

typedef uint32_t spi_drdy_gpio_t;

#define SPI_BUS_MAX_DEVICES 6
struct px4_spi_bus_device_t {
	uint32_t cs_gpio; ///< chip-select GPIO (0 if this device is not used)
	spi_drdy_gpio_t drdy_gpio; ///< data ready GPIO (0 if not set)
	uint32_t devid; ///< SPIDEV_ID(type,index). For PX4 devices on NuttX: index is the device type, and for external buses the CS index
	uint16_t devtype_driver; ///< driver device type, e.g. DRV_IMU_DEVTYPE_ICM20689 (on NuttX: PX4_SPI_DEV_ID(devid) == devtype_driver)
};

struct px4_spi_bus_devices_t {
	px4_spi_bus_device_t devices[SPI_BUS_MAX_DEVICES];
};

struct px4_spi_bus_t {
	px4_spi_bus_device_t devices[SPI_BUS_MAX_DEVICES];
	int bus{-1}; ///< physical bus number (1, ...) (-1 means this is unused)
	uint32_t power_enable_gpio{0}; ///< GPIO (if non-zero) to control the power of the attached devices on this bus (0 means power is off)
	bool is_external; ///< static external configuration. Use px4_spi_bus_external() to check if a bus is really external
	bool requires_locking; ///< whether the bus should be locked during transfers (true if NuttX drivers access the bus)
};


struct px4_spi_bus_all_hw_t {
	px4_spi_bus_t buses[SPI_BUS_MAX_BUS_ITEMS];
	int board_hw_version_revision{-1}; ///< 0=default, >0 for a specific revision (see board_get_hw_version & board_get_hw_revision), -1=unused
};

#if BOARD_NUM_SPI_CFG_HW_VERSIONS > 1
/**
 * initialze px4_spi_buses from px4_spi_buses_all_hw and the hardware version.
 * Call this on early boot before anything else accesses px4_spi_buses (e.g. from stm32_spiinitialize).
 */
__EXPORT void px4_set_spi_buses_from_hw_version();

__EXPORT extern const px4_spi_bus_all_hw_t
px4_spi_buses_all_hw[BOARD_NUM_SPI_CFG_HW_VERSIONS]; ///< board-specific SPI bus configuration all hw versions

__EXPORT extern const px4_spi_bus_t *px4_spi_buses; ///< board-specific SPI bus configuration for current board revision
#else
static inline void px4_set_spi_buses_from_hw_version() {}
__EXPORT extern const px4_spi_bus_t px4_spi_buses[SPI_BUS_MAX_BUS_ITEMS]; ///< board-specific SPI bus configuration
#endif


/**
 * Find a SPI bus given a device ID. Note: only internal buses are checked.
 * @return the bus or -1
 */
__EXPORT int px4_find_spi_bus(uint32_t devid);

/**
 * Check if a bus requires locking during a SPI transfer (because it is potentially accessed by different threads)
 */
__EXPORT bool px4_spi_bus_requires_locking(int bus);

/**
 * runtime-check if a board has a specific bus as external.
 * This can be overridden by a board to add run-time checks.
 */
__EXPORT bool px4_spi_bus_external(const px4_spi_bus_t &bus);

/**
 * runtime-check if a board has a specific bus as external.
 */
static inline bool px4_spi_bus_external(int bus)
{
	for (int i = 0; i < SPI_BUS_MAX_BUS_ITEMS; ++i) {
		if (px4_spi_buses[i].bus == bus) {
			return px4_spi_bus_external(px4_spi_buses[i]);
		}
	}

	return true;
}


/**
 * @class SPIBusIterator
 * Iterate over configured SPI buses by the board
 */
class SPIBusIterator
{
public:
	enum class FilterType {
		InternalBus, ///< specific or all internal buses
		ExternalBus, ///< specific external bus + CS index
	};

	/**
	 * Constructor
	 * Note: only for devices of type PX4_SPI_DEVICE_ID
	 * @param filter
	 * @param devid_driver_index DRV_*
	 * @param chipselect pin of SPIInternal (-1=all) or chip-select index of SPIExternal starting from 1 (optional)
	 * @param bus starts with 1 (-1=all, but only for internal). Numbering for internal is arch-specific, for external
	 *            it is the n-th external bus.
	 */
	SPIBusIterator(FilterType filter, uint16_t devid_driver_index, int16_t chipselect = -1, int bus = -1)
		: _filter(filter), _devid_driver_index(devid_driver_index),
		  _chipselect(chipselect),
		  _bus(filter == FilterType::ExternalBus && bus == -1 ? 1 : bus) {}

	bool next();

	const px4_spi_bus_t &bus() const { return px4_spi_buses[_index]; }
	spi_drdy_gpio_t DRDYGPIO() const { return px4_spi_buses[_index].devices[_bus_device_index].drdy_gpio; }

	uint32_t devid() const { return px4_spi_buses[_index].devices[_bus_device_index].devid; }

	int externalBusIndex() const { return _external_bus_counter; }

	bool external() const { return px4_spi_bus_external(bus()); }

	int busDeviceIndex() const { return _bus_device_index; }

private:
	const FilterType _filter;
	const uint16_t _devid_driver_index;
	const int16_t _chipselect;
	const int _bus;
	int _index{0};
	int _external_bus_counter{1};
	int _bus_device_index{-1};
};

#endif // CONFIG_SPI
