291 lines
7.3 KiB
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"
|
|
);
|
|
|