// SPDX-License-Identifier: GPL-2.0+ /* Ten64 Board Microcontroller Driver * Copyright 2021 Traverse Technologies Australia * */ #include #include #include #include #include #include #include #include #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 };