u-boot/board/nm/common/nbhw_pcie_fixup.c

291 lines
7.3 KiB
C

/******************************************************************************
* (c) COPYRIGHT 2015 by NetModule AG, Switzerland. All rights reserved.
*
* 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.
*
*****************************************************************************/
/* #define DEBUG */
#include <common.h>
#include <environment.h>
#include <asm/io.h>
#include <u-boot/md5.h>
#include "config.h"
#include "../common/nbhw_bd.h"
enum serdes_type get_serdes_type(int index);
static void pcie_fixup_modify(u32 reg, u32 val, u32 mask)
{
u32 regval;
regval = readl(reg);
regval &= ~mask;
regval |= (val & mask);
writel(regval, reg);
}
/* We have a problem with the hardware during the detection phase this hack
* reduces the detection timeout from 4ns to 0ns so a minmal impulse will
* already trigger a detection. This is necessary to work with WLE600 modules. */
static void pcie_fixup(void)
{
int i;
u32 reg;
#define PCIE_USB3_CONTROL_TIMING_MASK 0xC0
#define PCIE_USB3_CONTROL_TIMING_VALUE 0x00
for (i = 0; i<6; i++) {
enum serdes_type type = get_serdes_type(i);
if ((type >= PEX0) && (type <= PEX3)) {
switch (i) {
case 0 :
reg = 0xF10A0120; /* PCIe/USB3 Control Register SERDES0 */
break;
case 1 :
reg = 0xF10A0920; /* PCIe/USB3 Control Register SERDES1 */
break;
case 2 :
reg = 0xF10A1120; /* PCIe/USB3 Control Register SERDES2 */
break;
case 3 :
reg = 0xF10A1920; /* PCIe/USB3 Control Register SERDES3 */
break;
case 4 :
reg = 0xF10A2120; /* PCIe/USB3 Control Register SERDES4 */
break;
case 5 :
reg = 0xF10A2920; /* PCIe/USB3 Control Register SERDES5 */
break;
default :
return;
}
pcie_fixup_modify(reg, PCIE_USB3_CONTROL_TIMING_VALUE,
PCIE_USB3_CONTROL_TIMING_MASK);
}
}
}
static int has_slot_wlan(int slot)
{
int module;
char slotDescr[20];
char pdValue[200];
sprintf(slotDescr, "slot=%d", slot);
for (module=0; module<6; module++) {
strcpy(pdValue, "" ); /*init with an empty string*/
if (bd_get_pd_module(module, pdValue, sizeof(pdValue))==0) {
/* Wifi module needs PCIe */
if ((strstr(pdValue, slotDescr)) && (strstr(pdValue, "wlan-")))
return 1;
}
}
return 0;
}
/* PCIE0 is on slot 1 (sw)*/
#define PCIE0_STATUS_REG 0xF1081A04
#define PCIE1_STATUS_REG 0xF1041A04
#define PCIE2_STATUS_REG 0xF1045A04
#define PCIE3_STATUS_REG 0xF1049A04
#define PCIE_DL_DOWN_MASK 0x01
int pcie_lane_by_slot(int slot);
static int has_pcie_link_on_slot(int slot)
{
/* If there is no module populated we say we have link
* (because we don't care) */
switch (pcie_lane_by_slot(slot)) {
case 0:
if (readw(PCIE0_STATUS_REG) & PCIE_DL_DOWN_MASK)
return 0;
break;
case 1:
if (readw(PCIE1_STATUS_REG) & PCIE_DL_DOWN_MASK)
return 0;
break;
case 2:
if (readw(PCIE2_STATUS_REG) & PCIE_DL_DOWN_MASK)
return 0;
break;
case 3:
if (readw(PCIE3_STATUS_REG) & PCIE_DL_DOWN_MASK)
return 0;
break;
default:
printf("Slot %d does not support PCIE\n", slot);
}
return 1;
}
static int check_pcie_slot_status(int slot){
int ret = 1;
/* If WLAN on slot we need to check if we have link */
if (has_slot_wlan(slot)) {
ret = has_pcie_link_on_slot(slot);
}
return ret;
}
/* The reset counter is at 256 MB, make sure it is not overlapping the address
* of the reset-reason mechanim, which is located at 1GB - 512kB.
* The reset reason counter is necessary to not try to reset
* the system more than 3 times */
#define RESET_COUNTER_ADDRESS ((int *)0x10000000)
#define RESET_COUNTER_MD5 (RESET_COUNTER_ADDRESS + 4)
int get_reset_counter(void)
{
volatile int *reset_counter = RESET_COUNTER_ADDRESS;
unsigned char reset_counter_checksum[16];
debug("reset_counter before inc: %d\n", *reset_counter);
md5((unsigned char*) reset_counter, 4, reset_counter_checksum);
/* Invalid checksum means it's the first boot. In this case we should see
* the counter as zero */
if (memcmp(reset_counter_checksum, RESET_COUNTER_MD5, 16) != 0) {
*reset_counter = 0;
}
return *reset_counter;
}
int inc_reset_counter(void)
{
volatile int *reset_counter = RESET_COUNTER_ADDRESS;
unsigned char reset_counter_checksum[16];
(*reset_counter)++;
debug("reset_counter after inc: %d\n", *reset_counter);
md5((unsigned char*) reset_counter, 4, reset_counter_checksum);
memcpy(RESET_COUNTER_MD5, reset_counter_checksum, 16);
return *reset_counter;
}
int reset_reset_counter(void)
{
volatile int *reset_counter = RESET_COUNTER_ADDRESS;
/* We don't need to do anything with the checksum,
* because invalid checksum = 0 */
(*reset_counter) = 0;
return *reset_counter;
}
static int is_module_check_abort(void) {
char c;
/* Check for abort */
if (tstc()) {
c = getc();
if (c == 'a') {
return 1;
}
}
return 0;
}
static void wait_for_reset(int reset_counter)
{
int i;
/* 10s*reset_counter=10000ms*reset_counter */
for (i = 0;i < reset_counter*10000;i++) {
udelay(1000); /* 1ms delay */
if (is_module_check_abort()) {
break;
}
}
if (i == reset_counter*100) {
do_reset(NULL, 0, 0, NULL);
}
else {
printf("Do normal boot without modules\n");
}
}
/* Check if pcie link is detected for wlan modules, some WLE600 modules have
* even with the pcie_fixup patch a problem that they don't appear during the first
* boot */
static int do_wlan_fixup(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
int i, j;
#define MAX_RESET_COUNT 6
int reset_counter = get_reset_counter();
int ret;
pcie_fixup();
/* If we do a reboot it can happen that reset_counter is still 3, in this case
* we need to restart the counter */
if (reset_counter >= MAX_RESET_COUNT) {
reset_counter = reset_reset_counter();
}
puts("Checking PCIe WLAN modules (abort with a)\n");
/* Try it for at least one second */
for (i = 0; i < 100; i++) {
ret = 0x3f;
for (j = 0; j < 6; j++) {
if (check_pcie_slot_status(j)) ret &= ~(1 << j);
}
if (is_module_check_abort()) {
puts("Abort WLAN module check\n");
ret = 0;
}
if (ret == 0) break;
udelay(10000);
}
/* One module that should have link does not have link */
if (ret) {
/* Be carefull we can't simply increment the reset counter in c style
* because of the checksum calculation */
reset_counter = inc_reset_counter();
if (reset_counter >= MAX_RESET_COUNT) {
printf("WLAN Modules missing but reset counter reached %d, "
"continue\n", reset_counter);
return 0;
}
printf ("Not all WLAN Modules from the bd came up (ret=0x%08X), "
"reset (try %d of %d)\n", ret, reset_counter, MAX_RESET_COUNT);
printf ("Wait with reset for %ds (abort with a)\n",
reset_counter * 10);
wait_for_reset(reset_counter);
}
/* Reset the counter in case of success */
reset_reset_counter();
return 0;
}
U_BOOT_CMD(
nbhw_wlan_fixup, 1, 1, do_wlan_fixup,
"NBHW WLAN fixup for some Wifi modules",
"Checks if WLAN modules have been detected correctly, or resets\n"
);