Skip to content
Navigation Menu
Toggle navigation
Sign in
In this repository
All GitHub Enterprise
↵
Jump to
↵
No suggested jump to results
In this repository
All GitHub Enterprise
↵
Jump to
↵
In this organization
All GitHub Enterprise
↵
Jump to
↵
In this repository
All GitHub Enterprise
↵
Jump to
↵
Sign in
Reseting focus
You signed in with another tab or window.
Reload
to refresh your session.
You signed out in another tab or window.
Reload
to refresh your session.
You switched accounts on another tab or window.
Reload
to refresh your session.
Dismiss alert
{{ message }}
mariux64
/
linux
Public
Notifications
You must be signed in to change notification settings
Fork
0
Star
0
Code
Issues
2
Pull requests
0
Actions
Projects
0
Wiki
Security
Insights
Additional navigation options
Code
Issues
Pull requests
Actions
Projects
Wiki
Security
Insights
Files
081e84f
Documentation
LICENSES
arch
block
certs
crypto
drivers
accel
accessibility
acpi
amba
android
ata
atm
auxdisplay
base
bcma
block
bluetooth
bus
cache
cdrom
cdx
char
clk
clocksource
comedi
connector
counter
cpufreq
cpuidle
crypto
cxl
dax
dca
devfreq
dio
dma-buf
dma
dpll
edac
eisa
extcon
firewire
firmware
fpga
fsi
gnss
gpio
gpu
greybus
hid
hsi
hte
hv
hwmon
hwspinlock
hwtracing
i2c
i3c
idle
iio
infiniband
input
interconnect
iommu
ipack
irqchip
isdn
leds
macintosh
mailbox
mcb
md
media
memory
memstick
message
mfd
misc
mmc
most
mtd
mux
net
arcnet
bonding
caif
can
dsa
b53
hirschmann
microchip
Kconfig
Makefile
ksz8.h
ksz8795.c
ksz8795_reg.h
ksz8863_smi.c
ksz9477.c
ksz9477.h
ksz9477_acl.c
ksz9477_i2c.c
ksz9477_reg.h
ksz9477_tc_flower.c
ksz_common.c
ksz_common.h
ksz_ptp.c
ksz_ptp.h
ksz_ptp_reg.h
ksz_spi.c
lan937x.h
lan937x_main.c
lan937x_reg.h
mv88e6xxx
ocelot
qca
realtek
sja1105
xrs700x
Kconfig
Makefile
bcm_sf2.c
bcm_sf2.h
bcm_sf2_cfp.c
bcm_sf2_regs.h
dsa_loop.c
dsa_loop.h
dsa_loop_bdinfo.c
lan9303-core.c
lan9303.h
lan9303_i2c.c
lan9303_mdio.c
lantiq_gswip.c
lantiq_pce.h
mt7530-mdio.c
mt7530-mmio.c
mt7530.c
mt7530.h
mv88e6060.c
mv88e6060.h
rzn1_a5psw.c
rzn1_a5psw.h
vitesse-vsc73xx-core.c
vitesse-vsc73xx-platform.c
vitesse-vsc73xx-spi.c
vitesse-vsc73xx.h
ethernet
fddi
fjes
hamradio
hippi
hyperv
ieee802154
ipa
ipvlan
mctp
mdio
netdevsim
pcs
phy
plip
ppp
pse-pd
slip
team
thunderbolt
usb
vmxnet3
vxlan
wan
wireguard
wireless
wwan
xen-netback
Kconfig
LICENSE.SRC
Makefile
Space.c
amt.c
bareudp.c
dummy.c
eql.c
geneve.c
gtp.c
ifb.c
loopback.c
macsec.c
macvlan.c
macvtap.c
mdio.c
mhi_net.c
mii.c
net_failover.c
netconsole.c
netkit.c
nlmon.c
ntb_netdev.c
pfcp.c
rionet.c
sb1000.c
sungem_phy.c
tap.c
tun.c
veth.c
virtio_net.c
vrf.c
vsockmon.c
xen-netfront.c
nfc
ntb
nubus
nvdimm
nvme
nvmem
of
opp
parisc
parport
pci
pcmcia
peci
perf
phy
pinctrl
platform
pmdomain
pnp
power
powercap
pps
ps3
ptp
pwm
rapidio
ras
regulator
remoteproc
reset
rpmsg
rtc
s390
sbus
scsi
sh
siox
slimbus
soc
soundwire
spi
spmi
ssb
staging
target
tc
tee
thermal
thunderbolt
tty
ufs
uio
usb
vdpa
vfio
vhost
video
virt
virtio
w1
watchdog
xen
zorro
Kconfig
Makefile
fs
include
init
io_uring
ipc
kernel
lib
mm
net
rust
samples
scripts
security
sound
tools
usr
virt
.clang-format
.cocciconfig
.editorconfig
.get_maintainer.ignore
.gitattributes
.gitignore
.mailmap
.rustfmt.toml
COPYING
CREDITS
Kbuild
Kconfig
MAINTAINERS
Makefile
README
Breadcrumbs
linux
/
drivers
/
net
/
dsa
/
microchip
/
ksz8795.c
Blame
Blame
Latest commit
History
History
1834 lines (1520 loc) · 47 KB
Breadcrumbs
linux
/
drivers
/
net
/
dsa
/
microchip
/
ksz8795.c
Top
File metadata and controls
Code
Blame
1834 lines (1520 loc) · 47 KB
Raw
// SPDX-License-Identifier: GPL-2.0 /* * Microchip KSZ8795 switch driver * * Copyright (C) 2017 Microchip Technology Inc. * Tristram Ha <Tristram.Ha@microchip.com> */ #include <linux/bitfield.h> #include <linux/delay.h> #include <linux/export.h> #include <linux/gpio.h> #include <linux/if_vlan.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/platform_data/microchip-ksz.h> #include <linux/phy.h> #include <linux/etherdevice.h> #include <linux/if_bridge.h> #include <linux/micrel_phy.h> #include <net/dsa.h> #include <net/switchdev.h> #include <linux/phylink.h> #include "ksz_common.h" #include "ksz8795_reg.h" #include "ksz8.h" static void ksz_cfg(struct ksz_device *dev, u32 addr, u8 bits, bool set) { regmap_update_bits(ksz_regmap_8(dev), addr, bits, set ? bits : 0); } static void ksz_port_cfg(struct ksz_device *dev, int port, int offset, u8 bits, bool set) { regmap_update_bits(ksz_regmap_8(dev), PORT_CTRL_ADDR(port, offset), bits, set ? bits : 0); } static int ksz8_ind_write8(struct ksz_device *dev, u8 table, u16 addr, u8 data) { const u16 *regs; u16 ctrl_addr; int ret = 0; regs = dev->info->regs; mutex_lock(&dev->alu_mutex); ctrl_addr = IND_ACC_TABLE(table) | addr; ret = ksz_write16(dev, regs[REG_IND_CTRL_0], ctrl_addr); if (!ret) ret = ksz_write8(dev, regs[REG_IND_BYTE], data); mutex_unlock(&dev->alu_mutex); return ret; } int ksz8_reset_switch(struct ksz_device *dev) { if (ksz_is_ksz88x3(dev)) { /* reset switch */ ksz_cfg(dev, KSZ8863_REG_SW_RESET, KSZ8863_GLOBAL_SOFTWARE_RESET | KSZ8863_PCS_RESET, true); ksz_cfg(dev, KSZ8863_REG_SW_RESET, KSZ8863_GLOBAL_SOFTWARE_RESET | KSZ8863_PCS_RESET, false); } else { /* reset switch */ ksz_write8(dev, REG_POWER_MANAGEMENT_1, SW_SOFTWARE_POWER_DOWN << SW_POWER_MANAGEMENT_MODE_S); ksz_write8(dev, REG_POWER_MANAGEMENT_1, 0); } return 0; } static int ksz8863_change_mtu(struct ksz_device *dev, int frame_size) { u8 ctrl2 = 0; if (frame_size <= KSZ8_LEGAL_PACKET_SIZE) ctrl2 |= KSZ8863_LEGAL_PACKET_ENABLE; else if (frame_size > KSZ8863_NORMAL_PACKET_SIZE) ctrl2 |= KSZ8863_HUGE_PACKET_ENABLE; return ksz_rmw8(dev, REG_SW_CTRL_2, KSZ8863_LEGAL_PACKET_ENABLE | KSZ8863_HUGE_PACKET_ENABLE, ctrl2); } static int ksz8795_change_mtu(struct ksz_device *dev, int frame_size) { u8 ctrl1 = 0, ctrl2 = 0; int ret; if (frame_size > KSZ8_LEGAL_PACKET_SIZE) ctrl2 |= SW_LEGAL_PACKET_DISABLE; if (frame_size > KSZ8863_NORMAL_PACKET_SIZE) ctrl1 |= SW_HUGE_PACKET; ret = ksz_rmw8(dev, REG_SW_CTRL_1, SW_HUGE_PACKET, ctrl1); if (ret) return ret; return ksz_rmw8(dev, REG_SW_CTRL_2, SW_LEGAL_PACKET_DISABLE, ctrl2); } int ksz8_change_mtu(struct ksz_device *dev, int port, int mtu) { u16 frame_size; if (!dsa_is_cpu_port(dev->ds, port)) return 0; frame_size = mtu + VLAN_ETH_HLEN + ETH_FCS_LEN; switch (dev->chip_id) { case KSZ8795_CHIP_ID: case KSZ8794_CHIP_ID: case KSZ8765_CHIP_ID: return ksz8795_change_mtu(dev, frame_size); case KSZ8830_CHIP_ID: return ksz8863_change_mtu(dev, frame_size); } return -EOPNOTSUPP; } static void ksz8795_set_prio_queue(struct ksz_device *dev, int port, int queue) { u8 hi, lo; /* Number of queues can only be 1, 2, or 4. */ switch (queue) { case 4: case 3: queue = PORT_QUEUE_SPLIT_4; break; case 2: queue = PORT_QUEUE_SPLIT_2; break; default: queue = PORT_QUEUE_SPLIT_1; } ksz_pread8(dev, port, REG_PORT_CTRL_0, &lo); ksz_pread8(dev, port, P_DROP_TAG_CTRL, &hi); lo &= ~PORT_QUEUE_SPLIT_L; if (queue & PORT_QUEUE_SPLIT_2) lo |= PORT_QUEUE_SPLIT_L; hi &= ~PORT_QUEUE_SPLIT_H; if (queue & PORT_QUEUE_SPLIT_4) hi |= PORT_QUEUE_SPLIT_H; ksz_pwrite8(dev, port, REG_PORT_CTRL_0, lo); ksz_pwrite8(dev, port, P_DROP_TAG_CTRL, hi); /* Default is port based for egress rate limit. */ if (queue != PORT_QUEUE_SPLIT_1) ksz_cfg(dev, REG_SW_CTRL_19, SW_OUT_RATE_LIMIT_QUEUE_BASED, true); } void ksz8_r_mib_cnt(struct ksz_device *dev, int port, u16 addr, u64 *cnt) { const u32 *masks; const u16 *regs; u16 ctrl_addr; u32 data; u8 check; int loop; masks = dev->info->masks; regs = dev->info->regs; ctrl_addr = addr + dev->info->reg_mib_cnt * port; ctrl_addr |= IND_ACC_TABLE(TABLE_MIB | TABLE_READ); mutex_lock(&dev->alu_mutex); ksz_write16(dev, regs[REG_IND_CTRL_0], ctrl_addr); /* It is almost guaranteed to always read the valid bit because of * slow SPI speed. */ for (loop = 2; loop > 0; loop--) { ksz_read8(dev, regs[REG_IND_MIB_CHECK], &check); if (check & masks[MIB_COUNTER_VALID]) { ksz_read32(dev, regs[REG_IND_DATA_LO], &data); if (check & masks[MIB_COUNTER_OVERFLOW]) *cnt += MIB_COUNTER_VALUE + 1; *cnt += data & MIB_COUNTER_VALUE; break; } } mutex_unlock(&dev->alu_mutex); } static void ksz8795_r_mib_pkt(struct ksz_device *dev, int port, u16 addr, u64 *dropped, u64 *cnt) { const u32 *masks; const u16 *regs; u16 ctrl_addr; u32 data; u8 check; int loop; masks = dev->info->masks; regs = dev->info->regs; addr -= dev->info->reg_mib_cnt; ctrl_addr = (KSZ8795_MIB_TOTAL_RX_1 - KSZ8795_MIB_TOTAL_RX_0) * port; ctrl_addr += addr + KSZ8795_MIB_TOTAL_RX_0; ctrl_addr |= IND_ACC_TABLE(TABLE_MIB | TABLE_READ); mutex_lock(&dev->alu_mutex); ksz_write16(dev, regs[REG_IND_CTRL_0], ctrl_addr); /* It is almost guaranteed to always read the valid bit because of * slow SPI speed. */ for (loop = 2; loop > 0; loop--) { ksz_read8(dev, regs[REG_IND_MIB_CHECK], &check); if (check & masks[MIB_COUNTER_VALID]) { ksz_read32(dev, regs[REG_IND_DATA_LO], &data); if (addr < 2) { u64 total; total = check & MIB_TOTAL_BYTES_H; total <<= 32; *cnt += total; *cnt += data; if (check & masks[MIB_COUNTER_OVERFLOW]) { total = MIB_TOTAL_BYTES_H + 1; total <<= 32; *cnt += total; } } else { if (check & masks[MIB_COUNTER_OVERFLOW]) *cnt += MIB_PACKET_DROPPED + 1; *cnt += data & MIB_PACKET_DROPPED; } break; } } mutex_unlock(&dev->alu_mutex); } static void ksz8863_r_mib_pkt(struct ksz_device *dev, int port, u16 addr, u64 *dropped, u64 *cnt) { u32 *last = (u32 *)dropped; const u16 *regs; u16 ctrl_addr; u32 data; u32 cur; regs = dev->info->regs; addr -= dev->info->reg_mib_cnt; ctrl_addr = addr ? KSZ8863_MIB_PACKET_DROPPED_TX_0 : KSZ8863_MIB_PACKET_DROPPED_RX_0; ctrl_addr += port; ctrl_addr |= IND_ACC_TABLE(TABLE_MIB | TABLE_READ); mutex_lock(&dev->alu_mutex); ksz_write16(dev, regs[REG_IND_CTRL_0], ctrl_addr); ksz_read32(dev, regs[REG_IND_DATA_LO], &data); mutex_unlock(&dev->alu_mutex); data &= MIB_PACKET_DROPPED; cur = last[addr]; if (data != cur) { last[addr] = data; if (data < cur) data += MIB_PACKET_DROPPED + 1; data -= cur; *cnt += data; } } void ksz8_r_mib_pkt(struct ksz_device *dev, int port, u16 addr, u64 *dropped, u64 *cnt) { if (ksz_is_ksz88x3(dev)) ksz8863_r_mib_pkt(dev, port, addr, dropped, cnt); else ksz8795_r_mib_pkt(dev, port, addr, dropped, cnt); } void ksz8_freeze_mib(struct ksz_device *dev, int port, bool freeze) { if (ksz_is_ksz88x3(dev)) return; /* enable the port for flush/freeze function */ if (freeze) ksz_cfg(dev, REG_SW_CTRL_6, BIT(port), true); ksz_cfg(dev, REG_SW_CTRL_6, SW_MIB_COUNTER_FREEZE, freeze); /* disable the port after freeze is done */ if (!freeze) ksz_cfg(dev, REG_SW_CTRL_6, BIT(port), false); } void ksz8_port_init_cnt(struct ksz_device *dev, int port) { struct ksz_port_mib *mib = &dev->ports[port].mib; u64 *dropped; if (!ksz_is_ksz88x3(dev)) { /* flush all enabled port MIB counters */ ksz_cfg(dev, REG_SW_CTRL_6, BIT(port), true); ksz_cfg(dev, REG_SW_CTRL_6, SW_MIB_COUNTER_FLUSH, true); ksz_cfg(dev, REG_SW_CTRL_6, BIT(port), false); } mib->cnt_ptr = 0; /* Some ports may not have MIB counters before SWITCH_COUNTER_NUM. */ while (mib->cnt_ptr < dev->info->reg_mib_cnt) { dev->dev_ops->r_mib_cnt(dev, port, mib->cnt_ptr, &mib->counters[mib->cnt_ptr]); ++mib->cnt_ptr; } /* last one in storage */ dropped = &mib->counters[dev->info->mib_cnt]; /* Some ports may not have MIB counters after SWITCH_COUNTER_NUM. */ while (mib->cnt_ptr < dev->info->mib_cnt) { dev->dev_ops->r_mib_pkt(dev, port, mib->cnt_ptr, dropped, &mib->counters[mib->cnt_ptr]); ++mib->cnt_ptr; } } static int ksz8_r_table(struct ksz_device *dev, int table, u16 addr, u64 *data) { const u16 *regs; u16 ctrl_addr; int ret; regs = dev->info->regs; ctrl_addr = IND_ACC_TABLE(table | TABLE_READ) | addr; mutex_lock(&dev->alu_mutex); ret = ksz_write16(dev, regs[REG_IND_CTRL_0], ctrl_addr); if (ret) goto unlock_alu; ret = ksz_read64(dev, regs[REG_IND_DATA_HI], data); unlock_alu: mutex_unlock(&dev->alu_mutex); return ret; } static int ksz8_w_table(struct ksz_device *dev, int table, u16 addr, u64 data) { const u16 *regs; u16 ctrl_addr; int ret; regs = dev->info->regs; ctrl_addr = IND_ACC_TABLE(table) | addr; mutex_lock(&dev->alu_mutex); ret = ksz_write64(dev, regs[REG_IND_DATA_HI], data); if (ret) goto unlock_alu; ret = ksz_write16(dev, regs[REG_IND_CTRL_0], ctrl_addr); unlock_alu: mutex_unlock(&dev->alu_mutex); return ret; } static int ksz8_valid_dyn_entry(struct ksz_device *dev, u8 *data) { int timeout = 100; const u32 *masks; const u16 *regs; masks = dev->info->masks; regs = dev->info->regs; do { ksz_read8(dev, regs[REG_IND_DATA_CHECK], data); timeout--; } while ((*data & masks[DYNAMIC_MAC_TABLE_NOT_READY]) && timeout); /* Entry is not ready for accessing. */ if (*data & masks[DYNAMIC_MAC_TABLE_NOT_READY]) { return -ETIMEDOUT; /* Entry is ready for accessing. */ } else { ksz_read8(dev, regs[REG_IND_DATA_8], data); /* There is no valid entry in the table. */ if (*data & masks[DYNAMIC_MAC_TABLE_MAC_EMPTY]) return -ENXIO; } return 0; } static int ksz8_r_dyn_mac_table(struct ksz_device *dev, u16 addr, u8 *mac_addr, u8 *fid, u8 *src_port, u16 *entries) { u32 data_hi, data_lo; const u8 *shifts; const u32 *masks; const u16 *regs; u16 ctrl_addr; u64 buf = 0; u8 data; int cnt; int ret; shifts = dev->info->shifts; masks = dev->info->masks; regs = dev->info->regs; ctrl_addr = IND_ACC_TABLE(TABLE_DYNAMIC_MAC | TABLE_READ) | addr; mutex_lock(&dev->alu_mutex); ksz_write16(dev, regs[REG_IND_CTRL_0], ctrl_addr); ret = ksz8_valid_dyn_entry(dev, &data); if (ret == -ENXIO) { *entries = 0; goto unlock_alu; } if (ret) goto unlock_alu; ksz_read64(dev, regs[REG_IND_DATA_HI], &buf); data_hi = (u32)(buf >> 32); data_lo = (u32)buf; /* Check out how many valid entry in the table. */ cnt = data & masks[DYNAMIC_MAC_TABLE_ENTRIES_H]; cnt <<= shifts[DYNAMIC_MAC_ENTRIES_H]; cnt |= (data_hi & masks[DYNAMIC_MAC_TABLE_ENTRIES]) >> shifts[DYNAMIC_MAC_ENTRIES]; *entries = cnt + 1; *fid = (data_hi & masks[DYNAMIC_MAC_TABLE_FID]) >> shifts[DYNAMIC_MAC_FID]; *src_port = (data_hi & masks[DYNAMIC_MAC_TABLE_SRC_PORT]) >> shifts[DYNAMIC_MAC_SRC_PORT]; mac_addr[5] = (u8)data_lo; mac_addr[4] = (u8)(data_lo >> 8); mac_addr[3] = (u8)(data_lo >> 16); mac_addr[2] = (u8)(data_lo >> 24); mac_addr[1] = (u8)data_hi; mac_addr[0] = (u8)(data_hi >> 8); ret = 0; unlock_alu: mutex_unlock(&dev->alu_mutex); return ret; } static int ksz8_r_sta_mac_table(struct ksz_device *dev, u16 addr, struct alu_struct *alu, bool *valid) { u32 data_hi, data_lo; const u8 *shifts; const u32 *masks; u64 data; int ret; shifts = dev->info->shifts; masks = dev->info->masks; ret = ksz8_r_table(dev, TABLE_STATIC_MAC, addr, &data); if (ret) return ret; data_hi = data >> 32; data_lo = (u32)data; if (!(data_hi & (masks[STATIC_MAC_TABLE_VALID] | masks[STATIC_MAC_TABLE_OVERRIDE]))) { *valid = false; return 0; } alu->mac[5] = (u8)data_lo; alu->mac[4] = (u8)(data_lo >> 8); alu->mac[3] = (u8)(data_lo >> 16); alu->mac[2] = (u8)(data_lo >> 24); alu->mac[1] = (u8)data_hi; alu->mac[0] = (u8)(data_hi >> 8); alu->port_forward = (data_hi & masks[STATIC_MAC_TABLE_FWD_PORTS]) >> shifts[STATIC_MAC_FWD_PORTS]; alu->is_override = (data_hi & masks[STATIC_MAC_TABLE_OVERRIDE]) ? 1 : 0; /* KSZ8795 family switches have STATIC_MAC_TABLE_USE_FID and * STATIC_MAC_TABLE_FID definitions off by 1 when doing read on the * static MAC table compared to doing write. */ if (ksz_is_ksz87xx(dev)) data_hi >>= 1; alu->is_static = true; alu->is_use_fid = (data_hi & masks[STATIC_MAC_TABLE_USE_FID]) ? 1 : 0; alu->fid = (data_hi & masks[STATIC_MAC_TABLE_FID]) >> shifts[STATIC_MAC_FID]; *valid = true; return 0; } static int ksz8_w_sta_mac_table(struct ksz_device *dev, u16 addr, struct alu_struct *alu) { u32 data_hi, data_lo; const u8 *shifts; const u32 *masks; u64 data; shifts = dev->info->shifts; masks = dev->info->masks; data_lo = ((u32)alu->mac[2] << 24) | ((u32)alu->mac[3] << 16) | ((u32)alu->mac[4] << 8) | alu->mac[5]; data_hi = ((u32)alu->mac[0] << 8) | alu->mac[1]; data_hi |= (u32)alu->port_forward << shifts[STATIC_MAC_FWD_PORTS]; if (alu->is_override) data_hi |= masks[STATIC_MAC_TABLE_OVERRIDE]; if (alu->is_use_fid) { data_hi |= masks[STATIC_MAC_TABLE_USE_FID]; data_hi |= (u32)alu->fid << shifts[STATIC_MAC_FID]; } if (alu->is_static) data_hi |= masks[STATIC_MAC_TABLE_VALID]; else data_hi &= ~masks[STATIC_MAC_TABLE_OVERRIDE]; data = (u64)data_hi << 32 | data_lo; return ksz8_w_table(dev, TABLE_STATIC_MAC, addr, data); } static void ksz8_from_vlan(struct ksz_device *dev, u32 vlan, u8 *fid, u8 *member, u8 *valid) { const u8 *shifts; const u32 *masks; shifts = dev->info->shifts; masks = dev->info->masks; *fid = vlan & masks[VLAN_TABLE_FID]; *member = (vlan & masks[VLAN_TABLE_MEMBERSHIP]) >> shifts[VLAN_TABLE_MEMBERSHIP_S]; *valid = !!(vlan & masks[VLAN_TABLE_VALID]); } static void ksz8_to_vlan(struct ksz_device *dev, u8 fid, u8 member, u8 valid, u16 *vlan) { const u8 *shifts; const u32 *masks; shifts = dev->info->shifts; masks = dev->info->masks; *vlan = fid; *vlan |= (u16)member << shifts[VLAN_TABLE_MEMBERSHIP_S]; if (valid) *vlan |= masks[VLAN_TABLE_VALID]; } static void ksz8_r_vlan_entries(struct ksz_device *dev, u16 addr) { const u8 *shifts; u64 data; int i; shifts = dev->info->shifts; ksz8_r_table(dev, TABLE_VLAN, addr, &data); addr *= 4; for (i = 0; i < 4; i++) { dev->vlan_cache[addr + i].table[0] = (u16)data; data >>= shifts[VLAN_TABLE]; } } static void ksz8_r_vlan_table(struct ksz_device *dev, u16 vid, u16 *vlan) { int index; u16 *data; u16 addr; u64 buf; data = (u16 *)&buf; addr = vid / 4; index = vid & 3; ksz8_r_table(dev, TABLE_VLAN, addr, &buf); *vlan = data[index]; } static void ksz8_w_vlan_table(struct ksz_device *dev, u16 vid, u16 vlan) { int index; u16 *data; u16 addr; u64 buf; data = (u16 *)&buf; addr = vid / 4; index = vid & 3; ksz8_r_table(dev, TABLE_VLAN, addr, &buf); data[index] = vlan; dev->vlan_cache[vid].table[0] = vlan; ksz8_w_table(dev, TABLE_VLAN, addr, buf); } /** * ksz879x_get_loopback - KSZ879x specific function to get loopback * configuration status for a specific port * @dev: Pointer to the device structure * @port: Port number to query * @val: Pointer to store the result * * This function reads the SMI registers to determine whether loopback mode * is enabled for a specific port. * * Return: 0 on success, error code on failure. */ static int ksz879x_get_loopback(struct ksz_device *dev, u16 port, u16 *val) { u8 stat3; int ret; ret = ksz_pread8(dev, port, REG_PORT_STATUS_3, &stat3); if (ret) return ret; if (stat3 & PORT_PHY_LOOPBACK) *val |= BMCR_LOOPBACK; return 0; } /** * ksz879x_set_loopback - KSZ879x specific function to set loopback mode for * a specific port * @dev: Pointer to the device structure. * @port: Port number to modify. * @val: Value indicating whether to enable or disable loopback mode. * * This function translates loopback bit of the BMCR register into the * corresponding hardware register bit value and writes it to the SMI interface. * * Return: 0 on success, error code on failure. */ static int ksz879x_set_loopback(struct ksz_device *dev, u16 port, u16 val) { u8 stat3 = 0; if (val & BMCR_LOOPBACK) stat3 |= PORT_PHY_LOOPBACK; return ksz_prmw8(dev, port, REG_PORT_STATUS_3, PORT_PHY_LOOPBACK, stat3); } /** * ksz8_r_phy_ctrl - Translates and reads from the SMI interface to a MIIM PHY * Control register (Reg. 31). * @dev: The KSZ device instance. * @port: The port number to be read. * @val: The value read from the SMI interface. * * This function reads the SMI interface and translates the hardware register * bit values into their corresponding control settings for a MIIM PHY Control * register. * * Return: 0 on success, error code on failure. */ static int ksz8_r_phy_ctrl(struct ksz_device *dev, int port, u16 *val) { const u16 *regs = dev->info->regs; u8 reg_val; int ret; *val = 0; ret = ksz_pread8(dev, port, regs[P_LINK_STATUS], ®_val); if (ret < 0) return ret; if (reg_val & PORT_MDIX_STATUS) *val |= KSZ886X_CTRL_MDIX_STAT; ret = ksz_pread8(dev, port, REG_PORT_LINK_MD_CTRL, ®_val); if (ret < 0) return ret; if (reg_val & PORT_FORCE_LINK) *val |= KSZ886X_CTRL_FORCE_LINK; if (reg_val & PORT_POWER_SAVING) *val |= KSZ886X_CTRL_PWRSAVE; if (reg_val & PORT_PHY_REMOTE_LOOPBACK) *val |= KSZ886X_CTRL_REMOTE_LOOPBACK; return 0; } /** * ksz8_r_phy_bmcr - Translates and reads from the SMI interface to a MIIM PHY * Basic mode control register (Reg. 0). * @dev: The KSZ device instance. * @port: The port number to be read. * @val: The value read from the SMI interface. * * This function reads the SMI interface and translates the hardware register * bit values into their corresponding control settings for a MIIM PHY Basic * mode control register. * * MIIM Bit Mapping Comparison between KSZ8794 and KSZ8873 * ------------------------------------------------------------------- * MIIM Bit | KSZ8794 Reg/Bit | KSZ8873 Reg/Bit * ----------------------------+-----------------------------+---------------- * Bit 15 - Soft Reset | 0xF/4 | Not supported * Bit 14 - Loopback | 0xD/0 (MAC), 0xF/7 (PHY) ~ 0xD/0 (PHY) * Bit 13 - Force 100 | 0xC/6 = 0xC/6 * Bit 12 - AN Enable | 0xC/7 (reverse logic) ~ 0xC/7 * Bit 11 - Power Down | 0xD/3 = 0xD/3 * Bit 10 - PHY Isolate | 0xF/5 | Not supported * Bit 9 - Restart AN | 0xD/5 = 0xD/5 * Bit 8 - Force Full-Duplex | 0xC/5 = 0xC/5 * Bit 7 - Collision Test/Res. | Not supported | Not supported * Bit 6 - Reserved | Not supported | Not supported * Bit 5 - Hp_mdix | 0x9/7 ~ 0xF/7 * Bit 4 - Force MDI | 0xD/1 = 0xD/1 * Bit 3 - Disable MDIX | 0xD/2 = 0xD/2 * Bit 2 - Disable Far-End F. | ???? | 0xD/4 * Bit 1 - Disable Transmit | 0xD/6 = 0xD/6 * Bit 0 - Disable LED | 0xD/7 = 0xD/7 * ------------------------------------------------------------------- * * Return: 0 on success, error code on failure. */ static int ksz8_r_phy_bmcr(struct ksz_device *dev, u16 port, u16 *val) { const u16 *regs = dev->info->regs; u8 restart, speed, ctrl; int ret; *val = 0; ret = ksz_pread8(dev, port, regs[P_NEG_RESTART_CTRL], &restart); if (ret) return ret; ret = ksz_pread8(dev, port, regs[P_SPEED_STATUS], &speed); if (ret) return ret; ret = ksz_pread8(dev, port, regs[P_FORCE_CTRL], &ctrl); if (ret) return ret; if (ctrl & PORT_FORCE_100_MBIT) *val |= BMCR_SPEED100; if (ksz_is_ksz88x3(dev)) { if (restart & KSZ8873_PORT_PHY_LOOPBACK) *val |= BMCR_LOOPBACK; if ((ctrl & PORT_AUTO_NEG_ENABLE)) *val |= BMCR_ANENABLE; } else { ret = ksz879x_get_loopback(dev, port, val); if (ret) return ret; if (!(ctrl & PORT_AUTO_NEG_DISABLE)) *val |= BMCR_ANENABLE; } if (restart & PORT_POWER_DOWN) *val |= BMCR_PDOWN; if (restart & PORT_AUTO_NEG_RESTART) *val |= BMCR_ANRESTART; if (ctrl & PORT_FORCE_FULL_DUPLEX) *val |= BMCR_FULLDPLX; if (speed & PORT_HP_MDIX) *val |= KSZ886X_BMCR_HP_MDIX; if (restart & PORT_FORCE_MDIX) *val |= KSZ886X_BMCR_FORCE_MDI; if (restart & PORT_AUTO_MDIX_DISABLE) *val |= KSZ886X_BMCR_DISABLE_AUTO_MDIX; if (restart & PORT_TX_DISABLE) *val |= KSZ886X_BMCR_DISABLE_TRANSMIT; if (restart & PORT_LED_OFF) *val |= KSZ886X_BMCR_DISABLE_LED; return 0; } int ksz8_r_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 *val) { u8 ctrl, link, val1, val2; int processed = true; const u16 *regs; u16 data = 0; u16 p = phy; int ret; regs = dev->info->regs; switch (reg) { case MII_BMCR: ret = ksz8_r_phy_bmcr(dev, p, &data); if (ret) return ret; break; case MII_BMSR: ret = ksz_pread8(dev, p, regs[P_LINK_STATUS], &link); if (ret) return ret; data = BMSR_100FULL | BMSR_100HALF | BMSR_10FULL | BMSR_10HALF | BMSR_ANEGCAPABLE; if (link & PORT_AUTO_NEG_COMPLETE) data |= BMSR_ANEGCOMPLETE; if (link & PORT_STAT_LINK_GOOD) data |= BMSR_LSTATUS; break; case MII_PHYSID1: data = KSZ8795_ID_HI; break; case MII_PHYSID2: if (ksz_is_ksz88x3(dev)) data = KSZ8863_ID_LO; else data = KSZ8795_ID_LO; break; case MII_ADVERTISE: ret = ksz_pread8(dev, p, regs[P_LOCAL_CTRL], &ctrl); if (ret) return ret; data = ADVERTISE_CSMA; if (ctrl & PORT_AUTO_NEG_SYM_PAUSE) data |= ADVERTISE_PAUSE_CAP; if (ctrl & PORT_AUTO_NEG_100BTX_FD) data |= ADVERTISE_100FULL; if (ctrl & PORT_AUTO_NEG_100BTX) data |= ADVERTISE_100HALF; if (ctrl & PORT_AUTO_NEG_10BT_FD) data |= ADVERTISE_10FULL; if (ctrl & PORT_AUTO_NEG_10BT) data |= ADVERTISE_10HALF; break; case MII_LPA: ret = ksz_pread8(dev, p, regs[P_REMOTE_STATUS], &link); if (ret) return ret; data = LPA_SLCT; if (link & PORT_REMOTE_SYM_PAUSE) data |= LPA_PAUSE_CAP; if (link & PORT_REMOTE_100BTX_FD) data |= LPA_100FULL; if (link & PORT_REMOTE_100BTX) data |= LPA_100HALF; if (link & PORT_REMOTE_10BT_FD) data |= LPA_10FULL; if (link & PORT_REMOTE_10BT) data |= LPA_10HALF; if (data & ~LPA_SLCT) data |= LPA_LPACK; break; case PHY_REG_LINK_MD: ret = ksz_pread8(dev, p, REG_PORT_LINK_MD_CTRL, &val1); if (ret) return ret; ret = ksz_pread8(dev, p, REG_PORT_LINK_MD_RESULT, &val2); if (ret) return ret; if (val1 & PORT_START_CABLE_DIAG) data |= PHY_START_CABLE_DIAG; if (val1 & PORT_CABLE_10M_SHORT) data |= PHY_CABLE_10M_SHORT; data |= FIELD_PREP(PHY_CABLE_DIAG_RESULT_M, FIELD_GET(PORT_CABLE_DIAG_RESULT_M, val1)); data |= FIELD_PREP(PHY_CABLE_FAULT_COUNTER_M, (FIELD_GET(PORT_CABLE_FAULT_COUNTER_H, val1) << 8) | FIELD_GET(PORT_CABLE_FAULT_COUNTER_L, val2)); break; case PHY_REG_PHY_CTRL: ret = ksz8_r_phy_ctrl(dev, p, &data); if (ret) return ret; break; default: processed = false; break; } if (processed) *val = data; return 0; } /** * ksz8_w_phy_ctrl - Translates and writes to the SMI interface from a MIIM PHY * Control register (Reg. 31). * @dev: The KSZ device instance. * @port: The port number to be configured. * @val: The register value to be written. * * This function translates control settings from a MIIM PHY Control register * into their corresponding hardware register bit values for the SMI * interface. * * Return: 0 on success, error code on failure. */ static int ksz8_w_phy_ctrl(struct ksz_device *dev, int port, u16 val) { u8 reg_val = 0; int ret; if (val & KSZ886X_CTRL_FORCE_LINK) reg_val |= PORT_FORCE_LINK; if (val & KSZ886X_CTRL_PWRSAVE) reg_val |= PORT_POWER_SAVING; if (val & KSZ886X_CTRL_REMOTE_LOOPBACK) reg_val |= PORT_PHY_REMOTE_LOOPBACK; ret = ksz_prmw8(dev, port, REG_PORT_LINK_MD_CTRL, PORT_FORCE_LINK | PORT_POWER_SAVING | PORT_PHY_REMOTE_LOOPBACK, reg_val); return ret; } /** * ksz8_w_phy_bmcr - Translates and writes to the SMI interface from a MIIM PHY * Basic mode control register (Reg. 0). * @dev: The KSZ device instance. * @port: The port number to be configured. * @val: The register value to be written. * * This function translates control settings from a MIIM PHY Basic mode control * register into their corresponding hardware register bit values for the SMI * interface. * * MIIM Bit Mapping Comparison between KSZ8794 and KSZ8873 * ------------------------------------------------------------------- * MIIM Bit | KSZ8794 Reg/Bit | KSZ8873 Reg/Bit * ----------------------------+-----------------------------+---------------- * Bit 15 - Soft Reset | 0xF/4 | Not supported * Bit 14 - Loopback | 0xD/0 (MAC), 0xF/7 (PHY) ~ 0xD/0 (PHY) * Bit 13 - Force 100 | 0xC/6 = 0xC/6 * Bit 12 - AN Enable | 0xC/7 (reverse logic) ~ 0xC/7 * Bit 11 - Power Down | 0xD/3 = 0xD/3 * Bit 10 - PHY Isolate | 0xF/5 | Not supported * Bit 9 - Restart AN | 0xD/5 = 0xD/5 * Bit 8 - Force Full-Duplex | 0xC/5 = 0xC/5 * Bit 7 - Collision Test/Res. | Not supported | Not supported * Bit 6 - Reserved | Not supported | Not supported * Bit 5 - Hp_mdix | 0x9/7 ~ 0xF/7 * Bit 4 - Force MDI | 0xD/1 = 0xD/1 * Bit 3 - Disable MDIX | 0xD/2 = 0xD/2 * Bit 2 - Disable Far-End F. | ???? | 0xD/4 * Bit 1 - Disable Transmit | 0xD/6 = 0xD/6 * Bit 0 - Disable LED | 0xD/7 = 0xD/7 * ------------------------------------------------------------------- * * Return: 0 on success, error code on failure. */ static int ksz8_w_phy_bmcr(struct ksz_device *dev, u16 port, u16 val) { u8 restart, speed, ctrl, restart_mask; const u16 *regs = dev->info->regs; int ret; /* Do not support PHY reset function. */ if (val & BMCR_RESET) return 0; speed = 0; if (val & KSZ886X_BMCR_HP_MDIX) speed |= PORT_HP_MDIX; ret = ksz_prmw8(dev, port, regs[P_SPEED_STATUS], PORT_HP_MDIX, speed); if (ret) return ret; ctrl = 0; if (ksz_is_ksz88x3(dev)) { if ((val & BMCR_ANENABLE)) ctrl |= PORT_AUTO_NEG_ENABLE; } else { if (!(val & BMCR_ANENABLE)) ctrl |= PORT_AUTO_NEG_DISABLE; /* Fiber port does not support auto-negotiation. */ if (dev->ports[port].fiber) ctrl |= PORT_AUTO_NEG_DISABLE; } if (val & BMCR_SPEED100) ctrl |= PORT_FORCE_100_MBIT; if (val & BMCR_FULLDPLX) ctrl |= PORT_FORCE_FULL_DUPLEX; ret = ksz_prmw8(dev, port, regs[P_FORCE_CTRL], PORT_FORCE_100_MBIT | /* PORT_AUTO_NEG_ENABLE and PORT_AUTO_NEG_DISABLE are the same * bits */ PORT_FORCE_FULL_DUPLEX | PORT_AUTO_NEG_ENABLE, ctrl); if (ret) return ret; restart = 0; restart_mask = PORT_LED_OFF | PORT_TX_DISABLE | PORT_AUTO_NEG_RESTART | PORT_POWER_DOWN | PORT_AUTO_MDIX_DISABLE | PORT_FORCE_MDIX; if (val & KSZ886X_BMCR_DISABLE_LED) restart |= PORT_LED_OFF; if (val & KSZ886X_BMCR_DISABLE_TRANSMIT) restart |= PORT_TX_DISABLE; if (val & BMCR_ANRESTART) restart |= PORT_AUTO_NEG_RESTART; if (val & BMCR_PDOWN) restart |= PORT_POWER_DOWN; if (val & KSZ886X_BMCR_DISABLE_AUTO_MDIX) restart |= PORT_AUTO_MDIX_DISABLE; if (val & KSZ886X_BMCR_FORCE_MDI) restart |= PORT_FORCE_MDIX; if (ksz_is_ksz88x3(dev)) { restart_mask |= KSZ8873_PORT_PHY_LOOPBACK; if (val & BMCR_LOOPBACK) restart |= KSZ8873_PORT_PHY_LOOPBACK; } else { ret = ksz879x_set_loopback(dev, port, val); if (ret) return ret; } return ksz_prmw8(dev, port, regs[P_NEG_RESTART_CTRL], restart_mask, restart); } int ksz8_w_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 val) { const u16 *regs; u8 ctrl, data; u16 p = phy; int ret; regs = dev->info->regs; switch (reg) { case MII_BMCR: ret = ksz8_w_phy_bmcr(dev, p, val); if (ret) return ret; break; case MII_ADVERTISE: ret = ksz_pread8(dev, p, regs[P_LOCAL_CTRL], &ctrl); if (ret) return ret; data = ctrl; data &= ~(PORT_AUTO_NEG_SYM_PAUSE | PORT_AUTO_NEG_100BTX_FD | PORT_AUTO_NEG_100BTX | PORT_AUTO_NEG_10BT_FD | PORT_AUTO_NEG_10BT); if (val & ADVERTISE_PAUSE_CAP) data |= PORT_AUTO_NEG_SYM_PAUSE; if (val & ADVERTISE_100FULL) data |= PORT_AUTO_NEG_100BTX_FD; if (val & ADVERTISE_100HALF) data |= PORT_AUTO_NEG_100BTX; if (val & ADVERTISE_10FULL) data |= PORT_AUTO_NEG_10BT_FD; if (val & ADVERTISE_10HALF) data |= PORT_AUTO_NEG_10BT; if (data != ctrl) { ret = ksz_pwrite8(dev, p, regs[P_LOCAL_CTRL], data); if (ret) return ret; } break; case PHY_REG_LINK_MD: if (val & PHY_START_CABLE_DIAG) ksz_port_cfg(dev, p, REG_PORT_LINK_MD_CTRL, PORT_START_CABLE_DIAG, true); break; case PHY_REG_PHY_CTRL: ret = ksz8_w_phy_ctrl(dev, p, val); if (ret) return ret; break; default: break; } return 0; } void ksz8_cfg_port_member(struct ksz_device *dev, int port, u8 member) { u8 data; ksz_pread8(dev, port, P_MIRROR_CTRL, &data); data &= ~PORT_VLAN_MEMBERSHIP; data |= (member & dev->port_mask); ksz_pwrite8(dev, port, P_MIRROR_CTRL, data); } void ksz8_flush_dyn_mac_table(struct ksz_device *dev, int port) { u8 learn[DSA_MAX_PORTS]; int first, index, cnt; const u16 *regs; regs = dev->info->regs; if ((uint)port < dev->info->port_cnt) { first = port; cnt = port + 1; } else { /* Flush all ports. */ first = 0; cnt = dev->info->port_cnt; } for (index = first; index < cnt; index++) { ksz_pread8(dev, index, regs[P_STP_CTRL], &learn[index]); if (!(learn[index] & PORT_LEARN_DISABLE)) ksz_pwrite8(dev, index, regs[P_STP_CTRL], learn[index] | PORT_LEARN_DISABLE); } ksz_cfg(dev, S_FLUSH_TABLE_CTRL, SW_FLUSH_DYN_MAC_TABLE, true); for (index = first; index < cnt; index++) { if (!(learn[index] & PORT_LEARN_DISABLE)) ksz_pwrite8(dev, index, regs[P_STP_CTRL], learn[index]); } } int ksz8_fdb_dump(struct ksz_device *dev, int port, dsa_fdb_dump_cb_t *cb, void *data) { u8 mac[ETH_ALEN]; u8 src_port, fid; u16 entries = 0; int ret, i; for (i = 0; i < KSZ8_DYN_MAC_ENTRIES; i++) { ret = ksz8_r_dyn_mac_table(dev, i, mac, &fid, &src_port, &entries); if (ret == -ENXIO) return 0; if (ret) return ret; if (i >= entries) return 0; if (port == src_port) { ret = cb(mac, fid, false, data); if (ret) return ret; } } return 0; } static int ksz8_add_sta_mac(struct ksz_device *dev, int port, const unsigned char *addr, u16 vid) { struct alu_struct alu; int index, ret; int empty = 0; alu.port_forward = 0; for (index = 0; index < dev->info->num_statics; index++) { bool valid; ret = ksz8_r_sta_mac_table(dev, index, &alu, &valid); if (ret) return ret; if (!valid) { /* Remember the first empty entry. */ if (!empty) empty = index + 1; continue; } if (!memcmp(alu.mac, addr, ETH_ALEN) && alu.fid == vid) break; } /* no available entry */ if (index == dev->info->num_statics && !empty) return -ENOSPC; /* add entry */ if (index == dev->info->num_statics) { index = empty - 1; memset(&alu, 0, sizeof(alu)); memcpy(alu.mac, addr, ETH_ALEN); alu.is_static = true; } alu.port_forward |= BIT(port); if (vid) { alu.is_use_fid = true; /* Need a way to map VID to FID. */ alu.fid = vid; } return ksz8_w_sta_mac_table(dev, index, &alu); } static int ksz8_del_sta_mac(struct ksz_device *dev, int port, const unsigned char *addr, u16 vid) { struct alu_struct alu; int index, ret; for (index = 0; index < dev->info->num_statics; index++) { bool valid; ret = ksz8_r_sta_mac_table(dev, index, &alu, &valid); if (ret) return ret; if (!valid) continue; if (!memcmp(alu.mac, addr, ETH_ALEN) && alu.fid == vid) break; } /* no available entry */ if (index == dev->info->num_statics) return 0; /* clear port */ alu.port_forward &= ~BIT(port); if (!alu.port_forward) alu.is_static = false; return ksz8_w_sta_mac_table(dev, index, &alu); } int ksz8_mdb_add(struct ksz_device *dev, int port, const struct switchdev_obj_port_mdb *mdb, struct dsa_db db) { return ksz8_add_sta_mac(dev, port, mdb->addr, mdb->vid); } int ksz8_mdb_del(struct ksz_device *dev, int port, const struct switchdev_obj_port_mdb *mdb, struct dsa_db db) { return ksz8_del_sta_mac(dev, port, mdb->addr, mdb->vid); } int ksz8_fdb_add(struct ksz_device *dev, int port, const unsigned char *addr, u16 vid, struct dsa_db db) { return ksz8_add_sta_mac(dev, port, addr, vid); } int ksz8_fdb_del(struct ksz_device *dev, int port, const unsigned char *addr, u16 vid, struct dsa_db db) { return ksz8_del_sta_mac(dev, port, addr, vid); } int ksz8_port_vlan_filtering(struct ksz_device *dev, int port, bool flag, struct netlink_ext_ack *extack) { if (ksz_is_ksz88x3(dev)) return -ENOTSUPP; /* Discard packets with VID not enabled on the switch */ ksz_cfg(dev, S_MIRROR_CTRL, SW_VLAN_ENABLE, flag); /* Discard packets with VID not enabled on the ingress port */ for (port = 0; port < dev->phy_port_cnt; ++port) ksz_port_cfg(dev, port, REG_PORT_CTRL_2, PORT_INGRESS_FILTER, flag); return 0; } static void ksz8_port_enable_pvid(struct ksz_device *dev, int port, bool state) { if (ksz_is_ksz88x3(dev)) { ksz_cfg(dev, REG_SW_INSERT_SRC_PVID, 0x03 << (4 - 2 * port), state); } else { ksz_pwrite8(dev, port, REG_PORT_CTRL_12, state ? 0x0f : 0x00); } } int ksz8_port_vlan_add(struct ksz_device *dev, int port, const struct switchdev_obj_port_vlan *vlan, struct netlink_ext_ack *extack) { bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED; struct ksz_port *p = &dev->ports[port]; u16 data, new_pvid = 0; u8 fid, member, valid; if (ksz_is_ksz88x3(dev)) return -ENOTSUPP; /* If a VLAN is added with untagged flag different from the * port's Remove Tag flag, we need to change the latter. * Ignore VID 0, which is always untagged. * Ignore CPU port, which will always be tagged. */ if (untagged != p->remove_tag && vlan->vid != 0 && port != dev->cpu_port) { unsigned int vid; /* Reject attempts to add a VLAN that requires the * Remove Tag flag to be changed, unless there are no * other VLANs currently configured. */ for (vid = 1; vid < dev->info->num_vlans; ++vid) { /* Skip the VID we are going to add or reconfigure */ if (vid == vlan->vid) continue; ksz8_from_vlan(dev, dev->vlan_cache[vid].table[0], &fid, &member, &valid); if (valid && (member & BIT(port))) return -EINVAL; } ksz_port_cfg(dev, port, P_TAG_CTRL, PORT_REMOVE_TAG, untagged); p->remove_tag = untagged; } ksz8_r_vlan_table(dev, vlan->vid, &data); ksz8_from_vlan(dev, data, &fid, &member, &valid); /* First time to setup the VLAN entry. */ if (!valid) { /* Need to find a way to map VID to FID. */ fid = 1; valid = 1; } member |= BIT(port); ksz8_to_vlan(dev, fid, member, valid, &data); ksz8_w_vlan_table(dev, vlan->vid, data); /* change PVID */ if (vlan->flags & BRIDGE_VLAN_INFO_PVID) new_pvid = vlan->vid; if (new_pvid) { u16 vid; ksz_pread16(dev, port, REG_PORT_CTRL_VID, &vid); vid &= ~VLAN_VID_MASK; vid |= new_pvid; ksz_pwrite16(dev, port, REG_PORT_CTRL_VID, vid); ksz8_port_enable_pvid(dev, port, true); } return 0; } int ksz8_port_vlan_del(struct ksz_device *dev, int port, const struct switchdev_obj_port_vlan *vlan) { u16 data, pvid; u8 fid, member, valid; if (ksz_is_ksz88x3(dev)) return -ENOTSUPP; ksz_pread16(dev, port, REG_PORT_CTRL_VID, &pvid); pvid = pvid & 0xFFF; ksz8_r_vlan_table(dev, vlan->vid, &data); ksz8_from_vlan(dev, data, &fid, &member, &valid); member &= ~BIT(port); /* Invalidate the entry if no more member. */ if (!member) { fid = 0; valid = 0; } ksz8_to_vlan(dev, fid, member, valid, &data); ksz8_w_vlan_table(dev, vlan->vid, data); if (pvid == vlan->vid) ksz8_port_enable_pvid(dev, port, false); return 0; } int ksz8_port_mirror_add(struct ksz_device *dev, int port, struct dsa_mall_mirror_tc_entry *mirror, bool ingress, struct netlink_ext_ack *extack) { if (ingress) { ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_RX, true); dev->mirror_rx |= BIT(port); } else { ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_TX, true); dev->mirror_tx |= BIT(port); } ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_SNIFFER, false); /* configure mirror port */ if (dev->mirror_rx || dev->mirror_tx) ksz_port_cfg(dev, mirror->to_local_port, P_MIRROR_CTRL, PORT_MIRROR_SNIFFER, true); return 0; } void ksz8_port_mirror_del(struct ksz_device *dev, int port, struct dsa_mall_mirror_tc_entry *mirror) { u8 data; if (mirror->ingress) { ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_RX, false); dev->mirror_rx &= ~BIT(port); } else { ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_TX, false); dev->mirror_tx &= ~BIT(port); } ksz_pread8(dev, port, P_MIRROR_CTRL, &data); if (!dev->mirror_rx && !dev->mirror_tx) ksz_port_cfg(dev, mirror->to_local_port, P_MIRROR_CTRL, PORT_MIRROR_SNIFFER, false); } static void ksz8795_cpu_interface_select(struct ksz_device *dev, int port) { struct ksz_port *p = &dev->ports[port]; if (!ksz_is_ksz87xx(dev)) return; if (!p->interface && dev->compat_interface) { dev_warn(dev->dev, "Using legacy switch \"phy-mode\" property, because it is missing on port %d node. " "Please update your device tree.\n", port); p->interface = dev->compat_interface; } } void ksz8_port_setup(struct ksz_device *dev, int port, bool cpu_port) { struct dsa_switch *ds = dev->ds; const u32 *masks; u8 member; masks = dev->info->masks; /* enable broadcast storm limit */ ksz_port_cfg(dev, port, P_BCAST_STORM_CTRL, PORT_BROADCAST_STORM, true); if (!ksz_is_ksz88x3(dev)) ksz8795_set_prio_queue(dev, port, 4); /* disable DiffServ priority */ ksz_port_cfg(dev, port, P_PRIO_CTRL, PORT_DIFFSERV_ENABLE, false); /* replace priority */ ksz_port_cfg(dev, port, P_802_1P_CTRL, masks[PORT_802_1P_REMAPPING], false); /* enable 802.1p priority */ ksz_port_cfg(dev, port, P_PRIO_CTRL, PORT_802_1P_ENABLE, true); if (cpu_port) member = dsa_user_ports(ds); else member = BIT(dsa_upstream_port(ds, port)); ksz8_cfg_port_member(dev, port, member); } static void ksz88x3_config_rmii_clk(struct ksz_device *dev) { struct dsa_port *cpu_dp = dsa_to_port(dev->ds, dev->cpu_port); bool rmii_clk_internal; if (!ksz_is_ksz88x3(dev)) return; rmii_clk_internal = of_property_read_bool(cpu_dp->dn, "microchip,rmii-clk-internal"); ksz_cfg(dev, KSZ88X3_REG_FVID_AND_HOST_MODE, KSZ88X3_PORT3_RMII_CLK_INTERNAL, rmii_clk_internal); } void ksz8_config_cpu_port(struct dsa_switch *ds) { struct ksz_device *dev = ds->priv; struct ksz_port *p; const u32 *masks; const u16 *regs; u8 remote; int i; masks = dev->info->masks; regs = dev->info->regs; ksz_cfg(dev, regs[S_TAIL_TAG_CTRL], masks[SW_TAIL_TAG_ENABLE], true); ksz8_port_setup(dev, dev->cpu_port, true); ksz8795_cpu_interface_select(dev, dev->cpu_port); ksz88x3_config_rmii_clk(dev); for (i = 0; i < dev->phy_port_cnt; i++) { ksz_port_stp_state_set(ds, i, BR_STATE_DISABLED); } for (i = 0; i < dev->phy_port_cnt; i++) { p = &dev->ports[i]; if (!ksz_is_ksz88x3(dev)) { ksz_pread8(dev, i, regs[P_REMOTE_STATUS], &remote); if (remote & KSZ8_PORT_FIBER_MODE) p->fiber = 1; } if (p->fiber) ksz_port_cfg(dev, i, regs[P_STP_CTRL], PORT_FORCE_FLOW_CTRL, true); else ksz_port_cfg(dev, i, regs[P_STP_CTRL], PORT_FORCE_FLOW_CTRL, false); } } /** * ksz8_phy_port_link_up - Configures ports with integrated PHYs * @dev: The KSZ device instance. * @port: The port number to configure. * @duplex: The desired duplex mode. * @tx_pause: If true, enables transmit pause. * @rx_pause: If true, enables receive pause. * * Description: * The function configures flow control settings for a given port based on the * desired settings and current duplex mode. * * According to the KSZ8873 datasheet, the PORT_FORCE_FLOW_CTRL bit in the * Port Control 2 register (0x1A for Port 1, 0x22 for Port 2, 0x32 for Port 3) * determines how flow control is handled on the port: * "1 = will always enable full-duplex flow control on the port, regardless * of AN result. * 0 = full-duplex flow control is enabled based on AN result." * * This means that the flow control behavior depends on the state of this bit: * - If PORT_FORCE_FLOW_CTRL is set to 1, the switch will ignore AN results and * force flow control on the port. * - If PORT_FORCE_FLOW_CTRL is set to 0, the switch will enable or disable * flow control based on the AN results. * * However, there is a potential limitation in this configuration. It is * currently not possible to force disable flow control on a port if we still * advertise pause support. While such a configuration is not currently * supported by Linux, and may not make practical sense, it's important to be * aware of this limitation when working with the KSZ8873 and similar devices. */ static void ksz8_phy_port_link_up(struct ksz_device *dev, int port, int duplex, bool tx_pause, bool rx_pause) { const u16 *regs = dev->info->regs; u8 sctrl = 0; /* The KSZ8795 switch differs from the KSZ8873 by supporting * asymmetric pause control. However, since a single bit is used to * control both RX and TX pause, we can't enforce asymmetric pause * control - both TX and RX pause will be either enabled or disabled * together. * * If auto-negotiation is enabled, we usually allow the flow control to * be determined by the auto-negotiation process based on the * capabilities of both link partners. However, for KSZ8873, the * PORT_FORCE_FLOW_CTRL bit may be set by the hardware bootstrap, * ignoring the auto-negotiation result. Thus, even in auto-negotiation * mode, we need to ensure that the PORT_FORCE_FLOW_CTRL bit is * properly cleared. * * In the absence of pause auto-negotiation, we will enforce symmetric * pause control for both variants of switches - KSZ8873 and KSZ8795. * * Autoneg Pause Autoneg rx,tx PORT_FORCE_FLOW_CTRL * 1 1 x 0 * 0 1 x 0 (flow control probably disabled) * x 0 1 1 (flow control force enabled) * 1 0 0 0 (flow control still depends on * aneg result due to hardware) * 0 0 0 0 (flow control probably disabled) */ if (dev->ports[port].manual_flow && tx_pause) sctrl |= PORT_FORCE_FLOW_CTRL; ksz_prmw8(dev, port, regs[P_STP_CTRL], PORT_FORCE_FLOW_CTRL, sctrl); } /** * ksz8_cpu_port_link_up - Configures the CPU port of the switch. * @dev: The KSZ device instance. * @speed: The desired link speed. * @duplex: The desired duplex mode. * @tx_pause: If true, enables transmit pause. * @rx_pause: If true, enables receive pause. * * Description: * The function configures flow control and speed settings for the CPU * port of the switch based on the desired settings, current duplex mode, and * speed. */ static void ksz8_cpu_port_link_up(struct ksz_device *dev, int speed, int duplex, bool tx_pause, bool rx_pause) { const u16 *regs = dev->info->regs; u8 ctrl = 0; /* SW_FLOW_CTRL, SW_HALF_DUPLEX, and SW_10_MBIT bits are bootstrappable * at least on KSZ8873. They can have different values depending on your * board setup. */ if (tx_pause || rx_pause) ctrl |= SW_FLOW_CTRL; if (duplex == DUPLEX_HALF) ctrl |= SW_HALF_DUPLEX; /* This hardware only supports SPEED_10 and SPEED_100. For SPEED_10 * we need to set the SW_10_MBIT bit. Otherwise, we can leave it 0. */ if (speed == SPEED_10) ctrl |= SW_10_MBIT; ksz_rmw8(dev, regs[S_BROADCAST_CTRL], SW_HALF_DUPLEX | SW_FLOW_CTRL | SW_10_MBIT, ctrl); } void ksz8_phylink_mac_link_up(struct ksz_device *dev, int port, unsigned int mode, phy_interface_t interface, struct phy_device *phydev, int speed, int duplex, bool tx_pause, bool rx_pause) { /* If the port is the CPU port, apply special handling. Only the CPU * port is configured via global registers. */ if (dev->cpu_port == port) ksz8_cpu_port_link_up(dev, speed, duplex, tx_pause, rx_pause); else if (dev->info->internal_phy[port]) ksz8_phy_port_link_up(dev, port, duplex, tx_pause, rx_pause); } static int ksz8_handle_global_errata(struct dsa_switch *ds) { struct ksz_device *dev = ds->priv; int ret = 0; /* KSZ87xx Errata DS80000687C. * Module 2: Link drops with some EEE link partners. * An issue with the EEE next page exchange between the * KSZ879x/KSZ877x/KSZ876x and some EEE link partners may result in * the link dropping. */ if (dev->info->ksz87xx_eee_link_erratum) ret = ksz8_ind_write8(dev, TABLE_EEE, REG_IND_EEE_GLOB2_HI, 0); return ret; } int ksz8_enable_stp_addr(struct ksz_device *dev) { struct alu_struct alu; /* Setup STP address for STP operation. */ memset(&alu, 0, sizeof(alu)); ether_addr_copy(alu.mac, eth_stp_addr); alu.is_static = true; alu.is_override = true; alu.port_forward = dev->info->cpu_ports; return ksz8_w_sta_mac_table(dev, 0, &alu); } int ksz8_setup(struct dsa_switch *ds) { struct ksz_device *dev = ds->priv; int i; ds->mtu_enforcement_ingress = true; /* We rely on software untagging on the CPU port, so that we * can support both tagged and untagged VLANs */ ds->untag_bridge_pvid = true; /* VLAN filtering is partly controlled by the global VLAN * Enable flag */ ds->vlan_filtering_is_global = true; /* Enable automatic fast aging when link changed detected. */ ksz_cfg(dev, S_LINK_AGING_CTRL, SW_LINK_AUTO_AGING, true); /* Enable aggressive back off algorithm in half duplex mode. */ regmap_update_bits(ksz_regmap_8(dev), REG_SW_CTRL_1, SW_AGGR_BACKOFF, SW_AGGR_BACKOFF); /* * Make sure unicast VLAN boundary is set as default and * enable no excessive collision drop. */ regmap_update_bits(ksz_regmap_8(dev), REG_SW_CTRL_2, UNICAST_VLAN_BOUNDARY | NO_EXC_COLLISION_DROP, UNICAST_VLAN_BOUNDARY | NO_EXC_COLLISION_DROP); ksz_cfg(dev, S_REPLACE_VID_CTRL, SW_REPLACE_VID, false); ksz_cfg(dev, S_MIRROR_CTRL, SW_MIRROR_RX_TX, false); if (!ksz_is_ksz88x3(dev)) ksz_cfg(dev, REG_SW_CTRL_19, SW_INS_TAG_ENABLE, true); for (i = 0; i < (dev->info->num_vlans / 4); i++) ksz8_r_vlan_entries(dev, i); return ksz8_handle_global_errata(ds); } void ksz8_get_caps(struct ksz_device *dev, int port, struct phylink_config *config) { config->mac_capabilities = MAC_10 | MAC_100; /* Silicon Errata Sheet (DS80000830A): * "Port 1 does not respond to received flow control PAUSE frames" * So, disable Pause support on "Port 1" (port == 0) for all ksz88x3 * switches. */ if (!ksz_is_ksz88x3(dev) || port) config->mac_capabilities |= MAC_SYM_PAUSE; /* Asym pause is not supported on KSZ8863 and KSZ8873 */ if (!ksz_is_ksz88x3(dev)) config->mac_capabilities |= MAC_ASYM_PAUSE; } u32 ksz8_get_port_addr(int port, int offset) { return PORT_CTRL_ADDR(port, offset); } int ksz8_switch_init(struct ksz_device *dev) { dev->cpu_port = fls(dev->info->cpu_ports) - 1; dev->phy_port_cnt = dev->info->port_cnt - 1; dev->port_mask = (BIT(dev->phy_port_cnt) - 1) | dev->info->cpu_ports; return 0; } void ksz8_switch_exit(struct ksz_device *dev) { ksz8_reset_switch(dev); } MODULE_AUTHOR("Tristram Ha <Tristram.Ha@microchip.com>"); MODULE_DESCRIPTION("Microchip KSZ8795 Series Switch DSA Driver"); MODULE_LICENSE("GPL");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
You can’t perform that action at this time.