Commit 6c05ceb4 authored by Alexander Potashev's avatar Alexander Potashev

RT73025. ea-lpc1788: fix hang-up in Ethernet driver after SYSRESET

To avoid hang-up of the Ethernet block after Cortex-M3 software reset
(SYSRESET), we need to reset the Ethernet PHY immediately before
performing the SYSRESET.

All new code added in this patch should be in placed in `.ramcode`,
because we might want to do a software reset after self-upgrade.

Since we cannot use `printf()` in functions that may be called during
self-upgrade (`printf()` is too big for `.ramcode`), the
`lpc178x_phy_init()` function cannot be easily used in
`lpc178x_phy_final_reset()`. Because of this, we use a pre-set PHY
address (`CONFIG_LPC178X_ETH_PHY_ADDR`) instead of doing automatic
PHY discovery that is usually done in `lpc178x_phy_init()`.

If Ethernet is not enabled in the U-Boot configuration file, we do not
perform the PHY reset.
This leads to a minor bug: if you install U-Boot without Ethernet
support into your board and do a self-upgrade to another build of
U-Boot with Ethernet support, the Ethernet driver will hang in the
latter U-Boot unless you have done a full reset (by pushing the SW1
button) after self-upgrade.
parent 10dd9188
......@@ -112,6 +112,18 @@ void __attribute__((section(".ramcode")))
__attribute__ ((long_call))
reset_cpu(ulong addr)
{
#ifdef CONFIG_SYS_LPC178X
/*
* We use the function `lpc178x_pre_reset_cpu()` to reset
* the Ethernet PHY.
*
* LPC178x/7x requires a PHY reset immediately before resetting
* the SoC, otherwise the Ethernet block hangs after software reset
* of the SoC.
*/
lpc178x_pre_reset_cpu();
#endif
/*
* Perform reset but keep priority group unchanged.
*/
......
......@@ -22,6 +22,7 @@
#include <common.h>
#include <asm/arch/lpc178x.h>
#include <asm/arch/lpc178x_eth.h>
#include "clock.h"
/*
......@@ -41,3 +42,23 @@ int print_cpuinfo(void)
return 0;
}
/*
* Prepare for software reset
*
* This function will be called from `reset_cpu()`, therefore this should also
* be in `.ramcode`.
*/
void __attribute__((section(".ramcode")))
__attribute__ ((long_call))
lpc178x_pre_reset_cpu(void)
{
#ifdef CONFIG_LPC178X_ETH
/*
* If we do not perform a PHY reset immediately before SYSRESET
* (the `cpu_reset()` call), then the Ethernet block will hang
* after this software reset.
*/
lpc178x_phy_final_reset();
#endif
}
......@@ -25,8 +25,13 @@
/*
* Enable or disable power on a peripheral device (timers, UARTs, USB, etc)
*
* This function will be called from `lpc178x_phy_final_reset()`, therefore this
* should also be in `.ramcode`.
*/
void lpc178x_periph_enable(u32 pconp_mask, int enable)
void __attribute__((section(".ramcode")))
__attribute__ ((long_call))
lpc178x_periph_enable(u32 pconp_mask, int enable)
{
if (enable)
LPC178X_SCC->pconp |= pconp_mask;
......
......@@ -644,14 +644,65 @@ static int lpc178x_phy_wait_busy(int timeout)
}
/*
* Write PHY
* This function will be called from `lpc178x_phy_final_reset()` which resides
* in `.ramcode`.
* If the compiler makes this funtion `inline`, nothing will be broken, because
* `lpc178x_phy_final_reset()` cannot become `inline` (it is not `static`.)
*/
static int lpc178x_phy_write(struct lpc178x_eth_dev *mac, u16 reg, u16 val)
static void __attribute__((section(".ramcode")))
__attribute__ ((long_call))
lpc178x_phy_write_nowait(u32 phy_adr, u16 reg, u16 val)
{
LPC178X_ETH->mcmd = 0;
LPC178X_ETH->madr = (mac->phy_adr << LPC178X_ETH_MADR_PA_BITS) |
LPC178X_ETH->madr = (phy_adr << LPC178X_ETH_MADR_PA_BITS) |
(reg << LPC178X_ETH_MADR_RA_BITS);
LPC178X_ETH->mwtd = val;
}
/*
* Final PHY reset before performing SYSRESET of SoC
*
* If we do not reset the Ethernet PHY immediately before resetting the SoC,
* the Ethernet block of the SoC will hang later and will not allow us to use
* Ethernet after SoC reset.
*
* This function will be called from `lpc178x_pre_reset_cpu()` which resides
* in `.ramcode`.
*/
void __attribute__((section(".ramcode")))
__attribute__ ((long_call))
lpc178x_phy_final_reset(void)
{
/*
* Enable power on the Ethernet block
*/
lpc178x_periph_enable(LPC178X_SCC_PCONP_PCENET_MSK, 1);
/*
* Minimal MAC initialization
*/
LPC178X_ETH->mcfg = (CONFIG_LPC178X_ETH_DIV_SEL << LPC178X_ETH_MCFG_CS_BITS);
LPC178X_ETH->mac1 = 0;
LPC178X_ETH->mac2 = 0;
/*
* Reset PHY and wait for write completion
*/
lpc178x_phy_write_nowait(CONFIG_LPC178X_ETH_PHY_ADDR, PHY_BMCR, PHY_BMCR_RESET);
while (LPC178X_ETH->mind & LPC178X_ETH_MIND_BUSY_MSK);
/*
* Disable power on the Ethernet block
*/
lpc178x_periph_enable(LPC178X_SCC_PCONP_PCENET_MSK, 0);
}
/*
* Write PHY
*/
static int lpc178x_phy_write(struct lpc178x_eth_dev *mac, u16 reg, u16 val)
{
lpc178x_phy_write_nowait(mac->phy_adr, reg, val);
return lpc178x_phy_wait_busy(LPC178X_PHY_WRITE_TIMEOUT);
}
......
......@@ -134,8 +134,13 @@ struct lpc178x_scc_regs {
/*
* Enable or disable power on a peripheral device (timers, UARTs, USB, etc)
*
* This function will be called from `lpc178x_pre_reset_cpu()`, therefore this
* should also be in `.ramcode`.
*/
extern void lpc178x_periph_enable(u32 pconp_mask, int enable);
void __attribute__((section(".ramcode")))
__attribute__ ((long_call))
lpc178x_periph_enable(u32 pconp_mask, int enable);
/*
* Clocks enumeration
......@@ -159,4 +164,14 @@ enum clock {
*/
unsigned long clock_get(enum clock clck);
/*
* Prepare for software reset
*
* This function will be called from `reset_cpu()`, therefore this should also
* be in `.ramcode`.
*/
void __attribute__((section(".ramcode")))
__attribute__ ((long_call))
lpc178x_pre_reset_cpu(void);
#endif /* _MACH_LPC178X_H_ */
/*
* (C) Copyright 2011
*
* Alexander Potashev, Emcraft Systems, aspotashev@emcraft.com
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*/
#ifndef _LPC178X_ETH_H_
#define _LPC178X_ETH_H_
/*
* Final PHY reset before performing SYSRESET of SoC
*/
void __attribute__((section(".ramcode")))
__attribute__ ((long_call))
lpc178x_phy_final_reset(void);
#endif /* _LPC178X_ETH_H_ */
......@@ -260,6 +260,11 @@
#define CONFIG_LPC178X_ETH
#define CONFIG_LPC178X_ENET_USE_PHY_RMII
#define CONFIG_LPC178X_ETH_DIV_SEL 7 /* HCLK/28 */
/*
* Used only for the final PHY reset, see `lpc178x_phy_final_reset()`.
* For other code, we use automatic PHY discovery.
*/
#define CONFIG_LPC178X_ETH_PHY_ADDR 1
/*
* Ethernet RX buffers are malloced from the internal SRAM (more precisely,
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment