This repository has been archived on 2025-02-01. You can view files and clone it, but cannot push or open issues or pull requests.
reprapfirmware-dc42/Libraries/EMAC/ethernet_phy.c
David Crocker ceae4df7a1 Fix jerky SD card printing when no network cable
Fixed problem whereby gcode files printed from SD card that used lots of
small fast moves in sequence would print jerkily if a network cable had
not been connected since reset.
Fixed problem whereby object height and filament needed were not
retrieved from the SD card file to be printed if the filename contained
spaces.
Added support for deprecated M190 command, because slic3r appears to
generate it sometimes
Added support for M564 command to allow movement without limits, copied
from RepRapPro dev branch
2014-05-05 18:47:22 +01:00

532 lines
13 KiB
C

/**
* \file
*
* \brief API driver for KSZ8051RNL PHY based on driver for DM9161A PHY PHY.
* Adapted from the ethernet_phy.c file provided as part of LWIP under the license below:
* Only designed to work with the KSZ8051RNL PHY, not yet fully tested.
*
* Copyright (c) 2012 Atmel Corporation. All rights reserved.
*
* \asf_license_start
*
* \page License
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. The name of Atmel may not be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* 4. This software may only be redistributed and used in connection with an
* Atmel microcontroller product.
*
* THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
* EXPRESSLY AND SPECIFICALLY DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* \asf_license_stop
*
*/
#include "ethernet_phy.h"
#include "rmii.h"
/// @cond 0
/**INDENT-OFF**/
#ifdef __cplusplus
extern "C" {
#endif
/**INDENT-ON**/
/// @endcond
/**
* \defgroup KSZ8051RNL_ethernet_phy_group PHY component (KSZ8051RNL)
*
* Driver for the KSZ8051RNL component. This driver provides access to the main
* features of the PHY.
*
* \section dependencies Dependencies
* This driver depends on the following modules:
* - \ref emac_group Ethernet Media Access Controller (EMAC) module.
*
* @{
*/
/* Max PHY number */
#define ETH_PHY_MAX_ADDR 8
/* Ethernet PHY operation max retry count */
#define ETH_PHY_RETRY_MAX 1000000
/* Ethernet PHY operation timeout */
#define ETH_PHY_TIMEOUT 10
//*****************************AB
/**
* \brief Read a Register of the PHY.
*
* \param p_emac Pointer to the EMAC instance.
* \param uc_phy_addr PHY address.
* \param ul_phy_reg PHY Register to be read
*
* Return Register contents
*/
uint8_t ethernet_phy_read_register(Emac *p_emac, uint8_t uc_phy_addr, uint32_t ul_phy_reg,uint32_t *p_ul_reg_cont)
{
uint32_t ul_phy_register;
uint8_t uc_phy_address, uc_speed, uc_fd;
uint8_t uc_rc = EMAC_TIMEOUT;
emac_enable_management(p_emac, true);
uc_phy_address = uc_phy_addr;
ul_phy_register = ul_phy_reg;
uc_rc = emac_phy_read(p_emac, uc_phy_address, ul_phy_reg, p_ul_reg_cont);
if (uc_rc != EMAC_OK) {
/* Disable PHY management and start the EMAC transfer */
emac_enable_management(p_emac, false);
return uc_rc;
}
/* Start the EMAC transfers */
emac_enable_management(p_emac, false);
return uc_rc;
}
//*****************************AB
#if 0 // this function is unused in the Duet firmware
/**
* \brief Find a valid PHY Address ( from addrStart to 31 ).
*
* \param p_emac Pointer to the EMAC instance.
* \param uc_phy_addr PHY address.
* \param uc_start_addr Start address of the PHY to be searched.
*
* \return 0xFF when no valid PHY address is found.
*/
static uint8_t ethernet_phy_find_valid(Emac *p_emac, uint8_t uc_phy_addr,
uint8_t addrStart)
{
uint32_t ul_value = 0;
uint8_t uc_rc;
uint8_t uc_cnt;
uint8_t uc_phy_address = uc_phy_addr;
emac_enable_management(p_emac, true);
/* Check the current PHY address */
uc_rc = uc_phy_address;
if (emac_phy_read(p_emac, uc_phy_addr, MII_PHYID1, &ul_value) != EMAC_OK) {
}
/* Find another one */
if (ul_value != MII_OUI1) {
uc_rc = 0xFF;
for (uc_cnt = addrStart; uc_cnt <= ETH_PHY_MAX_ADDR; uc_cnt++) {
uc_phy_address = (uc_phy_address + 1) & 0x1F;
emac_phy_read(p_emac, uc_phy_address, MII_PHYID1, &ul_value);
if (ul_value == MII_OUI1) {
uc_rc = uc_phy_address;
break;
}
}
}
emac_enable_management(p_emac, false);
if (uc_rc != 0xFF) {
emac_phy_read(p_emac, uc_phy_address, MII_OMSS, &ul_value);
}
return uc_rc;
}
#endif
/**
* \brief Perform a HW initialization to the PHY ( via RSTC ) and set up clocks.
*
* This should be called only once to initialize the PHY pre-settings.
* The PHY address is the reset status of CRS, RXD[3:0] (the emacPins' pullups).
* The COL pin is used to select MII mode on reset (pulled up for Reduced MII).
* The RXDV pin is used to select test mode on reset (pulled up for test mode).
* The above pins should be predefined for corresponding settings in resetPins.
* The EMAC peripheral pins are configured after the reset is done.
*
* \param p_emac Pointer to the EMAC instance.
* \param uc_phy_addr PHY address.
* \param ul_mck EMAC MCK.
*
* Return EMAC_OK if successfully, EMAC_TIMEOUT if timeout.
*/
uint8_t ethernet_phy_init(Emac *p_emac, uint8_t uc_phy_addr, uint32_t mck)
{
uint8_t uc_rc = EMAC_TIMEOUT;
uint8_t uc_phy;
/* Configure EMAC runtime clock */
uc_rc = emac_set_clock(p_emac, mck);
if (uc_rc != EMAC_OK) {
return uc_rc;
}
/* Check PHY Address - Not used as we know the PHY address*/
/*
uc_phy = ethernet_phy_find_valid(p_emac, uc_phy_addr, 0);
if (uc_phy == 0xFF) {
return uc_phy;
}
if (uc_phy != uc_phy_addr) {
ethernet_phy_reset(p_emac, uc_phy_addr);
}
*/
return uc_rc;
}
/**
* \brief Get the Link & speed settings, and automatically set up the EMAC with the
* settings.
*
* \param p_emac Pointer to the EMAC instance.
* \param uc_phy_addr PHY address.
* \param uc_apply_setting_flag Set to 0 to not apply the PHY configurations, else to apply.
*
* Return EMAC_OK if successfully, EMAC_TIMEOUT if timeout.
*/
uint8_t ethernet_phy_set_link(Emac *p_emac, uint8_t uc_phy_addr,
uint8_t uc_apply_setting_flag)
{
uint32_t ul_stat1;
uint32_t ul_stat2;
uint8_t uc_phy_address, uc_speed, uc_fd;
uint8_t uc_rc = EMAC_TIMEOUT;
emac_enable_management(p_emac, true);
uc_phy_address = uc_phy_addr;
uc_rc = emac_phy_read(p_emac, uc_phy_address, MII_BMSR, &ul_stat1);
if (uc_rc != EMAC_OK) {
/* Disable PHY management and start the EMAC transfer */
emac_enable_management(p_emac, false);
return uc_rc;
}
if ((ul_stat1 & MII_LINK_STATUS) == 0) {
/* Disable PHY management and start the EMAC transfer */
emac_enable_management(p_emac, false);
return EMAC_INVALID;
}
if (uc_apply_setting_flag == 0) {
/* Disable PHY management and start the EMAC transfer */
emac_enable_management(p_emac, false);
return uc_rc;
}
// Re-configure Link speed
uc_rc = emac_phy_read(p_emac, uc_phy_address, MII_PC1, &ul_stat2);
if (uc_rc != EMAC_OK) {
//Disable PHY management and start the EMAC transfer
emac_enable_management(p_emac, false);
return uc_rc;
}
if ((ul_stat1 & MII_100BASE_TX_FD) && (ul_stat2 & MII_OMI_100_FD)) {
// Set EMAC for 100BaseTX and Full Duplex
uc_speed = true;
uc_fd = true;
}
else if ((ul_stat1 & MII_10BASE_T_FD) && (ul_stat2 & MII_OMI_10_FD)) {
// Set MII for 10BaseT and Full Duplex
uc_speed = false;
uc_fd = true;
}
else if ((ul_stat1 & MII_100BASE_TX_HD) && (ul_stat2 & MII_OMI_100_HD)) {
// Set MII for 100BaseTX and Half Duplex
uc_speed = true;
uc_fd = false;
}
else if ((ul_stat1 & MII_10BASE_T_HD) && (ul_stat2 & MII_OMI_10_HD)) {
// Set MII for 10BaseT and Half Duplex
uc_speed = false;
uc_fd = false;
}
emac_set_speed(p_emac, uc_speed);
emac_enable_full_duplex(p_emac, uc_fd);
/* Start the EMAC transfers */
emac_enable_management(p_emac, false);
return uc_rc;
}
/**
* \brief Issue an auto negotiation of the PHY.
*
* \param p_emac Pointer to the EMAC instance.
* \param uc_phy_addr PHY address.
*
* Return EMAC_OK if successfully, EMAC_TIMEOUT if timeout (in which case we can keep calling this).
* This has been refactored as a state machine, because we must not spend much time calling this when
* no network cable is connected, otherwise it slows down printing.
*/
uint8_t ethernet_phy_auto_negotiate(Emac *p_emac, uint8_t uc_phy_addr)
{
static uint8_t state = 0;
static uint8_t ul_retry_count;
static uint32_t ul_value;
static uint32_t ul_phy_anar;
switch(state)
{
case 0:
{
emac_enable_management(p_emac, true);
/* Set up control register */
uint8_t uc_rc = emac_phy_read(p_emac, uc_phy_addr, MII_BMCR, &ul_value);
if (uc_rc != EMAC_OK)
{
emac_enable_management(p_emac, false);
state = 0;
return uc_rc;
}
++state;
}
break;
case 1:
{
ul_value &= ~MII_AUTONEG; /* Remove auto-negotiation enable */
ul_value &= ~(MII_LOOPBACK | MII_POWER_DOWN);
ul_value |= MII_ISOLATE; /* Electrically isolate PHY */
uint8_t uc_rc = emac_phy_write(p_emac, uc_phy_addr, MII_BMCR, ul_value);
if (uc_rc != EMAC_OK)
{
emac_enable_management(p_emac, false);
state = 0;
return uc_rc;
}
++state;
}
break;
case 2:
{
/*
* Set the Auto_negotiation Advertisement Register.
* MII advertising for Next page.
* 100BaseTxFD and HD, 10BaseTFD and HD, IEEE 802.3.
*/
ul_phy_anar = MII_TX_FDX | MII_TX_HDX | MII_10_FDX | MII_10_HDX | MII_AN_IEEE_802_3;
uint8_t uc_rc = emac_phy_write(p_emac, uc_phy_addr, MII_ANAR, ul_phy_anar);
if (uc_rc != EMAC_OK)
{
emac_enable_management(p_emac, false);
state = 0;
return uc_rc;
}
++state;
}
break;
case 3:
{
/* Read & modify control register */
uint8_t uc_rc = emac_phy_read(p_emac, uc_phy_addr, MII_BMCR, &ul_value);
if (uc_rc != EMAC_OK)
{
emac_enable_management(p_emac, false);
state = 0;
return uc_rc;
}
++state;
}
break;
case 4:
{
ul_value |= MII_SPEED_SELECT | MII_AUTONEG | MII_DUPLEX_MODE;
uint8_t uc_rc = emac_phy_write(p_emac, uc_phy_addr, MII_BMCR, ul_value);
if (uc_rc != EMAC_OK)
{
emac_enable_management(p_emac, false);
state = 0;
return uc_rc;
}
++state;
}
break;
case 5:
{
/* Restart auto negotiation */
ul_value |= MII_RESTART_AUTONEG;
ul_value &= ~MII_ISOLATE;
uint8_t uc_rc = emac_phy_write(p_emac, uc_phy_addr, MII_BMCR, ul_value);
if (uc_rc != EMAC_OK)
{
emac_enable_management(p_emac, false);
state = 0;
return uc_rc;
}
ul_retry_count = 0;
++state;
}
break;
case 6:
{
/* Check if auto negotiation is completed */
uint8_t uc_rc = emac_phy_read(p_emac, uc_phy_addr, MII_BMSR, &ul_value);
if (uc_rc != EMAC_OK)
{
emac_enable_management(p_emac, false);
state = 0;
return uc_rc;
}
/* Done successfully */
if (!(ul_value & MII_AUTONEG_COMP))
{
++ul_retry_count;
if (ul_retry_count >= 10000) // timeout check
{
state = 0; // start again
}
return EMAC_TIMEOUT;
}
++state;
}
break;
case 7:
{
uint32_t ul_phy_analpar;
/* Get the auto negotiate link partner base page */
uint8_t uc_rc = emac_phy_read(p_emac, uc_phy_addr, MII_ANLPAR, &ul_phy_analpar);
if (uc_rc != EMAC_OK)
{
emac_enable_management(p_emac, false);
state = 0;
return uc_rc;
}
// The rest only happens once. so we do it all in one go
uint8_t uc_fd = false;
uint8_t uc_speed = false;
/* Set up the EMAC link speed */
if ((ul_phy_anar & ul_phy_analpar) & MII_TX_FDX)
{
/* Set MII for 100BaseTX and Full Duplex */
uc_speed = true;
uc_fd = true;
}
else if ((ul_phy_anar & ul_phy_analpar) & MII_10_FDX)
{
/* Set MII for 10BaseT and Full Duplex */
uc_speed = false;
uc_fd = true;
}
else if ((ul_phy_anar & ul_phy_analpar) & MII_TX_HDX)
{
/* Set MII for 100BaseTX and half Duplex */
uc_speed = true;
uc_fd = false;
}
else if ((ul_phy_anar & ul_phy_analpar) & MII_10_HDX)
{
/* Set MII for 10BaseT and half Duplex */
uc_speed = false;
uc_fd = false;
}
emac_set_speed(p_emac, uc_speed);
emac_enable_full_duplex(p_emac, uc_fd);
emac_enable_rmii(p_emac, ETH_PHY_MODE);
emac_enable_transceiver_clock(p_emac, true);
emac_enable_management(p_emac, false);
state = 0; // in case we get called again
return EMAC_OK;
}
}
return EMAC_TIMEOUT; // this just means that we needs to be called again
}
/**
* \brief Issue a SW reset to reset all registers of the PHY.
*
* \param p_emac Pointer to the EMAC instance.
* \param uc_phy_addr PHY address.
*
* \Return EMAC_OK if successfully, EMAC_TIMEOUT if timeout.
*/
uint8_t ethernet_phy_reset(Emac *p_emac, uint8_t uc_phy_addr)
{
uint32_t ul_bmcr = MII_RESET;
uint8_t uc_phy_address = uc_phy_addr;
uint32_t ul_timeout = ETH_PHY_TIMEOUT;
uint8_t uc_rc = EMAC_TIMEOUT;
emac_enable_management(p_emac, true);
ul_bmcr = MII_RESET;
emac_phy_write(p_emac, uc_phy_address, MII_BMCR, ul_bmcr);
do {
emac_phy_read(p_emac, uc_phy_address, MII_BMCR, &ul_bmcr);
ul_timeout--;
} while ((ul_bmcr & MII_RESET) && ul_timeout);
emac_enable_management(p_emac, false);
if (!ul_timeout) {
uc_rc = EMAC_OK;
}
return (uc_rc);
}
/// @cond 0
/**INDENT-OFF**/
#ifdef __cplusplus
}
#endif
/**INDENT-ON**/
/// @endcond
/**
* \}
*/