/* * This file is part of the coreboot project. * * Copyright 2013 Google Inc. * * 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; version 2 of the License. * * 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 <device/mmio.h> #include <console/console.h> #include <delay.h> #include <device/i2c_simple.h> #include <stdlib.h> #include <string.h> #include <soc/addressmap.h> #include "i2c.h" static void do_bus_clear(int bus) { struct tegra_i2c_bus_info *info = &tegra_i2c_info[bus]; struct tegra_i2c_regs * const regs = info->base; uint32_t bc; int i, timeout_ms = 10; // BUS CLEAR regs (from TRM): // 1. Reset the I2C controller (already done) // 2. Set the # of clock pulses required (using default of 9) // 3. Select STOP condition (using default of 1 = STOP) // 4. Set TERMINATE condition (1 = IMMEDIATE) bc = read32(®s->bus_clear_config); bc |= I2C_BUS_CLEAR_CONFIG_BC_TERMINATE_IMMEDIATE; write32(®s->bus_clear_config, bc); // 4.1 Set MSTR_CONFIG_LOAD and wait for clear write32(®s->config_load, I2C_CONFIG_LOAD_MSTR_CONFIG_LOAD_ENABLE); for (i = 0; i < timeout_ms * 10 && (read32(®s->config_load) & I2C_CONFIG_LOAD_MSTR_CONFIG_LOAD_ENABLE); i++) { printk(BIOS_DEBUG, "%s: wait for MSTR_CONFIG_LOAD to clear\n", __func__); udelay(100); } // 5. Set ENABLE to start the bus clear op write32(®s->bus_clear_config, bc | I2C_BUS_CLEAR_CONFIG_BC_ENABLE); for (i = 0; i < timeout_ms * 10 && (read32(®s->bus_clear_config) & I2C_BUS_CLEAR_CONFIG_BC_ENABLE); i++) { printk(BIOS_DEBUG, "%s: wait for bus clear completion\n", __func__); udelay(100); } } static int tegra_i2c_send_recv(int bus, int read, uint32_t *headers, int header_words, uint8_t *data, int data_len) { struct tegra_i2c_bus_info *info = &tegra_i2c_info[bus]; struct tegra_i2c_regs * const regs = info->base; while (data_len) { uint32_t status = read32(®s->fifo_status); int tx_empty = status & I2C_FIFO_STATUS_TX_FIFO_EMPTY_CNT_MASK; tx_empty >>= I2C_FIFO_STATUS_TX_FIFO_EMPTY_CNT_SHIFT; int rx_full = status & I2C_FIFO_STATUS_RX_FIFO_FULL_CNT_MASK; rx_full >>= I2C_FIFO_STATUS_RX_FIFO_FULL_CNT_SHIFT; while (header_words && tx_empty) { write32(®s->tx_packet_fifo, *headers++); header_words--; tx_empty--; } if (!header_words) { if (read) { while (data_len && rx_full) { uint32_t word = read32(®s->rx_fifo); int todo = MIN(data_len, sizeof(word)); memcpy(data, &word, todo); data_len -= todo; data += sizeof(word); rx_full--; } } else { while (data_len && tx_empty) { uint32_t word; int todo = MIN(data_len, sizeof(word)); memcpy(&word, data, todo); write32(®s->tx_packet_fifo, word); data_len -= todo; data += sizeof(word); tx_empty--; } } } uint32_t transfer_status = read32(®s->packet_transfer_status); if (transfer_status & I2C_PKT_STATUS_NOACK_ADDR) { printk(BIOS_ERR, "%s: The address was not acknowledged.\n", __func__); info->reset_func(info->reset_bit); i2c_init(bus); return -1; } else if (transfer_status & I2C_PKT_STATUS_NOACK_DATA) { printk(BIOS_ERR, "%s: The data was not acknowledged.\n", __func__); info->reset_func(info->reset_bit); i2c_init(bus); return -1; } else if (transfer_status & I2C_PKT_STATUS_ARB_LOST) { printk(BIOS_ERR, "%s: Lost arbitration.\n", __func__); info->reset_func(info->reset_bit); /* Use Tegra bus clear registers to unlock SDA */ do_bus_clear(bus); /* re-init i2c controller */ i2c_init(bus); /* Return w/error, let caller decide what to do */ return -1; } } return 0; } static int tegra_i2c_request(int bus, unsigned chip, int cont, int restart, int read, void *data, int data_len) { uint32_t headers[3]; if (restart && cont) { printk(BIOS_ERR, "%s: Repeat start and continue xfer are " "mutually exclusive.\n", __func__); return -1; } headers[0] = (0 << IOHEADER_PROTHDRSZ_SHIFT) | (1 << IOHEADER_PKTID_SHIFT) | (bus << IOHEADER_CONTROLLER_ID_SHIFT) | IOHEADER_PROTOCOL_I2C | IOHEADER_PKTTYPE_REQUEST; headers[1] = (data_len - 1) << IOHEADER_PAYLOADSIZE_SHIFT; uint32_t slave_addr = (chip << 1) | (read ? 1 : 0); headers[2] = IOHEADER_I2C_REQ_ADDR_MODE_7BIT | (slave_addr << IOHEADER_I2C_REQ_SLAVE_ADDR_SHIFT); if (read) headers[2] |= IOHEADER_I2C_REQ_READ; if (restart) headers[2] |= IOHEADER_I2C_REQ_REPEAT_START; if (cont) headers[2] |= IOHEADER_I2C_REQ_CONTINUE_XFER; return tegra_i2c_send_recv(bus, read, headers, ARRAY_SIZE(headers), data, data_len); } static int i2c_transfer_segment(unsigned bus, unsigned chip, int restart, int read, void *buf, int len) { const uint32_t max_payload = (IOHEADER_PAYLOADSIZE_MASK + 1) >> IOHEADER_PAYLOADSIZE_SHIFT; while (len) { int todo = MIN(len, max_payload); int cont = (todo < len); if (tegra_i2c_request(bus, chip, cont, restart, read, buf, todo)) return -1; len -= todo; buf += todo; } return 0; } int platform_i2c_transfer(unsigned bus, struct i2c_msg *segments, int count) { struct i2c_msg *seg = segments; int i; if (bus >= g_num_i2c_buses) { printk(BIOS_ERR, "%s: ERROR: invalid I2C bus (%u)\n", __func__, bus); return -1; } for (i = 0; i < count; seg++, i++) { if (i2c_transfer_segment(bus, seg->slave, i < count - 1, seg->flags & I2C_M_RD, seg->buf, seg->len)) return -1; } return 0; } void i2c_init(unsigned bus) { struct tegra_i2c_regs *regs; if (bus >= g_num_i2c_buses) { printk(BIOS_ERR, "%s: ERROR: invalid I2C bus (%u)\n", __func__, bus); return; } regs = tegra_i2c_info[bus].base; write32(®s->cnfg, I2C_CNFG_PACKET_MODE_EN); }