diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index feced39a42..5e90d7098d 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -27,6 +27,7 @@ LIB := $(obj)libphy.o COBJS-$(CONFIG_BITBANGMII) += miiphybb.o COBJS-$(CONFIG_MV88E61XX_SWITCH) += mv88e61xx.o +COBJS-$(CONFIG_MV88E6352_SWITCH) += mv88e6352.o COBJS-$(CONFIG_PHYLIB) += phy.o COBJS-$(CONFIG_PHYLIB_10G) += generic_10g.o diff --git a/drivers/net/phy/mv88e6352.c b/drivers/net/phy/mv88e6352.c new file mode 100644 index 0000000000..7269a6afe1 --- /dev/null +++ b/drivers/net/phy/mv88e6352.c @@ -0,0 +1,318 @@ +/* + * (C) Copyright 2012 + * Valentin Lontgchamp, Keymile AG, valentin.longchamp@keymile.com + * + * See file CREDITS for list of people who contributed to this + * project. + * + * 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., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include +#include +#include +#include + +#define SMI_HDR ((0x8 | 0x1) << 12) +#define SMI_BUSY_MASK (0x8000) +#define SMIRD_OP (0x2 << 10) +#define SMIWR_OP (0x1 << 10) +#define SMI_MASK 0x1f +#define PORT_SHIFT 5 + +#define COMMAND_REG 0 +#define DATA_REG 1 + +/* global registers */ +#define GLOBAL 0x1b + +#define GLOBAL_STATUS 0x00 +#define PPU_STATE 0x8000 + +#define GLOBAL_CTRL 0x04 +#define SW_RESET 0x8000 +#define PPU_ENABLE 0x4000 + +static int sw_wait_rdy(const char *devname, u8 phy_addr) +{ + u16 command; + u32 timeout = 100; + int ret; + + /* wait till the SMI is not busy */ + do { + /* read command register */ + ret = miiphy_read(devname, phy_addr, COMMAND_REG, &command); + if (ret < 0) { + printf("%s: Error reading command register\n", + __func__); + return ret; + } + if (timeout-- == 0) { + printf("Err..(%s) SMI busy timeout\n", __func__); + return -EFAULT; + } + } while (command & SMI_BUSY_MASK); + + return 0; +} + +static int sw_reg_read(const char *devname, u8 phy_addr, u8 port, + u8 reg, u16 *data) +{ + int ret; + u16 command; + + ret = sw_wait_rdy(devname, phy_addr); + if (ret) + return ret; + + command = SMI_HDR | SMIRD_OP | ((port&SMI_MASK) << PORT_SHIFT) | + (reg & SMI_MASK); + debug("%s: write to command: %#x\n", __func__, command); + ret = miiphy_write(devname, phy_addr, COMMAND_REG, command); + if (ret) + return ret; + + ret = sw_wait_rdy(devname, phy_addr); + if (ret) + return ret; + + ret = miiphy_read(devname, phy_addr, DATA_REG, data); + + return ret; +} + +static int sw_reg_write(const char *devname, u8 phy_addr, u8 port, + u8 reg, u16 data) +{ + int ret; + u16 value; + + ret = sw_wait_rdy(devname, phy_addr); + if (ret) + return ret; + + debug("%s: write to data: %#x\n", __func__, data); + ret = miiphy_write(devname, phy_addr, DATA_REG, data); + if (ret) + return ret; + + value = SMI_HDR | SMIWR_OP | ((port & SMI_MASK) << PORT_SHIFT) | + (reg & SMI_MASK); + debug("%s: write to command: %#x\n", __func__, value); + ret = miiphy_write(devname, phy_addr, COMMAND_REG, value); + if (ret) + return ret; + + ret = sw_wait_rdy(devname, phy_addr); + if (ret) + return ret; + + return 0; +} + +static int ppu_enable(const char *devname, u8 phy_addr) +{ + int i, ret = 0; + u16 reg; + + ret = sw_reg_read(devname, phy_addr, GLOBAL, GLOBAL_CTRL, ®); + if (ret) { + printf("%s: Error reading global ctrl reg\n", __func__); + return ret; + } + + reg |= PPU_ENABLE; + + ret = sw_reg_write(devname, phy_addr, GLOBAL, GLOBAL_CTRL, reg); + if (ret) { + printf("%s: Error writing global ctrl reg\n", __func__); + return ret; + } + + for (i = 0; i < 1000; i++) { + sw_reg_read(devname, phy_addr, GLOBAL, GLOBAL_STATUS, + ®); + if ((reg & 0xc000) == 0xc000) + return 0; + udelay(1000); + } + + return -ETIMEDOUT; +} + +static int ppu_disable(const char *devname, u8 phy_addr) +{ + int i, ret = 0; + u16 reg; + + ret = sw_reg_read(devname, phy_addr, GLOBAL, GLOBAL_CTRL, ®); + if (ret) { + printf("%s: Error reading global ctrl reg\n", __func__); + return ret; + } + + reg &= ~PPU_ENABLE; + + ret = sw_reg_write(devname, phy_addr, GLOBAL, GLOBAL_CTRL, reg); + if (ret) { + printf("%s: Error writing global ctrl reg\n", __func__); + return ret; + } + + for (i = 0; i < 1000; i++) { + sw_reg_read(devname, phy_addr, GLOBAL, GLOBAL_STATUS, + ®); + if ((reg & 0xc000) != 0xc000) + return 0; + udelay(1000); + } + + return -ETIMEDOUT; +} + +int mv88e_sw_program(const char *devname, u8 phy_addr, + struct mv88e_sw_reg *regs, int regs_nb) +{ + int i, ret = 0; + + /* first we need to disable the PPU */ + ret = ppu_disable(devname, phy_addr); + if (ret) { + printf("%s: Error disabling PPU\n", __func__); + return ret; + } + + for (i = 0; i < regs_nb; i++) { + ret = sw_reg_write(devname, phy_addr, regs[i].port, + regs[i].reg, regs[i].value); + if (ret) { + printf("%s: Error configuring switch\n", __func__); + ppu_enable(devname, phy_addr); + return ret; + } + } + + /* re-enable the PPU */ + ret = ppu_enable(devname, phy_addr); + if (ret) { + printf("%s: Error enabling PPU\n", __func__); + return ret; + } + + return 0; +} + +int mv88e_sw_reset(const char *devname, u8 phy_addr) +{ + int i, ret = 0; + u16 reg; + + ret = sw_reg_read(devname, phy_addr, GLOBAL, GLOBAL_CTRL, ®); + if (ret) { + printf("%s: Error reading global ctrl reg\n", __func__); + return ret; + } + + reg = SW_RESET | PPU_ENABLE | 0x0400; + + ret = sw_reg_write(devname, phy_addr, GLOBAL, GLOBAL_CTRL, reg); + if (ret) { + printf("%s: Error writing global ctrl reg\n", __func__); + return ret; + } + + for (i = 0; i < 1000; i++) { + sw_reg_read(devname, phy_addr, GLOBAL, GLOBAL_STATUS, + ®); + if ((reg & 0xc800) != 0xc800) + return 0; + udelay(1000); + } + + return -ETIMEDOUT; +} + +int do_mvsw_reg_read(const char *name, int argc, char * const argv[]) +{ + u16 value = 0, phyaddr, reg, port; + int ret; + + phyaddr = simple_strtoul(argv[1], NULL, 10); + port = simple_strtoul(argv[2], NULL, 10); + reg = simple_strtoul(argv[3], NULL, 10); + + ret = sw_reg_read(name, phyaddr, port, reg, &value); + printf("%#x\n", value); + + return ret; +} + +int do_mvsw_reg_write(const char *name, int argc, char * const argv[]) +{ + u16 value = 0, phyaddr, reg, port; + int ret; + + phyaddr = simple_strtoul(argv[1], NULL, 10); + port = simple_strtoul(argv[2], NULL, 10); + reg = simple_strtoul(argv[3], NULL, 10); + value = simple_strtoul(argv[4], NULL, 16); + + ret = sw_reg_write(name, phyaddr, port, reg, value); + + return ret; +} + + +int do_mvsw_reg(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) +{ + int ret; + const char *cmd, *ethname; + + if (argc < 2) + return cmd_usage(cmdtp); + + cmd = argv[1]; + --argc; + ++argv; + + if (strcmp(cmd, "read") == 0) { + if (argc < 5) + return cmd_usage(cmdtp); + ethname = argv[1]; + --argc; + ++argv; + ret = do_mvsw_reg_read(ethname, argc, argv); + } else if (strcmp(cmd, "write") == 0) { + if (argc < 6) + return cmd_usage(cmdtp); + ethname = argv[1]; + --argc; + ++argv; + ret = do_mvsw_reg_write(ethname, argc, argv); + } else + return cmd_usage(cmdtp); + + return ret; +} + +U_BOOT_CMD( + mvsw_reg, 7, 1, do_mvsw_reg, + "marvell 88e6352 switch register access", + "write ethname phyaddr port reg value\n" + "mvsw_reg read ethname phyaddr port reg\n" + ); diff --git a/include/mv88e6352.h b/include/mv88e6352.h new file mode 100644 index 0000000000..cdc4db781e --- /dev/null +++ b/include/mv88e6352.h @@ -0,0 +1,92 @@ +/* + * (C) Copyright 2012 + * Valentin Lontgchamp, Keymile AG, valentin.longchamp@keymile.com + * + * See file CREDITS for list of people who contributed to this + * project. + * + * 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., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#ifndef __MV886352_H +#define __MV886352_H + +#include + +/* PHY registers */ +#define PHY(itf) (itf) + +#define PHY_CTRL 0x00 +#define PHY_100_MBPS 0x2000 +#define PHY_1_GBPS 0x0040 +#define AUTONEG_EN 0x1000 +#define AUTONEG_RST 0x0200 +#define FULL_DUPLEX 0x0100 +#define PHY_PWR_DOWN 0x0800 + +#define PHY_STATUS 0x01 +#define AN1000FIX 0x0001 + +#define PHY_SPEC_CTRL 0x10 +#define SPEC_PWR_DOWN 0x0004 +#define AUTO_MDIX_EN 0x0060 + +#define PHY_1000_CTRL 0x9 + +#define NO_ADV 0x0000 +#define ADV_1000_FDPX 0x0200 +#define ADV_1000_HDPX 0x0100 + +#define PHY_PAGE 0x16 + +#define AN1000FIX_PAGE 0x00fc + +/* PORT or MAC registers */ +#define PORT(itf) (itf+0x10) + +#define PORT_STATUS 0x00 +#define NO_PHY_DETECT 0x0000 + +#define PORT_PHY 0x01 +#define RX_RGMII_TIM 0x8000 +#define TX_RGMII_TIM 0x4000 +#define FLOW_CTRL_EN 0x0080 +#define FLOW_CTRL_FOR 0x0040 +#define LINK_VAL 0x0020 +#define LINK_FOR 0x0010 +#define FULL_DPX 0x0008 +#define FULL_DPX_FOR 0x0004 +#define NO_SPEED_FOR 0x0003 +#define SPEED_1000_FOR 0x0002 +#define SPEED_100_FOR 0x0001 +#define SPEED_10_FOR 0x0000 + +#define PORT_CTRL 0x04 +#define FORWARDING 0x0003 +#define EGRS_FLD_ALL 0x000c +#define PORT_DIS 0x0000 + +struct mv88e_sw_reg { + u8 port; + u8 reg; + u16 value; +}; + +int mv88e_sw_reset(const char *devname, u8 phy_addr); +int mv88e_sw_program(const char *devname, u8 phy_addr, + struct mv88e_sw_reg *regs, int regs_nb); + +#endif