blob: 00f3cf381b58dda92a6ec91b1b0e61bb6be9cfff [file] [log] [blame]
// Copyright 2023 The Fuchsia Authors.
//
// SPDX-License-Identifier: BSD-2-Clause
#include <fusb302-sink.h>
#include <common.h>
#include <i2c.h>
// The comments in this file reference the datasheet from onsemi titled
// "FUSB302B Programmable USB Type‐C Controller w/PD", order number FUSB302B/D,
// downloadable at https://www.onsemi.com/pdf/datasheet/fusb302b-d.pdf
//
// The section and page numbers reference Revision 5, published August 2021.
struct fusb302_device_info {
// Populated by fusb302_initialize_i2c(), used by other I/O code.
uint8_t i2c_address;
};
// Global that coordinates I/O.
static struct fusb302_device_info g_info = { .i2c_address = 0 };
// The result of a `fusb302_read_register()` call.
struct read_result {
int error; // Zero if no error occurred
uint8_t value; // Only meaningful if `error` is zero.
};
// Reads a single byte from an I2C-addressable register.
//
// This is the intended access method for most registers. The only exception is
// the FIFO register, which accepts burst reads / writes.
//
// The caller may assume that a descriptive message has been logged if an error
// is reported.
//
// Must only be called after `fusb302_initialize_i2c()` succeeds.
static struct read_result fusb302_read_register(uint8_t register_index)
{
struct read_result return_value = { .error = 0, .value = 0 };
return_value.error = i2c_read(g_info.i2c_address, register_index,
/*address_length=*/1, &return_value.value,
sizeof(return_value.value));
if (return_value.error != 0) {
error("fusb302-sink: I2C error reading from register 0x%02x - %d",
(int)register_index, return_value.error);
}
return return_value;
}
// Writes a single byte to an I2C-addressable register.
//
// Returns 0 for success, an error code otherwise. The caller may assume that a
// descriptive message has been logged if an error is returned
//
// This is the intended access method for most registers. The only exception is
// the FIFO register, which accepts burst reads / writes.
//
// Must only be called after `fusb302_initialize_i2c()` succeeds.
static int fusb302_write_register(uint8_t register_index, uint8_t value)
{
int write_result = i2c_write(g_info.i2c_address, register_index,
/*address_length=*/1, &value,
sizeof(value));
if (write_result != 0) {
error("fusb302-sink: I2C error writing 0x%02x to register 0x%02x - %d",
(int)value, (int)register_index, write_result);
}
return write_result;
}
// Returns 0 for success, an error code otherwise.
//
// The caller may assume that a descriptive message has been logged if an error
// is returned.
static int fusb302_initialize_i2c(void)
{
static const int i2c_speed = 0;
// The board does not act as an I2C device.
static const int board_i2c_device_address = 0;
i2c_init(i2c_speed, board_i2c_device_address);
// From Table 15 in the datasheet.
//
// We currently hard-code the I2C address for the FUSB302BMPX chip in the VIM3
// board. Some FUSB302B models use the addresses 0x23 / 0x24 / 0x25. We could
// iterate over all the I2C addresses until we get a response.
static const uint8_t fusb302_i2c_device_address = 0x22;
int probe_result = i2c_probe(fusb302_i2c_device_address);
if (probe_result != 0) {
error("fusb302-sink: I2C probe failed - %d", probe_result);
return probe_result;
}
g_info.i2c_address = fusb302_i2c_device_address;
return 0;
}
// Returns 0 for success, an error code otherwise.
//
// The caller may assume that a descriptive message has been logged if an error
// is returned.
static int fusb302_log_device_id(void)
{
// From Table 17 in the datasheet.
static const uint8_t device_id_address = 0x01;
struct read_result device_id_read_result =
fusb302_read_register(device_id_address);
if (device_id_read_result.error != 0) {
// `fusb302_read_register()` already logged an error.
return device_id_read_result.error;
}
printf("fusb302-sink: device ID 0x%02x\n", device_id_read_result.value);
return 0;
}
struct fusb302_register_assignment {
uint8_t reg_index;
uint8_t mask;
uint8_t expected_value;
};
// The values of all control registers after a software reset.
//
// From Table 16 in the datasheet.
static struct fusb302_register_assignment g_reset_state[] = {
// The reset configuration has CC1 and CC2 connected to 5.1 kOhm pull-down
// resistors (Rd), which means we're advertising as a Sink.
//
// Until the OS driver takes over, we'll appear to be a Sink that doesn't
// implement the USB PD protocol, which allows us to consume up to 15 W (5V @
// 3A) of power. The OS driver can attempt to negotiate a PD Contract that
// gives us more power.
{ .reg_index = 0x02, .mask = 0xff, .expected_value = 0x03 }, // Switches0
{ .reg_index = 0x03, .mask = 0xf7, .expected_value = 0x20 }, // Switches1
{ .reg_index = 0x04, .mask = 0x7f, .expected_value = 0x31 }, // Measure
{ .reg_index = 0x05, .mask = 0xff, .expected_value = 0x60 }, // Slice
{ .reg_index = 0x06, .mask = 0x6f, .expected_value = 0x24 }, // Control0
{ .reg_index = 0x07, .mask = 0x77, .expected_value = 0x00 }, // Control1
{ .reg_index = 0x08, .mask = 0xef, .expected_value = 0x02 }, // Control2
{ .reg_index = 0x09, .mask = 0x7f, .expected_value = 0x06 }, // Control3
{ .reg_index = 0x0a, .mask = 0xff, .expected_value = 0x00 }, // Mask1
{ .reg_index = 0x0b, .mask = 0x0f, .expected_value = 0x01 }, // Power
{ .reg_index = 0x0d, .mask = 0x0f, .expected_value = 0x0f }, // OCPreg
{ .reg_index = 0x0e, .mask = 0xff, .expected_value = 0x00 }, // Maska
{ .reg_index = 0x0f, .mask = 0x01, .expected_value = 0x00 }, // Maskb
{ .reg_index = 0x10, .mask = 0x01, .expected_value = 0x00 }, // Control4
};
// Checks the control registers against the documented reset values.
// Returns 0 for success, an error code otherwise. The caller may assume that a
// descriptive message has been logged if an error is returned.
static int fusb302_initialize_registers(void)
{
const int assignment_count =
sizeof(g_reset_state) / sizeof(g_reset_state[0]);
for (int i = 0; i < assignment_count; ++i) {
const uint8_t register_index = g_reset_state[i].reg_index;
const uint8_t defined_bits_mask = g_reset_state[i].mask;
const uint8_t expected_value = g_reset_state[i].expected_value;
const struct read_result read_result =
fusb302_read_register(register_index);
if (read_result.error != 0) {
// `fusb302_read_register()` already logged an error.
return read_result.error;
}
const uint8_t normalized_value = read_result.value &
defined_bits_mask;
if (normalized_value == expected_value) {
continue;
}
debug("fusb302-sink: control register 0x%02x has normalized value 0x%02x, "
"expected 0x%02x\n",
(int)register_index, (int)normalized_value,
(int)expected_value);
// Leave the undefined bits as they were, set the defined bits to their
// documented reset values.
const uint8_t reset_value =
(read_result.value & ~defined_bits_mask) |
expected_value;
const int write_result =
fusb302_write_register(register_index, reset_value);
if (write_result != 0) {
// `fusb302_write_register()` already logged an error.
return write_result;
}
}
return 0;
}
int fusb302_sink_init(void)
{
const int i2c_init_result = fusb302_initialize_i2c();
if (i2c_init_result != 0) {
return i2c_init_result;
}
const int device_id_init_result = fusb302_log_device_id();
if (device_id_init_result != 0) {
return device_id_init_result;
}
const int register_init_result = fusb302_initialize_registers();
if (register_init_result != 0) {
return register_init_result;
}
printf("fusb302-sink: initialized\n");
return 0;
}