/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */

/*
 * Copyright (c) 2017, Joyent, Inc.
 */

/*
 * Routines to get access to the phy and transceiver that require routines and
 * definitions that aren't part of the common ixgbe API.
 */

#include "ixgbe_sw.h"
#include "ixgbe_phy.h"

static int
ixgbe_transceiver_is_8472(ixgbe_t *ixgbe, boolean_t *valp)
{
	int32_t ret;
	uint8_t rev, swap;
	struct ixgbe_hw *hw = &ixgbe->hw;

	ASSERT(MUTEX_HELD(&ixgbe->gen_lock));
	if (hw->phy.ops.read_i2c_eeprom == NULL)
		return (ENOTSUP);

	ret = hw->phy.ops.read_i2c_eeprom(hw, IXGBE_SFF_SFF_8472_COMP, &rev);
	if (ret != 0)
		return (EIO);

	ret = hw->phy.ops.read_i2c_eeprom(hw, IXGBE_SFF_SFF_8472_SWAP, &swap);
	if (ret != 0)
		return (EIO);

	if (swap & IXGBE_SFF_ADDRESSING_MODE) {
		ixgbe_log(ixgbe, "transceiver requires unsupported address "
		    "change for page 0xa2. Access will only be allowed to "
		    "page 0xa0.");
	}

	if (rev == IXGBE_SFF_SFF_8472_UNSUP ||
	    (swap & IXGBE_SFF_ADDRESSING_MODE)) {
		*valp = B_FALSE;
	} else {
		*valp = B_TRUE;
	}

	return (0);
}

/*
 * Note, we presume that the mac perimeter is held during these calls. As such,
 * we rely on that for guaranteeing that only one thread is calling the i2c
 * routines at any time.
 */
int
ixgbe_transceiver_info(void *arg, uint_t id, mac_transceiver_info_t *infop)
{
	ixgbe_t *ixgbe = arg;
	struct ixgbe_hw *hw = &ixgbe->hw;
	boolean_t present, usable;

	if (id != 0 || infop == NULL)
		return (EINVAL);

	mutex_enter(&ixgbe->gen_lock);
	if (ixgbe_get_media_type(&ixgbe->hw) == ixgbe_media_type_copper) {
		mutex_exit(&ixgbe->gen_lock);
		return (ENOTSUP);
	}

	/*
	 * Make sure we have the latest sfp information. This is especially
	 * important if the SFP is removed as that doesn't trigger interrupts in
	 * our current configuration.
	 */
	(void) hw->phy.ops.identify_sfp(hw);
	if (hw->phy.type == ixgbe_phy_none ||
	    (hw->phy.type == ixgbe_phy_unknown &&
	    hw->phy.sfp_type == ixgbe_sfp_type_not_present)) {
		present = B_FALSE;
		usable = B_FALSE;
	} else {
		present = B_TRUE;
		usable = hw->phy.type != ixgbe_phy_sfp_unsupported;
	}

	mutex_exit(&ixgbe->gen_lock);

	mac_transceiver_info_set_present(infop, present);
	mac_transceiver_info_set_usable(infop, usable);

	return (0);
}

/*
 * Note, we presume that the mac perimeter is held during these calls. As such,
 * we rely on that for guaranteeing that only one thread is calling the i2c
 * routines at any time.
 */
int
ixgbe_transceiver_read(void *arg, uint_t id, uint_t page, void *bp,
    size_t nbytes, off_t offset, size_t *nread)
{
	ixgbe_t *ixgbe = arg;
	struct ixgbe_hw *hw = &ixgbe->hw;
	uint8_t *buf = bp;
	size_t i;
	boolean_t is8472;

	if (id != 0 || buf == NULL || nbytes == 0 || nread == NULL ||
	    (page != 0xa0 && page != 0xa2) || offset < 0)
		return (EINVAL);

	/*
	 * Both supported pages have a length of 256 bytes, ensure nothing asks
	 * us to go beyond that.
	 */
	if (nbytes > 256 || offset >= 256 || (offset + nbytes > 256)) {
		return (EINVAL);
	}

	mutex_enter(&ixgbe->gen_lock);
	if (ixgbe_get_media_type(&ixgbe->hw) == ixgbe_media_type_copper) {
		mutex_exit(&ixgbe->gen_lock);
		return (ENOTSUP);
	}

	if (hw->phy.ops.read_i2c_eeprom == NULL) {
		mutex_exit(&ixgbe->gen_lock);
		return (ENOTSUP);
	}

	if (ixgbe_transceiver_is_8472(ixgbe, &is8472) != 0) {
		mutex_exit(&ixgbe->gen_lock);
		return (EIO);
	}

	if (!is8472 && page == 0xa2) {
		mutex_exit(&ixgbe->gen_lock);
		return (EINVAL);
	}

	for (i = 0; i < nbytes; i++, offset++, buf++) {
		int32_t ret;

		if (page == 0xa0) {
			ret = hw->phy.ops.read_i2c_eeprom(hw, offset, buf);
		} else {
			ret = hw->phy.ops.read_i2c_sff8472(hw, offset, buf);
		}
		if (ret != 0) {
			mutex_exit(&ixgbe->gen_lock);
			return (EIO);
		}
	}
	mutex_exit(&ixgbe->gen_lock);
	*nread = i;

	return (0);
}