/* Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

/*
 * GPIO module for Chrome EC
 *
 * These functions are shared by the STM32F0 and STM32L variants.
 */

#include "common.h"
#include "gpio_chip.h"
#include "registers.h"
#include "util.h"

static uint32_t expand_to_2bit_mask(uint32_t mask)
{
	uint32_t mask_out = 0;
	while (mask) {
		int bit = get_next_bit(&mask);
		mask_out |= 3 << (bit * 2);
	}
	return mask_out;
}

int gpio_get_flags_by_mask(uint32_t port, uint32_t mask)
{
	uint32_t flags = 0;
	uint32_t val = 0;
	const uint32_t mask2 = expand_to_2bit_mask(mask);

	/* Only one bit must be set. */
	if ((mask != (mask & -mask)) || (mask == 0))
		return 0;

	/* Check output type. */
	val = STM32_GPIO_PUPDR(port) & mask2;
	if (val == (0x55555555 & mask2))
		flags |= GPIO_PULL_UP;
	if (val == (0xaaaaaaaa & mask2))
		flags |= GPIO_PULL_DOWN;

	if (STM32_GPIO_OTYPER(port) & mask)
		flags |= GPIO_OPEN_DRAIN;

	/* Check mode. */
	val = STM32_GPIO_MODER(port) & mask2;
	if (val == (0x55555555 & mask2))
		flags |= GPIO_OUTPUT;
	if (val == (0xFFFFFFFF & mask2))
		flags |= GPIO_ANALOG;
	if (val == (0x0 & mask2))
		flags |= GPIO_INPUT;
	if (val == (0xaaaaaaaa & mask2))
		flags |= GPIO_ALTERNATE;

	if (flags & GPIO_OUTPUT) {
		if (STM32_GPIO_ODR(port) & mask)
			flags |= GPIO_HIGH;
		else
			flags |= GPIO_LOW;
	}


	if (STM32_EXTI_RTSR & mask)
		flags |= GPIO_INT_F_RISING;
	if (STM32_EXTI_RTSR & mask)
		flags |= GPIO_INT_F_RISING;

	return flags;
}

void gpio_set_flags_by_mask(uint32_t port, uint32_t mask, uint32_t flags)
{
	/* Bitmask for registers with 2 bits per GPIO pin */
	const uint32_t mask2 = expand_to_2bit_mask(mask);
	uint32_t val;

	/* Set up pullup / pulldown */
	val = STM32_GPIO_PUPDR(port) & ~mask2;
	if (flags & GPIO_PULL_UP)
		val |= 0x55555555 & mask2;	/* Pull Up = 01 */
	else if (flags & GPIO_PULL_DOWN)
		val |= 0xaaaaaaaa & mask2;	/* Pull Down = 10 */
	STM32_GPIO_PUPDR(port) = val;

	/*
	 * Select open drain first, so that we don't glitch the signal when
	 * changing the line to an output.
	 */
	if (flags & GPIO_OPEN_DRAIN)
		STM32_GPIO_OTYPER(port) |= mask;
	else
		STM32_GPIO_OTYPER(port) &= ~mask;

	val = STM32_GPIO_MODER(port) & ~mask2;
	if (flags & GPIO_OUTPUT) {
		/*
		 * Set pin level first to avoid glitching.  This is harmless on
		 * STM32L because the set/reset register isn't connected to the
		 * output drivers until the pin is made an output.
		 */
		if (flags & GPIO_HIGH)
			STM32_GPIO_BSRR(port) = mask;
		else if (flags & GPIO_LOW)
			STM32_GPIO_BSRR(port) = mask << 16;

		/* General purpose, MODE = 01 */
		val |= 0x55555555 & mask2;
		STM32_GPIO_MODER(port) = val;

	} else if (flags & GPIO_ANALOG) {
		/* Analog, MODE=11 */
		val |= 0xFFFFFFFF & mask2;
		STM32_GPIO_MODER(port) = val;
	} else if (flags & GPIO_INPUT) {
		/* Input, MODE=00 */
		STM32_GPIO_MODER(port) = val;
	} else if (flags & GPIO_ALTERNATE) {
		/* Alternate, MODE=10 */
		val |= 0xaaaaaaaa & mask2;
		STM32_GPIO_MODER(port) = val;
	}

	/* Set up interrupts if necessary */
	ASSERT(!(flags & (GPIO_INT_F_LOW | GPIO_INT_F_HIGH)));
	if (flags & GPIO_INT_F_RISING)
		STM32_EXTI_RTSR |= mask;
	if (flags & GPIO_INT_F_FALLING)
		STM32_EXTI_FTSR |= mask;
	/* Interrupt is enabled by gpio_enable_interrupt() */
}

void gpio_set_alternate_function(uint32_t port, uint32_t mask, int func)
{
	int bit;
	uint32_t half;
	uint32_t afr;
	uint32_t moder = STM32_GPIO_MODER(port);

	if (func < 0) {
		/* Return to normal GPIO function, defaulting to input. */
		while (mask) {
			bit = get_next_bit(&mask);
			moder &= ~(0x3 << (bit * 2));
		}
		STM32_GPIO_MODER(port) = moder;
		return;
	}

	/* Low half of the GPIO bank */
	half = mask & 0xff;
	afr = STM32_GPIO_AFRL(port);
	while (half) {
		bit = get_next_bit(&half);
		afr &= ~(0xf << (bit * 4));
		afr |= func << (bit * 4);
		moder &= ~(0x3 << (bit * 2 + 0));
		moder |= 0x2 << (bit * 2 + 0);
	}
	STM32_GPIO_AFRL(port) = afr;

	/* High half of the GPIO bank */
	half = (mask >> 8) & 0xff;
	afr = STM32_GPIO_AFRH(port);
	while (half) {
		bit = get_next_bit(&half);
		afr &= ~(0xf << (bit * 4));
		afr |= func << (bit * 4);
		moder &= ~(0x3 << (bit * 2 + 16));
		moder |= 0x2 << (bit * 2 + 16);
	}
	STM32_GPIO_AFRH(port) = afr;
	STM32_GPIO_MODER(port) = moder;
}
