u-boot/drivers/net/ti/cpsw_mdio.c
Nishanth Menon a94a4071d4 tree-wide: Replace http:// link with https:// link for ti.com
Replace instances of http://www.ti.com with https://www.ti.com

Signed-off-by: Nishanth Menon <nm@ti.com>
2023-11-10 11:01:50 -05:00

454 lines
10 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/*
* CPSW MDIO generic driver for TI AMxx/K2x/EMAC devices.
*
* Copyright (C) 2018 Texas Instruments Incorporated - https://www.ti.com/
*/
#include <common.h>
#include <log.h>
#include <malloc.h>
#include <asm/io.h>
#include <miiphy.h>
#include <wait_bit.h>
#include <linux/bitops.h>
#include <linux/delay.h>
struct cpsw_mdio_regs {
u32 version;
u32 control;
#define CONTROL_IDLE BIT(31)
#define CONTROL_ENABLE BIT(30)
#define CONTROL_FAULT BIT(19)
#define CONTROL_FAULT_ENABLE BIT(18)
#define CONTROL_DIV_MASK GENMASK(15, 0)
#define MDIO_MAN_MDCLK_O BIT(2)
#define MDIO_MAN_OE BIT(1)
#define MDIO_MAN_PIN BIT(0)
#define MDIO_MANUALMODE BIT(31)
u32 alive;
u32 link;
u32 linkintraw;
u32 linkintmasked;
u32 __reserved_0[2];
u32 userintraw;
u32 userintmasked;
u32 userintmaskset;
u32 userintmaskclr;
u32 manualif;
u32 poll;
u32 __reserved_1[18];
struct {
u32 access;
u32 physel;
#define USERACCESS_GO BIT(31)
#define USERACCESS_WRITE BIT(30)
#define USERACCESS_ACK BIT(29)
#define USERACCESS_READ (0)
#define USERACCESS_PHY_REG_SHIFT (21)
#define USERACCESS_PHY_ADDR_SHIFT (16)
#define USERACCESS_DATA GENMASK(15, 0)
} user[2];
};
#define CPSW_MDIO_DIV_DEF 0xff
#define PHY_REG_MASK 0x1f
#define PHY_ID_MASK 0x1f
#define MDIO_BITRANGE 0x8000
#define C22_READ_PATTERN 0x6
#define C22_WRITE_PATTERN 0x5
#define C22_BITRANGE 0x8
#define PHY_BITRANGE 0x10
#define PHY_DATA_BITRANGE 0x8000
/*
* This timeout definition is a worst-case ultra defensive measure against
* unexpected controller lock ups. Ideally, we should never ever hit this
* scenario in practice.
*/
#define CPSW_MDIO_TIMEOUT 100 /* msecs */
enum cpsw_mdio_manual {
MDIO_PIN = 0,
MDIO_OE,
MDIO_MDCLK,
};
struct cpsw_mdio {
struct cpsw_mdio_regs *regs;
struct mii_dev *bus;
int div;
};
static void cpsw_mdio_disable(struct cpsw_mdio *mdio)
{
u32 reg;
/* Disable MDIO state machine */
reg = readl(&mdio->regs->control);
reg &= ~CONTROL_ENABLE;
writel(reg, &mdio->regs->control);
}
static void cpsw_mdio_enable_manual_mode(struct cpsw_mdio *mdio)
{
u32 reg;
/* set manual mode */
reg = readl(&mdio->regs->poll);
reg |= MDIO_MANUALMODE;
writel(reg, &mdio->regs->poll);
}
static void cpsw_mdio_sw_set_bit(struct cpsw_mdio *mdio,
enum cpsw_mdio_manual bit)
{
u32 reg;
reg = readl(&mdio->regs->manualif);
switch (bit) {
case MDIO_OE:
reg |= MDIO_MAN_OE;
writel(reg, &mdio->regs->manualif);
break;
case MDIO_PIN:
reg |= MDIO_MAN_PIN;
writel(reg, &mdio->regs->manualif);
break;
case MDIO_MDCLK:
reg |= MDIO_MAN_MDCLK_O;
writel(reg, &mdio->regs->manualif);
break;
default:
break;
};
}
static void cpsw_mdio_sw_clr_bit(struct cpsw_mdio *mdio,
enum cpsw_mdio_manual bit)
{
u32 reg;
reg = readl(&mdio->regs->manualif);
switch (bit) {
case MDIO_OE:
reg &= ~MDIO_MAN_OE;
writel(reg, &mdio->regs->manualif);
break;
case MDIO_PIN:
reg &= ~MDIO_MAN_PIN;
writel(reg, &mdio->regs->manualif);
break;
case MDIO_MDCLK:
reg = readl(&mdio->regs->manualif);
reg &= ~MDIO_MAN_MDCLK_O;
writel(reg, &mdio->regs->manualif);
break;
default:
break;
};
}
static int cpsw_mdio_test_man_bit(struct cpsw_mdio *mdio,
enum cpsw_mdio_manual bit)
{
u32 reg;
reg = readl(&mdio->regs->manualif);
return test_bit(bit, &reg);
}
static void cpsw_mdio_toggle_man_bit(struct cpsw_mdio *mdio,
enum cpsw_mdio_manual bit)
{
cpsw_mdio_sw_clr_bit(mdio, bit);
cpsw_mdio_sw_set_bit(mdio, bit);
}
static void cpsw_mdio_man_send_pattern(struct cpsw_mdio *mdio,
u32 bitrange, u32 val)
{
u32 i;
for (i = bitrange; i; i = i >> 1) {
if (i & val)
cpsw_mdio_sw_set_bit(mdio, MDIO_PIN);
else
cpsw_mdio_sw_clr_bit(mdio, MDIO_PIN);
cpsw_mdio_toggle_man_bit(mdio, MDIO_MDCLK);
}
}
static void cpsw_mdio_sw_preamble(struct cpsw_mdio *mdio)
{
u32 i;
cpsw_mdio_sw_clr_bit(mdio, MDIO_OE);
cpsw_mdio_sw_clr_bit(mdio, MDIO_MDCLK);
cpsw_mdio_sw_clr_bit(mdio, MDIO_MDCLK);
cpsw_mdio_sw_clr_bit(mdio, MDIO_MDCLK);
cpsw_mdio_sw_set_bit(mdio, MDIO_MDCLK);
for (i = 0; i < 32; i++) {
cpsw_mdio_sw_clr_bit(mdio, MDIO_MDCLK);
cpsw_mdio_sw_clr_bit(mdio, MDIO_MDCLK);
cpsw_mdio_sw_clr_bit(mdio, MDIO_MDCLK);
cpsw_mdio_toggle_man_bit(mdio, MDIO_MDCLK);
}
}
static int cpsw_mdio_sw_read(struct mii_dev *bus, int phy_id,
int dev_addr, int phy_reg)
{
struct cpsw_mdio *mdio = bus->priv;
u32 reg, i;
u8 ack;
if (phy_reg & ~PHY_REG_MASK || phy_id & ~PHY_ID_MASK)
return -EINVAL;
cpsw_mdio_disable(mdio);
cpsw_mdio_enable_manual_mode(mdio);
cpsw_mdio_sw_preamble(mdio);
cpsw_mdio_sw_clr_bit(mdio, MDIO_MDCLK);
cpsw_mdio_sw_set_bit(mdio, MDIO_OE);
/* Issue clause 22 MII read function {0,1,1,0} */
cpsw_mdio_man_send_pattern(mdio, C22_BITRANGE, C22_READ_PATTERN);
/* Send the device number MSB first */
cpsw_mdio_man_send_pattern(mdio, PHY_BITRANGE, phy_id);
/* Send the register number MSB first */
cpsw_mdio_man_send_pattern(mdio, PHY_BITRANGE, phy_reg);
/* Send turn around cycles */
cpsw_mdio_sw_clr_bit(mdio, MDIO_OE);
cpsw_mdio_toggle_man_bit(mdio, MDIO_MDCLK);
ack = cpsw_mdio_test_man_bit(mdio, MDIO_PIN);
cpsw_mdio_toggle_man_bit(mdio, MDIO_MDCLK);
reg = 0;
if (ack == 0) {
for (i = MDIO_BITRANGE; i; i = i >> 1) {
if (cpsw_mdio_test_man_bit(mdio, MDIO_PIN))
reg |= i;
cpsw_mdio_toggle_man_bit(mdio, MDIO_MDCLK);
}
} else {
for (i = MDIO_BITRANGE; i; i = i >> 1)
cpsw_mdio_toggle_man_bit(mdio, MDIO_MDCLK);
reg = 0xFFFF;
}
cpsw_mdio_sw_clr_bit(mdio, MDIO_MDCLK);
cpsw_mdio_sw_set_bit(mdio, MDIO_MDCLK);
cpsw_mdio_sw_set_bit(mdio, MDIO_MDCLK);
cpsw_mdio_toggle_man_bit(mdio, MDIO_MDCLK);
return reg;
}
static int cpsw_mdio_sw_write(struct mii_dev *bus, int phy_id,
int dev_addr, int phy_reg, u16 phy_data)
{
struct cpsw_mdio *mdio = bus->priv;
if ((phy_reg & ~PHY_REG_MASK) || (phy_id & ~PHY_ID_MASK))
return -EINVAL;
cpsw_mdio_disable(mdio);
cpsw_mdio_enable_manual_mode(mdio);
cpsw_mdio_sw_preamble(mdio);
cpsw_mdio_sw_clr_bit(mdio, MDIO_MDCLK);
cpsw_mdio_sw_set_bit(mdio, MDIO_OE);
/* Issue clause 22 MII write function {0,1,0,1} */
cpsw_mdio_man_send_pattern(mdio, C22_BITRANGE, C22_WRITE_PATTERN);
/* Send the device number MSB first */
cpsw_mdio_man_send_pattern(mdio, PHY_BITRANGE, phy_id);
/* Send the register number MSB first */
cpsw_mdio_man_send_pattern(mdio, PHY_BITRANGE, phy_reg);
/* set turn-around cycles */
cpsw_mdio_sw_set_bit(mdio, MDIO_PIN);
cpsw_mdio_toggle_man_bit(mdio, MDIO_MDCLK);
cpsw_mdio_sw_clr_bit(mdio, MDIO_PIN);
cpsw_mdio_toggle_man_bit(mdio, MDIO_MDCLK);
/* Send Register data MSB first */
cpsw_mdio_man_send_pattern(mdio, PHY_DATA_BITRANGE, phy_data);
cpsw_mdio_sw_clr_bit(mdio, MDIO_OE);
cpsw_mdio_sw_clr_bit(mdio, MDIO_MDCLK);
cpsw_mdio_sw_clr_bit(mdio, MDIO_MDCLK);
cpsw_mdio_sw_clr_bit(mdio, MDIO_MDCLK);
cpsw_mdio_toggle_man_bit(mdio, MDIO_MDCLK);
return 0;
}
/* wait until hardware is ready for another user access */
static int cpsw_mdio_wait_for_user_access(struct cpsw_mdio *mdio)
{
return wait_for_bit_le32(&mdio->regs->user[0].access,
USERACCESS_GO, false,
CPSW_MDIO_TIMEOUT, false);
}
static int cpsw_mdio_read(struct mii_dev *bus, int phy_id,
int dev_addr, int phy_reg)
{
struct cpsw_mdio *mdio = bus->priv;
int data, ret;
u32 reg;
if (phy_reg & ~PHY_REG_MASK || phy_id & ~PHY_ID_MASK)
return -EINVAL;
ret = cpsw_mdio_wait_for_user_access(mdio);
if (ret)
return ret;
reg = (USERACCESS_GO | USERACCESS_READ |
(phy_reg << USERACCESS_PHY_REG_SHIFT) |
(phy_id << USERACCESS_PHY_ADDR_SHIFT));
writel(reg, &mdio->regs->user[0].access);
ret = cpsw_mdio_wait_for_user_access(mdio);
if (ret)
return ret;
reg = readl(&mdio->regs->user[0].access);
data = (reg & USERACCESS_ACK) ? (reg & USERACCESS_DATA) : -1;
return data;
}
static int cpsw_mdio_write(struct mii_dev *bus, int phy_id, int dev_addr,
int phy_reg, u16 data)
{
struct cpsw_mdio *mdio = bus->priv;
u32 reg;
int ret;
if (phy_reg & ~PHY_REG_MASK || phy_id & ~PHY_ID_MASK)
return -EINVAL;
ret = cpsw_mdio_wait_for_user_access(mdio);
if (ret)
return ret;
reg = (USERACCESS_GO | USERACCESS_WRITE |
(phy_reg << USERACCESS_PHY_REG_SHIFT) |
(phy_id << USERACCESS_PHY_ADDR_SHIFT) |
(data & USERACCESS_DATA));
writel(reg, &mdio->regs->user[0].access);
return cpsw_mdio_wait_for_user_access(mdio);
}
u32 cpsw_mdio_get_alive(struct mii_dev *bus)
{
struct cpsw_mdio *mdio = bus->priv;
u32 val;
val = readl(&mdio->regs->alive);
return val & GENMASK(7, 0);
}
struct mii_dev *cpsw_mdio_init(const char *name, phys_addr_t mdio_base,
u32 bus_freq, int fck_freq, bool manual_mode)
{
struct cpsw_mdio *cpsw_mdio;
int ret;
cpsw_mdio = calloc(1, sizeof(*cpsw_mdio));
if (!cpsw_mdio) {
debug("failed to alloc cpsw_mdio\n");
return NULL;
}
cpsw_mdio->bus = mdio_alloc();
if (!cpsw_mdio->bus) {
debug("failed to alloc mii bus\n");
free(cpsw_mdio);
return NULL;
}
cpsw_mdio->regs = (struct cpsw_mdio_regs *)(uintptr_t)mdio_base;
if (!bus_freq || !fck_freq)
cpsw_mdio->div = CPSW_MDIO_DIV_DEF;
else
cpsw_mdio->div = (fck_freq / bus_freq) - 1;
cpsw_mdio->div &= CONTROL_DIV_MASK;
/* set enable and clock divider */
writel(cpsw_mdio->div | CONTROL_ENABLE | CONTROL_FAULT |
CONTROL_FAULT_ENABLE, &cpsw_mdio->regs->control);
wait_for_bit_le32(&cpsw_mdio->regs->control,
CONTROL_IDLE, false, CPSW_MDIO_TIMEOUT, true);
/*
* wait for scan logic to settle:
* the scan time consists of (a) a large fixed component, and (b) a
* small component that varies with the mii bus frequency. These
* were estimated using measurements at 1.1 and 2.2 MHz on tnetv107x
* silicon. Since the effect of (b) was found to be largely
* negligible, we keep things simple here.
*/
mdelay(1);
if (manual_mode) {
cpsw_mdio->bus->read = cpsw_mdio_sw_read;
cpsw_mdio->bus->write = cpsw_mdio_sw_write;
} else {
cpsw_mdio->bus->read = cpsw_mdio_read;
cpsw_mdio->bus->write = cpsw_mdio_write;
}
cpsw_mdio->bus->priv = cpsw_mdio;
snprintf(cpsw_mdio->bus->name, sizeof(cpsw_mdio->bus->name), name);
ret = mdio_register(cpsw_mdio->bus);
if (ret < 0) {
debug("failed to register mii bus\n");
goto free_bus;
}
return cpsw_mdio->bus;
free_bus:
mdio_free(cpsw_mdio->bus);
free(cpsw_mdio);
return NULL;
}
void cpsw_mdio_free(struct mii_dev *bus)
{
struct cpsw_mdio *mdio = bus->priv;
u32 reg;
/* disable mdio */
reg = readl(&mdio->regs->control);
reg &= ~CONTROL_ENABLE;
writel(reg, &mdio->regs->control);
mdio_unregister(bus);
mdio_free(bus);
free(mdio);
}