text: /* Copyright 2019 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.
   *
   * Driver for Intel Burnside Bridge - Thunderbolt/USB/DisplayPort Retimer
   */
  
  #include "bb_retimer.h"
  #include "common.h"
  #include "console.h"
  #include "i2c.h"
  #include "timer.h"
  #include "usb_pd.h"
  #include "util.h"
  
  #define BB_RETIMER_REG_SIZE	4
  #define BB_RETIMER_READ_SIZE	(BB_RETIMER_REG_SIZE + 1)
  #define BB_RETIMER_WRITE_SIZE	(BB_RETIMER_REG_SIZE + 2)
  
  #define CPRINTS(format, args...) cprints(CC_USBCHARGE, format, ## args)
  #define CPRINTF(format, args...) cprintf(CC_USBCHARGE, format, ## args)
  
  /**
   * Utility functions
   */
  static int bb_retimer_read(int port, const uint8_t offset, uint32_t *data)
  {
  	int rv;
  	uint8_t buf[BB_RETIMER_READ_SIZE];
  
  	/*
  	 * Read sequence
  	 * Slave Addr(w) - Reg offset - repeated start - Slave Addr(r)
  	 * byte[0]   : Read size
  	 * byte[1:4] : Data [LSB -> MSB]
  	 * Stop
  	 */
  	rv = i2c_xfer(usb_retimers[port].i2c_port,
  		      usb_retimers[port].i2c_addr_flags,
  		      &offset, 1, buf, BB_RETIMER_READ_SIZE);
  	if (rv)
  		return rv;
  	if (buf[0] != BB_RETIMER_REG_SIZE)
  		return EC_ERROR_UNKNOWN;
  
  	*data = buf[1] | (buf[2] << 8) | (buf[3] << 16) | (buf[4] << 24);
  
  	return EC_SUCCESS;
  }
  
  static int bb_retimer_write(int port, const uint8_t offset, uint32_t data)
  {
  	uint8_t buf[BB_RETIMER_WRITE_SIZE];
  
  	/*
  	 * Write sequence
  	 * Slave Addr(w)
  	 * byte[0]   : Reg offset
  	 * byte[1]   : Write Size
  	 * byte[2:5] : Data [LSB -> MSB]
  	 * stop
  	 */
  	buf[0] = offset;
  	buf[1] = BB_RETIMER_REG_SIZE;
  	buf[2] = data & 0xFF;
  	buf[3] = (data >> 8) & 0xFF;
  	buf[4] = (data >> 16) & 0xFF;
  	buf[5] = (data >> 24) & 0xFF;
  
  	return i2c_xfer(usb_retimers[port].i2c_port,
  			usb_retimers[port].i2c_addr_flags,
  			buf, BB_RETIMER_WRITE_SIZE, NULL, 0);
  }
  
  static void bb_retimer_power_handle(int port, int on_off)
  {
  	const struct usb_retimer * const retimer = &usb_retimers[port];
  
  	/* handle retimer's power domain */
  
  	if (on_off) {
  		gpio_set_level(retimer->usb_ls_en_gpio, 1);
  		msleep(1);
  		gpio_set_level(retimer->retimer_rst_gpio, 1);
  		msleep(10);
  		gpio_set_level(retimer->force_power_gpio, 1);
  
  		/*
  		 * If BB retimer NVM is shared between multiple ports, allow
  		 * 40ms time for all the retimers to be initialized.
  		 * Else allow 20ms to initialize.
  		 */
  		if (retimer->shared_nvm)
  			msleep(40);
  		else
  			msleep(20);
  	} else {
  		gpio_set_level(retimer->force_power_gpio, 0);
  		msleep(1);
  		gpio_set_level(retimer->retimer_rst_gpio, 0);
  		msleep(1);
  		gpio_set_level(retimer->usb_ls_en_gpio, 0);
  	}
  }
  
  /**
   * Driver interface functions
   */
  static int retimer_set_state(int port, mux_state_t mux_state)
  {
  	uint32_t set_retimer_con = 0;
  	uint8_t dp_pin_mode;
  
  	/*
  	 * Bit 0: DATA_CONNECTION_PRESENT
  	 * 0 - No connection present
  	 * 1 - Connection present
  	 */
  	if (mux_state & USB_PD_MUX_USB_ENABLED ||
  		mux_state & USB_PD_MUX_DP_ENABLED)
  		set_retimer_con |= BB_RETIMER_DATA_CONNECTION_PRESENT;
  
  	/*
  	 * Bit 1: CONNECTION_ORIENTATION
  	 * 0 - Normal
  	 * 1 - reversed
  	 */
  	if (mux_state & USB_PD_MUX_POLARITY_INVERTED)
  		set_retimer_con |= BB_RETIMER_CONNECTION_ORIENTATION;
  
  	/*
  	 * TODO: b:129990370
  	 * Bit 2: ACTIVE_CABLE
  	 * 0 - Passive
  	 * 1 -TBT Active cable
  	 */
  
  	/*
  	 * Bit 5: USB_3_CONNECTION
  	 * 0 - No USB3.1 Connection
  	 * 1 - USB3.1 connection
  	 */
  	if (mux_state & USB_PD_MUX_USB_ENABLED) {
  		set_retimer_con |= BB_RETIMER_USB_3_CONNECTION;
  
  		/*
  		 * Bit 7: USB_DATA_ROLE (ignored if BIT5=0)
  		 * 0 - DFP
  		 * 1 - UPF
  		 */
  		if (pd_partner_is_ufp(port))
  			set_retimer_con |= BB_RETIMER_USB_DATA_ROLE;
  	}
  
  	/*
  	 * Bit 8: DP_CONNECTION
  	 * 0 – No DP connection
  	 * 1 – DP connected
  	 */
  	if (mux_state & USB_PD_MUX_DP_ENABLED) {
  		set_retimer_con |= BB_RETIMER_DP_CONNECTION;
  
  		/*
  		 * Bit 10-11: DP_PIN_ASSIGNMENT (ignored if BIT8 = 0)
  		 * 00 – Pin assignments E/E’
  		 * 01 – Pin assignments C/C’/D/D’1,2
  		 * 10, 11 - reserved
  		 */
  		dp_pin_mode = get_dp_pin_mode(port);
  		if (dp_pin_mode == MODE_DP_PIN_C ||
  			dp_pin_mode == MODE_DP_PIN_D)
  			set_retimer_con |= BB_RETIMER_DP_PIN_ASSIGNMENT;
  
  		/*
  		 * Bit 14: IRQ_HPD (ignored if BIT8 = 0)
  		 * 0 - No IRQ_HPD
  		 * 1 - IRQ_HPD received
  		 */
  		if (mux_state & USB_PD_MUX_HPD_IRQ)
  			set_retimer_con |= BB_RETIMER_IRQ_HPD;
  
  		/*
  		 * Bit 15: HPD_LVL (ignored if BIT8 = 0)
  		 * 0 - HPD_State Low
  		 * 1 - HPD_State High
  		 */
  		if (mux_state & USB_PD_MUX_HPD_LVL)
  			set_retimer_con |= BB_RETIMER_HPD_LVL;
  	}
  
  	/*
  	 * Bit 12: DEBUG_ACCESSORY_MODE
  	 * 0 - Not in debug mode
  	 * 1 - In debug accessory mode
  	 */
  	if (pd_is_debug_acc(port))
  		set_retimer_con |= BB_RETIMER_DEBUG_ACCESSORY_MODE;
  
  	/* Writing the register4 */
  	return bb_retimer_write(port, BB_RETIMER_REG_CONNECTION_STATE,
  			set_retimer_con);
  }
  
  static int retimer_low_power_mode(int port)
  {
  	bb_retimer_power_handle(port, 0);
  	return EC_SUCCESS;
  }
  
  static int retimer_init(int port)
  {
  	int rv;
  	uint32_t data;
  
  	bb_retimer_power_handle(port, 1);
  
  	rv = bb_retimer_read(port, BB_RETIMER_REG_VENDOR_ID, &data);
  	if (rv)
  		return rv;
  	if (data != BB_RETIMER_VENDOR_ID)
  		return EC_ERROR_UNKNOWN;
  
  	rv = bb_retimer_read(port, BB_RETIMER_REG_DEVICE_ID, &data);
  	if (rv)
  		return rv;
  
  	if (data != BB_RETIMER_DEVICE_ID)
  		return EC_ERROR_UNKNOWN;
  
  	return EC_SUCCESS;
  }
  
  const struct usb_retimer_driver bb_usb_retimer = {
  	.init = retimer_init,
  	.set = retimer_set_state,
  	.enter_low_power_mode = retimer_low_power_mode,
  };
  
  #ifdef CONFIG_CMD_RETIMER
  static int console_command_bb_retimer(int argc, char **argv)
  {
  	char rw, *e;
  	int rv, port, reg, data, val;
  
  	if (argc < 4)
  		return EC_ERROR_PARAM_COUNT;
  
  	/* Get port number */
  	port = strtoi(argv[1], &e, 0);
  	if (*e || port < 0 || port > board_get_usb_pd_port_count() ||
  		!usb_retimers[port].driver)
  		return EC_ERROR_PARAM1;
  
  	/* Validate r/w selection */
  	rw = argv[2][0];
  	if (rw != 'w' && rw != 'r')
  		return EC_ERROR_PARAM2;
  
  	/* Get register address */
  	reg = strtoi(argv[3], &e, 0);
  	if (*e || reg < 0)
  		return EC_ERROR_PARAM3;
  
  	if (rw == 'r')
  		rv = bb_retimer_read(port, reg, &data);
  	else {
  		/* Get value to be written */
  		val = strtoi(argv[4], &e, 0);
  		if (*e || val < 0)
  			return EC_ERROR_PARAM4;
  
  		rv = bb_retimer_write(port, reg, val);
  		if (rv == EC_SUCCESS) {
  			rv = bb_retimer_read(port, reg, &data);
  			if (rv == EC_SUCCESS && data != val)
  				rv = EC_ERROR_UNKNOWN;
  		}
  	}
  
  	if (rv == EC_SUCCESS)
  		CPRINTS("register 0x%x [%d] = 0x%x [%d]", reg, reg, data, data);
  
  	return rv;
  }
  DECLARE_CONSOLE_COMMAND(bb, console_command_bb_retimer,
  			"<port> <r/w> <reg> | <val>",
  			"Read or write to BB retimer register");
  #endif /* CONFIG_CMD_RETIMER */
  