aboutsummaryrefslogtreecommitdiff
path: root/src/soc/intel/fsp_baytrail/i2c.c
diff options
context:
space:
mode:
authorWerner Zeh <werner.zeh@siemens.com>2015-02-10 13:02:34 +0100
committerPatrick Georgi <pgeorgi@google.com>2015-03-05 14:19:34 +0100
commit0f9c9de35a84ce1d038e8f906a4b02b75f8e5869 (patch)
tree82e50067afb67ca08e4ca05f1c43751e51c2e417 /src/soc/intel/fsp_baytrail/i2c.c
parentb5a374d58befa96f718d0c2cee9afafb60867f18 (diff)
fsp_baytrail: Add I2C driver
Add a driver wich can handle the internal I2C controllers of Baytrail SoC. This driver is not suitable for the SMBus controller. Change-Id: I841c3991a2fb0f8b92b8e59ec02d62f5866f5bdf Signed-off-by: Werner Zeh <werner.zeh@siemens.com> Reviewed-on: http://review.coreboot.org/8401 Tested-by: build bot (Jenkins) Reviewed-by: Patrick Georgi <pgeorgi@google.com>
Diffstat (limited to 'src/soc/intel/fsp_baytrail/i2c.c')
-rw-r--r--src/soc/intel/fsp_baytrail/i2c.c257
1 files changed, 257 insertions, 0 deletions
diff --git a/src/soc/intel/fsp_baytrail/i2c.c b/src/soc/intel/fsp_baytrail/i2c.c
new file mode 100644
index 0000000000..1b13e0330b
--- /dev/null
+++ b/src/soc/intel/fsp_baytrail/i2c.c
@@ -0,0 +1,257 @@
+/*
+ * 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;
+}