// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright (C) 2015-2016 Marvell International Ltd.
 */

#include <log.h>
#include <asm/io.h>

#include "comphy_core.h"

/*
 * comphy_mux_check_config()
 * description: this function passes over the COMPHY lanes and check if the type
 *              is valid for specific lane. If the type is not valid,
 *              the function update the struct and set the type of the lane as
 *              COMPHY_TYPE_UNCONNECTED
 */
static void comphy_mux_check_config(struct comphy_mux_data *mux_data,
		struct comphy_map *comphy_map_data, int comphy_max_lanes)
{
	struct comphy_mux_options *mux_opt;
	int lane, opt, valid;

	debug_enter();

	for (lane = 0; lane < comphy_max_lanes;
	     lane++, comphy_map_data++, mux_data++) {
		/* Don't check ignored COMPHYs */
		if (comphy_map_data->type == COMPHY_TYPE_IGNORE)
			continue;

		mux_opt = mux_data->mux_values;
		for (opt = 0, valid = 0; opt < mux_data->max_lane_values;
		     opt++, mux_opt++) {
			if (mux_opt->type == comphy_map_data->type) {
				valid = 1;
				break;
			}
		}
		if (valid == 0) {
			debug("lane number %d, had invalid type %d\n",
			      lane, comphy_map_data->type);
			debug("set lane %d as type %d\n", lane,
			      COMPHY_TYPE_UNCONNECTED);
			comphy_map_data->type = COMPHY_TYPE_UNCONNECTED;
		} else {
			debug("lane number %d, has type %d\n",
			      lane, comphy_map_data->type);
		}
	}

	debug_exit();
}

static u32 comphy_mux_get_mux_value(struct comphy_mux_data *mux_data,
				    u32 type, int lane)
{
	struct comphy_mux_options *mux_opt;
	int opt;
	u32 value = 0;

	debug_enter();

	mux_opt = mux_data->mux_values;
	for (opt = 0 ; opt < mux_data->max_lane_values; opt++, mux_opt++) {
		if (mux_opt->type == type) {
			value = mux_opt->mux_value;
			break;
		}
	}

	debug_exit();

	return value;
}

static void comphy_mux_reg_write(struct comphy_mux_data *mux_data,
				 struct comphy_map *comphy_map_data,
				 int comphy_max_lanes,
				 void __iomem *selector_base,
				 const fdt32_t *mux_lane_order, u32 bitcount)
{
	u32 lane, value, offset, mask;

	debug_enter();

	for (lane = 0; lane < comphy_max_lanes;
	     lane++, comphy_map_data++, mux_data++) {
		if (comphy_map_data->type == COMPHY_TYPE_IGNORE)
			continue;

		/*
		 * if the order of nodes in selector base register is
		 * nontrivial, use mapping from mux_lane_order
		 */
		if (mux_lane_order)
			offset = fdt32_to_cpu(mux_lane_order[lane]) * bitcount;
		else
			offset = lane * bitcount;

		mask = (((1 << bitcount) - 1) << offset);
		value = (comphy_mux_get_mux_value(mux_data,
						  comphy_map_data->type,
						  lane) << offset);
		reg_set(selector_base, value, mask);
	}

	debug_exit();
}

void comphy_mux_init(struct chip_serdes_phy_config *chip_cfg,
		     struct comphy_map *comphy_map_data,
		     void __iomem *selector_base)
{
	struct comphy_mux_data *mux_data;
	const fdt32_t *mux_lane_order;
	u32 mux_bitcount;
	u32 comphy_max_lanes;

	debug_enter();

	comphy_max_lanes = chip_cfg->comphy_lanes_count;
	mux_data = chip_cfg->mux_data;
	mux_lane_order = chip_cfg->comphy_mux_lane_order;
	mux_bitcount = chip_cfg->comphy_mux_bitcount;

	/* check if the configuration is valid */
	comphy_mux_check_config(mux_data, comphy_map_data, comphy_max_lanes);
	/* Init COMPHY selectors */
	comphy_mux_reg_write(mux_data, comphy_map_data, comphy_max_lanes,
			     selector_base, mux_lane_order, mux_bitcount);

	debug_exit();
}
