/*
 * This file is part of the coreboot project.
 *
 * Copyright (C) 2014 Google Inc.
 * Copyright (C) 2015 Intel Corporation.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include <stdint.h>
#include <string.h>
#include <arch/io.h>
#include <console/console.h>
#include <device/device.h>
#include <device/pci.h>
#include <gpio.h>
#include <intelblocks/pcr.h>
#include <soc/pcr_ids.h>
#include <soc/iomap.h>
#include <soc/pm.h>

static const int gpio_debug = 0;

/* There are 4 communities with 8 GPIO groups (GPP_[A:G] and GPD) */
struct gpio_community {
	int port_id;
	/* Inclusive pads within the community. */
	gpio_t min;
	gpio_t max;
};

/* This is ordered to match ACPI and OS driver. */
static const struct gpio_community communities[] = {
	{
		.port_id = PID_GPIOCOM0,
		.min = GPP_A0,
		.max = GPP_B23,
	},
	{
		.port_id = PID_GPIOCOM1,
		.min = GPP_C0,
#if IS_ENABLED(CONFIG_SKYLAKE_SOC_PCH_H)
		.max = GPP_H23,
#else
		.max = GPP_E23,
#endif
	},
	{
		.port_id = PID_GPIOCOM3,
#if IS_ENABLED(CONFIG_SKYLAKE_SOC_PCH_H)
		.min = GPP_I0,
		.max = GPP_I10,
#else
		.min = GPP_F0,
		.max = GPP_G7,
#endif
	},
	{
		.port_id = PID_GPIOCOM2,
		.min = GPD0,
		.max = GPD11,
	},
};

static const char *gpio_group_names[GPIO_NUM_GROUPS] = {
	"GPP_A",
	"GPP_B",
	"GPP_C",
	"GPP_D",
	"GPP_E",
	"GPP_F",
	"GPP_G",
#if IS_ENABLED(CONFIG_SKYLAKE_SOC_PCH_H)
	"GPP_H",
	"GPP_I",
#endif
	"GPD",
};

static inline void *gpio_community_regs(int port_id)
{
	return pcr_reg_address(port_id, 0);
}

static inline size_t gpios_in_community(const struct gpio_community *comm)
{
	/* max is inclusive */
	return comm->max - comm->min + 1;
}

static inline size_t groups_in_community(const struct gpio_community *comm)
{
	size_t n = gpios_in_community(comm) + GPIO_MAX_NUM_PER_GROUP - 1;
	return n / GPIO_MAX_NUM_PER_GROUP;
}

static inline int gpio_index_gpd(gpio_t gpio)
{
	if (gpio >= GPD0 && gpio <= GPD11)
		return 1;
	return 0;
}

static const struct gpio_community *gpio_get_community(gpio_t pad)
{
	size_t i;

	for (i = 0; i < ARRAY_SIZE(communities); i++) {
		const struct gpio_community *c = &communities[i];

		if (pad >= c->min && pad <= c->max)
			return c;
	}

	return NULL;
}

static size_t community_clr_get_smi_sts(const struct gpio_community *comm,
					uint32_t *sts)
{
	uint8_t *regs;
	size_t i;
	uint32_t *gpi_status_reg;
	uint32_t *gpi_en_reg;
	const size_t num_grps = groups_in_community(comm);

	/* Not all groups can be routed to SMI. However, the registers
	 * read as 0. In order to simplify the logic read everything from
	 * each community. */
	regs = gpio_community_regs(comm->port_id);
	gpi_status_reg = (void *)&regs[GPI_SMI_STS_OFFSET];
	gpi_en_reg = (void *)&regs[GPI_SMI_EN_OFFSET];
	for (i = 0; i < num_grps; i++) {
		sts[i] = read32(gpi_status_reg + i) & read32(gpi_en_reg + i);
		/* Clear the enabled and set status bits. */
		write32(gpi_status_reg + i, sts[i]);
	}

	return num_grps;
}

static void print_gpi_status(uint32_t status, const char *grp_name)
{
	int i;

	if (!status)
		return;

	for (i = 31; i >= 0; i--) {
		if (status & (1 << i))
			printk(BIOS_DEBUG, "%s%d ", grp_name, i);
	}
}

void gpi_clear_get_smi_status(struct gpi_status *sts)
{
	int i;
	int do_print;
	size_t sts_index = 0;

	for (i = 0; i < ARRAY_SIZE(communities); i++) {
		const struct gpio_community *comm = &communities[i];
		sts_index += community_clr_get_smi_sts(comm,
						&sts->grp[sts_index]);
	}

	do_print = 0;
	for (i = 0; i < ARRAY_SIZE(sts->grp); i++) {
		if (sts->grp[i] == 0)
			continue;
		do_print = 1;
		break;
	}

	if (!do_print)
		return;

	printk(BIOS_DEBUG, "GPI_SMI_STS: ");
	for (i = 0; i < ARRAY_SIZE(sts->grp); i++)
		print_gpi_status(sts->grp[i], gpio_group_names[i]);
	printk(BIOS_DEBUG, "\n");
}

int gpi_status_get(const struct gpi_status *sts, gpio_t gpi)
{
	const uint32_t *gpi_sts;

	/* Check if valid gpi */
	if (gpio_get_community(gpi) == NULL)
		return 0;

	/* If not in GPD group the index is a linear function based on
	 * GPI number and GPIO_MAX_NUM_PER_GROUP. */
	if (gpio_index_gpd(gpi))
		gpi_sts = &sts->grp[GPD];
	else
		gpi_sts = &sts->grp[gpi / GPIO_MAX_NUM_PER_GROUP];

	return !!(*gpi_sts & (1 << (gpi % GPIO_MAX_NUM_PER_GROUP)));
}

void gpio_route_gpe(uint16_t gpe0_route)
{
	int i;
	uint32_t misc_cfg;
	const uint32_t misc_cfg_reg_mask = GPE_DW_MASK;

	misc_cfg = (uint32_t)gpe0_route << GPE_DW_SHIFT;
	misc_cfg &= misc_cfg_reg_mask;

	for (i = 0; i < ARRAY_SIZE(communities); i++) {
		uint8_t *regs;
		uint32_t reg;
		const struct gpio_community *comm = &communities[i];

		regs = gpio_community_regs(comm->port_id);

		reg = read32(regs + MISCCFG_OFFSET);
		reg &= ~misc_cfg_reg_mask;
		reg |= misc_cfg;
		write32(regs + MISCCFG_OFFSET, reg);
	}
}

static void *gpio_dw_regs(gpio_t pad)
{
	const struct gpio_community *comm;
	uint8_t *regs;
	size_t pad_relative;

	comm = gpio_get_community(pad);

	if (comm == NULL)
		return NULL;

	regs = gpio_community_regs(comm->port_id);

	pad_relative = pad - comm->min;

	/* DW0 and DW1 regs are 4 bytes each. */
	return &regs[PAD_CFG_DW_OFFSET + pad_relative *
			GPIO_DWx_SIZE(GPIO_DWx_COUNT)];
}

static void *gpio_hostsw_reg(gpio_t pad, size_t *bit)
{
	const struct gpio_community *comm;
	uint8_t *regs;
	size_t pad_relative;

	comm = gpio_get_community(pad);

	if (comm == NULL)
		return NULL;

	regs = gpio_community_regs(comm->port_id);

	pad_relative = pad - comm->min;

	/* Update the bit for this pad. */
	*bit = (pad_relative % HOSTSW_OWN_PADS_PER);

	/* HostSw regs are 4 bytes each. */
	regs = &regs[HOSTSW_OWN_REG_OFFSET];
	return &regs[(pad_relative / HOSTSW_OWN_PADS_PER) * 4];
}

static void gpio_handle_pad_mode(const struct pad_config *cfg)
{
	size_t bit;
	uint32_t *hostsw_own_reg;
	uint32_t reg;

	bit = 0;
	hostsw_own_reg = gpio_hostsw_reg(cfg->pad, &bit);

	if (hostsw_own_reg == NULL)
		return;

	reg = read32(hostsw_own_reg);
	reg &= ~(1U << bit);

	if ((cfg->attrs & PAD_FIELD(HOSTSW, GPIO)) == PAD_FIELD(HOSTSW, GPIO))
		reg |= (HOSTSW_GPIO << bit);
	else
		reg |= (HOSTSW_ACPI << bit);

	write32(hostsw_own_reg, reg);
}

static void gpi_enable_smi(gpio_t pad)
{
	const struct gpio_community *comm;
	uint8_t *regs;
	uint32_t *gpi_status_reg;
	uint32_t *gpi_en_reg;
	size_t group_offset;
	uint32_t pad_mask;

	comm = gpio_get_community(pad);
	if (comm == NULL)
		return;
	regs = gpio_community_regs(comm->port_id);
	gpi_status_reg = (void *)&regs[GPI_SMI_STS_OFFSET];
	gpi_en_reg = (void *)&regs[GPI_SMI_EN_OFFSET];

	/* Offset of SMI STS/EN for this pad's group within the community. */
	group_offset = (pad - comm->min) / GPIO_MAX_NUM_PER_GROUP;

	/* Clear status then set enable. */
	pad_mask = 1 << ((pad - comm->min) % GPIO_MAX_NUM_PER_GROUP);
	write32(&gpi_status_reg[group_offset], pad_mask);
	write32(&gpi_en_reg[group_offset],
		read32(&gpi_en_reg[group_offset]) | pad_mask);
}

static void gpio_configure_pad(const struct pad_config *cfg)
{
	uint32_t *dw_regs;
	uint32_t reg;
	uint32_t dw0;
	uint32_t mask;

	dw_regs = gpio_dw_regs(cfg->pad);

	if (dw_regs == NULL)
		return;

	dw0 = cfg->dw0;

	write32(&dw_regs[0], dw0);
	reg = read32(&dw_regs[1]);

	/* Apply termination field */
	mask = PAD_TERM_MASK << PAD_TERM_SHIFT;
	reg &= ~mask;
	reg |= cfg->attrs & mask;

	/* Apply voltage tolerance field */
	mask = PAD_TOL_MASK << PAD_TOL_SHIFT;
	reg &= ~mask;
	reg |= cfg->attrs & mask;
	write32(&dw_regs[1], reg);

	gpio_handle_pad_mode(cfg);

	if ((dw0 & PAD_FIELD(GPIROUTSMI, MASK)) == PAD_FIELD(GPIROUTSMI, YES))
		gpi_enable_smi(cfg->pad);

	if (gpio_debug)
		printk(BIOS_DEBUG,
			"Write Pad: Base(%p) - conf0 = %x conf1= %x pad # = %d\n",
			&dw_regs[0], dw0, reg, cfg->pad);
}

void gpio_configure_pads(const struct pad_config *cfgs, size_t num)
{
	size_t i;

	for (i = 0; i < num; i++)
		gpio_configure_pad(&cfgs[i]);
}

void gpio_input_pulldown(gpio_t gpio)
{
	struct pad_config cfg = PAD_CFG_GPI(gpio, 5K_PD, DEEP);
	gpio_configure_pad(&cfg);
}

void gpio_input_pullup(gpio_t gpio)
{
	struct pad_config cfg = PAD_CFG_GPI(gpio, 5K_PU, DEEP);
	gpio_configure_pad(&cfg);
}

void gpio_input(gpio_t gpio)
{
	struct pad_config cfg = PAD_CFG_GPI(gpio, NONE, DEEP);
	gpio_configure_pad(&cfg);
}

void gpio_output(gpio_t gpio, int value)
{
	struct pad_config cfg = PAD_CFG_GPO(gpio, value, DEEP);
	gpio_configure_pad(&cfg);
}

int gpio_get(gpio_t gpio_num)
{
	uint32_t *dw_regs;
	uint32_t reg;

	dw_regs = gpio_dw_regs(gpio_num);

	if (dw_regs == NULL)
		return -1;

	reg = read32(&dw_regs[0]);

	return (reg >> GPIORXSTATE_SHIFT) & GPIORXSTATE_MASK;
}

void gpio_set(gpio_t gpio_num, int value)
{
	uint32_t *dw_regs;
	uint32_t reg;

	dw_regs = gpio_dw_regs(gpio_num);

	if (dw_regs == NULL)
		return;

	reg = read32(&dw_regs[0]);
	reg &= ~PAD_FIELD(GPIOTXSTATE, MASK);
	reg |= PAD_FIELD_VAL(GPIOTXSTATE, value);
	write32(&dw_regs[0], reg);
	/* GPIO port ids support posted write semantics. */
}

const char *gpio_acpi_path(gpio_t gpio_num)
{
	return "\\_SB.PCI0.GPIO";
}

uint16_t gpio_acpi_pin(gpio_t gpio_num)
{
	return gpio_num;
}
