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