diff options
author | Uwe Poeche <uwe.poeche@siemens.com> | 2019-09-24 09:44:58 +0200 |
---|---|---|
committer | Patrick Georgi <pgeorgi@google.com> | 2019-10-24 07:50:02 +0000 |
commit | ed309e58b0d44f7757f7144a995ca95a94e8a52a (patch) | |
tree | b49fc915988eb5bc310bbd956cd5d7d1ece5c024 /src/soc/intel/fsp_baytrail/i2c.c | |
parent | 285975dbba8c7f3bbb9f9950e79a30bb983d5123 (diff) |
soc/intel/fsp_baytrail: use designware I2C driver
Refactor I2C driver for fsp_baytrail to match the coreboot supported I2C
bus device structure. The internal I2C controllers are now handled by
the generic PCI driver approach and generic I2C access is enabled.
As orientation for the I2C code the actual solution from
soc/intel/apollolake I2C was taken. All the I2C specific parts were
removed from lpss.c and have been implemented in the I2C driver.
Future merge to soc/intel/common/block/i2c/i2c.c would be possible.
With this patch I2C chip devices can now be used in devicetree.
TEST=Booted siemens/tcu3 and verified that access to PTN3460 worked.
Change-Id: I3b87bd7c27e4c1afcce7cd4225cca02599f43c60
Signed-off-by: Uwe Poeche <uwe.poeche@siemens.com>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/36062
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Werner Zeh <werner.zeh@siemens.com>
Diffstat (limited to 'src/soc/intel/fsp_baytrail/i2c.c')
-rw-r--r-- | src/soc/intel/fsp_baytrail/i2c.c | 456 |
1 files changed, 240 insertions, 216 deletions
diff --git a/src/soc/intel/fsp_baytrail/i2c.c b/src/soc/intel/fsp_baytrail/i2c.c index 5f6ca467ea..37ce2d0b21 100644 --- a/src/soc/intel/fsp_baytrail/i2c.c +++ b/src/soc/intel/fsp_baytrail/i2c.c @@ -1,7 +1,7 @@ /* * This file is part of the coreboot project. * - * Copyright (C) 2014 Siemens AG + * Copyright (C) 2014-2019 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 @@ -13,258 +13,282 @@ * GNU General Public License for more details. */ -#include <device/pci.h> -#include <device/pci_ops.h> +#include <cbmem.h> #include <console/console.h> -#include <soc/baytrail.h> -#include <soc/pci_devs.h> -#include <soc/iosf.h> -#include <device/mmio.h> -#include <delay.h> +#include <device/device.h> +#include <device/pci_def.h> +#include <device/pci.h> +#include <device/pci_ids.h> +#include <drivers/i2c/designware/dw_i2c.h> #include <soc/i2c.h> +#include <soc/iosf.h> +#include <soc/iomap.h> +#include <soc/nvs.h> +#include <soc/pci_devs.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) +#include "chip.h" + +/* Convert I2C bus number to PCI device and function */ +int dw_i2c_soc_bus_to_devfn(unsigned int bus) { - int i; - u32 as; - - as = read32(base_adr + I2C_ABORT_SOURCE) & 0x1ffff; - if (as) { - /* Reading back I2C_CLR_TX_ABRT resets abort lock on TX FIFO */ - i = read32(base_adr + I2C_CLR_TX_ABRT); - return I2C_ERR_ABORT | as; - } + if (bus <= 6) + return PCI_DEVFN(SIO1_DEV, bus + 1); + else + return -1; +} + +/* Convert PCI device and function to I2C bus number */ +int dw_i2c_soc_dev_to_bus(struct device *dev) +{ + pci_devfn_t devfn = dev->path.pci.devfn; + if ((devfn >= SOC_DEVFN_I2C1) && (devfn <= SOC_DEVFN_I2C7)) + return PCI_FUNC(devfn) - 1; + else + return -1; +} + +/* Getting I2C bus configuration from devicetree config */ +const struct dw_i2c_bus_config *dw_i2c_get_soc_cfg(unsigned int bus) +{ + const struct soc_intel_fsp_baytrail_config *config; + const struct device *dev = pcidev_path_on_root(SOC_DEVFN_SOC); - /* Wait here for a free slot in TX-FIFO */ - i = I2C_TIMEOUT_US; - while (!(read32(base_adr + I2C_STATUS) & I2C_TFNF)) { - udelay(1); - if (!--i) - return I2C_ERR_TIMEOUT; + if (dev && dev->chip_info) { + config = dev->chip_info; + return &config->i2c[bus]; } - return I2C_SUCCESS; + die("Could not find SA_DEV_ROOT devicetree config!\n"); } -/* 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) +#if !ENV_RAMSTAGE +static int lpss_i2c_early_init_bus(unsigned int bus) { - int i; - u32 as; - - as = read32(base_adr + I2C_ABORT_SOURCE) & 0x1ffff; - if (as) { - /* Reading back I2C_CLR_TX_ABRT resets abort lock on TX FIFO */ - i = read32(base_adr + I2C_CLR_TX_ABRT); - return I2C_ERR_ABORT | as; + const struct dw_i2c_bus_config *config; + const struct device *tree_dev; + pci_devfn_t dev; + int devfn; + uintptr_t base; + + /* Find the PCI device for this bus controller */ + devfn = dw_i2c_soc_bus_to_devfn(bus); + if (devfn < 0) { + printk(BIOS_ERR, "I2C%u device not found\n", bus); + return -1; + } + + /* Look up the controller device in the devicetree */ + dev = PCI_DEV(0, PCI_SLOT(devfn), PCI_FUNC(devfn)); + tree_dev = pcidev_path_on_root(devfn); + if (!tree_dev || !tree_dev->enabled) { + printk(BIOS_ERR, "I2C%u device not enabled\n", bus); + return -1; } - /* Wait here for a received entry in RX-FIFO */ - i = I2C_TIMEOUT_US; - while (!(read32(base_adr + I2C_STATUS) & I2C_RFNE)) { - udelay(1); - if (!--i) - return I2C_ERR_TIMEOUT; + /* Skip if not enabled for early init */ + config = dw_i2c_get_soc_cfg(bus); + if (!config || !config->early_init) { + printk(BIOS_DEBUG, "I2C%u not enabled for early init\n", bus); + return -1; + } + + /* Prepare early base address for access before memory */ + base = EARLY_I2C_BASE(bus); + pci_write_config32(dev, PCI_BASE_ADDRESS_0, base); + pci_write_config32(dev, PCI_COMMAND, + PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER); + + /* Take device out of reset */ + write32((void *)((uint32_t)base + I2C_SOFTWARE_RESET), I2C_RESET_APB | I2C_RESET_FUNC); + + /* Initialize the controller */ + if (dw_i2c_init(bus, config) < 0) { + printk(BIOS_ERR, "I2C%u failed to initialize\n", bus); + return -1; } - return I2C_SUCCESS; + return 0; } -/* 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) +uintptr_t dw_i2c_base_address(unsigned int bus) { - int i; - int status; - - /* For IDLE, increase timeout by ten times */ - i = I2C_TIMEOUT_US * 10; - status = read32(base_adr + I2C_STATUS); - while (((status & I2C_MST_ACTIVITY) || (!(status & I2C_TFE)))) { - status = read32(base_adr + I2C_STATUS); - udelay(1); - if (!--i) - return I2C_ERR_TIMEOUT; - } + int devfn; + pci_devfn_t dev; + uintptr_t base; + + /* Find device+function for this controller */ + devfn = dw_i2c_soc_bus_to_devfn(bus); + if (devfn < 0) + return 0; - return I2C_SUCCESS; + /* Form a PCI address for this device */ + dev = PCI_DEV(0, PCI_SLOT(devfn), PCI_FUNC(devfn)); + + /* Read the first base address for this device */ + base = pci_read_config32(dev, PCI_BASE_ADDRESS_0) & 0xfffffff0; + + /* Attempt to initialize bus if base is not set yet */ + if (!base && !lpss_i2c_early_init_bus(bus)) + base = pci_read_config32(dev, PCI_BASE_ADDRESS_0) & 0xfffffff0; + return base; } +#else -/** \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) +uintptr_t dw_i2c_base_address(unsigned int bus) { + int devfn; struct device *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; + struct resource *bar = NULL; + + /* bus -> devfn */ + devfn = dw_i2c_soc_bus_to_devfn(bus); + + if (devfn < 0) + return (uintptr_t)NULL; + + /* devfn -> dev */ + dev = pcidev_path_on_root(devfn); + if (dev && dev->enabled) { + /* dev -> bar0 */ + bar = find_resource(dev, PCI_BASE_ADDRESS_0); } - base_ptr = (char*)base_adr[bus]; - /* Set the I2C-device the user wants to use */ - dev = pcidev_on_root(PCH_DEV_SLOT_I2C1, bus + 1); + if (bar) + return bar->base; + else + return (uintptr_t)NULL; +} - /* 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; +static void i2c_enable_acpi_mode(struct device *dev, int iosf_reg, int nvs_index) +{ + struct resource *bar; + global_nvs_t *gnvs; + uint32_t val; + + /* Find ACPI NVS to update BARs */ + gnvs = (global_nvs_t *)cbmem_find(CBMEM_ID_ACPI_GNVS); + if (!gnvs) { + printk(BIOS_ERR, "Unable to locate Global NVS\n"); + return; } - /* Set memory base */ - pci_write_config32(dev, PCI_BASE_ADDRESS_0, (int)base_ptr); + /* Save BAR0 and BAR1 to ACPI NVS */ + bar = find_resource(dev, PCI_BASE_ADDRESS_0); + if (bar) + gnvs->dev.lpss_bar0[nvs_index] = (uint32_t)bar->base; + + bar = find_resource(dev, PCI_BASE_ADDRESS_1); + if (bar) + gnvs->dev.lpss_bar1[nvs_index] = (uint32_t)bar->base; + + /* Device is enabled in ACPI mode */ + gnvs->dev.lpss_en[nvs_index] = 1; + + /* Put device in ACPI mode */ + val = iosf_lpss_read(iosf_reg); + val |= (LPSS_CTL_PCI_CFG_DIS | LPSS_CTL_ACPI_INT_EN); + iosf_lpss_write(iosf_reg, val); + val = pci_read_config32(dev, PCI_COMMAND); + val |= PCI_COMMAND_INT_DISABLE; + pci_write_config32(dev, PCI_COMMAND, val); +} - /* Enable memory space */ - pci_write_config32(dev, PCI_COMMAND, - (pci_read_config32(dev, PCI_COMMAND) | 0x2)); - - /* Set up some settings of I2C controller */ - write32(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 */ - write32(base_ptr + I2C_SS_SCL_HCNT, 0x1f4); - write32(base_ptr + I2C_SS_SCL_LCNT, 0x1f4); - /* For 400 kHz, the counter value is 0x7d */ - write32(base_ptr + I2C_FS_SCL_HCNT, 0x7d); - write32(base_ptr + I2C_FS_SCL_LCNT, 0x7d); - /* no interrupts in BIOS */ - write32(base_ptr + I2C_INTR_MASK, 0); - - /* Enable the I2C controller for operation */ - write32(base_ptr + I2C_ENABLE, 0x1); - - printk(BIOS_INFO, "I2C: Controller %d enabled.\n", bus); - return I2C_SUCCESS; +static void dev_enable_snoop_and_pm(struct device *dev, int iosf_reg) +{ + uint32_t val; + + val = iosf_lpss_read(iosf_reg); + val &= ~(LPSS_CTL_SNOOP | LPSS_CTL_NOSNOOP); + val |= (LPSS_CTL_SNOOP | LPSS_CTL_PM_CAP_PRSNT); + iosf_lpss_write(iosf_reg, val); } -/** \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) +static void dev_ctl_reg(struct device *dev, int *iosf_reg, int *nvs_index) { - int i = 0; - char *base_ptr = NULL; - struct device *dev; - unsigned int val; - int stat; - - /* Get base address of desired I2C-controller */ - dev = pcidev_on_root(PCH_DEV_SLOT_I2C1, 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; - } + int bus; - /* Ensure I2C controller is not active before setting slave address */ - stat = wait_for_idle(base_ptr); - if (stat != I2C_SUCCESS) - return stat; - - /* clear any abort status from a previous transaction */ - read32(base_ptr + I2C_CLR_TX_ABRT); - - /* Now we can program the desired slave address and start transfer */ - write32(base_ptr + I2C_TARGET_ADR, chip & 0xff); - /* Send address inside slave to read from */ - write32(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 */ - write32(base_ptr + I2C_DATA_CMD, val); - stat = wait_rx_fifo(base_ptr); - if (stat) - return stat; - buf[i] = read32(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; - } + bus = dw_i2c_soc_dev_to_bus(dev); + if (bus >= 0) { + *iosf_reg = LPSS_I2C1_CTL + (bus * 8); + *nvs_index = bus + 1; + } else { + + *iosf_reg = -1; + *nvs_index = -1; } - 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) +static void i2c_disable_resets(struct device *dev) { - int i; - char *base_ptr; - struct device *dev; - unsigned int val; - int stat; - - /* Get base address of desired I2C-controller */ - dev = pcidev_on_root(PCH_DEV_SLOT_I2C1, bus + 1); - base_ptr = (char *)pci_read_config32(dev, PCI_BASE_ADDRESS_0); - if (base_ptr == NULL) { - return I2C_ERR_INVALID_ADR; - } + uint32_t base; - /* Ensure I2C controller is not active yet */ - stat = wait_for_idle(base_ptr); - if (stat) { - return stat; - } + printk(BIOS_DEBUG, "Releasing I2C device from reset.\n"); + base = pci_read_config32(dev, PCI_BASE_ADDRESS_0) & 0xfffffff0; + write32((void *)(base + I2C_SOFTWARE_RESET), I2C_RESET_APB | I2C_RESET_FUNC); +} - /* clear any abort status from a previous transaction */ - read32(base_ptr + I2C_CLR_TX_ABRT); - - /* Program slave address to use for this transfer */ - write32(base_ptr + I2C_TARGET_ADR, chip & 0xff); - - /* Send address inside slave to write data to */ - write32(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; - } - write32(base_ptr + I2C_DATA_CMD, val); +static void i2c_lpss_init(struct device *dev) +{ + struct soc_intel_fsp_baytrail_config *config = dev->chip_info; + int iosf_reg, nvs_index; + + dev_ctl_reg(dev, &iosf_reg, &nvs_index); + + if (iosf_reg < 0) { + int slot = PCI_SLOT(dev->path.pci.devfn); + int func = PCI_FUNC(dev->path.pci.devfn); + printk(BIOS_DEBUG, "Could not find iosf_reg for %02x.%01x\n", + slot, func); + return; } - return I2C_SUCCESS; + dev_enable_snoop_and_pm(dev, iosf_reg); + i2c_disable_resets(dev); + + if (config && (config->PcdLpssSioEnablePciMode == LPSS_PCI_MODE_DISABLE)) + i2c_enable_acpi_mode(dev, iosf_reg, nvs_index); } +/* + * This function ensures that the device is actually out of reset and + * it is ready for initialization sequence. + */ +static void dw_i2c_device_init(struct device *dev) +{ + int bus = dw_i2c_soc_dev_to_bus(dev); + + if (bus < 0) + return; + + if (!dw_i2c_base_address(bus)) + return; + i2c_lpss_init(dev); + dw_i2c_dev_init(dev); +} + +static struct device_operations i2c_dev_ops = { + .read_resources = pci_dev_read_resources, + .set_resources = pci_dev_set_resources, + .enable_resources = pci_dev_enable_resources, + .scan_bus = scan_smbus, + .ops_i2c_bus = &dw_i2c_bus_ops, + .ops_pci = &pci_dev_ops_pci, + .init = dw_i2c_device_init, + .acpi_fill_ssdt_generator = dw_i2c_acpi_fill_ssdt, +}; + +static const unsigned short pci_device_ids[] = { + I2C1_DEVID, + I2C2_DEVID, + I2C3_DEVID, + I2C4_DEVID, + I2C5_DEVID, + I2C6_DEVID, + I2C7_DEVID, + 0 +}; + +static const struct pci_driver pch_i2c __pci_driver = { + .ops = &i2c_dev_ops, + .vendor = PCI_VENDOR_ID_INTEL, + .devices = pci_device_ids, +}; +#endif |