Duncan Laurie (dlaurie@chromium.org) just uploaded a new patch set to gerrit, which you can find at https://review.coreboot.org/15480
-gerrit
commit 9f05b482405ce7c57f17a089b1fb3ad38e59b934 Author: Duncan Laurie dlaurie@chromium.org Date: Mon Jun 27 10:57:13 2016 -0700
soc/intel/apollolake: Add support for LPSS I2C driver
Support the I2C interfaces on this SOC using the Intel common lpss_i2c driver. The controllers are supported in pre-ram environments by setting a temporary base address in bootblock and in ramstage using the naturally enumerated base address.
The base speed of this controller is 133MHz and the SCL/SDA timing values that are reported to the OS are calculated using that clock.
This was tested on a google/reef board doing I2C transactions to the trackpad both in verstage and in ramstage.
Change-Id: I0a9d62cd1007caa95cdf4754f30c30aaff9f78f9 Signed-off-by: Duncan Laurie dlaurie@chromium.org --- src/soc/intel/apollolake/Kconfig | 8 +- src/soc/intel/apollolake/Makefile.inc | 3 + src/soc/intel/apollolake/chip.h | 17 +++ src/soc/intel/apollolake/i2c.c | 154 +++++++++++++++++++++++++++ src/soc/intel/apollolake/i2c_early.c | 116 ++++++++++++++++++++ src/soc/intel/apollolake/include/soc/i2c.h | 50 +++++++++ src/soc/intel/apollolake/include/soc/iomap.h | 5 +- 7 files changed, 351 insertions(+), 2 deletions(-)
diff --git a/src/soc/intel/apollolake/Kconfig b/src/soc/intel/apollolake/Kconfig index 98ce7d8..749c15b 100644 --- a/src/soc/intel/apollolake/Kconfig +++ b/src/soc/intel/apollolake/Kconfig @@ -38,8 +38,9 @@ config CPU_SPECIFIC_OPTIONS select RELOCATABLE_RAMSTAGE # Build fails if this is not selected select SMM_TSEG select SOC_INTEL_COMMON - select SOC_INTEL_COMMON_SMI select SOC_INTEL_COMMON_ACPI + select SOC_INTEL_COMMON_LPSS_I2C + select SOC_INTEL_COMMON_SMI select SPI_FLASH select UDELAY_TSC select TSC_CONSTANT_RATE @@ -93,6 +94,11 @@ config CPU_ADDR_BITS int default 36
+config SOC_INTEL_COMMON_LPSS_I2C_CLOCK_MHZ + depends on SOC_INTEL_COMMON_LPSS_I2C + int + default 133 + config CONSOLE_UART_BASE_ADDRESS depends on CONSOLE_SERIAL hex "MMIO base address for UART" diff --git a/src/soc/intel/apollolake/Makefile.inc b/src/soc/intel/apollolake/Makefile.inc index 7326f14..c639eec 100644 --- a/src/soc/intel/apollolake/Makefile.inc +++ b/src/soc/intel/apollolake/Makefile.inc @@ -23,6 +23,7 @@ bootblock-$(CONFIG_SOC_UART_DEBUG) += uart_early.c romstage-y += car.c romstage-$(CONFIG_PLATFORM_USES_FSP2_0) += romstage.c romstage-y += gpio.c +romstage-y += i2c_early.c romstage-$(CONFIG_SOC_UART_DEBUG) += uart_early.c romstage-y += lpc_lib.c romstage-y += memmap.c @@ -45,6 +46,7 @@ ramstage-y += cpu.c ramstage-y += chip.c ramstage-y += gpio.c ramstage-y += graphics.c +ramstage-y += i2c.c ramstage-$(CONFIG_SOC_UART_DEBUG) += uart_early.c ramstage-y += lpc.c ramstage-y += lpc_lib.c @@ -68,6 +70,7 @@ postcar-$(CONFIG_SOC_UART_DEBUG) += uart_early.c postcar-y += tsc_freq.c
verstage-y += car.c +verstage-y += i2c_early.c verstage-y += memmap.c verstage-y += mmap_boot.c verstage-$(CONFIG_SOC_UART_DEBUG) += uart_early.c diff --git a/src/soc/intel/apollolake/chip.h b/src/soc/intel/apollolake/chip.h index ef82c53..c83f973 100644 --- a/src/soc/intel/apollolake/chip.h +++ b/src/soc/intel/apollolake/chip.h @@ -18,7 +18,21 @@ #ifndef _SOC_APOLLOLAKE_CHIP_H_ #define _SOC_APOLLOLAKE_CHIP_H_
+#include <soc/gpio.h> +#include <soc/intel/common/lpss_i2c.h> +#include <device/i2c.h> + #define CLKREQ_DISABLED 0xf +#define APOLLOLAKE_I2C_DEV_MAX 8 + +struct apollolake_i2c_config { + /* Bus should be enabled prior to ramstage with temporary base */ + int early_init; + /* Bus speed in Hz, default is I2C_SPEED_FAST (400 KHz) */ + enum i2c_speed speed; + /* Specific bus speed configuration */ + struct lpss_i2c_speed_config speed_config[LPSS_I2C_SPEED_CONFIG_COUNT]; +};
/* Serial IRQ control. SERIRQ_QUIET is the default (0). */ enum serirq_mode { @@ -79,6 +93,9 @@ struct soc_intel_apollolake_config {
/* Integrated Sensor Hub */ uint8_t integrated_sensor_hub_enable; + + /* I2C bus configuration */ + struct apollolake_i2c_config i2c[APOLLOLAKE_I2C_DEV_MAX]; };
#endif /* _SOC_APOLLOLAKE_CHIP_H_ */ diff --git a/src/soc/intel/apollolake/i2c.c b/src/soc/intel/apollolake/i2c.c new file mode 100644 index 0000000..78f8911 --- /dev/null +++ b/src/soc/intel/apollolake/i2c.c @@ -0,0 +1,154 @@ +/* + * This file is part of the coreboot project. + * + * Copyright 2016 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 <arch/acpi_device.h> +#include <arch/acpigen.h> +#include <device/device.h> +#include <device/i2c.h> +#include <device/pci.h> +#include <device/pci_def.h> +#include <device/pci_ids.h> +#include <soc/i2c.h> +#include <soc/intel/common/lpss_i2c.h> +#include <soc/pci_devs.h> +#include <soc/pci_ids.h> +#include <string.h> +#include "chip.h" + +uintptr_t lpss_i2c_base_address(unsigned bus) +{ + unsigned devfn; + struct device *dev; + struct resource *res; + + /* bus -> devfn */ + devfn = i2c_bus_to_devfn(bus); + if (devfn >= 0) { + /* devfn -> dev */ + dev = dev_find_slot(0, devfn); + if (dev) { + /* dev -> bar0 */ + res = find_resource(dev, PCI_BASE_ADDRESS_0); + if (res) + return res->base; + } + } + + return (uintptr_t)NULL; +} + +static int i2c_dev_to_bus(struct device *dev) +{ + return i2c_devfn_to_bus(dev->path.pci.devfn); +} + +/* + * The device should already be enabled and out of reset, + * either from early init in coreboot or FSP-S. + */ +static void i2c_dev_init(struct device *dev) +{ + struct soc_intel_apollolake_config *config = dev->chip_info; + const struct lpss_i2c_speed_config *sptr; + enum i2c_speed speed; + int i, bus = i2c_dev_to_bus(dev); + + if (!config || bus < 0) + return; + + speed = config->i2c[bus].speed ? : I2C_SPEED_FAST; + lpss_i2c_init(bus, speed); + + /* Apply custom speed config if it has been set by the board */ + for (i = 0; i < LPSS_I2C_SPEED_CONFIG_COUNT; i++) { + sptr = &config->i2c[bus].speed_config[i]; + if (sptr->speed == speed) { + lpss_i2c_set_speed_config(bus, sptr); + break; + } + } +} + +static void i2c_fill_ssdt(struct device *dev) +{ + struct soc_intel_apollolake_config *config = dev->chip_info; + struct lpss_i2c_speed_config *sptr, sgen; + int i, bus = i2c_dev_to_bus(dev); + enum i2c_speed speeds[LPSS_I2C_SPEED_CONFIG_COUNT] = { + I2C_SPEED_STANDARD, + I2C_SPEED_FAST, + I2C_SPEED_FAST_PLUS, + I2C_SPEED_HIGH, + }; + + if (!config || bus < 0) + return; + + /* I2C device is defined in the DSDT so use existing device scope */ + acpigen_write_scope(acpi_device_path(dev)); + + /* Report timing values for the OS driver */ + for (i = 0; i < LPSS_I2C_SPEED_CONFIG_COUNT; i++) { + /* Generate speed config for default case */ + if (lpss_i2c_gen_speed_config(speeds[i], &sgen) < 0) + continue; + + /* Apply board specific override for this speed if found */ + for (sptr = config->i2c[bus].speed_config; + sptr && sptr->speed; sptr++) { + if (sptr->speed == speeds[i]) { + memcpy(&sgen, sptr, sizeof(sgen)); + break; + } + } + + /* Generate ACPI based on selected speed config */ + lpss_i2c_acpi_write_speed_config(&sgen); + } + + acpigen_pop_len(); +} + +static struct i2c_bus_operations i2c_bus_ops = { + .dev_to_bus = &i2c_dev_to_bus, +}; + +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 = &i2c_bus_ops, + .init = &i2c_dev_init, + .acpi_fill_ssdt_generator = &i2c_fill_ssdt, +}; + +static const unsigned short pci_device_ids[] = { + PCI_DEVICE_ID_APOLLOLAKE_I2C0, + PCI_DEVICE_ID_APOLLOLAKE_I2C1, + PCI_DEVICE_ID_APOLLOLAKE_I2C2, + PCI_DEVICE_ID_APOLLOLAKE_I2C3, + PCI_DEVICE_ID_APOLLOLAKE_I2C4, + PCI_DEVICE_ID_APOLLOLAKE_I2C5, + PCI_DEVICE_ID_APOLLOLAKE_I2C6, + PCI_DEVICE_ID_APOLLOLAKE_I2C7, + 0, +}; + +static const struct pci_driver pch_i2c __pci_driver = { + .ops = &i2c_dev_ops, + .vendor = PCI_VENDOR_ID_INTEL, + .devices = pci_device_ids, +}; diff --git a/src/soc/intel/apollolake/i2c_early.c b/src/soc/intel/apollolake/i2c_early.c new file mode 100644 index 0000000..968e993 --- /dev/null +++ b/src/soc/intel/apollolake/i2c_early.c @@ -0,0 +1,116 @@ +/* + * This file is part of the coreboot project. + * + * Copyright 2016 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 <arch/io.h> +#include <commonlib/helpers.h> +#include <console/console.h> +#include <device/device.h> +#include <device/i2c.h> +#include <device/pci_def.h> +#include <soc/intel/common/lpss_i2c.h> +#include <soc/i2c.h> +#include <soc/iomap.h> +#include <soc/pci_devs.h> +#include "chip.h" + +static int i2c_early_init_bus(unsigned bus) +{ + ROMSTAGE_CONST struct soc_intel_apollolake_config *config; + ROMSTAGE_CONST struct device *tree_dev; + const struct lpss_i2c_speed_config *sptr; + enum i2c_speed speed; + pci_devfn_t dev; + unsigned devfn; + uintptr_t base; + uint32_t value; + void *reg; + + /* Find the PCI device for this bus controller */ + devfn = i2c_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 = dev_find_slot(0, devfn); + if (!tree_dev || !tree_dev->enabled) { + printk(BIOS_ERR, "I2C%u device not enabled\n", bus); + return -1; + } + + /* Skip if not enabled for early init */ + config = tree_dev->chip_info; + if (!config || !config->i2c[bus].early_init) { + printk(BIOS_ERR, "I2C%u not enabled for early init\n", bus); + return -1; + } + + /* Prepare early base address for access before memory */ + base = PRERAM_I2C_BASE_ADDRESS(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 */ + reg = (void *)(base + I2C_LPSS_REG_RESET); + value = read32(reg); + value |= I2C_LPSS_RESET_RELEASE_HC; + write32(reg, value); + + /* Initialize the controller */ + speed = config->i2c[bus].speed ? : I2C_SPEED_FAST; + if (lpss_i2c_init(bus, speed) < 0) { + printk(BIOS_ERR, "I2C%u failed to initialize\n", bus); + return -1; + } + + /* Apply custom speed config if it has been set by the board */ + for (value = 0; value < LPSS_I2C_SPEED_CONFIG_COUNT; value++) { + sptr = &config->i2c[bus].speed_config[value]; + if (sptr->speed == speed) { + lpss_i2c_set_speed_config(bus, sptr); + break; + } + } + + return 0; +} + +uintptr_t lpss_i2c_base_address(unsigned bus) +{ + unsigned devfn; + pci_devfn_t dev; + uintptr_t base; + + /* Find device+function for this controller */ + devfn = i2c_bus_to_devfn(bus); + if (devfn < 0) + return (uintptr_t)NULL; + + /* 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 = ALIGN_DOWN(pci_read_config32(dev, PCI_BASE_ADDRESS_0), 16); + + /* Attempt to initialize bus if base is not set yet */ + if (!base && !i2c_early_init_bus(bus)) + base = ALIGN_DOWN(pci_read_config32(dev, PCI_BASE_ADDRESS_0), + 16); + + return base; +} diff --git a/src/soc/intel/apollolake/include/soc/i2c.h b/src/soc/intel/apollolake/include/soc/i2c.h new file mode 100644 index 0000000..da700f2 --- /dev/null +++ b/src/soc/intel/apollolake/include/soc/i2c.h @@ -0,0 +1,50 @@ +/* + * This file is part of the coreboot project. + * + * Copyright 2016 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef _SOC_APOLLOLAKE_I2C_H_ +#define _SOC_APOLLOLAKE_I2C_H_ + +#include <device/pci_def.h> +#include <soc/pci_devs.h> + +/* I2C Controller Reset in MMIO private region */ +#define I2C_LPSS_REG_RESET 0x204 +#define I2C_LPSS_RESET_RELEASE_HC ((1 << 1) | (1 << 0)) +#define I2C_LPSS_RESET_RELEASE_IDMA (1 << 2) + +/* Convert I2C bus number to PCI device and function */ +static inline int i2c_bus_to_devfn(unsigned bus) +{ + if (bus >= 0 && bus <= 3) + return PCI_DEVFN(LPSS_DEV_SLOT_I2C_D0, bus); + else if (bus >= 4 && bus <= 7) + return PCI_DEVFN(LPSS_DEV_SLOT_I2C_D1, (bus - 4)); + else + return -1; +} + +/* Convert PCI device and function to I2C bus number */ +static inline int i2c_devfn_to_bus(unsigned devfn) +{ + if (PCI_SLOT(devfn) == LPSS_DEV_SLOT_I2C_D0) + return PCI_FUNC(devfn); + else if (PCI_SLOT(devfn) == LPSS_DEV_SLOT_I2C_D1) + return PCI_FUNC(devfn) + 4; + else + return -1; +} + +#endif /* _SOC_APOLLOLAKE_I2C_H_ */ diff --git a/src/soc/intel/apollolake/include/soc/iomap.h b/src/soc/intel/apollolake/include/soc/iomap.h index 716c2a6..e9caeca 100644 --- a/src/soc/intel/apollolake/include/soc/iomap.h +++ b/src/soc/intel/apollolake/include/soc/iomap.h @@ -31,6 +31,9 @@ #define PMC_BAR1 0xfe044000
/* Temporary BAR for SPI until PCI enumeration assigns a BAR in ramstage. */ -#define PRERAM_SPI_BASE_ADDRESS 0xfe010000 +#define PRERAM_SPI_BASE_ADDRESS 0xfe010000 + +/* Temporary BAR for early I2C bus access */ +#define PRERAM_I2C_BASE_ADDRESS(x) (0xfe020000 + (0x1000 * (x)))
#endif /* _SOC_APOLLOLAKE_IOMAP_H_ */