/*******************************************************************************
 * (c) Copyright 2010-2012 Actel Corporation.  All rights reserved.
 *
 *  SmartFusion MPM reference design driver API providing the firmware
 *  interface to the MPM engine.
 *
 *  SVN $Revision: 13756 $
 *  SVN $Date: 2010-07-15 14:47:11 +0100 (Thu, 15 Jul 2010) $
 *
 */

/*
 * Includes 
 */

#include "mpm.h"
#include "mpm_config.h"	/* MPM configuration data layout details */

#include "../drivers/mss_ace/mss_ace.h"
#include "../drivers/mss_gpio/mss_gpio.h"
#include "../drivers/mss_i2c/mss_i2c.h"
#include "../drivers/mss_nvm/mss_nvm.h"
#include "../drivers/mss_rtc/mss_rtc.h"
#include "../drivers/mss_timer/mss_timer.h"
#include "../drivers/CoreGPIO/core_gpio.h"
#include "../drivers/CorePWM/core_pwm.h"
#include "../drivers/pmbus/pmbus.h"
#include "../drivers/mss_uart/mss_uart.h"
#include "../CMSIS/mss_assert.h"	/* For CMSIS assert support */

#include <string.h>	/* For memset() */
#include <stdlib.h> /* For atoi() */

/*
 * #defines
 * 
 * Note that many symbols are only defined here if not already defined elsewhere
 * allowing defaults to be overridden using -D<name>=<value> in the project 
 * properties.
 */

/* MPM memory map addresses */

/* Base address and extent of MPM configuration data register map in ENVM.
 * These must match the configuration of the MPM_Configuration_Data MSS ENVM
 * data storage client in the Libero project.
 */
#ifndef MPM_CONFIGURATION_DATA_BASE_ADDRESS
#define MPM_CONFIGURATION_DATA_BASE_ADDRESS (0x6003E000)
#endif
#define MPM_CONFIGURATION_DATA_LENGTH (0x0000D00)

/* Base address and extent of MPM event logging area in ENVM.
 * These must match the configuration of the MPM_Event_Logging MSS ENVM
 * data storage client in the Libero project
 */
#ifndef MPM_EVENT_LOG_BASE_ADDRESS
#define MPM_EVENT_LOG_BASE_ADDRESS (0x6003A000)
#endif

#ifndef MPM_EVENT_LOG_LENGTH
#define MPM_EVENT_LOG_LENGTH (0x0004000)
#endif


/* Base address of regulator enables CoreGPIO */
#ifndef MPM_REGULATOR_ENABLES_GPIO_BASE_ADDRESS 
#define MPM_REGULATOR_ENABLES_GPIO_BASE_ADDRESS (0x40050000)
#endif

/* Base address of digital I/Os CoreGPIO */
#ifndef MPM_DIGITAL_IOS_GPIO_BASE_ADDRESS 
#define MPM_DIGITAL_IOS_GPIO_BASE_ADDRESS (0x40050200)
#endif

/* Base address of trimming CorePWM */
#ifndef MPM_TRIMMING_PWM_BASE_ADDRESS 
#define MPM_TRIMMING_PWM_BASE_ADDRESS (0x40050400)
#endif

/* Base address of DPOL PMBus CoreI2C */
#ifndef MPM_DPOL_PMBUS_I2C_BASE_ADDRESS
#define MPM_DPOL_PMBUS_I2C_BASE_ADDRESS (0x40050600)
#endif

/* Max number of ACE channels */
#if defined(MPM_USING_A2F060)
#define ACE_NUMBER_OF_ADCS 1
#elif defined(MPM_USING_A2F200)
#define ACE_NUMBER_OF_ADCS 2
#else	/* Assume MPM_USING_A2F500 */
#define ACE_NUMBER_OF_ADCS 3
#endif

/* One SDD per ADC available for trimming */
#define ACE_NUMBER_OF_SDDS ACE_NUMBER_OF_ADCS

#define ACE_NUMBER_OF_CHANNELS_PER_ADC 14

#define ACE_MAX_NUMBER_OF_CHANNELS \
    ((ACE_NUMBER_OF_ADCS) * (ACE_NUMBER_OF_CHANNELS_PER_ADC))

/* MPM strings used in ACE configuration of MPM channels */
#ifndef MPM_CHANNEL_NAME_PREFIX
#define MPM_CHANNEL_NAME_PREFIX	"MPM_Channel_"
#endif

#ifndef MPM_UP_THRESHOLD_NAME
#define MPM_UP_THRESHOLD_NAME 	"UP"
#endif

#ifndef MPM_DOWN_THRESHOLD_NAME 
#define MPM_DOWN_THRESHOLD_NAME "DOWN"
#endif

/* Min/max 12 bit PPE values */
#define PPE_MIN_VALUE (0x000)
#define PPE_MAX_VALUE (0xFFF)

/* Sentinel invalid channel/slot numbers
 * In the GUI, ACE configuration and here in the MPM "driver" channels are
 * identified by channel number n where 1 <= n <= min(32, 
 * MPM_MAX_NUMBER_OF_CHANNELS). Anything else is an invalid channel number 
 * and 0 is used in various places as a sentinel value. Note that the MPM 
 * channel descriptor table is indexed from 0 so channel n is in slot n-1. 
 *
 * Similarly for slot numbers.
 */
#define MPM_INVALID_SLOT_NUMBER (0)

/* Number of channel states - i.e.
 * positive voltage channels: (0V < ) OFF < UV2 < UV1 < NOM < OV1 < OV2
 * negative voltage channels: (0V > ) OFF > OV2 > OV2 > NOM > UV1 > UV2
 */
#define MPM_NUMBER_OF_CHANNEL_STATES (6)

#ifndef MPM_NUMBER_OF_DIGITAL_INPUTS 
#define MPM_NUMBER_OF_DIGITAL_INPUTS (32)
#endif

#ifndef MPM_NUMBER_OF_FLAG_OUTPUTS 
#define MPM_NUMBER_OF_FLAG_OUTPUTS (32)
#endif

#ifndef MPM_TIM2_PERIOD_IN_MICROSECONDS
#define MPM_TIM2_PERIOD_IN_MICROSECONDS (1000)
#endif

/* Trimming DAC resolution
 * Assuming 16 bit resolution CorePWM and ACE SDDs
 */
#ifndef MPM_DAC_MAX
#define MPM_DAC_MAX (0xFFFF)
#endif

/* I2C defines */

/*
 * Format of I2C received "packet" is
 *
 * Byte 0: MPM register address [15:8]
 * Byte 1: MPM register address [7:0]
 * Byte 2: Data length [7:0] (1..64)
 * Bytes 3-66: Data bytes 0..63 (between 1 and 64 data bytes)
 *
 * For command "packets"
 * Byte 3: Command opcode
 * Bytes 4-66: Command parameter data
 */
#ifdef MPM_ENABLE_I2C
#define MPM_I2C_REG_ADDR_LEN (2)
#define MPM_I2C_DATALEN_LEN  (1)
#define MPM_I2C_REG_READ_LEN (3)
#define MPM_I2C_MIN_DATA_LEN (1)
#define MPM_I2C_MAX_DATA_LEN (64)

/* Offsets to protocol byte fields in rx buffer */
#define MPM_I2C_REGADDRH	(0)
#define MPM_I2C_REGADDRL	(1)
#define MPM_I2C_DATALEN		(2)
#define MPM_I2C_DATA0		(3)
#define MPM_I2C_CMD_OPCODE	(3)
#define MPM_I2C_DATA1		(4)

/* I2C buffer lengths */
#define MPM_I2C_RX_BUFFER_LEN \
	(MPM_I2C_REG_ADDR_LEN + MPM_I2C_DATALEN_LEN + MPM_I2C_MAX_DATA_LEN)
#define MPM_I2C_TX_BUFFER_LEN MPM_I2C_MAX_DATA_LEN
#endif

/* Logging defines */
#ifdef MPM_ENABLE_LOGGING
#define MPM_LOG_RECORD_SIZE	(7)
#endif

/* PMBus/DPOL defines */
#define MPM_PMBUS_VOUT_MODE 			(0x20)
#define MPM_PMBUS_VOUT_MODE_MODE_MASK 	(0xE0)
#define MPM_PMBUS_VOUT_MODE_MODE_SHIFT 	(5)
#define MPM_PMBUS_VOUT_MODE_PARAMETER_MASK (0x1F)
#define MPM_PMBUS_VOUT_MODE_LINEAR_MODE (0)
#define MPM_PMBUS_VOUT_MODE_VID_MODE 	(1)
#define MPM_PMBUS_VOUT_MODE_DIRECT_MODE (2)
#define MPM_PMBUS_VOUT_MODE_LINEAR_EXPONENT_NEGATIVE_PAD (0xE0)

#define MPM_PMBUS_READ_VOUT				(0x8B)

#define MPM_PMBUS_MFR_SPECIFIC_00 		(0xD0)
#define MPM_PMBUS_INTERSIL_MFR_CONFIG 	(MPM_PMBUS_MFR_SPECIFIC_00)
#define MPM_PMBUS_INTERSIL_MFR_CONFIG_VALUE 	(0x8B10)

#define MPM_PMBUS_MFR_SPECIFIC_01 		(0xD1)
#define MPM_PMBUS_INTERSIL_USER_CONFIG 			(MPM_PMBUS_MFR_SPECIFIC_01)
#define MPM_PMBUS_INTERSIL_USER_CONFIG_VALUE 	(0x0010)

#ifndef MPM_PMBUS_MASTER_I2C_ADDR
#define MPM_PMBUS_MASTER_I2C_ADDR 		(0x10)
#endif

#define MPM_PMBUS_VOUT_COMMAND			(0x21)
#define MPM_PMBUS_VOUT_MAX				(0x24)
#define MPM_PMBUS_VOUT_MARGIN_HIGH		(0x25)
#define MPM_PMBUS_VOUT_MARGIN_LOW		(0x26)
#define MPM_PMBUS_VOUT_SCALE_LOOP		(0x29)
#define MPM_PMBUS_VOUT_OV_FAULT_LIMIT	(0x41)
#define MPM_PMBUS_VOUT_UV_FAULT_LIMIT	(0x44)
#define MPM_PMBUS_POWER_GOOD_ON 		(0x5E)

#define MPM_PMBUS_OPERATION 							(0x01)
#define MPM_PMBUS_OPERATION_IMMEDIATE_OFF 				(0x00)
#define MPM_PMBUS_OPERATION_SOFT_OFF					(0x40)
#define MPM_PMBUS_OPERATION_ON							(0x80)
#define MPM_PMBUS_OPERATION_MARGIN_LOW_IGNORE_FAULT		(0x94)
#define MPM_PMBUS_OPERATION_MARGIN_LOW_ACT_ON_FAULT		(0x98)
#define MPM_PMBUS_OPERATION_MARGIN_HIGH_IGNORE_FAULT	(0xA4)
#define MPM_PMBUS_OPERATION_MARGIN_HIGH_ACT_ON_FAULT	(0xA8)

#define MPM_PMBUS_LINEAGE_VOUT_SCALE_LOOP_VALUE 	(0xB0CE)


int count =0;
int first_time = 1;
#define SLOPE 1
#define CONSTANT 5
#define WAITING_TIME 2000
/* Typedefs */

/* Logging */
#ifdef MPM_ENABLE_LOGGING
typedef enum mpm_logging_record_type
{
	mpm_logging_record_type_null,
	mpm_logging_record_type_mpm_state_changed,
	mpm_logging_record_type_flag_changed,
	mpm_logging_record_type_power_on,
	mpm_logging_record_type_power_off,
	mpm_logging_record_type_max
} mpm_logging_record_type_t;

typedef enum mpm_logging_power_seq_event
{
	mpm_logging_power_seq_event_start,
	mpm_logging_power_seq_event_finish,
	mpm_logging_power_seq_event_timeout,
	mpm_logging_power_seq_event_hold,
	mpm_logging_power_seq_event_shutdown,
	mpm_logging_power_seq_event_restart_slot,
	mpm_logging_power_seq_event_restart_sequence,
	mpm_logging_power_seq_event_skip_slot,
	mpm_logging_power_seq_event_max
} mpm_logging_power_seq_event_t;
#endif

/* Channel type */
typedef enum mpm_channel_type
{
	mpm_channel_type_APOL,
	mpm_channel_type_DPOL,
	mpm_channel_type_unused,
    mpm_channel_type_max
} mpm_channel_type_t;

/* Thresholds */
typedef enum mpm_threshold_index
{
	mpm_off_index,
	mpm_uv2_index,
	mpm_uv1_index,
	mpm_ov1_index,
	mpm_ov2_index,
	mpm_num_thresholds
} mpm_threshold_index_t;

/* Trimming */
typedef enum mpm_trim_type
{
	mpm_trim_type_none,
	mpm_trim_type_open_loop,
	mpm_trim_type_closed_loop,
	mpm_trim_type_max
} mpm_trim_type_t;

typedef enum mpm_trim_dac_type
{
	mpm_trim_dac_type_sdd,
	mpm_trim_dac_type_pwm,
	mpm_trim_dac_type_max
} mpm_trim_dac_type_t;

typedef enum mpm_trim_target
{
	mpm_trim_target_nominal,
	mpm_trim_target_low,
	mpm_trim_target_high,
	mpm_trim_target_max
} mpm_trim_target_t;

/* I2C status register values */
#ifdef MPM_ENABLE_I2C
typedef enum mpm_i2c_status
{
	mpm_i2c_status_success,
	mpm_i2c_status_invalid_reg_addr,
	mpm_i2c_status_unrecognized_cmd,
	mpm_i2c_status_invalid_data_len,
	mpm_i2c_status_data_len_mismatch,
	mpm_i2c_status_nvm_write_failure,
	mpm_i2c_status_malformed_request,
	mpm_i2c_status_wrong_state,
	mpm_i2c_status_trim_failed
} mpm_i2c_status_t;
#endif

/* MPM channel descriptor */
typedef struct mpm_channel
{
	/* Channel type */
	mpm_channel_type_t channel_type;

	/* ACE channel & threshold handles for APOL channel */
	ace_channel_handle_t h_ace_channel;
	ace_flag_handle_t h_ace_up_threshold;
	ace_flag_handle_t h_ace_down_threshold;

	/* Nominal, threshold & hysteresis voltages
	 * - in ACE/PPE units for APOL channel
	 * - in real world mV units for DPOL channel
	 *
	 * NOTE:
	 * Positive voltage channels: 0V < OFF < UV2 < UV1 < NOM < OV1 < OV2
	 * Negative voltage channels: 0V > OFF > OV2 > OV1 > NOM > UV1 > UV2
	 */
	uint16_t thresholds[mpm_num_thresholds];
	uint16_t hysteresis;

	/* Sequencing details
	 *
	 * Sequencing slot = 1..32, 0 means not sequenced.
	 * On and off slots same except for power off simultaneous
	 * where off slot set to 1 for all channels.
	 */
	uint8_t on_sequencing_slot;
	uint8_t off_sequencing_slot;
	uint16_t on_delay_ms;
	uint16_t off_delay_ms;

	/* Trimming details */
	mpm_trim_type_t trim_type;
	mpm_trim_dac_type_t trim_dac_type;
	uint8_t trimming_sdd;
	uint16_t open_loop_trim_pin_voltage[mpm_trim_type_max];
	uint16_t closed_loop_trim_target_channel_voltage[mpm_trim_type_max];
	uint16_t dac_duty_cycle;
	uint16_t x_value ;
	mpm_trim_target_t trim_target;

	/* Channel state */
	mpm_channel_state_t state;
	bool is_positive;

	/* DPOL PMBus details */
	pmbus_slave_info_t pmbus_slave_info;
	uint8_t pmbus_i2c_bus;
	uint8_t pmbus_i2c_slave_addr;
	int8_t pmbus_vout_mode_linear_exponent;

} mpm_channel_t;

/* Power sequencing typedefs */
typedef enum mpm_slot_timeout_action
{
	mpm_slot_timeout_disabled,
	mpm_slot_timeout_shutdown,
	mpm_slot_timeout_restart_slot,
	mpm_slot_timeout_restart_sequence,
	mpm_slot_timeout_skip_slot,
	mpm_slot_timeout_max
} mpm_slot_timeout_action_t;

typedef enum mpm_off_sequence
{
	mpm_off_sequence_reverse,
	mpm_off_sequence_forward,
	mpm_off_sequence_simultaneous,
	mpm_off_sequence_max
} mpm_off_sequence_t;

typedef struct mpm_sequencing
{
	/* Power on/off sequencing channel lists */
	mpm_channel_number_t on_channels[MPM_MAX_NUMBER_OF_CHANNELS + 1];
	mpm_channel_number_t off_channels[MPM_MAX_NUMBER_OF_CHANNELS + 1];

	/* Sequencing configuration */
	uint16_t timeout;
	mpm_slot_timeout_action_t slot_timeout_action;
	mpm_off_sequence_t off_sequence;

	/* Current slot data */
	uint8_t current_slot_number;
	uint32_t current_slot_timer;
	uint8_t current_slot_1st_channel_index;
	uint32_t current_slot_channel_bitmask;

	/* Power on/off working channel bitmasks */
	uint32_t on_channel_bitmask;
	uint32_t off_channel_bitmask;
	uint8_t current_channel_index;
} mpm_sequencing_t;

/* Type of operation used when feeding digital input <n> into the 
 * logic which generated output flag digital output <n> 
 */

typedef enum mpm_digital_input_logic
{
	mpm_input_ignore,
	mpm_input_or,
	mpm_input_and,
	mpm_input_xor,
	mpm_input_not_or,
	mpm_input_not_and,
	mpm_input_max
} mpm_digital_input_logic_t;

/* MPM engine descriptor */
typedef struct mpm_engine
{
	mpm_state_t state;

	/* List of MPM channel descriptors indexed by MPM channel number minus 1 */
	mpm_channel_t channels[MPM_MAX_NUMBER_OF_CHANNELS];

	/* ACE channel handle to MPM channel number lookup table.
	 * MPM channels are numbered from 1 so 0 entry means no MPM channel
	 * for the relevant ACE channel handle.
	 */
	mpm_channel_number_t ace_to_mpm_channel_lut[ACE_MAX_NUMBER_OF_CHANNELS];

	/* Regulator enables & digital I/Os GPIOs */
	gpio_instance_t gpio_regulator_enables;
	gpio_instance_t gpio_digital_ios;

	/* Power sequencing */
	mpm_sequencing_t sequencing;

	/* Output flags generation support */

	/* Output flags table - each bit column x (0 <= x < 32) refers to flag
	 * (x+1). Each row y (numbered from 0) refers to channel ((y/6)+1) and 
	 * state (y%6) - where 6 = MPM_NUMBER_OF_CHANNEL_STATES and states are 
	 * numbered as follows 0:OFF, 1:UV2, 2:UV1, 3:NOM, 4:OV1, 5:OV2
	 * 
	 * Row 0: Channel 1/State OFF
	 * Row 1: Channel 1/State UV2
	 * Row 2: Channel 1/State UV1
	 * Row 3: Channel 1/State NOM
	 * Row 4: Channel 1/State OV1
	 * Row 5: Channel 1/State O2
	 * Row 6: Channel 2/State OFF
	 * ...
	 * etc.
	 *
	 * Each x/y bit position in the table indicates how the channel ((y/6)+1)'s
	 * state (y%6) contributes towards the evaluation of flag (x+1). Output
	 * flag evaluation involves iterating over one row in the table per channel
	 * (i.e. the row for that channel's current state) and OR'ing the values
	 * together. For output flags based on OR logic that is the result which 
	 * may also need to be combined with the invert/active low mask. For 
	 * output flags based on AND logic an additional XOR is performed with the
	 * AND bitmask because such equations have been transformed to OR equations
	 * using De Morgan's law.  
	 */
	uint32_t output_flags_table [
	MPM_MAX_NUMBER_OF_CHANNELS * MPM_NUMBER_OF_CHANNEL_STATES ];

	/* Which output flags use AND rather than OR logic and which are 
	 * active low? 
	 */
	uint32_t output_flags_and_mask;
	uint32_t output_flags_invert_mask;

	/* Options for updating and logging outputs */
#ifdef MPM_ENABLE_LOGGING
	uint32_t output_flags_log_0_to_1_mask;
	uint32_t output_flags_log_1_to_0_mask;
	uint32_t output_flags_log_during_power_on_mask;
	uint32_t output_flags_log_during_power_off_mask;
#endif
	uint32_t output_flags_update_during_sequencing_mask;

	/* How is digital input <n> combined to generate flag output <n>? */
	mpm_digital_input_logic_t
			output_flags_input_logic[MPM_NUMBER_OF_FLAG_OUTPUTS];

	/* Trimming */
	pwm_instance_t pwm_trimmer;
	bool b_do_open_loop_trimming;
	bool b_do_closed_loop_trimming;
	uint8_t mss_ace_next_sdd;

	/* I2C support */
#ifdef MPM_ENABLE_I2C
	uint8_t i2c_rx_buffer[MPM_I2C_RX_BUFFER_LEN];
	uint8_t i2c_tx_buffer[MPM_I2C_TX_BUFFER_LEN];
	uint8_t i2c_status_reg;
#endif

	/* Logging */
#ifdef MPM_ENABLE_LOGGING
	bool b_log_mpm_engine_state;
	bool b_log_power_on_events;
	bool b_log_power_off_events;
	uint16_t log_cursor;
#endif

	/* PMBus I2C */
	i2c_instance_t pmbus_corei2c;
	ui2c_channel_t pmbus_ui2c_channel;

} mpm_engine_t;

/* 
 * Forward declarations of private implementation functions
 */

/* Internal initialization */
static void mpm_init_engine();
static void mpm_clear_channel_list();
static void mpm_enumerate_analog_channels();
static void mpm_init_analog_channel_descriptor(mpm_channel_number_t channel_num);
static void mpm_enumerate_digital_channels();

/* MPM engine state management */
static void mpm_set_engine_state(mpm_state_t state);

/* Threshold detection */
static void mpm_enable_disable_threshold_detection(bool b_enable);
static void mpm_channel_threshold_isr(ace_flag_handle_t h_ace_threshold);

static void mpm_enable_threshold_detection(void);
static void mpm_disable_threshold_detection(void);
static mpm_channel_state_t mpm_get_state_for_voltage(
		mpm_channel_t *p_mpm_channel, uint16_t voltage);

/* Power on/off sequencing */
static void mpm_start_power_sequencing(bool b_power_on);
static int compare_channels_on(const void *pv_left, const void *pv_right);
static int compare_channels_off_forward(const void *pv_left,
		const void *pv_right);
static int compare_channels_off_reverse(const void *pv_left,
		const void *pv_right);
static void mpm_init_power_sequencing();
static void mpm_switch_regulators(uint32_t regs, bool b_on);
static void mpm_do_power_sequencing(bool b_starting);

/* Output flags */
static void mpm_init_output_flags_table();
static void mpm_generate_flags();
static void mpm_set_flag_outputs(uint32_t flags);

/* Trimming */
static void mpm_do_closed_loop_trimming();
static void mpm_do_open_loop_trimming(bool b_start, uint32_t channel_bitmask);
#ifdef MPM_ENABLE_I2C
static bool mpm_adjust_channel_trim
(
	mpm_channel_number_t channel_num,
	mpm_trim_target_t trim_target
);
#endif

/* I2C slave access */
#ifdef MPM_ENABLE_I2C
static mss_i2c_slave_handler_ret_t mpm_i2c_slave_write_handler
(
	mss_i2c_instance_t *instance,
	uint8_t *p_rx_data,
	uint16_t rx_len
);
#endif

/* Logging */
#ifdef MPM_ENABLE_LOGGING
static bool mpm_logging_clear_if_required();
static void mpm_logging_init_cursor();
static bool mpm_logging_write(mpm_logging_record_type_t record_type, uint8_t record_data);
static bool mpm_logging_is_full();
static void mpm_log_power_sequencing_event(
	mpm_logging_record_type_t record_type,
	mpm_logging_power_seq_event_t event_type,
	uint8_t slot_number);
static void mpm_log_flags(uint32_t changed_to_0, uint32_t changed_to_1);
#endif

#if defined(MPM_ENABLE_I2C) || defined(MPM_ENABLE_LOGGING)
static void mpm_rtc_to_byte_array(uint8_t *p_rtc_bytes);
#endif


/* MPM configuration data access functions (mpm_config_get_*()) */
static uint16_t
		mpm_config_get_value(uint16_t bit_offset, uint8_t length_in_bits);

static bool mpm_config_get_channel_is_dpol(mpm_channel_number_t channel_num);
static int16_t
		mpm_config_get_channel_threshold(mpm_channel_number_t channel_num,
				mpm_threshold_index_t threshold_index);
static int16_t mpm_config_get_channel_hysteresis(
		mpm_channel_number_t channel_num);
static uint8_t mpm_config_get_channel_sequencing_slot(
		mpm_channel_number_t channel_num);
static uint16_t mpm_config_get_channel_on_off_delay_ms(
		mpm_channel_number_t channel_num, bool b_on_delay);
static mpm_trim_type_t mpm_config_get_channel_trim_type(
		mpm_channel_number_t channel_num);
static mpm_trim_dac_type_t mpm_config_get_channel_trim_dac_type(
		mpm_channel_number_t channel_num);
static uint16_t mpm_config_get_channel_trim_pin_voltage(
		mpm_channel_number_t channel_num,
		mpm_trim_target_t trim_target);
static uint16_t mpm_config_get_channel_margin_voltage(
		mpm_channel_number_t channel_num,
		mpm_trim_target_t trim_target);
static uint8_t mpm_config_get_channel_i2c_bus(
		mpm_channel_number_t channel_num);
static uint8_t mpm_config_get_channel_i2c_address(
		mpm_channel_number_t channel_num);

static uint16_t mpm_config_get_sequence_slot_timeout_ms();
static mpm_off_sequence_t mpm_config_get_power_off_sequence_type();
static mpm_slot_timeout_action_t mpm_config_get_sequence_slot_timeout_action();
static uint8_t mpm_config_get_flag_condition_channel_state_bitmask(
		uint8_t flag_num, mpm_channel_number_t channel_num);
static bool mpm_config_get_flag_uses_and_logic(uint8_t flag_number);
static bool mpm_config_get_flag_is_active_low(uint8_t flag_number);
static mpm_digital_input_logic_t mpm_config_get_flag_input_logic(
		uint8_t flag_number);

static bool mpm_config_get_flag_update_during_sequencing(uint8_t flag_number);
#ifdef MPM_ENABLE_LOGGING
static bool mpm_config_get_flag_log_during_power_on(uint8_t flag_number);
static bool mpm_config_get_flag_log_during_power_off(uint8_t flag_number);
static bool mpm_config_get_flag_log_0_to_1(uint8_t flag_number);
static bool mpm_config_get_flag_log_1_to_0(uint8_t flag_number);
#endif

#ifdef MPM_ENABLE_I2C
static uint8_t mpm_config_get_i2c_slave_address();
#endif

#ifdef MPM_ENABLE_LOGGING
static bool mpm_config_get_log_mpm_state();
static bool mpm_config_get_log_power_on_events();
static bool mpm_config_get_log_power_off_events();
static bool mpm_config_get_clear_log();
#endif

/* PMBus support */
static void mpm_pmbus_mv_to_vout_linear(
	uint16_t mv,
	uint8_t *pmbus_buffer,
	int8_t exponent);

static uint16_t mpm_pmbus_vout_linear_to_mv(
	uint8_t *pmbus_buffer,
	int8_t exponent);

/* 
 * Static global data 
 */

static mpm_engine_t g_mpm_engine;
static bool g_b_mpm_engine_initialized = false;

/* 
 * Public methods - see mpm.h
 */

void mpm_init()
{
	SystemCoreClockUpdate();
	ACE_init();
	mpm_init_engine();
	g_b_mpm_engine_initialized = true;
}

void mpm_start()
{
	ASSERT(g_b_mpm_engine_initialized);
	mpm_start_power_sequencing(true);
}

void mpm_stop()
{
	ASSERT(g_b_mpm_engine_initialized);
	mpm_start_power_sequencing(false);
}

mpm_state_t mpm_get_state()
{
	ASSERT(g_b_mpm_engine_initialized);
	return g_mpm_engine.state;
}

int32_t mpm_get_channel_voltage_mv(mpm_channel_number_t channel_num)
{
	/* NOTE: 0mV will be returned for invalid/unrecognised channels */
	int32_t channel_voltage = 0;
	mpm_channel_t *p_mpm_channel =
			&(g_mpm_engine.channels[channel_num - 1]);

	ASSERT(g_b_mpm_engine_initialized);

	if (mpm_is_valid_channel(channel_num))
	{
		switch (p_mpm_channel->channel_type)
		{
			case mpm_channel_type_APOL:
			{
				uint16_t ace_ppe_sample =
						ACE_get_ppe_sample(p_mpm_channel->h_ace_channel);
				channel_voltage = ACE_convert_to_mV(p_mpm_channel->h_ace_channel,
						ace_ppe_sample);

				/* We only cater for unipolar positive or negative voltage channels.
				 * ACE ABPSs can return small negative voltages for positive channels
				 * and small positive voltages for negative channels around 0/OFF.
				 * So we normalise to 0 if the result would be the "wrong" polarity.
				 */
				if (((p_mpm_channel->is_positive) && (channel_voltage < 0))
						|| (!(p_mpm_channel->is_positive) && (channel_voltage > 0)))
				{
					channel_voltage = 0;
				}
			}
			break;

			case mpm_channel_type_DPOL:
			{
				uint8_t pmbus_buffer[2];

				/* Disable MPM I2C slave ISR.
				 * Prevent MPM I2C slave initiating a PMBus read
				 * while something else is doing one otherwise
				 * data corruption will result.
				 * TODO: read current ISR status, save and restore later
				 * rather than just doing a disable/enable.
				 */
			    NVIC_DisableIRQ(g_mss_i2c1.irqn);

				if (PMBUS_read_word(
					&(p_mpm_channel->pmbus_slave_info),
					MPM_PMBUS_READ_VOUT,
					0 /* is_extended_command */,
					pmbus_buffer) == PMBUS_READ_SUCCESS)
				{
					/* TODO: assuming VOUT linear mode here - may need to cater
					 * for VID and/or direct modes too
					 */
					channel_voltage = mpm_pmbus_vout_linear_to_mv(
						pmbus_buffer,
						p_mpm_channel->pmbus_vout_mode_linear_exponent);
				}

				/* (Re)enable MPM I2C slave ISR. */
			    NVIC_EnableIRQ(g_mss_i2c1.irqn);
			}
			break;

			case mpm_channel_type_unused:
			default:
			break;
		}
	}

	return channel_voltage;
}

mpm_channel_state_t mpm_get_channel_state(mpm_channel_number_t channel_num)
{
	ASSERT(g_b_mpm_engine_initialized);
	ASSERT((channel_num> 0) && (channel_num <= MPM_MAX_NUMBER_OF_CHANNELS));
	return (g_mpm_engine.channels[channel_num - 1].state);
}

bool mpm_is_valid_channel(mpm_channel_number_t channel_num)
{
	if ((channel_num > 0) && (channel_num <= MPM_MAX_NUMBER_OF_CHANNELS)
			&& (g_mpm_engine.channels[channel_num - 1].channel_type
					!= mpm_channel_type_unused))
	{
		return true;
	}
	else
	{
		return false;
	}
}

uint32_t mpm_get_digital_outputs()
{
	ASSERT(g_b_mpm_engine_initialized);
	return GPIO_get_outputs(&(g_mpm_engine.gpio_digital_ios));
}

uint32_t mpm_get_digital_inputs()
{
	ASSERT(g_b_mpm_engine_initialized);
	return GPIO_get_inputs(&(g_mpm_engine.gpio_digital_ios));
}

uint32_t mpm_get_regulator_enable_outputs()
{
	ASSERT(g_b_mpm_engine_initialized);
	return GPIO_get_outputs(&(g_mpm_engine.gpio_regulator_enables));
}

/*
 * Static functions representing the internal implementation of the 
 * MPM engine driver
 */

/*
 * Internal initialization
 */

/* Initialize MPM engine descriptor */
static void mpm_init_engine()
{
	/* Clear MPM engine descriptor */
	memset(&g_mpm_engine, 0, sizeof(g_mpm_engine));

	/* Ensure that RTC is running */
	MSS_RTC_init();
	MSS_RTC_start();

	/* Logging */
#ifdef MPM_ENABLE_LOGGING
	g_mpm_engine.b_log_mpm_engine_state = mpm_config_get_log_mpm_state();
	g_mpm_engine.b_log_power_on_events = mpm_config_get_log_power_on_events();
	g_mpm_engine.b_log_power_off_events = mpm_config_get_log_power_off_events();
	mpm_logging_clear_if_required();
	mpm_logging_init_cursor();
#endif

	/* Initialize MPM engine state */
	if (!g_b_mpm_engine_initialized)
	{
		/* Fake state change to allow next real one to be logged if necessary */
		g_mpm_engine.state = mpm_is_stopping;
	}
	mpm_set_engine_state(mpm_is_stopped);

	/* Trimming
	 * Initialize trimming PWM and assume no trimming to be done. The boolean
	 * flags are updated as appropriate when initializing the channel
	 * descriptors
	 *
	 * TODO: only initialize/use CorePWM driver APIs if at least one channel
	 * is CorePWM trimmed. This would allow CorePWM to be removed completely
	 * from the hardware design if no CorePWM trimming is needed.
	 */
	g_mpm_engine.b_do_open_loop_trimming = false;
	g_mpm_engine.b_do_closed_loop_trimming = false;
	PWM_init(&(g_mpm_engine.pwm_trimmer), MPM_TRIMMING_PWM_BASE_ADDRESS, 0, MPM_DAC_MAX);

	/* Initialize GPIOs */
	GPIO_init(&(g_mpm_engine.gpio_regulator_enables),
	MPM_REGULATOR_ENABLES_GPIO_BASE_ADDRESS, GPIO_APB_32_BITS_BUS);

	GPIO_init(&(g_mpm_engine.gpio_digital_ios),
	MPM_DIGITAL_IOS_GPIO_BASE_ADDRESS, GPIO_APB_32_BITS_BUS);

#if 0
	/* Example code for dealing with CoreGPIO based digital inputs on an 
	 * interrupt driven basis. E.g. A2F-DEV-KIT board A2F_DIP switches
	 * wired to a fabric based CoreGPIO which raises an interrupt to the 
	 * MSS/M3 when a switch is changed.
	 */
	NVIC_ClearPendingIRQ(Fabric_IRQn);
	NVIC_EnableIRQ(Fabric_IRQn);
	GPIO_enable_irq(&(g_mpm_engine.gpio_digital_ios), GPIO_0);
	GPIO_enable_irq(&(g_mpm_engine.gpio_digital_ios), GPIO_1);
	GPIO_enable_irq(&(g_mpm_engine.gpio_digital_ios), GPIO_2);
	GPIO_enable_irq(&(g_mpm_engine.gpio_digital_ios), GPIO_3);
#endif

	/* Get channel details */
	mpm_clear_channel_list();
	mpm_enumerate_analog_channels();
	mpm_enumerate_digital_channels();

	/* Get power sequencing details */
	mpm_init_power_sequencing();

	/* Output flags support */
	mpm_init_output_flags_table();

	/* Clear outputs - note that off bits map directly to "invert" bits */
	mpm_set_flag_outputs(g_mpm_engine.output_flags_invert_mask);

	/* I2C slave support */
#ifdef MPM_ENABLE_I2C
	/* Only initialize I2C once - the first time MPM is initialized.
	 * This is because mpm_init() can be called via I2C itself so
	 * (re)initializing I2C while using it will obviously cause problems!
	 */
	if (!g_b_mpm_engine_initialized)
	{
		MSS_I2C_init
		(
			&g_mss_i2c1,
			mpm_config_get_i2c_slave_address(),
			/* Clock divider required by init but not relevant to slave */
			MSS_I2C_PCLK_DIV_256
		);

		MSS_I2C_set_slave_rx_buffer(
			&g_mss_i2c1,
			g_mpm_engine.i2c_rx_buffer,
			sizeof(g_mpm_engine.i2c_rx_buffer)
		);

		MSS_I2C_register_write_handler(
			&g_mss_i2c1,
			mpm_i2c_slave_write_handler
		);

		g_mpm_engine.i2c_status_reg = mpm_i2c_status_success;
		MSS_I2C_enable_slave(&g_mss_i2c1);
	}
#endif

	/* Init MSS GPIOs for demo DPOL PG (Power Good) signal handling
	 *
	 * Note that the default demo program already calls MSS_GPIO_init() before
	 * calling mpm_init() so we cannot call it again here or we would reset it
	 * and lose all earlier MSS GPIO configuration. If the demo program is
	 * changed so that it does NOT call MSS_GPIO_init() then the following
	 * line of code needs to be restored.
	 */
#if 0
	MSS_GPIO_init();
#endif

	/* MPM Channel 3/4/5 = DPOL 1/2/3 with PGs on MSS GPIO 13/14/15 */
	MSS_GPIO_config(MSS_GPIO_13, MSS_GPIO_INPUT_MODE | MSS_GPIO_IRQ_EDGE_BOTH);
	MSS_GPIO_config(MSS_GPIO_14, MSS_GPIO_INPUT_MODE | MSS_GPIO_IRQ_EDGE_BOTH);
	MSS_GPIO_config(MSS_GPIO_15, MSS_GPIO_INPUT_MODE | MSS_GPIO_IRQ_EDGE_BOTH);
	MSS_GPIO_enable_irq(MSS_GPIO_13);
	MSS_GPIO_enable_irq(MSS_GPIO_14);
	MSS_GPIO_enable_irq(MSS_GPIO_15);

	/* Make I2C1_IRQHandler lower priority than Fabric_IRQHandler */
	*((uint8_t *)0xE000E411) = 0xF8;
}

static void mpm_clear_channel_list()
{
	uint8_t i;

	/* Clear each channel descriptor */
	for (i = 0; i < MPM_MAX_NUMBER_OF_CHANNELS; i++)
	{
		g_mpm_engine.channels[i].channel_type = mpm_channel_type_unused;
		/* TODO: remove? */
		g_mpm_engine.channels[i].h_ace_channel = INVALID_CHANNEL_HANDLE;
		g_mpm_engine.channels[i].state = mpm_channel_is_off;
	}

	/* Clear ACE handle to MPM channel number lookup table */
	memset(g_mpm_engine.ace_to_mpm_channel_lut, 0,
		sizeof(g_mpm_engine.ace_to_mpm_channel_lut));
}

static void mpm_enumerate_analog_channels()
{
	uint8_t num_ace_channels = 0;
	ace_channel_handle_t h_ace_channel;
	mpm_channel_number_t mpm_channel_num = 0;

	/* Iterate over all ACE channels */
	h_ace_channel = ACE_get_first_channel();
	while (num_ace_channels < NB_OF_ACE_CHANNEL_HANDLES)
	{
		/* Check if this ACE channel name to see if it is an MPM channel */
		const char *psz_channel_name =
				(const char *)ACE_get_channel_name(h_ace_channel);

		/* Check ACE channel name for MPM channel name prefix */
		if (strncasecmp(psz_channel_name, 
		MPM_CHANNEL_NAME_PREFIX, strlen(MPM_CHANNEL_NAME_PREFIX)) == 0)
		{
			/* Parse MPM channel number from ACE channel name */
			mpm_channel_num = atoi(psz_channel_name
					+ strlen(MPM_CHANNEL_NAME_PREFIX));
		}
		else
		{
			mpm_channel_num = MPM_INVALID_CHANNEL_NUMBER;
		}

		/* Did we detect a valid MPM APOL channel? */
		if ((mpm_channel_num > MPM_INVALID_CHANNEL_NUMBER) &&
			(mpm_channel_num <= MPM_MAX_NUMBER_OF_CHANNELS) &&
			!mpm_config_get_channel_is_dpol(mpm_channel_num))
		{
			/* Yes - now try to get "UP" and "DOWN" threshold handles */
			uint16_t flag_iterator;
			ace_flag_handle_t h_first_flag;
			ace_flag_handle_t h_current_flag;

			ace_flag_handle_t h_up_flag;
			ace_flag_handle_t h_down_flag;
			h_up_flag = h_down_flag = INVALID_FLAG_HANDLE;

			h_first_flag = ACE_get_channel_first_flag(h_ace_channel,
					&flag_iterator);
			h_current_flag = h_first_flag;
			if (h_current_flag != INVALID_FLAG_HANDLE)
			{
				do
				{
					/* Find start of <flag name> in <chan_name>:<flag_name> */
					const char *psz_flag_name;
					const char *psz_channel_flag_name =
							(const char *)ACE_get_flag_name(h_current_flag);

					for (psz_flag_name = psz_channel_flag_name
							+ strlen(psz_channel_flag_name); *(psz_flag_name
							- 1) != ':'; psz_flag_name--)
					{
						; /* Empty loop */
					}

					/* Check for "UP" and "DOWN" flags */
					if (0 == strcasecmp(psz_flag_name, MPM_DOWN_THRESHOLD_NAME))
					{
						h_down_flag = h_current_flag;
					}
					else if (0 == strcasecmp(psz_flag_name, 
					MPM_UP_THRESHOLD_NAME))
					{
						h_up_flag = h_current_flag;
					}

					h_current_flag = ACE_get_channel_next_flag(h_ace_channel,
							&flag_iterator);
				} while ((h_current_flag != h_first_flag) && (h_current_flag
						!= INVALID_FLAG_HANDLE));
			}

			/* Do we have all the details needed for an MPM channel? */
			if ((h_up_flag != INVALID_FLAG_HANDLE) &&
				(h_down_flag != INVALID_FLAG_HANDLE))
			{
				/* Assertion checks */
				/* Range check ACE channel handle and MPM channel number */
				ASSERT(h_ace_channel < ACE_MAX_NUMBER_OF_CHANNELS);
				ASSERT(
						(mpm_channel_num> 0) &&
						(mpm_channel_num <= MPM_MAX_NUMBER_OF_CHANNELS)
				);
				/* Check that we haven't already seen this MPM channel number */
				ASSERT(0 == g_mpm_engine.ace_to_mpm_channel_lut[h_ace_channel]);
				/* Check that it's a voltage channel */
				ASSERT(ACE_get_channel_type(h_ace_channel) == VOLTAGE);

				/* Store channel details */
				g_mpm_engine.ace_to_mpm_channel_lut[h_ace_channel]
						= mpm_channel_num;
				g_mpm_engine.channels[mpm_channel_num - 1].h_ace_channel
						= h_ace_channel;
				g_mpm_engine.channels[mpm_channel_num - 1].h_ace_down_threshold
						= h_down_flag;
				g_mpm_engine.channels[mpm_channel_num - 1].h_ace_up_threshold
						= h_up_flag;

				/* Mark channel as valid APOL */
				g_mpm_engine.channels[mpm_channel_num - 1].channel_type =
					mpm_channel_type_APOL;

				/* Complete channel descriptor initialization */
				mpm_init_analog_channel_descriptor(mpm_channel_num);
			}
		}

		/* Next channel */
		h_ace_channel = ACE_get_next_channel(h_ace_channel);
		num_ace_channels++;
	}
}

static void mpm_init_analog_channel_descriptor(mpm_channel_number_t channel_num)
{
	uint8_t i;
	mpm_channel_t *p_channel;
	uint16_t threshold_min;
	uint16_t threshold_max;
	bool b_invalidate_channel = false;

	ASSERT((channel_num> 0) && (channel_num <= MPM_MAX_NUMBER_OF_CHANNELS));

	p_channel = &(g_mpm_engine.channels[channel_num - 1]);

	/* Infer channel polarity from sign of first threshold */
	p_channel->is_positive = mpm_config_get_channel_threshold(channel_num, 0)
			>= 0 ? true : false;

	/* Thresholds */
	for (i = 0; i < mpm_num_thresholds; i++)
	{
		p_channel->thresholds[i] = ACE_convert_from_mV(
				p_channel->h_ace_channel, mpm_config_get_channel_threshold(
						channel_num, i));
	}

	/* Hysteresis - note normalization with respect to 0V */
	p_channel->hysteresis = mpm_config_get_channel_hysteresis(channel_num);
	p_channel->hysteresis = ACE_convert_from_mV(p_channel->h_ace_channel,
			p_channel->hysteresis);
	p_channel->hysteresis -= ACE_convert_from_mV(p_channel->h_ace_channel, 0);

	/* Assertion and runtime based checking of channel voltage values.
	 * If anything is outside of the range of the ACE channel then invalidate
	 * this channel so that MPM ignores it.
	 */
	if (p_channel->is_positive)
	{
		threshold_min = ACE_convert_from_mV(p_channel->h_ace_channel, 0)
				+ p_channel->hysteresis;
		threshold_max = PPE_MAX_VALUE - p_channel->hysteresis;
	}
	else
	{
		threshold_min = PPE_MIN_VALUE + p_channel->hysteresis;
		threshold_max = ACE_convert_from_mV(p_channel->h_ace_channel, 0)
				- p_channel->hysteresis;
	}

	for (i = 0; (i < mpm_num_thresholds) && (!b_invalidate_channel); i++)
	{
		if ((p_channel->thresholds[i] < threshold_min)
				|| (p_channel->thresholds[i] > threshold_max))
		{
			ASSERT(0);
			b_invalidate_channel = true;
		}
	}

	if (b_invalidate_channel)
	{
		/* Something is out of range so ignore this channel */
		g_mpm_engine.ace_to_mpm_channel_lut[p_channel->h_ace_channel]
				= MPM_INVALID_CHANNEL_NUMBER;
		p_channel->channel_type = mpm_channel_type_unused;
		/* TODO: remove? */
		p_channel->h_ace_channel = INVALID_CHANNEL_HANDLE;
	}
	else
	{
		/* Set channel hysteresis */
		ACE_set_channel_hysteresis(p_channel->h_ace_channel,
				p_channel->hysteresis);

		/* Register ISR for thresholds */
		ACE_register_flag_isr(p_channel->h_ace_up_threshold,
				mpm_channel_threshold_isr);
		ACE_register_flag_isr(p_channel->h_ace_down_threshold,
				mpm_channel_threshold_isr);

		/* Sequencing */
		p_channel->on_sequencing_slot
				= mpm_config_get_channel_sequencing_slot(channel_num);
		p_channel->off_sequencing_slot = p_channel->on_sequencing_slot;

		p_channel->on_delay_ms = mpm_config_get_channel_on_off_delay_ms(
				channel_num, true);
		p_channel->off_delay_ms = mpm_config_get_channel_on_off_delay_ms(
				channel_num, false);

		/* Trimming */
		p_channel->trim_type = mpm_config_get_channel_trim_type(channel_num);
		p_channel->trim_dac_type = mpm_config_get_channel_trim_dac_type(channel_num);
		p_channel->trim_target = mpm_trim_target_nominal;

		/* Allocate an ACE SDD for trimming if necessary/possible. Note that
		 * SDDs are allocated on a first come first served basis by MPM channel
		 * number
		 */
		if (mpm_trim_type_none != p_channel->trim_type)
		{
			if (mpm_trim_dac_type_sdd == p_channel->trim_dac_type)
			{
				if (g_mpm_engine.mss_ace_next_sdd < ACE_NUMBER_OF_SDDS)
				{
					/* Allocate next available SDD to this channel & init it */
					p_channel->trimming_sdd = g_mpm_engine.mss_ace_next_sdd;
					g_mpm_engine.mss_ace_next_sdd++;
					ACE_configure_sdd(
						p_channel->trimming_sdd,
						SDD_16_BITS,
						SDD_VOLTAGE_MODE | SDD_RETURN_TO_ZERO,
						INDIVIDUAL_UPDATE);
				}
				else
				{
					ASSERT(g_mpm_engine.mss_ace_next_sdd < ACE_NUMBER_OF_SDDS);
					/* No SDD available so disable trimming for this channel */
					p_channel->trim_type = mpm_trim_type_none;
				}
			}
		}

		/* Get open loop min/nom/max trim pin voltages and/or closed loop
		 * channel target min/nom/max voltages if required.
		 */
		switch (p_channel->trim_type)
		{
			case mpm_trim_type_closed_loop:
			{
				/* Get low, nominal & high closed loop target voltages */
				for (i = 0; i < mpm_trim_target_max; i++)
				{
					p_channel->closed_loop_trim_target_channel_voltage[i] =
						ACE_convert_from_mV(
							p_channel->h_ace_channel,
							mpm_config_get_channel_margin_voltage(channel_num, i)
						);
				}
				g_mpm_engine.b_do_closed_loop_trimming = true;
			}
			/* Note - no break - deliberate fall through to open loop case
			 * because closed loop implies open loop
			 */

			case mpm_trim_type_open_loop:
			{
				/* Get low, nominal & high open loop trim pin voltages */
				for (i = 0; i < mpm_trim_target_max; i++)
				{
					p_channel->open_loop_trim_pin_voltage[i] =
						mpm_config_get_channel_trim_pin_voltage(channel_num, i);
				}
				g_mpm_engine.b_do_open_loop_trimming = true;
			}
			break;

			case mpm_trim_type_none:
			default:
			{
				/* Do nothing */
			}
			break;
		}
	}
}

static void mpm_enumerate_digital_channels()
{
	/* TODO:
	 * - some code duplicated from mpm_enumerate_analog_channels()
	 *   could be factored into shared utility functions.
	 * - analog channels store ACE/PPE (threshold etc.) values in uint16_t but
	 *   for digital channels we store real world mV values in same type. Is
	 *   this safe?
	 * - sanity check that DPOL can be contacted via PMBus
	 * - read/compare & only program if necessary
	 * - what I2C master address to use (currently fixed to 0x10)?
	 * - only init I2C/PMBus if at least one DPOL configured
	 */
	mpm_channel_number_t channel_num;
	mpm_channel_t *p_channel;
    uint8_t  pmbus_buffer[2] = { 0, 0 };

	/*
	 * Initialize PMBus first
	 */

	/* Init CoreI2C */
	I2C_init(
		&(g_mpm_engine.pmbus_corei2c),
		MPM_DPOL_PMBUS_I2C_BASE_ADDRESS,
		MPM_PMBUS_MASTER_I2C_ADDR,
		I2C_PCLK_DIV_256);

	/* Open UI2C channel */
    UI2C_core_i2c_open(
    	&(g_mpm_engine.pmbus_ui2c_channel),
    	&(g_mpm_engine.pmbus_corei2c));

    /* Enable SMBus timing
     * Note: pass dummy 0MHz frequency - see ui2c_dal.h:UI2C_smbus_init()
     * comments
     */
    UI2C_smbus_init(&(g_mpm_engine.pmbus_ui2c_channel), 0);

	for (
		channel_num = 1;
		channel_num < MPM_MAX_NUMBER_OF_CHANNELS;
		channel_num++)
	{
		/* Skip non DPOL channels */
		if (!mpm_config_get_channel_is_dpol(channel_num))
		{
			continue;
		}

		/* Get channel descriptor */
		p_channel = &(g_mpm_engine.channels[channel_num - 1]);

		/* Sanity check channel not already used */
		ASSERT(p_channel->channel_type == mpm_channel_type_unused);
		if (mpm_channel_type_unused != p_channel->channel_type)
		{
			continue;
		}

		/* We have a DPOL/digital channel! */
		p_channel->channel_type = mpm_channel_type_DPOL;

		/* Infer channel polarity from sign of first threshold */
		p_channel->is_positive =
			(mpm_config_get_channel_threshold(channel_num, 0) >= 0) ?
				true : false;

		/* PMBus support */
		p_channel->pmbus_i2c_bus = mpm_config_get_channel_i2c_bus(channel_num);
		p_channel->pmbus_i2c_slave_addr = mpm_config_get_channel_i2c_address(channel_num);

	    PMBUS_add_slave
	    (
	    	&(g_mpm_engine.pmbus_ui2c_channel),
	    	&(p_channel->pmbus_slave_info),
	    	p_channel,
	    	0 /* total_supported_page */,
	    	mpm_config_get_channel_i2c_address(channel_num),
	    	0 /* pec_support */
	    );

		/*
		 * Get VOUT_MODE
		 * TODO: for now only deals with getting linear mode exponent.
		 * Needs to be extended to cater for VID and direct modes too?
		 */
		p_channel->pmbus_vout_mode_linear_exponent = 0;

		if (PMBUS_read_byte(
			&(p_channel->pmbus_slave_info),
			MPM_PMBUS_VOUT_MODE,
			0 /* is_extended_command */,
			pmbus_buffer) == PMBUS_READ_SUCCESS)
		{
			/* Switch on VOUT_MODE[7:5]
			 * 000 = linear
			 * 001 = VID
			 * 010 = direct
			 */
			switch ((pmbus_buffer[0] & MPM_PMBUS_VOUT_MODE_MODE_MASK)
				>> MPM_PMBUS_VOUT_MODE_MODE_SHIFT)
			{
				case MPM_PMBUS_VOUT_MODE_LINEAR_MODE:
				{
					/* VOUT_MODE[4:0] parameter is signed 5 bit exponent
					 * which we extend to a 16 bit signed integer
					 */
					p_channel->pmbus_vout_mode_linear_exponent =
						pmbus_buffer[0] & MPM_PMBUS_VOUT_MODE_PARAMETER_MASK;
					if (p_channel->pmbus_vout_mode_linear_exponent >
						(MPM_PMBUS_VOUT_MODE_PARAMETER_MASK >> 1))
					{
						/* Negative exponent */
						p_channel->pmbus_vout_mode_linear_exponent |=
							MPM_PMBUS_VOUT_MODE_LINEAR_EXPONENT_NEGATIVE_PAD;
					}
				}
				break;

				case MPM_PMBUS_VOUT_MODE_VID_MODE:
				case MPM_PMBUS_VOUT_MODE_DIRECT_MODE:
				default:
				{
					ASSERT(0);
				}
				break;
			}
		}

		/* Intersil [ZL6105] DPOLs seem to require MFR_CONFIG (MFR_SPECIFIC_00)
		 * and USER_CONFIG (MFR_SPECIFIC_01) to be programmed in order to
		 * work out of the box with the DMPM-DB
		 */
		/* MFR_CONFIG */
#undef MPM_PMBUS_INTERSIL_MFR_CONFIG_VALUE
#define MPM_PMBUS_INTERSIL_MFR_CONFIG_VALUE 0x8B10

		pmbus_buffer[0] = (uint8_t)(MPM_PMBUS_INTERSIL_MFR_CONFIG_VALUE);
		pmbus_buffer[1] = (uint8_t)(MPM_PMBUS_INTERSIL_MFR_CONFIG_VALUE >> 8);

		PMBUS_write_word(
			&(p_channel->pmbus_slave_info),
			MPM_PMBUS_INTERSIL_MFR_CONFIG,
			0 /* is_extended_command */,
			pmbus_buffer
		);

#undef MPM_PMBUS_INTERSIL_USER_CONFIG_VALUE
#define MPM_PMBUS_INTERSIL_USER_CONFIG_VALUE 0x0011

		pmbus_buffer[0] = (uint8_t)(MPM_PMBUS_INTERSIL_USER_CONFIG_VALUE);
		pmbus_buffer[1] = (uint8_t)(MPM_PMBUS_INTERSIL_USER_CONFIG_VALUE >> 8);

		PMBUS_write_word(
			&(p_channel->pmbus_slave_info),
			MPM_PMBUS_INTERSIL_USER_CONFIG,
			0 /* is_extended_command */,
			pmbus_buffer
		);

		/* Lineage PDT012A0X needs VOUT_SCALE_LOOP programmed in order
		 * for VOUT_MARGIN_LOW/HIGH to work properly.
		 */
		pmbus_buffer[0] = (uint8_t)(MPM_PMBUS_LINEAGE_VOUT_SCALE_LOOP_VALUE);
		pmbus_buffer[1] = (uint8_t)(MPM_PMBUS_LINEAGE_VOUT_SCALE_LOOP_VALUE >> 8);

		PMBUS_write_word(
			&(p_channel->pmbus_slave_info),
			MPM_PMBUS_VOUT_SCALE_LOOP,
			0 /* is_extended_command */,
			pmbus_buffer
		);

		/* Program DPOL nominal, thresholds & margin low/high */

		/* Nominal */
		mpm_pmbus_mv_to_vout_linear(
			mpm_config_get_channel_margin_voltage(channel_num, mpm_trim_target_nominal),
			pmbus_buffer,
			p_channel->pmbus_vout_mode_linear_exponent);

		PMBUS_write_word(
			&(p_channel->pmbus_slave_info),
			MPM_PMBUS_VOUT_COMMAND,
			0 /* is_extended_command */,
			pmbus_buffer
		);

		/* Margin low */
		mpm_pmbus_mv_to_vout_linear(
			mpm_config_get_channel_margin_voltage(channel_num, mpm_trim_target_low),
			pmbus_buffer,
			p_channel->pmbus_vout_mode_linear_exponent);

		PMBUS_write_word(
			&(p_channel->pmbus_slave_info),
			MPM_PMBUS_VOUT_MARGIN_LOW,
			0 /* is_extended_command */,
			pmbus_buffer
		);

		/* Margin high */
		mpm_pmbus_mv_to_vout_linear(
			mpm_config_get_channel_margin_voltage(channel_num, mpm_trim_target_high),
			pmbus_buffer,
			p_channel->pmbus_vout_mode_linear_exponent);

		PMBUS_write_word(
			&(p_channel->pmbus_slave_info),
			MPM_PMBUS_VOUT_MARGIN_HIGH,
			0 /* is_extended_command */,
			pmbus_buffer
		);

		/* OV2 */
		mpm_pmbus_mv_to_vout_linear(
			mpm_config_get_channel_threshold(channel_num, mpm_ov2_index),
			pmbus_buffer,
			p_channel->pmbus_vout_mode_linear_exponent);

		PMBUS_write_word(
			&(p_channel->pmbus_slave_info),
			MPM_PMBUS_VOUT_OV_FAULT_LIMIT,
			0 /* is_extended_command */,
			pmbus_buffer
		);

		/* OV1 */
		mpm_pmbus_mv_to_vout_linear(
			mpm_config_get_channel_threshold(channel_num, mpm_ov1_index),
			pmbus_buffer,
			p_channel->pmbus_vout_mode_linear_exponent);

		PMBUS_write_word(
			&(p_channel->pmbus_slave_info),
			MPM_PMBUS_VOUT_MAX,
			0 /* is_extended_command */,
			pmbus_buffer
		);

		/* UV1 */
		mpm_pmbus_mv_to_vout_linear(
			mpm_config_get_channel_threshold(channel_num, mpm_uv1_index),
			pmbus_buffer,
			p_channel->pmbus_vout_mode_linear_exponent);

		PMBUS_write_word(
			&(p_channel->pmbus_slave_info),
			MPM_PMBUS_POWER_GOOD_ON,
			0 /* is_extended_command */,
			pmbus_buffer
		);

		/* UV2 */
		mpm_pmbus_mv_to_vout_linear(
			mpm_config_get_channel_threshold(channel_num, mpm_uv2_index),
			pmbus_buffer,
			p_channel->pmbus_vout_mode_linear_exponent);

		PMBUS_write_word(
			&(p_channel->pmbus_slave_info),
			MPM_PMBUS_VOUT_UV_FAULT_LIMIT,
			0 /* is_extended_command */,
			pmbus_buffer
		);

		/* Sequencing */
		p_channel->on_sequencing_slot
				= mpm_config_get_channel_sequencing_slot(channel_num);
		p_channel->off_sequencing_slot = p_channel->on_sequencing_slot;

		p_channel->on_delay_ms = mpm_config_get_channel_on_off_delay_ms(
				channel_num, true);
		p_channel->off_delay_ms = mpm_config_get_channel_on_off_delay_ms(
				channel_num, false);
	}
}

/* 
 * MPM engine state management
 */

static void mpm_set_engine_state(mpm_state_t state)
{
	/* Change state if different to existing state */
	if (state != g_mpm_engine.state)
	{
		g_mpm_engine.state = state;

		/* Optionally log state change */
#ifdef MPM_ENABLE_LOGGING
		if (g_mpm_engine.b_log_mpm_engine_state)
		{
			mpm_logging_write(
				mpm_logging_record_type_mpm_state_changed,
				g_mpm_engine.state);
		}
#endif
	}
}

/*
 * Threshold detection
 */

static void mpm_enable_disable_threshold_detection(bool b_enable)
{
	mpm_channel_number_t channel_num;

	for (channel_num = 1; channel_num <= MPM_MAX_NUMBER_OF_CHANNELS; channel_num++)
	{
		if (mpm_is_valid_channel(channel_num))
		{
			mpm_channel_t *p_mpm_channel =
					&(g_mpm_engine.channels[channel_num - 1]);

			if (b_enable)
			{
				/* Call ISR directly to kick-start threshold detection.
				 * Among other things this will enable/disable threshold flag
				 * interrupts as required.
				 */
				mpm_channel_threshold_isr(p_mpm_channel->h_ace_down_threshold);
			}
			else
			{
				/* Disable/clear flag IRQs */
				ACE_disable_flag_irq(p_mpm_channel->h_ace_up_threshold);
				ACE_disable_flag_irq(p_mpm_channel->h_ace_down_threshold);
				ACE_clear_flag_irq(p_mpm_channel->h_ace_up_threshold);
				ACE_clear_flag_irq(p_mpm_channel->h_ace_down_threshold);
			}
		}
	}
}

static void mpm_enable_threshold_detection(void)
{
	mpm_enable_disable_threshold_detection(true);
}

static void mpm_disable_threshold_detection(void)
{
	mpm_enable_disable_threshold_detection(false);
}

/* Translate channel voltage to one of 6 states */
static mpm_channel_state_t mpm_get_state_for_voltage(
		mpm_channel_t *p_mpm_channel, uint16_t voltage)
{
	mpm_channel_state_t channel_state = mpm_channel_is_off;

	if (p_mpm_channel->is_positive)
	{
		if (voltage < p_mpm_channel->thresholds[mpm_off_index])
		{
			channel_state = mpm_channel_is_off;
		}
		else if ((voltage >= p_mpm_channel->thresholds[mpm_off_index])
				&& (voltage < p_mpm_channel->thresholds[mpm_uv2_index]))
		{
			channel_state = mpm_channel_is_uv2;
		}
		else if ((voltage >= p_mpm_channel->thresholds[mpm_uv2_index])
				&& (voltage < p_mpm_channel->thresholds[mpm_uv1_index]))
		{
			channel_state = mpm_channel_is_uv1;
		}
		else if ((voltage >= p_mpm_channel->thresholds[mpm_uv1_index])
				&& (voltage <= p_mpm_channel->thresholds[mpm_ov1_index]))
		{
			channel_state = mpm_channel_is_nominal;
		}
		else if ((voltage > p_mpm_channel->thresholds[mpm_ov1_index])
				&& (voltage <= p_mpm_channel->thresholds[mpm_ov2_index]))
		{
			channel_state = mpm_channel_is_ov1;
		}
		else if (voltage > p_mpm_channel->thresholds[mpm_ov2_index])
		{
			channel_state = mpm_channel_is_ov2;
		}
	}
	else
	{
		if (voltage > p_mpm_channel->thresholds[mpm_off_index])
		{
			channel_state = mpm_channel_is_off;
		}
		else if ((voltage <= p_mpm_channel->thresholds[mpm_off_index])
				&& (voltage > p_mpm_channel->thresholds[mpm_ov2_index]))
		{
			channel_state = mpm_channel_is_ov2;
		}
		else if ((voltage <= p_mpm_channel->thresholds[mpm_ov2_index])
				&& (voltage > p_mpm_channel->thresholds[mpm_ov1_index]))
		{
			channel_state = mpm_channel_is_ov1;
		}
		else if ((voltage <= p_mpm_channel->thresholds[mpm_ov1_index])
				&& (voltage >= p_mpm_channel->thresholds[mpm_uv1_index]))
		{
			channel_state = mpm_channel_is_nominal;
		}
		else if ((voltage < p_mpm_channel->thresholds[mpm_uv1_index])
				&& (voltage >= p_mpm_channel->thresholds[mpm_uv2_index]))
		{
			channel_state = mpm_channel_is_uv1;
		}
		else if (voltage < p_mpm_channel->thresholds[mpm_uv2_index])
		{
			channel_state = mpm_channel_is_uv2;
		}
	}

	return channel_state;
}

/* Channel threshold ISR - triggered by an ACE threshold ISR. Note 
 * that the two thresholds preconfigured in the ACE for MPM channels
 * (a OVER threshold named "UP" and an UNDER threshold named "DOWN")
 * are dynamically adjusted at runtime by the MPM engine.
 */
static void mpm_channel_threshold_isr(ace_flag_handle_t h_ace_threshold)
{
	mpm_channel_number_t mpm_channel_num= MPM_INVALID_CHANNEL_NUMBER;
	mpm_channel_t *p_mpm_channel;
	ace_channel_handle_t h_ace_channel;
	uint16_t channel_voltage;
	uint16_t new_up_threshold = 0;
	uint16_t new_down_threshold = 0;
	bool b_finished = false;

	/* Get channel details before entering loop */
	h_ace_channel = ACE_get_flag_channel(h_ace_threshold);
	channel_voltage = ACE_get_ppe_sample(h_ace_channel);
	mpm_channel_num = g_mpm_engine.ace_to_mpm_channel_lut[h_ace_channel];
	ASSERT(h_ace_channel < ACE_MAX_NUMBER_OF_CHANNELS);
	ASSERT((mpm_channel_num> 0) && (mpm_channel_num <= MPM_MAX_NUMBER_OF_CHANNELS));
	p_mpm_channel = &(g_mpm_engine.channels[mpm_channel_num - 1]);
	ASSERT(p_mpm_channel->h_ace_channel != INVALID_CHANNEL_HANDLE);

	/* If MPM stopped then just clear interrupt and return. This could happen
	 * if threshold ISRs are received after user asked MPM to stop.
	 */
	if (mpm_is_stopped == g_mpm_engine.state)
	{
		ACE_clear_flag_irq(h_ace_threshold);
		/* TODO: fix breach of "single point of return" coding standard */
		return;
	}

	/* Loop while channel is jumping between states */
	while (!b_finished)
	{
		/* Clear any outstanding threshold flag interrupts */
		ACE_clear_flag_irq(p_mpm_channel->h_ace_down_threshold);
		ACE_clear_flag_irq(p_mpm_channel->h_ace_up_threshold);

		/* Find out what state we are in */
		p_mpm_channel->state = mpm_get_state_for_voltage(p_mpm_channel,
				channel_voltage);

		/* Calculate new thresholds */
		switch (p_mpm_channel->state)
		{
		case mpm_channel_is_off:
		{
			if (p_mpm_channel->is_positive)
			{
				new_up_threshold = p_mpm_channel->thresholds[mpm_off_index];
				new_down_threshold = 0;
			}
			else
			{
				new_up_threshold = 0;
				new_down_threshold = p_mpm_channel->thresholds[mpm_off_index];
			}

			/* Update power off sequencing bitmask by clearing bit for the 
			 * channel that has just switched off. Note that both the 
			 * threshold and timer ISRs update this bitmask so wrap update
			 * in a critical section.
			 */
			if (mpm_is_stopping == g_mpm_engine.state)
			{
				__disable_irq();
				g_mpm_engine.sequencing.off_channel_bitmask &= ~(1
						<< (mpm_channel_num - 1));
				__enable_irq();
			}
		}
			break;

		case mpm_channel_is_uv2:
		{
			if (p_mpm_channel->is_positive)
			{
				new_up_threshold = p_mpm_channel->thresholds[mpm_uv2_index];
				new_down_threshold = p_mpm_channel->thresholds[mpm_off_index];
			}
			else
			{
				new_up_threshold = p_mpm_channel->thresholds[mpm_uv2_index];
				new_down_threshold = 0;
			}
		}
			break;

		case mpm_channel_is_uv1:
		{
			new_up_threshold = p_mpm_channel->thresholds[mpm_uv1_index];
			new_down_threshold = p_mpm_channel->thresholds[mpm_uv2_index];
		}
			break;

		case mpm_channel_is_nominal:
		{
			new_up_threshold = p_mpm_channel->thresholds[mpm_ov1_index];
			new_down_threshold = p_mpm_channel->thresholds[mpm_uv1_index];

			/* Update power on sequencing bitmask by clearing bit for the 
			 * channel that has just switched off. Note that both the 
			 * threshold and timer ISRs update this bitmask so wrap update
			 * in a critical section.
			 */
			if (mpm_is_starting == g_mpm_engine.state)
			{
				__disable_irq();
				g_mpm_engine.sequencing.on_channel_bitmask &= ~(1
						<< (mpm_channel_num - 1));
				__enable_irq();
			}
		}
			break;

		case mpm_channel_is_ov1:
		{
			new_up_threshold = p_mpm_channel->thresholds[mpm_ov2_index];
			new_down_threshold = p_mpm_channel->thresholds[mpm_ov1_index];
		}
			break;

		case mpm_channel_is_ov2:
		{
			if (p_mpm_channel->is_positive)
			{
				new_up_threshold = 0;
				new_down_threshold = p_mpm_channel->thresholds[mpm_ov2_index];
			}
			else
			{
				new_up_threshold = p_mpm_channel->thresholds[mpm_off_index];
				new_down_threshold = p_mpm_channel->thresholds[mpm_ov2_index];
			}
		}
			break;

		default:
		{
			/* Placeholder - won't happen */
			ASSERT(0);
		}
			break;
		}

		/* Adjust thresholds */
		if (0 != new_up_threshold)
		{
			ACE_set_flag_threshold(p_mpm_channel->h_ace_up_threshold,
					new_up_threshold);
			ACE_enable_flag_irq(p_mpm_channel->h_ace_up_threshold);
		}
		else
		{
			ACE_disable_flag_irq(p_mpm_channel->h_ace_up_threshold);
		}

		if (0 != new_down_threshold)
		{
			ACE_set_flag_threshold(p_mpm_channel->h_ace_down_threshold,
					new_down_threshold);
			ACE_enable_flag_irq(p_mpm_channel->h_ace_down_threshold);
		}
		else
		{
			ACE_disable_flag_irq(p_mpm_channel->h_ace_down_threshold);
		}

		/* Update flags */
		mpm_generate_flags();

		/* Check in case voltage jumped to another state while processing ISR */

		{
			/* Delay long enough for PPE to resample */
			#define MPM_PPE_LATENCY_DELAY 0x1FF
			volatile uint16_t delay;
			for (delay = 0; delay < MPM_PPE_LATENCY_DELAY; delay++)
			{
				;	/* Do nothing */
			}
		}
		channel_voltage = ACE_get_ppe_sample(h_ace_channel);
		if (mpm_get_state_for_voltage(p_mpm_channel, channel_voltage)
				== p_mpm_channel->state)
		{
			b_finished = true;
		}
	}
}

/* 
 * Power on/off sequencing 
 */

/* Used with qsort() to sort power on sequencing channel list into 
 * ascending order by slot number and power on delay with any 0 (unused)
 * entries appearing at the end of the list (i.e. list is 0 terminated)
 */

/* 
 * On channels sequencing list is sorted...
 * (a) ascending by primary sort key slot number
 * (b) ascending by secondary sort key on delay
 * (c) unused/zero entries at end so list is "0 terminated"
 */
static int compare_channels_on(const void *pv_left, const void *pv_right)
{
	int result = 0;

	mpm_channel_number_t left = *((mpm_channel_number_t *)pv_left);
	mpm_channel_number_t right = *((mpm_channel_number_t *)pv_right);

	if ((MPM_INVALID_CHANNEL_NUMBER == left)
			|| (MPM_INVALID_CHANNEL_NUMBER == right))
	{
		/* Unused entries always "greater" than used entries so
		 * they end up at end of list and list is "0 terminated" 
		 */
		result = (right - left);
	}
	else if (g_mpm_engine.channels[left - 1].on_sequencing_slot
			== g_mpm_engine.channels[right - 1].on_sequencing_slot)
	{
		/* Same slot so sort ascending by on delay */
		result = g_mpm_engine.channels[left - 1].on_delay_ms
				- g_mpm_engine.channels[right - 1].on_delay_ms;
	}
	else
	{
		/* Different slots so sort ascending by slot number */
		result = g_mpm_engine.channels[left - 1].on_sequencing_slot
				- g_mpm_engine.channels[right - 1].on_sequencing_slot;
	}

	return result;
}

/* 
 * Off channels sequencing list forward mode is sorted...
 * (a) ascending by primary sort key slot number
 * (b) ascending by secondary sort key off delay
 * (c) unused/zero entries at end so list is "0 terminated"
 */
static int compare_channels_off_forward(const void *pv_left,
		const void *pv_right)
{
	int result = 0;

	mpm_channel_number_t left = *((mpm_channel_number_t *)pv_left);
	mpm_channel_number_t right = *((mpm_channel_number_t *)pv_right);

	if ((MPM_INVALID_CHANNEL_NUMBER == left)
			|| (MPM_INVALID_CHANNEL_NUMBER == right))
	{
		/* Unused entries always "greater" than used entries so
		 * they end up at end of list and list is "0 terminated" 
		 */
		result = (right - left);
	}
	else if (g_mpm_engine.channels[left - 1].off_sequencing_slot
			== g_mpm_engine.channels[right - 1].off_sequencing_slot)
	{
		/* Same slot so sort ascending by off delay */
		result = g_mpm_engine.channels[left - 1].off_delay_ms
				- g_mpm_engine.channels[right - 1].off_delay_ms;
	}
	else
	{
		/* Different slots so sort ascending by slot number */
		result = g_mpm_engine.channels[left - 1].off_sequencing_slot
				- g_mpm_engine.channels[right - 1].off_sequencing_slot;
	}

	return result;
}

/* 
 * Off channels sequencing list reverse mode is sorted...
 * (a) descending by primary sort key slot number
 * (b) ascending by secondary sort key off delay
 * (c) unused/zero entries at end so list is "0 terminated"
 */
static int compare_channels_off_reverse(const void *pv_left,
		const void *pv_right)
{
	int result = 0;

	mpm_channel_number_t left = *((mpm_channel_number_t *)pv_left);
	mpm_channel_number_t right = *((mpm_channel_number_t *)pv_right);

	if ((MPM_INVALID_CHANNEL_NUMBER == left)
			|| (MPM_INVALID_CHANNEL_NUMBER == right))
	{
		/* Unused entries always "greater" than used entries so
		 * they end up at end of list and list is "0 terminated" 
		 */
		result = (right - left);
	}
	else if (g_mpm_engine.channels[left - 1].off_sequencing_slot
			== g_mpm_engine.channels[right - 1].off_sequencing_slot)
	{
		/* Same slot so sort ascending by off delay */
		result = g_mpm_engine.channels[left - 1].off_delay_ms
				- g_mpm_engine.channels[right - 1].off_delay_ms;
	}
	else
	{
		/* Different slots so sort descending by slot number */
		result = g_mpm_engine.channels[right - 1].off_sequencing_slot
				- g_mpm_engine.channels[left - 1].off_sequencing_slot;
	}

	return result;
}

static void mpm_init_power_sequencing()
{
	mpm_channel_number_t channel_num;
	int (*p_compare_fn)(const void *, const void *) = NULL;

	/* Clear sequencing structure */
	memset(&(g_mpm_engine.sequencing), 0, sizeof(g_mpm_engine.sequencing));

	/* Get sequencing configuration settings */
	g_mpm_engine.sequencing.timeout = mpm_config_get_sequence_slot_timeout_ms();
	g_mpm_engine.sequencing.slot_timeout_action
			= mpm_config_get_sequence_slot_timeout_action();
	g_mpm_engine.sequencing.off_sequence
			= mpm_config_get_power_off_sequence_type();

	/* Enumerate sequenced channels */
	for (channel_num = 1; channel_num <= MPM_MAX_NUMBER_OF_CHANNELS; channel_num++)
	{
		if ((mpm_is_valid_channel(channel_num)) && (MPM_INVALID_SLOT_NUMBER !=
		g_mpm_engine.channels[channel_num - 1].on_sequencing_slot))
		{
			g_mpm_engine.sequencing.on_channels[channel_num] = channel_num;
			g_mpm_engine.sequencing.off_channels[channel_num] = channel_num;
		}
	}

	/* Sort power on channel list */
	qsort(g_mpm_engine.sequencing.on_channels,
		sizeof(g_mpm_engine.sequencing.on_channels) / sizeof(mpm_channel_number_t),
		sizeof(mpm_channel_number_t),
		compare_channels_on);

	/* Sort power off channel list */
	switch (g_mpm_engine.sequencing.off_sequence)
	{
		case mpm_off_sequence_reverse:
		{
			p_compare_fn = compare_channels_off_reverse;
		}
		break;

		case mpm_off_sequence_forward:
		{
			p_compare_fn = compare_channels_off_forward;
		}
		break;

		case mpm_off_sequence_simultaneous:
		{
			/* Power off simultaneous means treat all channels as if they
			 * were in a single slot and just pay attention to off delays.
			 * So here we set the off sequencing slot for all channels to 1
			 */
			for (channel_num = 1;
				 channel_num <= MPM_MAX_NUMBER_OF_CHANNELS;
				 channel_num++)
			{
				if (MPM_INVALID_CHANNEL_NUMBER !=
					g_mpm_engine.sequencing.off_channels[channel_num])
				{
					mpm_channel_t *p_channel;
					p_channel = &(g_mpm_engine.channels[channel_num - 1]);
					p_channel->off_sequencing_slot = 1;
				}
			}
			p_compare_fn = compare_channels_off_forward;
		}
		break;

		default:
		{
			/* Placeholder - won't happen */
			ASSERT(0);
		}
		break;
	}

	/* Sort power off channel list */
	qsort(g_mpm_engine.sequencing.off_channels,
		sizeof(g_mpm_engine.sequencing.off_channels) / sizeof(mpm_channel_number_t),
		sizeof(mpm_channel_number_t),
		p_compare_fn);
}

/* Start power on/off sequencing */
static void mpm_start_power_sequencing(bool b_power_on)
{
	/* Only power on if already stopped. Only power off if already started */
	ASSERT((b_power_on && (g_mpm_engine.state == mpm_is_stopped)) ||
			(!b_power_on && (g_mpm_engine.state == mpm_is_started)));

	if ((b_power_on && (g_mpm_engine.state == mpm_is_stopped)) || (!b_power_on
			&& (g_mpm_engine.state == mpm_is_started)))
	{
		/* If no channels sequenced then we're already done! */
		if (g_mpm_engine.sequencing.on_channels[0]
				== MPM_INVALID_CHANNEL_NUMBER)
		{
            /* TM 20120502: fix for http://bugzilla/show_bug.cgi?id=38218
             * Enable threshold detection even if no channels sequenced.
             * Also artificially transition MPM through starting/started or
             * stopping/stopped and optional log intervening "null" power
             * sequence start/finish events.
             */

			/* First move MPM to starting or stopping state */
			mpm_set_engine_state(b_power_on ? mpm_is_starting : mpm_is_stopping);

			/* Optionally log "null" power sequence start/finish events */
#ifdef MPM_ENABLE_LOGGING
            mpm_log_power_sequencing_event(
            	b_power_on ?
            		mpm_logging_record_type_power_on :
                    mpm_logging_record_type_power_off,
                mpm_logging_power_seq_event_start,
                0 /* slot number not relevant */);

            mpm_log_power_sequencing_event(
            	b_power_on ?
            		mpm_logging_record_type_power_on :
                    mpm_logging_record_type_power_off,
                mpm_logging_power_seq_event_finish,
                0 /* slot number not relevant */);
#endif

            /* Now move MPM to started or stopped state */
			mpm_set_engine_state(b_power_on ? mpm_is_started : mpm_is_stopped);

            /* Start threshold detection */
            if (b_power_on)
            {
            	mpm_enable_threshold_detection();
            }
            else
            {
            	mpm_disable_threshold_detection();
            }
		}
		else
		{
			/* Start power sequencing */
			g_mpm_engine.sequencing.current_slot_number
					= MPM_INVALID_SLOT_NUMBER;
			g_mpm_engine.sequencing.current_channel_index = 0;
			g_mpm_engine.sequencing.current_slot_1st_channel_index = 0;
			g_mpm_engine.sequencing.current_slot_channel_bitmask = 0;
			g_mpm_engine.sequencing.current_slot_timer = 0;
			__disable_irq();
			g_mpm_engine.sequencing.on_channel_bitmask = 0;
			g_mpm_engine.sequencing.off_channel_bitmask = 0;
			__enable_irq();

			/* MPM engine state:
			 * mpm_is_stopped -> mpm_is_starting OR
			 * mpm_is_started -> mpm_is_stopping
			 */
			mpm_set_engine_state(b_power_on ? mpm_is_starting : mpm_is_stopping);

			/* Log power sequencing start */
#ifdef MPM_ENABLE_LOGGING
			mpm_log_power_sequencing_event(
				b_power_on ?
					mpm_logging_record_type_power_on :
					mpm_logging_record_type_power_off,
				mpm_logging_power_seq_event_start,
				0 /* slot number not relevant */);
#endif

			/* Start threshold detection */
			if (b_power_on)
			{
				mpm_enable_threshold_detection();
			}

			/* Start timer */
			MSS_TIM2_init(MSS_TIMER_PERIODIC_MODE);
			MSS_TIM2_load_immediate(g_FrequencyPCLK0
					/ MPM_TIM2_PERIOD_IN_MICROSECONDS);
			MSS_TIM2_start();
			MSS_TIM2_clear_irq();
			MSS_TIM2_enable_irq();
			/* Start timer 1 for shutdown if MPM state is OFF, UV2, OV2 */
			MSS_TIM1_init(MSS_TIMER_PERIODIC_MODE);
			MSS_TIM1_load_immediate(g_FrequencyPCLK0/2); /* 500ms */
			MSS_TIM1_start();
			MSS_TIM1_clear_irq();
			MSS_TIM1_enable_irq();
		}
	}
}

/* Switch on/off regulators for channels identified by regs bitmask */
static void mpm_switch_regulators(uint32_t regs, bool b_on)
{
	uint32_t regs_now;

	regs_now = GPIO_get_outputs(&(g_mpm_engine.gpio_regulator_enables));

	if (b_on)
	{
		/* Start open loop trimming where applicable */
		if (g_mpm_engine.b_do_open_loop_trimming)
		{
			mpm_do_open_loop_trimming(true, regs);
		}
		/* Switch regulator(s) on */
		GPIO_set_outputs(&(g_mpm_engine.gpio_regulator_enables), regs_now | regs);
	}
	else
	{
		/* Switch regulator(s) off */
		GPIO_set_outputs(&(g_mpm_engine.gpio_regulator_enables), regs_now & (~regs));
		/* Stop open loop trimming where applicable */
		if (g_mpm_engine.b_do_open_loop_trimming)
		{
			mpm_do_open_loop_trimming(false, regs);
		}
	}
}

static void mpm_do_power_sequencing(bool b_starting)
{
	mpm_channel_t *p_channel;
	mpm_channel_number_t channel_num;

	/* Get current channel number and descriptor pointer */
	if (b_starting)
	{
		channel_num = g_mpm_engine.sequencing.on_channels[
		g_mpm_engine.sequencing.current_channel_index
		];
	}
	else
	{
		channel_num = g_mpm_engine.sequencing.off_channels[
		g_mpm_engine.sequencing.current_channel_index
		];
	}
	p_channel= &(g_mpm_engine.channels[channel_num - 1]);

	/* Increment slot timer */
	g_mpm_engine.sequencing.current_slot_timer++;

	/* Find out where we are in sequence */
	if ((b_starting ? p_channel->on_sequencing_slot
			: p_channel->off_sequencing_slot)
			!= g_mpm_engine.sequencing.current_slot_number)
	{
		/* End of slot reached */
		if (0 == (b_starting ? g_mpm_engine.sequencing.on_channel_bitmask
				: g_mpm_engine.sequencing.off_channel_bitmask))
		{
			/* All channels in current slot have powered up/down */
			if (MPM_INVALID_CHANNEL_NUMBER == channel_num)
			{
				/* Finished all slots so sequencing complete.
				 * Update engine state and disable timer if no longer needed
				 */

				/* Log power sequencing completed event */
#ifdef MPM_ENABLE_LOGGING
				mpm_log_power_sequencing_event(
					b_starting ?
						mpm_logging_record_type_power_on :
						mpm_logging_record_type_power_off,
					mpm_logging_power_seq_event_finish,
					0 /* Slot number not relevant */);
#endif

				if (b_starting)
				{
					/* MPM engine state: mpm_is_starting -> mpm_is_started */
					mpm_set_engine_state(mpm_is_started);

					/* Disable timer unless needed for closed loop trimming */
					if (!g_mpm_engine.b_do_closed_loop_trimming)
					{
						MSS_TIM2_disable_irq();
					}
				}
				else
				{
					/* Stopped so disable timer interrupt */
					MSS_TIM2_disable_irq();
					MSS_TIM2_clear_irq();
					MSS_TIM1_disable_irq();
					MSS_TIM1_clear_irq();

					/* Stop threshold detection */
					mpm_disable_threshold_detection();

					/* MPM engine state: mpm_is_stopping -> mpm_is_stopped */
					mpm_set_engine_state(mpm_is_stopped);

					/* Clear outputs */
					mpm_set_flag_outputs(g_mpm_engine.output_flags_invert_mask);
				}
			}
			else
			{
				/* Finished slot - move to next slot */
				g_mpm_engine.sequencing.current_slot_timer = 0;
				g_mpm_engine.sequencing.current_slot_1st_channel_index
						= g_mpm_engine.sequencing.current_channel_index;
				g_mpm_engine.sequencing.current_slot_number
						= (b_starting ? p_channel->on_sequencing_slot
								: p_channel->off_sequencing_slot);
				g_mpm_engine.sequencing.current_slot_channel_bitmask = 0;

				/* Create bitmask of channels in this slot */
				{
					uint8_t i =
						g_mpm_engine.sequencing.current_slot_1st_channel_index;
					do
					{
						g_mpm_engine.sequencing.current_slot_channel_bitmask
								|= (1 << (channel_num - 1));
						i++;
						channel_num =
							b_starting ? g_mpm_engine.sequencing.on_channels[i]
								: g_mpm_engine.sequencing.off_channels[i];
						p_channel= &(g_mpm_engine.channels[channel_num - 1]);
					} while ((MPM_INVALID_CHANNEL_NUMBER != channel_num)
							&& ((b_starting ? p_channel->on_sequencing_slot
									: p_channel->off_sequencing_slot)
									== g_mpm_engine.sequencing.current_slot_number));
				}

				if (b_starting)
				{
					__disable_irq();
					g_mpm_engine.sequencing.on_channel_bitmask
						= g_mpm_engine.sequencing.current_slot_channel_bitmask;
					__enable_irq();
				}
				else
				{
					__disable_irq();
					g_mpm_engine.sequencing.off_channel_bitmask
						= g_mpm_engine.sequencing.current_slot_channel_bitmask;
					__enable_irq();
				}

			}
		}
		else if (b_starting &&
				(mpm_slot_timeout_disabled != g_mpm_engine.sequencing.slot_timeout_action) &&
				(g_mpm_engine.sequencing.current_slot_timer >= g_mpm_engine.sequencing.timeout))
		{
			/* Log power on sequencing timeout event */
#ifdef MPM_ENABLE_LOGGING
			mpm_log_power_sequencing_event(
				mpm_logging_record_type_power_on,
				mpm_logging_power_seq_event_timeout,
				0 /* Slot number not relevant */);
#endif

			/* Slot timeout detection & failure action */
			switch (g_mpm_engine.sequencing.slot_timeout_action)
			{
			case mpm_slot_timeout_restart_sequence:
			{
				/* Log power on sequencing restart sequence event */
#ifdef MPM_ENABLE_LOGGING
				mpm_log_power_sequencing_event(
					mpm_logging_record_type_power_on,
					mpm_logging_power_seq_event_restart_sequence,
					0 /* Slot number not relevant */);
#endif

				/* Switch off all regulators */
				mpm_switch_regulators(~0, false);

				g_mpm_engine.sequencing.current_slot_number
						= MPM_INVALID_SLOT_NUMBER;
				g_mpm_engine.sequencing.current_channel_index = 0;
				g_mpm_engine.sequencing.current_slot_1st_channel_index = 0;
				g_mpm_engine.sequencing.current_slot_channel_bitmask = 0;
				g_mpm_engine.sequencing.current_slot_timer = 0;
				__disable_irq();
				g_mpm_engine.sequencing.on_channel_bitmask = 0;
				g_mpm_engine.sequencing.off_channel_bitmask = 0;
				__enable_irq();
			}
			break;

			case mpm_slot_timeout_restart_slot:
			{
				/* Log power on sequencing restart slot event */
#ifdef MPM_ENABLE_LOGGING
				mpm_log_power_sequencing_event(
					mpm_logging_record_type_power_on,
					mpm_logging_power_seq_event_restart_slot,
					g_mpm_engine.sequencing.current_slot_number - 1);
#endif

				/* Switch off regulators for channels in this slot */
				mpm_switch_regulators(
					g_mpm_engine.sequencing.current_slot_channel_bitmask,
					false);

				/* Go back to first channel in this slot */
				g_mpm_engine.sequencing.current_channel_index
					= g_mpm_engine.sequencing.current_slot_1st_channel_index;
				g_mpm_engine.sequencing.current_slot_timer = 0;

				/* Reset power on sequencing channel bitmask */
				__disable_irq();
				g_mpm_engine.sequencing.on_channel_bitmask
					= g_mpm_engine.sequencing.current_slot_channel_bitmask;
				__enable_irq();
			}
			break;

			case mpm_slot_timeout_shutdown:
			{
				/* Log power on sequencing shutdown */
#ifdef MPM_ENABLE_LOGGING
				mpm_log_power_sequencing_event(
					mpm_logging_record_type_power_on,
					mpm_logging_power_seq_event_shutdown,
					0 /* Slot number not relevant */);
#endif

				/* Stop threshold monitoring */
				mpm_disable_threshold_detection();

				/* Switch off all regulators */
				mpm_switch_regulators(~0, false);

				/* Stop timer */
				MSS_TIM2_disable_irq();
				MSS_TIM2_clear_irq();
				MSS_TIM1_disable_irq();
				MSS_TIM1_clear_irq();

				/* Stop threshold detection */
				mpm_disable_threshold_detection();

				/* MPM engine state: mpm_is_stopping -> mpm_is_stopped */
				mpm_set_engine_state(mpm_is_stopped);

				/* Clear outputs */
				mpm_set_flag_outputs(g_mpm_engine.output_flags_invert_mask);
			}
			break;

			case mpm_slot_timeout_skip_slot:
			{
				/* Log power on sequencing skip slot */
#ifdef MPM_ENABLE_LOGGING
				mpm_log_power_sequencing_event(
					mpm_logging_record_type_power_on,
					mpm_logging_power_seq_event_skip_slot,
					g_mpm_engine.sequencing.current_slot_number - 1);
#endif

				/* Ignore timeout & move to next slot */
				__disable_irq();
				g_mpm_engine.sequencing.on_channel_bitmask = 0;
				__enable_irq();
			}
				break;

			case mpm_slot_timeout_disabled:
			default:
			{
				/* Placeholder - won't happen */
				ASSERT(0);
			}
				break;
			}
		}
	}
	else
	{
		/* Still in same slot */
		if (b_starting && (g_mpm_engine.sequencing.current_slot_timer
				>= p_channel->on_delay_ms))
		{
			/* Switch regulator on */
			mpm_switch_regulators(1 << (channel_num - 1), true);

			/* Check if channel is immediately nominal */
			if (mpm_channel_is_nominal == mpm_get_channel_state(channel_num))
			{
				__disable_irq();
				g_mpm_engine.sequencing.on_channel_bitmask &= ~(1
						<< (channel_num - 1));
				__enable_irq();
			}

			/* Next channel */
			g_mpm_engine.sequencing.current_channel_index++;
		}
		else if ((!b_starting) && (g_mpm_engine.sequencing.current_slot_timer
				>= p_channel->off_delay_ms))
		{
			/* Switch regulator off */
			mpm_switch_regulators(1 << (channel_num - 1), false);

			/* Check if channel is immediately off */
			if (mpm_channel_is_off == mpm_get_channel_state(channel_num))
			{
				__disable_irq();
				g_mpm_engine.sequencing.off_channel_bitmask &= ~(1
						<< (channel_num - 1));
				__enable_irq();
			}

			/* Next channel */
			g_mpm_engine.sequencing.current_channel_index++;
		}

		/* TODO: check for further channels in current slot with same
		 * on/off delay as current channel. For now channels in the same
		 * slot with the same on/off delay will actually switch on/off
		 * at least 1ms apart.
		 */
	}
}

/*
 * Output flags
 */

static void mpm_init_output_flags_table()
{
	mpm_channel_number_t channel_num;
	uint8_t flag_number;

	/* For each channel */
	for (channel_num = 1; channel_num <= MPM_MAX_NUMBER_OF_CHANNELS; channel_num++)
	{
		if (mpm_is_valid_channel(channel_num))
		{
			uint8_t channel_state;
			mpm_channel_t *p_channel;
			p_channel = &(g_mpm_engine.channels[channel_num - 1]);

			/* For each channel state */
			for (channel_state = 0;
				 channel_state < MPM_NUMBER_OF_CHANNEL_STATES;
				 channel_state++)
			{
				/* Bitmask indicating how the current channel and state 
				 * contribute towards the evaluation of the current flag
				 */
				uint32_t bitmask = 0;

				/* For each output flag */
				for (flag_number = 0;
					 flag_number < MPM_NUMBER_OF_FLAG_OUTPUTS;
					 flag_number++)
				{
					/* Get the channel state bitmask for the condition
					 * configured for this output and channel
					 */
					uint8_t channel_state_bitmask;
					uint8_t set_for_or;
					uint8_t flag_is_anded;

					channel_state_bitmask
							= mpm_config_get_flag_condition_channel_state_bitmask(
									flag_number, channel_num);

					/* Does the current output flag depend on the current
					 * channel state? Remember that AND logic equations are 
					 * converted using De Morgan.
					 */
					set_for_or
							= ((channel_state_bitmask & (1 << channel_state))
									!= 0) ? 1 : 0;

					flag_is_anded
							= mpm_config_get_flag_uses_and_logic(flag_number) ? 1
									: 0;

					if ((set_for_or ^ flag_is_anded) && (channel_state_bitmask
							!= 0))
					{
						bitmask |= 1 << flag_number;
					}
				}

				g_mpm_engine.output_flags_table[
				    ((channel_num - 1) * MPM_NUMBER_OF_CHANNEL_STATES) +
				    channel_state] = bitmask;
			}
		}
	}

	/* Contruct bitmasks indicating which flags use AND rather than OR
	 * logic, which falgs are inverted, which flags are updated during
	 * power sequencing and how/when flags are logged.
	 */
	g_mpm_engine.output_flags_and_mask = 0;
	g_mpm_engine.output_flags_invert_mask = 0;
	g_mpm_engine.output_flags_update_during_sequencing_mask = 0;
#ifdef MPM_ENABLE_LOGGING
	g_mpm_engine.output_flags_log_0_to_1_mask = 0;
	g_mpm_engine.output_flags_log_1_to_0_mask = 0;
	g_mpm_engine.output_flags_log_during_power_on_mask = 0;
	g_mpm_engine.output_flags_log_during_power_off_mask = 0;
#endif

	for (flag_number = 0;
		 flag_number < MPM_NUMBER_OF_FLAG_OUTPUTS;
		 flag_number++)
	{
		/* AND mask */
		if (mpm_config_get_flag_uses_and_logic(flag_number))
		{
			g_mpm_engine.output_flags_and_mask |= (1 << flag_number);
		}

		/* Invert mask */
		if (mpm_config_get_flag_is_active_low(flag_number))
		{
			g_mpm_engine.output_flags_invert_mask |= (1 << flag_number);
		}

		/* Update during power sequencing mask */
		if (mpm_config_get_flag_update_during_sequencing(flag_number))
		{
			g_mpm_engine.output_flags_update_during_sequencing_mask |= (1 << flag_number);
		}

		/* Logging masks */
#ifdef MPM_ENABLE_LOGGING
		if (mpm_config_get_flag_log_during_power_on(flag_number))
		{
			g_mpm_engine.output_flags_log_during_power_on_mask |= (1 << flag_number);
		}

		if (mpm_config_get_flag_log_during_power_off(flag_number))
		{
			g_mpm_engine.output_flags_log_during_power_off_mask |= (1 << flag_number);
		}

		if (mpm_config_get_flag_log_0_to_1(flag_number))
		{
			g_mpm_engine.output_flags_log_0_to_1_mask |= (1 << flag_number);
		}

		if (mpm_config_get_flag_log_1_to_0(flag_number))
		{
			g_mpm_engine.output_flags_log_1_to_0_mask |= (1 << flag_number);
		}
#endif
	}

	/* For each flag the digital input logic operation */
	for (flag_number = 0;
		 flag_number < MPM_NUMBER_OF_FLAG_OUTPUTS;
		 flag_number++)
	{
		g_mpm_engine.output_flags_input_logic[flag_number]
			= mpm_config_get_flag_input_logic(flag_number);
	}
}

static void mpm_generate_flags()
{
	mpm_channel_number_t channel_num;
	mpm_channel_t *p_channel;
	uint32_t flags_old;
	uint32_t flags_new;
	uint32_t flags_temp;
	uint32_t inputs;
	uint8_t flag;
#ifdef MPM_ENABLE_LOGGING
	uint32_t changed;
#endif

	/* Get current flags */
	flags_old = mpm_get_digital_outputs();

	/* Update output flags based on current channel states */
	flags_temp = 0;
	for (channel_num = 1;
		 channel_num <= MPM_MAX_NUMBER_OF_CHANNELS;
		 channel_num++)
	{
		p_channel = &(g_mpm_engine.channels[channel_num - 1]);
		flags_temp |= g_mpm_engine.output_flags_table[
		    ((channel_num - 1) * MPM_NUMBER_OF_CHANNEL_STATES) + p_channel->state];
	}

	/* Apply AND mask. Remember that output flag logic equations using AND
	 * are transformed using De Morgan.
	 */
	flags_temp ^= g_mpm_engine.output_flags_and_mask;

	/* Combine input flag <n> with output <n> if necessary */
	inputs = mpm_get_digital_inputs();
	flags_new = 0;

	/* Combine bits starting at MSb and working down to LSb */
	for (flag = MPM_NUMBER_OF_FLAG_OUTPUTS; flag != 0; flag--)
	{
		/* Extract next bit from flags_temp and inputs */
		uint8_t shift = flag - 1;
		uint8_t flag_bit = (flags_temp >> shift) & 1;
		uint8_t input_bit = (inputs >> shift) & 1;

		/* Combine as required */
		switch (g_mpm_engine.output_flags_input_logic[flag - 1])
		{
			case mpm_input_ignore:
			break;

			case mpm_input_or:
			{
				flag_bit |= input_bit;
			}
			break;

			case mpm_input_and:
			{
				flag_bit &= input_bit;
			}
			break;

			case mpm_input_xor:
			{
				flag_bit ^= input_bit;
			}
			break;

			case mpm_input_not_or:
			{
				flag_bit |= (input_bit ? 0 : 1);
			}
			break;

			case mpm_input_not_and:
			{
				flag_bit &= (input_bit ? 0 : 1);
			}
			break;

			default:
			{
				ASSERT(0);
			}
			break;
		}

		/* Store resultant bit in bit position 0 */
		flags_new <<= 1;
		flags_new |= flag_bit;
	}

	/* Apply inversion mask */
	flags_new ^= g_mpm_engine.output_flags_invert_mask;

	/* Mask out flag updates during power sequencing where applicable */
	if (g_mpm_engine.state != mpm_is_started)
	{
		flags_new =
			(flags_new & g_mpm_engine.output_flags_update_during_sequencing_mask) |
			(flags_old & ~(g_mpm_engine.output_flags_update_during_sequencing_mask));
	}

	/* Write flags out to GPIO */
	mpm_set_flag_outputs(flags_new);

	/* Logging - if necessary (i.e. something changed & something logged) */
#ifdef MPM_ENABLE_LOGGING
	changed = flags_old ^ flags_new;
	if (changed &&
		((g_mpm_engine.output_flags_log_0_to_1_mask != 0) ||
		 (g_mpm_engine.output_flags_log_1_to_0_mask != 0)))
	{
		uint32_t changed_to_1;
		uint32_t changed_to_0;

		/* What 0 -> 1 and 1 -> 0 transitions? */
		changed_to_1 = changed & flags_new;
		changed_to_0 = changed & flags_old;

		/* Mask out changes during power sequencing where applicable */
		if (g_mpm_engine.state == mpm_is_starting)
		{
			changed_to_1 &= g_mpm_engine.output_flags_log_during_power_on_mask;
			changed_to_0 &= g_mpm_engine.output_flags_log_during_power_on_mask;
		}
		else if (g_mpm_engine.state == mpm_is_stopping)
		{
			changed_to_1 &= g_mpm_engine.output_flags_log_during_power_off_mask;
			changed_to_0 &= g_mpm_engine.output_flags_log_during_power_off_mask;
		}

		/* What do we need to log? */
		changed_to_1 &= g_mpm_engine.output_flags_log_0_to_1_mask;
		changed_to_0 &= g_mpm_engine.output_flags_log_1_to_0_mask;

		/* Finally - log whatever's left */
		if ((changed_to_1 != 0) || (changed_to_0 != 0))
		{
			mpm_log_flags(changed_to_0, changed_to_1);
		}
	}
#endif
}

static void mpm_set_flag_outputs(uint32_t flags)
{
	GPIO_set_outputs(&(g_mpm_engine.gpio_digital_ios), flags);
}

/* 
 * Open/closed loop trimming 
 */

/* DAC max and step values */

/* Step size determined empirically using Actel boards */
#ifndef MPM_PWM_DAC_STEP
#define MPM_PWM_DAC_STEP (0xFF)
#endif

#ifndef MPM_SDD_DAC_STEP
#define MPM_SDD_DAC_STEP (0x20)
#endif


static void mpm_do_closed_loop_trimming()
{
	uint32_t test_var2;
	char print_buf[10];


	/* Iterate over all channels */
	mpm_channel_number_t channel_num;
	for (channel_num = 1;
		 channel_num <= MPM_MAX_NUMBER_OF_CHANNELS;
		 channel_num++)
	{

		if (mpm_is_valid_channel(channel_num))
		{
			mpm_channel_t *p_channel;
			p_channel = &(g_mpm_engine.channels[channel_num - 1]);

			p_channel->dac_duty_cycle = ((SLOPE*(p_channel->x_value))+CONSTANT);

			/* Is open loop trimming enabled for this channel? */
			if (p_channel->trim_type == mpm_trim_type_closed_loop)
			{
				/* Read channel output */
				uint16_t channel_output;
				int16_t dac_delta = 0;
				channel_output = ACE_get_ppe_sample(p_channel->h_ace_channel);

				/* Adjust trim DAC as required to get back to target voltage */
				switch (p_channel->trim_dac_type)
				{
					case mpm_trim_dac_type_sdd:
						dac_delta = MPM_SDD_DAC_STEP;
					break;

					case mpm_trim_dac_type_pwm:
						dac_delta = MPM_PWM_DAC_STEP;
					break;

					default:
					break;
				}


					if ((mpm_channel_is_nominal > mpm_get_channel_state(channel_num)))
				{
					p_channel->x_value -= dac_delta;
				}
					else if ((mpm_channel_is_nominal < mpm_get_channel_state(channel_num)))
				{
					p_channel->x_value += dac_delta;
				}


				channel_output = p_channel->dac_duty_cycle;

				/* Guard against duty cycle under/overflow */
				if (((dac_delta < 0) && (p_channel->dac_duty_cycle
						>= -dac_delta)) || ((dac_delta > 0)
						&& (p_channel->dac_duty_cycle
								<= (MPM_DAC_MAX - dac_delta))))
				{

					//test_var1 = p_channel->x_value;
					test_var2 = p_channel->dac_duty_cycle;
					if(channel_num==2)
					{
					tfp_sprintf(
						print_buf,
						"%d \n\r",
						test_var2);

				//	MSS_UART_polled_tx(&g_mss_uart0,print_buf,sizeof(print_buf));
					}

					/* Apply new settings */
					switch (p_channel->trim_dac_type)
					{
						case mpm_trim_dac_type_sdd:
							ACE_set_sdd_value(
								p_channel->trimming_sdd,
								p_channel->dac_duty_cycle);
						break;

						case mpm_trim_dac_type_pwm:
							PWM_set_duty_cycle(
								&(g_mpm_engine.pwm_trimmer),
								channel_num,
								p_channel->dac_duty_cycle);
						break;

						default:
						break;
					}
					ASSERT (((dac_delta < 0) && (p_channel->dac_duty_cycle < channel_output)) ||
							((dac_delta> 0) && (p_channel->dac_duty_cycle> channel_output)));
				}
			}
		}
	}

/*	tfp_sprintf(
		print_buf,
		" \n\r"
		);

	MSS_UART_polled_tx(&g_mss_uart0,print_buf,sizeof(print_buf));*/

}

/* Maximum trim pin voltages in mV */
#ifndef MAX_PWM_TRIM_PIN_VOLTAGE
#define MAX_PWM_TRIM_PIN_VOLTAGE 3300
#endif

#ifndef MAX_SDD_TRIM_PIN_VOLTAGE
#define MAX_SDD_TRIM_PIN_VOLTAGE 2560
#endif

static uint16_t mpm_trim_pin_voltage_to_dac_duty_cycle(uint16_t voltage, mpm_trim_dac_type_t dac_type)
{
	uint16_t dac_duty_cycle = 0;

	switch (dac_type)
	{
		case mpm_trim_dac_type_sdd:
		{
			ASSERT(voltage <= MAX_SDD_TRIM_PIN_VOLTAGE);
			dac_duty_cycle =
				((MPM_DAC_MAX / MAX_SDD_TRIM_PIN_VOLTAGE) * voltage);
				// ((double)voltage / MAX_SDD_TRIM_PIN_VOLTAGE) * MPM_DAC_MAX;
		}
		break;

		case mpm_trim_dac_type_pwm:
		{
			ASSERT(voltage <= MAX_PWM_TRIM_PIN_VOLTAGE);
			dac_duty_cycle =
				((MPM_DAC_MAX / MAX_PWM_TRIM_PIN_VOLTAGE) * voltage);
				// ((double)voltage / MAX_PWM_TRIM_PIN_VOLTAGE) * MPM_DAC_MAX;
		}
		break;

		default:
		{
			/* Placeholder - won't happen */
		}
		break;
	}

	return dac_duty_cycle;
}

static void mpm_do_open_loop_trimming(bool b_start, uint32_t channel_bitmask)
{
	/* Iterate over all channels indicated by bitmask */
	mpm_channel_number_t channel_num;
	channel_num = 1;
	while ((channel_num <= MPM_MAX_NUMBER_OF_CHANNELS) && (channel_bitmask != 0))
	{
		if ((channel_bitmask & 1) && mpm_is_valid_channel(channel_num))
		{
			mpm_channel_t *p_channel;
			p_channel = &(g_mpm_engine.channels[channel_num - 1]);

			/* Is open loop trimming enabled for this channel? */
			if (p_channel->trim_type != mpm_trim_type_none)
			{
				if (b_start)
				{
					/* Convert trim pin voltage to DAC duty cycle */
					p_channel->dac_duty_cycle =
						mpm_trim_pin_voltage_to_dac_duty_cycle(
							p_channel->open_loop_trim_pin_voltage[p_channel->trim_target],
							p_channel->trim_dac_type);

					/* Enable trimming DAC & set duty cycle */
					switch (p_channel->trim_dac_type)
					{
						case mpm_trim_dac_type_sdd:
						{
							ACE_set_sdd_value(
								p_channel->trimming_sdd,
								p_channel->dac_duty_cycle);
							ACE_enable_sdd(p_channel->trimming_sdd);
						}
						break;

						case mpm_trim_dac_type_pwm:
						{
							/* PWM_set_duty_cycle() also enables the PWM output */
							PWM_set_duty_cycle(
								&(g_mpm_engine.pwm_trimmer),
								channel_num, p_channel->dac_duty_cycle);
						}
						break;

						default:
						{
							/* Placeholder - won't happen */
						}
						break;
					}
				}
				else
				{
					/* Disable trimming DAC */
					/* TODO: perhaps drive trimming outputs with Z as well? */
					switch (p_channel->trim_dac_type)
					{
						case mpm_trim_dac_type_sdd:
						{
							ACE_disable_sdd(p_channel->trimming_sdd);
						}
						break;

						case mpm_trim_dac_type_pwm:
						{
							PWM_disable(&(g_mpm_engine.pwm_trimmer), channel_num);
						}
						break;

						default:
						{
							/* Placeholder - won't happen */
						}
						break;
					}
				}
			}
		}

		channel_num++;
		channel_bitmask >>= 1;
	}
}

#ifdef MPM_ENABLE_I2C
static bool mpm_adjust_channel_trim
(
	mpm_channel_number_t channel_num,
	mpm_trim_target_t trim_target
)
{
	bool b_result = true;

	if (mpm_is_valid_channel(channel_num))
	{
		mpm_channel_t *p_channel;
		p_channel = &(g_mpm_engine.channels[channel_num - 1]);

		if (p_channel->channel_type == mpm_channel_type_DPOL)
		{
			uint8_t pmbus_buffer[1];

			switch (trim_target)
			{
				case mpm_trim_target_nominal:
				{
					pmbus_buffer[0] = MPM_PMBUS_OPERATION_ON;
				}
				break;

				case mpm_trim_target_low:
				{
					pmbus_buffer[0] = MPM_PMBUS_OPERATION_MARGIN_LOW_ACT_ON_FAULT;
				}
				break;

				case mpm_trim_target_high:
				{
					pmbus_buffer[0] = MPM_PMBUS_OPERATION_MARGIN_HIGH_ACT_ON_FAULT;
				}
				break;

				default:
				{
					ASSERT(0);
				}
				break;
			}

			PMBUS_write_byte(
				&(p_channel->pmbus_slave_info),
				MPM_PMBUS_OPERATION,
				0 /* is_extended_command */,
				pmbus_buffer
			);
		}
		else
		{
			switch (p_channel->trim_type)
			{
				case mpm_trim_type_closed_loop:
				{
					/* mpm_do_closed_loop_trimming() will use this */
					p_channel->trim_target = trim_target;
				}
				break;

				case mpm_trim_type_open_loop:
				{
					/* Adjust the channel's trimming DAC duty cycle */
					p_channel->trim_target = trim_target;

					/* Convert trim pin voltage to DAC duty cycle */
					p_channel->dac_duty_cycle =
						mpm_trim_pin_voltage_to_dac_duty_cycle(
							p_channel->open_loop_trim_pin_voltage[p_channel->trim_target],
							p_channel->trim_dac_type);

					switch (p_channel->trim_dac_type)
					{
						case mpm_trim_dac_type_sdd:
						{
							ACE_set_sdd_value(
								p_channel->trimming_sdd,
								p_channel->dac_duty_cycle);
						}
						break;

						case mpm_trim_dac_type_pwm:
						{
							PWM_set_duty_cycle(
								&(g_mpm_engine.pwm_trimmer),
								channel_num, p_channel->dac_duty_cycle);
						}
						break;

						default:
						{
							/* Placeholder - won't happen */
						}
						break;
					}
				}
				break;

				default:
				{
					b_result = false;
				}
				break;
			}
		}
	}
	else
	{
		b_result = false;
	}

	return b_result;
}
#endif

#if 0
/* Example code for dealing with CoreGPIO based digital inputs on an 
 * interrupt driven basis. E.g. A2F-DEV-KIT board A2F_DIP switches
 * wired to a fabric based CoreGPIO which raises an interrupt to the 
 * MSS/M3 when a switch is changed.
 */
__attribute__((__interrupt__)) void Fabric_IRQHandler(void)
{
	gpio_id_t input;

	/* Update flags */
	mpm_generate_flags();

	/* Clear all CoreGPIO interrupts */
	for (input = GPIO_0; input <= GPIO_31; input++)
	{
		GPIO_clear_irq(&(g_mpm_engine.gpio_digital_ios), input);
	}

	/* Clear FABINT */
	NVIC_ClearPendingIRQ(Fabric_IRQn);
}
#endif

/* 
 * MSS TIM2 timer interrupt handler which drives power on/off sequencing and
 * closed loop trimming
 */

__attribute__((__interrupt__)) void Timer2_IRQHandler(void)
{
	switch (g_mpm_engine.state)
	{
		/* Power on sequencing */
		case mpm_is_starting:
		{
			mpm_do_power_sequencing(true);
		}
		break;

		/* Power off sequencing */
		case mpm_is_stopping:
		{
			mpm_do_power_sequencing(false);
		}
		break;

		/* Closed loop trimming (if necessary) */
		case mpm_is_started:
		{
			if (g_mpm_engine.b_do_closed_loop_trimming)
			{
				mpm_do_closed_loop_trimming();
			}
		}
		break;

		/* Placeholder - TIM2 timer ISR should not be called when stopped */
		case mpm_is_stopped:
		default:
		{
			ASSERT(0);
		}
		break;
	}

	/* Clear TIM2 interrupt */
	MSS_TIM2_clear_irq();
}
__attribute__((__interrupt__)) void Timer1_IRQHandler(void)
{

#define MPM_NUM_CHANNELS_TO_CHECK 5
	    if (mpm_is_started == mpm_get_state())
		{
			mpm_channel_number_t chan_num;

			for (chan_num = 1;
			     chan_num <= MPM_NUM_CHANNELS_TO_CHECK;
			     chan_num++)
			{
				if (mpm_is_valid_channel(chan_num))
				{
					mpm_channel_state_t chan_state;
					chan_state = mpm_get_channel_state(chan_num);

					if ((mpm_channel_is_off == chan_state) ||
						(mpm_channel_is_uv2 == chan_state) ||
						(mpm_channel_is_ov2 == chan_state))
					{
						mpm_stop();
						break;
					}
				}
			}
		}

	/* Clear TIM1 interrupt */
	MSS_TIM1_clear_irq();
}
/* 
 * MPM configuration data access functions 
 */

/* Get raw MPM configuration data item given bit offset and length */
static uint16_t mpm_config_get_value(uint16_t bit_offset, uint8_t length_in_bits)
{
	uint8_t *p_mpm_config_base =
			(uint8_t *)(MPM_CONFIGURATION_DATA_BASE_ADDRESS);
	uint16_t byte_offset = bit_offset / 8;
	uint16_t config_value;

	/* Get 16 bit "word" of MPM register data */
	config_value = p_mpm_config_base[byte_offset]
			| (p_mpm_config_base[byte_offset + 1] << ByteSizeInBits);

	/* Shift and mask to get just the required bits */
	config_value >>= (bit_offset % ByteSizeInBits);
	config_value &= (((uint16_t)(~0))
			>> ((ByteSizeInBits * 2) - length_in_bits));

	return config_value;
}

static bool mpm_config_get_channel_is_dpol(mpm_channel_number_t channel_num)
{
	--channel_num;
	return ((bool)mpm_config_get_value(
		Channel_ControlerIsDigital + (channel_num * ChannelPageSize),
		Channel_ControlerIsDigitalSize));
}

/* Get channel threshold */
static int16_t mpm_config_get_channel_threshold(
		mpm_channel_number_t channel_num, mpm_threshold_index_t threshold_index)
{
	--channel_num;
	/*
	 * We're indexing both on page and on threshold.
	 * Notice that OFFLimit is last in the GUI so we use negative indexing
	 * relative to OFFLimit to select the threshold.
	 */
	return ((int16_t)mpm_config_get_value(
	Channel_iOFFLimit + (channel_num * ChannelPageSize) +
	(-((int16_t)threshold_index) * Channel_iOFFLimitSize),
	Channel_iOFFLimitSize));
}

/* Get channel hysteresis */
static int16_t mpm_config_get_channel_hysteresis(
		mpm_channel_number_t channel_num)
{
	--channel_num;
	return ((int16_t)mpm_config_get_value(
	Channel_iHysteresis + (channel_num * ChannelPageSize),
	Channel_iHysteresisSize));
}

/* Get channel power on/off sequencing slot */
static uint8_t mpm_config_get_channel_sequencing_slot(
		mpm_channel_number_t channel_num)
{
	--channel_num;
	return ((uint8_t)mpm_config_get_value(
	Channel_iSlot + (channel_num * ChannelPageSize),
	Channel_iSlotSize));
}

/* Get channel power on/off sequencing delay */
static uint16_t mpm_config_get_channel_on_off_delay_ms(
		mpm_channel_number_t channel_num, bool b_on_delay)
{
	uint16_t delay;
	--channel_num;

	if (b_on_delay)
	{
		delay = mpm_config_get_value
		(
		    Channel_iOnDelay + (channel_num * ChannelPageSize),
		    Channel_iOnDelaySize
		);
	}
	else
	{
		delay = mpm_config_get_value
		(
		    Channel_iOffDelay + (channel_num * ChannelPageSize),
		    Channel_iOffDelaySize
		);
	}

	return delay;
}

/* Get channel trimming type */
static mpm_trim_type_t mpm_config_get_channel_trim_type(
		mpm_channel_number_t channel_num)
{
	uint8_t trim_type;
	--channel_num;

	trim_type = mpm_config_get_value(
		Channel_iTrimType + (channel_num * ChannelPageSize),
		Channel_iTrimTypeSize);

	ASSERT(trim_type < mpm_trim_type_max);
	return (trim_type);
}

/* Get channel trimming DAC type */
static mpm_trim_dac_type_t mpm_config_get_channel_trim_dac_type(
		mpm_channel_number_t channel_num)
{
	uint8_t trim_dac_type;
	--channel_num;

	trim_dac_type = mpm_config_get_value(
		Channel_iControllerType + (channel_num * ChannelPageSize),
		Channel_iControllerTypeSize);

	ASSERT(trim_dac_type < mpm_trim_dac_type_max);
	return (trim_dac_type);
}

/* Get channel open loop low/nominal/high trim pin voltages */
static uint16_t mpm_config_get_channel_trim_pin_voltage(
		mpm_channel_number_t channel_num,
		mpm_trim_target_t trim_target)
{
	uint16_t trim_pin_voltage = 0;
	--channel_num;

	switch (trim_target)
	{
		case mpm_trim_target_nominal:
		{
			trim_pin_voltage = mpm_config_get_value(
			Channel_iTrimPinVoltage + (channel_num * ChannelPageSize),
			Channel_iTrimPinVoltageSize);
		}
		break;

		case mpm_trim_target_low:
		{
			trim_pin_voltage = mpm_config_get_value(
			Channel_iTrimLowTrim + (channel_num * ChannelPageSize),
			Channel_iTrimLowTrimSize);
		}
		break;

		case mpm_trim_target_high:
		{
			trim_pin_voltage = mpm_config_get_value(
			Channel_iTrimHighTrim + (channel_num * ChannelPageSize),
			Channel_iTrimHighTrimSize);
		}
		break;

		default:
		{
			/* Placeholder - will not happen */
		}
		break;
	}

	return trim_pin_voltage;
}

/* Get channel closed loop low/nominal/high target output voltages */
static uint16_t mpm_config_get_channel_margin_voltage(
		mpm_channel_number_t channel_num,
		mpm_trim_target_t trim_target)
{
	int16_t target_voltage = 0;
	--channel_num;

	switch (trim_target)
	{
		case mpm_trim_target_nominal:
		{
			target_voltage = mpm_config_get_value(
			Channel_iNominal + (channel_num * ChannelPageSize),
			Channel_iNominalSize);
		}
		break;

		case mpm_trim_target_low:
		{
			target_voltage = mpm_config_get_value(
			Channel_iMarginLowTrim + (channel_num * ChannelPageSize),
			Channel_iMarginLowTrimSize);
		}
		break;

		case mpm_trim_target_high:
		{
			target_voltage = mpm_config_get_value(
			Channel_iMarginHighTrim + (channel_num * ChannelPageSize),
			Channel_iMarginHighTrimSize);
		}
		break;

		default:
		{
			/* Placeholder - will not happen */
		}
		break;
	}

	return target_voltage;
}

/* Get channel I2C bus number & address (for DPOL PMBus access) */
static uint8_t mpm_config_get_channel_i2c_bus(
	mpm_channel_number_t channel_num)
{
	--channel_num;
	return ((uint8_t)mpm_config_get_value(
		Channel_iI2CBus + (channel_num * ChannelPageSize),
		Channel_iI2CBusSize));
}

static uint8_t mpm_config_get_channel_i2c_address(
	mpm_channel_number_t channel_num)
{
	--channel_num;
	return ((uint8_t)mpm_config_get_value(
		Channel_iI2CAddress + (channel_num * ChannelPageSize),
		Channel_iI2CAddressSize));
}

/* Get power on/off sequencing global slot timeout */
static uint16_t mpm_config_get_sequence_slot_timeout_ms()
{
	return mpm_config_get_value
	(
	    Misc_iSlotTimeout,
	    Misc_iSlotTimeoutSize
	);
}

/* Get power off sequencing type */
static mpm_off_sequence_t mpm_config_get_power_off_sequence_type()
{
	uint8_t
			seq_type =
					mpm_config_get_value(Misc_PowerOffSequence, Misc_PowerOffSequenceSize);
	ASSERT(seq_type < mpm_off_sequence_max);
	return seq_type;
}

/* Get power on sequence slot timeout/failure action */
static mpm_slot_timeout_action_t mpm_config_get_sequence_slot_timeout_action()
{
	uint8_t fail_action = mpm_config_get_value(Misc_iOnFail, Misc_iOnFailSize);
	ASSERT(fail_action < mpm_slot_timeout_max);
	return fail_action;
}

/* For a particular output flag ... for a given channel get a bitmask
 * indicating the relevant or otherwise of that channel's 6 states to 
 * the logic for generating the flag 
 */
static uint8_t mpm_config_get_flag_condition_channel_state_bitmask(
		uint8_t flag_num, mpm_channel_number_t channel_num)
{
	/* For each flag condition, which channel states does the condition depend
	 * on? Remember that channel states are ordered as follows:
	 *
	 * Positive channels: OV2 > OV1 > NOM > UV1 > UV2 > OFF > 0
	 * Negative channels: 0   > OFF > OV2 > OV1 > NOM > UV1 > UV2
	 *
	 * For all channels (regardless of polarity) bitmasks are 6 bits organized
	 * as follows:
	 *
	 * Bit    5   4   3   2   1   0
	 * State OV2 OV1 NOM UV1 UV2 OFF
	 */

	static uint8_t mpm_flag_condition_to_pos_channel_state_bitmask[] =
	{ 0x00, /* Not Relevant */
	0x20, /* OV2          */
	0x30, /* OV1          */
	0x07, /* UV1          */
	0x03, /* UV2          */
	0x08, /* NOT UV1|OV1  */
	0x37, /* UV1|OV1      */
	0x33, /* UV2|OV1      */
	0x27, /* UV1|OV2      */
	0x23, /* UV2|OV2      */
	0x01, /* OFF          */
	0x3E, /* NOT OFF      */
};

static uint8_t mpm_flag_condition_to_neg_channel_state_bitmask[] =
{
	0x00, /* Not Relevant */
	0x21, /* OV2          */
	0x31, /* OV1          */
	0x06, /* UV1          */
	0x02, /* UV2          */
	0x08, /* NOT UV1|OV1  */
	0x37, /* UV1|OV1      */
	0x33, /* UV2|OV1      */
	0x27, /* UV1|OV2      */
	0x23, /* UV2|OV2      */
	0x01, /* OFF          */
	0x3E, /* NOT OFF      */
};

uint8_t flag_condition_for_this_channel;
mpm_channel_t *p_channel;

/* Get channel descriptor pointer */
--channel_num;
p_channel = &(g_mpm_engine.channels[channel_num]);

/* Read configured condition for this flag/channel tuple */
flag_condition_for_this_channel =
mpm_config_get_value
(
		Output_iRailA1 + (flag_num * OutputPageSize) +
		(channel_num * Output_iRailA1Size),
		Output_iRailA1Size
);

/* Translate condition to channel state bitmask */
return
(
		p_channel->is_positive ?
		mpm_flag_condition_to_pos_channel_state_bitmask
		[flag_condition_for_this_channel] :
		mpm_flag_condition_to_neg_channel_state_bitmask
		[flag_condition_for_this_channel]
);
}

/* Does a given flag use AND (true) or OR (false) logic */
static bool mpm_config_get_flag_uses_and_logic(uint8_t flag_number)
{
	return (mpm_config_get_value(
	Output_Combiner + (flag_number * OutputPageSize),
	Output_CombinerSize) == 1);
}

/* Is a given output flag updated during power sequencing */
static bool mpm_config_get_flag_update_during_sequencing(uint8_t flag_number)
{
	return (mpm_config_get_value(
	Output_bUpdateOutput + (flag_number * OutputPageSize),
	Output_bUpdateOutputSize) == 1);
}

#ifdef MPM_ENABLE_LOGGING

/* Is a given output flag logged during power on sequencing */
static bool mpm_config_get_flag_log_during_power_on(uint8_t flag_number)
{
	return (mpm_config_get_value(
	Output_bLogDuringPowerOnSequence + (flag_number * OutputPageSize),
	Output_bLogDuringPowerOnSequenceSize) == 1);
}

/* Is a given output flag logged during power off sequencing */
static bool mpm_config_get_flag_log_during_power_off(uint8_t flag_number)
{
	return (mpm_config_get_value(
	Output_bLogDuringPowerOffSequence + (flag_number * OutputPageSize),
	Output_bLogDuringPowerOffSequenceSize) == 1);
}

/* For a given output flag log 0 -> 1 transitions? */
static bool mpm_config_get_flag_log_0_to_1(uint8_t flag_number)
{
	return (mpm_config_get_value(
	Output_bLog01Transitions + (flag_number * OutputPageSize),
	Output_bLog01TransitionsSize) == 1);
}

/* For a given output flag log 1 -> 0 transitions? */
static bool mpm_config_get_flag_log_1_to_0(uint8_t flag_number)
{
	return (mpm_config_get_value(
	Output_bLog10Transitions + (flag_number * OutputPageSize),
	Output_bLog10TransitionsSize) == 1);
}

#endif

/* Is a given output flag active low (true) or high (false) */
static bool mpm_config_get_flag_is_active_low(uint8_t flag_number)
{
	return (mpm_config_get_value(
	Output_PinsAssertedLo + (flag_number * OutputPageSize),
	Output_PinsAssertedLoSize) == 1);
}




/* For a given flag what logic is used for combining the relevant 
 * digital input?
 */
static mpm_digital_input_logic_t mpm_config_get_flag_input_logic(
		uint8_t flag_number)
{
	uint8_t input_logic = mpm_config_get_value(
	Output_iCombine + (flag_number * OutputPageSize),
	Output_iCombineSize);

	ASSERT(input_logic < mpm_input_max);
	return input_logic;
}

#ifdef MPM_ENABLE_I2C

/* I2C slave address */
static uint8_t mpm_config_get_i2c_slave_address()
{
	/* MPM configuration holds the MPM I2C slave address in
	 * 8 bit format with bit 0 reserved for carrying the I2C R/W
	 * bit or the MSS I2C peripheral GCA address "enable" bit - i.e.
	 *
	 * {mpm_i2c_slave_addr[6:0], X}
	 *
	 * Many I2C masters will use the same 8 bit format for addresses but
	 * any that use the 7 bit I2C slave address only (and no additional
	 * bit 0) format will need to divide the configured MPM I2C slave
	 * address by 2.
	 */
	return (mpm_config_get_value(Misc_iI2CAddress, Misc_iI2CAddressSize));
}

/* Miscellaneous logging configuration */

#ifdef MPM_ENABLE_LOGGING

/* Log MPM engine state changes? */
static bool mpm_config_get_log_mpm_state()
{
	return (mpm_config_get_value(Misc_bMPMState, Misc_bMPMStateSize) == 1);
}

/* Log power on sequencing events? */
static bool mpm_config_get_log_power_on_events()
{
	return (mpm_config_get_value(
		Misc_bPowerOnEvents, Misc_bPowerOnEventsSize) == 1);
}

/* Log power off sequencing events? */
static bool mpm_config_get_log_power_off_events()
{
	return (mpm_config_get_value(
		Misc_bPowerOffEvents, Misc_bPowerOffEventsSize) == 1);
}

/* Clear log? */
static bool mpm_config_get_clear_log()
{
	return (mpm_config_get_value(
		Misc_bClearLog, Misc_bClearLogSize) == 1);
}

#endif

/* I2C slave write handler - processes I2C communications addressed
 * to the MPM I2C slave from a master.
 */

static mss_i2c_slave_handler_ret_t mpm_i2c_slave_write_handler
(
	mss_i2c_instance_t *instance,
	uint8_t *p_rx_data,
	uint16_t rx_len
)
{
	/* Expected format of requests are:
	 *
	 * 1. Register read - 3 bytes from master to slave
	 *
	 * A[15:8] A[7:0] L[7:0]
	 *
	 * and slave responds with L bytes (or fewer if not enough data)
	 *
	 * 2. Register write - master to slave:
	 *
	 * A[15:8] A[7:0] L[7:0] D0[7:0] D1[7:0] ... D<L-1>[7:0]
	 *
	 * 3. Command - master to slave
	 *
	 * A[15:8] A[7:0] L[7:0] C[7:0] D1[7:0] ... D<L-2>[7:0]
	 *
	 * Where
	 *
	 * A = 16 bit MPM I2C slave register address
	 * L = 8 bit read/write/command data length (1 <= L <= 64)
	 * D<n> = data byte <n> (0 <= n <= 63 for register reads/writes
	 *                      (0 <= n <= 62 for commands)
	 * C = 8 bit command opcode
	 */

	uint16_t reg_addr;
	uint8_t data_len;
	uint8_t *p_tx_buffer;
	uint16_t tx_size;
	uint8_t previous_i2c_status_reg;

	/* Assume 0 length response & success - overridden below if necessary */
	p_tx_buffer = g_mpm_engine.i2c_tx_buffer;
	tx_size = 0;
	previous_i2c_status_reg = g_mpm_engine.i2c_status_reg;
	g_mpm_engine.i2c_status_reg = mpm_i2c_status_success;

	if (rx_len >= MPM_I2C_REG_READ_LEN)
	{
		/* Extract MPM I2C slave register address and data length */
	    reg_addr = (p_rx_data[MPM_I2C_REGADDRH] << 8) | p_rx_data[MPM_I2C_REGADDRL];
	    data_len = p_rx_data[MPM_I2C_DATALEN];

	    /* Validate data_len */
	    if ((data_len >= MPM_I2C_MIN_DATA_LEN) &&
	    	(data_len <= MPM_I2C_MAX_DATA_LEN))
	    {
			if (MPM_I2C_REG_READ_LEN == rx_len)
			{
				/*
				 * Register read
				 */
				if (reg_addr <= MPM_I2C_CONFIG_REG_MAX_ADDR)
				{
					/*
					 * Configuration register read
					 */
					p_tx_buffer = (uint8_t *)(MPM_CONFIGURATION_DATA_BASE_ADDRESS + reg_addr);
					tx_size =
						((reg_addr + data_len) <= (MPM_I2C_CONFIG_REG_MAX_ADDR + 1)) ?
						data_len : (MPM_I2C_CONFIG_REG_MAX_ADDR + 1 - reg_addr);
				}
				else if ((reg_addr >= MPM_I2C_MONITOR_REG_MIN_ADDR) &&
						 (reg_addr <= MPM_I2C_MONITOR_REG_MAX_ADDR))
				{
					/*
					 * Monitoring "pseudo" register read
					 */
					mpm_channel_number_t first_chan;
					mpm_channel_number_t last_chan;
					int16_t chan_voltage;
					uint8_t buf_idx;

					/* Work out first and last channels to be read */
					first_chan = ((reg_addr - MPM_I2C_MONITOR_REG_MIN_ADDR) / 2) + 1;
					last_chan = first_chan + (data_len / 2) + (data_len % 2) - 1;

					/* Read channel voltages */
					for (buf_idx = 0;
						first_chan <= last_chan;
						first_chan++)
					{
						/* Get voltage as 16 bit signed value */
						chan_voltage =
							(int16_t)(mpm_get_channel_voltage_mv(first_chan));
						/* Most significant byte */
						g_mpm_engine.i2c_tx_buffer[buf_idx++] =
							(uint8_t)((chan_voltage >> 8) & 0xFF);
						/* Least significant byte */
						g_mpm_engine.i2c_tx_buffer[buf_idx++] =
							(uint8_t)(chan_voltage & 0xFF);
					}

					/* Allow for non 16 bit aligned access */
					if (reg_addr & 1)
					{
						p_tx_buffer = &(g_mpm_engine.i2c_tx_buffer[1]);
					}
					tx_size = buf_idx;
				}
				else if ((reg_addr >= MPM_I2C_RTC_REG_MIN_ADDR) &&
						 (reg_addr <= MPM_I2C_RTC_REG_MAX_ADDR))
				{
					/* Calculate effective read size */
					tx_size =
						((reg_addr + data_len) <= (MPM_I2C_RTC_REG_MAX_ADDR + 1)) ?
						data_len : (MPM_I2C_RTC_REG_MAX_ADDR + 1 - reg_addr);

					/* Read RTC & convert to array of bytes */
					mpm_rtc_to_byte_array(g_mpm_engine.i2c_tx_buffer);

					/* Calculate initial read byte */
					p_tx_buffer = &(g_mpm_engine.i2c_tx_buffer[reg_addr - MPM_I2C_RTC_REG_MIN_ADDR]);
				}
				else if ((reg_addr >=  MPM_I2C_LOG_MIN_ADDR) &&
						 (reg_addr <= MPM_I2C_LOG_MAX_ADDR))
				{
					/*
					 * Log file read
					 */
					p_tx_buffer = (uint8_t *)
						(MPM_EVENT_LOG_BASE_ADDRESS + reg_addr - MPM_I2C_LOG_MIN_ADDR);
					tx_size =
						((reg_addr + data_len) <= (MPM_I2C_LOG_MAX_ADDR + 1)) ?
						data_len : (MPM_I2C_LOG_MAX_ADDR + 1 - reg_addr);
				}

				else if (MPM_I2C_STATUS_REG_ADDR == reg_addr)
				{
					/*
					 * Status register read (and clear)
					 */
					g_mpm_engine.i2c_tx_buffer[0] = previous_i2c_status_reg;
					tx_size = 1;
				}
				else if (MPM_I2C_LOG_STATUS_REG_ADDR == reg_addr)
				{
					/*
					 * Log status register read
					 */
#ifdef MPM_ENABLE_LOGGING
					g_mpm_engine.i2c_tx_buffer[0] =
						mpm_logging_is_full() ? 1 : 0;
#else
					g_mpm_engine.i2c_tx_buffer[0] = 0;
#endif
					tx_size = 1;
				}
				else
				{
					/*
					 * Invalid register read
					 */
					g_mpm_engine.i2c_status_reg = mpm_i2c_status_invalid_reg_addr;
				}
			}
			else
			{
				/*
				 * Register write
				 */
				if ((rx_len - MPM_I2C_REG_READ_LEN) != data_len)
				{
					g_mpm_engine.i2c_status_reg = mpm_i2c_status_data_len_mismatch;
				}
				else
				{
					if (reg_addr <= MPM_I2C_CONFIG_REG_MAX_ADDR)
					{
						/*
						 * Configuration register write (to NVM)
						 */

						/* Ensure only MPM config ENVM space is written */
						tx_size =
							((reg_addr + data_len) <= (MPM_I2C_CONFIG_REG_MAX_ADDR + 1)) ?
							data_len : (MPM_I2C_CONFIG_REG_MAX_ADDR + 1 - reg_addr);

						if (NVM_write(
							MPM_CONFIGURATION_DATA_BASE_ADDRESS + reg_addr,
							&(p_rx_data[MPM_I2C_DATA0]),
							tx_size) != NVM_SUCCESS)
						{
							g_mpm_engine.i2c_status_reg = mpm_i2c_status_nvm_write_failure;
						}
					}
					else if (MPM_I2C_CMD_REG_ADDR == reg_addr)
					{
						/*
						 * MPM command register write
						 */

						/* Warning: attempts to (re-)initialize MPM or initiate
						 * power up/power down sequencing via I2C while also
						 * doing the same via the demo program (SW1) could
						 * cause conflicts so only use one or other mechanism
						 * for controlling MPM at any one time.
						 */

						/* Which command opcode? */
						switch (p_rx_data[MPM_I2C_CMD_OPCODE])
						{
							/* Call mpm_init() to reload configuration data */
							case MPM_I2C_CMD_INIT:
							{
								if (mpm_get_state() == mpm_is_stopped)
								{
									mpm_init();
								}
								else
								{
									g_mpm_engine.i2c_status_reg = mpm_i2c_status_wrong_state;
								}
							}
							break;

							/* Initiate power up sequencing */
							case MPM_I2C_CMD_START:
							{
								if (mpm_get_state() == mpm_is_stopped)
								{
									mpm_start();
								}
								else
								{
									g_mpm_engine.i2c_status_reg = mpm_i2c_status_wrong_state;
								}
							}
							break;

							/* Initiate power down sequencing */
							case MPM_I2C_CMD_STOP:
							{
								if (mpm_get_state() == mpm_is_started)
								{
									mpm_stop();
								}
								else
								{
									g_mpm_engine.i2c_status_reg = mpm_i2c_status_wrong_state;
								}
							}
							break;

							/* Margin channel to low/nominal/high target voltage */
							case MPM_I2C_CMD_MARGIN_LOW:
							case MPM_I2C_CMD_MARGIN_NOM:
							case MPM_I2C_CMD_MARGIN_HIGH:
							{
								if (mpm_get_state() != mpm_is_started)
								{
									g_mpm_engine.i2c_status_reg = mpm_i2c_status_wrong_state;
								}
								else if (data_len >= 2)
								{
									mpm_trim_target_t trim_target;
									mpm_channel_number_t channel_num;

									channel_num = p_rx_data[MPM_I2C_DATA1];

									switch (p_rx_data[MPM_I2C_CMD_OPCODE])
									{
										case MPM_I2C_CMD_MARGIN_LOW:
										{
											trim_target = mpm_trim_target_low;
										}
										break;

										case MPM_I2C_CMD_MARGIN_HIGH:
										{
											trim_target = mpm_trim_target_high;
										}
										break;

										default:
										{
											trim_target = mpm_trim_target_nominal;
										}
										break;
									}

									/* Adjust trim target for selected channel */
									if (!mpm_adjust_channel_trim(
										channel_num, trim_target))
									{
										g_mpm_engine.i2c_status_reg = mpm_i2c_status_trim_failed;
									}
								}
								else
								{
									/* Trim channel number missing */
									g_mpm_engine.i2c_status_reg = mpm_i2c_status_malformed_request;
								}
							}
							break;

							default:
							{
								g_mpm_engine.i2c_status_reg = mpm_i2c_status_unrecognized_cmd;
							}
							break;
						}
					}
					else
					{
						/*
						 * Invalid register write
						 */
						g_mpm_engine.i2c_status_reg = mpm_i2c_status_invalid_reg_addr;
					}
				}
			}
	    }
	    else
	    {
	    	/*
	    	 * Invalid data length
	    	 */
	    	g_mpm_engine.i2c_status_reg = mpm_i2c_status_invalid_data_len;
	    }
	}
	else
	{
		/*
		 * Malformed request
		 */
		g_mpm_engine.i2c_status_reg = mpm_i2c_status_malformed_request;
	}

	/* Return result */
	MSS_I2C_set_slave_tx_buffer(&g_mss_i2c1, p_tx_buffer, tx_size);
	return MSS_I2C_REENABLE_SLAVE_RX;
}

#endif


/* Logging */

#ifdef MPM_ENABLE_LOGGING

/* Clear/truncate event log if necessary */
static bool mpm_logging_clear_if_required()
{
	bool b_result = false;

	if (mpm_config_get_clear_log())
	{
		/* Write 0 marker to start of log */
		uint8_t zero_byte = 0;
		if (NVM_write(
			MPM_EVENT_LOG_BASE_ADDRESS,
			&zero_byte,
			sizeof(zero_byte)) == NVM_SUCCESS)
		{
			/* Reset the "clear log" configuration register */
			if (NVM_write(
				MPM_CONFIGURATION_DATA_BASE_ADDRESS + MPM_I2C_CLEAR_LOG_REG_ADDR,
				&zero_byte,
				sizeof(zero_byte)) == NVM_SUCCESS)
			{
				b_result = true;
			}
		}
	}
	else
	{
		b_result = true;
	}

	return b_result;
}

/* Find first record size aligned 0 byte which is the
 * marker for the next free space in the log.
 */
static void mpm_logging_init_cursor()
{
	uint16_t i;

	for (i = 0;
		(i < (MPM_EVENT_LOG_BASE_ADDRESS + MPM_EVENT_LOG_LENGTH)) &&
		(*((uint8_t *)(MPM_EVENT_LOG_BASE_ADDRESS + i)) != 0);
		i += MPM_LOG_RECORD_SIZE)
	{
		/* Do nothing */
	}

	g_mpm_engine.log_cursor = i;
}

static bool mpm_logging_is_full()
{
	/* Room for another record plus 0 marker? */
	return (!(g_mpm_engine.log_cursor <
		(MPM_EVENT_LOG_LENGTH - MPM_LOG_RECORD_SIZE)));
}

static bool mpm_logging_write(mpm_logging_record_type_t record_type, uint8_t record_data)
{
	bool b_result;

	/* Logging can happen from different ISR "threads" so we need to enclose
	 * it in a critical section by disabling and then reenabling interrupts.
	 */

	__disable_irq();

	b_result = false;

	if (!mpm_logging_is_full())
	{
		/* Log record plus trailing 0 marker */
		uint8_t log_record[MPM_LOG_RECORD_SIZE + 1];

		/* Event details */
		log_record[0] = record_type;
		log_record[1] = record_data;
		mpm_rtc_to_byte_array(&log_record[2]);
		log_record[MPM_LOG_RECORD_SIZE] = mpm_logging_record_type_null;

		/* Commit to log */
		if (NVM_write(
			MPM_EVENT_LOG_BASE_ADDRESS + g_mpm_engine.log_cursor,
			log_record,
			sizeof(log_record)) == NVM_SUCCESS)
		{
			/* Move logging cursor on */
			g_mpm_engine.log_cursor += MPM_LOG_RECORD_SIZE;
			b_result = true;
		}
	}

	/* Exit critical section */
	__enable_irq();

	return b_result;
}

static void mpm_log_power_sequencing_event(
	mpm_logging_record_type_t record_type,
	mpm_logging_power_seq_event_t event_type,
	uint8_t slot_number)
{
	if (((record_type == mpm_logging_record_type_power_on) &&
		 (g_mpm_engine.b_log_power_on_events)) ||
		((record_type == mpm_logging_record_type_power_off) &&
		 (g_mpm_engine.b_log_power_off_events)))
	{
		mpm_logging_write(record_type, event_type | (slot_number << 3));
	}
}

#define MPM_FLAG_0_TO_1	(0x20)

static void mpm_log_flags(uint32_t changed_to_0, uint32_t changed_to_1)
{
	uint8_t i;

	i = 0;
	while (changed_to_0 != 0)
	{
		if (changed_to_0 & 1)
		{
			mpm_logging_write(mpm_logging_record_type_flag_changed, i);
		}

		i++;
		changed_to_0 >>= 1;
	}

	i = 0;
	while (changed_to_1 != 0)
	{
		if (changed_to_1 & 1)
		{
			mpm_logging_write(
				mpm_logging_record_type_flag_changed,
				MPM_FLAG_0_TO_1 | i);
		}

		i++;
		changed_to_1 >>= 1;
	}
}

#endif

/* Convert 40 bit RTC to an array of 5 bytes in MSB..LSB order:
 * rtc[39:32] rtc[31:24] rtc[23:16] rtc[15:8] rtc[7:0]
 */
#if defined(MPM_ENABLE_2C) || defined(MPM_ENABLE_LOGGING)
static void mpm_rtc_to_byte_array(uint8_t *p_rtc_bytes)
{
	uint64_t rtc;
	uint8_t i;

	rtc = MSS_RTC_get_rtc_count();
	for (i = 0; i < 5; i++)
	{
		p_rtc_bytes[i] = (rtc >> ((4 - i) * 8));
	}
}
#endif


/* MPM Channel 6/7 DPOL PG (Power Good) signals are connected to MSS GPIOs
 * 25/24 (pins V19/T17) respectively. These ISRs handle changes to these.
 * Further DPOL PGs need appropriate pin mapping and firmware support.
 */

static void mpm_dpol_pg_changed(mpm_channel_number_t mpm_channel_num, bool pg_asserted)
{
	/* Note that Intersil/Zilker Labs ZL2008 PG asserts when VOUT >=
	 * POWER_GOOD_ON and deasserts when VOUT <= VOUT_UV_FAULT_LIMIT.
	 * Not yet sure if this applies to most or all other DPOLs and if
	 * others support VOUT_[O|U]V_WARN_LIMIT etc. For now assume
	 * PG asserted means NOMINAL and PG deasserted means UV2 with
	 * additional check of STATUS_BYTE for OFF.
	 */
	mpm_channel_t *p_channel = &(g_mpm_engine.channels[mpm_channel_num - 1]);

	ASSERT(mpm_is_valid_channel(mpm_channel_num));
	ASSERT(mpm_channel_type_DPOL == p_channel->channel_type);
	ASSERT(mpm_is_stopped != g_mpm_engine.state);


#if 0
	{
		uint8_t pmbus_status_byte;
		uint16_t pmbus_status_word;
		uint8_t pmbus_vout_status;

		read_command(
			&(p_channel->pmbus_channel),
			STATUS_BYTE_CMD,
			&pmbus_status_byte);
		read_command(
			&(p_channel->pmbus_channel),
			STATUS_WORD_CMD,
			&pmbus_status_word);
		read_command(
			&(p_channel->pmbus_channel),
			STATUS_VOUT_CMD,
			&pmbus_vout_status);
	}
#endif

	/* For now assume PG asserted means nominal and PG deasserted means off */
	p_channel->state = pg_asserted ? mpm_channel_is_nominal : mpm_channel_is_off;

	/* If necessary update power on/off sequencing channel bitmasks */
	if (mpm_channel_is_nominal == p_channel->state)
	{
		if (mpm_is_starting == g_mpm_engine.state)
		{
			__disable_irq();
			g_mpm_engine.sequencing.on_channel_bitmask &= ~(1
					<< (mpm_channel_num - 1));
			__enable_irq();
		}
	}
	else if (mpm_channel_is_off == p_channel->state)
	{
		if (mpm_is_stopping == g_mpm_engine.state)
		{
			__disable_irq();
			g_mpm_engine.sequencing.off_channel_bitmask &= ~(1
					<< (mpm_channel_num - 1));
			__enable_irq();
		}
	}

	/* Update flags */
	mpm_generate_flags();
}

/* MPM Channel 3/4/5 = DPOL 1/2/3 = MSS GPIO 13/14/15 */
__attribute__((__interrupt__)) void GPIO13_IRQHandler(void)
{
	mpm_dpol_pg_changed(3, (MSS_GPIO_get_inputs() >> MSS_GPIO_13) & 1);
    MSS_GPIO_clear_irq(MSS_GPIO_13);
}

__attribute__((__interrupt__)) void GPIO14_IRQHandler(void)
{
	mpm_dpol_pg_changed(4, (MSS_GPIO_get_inputs() >> MSS_GPIO_14) & 1);
    MSS_GPIO_clear_irq(MSS_GPIO_14);
}

__attribute__((__interrupt__)) void GPIO15_IRQHandler(void)
{
	mpm_dpol_pg_changed(5, (MSS_GPIO_get_inputs() >> MSS_GPIO_15) & 1);
    MSS_GPIO_clear_irq(MSS_GPIO_15);
}


/* DPOL PMBus CoreI2C interrupt handling */

/*------------------------------------------------------------------------------
 * pmbus_alert_handler()
 */
void pmbus_alert_handler( uint8_t * channel_no)
{
#if 0
	uint8_t vout_status = 0 ;
	int32_t voltage_in_millivolt = 0;

    /* Check the Vout Status */
    read_command( &g_channel_i2c_map[*channel_no], STATUS_VOUT_CMD, &vout_status );
    if( vout_status & VOUT_OV_FAULT_MASK)
    {
	    /* Read the voltage VOUT*/
	    voltage_in_millivolt = read_voltage( &g_channel_i2c_map[*channel_no], READ_VOUT_CMD );
    }
#endif
}

/*------------------------------------------------------------------------------
 * PMBus_smbalert_isr(). This should be single interrupt generated by one slave.
 */
#if PMBUS
void PMBus_smbalert_isr( i2c_type_t i2c_device_type, void *this_i2c )
{
#ifdef MPM_ENABLE_DPOLS

    uint8_t count = 0;
    uint8_t channel_number = 0 ;

    while((g_channel_i2c_map[count].pmbus_master != NULL )&& ( count < MAX_PMBUS_CHANNEL) )
    {
    	channel_number = count ;
    	switch(i2c_device_type )
    	{
#if ( I2C_MSS_DEF )
    	    case I2C_MSS :
    	    {
    	    	if (g_channel_i2c_map[count].pmbus_master->instance.mss_i2c == this_i2c)
    	    	{
    	    		/* run the handler */
    	    		(* g_channel_i2c_map[count].slave_device_info.pmbus_irq_handler )( &channel_number );

    	    	}
    	    	break ;
    	    }
#endif
#if ( I2C_SOFTCORE_DEF )
    	    case I2C_SOFTCORE :
    	    {
    	    	if (g_channel_i2c_map[count].pmbus_master->instance.i2c_apb == this_i2c)
    	    	{
    	    		/* run the handler */
    	    		(* g_channel_i2c_map[count].slave_device_info.pmbus_irq_handler )( &channel_number );

    	    	}
    	    	break ;
    	    }
#endif
    	    default:
    	        break ;
    	}
    	count++ ;
    }
#endif
}
#endif

/*
 * This is basically Interrupt handler for CoreI2C.
 * This is making an assumption that both CoreI2C IRQ and CoreI2C
 * SMBUSAKER IRQ is routed through Fabric interrupt
 *
 * Note: PMBus_get_corei2c_irq_type returns coreI2c interrupt type.
 *        returns 0x00 - If There is no interrupt
 *        returns 0x01 - if INTR
 *        returns 0x02 - If SMBUSALERT
 *        returns 0x03 - if both INTR/SMBUSALERT
 *
 * This is just a prototype. Testing is not done yet.
 */

#if defined(__GNUC__)
__attribute__((__interrupt__)) void Fabric_IRQHandler( void )
#else
void Fabric_IRQHandler( void )
#endif
{
    I2C_isr(&(g_mpm_engine.pmbus_corei2c));
    NVIC_ClearPendingIRQ(Fabric_IRQn);

#if PMBUS
	uint8_t irq_type = COREI2C_NO_IRQ;

	irq_type = PMBus_get_corei2c_irq_type(&(g_mpm_engine.pmbus_i2c));

	/* Handles CoreI2C INTR handling */
    if( irq_type & COREI2C_INTR_IRQ )
    {
        I2C_isr(&(g_mpm_engine.pmbus_corei2c));
    }

	/* Handles CoreI2C SMBAlert handling */
    if (irq_type & COREI2C_SMBALERT_IRQ )
    {
        PMBus_smbalert_isr( I2C_SOFTCORE, &(g_mpm_engine.pmbus_corei2c) );
    }

    NVIC_ClearPendingIRQ( Fabric_IRQn );
#endif
}

/*
 * PMBus support
 */

static void mpm_pmbus_mv_to_vout_linear(
	uint16_t mv,
	uint8_t *pmbus_buffer,
	int8_t exponent)
{
	pmbus_buffer[0] = (uint8_t)mv;
	pmbus_buffer[1] = (uint8_t)(mv >> 8);

	mv = PMBUS_get_linear_format_data(
		pmbus_buffer,
		PMBUS_VOUT_LINEAR_FORMAT,
		exponent);

	pmbus_buffer[0] = (uint8_t)mv;
	pmbus_buffer[1] = (uint8_t)(mv >> 8);
}

static uint16_t mpm_pmbus_vout_linear_to_mv(
	uint8_t *pmbus_buffer,
	int8_t exponent)
{
	PMBUS_get_data_from_linear_frmt(
		PMBUS_VOUT_LINEAR_FORMAT,
		exponent,
		pmbus_buffer[0] | (pmbus_buffer[1] << 8),
		pmbus_buffer);

	return (pmbus_buffer[0] | (pmbus_buffer[1] << 8));
}
