1
0
mirror of https://review.coreboot.org/flashrom.git synced 2025-04-27 07:02:34 +02:00
flashrom/mediatek_i2c_spi.c
Aarya Chaumal edcea80d68 spi: Add function to probe erase command opcode for all spi_master
Add a field, probe_opcode, to struct spi_master which points to a
function returning a bool by checking if a given command is supported by
the programmer in use. This is used for getting a whitelist of commands
supported by the programmer, as some programmers like ichspi don't
support all opcodes.

Most programmers use the default function, which just returns true.
ICHSPI and dummyflasher use their specialized function.

Change-Id: I6852ef92788221f471a859c879f8aff42558d36d
Signed-off-by: Aarya Chaumal <aarya.chaumal@gmail.com>
Reviewed-on: https://review.coreboot.org/c/flashrom/+/65183
Reviewed-by: Thomas Heijligen <src@posteo.de>
Reviewed-by: Anastasia Klimchuk <aklm@chromium.org>
Reviewed-by: Nico Huber <nico.h@gmx.de>
Reviewed-by: Felix Singer <felixsinger@posteo.net>
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
2022-07-11 12:15:13 +00:00

514 lines
13 KiB
C

/*
* This file is part of the flashrom project.
*
* Copyright (C) 2021 The Chromium OS Authors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/i2c-dev.h>
#include <linux/i2c.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include "flash.h"
#include "i2c_helper.h"
#include "programmer.h"
#include "spi.h"
#define ISP_PORT (0x92 >> 1)
#define DEBUG_PORT (0xb2 >> 1)
#define MTK_CMD_WRITE 0x10
#define MTK_CMD_READ 0x11
#define MTK_CMD_END 0x12
struct mediatek_data {
int fd;
unsigned long funcs;
};
// Returns null pointer on error.
static const struct mediatek_data *get_data_from_context(
const struct flashctx *flash)
{
if (!flash || !flash->mst || !flash->mst->spi.data) {
msg_pdbg("Unable to extract data from flash context\n");
return NULL;
}
return (const struct mediatek_data *)flash->mst->spi.data;
}
// Returns bytes read, negative value on error.
static int read_raw(const struct mediatek_data *port, uint16_t addr,
uint8_t *buf, size_t len)
{
int ret;
if (len < 1 || len > I2C_SMBUS_BLOCK_MAX) {
msg_pdbg("Invalid length for read command: %zu\n", len);
return SPI_INVALID_LENGTH;
}
ret = ioctl(port->fd, I2C_SLAVE, addr);
if (ret) {
msg_pdbg("Failed to set slave address 0x%02x (%d)\n", addr, ret);
return ret;
}
if (port->funcs & I2C_FUNC_I2C) {
// Use raw I2C read.
uint8_t command = MTK_CMD_READ;
if (write(port->fd, &command, 1) != 1) {
return SPI_GENERIC_ERROR;
}
if (read(port->fd, buf, len) != (int)len) {
return SPI_GENERIC_ERROR;
}
return len;
}
union i2c_smbus_data data = {
.block[0] = len,
};
struct i2c_smbus_ioctl_data args = {
.read_write = I2C_SMBUS_READ,
.command = MTK_CMD_READ,
.size = I2C_SMBUS_I2C_BLOCK_DATA,
.data = &data,
};
ret = ioctl(port->fd, I2C_SMBUS, &args);
if (ret) {
msg_pdbg("Failed to read SMBus I2C block data\n");
return ret;
}
memcpy(buf, args.data->block + 1, len);
return args.data->block[0];
}
// Returns non-zero value on error.
static int write_command(const struct mediatek_data *port, uint16_t addr,
uint8_t command, const uint8_t *buf, size_t len)
{
int ret;
if (len > I2C_SMBUS_BLOCK_MAX) {
msg_pdbg("Invalid length for write command: %zu\n", len);
return SPI_INVALID_LENGTH;
}
ret = ioctl(port->fd, I2C_SLAVE, addr);
if (ret) {
msg_pdbg("Failed to set slave address: 0x%02x\n", addr);
return ret;
}
if (port->funcs & I2C_FUNC_I2C) {
// Use raw I2C write.
uint8_t raw_buffer[I2C_SMBUS_BLOCK_MAX + 1];
raw_buffer[0] = command;
if (len > 0) {
memcpy(raw_buffer + 1, buf, len);
}
if (write(port->fd, raw_buffer, len + 1) != (int)(len + 1)) {
return SPI_GENERIC_ERROR;
}
return 0;
}
union i2c_smbus_data data = {0};
struct i2c_smbus_ioctl_data args = {
.read_write = I2C_SMBUS_WRITE,
.command = command,
.data = &data,
};
// Special case single byte commands, as empty I2C block data commands
// failed on this component in practice.
if (len == 0) {
args.size = I2C_SMBUS_BYTE;
ret = ioctl(port->fd, I2C_SMBUS, &args);
if (ret) {
msg_pdbg("Failed to write SMBus byte: 0x%02x\n", command);
}
return ret;
}
args.size = I2C_SMBUS_I2C_BLOCK_DATA;
data.block[0] = len;
if (buf && len) {
memcpy(data.block + 1, buf, len);
}
ret = ioctl(port->fd, I2C_SMBUS, &args);
if (ret) {
msg_pdbg("Failed to write SMBus I2C block data: 0x%02x\n", command);
}
return ret;
}
// Returns non-zero value on error.
static int write_raw(const struct mediatek_data *port, uint16_t addr,
const uint8_t *buf, size_t len)
{
if (len < 1) {
msg_pdbg("Invalid write length: %zu\n", len);
return SPI_INVALID_LENGTH;
}
return write_command(port, addr, buf[0], buf + 1, len - 1);
}
// Read from a GPIO register via debug port. Returns non-zero value on error.
static int mediatek_read_gpio(const struct mediatek_data *port,
uint16_t gpio_addr, uint8_t *value)
{
int ret;
uint8_t data[2];
data[0] = gpio_addr >> 8;
data[1] = gpio_addr & 0xff;
ret = write_command(port, DEBUG_PORT, MTK_CMD_WRITE, data, ARRAY_SIZE(data));
if (ret) {
msg_pdbg("Failed to issue read GPIO command at address 0x%04x\n", gpio_addr);
return ret;
}
size_t len = 1;
ret = read_raw(port, DEBUG_PORT, value, len);
if (ret < 0) {
msg_pdbg("Failed to read GPIO register at address 0x%04x\n", gpio_addr);
return ret;
}
if (ret != 1) {
msg_pdbg("GPIO read returned improper length: %d\n", ret);
return SPI_INVALID_LENGTH;
}
return 0;
}
// Write to a GPIO register via the debug port. Returns non-zero value on error.
static int mediatek_write_gpio(const struct mediatek_data *port,
uint16_t gpio_addr, uint8_t value)
{
uint8_t data[3];
data[0] = gpio_addr >> 8;
data[1] = gpio_addr & 0xff;
data[2] = value;
return write_command(port, DEBUG_PORT, MTK_CMD_WRITE, data, ARRAY_SIZE(data));
}
// Write a single bit value to a GPIO register via the debug port. Returns
// non-zero value on error.
static int mediatek_set_gpio_bit(const struct mediatek_data *port,
uint16_t gpio_addr, int bit, int value)
{
int ret;
uint8_t data;
ret = mediatek_read_gpio(port, gpio_addr, &data);
if (ret) {
msg_pdbg("Failed to read GPIO 0x%04x\n", gpio_addr);
return ret;
}
if (value) {
data |= (1 << bit);
} else {
data &= ~(1 << bit);
}
ret = mediatek_write_gpio(port, gpio_addr, data);
if (ret) {
msg_pdbg("Failed to set GPIO 0x%04x to 0x%02x\n", gpio_addr, data);
return ret;
}
return ret;
}
// Poke the GPIO line that goes to SPI WP# using the MST9U GPIO register
// addresses. Returns non-zero value on error.
static int mediatek_set_write_protect(const struct mediatek_data *port,
int protected)
{
int ret;
ret = mediatek_set_gpio_bit(port, 0x426, 7, (protected == 0));
if (ret) {
msg_perr("Failed to set GPIO out value: %d\n", protected == 0);
return ret;
}
ret = mediatek_set_gpio_bit(port, 0x428, 7, (protected != 0));
if (ret) {
msg_perr("Failed to set GPIO enable value: %d\n", protected != 0);
return ret;
}
return 0;
}
// Enter serial debug mode using "Option 1" for MST9U and select I2C channel 0
// to configure write protect GPIOs. Returns non-zero value on error.
static int mediatek_enter_serial_debug_mode(const struct mediatek_data *port)
{
int ret;
uint8_t enter_serial_debug[] = { 'S', 'E', 'R', 'D', 'B' };
ret = write_raw(port, DEBUG_PORT, enter_serial_debug,
ARRAY_SIZE(enter_serial_debug));
if (ret) {
msg_perr("Failed to send enter serial debug mode command\n");
return ret;
}
uint8_t enter_single_step_1[] = { 0xc0, 0xc1, 0x53 };
ret = write_command(port, DEBUG_PORT, MTK_CMD_WRITE,
enter_single_step_1, ARRAY_SIZE(enter_single_step_1));
if (ret) {
msg_perr("Failed to enter serial single step mode (part 1)\n");
return ret;
}
uint8_t enter_single_step_2[] = { 0x1f, 0xc1, 0x53 };
ret = write_command(port, DEBUG_PORT, MTK_CMD_WRITE, enter_single_step_2,
ARRAY_SIZE(enter_single_step_2));
if (ret) {
msg_perr("Failed to enter serial single step mode (part 2)\n");
return ret;
}
// Send each I2C channel 0 configuration byte individually.
uint8_t i2c_channel_0_config[] =
{ 0x80, 0x82, 0x84, 0x51, 0x7f, 0x37, 0x61 };
for (size_t i = 0; i < ARRAY_SIZE(i2c_channel_0_config); ++i) {
ret = write_command(
port, DEBUG_PORT, i2c_channel_0_config[i], NULL, 0);
if (ret) {
msg_perr("Failed to configure i2c channel 0: 0x%02x\n",
i2c_channel_0_config[i]);
return ret;
}
}
uint8_t enter_single_step_3[] = { 0x00, 0x00, 0x00 };
ret = write_command(port, DEBUG_PORT, MTK_CMD_WRITE, enter_single_step_3,
ARRAY_SIZE(enter_single_step_3));
if (ret) {
msg_perr("Failed to enter serial single step mode (part 3)\n");
return ret;
}
ret = write_command(port, DEBUG_PORT, 0x35, NULL, 0);
if (ret) {
msg_perr("Failed to send serial debug command (part 1)\n");
return ret;
}
ret = write_command(port, DEBUG_PORT, 0x71, NULL, 0);
if (ret) {
msg_perr("Failed to send serial debug command (part 2)\n");
return ret;
}
return 0;
}
// Returns non-zero value on error.
static int mediatek_enter_isp(const struct mediatek_data *port)
{
int ret;
ret = mediatek_enter_serial_debug_mode(port);
if (ret) {
msg_perr("Failed to enter serial debug mode\n");
return ret;
}
// MediaTek documentation says to do this twice just in case.
uint8_t enter_isp[] = { 'M', 'S', 'T', 'A', 'R' };
ret = write_raw(port, ISP_PORT, enter_isp, ARRAY_SIZE(enter_isp));
if (ret) {
msg_gwarn("Failed to enter ISP mode, trying again\n");
}
ret = write_raw(port, ISP_PORT, enter_isp, ARRAY_SIZE(enter_isp));
if (ret) {
msg_gwarn("Might already be in ISP mode, ignoring\n");
}
ret = mediatek_set_write_protect(port, 0);
if (ret) {
msg_perr("Failed to disable write protection\n");
return ret;
}
return 0;
}
// Returns non-zero value on error.
static int mediatek_exit_isp(const struct mediatek_data *port)
{
int ret;
ret = mediatek_set_write_protect(port, 1);
if (ret) {
msg_perr("Failed to re-enable write protect\n");
return ret;
}
uint8_t exit_single_step[] = { 0xc0, 0xc1, 0xff };
ret = write_command(port, DEBUG_PORT, MTK_CMD_WRITE, exit_single_step,
ARRAY_SIZE(exit_single_step));
if (ret) {
msg_perr("Failed to exit single step mode\n");
return ret;
}
ret = write_command(port, DEBUG_PORT, 0x34, NULL, 0);
if (ret) {
msg_perr("Failed to exit serial debug mode (1), ignoring\n");
}
ret = write_command(port, DEBUG_PORT, 0x45, NULL, 0);
if (ret) {
msg_perr("Failed to exit serial debug mode (2), ignoring\n");
}
ret = write_command(port, ISP_PORT, 0x24, NULL, 0);
if (ret) {
msg_perr("Failed to exit ISP mode command, ignoring\n");
}
return 0;
}
static int mediatek_send_command(const struct flashctx *flash,
unsigned int writecnt, unsigned int readcnt,
const uint8_t *writearr, uint8_t *readarr)
{
const struct mediatek_data *port = get_data_from_context(flash);
if (!port) {
msg_perr("Failed to extract chip data for ISP command\n");
return SPI_GENERIC_ERROR;
}
int ret;
if (writecnt) {
ret = write_command(port, ISP_PORT, MTK_CMD_WRITE,
writearr, writecnt);
if (ret) {
msg_perr("Failed to issue ISP write command\n");
return ret;
}
}
if (readcnt) {
size_t len = readcnt;
ret = read_raw(port, ISP_PORT, readarr, len);
if (ret < 0) {
msg_perr("Failed to read ISP command result\n");
return ret;
}
if (ret != (int)readcnt) {
msg_perr("Read length mismatched: expected %u got %d\n", readcnt, ret);
return SPI_INVALID_LENGTH;
}
}
// End the current command.
ret = write_command(port, ISP_PORT, MTK_CMD_END, NULL, 0);
if (ret) {
msg_perr("Failed to end ISP command\n");
return ret;
}
return 0;
}
static const struct spi_master spi_master_i2c_mediatek = {
.max_data_read = I2C_SMBUS_BLOCK_MAX,
// Leave room for 1-byte command and up to a 4-byte address.
.max_data_write = I2C_SMBUS_BLOCK_MAX - 5,
.command = mediatek_send_command,
.multicommand = default_spi_send_multicommand,
.read = default_spi_read,
.write_256 = default_spi_write_256,
.write_aai = default_spi_write_aai,
.probe_opcode = default_spi_probe_opcode,
};
static int mediatek_shutdown(void *data)
{
int ret = 0;
struct mediatek_data *port = (struct mediatek_data *)data;
ret |= mediatek_exit_isp(port);
i2c_close(port->fd);
free(data);
return ret;
}
static int mediatek_init(void)
{
int ret;
int fd = i2c_open_from_programmer_params(ISP_PORT, 0);
if (fd < 0) {
msg_perr("Failed to open i2c\n");
return fd;
}
struct mediatek_data *port = calloc(1, sizeof(*port));
if (!port) {
msg_perr("Unable to allocate space for SPI master data\n");
i2c_close(fd);
return SPI_GENERIC_ERROR;
}
port->fd = fd;
ret = ioctl(fd, I2C_FUNCS, &(port->funcs));
if (ret) {
msg_perr("Failed to fetch I2C bus functionality\n");
free(port);
i2c_close(fd);
return ret;
}
ret = mediatek_enter_isp(port);
if (ret) {
msg_perr("Failed to enter ISP mode\n");
free(port);
i2c_close(fd);
return ret;
}
ret |= register_shutdown(mediatek_shutdown, port);
ret |= register_spi_master(&spi_master_i2c_mediatek, port);
return ret;
}
const struct programmer_entry programmer_mediatek_i2c_spi = {
.name = "mediatek_i2c_spi",
.type = OTHER,
.devs.note = "Device files /dev/i2c-*\n",
.init = mediatek_init,
.map_flash_region = fallback_map,
.unmap_flash_region = fallback_unmap,
.delay = internal_delay,
};