summaryrefslogtreecommitdiffstats
path: root/arch/arm/boards/novena
diff options
context:
space:
mode:
Diffstat (limited to 'arch/arm/boards/novena')
-rw-r--r--arch/arm/boards/novena/Makefile4
-rw-r--r--arch/arm/boards/novena/board.c109
-rw-r--r--arch/arm/boards/novena/ddr_regs.h119
-rw-r--r--arch/arm/boards/novena/flash-header-novena.imxcfg6
-rw-r--r--arch/arm/boards/novena/lowlevel.c173
5 files changed, 411 insertions, 0 deletions
diff --git a/arch/arm/boards/novena/Makefile b/arch/arm/boards/novena/Makefile
new file mode 100644
index 0000000000..3111392bf9
--- /dev/null
+++ b/arch/arm/boards/novena/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+obj-y += board.o
+lwl-y += lowlevel.o
diff --git a/arch/arm/boards/novena/board.c b/arch/arm/boards/novena/board.c
new file mode 100644
index 0000000000..b6c59aff44
--- /dev/null
+++ b/arch/arm/boards/novena/board.c
@@ -0,0 +1,109 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2023 John Watts <contact@jookia.org>
+
+#include <common.h>
+#include <deep-probe.h>
+#include <fs.h>
+#include <libfile.h>
+#include <net.h>
+
+struct novena_eeprom {
+ uint8_t signature[6]; /* 'Novena' */
+ uint8_t version; /* 1 or 2, not checked */
+ uint8_t page_size; /* v2 only: EEPROM read/write page */
+ uint32_t serial; /* 32-bit serial number */
+ uint8_t mac[6]; /* Gigabit MAC address */
+ uint16_t features; /* features */
+ /* ... extra fields omitted ... */
+} __packed;
+
+static void power_on_audio_codec(void)
+{
+ int rc = of_devices_ensure_probed_by_name("regulator-audio-codec");
+
+ if (rc < 0)
+ pr_err("Unable to power on audio codec: %s\n", strerror(-rc));
+}
+
+static struct novena_eeprom *novena_read_eeprom(void)
+{
+ size_t read;
+ loff_t max = sizeof(struct novena_eeprom);
+ void *eeprom;
+ int rc;
+
+ /*
+ * When powered off the audio codec pulls down the EEPROM's I2C line.
+ * Power it on so we can actually read data.
+ */
+ power_on_audio_codec();
+
+ rc = of_device_ensure_probed_by_alias("eeprom0");
+ if (rc < 0) {
+ pr_err("Unable to probe eeprom0: %s\n", strerror(-rc));
+ return NULL;
+ }
+
+ rc = read_file_2("/dev/eeprom0", &read, &eeprom, max);
+
+ if (rc < 0 && rc != -EFBIG) {
+ pr_err("Unable to read Novena EEPROM: %s\n", strerror(-rc));
+ return NULL;
+ } else if (read != max) {
+ pr_err("Short read from Novena EEPROM?\n");
+ free(eeprom);
+ return NULL;
+ }
+
+ return eeprom;
+}
+
+static bool novena_check_eeprom(struct novena_eeprom *eeprom)
+{
+ char *sig = eeprom->signature;
+ size_t size = sizeof(eeprom->signature);
+
+ if (memcmp("Novena", sig, size) != 0) {
+ pr_err("Unknown Novena EEPROM signature\n");
+ return false;
+ }
+
+ return true;
+}
+
+static void novena_set_mac(struct novena_eeprom *eeprom)
+{
+ struct device_node *dnode;
+
+ dnode = of_find_node_by_alias(of_get_root_node(), "ethernet0");
+ if (dnode)
+ of_eth_register_ethaddr(dnode, eeprom->mac);
+ else
+ pr_err("Unable to find ethernet node\n");
+}
+
+static int novena_probe(struct device *dev)
+{
+ struct novena_eeprom *eeprom = novena_read_eeprom();
+
+ if (eeprom && novena_check_eeprom(eeprom))
+ novena_set_mac(eeprom);
+
+ free(eeprom);
+
+ return 0;
+}
+
+static const struct of_device_id novena_of_match[] = {
+ { .compatible = "kosagi,imx6q-novena", },
+ { /* sentinel */ }
+};
+
+static struct driver novena_board_driver = {
+ .name = "board-novena",
+ .probe = novena_probe,
+ .of_compatible = novena_of_match,
+};
+coredevice_platform_driver(novena_board_driver);
+
+BAREBOX_DEEP_PROBE_ENABLE(novena_of_match);
diff --git a/arch/arm/boards/novena/ddr_regs.h b/arch/arm/boards/novena/ddr_regs.h
new file mode 100644
index 0000000000..5f18d5e0e4
--- /dev/null
+++ b/arch/arm/boards/novena/ddr_regs.h
@@ -0,0 +1,119 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* SPDX-FileCopyrightText: 2014 Marek Vasut <marex@denx.de> */
+
+#ifndef NOVENA_DDR_REGS_H
+#define NOVENA_DDR_REGS_H
+
+/* MEMORY CONTROLLER CONFIGURATION */
+
+static struct mx6dq_iomux_ddr_regs novena_ddr_regs = {
+ /* SDCLK[0:1], CAS, RAS, Reset: Differential input, 40ohm */
+ .dram_sdclk_0 = 0x00020038,
+ .dram_sdclk_1 = 0x00020038,
+ .dram_cas = 0x00000038,
+ .dram_ras = 0x00000038,
+ .dram_reset = 0x00000038,
+ /* SDCKE[0:1]: 100k pull-up */
+ .dram_sdcke0 = 0x00003000,
+ .dram_sdcke1 = 0x00003000,
+ /* SDBA2: pull-up disabled */
+ .dram_sdba2 = 0x00000000,
+ /* SDODT[0:1]: 100k pull-up, 40 ohm */
+ .dram_sdodt0 = 0x00000038,
+ .dram_sdodt1 = 0x00000038,
+ /* SDQS[0:7]: Differential input, 40 ohm */
+ .dram_sdqs0 = 0x00000038,
+ .dram_sdqs1 = 0x00000038,
+ .dram_sdqs2 = 0x00000038,
+ .dram_sdqs3 = 0x00000038,
+ .dram_sdqs4 = 0x00000038,
+ .dram_sdqs5 = 0x00000038,
+ .dram_sdqs6 = 0x00000038,
+ .dram_sdqs7 = 0x00000038,
+
+ /* DQM[0:7]: Differential input, 40 ohm */
+ .dram_dqm0 = 0x00000038,
+ .dram_dqm1 = 0x00000038,
+ .dram_dqm2 = 0x00000038,
+ .dram_dqm3 = 0x00000038,
+ .dram_dqm4 = 0x00000038,
+ .dram_dqm5 = 0x00000038,
+ .dram_dqm6 = 0x00000038,
+ .dram_dqm7 = 0x00000038,
+};
+
+static struct mx6dq_iomux_grp_regs novena_grp_regs = {
+ /* DDR3 */
+ .grp_ddr_type = 0x000c0000,
+ .grp_ddrmode_ctl = 0x00020000,
+ /* Disable DDR pullups */
+ .grp_ddrpke = 0x00000000,
+ /* ADDR[00:16], SDBA[0:1]: 40 ohm */
+ .grp_addds = 0x00000038,
+ /* CS0/CS1/SDBA2/CKE0/CKE1/SDWE: 40 ohm */
+ .grp_ctlds = 0x00000038,
+ /* DATA[00:63]: Differential input, 40 ohm */
+ .grp_ddrmode = 0x00020000,
+ .grp_b0ds = 0x00000038,
+ .grp_b1ds = 0x00000038,
+ .grp_b2ds = 0x00000038,
+ .grp_b3ds = 0x00000038,
+ .grp_b4ds = 0x00000038,
+ .grp_b5ds = 0x00000038,
+ .grp_b6ds = 0x00000038,
+ .grp_b7ds = 0x00000038,
+};
+
+/* MEMORY STICK CONFIGURATION */
+
+static struct mx6_mmdc_calibration novena_mmdc_calib = {
+ /* write leveling calibration determine */
+ .p0_mpwldectrl0 = 0x00420048,
+ .p0_mpwldectrl1 = 0x006f0059,
+ .p1_mpwldectrl0 = 0x005a0104,
+ .p1_mpwldectrl1 = 0x01070113,
+ /* Read DQS Gating calibration */
+ .p0_mpdgctrl0 = 0x437c040b,
+ .p0_mpdgctrl1 = 0x0413040e,
+ .p1_mpdgctrl0 = 0x444f0446,
+ .p1_mpdgctrl1 = 0x044d0422,
+ /* Read Calibration: DQS delay relative to DQ read access */
+ .p0_mprddlctl = 0x4c424249,
+ .p1_mprddlctl = 0x4e48414f,
+ /* Write Calibration: DQ/DM delay relative to DQS write access */
+ .p0_mpwrdlctl = 0x42414641,
+ .p1_mpwrdlctl = 0x46374b43,
+};
+
+static struct mx6_ddr_sysinfo novena_ddr_info = {
+ /* Width of data bus: 0=16, 1=32, 2=64 */
+ .dsize = 2,
+ /* Config for full 4GB range so that get_mem_size() works */
+ .cs_density = 32, /* 32Gb per CS */
+ /* Single chip select */
+ .ncs = 1,
+ .cs1_mirror = 0,
+ .rtt_wr = 1, /* RTT_Wr = RZQ/4 */
+ .rtt_nom = 2, /* RTT_Nom = RZQ/2 */
+ .walat = 3, /* Write additional latency */
+ .ralat = 7, /* Read additional latency */
+ .mif3_mode = 3, /* Command prediction working mode */
+ .bi_on = 0, /* Bank interleaving disabled */
+ .sde_to_rst = 0x10, /* 14 cycles, 200us (JEDEC default) */
+ .rst_to_cke = 0x23, /* 33 cycles, 500us (JEDEC default) */
+};
+
+static struct mx6_ddr3_cfg novena_ddr_cfg = {
+ .mem_speed = 1600,
+ .density = 4,
+ .width = 64,
+ .banks = 8,
+ .rowaddr = 16,
+ .coladdr = 10,
+ .pagesz = 1,
+ .trcd = 1300,
+ .trcmin = 4900,
+ .trasmin = 3590,
+};
+
+#endif
diff --git a/arch/arm/boards/novena/flash-header-novena.imxcfg b/arch/arm/boards/novena/flash-header-novena.imxcfg
new file mode 100644
index 0000000000..0612542c19
--- /dev/null
+++ b/arch/arm/boards/novena/flash-header-novena.imxcfg
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+loadaddr 0x00907000
+soc imx6
+max_load_size 0x11000
+ivtofs 0x400
diff --git a/arch/arm/boards/novena/lowlevel.c b/arch/arm/boards/novena/lowlevel.c
new file mode 100644
index 0000000000..70aa92d5b4
--- /dev/null
+++ b/arch/arm/boards/novena/lowlevel.c
@@ -0,0 +1,173 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2023 John Watts <contact@jookia.org>
+
+#include <asm/barebox-arm.h>
+#include <common.h>
+#include <ddr_dimms.h>
+#include <ddr_spd.h>
+#include <debug_ll.h>
+#include <mach/imx/debug_ll.h>
+#include <mach/imx/esdctl.h>
+#include <mach/imx/generic.h>
+#include <mach/imx/imx6.h>
+#include <mach/imx/imx6-mmdc.h>
+#include <mach/imx/iomux-mx6.h>
+#include <mach/imx/xload.h>
+#include <pbl/i2c.h>
+#include <soc/fsl/fsl_udc.h>
+#include "ddr_regs.h"
+
+#define STACK_TOP (MX6_OCRAM_BASE_ADDR + MX6_OCRAM_MAX_SIZE)
+
+extern char __dtb_z_imx6q_novena_start[];
+
+static struct spd_eeprom spd_eeprom;
+static struct dimm_params dimm_params;
+
+static struct pbl_i2c *setup_spd_i2c(void)
+{
+ void __iomem *iomuxbase = IOMEM(MX6_IOMUXC_BASE_ADDR);
+ void __iomem *i2c1base = IOMEM(MX6_I2C1_BASE_ADDR);
+
+ imx_setup_pad(iomuxbase, MX6Q_PAD_CSI0_DAT8__I2C1_SDA);
+ imx_setup_pad(iomuxbase, MX6Q_PAD_CSI0_DAT9__I2C1_SCL);
+
+ return imx6_i2c_early_init(i2c1base);
+}
+
+static struct spd_eeprom *read_spd(void)
+{
+ struct spd_eeprom *eeprom = &spd_eeprom;
+ struct pbl_i2c *i2c = setup_spd_i2c();
+ int rc;
+
+ rc = spd_read_eeprom(i2c, 0x50, eeprom, SPD_MEMTYPE_DDR3);
+ if (rc < 0) {
+ pr_err("Couldn't read SPD EEPROM: %i\n", rc);
+ return NULL;
+ }
+
+ rc = ddr3_spd_check(&eeprom->ddr3);
+ if (rc < 0) {
+ pr_err("Couldn't verify SPD data: %i\n", rc);
+ return NULL;
+ }
+
+ return eeprom;
+}
+
+static void setup_dimm_settings(struct dimm_params *params,
+ struct mx6_ddr_sysinfo *info,
+ struct mx6_ddr3_cfg *cfg)
+{
+ int capacity_gbit = params->capacity / 0x8000000;
+ int density_rank = capacity_gbit / params->n_ranks;
+
+ info->ncs = params->n_ranks;
+ info->cs_density = density_rank;
+ cfg->mem_speed = params->tckmin_x_ps;
+ cfg->density = density_rank / params->n_banks_per_sdram_device;
+ cfg->width = params->data_width;
+ cfg->banks = params->n_banks_per_sdram_device;
+ cfg->rowaddr = params->n_row_addr;
+ cfg->coladdr = params->n_col_addr;
+ cfg->trcd = params->trcd_ps / 10;
+ cfg->trcmin = params->trc_ps / 10;
+ cfg->trasmin = params->tras_ps / 10;
+ cfg->SRT = params->extended_op_srt;
+
+ if (params->device_width >= 16)
+ cfg->pagesz = 2;
+}
+
+static void read_dimm_settings(void)
+{
+ struct spd_eeprom *eeprom = read_spd();
+ struct dimm_params *params = &dimm_params;
+ int rc;
+
+ if (!eeprom) {
+ pr_err("Couldn't read SPD EEPROM, using default settings\n");
+ return;
+ }
+
+ rc = ddr3_compute_dimm_parameters(&eeprom->ddr3, params);
+ if (rc < 0) {
+ pr_err("Couldn't compute DIMM params: %i\n", rc);
+ return;
+ }
+
+ pr_info("Found DIMM: %s\n", params->mpart);
+
+ if (params->primary_sdram_width != 64) {
+ pr_err("ERROR: DIMM stick memory width is not 64 bits\n");
+ hang();
+ }
+
+ setup_dimm_settings(params, &novena_ddr_info, &novena_ddr_cfg);
+}
+
+static bool running_from_ram(void)
+{
+ return (get_pc() >= MX6_MMDC_PORT01_BASE_ADDR);
+}
+
+static void setup_uart(void)
+{
+ void __iomem *iomuxbase = IOMEM(MX6_IOMUXC_BASE_ADDR);
+ void __iomem *uart2base = IOMEM(MX6_UART2_BASE_ADDR);
+
+ /* NOTE: RX is needed for TX to work on this board */
+ imx_setup_pad(iomuxbase, MX6Q_PAD_EIM_D26__UART2_RXD);
+ imx_setup_pad(iomuxbase, MX6Q_PAD_EIM_D27__UART2_TXD);
+
+ imx6_uart_setup(uart2base);
+ pbl_set_putc(imx_uart_putc, uart2base);
+
+ pr_debug(">");
+}
+
+static void setup_ram(void)
+{
+ read_dimm_settings();
+
+ mx6dq_dram_iocfg(64, &novena_ddr_regs, &novena_grp_regs);
+ mx6_dram_cfg(&novena_ddr_info, &novena_mmdc_calib, &novena_ddr_cfg);
+
+ mmdc_do_write_level_calibration();
+ mmdc_do_dqs_calibration();
+}
+
+static void load_barebox(void)
+{
+ enum bootsource bootsrc;
+ int bootinstance;
+
+ imx6_get_boot_source(&bootsrc, &bootinstance);
+
+ if (bootsrc == BOOTSOURCE_SERIAL)
+ imx6_barebox_start_usb(IOMEM(MX6_MMDC_PORT01_BASE_ADDR));
+ else if (bootsrc == BOOTSOURCE_MMC)
+ imx6_esdhc_start_image(bootinstance);
+
+ pr_err("Unsupported boot source %i instance %i\n",
+ bootsrc, bootinstance);
+ hang();
+}
+
+ENTRY_FUNCTION_WITHSTACK(start_imx6q_novena, STACK_TOP, r0, r1, r2)
+{
+ imx6_cpu_lowlevel_init();
+ relocate_to_current_adr();
+ setup_c();
+
+ imx6_ungate_all_peripherals();
+ setup_uart();
+
+ if (!running_from_ram()) {
+ setup_ram();
+ load_barebox();
+ } else {
+ imx6q_barebox_entry(__dtb_z_imx6q_novena_start);
+ }
+}