board: traverse: add Ten64 board controller driver
Traverse Technologies Ten64 family boards use a microcontroller
to control low level board functions like startup and reset,
as well as holding details such as the board MAC address.
Communication between the CPU and microcontroller is via
I2C.
To keep the driver structure clean between the Ten64 board
file, DM_I2C, and a future utility command, this driver
has been implemented as a misc uclass device.
Signed-off-by: Mathew McBride <matt@traverse.com.au>
Reviewed-by: Priyanka Jain <priyanka.jain@nxp.com>
diff --git a/board/traverse/common/ten64_controller.c b/board/traverse/common/ten64_controller.c
new file mode 100644
index 0000000..d6ef8a8
--- /dev/null
+++ b/board/traverse/common/ten64_controller.c
@@ -0,0 +1,240 @@
+// 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
+};