/*
 * Copyright (C) 2007-2013 Apple Inc. All rights reserved.
 *
 * This document is the property of Apple Inc.
 * It is considered confidential and proprietary.
 *
 * This document may not be reproduced or transmitted in any form,
 * in whole or in part, without the express written permission of
 * Apple Inc.
 */
#include <debug.h>
#include <drivers/apple/gpio.h>
#include <drivers/spi.h>
#include <platform.h>
#include <platform/gpio.h>
#include <platform/gpiodef.h>
#include <platform/memconfig.h>
#include <platform/soc/hwregbase.h>
#if GPIO_VERSION > 5
#include <platform/soc/pmgr.h>
#endif

static const struct {
	uintptr_t	base_addr;
	uint32_t	pin_count;
} gpio_cfgs[GPIOC_COUNT] = {
#if GPIOC_COUNT > 0
	{ GPIO_BASE_ADDR, GPIO_GROUP_COUNT * GPIOPADPINS },
#endif
#if GPIOC_COUNT > 1
	{ GPIO_1_BASE_ADDR, GPIO_1_GROUP_COUNT * GPIOPADPINS },
#endif
};

uint32_t gpio_read(gpio_t gpio)
{
	uint8_t gpioc = GPIO2CONTROLLER(gpio);
	uint8_t pad = GPIO2PAD(gpio);
	uint8_t pin = GPIO2PIN(gpio);
	uint8_t gpion = pad * GPIOPADPINS + pin;

#if WITH_HW_SPI && defined(GPIO_PAD_SPI)
	if (pad == GPIO_PAD_SPI) return spi_gpio_read(pin);
#endif

	return (rGPIOCFG(gpio_cfgs[gpioc].base_addr, gpion) & 1) ? 1UL : 0UL;
}

void gpio_write(gpio_t gpio, uint32_t val)
{
	uint32_t cfg_val, cfg_mask;
	uint8_t gpioc = GPIO2CONTROLLER(gpio);
	uint8_t pad = GPIO2PAD(gpio);
	uint8_t pin = GPIO2PIN(gpio);
	uint8_t gpion = pad * GPIOPADPINS + pin;

#if WITH_HW_SPI && defined(GPIO_PAD_SPI)
	if (pad == GPIO_PAD_SPI) {	
		spi_gpio_write(pin, val);
		return;
	}
#endif

	cfg_val  = (val) ? CFG_OUT_1 : CFG_OUT_0;
	cfg_mask = INPUT_ENABLE | FUNC_MASK | INT_MASKED | CFG_MASK | DATA_1;

	rGPIOCFG(gpio_cfgs[gpioc].base_addr, gpion) = (rGPIOCFG(gpio_cfgs[gpioc].base_addr, gpion) & ~cfg_mask) | (cfg_val & cfg_mask);
}

void gpio_safe_reconfigure(uint8_t gpioc, uint8_t gpion, uint32_t config)
{
	uint32_t old_config;
	uint32_t new_input_enable;
	uint32_t old_input_enable;

	old_config = rGPIOCFG(gpio_cfgs[gpioc].base_addr, gpion);
	new_input_enable = config & INPUT_ENABLE;

	if (new_input_enable == 0) {
		rGPIOCFG(gpio_cfgs[gpioc].base_addr, gpion) = old_config & ~INPUT_ENABLE;
		old_input_enable = 0;
	} else {
		old_input_enable = old_config & INPUT_ENABLE;
	}

	rGPIOCFG(gpio_cfgs[gpioc].base_addr, gpion) = (config & ~INPUT_ENABLE) | old_input_enable;

	if (new_input_enable != 0 && old_input_enable == 0)
		rGPIOCFG(gpio_cfgs[gpioc].base_addr, gpion) = config;
}

void gpio_configure(gpio_t gpio, uint32_t config)
{
	uint32_t cfg_val, cfg_mask;
	uint8_t gpioc = GPIO2CONTROLLER(gpio);
	uint8_t pad = GPIO2PAD(gpio);
	uint8_t pin = GPIO2PIN(gpio);
	uint8_t gpion = pad * GPIOPADPINS + pin;

#if WITH_HW_SPI && defined(GPIO_PAD_SPI)
	if (pad == GPIO_PAD_SPI) {
		spi_gpio_configure(pin, config);
		return;
	}
#endif

	cfg_mask = INPUT_ENABLE | FUNC_MASK | INT_MASKED | CFG_MASK;

	switch (config) {
	       	case GPIO_CFG_IN    : cfg_val = CFG_IN;								break;
	       	case GPIO_CFG_OUT_0 : cfg_val = CFG_OUT_0;	 	 cfg_mask |= DATA_1; 			break;
	       	case GPIO_CFG_OUT_1 : cfg_val = CFG_OUT_1;		 cfg_mask |= DATA_1; 			break;
	       	case GPIO_CFG_OUT   : cfg_val = CFG_OUT;	 	 	    				break;
	       	case GPIO_CFG_FUNC0 : cfg_val = CFG_FUNC0;							break;
	       	case GPIO_CFG_FUNC1 : cfg_val = CFG_FUNC1;							break;
	       	case GPIO_CFG_FUNC2 : cfg_val = CFG_FUNC2;							break;
		case GPIO_CFG_DFLT  :
			cfg_val = platform_get_default_gpio_cfg(gpioc)[gpion]; cfg_mask |= PULL_MASK | DATA_1;	break;
		default :											return;
	}

	gpio_safe_reconfigure(gpioc, gpion, (rGPIOCFG(gpio_cfgs[gpioc].base_addr, gpion) & ~cfg_mask) | (cfg_val & cfg_mask));
}

void gpio_configure_pupdn(gpio_t gpio, int32_t pupdn)
{
	uint8_t gpioc = GPIO2CONTROLLER(gpio);
	uint8_t pad = GPIO2PAD(gpio);
	uint8_t pin = GPIO2PIN(gpio);
	uint8_t gpion = pad * GPIOPADPINS + pin;
	uint32_t cfg_val;

	if (pupdn < GPIO_NO_PUPDN) {
		cfg_val = PULL_DOWN;
	} else if (pupdn > GPIO_NO_PUPDN) {
		#if GPIO_VERSION < 5
			cfg_val = PULL_UP;
		#else
			cfg_val = (pupdn > GPIO_PUP) ? PULL_UP_STRONG : PULL_UP;	
		#endif
	} else {
		/* GPIO_NO_PUPDN */
		cfg_val = PULL_NONE;
	}

	rGPIOCFG(gpio_cfgs[gpioc].base_addr, gpion) = (rGPIOCFG(gpio_cfgs[gpioc].base_addr, gpion) & ~PULL_MASK) | (cfg_val & PULL_MASK);
}


int gpio_init_pinconfig(void)
{
	uint8_t cnt, gpioc;

	for (gpioc = 0; gpioc < GPIOC_COUNT; gpioc++) {
		const uint32_t *pin_config = platform_get_default_gpio_cfg(gpioc);

		for (cnt = 0; cnt < gpio_cfgs[gpioc].pin_count; cnt++) {
			gpio_safe_reconfigure(gpioc, cnt, pin_config[cnt]);
		}

#ifdef FMI_SLEW_RATE
		// Adjust global FMI pads slew rate.
		rGPIO_SR_EN(gpio_cfgs[gpioc].base_addr) = (rGPIO_SR_EN(gpio_cfgs[gpioc].base_addr) & ~1) | (FMI_SLEW_RATE & 1);
#endif

#ifdef FMI_DIFF_SEL
		// Configure FMI differential pin selection
		rGPIO_FMI_DIFF_SEL(gpio_cfgs[gpioc].base_addr) = FMI_DIFF_SEL;
#endif

#if GPIO_VERSION > 0
		// Enable interrupt when pin not configured as GPIO
		rGPIO_NPL_IN_EN(gpio_cfgs[gpioc].base_addr) = 1;
#endif
	}

#if GPIO_VERSION > 6
#if !APPLICATION_SECUREROM
	// The GPIO Micro-Architecture Specification v1.3, section 4.1 (5) says:
	//
	//	"To control the XTAL_DRIVE strength software needs to program
	//	the GPIO_MISC_CTRL[XTAL_DRIVE_STRENGTH] bit. This should be
	//	programmed only after 140,000 24MHZ clocks to allow the xtal
	//	to lock to 24MHz. Once this change is made we should allow
	//	20,000 clocks for a stable duty cycle.
	//
	// Since we're not doing this in the SecureROM we're definitely far
	// enough along here that we've met the 140K cycles requirement.
	rGPIO_MISC_CTRL(gpio_cfgs[0].base_addr) = 0;
#endif
#endif

	return 0;
}

void gpio_fixup_pinconfig(const uint32_t *fixup_list) {
	int32_t i;
	
	for (i = 0; fixup_list[i * 2] != UINT32_MAX; i ++) {
		gpio_t gpio = fixup_list[(i * 2) + 0];
		uint8_t gpioc = GPIO2CONTROLLER(gpio);
		uint8_t pad = GPIO2PAD(gpio);
		uint8_t pin = GPIO2PIN(gpio);
		uint8_t gpion = pad * GPIOPADPINS + pin;

		gpio_safe_reconfigure(gpioc, gpion, fixup_list[(i * 2) + 1]);
	}
}

int gpio_init_memory(uint32_t drive_strength)
{
	gpio_t   gpio;
	uint32_t pin, pad, gpion, config;
	int32_t  cnt, pin_count  = sizeof(memory_interface_gpios) / sizeof(memory_interface_gpios[0]);

	/* 
	 * XXXX only one gpio controller is supported
	 */

	config = CFG_IN;
#if GPIO_VERSION < 5
	switch (drive_strength) {
		case GPIO_DRIVE_X1 : config |= DRIVE_X1; break;
		case GPIO_DRIVE_X2 : config |= DRIVE_X2; break;
		case GPIO_DRIVE_X3 : config |= DRIVE_X3; break;
		case GPIO_DRIVE_X4 : config |= DRIVE_X4; break;
		case GPIO_DRIVE_X6 : config |= DRIVE_X6; break;
	}
#else
	switch (drive_strength) {
		case GPIO_DRIVE_S0 : config |= DRIVE_S0; break;
		case GPIO_DRIVE_S1 : config |= DRIVE_S1; break;
		case GPIO_DRIVE_S2 : config |= DRIVE_S2; break;
		case GPIO_DRIVE_S3 : config |= DRIVE_S3; break;
		case GPIO_DRIVE_S4 : config |= DRIVE_S4; break;
		case GPIO_DRIVE_S5 : config |= DRIVE_S5; break;
		case GPIO_DRIVE_S6 : config |= DRIVE_S6; break;
		case GPIO_DRIVE_S7 : config |= DRIVE_S7; break;
		case GPIO_DRIVE_S8 : config |= DRIVE_S8; break;
		case GPIO_DRIVE_S9 : config |= DRIVE_S9; break;
		case GPIO_DRIVE_S10 : config |= DRIVE_S10; break;
		case GPIO_DRIVE_S11 : config |= DRIVE_S11; break;
		case GPIO_DRIVE_S12 : config |= DRIVE_S12; break;
		case GPIO_DRIVE_S13 : config |= DRIVE_S13; break;
		case GPIO_DRIVE_S14 : config |= DRIVE_S14; break;
		case GPIO_DRIVE_S15 : config |= DRIVE_S15; break;
	}
#endif

	for (cnt = 0; cnt < pin_count; cnt++) {
		gpio = memory_interface_gpios[cnt];
		pad = GPIO2PAD(gpio);
		pin = GPIO2PIN(gpio);
		gpion = pad * GPIOPADPINS + pin;

		gpio_safe_reconfigure(0, gpion, config);
	}

	return 0;
}

uint32_t gpio_get_board_id(void)
{
#if GPIO_VERSION > 5
	// The board ID pins must have been sampled by the time this request is made.
	RELEASE_ASSERT(rGPIO_PINSTRAPS_VALID(gpio_cfgs[0].base_addr) & 1);

	// Return the sampled board ID value.
	return MINIPMGR_SECURITY_GPIO_STRAPS_BOARD_ID_XTRCT(rMINIPMGR_SECURITY_GPIO_STRAPS);
#else
	panic("gpio_get_board_id() not supported for this platform");
#endif
}

#if APPLICATION_IBOOT

int gpio_diag_pinconfig(void)
{
	return 0;
}

#endif /* APPLICATION_IBOOT */
