/* SPDX-License-Identifier: GPL-2.0-only */ #include <assert.h> #include <timer.h> #include <console/console.h> #include <device/i2c_simple.h> /* * The implementation is based on Wikipedia. */ #define DEBUG 0 /* Set to 1 for per-byte output */ #define SPEW 0 /* Set to 1 for verbose bitwise/line-state output */ #define DELAY_US 5 /* Default setup delay: 4us (+1 for timer inaccuracy) */ #define TIMEOUT_US 50000 /* Maximum clock stretching time we want to allow */ #define spew(...) do { if (SPEW) printk(BIOS_SPEW, ##__VA_ARGS__); } while (0) #define ERR_NACK -2 #define ERR_TIMEOUT -3 #define ERR_ARB -4 #define ERR_WEDGE -5 struct software_i2c_ops *software_i2c[SOFTWARE_I2C_MAX_BUS]; /* * Waits until either timeout_us have passed or (iff for_scl is set) until SCL * goes high. Will report random line changes during the wait and return SCL. */ static int __wait(unsigned int bus, int timeout_us, int for_scl) { int us; int sda = software_i2c[bus]->get_sda(bus); int scl = software_i2c[bus]->get_scl(bus); struct stopwatch sw; stopwatch_init_usecs_expire(&sw, timeout_us); do { int old_sda = sda; int old_scl = scl; us = stopwatch_duration_usecs(&sw); if (old_sda != (sda = software_i2c[bus]->get_sda(bus))) spew("[SDA transitioned to %d after %dus] ", sda, us); if (old_scl != (scl = software_i2c[bus]->get_scl(bus))) spew("[SCL transitioned to %d after %dus] ", scl, us); } while (!stopwatch_expired(&sw) && (!for_scl || !scl)); return scl; } /* Waits the default DELAY_US to allow line state to stabilize. */ static void wait(unsigned int bus) { __wait(bus, DELAY_US, 0); } /* Waits until SCL goes high. Prints a contextual error message on timeout. */ static int wait_for_scl(unsigned int bus, const char *error_context) { if (!__wait(bus, TIMEOUT_US, 1)) { printk(BIOS_ERR, "software_i2c(%d): ERROR: Clock stretching " "timeout %s!\n", bus, error_context); return ERR_TIMEOUT; } return 0; } static int start_cond(unsigned int bus) { spew("software_i2c(%d): Sending start condition... ", bus); /* SDA might not yet be high if repeated start. */ software_i2c[bus]->set_sda(bus, 1); wait(bus); /* Might need to wait for clock stretching if repeated start. */ software_i2c[bus]->set_scl(bus, 1); if (wait_for_scl(bus, "before start condition")) return ERR_TIMEOUT; wait(bus); /* Repeated start setup time, minimum 4.7us */ if (!software_i2c[bus]->get_sda(bus)) { printk(BIOS_ERR, "software_i2c(%d): Arbitration lost trying " "to send start condition!\n", bus); return ERR_ARB; } /* SCL is high, transition SDA low as first part of start condition. */ software_i2c[bus]->set_sda(bus, 0); wait(bus); assert(software_i2c[bus]->get_scl(bus)); /* Pull SCL low to finish start condition (next pulse will be data). */ software_i2c[bus]->set_scl(bus, 0); spew("Start condition transmitted!\n"); return 0; } static int stop_cond(unsigned int bus) { spew("software_i2c(%d): Sending stop condition... ", bus); /* SDA is unknown, set it to low. SCL must be low. */ software_i2c[bus]->set_sda(bus, 0); wait(bus); /* Clock stretching */ assert(!software_i2c[bus]->get_scl(bus)); software_i2c[bus]->set_scl(bus, 1); if (wait_for_scl(bus, "before stop condition")) return ERR_TIMEOUT; wait(bus); /* Stop bit setup time, minimum 4us */ /* SCL is high, transition SDA high to signal stop condition. */ software_i2c[bus]->set_sda(bus, 1); wait(bus); if (!software_i2c[bus]->get_sda(bus)) { printk(BIOS_WARNING, "software_i2c(%d): WARNING: SDA low after " "stop condition... access by another master or line " "stuck from faulty slave?\n", bus); /* Could theoretically happen with multi-master, so no -1. */ } spew("Stop condition transmitted\n"); return 0; } static int out_bit(unsigned int bus, int bit) { spew("software_i2c(%d): Sending a %d bit... ", bus, bit); software_i2c[bus]->set_sda(bus, bit); wait(bus); if (bit && !software_i2c[bus]->get_sda(bus)) { printk(BIOS_ERR, "software_i2c(%d): ERROR: SDA wedged low " "by slave before clock pulse on transmit!\n", bus); return ERR_WEDGE; } /* Clock stretching */ assert(!software_i2c[bus]->get_scl(bus)); software_i2c[bus]->set_scl(bus, 1); if (wait_for_scl(bus, "on transmit")) return ERR_TIMEOUT; wait(bus); if (bit && !software_i2c[bus]->get_sda(bus)) { printk(BIOS_ERR, "software_i2c(%d): ERROR: SDA wedged low " "by slave after clock pulse on transmit!\n", bus); return ERR_WEDGE; } assert(software_i2c[bus]->get_scl(bus)); software_i2c[bus]->set_scl(bus, 0); spew("%d bit sent!\n", bit); return 0; } static int in_bit(unsigned int bus) { int bit; spew("software_i2c(%d): Receiving a bit... ", bus); /* Let the slave drive data */ software_i2c[bus]->set_sda(bus, 1); wait(bus); /* Clock stretching */ assert(!software_i2c[bus]->get_scl(bus)); software_i2c[bus]->set_scl(bus, 1); if (wait_for_scl(bus, "on receive")) return ERR_TIMEOUT; /* SCL is high, now data is valid */ bit = software_i2c[bus]->get_sda(bus); wait(bus); assert(software_i2c[bus]->get_scl(bus)); software_i2c[bus]->set_scl(bus, 0); spew("Received a %d!\n", bit); return bit; } /* Write a byte to I2C bus. Return 0 if ack by the slave. */ static int out_byte(unsigned int bus, u8 byte) { unsigned int bit; int nack, ret; for (bit = 0; bit < 8; bit++) if ((ret = out_bit(bus, (byte >> (7 - bit)) & 0x1)) < 0) return ret; nack = in_bit(bus); if (DEBUG && nack >= 0) printk(BIOS_DEBUG, "software_i2c(%d): wrote byte 0x%02x, " "received %s\n", bus, byte, nack ? "NAK" : "ACK"); return nack > 0 ? ERR_NACK : nack; } static int in_byte(unsigned int bus, int ack) { u8 byte = 0; int i, ret; for (i = 0; i < 8; ++i) { int bit = in_bit(bus); if (bit < 0) return bit; byte = (byte << 1) | bit; } if ((ret = out_bit(bus, !ack)) < 0) return ret; if (DEBUG) printk(BIOS_DEBUG, "software_i2c(%d): read byte 0x%02x, " "sent %s\n", bus, byte, ack ? "ACK" : "NAK"); return byte; } int software_i2c_transfer(unsigned int bus, struct i2c_msg *segments, int count) { int i, ret; struct i2c_msg *seg; for (seg = segments; seg - segments < count; seg++) { if ((ret = start_cond(bus)) < 0) return ret; const u8 addr_dir = seg->slave << 1 | !!(seg->flags & I2C_M_RD); if ((ret = out_byte(bus, addr_dir)) < 0) return ret; for (i = 0; i < seg->len; i++) { if (seg->flags & I2C_M_RD) { ret = in_byte(bus, i < seg->len - 1); seg->buf[i] = (u8)ret; } else { ret = out_byte(bus, seg->buf[i]); } if (ret < 0) return ret; } } if ((ret = stop_cond(bus)) < 0) return ret; return 0; } void software_i2c_wedge_ack(unsigned int bus, u8 chip) { int i; /* Start a command to 'chip'... */ start_cond(bus); /* Send the address bits but don't yet read the ACK. */ chip <<= 1; for (i = 0; i < 8; ++i) out_bit(bus, (chip >> (7 - i)) & 0x1); /* Let the slave drive it's ACK but keep the clock high forever. */ software_i2c[bus]->set_sda(bus, 1); wait(bus); software_i2c[bus]->set_scl(bus, 1); wait_for_scl(bus, "on wedge_ack()"); printk(BIOS_INFO, "software_i2c(%d): wedged address write on slave " "ACK. SDA %d, SCL %d\n", bus, software_i2c[bus]->get_sda(bus), software_i2c[bus]->get_scl(bus)); } void software_i2c_wedge_read(unsigned int bus, u8 chip, u8 reg, int bits) { int i; /* Start a command to 'chip'... */ start_cond(bus); out_byte(bus, chip << 1); /* ...for register 'reg'. */ out_byte(bus, reg); /* Start a read command... */ start_cond(bus); out_byte(bus, chip << 1 | 1); /* Read bit_count bits and stop */ for (i = 0; i < bits; ++i) in_bit(bus); /* Let the slave drive SDA but keep the clock high forever. */ software_i2c[bus]->set_sda(bus, 1); wait(bus); software_i2c[bus]->set_scl(bus, 1); wait_for_scl(bus, "on wedge_read()"); printk(BIOS_INFO, "software_i2c(%d): wedged data read after %d bits. " "SDA %d, SCL %d\n", bus, bits, software_i2c[bus]->get_sda(bus), software_i2c[bus]->get_scl(bus)); } void software_i2c_wedge_write(unsigned int bus, u8 chip, u8 reg, int bits) { int i; /* Start a command to 'chip'... */ start_cond(bus); out_byte(bus, chip << 1); /* Write bit_count register bits and stop */ for (i = 0; i < bits; ++i) out_bit(bus, (reg >> (7 - i)) & 0x1); /* Pretend to write another 1 bit but keep the clock high forever. */ software_i2c[bus]->set_sda(bus, 1); wait(bus); software_i2c[bus]->set_scl(bus, 1); wait_for_scl(bus, "on wedge_write()"); printk(BIOS_INFO, "software_i2c(%d): wedged data write after %d bits. " "SDA %d, SCL %d\n", bus, bits, software_i2c[bus]->get_sda(bus), software_i2c[bus]->get_scl(bus)); }