| /* |
| * This file is part of the coreboot project. |
| * |
| * Copyright (C) 2014 Siemens AG |
| * |
| * 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. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| */ |
| |
| #include <device/pci.h> |
| #include <baytrail/baytrail.h> |
| #include <baytrail/pci_devs.h> |
| #include <baytrail/iosf.h> |
| #include <delay.h> |
| #include <baytrail/i2c.h> |
| |
| /* Wait for the transmit FIFO till there is at least one slot empty. |
| * FIFO stall due to transmit abort will be checked and resolved |
| */ |
| static int wait_tx_fifo(char *base_adr) { |
| int i; |
| |
| if (read32(base_adr + I2C_ABORT_SOURCE) & 0x1ffff) { |
| /* Reading back I2C_CLR_TX_ABRT resets abort lock on TX FIFO */ |
| i = *((volatile unsigned int *)(base_adr + I2C_CLR_TX_ABRT)); |
| return I2C_ERR_ABORT | |
| (*((unsigned int *)(base_adr + I2C_ABORT_SOURCE)) & 0x1ffff); |
| } |
| |
| /* Wait here for a free slot in TX-FIFO */ |
| i = I2C_TIMEOUT_US; |
| while ((!(*((volatile unsigned int *)(base_adr + I2C_STATUS)) & I2C_TFNF))) { |
| udelay(1); |
| if (!--i) |
| return I2C_ERR_TIMEOUT; |
| } |
| |
| return I2C_SUCCESS; |
| } |
| |
| /* Wait for the receive FIFO till there is at least one valid entry to read. |
| * FIFO stall due to transmit abort will be checked and resolved |
| */ |
| static int wait_rx_fifo(char *base_adr) { |
| int i; |
| if (read32(base_adr + I2C_ABORT_SOURCE) & 0x1ffff) { |
| /* Reading back I2C_CLR_TX_ABRT resets abort lock on TX FIFO */ |
| i = *((volatile unsigned int *)(base_adr + I2C_CLR_TX_ABRT)); |
| return I2C_ERR_ABORT | |
| (*((unsigned int *)(base_adr + I2C_ABORT_SOURCE)) & 0x1ffff); |
| } |
| |
| /* Wait here for a received entry in RX-FIFO */ |
| i = I2C_TIMEOUT_US; |
| while ((!(*((volatile unsigned int *)(base_adr + I2C_STATUS)) & I2C_RFNE))) { |
| udelay(1); |
| if (!--i) |
| return I2C_ERR_TIMEOUT; |
| } |
| |
| return I2C_SUCCESS; |
| } |
| |
| /* When there will be a fast switch between send and receive, one have |
| * to wait until the first operation is completely finished |
| * before starting the second operation |
| */ |
| static int wait_for_idle(char *base_adr) |
| { |
| int i; |
| volatile int status; |
| |
| /* For IDLE, increase timeout by ten times */ |
| i = I2C_TIMEOUT_US * 10; |
| status = *((volatile unsigned int *)(base_adr + I2C_STATUS)); |
| while (((status & I2C_MST_ACTIVITY) || (!(status & I2C_TFE)))) { |
| status = *((volatile unsigned int *)(base_adr + I2C_STATUS)); |
| udelay(1); |
| if (!--i) |
| return I2C_ERR_TIMEOUT; |
| } |
| |
| return I2C_SUCCESS; |
| |
| } |
| |
| /** \brief Enables I2C-controller, sets up BAR and timing parameters |
| * @param bus Number of the I2C-controller to use (0...6) |
| * @return I2C_SUCCESS on success, otherwise error code |
| */ |
| int i2c_init(unsigned bus) |
| { |
| device_t dev; |
| int base_adr[7] = {I2C0_MEM_BASE, I2C1_MEM_BASE, I2C2_MEM_BASE, |
| I2C3_MEM_BASE, I2C4_MEM_BASE, I2C5_MEM_BASE, |
| I2C6_MEM_BASE}; |
| char *base_ptr; |
| /* Ensure the desired device is valid */ |
| if (bus > ARRAY_SIZE(base_adr)) { |
| printk(BIOS_ERR, "I2C: Only I2C controllers 0...6 are available.\n"); |
| return I2C_ERR; |
| } |
| |
| base_ptr = (char*)base_adr[bus]; |
| /* Set the I2C-device the user wants to use */ |
| dev = dev_find_slot(0, PCI_DEVFN(I2C1_DEV, bus + 1)); |
| |
| /* Ensure we have the right PCI device */ |
| if ((pci_read_config16(dev, 0x0) != I2C_PCI_VENDOR_ID) || |
| (pci_read_config16(dev, 0x2) != (I2C0_PCI_DEV_ID + bus))) { |
| printk(BIOS_ERR, "I2C: Controller %d not found!\n", bus); |
| return I2C_ERR; |
| } |
| |
| /* Set memory base */ |
| pci_write_config32(dev, PCI_BASE_ADDRESS_0, (int)base_ptr); |
| |
| /* Enable memory space */ |
| pci_write_config32(dev, PCI_COMMAND, |
| (pci_read_config32(dev, PCI_COMMAND) | 0x2)); |
| |
| /* Set up some settings of I2C controller */ |
| *((unsigned int *)(base_ptr + I2C_CTRL)) = (I2C_RESTART_EN | |
| (I2C_STANDARD_MODE << 1) | |
| I2C_MASTER_ENABLE); |
| /* Adjust frequency for standard mode to 100 kHz */ |
| /* The counter value can be computed by N=100MHz/2/I2C_CLK */ |
| /* Thus, for 100 kHz I2C_CLK, N is 0x1F4 */ |
| *((unsigned int *)(base_ptr + I2C_SS_SCL_HCNT)) = 0x1f4; |
| *((unsigned int *)(base_ptr + I2C_SS_SCL_LCNT)) = 0x1f4; |
| /* For 400 kHz, the counter value is 0x7d */ |
| *((unsigned int *)(base_ptr + I2C_FS_SCL_HCNT)) = 0x7d; |
| *((unsigned int *)(base_ptr + I2C_FS_SCL_LCNT)) = 0x7d; |
| |
| /* Enable the I2C controller for operation */ |
| *((unsigned int *)(base_ptr + I2C_ENABLE)) = 0x1; |
| |
| printk(BIOS_INFO, "I2C: Controller %d enabled.\n", bus); |
| return I2C_SUCCESS; |
| } |
| |
| /** \brief Read bytes over I2C-Bus from a slave. This function tries only one |
| * time to transmit data. In case of an error (abort) error code is |
| * returned. Retransmission has to be done from caller! |
| * @param bus Number of the I2C-controller to use (0...6) |
| * @param chip 7 Bit of the slave address on I2C bus |
| * @param addr Address inside slave where to read from |
| * @param *buf Pointer to the buffer where to store read data |
| * @param len Number of bytes to read |
| * @return I2C_SUCCESS when read was successful, otherwise error code |
| */ |
| int i2c_read(unsigned bus, unsigned chip, unsigned addr, |
| uint8_t *buf, unsigned len) |
| { |
| int i = 0; |
| char *base_ptr = NULL; |
| device_t dev; |
| unsigned int val; |
| int stat; |
| |
| /* Get base address of desired I2C-controller */ |
| dev = dev_find_slot(0, PCI_DEVFN(I2C1_DEV, bus + 1)); |
| base_ptr = (char *)pci_read_config32(dev, PCI_BASE_ADDRESS_0); |
| if (base_ptr == NULL) { |
| printk(BIOS_INFO, "I2C: Invalid Base address\n"); |
| return I2C_ERR_INVALID_ADR; |
| } |
| |
| /* Ensure I2C controller is not active before setting slave address */ |
| stat = wait_for_idle(base_ptr); |
| if (stat != I2C_SUCCESS) |
| return stat; |
| /* Now we can program the desired slave address and start transfer */ |
| *((unsigned int *)(base_ptr + I2C_TARGET_ADR)) = (chip & 0xff); |
| /* Send address inside slave to read from */ |
| *((unsigned int *)(base_ptr + I2C_DATA_CMD)) = (addr & 0xff); |
| |
| /* For the next byte we need a repeated start condition */ |
| val = I2C_RW_CMD | I2C_RESTART; |
| /* Now we can read desired amount of data over I2C */ |
| for (i = 0; i < len; i++) { |
| /* A read is initiated by writing dummy data to the DATA-register */ |
| *((unsigned int *)(base_ptr + I2C_DATA_CMD)) = val; |
| stat = wait_rx_fifo(base_ptr); |
| if (stat) |
| return stat; |
| buf[i] = (*((unsigned int *)(base_ptr + I2C_DATA_CMD))) & 0xff; |
| val = I2C_RW_CMD; |
| if (i == (len - 2)) { |
| /* For the last byte we need a stop condition to be generated */ |
| val |= I2C_STOP; |
| } |
| } |
| return I2C_SUCCESS; |
| } |
| |
| /** \brief Write bytes over I2C-Bus from a slave. This function tries only one |
| * time to transmit data. In case of an error (abort) error code is |
| * returned. Retransmission has to be done from caller! |
| * @param bus Number of the I2C-controller to use (0...6) |
| * @param chip 7 Bit of the slave address on I2C bus |
| * @param addr Address inside slave where to write to |
| * @param *buf Pointer to the buffer where data to write is stored |
| * @param len Number of bytes to write |
| * @return I2C_SUCCESS when read was successful, otherwise error code |
| */ |
| int i2c_write(unsigned bus, unsigned chip, unsigned addr, |
| const uint8_t *buf, unsigned len) |
| { |
| int i; |
| char *base_ptr; |
| device_t dev; |
| unsigned int val; |
| int stat; |
| |
| /* Get base address of desired I2C-controller */ |
| dev = dev_find_slot(0, PCI_DEVFN(I2C1_DEV, bus + 1)); |
| base_ptr = (char *)pci_read_config32(dev, PCI_BASE_ADDRESS_0); |
| if (base_ptr == NULL) { |
| return I2C_ERR_INVALID_ADR; |
| } |
| |
| /* Ensure I2C controller is not active yet */ |
| stat = wait_for_idle(base_ptr); |
| if (stat) { |
| return stat; |
| } |
| /* Program slave address to use for this transfer */ |
| *((unsigned int *)(base_ptr + I2C_TARGET_ADR)) = (chip & 0xff); |
| |
| /* Send address inside slave to write data to */ |
| *((unsigned int *)(base_ptr + I2C_DATA_CMD)) = (addr & 0xff); |
| |
| for (i = 0; i < len; i++) { |
| val = (unsigned int)(buf[i] & 0xff); /* Take only 8 bits */ |
| if (i == (len - 1)) { |
| /* For the last byte we need a stop condition */ |
| val |= I2C_STOP; |
| } |
| stat = wait_tx_fifo(base_ptr); |
| if (stat) { |
| return stat; |
| } |
| *((unsigned int *)(base_ptr + I2C_DATA_CMD)) = val; |
| } |
| return I2C_SUCCESS; |
| } |