| // 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 |
| }; |