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.
   *
   * AMD FP5 USB/DP Mux.
   */
  
  #include "amd_fp5.h"
  #include "chipset.h"
  #include "common.h"
  #include "hooks.h"
  #include "i2c.h"
  #include "queue.h"
  #include "timer.h"
  #include "usb_mux.h"
  
  static mux_state_t saved_mux_state[CONFIG_USB_PD_PORT_MAX_COUNT];
  
  static inline int amd_fp5_mux_read(const struct usb_mux *me, uint8_t *val)
  {
  	uint8_t buf[3] = { 0 };
  	int rv;
  
  	rv = i2c_xfer(me->i2c_port, me->i2c_addr_flags,
  		      NULL, 0, buf, 3);
  	if (rv)
  		return rv;
  
  	*val = buf[me->usb_port + 1];
  
  	return EC_SUCCESS;
  }
  
  static inline int amd_fp5_mux_write(const struct usb_mux *me, uint8_t val)
  {
  	return i2c_write8(me->i2c_port, me->i2c_addr_flags,
  			  me->usb_port, val);
  }
  
  static int amd_fp5_init(const struct usb_mux *me)
  {
  	return EC_SUCCESS;
  }
  
  static int amd_fp5_set_mux(const struct usb_mux *me, mux_state_t mux_state,
  			   bool *ack_required)
  {
  	uint8_t val = 0;
  
  	/* This driver does not use host command ACKs */
  	*ack_required = false;
  
  	saved_mux_state[me->usb_port] = mux_state;
  
  	/*
  	 * This MUX is on the FP5 SoC.  If that device is not powered then
  	 * we either have to complain that it is not powered or if we were
  	 * setting the state to OFF, then go ahead and report that we did
  	 * it because a powered down MUX is off.
  	 */
  	if (chipset_in_state(CHIPSET_STATE_HARD_OFF))
  		return (mux_state == USB_PD_MUX_NONE)
  			? EC_SUCCESS
  			: EC_ERROR_NOT_POWERED;
  
  	if ((mux_state & USB_PD_MUX_USB_ENABLED) &&
  		(mux_state & USB_PD_MUX_DP_ENABLED))
  		val = (mux_state & USB_PD_MUX_POLARITY_INVERTED)
  			? AMD_FP5_MUX_DOCK_INVERTED : AMD_FP5_MUX_DOCK;
  	else if (mux_state & USB_PD_MUX_USB_ENABLED)
  		val = (mux_state & USB_PD_MUX_POLARITY_INVERTED)
  			? AMD_FP5_MUX_USB_INVERTED : AMD_FP5_MUX_USB;
  	else if (mux_state & USB_PD_MUX_DP_ENABLED)
  		val = (mux_state & USB_PD_MUX_POLARITY_INVERTED)
  			? AMD_FP5_MUX_DP_INVERTED : AMD_FP5_MUX_DP;
  
  	return amd_fp5_mux_write(me, val);
  }
  
  static int amd_fp5_get_mux(const struct usb_mux *me, mux_state_t *mux_state)
  {
  	uint8_t val = AMD_FP5_MUX_SAFE;
  
  	/*
  	 * This MUX is on the FP5 SoC.  Only access the device if we
  	 * have power.  If that device is not powered then claim the
  	 * state to be NONE, which is SAFE.
  	 */
  	if (!chipset_in_state(CHIPSET_STATE_HARD_OFF)) {
  		int rv;
  
  		rv = amd_fp5_mux_read(me, &val);
  		if (rv)
  			return rv;
  	}
  
  	switch (val) {
  	case AMD_FP5_MUX_USB:
  		*mux_state = USB_PD_MUX_USB_ENABLED;
  		break;
  	case AMD_FP5_MUX_USB_INVERTED:
  		*mux_state = USB_PD_MUX_USB_ENABLED |
  				USB_PD_MUX_POLARITY_INVERTED;
  		break;
  	case AMD_FP5_MUX_DOCK:
  		*mux_state = USB_PD_MUX_USB_ENABLED | USB_PD_MUX_DP_ENABLED;
  		break;
  	case AMD_FP5_MUX_DOCK_INVERTED:
  		*mux_state = USB_PD_MUX_USB_ENABLED | USB_PD_MUX_DP_ENABLED
  			     | USB_PD_MUX_POLARITY_INVERTED;
  		break;
  	case AMD_FP5_MUX_DP:
  		*mux_state = USB_PD_MUX_DP_ENABLED;
  		break;
  	case AMD_FP5_MUX_DP_INVERTED:
  		*mux_state = USB_PD_MUX_DP_ENABLED |
  				USB_PD_MUX_POLARITY_INVERTED;
  		break;
  	case AMD_FP5_MUX_SAFE:
  	default:
  		*mux_state = USB_PD_MUX_NONE;
  		break;
  	}
  
  	return EC_SUCCESS;
  }
  
  static struct queue const chipset_reset_queue
  	= QUEUE_NULL(CONFIG_USB_PD_PORT_MAX_COUNT, struct usb_mux *);
  
  static void amd_fp5_chipset_reset_delay(void)
  {
  	struct usb_mux *me;
  	int rv;
  	bool unused;
  
  	while (queue_remove_unit(&chipset_reset_queue, &me)) {
  		rv = amd_fp5_set_mux(me, saved_mux_state[me->usb_port],
  				     &unused);
  		if (rv)
  			ccprints("C%d restore mux rv:%d", me->usb_port, rv);
  	}
  }
  DECLARE_DEFERRED(amd_fp5_chipset_reset_delay);
  
  /*
   * The AP's internal USB-C mux is reset when AP resets, so wait for
   * it to be ready and then restore the previous setting.
   */
  static int amd_fp5_chipset_reset(const struct usb_mux *me)
  {
  	queue_add_unit(&chipset_reset_queue, &me);
  	hook_call_deferred(&amd_fp5_chipset_reset_delay_data, 200 * MSEC);
  	return EC_SUCCESS;
  }
  
  const struct usb_mux_driver amd_fp5_usb_mux_driver = {
  	.init = &amd_fp5_init,
  	.set = &amd_fp5_set_mux,
  	.get = &amd_fp5_get_mux,
  	.chipset_reset = &amd_fp5_chipset_reset,
  };
  