diff --git a/Documentation/devicetree/bindings/net/adi,ad3306.yaml b/Documentation/devicetree/bindings/net/adi,ad3306.yaml new file mode 100644 index 000000000000..785d05c995db --- /dev/null +++ b/Documentation/devicetree/bindings/net/adi,ad3306.yaml @@ -0,0 +1,71 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/net/adi,ad3306.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: ADI ADIN1140 10BASE-T1S MAC-PHY + +maintainers: + - Ciprian Regus + +description: | + The ADIN1140 (also called AD3306) is a low power single port + 10BASE-T1S MAC-PHY. It integrates an Ethernet PHY with a MAC + and all the associated analog circuitry. + The device tries to implement the Open Alliance TC6 10BASE-T1x MAC-PHY + Serial Interface specification and is compliant with the + IEEE 802.3cg-2019 Ethernet standard for 10 Mbps single pair + Ethernet (SPE). The device has a 4-wire SPI interface for + communication between the MAC and host processor. + +allOf: + - $ref: /schemas/net/ethernet-controller.yaml# + - $ref: /schemas/spi/spi-peripheral-props.yaml# + +properties: + compatible: + oneOf: + - items: + - const: adi,adin1140 + - const: adi,ad3306 + - const: adi,ad3306 + + reg: + maxItems: 1 + + spi-max-frequency: + maximum: 25000000 + + interrupts: + maxItems: 1 + description: Interrupt from the MAC-PHY for receive data available + and error conditions + +required: + - compatible + - reg + - interrupts + - spi-max-frequency + +unevaluatedProperties: false + +examples: + - | + #include + + spi { + #address-cells = <1>; + #size-cells = <0>; + + ethernet@0 { + compatible = "adi,ad3306"; + reg = <0>; + spi-max-frequency = <23000000>; + + interrupt-parent = <&gpio>; + interrupts = <6 IRQ_TYPE_LEVEL_LOW>; + + local-mac-address = [ 00 11 22 33 44 55 ]; + }; + }; diff --git a/Documentation/networking/oa-tc6-framework.rst b/Documentation/networking/oa-tc6-framework.rst index fe2aabde923a..013824078cea 100644 --- a/Documentation/networking/oa-tc6-framework.rst +++ b/Documentation/networking/oa-tc6-framework.rst @@ -454,7 +454,8 @@ Device drivers API The include/linux/oa_tc6.h defines the following functions: .. c:function:: struct oa_tc6 *oa_tc6_init(struct spi_device *spi, \ - struct net_device *netdev) + struct net_device *netdev, \ + struct oa_tc6_quirks *quirks) Initialize OA TC6 lib. diff --git a/MAINTAINERS b/MAINTAINERS index 00e7b26e0a23..6940aa3d498b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1900,6 +1900,21 @@ S: Supported W: https://ez.analog.com/linux-software-drivers F: drivers/dma/dma-axi-dmac.c +ANALOG DEVICES INC ETHERNET DRIVERS +M: Ciprian Regus +L: netdev@vger.kernel.org +S: Maintained +W: https://ez.analog.com/linux-software-drivers +F: Documentation/devicetree/bindings/net/adi,ad3306.yaml +F: drivers/net/ethernet/adi/adin1140.c + +ANALOG DEVICES INC ETHERNET PHY DRIVERS +M: Ciprian Regus +L: netdev@vger.kernel.org +S: Maintained +W: https://ez.analog.com/linux-software-drivers +F: drivers/net/phy/adin1140-phy.c + ANALOG DEVICES INC IIO DRIVERS M: Nuno Sá M: Michael Hennerich diff --git a/drivers/net/ethernet/adi/Kconfig b/drivers/net/ethernet/adi/Kconfig index 760a9a60bc15..bdb8ff7d15da 100644 --- a/drivers/net/ethernet/adi/Kconfig +++ b/drivers/net/ethernet/adi/Kconfig @@ -26,4 +26,16 @@ config ADIN1110 Say yes here to build support for Analog Devices ADIN1110 Low Power 10BASE-T1L Ethernet MAC-PHY. +config ADIN1140 + tristate "Analog Devices ADIN1140 MAC-PHY" + depends on SPI + select ADIN1140_PHY + select OA_TC6 + help + Say yes here to build support for Analog Devices, Inc. ADIN1140 + 10BASE-T1S Ethernet MAC-PHY. + + To compile this driver as a module, choose M here. The module will be + called adin1140. + endif # NET_VENDOR_ADI diff --git a/drivers/net/ethernet/adi/Makefile b/drivers/net/ethernet/adi/Makefile index d0383d94303c..0390ca8ccc49 100644 --- a/drivers/net/ethernet/adi/Makefile +++ b/drivers/net/ethernet/adi/Makefile @@ -4,3 +4,4 @@ # obj-$(CONFIG_ADIN1110) += adin1110.o +obj-$(CONFIG_ADIN1140) += adin1140.o diff --git a/drivers/net/ethernet/adi/adin1140.c b/drivers/net/ethernet/adi/adin1140.c new file mode 100644 index 000000000000..93710baca151 --- /dev/null +++ b/drivers/net/ethernet/adi/adin1140.c @@ -0,0 +1,791 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for Analog Devices, Inc. ADIN1140 10BASE-T1S MAC-PHY + * + * Copyright 2026 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define ADIN1140_MAC_CONFIG2_STAT_CLR_RD BIT(6) +#define ADIN1140_MAC_CONFIG2_FWD_UNK2HOST BIT(2) + +#define ADIN1140_MAC_P1_LOOP_ADDR_REG 0xC4 + +#define ADIN1140_MAC_ADDR_FILT_UPR_REG 0x50 +#define ADIN1140_MAC_ADDR_FILT_APPLY2PORT1 BIT(30) +#define ADIN1140_MAC_ADDR_FILT_TO_HOST BIT(16) + +#define ADIN1140_MAC_ADDR_FILT_LWR_REG 0x51 + +#define ADIN1140_MAC_ADDR_MASK_UPR_REG 0x70 +#define ADIN1140_MAC_ADDR_MASK_LWR_REG 0x71 + +#define ADIN1140_MAC_FILT_MC_SLOT 0U +#define ADIN1140_MAC_FILT_BC_SLOT 1U +#define ADIN1140_MAC_FILT_UC_SLOT 2U +#define ADIN1140_MAC_FILT_MAX_SLOT 16U +#define ADIN1140_MAC_FILT_MASK_LIMIT 2U + +#define ADIN1140_MAC_RX_FRAME_CNT 0xA1 +#define ADIN1140_MAC_RX_BC_FRAME_CNT 0xA2 +#define ADIN1140_MAC_RX_MC_FRAME_CNT 0xA3 +#define ADIN1140_MAC_RX_CRC_ERR_CNT 0xA5 +#define ADIN1140_MAC_RX_ALIGN_ERR_CNT 0xA6 +#define ADIN1140_MAC_RX_PREAMBLE_ERR_CNT 0xA7 +#define ADIN1140_MAC_RX_SHORT_ERR_CNT 0xA8 +#define ADIN1140_MAC_RX_LONG_ERR_CNT 0xA9 +#define ADIN1140_MAC_RX_PHY_ERR_CNT 0xAA +#define ADIN1140_MAC_RX_DRP_FULL_CNT 0xAB +#define ADIN1140_MAC_RX_DRP_FILTER_CNT 0xAD +#define ADIN1140_MAC_RX_IFG_ERR_CNT 0xAE +#define ADIN1140_MAC_TX_FRAME_CNT 0xB1 +#define ADIN1140_MAC_TX_BC_FRAME_CNT 0xB2 +#define ADIN1140_MAC_TX_MC_FRAME_CNT 0xB3 +#define ADIN1140_MAC_TX_SINGLE_COL_CNT 0xB5 +#define ADIN1140_MAC_TX_MULTI_COL_CNT 0xB6 +#define ADIN1140_MAC_TX_DEFERRED_CNT 0xB7 +#define ADIN1140_MAC_TX_LATE_COL_CNT 0xB8 +#define ADIN1140_MAC_TX_EXCESS_COL_CNT 0xB9 +#define ADIN1140_MAC_TX_UNDERRUN_CNT 0xBA + +/* ADIN1140_MAC_FILT_MAX_SLOT - 3 (multicast, broadcast and unicast + * reserved slots) + */ +#define ADIN1140_MAC_FILT_AVAIL 13U + +#define ADIN1140_PHY_CTRL_DEFAULT 0x1000 +#define ADIN1140_PHY_STATUS_DEFAULT 0x082D +#define ADIN1140_PHY_ID1 0x0283 +#define ADIN1140_PHY_ID2 0xBE00 + +#define ADIN1140_STATS_CHECK_DELAY (3 * HZ) + +enum adin1140_statistics_entry { + rx_frames, + rx_bc_frames, + rx_mc_frames, + rx_crc_errors, + rx_align_errors, + rx_preamble_errors, + rx_short_frame_errors, + rx_long_frame_errors, + rx_phy_errors, + rx_fifo_full_dropped, + rx_addr_filter_dropped, + rx_ifg_errors, + tx_frames, + tx_bc_frames, + tx_mc_frames, + tx_single_collision, + tx_multi_collision, + tx_deferred, + tx_late_collision, + tx_excess_collision, + tx_underrun, + ADIN1140_STATS_CNT, +}; + +struct adin1140_statistics_reg { + const char *name; + enum adin1140_statistics_entry idx; +}; + +struct adin1140_priv { + struct net_device *netdev; + struct oa_tc6 *tc6; + struct mii_bus *mdiobus; + struct phy_device *phydev; + struct delayed_work stats_work; + + /* Protects stats[] from concurrent updates in adin1140_stats_work + * and reads in the get_stats functions + */ + spinlock_t stat_lock; + u64 stats[ADIN1140_STATS_CNT]; +}; + +static const u32 adin1140_stat_regs[] = { + [rx_frames] = ADIN1140_MAC_RX_FRAME_CNT, + [rx_bc_frames] = ADIN1140_MAC_RX_BC_FRAME_CNT, + [rx_mc_frames] = ADIN1140_MAC_RX_MC_FRAME_CNT, + [rx_crc_errors] = ADIN1140_MAC_RX_CRC_ERR_CNT, + [rx_align_errors] = ADIN1140_MAC_RX_ALIGN_ERR_CNT, + [rx_preamble_errors] = ADIN1140_MAC_RX_PREAMBLE_ERR_CNT, + [rx_short_frame_errors] = ADIN1140_MAC_RX_SHORT_ERR_CNT, + [rx_long_frame_errors] = ADIN1140_MAC_RX_LONG_ERR_CNT, + [rx_phy_errors] = ADIN1140_MAC_RX_PHY_ERR_CNT, + [rx_fifo_full_dropped] = ADIN1140_MAC_RX_DRP_FULL_CNT, + [rx_addr_filter_dropped] = ADIN1140_MAC_RX_DRP_FILTER_CNT, + [rx_ifg_errors] = ADIN1140_MAC_RX_IFG_ERR_CNT, + [tx_frames] = ADIN1140_MAC_TX_FRAME_CNT, + [tx_bc_frames] = ADIN1140_MAC_TX_BC_FRAME_CNT, + [tx_mc_frames] = ADIN1140_MAC_TX_MC_FRAME_CNT, + [tx_single_collision] = ADIN1140_MAC_TX_SINGLE_COL_CNT, + [tx_multi_collision] = ADIN1140_MAC_TX_MULTI_COL_CNT, + [tx_deferred] = ADIN1140_MAC_TX_DEFERRED_CNT, + [tx_late_collision] = ADIN1140_MAC_TX_LATE_COL_CNT, + [tx_excess_collision] = ADIN1140_MAC_TX_EXCESS_COL_CNT, + [tx_underrun] = ADIN1140_MAC_TX_UNDERRUN_CNT, +}; + +static const struct adin1140_statistics_reg adin1140_stats[] = { + {.name = "rx_preamble_errors", .idx = rx_preamble_errors}, + {.name = "rx_ifg_errors", .idx = rx_ifg_errors}, +}; + +static int adin1140_mac_filter_set(struct adin1140_priv *priv, + const u8 *addr, const u8 *mask, + u8 slot) +{ + u32 reg_address; + u32 val; + int ret; + + if (slot >= ADIN1140_MAC_FILT_MAX_SLOT) + return -ENOSPC; + + reg_address = ADIN1140_MAC_ADDR_FILT_UPR_REG + 2 * slot; + + ret = oa_tc6_write_register_mms(priv->tc6, OA_TC6_MAC_MMS1, + reg_address, + get_unaligned_be16(&addr[0]) | + ADIN1140_MAC_ADDR_FILT_APPLY2PORT1 | + ADIN1140_MAC_ADDR_FILT_TO_HOST); + if (ret) + return ret; + + reg_address = ADIN1140_MAC_ADDR_FILT_LWR_REG + 2 * slot; + ret = oa_tc6_write_register_mms(priv->tc6, OA_TC6_MAC_MMS1, + reg_address, + get_unaligned_be32(&addr[2])); + if (ret) + return ret; + + /* Only the first 2 destination MAC filter slots support masking. + * For the other entries, the destination address in the received + * frame must match exactly. + */ + if (slot >= ADIN1140_MAC_FILT_MASK_LIMIT) + return 0; + + val = get_unaligned_be16(&mask[0]); + reg_address = ADIN1140_MAC_ADDR_MASK_UPR_REG + (2 * slot); + + ret = oa_tc6_write_register_mms(priv->tc6, OA_TC6_MAC_MMS1, + reg_address, val); + if (ret) + return ret; + + val = get_unaligned_be32(&mask[2]); + reg_address = ADIN1140_MAC_ADDR_MASK_LWR_REG + (2 * slot); + + return oa_tc6_write_register_mms(priv->tc6, OA_TC6_MAC_MMS1, + reg_address, val); +} + +static int adin1140_mac_filter_clear(struct adin1140_priv *priv, u8 slot) +{ + u8 mask[ETH_ALEN]; + u8 addr[ETH_ALEN]; + + memset(mask, 0xFF, ETH_ALEN); + memset(addr, 0x0, ETH_ALEN); + + return adin1140_mac_filter_set(priv, addr, mask, slot); +} + +static int adin1140_filter_unicast(struct adin1140_priv *priv) +{ + /* Only the first 2 filter slots support masking, so no unicast + * address will ever need a mask. The first slots are used for the + * all multicast and broadcast filter. + */ + return adin1140_mac_filter_set(priv, priv->netdev->dev_addr, NULL, + ADIN1140_MAC_FILT_UC_SLOT); +} + +static int adin1140_filter_all_multicast(struct adin1140_priv *priv, bool en) +{ + u8 multicast_addr[ETH_ALEN] = {1, 0, 0, 0, 0, 0}; + + if (en) + return adin1140_mac_filter_set(priv, multicast_addr, + multicast_addr, + ADIN1140_MAC_FILT_MC_SLOT); + + return adin1140_mac_filter_clear(priv, ADIN1140_MAC_FILT_MC_SLOT); +} + +static int adin1140_filter_broadcast(struct adin1140_priv *priv, bool enabled) +{ + u8 mask[ETH_ALEN]; + + if (enabled) { + memset(mask, 0xFF, ETH_ALEN); + return adin1140_mac_filter_set(priv, mask, mask, + ADIN1140_MAC_FILT_BC_SLOT); + } + + return adin1140_mac_filter_clear(priv, ADIN1140_MAC_FILT_BC_SLOT); +} + +static int adin1140_default_filter_config(struct adin1140_priv *priv) +{ + int ret; + + ret = adin1140_filter_broadcast(priv, true); + if (ret) + return ret; + + return adin1140_filter_unicast(priv); +} + +static int adin1140_promiscuous_mode(struct adin1140_priv *priv, bool enabled) +{ + int ret; + u32 val; + + ret = oa_tc6_read_register(priv->tc6, OA_TC6_REG_CONFIG2, &val); + if (ret) + return ret; + + if (enabled) + val |= ADIN1140_MAC_CONFIG2_FWD_UNK2HOST; + else + val &= ~ADIN1140_MAC_CONFIG2_FWD_UNK2HOST; + + return oa_tc6_write_register(priv->tc6, OA_TC6_REG_CONFIG2, val); +} + +static int adin1140_rx_mode(struct net_device *dev, + struct netdev_hw_addr_list *uc, + struct netdev_hw_addr_list *mc) +{ + struct adin1140_priv *priv = netdev_priv(dev); + struct netdev_hw_addr *ha; + bool all_multi, promisc; + u32 mac_addrs; + u8 slot, i; + int ret; + + /* The ADIN1140 has 16 dest MAC address filter slots: + * 0 - reserved for all multicast filter. + * 1 - reserved for broadcast filter. + * 2 - reserved for the device's own unicast MAC. + * 3 -> 15 - available for other unicast/multicast filters. + */ + + mac_addrs = netdev_hw_addr_list_count(uc); + all_multi = false; + promisc = false; + + if (priv->netdev->flags & IFF_PROMISC) + promisc = true; + else if (priv->netdev->flags & IFF_ALLMULTI) + all_multi = true; + else + mac_addrs += netdev_hw_addr_list_count(mc); + + if (mac_addrs > ADIN1140_MAC_FILT_AVAIL) { + /* The filter table is full. Enable promisc mode. */ + promisc = true; + } + + ret = adin1140_promiscuous_mode(priv, promisc); + if (ret) + return ret; + + ret = adin1140_filter_all_multicast(priv, all_multi); + if (ret) + return ret; + + slot = ADIN1140_MAC_FILT_UC_SLOT + 1; + if (!promisc) { + netdev_hw_addr_list_for_each(ha, uc) { + ret = adin1140_mac_filter_set(priv, ha->addr, NULL, + slot); + if (ret) + return ret; + + slot++; + } + + if (!all_multi) { + netdev_hw_addr_list_for_each(ha, mc) { + ret = adin1140_mac_filter_set(priv, ha->addr, + NULL, slot); + if (ret) + return ret; + + slot++; + } + } + } + + for (i = slot; i < ADIN1140_MAC_FILT_MAX_SLOT; i++) { + ret = adin1140_mac_filter_clear(priv, i); + if (ret) + return ret; + } + + return 0; +} + +static void adin1140_stats_work(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct adin1140_priv *priv; + u32 reg_val; + int ret; + u32 i; + + priv = container_of(dwork, struct adin1140_priv, stats_work); + + for (i = 0; i < ARRAY_SIZE(adin1140_stat_regs); i++) { + ret = oa_tc6_read_register_mms(priv->tc6, OA_TC6_MAC_MMS1, + adin1140_stat_regs[i], + ®_val); + if (ret) + goto out; + + scoped_guard(spinlock, &priv->stat_lock) + priv->stats[i] += reg_val; + } + +out: + schedule_delayed_work(dwork, ADIN1140_STATS_CHECK_DELAY); +} + +static int adin1140_configure(struct adin1140_priv *priv) +{ + u32 reg_val; + int ret; + + ret = oa_tc6_zero_align_receive_frame_enable(priv->tc6); + if (ret) + return ret; + + /* Disable MAC loopback */ + ret = oa_tc6_write_register_mms(priv->tc6, OA_TC6_MAC_MMS1, + ADIN1140_MAC_P1_LOOP_ADDR_REG, 0x0); + if (ret) + return ret; + + ret = oa_tc6_read_register(priv->tc6, OA_TC6_REG_CONFIG2, ®_val); + if (ret) + return ret; + + reg_val |= ADIN1140_MAC_CONFIG2_STAT_CLR_RD; + + ret = oa_tc6_write_register(priv->tc6, OA_TC6_REG_CONFIG2, reg_val); + if (ret) + return ret; + + return adin1140_default_filter_config(priv); +} + +static int adin1140_open(struct net_device *netdev) +{ + struct adin1140_priv *priv = netdev_priv(netdev); + + schedule_delayed_work(&priv->stats_work, ADIN1140_STATS_CHECK_DELAY); + + phy_start(netdev->phydev); + netif_start_queue(netdev); + + return 0; +} + +static int adin1140_close(struct net_device *netdev) +{ + struct adin1140_priv *priv = netdev_priv(netdev); + + cancel_delayed_work_sync(&priv->stats_work); + + netif_stop_queue(netdev); + phy_stop(netdev->phydev); + + return 0; +} + +static netdev_tx_t adin1140_start_xmit(struct sk_buff *skb, + struct net_device *netdev) +{ + struct adin1140_priv *priv = netdev_priv(netdev); + + /* The MAC doesn't automatically pad the frame to a 60 byte minimum + * size in case the host sends a shorter skb, so we have to do it in + * the driver. The FCS will be added by the MAC. + */ + if (skb_put_padto(skb, ETH_ZLEN)) + return NETDEV_TX_OK; + + return oa_tc6_start_xmit(priv->tc6, skb); +} + +static int adin1140_set_mac_address(struct net_device *netdev, void *addr) +{ + struct adin1140_priv *priv = netdev_priv(netdev); + struct sockaddr *address = addr; + u8 mask[ETH_ALEN]; + int ret; + + ret = eth_prepare_mac_addr_change(netdev, addr); + if (ret < 0) + return ret; + + if (ether_addr_equal(address->sa_data, netdev->dev_addr)) + return 0; + + memset(mask, 0xFF, ETH_ALEN); + ret = adin1140_mac_filter_set(priv, address->sa_data, mask, + ADIN1140_MAC_FILT_UC_SLOT); + if (ret) + return ret; + + eth_commit_mac_addr_change(netdev, addr); + + return 0; +} + +static void __adin1140_ndo_get_stats64(struct adin1140_priv *priv, + struct rtnl_link_stats64 *storage) +{ + storage->rx_errors = priv->stats[rx_crc_errors] + + priv->stats[rx_align_errors] + + priv->stats[rx_preamble_errors] + + priv->stats[rx_short_frame_errors] + + priv->stats[rx_long_frame_errors] + + priv->stats[rx_phy_errors] + + priv->stats[rx_ifg_errors]; + + storage->tx_errors = priv->stats[tx_excess_collision] + + priv->stats[tx_underrun]; + + storage->rx_dropped = priv->stats[rx_fifo_full_dropped] + + priv->stats[rx_addr_filter_dropped]; + + storage->multicast = priv->stats[rx_mc_frames]; + + storage->collisions = priv->stats[tx_single_collision] + + priv->stats[tx_multi_collision]; + + storage->rx_length_errors = priv->stats[rx_short_frame_errors] + + priv->stats[rx_long_frame_errors]; + storage->rx_over_errors = priv->stats[rx_fifo_full_dropped]; + storage->rx_crc_errors = priv->stats[rx_crc_errors]; + storage->rx_frame_errors = priv->stats[rx_align_errors]; + storage->rx_missed_errors = priv->stats[rx_fifo_full_dropped]; + + storage->tx_aborted_errors = priv->stats[tx_excess_collision]; + storage->tx_fifo_errors = priv->stats[tx_underrun]; + storage->tx_window_errors = priv->stats[tx_late_collision]; +} + +static void adin1140_ndo_get_stats64(struct net_device *dev, + struct rtnl_link_stats64 *storage) +{ + struct adin1140_priv *priv = netdev_priv(dev); + + storage->rx_packets = priv->netdev->stats.rx_packets; + storage->tx_packets = priv->netdev->stats.tx_packets; + + storage->rx_bytes = priv->netdev->stats.rx_bytes; + storage->tx_bytes = priv->netdev->stats.tx_bytes; + + scoped_guard(spinlock, &priv->stat_lock) + __adin1140_ndo_get_stats64(priv, storage); +} + +static void adin1140_get_drvinfo(struct net_device *netdev, + struct ethtool_drvinfo *info) +{ + strscpy(info->driver, "ADIN1140", sizeof(info->driver)); + strscpy(info->bus_info, dev_name(netdev->dev.parent), + sizeof(info->bus_info)); +} + +static void adin1140_get_ethtool_stats(struct net_device *netdev, + struct ethtool_stats *stats, u64 *data) +{ + struct adin1140_priv *priv = netdev_priv(netdev); + u32 i; + + scoped_guard(spinlock, &priv->stat_lock) { + for (i = 0; i < ARRAY_SIZE(adin1140_stats); i++) + data[i] = priv->stats[adin1140_stats[i].idx]; + } +} + +static void adin1140_get_ethtool_strings(struct net_device *netdev, u32 sset, + u8 *p) +{ + u32 i; + + switch (sset) { + case ETH_SS_STATS: + for (i = 0; i < ARRAY_SIZE(adin1140_stats); i++) + ethtool_puts(&p, adin1140_stats[i].name); + + break; + } +} + +static int adin1140_get_sset_count(struct net_device *netdev, int sset) +{ + switch (sset) { + case ETH_SS_STATS: + return ARRAY_SIZE(adin1140_stats); + default: + return -EOPNOTSUPP; + } +} + +static void __adin1140_eth_mac_stats(struct adin1140_priv *priv, + struct ethtool_eth_mac_stats *mac_stats) +{ + mac_stats->FramesReceivedOK = priv->stats[rx_frames]; + mac_stats->BroadcastFramesReceivedOK = priv->stats[rx_bc_frames]; + mac_stats->MulticastFramesReceivedOK = priv->stats[rx_mc_frames]; + mac_stats->FrameCheckSequenceErrors = priv->stats[rx_crc_errors]; + mac_stats->AlignmentErrors = priv->stats[rx_align_errors]; + mac_stats->FrameTooLongErrors = priv->stats[rx_long_frame_errors]; + mac_stats->FramesLostDueToIntMACRcvError = + priv->stats[rx_fifo_full_dropped]; + mac_stats->FramesTransmittedOK = priv->stats[tx_frames]; + mac_stats->BroadcastFramesXmittedOK = priv->stats[tx_bc_frames]; + mac_stats->MulticastFramesXmittedOK = priv->stats[tx_mc_frames]; + mac_stats->SingleCollisionFrames = priv->stats[tx_single_collision]; + mac_stats->MultipleCollisionFrames = priv->stats[tx_multi_collision]; + mac_stats->FramesWithDeferredXmissions = priv->stats[tx_deferred]; + mac_stats->LateCollisions = priv->stats[tx_late_collision]; + mac_stats->FramesAbortedDueToXSColls = + priv->stats[tx_excess_collision]; + mac_stats->FramesLostDueToIntMACXmitError = priv->stats[tx_underrun]; +} + +static void adin1140_get_eth_mac_stats(struct net_device *netdev, + struct ethtool_eth_mac_stats *mac_stats) +{ + struct adin1140_priv *priv = netdev_priv(netdev); + + scoped_guard(spinlock, &priv->stat_lock) + __adin1140_eth_mac_stats(priv, mac_stats); +} + +static int adin1140_mdiobus_read(struct mii_bus *bus, int addr, int regnum) +{ + /* The ADIN1140's standard PHY C22 register map (OA TC6 0xFF00 - + * 0xFF1F), of which only 0xFF00 - 0xFF03 are implemented) cannot be + * accessed while frames are being received by the PHY. In case this + * happens the CONFIG0 and CONFIG2 register values will get corrupted, + * getting a random value. Both reads and writes cause the same + * behavior. This is a workaround that avoids MDIO accesses all + * together. Since this is a 10BASE-T1S PHY, only the loopback and + * reset (AN) bits in the control register (0x0) can be written. + * These functionalities have custom implementations in the PHY + * driver. C45 accesses do not cause this issue. + */ + + switch (regnum) { + case MII_BMCR: + return ADIN1140_PHY_CTRL_DEFAULT; + case MII_BMSR: + return ADIN1140_PHY_STATUS_DEFAULT; + case MII_PHYSID1: + return ADIN1140_PHY_ID1; + case MII_PHYSID2: + return ADIN1140_PHY_ID2; + default: + return 0xFFFF; + } +} + +static int adin1140_mdiobus_write(struct mii_bus *bus, int addr, int regnum, + u16 val) +{ + return -EIO; +} + +static int adin1140_mdio_register(struct adin1140_priv *priv, + struct spi_device *spidev) +{ + priv->mdiobus = devm_mdiobus_alloc(&spidev->dev); + if (!priv->mdiobus) + return dev_err_probe(&spidev->dev, -ENOMEM, + "MDIO bus alloc failed\n"); + + priv->mdiobus->name = "adin1140-mdiobus"; + priv->mdiobus->priv = priv->tc6; + priv->mdiobus->parent = &spidev->dev; + priv->mdiobus->phy_mask = GENMASK(31, 1); + priv->mdiobus->read = adin1140_mdiobus_read; + priv->mdiobus->write = adin1140_mdiobus_write; + priv->mdiobus->read_c45 = oa_tc6_mdiobus_read_c45; + priv->mdiobus->write_c45 = oa_tc6_mdiobus_write_c45; + + snprintf(priv->mdiobus->id, MII_BUS_ID_SIZE, "adin1140-%s.%u", + dev_name(&spidev->dev), spi_get_chipselect(spidev, 0)); + + return devm_mdiobus_register(&spidev->dev, priv->mdiobus); +} + +static void adin1140_handle_link_change(struct net_device *netdev) +{ + phy_print_status(netdev->phydev); +} + +static void adin1140_phy_remove(void *data) +{ + phy_disconnect(data); +} + +static int adin1140_phy_init(struct adin1140_priv *priv, + struct spi_device *spidev) +{ + int ret; + + ret = adin1140_mdio_register(priv, spidev); + if (ret) + return ret; + + priv->phydev = phy_find_first(priv->mdiobus); + if (!priv->phydev) + return dev_err_probe(&spidev->dev, -ENODEV, "No PHY found\n"); + + priv->phydev->is_internal = true; + ret = phy_connect_direct(priv->netdev, priv->phydev, + &adin1140_handle_link_change, + PHY_INTERFACE_MODE_INTERNAL); + if (ret) + return dev_err_probe(&spidev->dev, ret, + "Can't attach PHY to %s\n", + priv->mdiobus->id); + + ret = devm_add_action_or_reset(&spidev->dev, adin1140_phy_remove, + priv->phydev); + if (ret) + return ret; + + phy_attached_info(priv->phydev); + + return 0; +} + +static const struct ethtool_ops adin1140_ethtool_ops = { + .get_drvinfo = adin1140_get_drvinfo, + .get_link = ethtool_op_get_link, + .get_ethtool_stats = adin1140_get_ethtool_stats, + .get_sset_count = adin1140_get_sset_count, + .get_strings = adin1140_get_ethtool_strings, + .get_link_ksettings = phy_ethtool_get_link_ksettings, + .set_link_ksettings = phy_ethtool_set_link_ksettings, + .get_eth_mac_stats = adin1140_get_eth_mac_stats, +}; + +static const struct net_device_ops adin1140_netdev_ops = { + .ndo_open = adin1140_open, + .ndo_stop = adin1140_close, + .ndo_start_xmit = adin1140_start_xmit, + .ndo_set_mac_address = adin1140_set_mac_address, + .ndo_validate_addr = eth_validate_addr, + .ndo_set_rx_mode_async = adin1140_rx_mode, + .ndo_eth_ioctl = phy_do_ioctl_running, + .ndo_get_stats64 = adin1140_ndo_get_stats64, +}; + +static void adin1140_oa_tc6_remove(void *data) +{ + oa_tc6_exit(data); +} + +static int adin1140_probe(struct spi_device *spi) +{ + struct oa_tc6_quirks tc6_quirks = {}; + struct net_device *netdev; + struct adin1140_priv *priv; + int ret; + + netdev = devm_alloc_etherdev(&spi->dev, sizeof(struct adin1140_priv)); + if (!netdev) + return -ENOMEM; + + priv = netdev_priv(netdev); + priv->netdev = netdev; + spi_set_drvdata(spi, priv); + spin_lock_init(&priv->stat_lock); + + tc6_quirks.quirk_flags = OA_TC6_BROKEN_PHY; + + priv->tc6 = oa_tc6_init(spi, netdev, &tc6_quirks); + if (!priv->tc6) + return -ENODEV; + + ret = devm_add_action_or_reset(&spi->dev, adin1140_oa_tc6_remove, + priv->tc6); + if (ret) + return ret; + + ret = adin1140_phy_init(priv, spi); + if (ret) + return ret; + + if (device_get_ethdev_address(&spi->dev, netdev)) + eth_hw_addr_random(netdev); + + ret = adin1140_configure(priv); + if (ret) + return ret; + + INIT_DELAYED_WORK(&priv->stats_work, adin1140_stats_work); + + netdev->if_port = IF_PORT_10BASET; + netdev->irq = spi->irq; + netdev->netdev_ops = &adin1140_netdev_ops; + netdev->ethtool_ops = &adin1140_ethtool_ops; + netdev->netns_immutable = true; + netdev->priv_flags |= IFF_LIVE_ADDR_CHANGE | + IFF_UNICAST_FLT; + + ret = devm_register_netdev(&spi->dev, netdev); + if (ret) + return dev_err_probe(&spi->dev, ret, + "Failed to register netdev"); + + return 0; +} + +static const struct spi_device_id adin1140_spi_id[] = { + { .name = "ad3306" }, + { .name = "adin1140" }, + {}, +}; +MODULE_DEVICE_TABLE(spi, adin1140_spi_id); + +static const struct of_device_id adin1140_match_table[] = { + { .compatible = "adi,ad3306" }, + { .compatible = "adi,adin1140" }, + { } +}; +MODULE_DEVICE_TABLE(of, adin1140_match_table); + +static struct spi_driver adin1140_driver = { + .driver = { + .name = "adin1140", + .of_match_table = adin1140_match_table, + }, + .probe = adin1140_probe, + .id_table = adin1140_spi_id, +}; +module_spi_driver(adin1140_driver); + +MODULE_DESCRIPTION("Analog Devices, Inc. ADIN1140 10BASE-T1S MAC-PHY"); +MODULE_AUTHOR("Ciprian Regus "); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/ethernet/microchip/lan865x/lan865x.c b/drivers/net/ethernet/microchip/lan865x/lan865x.c index 0277d9737369..26a2761332a5 100644 --- a/drivers/net/ethernet/microchip/lan865x/lan865x.c +++ b/drivers/net/ethernet/microchip/lan865x/lan865x.c @@ -346,7 +346,7 @@ static int lan865x_probe(struct spi_device *spi) spi_set_drvdata(spi, priv); INIT_WORK(&priv->multicast_work, lan865x_multicast_work_handler); - priv->tc6 = oa_tc6_init(spi, netdev); + priv->tc6 = oa_tc6_init(spi, netdev, NULL); if (!priv->tc6) { ret = -ENODEV; goto free_netdev; diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6.c index 0727d53345a3..417c15d1ff42 100644 --- a/drivers/net/ethernet/oa_tc6.c +++ b/drivers/net/ethernet/oa_tc6.c @@ -12,46 +12,6 @@ #include #include -/* OPEN Alliance TC6 registers */ -/* Standard Capabilities Register */ -#define OA_TC6_REG_STDCAP 0x0002 -#define STDCAP_DIRECT_PHY_REG_ACCESS BIT(8) - -/* Reset Control and Status Register */ -#define OA_TC6_REG_RESET 0x0003 -#define RESET_SWRESET BIT(0) /* Software Reset */ - -/* Configuration Register #0 */ -#define OA_TC6_REG_CONFIG0 0x0004 -#define CONFIG0_SYNC BIT(15) -#define CONFIG0_ZARFE_ENABLE BIT(12) - -/* Status Register #0 */ -#define OA_TC6_REG_STATUS0 0x0008 -#define STATUS0_RESETC BIT(6) /* Reset Complete */ -#define STATUS0_HEADER_ERROR BIT(5) -#define STATUS0_LOSS_OF_FRAME_ERROR BIT(4) -#define STATUS0_RX_BUFFER_OVERFLOW_ERROR BIT(3) -#define STATUS0_TX_PROTOCOL_ERROR BIT(0) - -/* Buffer Status Register */ -#define OA_TC6_REG_BUFFER_STATUS 0x000B -#define BUFFER_STATUS_TX_CREDITS_AVAILABLE GENMASK(15, 8) -#define BUFFER_STATUS_RX_CHUNKS_AVAILABLE GENMASK(7, 0) - -/* Interrupt Mask Register #0 */ -#define OA_TC6_REG_INT_MASK0 0x000C -#define INT_MASK0_HEADER_ERR_MASK BIT(5) -#define INT_MASK0_LOSS_OF_FRAME_ERR_MASK BIT(4) -#define INT_MASK0_RX_BUFFER_OVERFLOW_ERR_MASK BIT(3) -#define INT_MASK0_TX_PROTOCOL_ERR_MASK BIT(0) -#define INT_MASK0_ALL_INTERRUPTS (GENMASK(5, 0) | \ - GENMASK(12, 7)) - -/* PHY Clause 22 registers base address and mask */ -#define OA_TC6_PHY_STD_REG_ADDR_BASE 0xFF00 -#define OA_TC6_PHY_STD_REG_ADDR_MASK 0x1F - /* Control command header */ #define OA_TC6_CTRL_HEADER_DATA_NOT_CTRL BIT(31) #define OA_TC6_CTRL_HEADER_WRITE_NOT_READ BIT(29) @@ -81,23 +41,17 @@ #define OA_TC6_DATA_FOOTER_END_BYTE_OFFSET GENMASK(13, 8) #define OA_TC6_DATA_FOOTER_TX_CREDITS GENMASK(5, 1) -/* PHY – Clause 45 registers memory map selector (MMS) as per table 6 in the - * OPEN Alliance specification. - */ -#define OA_TC6_PHY_C45_PCS_MMS2 2 /* MMD 3 */ -#define OA_TC6_PHY_C45_PMA_PMD_MMS3 3 /* MMD 1 */ -#define OA_TC6_PHY_C45_VS_PLCA_MMS4 4 /* MMD 31 */ -#define OA_TC6_PHY_C45_AUTO_NEG_MMS5 5 /* MMD 7 */ -#define OA_TC6_PHY_C45_POWER_UNIT_MMS6 6 /* MMD 13 */ - +#define OA_TC6_CTRL_PROT_REPLY_SIZE 4 #define OA_TC6_CTRL_HEADER_SIZE 4 #define OA_TC6_CTRL_REG_VALUE_SIZE 4 #define OA_TC6_CTRL_IGNORED_SIZE 4 #define OA_TC6_CTRL_MAX_REGISTERS 128 -#define OA_TC6_CTRL_SPI_BUF_SIZE (OA_TC6_CTRL_HEADER_SIZE +\ - (OA_TC6_CTRL_MAX_REGISTERS *\ - OA_TC6_CTRL_REG_VALUE_SIZE) +\ - OA_TC6_CTRL_IGNORED_SIZE) +#define OA_TC6_CTRL_SPI_BUF_SIZE (OA_TC6_CTRL_HEADER_SIZE +\ + (OA_TC6_CTRL_MAX_REGISTERS *\ + (OA_TC6_CTRL_REG_VALUE_SIZE +\ + OA_TC6_CTRL_PROT_REPLY_SIZE)) +\ + OA_TC6_CTRL_IGNORED_SIZE) + #define OA_TC6_CHUNK_PAYLOAD_SIZE 64 #define OA_TC6_DATA_HEADER_SIZE 4 #define OA_TC6_CHUNK_SIZE (OA_TC6_DATA_HEADER_SIZE +\ @@ -108,6 +62,8 @@ #define STATUS0_RESETC_POLL_DELAY 1000 #define STATUS0_RESETC_POLL_TIMEOUT 1000000 +#define OA_TC6_REG_MMS_MASK GENMASK(19, 16) + /* Internal structure for MAC-PHY drivers */ struct oa_tc6 { struct net_device *netdev; @@ -130,6 +86,8 @@ struct oa_tc6 { bool rx_buf_overflow; bool int_flag; bool disable_traffic; + bool prot_ctrl; + enum oa_tc6_quirk_flag quirk_flags; }; enum oa_tc6_header_type { @@ -213,25 +171,36 @@ static void oa_tc6_update_ctrl_write_data(struct oa_tc6 *tc6, u32 value[], { __be32 *tx_buf = tc6->spi_ctrl_tx_buf + OA_TC6_CTRL_HEADER_SIZE; - for (int i = 0; i < length; i++) + for (int i = 0; i < length; i++) { *tx_buf++ = cpu_to_be32(value[i]); + if (tc6->prot_ctrl) + *tx_buf++ = cpu_to_be32(~value[i]); + } } -static u16 oa_tc6_calculate_ctrl_buf_size(u8 length) +static u16 oa_tc6_calculate_ctrl_buf_size(u8 length, bool ctrl_prot) { + u32 reply_size = OA_TC6_CTRL_REG_VALUE_SIZE; + + if (ctrl_prot) + reply_size += OA_TC6_CTRL_PROT_REPLY_SIZE; + /* Control command consists 4 bytes header + 4 bytes register value for - * each register + 4 bytes ignored value. + * each register (+ 4 bytes for the register value complement in case + * protected mode is used) + 4 bytes ignored value. */ - return OA_TC6_CTRL_HEADER_SIZE + OA_TC6_CTRL_REG_VALUE_SIZE * length + + return OA_TC6_CTRL_HEADER_SIZE + reply_size * length + OA_TC6_CTRL_IGNORED_SIZE; } static void oa_tc6_prepare_ctrl_spi_buf(struct oa_tc6 *tc6, u32 address, u32 value[], u8 length, - enum oa_tc6_register_op reg_op) + enum oa_tc6_register_op reg_op, + u16 buf_size) { __be32 *tx_buf = tc6->spi_ctrl_tx_buf; + memset(tx_buf, 0, buf_size); *tx_buf = oa_tc6_prepare_ctrl_header(address, length, reg_op); if (reg_op == OA_TC6_CTRL_REG_WRITE) @@ -254,10 +223,12 @@ static int oa_tc6_check_ctrl_write_reply(struct oa_tc6 *tc6, u8 size) return 0; } -static int oa_tc6_check_ctrl_read_reply(struct oa_tc6 *tc6, u8 size) +static int oa_tc6_check_ctrl_read_reply(struct oa_tc6 *tc6, u8 length) { - u32 *rx_buf = tc6->spi_ctrl_rx_buf + OA_TC6_CTRL_IGNORED_SIZE; - u32 *tx_buf = tc6->spi_ctrl_tx_buf; + __be32 *rx_buf = tc6->spi_ctrl_rx_buf + OA_TC6_CTRL_IGNORED_SIZE; + __be32 *tx_buf = tc6->spi_ctrl_tx_buf; + u32 complement; + u32 reply; /* The echoed control read header must match with the one that was * transmitted. @@ -265,6 +236,20 @@ static int oa_tc6_check_ctrl_read_reply(struct oa_tc6 *tc6, u8 size) if (*tx_buf != *rx_buf) return -EPROTO; + if (tc6->prot_ctrl) { + /* Skip past the echoed header to the value/complement pairs */ + rx_buf += 1; + for (int i = 0; i < length; i++) { + reply = be32_to_cpu(rx_buf[0]); + complement = be32_to_cpu(rx_buf[1]); + + if (complement != ~reply) + return -EPROTO; + + rx_buf += 2; + } + } + return 0; } @@ -274,8 +259,13 @@ static void oa_tc6_copy_ctrl_read_data(struct oa_tc6 *tc6, u32 value[], __be32 *rx_buf = tc6->spi_ctrl_rx_buf + OA_TC6_CTRL_IGNORED_SIZE + OA_TC6_CTRL_HEADER_SIZE; - for (int i = 0; i < length; i++) + for (int i = 0; i < length; i++) { value[i] = be32_to_cpu(*rx_buf++); + + /* skip complement word */ + if (tc6->prot_ctrl) + rx_buf++; + } } static int oa_tc6_perform_ctrl(struct oa_tc6 *tc6, u32 address, u32 value[], @@ -284,10 +274,10 @@ static int oa_tc6_perform_ctrl(struct oa_tc6 *tc6, u32 address, u32 value[], u16 size; int ret; - /* Prepare control command and copy to SPI control buffer */ - oa_tc6_prepare_ctrl_spi_buf(tc6, address, value, length, reg_op); + size = oa_tc6_calculate_ctrl_buf_size(length, tc6->prot_ctrl); - size = oa_tc6_calculate_ctrl_buf_size(length); + /* Prepare control command and copy to SPI control buffer */ + oa_tc6_prepare_ctrl_spi_buf(tc6, address, value, length, reg_op, size); /* Perform SPI transfer */ ret = oa_tc6_spi_transfer(tc6, OA_TC6_CTRL_HEADER, size); @@ -302,7 +292,7 @@ static int oa_tc6_perform_ctrl(struct oa_tc6 *tc6, u32 address, u32 value[], return oa_tc6_check_ctrl_write_reply(tc6, size); /* Check echoed/received control read command reply for errors */ - ret = oa_tc6_check_ctrl_read_reply(tc6, size); + ret = oa_tc6_check_ctrl_read_reply(tc6, length); if (ret) return ret; @@ -355,6 +345,27 @@ int oa_tc6_read_register(struct oa_tc6 *tc6, u32 address, u32 *value) } EXPORT_SYMBOL_GPL(oa_tc6_read_register); +/** + * oa_tc6_read_register_mms - function for reading a MAC-PHY register in a + * specified memory map. + * @tc6: oa_tc6 struct. + * @mms: Memory map selector for the register. + * @address: register address of the MAC-PHY to be read. + * @value: value read from the @address register address of the MAC-PHY. + * + * Return: 0 on success or a negative error code on failure. + */ +int oa_tc6_read_register_mms(struct oa_tc6 *tc6, u8 mms, u16 address, + u32 *value) +{ + u32 mms_reg; + + mms_reg = FIELD_PREP(OA_TC6_REG_MMS_MASK, mms) | address; + + return oa_tc6_read_registers(tc6, mms_reg, value, 1); +} +EXPORT_SYMBOL_GPL(oa_tc6_read_register_mms); + /** * oa_tc6_write_registers - function for writing multiple consecutive registers. * @tc6: oa_tc6 struct. @@ -399,6 +410,27 @@ int oa_tc6_write_register(struct oa_tc6 *tc6, u32 address, u32 value) } EXPORT_SYMBOL_GPL(oa_tc6_write_register); +/** + * oa_tc6_write_register_mms - function for writing a MAC-PHY register in a + * specified memory map. + * @tc6: oa_tc6 struct. + * @mms: Memory map selector for the register. + * @address: register address of the MAC-PHY to be written. + * @value: value to be written in the @address register address of the MAC-PHY. + * + * Return: 0 on success or a negative error code on failure. + */ +int oa_tc6_write_register_mms(struct oa_tc6 *tc6, u8 mms, u16 address, + u32 value) +{ + u32 mms_reg; + + mms_reg = FIELD_PREP(OA_TC6_REG_MMS_MASK, mms) | address; + + return oa_tc6_write_registers(tc6, mms_reg, &value, 1); +} +EXPORT_SYMBOL_GPL(oa_tc6_write_register_mms); + static int oa_tc6_check_phy_reg_direct_access_capability(struct oa_tc6 *tc6) { u32 regval; @@ -408,7 +440,7 @@ static int oa_tc6_check_phy_reg_direct_access_capability(struct oa_tc6 *tc6) if (ret) return ret; - if (!(regval & STDCAP_DIRECT_PHY_REG_ACCESS)) + if (!(regval & OA_TC6_STDCAP_DIRECT_PHY_REG_ACCESS)) return -ENODEV; return 0; @@ -462,36 +494,39 @@ static int oa_tc6_get_phy_c45_mms(int devnum) } } -static int oa_tc6_mdiobus_read_c45(struct mii_bus *bus, int addr, int devnum, - int regnum) +int oa_tc6_mdiobus_read_c45(struct mii_bus *bus, int addr, int devnum, + int regnum) { struct oa_tc6 *tc6 = bus->priv; u32 regval; + int mms; int ret; - ret = oa_tc6_get_phy_c45_mms(devnum); - if (ret < 0) - return ret; + mms = oa_tc6_get_phy_c45_mms(devnum); + if (mms < 0) + return mms; - ret = oa_tc6_read_register(tc6, (ret << 16) | regnum, ®val); + ret = oa_tc6_read_register_mms(tc6, mms, regnum, ®val); if (ret) return ret; return regval; } +EXPORT_SYMBOL_GPL(oa_tc6_mdiobus_read_c45); -static int oa_tc6_mdiobus_write_c45(struct mii_bus *bus, int addr, int devnum, - int regnum, u16 val) +int oa_tc6_mdiobus_write_c45(struct mii_bus *bus, int addr, int devnum, + int regnum, u16 val) { struct oa_tc6 *tc6 = bus->priv; - int ret; + int mms; - ret = oa_tc6_get_phy_c45_mms(devnum); - if (ret < 0) - return ret; + mms = oa_tc6_get_phy_c45_mms(devnum); + if (mms < 0) + return mms; - return oa_tc6_write_register(tc6, (ret << 16) | regnum, val); + return oa_tc6_write_register_mms(tc6, mms, regnum, val); } +EXPORT_SYMBOL_GPL(oa_tc6_mdiobus_write_c45); static int oa_tc6_mdiobus_register(struct oa_tc6 *tc6) { @@ -544,6 +579,9 @@ static int oa_tc6_phy_init(struct oa_tc6 *tc6) { int ret; + if (tc6->quirk_flags & OA_TC6_BROKEN_PHY) + return 0; + ret = oa_tc6_check_phy_reg_direct_access_capability(tc6); if (ret) { netdev_err(tc6->netdev, @@ -580,6 +618,9 @@ static int oa_tc6_phy_init(struct oa_tc6 *tc6) static void oa_tc6_phy_exit(struct oa_tc6 *tc6) { + if (tc6->quirk_flags & OA_TC6_BROKEN_PHY) + return; + phy_disconnect(tc6->phydev); oa_tc6_mdiobus_unregister(tc6); } @@ -601,7 +642,7 @@ static int oa_tc6_read_status0(struct oa_tc6 *tc6) static int oa_tc6_sw_reset_macphy(struct oa_tc6 *tc6) { - u32 regval = RESET_SWRESET; + u32 regval = OA_TC6_RESET_SWRESET; int ret; ret = oa_tc6_write_register(tc6, OA_TC6_REG_RESET, regval); @@ -610,7 +651,7 @@ static int oa_tc6_sw_reset_macphy(struct oa_tc6 *tc6) /* Poll for soft reset complete for every 1ms until 1s timeout */ ret = readx_poll_timeout(oa_tc6_read_status0, tc6, regval, - regval & STATUS0_RESETC, + regval & OA_TC6_STATUS0_RESETC, STATUS0_RESETC_POLL_DELAY, STATUS0_RESETC_POLL_TIMEOUT); if (ret) @@ -629,10 +670,10 @@ static int oa_tc6_unmask_macphy_error_interrupts(struct oa_tc6 *tc6) if (ret) return ret; - regval &= ~(INT_MASK0_TX_PROTOCOL_ERR_MASK | - INT_MASK0_RX_BUFFER_OVERFLOW_ERR_MASK | - INT_MASK0_LOSS_OF_FRAME_ERR_MASK | - INT_MASK0_HEADER_ERR_MASK); + regval &= ~(OA_TC6_INT_MASK0_TX_PROTOCOL_ERR_MASK | + OA_TC6_INT_MASK0_RX_BUFFER_OVERFLOW_ERR_MASK | + OA_TC6_INT_MASK0_LOSS_OF_FRAME_ERR_MASK | + OA_TC6_INT_MASK0_HEADER_ERR_MASK); return oa_tc6_write_register(tc6, OA_TC6_REG_INT_MASK0, regval); } @@ -647,7 +688,7 @@ static int oa_tc6_enable_data_transfer(struct oa_tc6 *tc6) return ret; /* Enable configuration synchronization for data transfer */ - value |= CONFIG0_SYNC; + value |= OA_TC6_CONFIG0_SYNC; return oa_tc6_write_register(tc6, OA_TC6_REG_CONFIG0, value); } @@ -692,7 +733,7 @@ static void oa_tc6_free_pending_skbs(struct oa_tc6 *tc6) */ static void oa_tc6_disable_traffic(struct oa_tc6 *tc6) { - u32 regval = INT_MASK0_ALL_INTERRUPTS; + u32 regval = OA_TC6_INT_MASK0_ALL_INTERRUPTS; tc6->disable_traffic = true; oa_tc6_free_pending_skbs(tc6); @@ -722,25 +763,25 @@ static int oa_tc6_process_extended_status(struct oa_tc6 *tc6) return ret; } - if (FIELD_GET(STATUS0_RX_BUFFER_OVERFLOW_ERROR, value)) { + if (FIELD_GET(OA_TC6_STATUS0_RX_BUFFER_OVERFLOW_ERROR, value)) { tc6->rx_buf_overflow = true; oa_tc6_cleanup_ongoing_rx_skb(tc6); net_err_ratelimited("%s: Receive buffer overflow error\n", tc6->netdev->name); return -EAGAIN; } - if (FIELD_GET(STATUS0_TX_PROTOCOL_ERROR, value)) { + if (FIELD_GET(OA_TC6_STATUS0_TX_PROTOCOL_ERROR, value)) { netdev_err(tc6->netdev, "Transmit protocol error\n"); return -ENODEV; } /* TODO: Currently loss of frame and header errors are treated as * non-recoverable errors. They will be handled in the next version. */ - if (FIELD_GET(STATUS0_LOSS_OF_FRAME_ERROR, value)) { + if (FIELD_GET(OA_TC6_STATUS0_LOSS_OF_FRAME_ERROR, value)) { netdev_err(tc6->netdev, "Loss of frame error\n"); return -ENODEV; } - if (FIELD_GET(STATUS0_HEADER_ERROR, value)) { + if (FIELD_GET(OA_TC6_STATUS0_HEADER_ERROR, value)) { netdev_err(tc6->netdev, "Header error\n"); return -ENODEV; } @@ -1187,9 +1228,10 @@ static int oa_tc6_update_buffer_status_from_register(struct oa_tc6 *tc6) if (ret) return ret; - tc6->tx_credits = FIELD_GET(BUFFER_STATUS_TX_CREDITS_AVAILABLE, value); - tc6->rx_chunks_available = FIELD_GET(BUFFER_STATUS_RX_CHUNKS_AVAILABLE, - value); + tc6->tx_credits = FIELD_GET(OA_TC6_BUFFER_STATUS_TX_CREDITS_AVAILABLE, + value); + tc6->rx_chunks_available = + FIELD_GET(OA_TC6_BUFFER_STATUS_RX_CHUNKS_AVAILABLE, value); return 0; } @@ -1233,7 +1275,7 @@ int oa_tc6_zero_align_receive_frame_enable(struct oa_tc6 *tc6) return ret; /* Set Zero-Align Receive Frame Enable */ - regval |= CONFIG0_ZARFE_ENABLE; + regval |= OA_TC6_CONFIG0_ZARFE_ENABLE; return oa_tc6_write_register(tc6, OA_TC6_REG_CONFIG0, regval); } @@ -1272,15 +1314,31 @@ netdev_tx_t oa_tc6_start_xmit(struct oa_tc6 *tc6, struct sk_buff *skb) } EXPORT_SYMBOL_GPL(oa_tc6_start_xmit); +static int oa_tc6_check_ctrl_protection(struct oa_tc6 *tc6) +{ + u32 regval; + int ret; + + ret = oa_tc6_read_register(tc6, OA_TC6_REG_CONFIG0, ®val); + if (ret) + return ret; + + tc6->prot_ctrl = FIELD_GET(OA_TC6_CONFIG0_PROTE, regval); + + return 0; +} + /** * oa_tc6_init - allocates and initializes oa_tc6 structure. * @spi: device with which data will be exchanged. * @netdev: network device interface structure. + * @quirks: device specific modifiers for the OA TC6 protocol. * * Return: pointer reference to the oa_tc6 structure if the MAC-PHY * initialization is successful otherwise NULL. */ -struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev) +struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev, + struct oa_tc6_quirks *quirks) { struct oa_tc6 *tc6; int ret; @@ -1295,6 +1353,9 @@ struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev) mutex_init(&tc6->spi_ctrl_lock); spin_lock_init(&tc6->tx_skb_lock); + if (quirks) + tc6->quirk_flags = quirks->quirk_flags; + /* Set the SPI controller to pump at realtime priority */ tc6->spi->rt = true; if (spi_setup(tc6->spi) < 0) @@ -1324,6 +1385,14 @@ struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev) if (!tc6->spi_data_rx_buf) return NULL; + /* Check the PROTE bit status so that we can reset the device */ + ret = oa_tc6_check_ctrl_protection(tc6); + if (ret) { + dev_err(&tc6->spi->dev, + "Failed to check the protection mode: %d\n", ret); + return NULL; + } + ret = oa_tc6_sw_reset_macphy(tc6); if (ret) { dev_err(&tc6->spi->dev, diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index 099f25dceabb..a29d3fed8a05 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -136,6 +136,12 @@ config ADIN1100_PHY Currently supports the: - ADIN1100 - Robust,Industrial, Low Power 10BASE-T1L Ethernet PHY +config ADIN1140_PHY + tristate "Analog Devices ADIN1140 10BASE-T1S PHY" + help + Adds support for the Analog Devices, Inc. ADIN1140's internal + 10BASE-T1S PHY. + config AMCC_QT2025_PHY tristate "AMCC QT2025 PHY" depends on RUST_PHYLIB_ABSTRACTIONS diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index de660ae94945..e23df5e836e9 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -29,6 +29,7 @@ obj-y += $(sfp-obj-y) $(sfp-obj-m) obj-$(CONFIG_ADIN_PHY) += adin.o obj-$(CONFIG_ADIN1100_PHY) += adin1100.o +obj-$(CONFIG_ADIN1140_PHY) += adin1140-phy.o obj-$(CONFIG_AIR_AN8801_PHY) += air_an8801.o obj-$(CONFIG_AIR_EN8811H_PHY) += air_en8811h.o obj-$(CONFIG_AIR_NET_PHYLIB) += air_phy_lib.o diff --git a/drivers/net/phy/adin1140-phy.c b/drivers/net/phy/adin1140-phy.c new file mode 100644 index 000000000000..d35da4ad680d --- /dev/null +++ b/drivers/net/phy/adin1140-phy.c @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for Analog Devices, Inc. ADIN1140 10BASE-T1S PHY + * + * Copyright 2026 Analog Devices Inc. + */ + +#include +#include +#include + +#define ADIN1140_PHY_ID 0x0283be00 + +#define ADIN1140_PCS_CTRL 0x08f3 +#define ADIN1140_PCS_CTRL_LOOPBACK BIT(14) + +static int adin1140_config_aneg(struct phy_device *phydev) +{ + /* phylib tries to clear BIT(12) in MDIO_CTRL1, since AN is disabled. + * However, on the ADIN1140, that field is non-standard, being used + * to control the reset status of the PHY (thus it needs to remain set). + */ + return 0; +} + +static int adin1140_loopback(struct phy_device *phydev, bool enable, int speed) +{ + if (enable && speed) + return -EOPNOTSUPP; + + return phy_modify_mmd(phydev, MDIO_MMD_PCS, ADIN1140_PCS_CTRL, + ADIN1140_PCS_CTRL_LOOPBACK, + enable ? ADIN1140_PCS_CTRL_LOOPBACK : 0); +} + +static int adin1140_read_status(struct phy_device *phydev) +{ + phydev->link = 1; + phydev->duplex = DUPLEX_HALF; + phydev->speed = SPEED_10; + phydev->autoneg = AUTONEG_DISABLE; + + return 0; +} + +static struct phy_driver adin1140_driver[] = { + { + PHY_ID_MATCH_EXACT(ADIN1140_PHY_ID), + .name = "ADIN1140_PHY", + .features = PHY_BASIC_T1S_P2MP_FEATURES, + .read_status = adin1140_read_status, + .config_aneg = adin1140_config_aneg, + .set_loopback = adin1140_loopback, + .read_mmd = genphy_read_mmd_c45, + .write_mmd = genphy_write_mmd_c45, + .get_plca_cfg = genphy_c45_plca_get_cfg, + .set_plca_cfg = genphy_c45_plca_set_cfg, + .get_plca_status = genphy_c45_plca_get_status, + }, +}; +module_phy_driver(adin1140_driver); + +static const struct mdio_device_id __maybe_unused adin1140_tbl[] = { + { PHY_ID_MATCH_EXACT(ADIN1140_PHY_ID) }, + { } +}; + +MODULE_DEVICE_TABLE(mdio, adin1140_tbl); + +MODULE_DESCRIPTION("Analog Devices, Inc. ADIN1140 10BASE-T1S PHY"); +MODULE_AUTHOR("Ciprian Regus "); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/phy/microchip_t1s.c b/drivers/net/phy/microchip_t1s.c index e601d56b2507..73c23d311d72 100644 --- a/drivers/net/phy/microchip_t1s.c +++ b/drivers/net/phy/microchip_t1s.c @@ -506,34 +506,6 @@ static int lan86xx_read_status(struct phy_device *phydev) return 0; } -/* OPEN Alliance 10BASE-T1x compliance MAC-PHYs will have both C22 and - * C45 registers space. If the PHY is discovered via C22 bus protocol it assumes - * it uses C22 protocol and always uses C22 registers indirect access to access - * C45 registers. This is because, we don't have a clean separation between - * C22/C45 register space and C22/C45 MDIO bus protocols. Resulting, PHY C45 - * registers direct access can't be used which can save multiple SPI bus access. - * To support this feature, set .read_mmd/.write_mmd in the PHY driver to call - * .read_c45/.write_c45 in the OPEN Alliance framework - * drivers/net/ethernet/oa_tc6.c - */ -static int lan865x_phy_read_mmd(struct phy_device *phydev, int devnum, - u16 regnum) -{ - struct mii_bus *bus = phydev->mdio.bus; - int addr = phydev->mdio.addr; - - return __mdiobus_c45_read(bus, addr, devnum, regnum); -} - -static int lan865x_phy_write_mmd(struct phy_device *phydev, int devnum, - u16 regnum, u16 val) -{ - struct mii_bus *bus = phydev->mdio.bus; - int addr = phydev->mdio.addr; - - return __mdiobus_c45_write(bus, addr, devnum, regnum, val); -} - static struct phy_driver microchip_t1s_driver[] = { { PHY_ID_MATCH_EXACT(PHY_ID_LAN867X_REVB1), @@ -584,8 +556,8 @@ static struct phy_driver microchip_t1s_driver[] = { .features = PHY_BASIC_T1S_P2MP_FEATURES, .config_init = lan865x_revb_config_init, .read_status = lan86xx_read_status, - .read_mmd = lan865x_phy_read_mmd, - .write_mmd = lan865x_phy_write_mmd, + .read_mmd = genphy_read_mmd_c45, + .write_mmd = genphy_write_mmd_c45, .get_plca_cfg = genphy_c45_plca_get_cfg, .set_plca_cfg = lan86xx_plca_set_cfg, .get_plca_status = genphy_c45_plca_get_status, diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c index 0615228459ef..94b2e85e00a3 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -2770,6 +2770,31 @@ int genphy_read_abilities(struct phy_device *phydev) } EXPORT_SYMBOL(genphy_read_abilities); +/* Some PHYs support direct C45 register access but not C22 indirect + * MMD access (registers 13 and 14). When discovered via C22, phylib + * routes MMD access through the indirect path, which won't work on + * these devices. These helpers bypass indirect access and use the bus + * C45 accessors directly. + */ +int genphy_read_mmd_c45(struct phy_device *phydev, int devnum, u16 regnum) +{ + struct mii_bus *bus = phydev->mdio.bus; + int addr = phydev->mdio.addr; + + return __mdiobus_c45_read(bus, addr, devnum, regnum); +} +EXPORT_SYMBOL(genphy_read_mmd_c45); + +int genphy_write_mmd_c45(struct phy_device *phydev, int devnum, u16 regnum, + u16 val) +{ + struct mii_bus *bus = phydev->mdio.bus; + int addr = phydev->mdio.addr; + + return __mdiobus_c45_write(bus, addr, devnum, regnum, val); +} +EXPORT_SYMBOL(genphy_write_mmd_c45); + /* This is used for the phy device which doesn't support the MMD extended * register access, but it does have side effect when we are trying to access * the MMD register via indirect method. diff --git a/include/linux/oa_tc6.h b/include/linux/oa_tc6.h index 15f58e3c56c7..27f652d4920b 100644 --- a/include/linux/oa_tc6.h +++ b/include/linux/oa_tc6.h @@ -10,15 +10,86 @@ #include #include +/* OPEN Alliance TC6 registers */ +/* Standard Capabilities Register */ +#define OA_TC6_REG_STDCAP 0x0002 +#define OA_TC6_STDCAP_DIRECT_PHY_REG_ACCESS BIT(8) + +/* Reset Control and Status Register */ +#define OA_TC6_REG_RESET 0x0003 +#define OA_TC6_RESET_SWRESET BIT(0) /* Software Reset */ + +/* Configuration Register #0 */ +#define OA_TC6_REG_CONFIG0 0x0004 +#define OA_TC6_CONFIG0_SYNC BIT(15) +#define OA_TC6_CONFIG0_ZARFE_ENABLE BIT(12) +#define OA_TC6_CONFIG0_PROTE BIT(5) + +/* Configuration Register #2 */ +#define OA_TC6_REG_CONFIG2 0x0006 + +/* Status Register #0 */ +#define OA_TC6_REG_STATUS0 0x0008 +#define OA_TC6_STATUS0_RESETC BIT(6) /* Reset Complete */ +#define OA_TC6_STATUS0_HEADER_ERROR BIT(5) +#define OA_TC6_STATUS0_LOSS_OF_FRAME_ERROR BIT(4) +#define OA_TC6_STATUS0_RX_BUFFER_OVERFLOW_ERROR BIT(3) +#define OA_TC6_STATUS0_TX_PROTOCOL_ERROR BIT(0) + +/* Buffer Status Register */ +#define OA_TC6_REG_BUFFER_STATUS 0x000B +#define OA_TC6_BUFFER_STATUS_TX_CREDITS_AVAILABLE GENMASK(15, 8) +#define OA_TC6_BUFFER_STATUS_RX_CHUNKS_AVAILABLE GENMASK(7, 0) + +/* Interrupt Mask Register #0 */ +#define OA_TC6_REG_INT_MASK0 0x000C +#define OA_TC6_INT_MASK0_HEADER_ERR_MASK BIT(5) +#define OA_TC6_INT_MASK0_LOSS_OF_FRAME_ERR_MASK BIT(4) +#define OA_TC6_INT_MASK0_RX_BUFFER_OVERFLOW_ERR_MASK BIT(3) +#define OA_TC6_INT_MASK0_TX_PROTOCOL_ERR_MASK BIT(0) +#define OA_TC6_INT_MASK0_ALL_INTERRUPTS (GENMASK(5, 0) | \ + GENMASK(12, 7)) + +/* PHY Clause 22 registers base address and mask */ +#define OA_TC6_PHY_STD_REG_ADDR_BASE 0xFF00 +#define OA_TC6_PHY_STD_REG_ADDR_MASK 0x1F + +/* Memory map selector (MMS) values as per table 6 in the + * OPEN Alliance specification. + */ +#define OA_TC6_MAC_MMS1 1 +#define OA_TC6_PHY_C45_PCS_MMS2 2 /* MMD 3 */ +#define OA_TC6_PHY_C45_PMA_PMD_MMS3 3 /* MMD 1 */ +#define OA_TC6_PHY_C45_VS_PLCA_MMS4 4 /* MMD 31 */ +#define OA_TC6_PHY_C45_AUTO_NEG_MMS5 5 /* MMD 7 */ +#define OA_TC6_PHY_C45_POWER_UNIT_MMS6 6 /* MMD 13 */ + struct oa_tc6; -struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev); +enum oa_tc6_quirk_flag { + OA_TC6_BROKEN_PHY = BIT(0), +}; + +struct oa_tc6_quirks { + enum oa_tc6_quirk_flag quirk_flags; +}; + +struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev, + struct oa_tc6_quirks *quirks); void oa_tc6_exit(struct oa_tc6 *tc6); int oa_tc6_write_register(struct oa_tc6 *tc6, u32 address, u32 value); +int oa_tc6_write_register_mms(struct oa_tc6 *tc6, u8 mms, u16 address, + u32 value); int oa_tc6_write_registers(struct oa_tc6 *tc6, u32 address, u32 value[], u8 length); int oa_tc6_read_register(struct oa_tc6 *tc6, u32 address, u32 *value); +int oa_tc6_read_register_mms(struct oa_tc6 *tc6, u8 mms, u16 address, + u32 *value); int oa_tc6_read_registers(struct oa_tc6 *tc6, u32 address, u32 value[], u8 length); netdev_tx_t oa_tc6_start_xmit(struct oa_tc6 *tc6, struct sk_buff *skb); int oa_tc6_zero_align_receive_frame_enable(struct oa_tc6 *tc6); +int oa_tc6_mdiobus_read_c45(struct mii_bus *bus, int addr, int devnum, + int regnum); +int oa_tc6_mdiobus_write_c45(struct mii_bus *bus, int addr, int devnum, + int regnum, u16 val); diff --git a/include/linux/phy.h b/include/linux/phy.h index beff1d6fcc7c..11092c3175b3 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -2302,6 +2302,9 @@ static inline int genphy_no_config_intr(struct phy_device *phydev) { return 0; } +int genphy_read_mmd_c45(struct phy_device *phydev, int devnum, u16 regnum); +int genphy_write_mmd_c45(struct phy_device *phydev, int devnum, u16 regnum, + u16 val); int genphy_read_mmd_unsupported(struct phy_device *phdev, int devad, u16 regnum); int genphy_write_mmd_unsupported(struct phy_device *phdev, int devnum,