--- /dev/null
+// SPDX-License-Identifier: GPL-2.0+
+
+/* Ten64 Board Microcontroller Driver
+ * Copyright 2021 Traverse Technologies Australia
+ *
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <misc.h>
+#include <i2c.h>
+#include <hexdump.h>
+#include <dm/device_compat.h>
+#include <inttypes.h>
+#include <linux/delay.h>
+
+#include "ten64-controller.h"
+
+/* Microcontroller command set and structure
+ * These should not be used outside this file
+ */
+
+#define T64_UC_DATA_MAX_SIZE 128U
+#define T64_UC_API_MSG_HEADER_SIZE 4U
+#define T64_UC_API_HEADER_PREAMB 0xcabe
+
+enum {
+ TEN64_UC_CMD_SET_BOARD_MAC = 0x10,
+ TEN64_UC_CMD_GET_BOARD_INFO = 0x11,
+ TEN64_UC_CMD_GET_STATE = 0x20,
+ TEN64_UC_CMD_SET_RESET_BTN_HOLD_TIME = 0x21,
+ TEN64_UC_CMD_ENABLE_RESET_BUTTON = 0x22,
+ TEN64_UC_CMD_SET_NEXT_BOOTSRC = 0x23,
+ TEN64_UC_CMD_ENABLE_10G = 0x24,
+ TEN64_UC_CMD_FWUP_GET_INFO = 0xA0,
+ TEN64_UC_CMD_FWUP_INIT = 0xA1,
+ TEN64_UC_CMD_FWUP_XFER = 0xA2,
+ TEN64_UC_CMD_FWUP_CHECK = 0xA3,
+ TEN64_UC_CMD_FWUPBOOT = 0x0A
+};
+
+/** struct t64uc_message - Wire Format for microcontroller messages
+ * @preamb: Message preamble (always 0xcabe)
+ * @cmd: Command to invoke
+ * @len: Length of data
+ * @data: Command data, up to 128 bytes
+ */
+struct t64uc_message {
+ u16 preamb;
+ u8 cmd;
+ u8 len;
+ u8 data[T64_UC_DATA_MAX_SIZE];
+} __packed;
+
+enum {
+ T64_CTRL_IO_SET = 1U,
+ T64_CTRL_IO_CLEAR = 2U,
+ T64_CTRL_IO_TOGGLE = 3U,
+ T64_CTRL_IO_RESET = 4U,
+ T64_CTRL_IO_UNKNOWN = 5U
+};
+
+/** struct t64uc_board_10g_enable - Wrapper for 10G enable command
+ * @control: state to set the 10G retimer - either
+ * T64_CTRL_IO_CLEAR (0x02) for off or
+ * T64_CTRL_IO_SET (0x01) for on.
+ *
+ * This struct exists to simplify the wrapping of the
+ * command value into a microcontroller message and passing into
+ * functions.
+ */
+struct t64uc_board_10g_enable {
+ u8 control;
+} __packed;
+
+/** ten64_controller_send_recv_command() - Wrapper function to
+ * send a command to the microcontroller.
+ * @uc_chip: the DM I2C chip handle for the microcontroller
+ * @uc_cmd: the microcontroller API command code
+ * @uc_cmd_data: pointer to the data struct for this command
+ * @uc_data_len: size of command data struct
+ * @return_data: place to store response from microcontroller, NULL if not expected
+ * @expected_return_len: expected size of microcontroller command response
+ * @return_message_wait: wait this long (in us) before reading the response
+ *
+ * Invoke a microcontroller command and receive a response.
+ * This function includes communicating with the microcontroller over
+ * I2C and encoding a message in the wire format.
+ *
+ * Return: 0 if successful, error code otherwise.
+ * Returns -EBADMSG if the microcontroller response could not be validated,
+ * other error codes may be passed from dm_i2c_xfer()
+ */
+static int ten64_controller_send_recv_command(struct udevice *ucdev, u8 uc_cmd,
+ void *uc_cmd_data, u8 cmd_data_len,
+ void *return_data, u8 expected_return_len,
+ u16 return_message_wait)
+{
+ int ret;
+ struct t64uc_message send, recv;
+ struct i2c_msg command_message, return_message;
+ struct dm_i2c_chip *chip = dev_get_parent_plat(ucdev);
+
+ dev_dbg(ucdev, "%s sending cmd %02X len %d\n", __func__, uc_cmd, cmd_data_len);
+
+ send.preamb = T64_UC_API_HEADER_PREAMB;
+ send.cmd = uc_cmd;
+ send.len = cmd_data_len;
+ if (uc_cmd_data && cmd_data_len > 0)
+ memcpy(send.data, uc_cmd_data, cmd_data_len);
+
+ command_message.addr = chip->chip_addr;
+ command_message.len = T64_UC_API_MSG_HEADER_SIZE + send.len;
+ command_message.buf = (void *)&send;
+ command_message.flags = I2C_M_STOP;
+
+ ret = dm_i2c_xfer(ucdev, &command_message, 1);
+ if (!return_data)
+ return ret;
+
+ udelay(return_message_wait);
+
+ return_message.addr = chip->chip_addr;
+ return_message.len = T64_UC_API_MSG_HEADER_SIZE + expected_return_len;
+ return_message.buf = (void *)&recv;
+ return_message.flags = I2C_M_RD;
+
+ ret = dm_i2c_xfer(ucdev, &return_message, 1);
+ if (ret)
+ return ret;
+
+ if (recv.preamb != T64_UC_API_HEADER_PREAMB) {
+ dev_err(ucdev, "%s: No preamble received in microcontroller response\n",
+ __func__);
+ return -EBADMSG;
+ }
+ if (recv.cmd != uc_cmd) {
+ dev_err(ucdev, "%s: command response mismatch, got %02X expecting %02X\n",
+ __func__, recv.cmd, uc_cmd);
+ return -EBADMSG;
+ }
+ if (recv.len != expected_return_len) {
+ dev_err(ucdev, "%s: received message has unexpected length, got %d expected %d\n",
+ __func__, recv.len, expected_return_len);
+ return -EBADMSG;
+ }
+ memcpy(return_data, recv.data, expected_return_len);
+ return ret;
+}
+
+/** ten64_controller_send_command() - Send command to microcontroller without
+ * expecting a response (for example, invoking a control command)
+ * @uc_chip: the DM I2C chip handle for the microcontroller
+ * @uc_cmd: the microcontroller API command code
+ * @uc_cmd_data: pointer to the data struct for this command
+ * @uc_data_len: size of command data struct
+ */
+static int ten64_controller_send_command(struct udevice *ucdev, u8 uc_cmd,
+ void *uc_cmd_data, u8 cmd_data_len)
+{
+ return ten64_controller_send_recv_command(ucdev, uc_cmd,
+ uc_cmd_data, cmd_data_len,
+ NULL, 0, 0);
+}
+
+/** ten64_controller_get_board_info() -Get board information from microcontroller
+ * @dev: The microcontroller device handle
+ * @out: Pointer to a t64uc_board_info struct that has been allocated by the caller
+ */
+static int ten64_controller_get_board_info(struct udevice *dev, struct t64uc_board_info *out)
+{
+ int ret;
+
+ ret = ten64_controller_send_recv_command(dev, TEN64_UC_CMD_GET_BOARD_INFO,
+ NULL, 0, out,
+ sizeof(struct t64uc_board_info),
+ 10000);
+ if (ret) {
+ dev_err(dev, "%s unable to send board info command: %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+/**
+ * ten64_controller_10g_enable_command() - Sends a 10G (Retimer) enable command
+ * to the microcontroller.
+ * @ucdev: The microcontroller udevice
+ * @value: The value flag for the 10G state
+ */
+static int ten64_controller_10g_enable_command(struct udevice *ucdev, u8 value)
+{
+ int ret;
+ struct t64uc_board_10g_enable enable_msg;
+
+ enable_msg.control = value;
+
+ ret = ten64_controller_send_command(ucdev, TEN64_UC_CMD_ENABLE_10G,
+ &enable_msg, sizeof(enable_msg));
+ if (ret) {
+ dev_err(ucdev, "ERROR sending uC 10G Enable message: %d\n", ret);
+ return -1;
+ }
+
+ return 0;
+}
+
+int ten64_controller_call(struct udevice *dev, int msgid, void *tx_msg, int tx_size,
+ void *rx_msg, int rx_size)
+{
+ switch (msgid) {
+ case TEN64_CNTRL_GET_BOARD_INFO:
+ return ten64_controller_get_board_info(dev, (struct t64uc_board_info *)rx_msg);
+ case TEN64_CNTRL_10G_OFF:
+ return ten64_controller_10g_enable_command(dev, T64_CTRL_IO_CLEAR);
+ case TEN64_CNTRL_10G_ON:
+ return ten64_controller_10g_enable_command(dev, T64_CTRL_IO_SET);
+ default:
+ dev_err(dev, "%s: Unknown operation %d\n", __func__, msgid);
+ }
+ return -EINVAL;
+}
+
+static struct misc_ops ten64_ctrl_ops = {
+ .call = ten64_controller_call
+};
+
+static const struct udevice_id ten64_controller_ids[] = {
+ {.compatible = "traverse,ten64-controller"},
+ {}
+};
+
+U_BOOT_DRIVER(ten64_controller) = {
+ .name = "ten64-controller-i2c",
+ .id = UCLASS_MISC,
+ .of_match = ten64_controller_ids,
+ .ops = &ten64_ctrl_ops
+};