/*
 * Copyright (c) 2018, Intel Corporation
 *
 * Author:	Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
 *		Sathish Kuttan <sathish.k.kuttan@intel.com>
 *
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @file
 * @brief Public API header file for Digital Microphones
 *
 * This file contains the Digital Microphone APIs
 */

#ifndef ZEPHYR_INCLUDE_AUDIO_DMIC_H_
#define ZEPHYR_INCLUDE_AUDIO_DMIC_H_


/**
 * @defgroup audio_interface Audio
 * @{
 * @}
 */

/**
 * @brief Abstraction for digital microphones
 *
 * @defgroup audio_dmic_interface Digital Microphone Interface
 * @ingroup audio_interface
 * @{
 */

#include <kernel.h>
#include <device.h>

#ifdef __cplusplus
extern "C" {
#endif

/**
 * DMIC driver states
 */
enum dmic_state {
	DMIC_STATE_UNINIT,	/* Uninitialized */
	DMIC_STATE_INITIALIZED,	/* Initialized */
	DMIC_STATE_CONFIGURED,	/* Configured */
	DMIC_STATE_ACTIVE,	/* Active */
	DMIC_STATE_PAUSED,	/* Paused */
};

/**
 * DMIC driver trigger commands
 */
enum dmic_trigger {
	DMIC_TRIGGER_STOP,	/* stop stream */
	DMIC_TRIGGER_START,	/* start stream */
	DMIC_TRIGGER_PAUSE,	/* pause the stream */
	DMIC_TRIGGER_RELEASE,	/* release paused stream */
	DMIC_TRIGGER_RESET,	/* reset */
};

/**
 * PDM Channels LEFT / RIGHT
 */
enum pdm_lr {
	PDM_CHAN_LEFT,
	PDM_CHAN_RIGHT,
};

/**
 * PDM Input/Output signal configuration
 */
struct pdm_io_cfg {
	/* parameters global to all PDM controllers */

	/* minimum clock frequency supported by the mic */
	uint32_t	min_pdm_clk_freq;
	/* maximum clock frequency supported by the mic */
	uint32_t	max_pdm_clk_freq;
	/* minimum duty cycle in % supported by the mic */
	uint8_t	min_pdm_clk_dc;
	/* maximum duty cycle in % supported by the mic */
	uint8_t	max_pdm_clk_dc;

	/* parameters unique to each PDM controller */

	/* Bit mask to optionally invert PDM clock */
	uint8_t	pdm_clk_pol;
	/* Bit mask to optionally invert mic data */
	uint8_t	pdm_data_pol;
	/* Collection of clock skew values for each PDM port */
	uint32_t	pdm_clk_skew;
};

/**
 * Configuration of the PCM streams to be output by the PDM hardware
 */
struct pcm_stream_cfg {
	/*
	 * if either rate or width is set to 0 for a stream,
	 * the stream would be disabled
	 */

	/* PCM sample rate of stream */
	uint32_t			pcm_rate;
	/* PCM sample width of stream */
	uint8_t			pcm_width;
	/* PCM sample block size per transfer */
	uint16_t			block_size;
	/* SLAB for DMIC driver to allocate buffers for stream */
	struct k_mem_slab	*mem_slab;
};

/**
 * Mapping/ordering of the PDM channels to logical PCM output channel
 */
struct pdm_chan_cfg {
	/*
	 * mapping of PDM controller and mic channel to logical channel
	 * since each controller can have 2 audio channels (stereo),
	 * there can be total of 8x2=16 channels.
	 * The actual number of channels shall be described in
	 * pcm_stream_cfg.num_chan.
	 * if 2 streams are enabled, the channel order will be the same for
	 * both streams
	 * Each channel is described as a 4 bit number, the least significant
	 * bit indicates LEFT/RIGHT selection of the PDM controller.
	 * The most significant 3 bits indicate the PDM controller number.
	 *	bits 0-3 are for channel 0, bit 0 indicates LEFT or RIGHT
	 *	bits 4-7 are for channel 1, bit 4 indicates LEFT or RIGHT
	 *	and so on.
	 * CONSTRAINT: The LEFT and RIGHT channels of EACH PDM controller needs
	 * to be adjacent to each other.
	 */
	/* Requested channel map */
	uint32_t	req_chan_map_lo;	/* Channels 0 to 7 */
	uint32_t	req_chan_map_hi;	/* Channels 8 to 15 */
	/* Actual channel map that the driver could configure */
	uint32_t	act_chan_map_lo;	/* Channels 0 to 7 */
	uint32_t	act_chan_map_hi;	/* Channels 8 to 15 */
	/* requested number of channels */
	uint8_t	req_num_chan;
	/* Actual number of channels that the driver could configure */
	uint8_t	act_num_chan;
	/* requested number of streams for each channel */
	uint8_t	req_num_streams;
	/* Actual number of streams that the driver could configure */
	uint8_t	act_num_streams;
};

/**
 * Input configuration structure for the DMIC configuration API
 */
struct dmic_cfg {
	struct pdm_io_cfg io;
	/*
	 * Array of pcm_stream_cfg for application to provide
	 * configuration for each stream
	 */
	struct pcm_stream_cfg *streams;
	struct pdm_chan_cfg channel;
};

/**
 * Function pointers for the DMIC driver operations
 */
struct _dmic_ops {
	int (*configure)(const struct device *dev, struct dmic_cfg *config);
	int (*trigger)(const struct device *dev, enum dmic_trigger cmd);
	int (*read)(const struct device *dev, uint8_t stream, void **buffer,
			size_t *size, int32_t timeout);
};

/**
 * Build the channel map to populate struct pdm_chan_cfg
 *
 * Returns the map of PDM controller and LEFT/RIGHT channel shifted to
 * the bit position corresponding to the input logical channel value
 *
 * @param channel The logical channel number
 * @param pdm The PDM hardware controller number
 * @param lr LEFT/RIGHT channel within the chosen PDM hardware controller
 *
 * @return Bit-map containing the PDM and L/R channel information
 */
static inline uint32_t dmic_build_channel_map(uint8_t channel, uint8_t pdm,
		enum pdm_lr lr)
{
	return ((((pdm & BIT_MASK(3)) << 1) | lr) <<
			((channel & BIT_MASK(3)) * 4U));
}

/**
 * Helper function to parse the channel map in pdm_chan_cfg
 *
 * Returns the PDM controller and LEFT/RIGHT channel corresponding to
 * the channel map and the logical channel provided as input
 *
 * @param channel_map_lo Lower order/significant bits of the channel map
 * @param channel_map_hi Higher order/significant bits of the channel map
 * @param channel The logical channel number
 * @param pdm Pointer to the PDM hardware controller number
 * @param lr Pointer to the LEFT/RIGHT channel within the PDM controller
 *
 * @return none
 */
static inline void dmic_parse_channel_map(uint32_t channel_map_lo,
		uint32_t channel_map_hi, uint8_t channel, uint8_t *pdm, enum pdm_lr *lr)
{
	uint32_t channel_map;

	channel_map = (channel < 8) ? channel_map_lo : channel_map_hi;
	channel_map >>= ((channel & BIT_MASK(3)) * 4U);

	*pdm = (channel >> 1) & BIT_MASK(3);
	*lr = (enum pdm_lr) (channel & BIT(0));
}

/**
 * Build a bit map of clock skew values for each PDM channel
 *
 * Returns the bit-map of clock skew value shifted to the bit position
 * corresponding to the input PDM controller value
 *
 * @param pdm The PDM hardware controller number
 * @param skew The skew to apply for the clock output from the PDM controller
 *
 * @return Bit-map containing the clock skew information
 */
static inline uint32_t dmic_build_clk_skew_map(uint8_t pdm, uint8_t skew)
{
	return ((skew & BIT_MASK(4)) << ((pdm & BIT_MASK(3)) * 4U));
}

/**
 * Configure the DMIC driver and controller(s)
 *
 * Configures the DMIC driver device according to the number of channels,
 * channel mapping, PDM I/O configuration, PCM stream configuration, etc.
 *
 * @param dev Pointer to the device structure for DMIC driver instance
 * @param cfg Pointer to the structure containing the DMIC configuration
 *
 * @return 0 on success, a negative error code on failure
 */
static inline int dmic_configure(const struct device *dev,
				 struct dmic_cfg *cfg)
{
	const struct _dmic_ops *api =
		(const struct _dmic_ops *)dev->api;

	return api->configure(dev, cfg);
}

/**
 * Send a command to the DMIC driver
 *
 * Sends a command to the driver to perform a specific action
 *
 * @param dev Pointer to the device structure for DMIC driver instance
 * @param cmd The command to be sent to the driver instance
 *
 * @return 0 on success, a negative error code on failure
 */
static inline int dmic_trigger(const struct device *dev,
			       enum dmic_trigger cmd)
{
	const struct _dmic_ops *api =
		(const struct _dmic_ops *)dev->api;

	return api->trigger(dev, cmd);
}

/**
 * Read received decimated PCM data stream
 *
 * Optionally waits for audio to be received and provides the received
 * audio buffer from the requested stream
 *
 * @param dev Pointer to the device structure for DMIC driver instance
 * @param stream Stream identifier
 * @param buffer Pointer to the received buffer address
 * @param size Pointer to the received buffer size
 * @param timeout Timeout in milliseconds to wait in case audio is not yet
 * 		  received, or @ref SYS_FOREVER_MS
 *
 * @return 0 on success, a negative error code on failure
 */
static inline int dmic_read(const struct device *dev, uint8_t stream,
			    void **buffer,
			    size_t *size, int32_t timeout)
{
	const struct _dmic_ops *api =
		(const struct _dmic_ops *)dev->api;

	return api->read(dev, stream, buffer, size, timeout);
}

#ifdef __cplusplus
}
#endif

/**
 * @}
 */

#endif /* ZEPHYR_INCLUDE_AUDIO_DMIC_H_ */
