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