|  | /* | 
|  | * (C) Copyright 2012 | 
|  | * Valentin Lontgchamp, Keymile AG, valentin.longchamp@keymile.com | 
|  | * | 
|  | * SPDX-License-Identifier:	GPL-2.0+ | 
|  | */ | 
|  |  | 
|  | #include <common.h> | 
|  | #include <miiphy.h> | 
|  | #include <asm/errno.h> | 
|  | #include <mv88e6352.h> | 
|  |  | 
|  | #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" | 
|  | ); |