/*
 * Copyright (c) 2015, Xilinx Inc. and Contributors. All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

/*
 * @file	device.h
 * @brief	Bus abstraction for libmetal.
 */

#ifndef __METAL_BUS__H__
#define __METAL_BUS__H__

#include <stdint.h>
#include <metal/io.h>
#include <metal/list.h>
#include <metal/dma.h>
#include <metal/sys.h>

#ifdef __cplusplus
extern "C" {
#endif

/** \defgroup device Bus Abstraction
 *  @{
 */

#ifndef METAL_MAX_DEVICE_REGIONS
#define METAL_MAX_DEVICE_REGIONS	32
#endif

struct metal_bus;
struct metal_device;

/** Bus operations. */
struct metal_bus_ops {
	void		(*bus_close)(struct metal_bus *bus);
	int		(*dev_open)(struct metal_bus *bus,
				    const char *dev_name,
				    struct metal_device **device);
	void		(*dev_close)(struct metal_bus *bus,
				     struct metal_device *device);
	void		(*dev_irq_ack)(struct metal_bus *bus,
				       struct metal_device *device,
				       int irq);
	int		(*dev_dma_map)(struct metal_bus *bus,
				       struct metal_device *device,
				       uint32_t dir,
				       struct metal_sg *sg_in,
				       int nents_in,
				       struct metal_sg *sg_out);
	void		(*dev_dma_unmap)(struct metal_bus *bus,
					 struct metal_device *device,
					 uint32_t dir,
					 struct metal_sg *sg,
					 int nents);
};

/** Libmetal bus structure. */
struct metal_bus {
	const char		*name;
	struct metal_bus_ops	ops;
	struct metal_list	devices;
	struct metal_list	node;
};

/** Libmetal generic bus. */
extern struct metal_bus metal_generic_bus;

/** Libmetal device structure. */
struct metal_device {
	const char             *name;       /**< Device name */
	struct metal_bus       *bus;        /**< Bus that contains device */
	unsigned int           num_regions; /**< Number of I/O regions in
						 device */
	struct metal_io_region regions[METAL_MAX_DEVICE_REGIONS]; /**< Array of
							I/O regions in device*/
	struct metal_list      node;       /**< Node on bus' list of devices */
	int                    irq_num;    /**< Number of IRQs per device */
	void                   *irq_info;  /**< IRQ ID */
};

/**
 * @brief	Register a libmetal bus.
 * @param[in]	bus	Pre-initialized bus structure.
 * @return 0 on success, or -errno on failure.
 */
extern int metal_bus_register(struct metal_bus *bus);

/**
 * @brief	Unregister a libmetal bus.
 * @param[in]	bus	Pre-registered bus structure.
 * @return 0 on success, or -errno on failure.
 */
extern int metal_bus_unregister(struct metal_bus *bus);

/**
 * @brief	Find a libmetal bus by name.
 * @param[in]	name	Bus name.
 * @param[out]	bus	Returned bus handle.
 * @return 0 on success, or -errno on failure.
 */
extern int metal_bus_find(const char *name, struct metal_bus **bus);

/**
 * @brief	Statically register a generic libmetal device.
 *
 * In non-Linux systems, devices are always required to be statically
 * registered at application initialization.
 * In Linux system, devices can be dynamically opened via sysfs or libfdt based
 * enumeration at runtime.
 * This interface is used for static registration of devices. Subsequent calls
 * to metal_device_open() look up in this list of pre-registered devices on the
 * "generic" bus.
 * "generic" bus is used on non-Linux system to group the memory mapped devices.
 *
 * @param[in]	device	Generic device.
 * @return 0 on success, or -errno on failure.
 */
extern int metal_register_generic_device(struct metal_device *device);

/**
 * @brief	Open a libmetal device by name.
 * @param[in]	bus_name	Bus name.
 * @param[in]	dev_name	Device name.
 * @param[out]	device		Returned device handle.
 * @return 0 on success, or -errno on failure.
 */
extern int metal_device_open(const char *bus_name, const char *dev_name,
			     struct metal_device **device);

/**
 * @brief	Close a libmetal device.
 * @param[in]	device		Device handle.
 */
extern void metal_device_close(struct metal_device *device);

/**
 * @brief	Get an I/O region accessor for a device region.
 *
 * @param[in]	device		Device handle.
 * @param[in]	index		Region index.
 * @return I/O accessor handle, or NULL on failure.
 */
static inline struct metal_io_region *
metal_device_io_region(struct metal_device *device, unsigned int index)
{
	return (index < device->num_regions
		? &device->regions[index]
		: NULL);
}

/** @} */

#ifdef METAL_INTERNAL
extern int metal_generic_dev_sys_open(struct metal_device *dev);
extern int metal_generic_dev_open(struct metal_bus *bus, const char *dev_name,
				  struct metal_device **device);
extern int metal_generic_dev_dma_map(struct metal_bus *bus,
				     struct metal_device *device,
				     uint32_t dir,
				     struct metal_sg *sg_in,
				     int nents_in,
				     struct metal_sg *sg_out);
extern void metal_generic_dev_dma_unmap(struct metal_bus *bus,
					struct metal_device *device,
					uint32_t dir,
					struct metal_sg *sg,
					int nents);
#endif /* METAL_INTERNAL */

#ifdef __cplusplus
}
#endif

#endif /* __METAL_BUS__H__ */
