Skip to content

Commit

Permalink
atf: add support for flushing i2c bus before ddr init for reliable spd
Browse files Browse the repository at this point in the history
  • Loading branch information
Josua-SR committed Oct 25, 2024
1 parent a952542 commit df1f9a8
Show file tree
Hide file tree
Showing 3 changed files with 425 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,365 @@
From efa6cca1a194050caa37a16ddccbf5d6e568b42f Mon Sep 17 00:00:00 2001
From: Josua Mayer <[email protected]>
Date: Fri, 25 Oct 2024 15:36:57 +0200
Subject: [PATCH 09/11] lx2160a: support flushing i2c bus before ddr init

The i2c bus can get locked by a slave device holding sda low, when the
cpu is reset during a transaction.
Implement workaround according to LX2160A Chip Errata 07/2020 A-010650.

The workaround is inactive by default and must be explicitly enabled
through a board platform.mk file defining buses and muxes to flush:

LX2160_FLUSH_IIC: 1D array, takes comma separate uint8 indicating
human-readable i2c bus number,
e.g: IIC1 & IIC2 = "1, 2".

LX2160_FLUSH_IIC_MUX: 2D array, takes comma-separated 1D array
initializer expressions with i2c bus number, mux address on the bus and
a bitmask for channels to flush,
e.g. IIC1 mux @ 77, channels 1&2 = "{1, 0x77, 0x03}".

Signed-off-by: Josua Mayer <[email protected]>
---
plat/nxp/common/setup/common.mk | 1 +
plat/nxp/common/setup/include/plat_common.h | 2 +
plat/nxp/common/setup/ls_bl2_el3_setup.c | 1 +
plat/nxp/common/setup/ls_i2c_init.c | 274 ++++++++++++++++++++
plat/nxp/soc-lx2160a/soc.mk | 6 +
5 files changed, 284 insertions(+)
create mode 100644 plat/nxp/common/setup/ls_i2c_init.c

diff --git a/plat/nxp/common/setup/common.mk b/plat/nxp/common/setup/common.mk
index 1fcf1d093..6333ec515 100644
--- a/plat/nxp/common/setup/common.mk
+++ b/plat/nxp/common/setup/common.mk
@@ -80,6 +80,7 @@ BL2_SOURCES += drivers/io/io_fip.c \
plat/nxp/common/setup/ls_image_load.c \
plat/nxp/common/setup/ls_io_storage.c \
plat/nxp/common/setup/ls_bl2_el3_setup.c \
+ plat/nxp/common/setup/ls_i2c_init.c \
plat/nxp/common/setup/${ARCH}/ls_bl2_mem_params_desc.c

BL31_SOURCES += plat/nxp/common/setup/ls_bl31_setup.c \
diff --git a/plat/nxp/common/setup/include/plat_common.h b/plat/nxp/common/setup/include/plat_common.h
index 45832fa68..1c850ebe2 100644
--- a/plat/nxp/common/setup/include/plat_common.h
+++ b/plat/nxp/common/setup/include/plat_common.h
@@ -77,6 +77,8 @@ int open_backend(const uintptr_t spec);
void ls_bl2_plat_arch_setup(void);
void ls_bl2_el3_plat_arch_setup(void);

+void bl2_i2c_init(void);
+
enum boot_device {
BOOT_DEVICE_IFC_NOR,
BOOT_DEVICE_IFC_NAND,
diff --git a/plat/nxp/common/setup/ls_bl2_el3_setup.c b/plat/nxp/common/setup/ls_bl2_el3_setup.c
index a4cbaef45..63e3460d7 100644
--- a/plat/nxp/common/setup/ls_bl2_el3_setup.c
+++ b/plat/nxp/common/setup/ls_bl2_el3_setup.c
@@ -276,6 +276,7 @@ void bl2_el3_plat_prepare_exit(void)
*/
void bl2_plat_preload_setup(void)
{
+ bl2_i2c_init();

soc_preload_setup();

diff --git a/plat/nxp/common/setup/ls_i2c_init.c b/plat/nxp/common/setup/ls_i2c_init.c
new file mode 100644
index 000000000..5392b160d
--- /dev/null
+++ b/plat/nxp/common/setup/ls_i2c_init.c
@@ -0,0 +1,274 @@
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <common/debug.h>
+#include <drivers/delay_timer.h>
+#include <i2c.h>
+#include <soc.h>
+
+#define NXP_IIC1_ADDR 0x02000000
+#define NXP_IIC2_ADDR 0x02010000
+#define NXP_IIC3_ADDR 0x02020000
+#define NXP_IIC4_ADDR 0x02030000
+#define NXP_IIC5_ADDR 0x02040000
+#define NXP_IIC6_ADDR 0x02050000
+#define NXP_IIC7_ADDR 0x02060000
+#define NXP_IIC8_ADDR 0x02070000
+
+#define RCWSR12 0x01e0012c
+#define RCWSR12_IIC2_PMUX_MASK 0x00000007 /* [0..2] */
+#define RCWSR12_IIC2_PMUX_IIC2 0x00000000
+#define RCWSR12_IIC2_PMUX_GPIO 0x00000001
+#define RCWSR12_IIC3_PMUX_MASK 0x00000038 /* [3..5] */
+#define RCWSR12_IIC3_PMUX_IIC3 0x00000000
+#define RCWSR12_IIC3_PMUX_GPIO 0x00000008
+#define RCWSR12_IIC4_PMUX_MASK 0x000001c0 /* [6..8] */
+#define RCWSR12_IIC4_PMUX_IIC4 0x00000000
+#define RCWSR12_IIC4_PMUX_GPIO 0x00000040
+#define RCWSR12_IIC5_PMUX_MASK 0x00000e00 /* [9..11] */
+#define RCWSR12_IIC5_PMUX_IIC5 0x00000000
+#define RCWSR12_IIC5_PMUX_GPIO 0x00000200
+#define RCWSR12_IIC6_PMUX_MASK 0x00007000 /* [12..14] */
+#define RCWSR12_IIC6_PMUX_IIC6 0x00000000
+#define RCWSR12_IIC6_PMUX_GPIO 0x00001000
+#define RCWSR13 0x01e00130
+#define RCWSR12_SDHC2_DAT74_PMUX_MASK 0x00000003
+#define RCWSR12_SDHC2_DAT74_PMUX_SDHC2 0x00000000
+#define RCWSR12_SDHC2_DAT74_PMUX_IIC78 0x00000001
+#define RCWSR14 0x01e00134
+#define RCWSR14_IIC1_PMUX_MASK 0x00000400 /* [10] */
+#define RCWSR14_IIC1_PMUX_IIC1 0x00000000
+#define RCWSR14_IIC1_PMUX_GPIO 0x00000400
+
+static void ls_i2c_flush_pca9547(uint8_t busno, const char *busname, uint8_t address, uint8_t channels);
+static void ls_i2c_flush(uint8_t busno, const char *busname);
+
+/**
+ * Flush i2c buses to make slave devices release sda,
+ * in case the system was reset during a transaction.
+ */
+void bl2_i2c_init() {
+ const uint8_t bus_flush_list[] = {CONFIG_LX2160_FLUSH_IIC};
+ /*
+ * List of muxes and channels to flush. Takes 2D array:
+ * {<bus number>, <mux chip address>, <channel bitmask>},
+ */
+ const uint8_t mux_flush_list[][3] = {CONFIG_LX2160_FLUSH_IIC_MUX};
+ const uintptr_t iic_base_addr[] = {
+ NXP_IIC1_ADDR,
+ NXP_IIC2_ADDR,
+ NXP_IIC3_ADDR,
+ NXP_IIC4_ADDR,
+ NXP_IIC5_ADDR,
+ NXP_IIC6_ADDR,
+ };
+ const char *iic_name[] = {
+ "IIC1",
+ "IIC2",
+ "IIC3",
+ "IIC4",
+ "IIC5",
+ "IIC6",
+ };
+ int i;
+ uint8_t busno;
+ uint8_t address;
+ uint8_t channels;
+
+ /* flush i2c buses */
+ for (i = 0; i < ARRAY_SIZE(bus_flush_list); i++) {
+ busno = bus_flush_list[i] - 1;
+ i2c_init(iic_base_addr[busno]);
+ ls_i2c_flush(busno, iic_name[busno]);
+ }
+
+ /* flush muxes channels */
+ for (i = 0; i < ARRAY_SIZE(mux_flush_list); i++) {
+ busno = mux_flush_list[i][0] - 1;
+ address = mux_flush_list[i][1];
+ channels = mux_flush_list[i][2];
+ i2c_init(iic_base_addr[busno]);
+ ls_i2c_flush_pca9547(busno, iic_name[busno], address, channels);
+ }
+}
+
+static void ls_i2c_flush_pca9547(uint8_t busno, const char *busname, uint8_t chip, uint8_t channels) {
+ uint8_t channel, creg = 0;
+ char buffer[64];
+ int ret;
+
+ /* try read configuration register */
+ ret = i2c_read(chip, 0x00, 1, &creg, 1);
+ if(ret != 0) {
+ /* no device responding at address, skip */
+ return;
+ }
+
+ /* after reset configuration register reads 0x08 */
+ if(creg != 0x08) {
+ /* probably not a pca9547, skip */
+ return;
+ }
+
+ /* flush selected channels */
+ for(uint8_t i = 8; i > 0; i--) {
+ if (!(channels & (1 << (i-1))))
+ continue;
+
+ /* select channel i */
+ channel = 0x08 | (i-1);
+ i2c_write(chip, 0x00, 1, &channel, 1);
+
+ /* flush channel */
+ snprintf(buffer, sizeof(buffer), "%s mux@%02x channel %u", busname, chip, i-1);
+ ls_i2c_flush(busno, buffer);
+ }
+}
+
+static struct i2c_bus_info {
+ uintptr_t pinmux_addr;
+ uint32_t pinmux_mask;
+ uint32_t pinmux_sel;
+ uintptr_t gpio_addr;
+ uint8_t gpio_scl;
+ uint8_t gpio_sda;
+} ls_i2c_bus_info[] = {
+ {
+ .pinmux_addr = RCWSR14,
+ .pinmux_mask = RCWSR14_IIC1_PMUX_MASK,
+ .pinmux_sel = RCWSR14_IIC1_PMUX_GPIO,
+ .gpio_addr = NXP_GPIO1_ADDR,
+ .gpio_scl = 3, /* GPIO1_DAT03 */
+ .gpio_sda = 2, /* GPIO1_DAT02 */
+ },
+ {
+ .pinmux_addr = RCWSR12,
+ .pinmux_mask = RCWSR12_IIC2_PMUX_MASK,
+ .pinmux_sel = RCWSR12_IIC2_PMUX_GPIO,
+ .gpio_addr = NXP_GPIO1_ADDR,
+ .gpio_scl = 31, /* GPIO1_DAT31 */
+ .gpio_sda = 30, /* GPIO1_DAT30 */
+ },
+ {
+ .pinmux_addr = RCWSR12,
+ .pinmux_mask = RCWSR12_IIC3_PMUX_MASK,
+ .pinmux_sel = RCWSR12_IIC3_PMUX_GPIO,
+ .gpio_addr = NXP_GPIO1_ADDR,
+ .gpio_scl = 29, /* GPIO1_DAT29 */
+ .gpio_sda = 28, /* GPIO1_DAT28 */
+ },
+ {
+ .pinmux_addr = RCWSR12,
+ .pinmux_mask = RCWSR12_IIC4_PMUX_MASK,
+ .pinmux_sel = RCWSR12_IIC4_PMUX_GPIO,
+ .gpio_addr = NXP_GPIO1_ADDR,
+ .gpio_scl = 27, /* GPIO1_DAT27 */
+ .gpio_sda = 26, /* GPIO1_DAT26 */
+ },
+ {
+ .pinmux_addr = RCWSR12,
+ .pinmux_mask = RCWSR12_IIC5_PMUX_MASK,
+ .pinmux_sel = RCWSR12_IIC5_PMUX_GPIO,
+ .gpio_addr = NXP_GPIO1_ADDR,
+ .gpio_scl = 25, /* GPIO1_DAT25 */
+ .gpio_sda = 24, /* GPIO1_DAT24 */
+ },
+ {
+ .pinmux_addr = RCWSR12,
+ .pinmux_mask = RCWSR12_IIC6_PMUX_MASK,
+ .pinmux_sel = RCWSR12_IIC6_PMUX_GPIO,
+ .gpio_addr = NXP_GPIO1_ADDR,
+ .gpio_scl = 23, /* GPIO1_DAT23 */
+ .gpio_sda = 22, /* GPIO1_DAT22 */
+ },
+ {
+ .pinmux_addr = RCWSR13,
+ .pinmux_mask = RCWSR12_SDHC2_DAT74_PMUX_MASK,
+ .pinmux_sel = RCWSR12_SDHC2_DAT74_PMUX_IIC78,
+ .gpio_addr = NXP_GPIO2_ADDR,
+ .gpio_scl = 16, /* GPIO2_DAT16 */
+ .gpio_sda = 15, /* GPIO2_DAT15 */
+ },
+ {
+ .pinmux_addr = RCWSR13,
+ .pinmux_mask = RCWSR12_SDHC2_DAT74_PMUX_MASK,
+ .pinmux_sel = RCWSR12_SDHC2_DAT74_PMUX_IIC78,
+ .gpio_addr = NXP_GPIO2_ADDR,
+ .gpio_scl = 18, /* GPIO2_DAT18 */
+ .gpio_sda = 17, /* GPIO2_DAT17 */
+ },
+};
+
+/*
+ * Flush the i2c bus through any muxes with 9 clock cycles
+ * to ensure all slave devices release their locks on SDA.
+ * This is a work-around for i2c slave devices locking SDA,
+ * when the system has been reset during a transaction.
+ *
+ * The implementation is inspired by LX2160A Chip Errata 07/2020 A-010650.
+ */
+static void ls_i2c_flush(uint8_t busno, const char *busname) {
+ struct i2c_bus_info *info;
+ uintptr_t gpdir_addr, gpodr_addr, gpdat_addr;
+ uint32_t pinmux, gpdir, gpodr, gpdat;
+ struct {
+ uint32_t pinmux, gpdir, gpodr, gpdat;
+ } backup;
+ uint32_t scl_mask, sda_mask;
+
+ if(busno >= 8) {
+ ERROR("failed to flush i2c bus %u %s: invalid bus number!\n", busno, busname);
+ return;
+ }
+ /* load i2c bus specific information */
+ info = &ls_i2c_bus_info[busno];
+ gpdir_addr = info->gpio_addr + 0x0;
+ gpodr_addr = info->gpio_addr + 0x4;
+ gpdat_addr = info->gpio_addr + 0x8;
+ scl_mask = 0x80000000 >> info->gpio_scl;
+ sda_mask = 0x80000000 >> info->gpio_sda;
+
+ /* backup configuration registers */
+ pinmux = backup.pinmux = mmio_read_32(info->pinmux_addr);
+ gpdir = backup.gpdir = mmio_read_32(gpdir_addr);
+ gpodr = backup.gpodr = mmio_read_32(gpodr_addr);
+ gpdat = backup.gpdat = mmio_read_32(gpdat_addr);
+
+ /* configure SCL+SDA as GPIOs */
+ pinmux = (pinmux & ~info->pinmux_mask) | info->pinmux_sel;
+ mmio_write_32(info->pinmux_addr, pinmux);
+
+ /* configure SCL+SDA as output open drain */
+ gpdir |= scl_mask | sda_mask;
+ gpodr |= scl_mask | sda_mask;
+ gpdat |= scl_mask | sda_mask;
+ mmio_write_32(gpdir_addr, gpdir);
+ mmio_write_32(gpodr_addr, gpodr);
+ mmio_write_32(gpdat_addr, gpdat);
+
+ /*
+ * reliable detection of blocked bus is hard
+ * because sda depends on the last sent bit.
+ * Flush unconditionally instead.
+ */
+
+ VERBOSE("flushing i2c bus %u (%s)\n", busno, busname);
+
+ /* toggle clock 9 times */
+ for(uint8_t i = 0; i < 9; i++) {
+ mmio_write_32(gpdat_addr, gpdat & ~scl_mask);
+ udelay(10);
+ mmio_write_32(gpdat_addr, gpdat | scl_mask);
+ udelay(10);
+ }
+
+ /* restore configuration registers */
+ mmio_write_32(gpdat_addr, backup.gpdat);
+ mmio_write_32(gpodr_addr, backup.gpodr);
+ mmio_write_32(gpdir_addr, backup.gpdir);
+ mmio_write_32(info->pinmux_addr, backup.pinmux);
+
+ return;
+}
diff --git a/plat/nxp/soc-lx2160a/soc.mk b/plat/nxp/soc-lx2160a/soc.mk
index 20e64753c..fa8c63251 100644
--- a/plat/nxp/soc-lx2160a/soc.mk
+++ b/plat/nxp/soc-lx2160a/soc.mk
@@ -185,3 +185,9 @@ ifneq (${LX2160A_S5_GPIO_ADDR},0)
$(eval $(call add_define_val,CONFIG_LX2160A_S5_GPIO_ADDR,$(LX2160A_S5_GPIO_ADDR)))
$(eval $(call add_define_val,CONFIG_LX2160A_S5_GPIO,$(LX2160A_S5_GPIO)))
endif
+
+# I2C Bus Flushing (optional)
+LX2160_FLUSH_IIC ?= ""
+LX2160_FLUSH_IIC_MUX ?= ""
+$(eval $(call add_define_val,CONFIG_LX2160_FLUSH_IIC,"$(LX2160_FLUSH_IIC)"))
+$(eval $(call add_define_val,CONFIG_LX2160_FLUSH_IIC_MUX,"$(LX2160_FLUSH_IIC_MUX)"))
--
2.43.0

Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
From 668924da1e8df2036dc05022616eb7d15a8c8727 Mon Sep 17 00:00:00 2001
From: Josua Mayer <[email protected]>
Date: Fri, 25 Oct 2024 16:43:59 +0200
Subject: [PATCH 10/11] lx2160acex6: flush i2c bus with spd eeprom before ddr
init

Signed-off-by: Josua Mayer <[email protected]>
---
plat/nxp/soc-lx2160a/lx2160acex6/platform.mk | 5 +++++
1 file changed, 5 insertions(+)

diff --git a/plat/nxp/soc-lx2160a/lx2160acex6/platform.mk b/plat/nxp/soc-lx2160a/lx2160acex6/platform.mk
index 1165cfa09..950207878 100644
--- a/plat/nxp/soc-lx2160a/lx2160acex6/platform.mk
+++ b/plat/nxp/soc-lx2160a/lx2160acex6/platform.mk
@@ -29,6 +29,11 @@ CONFIG_DDR_NODIMM := 0
LX2160A_S5_GPIO_ADDR := NXP_GPIO3_ADDR
LX2160A_S5_GPIO := 0

+# I2C Bus Flushing: IIC1
+LX2160_FLUSH_IIC := 1,
+# I2C Mux Flushing: IIC1: PCA9547@77: Channel 0 (SPD EEPROM)
+LX2160_FLUSH_IIC_MUX := { 1, 0x77, 0x01 },
+
# DDR Errata
ERRATA_DDR_A011396 := 1
ERRATA_DDR_A050450 := 1
--
2.43.0

Loading

0 comments on commit df1f9a8

Please sign in to comment.