/* Copyright 2021 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.
 *
 * Cypress CCGXXF I/O Port expander (built inside PD chip) driver source
 */

#include "console.h"
#include "i2c.h"
#include "ioexpander.h"

/* Add after all include files */
#include "ccgxxf.h"

#define CPRINTS(format, args...) cprints(CC_GPIO, format, ## args)

static inline int ccgxxf_read8(int ioex, int reg, int *data)
{
	return i2c_read8(ioex_config[ioex].i2c_host_port,
			ioex_config[ioex].i2c_addr_flags, reg, data);
}

static inline int ccgxxf_update8(int ioex, int reg, uint8_t mask,
				enum mask_update_action action)
{
	return i2c_update8(ioex_config[ioex].i2c_host_port,
			ioex_config[ioex].i2c_addr_flags, reg, mask, action);
}

static inline int ccgxxf_write16(int ioex, uint16_t reg, uint16_t data)
{
	return i2c_write16(ioex_config[ioex].i2c_host_port,
			ioex_config[ioex].i2c_addr_flags, reg, data);
}

static int ccgxxf_get_level(int ioex, int port, int mask, int *val)
{
	int rv;

	rv = ccgxxf_read8(ioex, CCGXXF_REG_GPIO_STATUS(port), val);
	if (!rv)
		*val = !!(*val & mask);

	return rv;
}

static int ccgxxf_set_level(int ioex, int port, int mask, int val)
{
	return ccgxxf_update8(ioex, CCGXXF_REG_GPIO_CONTROL(port), mask, val);
}

/*
 * Following type of pins are supported
 * - Output pins are supported with open-drain & pull-up
 * - Input pins are supported with pull-up & pull-down
 * - Analog pins
 * - 1.8V level GPIOs are supported per port and outputs can only be
 *   open-drain pins
 */
static int ccgxxf_set_flags_by_mask(int ioex, int port, int mask, int flags)
{
	uint16_t pin_mode;
	int rv;

	/* Push-pull output can't be configured for 1.8V level */
	if ((flags & GPIO_OUTPUT) && (flags & GPIO_SEL_1P8V) &&
		!(flags & GPIO_OPEN_DRAIN)) {
		CPRINTS("Invalid flags: ioex=%d, port=%d, mask=%d, flags=0x%x",
				ioex, port, mask, flags);

		return EC_ERROR_INVAL;
	}

	if (flags & GPIO_OUTPUT) {
		if (flags & GPIO_OPEN_DRAIN) {
			if (flags & GPIO_PULL_UP)
				pin_mode = CCGXXF_GPIO_MODE_RES_UP;
			else
				pin_mode = CCGXXF_GPIO_MODE_OD_LOW;
		} else {
			pin_mode = CCGXXF_GPIO_MODE_STRONG;
		}
	} else if (flags & GPIO_INPUT) {
		if (flags & GPIO_PULL_UP) {
			pin_mode = CCGXXF_GPIO_MODE_RES_UP;
			flags |= GPIO_HIGH;
		} else if (flags & GPIO_PULL_DOWN) {
			pin_mode = CCGXXF_GPIO_MODE_RES_DWN;
			flags |= GPIO_LOW;
		} else {
			pin_mode = CCGXXF_GPIO_MODE_HIZ_DIGITAL;
		}
	} else if (flags & GPIO_ANALOG) {
		pin_mode = CCGXXF_GPIO_MODE_HIZ_ANALOG;
	} else {
		return EC_ERROR_INVAL;
	}

	pin_mode = port | (pin_mode << CCGXXF_GPIO_PIN_MODE_SHIFT) |
			(mask << CCGXXF_GPIO_PIN_MASK_SHIFT);

	/* Note: once set the 1.8V level affect whole GPIO port */
	if (flags & GPIO_SEL_1P8V)
		pin_mode |= CCGXXF_GPIO_1P8V_SEL;

	/*
	 * Before setting the GPIO mode, initilaize the pins to default value
	 * to avoid spike on pins.
	 */
	if (flags & (GPIO_HIGH | GPIO_LOW)) {
		rv = ccgxxf_set_level(ioex, port, mask,
					flags & GPIO_HIGH ? 1 : 0);
		if (rv)
			return rv;
	}

	return	ccgxxf_write16(ioex, CCGXXF_REG_GPIO_MODE, pin_mode);
}

static int ccgxxf_get_flags_by_mask(int ioex, int port, int mask, int *flags)
{
	 /* TODO: Add it after implementing in the CCGXXF firmware. */
	return EC_SUCCESS;
}

static int ccgxxf_enable_interrupt(int ioex, int port, int mask, int enable)
{
	/* CCGXXF doesn't have interrupt capability on I/O expnader pins */
	return EC_ERROR_UNIMPLEMENTED;
}

int ccgxxf_init(int ioex)
{
	/* TCPC init of CCGXXF should handle initialization */
	return EC_SUCCESS;
}

const struct ioexpander_drv ccgxxf_ioexpander_drv = {
	.init			= &ccgxxf_init,
	.get_level		= &ccgxxf_get_level,
	.set_level		= &ccgxxf_set_level,
	.get_flags_by_mask	= &ccgxxf_get_flags_by_mask,
	.set_flags_by_mask	= &ccgxxf_set_flags_by_mask,
	.enable_interrupt	= &ccgxxf_enable_interrupt,
};
