u-boot-2016/drivers/net/ipq_common/ipq_mdio.c
Selvam Sathappan Periakaruppan 18d2b93ab3 drivers: net: Add support for QCA8084 PHY
This patch adds initial support for qca8084 PHY
which is based on qca8081 PHY.

qca8084 PHY has support for 4x2.5G.

Change-Id: Ic767c19fad050e5ee9a97ad7fa50c1b6b27893dd
Signed-off-by: Selvam Sathappan Periakaruppan <quic_speriaka@quicinc.com>
2022-05-25 04:08:31 -07:00

465 lines
10 KiB
C

/*
* Copyright (c) 2015-2017, The Linux Foundation. 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 version 2 and
* only version 2 as published by the Free Software Foundation.
* 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.
*/
#include <linux/bitops.h>
#include <linux/compat.h>
#include <common.h>
#include <command.h>
#include <miiphy.h>
#include <phy.h>
#include <asm/io.h>
#include <errno.h>
#include "ipq_mdio.h"
#ifdef DEBUG
#define pr_debug(fmt, args...) printf(fmt, ##args);
#else
#define pr_debug(fmt, args...)
#endif
struct ipq_mdio_data {
struct mii_bus *bus;
int phy_irq[PHY_MAX_ADDR];
};
static int ipq_mdio_wait_busy(void)
{
int i;
u32 busy;
for (i = 0; i < IPQ_MDIO_RETRY; i++) {
busy = readl(IPQ_MDIO_BASE +
MDIO_CTRL_4_REG) &
MDIO_CTRL_4_ACCESS_BUSY;
if (!busy)
return 0;
}
printf("%s: MDIO operation timed out\n",
__func__);
return -ETIMEDOUT;
}
int ipq_mdio_write(int mii_id, int regnum, u16 value)
{
u32 cmd;
if (regnum & MII_ADDR_C45) {
unsigned int mmd = (regnum >> 16) & 0x1F;
unsigned int reg = regnum & 0xFFFF;
writel(CTRL_0_REG_C45_DEFAULT_VALUE,
IPQ_MDIO_BASE + MDIO_CTRL_0_REG);
/* Issue the phy address and reg */
writel((mii_id << 8) | mmd,
IPQ_MDIO_BASE + MDIO_CTRL_1_REG);
writel(reg, IPQ_MDIO_BASE + MDIO_CTRL_2_REG);
/* issue read command */
cmd = MDIO_CTRL_4_ACCESS_START | MDIO_CTRL_4_ACCESS_CODE_C45_ADDR;
writel(cmd, IPQ_MDIO_BASE + MDIO_CTRL_4_REG);
if (ipq_mdio_wait_busy())
return -ETIMEDOUT;
} else {
writel(CTRL_0_REG_DEFAULT_VALUE,
IPQ_MDIO_BASE + MDIO_CTRL_0_REG);
/* Issue the phy addreass and reg */
writel((mii_id << 8 | regnum),
IPQ_MDIO_BASE + MDIO_CTRL_1_REG);
}
/* Issue a write data */
writel(value, IPQ_MDIO_BASE + MDIO_CTRL_2_REG);
if (regnum & MII_ADDR_C45) {
cmd = MDIO_CTRL_4_ACCESS_START | MDIO_CTRL_4_ACCESS_CODE_C45_WRITE ;
} else {
cmd = MDIO_CTRL_4_ACCESS_START | MDIO_CTRL_4_ACCESS_CODE_WRITE ;
}
writel(cmd, IPQ_MDIO_BASE + MDIO_CTRL_4_REG);
/* Wait for write complete */
if (ipq_mdio_wait_busy())
return -ETIMEDOUT;
return 0;
}
int ipq_mdio_read(int mii_id, int regnum, ushort *data)
{
u32 val,cmd;
if (regnum & MII_ADDR_C45) {
unsigned int mmd = (regnum >> 16) & 0x1F;
unsigned int reg = regnum & 0xFFFF;
writel(CTRL_0_REG_C45_DEFAULT_VALUE,
IPQ_MDIO_BASE + MDIO_CTRL_0_REG);
/* Issue the phy address and reg */
writel((mii_id << 8) | mmd,
IPQ_MDIO_BASE + MDIO_CTRL_1_REG);
writel(reg, IPQ_MDIO_BASE + MDIO_CTRL_2_REG);
/* issue read command */
cmd = MDIO_CTRL_4_ACCESS_START | MDIO_CTRL_4_ACCESS_CODE_C45_ADDR;
} else {
writel(CTRL_0_REG_DEFAULT_VALUE,
IPQ_MDIO_BASE + MDIO_CTRL_0_REG);
/* Issue the phy address and reg */
writel((mii_id << 8 | regnum ) ,
IPQ_MDIO_BASE + MDIO_CTRL_1_REG);
/* issue read command */
cmd = MDIO_CTRL_4_ACCESS_START | MDIO_CTRL_4_ACCESS_CODE_READ ;
}
/* issue read command */
writel(cmd, IPQ_MDIO_BASE + MDIO_CTRL_4_REG);
if (ipq_mdio_wait_busy())
return -ETIMEDOUT;
if (regnum & MII_ADDR_C45) {
cmd = MDIO_CTRL_4_ACCESS_START | MDIO_CTRL_4_ACCESS_CODE_C45_READ;
writel(cmd, IPQ_MDIO_BASE + MDIO_CTRL_4_REG);
if (ipq_mdio_wait_busy())
return -ETIMEDOUT;
}
/* Read data */
val = readl(IPQ_MDIO_BASE + MDIO_CTRL_3_REG);
if (data != NULL)
*data = val;
return val;
}
int ipq_phy_write(struct mii_dev *bus,
int addr, int dev_addr,
int regnum, ushort value)
{
return ipq_mdio_write(addr, regnum, value);
}
int ipq_phy_read(struct mii_dev *bus,
int addr, int dev_addr, int regnum)
{
return ipq_mdio_read(addr, regnum, NULL);
}
#ifdef CONFIG_QCA8084_PHY
static inline void split_addr(uint32_t regaddr, uint16_t *r1, uint16_t *r2,
uint16_t *page, uint16_t *switch_phy_id)
{
*r1 = regaddr & 0x1c;
regaddr >>= 5;
*r2 = regaddr & 0x7;
regaddr >>= 3;
*page = regaddr & 0xffff;
regaddr >>= 16;
*switch_phy_id = regaddr & 0xff;
}
uint32_t ipq_mii_read(uint32_t reg)
{
uint16_t r1, r2, page, switch_phy_id;
uint16_t lo, hi;
split_addr((uint32_t) reg, &r1, &r2, &page, &switch_phy_id);
mutex_lock(&switch_mdio_lock);
ipq_mdio_write(0x18 | (switch_phy_id >> 5), switch_phy_id & 0x1f, page);
udelay(100);
lo = ipq_mdio_read(0x10 | r2, r1, NULL);
hi = ipq_mdio_read(0x10 | r2, r1 + 2, NULL);
mutex_unlock(&switch_mdio_lock);
return (hi << 16) | lo;
}
void ipq_mii_write(uint32_t reg, uint32_t val)
{
uint16_t r1, r2, page, switch_phy_id;
uint16_t lo, hi;
split_addr((uint32_t) reg, &r1, &r2, &page, &switch_phy_id);
lo = val & 0xffff;
hi = (uint16_t) (val >> 16);
mutex_lock(&switch_mdio_lock);
ipq_mdio_write(0x18 | (switch_phy_id >> 5), switch_phy_id & 0x1f, page);
udelay(100);
ipq_mdio_write(0x10 | r2, r1, lo);
ipq_mdio_write(0x10 | r2, r1 + 2, hi);
mutex_unlock(&switch_mdio_lock);
}
void ipq_mii_update(uint32_t reg, uint32_t mask, uint32_t val)
{
uint32_t new_val = 0, org_val = 0;
org_val = ipq_mii_read(reg);
new_val = org_val & ~mask;
new_val |= val & mask;
if (new_val != org_val)
ipq_mii_write(reg, new_val);
}
static inline void ipq_clk_enable(uint32_t reg)
{
u32 val;
val = ipq_mii_read(reg);
val |= BIT(0);
ipq_mii_write(reg, val);
}
static inline void ipq_clk_disable(uint32_t reg)
{
u32 val;
val = ipq_mii_read(reg);
val &= ~BIT(0);
ipq_mii_write(reg, val);
}
static inline void ipq_clk_reset(uint32_t reg)
{
u32 val;
val = ipq_mii_read(reg);
val |= BIT(2);
ipq_mii_write(reg, val);
udelay(21000);
val &= ~BIT(2);
ipq_mii_write(reg, val);
}
void ipq_clock_init(void)
{
u32 val = 0;
int i;
/* Enable serdes */
ipq_clk_enable(SRDS0_SYS_CBCR);
ipq_clk_enable(SRDS1_SYS_CBCR);
/* Reset serdes */
ipq_clk_reset(SRDS0_SYS_CBCR);
ipq_clk_reset(SRDS1_SYS_CBCR);
/* Disable EPHY GMII clock */
i = 0;
while (i < 2 * PHY_ADDR_NUM) {
ipq_clk_disable(GEPHY0_TX_CBCR + i*0x20);
i++;
}
/* Enable ephy */
ipq_clk_enable(EPHY0_SYS_CBCR);
ipq_clk_enable(EPHY1_SYS_CBCR);
ipq_clk_enable(EPHY2_SYS_CBCR);
ipq_clk_enable(EPHY3_SYS_CBCR);
/* Reset ephy */
ipq_clk_reset(EPHY0_SYS_CBCR);
ipq_clk_reset(EPHY1_SYS_CBCR);
ipq_clk_reset(EPHY2_SYS_CBCR);
ipq_clk_reset(EPHY3_SYS_CBCR);
/* Deassert EPHY DSP */
val = ipq_mii_read(GCC_GEPHY_MISC);
val &= ~GENMASK(4, 0);
ipq_mii_write(GCC_GEPHY_MISC, val);
/* Enable efuse loading into analog circuit */
val = ipq_mii_read(EPHY_CFG);
/* BIT20 for PHY0 and PHY1, BIT21 for PHY2 and PHY3 */
val &= ~GENMASK(21, 20);
ipq_mii_write(EPHY_CFG, val);
udelay(11000);
}
void ipq_phy_addr_fixup(void)
{
int phy_index, addr;
u32 val;
unsigned long phyaddr_mask = 0;
val = ipq_mii_read(EPHY_CFG);
for (phy_index = 0, addr = 1; addr <= 4; phy_index++, addr++) {
phyaddr_mask |= BIT(addr);
addr &= GENMASK(4, 0);
val &= ~(GENMASK(4, 0) << (phy_index * PHY_ADDR_LENGTH));
val |= addr << (phy_index * PHY_ADDR_LENGTH);
}
pr_debug("programme EPHY reg 0x%x with 0x%x\n", EPHY_CFG, val);
ipq_mii_write(EPHY_CFG, val);
/* Programe the UNIPHY address if uniphyaddr_fixup specified.
* the UNIPHY address will select three MDIO address from
* unoccupied MDIO address space.
*/
val = ipq_mii_read(UNIPHY_CFG);
/* For qca8386, the switch occupies the other 16 MDIO address,
* for example, if the phy address is in the range of 0 to 15,
* the switch will occupy the MDIO address from 16 to 31.
*/
phyaddr_mask |= GENMASK(31, 16);
phy_index = 0;
for_each_clear_bit_from(addr, &phyaddr_mask, PHY_MAX_ADDR) {
if (phy_index >= UNIPHY_ADDR_NUM)
break;
val &= ~(GENMASK(4, 0) << (phy_index * PHY_ADDR_LENGTH));
val |= addr << (phy_index * PHY_ADDR_LENGTH);
phy_index++;
}
if (phy_index < UNIPHY_ADDR_NUM) {
for_each_clear_bit(addr, &phyaddr_mask, PHY_MAX_ADDR) {
if (phy_index >= UNIPHY_ADDR_NUM)
break;
val &= ~(GENMASK(4, 0) << (phy_index * PHY_ADDR_LENGTH));
val |= addr << (phy_index * PHY_ADDR_LENGTH);
phy_index++;
}
}
pr_debug("programme UNIPHY reg 0x%x with 0x%x\n", UNIPHY_CFG, val);
ipq_mii_write(UNIPHY_CFG, val);
}
#endif
int ipq_sw_mdio_init(const char *name)
{
struct mii_dev *bus = mdio_alloc();
if(!bus) {
printf("Failed to allocate IPQ MDIO bus\n");
return -1;
}
bus->read = ipq_phy_read;
bus->write = ipq_phy_write;
bus->reset = NULL;
snprintf(bus->name, MDIO_NAME_LEN, name);
return mdio_register(bus);
}
#ifdef CONFIG_QCA8084_PHY
static int do_ipq_mii(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
char op[2];
unsigned int reg = 0;
unsigned short data = 0;
if (argc < 2)
return CMD_RET_USAGE;
op[0] = argv[1][0];
if (strlen(argv[1]) > 1)
op[1] = argv[1][1];
else
op[1] = '\0';
if (argc >= 3)
reg = simple_strtoul(argv[2], NULL, 16);
if (argc >= 4)
data = simple_strtoul(argv[3], NULL, 16);
if (op[0] == 'r') {
data = ipq_mii_read(reg);
printf("0x%x\n", data);
} else if (op[0] == 'w') {
ipq_mii_write(reg, data);
} else {
return CMD_RET_USAGE;
}
return 0;
}
U_BOOT_CMD(
ipq_mii, 4, 1, do_ipq_mii,
"IPQ mii utility commands",
"ipq_mii read <reg> - read IPQ MII register <reg>\n"
"ipq_mii write <reg> <data> - write IPQ MII register <reg> with <data>\n"
);
#endif
static int do_ipq_mdio(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
char op[2];
unsigned int addr = 0, reg = 0;
unsigned short data = 0;
if (argc < 2)
return CMD_RET_USAGE;
op[0] = argv[1][0];
if (strlen(argv[1]) > 1)
op[1] = argv[1][1];
else
op[1] = '\0';
if (argc >= 3)
addr = simple_strtoul(argv[2], NULL, 16);
if (argc >= 4)
reg = simple_strtoul(argv[3], NULL, 16);
if (argc >= 5)
data = simple_strtoul(argv[4], NULL, 16);
if (op[0] == 'r') {
data = ipq_mdio_read(addr, reg, NULL);
printf("0x%x\n", data);
} else if (op[0] == 'w') {
ipq_mdio_write(addr, reg, data);
} else {
return CMD_RET_USAGE;
}
return 0;
}
U_BOOT_CMD(
ipq_mdio, 5, 1, do_ipq_mdio,
"IPQ mdio utility commands",
"ipq_mdio read <addr> <reg> - read IPQ MDIO PHY <addr> register <reg>\n"
"ipq_mdio write <addr> <reg> <data> - write IPQ MDIO PHY <addr> register <reg>\n"
"Addr and/or reg may be ranges, e.g. 0-7."
);