| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * (C) Copyright 2012 |
| * Valentin Lontgchamp, Keymile AG, valentin.longchamp@keymile.com |
| */ |
| |
| #include <command.h> |
| #include <log.h> |
| #include <miiphy.h> |
| #include <linux/delay.h> |
| #include <linux/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 = dectoul(argv[1], NULL); |
| port = dectoul(argv[2], NULL); |
| reg = dectoul(argv[3], NULL); |
| |
| 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 = dectoul(argv[1], NULL); |
| port = dectoul(argv[2], NULL); |
| reg = dectoul(argv[3], NULL); |
| value = hextoul(argv[4], NULL); |
| |
| ret = sw_reg_write(name, phyaddr, port, reg, value); |
| |
| return ret; |
| } |
| |
| |
| int do_mvsw_reg(struct cmd_tbl *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" |
| ); |