From 034dd340b08be1f2f0477ad16131d609f9dbd53c Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sun, 30 Aug 2026 09:22:00 -0700 Subject: Merge tag 'trace-v7.3-2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace Pull tracing fixes from Steven Rostedt: - Fix error output of boot instance creation failure Currently if a boot instance creation fails, instead of printing out the name of the instance that failed, it prints "(null)". That is because it prints "cur_str" that had already been processed by strsep(). Print the saved name instead. While at it, print the error code of the failure. - Fix use-after-free for same named historgrams Histograms can be named so that they can be used in multiple events. But if the named histogram has a variable attached, the second event that uses the named histogram which duplicates it and needs to free the original after duplication leaves the old variable in place and still visible. If another histogram uses than variable, it will use the stale one which will try to reference the freed duplicate histogram and crash the kernel. Free the duplicate variables along with the duplicated histogram data. - Check return value of kthread_run() in event self test The events self tests uses a kthread for testing but does not check if it succeeded in creating a kthread. If the kthread creation were to fail, the code will still try to call kthread_stop() on the error returned. - Fix race between reading trace_pipe and updating subbuffer size If a user is reading the trace_pipe file at the same time they update the ring buffer sub-buffer size, can cause the trace_pipe read to read stale data. Add trace_access_lock() around updating the ring buffer sub-buffer size. - Fix eventfs_inode on failure path in creation of the events directory In the creation of the "events" directory, if after allocating the eventfs_inode a failure is detected, it calls cleanup_ei() which calls free_ei(). The free_ei() will test if eventfs_inode being freed has no children. It is a bug if it does. But on the failure case of the creation of the "events" directory, the children lists have not yet been initialized and the free will trigger a warning because list_empty() on an uninitialized list returns false. Move the initialization into init_ei() where it makes more sense and makes sure that a created eventfs_inode has its lists initialized upon creation. - Check return value of kthread_run() in ftrace direct sample code The sample code that shows how to use the ftrace direct calls does not test the return of kthread_run() to see if it succeeds. Return a failure if the kthread_run() doesn't succeed. - Clear user events state on fork in case of alloc failure On fork, the child gets a pointer to the parent's user events state. It makes a copy of it then updates the child's pointer to it. But if the allocation fails, the duplication function leaves the child with a pointer to its parent's descriptor. When the child cleans up its data, it will free the parent's descriptor while the parent is still using it. In the duplication function, set the child's user_event_mm to NULL before testing if the allocation succeeded, and when it exits it will not free the parent's descriptor. - Fix retry exhaustion in simple ring buffer reader swap simple_ring_buffer_swap_reader_page() starts with retry set to 8 and post-decrements it only after a failed link replacement. On the final attempt, a successful replacement leaves retry at zero, while a failed replacement leaves it at -1. But the check for success expects the retry value to be non-zero and exits with an error on zero. This is the opposite result. Fix it. - Fail nicely when the remote swap_reader_page() returns an error Currently, if the swap_reader_page() of a remote buffer fails, it triggers a WARN_ON_ONCE() and continues normally. Instead, have it exit with an error and a pr_warn() print instead of a full WARNING. * tag 'trace-v7.3-2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace: ring-buffer: Stop remote reader update when page swap fails tracing: Fix retry exhaustion in simple ring buffer reader swap tracing/user_events: Clear copied tracing state before fork duplication samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-multi-modify samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-modify eventfs: Initialize ei->children and ei->list in init_ei() tracing: Fix use-after-free in trace_pipe read on sub-buffer order change tracing: Fix crash passing ERR_PTR to kthread_stop() tracing: Fix use-after-free with same-name named triggers tracing: Fix logged instance name on creation failure --- drivers/net/ethernet/micrel/Kconfig | 64 + drivers/net/ethernet/micrel/Makefile | 9 + drivers/net/ethernet/micrel/ks8842.c | 1259 +++++ drivers/net/ethernet/micrel/ks8851.h | 451 ++ drivers/net/ethernet/micrel/ks8851_common.c | 1226 +++++ drivers/net/ethernet/micrel/ks8851_par.c | 343 ++ drivers/net/ethernet/micrel/ks8851_spi.c | 478 ++ drivers/net/ethernet/micrel/ksz884x.c | 6877 +++++++++++++++++++++++++++ 8 files changed, 10707 insertions(+) create mode 100644 drivers/net/ethernet/micrel/Kconfig create mode 100644 drivers/net/ethernet/micrel/Makefile create mode 100644 drivers/net/ethernet/micrel/ks8842.c create mode 100644 drivers/net/ethernet/micrel/ks8851.h create mode 100644 drivers/net/ethernet/micrel/ks8851_common.c create mode 100644 drivers/net/ethernet/micrel/ks8851_par.c create mode 100644 drivers/net/ethernet/micrel/ks8851_spi.c create mode 100644 drivers/net/ethernet/micrel/ksz884x.c (limited to 'drivers/net/ethernet/micrel') diff --git a/drivers/net/ethernet/micrel/Kconfig b/drivers/net/ethernet/micrel/Kconfig new file mode 100644 index 000000000..830363baf --- /dev/null +++ b/drivers/net/ethernet/micrel/Kconfig @@ -0,0 +1,64 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Micrel device configuration +# + +config NET_VENDOR_MICREL + bool "Micrel devices" + default y + depends on (HAS_IOMEM && DMA_ENGINE) || SPI || PCI || HAS_IOMEM + help + If you have a network (Ethernet) card belonging to this class, say Y. + + Note that the answer to this question doesn't directly affect the + kernel: saying N will just cause the configurator to skip all + the questions about Micrel devices. If you say Y, you will be asked + for your specific card in the following questions. + +if NET_VENDOR_MICREL + +config KS8842 + tristate "Micrel KSZ8841/42 with generic bus interface" + depends on HAS_IOMEM && DMA_ENGINE + help + This platform driver is for KSZ8841(1-port) / KS8842(2-port) + ethernet switch chip (managed, VLAN, QoS) from Micrel or + Timberdale(FPGA). + +config KS8851 + tristate "Micrel KS8851 SPI" + depends on SPI + depends on PTP_1588_CLOCK_OPTIONAL + select MII + select CRC32 + select EEPROM_93CX6 + select PHYLIB + select MICREL_PHY + help + SPI driver for Micrel KS8851 SPI attached network chip. + +config KS8851_MLL + tristate "Micrel KS8851 MLL" + depends on HAS_IOMEM + depends on PTP_1588_CLOCK_OPTIONAL + select MII + select CRC32 + select EEPROM_93CX6 + select PHYLIB + select MICREL_PHY + help + This platform driver is for Micrel KS8851 Address/data bus + multiplexed network chip. + +config KSZ884X_PCI + tristate "Micrel KSZ8841/2 PCI" + depends on PCI + select MII + select CRC32 + help + This PCI driver is for Micrel KSZ8841/KSZ8842 PCI Ethernet chip. + + To compile this driver as a module, choose M here. The module + will be called ksz884x. + +endif # NET_VENDOR_MICREL diff --git a/drivers/net/ethernet/micrel/Makefile b/drivers/net/ethernet/micrel/Makefile new file mode 100644 index 000000000..6ecc4eb30 --- /dev/null +++ b/drivers/net/ethernet/micrel/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the Micrel network device drivers. +# + +obj-$(CONFIG_KS8842) += ks8842.o +obj-$(CONFIG_KS8851) += ks8851_common.o ks8851_spi.o +obj-$(CONFIG_KS8851_MLL) += ks8851_common.o ks8851_par.o +obj-$(CONFIG_KSZ884X_PCI) += ksz884x.o diff --git a/drivers/net/ethernet/micrel/ks8842.c b/drivers/net/ethernet/micrel/ks8842.c new file mode 100644 index 000000000..b7cf2ee91 --- /dev/null +++ b/drivers/net/ethernet/micrel/ks8842.c @@ -0,0 +1,1259 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ks8842.c timberdale KS8842 ethernet driver + * Copyright (c) 2009 Intel Corporation + */ + +/* Supports: + * The Micrel KS8842 behind the timberdale FPGA + * The genuine Micrel KS8841/42 device with ISA 16/32bit bus interface + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRV_NAME "ks8842" + +/* Timberdale specific Registers */ +#define REG_TIMB_RST 0x1c +#define REG_TIMB_FIFO 0x20 +#define REG_TIMB_ISR 0x24 +#define REG_TIMB_IER 0x28 +#define REG_TIMB_IAR 0x2C +#define REQ_TIMB_DMA_RESUME 0x30 + +/* KS8842 registers */ + +#define REG_SELECT_BANK 0x0e + +/* bank 0 registers */ +#define REG_QRFCR 0x04 + +/* bank 2 registers */ +#define REG_MARL 0x00 +#define REG_MARM 0x02 +#define REG_MARH 0x04 + +/* bank 3 registers */ +#define REG_GRR 0x06 + +/* bank 16 registers */ +#define REG_TXCR 0x00 +#define REG_TXSR 0x02 +#define REG_RXCR 0x04 +#define REG_TXMIR 0x08 +#define REG_RXMIR 0x0A + +/* bank 17 registers */ +#define REG_TXQCR 0x00 +#define REG_RXQCR 0x02 +#define REG_TXFDPR 0x04 +#define REG_RXFDPR 0x06 +#define REG_QMU_DATA_LO 0x08 +#define REG_QMU_DATA_HI 0x0A + +/* bank 18 registers */ +#define REG_IER 0x00 +#define IRQ_LINK_CHANGE 0x8000 +#define IRQ_TX 0x4000 +#define IRQ_RX 0x2000 +#define IRQ_RX_OVERRUN 0x0800 +#define IRQ_TX_STOPPED 0x0200 +#define IRQ_RX_STOPPED 0x0100 +#define IRQ_RX_ERROR 0x0080 +#define ENABLED_IRQS (IRQ_LINK_CHANGE | IRQ_TX | IRQ_RX | IRQ_RX_STOPPED | \ + IRQ_TX_STOPPED | IRQ_RX_OVERRUN | IRQ_RX_ERROR) +/* When running via timberdale in DMA mode, the RX interrupt should be + enabled in the KS8842, but not in the FPGA IP, since the IP handles + RX DMA internally. + TX interrupts are not needed it is handled by the FPGA the driver is + notified via DMA callbacks. +*/ +#define ENABLED_IRQS_DMA_IP (IRQ_LINK_CHANGE | IRQ_RX_STOPPED | \ + IRQ_TX_STOPPED | IRQ_RX_OVERRUN | IRQ_RX_ERROR) +#define ENABLED_IRQS_DMA (ENABLED_IRQS_DMA_IP | IRQ_RX) +#define REG_ISR 0x02 +#define REG_RXSR 0x04 +#define RXSR_VALID 0x8000 +#define RXSR_BROADCAST 0x80 +#define RXSR_MULTICAST 0x40 +#define RXSR_UNICAST 0x20 +#define RXSR_FRAMETYPE 0x08 +#define RXSR_TOO_LONG 0x04 +#define RXSR_RUNT 0x02 +#define RXSR_CRC_ERROR 0x01 +#define RXSR_ERROR (RXSR_TOO_LONG | RXSR_RUNT | RXSR_CRC_ERROR) + +/* bank 32 registers */ +#define REG_SW_ID_AND_ENABLE 0x00 +#define REG_SGCR1 0x02 +#define REG_SGCR2 0x04 +#define REG_SGCR3 0x06 + +/* bank 39 registers */ +#define REG_MACAR1 0x00 +#define REG_MACAR2 0x02 +#define REG_MACAR3 0x04 + +/* bank 45 registers */ +#define REG_P1MBCR 0x00 +#define REG_P1MBSR 0x02 + +/* bank 46 registers */ +#define REG_P2MBCR 0x00 +#define REG_P2MBSR 0x02 + +/* bank 48 registers */ +#define REG_P1CR2 0x02 + +/* bank 49 registers */ +#define REG_P1CR4 0x02 +#define REG_P1SR 0x04 + +/* flags passed by platform_device for configuration */ +#define MICREL_KS884X 0x01 /* 0=Timeberdale(FPGA), 1=Micrel */ +#define KS884X_16BIT 0x02 /* 1=16bit, 0=32bit */ + +#define DMA_BUFFER_SIZE 2048 + +struct ks8842_tx_dma_ctl { + struct dma_chan *chan; + struct dma_async_tx_descriptor *adesc; + void *buf; + struct scatterlist sg; + int channel; +}; + +struct ks8842_rx_dma_ctl { + struct dma_chan *chan; + struct dma_async_tx_descriptor *adesc; + struct sk_buff *skb; + struct scatterlist sg; + struct tasklet_struct tasklet; + int channel; +}; + +#define KS8842_USE_DMA(adapter) (((adapter)->dma_tx.channel != -1) && \ + ((adapter)->dma_rx.channel != -1)) + +struct ks8842_adapter { + void __iomem *hw_addr; + int irq; + unsigned long conf_flags; /* copy of platform_device config */ + struct tasklet_struct tasklet; + spinlock_t lock; /* spinlock to be interrupt safe */ + struct work_struct timeout_work; + struct net_device *netdev; + struct device *dev; + struct ks8842_tx_dma_ctl dma_tx; + struct ks8842_rx_dma_ctl dma_rx; +}; + +static void ks8842_dma_rx_cb(void *data); +static void ks8842_dma_tx_cb(void *data); + +static inline void ks8842_resume_dma(struct ks8842_adapter *adapter) +{ + iowrite32(1, adapter->hw_addr + REQ_TIMB_DMA_RESUME); +} + +static inline void ks8842_select_bank(struct ks8842_adapter *adapter, u16 bank) +{ + iowrite16(bank, adapter->hw_addr + REG_SELECT_BANK); +} + +static inline void ks8842_write8(struct ks8842_adapter *adapter, u16 bank, + u8 value, int offset) +{ + ks8842_select_bank(adapter, bank); + iowrite8(value, adapter->hw_addr + offset); +} + +static inline void ks8842_write16(struct ks8842_adapter *adapter, u16 bank, + u16 value, int offset) +{ + ks8842_select_bank(adapter, bank); + iowrite16(value, adapter->hw_addr + offset); +} + +static inline void ks8842_enable_bits(struct ks8842_adapter *adapter, u16 bank, + u16 bits, int offset) +{ + u16 reg; + ks8842_select_bank(adapter, bank); + reg = ioread16(adapter->hw_addr + offset); + reg |= bits; + iowrite16(reg, adapter->hw_addr + offset); +} + +static inline void ks8842_clear_bits(struct ks8842_adapter *adapter, u16 bank, + u16 bits, int offset) +{ + u16 reg; + ks8842_select_bank(adapter, bank); + reg = ioread16(adapter->hw_addr + offset); + reg &= ~bits; + iowrite16(reg, adapter->hw_addr + offset); +} + +static inline void ks8842_write32(struct ks8842_adapter *adapter, u16 bank, + u32 value, int offset) +{ + ks8842_select_bank(adapter, bank); + iowrite32(value, adapter->hw_addr + offset); +} + +static inline u8 ks8842_read8(struct ks8842_adapter *adapter, u16 bank, + int offset) +{ + ks8842_select_bank(adapter, bank); + return ioread8(adapter->hw_addr + offset); +} + +static inline u16 ks8842_read16(struct ks8842_adapter *adapter, u16 bank, + int offset) +{ + ks8842_select_bank(adapter, bank); + return ioread16(adapter->hw_addr + offset); +} + +static inline u32 ks8842_read32(struct ks8842_adapter *adapter, u16 bank, + int offset) +{ + ks8842_select_bank(adapter, bank); + return ioread32(adapter->hw_addr + offset); +} + +static void ks8842_reset(struct ks8842_adapter *adapter) +{ + if (adapter->conf_flags & MICREL_KS884X) { + ks8842_write16(adapter, 3, 1, REG_GRR); + msleep(10); + iowrite16(0, adapter->hw_addr + REG_GRR); + } else { + /* The KS8842 goes haywire when doing software reset + * a work around in the timberdale IP is implemented to + * do a hardware reset instead + ks8842_write16(adapter, 3, 1, REG_GRR); + msleep(10); + iowrite16(0, adapter->hw_addr + REG_GRR); + */ + iowrite32(0x1, adapter->hw_addr + REG_TIMB_RST); + msleep(20); + } +} + +static void ks8842_update_link_status(struct net_device *netdev, + struct ks8842_adapter *adapter) +{ + /* check the status of the link */ + if (ks8842_read16(adapter, 45, REG_P1MBSR) & 0x4) { + netif_carrier_on(netdev); + netif_wake_queue(netdev); + } else { + netif_stop_queue(netdev); + netif_carrier_off(netdev); + } +} + +static void ks8842_enable_tx(struct ks8842_adapter *adapter) +{ + ks8842_enable_bits(adapter, 16, 0x01, REG_TXCR); +} + +static void ks8842_disable_tx(struct ks8842_adapter *adapter) +{ + ks8842_clear_bits(adapter, 16, 0x01, REG_TXCR); +} + +static void ks8842_enable_rx(struct ks8842_adapter *adapter) +{ + ks8842_enable_bits(adapter, 16, 0x01, REG_RXCR); +} + +static void ks8842_disable_rx(struct ks8842_adapter *adapter) +{ + ks8842_clear_bits(adapter, 16, 0x01, REG_RXCR); +} + +static void ks8842_reset_hw(struct ks8842_adapter *adapter) +{ + /* reset the HW */ + ks8842_reset(adapter); + + /* Enable QMU Transmit flow control / transmit padding / Transmit CRC */ + ks8842_write16(adapter, 16, 0x000E, REG_TXCR); + + /* enable the receiver, uni + multi + broadcast + flow ctrl + + crc strip */ + ks8842_write16(adapter, 16, 0x8 | 0x20 | 0x40 | 0x80 | 0x400, + REG_RXCR); + + /* TX frame pointer autoincrement */ + ks8842_write16(adapter, 17, 0x4000, REG_TXFDPR); + + /* RX frame pointer autoincrement */ + ks8842_write16(adapter, 17, 0x4000, REG_RXFDPR); + + /* RX 2 kb high watermark */ + ks8842_write16(adapter, 0, 0x1000, REG_QRFCR); + + /* aggressive back off in half duplex */ + ks8842_enable_bits(adapter, 32, 1 << 8, REG_SGCR1); + + /* enable no excessive collision drop */ + ks8842_enable_bits(adapter, 32, 1 << 3, REG_SGCR2); + + /* Enable port 1 force flow control / back pressure / transmit / recv */ + ks8842_write16(adapter, 48, 0x1E07, REG_P1CR2); + + /* restart port auto-negotiation */ + ks8842_enable_bits(adapter, 49, 1 << 13, REG_P1CR4); + + /* Enable the transmitter */ + ks8842_enable_tx(adapter); + + /* Enable the receiver */ + ks8842_enable_rx(adapter); + + /* clear all interrupts */ + ks8842_write16(adapter, 18, 0xffff, REG_ISR); + + /* enable interrupts */ + if (KS8842_USE_DMA(adapter)) { + /* When running in DMA Mode the RX interrupt is not enabled in + timberdale because RX data is received by DMA callbacks + it must still be enabled in the KS8842 because it indicates + to timberdale when there is RX data for its DMA FIFOs */ + iowrite16(ENABLED_IRQS_DMA_IP, adapter->hw_addr + REG_TIMB_IER); + ks8842_write16(adapter, 18, ENABLED_IRQS_DMA, REG_IER); + } else { + if (!(adapter->conf_flags & MICREL_KS884X)) + iowrite16(ENABLED_IRQS, + adapter->hw_addr + REG_TIMB_IER); + ks8842_write16(adapter, 18, ENABLED_IRQS, REG_IER); + } + /* enable the switch */ + ks8842_write16(adapter, 32, 0x1, REG_SW_ID_AND_ENABLE); +} + +static void ks8842_init_mac_addr(struct ks8842_adapter *adapter) +{ + u8 addr[ETH_ALEN]; + int i; + u16 mac; + + for (i = 0; i < ETH_ALEN; i++) + addr[ETH_ALEN - i - 1] = ks8842_read8(adapter, 2, REG_MARL + i); + eth_hw_addr_set(adapter->netdev, addr); + + if (adapter->conf_flags & MICREL_KS884X) { + /* + the sequence of saving mac addr between MAC and Switch is + different. + */ + + mac = ks8842_read16(adapter, 2, REG_MARL); + ks8842_write16(adapter, 39, mac, REG_MACAR3); + mac = ks8842_read16(adapter, 2, REG_MARM); + ks8842_write16(adapter, 39, mac, REG_MACAR2); + mac = ks8842_read16(adapter, 2, REG_MARH); + ks8842_write16(adapter, 39, mac, REG_MACAR1); + } else { + + /* make sure the switch port uses the same MAC as the QMU */ + mac = ks8842_read16(adapter, 2, REG_MARL); + ks8842_write16(adapter, 39, mac, REG_MACAR1); + mac = ks8842_read16(adapter, 2, REG_MARM); + ks8842_write16(adapter, 39, mac, REG_MACAR2); + mac = ks8842_read16(adapter, 2, REG_MARH); + ks8842_write16(adapter, 39, mac, REG_MACAR3); + } +} + +static void ks8842_write_mac_addr(struct ks8842_adapter *adapter, const u8 *mac) +{ + unsigned long flags; + unsigned i; + + spin_lock_irqsave(&adapter->lock, flags); + for (i = 0; i < ETH_ALEN; i++) { + ks8842_write8(adapter, 2, mac[ETH_ALEN - i - 1], REG_MARL + i); + if (!(adapter->conf_flags & MICREL_KS884X)) + ks8842_write8(adapter, 39, mac[ETH_ALEN - i - 1], + REG_MACAR1 + i); + } + + if (adapter->conf_flags & MICREL_KS884X) { + /* + the sequence of saving mac addr between MAC and Switch is + different. + */ + + u16 mac; + + mac = ks8842_read16(adapter, 2, REG_MARL); + ks8842_write16(adapter, 39, mac, REG_MACAR3); + mac = ks8842_read16(adapter, 2, REG_MARM); + ks8842_write16(adapter, 39, mac, REG_MACAR2); + mac = ks8842_read16(adapter, 2, REG_MARH); + ks8842_write16(adapter, 39, mac, REG_MACAR1); + } + spin_unlock_irqrestore(&adapter->lock, flags); +} + +static inline u16 ks8842_tx_fifo_space(struct ks8842_adapter *adapter) +{ + return ks8842_read16(adapter, 16, REG_TXMIR) & 0x1fff; +} + +static int ks8842_tx_frame_dma(struct sk_buff *skb, struct net_device *netdev) +{ + struct ks8842_adapter *adapter = netdev_priv(netdev); + struct ks8842_tx_dma_ctl *ctl = &adapter->dma_tx; + u8 *buf = ctl->buf; + + if (ctl->adesc) { + netdev_dbg(netdev, "%s: TX ongoing\n", __func__); + /* transfer ongoing */ + return NETDEV_TX_BUSY; + } + + sg_dma_len(&ctl->sg) = skb->len + sizeof(u32); + + /* copy data to the TX buffer */ + /* the control word, enable IRQ, port 1 and the length */ + *buf++ = 0x00; + *buf++ = 0x01; /* Port 1 */ + *buf++ = skb->len & 0xff; + *buf++ = (skb->len >> 8) & 0xff; + skb_copy_from_linear_data(skb, buf, skb->len); + + dma_sync_single_range_for_device(adapter->dev, + sg_dma_address(&ctl->sg), 0, sg_dma_len(&ctl->sg), + DMA_TO_DEVICE); + + /* make sure the length is a multiple of 4 */ + if (sg_dma_len(&ctl->sg) % 4) + sg_dma_len(&ctl->sg) += 4 - sg_dma_len(&ctl->sg) % 4; + + ctl->adesc = dmaengine_prep_slave_sg(ctl->chan, + &ctl->sg, 1, DMA_MEM_TO_DEV, DMA_PREP_INTERRUPT); + if (!ctl->adesc) + return NETDEV_TX_BUSY; + + ctl->adesc->callback_param = netdev; + ctl->adesc->callback = ks8842_dma_tx_cb; + ctl->adesc->tx_submit(ctl->adesc); + + netdev->stats.tx_bytes += skb->len; + + dev_kfree_skb(skb); + + return NETDEV_TX_OK; +} + +static int ks8842_tx_frame(struct sk_buff *skb, struct net_device *netdev) +{ + struct ks8842_adapter *adapter = netdev_priv(netdev); + int len = skb->len; + + netdev_dbg(netdev, "%s: len %u head %p data %p tail %p end %p\n", + __func__, skb->len, skb->head, skb->data, + skb_tail_pointer(skb), skb_end_pointer(skb)); + + /* check FIFO buffer space, we need space for CRC and command bits */ + if (ks8842_tx_fifo_space(adapter) < len + 8) + return NETDEV_TX_BUSY; + + if (adapter->conf_flags & KS884X_16BIT) { + u16 *ptr16 = (u16 *)skb->data; + ks8842_write16(adapter, 17, 0x8000 | 0x100, REG_QMU_DATA_LO); + ks8842_write16(adapter, 17, (u16)len, REG_QMU_DATA_HI); + netdev->stats.tx_bytes += len; + + /* copy buffer */ + while (len > 0) { + iowrite16(*ptr16++, adapter->hw_addr + REG_QMU_DATA_LO); + iowrite16(*ptr16++, adapter->hw_addr + REG_QMU_DATA_HI); + len -= sizeof(u32); + } + } else { + + u32 *ptr = (u32 *)skb->data; + u32 ctrl; + /* the control word, enable IRQ, port 1 and the length */ + ctrl = 0x8000 | 0x100 | (len << 16); + ks8842_write32(adapter, 17, ctrl, REG_QMU_DATA_LO); + + netdev->stats.tx_bytes += len; + + /* copy buffer */ + while (len > 0) { + iowrite32(*ptr, adapter->hw_addr + REG_QMU_DATA_LO); + len -= sizeof(u32); + ptr++; + } + } + + /* enqueue packet */ + ks8842_write16(adapter, 17, 1, REG_TXQCR); + + dev_kfree_skb(skb); + + return NETDEV_TX_OK; +} + +static void ks8842_update_rx_err_counters(struct net_device *netdev, u32 status) +{ + netdev_dbg(netdev, "RX error, status: %x\n", status); + + netdev->stats.rx_errors++; + if (status & RXSR_TOO_LONG) + netdev->stats.rx_length_errors++; + if (status & RXSR_CRC_ERROR) + netdev->stats.rx_crc_errors++; + if (status & RXSR_RUNT) + netdev->stats.rx_frame_errors++; +} + +static void ks8842_update_rx_counters(struct net_device *netdev, u32 status, + int len) +{ + netdev_dbg(netdev, "RX packet, len: %d\n", len); + + netdev->stats.rx_packets++; + netdev->stats.rx_bytes += len; + if (status & RXSR_MULTICAST) + netdev->stats.multicast++; +} + +static int __ks8842_start_new_rx_dma(struct net_device *netdev) +{ + struct ks8842_adapter *adapter = netdev_priv(netdev); + struct ks8842_rx_dma_ctl *ctl = &adapter->dma_rx; + struct scatterlist *sg = &ctl->sg; + int err; + + ctl->skb = netdev_alloc_skb(netdev, DMA_BUFFER_SIZE); + if (ctl->skb) { + sg_init_table(sg, 1); + sg_dma_address(sg) = dma_map_single(adapter->dev, + ctl->skb->data, DMA_BUFFER_SIZE, DMA_FROM_DEVICE); + if (dma_mapping_error(adapter->dev, sg_dma_address(sg))) { + err = -ENOMEM; + sg_dma_address(sg) = 0; + goto out; + } + + sg_dma_len(sg) = DMA_BUFFER_SIZE; + + ctl->adesc = dmaengine_prep_slave_sg(ctl->chan, + sg, 1, DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT); + + if (!ctl->adesc) { + err = -ENOMEM; + goto out; + } + + ctl->adesc->callback_param = netdev; + ctl->adesc->callback = ks8842_dma_rx_cb; + ctl->adesc->tx_submit(ctl->adesc); + } else { + err = -ENOMEM; + sg_dma_address(sg) = 0; + goto out; + } + + return 0; +out: + if (sg_dma_address(sg)) + dma_unmap_single(adapter->dev, sg_dma_address(sg), + DMA_BUFFER_SIZE, DMA_FROM_DEVICE); + sg_dma_address(sg) = 0; + dev_kfree_skb(ctl->skb); + ctl->skb = NULL; + + printk(KERN_ERR DRV_NAME": Failed to start RX DMA: %d\n", err); + return err; +} + +static void ks8842_rx_frame_dma_tasklet(struct tasklet_struct *t) +{ + struct ks8842_adapter *adapter = from_tasklet(adapter, t, dma_rx.tasklet); + struct net_device *netdev = adapter->netdev; + struct ks8842_rx_dma_ctl *ctl = &adapter->dma_rx; + struct sk_buff *skb = ctl->skb; + dma_addr_t addr = sg_dma_address(&ctl->sg); + u32 status; + + ctl->adesc = NULL; + + /* kick next transfer going */ + __ks8842_start_new_rx_dma(netdev); + + /* now handle the data we got */ + dma_unmap_single(adapter->dev, addr, DMA_BUFFER_SIZE, DMA_FROM_DEVICE); + + status = *((u32 *)skb->data); + + netdev_dbg(netdev, "%s - rx_data: status: %x\n", + __func__, status & 0xffff); + + /* check the status */ + if ((status & RXSR_VALID) && !(status & RXSR_ERROR)) { + int len = (status >> 16) & 0x7ff; + + ks8842_update_rx_counters(netdev, status, len); + + /* reserve 4 bytes which is the status word */ + skb_reserve(skb, 4); + skb_put(skb, len); + + skb->protocol = eth_type_trans(skb, netdev); + netif_rx(skb); + } else { + ks8842_update_rx_err_counters(netdev, status); + dev_kfree_skb(skb); + } +} + +static void ks8842_rx_frame(struct net_device *netdev, + struct ks8842_adapter *adapter) +{ + u32 status; + int len; + + if (adapter->conf_flags & KS884X_16BIT) { + status = ks8842_read16(adapter, 17, REG_QMU_DATA_LO); + len = ks8842_read16(adapter, 17, REG_QMU_DATA_HI); + netdev_dbg(netdev, "%s - rx_data: status: %x\n", + __func__, status); + } else { + status = ks8842_read32(adapter, 17, REG_QMU_DATA_LO); + len = (status >> 16) & 0x7ff; + status &= 0xffff; + netdev_dbg(netdev, "%s - rx_data: status: %x\n", + __func__, status); + } + + /* check the status */ + if ((status & RXSR_VALID) && !(status & RXSR_ERROR)) { + struct sk_buff *skb = netdev_alloc_skb_ip_align(netdev, len + 3); + + if (skb) { + + ks8842_update_rx_counters(netdev, status, len); + + if (adapter->conf_flags & KS884X_16BIT) { + u16 *data16 = skb_put(skb, len); + ks8842_select_bank(adapter, 17); + while (len > 0) { + *data16++ = ioread16(adapter->hw_addr + + REG_QMU_DATA_LO); + *data16++ = ioread16(adapter->hw_addr + + REG_QMU_DATA_HI); + len -= sizeof(u32); + } + } else { + u32 *data = skb_put(skb, len); + + ks8842_select_bank(adapter, 17); + while (len > 0) { + *data++ = ioread32(adapter->hw_addr + + REG_QMU_DATA_LO); + len -= sizeof(u32); + } + } + skb->protocol = eth_type_trans(skb, netdev); + netif_rx(skb); + } else + netdev->stats.rx_dropped++; + } else + ks8842_update_rx_err_counters(netdev, status); + + /* set high watermark to 3K */ + ks8842_clear_bits(adapter, 0, 1 << 12, REG_QRFCR); + + /* release the frame */ + ks8842_write16(adapter, 17, 0x01, REG_RXQCR); + + /* set high watermark to 2K */ + ks8842_enable_bits(adapter, 0, 1 << 12, REG_QRFCR); +} + +static void ks8842_handle_rx(struct net_device *netdev, + struct ks8842_adapter *adapter) +{ + u16 rx_data = ks8842_read16(adapter, 16, REG_RXMIR) & 0x1fff; + netdev_dbg(netdev, "%s Entry - rx_data: %d\n", __func__, rx_data); + while (rx_data) { + ks8842_rx_frame(netdev, adapter); + rx_data = ks8842_read16(adapter, 16, REG_RXMIR) & 0x1fff; + } +} + +static void ks8842_handle_tx(struct net_device *netdev, + struct ks8842_adapter *adapter) +{ + u16 sr = ks8842_read16(adapter, 16, REG_TXSR); + netdev_dbg(netdev, "%s - entry, sr: %x\n", __func__, sr); + netdev->stats.tx_packets++; + if (netif_queue_stopped(netdev)) + netif_wake_queue(netdev); +} + +static void ks8842_handle_rx_overrun(struct net_device *netdev, + struct ks8842_adapter *adapter) +{ + netdev_dbg(netdev, "%s: entry\n", __func__); + netdev->stats.rx_errors++; + netdev->stats.rx_fifo_errors++; +} + +static void ks8842_tasklet(struct tasklet_struct *t) +{ + struct ks8842_adapter *adapter = from_tasklet(adapter, t, tasklet); + struct net_device *netdev = adapter->netdev; + u16 isr; + unsigned long flags; + u16 entry_bank; + + /* read current bank to be able to set it back */ + spin_lock_irqsave(&adapter->lock, flags); + entry_bank = ioread16(adapter->hw_addr + REG_SELECT_BANK); + spin_unlock_irqrestore(&adapter->lock, flags); + + isr = ks8842_read16(adapter, 18, REG_ISR); + netdev_dbg(netdev, "%s - ISR: 0x%x\n", __func__, isr); + + /* when running in DMA mode, do not ack RX interrupts, it is handled + internally by timberdale, otherwise it's DMA FIFO:s would stop + */ + if (KS8842_USE_DMA(adapter)) + isr &= ~IRQ_RX; + + /* Ack */ + ks8842_write16(adapter, 18, isr, REG_ISR); + + if (!(adapter->conf_flags & MICREL_KS884X)) + /* Ack in the timberdale IP as well */ + iowrite32(0x1, adapter->hw_addr + REG_TIMB_IAR); + + if (!netif_running(netdev)) + return; + + if (isr & IRQ_LINK_CHANGE) + ks8842_update_link_status(netdev, adapter); + + /* should not get IRQ_RX when running DMA mode */ + if (isr & (IRQ_RX | IRQ_RX_ERROR) && !KS8842_USE_DMA(adapter)) + ks8842_handle_rx(netdev, adapter); + + /* should only happen when in PIO mode */ + if (isr & IRQ_TX) + ks8842_handle_tx(netdev, adapter); + + if (isr & IRQ_RX_OVERRUN) + ks8842_handle_rx_overrun(netdev, adapter); + + if (isr & IRQ_TX_STOPPED) { + ks8842_disable_tx(adapter); + ks8842_enable_tx(adapter); + } + + if (isr & IRQ_RX_STOPPED) { + ks8842_disable_rx(adapter); + ks8842_enable_rx(adapter); + } + + /* re-enable interrupts, put back the bank selection register */ + spin_lock_irqsave(&adapter->lock, flags); + if (KS8842_USE_DMA(adapter)) + ks8842_write16(adapter, 18, ENABLED_IRQS_DMA, REG_IER); + else + ks8842_write16(adapter, 18, ENABLED_IRQS, REG_IER); + iowrite16(entry_bank, adapter->hw_addr + REG_SELECT_BANK); + + /* Make sure timberdale continues DMA operations, they are stopped while + we are handling the ks8842 because we might change bank */ + if (KS8842_USE_DMA(adapter)) + ks8842_resume_dma(adapter); + + spin_unlock_irqrestore(&adapter->lock, flags); +} + +static irqreturn_t ks8842_irq(int irq, void *devid) +{ + struct net_device *netdev = devid; + struct ks8842_adapter *adapter = netdev_priv(netdev); + u16 isr; + u16 entry_bank = ioread16(adapter->hw_addr + REG_SELECT_BANK); + irqreturn_t ret = IRQ_NONE; + + isr = ks8842_read16(adapter, 18, REG_ISR); + netdev_dbg(netdev, "%s - ISR: 0x%x\n", __func__, isr); + + if (isr) { + if (KS8842_USE_DMA(adapter)) + /* disable all but RX IRQ, since the FPGA relies on it*/ + ks8842_write16(adapter, 18, IRQ_RX, REG_IER); + else + /* disable IRQ */ + ks8842_write16(adapter, 18, 0x00, REG_IER); + + /* schedule tasklet */ + tasklet_schedule(&adapter->tasklet); + + ret = IRQ_HANDLED; + } + + iowrite16(entry_bank, adapter->hw_addr + REG_SELECT_BANK); + + /* After an interrupt, tell timberdale to continue DMA operations. + DMA is disabled while we are handling the ks8842 because we might + change bank */ + ks8842_resume_dma(adapter); + + return ret; +} + +static void ks8842_dma_rx_cb(void *data) +{ + struct net_device *netdev = data; + struct ks8842_adapter *adapter = netdev_priv(netdev); + + netdev_dbg(netdev, "RX DMA finished\n"); + /* schedule tasklet */ + if (adapter->dma_rx.adesc) + tasklet_schedule(&adapter->dma_rx.tasklet); +} + +static void ks8842_dma_tx_cb(void *data) +{ + struct net_device *netdev = data; + struct ks8842_adapter *adapter = netdev_priv(netdev); + struct ks8842_tx_dma_ctl *ctl = &adapter->dma_tx; + + netdev_dbg(netdev, "TX DMA finished\n"); + + if (!ctl->adesc) + return; + + netdev->stats.tx_packets++; + ctl->adesc = NULL; + + if (netif_queue_stopped(netdev)) + netif_wake_queue(netdev); +} + +static void ks8842_stop_dma(struct ks8842_adapter *adapter) +{ + struct ks8842_tx_dma_ctl *tx_ctl = &adapter->dma_tx; + struct ks8842_rx_dma_ctl *rx_ctl = &adapter->dma_rx; + + tx_ctl->adesc = NULL; + if (tx_ctl->chan) + dmaengine_terminate_all(tx_ctl->chan); + + rx_ctl->adesc = NULL; + if (rx_ctl->chan) + dmaengine_terminate_all(rx_ctl->chan); + + if (sg_dma_address(&rx_ctl->sg)) + dma_unmap_single(adapter->dev, sg_dma_address(&rx_ctl->sg), + DMA_BUFFER_SIZE, DMA_FROM_DEVICE); + sg_dma_address(&rx_ctl->sg) = 0; + + dev_kfree_skb(rx_ctl->skb); + rx_ctl->skb = NULL; +} + +static void ks8842_dealloc_dma_bufs(struct ks8842_adapter *adapter) +{ + struct ks8842_tx_dma_ctl *tx_ctl = &adapter->dma_tx; + struct ks8842_rx_dma_ctl *rx_ctl = &adapter->dma_rx; + + ks8842_stop_dma(adapter); + + if (tx_ctl->chan) + dma_release_channel(tx_ctl->chan); + tx_ctl->chan = NULL; + + if (rx_ctl->chan) + dma_release_channel(rx_ctl->chan); + rx_ctl->chan = NULL; + + tasklet_kill(&rx_ctl->tasklet); + + if (sg_dma_address(&tx_ctl->sg)) + dma_unmap_single(adapter->dev, sg_dma_address(&tx_ctl->sg), + DMA_BUFFER_SIZE, DMA_TO_DEVICE); + sg_dma_address(&tx_ctl->sg) = 0; + + kfree(tx_ctl->buf); + tx_ctl->buf = NULL; +} + +static bool ks8842_dma_filter_fn(struct dma_chan *chan, void *filter_param) +{ + return chan->chan_id == (long)filter_param; +} + +static int ks8842_alloc_dma_bufs(struct net_device *netdev) +{ + struct ks8842_adapter *adapter = netdev_priv(netdev); + struct ks8842_tx_dma_ctl *tx_ctl = &adapter->dma_tx; + struct ks8842_rx_dma_ctl *rx_ctl = &adapter->dma_rx; + int err; + + dma_cap_mask_t mask; + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + dma_cap_set(DMA_PRIVATE, mask); + + sg_init_table(&tx_ctl->sg, 1); + + tx_ctl->chan = dma_request_channel(mask, ks8842_dma_filter_fn, + (void *)(long)tx_ctl->channel); + if (!tx_ctl->chan) { + err = -ENODEV; + goto err; + } + + /* allocate DMA buffer */ + tx_ctl->buf = kmalloc(DMA_BUFFER_SIZE, GFP_KERNEL); + if (!tx_ctl->buf) { + err = -ENOMEM; + goto err; + } + + sg_dma_address(&tx_ctl->sg) = dma_map_single(adapter->dev, + tx_ctl->buf, DMA_BUFFER_SIZE, DMA_TO_DEVICE); + if (dma_mapping_error(adapter->dev, sg_dma_address(&tx_ctl->sg))) { + err = -ENOMEM; + sg_dma_address(&tx_ctl->sg) = 0; + goto err; + } + + rx_ctl->chan = dma_request_channel(mask, ks8842_dma_filter_fn, + (void *)(long)rx_ctl->channel); + if (!rx_ctl->chan) { + err = -ENODEV; + goto err; + } + + tasklet_setup(&rx_ctl->tasklet, ks8842_rx_frame_dma_tasklet); + + return 0; +err: + ks8842_dealloc_dma_bufs(adapter); + return err; +} + +/* Netdevice operations */ + +static int ks8842_open(struct net_device *netdev) +{ + struct ks8842_adapter *adapter = netdev_priv(netdev); + int err; + + netdev_dbg(netdev, "%s - entry\n", __func__); + + if (KS8842_USE_DMA(adapter)) { + err = ks8842_alloc_dma_bufs(netdev); + + if (!err) { + /* start RX dma */ + err = __ks8842_start_new_rx_dma(netdev); + if (err) + ks8842_dealloc_dma_bufs(adapter); + } + + if (err) { + printk(KERN_WARNING DRV_NAME + ": Failed to initiate DMA, running PIO\n"); + ks8842_dealloc_dma_bufs(adapter); + adapter->dma_rx.channel = -1; + adapter->dma_tx.channel = -1; + } + } + + /* reset the HW */ + ks8842_reset_hw(adapter); + + ks8842_write_mac_addr(adapter, netdev->dev_addr); + + ks8842_update_link_status(netdev, adapter); + + err = request_irq(adapter->irq, ks8842_irq, IRQF_SHARED, DRV_NAME, + netdev); + if (err) { + pr_err("Failed to request IRQ: %d: %d\n", adapter->irq, err); + return err; + } + + return 0; +} + +static int ks8842_close(struct net_device *netdev) +{ + struct ks8842_adapter *adapter = netdev_priv(netdev); + + netdev_dbg(netdev, "%s - entry\n", __func__); + + cancel_work_sync(&adapter->timeout_work); + + if (KS8842_USE_DMA(adapter)) + ks8842_dealloc_dma_bufs(adapter); + + /* free the irq */ + free_irq(adapter->irq, netdev); + + /* disable the switch */ + ks8842_write16(adapter, 32, 0x0, REG_SW_ID_AND_ENABLE); + + return 0; +} + +static netdev_tx_t ks8842_xmit_frame(struct sk_buff *skb, + struct net_device *netdev) +{ + int ret; + struct ks8842_adapter *adapter = netdev_priv(netdev); + + netdev_dbg(netdev, "%s: entry\n", __func__); + + if (KS8842_USE_DMA(adapter)) { + unsigned long flags; + ret = ks8842_tx_frame_dma(skb, netdev); + /* for now only allow one transfer at the time */ + spin_lock_irqsave(&adapter->lock, flags); + if (adapter->dma_tx.adesc) + netif_stop_queue(netdev); + spin_unlock_irqrestore(&adapter->lock, flags); + return ret; + } + + ret = ks8842_tx_frame(skb, netdev); + + if (ks8842_tx_fifo_space(adapter) < netdev->mtu + 8) + netif_stop_queue(netdev); + + return ret; +} + +static int ks8842_set_mac(struct net_device *netdev, void *p) +{ + struct ks8842_adapter *adapter = netdev_priv(netdev); + struct sockaddr *addr = p; + char *mac = (u8 *)addr->sa_data; + + netdev_dbg(netdev, "%s: entry\n", __func__); + + if (!is_valid_ether_addr(addr->sa_data)) + return -EADDRNOTAVAIL; + + eth_hw_addr_set(netdev, mac); + + ks8842_write_mac_addr(adapter, mac); + return 0; +} + +static void ks8842_tx_timeout_work(struct work_struct *work) +{ + struct ks8842_adapter *adapter = + container_of(work, struct ks8842_adapter, timeout_work); + struct net_device *netdev = adapter->netdev; + unsigned long flags; + + netdev_dbg(netdev, "%s: entry\n", __func__); + + spin_lock_irqsave(&adapter->lock, flags); + + if (KS8842_USE_DMA(adapter)) + ks8842_stop_dma(adapter); + + /* disable interrupts */ + ks8842_write16(adapter, 18, 0, REG_IER); + ks8842_write16(adapter, 18, 0xFFFF, REG_ISR); + + netif_stop_queue(netdev); + + spin_unlock_irqrestore(&adapter->lock, flags); + + ks8842_reset_hw(adapter); + + ks8842_write_mac_addr(adapter, netdev->dev_addr); + + ks8842_update_link_status(netdev, adapter); + + if (KS8842_USE_DMA(adapter)) + __ks8842_start_new_rx_dma(netdev); +} + +static void ks8842_tx_timeout(struct net_device *netdev, unsigned int txqueue) +{ + struct ks8842_adapter *adapter = netdev_priv(netdev); + + netdev_dbg(netdev, "%s: entry\n", __func__); + + schedule_work(&adapter->timeout_work); +} + +static const struct net_device_ops ks8842_netdev_ops = { + .ndo_open = ks8842_open, + .ndo_stop = ks8842_close, + .ndo_start_xmit = ks8842_xmit_frame, + .ndo_set_mac_address = ks8842_set_mac, + .ndo_tx_timeout = ks8842_tx_timeout, + .ndo_validate_addr = eth_validate_addr +}; + +static const struct ethtool_ops ks8842_ethtool_ops = { + .get_link = ethtool_op_get_link, +}; + +static int ks8842_probe(struct platform_device *pdev) +{ + int err = -ENOMEM; + struct resource *iomem; + struct net_device *netdev; + struct ks8842_adapter *adapter; + struct ks8842_platform_data *pdata = dev_get_platdata(&pdev->dev); + u16 id; + unsigned i; + + iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!iomem) { + dev_err(&pdev->dev, "Invalid resource\n"); + return -EINVAL; + } + if (!request_mem_region(iomem->start, resource_size(iomem), DRV_NAME)) + goto err_mem_region; + + netdev = alloc_etherdev(sizeof(struct ks8842_adapter)); + if (!netdev) + goto err_alloc_etherdev; + + SET_NETDEV_DEV(netdev, &pdev->dev); + + adapter = netdev_priv(netdev); + adapter->netdev = netdev; + INIT_WORK(&adapter->timeout_work, ks8842_tx_timeout_work); + adapter->hw_addr = ioremap(iomem->start, resource_size(iomem)); + adapter->conf_flags = iomem->flags; + + if (!adapter->hw_addr) + goto err_ioremap; + + adapter->irq = platform_get_irq(pdev, 0); + if (adapter->irq < 0) { + err = adapter->irq; + goto err_get_irq; + } + + adapter->dev = (pdev->dev.parent) ? pdev->dev.parent : &pdev->dev; + + /* DMA is only supported when accessed via timberdale */ + if (!(adapter->conf_flags & MICREL_KS884X) && pdata && + (pdata->tx_dma_channel != -1) && + (pdata->rx_dma_channel != -1)) { + adapter->dma_rx.channel = pdata->rx_dma_channel; + adapter->dma_tx.channel = pdata->tx_dma_channel; + } else { + adapter->dma_rx.channel = -1; + adapter->dma_tx.channel = -1; + } + + tasklet_setup(&adapter->tasklet, ks8842_tasklet); + spin_lock_init(&adapter->lock); + + netdev->netdev_ops = &ks8842_netdev_ops; + netdev->ethtool_ops = &ks8842_ethtool_ops; + + /* Check if a mac address was given */ + i = netdev->addr_len; + if (pdata) { + for (i = 0; i < netdev->addr_len; i++) + if (pdata->macaddr[i] != 0) + break; + + if (i < netdev->addr_len) + /* an address was passed, use it */ + eth_hw_addr_set(netdev, pdata->macaddr); + } + + if (i == netdev->addr_len) { + ks8842_init_mac_addr(adapter); + + if (!is_valid_ether_addr(netdev->dev_addr)) + eth_hw_addr_random(netdev); + } + + id = ks8842_read16(adapter, 32, REG_SW_ID_AND_ENABLE); + + strcpy(netdev->name, "eth%d"); + err = register_netdev(netdev); + if (err) + goto err_register; + + platform_set_drvdata(pdev, netdev); + + pr_info("Found chip, family: 0x%x, id: 0x%x, rev: 0x%x\n", + (id >> 8) & 0xff, (id >> 4) & 0xf, (id >> 1) & 0x7); + + return 0; + +err_register: +err_get_irq: + iounmap(adapter->hw_addr); +err_ioremap: + free_netdev(netdev); +err_alloc_etherdev: + release_mem_region(iomem->start, resource_size(iomem)); +err_mem_region: + return err; +} + +static void ks8842_remove(struct platform_device *pdev) +{ + struct net_device *netdev = platform_get_drvdata(pdev); + struct ks8842_adapter *adapter = netdev_priv(netdev); + struct resource *iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + unregister_netdev(netdev); + tasklet_kill(&adapter->tasklet); + iounmap(adapter->hw_addr); + free_netdev(netdev); + release_mem_region(iomem->start, resource_size(iomem)); +} + + +static struct platform_driver ks8842_platform_driver = { + .driver = { + .name = DRV_NAME, + }, + .probe = ks8842_probe, + .remove = ks8842_remove, +}; + +module_platform_driver(ks8842_platform_driver); + +MODULE_DESCRIPTION("Timberdale KS8842 ethernet driver"); +MODULE_AUTHOR("Mocean Laboratories "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:ks8842"); + diff --git a/drivers/net/ethernet/micrel/ks8851.h b/drivers/net/ethernet/micrel/ks8851.h new file mode 100644 index 000000000..b795a3a60 --- /dev/null +++ b/drivers/net/ethernet/micrel/ks8851.h @@ -0,0 +1,451 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* drivers/net/ethernet/micrel/ks8851.h + * + * Copyright 2009 Simtec Electronics + * Ben Dooks + * + * KS8851 register definitions +*/ + +#ifndef __KS8851_H__ +#define __KS8851_H__ + +#include + +#define KS_CCR 0x08 +#define CCR_LE (1 << 10) /* KSZ8851-16MLL */ +#define CCR_EEPROM (1 << 9) +#define CCR_SPI (1 << 8) /* KSZ8851SNL */ +#define CCR_8BIT (1 << 7) /* KSZ8851-16MLL */ +#define CCR_16BIT (1 << 6) /* KSZ8851-16MLL */ +#define CCR_32BIT (1 << 5) /* KSZ8851-16MLL */ +#define CCR_SHARED (1 << 4) /* KSZ8851-16MLL */ +#define CCR_48PIN (1 << 1) /* KSZ8851-16MLL */ +#define CCR_32PIN (1 << 0) /* KSZ8851SNL */ + +/* MAC address registers */ +#define KS_MAR(_m) (0x14 - (_m)) +#define KS_MARL 0x10 +#define KS_MARM 0x12 +#define KS_MARH 0x14 + +#define KS_OBCR 0x20 +#define OBCR_ODS_16mA (1 << 6) + +#define KS_EEPCR 0x22 +#define EEPCR_EESRWA (1 << 5) +#define EEPCR_EESA (1 << 4) +#define EEPCR_EESB (1 << 3) +#define EEPCR_EEDO (1 << 2) +#define EEPCR_EESCK (1 << 1) +#define EEPCR_EECS (1 << 0) + +#define KS_MBIR 0x24 +#define MBIR_TXMBF (1 << 12) +#define MBIR_TXMBFA (1 << 11) +#define MBIR_RXMBF (1 << 4) +#define MBIR_RXMBFA (1 << 3) + +#define KS_GRR 0x26 +#define GRR_QMU (1 << 1) +#define GRR_GSR (1 << 0) + +#define KS_WFCR 0x2A +#define WFCR_MPRXE (1 << 7) +#define WFCR_WF3E (1 << 3) +#define WFCR_WF2E (1 << 2) +#define WFCR_WF1E (1 << 1) +#define WFCR_WF0E (1 << 0) + +#define KS_WF0CRC0 0x30 +#define KS_WF0CRC1 0x32 +#define KS_WF0BM0 0x34 +#define KS_WF0BM1 0x36 +#define KS_WF0BM2 0x38 +#define KS_WF0BM3 0x3A + +#define KS_WF1CRC0 0x40 +#define KS_WF1CRC1 0x42 +#define KS_WF1BM0 0x44 +#define KS_WF1BM1 0x46 +#define KS_WF1BM2 0x48 +#define KS_WF1BM3 0x4A + +#define KS_WF2CRC0 0x50 +#define KS_WF2CRC1 0x52 +#define KS_WF2BM0 0x54 +#define KS_WF2BM1 0x56 +#define KS_WF2BM2 0x58 +#define KS_WF2BM3 0x5A + +#define KS_WF3CRC0 0x60 +#define KS_WF3CRC1 0x62 +#define KS_WF3BM0 0x64 +#define KS_WF3BM1 0x66 +#define KS_WF3BM2 0x68 +#define KS_WF3BM3 0x6A + +#define KS_TXCR 0x70 +#define TXCR_TCGICMP (1 << 8) +#define TXCR_TCGUDP (1 << 7) +#define TXCR_TCGTCP (1 << 6) +#define TXCR_TCGIP (1 << 5) +#define TXCR_FTXQ (1 << 4) +#define TXCR_TXFCE (1 << 3) +#define TXCR_TXPE (1 << 2) +#define TXCR_TXCRC (1 << 1) +#define TXCR_TXE (1 << 0) + +#define KS_TXSR 0x72 +#define TXSR_TXLC (1 << 13) +#define TXSR_TXMC (1 << 12) +#define TXSR_TXFID_MASK (0x3f << 0) +#define TXSR_TXFID_SHIFT (0) +#define TXSR_TXFID_GET(_v) (((_v) >> 0) & 0x3f) + +#define KS_RXCR1 0x74 +#define RXCR1_FRXQ (1 << 15) +#define RXCR1_RXUDPFCC (1 << 14) +#define RXCR1_RXTCPFCC (1 << 13) +#define RXCR1_RXIPFCC (1 << 12) +#define RXCR1_RXPAFMA (1 << 11) +#define RXCR1_RXFCE (1 << 10) +#define RXCR1_RXEFE (1 << 9) +#define RXCR1_RXMAFMA (1 << 8) +#define RXCR1_RXBE (1 << 7) +#define RXCR1_RXME (1 << 6) +#define RXCR1_RXUE (1 << 5) +#define RXCR1_RXAE (1 << 4) +#define RXCR1_RXINVF (1 << 1) +#define RXCR1_RXE (1 << 0) + +#define KS_RXCR2 0x76 +#define RXCR2_SRDBL_MASK (0x7 << 5) /* KSZ8851SNL */ +#define RXCR2_SRDBL_SHIFT (5) /* KSZ8851SNL */ +#define RXCR2_SRDBL_4B (0x0 << 5) /* KSZ8851SNL */ +#define RXCR2_SRDBL_8B (0x1 << 5) /* KSZ8851SNL */ +#define RXCR2_SRDBL_16B (0x2 << 5) /* KSZ8851SNL */ +#define RXCR2_SRDBL_32B (0x3 << 5) /* KSZ8851SNL */ +#define RXCR2_SRDBL_FRAME (0x4 << 5) /* KSZ8851SNL */ +#define RXCR2_IUFFP (1 << 4) +#define RXCR2_RXIUFCEZ (1 << 3) +#define RXCR2_UDPLFE (1 << 2) +#define RXCR2_RXICMPFCC (1 << 1) +#define RXCR2_RXSAF (1 << 0) + +#define KS_TXMIR 0x78 + +#define KS_RXFHSR 0x7C +#define RXFSHR_RXFV (1 << 15) +#define RXFSHR_RXICMPFCS (1 << 13) +#define RXFSHR_RXIPFCS (1 << 12) +#define RXFSHR_RXTCPFCS (1 << 11) +#define RXFSHR_RXUDPFCS (1 << 10) +#define RXFSHR_RXBF (1 << 7) +#define RXFSHR_RXMF (1 << 6) +#define RXFSHR_RXUF (1 << 5) +#define RXFSHR_RXMR (1 << 4) +#define RXFSHR_RXFT (1 << 3) +#define RXFSHR_RXFTL (1 << 2) +#define RXFSHR_RXRF (1 << 1) +#define RXFSHR_RXCE (1 << 0) + +#define KS_RXFHBCR 0x7E +#define RXFHBCR_CNT_MASK (0xfff << 0) + +#define KS_TXQCR 0x80 +#define TXQCR_AETFE (1 << 2) /* KSZ8851SNL */ +#define TXQCR_TXQMAM (1 << 1) +#define TXQCR_METFE (1 << 0) + +#define KS_RXQCR 0x82 +#define RXQCR_RXDTTS (1 << 12) +#define RXQCR_RXDBCTS (1 << 11) +#define RXQCR_RXFCTS (1 << 10) +#define RXQCR_RXIPHTOE (1 << 9) +#define RXQCR_RXDTTE (1 << 7) +#define RXQCR_RXDBCTE (1 << 6) +#define RXQCR_RXFCTE (1 << 5) +#define RXQCR_ADRFE (1 << 4) +#define RXQCR_SDA (1 << 3) +#define RXQCR_RRXEF (1 << 0) + +#define KS_TXFDPR 0x84 +#define TXFDPR_TXFPAI (1 << 14) +#define TXFDPR_TXFP_MASK (0x7ff << 0) +#define TXFDPR_TXFP_SHIFT (0) + +#define KS_RXFDPR 0x86 +#define RXFDPR_RXFPAI (1 << 14) +#define RXFDPR_WST (1 << 12) /* KSZ8851-16MLL */ +#define RXFDPR_EMS (1 << 11) /* KSZ8851-16MLL */ +#define RXFDPR_RXFP_MASK (0x7ff << 0) +#define RXFDPR_RXFP_SHIFT (0) + +#define KS_RXDTTR 0x8C +#define KS_RXDBCTR 0x8E + +#define KS_IER 0x90 +#define KS_ISR 0x92 +#define IRQ_LCI (1 << 15) +#define IRQ_TXI (1 << 14) +#define IRQ_RXI (1 << 13) +#define IRQ_RXOI (1 << 11) +#define IRQ_TXPSI (1 << 9) +#define IRQ_RXPSI (1 << 8) +#define IRQ_TXSAI (1 << 6) +#define IRQ_RXWFDI (1 << 5) +#define IRQ_RXMPDI (1 << 4) +#define IRQ_LDI (1 << 3) +#define IRQ_EDI (1 << 2) +#define IRQ_SPIBEI (1 << 1) /* KSZ8851SNL */ +#define IRQ_DEDI (1 << 0) + +#define KS_RXFCTR 0x9C +#define KS_RXFC 0x9D +#define RXFCTR_RXFC_MASK (0xff << 8) +#define RXFCTR_RXFC_SHIFT (8) +#define RXFCTR_RXFC_GET(_v) (((_v) >> 8) & 0xff) +#define RXFCTR_RXFCT_MASK (0xff << 0) +#define RXFCTR_RXFCT_SHIFT (0) + +#define KS_TXNTFSR 0x9E + +#define KS_MAHTR0 0xA0 +#define KS_MAHTR1 0xA2 +#define KS_MAHTR2 0xA4 +#define KS_MAHTR3 0xA6 + +#define KS_FCLWR 0xB0 +#define KS_FCHWR 0xB2 +#define KS_FCOWR 0xB4 + +#define KS_CIDER 0xC0 +#define CIDER_ID 0x8870 +#define CIDER_REV_MASK (0x7 << 1) +#define CIDER_REV_SHIFT (1) +#define CIDER_REV_GET(_v) (((_v) >> 1) & 0x7) + +#define KS_CGCR 0xC6 + +#define KS_IACR 0xC8 +#define IACR_RDEN (1 << 12) +#define IACR_TSEL_MASK (0x3 << 10) +#define IACR_TSEL_SHIFT (10) +#define IACR_TSEL_MIB (0x3 << 10) +#define IACR_ADDR_MASK (0x1f << 0) +#define IACR_ADDR_SHIFT (0) + +#define KS_IADLR 0xD0 +#define KS_IAHDR 0xD2 + +#define KS_PMECR 0xD4 +#define PMECR_PME_DELAY (1 << 14) +#define PMECR_PME_POL (1 << 12) +#define PMECR_WOL_WAKEUP (1 << 11) +#define PMECR_WOL_MAGICPKT (1 << 10) +#define PMECR_WOL_LINKUP (1 << 9) +#define PMECR_WOL_ENERGY (1 << 8) +#define PMECR_AUTO_WAKE_EN (1 << 7) +#define PMECR_WAKEUP_NORMAL (1 << 6) +#define PMECR_WKEVT_MASK (0xf << 2) +#define PMECR_WKEVT_SHIFT (2) +#define PMECR_WKEVT_GET(_v) (((_v) >> 2) & 0xf) +#define PMECR_WKEVT_ENERGY (0x1 << 2) +#define PMECR_WKEVT_LINK (0x2 << 2) +#define PMECR_WKEVT_MAGICPKT (0x4 << 2) +#define PMECR_WKEVT_FRAME (0x8 << 2) +#define PMECR_PM_MASK (0x3 << 0) +#define PMECR_PM_SHIFT (0) +#define PMECR_PM_NORMAL (0x0 << 0) +#define PMECR_PM_ENERGY (0x1 << 0) +#define PMECR_PM_SOFTDOWN (0x2 << 0) +#define PMECR_PM_POWERSAVE (0x3 << 0) + +/* Standard MII PHY data */ +#define KS_P1MBCR 0xE4 +#define KS_P1MBSR 0xE6 +#define KS_PHY1ILR 0xE8 +#define KS_PHY1IHR 0xEA +#define KS_P1ANAR 0xEC +#define KS_P1ANLPR 0xEE + +#define KS_P1SCLMD 0xF4 + +#define KS_P1CR 0xF6 +#define P1CR_LEDOFF (1 << 15) +#define P1CR_TXIDS (1 << 14) +#define P1CR_RESTARTAN (1 << 13) +#define P1CR_DISAUTOMDIX (1 << 10) +#define P1CR_FORCEMDIX (1 << 9) +#define P1CR_AUTONEGEN (1 << 7) +#define P1CR_FORCE100 (1 << 6) +#define P1CR_FORCEFDX (1 << 5) +#define P1CR_ADV_FLOW (1 << 4) +#define P1CR_ADV_100BT_FDX (1 << 3) +#define P1CR_ADV_100BT_HDX (1 << 2) +#define P1CR_ADV_10BT_FDX (1 << 1) +#define P1CR_ADV_10BT_HDX (1 << 0) + +#define KS_P1SR 0xF8 +#define P1SR_HP_MDIX (1 << 15) +#define P1SR_REV_POL (1 << 13) +#define P1SR_OP_100M (1 << 10) +#define P1SR_OP_FDX (1 << 9) +#define P1SR_OP_MDI (1 << 7) +#define P1SR_AN_DONE (1 << 6) +#define P1SR_LINK_GOOD (1 << 5) +#define P1SR_PNTR_FLOW (1 << 4) +#define P1SR_PNTR_100BT_FDX (1 << 3) +#define P1SR_PNTR_100BT_HDX (1 << 2) +#define P1SR_PNTR_10BT_FDX (1 << 1) +#define P1SR_PNTR_10BT_HDX (1 << 0) + +/* TX Frame control */ +#define TXFR_TXIC (1 << 15) +#define TXFR_TXFID_MASK (0x3f << 0) +#define TXFR_TXFID_SHIFT (0) + +/** + * struct ks8851_rxctrl - KS8851 driver rx control + * @mchash: Multicast hash-table data. + * @rxcr1: KS_RXCR1 register setting + * @rxcr2: KS_RXCR2 register setting + * + * Representation of the settings needs to control the receive filtering + * such as the multicast hash-filter and the receive register settings. This + * is used to make the job of working out if the receive settings change and + * then issuing the new settings to the worker that will send the necessary + * commands. + */ +struct ks8851_rxctrl { + u16 mchash[4]; + u16 rxcr1; + u16 rxcr2; +}; + +/** + * union ks8851_tx_hdr - tx header data + * @txb: The header as bytes + * @txw: The header as 16bit, little-endian words + * + * A dual representation of the tx header data to allow + * access to individual bytes, and to allow 16bit accesses + * with 16bit alignment. + */ +union ks8851_tx_hdr { + u8 txb[6]; + __le16 txw[3]; +}; + +/** + * struct ks8851_net - KS8851 driver private data + * @netdev: The network device we're bound to + * @statelock: Lock on this structure for tx list. + * @mii: The MII state information for the mii calls. + * @rxctrl: RX settings for @rxctrl_work. + * @rxctrl_work: Work queue for updating RX mode and multicast lists + * @txq: Queue of packets for transmission. + * @txh: Space for generating packet TX header in DMA-able data + * @rxd: Space for receiving SPI data, in DMA-able space. + * @txd: Space for transmitting SPI data, in DMA-able space. + * @msg_enable: The message flags controlling driver output (see ethtool). + * @tx_space: Free space in the hardware TX buffer (cached copy of KS_TXMIR). + * @queued_len: Space required in hardware TX buffer for queued packets in txq. + * @fid: Incrementing frame id tag. + * @rc_ier: Cached copy of KS_IER. + * @rc_ccr: Cached copy of KS_CCR. + * @rc_rxqcr: Cached copy of KS_RXQCR. + * @eeprom: 93CX6 EEPROM state for accessing on-board EEPROM. + * @vdd_reg: Optional regulator supplying the chip + * @vdd_io: Optional digital power supply for IO + * @gpio: Optional reset_n gpio + * @mii_bus: Pointer to MII bus structure + * @lock: Bus access lock callback + * @unlock: Bus access unlock callback + * @rdreg16: 16bit register read callback + * @wrreg16: 16bit register write callback + * @rdfifo: FIFO read callback + * @wrfifo: FIFO write callback + * @start_xmit: start_xmit() implementation callback + * @flush_tx_work: flush_tx_work() implementation callback + * + * The @statelock is used to protect information in the structure which may + * need to be accessed via several sources, such as the network driver layer + * or one of the work queues. + * + * We align the buffers we may use for rx/tx to ensure that if the SPI driver + * wants to DMA map them, it will not have any problems with data the driver + * modifies. + */ +struct ks8851_net { + struct net_device *netdev; + spinlock_t statelock; + + union ks8851_tx_hdr txh ____cacheline_aligned; + u8 rxd[8]; + u8 txd[8]; + + u32 msg_enable ____cacheline_aligned; + u16 tx_space; + u8 fid; + + u16 rc_ier; + u16 rc_rxqcr; + u16 rc_ccr; + + struct mii_if_info mii; + struct ks8851_rxctrl rxctrl; + + struct work_struct rxctrl_work; + + struct sk_buff_head txq; + unsigned int queued_len; + + struct eeprom_93cx6 eeprom; + struct regulator *vdd_reg; + struct regulator *vdd_io; + struct gpio_desc *gpio; + struct mii_bus *mii_bus; + + void (*lock)(struct ks8851_net *ks); + void (*unlock)(struct ks8851_net *ks); + unsigned int (*rdreg16)(struct ks8851_net *ks, + unsigned int reg); + void (*wrreg16)(struct ks8851_net *ks, + unsigned int reg, unsigned int val); + void (*rdfifo)(struct ks8851_net *ks, u8 *buff, + unsigned int len); + void (*wrfifo)(struct ks8851_net *ks, + struct sk_buff *txp, bool irq); + netdev_tx_t (*start_xmit)(struct sk_buff *skb, + struct net_device *dev); + void (*flush_tx_work)(struct ks8851_net *ks); +}; + +int ks8851_probe_common(struct net_device *netdev, struct device *dev, + int msg_en); +void ks8851_remove_common(struct device *dev); +int ks8851_suspend(struct device *dev); +int ks8851_resume(struct device *dev); + +static __maybe_unused SIMPLE_DEV_PM_OPS(ks8851_pm_ops, + ks8851_suspend, ks8851_resume); + +/** + * ks8851_done_tx - update and then free skbuff after transmitting + * @ks: The device state + * @txb: The buffer transmitted + */ +static void __maybe_unused ks8851_done_tx(struct ks8851_net *ks, + struct sk_buff *txb) +{ + struct net_device *dev = ks->netdev; + + dev->stats.tx_bytes += txb->len; + dev->stats.tx_packets++; + + dev_kfree_skb(txb); +} + +#endif /* __KS8851_H__ */ diff --git a/drivers/net/ethernet/micrel/ks8851_common.c b/drivers/net/ethernet/micrel/ks8851_common.c new file mode 100644 index 000000000..4afbb40bc --- /dev/null +++ b/drivers/net/ethernet/micrel/ks8851_common.c @@ -0,0 +1,1226 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* drivers/net/ethernet/micrel/ks8851.c + * + * Copyright 2009 Simtec Electronics + * http://www.simtec.co.uk/ + * Ben Dooks + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "ks8851.h" + +/** + * ks8851_lock - register access lock + * @ks: The chip state + * + * Claim chip register access lock + */ +static void ks8851_lock(struct ks8851_net *ks) +{ + ks->lock(ks); +} + +/** + * ks8851_unlock - register access unlock + * @ks: The chip state + * + * Release chip register access lock + */ +static void ks8851_unlock(struct ks8851_net *ks) +{ + ks->unlock(ks); +} + +/** + * ks8851_wrreg16 - write 16bit register value to chip + * @ks: The chip state + * @reg: The register address + * @val: The value to write + * + * Issue a write to put the value @val into the register specified in @reg. + */ +static void ks8851_wrreg16(struct ks8851_net *ks, unsigned int reg, + unsigned int val) +{ + ks->wrreg16(ks, reg, val); +} + +/** + * ks8851_rdreg16 - read 16 bit register from device + * @ks: The chip information + * @reg: The register address + * + * Read a 16bit register from the chip, returning the result + */ +static unsigned int ks8851_rdreg16(struct ks8851_net *ks, + unsigned int reg) +{ + return ks->rdreg16(ks, reg); +} + +/** + * ks8851_soft_reset - issue one of the soft reset to the device + * @ks: The device state. + * @op: The bit(s) to set in the GRR + * + * Issue the relevant soft-reset command to the device's GRR register + * specified by @op. + * + * Note, the delays are in there as a caution to ensure that the reset + * has time to take effect and then complete. Since the datasheet does + * not currently specify the exact sequence, we have chosen something + * that seems to work with our device. + */ +static void ks8851_soft_reset(struct ks8851_net *ks, unsigned op) +{ + ks8851_wrreg16(ks, KS_GRR, op); + mdelay(1); /* wait a short time to effect reset */ + ks8851_wrreg16(ks, KS_GRR, 0); + mdelay(1); /* wait for condition to clear */ +} + +/** + * ks8851_set_powermode - set power mode of the device + * @ks: The device state + * @pwrmode: The power mode value to write to KS_PMECR. + * + * Change the power mode of the chip. + */ +static void ks8851_set_powermode(struct ks8851_net *ks, unsigned pwrmode) +{ + unsigned pmecr; + + netif_dbg(ks, hw, ks->netdev, "setting power mode %d\n", pwrmode); + + pmecr = ks8851_rdreg16(ks, KS_PMECR); + pmecr &= ~PMECR_PM_MASK; + pmecr |= pwrmode; + + ks8851_wrreg16(ks, KS_PMECR, pmecr); +} + +/** + * ks8851_write_mac_addr - write mac address to device registers + * @dev: The network device + * + * Update the KS8851 MAC address registers from the address in @dev. + * + * This call assumes that the chip is not running, so there is no need to + * shutdown the RXQ process whilst setting this. +*/ +static int ks8851_write_mac_addr(struct net_device *dev) +{ + struct ks8851_net *ks = netdev_priv(dev); + u16 val; + int i; + + ks8851_lock(ks); + + /* + * Wake up chip in case it was powered off when stopped; otherwise, + * the first write to the MAC address does not take effect. + */ + ks8851_set_powermode(ks, PMECR_PM_NORMAL); + + for (i = 0; i < ETH_ALEN; i += 2) { + val = (dev->dev_addr[i] << 8) | dev->dev_addr[i + 1]; + ks8851_wrreg16(ks, KS_MAR(i), val); + } + + if (!netif_running(dev)) + ks8851_set_powermode(ks, PMECR_PM_SOFTDOWN); + + ks8851_unlock(ks); + + return 0; +} + +/** + * ks8851_read_mac_addr - read mac address from device registers + * @dev: The network device + * + * Update our copy of the KS8851 MAC address from the registers of @dev. +*/ +static void ks8851_read_mac_addr(struct net_device *dev) +{ + struct ks8851_net *ks = netdev_priv(dev); + u8 addr[ETH_ALEN]; + u16 reg; + int i; + + ks8851_lock(ks); + + for (i = 0; i < ETH_ALEN; i += 2) { + reg = ks8851_rdreg16(ks, KS_MAR(i)); + addr[i] = reg >> 8; + addr[i + 1] = reg & 0xff; + } + eth_hw_addr_set(dev, addr); + + ks8851_unlock(ks); +} + +/** + * ks8851_init_mac - initialise the mac address + * @ks: The device structure + * @np: The device node pointer + * + * Get or create the initial mac address for the device and then set that + * into the station address register. A mac address supplied in the device + * tree takes precedence. Otherwise, if there is an EEPROM present, then + * we try that. If no valid mac address is found we use eth_random_addr() + * to create a new one. + */ +static void ks8851_init_mac(struct ks8851_net *ks, struct device_node *np) +{ + struct net_device *dev = ks->netdev; + int ret; + + ret = of_get_ethdev_address(np, dev); + if (!ret) { + ks8851_write_mac_addr(dev); + return; + } + + if (ks->rc_ccr & CCR_EEPROM) { + ks8851_read_mac_addr(dev); + if (is_valid_ether_addr(dev->dev_addr)) + return; + + netdev_err(ks->netdev, "invalid mac address read %pM\n", + dev->dev_addr); + } + + eth_hw_addr_random(dev); + ks8851_write_mac_addr(dev); +} + +/** + * ks8851_rx_pkts - receive packets from the host + * @ks: The device information. + * @rxq: Queue of packets received in this function. + * + * This is called from the IRQ work queue when the system detects that there + * are packets in the receive queue. Find out how many packets there are and + * read them from the FIFO. + */ +static void ks8851_rx_pkts(struct ks8851_net *ks, struct sk_buff_head *rxq) +{ + struct sk_buff *skb; + unsigned rxfc; + unsigned rxlen; + unsigned rxstat; + u8 *rxpkt; + + rxfc = (ks8851_rdreg16(ks, KS_RXFCTR) >> 8) & 0xff; + + netif_dbg(ks, rx_status, ks->netdev, + "%s: %d packets\n", __func__, rxfc); + + /* Currently we're issuing a read per packet, but we could possibly + * improve the code by issuing a single read, getting the receive + * header, allocating the packet and then reading the packet data + * out in one go. + * + * This form of operation would require us to hold the SPI bus' + * chipselect low during the entie transaction to avoid any + * reset to the data stream coming from the chip. + */ + + for (; rxfc != 0; rxfc--) { + rxstat = ks8851_rdreg16(ks, KS_RXFHSR); + rxlen = ks8851_rdreg16(ks, KS_RXFHBCR) & RXFHBCR_CNT_MASK; + + netif_dbg(ks, rx_status, ks->netdev, + "rx: stat 0x%04x, len 0x%04x\n", rxstat, rxlen); + + /* the length of the packet includes the 32bit CRC */ + + /* set dma read address */ + ks8851_wrreg16(ks, KS_RXFDPR, RXFDPR_RXFPAI | 0x00); + + /* start DMA access */ + ks8851_wrreg16(ks, KS_RXQCR, ks->rc_rxqcr | RXQCR_SDA); + + if (rxlen > 4) { + unsigned int rxalign; + + rxlen -= 4; + rxalign = ALIGN(rxlen, 4); + skb = netdev_alloc_skb_ip_align(ks->netdev, rxalign); + if (skb) { + + /* 4 bytes of status header + 4 bytes of + * garbage: we put them before ethernet + * header, so that they are copied, + * but ignored. + */ + + rxpkt = skb_put(skb, rxlen) - 8; + + ks->rdfifo(ks, rxpkt, rxalign + 8); + + netif_dbg(ks, pktdata, ks->netdev, + "pkt %12ph\n", &rxpkt[4]); + + skb->protocol = eth_type_trans(skb, ks->netdev); + __skb_queue_tail(rxq, skb); + + ks->netdev->stats.rx_packets++; + ks->netdev->stats.rx_bytes += rxlen; + } + } + + /* end DMA access and dequeue packet */ + ks8851_wrreg16(ks, KS_RXQCR, ks->rc_rxqcr | RXQCR_RRXEF); + } +} + +/** + * ks8851_irq - IRQ handler for dealing with interrupt requests + * @irq: IRQ number + * @_ks: cookie + * + * This handler is invoked when the IRQ line asserts to find out what happened. + * As we cannot allow ourselves to sleep in HARDIRQ context, this handler runs + * in thread context. + * + * Read the interrupt status, work out what needs to be done and then clear + * any of the interrupts that are not needed. + */ +static irqreturn_t ks8851_irq(int irq, void *_ks) +{ + struct ks8851_net *ks = _ks; + struct sk_buff_head rxq; + unsigned int status; + struct sk_buff *skb; + + ks8851_lock(ks); + + status = ks8851_rdreg16(ks, KS_ISR); + ks8851_wrreg16(ks, KS_ISR, status); + + netif_dbg(ks, intr, ks->netdev, + "%s: status 0x%04x\n", __func__, status); + + if (status & IRQ_LDI) { + u16 pmecr = ks8851_rdreg16(ks, KS_PMECR); + pmecr &= ~PMECR_WKEVT_MASK; + ks8851_wrreg16(ks, KS_PMECR, pmecr | PMECR_WKEVT_LINK); + } + + if (status & IRQ_TXI) { + unsigned short tx_space = ks8851_rdreg16(ks, KS_TXMIR); + + netif_dbg(ks, intr, ks->netdev, + "%s: txspace %d\n", __func__, tx_space); + + spin_lock_bh(&ks->statelock); + ks->tx_space = tx_space; + if (netif_queue_stopped(ks->netdev)) + netif_wake_queue(ks->netdev); + spin_unlock_bh(&ks->statelock); + } + + if (status & IRQ_SPIBEI) { + netdev_err(ks->netdev, "%s: spi bus error\n", __func__); + } + + if (status & IRQ_RXI) { + /* the datasheet says to disable the rx interrupt during + * packet read-out, however we're masking the interrupt + * from the device so do not bother masking just the RX + * from the device. */ + + __skb_queue_head_init(&rxq); + ks8851_rx_pkts(ks, &rxq); + } + + /* if something stopped the rx process, probably due to wanting + * to change the rx settings, then do something about restarting + * it. */ + if (status & IRQ_RXPSI) { + struct ks8851_rxctrl *rxc = &ks->rxctrl; + + /* update the multicast hash table */ + ks8851_wrreg16(ks, KS_MAHTR0, rxc->mchash[0]); + ks8851_wrreg16(ks, KS_MAHTR1, rxc->mchash[1]); + ks8851_wrreg16(ks, KS_MAHTR2, rxc->mchash[2]); + ks8851_wrreg16(ks, KS_MAHTR3, rxc->mchash[3]); + + ks8851_wrreg16(ks, KS_RXCR2, rxc->rxcr2); + ks8851_wrreg16(ks, KS_RXCR1, rxc->rxcr1); + } + + ks8851_unlock(ks); + + if (status & IRQ_LCI) + mii_check_link(&ks->mii); + + if (status & IRQ_RXI) { + local_bh_disable(); + while ((skb = __skb_dequeue(&rxq))) + netif_rx(skb); + local_bh_enable(); + } + + return IRQ_HANDLED; +} + +/** + * ks8851_flush_tx_work - flush outstanding TX work + * @ks: The device state + */ +static void ks8851_flush_tx_work(struct ks8851_net *ks) +{ + if (ks->flush_tx_work) + ks->flush_tx_work(ks); +} + +/** + * ks8851_net_open - open network device + * @dev: The network device being opened. + * + * Called when the network device is marked active, such as a user executing + * 'ifconfig up' on the device. + */ +static int ks8851_net_open(struct net_device *dev) +{ + struct ks8851_net *ks = netdev_priv(dev); + int ret; + + ret = request_threaded_irq(dev->irq, NULL, ks8851_irq, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + dev->name, ks); + if (ret < 0) { + netdev_err(dev, "failed to get irq\n"); + return ret; + } + + /* lock the card, even if we may not actually be doing anything + * else at the moment */ + ks8851_lock(ks); + + netif_dbg(ks, ifup, ks->netdev, "opening\n"); + + /* bring chip out of any power saving mode it was in */ + ks8851_set_powermode(ks, PMECR_PM_NORMAL); + + /* issue a soft reset to the RX/TX QMU to put it into a known + * state. */ + ks8851_soft_reset(ks, GRR_QMU); + + /* setup transmission parameters */ + + ks8851_wrreg16(ks, KS_TXCR, (TXCR_TXE | /* enable transmit process */ + TXCR_TXPE | /* pad to min length */ + TXCR_TXCRC | /* add CRC */ + TXCR_TXFCE)); /* enable flow control */ + + /* auto-increment tx data, reset tx pointer */ + ks8851_wrreg16(ks, KS_TXFDPR, TXFDPR_TXFPAI); + + /* setup receiver control */ + + ks8851_wrreg16(ks, KS_RXCR1, (RXCR1_RXPAFMA | /* from mac filter */ + RXCR1_RXFCE | /* enable flow control */ + RXCR1_RXBE | /* broadcast enable */ + RXCR1_RXUE | /* unicast enable */ + RXCR1_RXE)); /* enable rx block */ + + /* transfer entire frames out in one go */ + ks8851_wrreg16(ks, KS_RXCR2, RXCR2_SRDBL_FRAME); + + /* set receive counter timeouts */ + ks8851_wrreg16(ks, KS_RXDTTR, 1000); /* 1ms after first frame to IRQ */ + ks8851_wrreg16(ks, KS_RXDBCTR, 4096); /* >4Kbytes in buffer to IRQ */ + ks8851_wrreg16(ks, KS_RXFCTR, 10); /* 10 frames to IRQ */ + + ks->rc_rxqcr = (RXQCR_RXFCTE | /* IRQ on frame count exceeded */ + RXQCR_RXDBCTE | /* IRQ on byte count exceeded */ + RXQCR_RXDTTE); /* IRQ on time exceeded */ + + ks8851_wrreg16(ks, KS_RXQCR, ks->rc_rxqcr); + + /* clear then enable interrupts */ + ks8851_wrreg16(ks, KS_ISR, ks->rc_ier); + ks8851_wrreg16(ks, KS_IER, ks->rc_ier); + + ks->queued_len = 0; + ks->tx_space = ks8851_rdreg16(ks, KS_TXMIR); + netif_start_queue(ks->netdev); + + netif_dbg(ks, ifup, ks->netdev, "network device up\n"); + + ks8851_unlock(ks); + mii_check_link(&ks->mii); + return 0; +} + +/** + * ks8851_net_stop - close network device + * @dev: The device being closed. + * + * Called to close down a network device which has been active. Cancel any + * work, shutdown the RX and TX process and then place the chip into a low + * power state whilst it is not being used. + */ +static int ks8851_net_stop(struct net_device *dev) +{ + struct ks8851_net *ks = netdev_priv(dev); + + netif_info(ks, ifdown, dev, "shutting down\n"); + + netif_stop_queue(dev); + + ks8851_lock(ks); + /* turn off the IRQs and ack any outstanding */ + ks8851_wrreg16(ks, KS_IER, 0x0000); + ks8851_wrreg16(ks, KS_ISR, 0xffff); + ks8851_unlock(ks); + + /* stop any outstanding work */ + ks8851_flush_tx_work(ks); + flush_work(&ks->rxctrl_work); + + ks8851_lock(ks); + /* shutdown RX process */ + ks8851_wrreg16(ks, KS_RXCR1, 0x0000); + + /* shutdown TX process */ + ks8851_wrreg16(ks, KS_TXCR, 0x0000); + + /* set powermode to soft power down to save power */ + ks8851_set_powermode(ks, PMECR_PM_SOFTDOWN); + ks8851_unlock(ks); + + /* ensure any queued tx buffers are dumped */ + while (!skb_queue_empty(&ks->txq)) { + struct sk_buff *txb = skb_dequeue(&ks->txq); + + netif_dbg(ks, ifdown, ks->netdev, + "%s: freeing txb %p\n", __func__, txb); + + dev_kfree_skb(txb); + } + + free_irq(dev->irq, ks); + + return 0; +} + +/** + * ks8851_start_xmit - transmit packet + * @skb: The buffer to transmit + * @dev: The device used to transmit the packet. + * + * Called by the network layer to transmit the @skb. Queue the packet for + * the device and schedule the necessary work to transmit the packet when + * it is free. + * + * We do this to firstly avoid sleeping with the network device locked, + * and secondly so we can round up more than one packet to transmit which + * means we can try and avoid generating too many transmit done interrupts. + */ +static netdev_tx_t ks8851_start_xmit(struct sk_buff *skb, + struct net_device *dev) +{ + struct ks8851_net *ks = netdev_priv(dev); + + return ks->start_xmit(skb, dev); +} + +/** + * ks8851_rxctrl_work - work handler to change rx mode + * @work: The work structure this belongs to. + * + * Lock the device and issue the necessary changes to the receive mode from + * the network device layer. This is done so that we can do this without + * having to sleep whilst holding the network device lock. + * + * Since the recommendation from Micrel is that the RXQ is shutdown whilst the + * receive parameters are programmed, we issue a write to disable the RXQ and + * then wait for the interrupt handler to be triggered once the RXQ shutdown is + * complete. The interrupt handler then writes the new values into the chip. + */ +static void ks8851_rxctrl_work(struct work_struct *work) +{ + struct ks8851_net *ks = container_of(work, struct ks8851_net, rxctrl_work); + + ks8851_lock(ks); + + /* need to shutdown RXQ before modifying filter parameters */ + ks8851_wrreg16(ks, KS_RXCR1, 0x00); + + ks8851_unlock(ks); +} + +static void ks8851_set_rx_mode(struct net_device *dev) +{ + struct ks8851_net *ks = netdev_priv(dev); + struct ks8851_rxctrl rxctrl; + + memset(&rxctrl, 0, sizeof(rxctrl)); + + if (dev->flags & IFF_PROMISC) { + /* interface to receive everything */ + + rxctrl.rxcr1 = RXCR1_RXAE | RXCR1_RXINVF; + } else if (dev->flags & IFF_ALLMULTI) { + /* accept all multicast packets */ + + rxctrl.rxcr1 = (RXCR1_RXME | RXCR1_RXAE | + RXCR1_RXPAFMA | RXCR1_RXMAFMA); + } else if (dev->flags & IFF_MULTICAST && !netdev_mc_empty(dev)) { + struct netdev_hw_addr *ha; + u32 crc; + + /* accept some multicast */ + + netdev_for_each_mc_addr(ha, dev) { + crc = ether_crc(ETH_ALEN, ha->addr); + crc >>= (32 - 6); /* get top six bits */ + + rxctrl.mchash[crc >> 4] |= (1 << (crc & 0xf)); + } + + rxctrl.rxcr1 = RXCR1_RXME | RXCR1_RXPAFMA; + } else { + /* just accept broadcast / unicast */ + rxctrl.rxcr1 = RXCR1_RXPAFMA; + } + + rxctrl.rxcr1 |= (RXCR1_RXUE | /* unicast enable */ + RXCR1_RXBE | /* broadcast enable */ + RXCR1_RXE | /* RX process enable */ + RXCR1_RXFCE); /* enable flow control */ + + rxctrl.rxcr2 |= RXCR2_SRDBL_FRAME; + + /* schedule work to do the actual set of the data if needed */ + + spin_lock_bh(&ks->statelock); + + if (memcmp(&rxctrl, &ks->rxctrl, sizeof(rxctrl)) != 0) { + memcpy(&ks->rxctrl, &rxctrl, sizeof(ks->rxctrl)); + schedule_work(&ks->rxctrl_work); + } + + spin_unlock_bh(&ks->statelock); +} + +static int ks8851_set_mac_address(struct net_device *dev, void *addr) +{ + struct sockaddr *sa = addr; + + if (netif_running(dev)) + return -EBUSY; + + if (!is_valid_ether_addr(sa->sa_data)) + return -EADDRNOTAVAIL; + + eth_hw_addr_set(dev, sa->sa_data); + return ks8851_write_mac_addr(dev); +} + +static int ks8851_net_ioctl(struct net_device *dev, struct ifreq *req, int cmd) +{ + struct ks8851_net *ks = netdev_priv(dev); + + if (!netif_running(dev)) + return -EINVAL; + + return generic_mii_ioctl(&ks->mii, if_mii(req), cmd, NULL); +} + +static const struct net_device_ops ks8851_netdev_ops = { + .ndo_open = ks8851_net_open, + .ndo_stop = ks8851_net_stop, + .ndo_eth_ioctl = ks8851_net_ioctl, + .ndo_start_xmit = ks8851_start_xmit, + .ndo_set_mac_address = ks8851_set_mac_address, + .ndo_set_rx_mode = ks8851_set_rx_mode, + .ndo_validate_addr = eth_validate_addr, +}; + +/* ethtool support */ + +static void ks8851_get_drvinfo(struct net_device *dev, + struct ethtool_drvinfo *di) +{ + strscpy(di->driver, "KS8851", sizeof(di->driver)); + strscpy(di->version, "1.00", sizeof(di->version)); + strscpy(di->bus_info, dev_name(dev->dev.parent), sizeof(di->bus_info)); +} + +static u32 ks8851_get_msglevel(struct net_device *dev) +{ + struct ks8851_net *ks = netdev_priv(dev); + return ks->msg_enable; +} + +static void ks8851_set_msglevel(struct net_device *dev, u32 to) +{ + struct ks8851_net *ks = netdev_priv(dev); + ks->msg_enable = to; +} + +static int ks8851_get_link_ksettings(struct net_device *dev, + struct ethtool_link_ksettings *cmd) +{ + struct ks8851_net *ks = netdev_priv(dev); + + mii_ethtool_get_link_ksettings(&ks->mii, cmd); + + return 0; +} + +static int ks8851_set_link_ksettings(struct net_device *dev, + const struct ethtool_link_ksettings *cmd) +{ + struct ks8851_net *ks = netdev_priv(dev); + return mii_ethtool_set_link_ksettings(&ks->mii, cmd); +} + +static u32 ks8851_get_link(struct net_device *dev) +{ + struct ks8851_net *ks = netdev_priv(dev); + return mii_link_ok(&ks->mii); +} + +static int ks8851_nway_reset(struct net_device *dev) +{ + struct ks8851_net *ks = netdev_priv(dev); + return mii_nway_restart(&ks->mii); +} + +/* EEPROM support */ + +static void ks8851_eeprom_regread(struct eeprom_93cx6 *ee) +{ + struct ks8851_net *ks = ee->data; + unsigned val; + + val = ks8851_rdreg16(ks, KS_EEPCR); + + ee->reg_data_out = (val & EEPCR_EESB) ? 1 : 0; + ee->reg_data_clock = (val & EEPCR_EESCK) ? 1 : 0; + ee->reg_chip_select = (val & EEPCR_EECS) ? 1 : 0; +} + +static void ks8851_eeprom_regwrite(struct eeprom_93cx6 *ee) +{ + struct ks8851_net *ks = ee->data; + unsigned val = EEPCR_EESA; /* default - eeprom access on */ + + if (ee->drive_data) + val |= EEPCR_EESRWA; + if (ee->reg_data_in) + val |= EEPCR_EEDO; + if (ee->reg_data_clock) + val |= EEPCR_EESCK; + if (ee->reg_chip_select) + val |= EEPCR_EECS; + + ks8851_wrreg16(ks, KS_EEPCR, val); +} + +/** + * ks8851_eeprom_claim - claim device EEPROM and activate the interface + * @ks: The network device state. + * + * Check for the presence of an EEPROM, and then activate software access + * to the device. + */ +static int ks8851_eeprom_claim(struct ks8851_net *ks) +{ + /* start with clock low, cs high */ + ks8851_wrreg16(ks, KS_EEPCR, EEPCR_EESA | EEPCR_EECS); + return 0; +} + +/** + * ks8851_eeprom_release - release the EEPROM interface + * @ks: The device state + * + * Release the software access to the device EEPROM + */ +static void ks8851_eeprom_release(struct ks8851_net *ks) +{ + unsigned val = ks8851_rdreg16(ks, KS_EEPCR); + + ks8851_wrreg16(ks, KS_EEPCR, val & ~EEPCR_EESA); +} + +#define KS_EEPROM_MAGIC (0x00008851) + +static int ks8851_set_eeprom(struct net_device *dev, + struct ethtool_eeprom *ee, u8 *data) +{ + struct ks8851_net *ks = netdev_priv(dev); + int offset = ee->offset; + int len = ee->len; + u16 tmp; + + /* currently only support byte writing */ + if (len != 1) + return -EINVAL; + + if (ee->magic != KS_EEPROM_MAGIC) + return -EINVAL; + + if (!(ks->rc_ccr & CCR_EEPROM)) + return -ENOENT; + + ks8851_lock(ks); + + ks8851_eeprom_claim(ks); + + eeprom_93cx6_wren(&ks->eeprom, true); + + /* ethtool currently only supports writing bytes, which means + * we have to read/modify/write our 16bit EEPROMs */ + + eeprom_93cx6_read(&ks->eeprom, offset/2, &tmp); + + if (offset & 1) { + tmp &= 0xff; + tmp |= *data << 8; + } else { + tmp &= 0xff00; + tmp |= *data; + } + + eeprom_93cx6_write(&ks->eeprom, offset/2, tmp); + eeprom_93cx6_wren(&ks->eeprom, false); + + ks8851_eeprom_release(ks); + ks8851_unlock(ks); + + return 0; +} + +static int ks8851_get_eeprom(struct net_device *dev, + struct ethtool_eeprom *ee, u8 *data) +{ + struct ks8851_net *ks = netdev_priv(dev); + int offset = ee->offset; + int len = ee->len; + + /* must be 2 byte aligned */ + if (len & 1 || offset & 1) + return -EINVAL; + + if (!(ks->rc_ccr & CCR_EEPROM)) + return -ENOENT; + + ks8851_lock(ks); + + ks8851_eeprom_claim(ks); + + ee->magic = KS_EEPROM_MAGIC; + + eeprom_93cx6_multiread(&ks->eeprom, offset/2, (__le16 *)data, len/2); + ks8851_eeprom_release(ks); + ks8851_unlock(ks); + + return 0; +} + +static int ks8851_get_eeprom_len(struct net_device *dev) +{ + struct ks8851_net *ks = netdev_priv(dev); + + /* currently, we assume it is an 93C46 attached, so return 128 */ + return ks->rc_ccr & CCR_EEPROM ? 128 : 0; +} + +static const struct ethtool_ops ks8851_ethtool_ops = { + .get_drvinfo = ks8851_get_drvinfo, + .get_msglevel = ks8851_get_msglevel, + .set_msglevel = ks8851_set_msglevel, + .get_link = ks8851_get_link, + .nway_reset = ks8851_nway_reset, + .get_eeprom_len = ks8851_get_eeprom_len, + .get_eeprom = ks8851_get_eeprom, + .set_eeprom = ks8851_set_eeprom, + .get_link_ksettings = ks8851_get_link_ksettings, + .set_link_ksettings = ks8851_set_link_ksettings, +}; + +/* MII interface controls */ + +/** + * ks8851_phy_reg - convert MII register into a KS8851 register + * @reg: MII register number. + * + * Return the KS8851 register number for the corresponding MII PHY register + * if possible. Return zero if the MII register has no direct mapping to the + * KS8851 register set. + */ +static int ks8851_phy_reg(int reg) +{ + switch (reg) { + case MII_BMCR: + return KS_P1MBCR; + case MII_BMSR: + return KS_P1MBSR; + case MII_PHYSID1: + return KS_PHY1ILR; + case MII_PHYSID2: + return KS_PHY1IHR; + case MII_ADVERTISE: + return KS_P1ANAR; + case MII_LPA: + return KS_P1ANLPR; + } + + return -EOPNOTSUPP; +} + +static int ks8851_phy_read_common(struct net_device *dev, int phy_addr, int reg) +{ + struct ks8851_net *ks = netdev_priv(dev); + int result; + int ksreg; + + ksreg = ks8851_phy_reg(reg); + if (ksreg < 0) + return ksreg; + + ks8851_lock(ks); + result = ks8851_rdreg16(ks, ksreg); + ks8851_unlock(ks); + + return result; +} + +/** + * ks8851_phy_read - MII interface PHY register read. + * @dev: The network device the PHY is on. + * @phy_addr: Address of PHY (ignored as we only have one) + * @reg: The register to read. + * + * This call reads data from the PHY register specified in @reg. Since the + * device does not support all the MII registers, the non-existent values + * are always returned as zero. + * + * We return zero for unsupported registers as the MII code does not check + * the value returned for any error status, and simply returns it to the + * caller. The mii-tool that the driver was tested with takes any -ve error + * as real PHY capabilities, thus displaying incorrect data to the user. + */ +static int ks8851_phy_read(struct net_device *dev, int phy_addr, int reg) +{ + int ret; + + ret = ks8851_phy_read_common(dev, phy_addr, reg); + if (ret < 0) + return 0x0; /* no error return allowed, so use zero */ + + return ret; +} + +static void ks8851_phy_write(struct net_device *dev, + int phy, int reg, int value) +{ + struct ks8851_net *ks = netdev_priv(dev); + int ksreg; + + ksreg = ks8851_phy_reg(reg); + if (ksreg >= 0) { + ks8851_lock(ks); + ks8851_wrreg16(ks, ksreg, value); + ks8851_unlock(ks); + } +} + +static int ks8851_mdio_read(struct mii_bus *bus, int phy_id, int reg) +{ + struct ks8851_net *ks = bus->priv; + + if (phy_id != 0) + return -EOPNOTSUPP; + + /* KS8851 PHY ID registers are swapped in HW, swap them back. */ + if (reg == MII_PHYSID1) + reg = MII_PHYSID2; + else if (reg == MII_PHYSID2) + reg = MII_PHYSID1; + + return ks8851_phy_read_common(ks->netdev, phy_id, reg); +} + +static int ks8851_mdio_write(struct mii_bus *bus, int phy_id, int reg, u16 val) +{ + struct ks8851_net *ks = bus->priv; + + ks8851_phy_write(ks->netdev, phy_id, reg, val); + return 0; +} + +/** + * ks8851_read_selftest - read the selftest memory info. + * @ks: The device state + * + * Read and check the TX/RX memory selftest information. + */ +static void ks8851_read_selftest(struct ks8851_net *ks) +{ + unsigned both_done = MBIR_TXMBF | MBIR_RXMBF; + unsigned rd; + + rd = ks8851_rdreg16(ks, KS_MBIR); + + if ((rd & both_done) != both_done) { + netdev_warn(ks->netdev, "Memory selftest not finished\n"); + return; + } + + if (rd & MBIR_TXMBFA) + netdev_err(ks->netdev, "TX memory selftest fail\n"); + + if (rd & MBIR_RXMBFA) + netdev_err(ks->netdev, "RX memory selftest fail\n"); +} + +/* driver bus management functions */ + +#ifdef CONFIG_PM_SLEEP + +int ks8851_suspend(struct device *dev) +{ + struct ks8851_net *ks = dev_get_drvdata(dev); + struct net_device *netdev = ks->netdev; + + if (netif_running(netdev)) { + netif_device_detach(netdev); + ks8851_net_stop(netdev); + } + + return 0; +} +EXPORT_SYMBOL_GPL(ks8851_suspend); + +int ks8851_resume(struct device *dev) +{ + struct ks8851_net *ks = dev_get_drvdata(dev); + struct net_device *netdev = ks->netdev; + + if (netif_running(netdev)) { + ks8851_net_open(netdev); + netif_device_attach(netdev); + } + + return 0; +} +EXPORT_SYMBOL_GPL(ks8851_resume); +#endif + +static int ks8851_register_mdiobus(struct ks8851_net *ks, struct device *dev) +{ + struct mii_bus *mii_bus; + int ret; + + mii_bus = mdiobus_alloc(); + if (!mii_bus) + return -ENOMEM; + + mii_bus->name = "ks8851_eth_mii"; + mii_bus->read = ks8851_mdio_read; + mii_bus->write = ks8851_mdio_write; + mii_bus->priv = ks; + mii_bus->parent = dev; + mii_bus->phy_mask = ~((u32)BIT(0)); + snprintf(mii_bus->id, MII_BUS_ID_SIZE, "%s", dev_name(dev)); + + ret = mdiobus_register(mii_bus); + if (ret) + goto err_mdiobus_register; + + ks->mii_bus = mii_bus; + + return 0; + +err_mdiobus_register: + mdiobus_free(mii_bus); + return ret; +} + +static void ks8851_unregister_mdiobus(struct ks8851_net *ks) +{ + mdiobus_unregister(ks->mii_bus); + mdiobus_free(ks->mii_bus); +} + +int ks8851_probe_common(struct net_device *netdev, struct device *dev, + int msg_en) +{ + struct ks8851_net *ks = netdev_priv(netdev); + unsigned cider; + int ret; + + ks->netdev = netdev; + + ks->gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); + ret = PTR_ERR_OR_ZERO(ks->gpio); + if (ret) { + if (ret != -EPROBE_DEFER) + dev_err(dev, "reset gpio request failed: %d\n", ret); + return ret; + } + + ret = gpiod_set_consumer_name(ks->gpio, "ks8851_rst_n"); + if (ret) { + dev_err(dev, "failed to set reset gpio name: %d\n", ret); + return ret; + } + + ks->vdd_io = devm_regulator_get(dev, "vdd-io"); + if (IS_ERR(ks->vdd_io)) { + ret = PTR_ERR(ks->vdd_io); + goto err_reg_io; + } + + ret = regulator_enable(ks->vdd_io); + if (ret) { + dev_err(dev, "regulator vdd_io enable fail: %d\n", ret); + goto err_reg_io; + } + + ks->vdd_reg = devm_regulator_get(dev, "vdd"); + if (IS_ERR(ks->vdd_reg)) { + ret = PTR_ERR(ks->vdd_reg); + goto err_reg; + } + + ret = regulator_enable(ks->vdd_reg); + if (ret) { + dev_err(dev, "regulator vdd enable fail: %d\n", ret); + goto err_reg; + } + + if (ks->gpio) { + usleep_range(10000, 11000); + gpiod_set_value_cansleep(ks->gpio, 0); + } + + spin_lock_init(&ks->statelock); + + INIT_WORK(&ks->rxctrl_work, ks8851_rxctrl_work); + + SET_NETDEV_DEV(netdev, dev); + + /* setup EEPROM state */ + ks->eeprom.data = ks; + ks->eeprom.width = PCI_EEPROM_WIDTH_93C46; + ks->eeprom.register_read = ks8851_eeprom_regread; + ks->eeprom.register_write = ks8851_eeprom_regwrite; + + /* setup mii state */ + ks->mii.dev = netdev; + ks->mii.phy_id = 1; + ks->mii.phy_id_mask = 1; + ks->mii.reg_num_mask = 0xf; + ks->mii.mdio_read = ks8851_phy_read; + ks->mii.mdio_write = ks8851_phy_write; + + dev_info(dev, "message enable is %d\n", msg_en); + + ret = ks8851_register_mdiobus(ks, dev); + if (ret) + goto err_mdio; + + /* set the default message enable */ + ks->msg_enable = netif_msg_init(msg_en, NETIF_MSG_DRV | + NETIF_MSG_PROBE | + NETIF_MSG_LINK); + + skb_queue_head_init(&ks->txq); + + netdev->ethtool_ops = &ks8851_ethtool_ops; + + dev_set_drvdata(dev, ks); + + netif_carrier_off(ks->netdev); + netdev->if_port = IF_PORT_100BASET; + netdev->netdev_ops = &ks8851_netdev_ops; + + /* issue a global soft reset to reset the device. */ + ks8851_soft_reset(ks, GRR_GSR); + + /* simple check for a valid chip being connected to the bus */ + cider = ks8851_rdreg16(ks, KS_CIDER); + if ((cider & ~CIDER_REV_MASK) != CIDER_ID) { + dev_err(dev, "failed to read device ID\n"); + ret = -ENODEV; + goto err_id; + } + + /* cache the contents of the CCR register for EEPROM, etc. */ + ks->rc_ccr = ks8851_rdreg16(ks, KS_CCR); + + ks8851_read_selftest(ks); + ks8851_init_mac(ks, dev->of_node); + + ret = register_netdev(netdev); + if (ret) { + dev_err(dev, "failed to register network device\n"); + goto err_id; + } + + netdev_info(netdev, "revision %d, MAC %pM, IRQ %d, %s EEPROM\n", + CIDER_REV_GET(cider), netdev->dev_addr, netdev->irq, + ks->rc_ccr & CCR_EEPROM ? "has" : "no"); + + return 0; + +err_id: + ks8851_unregister_mdiobus(ks); +err_mdio: + if (ks->gpio) + gpiod_set_value_cansleep(ks->gpio, 1); + regulator_disable(ks->vdd_reg); +err_reg: + regulator_disable(ks->vdd_io); +err_reg_io: + return ret; +} +EXPORT_SYMBOL_GPL(ks8851_probe_common); + +void ks8851_remove_common(struct device *dev) +{ + struct ks8851_net *priv = dev_get_drvdata(dev); + + ks8851_unregister_mdiobus(priv); + + if (netif_msg_drv(priv)) + dev_info(dev, "remove\n"); + + unregister_netdev(priv->netdev); + if (priv->gpio) + gpiod_set_value_cansleep(priv->gpio, 1); + regulator_disable(priv->vdd_reg); + regulator_disable(priv->vdd_io); +} +EXPORT_SYMBOL_GPL(ks8851_remove_common); + +MODULE_DESCRIPTION("KS8851 Network driver"); +MODULE_AUTHOR("Ben Dooks "); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/ethernet/micrel/ks8851_par.c b/drivers/net/ethernet/micrel/ks8851_par.c new file mode 100644 index 000000000..9f1c33f6d --- /dev/null +++ b/drivers/net/ethernet/micrel/ks8851_par.c @@ -0,0 +1,343 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* drivers/net/ethernet/micrel/ks8851.c + * + * Copyright 2009 Simtec Electronics + * http://www.simtec.co.uk/ + * Ben Dooks + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "ks8851.h" + +static int msg_enable; + +#define BE3 0x8000 /* Byte Enable 3 */ +#define BE2 0x4000 /* Byte Enable 2 */ +#define BE1 0x2000 /* Byte Enable 1 */ +#define BE0 0x1000 /* Byte Enable 0 */ + +/** + * struct ks8851_net_par - KS8851 Parallel driver private data + * @ks8851: KS8851 driver common private data + * @lock: Lock to ensure that the device is not accessed when busy. + * @hw_addr : start address of data register. + * @hw_addr_cmd : start address of command register. + * @cmd_reg_cache : command register cached. + * + * The @lock ensures that the chip is protected when certain operations are + * in progress. When the read or write packet transfer is in progress, most + * of the chip registers are not accessible until the transfer is finished + * and the DMA has been de-asserted. + */ +struct ks8851_net_par { + struct ks8851_net ks8851; + spinlock_t lock; + void __iomem *hw_addr; + void __iomem *hw_addr_cmd; + u16 cmd_reg_cache; +}; + +#define to_ks8851_par(ks) container_of((ks), struct ks8851_net_par, ks8851) + +/** + * ks8851_lock_par - register access lock + * @ks: The chip state + * + * Claim chip register access lock + */ +static void ks8851_lock_par(struct ks8851_net *ks) +{ + struct ks8851_net_par *ksp = to_ks8851_par(ks); + + spin_lock_bh(&ksp->lock); +} + +/** + * ks8851_unlock_par - register access unlock + * @ks: The chip state + * + * Release chip register access lock + */ +static void ks8851_unlock_par(struct ks8851_net *ks) +{ + struct ks8851_net_par *ksp = to_ks8851_par(ks); + + spin_unlock_bh(&ksp->lock); +} + +/** + * ks_check_endian - Check whether endianness of the bus is correct + * @ks : The chip information + * + * The KS8851-16MLL EESK pin allows selecting the endianness of the 16bit + * bus. To maintain optimum performance, the bus endianness should be set + * such that it matches the endianness of the CPU. + */ +static int ks_check_endian(struct ks8851_net *ks) +{ + struct ks8851_net_par *ksp = to_ks8851_par(ks); + u16 cider; + + /* + * Read CIDER register first, however read it the "wrong" way around. + * If the endian strap on the KS8851-16MLL in incorrect and the chip + * is operating in different endianness than the CPU, then the meaning + * of BE[3:0] byte-enable bits is also swapped such that: + * BE[3,2,1,0] becomes BE[1,0,3,2] + * + * Luckily for us, the byte-enable bits are the top four MSbits of + * the address register and the CIDER register is at offset 0xc0. + * Hence, by reading address 0xc0c0, which is not impacted by endian + * swapping, we assert either BE[3:2] or BE[1:0] while reading the + * CIDER register. + * + * If the bus configuration is correct, reading 0xc0c0 asserts + * BE[3:2] and this read returns 0x0000, because to read register + * with bottom two LSbits of address set to 0, BE[1:0] must be + * asserted. + * + * If the bus configuration is NOT correct, reading 0xc0c0 asserts + * BE[1:0] and this read returns non-zero 0x8872 value. + */ + iowrite16(BE3 | BE2 | KS_CIDER, ksp->hw_addr_cmd); + cider = ioread16(ksp->hw_addr); + if (!cider) + return 0; + + netdev_err(ks->netdev, "incorrect EESK endian strap setting\n"); + + return -EINVAL; +} + +/** + * ks8851_wrreg16_par - write 16bit register value to chip + * @ks: The chip state + * @reg: The register address + * @val: The value to write + * + * Issue a write to put the value @val into the register specified in @reg. + */ +static void ks8851_wrreg16_par(struct ks8851_net *ks, unsigned int reg, + unsigned int val) +{ + struct ks8851_net_par *ksp = to_ks8851_par(ks); + + ksp->cmd_reg_cache = (u16)reg | ((BE1 | BE0) << (reg & 0x02)); + iowrite16(ksp->cmd_reg_cache, ksp->hw_addr_cmd); + iowrite16(val, ksp->hw_addr); +} + +/** + * ks8851_rdreg16_par - read 16 bit register from chip + * @ks: The chip information + * @reg: The register address + * + * Read a 16bit register from the chip, returning the result + */ +static unsigned int ks8851_rdreg16_par(struct ks8851_net *ks, unsigned int reg) +{ + struct ks8851_net_par *ksp = to_ks8851_par(ks); + + ksp->cmd_reg_cache = (u16)reg | ((BE1 | BE0) << (reg & 0x02)); + iowrite16(ksp->cmd_reg_cache, ksp->hw_addr_cmd); + return ioread16(ksp->hw_addr); +} + +/** + * ks8851_rdfifo_par - read data from the receive fifo + * @ks: The device state. + * @buff: The buffer address + * @len: The length of the data to read + * + * Issue an RXQ FIFO read command and read the @len amount of data from + * the FIFO into the buffer specified by @buff. + */ +static void ks8851_rdfifo_par(struct ks8851_net *ks, u8 *buff, unsigned int len) +{ + struct ks8851_net_par *ksp = to_ks8851_par(ks); + + netif_dbg(ks, rx_status, ks->netdev, + "%s: %d@%p\n", __func__, len, buff); + + ioread16_rep(ksp->hw_addr, (u16 *)buff + 1, len / 2); +} + +/** + * ks8851_wrfifo_par - write packet to TX FIFO + * @ks: The device state. + * @txp: The sk_buff to transmit. + * @irq: IRQ on completion of the packet. + * + * Send the @txp to the chip. This means creating the relevant packet header + * specifying the length of the packet and the other information the chip + * needs, such as IRQ on completion. Send the header and the packet data to + * the device. + */ +static void ks8851_wrfifo_par(struct ks8851_net *ks, struct sk_buff *txp, + bool irq) +{ + struct ks8851_net_par *ksp = to_ks8851_par(ks); + unsigned int len = ALIGN(txp->len, 4); + unsigned int fid = 0; + + netif_dbg(ks, tx_queued, ks->netdev, "%s: skb %p, %d@%p, irq %d\n", + __func__, txp, txp->len, txp->data, irq); + + fid = ks->fid++; + fid &= TXFR_TXFID_MASK; + + if (irq) + fid |= TXFR_TXIC; /* irq on completion */ + + iowrite16(fid, ksp->hw_addr); + iowrite16(txp->len, ksp->hw_addr); + + iowrite16_rep(ksp->hw_addr, txp->data, len / 2); +} + +static unsigned int ks8851_rdreg16_par_txqcr(struct ks8851_net *ks) +{ + return ks8851_rdreg16_par(ks, KS_TXQCR); +} + +/** + * ks8851_start_xmit_par - transmit packet + * @skb: The buffer to transmit + * @dev: The device used to transmit the packet. + * + * Called by the network layer to transmit the @skb. Queue the packet for + * the device and schedule the necessary work to transmit the packet when + * it is free. + * + * We do this to firstly avoid sleeping with the network device locked, + * and secondly so we can round up more than one packet to transmit which + * means we can try and avoid generating too many transmit done interrupts. + */ +static netdev_tx_t ks8851_start_xmit_par(struct sk_buff *skb, + struct net_device *dev) +{ + struct ks8851_net *ks = netdev_priv(dev); + netdev_tx_t ret = NETDEV_TX_OK; + unsigned int txqcr; + u16 txmir; + int err; + + netif_dbg(ks, tx_queued, ks->netdev, + "%s: skb %p, %d@%p\n", __func__, skb, skb->len, skb->data); + + ks8851_lock_par(ks); + + txmir = ks8851_rdreg16_par(ks, KS_TXMIR) & 0x1fff; + + if (likely(txmir >= skb->len + 12)) { + ks8851_wrreg16_par(ks, KS_RXQCR, ks->rc_rxqcr | RXQCR_SDA); + ks8851_wrfifo_par(ks, skb, false); + ks8851_wrreg16_par(ks, KS_RXQCR, ks->rc_rxqcr); + ks8851_wrreg16_par(ks, KS_TXQCR, TXQCR_METFE); + + err = readx_poll_timeout_atomic(ks8851_rdreg16_par_txqcr, ks, + txqcr, !(txqcr & TXQCR_METFE), + 5, 1000000); + if (err) + ret = NETDEV_TX_BUSY; + + ks8851_done_tx(ks, skb); + } else { + ret = NETDEV_TX_BUSY; + } + + ks8851_unlock_par(ks); + + return ret; +} + +static int ks8851_probe_par(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct ks8851_net_par *ksp; + struct net_device *netdev; + struct ks8851_net *ks; + int ret; + + netdev = devm_alloc_etherdev(dev, sizeof(struct ks8851_net_par)); + if (!netdev) + return -ENOMEM; + + ks = netdev_priv(netdev); + + ks->lock = ks8851_lock_par; + ks->unlock = ks8851_unlock_par; + ks->rdreg16 = ks8851_rdreg16_par; + ks->wrreg16 = ks8851_wrreg16_par; + ks->rdfifo = ks8851_rdfifo_par; + ks->wrfifo = ks8851_wrfifo_par; + ks->start_xmit = ks8851_start_xmit_par; + +#define STD_IRQ (IRQ_LCI | /* Link Change */ \ + IRQ_RXI | /* RX done */ \ + IRQ_RXPSI) /* RX process stop */ + ks->rc_ier = STD_IRQ; + + ksp = to_ks8851_par(ks); + spin_lock_init(&ksp->lock); + + ksp->hw_addr = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(ksp->hw_addr)) + return PTR_ERR(ksp->hw_addr); + + ksp->hw_addr_cmd = devm_platform_ioremap_resource(pdev, 1); + if (IS_ERR(ksp->hw_addr_cmd)) + return PTR_ERR(ksp->hw_addr_cmd); + + ret = ks_check_endian(ks); + if (ret) + return ret; + + netdev->irq = platform_get_irq(pdev, 0); + if (netdev->irq < 0) + return netdev->irq; + + return ks8851_probe_common(netdev, dev, msg_enable); +} + +static void ks8851_remove_par(struct platform_device *pdev) +{ + ks8851_remove_common(&pdev->dev); +} + +static const struct of_device_id ks8851_match_table[] = { + { .compatible = "micrel,ks8851-mll" }, + { } +}; +MODULE_DEVICE_TABLE(of, ks8851_match_table); + +static struct platform_driver ks8851_driver = { + .driver = { + .name = "ks8851", + .of_match_table = ks8851_match_table, + .pm = &ks8851_pm_ops, + }, + .probe = ks8851_probe_par, + .remove = ks8851_remove_par, +}; +module_platform_driver(ks8851_driver); + +MODULE_DESCRIPTION("KS8851 Network driver"); +MODULE_AUTHOR("Ben Dooks "); +MODULE_LICENSE("GPL"); + +module_param_named(message, msg_enable, int, 0); +MODULE_PARM_DESC(message, "Message verbosity level (0=none, 31=all)"); diff --git a/drivers/net/ethernet/micrel/ks8851_spi.c b/drivers/net/ethernet/micrel/ks8851_spi.c new file mode 100644 index 000000000..b9e685202 --- /dev/null +++ b/drivers/net/ethernet/micrel/ks8851_spi.c @@ -0,0 +1,478 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* drivers/net/ethernet/micrel/ks8851.c + * + * Copyright 2009 Simtec Electronics + * http://www.simtec.co.uk/ + * Ben Dooks + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "ks8851.h" + +static int msg_enable; + +/** + * struct ks8851_net_spi - KS8851 SPI driver private data + * @lock: Lock to ensure that the device is not accessed when busy. + * @tx_work: Work queue for tx packets + * @ks8851: KS8851 driver common private data + * @spidev: The spi device we're bound to. + * @spi_msg1: pre-setup SPI transfer with one message, @spi_xfer1. + * @spi_msg2: pre-setup SPI transfer with two messages, @spi_xfer2. + * @spi_xfer1: @spi_msg1 SPI transfer structure + * @spi_xfer2: @spi_msg2 SPI transfer structure + * + * The @lock ensures that the chip is protected when certain operations are + * in progress. When the read or write packet transfer is in progress, most + * of the chip registers are not accessible until the transfer is finished and + * the DMA has been de-asserted. + */ +struct ks8851_net_spi { + struct ks8851_net ks8851; + struct mutex lock; + struct work_struct tx_work; + struct spi_device *spidev; + struct spi_message spi_msg1; + struct spi_message spi_msg2; + struct spi_transfer spi_xfer1; + struct spi_transfer spi_xfer2[2]; +}; + +#define to_ks8851_spi(ks) container_of((ks), struct ks8851_net_spi, ks8851) + +/* SPI frame opcodes */ +#define KS_SPIOP_RD 0x00 +#define KS_SPIOP_WR 0x40 +#define KS_SPIOP_RXFIFO 0x80 +#define KS_SPIOP_TXFIFO 0xC0 + +/* shift for byte-enable data */ +#define BYTE_EN(_x) ((_x) << 2) + +/* turn register number and byte-enable mask into data for start of packet */ +#define MK_OP(_byteen, _reg) \ + (BYTE_EN(_byteen) | (_reg) << (8 + 2) | (_reg) >> 6) + +/** + * ks8851_lock_spi - register access lock + * @ks: The chip state + * + * Claim chip register access lock + */ +static void ks8851_lock_spi(struct ks8851_net *ks) +{ + struct ks8851_net_spi *kss = to_ks8851_spi(ks); + + mutex_lock(&kss->lock); +} + +/** + * ks8851_unlock_spi - register access unlock + * @ks: The chip state + * + * Release chip register access lock + */ +static void ks8851_unlock_spi(struct ks8851_net *ks) +{ + struct ks8851_net_spi *kss = to_ks8851_spi(ks); + + mutex_unlock(&kss->lock); +} + +/* SPI register read/write calls. + * + * All these calls issue SPI transactions to access the chip's registers. They + * all require that the necessary lock is held to prevent accesses when the + * chip is busy transferring packet data (RX/TX FIFO accesses). + */ + +/** + * ks8851_wrreg16_spi - write 16bit register value to chip via SPI + * @ks: The chip state + * @reg: The register address + * @val: The value to write + * + * Issue a write to put the value @val into the register specified in @reg. + */ +static void ks8851_wrreg16_spi(struct ks8851_net *ks, unsigned int reg, + unsigned int val) +{ + struct ks8851_net_spi *kss = to_ks8851_spi(ks); + struct spi_transfer *xfer = &kss->spi_xfer1; + struct spi_message *msg = &kss->spi_msg1; + __le16 txb[2]; + int ret; + + txb[0] = cpu_to_le16(MK_OP(reg & 2 ? 0xC : 0x03, reg) | KS_SPIOP_WR); + txb[1] = cpu_to_le16(val); + + xfer->tx_buf = txb; + xfer->rx_buf = NULL; + xfer->len = 4; + + ret = spi_sync(kss->spidev, msg); + if (ret < 0) + netdev_err(ks->netdev, "spi_sync() failed\n"); +} + +/** + * ks8851_rdreg - issue read register command and return the data + * @ks: The device state + * @op: The register address and byte enables in message format. + * @rxb: The RX buffer to return the result into + * @rxl: The length of data expected. + * + * This is the low level read call that issues the necessary spi message(s) + * to read data from the register specified in @op. + */ +static void ks8851_rdreg(struct ks8851_net *ks, unsigned int op, + u8 *rxb, unsigned int rxl) +{ + struct ks8851_net_spi *kss = to_ks8851_spi(ks); + struct spi_transfer *xfer; + struct spi_message *msg; + __le16 *txb = (__le16 *)ks->txd; + u8 *trx = ks->rxd; + int ret; + + txb[0] = cpu_to_le16(op | KS_SPIOP_RD); + + if (kss->spidev->controller->flags & SPI_CONTROLLER_HALF_DUPLEX) { + msg = &kss->spi_msg2; + xfer = kss->spi_xfer2; + + xfer->tx_buf = txb; + xfer->rx_buf = NULL; + xfer->len = 2; + + xfer++; + xfer->tx_buf = NULL; + xfer->rx_buf = trx; + xfer->len = rxl; + } else { + msg = &kss->spi_msg1; + xfer = &kss->spi_xfer1; + + xfer->tx_buf = txb; + xfer->rx_buf = trx; + xfer->len = rxl + 2; + } + + ret = spi_sync(kss->spidev, msg); + if (ret < 0) + netdev_err(ks->netdev, "read: spi_sync() failed\n"); + else if (kss->spidev->controller->flags & SPI_CONTROLLER_HALF_DUPLEX) + memcpy(rxb, trx, rxl); + else + memcpy(rxb, trx + 2, rxl); +} + +/** + * ks8851_rdreg16_spi - read 16 bit register from device via SPI + * @ks: The chip information + * @reg: The register address + * + * Read a 16bit register from the chip, returning the result + */ +static unsigned int ks8851_rdreg16_spi(struct ks8851_net *ks, unsigned int reg) +{ + __le16 rx = 0; + + ks8851_rdreg(ks, MK_OP(reg & 2 ? 0xC : 0x3, reg), (u8 *)&rx, 2); + return le16_to_cpu(rx); +} + +/** + * ks8851_rdfifo_spi - read data from the receive fifo via SPI + * @ks: The device state. + * @buff: The buffer address + * @len: The length of the data to read + * + * Issue an RXQ FIFO read command and read the @len amount of data from + * the FIFO into the buffer specified by @buff. + */ +static void ks8851_rdfifo_spi(struct ks8851_net *ks, u8 *buff, unsigned int len) +{ + struct ks8851_net_spi *kss = to_ks8851_spi(ks); + struct spi_transfer *xfer = kss->spi_xfer2; + struct spi_message *msg = &kss->spi_msg2; + u8 txb[1]; + int ret; + + netif_dbg(ks, rx_status, ks->netdev, + "%s: %d@%p\n", __func__, len, buff); + + /* set the operation we're issuing */ + txb[0] = KS_SPIOP_RXFIFO; + + xfer->tx_buf = txb; + xfer->rx_buf = NULL; + xfer->len = 1; + + xfer++; + xfer->rx_buf = buff; + xfer->tx_buf = NULL; + xfer->len = len; + + ret = spi_sync(kss->spidev, msg); + if (ret < 0) + netdev_err(ks->netdev, "%s: spi_sync() failed\n", __func__); +} + +/** + * ks8851_wrfifo_spi - write packet to TX FIFO via SPI + * @ks: The device state. + * @txp: The sk_buff to transmit. + * @irq: IRQ on completion of the packet. + * + * Send the @txp to the chip. This means creating the relevant packet header + * specifying the length of the packet and the other information the chip + * needs, such as IRQ on completion. Send the header and the packet data to + * the device. + */ +static void ks8851_wrfifo_spi(struct ks8851_net *ks, struct sk_buff *txp, + bool irq) +{ + struct ks8851_net_spi *kss = to_ks8851_spi(ks); + struct spi_transfer *xfer = kss->spi_xfer2; + struct spi_message *msg = &kss->spi_msg2; + unsigned int fid = 0; + int ret; + + netif_dbg(ks, tx_queued, ks->netdev, "%s: skb %p, %d@%p, irq %d\n", + __func__, txp, txp->len, txp->data, irq); + + fid = ks->fid++; + fid &= TXFR_TXFID_MASK; + + if (irq) + fid |= TXFR_TXIC; /* irq on completion */ + + /* start header at txb[1] to align txw entries */ + ks->txh.txb[1] = KS_SPIOP_TXFIFO; + ks->txh.txw[1] = cpu_to_le16(fid); + ks->txh.txw[2] = cpu_to_le16(txp->len); + + xfer->tx_buf = &ks->txh.txb[1]; + xfer->rx_buf = NULL; + xfer->len = 5; + + xfer++; + xfer->tx_buf = txp->data; + xfer->rx_buf = NULL; + xfer->len = ALIGN(txp->len, 4); + + ret = spi_sync(kss->spidev, msg); + if (ret < 0) + netdev_err(ks->netdev, "%s: spi_sync() failed\n", __func__); +} + +/** + * calc_txlen - calculate size of message to send packet + * @len: Length of data + * + * Returns the size of the TXFIFO message needed to send + * this packet. + */ +static unsigned int calc_txlen(unsigned int len) +{ + return ALIGN(len + 4, 4); +} + +/** + * ks8851_tx_work - process tx packet(s) + * @work: The work structure what was scheduled. + * + * This is called when a number of packets have been scheduled for + * transmission and need to be sent to the device. + */ +static void ks8851_tx_work(struct work_struct *work) +{ + unsigned int dequeued_len = 0; + struct ks8851_net_spi *kss; + unsigned short tx_space; + struct ks8851_net *ks; + struct sk_buff *txb; + bool last; + + kss = container_of(work, struct ks8851_net_spi, tx_work); + ks = &kss->ks8851; + last = skb_queue_empty(&ks->txq); + + ks8851_lock_spi(ks); + + while (!last) { + txb = skb_dequeue(&ks->txq); + last = skb_queue_empty(&ks->txq); + + if (txb) { + dequeued_len += calc_txlen(txb->len); + + ks8851_wrreg16_spi(ks, KS_RXQCR, + ks->rc_rxqcr | RXQCR_SDA); + ks8851_wrfifo_spi(ks, txb, last); + ks8851_wrreg16_spi(ks, KS_RXQCR, ks->rc_rxqcr); + ks8851_wrreg16_spi(ks, KS_TXQCR, TXQCR_METFE); + + ks8851_done_tx(ks, txb); + } + } + + tx_space = ks8851_rdreg16_spi(ks, KS_TXMIR); + + spin_lock_bh(&ks->statelock); + ks->queued_len -= dequeued_len; + ks->tx_space = tx_space; + spin_unlock_bh(&ks->statelock); + + ks8851_unlock_spi(ks); +} + +/** + * ks8851_flush_tx_work_spi - flush outstanding TX work + * @ks: The device state + */ +static void ks8851_flush_tx_work_spi(struct ks8851_net *ks) +{ + struct ks8851_net_spi *kss = to_ks8851_spi(ks); + + flush_work(&kss->tx_work); +} + +/** + * ks8851_start_xmit_spi - transmit packet using SPI + * @skb: The buffer to transmit + * @dev: The device used to transmit the packet. + * + * Called by the network layer to transmit the @skb. Queue the packet for + * the device and schedule the necessary work to transmit the packet when + * it is free. + * + * We do this to firstly avoid sleeping with the network device locked, + * and secondly so we can round up more than one packet to transmit which + * means we can try and avoid generating too many transmit done interrupts. + */ +static netdev_tx_t ks8851_start_xmit_spi(struct sk_buff *skb, + struct net_device *dev) +{ + unsigned int needed = calc_txlen(skb->len); + struct ks8851_net *ks = netdev_priv(dev); + netdev_tx_t ret = NETDEV_TX_OK; + struct ks8851_net_spi *kss; + + kss = to_ks8851_spi(ks); + + netif_dbg(ks, tx_queued, ks->netdev, + "%s: skb %p, %d@%p\n", __func__, skb, skb->len, skb->data); + + spin_lock(&ks->statelock); + + if (ks->queued_len + needed > ks->tx_space) { + netif_stop_queue(dev); + ret = NETDEV_TX_BUSY; + } else { + ks->queued_len += needed; + skb_queue_tail(&ks->txq, skb); + } + + spin_unlock(&ks->statelock); + if (ret == NETDEV_TX_OK) + schedule_work(&kss->tx_work); + + return ret; +} + +static int ks8851_probe_spi(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct ks8851_net_spi *kss; + struct net_device *netdev; + struct ks8851_net *ks; + + netdev = devm_alloc_etherdev(dev, sizeof(struct ks8851_net_spi)); + if (!netdev) + return -ENOMEM; + + spi->bits_per_word = 8; + + kss = netdev_priv(netdev); + ks = &kss->ks8851; + + ks->lock = ks8851_lock_spi; + ks->unlock = ks8851_unlock_spi; + ks->rdreg16 = ks8851_rdreg16_spi; + ks->wrreg16 = ks8851_wrreg16_spi; + ks->rdfifo = ks8851_rdfifo_spi; + ks->wrfifo = ks8851_wrfifo_spi; + ks->start_xmit = ks8851_start_xmit_spi; + ks->flush_tx_work = ks8851_flush_tx_work_spi; + +#define STD_IRQ (IRQ_LCI | /* Link Change */ \ + IRQ_TXI | /* TX done */ \ + IRQ_RXI | /* RX done */ \ + IRQ_SPIBEI | /* SPI bus error */ \ + IRQ_TXPSI | /* TX process stop */ \ + IRQ_RXPSI) /* RX process stop */ + ks->rc_ier = STD_IRQ; + + kss->spidev = spi; + mutex_init(&kss->lock); + INIT_WORK(&kss->tx_work, ks8851_tx_work); + + /* initialise pre-made spi transfer messages */ + spi_message_init(&kss->spi_msg1); + spi_message_add_tail(&kss->spi_xfer1, &kss->spi_msg1); + + spi_message_init(&kss->spi_msg2); + spi_message_add_tail(&kss->spi_xfer2[0], &kss->spi_msg2); + spi_message_add_tail(&kss->spi_xfer2[1], &kss->spi_msg2); + + netdev->irq = spi->irq; + + return ks8851_probe_common(netdev, dev, msg_enable); +} + +static void ks8851_remove_spi(struct spi_device *spi) +{ + ks8851_remove_common(&spi->dev); +} + +static const struct of_device_id ks8851_match_table[] = { + { .compatible = "micrel,ks8851" }, + { } +}; +MODULE_DEVICE_TABLE(of, ks8851_match_table); + +static struct spi_driver ks8851_driver = { + .driver = { + .name = "ks8851", + .of_match_table = ks8851_match_table, + .pm = &ks8851_pm_ops, + }, + .probe = ks8851_probe_spi, + .remove = ks8851_remove_spi, +}; +module_spi_driver(ks8851_driver); + +MODULE_DESCRIPTION("KS8851 Network driver"); +MODULE_AUTHOR("Ben Dooks "); +MODULE_LICENSE("GPL"); + +module_param_named(message, msg_enable, int, 0); +MODULE_PARM_DESC(message, "Message verbosity level (0=none, 31=all)"); +MODULE_ALIAS("spi:ks8851"); diff --git a/drivers/net/ethernet/micrel/ksz884x.c b/drivers/net/ethernet/micrel/ksz884x.c new file mode 100644 index 000000000..773fdd215 --- /dev/null +++ b/drivers/net/ethernet/micrel/ksz884x.c @@ -0,0 +1,6877 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * drivers/net/ethernet/micrel/ksx884x.c - Micrel KSZ8841/2 PCI Ethernet driver + * + * Copyright (c) 2009-2010 Micrel, Inc. + * Tristram Ha + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* DMA Registers */ + +#define KS_DMA_TX_CTRL 0x0000 +#define DMA_TX_ENABLE 0x00000001 +#define DMA_TX_CRC_ENABLE 0x00000002 +#define DMA_TX_PAD_ENABLE 0x00000004 +#define DMA_TX_LOOPBACK 0x00000100 +#define DMA_TX_FLOW_ENABLE 0x00000200 +#define DMA_TX_CSUM_IP 0x00010000 +#define DMA_TX_CSUM_TCP 0x00020000 +#define DMA_TX_CSUM_UDP 0x00040000 +#define DMA_TX_BURST_SIZE 0x3F000000 + +#define KS_DMA_RX_CTRL 0x0004 +#define DMA_RX_ENABLE 0x00000001 +#define KS884X_DMA_RX_MULTICAST 0x00000002 +#define DMA_RX_PROMISCUOUS 0x00000004 +#define DMA_RX_ERROR 0x00000008 +#define DMA_RX_UNICAST 0x00000010 +#define DMA_RX_ALL_MULTICAST 0x00000020 +#define DMA_RX_BROADCAST 0x00000040 +#define DMA_RX_FLOW_ENABLE 0x00000200 +#define DMA_RX_CSUM_IP 0x00010000 +#define DMA_RX_CSUM_TCP 0x00020000 +#define DMA_RX_CSUM_UDP 0x00040000 +#define DMA_RX_BURST_SIZE 0x3F000000 + +#define DMA_BURST_SHIFT 24 +#define DMA_BURST_DEFAULT 8 + +#define KS_DMA_TX_START 0x0008 +#define KS_DMA_RX_START 0x000C +#define DMA_START 0x00000001 + +#define KS_DMA_TX_ADDR 0x0010 +#define KS_DMA_RX_ADDR 0x0014 + +#define DMA_ADDR_LIST_MASK 0xFFFFFFFC +#define DMA_ADDR_LIST_SHIFT 2 + +/* MTR0 */ +#define KS884X_MULTICAST_0_OFFSET 0x0020 +#define KS884X_MULTICAST_1_OFFSET 0x0021 +#define KS884X_MULTICAST_2_OFFSET 0x0022 +#define KS884x_MULTICAST_3_OFFSET 0x0023 +/* MTR1 */ +#define KS884X_MULTICAST_4_OFFSET 0x0024 +#define KS884X_MULTICAST_5_OFFSET 0x0025 +#define KS884X_MULTICAST_6_OFFSET 0x0026 +#define KS884X_MULTICAST_7_OFFSET 0x0027 + +/* Interrupt Registers */ + +/* INTEN */ +#define KS884X_INTERRUPTS_ENABLE 0x0028 +/* INTST */ +#define KS884X_INTERRUPTS_STATUS 0x002C + +#define KS884X_INT_RX_STOPPED 0x02000000 +#define KS884X_INT_TX_STOPPED 0x04000000 +#define KS884X_INT_RX_OVERRUN 0x08000000 +#define KS884X_INT_TX_EMPTY 0x10000000 +#define KS884X_INT_RX 0x20000000 +#define KS884X_INT_TX 0x40000000 +#define KS884X_INT_PHY 0x80000000 + +#define KS884X_INT_RX_MASK \ + (KS884X_INT_RX | KS884X_INT_RX_OVERRUN) +#define KS884X_INT_TX_MASK \ + (KS884X_INT_TX | KS884X_INT_TX_EMPTY) +#define KS884X_INT_MASK (KS884X_INT_RX | KS884X_INT_TX | KS884X_INT_PHY) + +/* MAC Additional Station Address */ + +/* MAAL0 */ +#define KS_ADD_ADDR_0_LO 0x0080 +/* MAAH0 */ +#define KS_ADD_ADDR_0_HI 0x0084 +/* MAAL1 */ +#define KS_ADD_ADDR_1_LO 0x0088 +/* MAAH1 */ +#define KS_ADD_ADDR_1_HI 0x008C +/* MAAL2 */ +#define KS_ADD_ADDR_2_LO 0x0090 +/* MAAH2 */ +#define KS_ADD_ADDR_2_HI 0x0094 +/* MAAL3 */ +#define KS_ADD_ADDR_3_LO 0x0098 +/* MAAH3 */ +#define KS_ADD_ADDR_3_HI 0x009C +/* MAAL4 */ +#define KS_ADD_ADDR_4_LO 0x00A0 +/* MAAH4 */ +#define KS_ADD_ADDR_4_HI 0x00A4 +/* MAAL5 */ +#define KS_ADD_ADDR_5_LO 0x00A8 +/* MAAH5 */ +#define KS_ADD_ADDR_5_HI 0x00AC +/* MAAL6 */ +#define KS_ADD_ADDR_6_LO 0x00B0 +/* MAAH6 */ +#define KS_ADD_ADDR_6_HI 0x00B4 +/* MAAL7 */ +#define KS_ADD_ADDR_7_LO 0x00B8 +/* MAAH7 */ +#define KS_ADD_ADDR_7_HI 0x00BC +/* MAAL8 */ +#define KS_ADD_ADDR_8_LO 0x00C0 +/* MAAH8 */ +#define KS_ADD_ADDR_8_HI 0x00C4 +/* MAAL9 */ +#define KS_ADD_ADDR_9_LO 0x00C8 +/* MAAH9 */ +#define KS_ADD_ADDR_9_HI 0x00CC +/* MAAL10 */ +#define KS_ADD_ADDR_A_LO 0x00D0 +/* MAAH10 */ +#define KS_ADD_ADDR_A_HI 0x00D4 +/* MAAL11 */ +#define KS_ADD_ADDR_B_LO 0x00D8 +/* MAAH11 */ +#define KS_ADD_ADDR_B_HI 0x00DC +/* MAAL12 */ +#define KS_ADD_ADDR_C_LO 0x00E0 +/* MAAH12 */ +#define KS_ADD_ADDR_C_HI 0x00E4 +/* MAAL13 */ +#define KS_ADD_ADDR_D_LO 0x00E8 +/* MAAH13 */ +#define KS_ADD_ADDR_D_HI 0x00EC +/* MAAL14 */ +#define KS_ADD_ADDR_E_LO 0x00F0 +/* MAAH14 */ +#define KS_ADD_ADDR_E_HI 0x00F4 +/* MAAL15 */ +#define KS_ADD_ADDR_F_LO 0x00F8 +/* MAAH15 */ +#define KS_ADD_ADDR_F_HI 0x00FC + +#define ADD_ADDR_HI_MASK 0x0000FFFF +#define ADD_ADDR_ENABLE 0x80000000 +#define ADD_ADDR_INCR 8 + +/* Miscellaneous Registers */ + +/* MARL */ +#define KS884X_ADDR_0_OFFSET 0x0200 +#define KS884X_ADDR_1_OFFSET 0x0201 +/* MARM */ +#define KS884X_ADDR_2_OFFSET 0x0202 +#define KS884X_ADDR_3_OFFSET 0x0203 +/* MARH */ +#define KS884X_ADDR_4_OFFSET 0x0204 +#define KS884X_ADDR_5_OFFSET 0x0205 + +/* OBCR */ +#define KS884X_BUS_CTRL_OFFSET 0x0210 + +#define BUS_SPEED_125_MHZ 0x0000 +#define BUS_SPEED_62_5_MHZ 0x0001 +#define BUS_SPEED_41_66_MHZ 0x0002 +#define BUS_SPEED_25_MHZ 0x0003 + +/* EEPCR */ +#define KS884X_EEPROM_CTRL_OFFSET 0x0212 + +#define EEPROM_CHIP_SELECT 0x0001 +#define EEPROM_SERIAL_CLOCK 0x0002 +#define EEPROM_DATA_OUT 0x0004 +#define EEPROM_DATA_IN 0x0008 +#define EEPROM_ACCESS_ENABLE 0x0010 + +/* MBIR */ +#define KS884X_MEM_INFO_OFFSET 0x0214 + +#define RX_MEM_TEST_FAILED 0x0008 +#define RX_MEM_TEST_FINISHED 0x0010 +#define TX_MEM_TEST_FAILED 0x0800 +#define TX_MEM_TEST_FINISHED 0x1000 + +/* GCR */ +#define KS884X_GLOBAL_CTRL_OFFSET 0x0216 +#define GLOBAL_SOFTWARE_RESET 0x0001 + +#define KS8841_POWER_MANAGE_OFFSET 0x0218 + +/* WFCR */ +#define KS8841_WOL_CTRL_OFFSET 0x021A +#define KS8841_WOL_MAGIC_ENABLE 0x0080 +#define KS8841_WOL_FRAME3_ENABLE 0x0008 +#define KS8841_WOL_FRAME2_ENABLE 0x0004 +#define KS8841_WOL_FRAME1_ENABLE 0x0002 +#define KS8841_WOL_FRAME0_ENABLE 0x0001 + +/* WF0 */ +#define KS8841_WOL_FRAME_CRC_OFFSET 0x0220 +#define KS8841_WOL_FRAME_BYTE0_OFFSET 0x0224 +#define KS8841_WOL_FRAME_BYTE2_OFFSET 0x0228 + +/* IACR */ +#define KS884X_IACR_P 0x04A0 +#define KS884X_IACR_OFFSET KS884X_IACR_P + +/* IADR1 */ +#define KS884X_IADR1_P 0x04A2 +#define KS884X_IADR2_P 0x04A4 +#define KS884X_IADR3_P 0x04A6 +#define KS884X_IADR4_P 0x04A8 +#define KS884X_IADR5_P 0x04AA + +#define KS884X_ACC_CTRL_SEL_OFFSET KS884X_IACR_P +#define KS884X_ACC_CTRL_INDEX_OFFSET (KS884X_ACC_CTRL_SEL_OFFSET + 1) + +#define KS884X_ACC_DATA_0_OFFSET KS884X_IADR4_P +#define KS884X_ACC_DATA_1_OFFSET (KS884X_ACC_DATA_0_OFFSET + 1) +#define KS884X_ACC_DATA_2_OFFSET KS884X_IADR5_P +#define KS884X_ACC_DATA_3_OFFSET (KS884X_ACC_DATA_2_OFFSET + 1) +#define KS884X_ACC_DATA_4_OFFSET KS884X_IADR2_P +#define KS884X_ACC_DATA_5_OFFSET (KS884X_ACC_DATA_4_OFFSET + 1) +#define KS884X_ACC_DATA_6_OFFSET KS884X_IADR3_P +#define KS884X_ACC_DATA_7_OFFSET (KS884X_ACC_DATA_6_OFFSET + 1) +#define KS884X_ACC_DATA_8_OFFSET KS884X_IADR1_P + +/* P1MBCR */ +#define KS884X_P1MBCR_P 0x04D0 +#define KS884X_P1MBSR_P 0x04D2 +#define KS884X_PHY1ILR_P 0x04D4 +#define KS884X_PHY1IHR_P 0x04D6 +#define KS884X_P1ANAR_P 0x04D8 +#define KS884X_P1ANLPR_P 0x04DA + +/* P2MBCR */ +#define KS884X_P2MBCR_P 0x04E0 +#define KS884X_P2MBSR_P 0x04E2 +#define KS884X_PHY2ILR_P 0x04E4 +#define KS884X_PHY2IHR_P 0x04E6 +#define KS884X_P2ANAR_P 0x04E8 +#define KS884X_P2ANLPR_P 0x04EA + +#define KS884X_PHY_1_CTRL_OFFSET KS884X_P1MBCR_P +#define PHY_CTRL_INTERVAL (KS884X_P2MBCR_P - KS884X_P1MBCR_P) + +#define KS884X_PHY_CTRL_OFFSET 0x00 + +#define KS884X_PHY_STATUS_OFFSET 0x02 + +#define KS884X_PHY_ID_1_OFFSET 0x04 +#define KS884X_PHY_ID_2_OFFSET 0x06 + +#define KS884X_PHY_AUTO_NEG_OFFSET 0x08 + +#define KS884X_PHY_REMOTE_CAP_OFFSET 0x0A + +/* P1VCT */ +#define KS884X_P1VCT_P 0x04F0 +#define KS884X_P1PHYCTRL_P 0x04F2 + +/* P2VCT */ +#define KS884X_P2VCT_P 0x04F4 +#define KS884X_P2PHYCTRL_P 0x04F6 + +#define KS884X_PHY_SPECIAL_OFFSET KS884X_P1VCT_P +#define PHY_SPECIAL_INTERVAL (KS884X_P2VCT_P - KS884X_P1VCT_P) + +#define KS884X_PHY_LINK_MD_OFFSET 0x00 + +#define PHY_START_CABLE_DIAG 0x8000 +#define PHY_CABLE_DIAG_RESULT 0x6000 +#define PHY_CABLE_STAT_NORMAL 0x0000 +#define PHY_CABLE_STAT_OPEN 0x2000 +#define PHY_CABLE_STAT_SHORT 0x4000 +#define PHY_CABLE_STAT_FAILED 0x6000 +#define PHY_CABLE_10M_SHORT 0x1000 +#define PHY_CABLE_FAULT_COUNTER 0x01FF + +#define KS884X_PHY_PHY_CTRL_OFFSET 0x02 + +#define PHY_STAT_REVERSED_POLARITY 0x0020 +#define PHY_STAT_MDIX 0x0010 +#define PHY_FORCE_LINK 0x0008 +#define PHY_POWER_SAVING_DISABLE 0x0004 +#define PHY_REMOTE_LOOPBACK 0x0002 + +/* SIDER */ +#define KS884X_SIDER_P 0x0400 +#define KS884X_CHIP_ID_OFFSET KS884X_SIDER_P +#define KS884X_FAMILY_ID_OFFSET (KS884X_CHIP_ID_OFFSET + 1) + +#define REG_FAMILY_ID 0x88 + +#define REG_CHIP_ID_41 0x8810 +#define REG_CHIP_ID_42 0x8800 + +#define KS884X_CHIP_ID_MASK_41 0xFF10 +#define KS884X_CHIP_ID_MASK 0xFFF0 +#define KS884X_CHIP_ID_SHIFT 4 +#define KS884X_REVISION_MASK 0x000E +#define KS884X_REVISION_SHIFT 1 +#define KS8842_START 0x0001 + +#define CHIP_IP_41_M 0x8810 +#define CHIP_IP_42_M 0x8800 +#define CHIP_IP_61_M 0x8890 +#define CHIP_IP_62_M 0x8880 + +#define CHIP_IP_41_P 0x8850 +#define CHIP_IP_42_P 0x8840 +#define CHIP_IP_61_P 0x88D0 +#define CHIP_IP_62_P 0x88C0 + +/* SGCR1 */ +#define KS8842_SGCR1_P 0x0402 +#define KS8842_SWITCH_CTRL_1_OFFSET KS8842_SGCR1_P + +#define SWITCH_PASS_ALL 0x8000 +#define SWITCH_TX_FLOW_CTRL 0x2000 +#define SWITCH_RX_FLOW_CTRL 0x1000 +#define SWITCH_CHECK_LENGTH 0x0800 +#define SWITCH_AGING_ENABLE 0x0400 +#define SWITCH_FAST_AGING 0x0200 +#define SWITCH_AGGR_BACKOFF 0x0100 +#define SWITCH_PASS_PAUSE 0x0008 +#define SWITCH_LINK_AUTO_AGING 0x0001 + +/* SGCR2 */ +#define KS8842_SGCR2_P 0x0404 +#define KS8842_SWITCH_CTRL_2_OFFSET KS8842_SGCR2_P + +#define SWITCH_VLAN_ENABLE 0x8000 +#define SWITCH_IGMP_SNOOP 0x4000 +#define IPV6_MLD_SNOOP_ENABLE 0x2000 +#define IPV6_MLD_SNOOP_OPTION 0x1000 +#define PRIORITY_SCHEME_SELECT 0x0800 +#define SWITCH_MIRROR_RX_TX 0x0100 +#define UNICAST_VLAN_BOUNDARY 0x0080 +#define MULTICAST_STORM_DISABLE 0x0040 +#define SWITCH_BACK_PRESSURE 0x0020 +#define FAIR_FLOW_CTRL 0x0010 +#define NO_EXC_COLLISION_DROP 0x0008 +#define SWITCH_HUGE_PACKET 0x0004 +#define SWITCH_LEGAL_PACKET 0x0002 +#define SWITCH_BUF_RESERVE 0x0001 + +/* SGCR3 */ +#define KS8842_SGCR3_P 0x0406 +#define KS8842_SWITCH_CTRL_3_OFFSET KS8842_SGCR3_P + +#define BROADCAST_STORM_RATE_LO 0xFF00 +#define SWITCH_REPEATER 0x0080 +#define SWITCH_HALF_DUPLEX 0x0040 +#define SWITCH_FLOW_CTRL 0x0020 +#define SWITCH_10_MBIT 0x0010 +#define SWITCH_REPLACE_NULL_VID 0x0008 +#define BROADCAST_STORM_RATE_HI 0x0007 + +#define BROADCAST_STORM_RATE 0x07FF + +/* SGCR4 */ +#define KS8842_SGCR4_P 0x0408 + +/* SGCR5 */ +#define KS8842_SGCR5_P 0x040A +#define KS8842_SWITCH_CTRL_5_OFFSET KS8842_SGCR5_P + +#define LED_MODE 0x8200 +#define LED_SPEED_DUPLEX_ACT 0x0000 +#define LED_SPEED_DUPLEX_LINK_ACT 0x8000 +#define LED_DUPLEX_10_100 0x0200 + +/* SGCR6 */ +#define KS8842_SGCR6_P 0x0410 +#define KS8842_SWITCH_CTRL_6_OFFSET KS8842_SGCR6_P + +#define KS8842_PRIORITY_MASK 3 +#define KS8842_PRIORITY_SHIFT 2 + +/* SGCR7 */ +#define KS8842_SGCR7_P 0x0412 +#define KS8842_SWITCH_CTRL_7_OFFSET KS8842_SGCR7_P + +#define SWITCH_UNK_DEF_PORT_ENABLE 0x0008 +#define SWITCH_UNK_DEF_PORT_3 0x0004 +#define SWITCH_UNK_DEF_PORT_2 0x0002 +#define SWITCH_UNK_DEF_PORT_1 0x0001 + +/* MACAR1 */ +#define KS8842_MACAR1_P 0x0470 +#define KS8842_MACAR2_P 0x0472 +#define KS8842_MACAR3_P 0x0474 +#define KS8842_MAC_ADDR_1_OFFSET KS8842_MACAR1_P +#define KS8842_MAC_ADDR_0_OFFSET (KS8842_MAC_ADDR_1_OFFSET + 1) +#define KS8842_MAC_ADDR_3_OFFSET KS8842_MACAR2_P +#define KS8842_MAC_ADDR_2_OFFSET (KS8842_MAC_ADDR_3_OFFSET + 1) +#define KS8842_MAC_ADDR_5_OFFSET KS8842_MACAR3_P +#define KS8842_MAC_ADDR_4_OFFSET (KS8842_MAC_ADDR_5_OFFSET + 1) + +/* TOSR1 */ +#define KS8842_TOSR1_P 0x0480 +#define KS8842_TOSR2_P 0x0482 +#define KS8842_TOSR3_P 0x0484 +#define KS8842_TOSR4_P 0x0486 +#define KS8842_TOSR5_P 0x0488 +#define KS8842_TOSR6_P 0x048A +#define KS8842_TOSR7_P 0x0490 +#define KS8842_TOSR8_P 0x0492 +#define KS8842_TOS_1_OFFSET KS8842_TOSR1_P +#define KS8842_TOS_2_OFFSET KS8842_TOSR2_P +#define KS8842_TOS_3_OFFSET KS8842_TOSR3_P +#define KS8842_TOS_4_OFFSET KS8842_TOSR4_P +#define KS8842_TOS_5_OFFSET KS8842_TOSR5_P +#define KS8842_TOS_6_OFFSET KS8842_TOSR6_P + +#define KS8842_TOS_7_OFFSET KS8842_TOSR7_P +#define KS8842_TOS_8_OFFSET KS8842_TOSR8_P + +/* P1CR1 */ +#define KS8842_P1CR1_P 0x0500 +#define KS8842_P1CR2_P 0x0502 +#define KS8842_P1VIDR_P 0x0504 +#define KS8842_P1CR3_P 0x0506 +#define KS8842_P1IRCR_P 0x0508 +#define KS8842_P1ERCR_P 0x050A +#define KS884X_P1SCSLMD_P 0x0510 +#define KS884X_P1CR4_P 0x0512 +#define KS884X_P1SR_P 0x0514 + +/* P2CR1 */ +#define KS8842_P2CR1_P 0x0520 +#define KS8842_P2CR2_P 0x0522 +#define KS8842_P2VIDR_P 0x0524 +#define KS8842_P2CR3_P 0x0526 +#define KS8842_P2IRCR_P 0x0528 +#define KS8842_P2ERCR_P 0x052A +#define KS884X_P2SCSLMD_P 0x0530 +#define KS884X_P2CR4_P 0x0532 +#define KS884X_P2SR_P 0x0534 + +/* P3CR1 */ +#define KS8842_P3CR1_P 0x0540 +#define KS8842_P3CR2_P 0x0542 +#define KS8842_P3VIDR_P 0x0544 +#define KS8842_P3CR3_P 0x0546 +#define KS8842_P3IRCR_P 0x0548 +#define KS8842_P3ERCR_P 0x054A + +#define KS8842_PORT_1_CTRL_1 KS8842_P1CR1_P +#define KS8842_PORT_2_CTRL_1 KS8842_P2CR1_P +#define KS8842_PORT_3_CTRL_1 KS8842_P3CR1_P + +#define PORT_CTRL_ADDR(port, addr) \ + (addr = KS8842_PORT_1_CTRL_1 + (port) * \ + (KS8842_PORT_2_CTRL_1 - KS8842_PORT_1_CTRL_1)) + +#define KS8842_PORT_CTRL_1_OFFSET 0x00 + +#define PORT_BROADCAST_STORM 0x0080 +#define PORT_DIFFSERV_ENABLE 0x0040 +#define PORT_802_1P_ENABLE 0x0020 +#define PORT_BASED_PRIORITY_MASK 0x0018 +#define PORT_BASED_PRIORITY_BASE 0x0003 +#define PORT_BASED_PRIORITY_SHIFT 3 +#define PORT_BASED_PRIORITY_0 0x0000 +#define PORT_BASED_PRIORITY_1 0x0008 +#define PORT_BASED_PRIORITY_2 0x0010 +#define PORT_BASED_PRIORITY_3 0x0018 +#define PORT_INSERT_TAG 0x0004 +#define PORT_REMOVE_TAG 0x0002 +#define PORT_PRIO_QUEUE_ENABLE 0x0001 + +#define KS8842_PORT_CTRL_2_OFFSET 0x02 + +#define PORT_INGRESS_VLAN_FILTER 0x4000 +#define PORT_DISCARD_NON_VID 0x2000 +#define PORT_FORCE_FLOW_CTRL 0x1000 +#define PORT_BACK_PRESSURE 0x0800 +#define PORT_TX_ENABLE 0x0400 +#define PORT_RX_ENABLE 0x0200 +#define PORT_LEARN_DISABLE 0x0100 +#define PORT_MIRROR_SNIFFER 0x0080 +#define PORT_MIRROR_RX 0x0040 +#define PORT_MIRROR_TX 0x0020 +#define PORT_USER_PRIORITY_CEILING 0x0008 +#define PORT_VLAN_MEMBERSHIP 0x0007 + +#define KS8842_PORT_CTRL_VID_OFFSET 0x04 + +#define PORT_DEFAULT_VID 0x0001 + +#define KS8842_PORT_CTRL_3_OFFSET 0x06 + +#define PORT_INGRESS_LIMIT_MODE 0x000C +#define PORT_INGRESS_ALL 0x0000 +#define PORT_INGRESS_UNICAST 0x0004 +#define PORT_INGRESS_MULTICAST 0x0008 +#define PORT_INGRESS_BROADCAST 0x000C +#define PORT_COUNT_IFG 0x0002 +#define PORT_COUNT_PREAMBLE 0x0001 + +#define KS8842_PORT_IN_RATE_OFFSET 0x08 +#define KS8842_PORT_OUT_RATE_OFFSET 0x0A + +#define PORT_PRIORITY_RATE 0x0F +#define PORT_PRIORITY_RATE_SHIFT 4 + +#define KS884X_PORT_LINK_MD 0x10 + +#define PORT_CABLE_10M_SHORT 0x8000 +#define PORT_CABLE_DIAG_RESULT 0x6000 +#define PORT_CABLE_STAT_NORMAL 0x0000 +#define PORT_CABLE_STAT_OPEN 0x2000 +#define PORT_CABLE_STAT_SHORT 0x4000 +#define PORT_CABLE_STAT_FAILED 0x6000 +#define PORT_START_CABLE_DIAG 0x1000 +#define PORT_FORCE_LINK 0x0800 +#define PORT_POWER_SAVING_DISABLE 0x0400 +#define PORT_PHY_REMOTE_LOOPBACK 0x0200 +#define PORT_CABLE_FAULT_COUNTER 0x01FF + +#define KS884X_PORT_CTRL_4_OFFSET 0x12 + +#define PORT_LED_OFF 0x8000 +#define PORT_TX_DISABLE 0x4000 +#define PORT_AUTO_NEG_RESTART 0x2000 +#define PORT_REMOTE_FAULT_DISABLE 0x1000 +#define PORT_POWER_DOWN 0x0800 +#define PORT_AUTO_MDIX_DISABLE 0x0400 +#define PORT_FORCE_MDIX 0x0200 +#define PORT_LOOPBACK 0x0100 +#define PORT_AUTO_NEG_ENABLE 0x0080 +#define PORT_FORCE_100_MBIT 0x0040 +#define PORT_FORCE_FULL_DUPLEX 0x0020 +#define PORT_AUTO_NEG_SYM_PAUSE 0x0010 +#define PORT_AUTO_NEG_100BTX_FD 0x0008 +#define PORT_AUTO_NEG_100BTX 0x0004 +#define PORT_AUTO_NEG_10BT_FD 0x0002 +#define PORT_AUTO_NEG_10BT 0x0001 + +#define KS884X_PORT_STATUS_OFFSET 0x14 + +#define PORT_HP_MDIX 0x8000 +#define PORT_REVERSED_POLARITY 0x2000 +#define PORT_RX_FLOW_CTRL 0x0800 +#define PORT_TX_FLOW_CTRL 0x1000 +#define PORT_STATUS_SPEED_100MBIT 0x0400 +#define PORT_STATUS_FULL_DUPLEX 0x0200 +#define PORT_REMOTE_FAULT 0x0100 +#define PORT_MDIX_STATUS 0x0080 +#define PORT_AUTO_NEG_COMPLETE 0x0040 +#define PORT_STATUS_LINK_GOOD 0x0020 +#define PORT_REMOTE_SYM_PAUSE 0x0010 +#define PORT_REMOTE_100BTX_FD 0x0008 +#define PORT_REMOTE_100BTX 0x0004 +#define PORT_REMOTE_10BT_FD 0x0002 +#define PORT_REMOTE_10BT 0x0001 + +/* +#define STATIC_MAC_TABLE_ADDR 00-0000FFFF-FFFFFFFF +#define STATIC_MAC_TABLE_FWD_PORTS 00-00070000-00000000 +#define STATIC_MAC_TABLE_VALID 00-00080000-00000000 +#define STATIC_MAC_TABLE_OVERRIDE 00-00100000-00000000 +#define STATIC_MAC_TABLE_USE_FID 00-00200000-00000000 +#define STATIC_MAC_TABLE_FID 00-03C00000-00000000 +*/ + +#define STATIC_MAC_TABLE_ADDR 0x0000FFFF +#define STATIC_MAC_TABLE_FWD_PORTS 0x00070000 +#define STATIC_MAC_TABLE_VALID 0x00080000 +#define STATIC_MAC_TABLE_OVERRIDE 0x00100000 +#define STATIC_MAC_TABLE_USE_FID 0x00200000 +#define STATIC_MAC_TABLE_FID 0x03C00000 + +#define STATIC_MAC_FWD_PORTS_SHIFT 16 +#define STATIC_MAC_FID_SHIFT 22 + +/* +#define VLAN_TABLE_VID 00-00000000-00000FFF +#define VLAN_TABLE_FID 00-00000000-0000F000 +#define VLAN_TABLE_MEMBERSHIP 00-00000000-00070000 +#define VLAN_TABLE_VALID 00-00000000-00080000 +*/ + +#define VLAN_TABLE_VID 0x00000FFF +#define VLAN_TABLE_FID 0x0000F000 +#define VLAN_TABLE_MEMBERSHIP 0x00070000 +#define VLAN_TABLE_VALID 0x00080000 + +#define VLAN_TABLE_FID_SHIFT 12 +#define VLAN_TABLE_MEMBERSHIP_SHIFT 16 + +/* +#define DYNAMIC_MAC_TABLE_ADDR 00-0000FFFF-FFFFFFFF +#define DYNAMIC_MAC_TABLE_FID 00-000F0000-00000000 +#define DYNAMIC_MAC_TABLE_SRC_PORT 00-00300000-00000000 +#define DYNAMIC_MAC_TABLE_TIMESTAMP 00-00C00000-00000000 +#define DYNAMIC_MAC_TABLE_ENTRIES 03-FF000000-00000000 +#define DYNAMIC_MAC_TABLE_MAC_EMPTY 04-00000000-00000000 +#define DYNAMIC_MAC_TABLE_RESERVED 78-00000000-00000000 +#define DYNAMIC_MAC_TABLE_NOT_READY 80-00000000-00000000 +*/ + +#define DYNAMIC_MAC_TABLE_ADDR 0x0000FFFF +#define DYNAMIC_MAC_TABLE_FID 0x000F0000 +#define DYNAMIC_MAC_TABLE_SRC_PORT 0x00300000 +#define DYNAMIC_MAC_TABLE_TIMESTAMP 0x00C00000 +#define DYNAMIC_MAC_TABLE_ENTRIES 0xFF000000 + +#define DYNAMIC_MAC_TABLE_ENTRIES_H 0x03 +#define DYNAMIC_MAC_TABLE_MAC_EMPTY 0x04 +#define DYNAMIC_MAC_TABLE_RESERVED 0x78 +#define DYNAMIC_MAC_TABLE_NOT_READY 0x80 + +#define DYNAMIC_MAC_FID_SHIFT 16 +#define DYNAMIC_MAC_SRC_PORT_SHIFT 20 +#define DYNAMIC_MAC_TIMESTAMP_SHIFT 22 +#define DYNAMIC_MAC_ENTRIES_SHIFT 24 +#define DYNAMIC_MAC_ENTRIES_H_SHIFT 8 + +/* +#define MIB_COUNTER_VALUE 00-00000000-3FFFFFFF +#define MIB_COUNTER_VALID 00-00000000-40000000 +#define MIB_COUNTER_OVERFLOW 00-00000000-80000000 +*/ + +#define MIB_COUNTER_VALUE 0x3FFFFFFF +#define MIB_COUNTER_VALID 0x40000000 +#define MIB_COUNTER_OVERFLOW 0x80000000 + +#define MIB_PACKET_DROPPED 0x0000FFFF + +#define KS_MIB_PACKET_DROPPED_TX_0 0x100 +#define KS_MIB_PACKET_DROPPED_TX_1 0x101 +#define KS_MIB_PACKET_DROPPED_TX 0x102 +#define KS_MIB_PACKET_DROPPED_RX_0 0x103 +#define KS_MIB_PACKET_DROPPED_RX_1 0x104 +#define KS_MIB_PACKET_DROPPED_RX 0x105 + +/* Change default LED mode. */ +#define SET_DEFAULT_LED LED_SPEED_DUPLEX_ACT + +#define MAC_ADDR_ORDER(i) (ETH_ALEN - 1 - (i)) + +#define MAX_ETHERNET_BODY_SIZE 1500 +#define ETHERNET_HEADER_SIZE (14 + VLAN_HLEN) + +#define MAX_ETHERNET_PACKET_SIZE \ + (MAX_ETHERNET_BODY_SIZE + ETHERNET_HEADER_SIZE) + +#define REGULAR_RX_BUF_SIZE (MAX_ETHERNET_PACKET_SIZE + 4) +#define MAX_RX_BUF_SIZE (1912 + 4) + +#define ADDITIONAL_ENTRIES 16 +#define MAX_MULTICAST_LIST 32 + +#define HW_MULTICAST_SIZE 8 + +#define HW_TO_DEV_PORT(port) (port - 1) + +enum { + media_connected, + media_disconnected +}; + +enum { + OID_COUNTER_UNKOWN, + + OID_COUNTER_FIRST, + + /* total transmit errors */ + OID_COUNTER_XMIT_ERROR, + + /* total receive errors */ + OID_COUNTER_RCV_ERROR, + + OID_COUNTER_LAST +}; + +/* + * Hardware descriptor definitions + */ + +#define DESC_ALIGNMENT 16 +#define BUFFER_ALIGNMENT 8 + +#define NUM_OF_RX_DESC 64 +#define NUM_OF_TX_DESC 64 + +#define KS_DESC_RX_FRAME_LEN 0x000007FF +#define KS_DESC_RX_FRAME_TYPE 0x00008000 +#define KS_DESC_RX_ERROR_CRC 0x00010000 +#define KS_DESC_RX_ERROR_RUNT 0x00020000 +#define KS_DESC_RX_ERROR_TOO_LONG 0x00040000 +#define KS_DESC_RX_ERROR_PHY 0x00080000 +#define KS884X_DESC_RX_PORT_MASK 0x00300000 +#define KS_DESC_RX_MULTICAST 0x01000000 +#define KS_DESC_RX_ERROR 0x02000000 +#define KS_DESC_RX_ERROR_CSUM_UDP 0x04000000 +#define KS_DESC_RX_ERROR_CSUM_TCP 0x08000000 +#define KS_DESC_RX_ERROR_CSUM_IP 0x10000000 +#define KS_DESC_RX_LAST 0x20000000 +#define KS_DESC_RX_FIRST 0x40000000 +#define KS_DESC_RX_ERROR_COND \ + (KS_DESC_RX_ERROR_CRC | \ + KS_DESC_RX_ERROR_RUNT | \ + KS_DESC_RX_ERROR_PHY | \ + KS_DESC_RX_ERROR_TOO_LONG) + +#define KS_DESC_HW_OWNED 0x80000000 + +#define KS_DESC_BUF_SIZE 0x000007FF +#define KS884X_DESC_TX_PORT_MASK 0x00300000 +#define KS_DESC_END_OF_RING 0x02000000 +#define KS_DESC_TX_CSUM_GEN_UDP 0x04000000 +#define KS_DESC_TX_CSUM_GEN_TCP 0x08000000 +#define KS_DESC_TX_CSUM_GEN_IP 0x10000000 +#define KS_DESC_TX_LAST 0x20000000 +#define KS_DESC_TX_FIRST 0x40000000 +#define KS_DESC_TX_INTERRUPT 0x80000000 + +#define KS_DESC_PORT_SHIFT 20 + +#define KS_DESC_RX_MASK (KS_DESC_BUF_SIZE) + +#define KS_DESC_TX_MASK \ + (KS_DESC_TX_INTERRUPT | \ + KS_DESC_TX_FIRST | \ + KS_DESC_TX_LAST | \ + KS_DESC_TX_CSUM_GEN_IP | \ + KS_DESC_TX_CSUM_GEN_TCP | \ + KS_DESC_TX_CSUM_GEN_UDP | \ + KS_DESC_BUF_SIZE) + +struct ksz_desc_rx_stat { +#ifdef __BIG_ENDIAN_BITFIELD + u32 hw_owned:1; + u32 first_desc:1; + u32 last_desc:1; + u32 csum_err_ip:1; + u32 csum_err_tcp:1; + u32 csum_err_udp:1; + u32 error:1; + u32 multicast:1; + u32 src_port:4; + u32 err_phy:1; + u32 err_too_long:1; + u32 err_runt:1; + u32 err_crc:1; + u32 frame_type:1; + u32 reserved1:4; + u32 frame_len:11; +#else + u32 frame_len:11; + u32 reserved1:4; + u32 frame_type:1; + u32 err_crc:1; + u32 err_runt:1; + u32 err_too_long:1; + u32 err_phy:1; + u32 src_port:4; + u32 multicast:1; + u32 error:1; + u32 csum_err_udp:1; + u32 csum_err_tcp:1; + u32 csum_err_ip:1; + u32 last_desc:1; + u32 first_desc:1; + u32 hw_owned:1; +#endif +}; + +struct ksz_desc_tx_stat { +#ifdef __BIG_ENDIAN_BITFIELD + u32 hw_owned:1; + u32 reserved1:31; +#else + u32 reserved1:31; + u32 hw_owned:1; +#endif +}; + +struct ksz_desc_rx_buf { +#ifdef __BIG_ENDIAN_BITFIELD + u32 reserved4:6; + u32 end_of_ring:1; + u32 reserved3:14; + u32 buf_size:11; +#else + u32 buf_size:11; + u32 reserved3:14; + u32 end_of_ring:1; + u32 reserved4:6; +#endif +}; + +struct ksz_desc_tx_buf { +#ifdef __BIG_ENDIAN_BITFIELD + u32 intr:1; + u32 first_seg:1; + u32 last_seg:1; + u32 csum_gen_ip:1; + u32 csum_gen_tcp:1; + u32 csum_gen_udp:1; + u32 end_of_ring:1; + u32 reserved4:1; + u32 dest_port:4; + u32 reserved3:9; + u32 buf_size:11; +#else + u32 buf_size:11; + u32 reserved3:9; + u32 dest_port:4; + u32 reserved4:1; + u32 end_of_ring:1; + u32 csum_gen_udp:1; + u32 csum_gen_tcp:1; + u32 csum_gen_ip:1; + u32 last_seg:1; + u32 first_seg:1; + u32 intr:1; +#endif +}; + +union desc_stat { + struct ksz_desc_rx_stat rx; + struct ksz_desc_tx_stat tx; + u32 data; +}; + +union desc_buf { + struct ksz_desc_rx_buf rx; + struct ksz_desc_tx_buf tx; + u32 data; +}; + +/** + * struct ksz_hw_desc - Hardware descriptor data structure + * @ctrl: Descriptor control value. + * @buf: Descriptor buffer value. + * @addr: Physical address of memory buffer. + * @next: Pointer to next hardware descriptor. + */ +struct ksz_hw_desc { + union desc_stat ctrl; + union desc_buf buf; + u32 addr; + u32 next; +}; + +/** + * struct ksz_sw_desc - Software descriptor data structure + * @ctrl: Descriptor control value. + * @buf: Descriptor buffer value. + * @buf_size: Current buffers size value in hardware descriptor. + */ +struct ksz_sw_desc { + union desc_stat ctrl; + union desc_buf buf; + u32 buf_size; +}; + +/** + * struct ksz_dma_buf - OS dependent DMA buffer data structure + * @skb: Associated socket buffer. + * @dma: Associated physical DMA address. + * @len: Actual len used. + */ +struct ksz_dma_buf { + struct sk_buff *skb; + dma_addr_t dma; + int len; +}; + +/** + * struct ksz_desc - Descriptor structure + * @phw: Hardware descriptor pointer to uncached physical memory. + * @sw: Cached memory to hold hardware descriptor values for + * manipulation. + * @dma_buf: Operating system dependent data structure to hold physical + * memory buffer allocation information. + */ +struct ksz_desc { + struct ksz_hw_desc *phw; + struct ksz_sw_desc sw; + struct ksz_dma_buf dma_buf; +}; + +#define DMA_BUFFER(desc) ((struct ksz_dma_buf *)(&(desc)->dma_buf)) + +/** + * struct ksz_desc_info - Descriptor information data structure + * @ring: First descriptor in the ring. + * @cur: Current descriptor being manipulated. + * @ring_virt: First hardware descriptor in the ring. + * @ring_phys: The physical address of the first descriptor of the ring. + * @size: Size of hardware descriptor. + * @alloc: Number of descriptors allocated. + * @avail: Number of descriptors available for use. + * @last: Index for last descriptor released to hardware. + * @next: Index for next descriptor available for use. + * @mask: Mask for index wrapping. + */ +struct ksz_desc_info { + struct ksz_desc *ring; + struct ksz_desc *cur; + struct ksz_hw_desc *ring_virt; + u32 ring_phys; + int size; + int alloc; + int avail; + int last; + int next; + int mask; +}; + +/* + * KSZ8842 switch definitions + */ + +enum { + TABLE_STATIC_MAC = 0, + TABLE_VLAN, + TABLE_DYNAMIC_MAC, + TABLE_MIB +}; + +#define LEARNED_MAC_TABLE_ENTRIES 1024 +#define STATIC_MAC_TABLE_ENTRIES 8 + +/** + * struct ksz_mac_table - Static MAC table data structure + * @mac_addr: MAC address to filter. + * @vid: VID value. + * @fid: FID value. + * @ports: Port membership. + * @override: Override setting. + * @use_fid: FID use setting. + * @valid: Valid setting indicating the entry is being used. + */ +struct ksz_mac_table { + u8 mac_addr[ETH_ALEN]; + u16 vid; + u8 fid; + u8 ports; + u8 override:1; + u8 use_fid:1; + u8 valid:1; +}; + +#define VLAN_TABLE_ENTRIES 16 + +/** + * struct ksz_vlan_table - VLAN table data structure + * @vid: VID value. + * @fid: FID value. + * @member: Port membership. + */ +struct ksz_vlan_table { + u16 vid; + u8 fid; + u8 member; +}; + +#define DIFFSERV_ENTRIES 64 +#define PRIO_802_1P_ENTRIES 8 +#define PRIO_QUEUES 4 + +#define SWITCH_PORT_NUM 2 +#define TOTAL_PORT_NUM (SWITCH_PORT_NUM + 1) +#define HOST_MASK (1 << SWITCH_PORT_NUM) +#define PORT_MASK 7 + +#define MAIN_PORT 0 +#define OTHER_PORT 1 +#define HOST_PORT SWITCH_PORT_NUM + +#define PORT_COUNTER_NUM 0x20 +#define TOTAL_PORT_COUNTER_NUM (PORT_COUNTER_NUM + 2) + +#define MIB_COUNTER_RX_LO_PRIORITY 0x00 +#define MIB_COUNTER_RX_HI_PRIORITY 0x01 +#define MIB_COUNTER_RX_UNDERSIZE 0x02 +#define MIB_COUNTER_RX_FRAGMENT 0x03 +#define MIB_COUNTER_RX_OVERSIZE 0x04 +#define MIB_COUNTER_RX_JABBER 0x05 +#define MIB_COUNTER_RX_SYMBOL_ERR 0x06 +#define MIB_COUNTER_RX_CRC_ERR 0x07 +#define MIB_COUNTER_RX_ALIGNMENT_ERR 0x08 +#define MIB_COUNTER_RX_CTRL_8808 0x09 +#define MIB_COUNTER_RX_PAUSE 0x0A +#define MIB_COUNTER_RX_BROADCAST 0x0B +#define MIB_COUNTER_RX_MULTICAST 0x0C +#define MIB_COUNTER_RX_UNICAST 0x0D +#define MIB_COUNTER_RX_OCTET_64 0x0E +#define MIB_COUNTER_RX_OCTET_65_127 0x0F +#define MIB_COUNTER_RX_OCTET_128_255 0x10 +#define MIB_COUNTER_RX_OCTET_256_511 0x11 +#define MIB_COUNTER_RX_OCTET_512_1023 0x12 +#define MIB_COUNTER_RX_OCTET_1024_1522 0x13 +#define MIB_COUNTER_TX_LO_PRIORITY 0x14 +#define MIB_COUNTER_TX_HI_PRIORITY 0x15 +#define MIB_COUNTER_TX_LATE_COLLISION 0x16 +#define MIB_COUNTER_TX_PAUSE 0x17 +#define MIB_COUNTER_TX_BROADCAST 0x18 +#define MIB_COUNTER_TX_MULTICAST 0x19 +#define MIB_COUNTER_TX_UNICAST 0x1A +#define MIB_COUNTER_TX_DEFERRED 0x1B +#define MIB_COUNTER_TX_TOTAL_COLLISION 0x1C +#define MIB_COUNTER_TX_EXCESS_COLLISION 0x1D +#define MIB_COUNTER_TX_SINGLE_COLLISION 0x1E +#define MIB_COUNTER_TX_MULTI_COLLISION 0x1F + +#define MIB_COUNTER_RX_DROPPED_PACKET 0x20 +#define MIB_COUNTER_TX_DROPPED_PACKET 0x21 + +/** + * struct ksz_port_mib - Port MIB data structure + * @cnt_ptr: Current pointer to MIB counter index. + * @link_down: Indication the link has just gone down. + * @state: Connection status of the port. + * @mib_start: The starting counter index. Some ports do not start at 0. + * @counter: 64-bit MIB counter value. + * @dropped: Temporary buffer to remember last read packet dropped values. + * + * MIB counters needs to be read periodically so that counters do not get + * overflowed and give incorrect values. A right balance is needed to + * satisfy this condition and not waste too much CPU time. + * + * It is pointless to read MIB counters when the port is disconnected. The + * @state provides the connection status so that MIB counters are read only + * when the port is connected. The @link_down indicates the port is just + * disconnected so that all MIB counters are read one last time to update the + * information. + */ +struct ksz_port_mib { + u8 cnt_ptr; + u8 link_down; + u8 state; + u8 mib_start; + + u64 counter[TOTAL_PORT_COUNTER_NUM]; + u32 dropped[2]; +}; + +/** + * struct ksz_port_cfg - Port configuration data structure + * @vid: VID value. + * @member: Port membership. + * @port_prio: Port priority. + * @rx_rate: Receive priority rate. + * @tx_rate: Transmit priority rate. + * @stp_state: Current Spanning Tree Protocol state. + */ +struct ksz_port_cfg { + u16 vid; + u8 member; + u8 port_prio; + u32 rx_rate[PRIO_QUEUES]; + u32 tx_rate[PRIO_QUEUES]; + int stp_state; +}; + +/** + * struct ksz_switch - KSZ8842 switch data structure + * @mac_table: MAC table entries information. + * @vlan_table: VLAN table entries information. + * @port_cfg: Port configuration information. + * @diffserv: DiffServ priority settings. Possible values from 6-bit of ToS + * (bit7 ~ bit2) field. + * @p_802_1p: 802.1P priority settings. Possible values from 3-bit of 802.1p + * Tag priority field. + * @br_addr: Bridge address. Used for STP. + * @other_addr: Other MAC address. Used for multiple network device mode. + * @broad_per: Broadcast storm percentage. + * @member: Current port membership. Used for STP. + */ +struct ksz_switch { + struct ksz_mac_table mac_table[STATIC_MAC_TABLE_ENTRIES]; + struct ksz_vlan_table vlan_table[VLAN_TABLE_ENTRIES]; + struct ksz_port_cfg port_cfg[TOTAL_PORT_NUM]; + + u8 diffserv[DIFFSERV_ENTRIES]; + u8 p_802_1p[PRIO_802_1P_ENTRIES]; + + u8 br_addr[ETH_ALEN]; + u8 other_addr[ETH_ALEN]; + + u8 broad_per; + u8 member; +}; + +#define TX_RATE_UNIT 10000 + +/** + * struct ksz_port_info - Port information data structure + * @state: Connection status of the port. + * @tx_rate: Transmit rate divided by 10000 to get Mbit. + * @duplex: Duplex mode. + * @advertised: Advertised auto-negotiation setting. Used to determine link. + * @partner: Auto-negotiation partner setting. Used to determine link. + * @port_id: Port index to access actual hardware register. + * @pdev: Pointer to OS dependent network device. + */ +struct ksz_port_info { + uint state; + uint tx_rate; + u8 duplex; + u8 advertised; + u8 partner; + u8 port_id; + void *pdev; +}; + +#define MAX_TX_HELD_SIZE 52000 + +/* Hardware features and bug fixes. */ +#define LINK_INT_WORKING (1 << 0) +#define SMALL_PACKET_TX_BUG (1 << 1) +#define HALF_DUPLEX_SIGNAL_BUG (1 << 2) +#define RX_HUGE_FRAME (1 << 4) +#define STP_SUPPORT (1 << 8) + +/* Software overrides. */ +#define PAUSE_FLOW_CTRL (1 << 0) +#define FAST_AGING (1 << 1) + +/** + * struct ksz_hw - KSZ884X hardware data structure + * @io: Virtual address assigned. + * @ksz_switch: Pointer to KSZ8842 switch. + * @port_info: Port information. + * @port_mib: Port MIB information. + * @dev_count: Number of network devices this hardware supports. + * @dst_ports: Destination ports in switch for transmission. + * @id: Hardware ID. Used for display only. + * @mib_cnt: Number of MIB counters this hardware has. + * @mib_port_cnt: Number of ports with MIB counters. + * @tx_cfg: Cached transmit control settings. + * @rx_cfg: Cached receive control settings. + * @intr_mask: Current interrupt mask. + * @intr_set: Current interrupt set. + * @intr_blocked: Interrupt blocked. + * @rx_desc_info: Receive descriptor information. + * @tx_desc_info: Transmit descriptor information. + * @tx_int_cnt: Transmit interrupt count. Used for TX optimization. + * @tx_int_mask: Transmit interrupt mask. Used for TX optimization. + * @tx_size: Transmit data size. Used for TX optimization. + * The maximum is defined by MAX_TX_HELD_SIZE. + * @perm_addr: Permanent MAC address. + * @override_addr: Overridden MAC address. + * @address: Additional MAC address entries. + * @addr_list_size: Additional MAC address list size. + * @mac_override: Indication of MAC address overridden. + * @promiscuous: Counter to keep track of promiscuous mode set. + * @all_multi: Counter to keep track of all multicast mode set. + * @multi_list: Multicast address entries. + * @multi_bits: Cached multicast hash table settings. + * @multi_list_size: Multicast address list size. + * @enabled: Indication of hardware enabled. + * @rx_stop: Indication of receive process stop. + * @reserved2: none + * @features: Hardware features to enable. + * @overrides: Hardware features to override. + * @parent: Pointer to parent, network device private structure. + */ +struct ksz_hw { + void __iomem *io; + + struct ksz_switch *ksz_switch; + struct ksz_port_info port_info[SWITCH_PORT_NUM]; + struct ksz_port_mib port_mib[TOTAL_PORT_NUM]; + int dev_count; + int dst_ports; + int id; + int mib_cnt; + int mib_port_cnt; + + u32 tx_cfg; + u32 rx_cfg; + u32 intr_mask; + u32 intr_set; + uint intr_blocked; + + struct ksz_desc_info rx_desc_info; + struct ksz_desc_info tx_desc_info; + + int tx_int_cnt; + int tx_int_mask; + int tx_size; + + u8 perm_addr[ETH_ALEN]; + u8 override_addr[ETH_ALEN]; + u8 address[ADDITIONAL_ENTRIES][ETH_ALEN]; + u8 addr_list_size; + u8 mac_override; + u8 promiscuous; + u8 all_multi; + u8 multi_list[MAX_MULTICAST_LIST][ETH_ALEN]; + u8 multi_bits[HW_MULTICAST_SIZE]; + u8 multi_list_size; + + u8 enabled; + u8 rx_stop; + u8 reserved2[1]; + + uint features; + uint overrides; + + void *parent; +}; + +enum { + PHY_NO_FLOW_CTRL, + PHY_FLOW_CTRL, + PHY_TX_ONLY, + PHY_RX_ONLY +}; + +/** + * struct ksz_port - Virtual port data structure + * @duplex: Duplex mode setting. 1 for half duplex, 2 for full + * duplex, and 0 for auto, which normally results in full + * duplex. + * @speed: Speed setting. 10 for 10 Mbit, 100 for 100 Mbit, and + * 0 for auto, which normally results in 100 Mbit. + * @force_link: Force link setting. 0 for auto-negotiation, and 1 for + * force. + * @flow_ctrl: Flow control setting. PHY_NO_FLOW_CTRL for no flow + * control, and PHY_FLOW_CTRL for flow control. + * PHY_TX_ONLY and PHY_RX_ONLY are not supported for 100 + * Mbit PHY. + * @first_port: Index of first port this port supports. + * @mib_port_cnt: Number of ports with MIB counters. + * @port_cnt: Number of ports this port supports. + * @counter: Port statistics counter. + * @hw: Pointer to hardware structure. + * @linked: Pointer to port information linked to this port. + */ +struct ksz_port { + u8 duplex; + u8 speed; + u8 force_link; + u8 flow_ctrl; + + int first_port; + int mib_port_cnt; + int port_cnt; + u64 counter[OID_COUNTER_LAST]; + + struct ksz_hw *hw; + struct ksz_port_info *linked; +}; + +/** + * struct ksz_timer_info - Timer information data structure + * @timer: Kernel timer. + * @cnt: Running timer counter. + * @max: Number of times to run timer; -1 for infinity. + * @period: Timer period in jiffies. + */ +struct ksz_timer_info { + struct timer_list timer; + int cnt; + int max; + int period; +}; + +/** + * struct ksz_shared_mem - OS dependent shared memory data structure + * @dma_addr: Physical DMA address allocated. + * @alloc_size: Allocation size. + * @phys: Actual physical address used. + * @alloc_virt: Virtual address allocated. + * @virt: Actual virtual address used. + */ +struct ksz_shared_mem { + dma_addr_t dma_addr; + uint alloc_size; + uint phys; + u8 *alloc_virt; + u8 *virt; +}; + +/** + * struct ksz_counter_info - OS dependent counter information data structure + * @counter: Wait queue to wakeup after counters are read. + * @time: Next time in jiffies to read counter. + * @read: Indication of counters read in full or not. + */ +struct ksz_counter_info { + wait_queue_head_t counter; + unsigned long time; + int read; +}; + +/** + * struct dev_info - Network device information data structure + * @dev: Pointer to network device. + * @pdev: Pointer to PCI device. + * @hw: Hardware structure. + * @desc_pool: Physical memory used for descriptor pool. + * @hwlock: Spinlock to prevent hardware from accessing. + * @lock: Mutex lock to prevent device from accessing. + * @dev_rcv: Receive process function used. + * @last_skb: Socket buffer allocated for descriptor rx fragments. + * @skb_index: Buffer index for receiving fragments. + * @skb_len: Buffer length for receiving fragments. + * @mib_read: Workqueue to read MIB counters. + * @mib_timer_info: Timer to read MIB counters. + * @counter: Used for MIB reading. + * @mtu: Current MTU used. The default is REGULAR_RX_BUF_SIZE; + * the maximum is MAX_RX_BUF_SIZE. + * @opened: Counter to keep track of device open. + * @rx_tasklet: Receive processing tasklet. + * @tx_tasklet: Transmit processing tasklet. + * @wol_enable: Wake-on-LAN enable set by ethtool. + * @wol_support: Wake-on-LAN support used by ethtool. + * @pme_wait: Used for KSZ8841 power management. + */ +struct dev_info { + struct net_device *dev; + struct pci_dev *pdev; + + struct ksz_hw hw; + struct ksz_shared_mem desc_pool; + + spinlock_t hwlock; + struct mutex lock; + + int (*dev_rcv)(struct dev_info *); + + struct sk_buff *last_skb; + int skb_index; + int skb_len; + + struct work_struct mib_read; + struct ksz_timer_info mib_timer_info; + struct ksz_counter_info counter[TOTAL_PORT_NUM]; + + int mtu; + int opened; + + struct tasklet_struct rx_tasklet; + struct tasklet_struct tx_tasklet; + + int wol_enable; + int wol_support; + unsigned long pme_wait; +}; + +/** + * struct dev_priv - Network device private data structure + * @adapter: Adapter device information. + * @port: Port information. + * @monitor_timer_info: Timer to monitor ports. + * @proc_sem: Semaphore for proc accessing. + * @id: Device ID. + * @mii_if: MII interface information. + * @advertising: Temporary variable to store advertised settings. + * @msg_enable: The message flags controlling driver output. + * @media_state: The connection status of the device. + * @multicast: The all multicast state of the device. + * @promiscuous: The promiscuous state of the device. + */ +struct dev_priv { + struct dev_info *adapter; + struct ksz_port port; + struct ksz_timer_info monitor_timer_info; + + struct semaphore proc_sem; + int id; + + struct mii_if_info mii_if; + u32 advertising; + + u32 msg_enable; + int media_state; + int multicast; + int promiscuous; +}; + +#define DRV_NAME "KSZ884X PCI" +#define DEVICE_NAME "KSZ884x PCI" +#define DRV_VERSION "1.0.0" +#define DRV_RELDATE "Feb 8, 2010" + +static char version[] = + "Micrel " DEVICE_NAME " " DRV_VERSION " (" DRV_RELDATE ")"; + +static u8 DEFAULT_MAC_ADDRESS[] = { 0x00, 0x10, 0xA1, 0x88, 0x42, 0x01 }; + +/* + * Interrupt processing primary routines + */ + +static inline void hw_ack_intr(struct ksz_hw *hw, uint interrupt) +{ + writel(interrupt, hw->io + KS884X_INTERRUPTS_STATUS); +} + +static inline void hw_dis_intr(struct ksz_hw *hw) +{ + hw->intr_blocked = hw->intr_mask; + writel(0, hw->io + KS884X_INTERRUPTS_ENABLE); + hw->intr_set = readl(hw->io + KS884X_INTERRUPTS_ENABLE); +} + +static inline void hw_set_intr(struct ksz_hw *hw, uint interrupt) +{ + hw->intr_set = interrupt; + writel(interrupt, hw->io + KS884X_INTERRUPTS_ENABLE); +} + +static inline void hw_ena_intr(struct ksz_hw *hw) +{ + hw->intr_blocked = 0; + hw_set_intr(hw, hw->intr_mask); +} + +static inline void hw_dis_intr_bit(struct ksz_hw *hw, uint bit) +{ + hw->intr_mask &= ~(bit); +} + +static inline void hw_turn_off_intr(struct ksz_hw *hw, uint interrupt) +{ + u32 read_intr; + + read_intr = readl(hw->io + KS884X_INTERRUPTS_ENABLE); + hw->intr_set = read_intr & ~interrupt; + writel(hw->intr_set, hw->io + KS884X_INTERRUPTS_ENABLE); + hw_dis_intr_bit(hw, interrupt); +} + +/** + * hw_turn_on_intr - turn on specified interrupts + * @hw: The hardware instance. + * @bit: The interrupt bits to be on. + * + * This routine turns on the specified interrupts in the interrupt mask so that + * those interrupts will be enabled. + */ +static void hw_turn_on_intr(struct ksz_hw *hw, u32 bit) +{ + hw->intr_mask |= bit; + + if (!hw->intr_blocked) + hw_set_intr(hw, hw->intr_mask); +} + +static inline void hw_read_intr(struct ksz_hw *hw, uint *status) +{ + *status = readl(hw->io + KS884X_INTERRUPTS_STATUS); + *status = *status & hw->intr_set; +} + +static inline void hw_restore_intr(struct ksz_hw *hw, uint interrupt) +{ + if (interrupt) + hw_ena_intr(hw); +} + +/** + * hw_block_intr - block hardware interrupts + * @hw: The hardware instance. + * + * This function blocks all interrupts of the hardware and returns the current + * interrupt enable mask so that interrupts can be restored later. + * + * Return the current interrupt enable mask. + */ +static uint hw_block_intr(struct ksz_hw *hw) +{ + uint interrupt = 0; + + if (!hw->intr_blocked) { + hw_dis_intr(hw); + interrupt = hw->intr_blocked; + } + return interrupt; +} + +/* + * Hardware descriptor routines + */ + +static inline void reset_desc(struct ksz_desc *desc, union desc_stat status) +{ + status.rx.hw_owned = 0; + desc->phw->ctrl.data = cpu_to_le32(status.data); +} + +static inline void release_desc(struct ksz_desc *desc) +{ + desc->sw.ctrl.tx.hw_owned = 1; + if (desc->sw.buf_size != desc->sw.buf.data) { + desc->sw.buf_size = desc->sw.buf.data; + desc->phw->buf.data = cpu_to_le32(desc->sw.buf.data); + } + desc->phw->ctrl.data = cpu_to_le32(desc->sw.ctrl.data); +} + +static void get_rx_pkt(struct ksz_desc_info *info, struct ksz_desc **desc) +{ + *desc = &info->ring[info->last]; + info->last++; + info->last &= info->mask; + info->avail--; + (*desc)->sw.buf.data &= ~KS_DESC_RX_MASK; +} + +static inline void set_rx_buf(struct ksz_desc *desc, u32 addr) +{ + desc->phw->addr = cpu_to_le32(addr); +} + +static inline void set_rx_len(struct ksz_desc *desc, u32 len) +{ + desc->sw.buf.rx.buf_size = len; +} + +static inline void get_tx_pkt(struct ksz_desc_info *info, + struct ksz_desc **desc) +{ + *desc = &info->ring[info->next]; + info->next++; + info->next &= info->mask; + info->avail--; + (*desc)->sw.buf.data &= ~KS_DESC_TX_MASK; +} + +static inline void set_tx_buf(struct ksz_desc *desc, u32 addr) +{ + desc->phw->addr = cpu_to_le32(addr); +} + +static inline void set_tx_len(struct ksz_desc *desc, u32 len) +{ + desc->sw.buf.tx.buf_size = len; +} + +/* Switch functions */ + +#define TABLE_READ 0x10 +#define TABLE_SEL_SHIFT 2 + +#define HW_DELAY(hw, reg) \ + do { \ + readw(hw->io + reg); \ + } while (0) + +/** + * sw_r_table - read 4 bytes of data from switch table + * @hw: The hardware instance. + * @table: The table selector. + * @addr: The address of the table entry. + * @data: Buffer to store the read data. + * + * This routine reads 4 bytes of data from the table of the switch. + * Hardware interrupts are disabled to minimize corruption of read data. + */ +static void sw_r_table(struct ksz_hw *hw, int table, u16 addr, u32 *data) +{ + u16 ctrl_addr; + uint interrupt; + + ctrl_addr = (((table << TABLE_SEL_SHIFT) | TABLE_READ) << 8) | addr; + + interrupt = hw_block_intr(hw); + + writew(ctrl_addr, hw->io + KS884X_IACR_OFFSET); + HW_DELAY(hw, KS884X_IACR_OFFSET); + *data = readl(hw->io + KS884X_ACC_DATA_0_OFFSET); + + hw_restore_intr(hw, interrupt); +} + +/** + * sw_w_table_64 - write 8 bytes of data to the switch table + * @hw: The hardware instance. + * @table: The table selector. + * @addr: The address of the table entry. + * @data_hi: The high part of data to be written (bit63 ~ bit32). + * @data_lo: The low part of data to be written (bit31 ~ bit0). + * + * This routine writes 8 bytes of data to the table of the switch. + * Hardware interrupts are disabled to minimize corruption of written data. + */ +static void sw_w_table_64(struct ksz_hw *hw, int table, u16 addr, u32 data_hi, + u32 data_lo) +{ + u16 ctrl_addr; + uint interrupt; + + ctrl_addr = ((table << TABLE_SEL_SHIFT) << 8) | addr; + + interrupt = hw_block_intr(hw); + + writel(data_hi, hw->io + KS884X_ACC_DATA_4_OFFSET); + writel(data_lo, hw->io + KS884X_ACC_DATA_0_OFFSET); + + writew(ctrl_addr, hw->io + KS884X_IACR_OFFSET); + HW_DELAY(hw, KS884X_IACR_OFFSET); + + hw_restore_intr(hw, interrupt); +} + +/** + * sw_w_sta_mac_table - write to the static MAC table + * @hw: The hardware instance. + * @addr: The address of the table entry. + * @mac_addr: The MAC address. + * @ports: The port members. + * @override: The flag to override the port receive/transmit settings. + * @valid: The flag to indicate entry is valid. + * @use_fid: The flag to indicate the FID is valid. + * @fid: The FID value. + * + * This routine writes an entry of the static MAC table of the switch. It + * calls sw_w_table_64() to write the data. + */ +static void sw_w_sta_mac_table(struct ksz_hw *hw, u16 addr, u8 *mac_addr, + u8 ports, int override, int valid, int use_fid, u8 fid) +{ + u32 data_hi; + u32 data_lo; + + data_lo = ((u32) mac_addr[2] << 24) | + ((u32) mac_addr[3] << 16) | + ((u32) mac_addr[4] << 8) | mac_addr[5]; + data_hi = ((u32) mac_addr[0] << 8) | mac_addr[1]; + data_hi |= (u32) ports << STATIC_MAC_FWD_PORTS_SHIFT; + + if (override) + data_hi |= STATIC_MAC_TABLE_OVERRIDE; + if (use_fid) { + data_hi |= STATIC_MAC_TABLE_USE_FID; + data_hi |= (u32) fid << STATIC_MAC_FID_SHIFT; + } + if (valid) + data_hi |= STATIC_MAC_TABLE_VALID; + + sw_w_table_64(hw, TABLE_STATIC_MAC, addr, data_hi, data_lo); +} + +/** + * sw_r_vlan_table - read from the VLAN table + * @hw: The hardware instance. + * @addr: The address of the table entry. + * @vid: Buffer to store the VID. + * @fid: Buffer to store the VID. + * @member: Buffer to store the port membership. + * + * This function reads an entry of the VLAN table of the switch. It calls + * sw_r_table() to get the data. + * + * Return 0 if the entry is valid; otherwise -1. + */ +static int sw_r_vlan_table(struct ksz_hw *hw, u16 addr, u16 *vid, u8 *fid, + u8 *member) +{ + u32 data; + + sw_r_table(hw, TABLE_VLAN, addr, &data); + if (data & VLAN_TABLE_VALID) { + *vid = (u16)(data & VLAN_TABLE_VID); + *fid = (u8)((data & VLAN_TABLE_FID) >> VLAN_TABLE_FID_SHIFT); + *member = (u8)((data & VLAN_TABLE_MEMBERSHIP) >> + VLAN_TABLE_MEMBERSHIP_SHIFT); + return 0; + } + return -1; +} + +/** + * port_r_mib_cnt - read MIB counter + * @hw: The hardware instance. + * @port: The port index. + * @addr: The address of the counter. + * @cnt: Buffer to store the counter. + * + * This routine reads a MIB counter of the port. + * Hardware interrupts are disabled to minimize corruption of read data. + */ +static void port_r_mib_cnt(struct ksz_hw *hw, int port, u16 addr, u64 *cnt) +{ + u32 data; + u16 ctrl_addr; + uint interrupt; + int timeout; + + ctrl_addr = addr + PORT_COUNTER_NUM * port; + + interrupt = hw_block_intr(hw); + + ctrl_addr |= (((TABLE_MIB << TABLE_SEL_SHIFT) | TABLE_READ) << 8); + writew(ctrl_addr, hw->io + KS884X_IACR_OFFSET); + HW_DELAY(hw, KS884X_IACR_OFFSET); + + for (timeout = 100; timeout > 0; timeout--) { + data = readl(hw->io + KS884X_ACC_DATA_0_OFFSET); + + if (data & MIB_COUNTER_VALID) { + if (data & MIB_COUNTER_OVERFLOW) + *cnt += MIB_COUNTER_VALUE + 1; + *cnt += data & MIB_COUNTER_VALUE; + break; + } + } + + hw_restore_intr(hw, interrupt); +} + +/** + * port_r_mib_pkt - read dropped packet counts + * @hw: The hardware instance. + * @port: The port index. + * @last: last one + * @cnt: Buffer to store the receive and transmit dropped packet counts. + * + * This routine reads the dropped packet counts of the port. + * Hardware interrupts are disabled to minimize corruption of read data. + */ +static void port_r_mib_pkt(struct ksz_hw *hw, int port, u32 *last, u64 *cnt) +{ + u32 cur; + u32 data; + u16 ctrl_addr; + uint interrupt; + int index; + + index = KS_MIB_PACKET_DROPPED_RX_0 + port; + do { + interrupt = hw_block_intr(hw); + + ctrl_addr = (u16) index; + ctrl_addr |= (((TABLE_MIB << TABLE_SEL_SHIFT) | TABLE_READ) + << 8); + writew(ctrl_addr, hw->io + KS884X_IACR_OFFSET); + HW_DELAY(hw, KS884X_IACR_OFFSET); + data = readl(hw->io + KS884X_ACC_DATA_0_OFFSET); + + hw_restore_intr(hw, interrupt); + + data &= MIB_PACKET_DROPPED; + cur = *last; + if (data != cur) { + *last = data; + if (data < cur) + data += MIB_PACKET_DROPPED + 1; + data -= cur; + *cnt += data; + } + ++last; + ++cnt; + index -= KS_MIB_PACKET_DROPPED_TX - + KS_MIB_PACKET_DROPPED_TX_0 + 1; + } while (index >= KS_MIB_PACKET_DROPPED_TX_0 + port); +} + +/** + * port_r_cnt - read MIB counters periodically + * @hw: The hardware instance. + * @port: The port index. + * + * This routine is used to read the counters of the port periodically to avoid + * counter overflow. The hardware should be acquired first before calling this + * routine. + * + * Return non-zero when not all counters not read. + */ +static int port_r_cnt(struct ksz_hw *hw, int port) +{ + struct ksz_port_mib *mib = &hw->port_mib[port]; + + if (mib->mib_start < PORT_COUNTER_NUM) + while (mib->cnt_ptr < PORT_COUNTER_NUM) { + port_r_mib_cnt(hw, port, mib->cnt_ptr, + &mib->counter[mib->cnt_ptr]); + ++mib->cnt_ptr; + } + if (hw->mib_cnt > PORT_COUNTER_NUM) + port_r_mib_pkt(hw, port, mib->dropped, + &mib->counter[PORT_COUNTER_NUM]); + mib->cnt_ptr = 0; + return 0; +} + +/** + * port_init_cnt - initialize MIB counter values + * @hw: The hardware instance. + * @port: The port index. + * + * This routine is used to initialize all counters to zero if the hardware + * cannot do it after reset. + */ +static void port_init_cnt(struct ksz_hw *hw, int port) +{ + struct ksz_port_mib *mib = &hw->port_mib[port]; + + mib->cnt_ptr = 0; + if (mib->mib_start < PORT_COUNTER_NUM) + do { + port_r_mib_cnt(hw, port, mib->cnt_ptr, + &mib->counter[mib->cnt_ptr]); + ++mib->cnt_ptr; + } while (mib->cnt_ptr < PORT_COUNTER_NUM); + if (hw->mib_cnt > PORT_COUNTER_NUM) + port_r_mib_pkt(hw, port, mib->dropped, + &mib->counter[PORT_COUNTER_NUM]); + memset((void *) mib->counter, 0, sizeof(u64) * TOTAL_PORT_COUNTER_NUM); + mib->cnt_ptr = 0; +} + +/* + * Port functions + */ + +/** + * port_cfg - set port register bits + * @hw: The hardware instance. + * @port: The port index. + * @offset: The offset of the port register. + * @bits: The data bits to set. + * @set: The flag indicating whether the bits are to be set or not. + * + * This routine sets or resets the specified bits of the port register. + */ +static void port_cfg(struct ksz_hw *hw, int port, int offset, u16 bits, + int set) +{ + u32 addr; + u16 data; + + PORT_CTRL_ADDR(port, addr); + addr += offset; + data = readw(hw->io + addr); + if (set) + data |= bits; + else + data &= ~bits; + writew(data, hw->io + addr); +} + +/** + * port_r8 - read byte from port register + * @hw: The hardware instance. + * @port: The port index. + * @offset: The offset of the port register. + * @data: Buffer to store the data. + * + * This routine reads a byte from the port register. + */ +static void port_r8(struct ksz_hw *hw, int port, int offset, u8 *data) +{ + u32 addr; + + PORT_CTRL_ADDR(port, addr); + addr += offset; + *data = readb(hw->io + addr); +} + +/** + * port_r16 - read word from port register. + * @hw: The hardware instance. + * @port: The port index. + * @offset: The offset of the port register. + * @data: Buffer to store the data. + * + * This routine reads a word from the port register. + */ +static void port_r16(struct ksz_hw *hw, int port, int offset, u16 *data) +{ + u32 addr; + + PORT_CTRL_ADDR(port, addr); + addr += offset; + *data = readw(hw->io + addr); +} + +/** + * port_w16 - write word to port register. + * @hw: The hardware instance. + * @port: The port index. + * @offset: The offset of the port register. + * @data: Data to write. + * + * This routine writes a word to the port register. + */ +static void port_w16(struct ksz_hw *hw, int port, int offset, u16 data) +{ + u32 addr; + + PORT_CTRL_ADDR(port, addr); + addr += offset; + writew(data, hw->io + addr); +} + +/** + * sw_chk - check switch register bits + * @hw: The hardware instance. + * @addr: The address of the switch register. + * @bits: The data bits to check. + * + * This function checks whether the specified bits of the switch register are + * set or not. + * + * Return 0 if the bits are not set. + */ +static int sw_chk(struct ksz_hw *hw, u32 addr, u16 bits) +{ + u16 data; + + data = readw(hw->io + addr); + return (data & bits) == bits; +} + +/** + * sw_cfg - set switch register bits + * @hw: The hardware instance. + * @addr: The address of the switch register. + * @bits: The data bits to set. + * @set: The flag indicating whether the bits are to be set or not. + * + * This function sets or resets the specified bits of the switch register. + */ +static void sw_cfg(struct ksz_hw *hw, u32 addr, u16 bits, int set) +{ + u16 data; + + data = readw(hw->io + addr); + if (set) + data |= bits; + else + data &= ~bits; + writew(data, hw->io + addr); +} + +/* Bandwidth */ + +static inline void port_cfg_broad_storm(struct ksz_hw *hw, int p, int set) +{ + port_cfg(hw, p, + KS8842_PORT_CTRL_1_OFFSET, PORT_BROADCAST_STORM, set); +} + +/* Driver set switch broadcast storm protection at 10% rate. */ +#define BROADCAST_STORM_PROTECTION_RATE 10 + +/* 148,800 frames * 67 ms / 100 */ +#define BROADCAST_STORM_VALUE 9969 + +/** + * sw_cfg_broad_storm - configure broadcast storm threshold + * @hw: The hardware instance. + * @percent: Broadcast storm threshold in percent of transmit rate. + * + * This routine configures the broadcast storm threshold of the switch. + */ +static void sw_cfg_broad_storm(struct ksz_hw *hw, u8 percent) +{ + u16 data; + u32 value = ((u32) BROADCAST_STORM_VALUE * (u32) percent / 100); + + if (value > BROADCAST_STORM_RATE) + value = BROADCAST_STORM_RATE; + + data = readw(hw->io + KS8842_SWITCH_CTRL_3_OFFSET); + data &= ~(BROADCAST_STORM_RATE_LO | BROADCAST_STORM_RATE_HI); + data |= ((value & 0x00FF) << 8) | ((value & 0xFF00) >> 8); + writew(data, hw->io + KS8842_SWITCH_CTRL_3_OFFSET); +} + +/** + * sw_get_broad_storm - get broadcast storm threshold + * @hw: The hardware instance. + * @percent: Buffer to store the broadcast storm threshold percentage. + * + * This routine retrieves the broadcast storm threshold of the switch. + */ +static void sw_get_broad_storm(struct ksz_hw *hw, u8 *percent) +{ + int num; + u16 data; + + data = readw(hw->io + KS8842_SWITCH_CTRL_3_OFFSET); + num = (data & BROADCAST_STORM_RATE_HI); + num <<= 8; + num |= (data & BROADCAST_STORM_RATE_LO) >> 8; + num = DIV_ROUND_CLOSEST(num * 100, BROADCAST_STORM_VALUE); + *percent = (u8) num; +} + +/** + * sw_dis_broad_storm - disable broadstorm + * @hw: The hardware instance. + * @port: The port index. + * + * This routine disables the broadcast storm limit function of the switch. + */ +static void sw_dis_broad_storm(struct ksz_hw *hw, int port) +{ + port_cfg_broad_storm(hw, port, 0); +} + +/** + * sw_ena_broad_storm - enable broadcast storm + * @hw: The hardware instance. + * @port: The port index. + * + * This routine enables the broadcast storm limit function of the switch. + */ +static void sw_ena_broad_storm(struct ksz_hw *hw, int port) +{ + sw_cfg_broad_storm(hw, hw->ksz_switch->broad_per); + port_cfg_broad_storm(hw, port, 1); +} + +/** + * sw_init_broad_storm - initialize broadcast storm + * @hw: The hardware instance. + * + * This routine initializes the broadcast storm limit function of the switch. + */ +static void sw_init_broad_storm(struct ksz_hw *hw) +{ + int port; + + hw->ksz_switch->broad_per = 1; + sw_cfg_broad_storm(hw, hw->ksz_switch->broad_per); + for (port = 0; port < TOTAL_PORT_NUM; port++) + sw_dis_broad_storm(hw, port); + sw_cfg(hw, KS8842_SWITCH_CTRL_2_OFFSET, MULTICAST_STORM_DISABLE, 1); +} + +/** + * hw_cfg_broad_storm - configure broadcast storm + * @hw: The hardware instance. + * @percent: Broadcast storm threshold in percent of transmit rate. + * + * This routine configures the broadcast storm threshold of the switch. + * It is called by user functions. The hardware should be acquired first. + */ +static void hw_cfg_broad_storm(struct ksz_hw *hw, u8 percent) +{ + if (percent > 100) + percent = 100; + + sw_cfg_broad_storm(hw, percent); + sw_get_broad_storm(hw, &percent); + hw->ksz_switch->broad_per = percent; +} + +/** + * sw_dis_prio_rate - disable switch priority rate + * @hw: The hardware instance. + * @port: The port index. + * + * This routine disables the priority rate function of the switch. + */ +static void sw_dis_prio_rate(struct ksz_hw *hw, int port) +{ + u32 addr; + + PORT_CTRL_ADDR(port, addr); + addr += KS8842_PORT_IN_RATE_OFFSET; + writel(0, hw->io + addr); +} + +/** + * sw_init_prio_rate - initialize switch priority rate + * @hw: The hardware instance. + * + * This routine initializes the priority rate function of the switch. + */ +static void sw_init_prio_rate(struct ksz_hw *hw) +{ + int port; + int prio; + struct ksz_switch *sw = hw->ksz_switch; + + for (port = 0; port < TOTAL_PORT_NUM; port++) { + for (prio = 0; prio < PRIO_QUEUES; prio++) { + sw->port_cfg[port].rx_rate[prio] = + sw->port_cfg[port].tx_rate[prio] = 0; + } + sw_dis_prio_rate(hw, port); + } +} + +/* Communication */ + +static inline void port_cfg_back_pressure(struct ksz_hw *hw, int p, int set) +{ + port_cfg(hw, p, + KS8842_PORT_CTRL_2_OFFSET, PORT_BACK_PRESSURE, set); +} + +/* Mirroring */ + +static inline void port_cfg_mirror_sniffer(struct ksz_hw *hw, int p, int set) +{ + port_cfg(hw, p, + KS8842_PORT_CTRL_2_OFFSET, PORT_MIRROR_SNIFFER, set); +} + +static inline void port_cfg_mirror_rx(struct ksz_hw *hw, int p, int set) +{ + port_cfg(hw, p, + KS8842_PORT_CTRL_2_OFFSET, PORT_MIRROR_RX, set); +} + +static inline void port_cfg_mirror_tx(struct ksz_hw *hw, int p, int set) +{ + port_cfg(hw, p, + KS8842_PORT_CTRL_2_OFFSET, PORT_MIRROR_TX, set); +} + +static inline void sw_cfg_mirror_rx_tx(struct ksz_hw *hw, int set) +{ + sw_cfg(hw, KS8842_SWITCH_CTRL_2_OFFSET, SWITCH_MIRROR_RX_TX, set); +} + +static void sw_init_mirror(struct ksz_hw *hw) +{ + int port; + + for (port = 0; port < TOTAL_PORT_NUM; port++) { + port_cfg_mirror_sniffer(hw, port, 0); + port_cfg_mirror_rx(hw, port, 0); + port_cfg_mirror_tx(hw, port, 0); + } + sw_cfg_mirror_rx_tx(hw, 0); +} + +/* Priority */ + +static inline void port_cfg_diffserv(struct ksz_hw *hw, int p, int set) +{ + port_cfg(hw, p, + KS8842_PORT_CTRL_1_OFFSET, PORT_DIFFSERV_ENABLE, set); +} + +static inline void port_cfg_802_1p(struct ksz_hw *hw, int p, int set) +{ + port_cfg(hw, p, + KS8842_PORT_CTRL_1_OFFSET, PORT_802_1P_ENABLE, set); +} + +static inline void port_cfg_replace_vid(struct ksz_hw *hw, int p, int set) +{ + port_cfg(hw, p, + KS8842_PORT_CTRL_2_OFFSET, PORT_USER_PRIORITY_CEILING, set); +} + +static inline void port_cfg_prio(struct ksz_hw *hw, int p, int set) +{ + port_cfg(hw, p, + KS8842_PORT_CTRL_1_OFFSET, PORT_PRIO_QUEUE_ENABLE, set); +} + +/** + * sw_dis_diffserv - disable switch DiffServ priority + * @hw: The hardware instance. + * @port: The port index. + * + * This routine disables the DiffServ priority function of the switch. + */ +static void sw_dis_diffserv(struct ksz_hw *hw, int port) +{ + port_cfg_diffserv(hw, port, 0); +} + +/** + * sw_dis_802_1p - disable switch 802.1p priority + * @hw: The hardware instance. + * @port: The port index. + * + * This routine disables the 802.1p priority function of the switch. + */ +static void sw_dis_802_1p(struct ksz_hw *hw, int port) +{ + port_cfg_802_1p(hw, port, 0); +} + +/** + * sw_cfg_replace_null_vid - + * @hw: The hardware instance. + * @set: The flag to disable or enable. + * + */ +static void sw_cfg_replace_null_vid(struct ksz_hw *hw, int set) +{ + sw_cfg(hw, KS8842_SWITCH_CTRL_3_OFFSET, SWITCH_REPLACE_NULL_VID, set); +} + +/** + * sw_cfg_replace_vid - enable switch 802.10 priority re-mapping + * @hw: The hardware instance. + * @port: The port index. + * @set: The flag to disable or enable. + * + * This routine enables the 802.1p priority re-mapping function of the switch. + * That allows 802.1p priority field to be replaced with the port's default + * tag's priority value if the ingress packet's 802.1p priority has a higher + * priority than port's default tag's priority. + */ +static void sw_cfg_replace_vid(struct ksz_hw *hw, int port, int set) +{ + port_cfg_replace_vid(hw, port, set); +} + +/** + * sw_cfg_port_based - configure switch port based priority + * @hw: The hardware instance. + * @port: The port index. + * @prio: The priority to set. + * + * This routine configures the port based priority of the switch. + */ +static void sw_cfg_port_based(struct ksz_hw *hw, int port, u8 prio) +{ + u16 data; + + if (prio > PORT_BASED_PRIORITY_BASE) + prio = PORT_BASED_PRIORITY_BASE; + + hw->ksz_switch->port_cfg[port].port_prio = prio; + + port_r16(hw, port, KS8842_PORT_CTRL_1_OFFSET, &data); + data &= ~PORT_BASED_PRIORITY_MASK; + data |= prio << PORT_BASED_PRIORITY_SHIFT; + port_w16(hw, port, KS8842_PORT_CTRL_1_OFFSET, data); +} + +/** + * sw_dis_multi_queue - disable transmit multiple queues + * @hw: The hardware instance. + * @port: The port index. + * + * This routine disables the transmit multiple queues selection of the switch + * port. Only single transmit queue on the port. + */ +static void sw_dis_multi_queue(struct ksz_hw *hw, int port) +{ + port_cfg_prio(hw, port, 0); +} + +/** + * sw_init_prio - initialize switch priority + * @hw: The hardware instance. + * + * This routine initializes the switch QoS priority functions. + */ +static void sw_init_prio(struct ksz_hw *hw) +{ + int port; + int tos; + struct ksz_switch *sw = hw->ksz_switch; + + /* + * Init all the 802.1p tag priority value to be assigned to different + * priority queue. + */ + sw->p_802_1p[0] = 0; + sw->p_802_1p[1] = 0; + sw->p_802_1p[2] = 1; + sw->p_802_1p[3] = 1; + sw->p_802_1p[4] = 2; + sw->p_802_1p[5] = 2; + sw->p_802_1p[6] = 3; + sw->p_802_1p[7] = 3; + + /* + * Init all the DiffServ priority value to be assigned to priority + * queue 0. + */ + for (tos = 0; tos < DIFFSERV_ENTRIES; tos++) + sw->diffserv[tos] = 0; + + /* All QoS functions disabled. */ + for (port = 0; port < TOTAL_PORT_NUM; port++) { + sw_dis_multi_queue(hw, port); + sw_dis_diffserv(hw, port); + sw_dis_802_1p(hw, port); + sw_cfg_replace_vid(hw, port, 0); + + sw->port_cfg[port].port_prio = 0; + sw_cfg_port_based(hw, port, sw->port_cfg[port].port_prio); + } + sw_cfg_replace_null_vid(hw, 0); +} + +/** + * port_get_def_vid - get port default VID. + * @hw: The hardware instance. + * @port: The port index. + * @vid: Buffer to store the VID. + * + * This routine retrieves the default VID of the port. + */ +static void port_get_def_vid(struct ksz_hw *hw, int port, u16 *vid) +{ + u32 addr; + + PORT_CTRL_ADDR(port, addr); + addr += KS8842_PORT_CTRL_VID_OFFSET; + *vid = readw(hw->io + addr); +} + +/** + * sw_init_vlan - initialize switch VLAN + * @hw: The hardware instance. + * + * This routine initializes the VLAN function of the switch. + */ +static void sw_init_vlan(struct ksz_hw *hw) +{ + int port; + int entry; + struct ksz_switch *sw = hw->ksz_switch; + + /* Read 16 VLAN entries from device's VLAN table. */ + for (entry = 0; entry < VLAN_TABLE_ENTRIES; entry++) { + sw_r_vlan_table(hw, entry, + &sw->vlan_table[entry].vid, + &sw->vlan_table[entry].fid, + &sw->vlan_table[entry].member); + } + + for (port = 0; port < TOTAL_PORT_NUM; port++) { + port_get_def_vid(hw, port, &sw->port_cfg[port].vid); + sw->port_cfg[port].member = PORT_MASK; + } +} + +/** + * sw_cfg_port_base_vlan - configure port-based VLAN membership + * @hw: The hardware instance. + * @port: The port index. + * @member: The port-based VLAN membership. + * + * This routine configures the port-based VLAN membership of the port. + */ +static void sw_cfg_port_base_vlan(struct ksz_hw *hw, int port, u8 member) +{ + u32 addr; + u8 data; + + PORT_CTRL_ADDR(port, addr); + addr += KS8842_PORT_CTRL_2_OFFSET; + + data = readb(hw->io + addr); + data &= ~PORT_VLAN_MEMBERSHIP; + data |= (member & PORT_MASK); + writeb(data, hw->io + addr); + + hw->ksz_switch->port_cfg[port].member = member; +} + +/** + * sw_set_addr - configure switch MAC address + * @hw: The hardware instance. + * @mac_addr: The MAC address. + * + * This function configures the MAC address of the switch. + */ +static void sw_set_addr(struct ksz_hw *hw, u8 *mac_addr) +{ + int i; + + for (i = 0; i < 6; i += 2) { + writeb(mac_addr[i], hw->io + KS8842_MAC_ADDR_0_OFFSET + i); + writeb(mac_addr[1 + i], hw->io + KS8842_MAC_ADDR_1_OFFSET + i); + } +} + +/** + * sw_set_global_ctrl - set switch global control + * @hw: The hardware instance. + * + * This routine sets the global control of the switch function. + */ +static void sw_set_global_ctrl(struct ksz_hw *hw) +{ + u16 data; + + /* Enable switch MII flow control. */ + data = readw(hw->io + KS8842_SWITCH_CTRL_3_OFFSET); + data |= SWITCH_FLOW_CTRL; + writew(data, hw->io + KS8842_SWITCH_CTRL_3_OFFSET); + + data = readw(hw->io + KS8842_SWITCH_CTRL_1_OFFSET); + + /* Enable aggressive back off algorithm in half duplex mode. */ + data |= SWITCH_AGGR_BACKOFF; + + /* Enable automatic fast aging when link changed detected. */ + data |= SWITCH_AGING_ENABLE; + data |= SWITCH_LINK_AUTO_AGING; + + if (hw->overrides & FAST_AGING) + data |= SWITCH_FAST_AGING; + else + data &= ~SWITCH_FAST_AGING; + writew(data, hw->io + KS8842_SWITCH_CTRL_1_OFFSET); + + data = readw(hw->io + KS8842_SWITCH_CTRL_2_OFFSET); + + /* Enable no excessive collision drop. */ + data |= NO_EXC_COLLISION_DROP; + writew(data, hw->io + KS8842_SWITCH_CTRL_2_OFFSET); +} + +enum { + STP_STATE_DISABLED = 0, + STP_STATE_LISTENING, + STP_STATE_LEARNING, + STP_STATE_FORWARDING, + STP_STATE_BLOCKED, + STP_STATE_SIMPLE +}; + +/** + * port_set_stp_state - configure port spanning tree state + * @hw: The hardware instance. + * @port: The port index. + * @state: The spanning tree state. + * + * This routine configures the spanning tree state of the port. + */ +static void port_set_stp_state(struct ksz_hw *hw, int port, int state) +{ + u16 data; + + port_r16(hw, port, KS8842_PORT_CTRL_2_OFFSET, &data); + switch (state) { + case STP_STATE_DISABLED: + data &= ~(PORT_TX_ENABLE | PORT_RX_ENABLE); + data |= PORT_LEARN_DISABLE; + break; + case STP_STATE_LISTENING: +/* + * No need to turn on transmit because of port direct mode. + * Turning on receive is required if static MAC table is not setup. + */ + data &= ~PORT_TX_ENABLE; + data |= PORT_RX_ENABLE; + data |= PORT_LEARN_DISABLE; + break; + case STP_STATE_LEARNING: + data &= ~PORT_TX_ENABLE; + data |= PORT_RX_ENABLE; + data &= ~PORT_LEARN_DISABLE; + break; + case STP_STATE_FORWARDING: + data |= (PORT_TX_ENABLE | PORT_RX_ENABLE); + data &= ~PORT_LEARN_DISABLE; + break; + case STP_STATE_BLOCKED: +/* + * Need to setup static MAC table with override to keep receiving BPDU + * messages. See sw_init_stp routine. + */ + data &= ~(PORT_TX_ENABLE | PORT_RX_ENABLE); + data |= PORT_LEARN_DISABLE; + break; + case STP_STATE_SIMPLE: + data |= (PORT_TX_ENABLE | PORT_RX_ENABLE); + data |= PORT_LEARN_DISABLE; + break; + } + port_w16(hw, port, KS8842_PORT_CTRL_2_OFFSET, data); + hw->ksz_switch->port_cfg[port].stp_state = state; +} + +#define STP_ENTRY 0 +#define BROADCAST_ENTRY 1 +#define BRIDGE_ADDR_ENTRY 2 +#define IPV6_ADDR_ENTRY 3 + +/** + * sw_clr_sta_mac_table - clear static MAC table + * @hw: The hardware instance. + * + * This routine clears the static MAC table. + */ +static void sw_clr_sta_mac_table(struct ksz_hw *hw) +{ + struct ksz_mac_table *entry; + int i; + + for (i = 0; i < STATIC_MAC_TABLE_ENTRIES; i++) { + entry = &hw->ksz_switch->mac_table[i]; + sw_w_sta_mac_table(hw, i, + entry->mac_addr, entry->ports, + entry->override, 0, + entry->use_fid, entry->fid); + } +} + +/** + * sw_init_stp - initialize switch spanning tree support + * @hw: The hardware instance. + * + * This routine initializes the spanning tree support of the switch. + */ +static void sw_init_stp(struct ksz_hw *hw) +{ + struct ksz_mac_table *entry; + + entry = &hw->ksz_switch->mac_table[STP_ENTRY]; + entry->mac_addr[0] = 0x01; + entry->mac_addr[1] = 0x80; + entry->mac_addr[2] = 0xC2; + entry->mac_addr[3] = 0x00; + entry->mac_addr[4] = 0x00; + entry->mac_addr[5] = 0x00; + entry->ports = HOST_MASK; + entry->override = 1; + entry->valid = 1; + sw_w_sta_mac_table(hw, STP_ENTRY, + entry->mac_addr, entry->ports, + entry->override, entry->valid, + entry->use_fid, entry->fid); +} + +/** + * sw_block_addr - block certain packets from the host port + * @hw: The hardware instance. + * + * This routine blocks certain packets from reaching to the host port. + */ +static void sw_block_addr(struct ksz_hw *hw) +{ + struct ksz_mac_table *entry; + int i; + + for (i = BROADCAST_ENTRY; i <= IPV6_ADDR_ENTRY; i++) { + entry = &hw->ksz_switch->mac_table[i]; + entry->valid = 0; + sw_w_sta_mac_table(hw, i, + entry->mac_addr, entry->ports, + entry->override, entry->valid, + entry->use_fid, entry->fid); + } +} + +static inline void hw_r_phy_ctrl(struct ksz_hw *hw, int phy, u16 *data) +{ + *data = readw(hw->io + phy + KS884X_PHY_CTRL_OFFSET); +} + +static inline void hw_w_phy_ctrl(struct ksz_hw *hw, int phy, u16 data) +{ + writew(data, hw->io + phy + KS884X_PHY_CTRL_OFFSET); +} + +/** + * hw_r_phy - read data from PHY register + * @hw: The hardware instance. + * @port: Port to read. + * @reg: PHY register to read. + * @val: Buffer to store the read data. + * + * This routine reads data from the PHY register. + */ +static void hw_r_phy(struct ksz_hw *hw, int port, u16 reg, u16 *val) +{ + int phy; + + phy = KS884X_PHY_1_CTRL_OFFSET + port * PHY_CTRL_INTERVAL + reg; + *val = readw(hw->io + phy); +} + +/** + * hw_w_phy - write data to PHY register + * @hw: The hardware instance. + * @port: Port to write. + * @reg: PHY register to write. + * @val: Word data to write. + * + * This routine writes data to the PHY register. + */ +static void hw_w_phy(struct ksz_hw *hw, int port, u16 reg, u16 val) +{ + int phy; + + phy = KS884X_PHY_1_CTRL_OFFSET + port * PHY_CTRL_INTERVAL + reg; + writew(val, hw->io + phy); +} + +/* + * EEPROM access functions + */ + +#define AT93C_CODE 0 +#define AT93C_WR_OFF 0x00 +#define AT93C_WR_ALL 0x10 +#define AT93C_ER_ALL 0x20 +#define AT93C_WR_ON 0x30 + +#define AT93C_WRITE 1 +#define AT93C_READ 2 +#define AT93C_ERASE 3 + +#define EEPROM_DELAY 4 + +static inline void drop_gpio(struct ksz_hw *hw, u8 gpio) +{ + u16 data; + + data = readw(hw->io + KS884X_EEPROM_CTRL_OFFSET); + data &= ~gpio; + writew(data, hw->io + KS884X_EEPROM_CTRL_OFFSET); +} + +static inline void raise_gpio(struct ksz_hw *hw, u8 gpio) +{ + u16 data; + + data = readw(hw->io + KS884X_EEPROM_CTRL_OFFSET); + data |= gpio; + writew(data, hw->io + KS884X_EEPROM_CTRL_OFFSET); +} + +static inline u8 state_gpio(struct ksz_hw *hw, u8 gpio) +{ + u16 data; + + data = readw(hw->io + KS884X_EEPROM_CTRL_OFFSET); + return (u8)(data & gpio); +} + +static void eeprom_clk(struct ksz_hw *hw) +{ + raise_gpio(hw, EEPROM_SERIAL_CLOCK); + udelay(EEPROM_DELAY); + drop_gpio(hw, EEPROM_SERIAL_CLOCK); + udelay(EEPROM_DELAY); +} + +static u16 spi_r(struct ksz_hw *hw) +{ + int i; + u16 temp = 0; + + for (i = 15; i >= 0; i--) { + raise_gpio(hw, EEPROM_SERIAL_CLOCK); + udelay(EEPROM_DELAY); + + temp |= (state_gpio(hw, EEPROM_DATA_IN)) ? 1 << i : 0; + + drop_gpio(hw, EEPROM_SERIAL_CLOCK); + udelay(EEPROM_DELAY); + } + return temp; +} + +static void spi_w(struct ksz_hw *hw, u16 data) +{ + int i; + + for (i = 15; i >= 0; i--) { + (data & (0x01 << i)) ? raise_gpio(hw, EEPROM_DATA_OUT) : + drop_gpio(hw, EEPROM_DATA_OUT); + eeprom_clk(hw); + } +} + +static void spi_reg(struct ksz_hw *hw, u8 data, u8 reg) +{ + int i; + + /* Initial start bit */ + raise_gpio(hw, EEPROM_DATA_OUT); + eeprom_clk(hw); + + /* AT93C operation */ + for (i = 1; i >= 0; i--) { + (data & (0x01 << i)) ? raise_gpio(hw, EEPROM_DATA_OUT) : + drop_gpio(hw, EEPROM_DATA_OUT); + eeprom_clk(hw); + } + + /* Address location */ + for (i = 5; i >= 0; i--) { + (reg & (0x01 << i)) ? raise_gpio(hw, EEPROM_DATA_OUT) : + drop_gpio(hw, EEPROM_DATA_OUT); + eeprom_clk(hw); + } +} + +#define EEPROM_DATA_RESERVED 0 +#define EEPROM_DATA_MAC_ADDR_0 1 +#define EEPROM_DATA_MAC_ADDR_1 2 +#define EEPROM_DATA_MAC_ADDR_2 3 +#define EEPROM_DATA_SUBSYS_ID 4 +#define EEPROM_DATA_SUBSYS_VEN_ID 5 +#define EEPROM_DATA_PM_CAP 6 + +/* User defined EEPROM data */ +#define EEPROM_DATA_OTHER_MAC_ADDR 9 + +/** + * eeprom_read - read from AT93C46 EEPROM + * @hw: The hardware instance. + * @reg: The register offset. + * + * This function reads a word from the AT93C46 EEPROM. + * + * Return the data value. + */ +static u16 eeprom_read(struct ksz_hw *hw, u8 reg) +{ + u16 data; + + raise_gpio(hw, EEPROM_ACCESS_ENABLE | EEPROM_CHIP_SELECT); + + spi_reg(hw, AT93C_READ, reg); + data = spi_r(hw); + + drop_gpio(hw, EEPROM_ACCESS_ENABLE | EEPROM_CHIP_SELECT); + + return data; +} + +/** + * eeprom_write - write to AT93C46 EEPROM + * @hw: The hardware instance. + * @reg: The register offset. + * @data: The data value. + * + * This procedure writes a word to the AT93C46 EEPROM. + */ +static void eeprom_write(struct ksz_hw *hw, u8 reg, u16 data) +{ + int timeout; + + raise_gpio(hw, EEPROM_ACCESS_ENABLE | EEPROM_CHIP_SELECT); + + /* Enable write. */ + spi_reg(hw, AT93C_CODE, AT93C_WR_ON); + drop_gpio(hw, EEPROM_CHIP_SELECT); + udelay(1); + + /* Erase the register. */ + raise_gpio(hw, EEPROM_CHIP_SELECT); + spi_reg(hw, AT93C_ERASE, reg); + drop_gpio(hw, EEPROM_CHIP_SELECT); + udelay(1); + + /* Check operation complete. */ + raise_gpio(hw, EEPROM_CHIP_SELECT); + timeout = 8; + mdelay(2); + do { + mdelay(1); + } while (!state_gpio(hw, EEPROM_DATA_IN) && --timeout); + drop_gpio(hw, EEPROM_CHIP_SELECT); + udelay(1); + + /* Write the register. */ + raise_gpio(hw, EEPROM_CHIP_SELECT); + spi_reg(hw, AT93C_WRITE, reg); + spi_w(hw, data); + drop_gpio(hw, EEPROM_CHIP_SELECT); + udelay(1); + + /* Check operation complete. */ + raise_gpio(hw, EEPROM_CHIP_SELECT); + timeout = 8; + mdelay(2); + do { + mdelay(1); + } while (!state_gpio(hw, EEPROM_DATA_IN) && --timeout); + drop_gpio(hw, EEPROM_CHIP_SELECT); + udelay(1); + + /* Disable write. */ + raise_gpio(hw, EEPROM_CHIP_SELECT); + spi_reg(hw, AT93C_CODE, AT93C_WR_OFF); + + drop_gpio(hw, EEPROM_ACCESS_ENABLE | EEPROM_CHIP_SELECT); +} + +/* + * Link detection routines + */ + +static u16 advertised_flow_ctrl(struct ksz_port *port, u16 ctrl) +{ + ctrl &= ~PORT_AUTO_NEG_SYM_PAUSE; + switch (port->flow_ctrl) { + case PHY_FLOW_CTRL: + ctrl |= PORT_AUTO_NEG_SYM_PAUSE; + break; + /* Not supported. */ + case PHY_TX_ONLY: + case PHY_RX_ONLY: + default: + break; + } + return ctrl; +} + +static void set_flow_ctrl(struct ksz_hw *hw, int rx, int tx) +{ + u32 rx_cfg; + u32 tx_cfg; + + rx_cfg = hw->rx_cfg; + tx_cfg = hw->tx_cfg; + if (rx) + hw->rx_cfg |= DMA_RX_FLOW_ENABLE; + else + hw->rx_cfg &= ~DMA_RX_FLOW_ENABLE; + if (tx) + hw->tx_cfg |= DMA_TX_FLOW_ENABLE; + else + hw->tx_cfg &= ~DMA_TX_FLOW_ENABLE; + if (hw->enabled) { + if (rx_cfg != hw->rx_cfg) + writel(hw->rx_cfg, hw->io + KS_DMA_RX_CTRL); + if (tx_cfg != hw->tx_cfg) + writel(hw->tx_cfg, hw->io + KS_DMA_TX_CTRL); + } +} + +static void determine_flow_ctrl(struct ksz_hw *hw, struct ksz_port *port, + u16 local, u16 remote) +{ + int rx; + int tx; + + if (hw->overrides & PAUSE_FLOW_CTRL) + return; + + rx = tx = 0; + if (port->force_link) + rx = tx = 1; + if (remote & LPA_PAUSE_CAP) { + if (local & ADVERTISE_PAUSE_CAP) { + rx = tx = 1; + } else if ((remote & LPA_PAUSE_ASYM) && + (local & + (ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM)) == + ADVERTISE_PAUSE_ASYM) { + tx = 1; + } + } else if (remote & LPA_PAUSE_ASYM) { + if ((local & (ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM)) + == (ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM)) + rx = 1; + } + if (!hw->ksz_switch) + set_flow_ctrl(hw, rx, tx); +} + +static inline void port_cfg_change(struct ksz_hw *hw, struct ksz_port *port, + struct ksz_port_info *info, u16 link_status) +{ + if ((hw->features & HALF_DUPLEX_SIGNAL_BUG) && + !(hw->overrides & PAUSE_FLOW_CTRL)) { + u32 cfg = hw->tx_cfg; + + /* Disable flow control in the half duplex mode. */ + if (1 == info->duplex) + hw->tx_cfg &= ~DMA_TX_FLOW_ENABLE; + if (hw->enabled && cfg != hw->tx_cfg) + writel(hw->tx_cfg, hw->io + KS_DMA_TX_CTRL); + } +} + +/** + * port_get_link_speed - get current link status + * @port: The port instance. + * + * This routine reads PHY registers to determine the current link status of the + * switch ports. + */ +static void port_get_link_speed(struct ksz_port *port) +{ + uint interrupt; + struct ksz_port_info *info; + struct ksz_port_info *linked = NULL; + struct ksz_hw *hw = port->hw; + u16 data; + u16 status; + u8 local; + u8 remote; + int i; + int p; + + interrupt = hw_block_intr(hw); + + for (i = 0, p = port->first_port; i < port->port_cnt; i++, p++) { + info = &hw->port_info[p]; + port_r16(hw, p, KS884X_PORT_CTRL_4_OFFSET, &data); + port_r16(hw, p, KS884X_PORT_STATUS_OFFSET, &status); + + /* + * Link status is changing all the time even when there is no + * cable connection! + */ + remote = status & (PORT_AUTO_NEG_COMPLETE | + PORT_STATUS_LINK_GOOD); + local = (u8) data; + + /* No change to status. */ + if (local == info->advertised && remote == info->partner) + continue; + + info->advertised = local; + info->partner = remote; + if (status & PORT_STATUS_LINK_GOOD) { + + /* Remember the first linked port. */ + if (!linked) + linked = info; + + info->tx_rate = 10 * TX_RATE_UNIT; + if (status & PORT_STATUS_SPEED_100MBIT) + info->tx_rate = 100 * TX_RATE_UNIT; + + info->duplex = 1; + if (status & PORT_STATUS_FULL_DUPLEX) + info->duplex = 2; + + if (media_connected != info->state) { + hw_r_phy(hw, p, KS884X_PHY_AUTO_NEG_OFFSET, + &data); + hw_r_phy(hw, p, KS884X_PHY_REMOTE_CAP_OFFSET, + &status); + determine_flow_ctrl(hw, port, data, status); + if (hw->ksz_switch) { + port_cfg_back_pressure(hw, p, + (1 == info->duplex)); + } + port_cfg_change(hw, port, info, status); + } + info->state = media_connected; + } else { + /* Indicate the link just goes down. */ + if (media_disconnected != info->state) + hw->port_mib[p].link_down = 1; + + info->state = media_disconnected; + } + hw->port_mib[p].state = (u8) info->state; + } + + if (linked && media_disconnected == port->linked->state) + port->linked = linked; + + hw_restore_intr(hw, interrupt); +} + +#define PHY_RESET_TIMEOUT 10 + +/** + * port_set_link_speed - set port speed + * @port: The port instance. + * + * This routine sets the link speed of the switch ports. + */ +static void port_set_link_speed(struct ksz_port *port) +{ + struct ksz_hw *hw = port->hw; + u16 data; + u16 cfg; + u8 status; + int i; + int p; + + for (i = 0, p = port->first_port; i < port->port_cnt; i++, p++) { + port_r16(hw, p, KS884X_PORT_CTRL_4_OFFSET, &data); + port_r8(hw, p, KS884X_PORT_STATUS_OFFSET, &status); + + cfg = 0; + if (status & PORT_STATUS_LINK_GOOD) + cfg = data; + + data |= PORT_AUTO_NEG_ENABLE; + data = advertised_flow_ctrl(port, data); + + data |= PORT_AUTO_NEG_100BTX_FD | PORT_AUTO_NEG_100BTX | + PORT_AUTO_NEG_10BT_FD | PORT_AUTO_NEG_10BT; + + /* Check if manual configuration is specified by the user. */ + if (port->speed || port->duplex) { + if (10 == port->speed) + data &= ~(PORT_AUTO_NEG_100BTX_FD | + PORT_AUTO_NEG_100BTX); + else if (100 == port->speed) + data &= ~(PORT_AUTO_NEG_10BT_FD | + PORT_AUTO_NEG_10BT); + if (1 == port->duplex) + data &= ~(PORT_AUTO_NEG_100BTX_FD | + PORT_AUTO_NEG_10BT_FD); + else if (2 == port->duplex) + data &= ~(PORT_AUTO_NEG_100BTX | + PORT_AUTO_NEG_10BT); + } + if (data != cfg) { + data |= PORT_AUTO_NEG_RESTART; + port_w16(hw, p, KS884X_PORT_CTRL_4_OFFSET, data); + } + } +} + +/** + * port_force_link_speed - force port speed + * @port: The port instance. + * + * This routine forces the link speed of the switch ports. + */ +static void port_force_link_speed(struct ksz_port *port) +{ + struct ksz_hw *hw = port->hw; + u16 data; + int i; + int phy; + int p; + + for (i = 0, p = port->first_port; i < port->port_cnt; i++, p++) { + phy = KS884X_PHY_1_CTRL_OFFSET + p * PHY_CTRL_INTERVAL; + hw_r_phy_ctrl(hw, phy, &data); + + data &= ~BMCR_ANENABLE; + + if (10 == port->speed) + data &= ~BMCR_SPEED100; + else if (100 == port->speed) + data |= BMCR_SPEED100; + if (1 == port->duplex) + data &= ~BMCR_FULLDPLX; + else if (2 == port->duplex) + data |= BMCR_FULLDPLX; + hw_w_phy_ctrl(hw, phy, data); + } +} + +static void port_set_power_saving(struct ksz_port *port, int enable) +{ + struct ksz_hw *hw = port->hw; + int i; + int p; + + for (i = 0, p = port->first_port; i < port->port_cnt; i++, p++) + port_cfg(hw, p, + KS884X_PORT_CTRL_4_OFFSET, PORT_POWER_DOWN, enable); +} + +/* + * KSZ8841 power management functions + */ + +/** + * hw_chk_wol_pme_status - check PMEN pin + * @hw: The hardware instance. + * + * This function is used to check PMEN pin is asserted. + * + * Return 1 if PMEN pin is asserted; otherwise, 0. + */ +static int hw_chk_wol_pme_status(struct ksz_hw *hw) +{ + struct dev_info *hw_priv = container_of(hw, struct dev_info, hw); + struct pci_dev *pdev = hw_priv->pdev; + u16 data; + + if (!pdev->pm_cap) + return 0; + pci_read_config_word(pdev, pdev->pm_cap + PCI_PM_CTRL, &data); + return (data & PCI_PM_CTRL_PME_STATUS) == PCI_PM_CTRL_PME_STATUS; +} + +/** + * hw_clr_wol_pme_status - clear PMEN pin + * @hw: The hardware instance. + * + * This routine is used to clear PME_Status to deassert PMEN pin. + */ +static void hw_clr_wol_pme_status(struct ksz_hw *hw) +{ + struct dev_info *hw_priv = container_of(hw, struct dev_info, hw); + struct pci_dev *pdev = hw_priv->pdev; + u16 data; + + if (!pdev->pm_cap) + return; + + /* Clear PME_Status to deassert PMEN pin. */ + pci_read_config_word(pdev, pdev->pm_cap + PCI_PM_CTRL, &data); + data |= PCI_PM_CTRL_PME_STATUS; + pci_write_config_word(pdev, pdev->pm_cap + PCI_PM_CTRL, data); +} + +/** + * hw_cfg_wol_pme - enable or disable Wake-on-LAN + * @hw: The hardware instance. + * @set: The flag indicating whether to enable or disable. + * + * This routine is used to enable or disable Wake-on-LAN. + */ +static void hw_cfg_wol_pme(struct ksz_hw *hw, int set) +{ + struct dev_info *hw_priv = container_of(hw, struct dev_info, hw); + struct pci_dev *pdev = hw_priv->pdev; + u16 data; + + if (!pdev->pm_cap) + return; + pci_read_config_word(pdev, pdev->pm_cap + PCI_PM_CTRL, &data); + data &= ~PCI_PM_CTRL_STATE_MASK; + if (set) + data |= PCI_PM_CTRL_PME_ENABLE | PCI_D3hot; + else + data &= ~PCI_PM_CTRL_PME_ENABLE; + pci_write_config_word(pdev, pdev->pm_cap + PCI_PM_CTRL, data); +} + +/** + * hw_cfg_wol - configure Wake-on-LAN features + * @hw: The hardware instance. + * @frame: The pattern frame bit. + * @set: The flag indicating whether to enable or disable. + * + * This routine is used to enable or disable certain Wake-on-LAN features. + */ +static void hw_cfg_wol(struct ksz_hw *hw, u16 frame, int set) +{ + u16 data; + + data = readw(hw->io + KS8841_WOL_CTRL_OFFSET); + if (set) + data |= frame; + else + data &= ~frame; + writew(data, hw->io + KS8841_WOL_CTRL_OFFSET); +} + +/** + * hw_set_wol_frame - program Wake-on-LAN pattern + * @hw: The hardware instance. + * @i: The frame index. + * @mask_size: The size of the mask. + * @mask: Mask to ignore certain bytes in the pattern. + * @frame_size: The size of the frame. + * @pattern: The frame data. + * + * This routine is used to program Wake-on-LAN pattern. + */ +static void hw_set_wol_frame(struct ksz_hw *hw, int i, uint mask_size, + const u8 *mask, uint frame_size, const u8 *pattern) +{ + int bits; + int from; + int len; + int to; + u32 crc; + u8 data[64]; + u8 val = 0; + + if (frame_size > mask_size * 8) + frame_size = mask_size * 8; + if (frame_size > 64) + frame_size = 64; + + i *= 0x10; + writel(0, hw->io + KS8841_WOL_FRAME_BYTE0_OFFSET + i); + writel(0, hw->io + KS8841_WOL_FRAME_BYTE2_OFFSET + i); + + bits = len = from = to = 0; + do { + if (bits) { + if ((val & 1)) + data[to++] = pattern[from]; + val >>= 1; + ++from; + --bits; + } else { + val = mask[len]; + writeb(val, hw->io + KS8841_WOL_FRAME_BYTE0_OFFSET + i + + len); + ++len; + if (val) + bits = 8; + else + from += 8; + } + } while (from < (int) frame_size); + if (val) { + bits = mask[len - 1]; + val <<= (from % 8); + bits &= ~val; + writeb(bits, hw->io + KS8841_WOL_FRAME_BYTE0_OFFSET + i + len - + 1); + } + crc = ether_crc(to, data); + writel(crc, hw->io + KS8841_WOL_FRAME_CRC_OFFSET + i); +} + +/** + * hw_add_wol_arp - add ARP pattern + * @hw: The hardware instance. + * @ip_addr: The IPv4 address assigned to the device. + * + * This routine is used to add ARP pattern for waking up the host. + */ +static void hw_add_wol_arp(struct ksz_hw *hw, const u8 *ip_addr) +{ + static const u8 mask[6] = { 0x3F, 0xF0, 0x3F, 0x00, 0xC0, 0x03 }; + u8 pattern[42] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x06, + 0x00, 0x01, 0x08, 0x00, 0x06, 0x04, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 }; + + memcpy(&pattern[38], ip_addr, 4); + hw_set_wol_frame(hw, 3, 6, mask, 42, pattern); +} + +/** + * hw_add_wol_bcast - add broadcast pattern + * @hw: The hardware instance. + * + * This routine is used to add broadcast pattern for waking up the host. + */ +static void hw_add_wol_bcast(struct ksz_hw *hw) +{ + static const u8 mask[] = { 0x3F }; + static const u8 pattern[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + + hw_set_wol_frame(hw, 2, 1, mask, ETH_ALEN, pattern); +} + +/** + * hw_add_wol_mcast - add multicast pattern + * @hw: The hardware instance. + * + * This routine is used to add multicast pattern for waking up the host. + * + * It is assumed the multicast packet is the ICMPv6 neighbor solicitation used + * by IPv6 ping command. Note that multicast packets are filtred through the + * multicast hash table, so not all multicast packets can wake up the host. + */ +static void hw_add_wol_mcast(struct ksz_hw *hw) +{ + static const u8 mask[] = { 0x3F }; + u8 pattern[] = { 0x33, 0x33, 0xFF, 0x00, 0x00, 0x00 }; + + memcpy(&pattern[3], &hw->override_addr[3], 3); + hw_set_wol_frame(hw, 1, 1, mask, 6, pattern); +} + +/** + * hw_add_wol_ucast - add unicast pattern + * @hw: The hardware instance. + * + * This routine is used to add unicast pattern to wakeup the host. + * + * It is assumed the unicast packet is directed to the device, as the hardware + * can only receive them in normal case. + */ +static void hw_add_wol_ucast(struct ksz_hw *hw) +{ + static const u8 mask[] = { 0x3F }; + + hw_set_wol_frame(hw, 0, 1, mask, ETH_ALEN, hw->override_addr); +} + +/** + * hw_enable_wol - enable Wake-on-LAN + * @hw: The hardware instance. + * @wol_enable: The Wake-on-LAN settings. + * @net_addr: The IPv4 address assigned to the device. + * + * This routine is used to enable Wake-on-LAN depending on driver settings. + */ +static void hw_enable_wol(struct ksz_hw *hw, u32 wol_enable, const u8 *net_addr) +{ + hw_cfg_wol(hw, KS8841_WOL_MAGIC_ENABLE, (wol_enable & WAKE_MAGIC)); + hw_cfg_wol(hw, KS8841_WOL_FRAME0_ENABLE, (wol_enable & WAKE_UCAST)); + hw_add_wol_ucast(hw); + hw_cfg_wol(hw, KS8841_WOL_FRAME1_ENABLE, (wol_enable & WAKE_MCAST)); + hw_add_wol_mcast(hw); + hw_cfg_wol(hw, KS8841_WOL_FRAME2_ENABLE, (wol_enable & WAKE_BCAST)); + hw_cfg_wol(hw, KS8841_WOL_FRAME3_ENABLE, (wol_enable & WAKE_ARP)); + hw_add_wol_arp(hw, net_addr); +} + +/** + * hw_init - check driver is correct for the hardware + * @hw: The hardware instance. + * + * This function checks the hardware is correct for this driver and sets the + * hardware up for proper initialization. + * + * Return number of ports or 0 if not right. + */ +static int hw_init(struct ksz_hw *hw) +{ + int rc = 0; + u16 data; + u16 revision; + + /* Set bus speed to 125MHz. */ + writew(BUS_SPEED_125_MHZ, hw->io + KS884X_BUS_CTRL_OFFSET); + + /* Check KSZ884x chip ID. */ + data = readw(hw->io + KS884X_CHIP_ID_OFFSET); + + revision = (data & KS884X_REVISION_MASK) >> KS884X_REVISION_SHIFT; + data &= KS884X_CHIP_ID_MASK_41; + if (REG_CHIP_ID_41 == data) + rc = 1; + else if (REG_CHIP_ID_42 == data) + rc = 2; + else + return 0; + + /* Setup hardware features or bug workarounds. */ + if (revision <= 1) { + hw->features |= SMALL_PACKET_TX_BUG; + if (1 == rc) + hw->features |= HALF_DUPLEX_SIGNAL_BUG; + } + return rc; +} + +/** + * hw_reset - reset the hardware + * @hw: The hardware instance. + * + * This routine resets the hardware. + */ +static void hw_reset(struct ksz_hw *hw) +{ + writew(GLOBAL_SOFTWARE_RESET, hw->io + KS884X_GLOBAL_CTRL_OFFSET); + + /* Wait for device to reset. */ + mdelay(10); + + /* Write 0 to clear device reset. */ + writew(0, hw->io + KS884X_GLOBAL_CTRL_OFFSET); +} + +/** + * hw_setup - setup the hardware + * @hw: The hardware instance. + * + * This routine setup the hardware for proper operation. + */ +static void hw_setup(struct ksz_hw *hw) +{ +#if SET_DEFAULT_LED + u16 data; + + /* Change default LED mode. */ + data = readw(hw->io + KS8842_SWITCH_CTRL_5_OFFSET); + data &= ~LED_MODE; + data |= SET_DEFAULT_LED; + writew(data, hw->io + KS8842_SWITCH_CTRL_5_OFFSET); +#endif + + /* Setup transmit control. */ + hw->tx_cfg = (DMA_TX_PAD_ENABLE | DMA_TX_CRC_ENABLE | + (DMA_BURST_DEFAULT << DMA_BURST_SHIFT) | DMA_TX_ENABLE); + + /* Setup receive control. */ + hw->rx_cfg = (DMA_RX_BROADCAST | DMA_RX_UNICAST | + (DMA_BURST_DEFAULT << DMA_BURST_SHIFT) | DMA_RX_ENABLE); + hw->rx_cfg |= KS884X_DMA_RX_MULTICAST; + + /* Hardware cannot handle UDP packet in IP fragments. */ + hw->rx_cfg |= (DMA_RX_CSUM_TCP | DMA_RX_CSUM_IP); + + if (hw->all_multi) + hw->rx_cfg |= DMA_RX_ALL_MULTICAST; + if (hw->promiscuous) + hw->rx_cfg |= DMA_RX_PROMISCUOUS; +} + +/** + * hw_setup_intr - setup interrupt mask + * @hw: The hardware instance. + * + * This routine setup the interrupt mask for proper operation. + */ +static void hw_setup_intr(struct ksz_hw *hw) +{ + hw->intr_mask = KS884X_INT_MASK | KS884X_INT_RX_OVERRUN; +} + +static void ksz_check_desc_num(struct ksz_desc_info *info) +{ +#define MIN_DESC_SHIFT 2 + + int alloc = info->alloc; + int shift; + + shift = 0; + while (!(alloc & 1)) { + shift++; + alloc >>= 1; + } + if (alloc != 1 || shift < MIN_DESC_SHIFT) { + pr_alert("Hardware descriptor numbers not right!\n"); + while (alloc) { + shift++; + alloc >>= 1; + } + if (shift < MIN_DESC_SHIFT) + shift = MIN_DESC_SHIFT; + alloc = 1 << shift; + info->alloc = alloc; + } + info->mask = info->alloc - 1; +} + +static void hw_init_desc(struct ksz_desc_info *desc_info, int transmit) +{ + int i; + u32 phys = desc_info->ring_phys; + struct ksz_hw_desc *desc = desc_info->ring_virt; + struct ksz_desc *cur = desc_info->ring; + struct ksz_desc *previous = NULL; + + for (i = 0; i < desc_info->alloc; i++) { + cur->phw = desc++; + phys += desc_info->size; + previous = cur++; + previous->phw->next = cpu_to_le32(phys); + } + previous->phw->next = cpu_to_le32(desc_info->ring_phys); + previous->sw.buf.rx.end_of_ring = 1; + previous->phw->buf.data = cpu_to_le32(previous->sw.buf.data); + + desc_info->avail = desc_info->alloc; + desc_info->last = desc_info->next = 0; + + desc_info->cur = desc_info->ring; +} + +/** + * hw_set_desc_base - set descriptor base addresses + * @hw: The hardware instance. + * @tx_addr: The transmit descriptor base. + * @rx_addr: The receive descriptor base. + * + * This routine programs the descriptor base addresses after reset. + */ +static void hw_set_desc_base(struct ksz_hw *hw, u32 tx_addr, u32 rx_addr) +{ + /* Set base address of Tx/Rx descriptors. */ + writel(tx_addr, hw->io + KS_DMA_TX_ADDR); + writel(rx_addr, hw->io + KS_DMA_RX_ADDR); +} + +static void hw_reset_pkts(struct ksz_desc_info *info) +{ + info->cur = info->ring; + info->avail = info->alloc; + info->last = info->next = 0; +} + +static inline void hw_resume_rx(struct ksz_hw *hw) +{ + writel(DMA_START, hw->io + KS_DMA_RX_START); +} + +/** + * hw_start_rx - start receiving + * @hw: The hardware instance. + * + * This routine starts the receive function of the hardware. + */ +static void hw_start_rx(struct ksz_hw *hw) +{ + writel(hw->rx_cfg, hw->io + KS_DMA_RX_CTRL); + + /* Notify when the receive stops. */ + hw->intr_mask |= KS884X_INT_RX_STOPPED; + + writel(DMA_START, hw->io + KS_DMA_RX_START); + hw_ack_intr(hw, KS884X_INT_RX_STOPPED); + hw->rx_stop++; + + /* Variable overflows. */ + if (0 == hw->rx_stop) + hw->rx_stop = 2; +} + +/** + * hw_stop_rx - stop receiving + * @hw: The hardware instance. + * + * This routine stops the receive function of the hardware. + */ +static void hw_stop_rx(struct ksz_hw *hw) +{ + hw->rx_stop = 0; + hw_turn_off_intr(hw, KS884X_INT_RX_STOPPED); + writel((hw->rx_cfg & ~DMA_RX_ENABLE), hw->io + KS_DMA_RX_CTRL); +} + +/** + * hw_start_tx - start transmitting + * @hw: The hardware instance. + * + * This routine starts the transmit function of the hardware. + */ +static void hw_start_tx(struct ksz_hw *hw) +{ + writel(hw->tx_cfg, hw->io + KS_DMA_TX_CTRL); +} + +/** + * hw_stop_tx - stop transmitting + * @hw: The hardware instance. + * + * This routine stops the transmit function of the hardware. + */ +static void hw_stop_tx(struct ksz_hw *hw) +{ + writel((hw->tx_cfg & ~DMA_TX_ENABLE), hw->io + KS_DMA_TX_CTRL); +} + +/** + * hw_disable - disable hardware + * @hw: The hardware instance. + * + * This routine disables the hardware. + */ +static void hw_disable(struct ksz_hw *hw) +{ + hw_stop_rx(hw); + hw_stop_tx(hw); + hw->enabled = 0; +} + +/** + * hw_enable - enable hardware + * @hw: The hardware instance. + * + * This routine enables the hardware. + */ +static void hw_enable(struct ksz_hw *hw) +{ + hw_start_tx(hw); + hw_start_rx(hw); + hw->enabled = 1; +} + +/** + * hw_alloc_pkt - allocate enough descriptors for transmission + * @hw: The hardware instance. + * @length: The length of the packet. + * @physical: Number of descriptors required. + * + * This function allocates descriptors for transmission. + * + * Return 0 if not successful; 1 for buffer copy; or number of descriptors. + */ +static int hw_alloc_pkt(struct ksz_hw *hw, int length, int physical) +{ + /* Always leave one descriptor free. */ + if (hw->tx_desc_info.avail <= 1) + return 0; + + /* Allocate a descriptor for transmission and mark it current. */ + get_tx_pkt(&hw->tx_desc_info, &hw->tx_desc_info.cur); + hw->tx_desc_info.cur->sw.buf.tx.first_seg = 1; + + /* Keep track of number of transmit descriptors used so far. */ + ++hw->tx_int_cnt; + hw->tx_size += length; + + /* Cannot hold on too much data. */ + if (hw->tx_size >= MAX_TX_HELD_SIZE) + hw->tx_int_cnt = hw->tx_int_mask + 1; + + if (physical > hw->tx_desc_info.avail) + return 1; + + return hw->tx_desc_info.avail; +} + +/** + * hw_send_pkt - mark packet for transmission + * @hw: The hardware instance. + * + * This routine marks the packet for transmission in PCI version. + */ +static void hw_send_pkt(struct ksz_hw *hw) +{ + struct ksz_desc *cur = hw->tx_desc_info.cur; + + cur->sw.buf.tx.last_seg = 1; + + /* Interrupt only after specified number of descriptors used. */ + if (hw->tx_int_cnt > hw->tx_int_mask) { + cur->sw.buf.tx.intr = 1; + hw->tx_int_cnt = 0; + hw->tx_size = 0; + } + + /* KSZ8842 supports port directed transmission. */ + cur->sw.buf.tx.dest_port = hw->dst_ports; + + release_desc(cur); + + writel(0, hw->io + KS_DMA_TX_START); +} + +static int empty_addr(u8 *addr) +{ + u32 *addr1 = (u32 *) addr; + u16 *addr2 = (u16 *) &addr[4]; + + return 0 == *addr1 && 0 == *addr2; +} + +/** + * hw_set_addr - set MAC address + * @hw: The hardware instance. + * + * This routine programs the MAC address of the hardware when the address is + * overridden. + */ +static void hw_set_addr(struct ksz_hw *hw) +{ + int i; + + for (i = 0; i < ETH_ALEN; i++) + writeb(hw->override_addr[MAC_ADDR_ORDER(i)], + hw->io + KS884X_ADDR_0_OFFSET + i); + + sw_set_addr(hw, hw->override_addr); +} + +/** + * hw_read_addr - read MAC address + * @hw: The hardware instance. + * + * This routine retrieves the MAC address of the hardware. + */ +static void hw_read_addr(struct ksz_hw *hw) +{ + int i; + + for (i = 0; i < ETH_ALEN; i++) + hw->perm_addr[MAC_ADDR_ORDER(i)] = readb(hw->io + + KS884X_ADDR_0_OFFSET + i); + + if (!hw->mac_override) { + memcpy(hw->override_addr, hw->perm_addr, ETH_ALEN); + if (empty_addr(hw->override_addr)) { + memcpy(hw->perm_addr, DEFAULT_MAC_ADDRESS, ETH_ALEN); + memcpy(hw->override_addr, DEFAULT_MAC_ADDRESS, + ETH_ALEN); + hw->override_addr[5] += hw->id; + hw_set_addr(hw); + } + } +} + +static void hw_ena_add_addr(struct ksz_hw *hw, int index, u8 *mac_addr) +{ + int i; + u32 mac_addr_lo; + u32 mac_addr_hi; + + mac_addr_hi = 0; + for (i = 0; i < 2; i++) { + mac_addr_hi <<= 8; + mac_addr_hi |= mac_addr[i]; + } + mac_addr_hi |= ADD_ADDR_ENABLE; + mac_addr_lo = 0; + for (i = 2; i < 6; i++) { + mac_addr_lo <<= 8; + mac_addr_lo |= mac_addr[i]; + } + index *= ADD_ADDR_INCR; + + writel(mac_addr_lo, hw->io + index + KS_ADD_ADDR_0_LO); + writel(mac_addr_hi, hw->io + index + KS_ADD_ADDR_0_HI); +} + +static void hw_set_add_addr(struct ksz_hw *hw) +{ + int i; + + for (i = 0; i < ADDITIONAL_ENTRIES; i++) { + if (empty_addr(hw->address[i])) + writel(0, hw->io + ADD_ADDR_INCR * i + + KS_ADD_ADDR_0_HI); + else + hw_ena_add_addr(hw, i, hw->address[i]); + } +} + +static int hw_add_addr(struct ksz_hw *hw, const u8 *mac_addr) +{ + int i; + int j = ADDITIONAL_ENTRIES; + + if (ether_addr_equal(hw->override_addr, mac_addr)) + return 0; + for (i = 0; i < hw->addr_list_size; i++) { + if (ether_addr_equal(hw->address[i], mac_addr)) + return 0; + if (ADDITIONAL_ENTRIES == j && empty_addr(hw->address[i])) + j = i; + } + if (j < ADDITIONAL_ENTRIES) { + memcpy(hw->address[j], mac_addr, ETH_ALEN); + hw_ena_add_addr(hw, j, hw->address[j]); + return 0; + } + return -1; +} + +static int hw_del_addr(struct ksz_hw *hw, const u8 *mac_addr) +{ + int i; + + for (i = 0; i < hw->addr_list_size; i++) { + if (ether_addr_equal(hw->address[i], mac_addr)) { + eth_zero_addr(hw->address[i]); + writel(0, hw->io + ADD_ADDR_INCR * i + + KS_ADD_ADDR_0_HI); + return 0; + } + } + return -1; +} + +/** + * hw_clr_multicast - clear multicast addresses + * @hw: The hardware instance. + * + * This routine removes all multicast addresses set in the hardware. + */ +static void hw_clr_multicast(struct ksz_hw *hw) +{ + int i; + + for (i = 0; i < HW_MULTICAST_SIZE; i++) { + hw->multi_bits[i] = 0; + + writeb(0, hw->io + KS884X_MULTICAST_0_OFFSET + i); + } +} + +/** + * hw_set_grp_addr - set multicast addresses + * @hw: The hardware instance. + * + * This routine programs multicast addresses for the hardware to accept those + * addresses. + */ +static void hw_set_grp_addr(struct ksz_hw *hw) +{ + int i; + int index; + int position; + int value; + + memset(hw->multi_bits, 0, sizeof(u8) * HW_MULTICAST_SIZE); + + for (i = 0; i < hw->multi_list_size; i++) { + position = (ether_crc(6, hw->multi_list[i]) >> 26) & 0x3f; + index = position >> 3; + value = 1 << (position & 7); + hw->multi_bits[index] |= (u8) value; + } + + for (i = 0; i < HW_MULTICAST_SIZE; i++) + writeb(hw->multi_bits[i], hw->io + KS884X_MULTICAST_0_OFFSET + + i); +} + +/** + * hw_set_multicast - enable or disable all multicast receiving + * @hw: The hardware instance. + * @multicast: To turn on or off the all multicast feature. + * + * This routine enables/disables the hardware to accept all multicast packets. + */ +static void hw_set_multicast(struct ksz_hw *hw, u8 multicast) +{ + /* Stop receiving for reconfiguration. */ + hw_stop_rx(hw); + + if (multicast) + hw->rx_cfg |= DMA_RX_ALL_MULTICAST; + else + hw->rx_cfg &= ~DMA_RX_ALL_MULTICAST; + + if (hw->enabled) + hw_start_rx(hw); +} + +/** + * hw_set_promiscuous - enable or disable promiscuous receiving + * @hw: The hardware instance. + * @prom: To turn on or off the promiscuous feature. + * + * This routine enables/disables the hardware to accept all packets. + */ +static void hw_set_promiscuous(struct ksz_hw *hw, u8 prom) +{ + /* Stop receiving for reconfiguration. */ + hw_stop_rx(hw); + + if (prom) + hw->rx_cfg |= DMA_RX_PROMISCUOUS; + else + hw->rx_cfg &= ~DMA_RX_PROMISCUOUS; + + if (hw->enabled) + hw_start_rx(hw); +} + +/** + * sw_enable - enable the switch + * @hw: The hardware instance. + * @enable: The flag to enable or disable the switch + * + * This routine is used to enable/disable the switch in KSZ8842. + */ +static void sw_enable(struct ksz_hw *hw, int enable) +{ + int port; + + for (port = 0; port < SWITCH_PORT_NUM; port++) { + if (hw->dev_count > 1) { + /* Set port-base vlan membership with host port. */ + sw_cfg_port_base_vlan(hw, port, + HOST_MASK | (1 << port)); + port_set_stp_state(hw, port, STP_STATE_DISABLED); + } else { + sw_cfg_port_base_vlan(hw, port, PORT_MASK); + port_set_stp_state(hw, port, STP_STATE_FORWARDING); + } + } + if (hw->dev_count > 1) + port_set_stp_state(hw, SWITCH_PORT_NUM, STP_STATE_SIMPLE); + else + port_set_stp_state(hw, SWITCH_PORT_NUM, STP_STATE_FORWARDING); + + if (enable) + enable = KS8842_START; + writew(enable, hw->io + KS884X_CHIP_ID_OFFSET); +} + +/** + * sw_setup - setup the switch + * @hw: The hardware instance. + * + * This routine setup the hardware switch engine for default operation. + */ +static void sw_setup(struct ksz_hw *hw) +{ + int port; + + sw_set_global_ctrl(hw); + + /* Enable switch broadcast storm protection at 10% percent rate. */ + sw_init_broad_storm(hw); + hw_cfg_broad_storm(hw, BROADCAST_STORM_PROTECTION_RATE); + for (port = 0; port < SWITCH_PORT_NUM; port++) + sw_ena_broad_storm(hw, port); + + sw_init_prio(hw); + + sw_init_mirror(hw); + + sw_init_prio_rate(hw); + + sw_init_vlan(hw); + + if (hw->features & STP_SUPPORT) + sw_init_stp(hw); + if (!sw_chk(hw, KS8842_SWITCH_CTRL_1_OFFSET, + SWITCH_TX_FLOW_CTRL | SWITCH_RX_FLOW_CTRL)) + hw->overrides |= PAUSE_FLOW_CTRL; + sw_enable(hw, 1); +} + +/** + * ksz_start_timer - start kernel timer + * @info: Kernel timer information. + * @time: The time tick. + * + * This routine starts the kernel timer after the specified time tick. + */ +static void ksz_start_timer(struct ksz_timer_info *info, int time) +{ + info->cnt = 0; + info->timer.expires = jiffies + time; + add_timer(&info->timer); + + /* infinity */ + info->max = -1; +} + +/** + * ksz_stop_timer - stop kernel timer + * @info: Kernel timer information. + * + * This routine stops the kernel timer. + */ +static void ksz_stop_timer(struct ksz_timer_info *info) +{ + if (info->max) { + info->max = 0; + timer_delete_sync(&info->timer); + } +} + +static void ksz_init_timer(struct ksz_timer_info *info, int period, + void (*function)(struct timer_list *)) +{ + info->max = 0; + info->period = period; + timer_setup(&info->timer, function, 0); +} + +static void ksz_update_timer(struct ksz_timer_info *info) +{ + ++info->cnt; + if (info->max > 0) { + if (info->cnt < info->max) { + info->timer.expires = jiffies + info->period; + add_timer(&info->timer); + } else + info->max = 0; + } else if (info->max < 0) { + info->timer.expires = jiffies + info->period; + add_timer(&info->timer); + } +} + +/** + * ksz_alloc_soft_desc - allocate software descriptors + * @desc_info: Descriptor information structure. + * @transmit: Indication that descriptors are for transmit. + * + * This local function allocates software descriptors for manipulation in + * memory. + * + * Return 0 if successful. + */ +static int ksz_alloc_soft_desc(struct ksz_desc_info *desc_info, int transmit) +{ + desc_info->ring = kzalloc_objs(struct ksz_desc, desc_info->alloc); + if (!desc_info->ring) + return 1; + hw_init_desc(desc_info, transmit); + return 0; +} + +/** + * ksz_alloc_desc - allocate hardware descriptors + * @adapter: Adapter information structure. + * + * This local function allocates hardware descriptors for receiving and + * transmitting. + * + * Return 0 if successful. + */ +static int ksz_alloc_desc(struct dev_info *adapter) +{ + struct ksz_hw *hw = &adapter->hw; + int offset; + + /* Allocate memory for RX & TX descriptors. */ + adapter->desc_pool.alloc_size = + hw->rx_desc_info.size * hw->rx_desc_info.alloc + + hw->tx_desc_info.size * hw->tx_desc_info.alloc + + DESC_ALIGNMENT; + + adapter->desc_pool.alloc_virt = + dma_alloc_coherent(&adapter->pdev->dev, + adapter->desc_pool.alloc_size, + &adapter->desc_pool.dma_addr, GFP_KERNEL); + if (adapter->desc_pool.alloc_virt == NULL) { + adapter->desc_pool.alloc_size = 0; + return 1; + } + + /* Align to the next cache line boundary. */ + offset = (((ulong) adapter->desc_pool.alloc_virt % DESC_ALIGNMENT) ? + (DESC_ALIGNMENT - + ((ulong) adapter->desc_pool.alloc_virt % DESC_ALIGNMENT)) : 0); + adapter->desc_pool.virt = adapter->desc_pool.alloc_virt + offset; + adapter->desc_pool.phys = adapter->desc_pool.dma_addr + offset; + + /* Allocate receive/transmit descriptors. */ + hw->rx_desc_info.ring_virt = (struct ksz_hw_desc *) + adapter->desc_pool.virt; + hw->rx_desc_info.ring_phys = adapter->desc_pool.phys; + offset = hw->rx_desc_info.alloc * hw->rx_desc_info.size; + hw->tx_desc_info.ring_virt = (struct ksz_hw_desc *) + (adapter->desc_pool.virt + offset); + hw->tx_desc_info.ring_phys = adapter->desc_pool.phys + offset; + + if (ksz_alloc_soft_desc(&hw->rx_desc_info, 0)) + return 1; + if (ksz_alloc_soft_desc(&hw->tx_desc_info, 1)) + return 1; + + return 0; +} + +/** + * free_dma_buf - release DMA buffer resources + * @adapter: Adapter information structure. + * @dma_buf: pointer to buf + * @direction: to or from device + * + * This routine is just a helper function to release the DMA buffer resources. + */ +static void free_dma_buf(struct dev_info *adapter, struct ksz_dma_buf *dma_buf, + int direction) +{ + dma_unmap_single(&adapter->pdev->dev, dma_buf->dma, dma_buf->len, + direction); + dev_kfree_skb(dma_buf->skb); + dma_buf->skb = NULL; + dma_buf->dma = 0; +} + +/** + * ksz_init_rx_buffers - initialize receive descriptors + * @adapter: Adapter information structure. + * + * This routine initializes DMA buffers for receiving. + */ +static void ksz_init_rx_buffers(struct dev_info *adapter) +{ + int i; + struct ksz_desc *desc; + struct ksz_dma_buf *dma_buf; + struct ksz_hw *hw = &adapter->hw; + struct ksz_desc_info *info = &hw->rx_desc_info; + + for (i = 0; i < hw->rx_desc_info.alloc; i++) { + get_rx_pkt(info, &desc); + + dma_buf = DMA_BUFFER(desc); + if (dma_buf->skb && dma_buf->len != adapter->mtu) + free_dma_buf(adapter, dma_buf, DMA_FROM_DEVICE); + dma_buf->len = adapter->mtu; + if (!dma_buf->skb) + dma_buf->skb = alloc_skb(dma_buf->len, GFP_ATOMIC); + if (dma_buf->skb && !dma_buf->dma) + dma_buf->dma = dma_map_single(&adapter->pdev->dev, + skb_tail_pointer(dma_buf->skb), + dma_buf->len, + DMA_FROM_DEVICE); + + /* Set descriptor. */ + set_rx_buf(desc, dma_buf->dma); + set_rx_len(desc, dma_buf->len); + release_desc(desc); + } +} + +/** + * ksz_alloc_mem - allocate memory for hardware descriptors + * @adapter: Adapter information structure. + * + * This function allocates memory for use by hardware descriptors for receiving + * and transmitting. + * + * Return 0 if successful. + */ +static int ksz_alloc_mem(struct dev_info *adapter) +{ + struct ksz_hw *hw = &adapter->hw; + + /* Determine the number of receive and transmit descriptors. */ + hw->rx_desc_info.alloc = NUM_OF_RX_DESC; + hw->tx_desc_info.alloc = NUM_OF_TX_DESC; + + /* Determine how many descriptors to skip transmit interrupt. */ + hw->tx_int_cnt = 0; + hw->tx_int_mask = NUM_OF_TX_DESC / 4; + if (hw->tx_int_mask > 8) + hw->tx_int_mask = 8; + while (hw->tx_int_mask) { + hw->tx_int_cnt++; + hw->tx_int_mask >>= 1; + } + if (hw->tx_int_cnt) { + hw->tx_int_mask = (1 << (hw->tx_int_cnt - 1)) - 1; + hw->tx_int_cnt = 0; + } + + /* Determine the descriptor size. */ + hw->rx_desc_info.size = + (((sizeof(struct ksz_hw_desc) + DESC_ALIGNMENT - 1) / + DESC_ALIGNMENT) * DESC_ALIGNMENT); + hw->tx_desc_info.size = + (((sizeof(struct ksz_hw_desc) + DESC_ALIGNMENT - 1) / + DESC_ALIGNMENT) * DESC_ALIGNMENT); + if (hw->rx_desc_info.size != sizeof(struct ksz_hw_desc)) + pr_alert("Hardware descriptor size not right!\n"); + ksz_check_desc_num(&hw->rx_desc_info); + ksz_check_desc_num(&hw->tx_desc_info); + + /* Allocate descriptors. */ + if (ksz_alloc_desc(adapter)) + return 1; + + return 0; +} + +/** + * ksz_free_desc - free software and hardware descriptors + * @adapter: Adapter information structure. + * + * This local routine frees the software and hardware descriptors allocated by + * ksz_alloc_desc(). + */ +static void ksz_free_desc(struct dev_info *adapter) +{ + struct ksz_hw *hw = &adapter->hw; + + /* Reset descriptor. */ + hw->rx_desc_info.ring_virt = NULL; + hw->tx_desc_info.ring_virt = NULL; + hw->rx_desc_info.ring_phys = 0; + hw->tx_desc_info.ring_phys = 0; + + /* Free memory. */ + if (adapter->desc_pool.alloc_virt) + dma_free_coherent(&adapter->pdev->dev, + adapter->desc_pool.alloc_size, + adapter->desc_pool.alloc_virt, + adapter->desc_pool.dma_addr); + + /* Reset resource pool. */ + adapter->desc_pool.alloc_size = 0; + adapter->desc_pool.alloc_virt = NULL; + + kfree(hw->rx_desc_info.ring); + hw->rx_desc_info.ring = NULL; + kfree(hw->tx_desc_info.ring); + hw->tx_desc_info.ring = NULL; +} + +/** + * ksz_free_buffers - free buffers used in the descriptors + * @adapter: Adapter information structure. + * @desc_info: Descriptor information structure. + * @direction: to or from device + * + * This local routine frees buffers used in the DMA buffers. + */ +static void ksz_free_buffers(struct dev_info *adapter, + struct ksz_desc_info *desc_info, int direction) +{ + int i; + struct ksz_dma_buf *dma_buf; + struct ksz_desc *desc = desc_info->ring; + + for (i = 0; i < desc_info->alloc; i++) { + dma_buf = DMA_BUFFER(desc); + if (dma_buf->skb) + free_dma_buf(adapter, dma_buf, direction); + desc++; + } +} + +/** + * ksz_free_mem - free all resources used by descriptors + * @adapter: Adapter information structure. + * + * This local routine frees all the resources allocated by ksz_alloc_mem(). + */ +static void ksz_free_mem(struct dev_info *adapter) +{ + /* Free transmit buffers. */ + ksz_free_buffers(adapter, &adapter->hw.tx_desc_info, DMA_TO_DEVICE); + + /* Free receive buffers. */ + ksz_free_buffers(adapter, &adapter->hw.rx_desc_info, DMA_FROM_DEVICE); + + /* Free descriptors. */ + ksz_free_desc(adapter); +} + +static void get_mib_counters(struct ksz_hw *hw, int first, int cnt, + u64 *counter) +{ + int i; + int mib; + int port; + struct ksz_port_mib *port_mib; + + memset(counter, 0, sizeof(u64) * TOTAL_PORT_COUNTER_NUM); + for (i = 0, port = first; i < cnt; i++, port++) { + port_mib = &hw->port_mib[port]; + for (mib = port_mib->mib_start; mib < hw->mib_cnt; mib++) + counter[mib] += port_mib->counter[mib]; + } +} + +/** + * send_packet - send packet + * @skb: Socket buffer. + * @dev: Network device. + * + * This routine is used to send a packet out to the network. + */ +static void send_packet(struct sk_buff *skb, struct net_device *dev) +{ + struct ksz_desc *desc; + struct ksz_desc *first; + struct dev_priv *priv = netdev_priv(dev); + struct dev_info *hw_priv = priv->adapter; + struct ksz_hw *hw = &hw_priv->hw; + struct ksz_desc_info *info = &hw->tx_desc_info; + struct ksz_dma_buf *dma_buf; + int len; + int last_frag = skb_shinfo(skb)->nr_frags; + + /* + * KSZ8842 with multiple device interfaces needs to be told which port + * to send. + */ + if (hw->dev_count > 1) + hw->dst_ports = 1 << priv->port.first_port; + + /* Hardware will pad the length to 60. */ + len = skb->len; + + /* Remember the very first descriptor. */ + first = info->cur; + desc = first; + + dma_buf = DMA_BUFFER(desc); + if (last_frag) { + int frag; + skb_frag_t *this_frag; + + dma_buf->len = skb_headlen(skb); + + dma_buf->dma = dma_map_single(&hw_priv->pdev->dev, skb->data, + dma_buf->len, DMA_TO_DEVICE); + set_tx_buf(desc, dma_buf->dma); + set_tx_len(desc, dma_buf->len); + + frag = 0; + do { + this_frag = &skb_shinfo(skb)->frags[frag]; + + /* Get a new descriptor. */ + get_tx_pkt(info, &desc); + + /* Keep track of descriptors used so far. */ + ++hw->tx_int_cnt; + + dma_buf = DMA_BUFFER(desc); + dma_buf->len = skb_frag_size(this_frag); + + dma_buf->dma = dma_map_single(&hw_priv->pdev->dev, + skb_frag_address(this_frag), + dma_buf->len, + DMA_TO_DEVICE); + set_tx_buf(desc, dma_buf->dma); + set_tx_len(desc, dma_buf->len); + + frag++; + if (frag == last_frag) + break; + + /* Do not release the last descriptor here. */ + release_desc(desc); + } while (1); + + /* current points to the last descriptor. */ + info->cur = desc; + + /* Release the first descriptor. */ + release_desc(first); + } else { + dma_buf->len = len; + + dma_buf->dma = dma_map_single(&hw_priv->pdev->dev, skb->data, + dma_buf->len, DMA_TO_DEVICE); + set_tx_buf(desc, dma_buf->dma); + set_tx_len(desc, dma_buf->len); + } + + if (skb->ip_summed == CHECKSUM_PARTIAL) { + (desc)->sw.buf.tx.csum_gen_tcp = 1; + (desc)->sw.buf.tx.csum_gen_udp = 1; + } + + /* + * The last descriptor holds the packet so that it can be returned to + * network subsystem after all descriptors are transmitted. + */ + dma_buf->skb = skb; + + hw_send_pkt(hw); + + /* Update transmit statistics. */ + dev->stats.tx_packets++; + dev->stats.tx_bytes += len; +} + +/** + * transmit_cleanup - clean up transmit descriptors + * @hw_priv: Network device. + * @normal: break if owned + * + * This routine is called to clean up the transmitted buffers. + */ +static void transmit_cleanup(struct dev_info *hw_priv, int normal) +{ + int last; + union desc_stat status; + struct ksz_hw *hw = &hw_priv->hw; + struct ksz_desc_info *info = &hw->tx_desc_info; + struct ksz_desc *desc; + struct ksz_dma_buf *dma_buf; + struct net_device *dev = NULL; + + spin_lock_irq(&hw_priv->hwlock); + last = info->last; + + while (info->avail < info->alloc) { + /* Get next descriptor which is not hardware owned. */ + desc = &info->ring[last]; + status.data = le32_to_cpu(desc->phw->ctrl.data); + if (status.tx.hw_owned) { + if (normal) + break; + else + reset_desc(desc, status); + } + + dma_buf = DMA_BUFFER(desc); + dma_unmap_single(&hw_priv->pdev->dev, dma_buf->dma, + dma_buf->len, DMA_TO_DEVICE); + + /* This descriptor contains the last buffer in the packet. */ + if (dma_buf->skb) { + dev = dma_buf->skb->dev; + + /* Release the packet back to network subsystem. */ + dev_kfree_skb_irq(dma_buf->skb); + dma_buf->skb = NULL; + } + + /* Free the transmitted descriptor. */ + last++; + last &= info->mask; + info->avail++; + } + info->last = last; + spin_unlock_irq(&hw_priv->hwlock); + + /* Notify the network subsystem that the packet has been sent. */ + if (dev) + netif_trans_update(dev); +} + +/** + * tx_done - transmit done processing + * @hw_priv: Network device. + * + * This routine is called when the transmit interrupt is triggered, indicating + * either a packet is sent successfully or there are transmit errors. + */ +static void tx_done(struct dev_info *hw_priv) +{ + struct ksz_hw *hw = &hw_priv->hw; + int port; + + transmit_cleanup(hw_priv, 1); + + for (port = 0; port < hw->dev_count; port++) { + struct net_device *dev = hw->port_info[port].pdev; + + if (netif_running(dev) && netif_queue_stopped(dev)) + netif_wake_queue(dev); + } +} + +static inline void copy_old_skb(struct sk_buff *old, struct sk_buff *skb) +{ + skb->dev = old->dev; + skb->protocol = old->protocol; + skb->ip_summed = old->ip_summed; + skb->csum = old->csum; + skb_set_network_header(skb, ETH_HLEN); + + dev_consume_skb_any(old); +} + +/** + * netdev_tx - send out packet + * @skb: Socket buffer. + * @dev: Network device. + * + * This function is used by the upper network layer to send out a packet. + * + * Return 0 if successful; otherwise an error code indicating failure. + */ +static netdev_tx_t netdev_tx(struct sk_buff *skb, struct net_device *dev) +{ + struct dev_priv *priv = netdev_priv(dev); + struct dev_info *hw_priv = priv->adapter; + struct ksz_hw *hw = &hw_priv->hw; + int left; + int num = 1; + int rc = 0; + + if (hw->features & SMALL_PACKET_TX_BUG) { + struct sk_buff *org_skb = skb; + + if (skb->len <= 48) { + if (skb_end_pointer(skb) - skb->data >= 50) { + memset(&skb->data[skb->len], 0, 50 - skb->len); + skb->len = 50; + } else { + skb = netdev_alloc_skb(dev, 50); + if (!skb) + return NETDEV_TX_BUSY; + memcpy(skb->data, org_skb->data, org_skb->len); + memset(&skb->data[org_skb->len], 0, + 50 - org_skb->len); + skb->len = 50; + copy_old_skb(org_skb, skb); + } + } + } + + spin_lock_irq(&hw_priv->hwlock); + + num = skb_shinfo(skb)->nr_frags + 1; + left = hw_alloc_pkt(hw, skb->len, num); + if (left) { + if (left < num || + (CHECKSUM_PARTIAL == skb->ip_summed && + skb->protocol == htons(ETH_P_IPV6))) { + struct sk_buff *org_skb = skb; + + skb = netdev_alloc_skb(dev, org_skb->len); + if (!skb) { + rc = NETDEV_TX_BUSY; + goto unlock; + } + skb_copy_and_csum_dev(org_skb, skb->data); + org_skb->ip_summed = CHECKSUM_NONE; + skb->len = org_skb->len; + copy_old_skb(org_skb, skb); + } + send_packet(skb, dev); + if (left <= num) + netif_stop_queue(dev); + } else { + /* Stop the transmit queue until packet is allocated. */ + netif_stop_queue(dev); + rc = NETDEV_TX_BUSY; + } +unlock: + spin_unlock_irq(&hw_priv->hwlock); + + return rc; +} + +/** + * netdev_tx_timeout - transmit timeout processing + * @dev: Network device. + * @txqueue: index of hanging queue + * + * This routine is called when the transmit timer expires. That indicates the + * hardware is not running correctly because transmit interrupts are not + * triggered to free up resources so that the transmit routine can continue + * sending out packets. The hardware is reset to correct the problem. + */ +static void netdev_tx_timeout(struct net_device *dev, unsigned int txqueue) +{ + static unsigned long last_reset; + + struct dev_priv *priv = netdev_priv(dev); + struct dev_info *hw_priv = priv->adapter; + struct ksz_hw *hw = &hw_priv->hw; + int port; + + if (hw->dev_count > 1) { + /* + * Only reset the hardware if time between calls is long + * enough. + */ + if (time_before_eq(jiffies, last_reset + dev->watchdog_timeo)) + hw_priv = NULL; + } + + last_reset = jiffies; + if (hw_priv) { + hw_dis_intr(hw); + hw_disable(hw); + + transmit_cleanup(hw_priv, 0); + hw_reset_pkts(&hw->rx_desc_info); + hw_reset_pkts(&hw->tx_desc_info); + ksz_init_rx_buffers(hw_priv); + + hw_reset(hw); + + hw_set_desc_base(hw, + hw->tx_desc_info.ring_phys, + hw->rx_desc_info.ring_phys); + hw_set_addr(hw); + if (hw->all_multi) + hw_set_multicast(hw, hw->all_multi); + else if (hw->multi_list_size) + hw_set_grp_addr(hw); + + if (hw->dev_count > 1) { + hw_set_add_addr(hw); + for (port = 0; port < SWITCH_PORT_NUM; port++) { + struct net_device *port_dev; + + port_set_stp_state(hw, port, + STP_STATE_DISABLED); + + port_dev = hw->port_info[port].pdev; + if (netif_running(port_dev)) + port_set_stp_state(hw, port, + STP_STATE_SIMPLE); + } + } + + hw_enable(hw); + hw_ena_intr(hw); + } + + netif_trans_update(dev); + netif_wake_queue(dev); +} + +static inline void csum_verified(struct sk_buff *skb) +{ + unsigned short protocol; + struct iphdr *iph; + + protocol = skb->protocol; + skb_reset_network_header(skb); + iph = (struct iphdr *) skb_network_header(skb); + if (protocol == htons(ETH_P_8021Q)) { + protocol = iph->tot_len; + skb_set_network_header(skb, VLAN_HLEN); + iph = (struct iphdr *) skb_network_header(skb); + } + if (protocol == htons(ETH_P_IP)) { + if (iph->protocol == IPPROTO_TCP) + skb->ip_summed = CHECKSUM_UNNECESSARY; + } +} + +static inline int rx_proc(struct net_device *dev, struct ksz_hw* hw, + struct ksz_desc *desc, union desc_stat status) +{ + int packet_len; + struct dev_priv *priv = netdev_priv(dev); + struct dev_info *hw_priv = priv->adapter; + struct ksz_dma_buf *dma_buf; + struct sk_buff *skb; + + /* Received length includes 4-byte CRC. */ + packet_len = status.rx.frame_len - 4; + + dma_buf = DMA_BUFFER(desc); + dma_sync_single_for_cpu(&hw_priv->pdev->dev, dma_buf->dma, + packet_len + 4, DMA_FROM_DEVICE); + + do { + /* skb->data != skb->head */ + skb = netdev_alloc_skb(dev, packet_len + 2); + if (!skb) { + dev->stats.rx_dropped++; + return -ENOMEM; + } + + /* + * Align socket buffer in 4-byte boundary for better + * performance. + */ + skb_reserve(skb, 2); + + skb_put_data(skb, dma_buf->skb->data, packet_len); + } while (0); + + skb->protocol = eth_type_trans(skb, dev); + + if (hw->rx_cfg & (DMA_RX_CSUM_UDP | DMA_RX_CSUM_TCP)) + csum_verified(skb); + + /* Update receive statistics. */ + dev->stats.rx_packets++; + dev->stats.rx_bytes += packet_len; + + /* Notify upper layer for received packet. */ + netif_rx(skb); + + return 0; +} + +static int dev_rcv_packets(struct dev_info *hw_priv) +{ + int next; + union desc_stat status; + struct ksz_hw *hw = &hw_priv->hw; + struct net_device *dev = hw->port_info[0].pdev; + struct ksz_desc_info *info = &hw->rx_desc_info; + int left = info->alloc; + struct ksz_desc *desc; + int received = 0; + + next = info->next; + while (left--) { + /* Get next descriptor which is not hardware owned. */ + desc = &info->ring[next]; + status.data = le32_to_cpu(desc->phw->ctrl.data); + if (status.rx.hw_owned) + break; + + /* Status valid only when last descriptor bit is set. */ + if (status.rx.last_desc && status.rx.first_desc) { + if (rx_proc(dev, hw, desc, status)) + goto release_packet; + received++; + } + +release_packet: + release_desc(desc); + next++; + next &= info->mask; + } + info->next = next; + + return received; +} + +static int port_rcv_packets(struct dev_info *hw_priv) +{ + int next; + union desc_stat status; + struct ksz_hw *hw = &hw_priv->hw; + struct net_device *dev = hw->port_info[0].pdev; + struct ksz_desc_info *info = &hw->rx_desc_info; + int left = info->alloc; + struct ksz_desc *desc; + int received = 0; + + next = info->next; + while (left--) { + /* Get next descriptor which is not hardware owned. */ + desc = &info->ring[next]; + status.data = le32_to_cpu(desc->phw->ctrl.data); + if (status.rx.hw_owned) + break; + + if (hw->dev_count > 1) { + /* Get received port number. */ + int p = HW_TO_DEV_PORT(status.rx.src_port); + + dev = hw->port_info[p].pdev; + if (!netif_running(dev)) + goto release_packet; + } + + /* Status valid only when last descriptor bit is set. */ + if (status.rx.last_desc && status.rx.first_desc) { + if (rx_proc(dev, hw, desc, status)) + goto release_packet; + received++; + } + +release_packet: + release_desc(desc); + next++; + next &= info->mask; + } + info->next = next; + + return received; +} + +static int dev_rcv_special(struct dev_info *hw_priv) +{ + int next; + union desc_stat status; + struct ksz_hw *hw = &hw_priv->hw; + struct net_device *dev = hw->port_info[0].pdev; + struct ksz_desc_info *info = &hw->rx_desc_info; + int left = info->alloc; + struct ksz_desc *desc; + int received = 0; + + next = info->next; + while (left--) { + /* Get next descriptor which is not hardware owned. */ + desc = &info->ring[next]; + status.data = le32_to_cpu(desc->phw->ctrl.data); + if (status.rx.hw_owned) + break; + + if (hw->dev_count > 1) { + /* Get received port number. */ + int p = HW_TO_DEV_PORT(status.rx.src_port); + + dev = hw->port_info[p].pdev; + if (!netif_running(dev)) + goto release_packet; + } + + /* Status valid only when last descriptor bit is set. */ + if (status.rx.last_desc && status.rx.first_desc) { + /* + * Receive without error. With receive errors + * disabled, packets with receive errors will be + * dropped, so no need to check the error bit. + */ + if (!status.rx.error || (status.data & + KS_DESC_RX_ERROR_COND) == + KS_DESC_RX_ERROR_TOO_LONG) { + if (rx_proc(dev, hw, desc, status)) + goto release_packet; + received++; + } else { + struct dev_priv *priv = netdev_priv(dev); + + /* Update receive error statistics. */ + priv->port.counter[OID_COUNTER_RCV_ERROR]++; + } + } + +release_packet: + release_desc(desc); + next++; + next &= info->mask; + } + info->next = next; + + return received; +} + +static void rx_proc_task(struct tasklet_struct *t) +{ + struct dev_info *hw_priv = from_tasklet(hw_priv, t, rx_tasklet); + struct ksz_hw *hw = &hw_priv->hw; + + if (!hw->enabled) + return; + if (unlikely(!hw_priv->dev_rcv(hw_priv))) { + + /* In case receive process is suspended because of overrun. */ + hw_resume_rx(hw); + + /* tasklets are interruptible. */ + spin_lock_irq(&hw_priv->hwlock); + hw_turn_on_intr(hw, KS884X_INT_RX_MASK); + spin_unlock_irq(&hw_priv->hwlock); + } else { + hw_ack_intr(hw, KS884X_INT_RX); + tasklet_schedule(&hw_priv->rx_tasklet); + } +} + +static void tx_proc_task(struct tasklet_struct *t) +{ + struct dev_info *hw_priv = from_tasklet(hw_priv, t, tx_tasklet); + struct ksz_hw *hw = &hw_priv->hw; + + hw_ack_intr(hw, KS884X_INT_TX_MASK); + + tx_done(hw_priv); + + /* tasklets are interruptible. */ + spin_lock_irq(&hw_priv->hwlock); + hw_turn_on_intr(hw, KS884X_INT_TX); + spin_unlock_irq(&hw_priv->hwlock); +} + +static inline void handle_rx_stop(struct ksz_hw *hw) +{ + /* Receive just has been stopped. */ + if (0 == hw->rx_stop) + hw->intr_mask &= ~KS884X_INT_RX_STOPPED; + else if (hw->rx_stop > 1) { + if (hw->enabled && (hw->rx_cfg & DMA_RX_ENABLE)) { + hw_start_rx(hw); + } else { + hw->intr_mask &= ~KS884X_INT_RX_STOPPED; + hw->rx_stop = 0; + } + } else + /* Receive just has been started. */ + hw->rx_stop++; +} + +/** + * netdev_intr - interrupt handling + * @irq: Interrupt number. + * @dev_id: Network device. + * + * This function is called by upper network layer to signal interrupt. + * + * Return IRQ_HANDLED if interrupt is handled. + */ +static irqreturn_t netdev_intr(int irq, void *dev_id) +{ + uint int_enable = 0; + struct net_device *dev = (struct net_device *) dev_id; + struct dev_priv *priv = netdev_priv(dev); + struct dev_info *hw_priv = priv->adapter; + struct ksz_hw *hw = &hw_priv->hw; + + spin_lock(&hw_priv->hwlock); + + hw_read_intr(hw, &int_enable); + + /* Not our interrupt! */ + if (!int_enable) { + spin_unlock(&hw_priv->hwlock); + return IRQ_NONE; + } + + do { + hw_ack_intr(hw, int_enable); + int_enable &= hw->intr_mask; + + if (unlikely(int_enable & KS884X_INT_TX_MASK)) { + hw_dis_intr_bit(hw, KS884X_INT_TX_MASK); + tasklet_schedule(&hw_priv->tx_tasklet); + } + + if (likely(int_enable & KS884X_INT_RX)) { + hw_dis_intr_bit(hw, KS884X_INT_RX); + tasklet_schedule(&hw_priv->rx_tasklet); + } + + if (unlikely(int_enable & KS884X_INT_RX_OVERRUN)) { + dev->stats.rx_fifo_errors++; + hw_resume_rx(hw); + } + + if (unlikely(int_enable & KS884X_INT_PHY)) { + struct ksz_port *port = &priv->port; + + hw->features |= LINK_INT_WORKING; + port_get_link_speed(port); + } + + if (unlikely(int_enable & KS884X_INT_RX_STOPPED)) { + handle_rx_stop(hw); + break; + } + + if (unlikely(int_enable & KS884X_INT_TX_STOPPED)) { + u32 data; + + hw->intr_mask &= ~KS884X_INT_TX_STOPPED; + pr_info("Tx stopped\n"); + data = readl(hw->io + KS_DMA_TX_CTRL); + if (!(data & DMA_TX_ENABLE)) + pr_info("Tx disabled\n"); + break; + } + } while (0); + + hw_ena_intr(hw); + + spin_unlock(&hw_priv->hwlock); + + return IRQ_HANDLED; +} + +/* + * Linux network device functions + */ + + +#ifdef CONFIG_NET_POLL_CONTROLLER +static void netdev_netpoll(struct net_device *dev) +{ + struct dev_priv *priv = netdev_priv(dev); + struct dev_info *hw_priv = priv->adapter; + + hw_dis_intr(&hw_priv->hw); + netdev_intr(dev->irq, dev); +} +#endif + +static void bridge_change(struct ksz_hw *hw) +{ + int port; + u8 member; + struct ksz_switch *sw = hw->ksz_switch; + + /* No ports in forwarding state. */ + if (!sw->member) { + port_set_stp_state(hw, SWITCH_PORT_NUM, STP_STATE_SIMPLE); + sw_block_addr(hw); + } + for (port = 0; port < SWITCH_PORT_NUM; port++) { + if (STP_STATE_FORWARDING == sw->port_cfg[port].stp_state) + member = HOST_MASK | sw->member; + else + member = HOST_MASK | (1 << port); + if (member != sw->port_cfg[port].member) + sw_cfg_port_base_vlan(hw, port, member); + } +} + +/** + * netdev_close - close network device + * @dev: Network device. + * + * This function process the close operation of network device. This is caused + * by the user command "ifconfig ethX down." + * + * Return 0 if successful; otherwise an error code indicating failure. + */ +static int netdev_close(struct net_device *dev) +{ + struct dev_priv *priv = netdev_priv(dev); + struct dev_info *hw_priv = priv->adapter; + struct ksz_port *port = &priv->port; + struct ksz_hw *hw = &hw_priv->hw; + int pi; + + netif_stop_queue(dev); + + ksz_stop_timer(&priv->monitor_timer_info); + + /* Need to shut the port manually in multiple device interfaces mode. */ + if (hw->dev_count > 1) { + port_set_stp_state(hw, port->first_port, STP_STATE_DISABLED); + + /* Port is closed. Need to change bridge setting. */ + if (hw->features & STP_SUPPORT) { + pi = 1 << port->first_port; + if (hw->ksz_switch->member & pi) { + hw->ksz_switch->member &= ~pi; + bridge_change(hw); + } + } + } + if (port->first_port > 0) + hw_del_addr(hw, dev->dev_addr); + if (!hw_priv->wol_enable) + port_set_power_saving(port, true); + + if (priv->multicast) + --hw->all_multi; + if (priv->promiscuous) + --hw->promiscuous; + + hw_priv->opened--; + if (!(hw_priv->opened)) { + ksz_stop_timer(&hw_priv->mib_timer_info); + flush_work(&hw_priv->mib_read); + + hw_dis_intr(hw); + hw_disable(hw); + hw_clr_multicast(hw); + + /* Delay for receive task to stop scheduling itself. */ + msleep(2000 / HZ); + + tasklet_kill(&hw_priv->rx_tasklet); + tasklet_kill(&hw_priv->tx_tasklet); + free_irq(dev->irq, hw_priv->dev); + + transmit_cleanup(hw_priv, 0); + hw_reset_pkts(&hw->rx_desc_info); + hw_reset_pkts(&hw->tx_desc_info); + + /* Clean out static MAC table when the switch is shutdown. */ + if (hw->features & STP_SUPPORT) + sw_clr_sta_mac_table(hw); + } + + return 0; +} + +static void hw_cfg_huge_frame(struct dev_info *hw_priv, struct ksz_hw *hw) +{ + if (hw->ksz_switch) { + u32 data; + + data = readw(hw->io + KS8842_SWITCH_CTRL_2_OFFSET); + if (hw->features & RX_HUGE_FRAME) + data |= SWITCH_HUGE_PACKET; + else + data &= ~SWITCH_HUGE_PACKET; + writew(data, hw->io + KS8842_SWITCH_CTRL_2_OFFSET); + } + if (hw->features & RX_HUGE_FRAME) { + hw->rx_cfg |= DMA_RX_ERROR; + hw_priv->dev_rcv = dev_rcv_special; + } else { + hw->rx_cfg &= ~DMA_RX_ERROR; + if (hw->dev_count > 1) + hw_priv->dev_rcv = port_rcv_packets; + else + hw_priv->dev_rcv = dev_rcv_packets; + } +} + +static int prepare_hardware(struct net_device *dev) +{ + struct dev_priv *priv = netdev_priv(dev); + struct dev_info *hw_priv = priv->adapter; + struct ksz_hw *hw = &hw_priv->hw; + int rc = 0; + + /* Remember the network device that requests interrupts. */ + hw_priv->dev = dev; + rc = request_irq(dev->irq, netdev_intr, IRQF_SHARED, dev->name, dev); + if (rc) + return rc; + tasklet_setup(&hw_priv->rx_tasklet, rx_proc_task); + tasklet_setup(&hw_priv->tx_tasklet, tx_proc_task); + + hw->promiscuous = 0; + hw->all_multi = 0; + hw->multi_list_size = 0; + + hw_reset(hw); + + hw_set_desc_base(hw, + hw->tx_desc_info.ring_phys, hw->rx_desc_info.ring_phys); + hw_set_addr(hw); + hw_cfg_huge_frame(hw_priv, hw); + ksz_init_rx_buffers(hw_priv); + return 0; +} + +static void set_media_state(struct net_device *dev, int media_state) +{ + struct dev_priv *priv = netdev_priv(dev); + + if (media_state == priv->media_state) + netif_carrier_on(dev); + else + netif_carrier_off(dev); + netif_info(priv, link, dev, "link %s\n", + media_state == priv->media_state ? "on" : "off"); +} + +/** + * netdev_open - open network device + * @dev: Network device. + * + * This function process the open operation of network device. This is caused + * by the user command "ifconfig ethX up." + * + * Return 0 if successful; otherwise an error code indicating failure. + */ +static int netdev_open(struct net_device *dev) +{ + struct dev_priv *priv = netdev_priv(dev); + struct dev_info *hw_priv = priv->adapter; + struct ksz_hw *hw = &hw_priv->hw; + struct ksz_port *port = &priv->port; + unsigned long next_jiffies; + int i; + int p; + int rc = 0; + + next_jiffies = jiffies + HZ * 2; + priv->multicast = 0; + priv->promiscuous = 0; + + /* Reset device statistics. */ + memset(&dev->stats, 0, sizeof(struct net_device_stats)); + memset((void *) port->counter, 0, + (sizeof(u64) * OID_COUNTER_LAST)); + + if (!(hw_priv->opened)) { + rc = prepare_hardware(dev); + if (rc) + return rc; + for (i = 0; i < hw->mib_port_cnt; i++) { + next_jiffies += HZ * 1; + hw_priv->counter[i].time = next_jiffies; + hw->port_mib[i].state = media_disconnected; + port_init_cnt(hw, i); + } + if (hw->ksz_switch) + hw->port_mib[HOST_PORT].state = media_connected; + else { + hw_add_wol_bcast(hw); + hw_cfg_wol_pme(hw, 0); + hw_clr_wol_pme_status(&hw_priv->hw); + } + } + port_set_power_saving(port, false); + + for (i = 0, p = port->first_port; i < port->port_cnt; i++, p++) { + /* + * Initialize to invalid value so that link detection + * is done. + */ + hw->port_info[p].partner = 0xFF; + hw->port_info[p].state = media_disconnected; + } + + /* Need to open the port in multiple device interfaces mode. */ + if (hw->dev_count > 1) { + port_set_stp_state(hw, port->first_port, STP_STATE_SIMPLE); + if (port->first_port > 0) + hw_add_addr(hw, dev->dev_addr); + } + + port_get_link_speed(port); + if (port->force_link) + port_force_link_speed(port); + else + port_set_link_speed(port); + + if (!(hw_priv->opened)) { + hw_setup_intr(hw); + hw_enable(hw); + hw_ena_intr(hw); + + if (hw->mib_port_cnt) + ksz_start_timer(&hw_priv->mib_timer_info, + hw_priv->mib_timer_info.period); + } + + hw_priv->opened++; + + ksz_start_timer(&priv->monitor_timer_info, + priv->monitor_timer_info.period); + + priv->media_state = port->linked->state; + + set_media_state(dev, media_connected); + netif_start_queue(dev); + + return 0; +} + +/* RX errors = rx_errors */ +/* RX dropped = rx_dropped */ +/* RX overruns = rx_fifo_errors */ +/* RX frame = rx_crc_errors + rx_frame_errors + rx_length_errors */ +/* TX errors = tx_errors */ +/* TX dropped = tx_dropped */ +/* TX overruns = tx_fifo_errors */ +/* TX carrier = tx_aborted_errors + tx_carrier_errors + tx_window_errors */ +/* collisions = collisions */ + +/** + * netdev_query_statistics - query network device statistics + * @dev: Network device. + * + * This function returns the statistics of the network device. The device + * needs not be opened. + * + * Return network device statistics. + */ +static struct net_device_stats *netdev_query_statistics(struct net_device *dev) +{ + struct dev_priv *priv = netdev_priv(dev); + struct ksz_port *port = &priv->port; + struct ksz_hw *hw = &priv->adapter->hw; + struct ksz_port_mib *mib; + int i; + int p; + + dev->stats.rx_errors = port->counter[OID_COUNTER_RCV_ERROR]; + dev->stats.tx_errors = port->counter[OID_COUNTER_XMIT_ERROR]; + + /* Reset to zero to add count later. */ + dev->stats.multicast = 0; + dev->stats.collisions = 0; + dev->stats.rx_length_errors = 0; + dev->stats.rx_crc_errors = 0; + dev->stats.rx_frame_errors = 0; + dev->stats.tx_window_errors = 0; + + for (i = 0, p = port->first_port; i < port->mib_port_cnt; i++, p++) { + mib = &hw->port_mib[p]; + + dev->stats.multicast += (unsigned long) + mib->counter[MIB_COUNTER_RX_MULTICAST]; + + dev->stats.collisions += (unsigned long) + mib->counter[MIB_COUNTER_TX_TOTAL_COLLISION]; + + dev->stats.rx_length_errors += (unsigned long)( + mib->counter[MIB_COUNTER_RX_UNDERSIZE] + + mib->counter[MIB_COUNTER_RX_FRAGMENT] + + mib->counter[MIB_COUNTER_RX_OVERSIZE] + + mib->counter[MIB_COUNTER_RX_JABBER]); + dev->stats.rx_crc_errors += (unsigned long) + mib->counter[MIB_COUNTER_RX_CRC_ERR]; + dev->stats.rx_frame_errors += (unsigned long)( + mib->counter[MIB_COUNTER_RX_ALIGNMENT_ERR] + + mib->counter[MIB_COUNTER_RX_SYMBOL_ERR]); + + dev->stats.tx_window_errors += (unsigned long) + mib->counter[MIB_COUNTER_TX_LATE_COLLISION]; + } + + return &dev->stats; +} + +/** + * netdev_set_mac_address - set network device MAC address + * @dev: Network device. + * @addr: Buffer of MAC address. + * + * This function is used to set the MAC address of the network device. + * + * Return 0 to indicate success. + */ +static int netdev_set_mac_address(struct net_device *dev, void *addr) +{ + struct dev_priv *priv = netdev_priv(dev); + struct dev_info *hw_priv = priv->adapter; + struct ksz_hw *hw = &hw_priv->hw; + struct sockaddr *mac = addr; + uint interrupt; + + if (priv->port.first_port > 0) + hw_del_addr(hw, dev->dev_addr); + else { + hw->mac_override = 1; + memcpy(hw->override_addr, mac->sa_data, ETH_ALEN); + } + + eth_hw_addr_set(dev, mac->sa_data); + + interrupt = hw_block_intr(hw); + + if (priv->port.first_port > 0) + hw_add_addr(hw, dev->dev_addr); + else + hw_set_addr(hw); + hw_restore_intr(hw, interrupt); + + return 0; +} + +static void dev_set_promiscuous(struct net_device *dev, struct dev_priv *priv, + struct ksz_hw *hw, int promiscuous) +{ + if (promiscuous != priv->promiscuous) { + u8 prev_state = hw->promiscuous; + + if (promiscuous) + ++hw->promiscuous; + else + --hw->promiscuous; + priv->promiscuous = promiscuous; + + /* Turn on/off promiscuous mode. */ + if (hw->promiscuous <= 1 && prev_state <= 1) + hw_set_promiscuous(hw, hw->promiscuous); + + /* + * Port is not in promiscuous mode, meaning it is released + * from the bridge. + */ + if ((hw->features & STP_SUPPORT) && !promiscuous && + netif_is_bridge_port(dev)) { + struct ksz_switch *sw = hw->ksz_switch; + int port = priv->port.first_port; + + port_set_stp_state(hw, port, STP_STATE_DISABLED); + port = 1 << port; + if (sw->member & port) { + sw->member &= ~port; + bridge_change(hw); + } + } + } +} + +static void dev_set_multicast(struct dev_priv *priv, struct ksz_hw *hw, + int multicast) +{ + if (multicast != priv->multicast) { + u8 all_multi = hw->all_multi; + + if (multicast) + ++hw->all_multi; + else + --hw->all_multi; + priv->multicast = multicast; + + /* Turn on/off all multicast mode. */ + if (hw->all_multi <= 1 && all_multi <= 1) + hw_set_multicast(hw, hw->all_multi); + } +} + +/** + * netdev_set_rx_mode + * @dev: Network device. + * + * This routine is used to set multicast addresses or put the network device + * into promiscuous mode. + */ +static void netdev_set_rx_mode(struct net_device *dev) +{ + struct dev_priv *priv = netdev_priv(dev); + struct dev_info *hw_priv = priv->adapter; + struct ksz_hw *hw = &hw_priv->hw; + struct netdev_hw_addr *ha; + int multicast = (dev->flags & IFF_ALLMULTI); + + dev_set_promiscuous(dev, priv, hw, (dev->flags & IFF_PROMISC)); + + if (hw_priv->hw.dev_count > 1) + multicast |= (dev->flags & IFF_MULTICAST); + dev_set_multicast(priv, hw, multicast); + + /* Cannot use different hashes in multiple device interfaces mode. */ + if (hw_priv->hw.dev_count > 1) + return; + + if ((dev->flags & IFF_MULTICAST) && !netdev_mc_empty(dev)) { + int i = 0; + + /* List too big to support so turn on all multicast mode. */ + if (netdev_mc_count(dev) > MAX_MULTICAST_LIST) { + if (MAX_MULTICAST_LIST != hw->multi_list_size) { + hw->multi_list_size = MAX_MULTICAST_LIST; + ++hw->all_multi; + hw_set_multicast(hw, hw->all_multi); + } + return; + } + + netdev_for_each_mc_addr(ha, dev) { + if (i >= MAX_MULTICAST_LIST) + break; + memcpy(hw->multi_list[i++], ha->addr, ETH_ALEN); + } + hw->multi_list_size = (u8) i; + hw_set_grp_addr(hw); + } else { + if (MAX_MULTICAST_LIST == hw->multi_list_size) { + --hw->all_multi; + hw_set_multicast(hw, hw->all_multi); + } + hw->multi_list_size = 0; + hw_clr_multicast(hw); + } +} + +static int netdev_change_mtu(struct net_device *dev, int new_mtu) +{ + struct dev_priv *priv = netdev_priv(dev); + struct dev_info *hw_priv = priv->adapter; + struct ksz_hw *hw = &hw_priv->hw; + int hw_mtu; + + if (netif_running(dev)) + return -EBUSY; + + /* Cannot use different MTU in multiple device interfaces mode. */ + if (hw->dev_count > 1) + if (dev != hw_priv->dev) + return 0; + + hw_mtu = new_mtu + ETHERNET_HEADER_SIZE + 4; + if (hw_mtu > REGULAR_RX_BUF_SIZE) { + hw->features |= RX_HUGE_FRAME; + hw_mtu = MAX_RX_BUF_SIZE; + } else { + hw->features &= ~RX_HUGE_FRAME; + hw_mtu = REGULAR_RX_BUF_SIZE; + } + hw_mtu = (hw_mtu + 3) & ~3; + hw_priv->mtu = hw_mtu; + WRITE_ONCE(dev->mtu, new_mtu); + + return 0; +} + +/** + * netdev_ioctl - I/O control processing + * @dev: Network device. + * @ifr: Interface request structure. + * @cmd: I/O control code. + * + * This function is used to process I/O control calls. + * + * Return 0 to indicate success. + */ +static int netdev_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) +{ + struct dev_priv *priv = netdev_priv(dev); + struct dev_info *hw_priv = priv->adapter; + struct ksz_hw *hw = &hw_priv->hw; + struct ksz_port *port = &priv->port; + int result = 0; + struct mii_ioctl_data *data = if_mii(ifr); + + if (down_interruptible(&priv->proc_sem)) + return -ERESTARTSYS; + + switch (cmd) { + /* Get address of MII PHY in use. */ + case SIOCGMIIPHY: + data->phy_id = priv->id; + fallthrough; + + /* Read MII PHY register. */ + case SIOCGMIIREG: + if (data->phy_id != priv->id || data->reg_num >= 6) + result = -EIO; + else + hw_r_phy(hw, port->linked->port_id, data->reg_num, + &data->val_out); + break; + + /* Write MII PHY register. */ + case SIOCSMIIREG: + if (!capable(CAP_NET_ADMIN)) + result = -EPERM; + else if (data->phy_id != priv->id || data->reg_num >= 6) + result = -EIO; + else + hw_w_phy(hw, port->linked->port_id, data->reg_num, + data->val_in); + break; + + default: + result = -EOPNOTSUPP; + } + + up(&priv->proc_sem); + + return result; +} + +/* + * MII support + */ + +/** + * mdio_read - read PHY register + * @dev: Network device. + * @phy_id: The PHY id. + * @reg_num: The register number. + * + * This function returns the PHY register value. + * + * Return the register value. + */ +static int mdio_read(struct net_device *dev, int phy_id, int reg_num) +{ + struct dev_priv *priv = netdev_priv(dev); + struct ksz_port *port = &priv->port; + struct ksz_hw *hw = port->hw; + u16 val_out; + + hw_r_phy(hw, port->linked->port_id, reg_num << 1, &val_out); + return val_out; +} + +/** + * mdio_write - set PHY register + * @dev: Network device. + * @phy_id: The PHY id. + * @reg_num: The register number. + * @val: The register value. + * + * This procedure sets the PHY register value. + */ +static void mdio_write(struct net_device *dev, int phy_id, int reg_num, int val) +{ + struct dev_priv *priv = netdev_priv(dev); + struct ksz_port *port = &priv->port; + struct ksz_hw *hw = port->hw; + int i; + int pi; + + for (i = 0, pi = port->first_port; i < port->port_cnt; i++, pi++) + hw_w_phy(hw, pi, reg_num << 1, val); +} + +/* + * ethtool support + */ + +#define EEPROM_SIZE 0x40 + +static u16 eeprom_data[EEPROM_SIZE] = { 0 }; + +#define ADVERTISED_ALL \ + (ADVERTISED_10baseT_Half | \ + ADVERTISED_10baseT_Full | \ + ADVERTISED_100baseT_Half | \ + ADVERTISED_100baseT_Full) + +/* These functions use the MII functions in mii.c. */ + +/** + * netdev_get_link_ksettings - get network device settings + * @dev: Network device. + * @cmd: Ethtool command. + * + * This function queries the PHY and returns its state in the ethtool command. + * + * Return 0 if successful; otherwise an error code. + */ +static int netdev_get_link_ksettings(struct net_device *dev, + struct ethtool_link_ksettings *cmd) +{ + struct dev_priv *priv = netdev_priv(dev); + struct dev_info *hw_priv = priv->adapter; + + mutex_lock(&hw_priv->lock); + mii_ethtool_get_link_ksettings(&priv->mii_if, cmd); + ethtool_link_ksettings_add_link_mode(cmd, advertising, TP); + mutex_unlock(&hw_priv->lock); + + /* Save advertised settings for workaround in next function. */ + ethtool_convert_link_mode_to_legacy_u32(&priv->advertising, + cmd->link_modes.advertising); + + return 0; +} + +/** + * netdev_set_link_ksettings - set network device settings + * @dev: Network device. + * @cmd: Ethtool command. + * + * This function sets the PHY according to the ethtool command. + * + * Return 0 if successful; otherwise an error code. + */ +static int netdev_set_link_ksettings(struct net_device *dev, + const struct ethtool_link_ksettings *cmd) +{ + struct dev_priv *priv = netdev_priv(dev); + struct dev_info *hw_priv = priv->adapter; + struct ksz_port *port = &priv->port; + struct ethtool_link_ksettings copy_cmd; + u32 speed = cmd->base.speed; + u32 advertising; + int rc; + + ethtool_convert_link_mode_to_legacy_u32(&advertising, + cmd->link_modes.advertising); + + /* + * ethtool utility does not change advertised setting if auto + * negotiation is not specified explicitly. + */ + if (cmd->base.autoneg && priv->advertising == advertising) { + advertising |= ADVERTISED_ALL; + if (10 == speed) + advertising &= + ~(ADVERTISED_100baseT_Full | + ADVERTISED_100baseT_Half); + else if (100 == speed) + advertising &= + ~(ADVERTISED_10baseT_Full | + ADVERTISED_10baseT_Half); + if (0 == cmd->base.duplex) + advertising &= + ~(ADVERTISED_100baseT_Full | + ADVERTISED_10baseT_Full); + else if (1 == cmd->base.duplex) + advertising &= + ~(ADVERTISED_100baseT_Half | + ADVERTISED_10baseT_Half); + } + mutex_lock(&hw_priv->lock); + if (cmd->base.autoneg && + (advertising & ADVERTISED_ALL) == ADVERTISED_ALL) { + port->duplex = 0; + port->speed = 0; + port->force_link = 0; + } else { + port->duplex = cmd->base.duplex + 1; + if (1000 != speed) + port->speed = speed; + if (cmd->base.autoneg) + port->force_link = 0; + else + port->force_link = 1; + } + + memcpy(©_cmd, cmd, sizeof(copy_cmd)); + ethtool_convert_legacy_u32_to_link_mode(copy_cmd.link_modes.advertising, + advertising); + rc = mii_ethtool_set_link_ksettings( + &priv->mii_if, + (const struct ethtool_link_ksettings *)©_cmd); + mutex_unlock(&hw_priv->lock); + return rc; +} + +/** + * netdev_nway_reset - restart auto-negotiation + * @dev: Network device. + * + * This function restarts the PHY for auto-negotiation. + * + * Return 0 if successful; otherwise an error code. + */ +static int netdev_nway_reset(struct net_device *dev) +{ + struct dev_priv *priv = netdev_priv(dev); + struct dev_info *hw_priv = priv->adapter; + int rc; + + mutex_lock(&hw_priv->lock); + rc = mii_nway_restart(&priv->mii_if); + mutex_unlock(&hw_priv->lock); + return rc; +} + +/** + * netdev_get_link - get network device link status + * @dev: Network device. + * + * This function gets the link status from the PHY. + * + * Return true if PHY is linked and false otherwise. + */ +static u32 netdev_get_link(struct net_device *dev) +{ + struct dev_priv *priv = netdev_priv(dev); + int rc; + + rc = mii_link_ok(&priv->mii_if); + return rc; +} + +/** + * netdev_get_drvinfo - get network driver information + * @dev: Network device. + * @info: Ethtool driver info data structure. + * + * This procedure returns the driver information. + */ +static void netdev_get_drvinfo(struct net_device *dev, + struct ethtool_drvinfo *info) +{ + struct dev_priv *priv = netdev_priv(dev); + struct dev_info *hw_priv = priv->adapter; + + strscpy(info->driver, DRV_NAME, sizeof(info->driver)); + strscpy(info->version, DRV_VERSION, sizeof(info->version)); + strscpy(info->bus_info, pci_name(hw_priv->pdev), + sizeof(info->bus_info)); +} + +static struct hw_regs { + int start; + int end; +} hw_regs_range[] = { + { KS_DMA_TX_CTRL, KS884X_INTERRUPTS_STATUS }, + { KS_ADD_ADDR_0_LO, KS_ADD_ADDR_F_HI }, + { KS884X_ADDR_0_OFFSET, KS8841_WOL_FRAME_BYTE2_OFFSET }, + { KS884X_SIDER_P, KS8842_SGCR7_P }, + { KS8842_MACAR1_P, KS8842_TOSR8_P }, + { KS884X_P1MBCR_P, KS8842_P3ERCR_P }, + { 0, 0 } +}; + +/** + * netdev_get_regs_len - get length of register dump + * @dev: Network device. + * + * This function returns the length of the register dump. + * + * Return length of the register dump. + */ +static int netdev_get_regs_len(struct net_device *dev) +{ + struct hw_regs *range = hw_regs_range; + int regs_len = 0x10 * sizeof(u32); + + while (range->end > range->start) { + regs_len += (range->end - range->start + 3) / 4 * 4; + range++; + } + return regs_len; +} + +/** + * netdev_get_regs - get register dump + * @dev: Network device. + * @regs: Ethtool registers data structure. + * @ptr: Buffer to store the register values. + * + * This procedure dumps the register values in the provided buffer. + */ +static void netdev_get_regs(struct net_device *dev, struct ethtool_regs *regs, + void *ptr) +{ + struct dev_priv *priv = netdev_priv(dev); + struct dev_info *hw_priv = priv->adapter; + struct ksz_hw *hw = &hw_priv->hw; + int *buf = (int *) ptr; + struct hw_regs *range = hw_regs_range; + int len; + + mutex_lock(&hw_priv->lock); + regs->version = 0; + for (len = 0; len < 0x40; len += 4) { + pci_read_config_dword(hw_priv->pdev, len, buf); + buf++; + } + while (range->end > range->start) { + for (len = range->start; len < range->end; len += 4) { + *buf = readl(hw->io + len); + buf++; + } + range++; + } + mutex_unlock(&hw_priv->lock); +} + +#define WOL_SUPPORT \ + (WAKE_PHY | WAKE_MAGIC | \ + WAKE_UCAST | WAKE_MCAST | \ + WAKE_BCAST | WAKE_ARP) + +/** + * netdev_get_wol - get Wake-on-LAN support + * @dev: Network device. + * @wol: Ethtool Wake-on-LAN data structure. + * + * This procedure returns Wake-on-LAN support. + */ +static void netdev_get_wol(struct net_device *dev, + struct ethtool_wolinfo *wol) +{ + struct dev_priv *priv = netdev_priv(dev); + struct dev_info *hw_priv = priv->adapter; + + wol->supported = hw_priv->wol_support; + wol->wolopts = hw_priv->wol_enable; + memset(&wol->sopass, 0, sizeof(wol->sopass)); +} + +/** + * netdev_set_wol - set Wake-on-LAN support + * @dev: Network device. + * @wol: Ethtool Wake-on-LAN data structure. + * + * This function sets Wake-on-LAN support. + * + * Return 0 if successful; otherwise an error code. + */ +static int netdev_set_wol(struct net_device *dev, + struct ethtool_wolinfo *wol) +{ + struct dev_priv *priv = netdev_priv(dev); + struct dev_info *hw_priv = priv->adapter; + + /* Need to find a way to retrieve the device IP address. */ + static const u8 net_addr[] = { 192, 168, 1, 1 }; + + if (wol->wolopts & ~hw_priv->wol_support) + return -EINVAL; + + hw_priv->wol_enable = wol->wolopts; + + /* Link wakeup cannot really be disabled. */ + if (wol->wolopts) + hw_priv->wol_enable |= WAKE_PHY; + hw_enable_wol(&hw_priv->hw, hw_priv->wol_enable, net_addr); + return 0; +} + +/** + * netdev_get_msglevel - get debug message level + * @dev: Network device. + * + * This function returns current debug message level. + * + * Return current debug message flags. + */ +static u32 netdev_get_msglevel(struct net_device *dev) +{ + struct dev_priv *priv = netdev_priv(dev); + + return priv->msg_enable; +} + +/** + * netdev_set_msglevel - set debug message level + * @dev: Network device. + * @value: Debug message flags. + * + * This procedure sets debug message level. + */ +static void netdev_set_msglevel(struct net_device *dev, u32 value) +{ + struct dev_priv *priv = netdev_priv(dev); + + priv->msg_enable = value; +} + +/** + * netdev_get_eeprom_len - get EEPROM length + * @dev: Network device. + * + * This function returns the length of the EEPROM. + * + * Return length of the EEPROM. + */ +static int netdev_get_eeprom_len(struct net_device *dev) +{ + return EEPROM_SIZE * 2; +} + +#define EEPROM_MAGIC 0x10A18842 + +/** + * netdev_get_eeprom - get EEPROM data + * @dev: Network device. + * @eeprom: Ethtool EEPROM data structure. + * @data: Buffer to store the EEPROM data. + * + * This function dumps the EEPROM data in the provided buffer. + * + * Return 0 if successful; otherwise an error code. + */ +static int netdev_get_eeprom(struct net_device *dev, + struct ethtool_eeprom *eeprom, u8 *data) +{ + struct dev_priv *priv = netdev_priv(dev); + struct dev_info *hw_priv = priv->adapter; + u8 *eeprom_byte = (u8 *) eeprom_data; + int i; + int len; + + len = (eeprom->offset + eeprom->len + 1) / 2; + for (i = eeprom->offset / 2; i < len; i++) + eeprom_data[i] = eeprom_read(&hw_priv->hw, i); + eeprom->magic = EEPROM_MAGIC; + memcpy(data, &eeprom_byte[eeprom->offset], eeprom->len); + + return 0; +} + +/** + * netdev_set_eeprom - write EEPROM data + * @dev: Network device. + * @eeprom: Ethtool EEPROM data structure. + * @data: Data buffer. + * + * This function modifies the EEPROM data one byte at a time. + * + * Return 0 if successful; otherwise an error code. + */ +static int netdev_set_eeprom(struct net_device *dev, + struct ethtool_eeprom *eeprom, u8 *data) +{ + struct dev_priv *priv = netdev_priv(dev); + struct dev_info *hw_priv = priv->adapter; + u16 eeprom_word[EEPROM_SIZE]; + u8 *eeprom_byte = (u8 *) eeprom_word; + int i; + int len; + + if (eeprom->magic != EEPROM_MAGIC) + return -EINVAL; + + len = (eeprom->offset + eeprom->len + 1) / 2; + for (i = eeprom->offset / 2; i < len; i++) + eeprom_data[i] = eeprom_read(&hw_priv->hw, i); + memcpy(eeprom_word, eeprom_data, EEPROM_SIZE * 2); + memcpy(&eeprom_byte[eeprom->offset], data, eeprom->len); + for (i = 0; i < EEPROM_SIZE; i++) + if (eeprom_word[i] != eeprom_data[i]) { + eeprom_data[i] = eeprom_word[i]; + eeprom_write(&hw_priv->hw, i, eeprom_data[i]); + } + + return 0; +} + +/** + * netdev_get_pauseparam - get flow control parameters + * @dev: Network device. + * @pause: Ethtool PAUSE settings data structure. + * + * This procedure returns the PAUSE control flow settings. + */ +static void netdev_get_pauseparam(struct net_device *dev, + struct ethtool_pauseparam *pause) +{ + struct dev_priv *priv = netdev_priv(dev); + struct dev_info *hw_priv = priv->adapter; + struct ksz_hw *hw = &hw_priv->hw; + + pause->autoneg = (hw->overrides & PAUSE_FLOW_CTRL) ? 0 : 1; + if (!hw->ksz_switch) { + pause->rx_pause = + (hw->rx_cfg & DMA_RX_FLOW_ENABLE) ? 1 : 0; + pause->tx_pause = + (hw->tx_cfg & DMA_TX_FLOW_ENABLE) ? 1 : 0; + } else { + pause->rx_pause = + (sw_chk(hw, KS8842_SWITCH_CTRL_1_OFFSET, + SWITCH_RX_FLOW_CTRL)) ? 1 : 0; + pause->tx_pause = + (sw_chk(hw, KS8842_SWITCH_CTRL_1_OFFSET, + SWITCH_TX_FLOW_CTRL)) ? 1 : 0; + } +} + +/** + * netdev_set_pauseparam - set flow control parameters + * @dev: Network device. + * @pause: Ethtool PAUSE settings data structure. + * + * This function sets the PAUSE control flow settings. + * Not implemented yet. + * + * Return 0 if successful; otherwise an error code. + */ +static int netdev_set_pauseparam(struct net_device *dev, + struct ethtool_pauseparam *pause) +{ + struct dev_priv *priv = netdev_priv(dev); + struct dev_info *hw_priv = priv->adapter; + struct ksz_hw *hw = &hw_priv->hw; + struct ksz_port *port = &priv->port; + + mutex_lock(&hw_priv->lock); + if (pause->autoneg) { + if (!pause->rx_pause && !pause->tx_pause) + port->flow_ctrl = PHY_NO_FLOW_CTRL; + else + port->flow_ctrl = PHY_FLOW_CTRL; + hw->overrides &= ~PAUSE_FLOW_CTRL; + port->force_link = 0; + if (hw->ksz_switch) { + sw_cfg(hw, KS8842_SWITCH_CTRL_1_OFFSET, + SWITCH_RX_FLOW_CTRL, 1); + sw_cfg(hw, KS8842_SWITCH_CTRL_1_OFFSET, + SWITCH_TX_FLOW_CTRL, 1); + } + port_set_link_speed(port); + } else { + hw->overrides |= PAUSE_FLOW_CTRL; + if (hw->ksz_switch) { + sw_cfg(hw, KS8842_SWITCH_CTRL_1_OFFSET, + SWITCH_RX_FLOW_CTRL, pause->rx_pause); + sw_cfg(hw, KS8842_SWITCH_CTRL_1_OFFSET, + SWITCH_TX_FLOW_CTRL, pause->tx_pause); + } else + set_flow_ctrl(hw, pause->rx_pause, pause->tx_pause); + } + mutex_unlock(&hw_priv->lock); + + return 0; +} + +/** + * netdev_get_ringparam - get tx/rx ring parameters + * @dev: Network device. + * @ring: Ethtool RING settings data structure. + * @kernel_ring: Ethtool external RING settings data structure. + * @extack: Netlink handle. + * + * This procedure returns the TX/RX ring settings. + */ +static void netdev_get_ringparam(struct net_device *dev, + struct ethtool_ringparam *ring, + struct kernel_ethtool_ringparam *kernel_ring, + struct netlink_ext_ack *extack) +{ + struct dev_priv *priv = netdev_priv(dev); + struct dev_info *hw_priv = priv->adapter; + struct ksz_hw *hw = &hw_priv->hw; + + ring->tx_max_pending = (1 << 9); + ring->tx_pending = hw->tx_desc_info.alloc; + ring->rx_max_pending = (1 << 9); + ring->rx_pending = hw->rx_desc_info.alloc; +} + +#define STATS_LEN (TOTAL_PORT_COUNTER_NUM) + +static struct { + char string[ETH_GSTRING_LEN]; +} ethtool_stats_keys[STATS_LEN] = { + { "rx_lo_priority_octets" }, + { "rx_hi_priority_octets" }, + { "rx_undersize_packets" }, + { "rx_fragments" }, + { "rx_oversize_packets" }, + { "rx_jabbers" }, + { "rx_symbol_errors" }, + { "rx_crc_errors" }, + { "rx_align_errors" }, + { "rx_mac_ctrl_packets" }, + { "rx_pause_packets" }, + { "rx_bcast_packets" }, + { "rx_mcast_packets" }, + { "rx_ucast_packets" }, + { "rx_64_or_less_octet_packets" }, + { "rx_65_to_127_octet_packets" }, + { "rx_128_to_255_octet_packets" }, + { "rx_256_to_511_octet_packets" }, + { "rx_512_to_1023_octet_packets" }, + { "rx_1024_to_1522_octet_packets" }, + + { "tx_lo_priority_octets" }, + { "tx_hi_priority_octets" }, + { "tx_late_collisions" }, + { "tx_pause_packets" }, + { "tx_bcast_packets" }, + { "tx_mcast_packets" }, + { "tx_ucast_packets" }, + { "tx_deferred" }, + { "tx_total_collisions" }, + { "tx_excessive_collisions" }, + { "tx_single_collisions" }, + { "tx_mult_collisions" }, + + { "rx_discards" }, + { "tx_discards" }, +}; + +/** + * netdev_get_strings - get statistics identity strings + * @dev: Network device. + * @stringset: String set identifier. + * @buf: Buffer to store the strings. + * + * This procedure returns the strings used to identify the statistics. + */ +static void netdev_get_strings(struct net_device *dev, u32 stringset, u8 *buf) +{ + struct dev_priv *priv = netdev_priv(dev); + struct dev_info *hw_priv = priv->adapter; + struct ksz_hw *hw = &hw_priv->hw; + + if (ETH_SS_STATS == stringset) + memcpy(buf, ðtool_stats_keys, + ETH_GSTRING_LEN * hw->mib_cnt); +} + +/** + * netdev_get_sset_count - get statistics size + * @dev: Network device. + * @sset: The statistics set number. + * + * This function returns the size of the statistics to be reported. + * + * Return size of the statistics to be reported. + */ +static int netdev_get_sset_count(struct net_device *dev, int sset) +{ + struct dev_priv *priv = netdev_priv(dev); + struct dev_info *hw_priv = priv->adapter; + struct ksz_hw *hw = &hw_priv->hw; + + switch (sset) { + case ETH_SS_STATS: + return hw->mib_cnt; + default: + return -EOPNOTSUPP; + } +} + +/** + * netdev_get_ethtool_stats - get network device statistics + * @dev: Network device. + * @stats: Ethtool statistics data structure. + * @data: Buffer to store the statistics. + * + * This procedure returns the statistics. + */ +static void netdev_get_ethtool_stats(struct net_device *dev, + struct ethtool_stats *stats, u64 *data) +{ + struct dev_priv *priv = netdev_priv(dev); + struct dev_info *hw_priv = priv->adapter; + struct ksz_hw *hw = &hw_priv->hw; + struct ksz_port *port = &priv->port; + int n_stats = stats->n_stats; + int i; + int n; + int p; + u64 counter[TOTAL_PORT_COUNTER_NUM]; + + mutex_lock(&hw_priv->lock); + n = SWITCH_PORT_NUM; + for (i = 0, p = port->first_port; i < port->mib_port_cnt; i++, p++) { + if (media_connected == hw->port_mib[p].state) { + hw_priv->counter[p].read = 1; + + /* Remember first port that requests read. */ + if (n == SWITCH_PORT_NUM) + n = p; + } + } + mutex_unlock(&hw_priv->lock); + + if (n < SWITCH_PORT_NUM) + schedule_work(&hw_priv->mib_read); + + if (1 == port->mib_port_cnt && n < SWITCH_PORT_NUM) { + p = n; + wait_event_interruptible_timeout( + hw_priv->counter[p].counter, + 2 == hw_priv->counter[p].read, + HZ * 1); + } else + for (i = 0, p = n; i < port->mib_port_cnt - n; i++, p++) { + if (0 == i) { + wait_event_interruptible_timeout( + hw_priv->counter[p].counter, + 2 == hw_priv->counter[p].read, + HZ * 2); + } else if (hw->port_mib[p].cnt_ptr) { + wait_event_interruptible_timeout( + hw_priv->counter[p].counter, + 2 == hw_priv->counter[p].read, + HZ * 1); + } + } + + get_mib_counters(hw, port->first_port, port->mib_port_cnt, counter); + n = hw->mib_cnt; + if (n > n_stats) + n = n_stats; + n_stats -= n; + for (i = 0; i < n; i++) + *data++ = counter[i]; +} + +/** + * netdev_set_features - set receive checksum support + * @dev: Network device. + * @features: New device features (offloads). + * + * This function sets receive checksum support setting. + * + * Return 0 if successful; otherwise an error code. + */ +static int netdev_set_features(struct net_device *dev, + netdev_features_t features) +{ + struct dev_priv *priv = netdev_priv(dev); + struct dev_info *hw_priv = priv->adapter; + struct ksz_hw *hw = &hw_priv->hw; + + mutex_lock(&hw_priv->lock); + + /* see note in hw_setup() */ + if (features & NETIF_F_RXCSUM) + hw->rx_cfg |= DMA_RX_CSUM_TCP | DMA_RX_CSUM_IP; + else + hw->rx_cfg &= ~(DMA_RX_CSUM_TCP | DMA_RX_CSUM_IP); + + if (hw->enabled) + writel(hw->rx_cfg, hw->io + KS_DMA_RX_CTRL); + + mutex_unlock(&hw_priv->lock); + + return 0; +} + +static const struct ethtool_ops netdev_ethtool_ops = { + .nway_reset = netdev_nway_reset, + .get_link = netdev_get_link, + .get_drvinfo = netdev_get_drvinfo, + .get_regs_len = netdev_get_regs_len, + .get_regs = netdev_get_regs, + .get_wol = netdev_get_wol, + .set_wol = netdev_set_wol, + .get_msglevel = netdev_get_msglevel, + .set_msglevel = netdev_set_msglevel, + .get_eeprom_len = netdev_get_eeprom_len, + .get_eeprom = netdev_get_eeprom, + .set_eeprom = netdev_set_eeprom, + .get_pauseparam = netdev_get_pauseparam, + .set_pauseparam = netdev_set_pauseparam, + .get_ringparam = netdev_get_ringparam, + .get_strings = netdev_get_strings, + .get_sset_count = netdev_get_sset_count, + .get_ethtool_stats = netdev_get_ethtool_stats, + .get_link_ksettings = netdev_get_link_ksettings, + .set_link_ksettings = netdev_set_link_ksettings, +}; + +/* + * Hardware monitoring + */ + +static void update_link(struct net_device *dev, struct dev_priv *priv, + struct ksz_port *port) +{ + if (priv->media_state != port->linked->state) { + priv->media_state = port->linked->state; + if (netif_running(dev)) + set_media_state(dev, media_connected); + } +} + +static void mib_read_work(struct work_struct *work) +{ + struct dev_info *hw_priv = + container_of(work, struct dev_info, mib_read); + struct ksz_hw *hw = &hw_priv->hw; + unsigned long next_jiffies; + struct ksz_port_mib *mib; + int i; + + next_jiffies = jiffies; + for (i = 0; i < hw->mib_port_cnt; i++) { + mib = &hw->port_mib[i]; + + /* Reading MIB counters or requested to read. */ + if (mib->cnt_ptr || 1 == hw_priv->counter[i].read) { + + /* Need to process receive interrupt. */ + if (port_r_cnt(hw, i)) + break; + hw_priv->counter[i].read = 0; + + /* Finish reading counters. */ + if (0 == mib->cnt_ptr) { + hw_priv->counter[i].read = 2; + wake_up_interruptible( + &hw_priv->counter[i].counter); + } + } else if (time_after_eq(jiffies, hw_priv->counter[i].time)) { + /* Only read MIB counters when the port is connected. */ + if (media_connected == mib->state) + hw_priv->counter[i].read = 1; + next_jiffies += HZ * 1 * hw->mib_port_cnt; + hw_priv->counter[i].time = next_jiffies; + + /* Port is just disconnected. */ + } else if (mib->link_down) { + mib->link_down = 0; + + /* Read counters one last time after link is lost. */ + hw_priv->counter[i].read = 1; + } + } +} + +static void mib_monitor(struct timer_list *t) +{ + struct dev_info *hw_priv = timer_container_of(hw_priv, t, + mib_timer_info.timer); + + mib_read_work(&hw_priv->mib_read); + + /* This is used to verify Wake-on-LAN is working. */ + if (hw_priv->pme_wait) { + if (time_is_before_eq_jiffies(hw_priv->pme_wait)) { + hw_clr_wol_pme_status(&hw_priv->hw); + hw_priv->pme_wait = 0; + } + } else if (hw_chk_wol_pme_status(&hw_priv->hw)) { + + /* PME is asserted. Wait 2 seconds to clear it. */ + hw_priv->pme_wait = jiffies + HZ * 2; + } + + ksz_update_timer(&hw_priv->mib_timer_info); +} + +/** + * dev_monitor - periodic monitoring + * @t: timer list containing a network device pointer. + * + * This routine is run in a kernel timer to monitor the network device. + */ +static void dev_monitor(struct timer_list *t) +{ + struct dev_priv *priv = timer_container_of(priv, t, + monitor_timer_info.timer); + struct net_device *dev = priv->mii_if.dev; + struct dev_info *hw_priv = priv->adapter; + struct ksz_hw *hw = &hw_priv->hw; + struct ksz_port *port = &priv->port; + + if (!(hw->features & LINK_INT_WORKING)) + port_get_link_speed(port); + update_link(dev, priv, port); + + ksz_update_timer(&priv->monitor_timer_info); +} + +/* + * Linux network device interface functions + */ + +/* Driver exported variables */ + +static int msg_enable; + +static char *macaddr = ":"; +static char *mac1addr = ":"; + +/* + * This enables multiple network device mode for KSZ8842, which contains a + * switch with two physical ports. Some users like to take control of the + * ports for running Spanning Tree Protocol. The driver will create an + * additional eth? device for the other port. + * + * Some limitations are the network devices cannot have different MTU and + * multicast hash tables. + */ +static int multi_dev; + +/* + * As most users select multiple network device mode to use Spanning Tree + * Protocol, this enables a feature in which most unicast and multicast packets + * are forwarded inside the switch and not passed to the host. Only packets + * that need the host's attention are passed to it. This prevents the host + * wasting CPU time to examine each and every incoming packets and do the + * forwarding itself. + * + * As the hack requires the private bridge header, the driver cannot compile + * with just the kernel headers. + * + * Enabling STP support also turns on multiple network device mode. + */ +static int stp; + +/* + * This enables fast aging in the KSZ8842 switch. Not sure what situation + * needs that. However, fast aging is used to flush the dynamic MAC table when + * STP support is enabled. + */ +static int fast_aging; + +/** + * netdev_init - initialize network device. + * @dev: Network device. + * + * This function initializes the network device. + * + * Return 0 if successful; otherwise an error code indicating failure. + */ +static int __init netdev_init(struct net_device *dev) +{ + struct dev_priv *priv = netdev_priv(dev); + + /* 500 ms timeout */ + ksz_init_timer(&priv->monitor_timer_info, 500 * HZ / 1000, + dev_monitor); + + /* 500 ms timeout */ + dev->watchdog_timeo = HZ / 2; + + dev->hw_features = NETIF_F_IP_CSUM | NETIF_F_SG | NETIF_F_RXCSUM; + + /* + * Hardware does not really support IPv6 checksum generation, but + * driver actually runs faster with this on. + */ + dev->hw_features |= NETIF_F_IPV6_CSUM; + + dev->features |= dev->hw_features; + + sema_init(&priv->proc_sem, 1); + + priv->mii_if.phy_id_mask = 0x1; + priv->mii_if.reg_num_mask = 0x7; + priv->mii_if.dev = dev; + priv->mii_if.mdio_read = mdio_read; + priv->mii_if.mdio_write = mdio_write; + priv->mii_if.phy_id = priv->port.first_port + 1; + + priv->msg_enable = netif_msg_init(msg_enable, + (NETIF_MSG_DRV | NETIF_MSG_PROBE | NETIF_MSG_LINK)); + + return 0; +} + +static const struct net_device_ops netdev_ops = { + .ndo_init = netdev_init, + .ndo_open = netdev_open, + .ndo_stop = netdev_close, + .ndo_get_stats = netdev_query_statistics, + .ndo_start_xmit = netdev_tx, + .ndo_tx_timeout = netdev_tx_timeout, + .ndo_change_mtu = netdev_change_mtu, + .ndo_set_features = netdev_set_features, + .ndo_set_mac_address = netdev_set_mac_address, + .ndo_validate_addr = eth_validate_addr, + .ndo_eth_ioctl = netdev_ioctl, + .ndo_set_rx_mode = netdev_set_rx_mode, +#ifdef CONFIG_NET_POLL_CONTROLLER + .ndo_poll_controller = netdev_netpoll, +#endif +}; + +static void netdev_free(struct net_device *dev) +{ + if (dev->watchdog_timeo) + unregister_netdev(dev); + + free_netdev(dev); +} + +struct platform_info { + struct dev_info dev_info; + struct net_device *netdev[SWITCH_PORT_NUM]; +}; + +static int net_device_present; + +static void get_mac_addr(struct dev_info *hw_priv, u8 *macaddr, int port) +{ + int i; + int j; + int got_num; + int num; + + i = j = num = got_num = 0; + while (j < ETH_ALEN) { + if (macaddr[i]) { + int digit; + + got_num = 1; + digit = hex_to_bin(macaddr[i]); + if (digit >= 0) + num = num * 16 + digit; + else if (':' == macaddr[i]) + got_num = 2; + else + break; + } else if (got_num) + got_num = 2; + else + break; + if (2 == got_num) { + if (MAIN_PORT == port) { + hw_priv->hw.override_addr[j++] = (u8) num; + hw_priv->hw.override_addr[5] += + hw_priv->hw.id; + } else { + hw_priv->hw.ksz_switch->other_addr[j++] = + (u8) num; + hw_priv->hw.ksz_switch->other_addr[5] += + hw_priv->hw.id; + } + num = got_num = 0; + } + i++; + } + if (ETH_ALEN == j) { + if (MAIN_PORT == port) + hw_priv->hw.mac_override = 1; + } +} + +#define KS884X_DMA_MASK (~0x0UL) + +static void read_other_addr(struct ksz_hw *hw) +{ + int i; + u16 data[3]; + struct ksz_switch *sw = hw->ksz_switch; + + for (i = 0; i < 3; i++) + data[i] = eeprom_read(hw, i + EEPROM_DATA_OTHER_MAC_ADDR); + if ((data[0] || data[1] || data[2]) && data[0] != 0xffff) { + sw->other_addr[5] = (u8) data[0]; + sw->other_addr[4] = (u8)(data[0] >> 8); + sw->other_addr[3] = (u8) data[1]; + sw->other_addr[2] = (u8)(data[1] >> 8); + sw->other_addr[1] = (u8) data[2]; + sw->other_addr[0] = (u8)(data[2] >> 8); + } +} + +#ifndef PCI_VENDOR_ID_MICREL_KS +#define PCI_VENDOR_ID_MICREL_KS 0x16c6 +#endif + +static int pcidev_init(struct pci_dev *pdev, const struct pci_device_id *id) +{ + struct net_device *dev; + struct dev_priv *priv; + struct dev_info *hw_priv; + struct ksz_hw *hw; + struct platform_info *info; + struct ksz_port *port; + unsigned long reg_base; + unsigned long reg_len; + int cnt; + int i; + int mib_port_count; + int pi; + int port_count; + int result; + char banner[sizeof(version)]; + struct ksz_switch *sw = NULL; + + result = pcim_enable_device(pdev); + if (result) + return result; + + result = -ENODEV; + + if (dma_set_mask(&pdev->dev, DMA_BIT_MASK(32)) || + dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32))) + return result; + + reg_base = pci_resource_start(pdev, 0); + reg_len = pci_resource_len(pdev, 0); + if ((pci_resource_flags(pdev, 0) & IORESOURCE_IO) != 0) + return result; + + if (!request_mem_region(reg_base, reg_len, DRV_NAME)) + return result; + pci_set_master(pdev); + + result = -ENOMEM; + + info = kzalloc_obj(struct platform_info); + if (!info) + goto pcidev_init_dev_err; + + hw_priv = &info->dev_info; + hw_priv->pdev = pdev; + + hw = &hw_priv->hw; + + hw->io = ioremap(reg_base, reg_len); + if (!hw->io) + goto pcidev_init_io_err; + + cnt = hw_init(hw); + if (!cnt) { + if (msg_enable & NETIF_MSG_PROBE) + pr_alert("chip not detected\n"); + result = -ENODEV; + goto pcidev_init_alloc_err; + } + + snprintf(banner, sizeof(banner), "%s", version); + banner[13] = cnt + '0'; /* Replace x in "Micrel KSZ884x" */ + dev_info(&hw_priv->pdev->dev, "%s\n", banner); + dev_dbg(&hw_priv->pdev->dev, "Mem = %p; IRQ = %d\n", hw->io, pdev->irq); + + /* Assume device is KSZ8841. */ + hw->dev_count = 1; + port_count = 1; + mib_port_count = 1; + hw->addr_list_size = 0; + hw->mib_cnt = PORT_COUNTER_NUM; + hw->mib_port_cnt = 1; + + /* KSZ8842 has a switch with multiple ports. */ + if (2 == cnt) { + if (fast_aging) + hw->overrides |= FAST_AGING; + + hw->mib_cnt = TOTAL_PORT_COUNTER_NUM; + + /* Multiple network device interfaces are required. */ + if (multi_dev) { + hw->dev_count = SWITCH_PORT_NUM; + hw->addr_list_size = SWITCH_PORT_NUM - 1; + } + + /* Single network device has multiple ports. */ + if (1 == hw->dev_count) { + port_count = SWITCH_PORT_NUM; + mib_port_count = SWITCH_PORT_NUM; + } + hw->mib_port_cnt = TOTAL_PORT_NUM; + hw->ksz_switch = kzalloc_obj(struct ksz_switch); + if (!hw->ksz_switch) + goto pcidev_init_alloc_err; + + sw = hw->ksz_switch; + } + for (i = 0; i < hw->mib_port_cnt; i++) + hw->port_mib[i].mib_start = 0; + + hw->parent = hw_priv; + + /* Default MTU is 1500. */ + hw_priv->mtu = (REGULAR_RX_BUF_SIZE + 3) & ~3; + + if (ksz_alloc_mem(hw_priv)) + goto pcidev_init_mem_err; + + hw_priv->hw.id = net_device_present; + + spin_lock_init(&hw_priv->hwlock); + mutex_init(&hw_priv->lock); + + for (i = 0; i < TOTAL_PORT_NUM; i++) + init_waitqueue_head(&hw_priv->counter[i].counter); + + if (macaddr[0] != ':') + get_mac_addr(hw_priv, macaddr, MAIN_PORT); + + /* Read MAC address and initialize override address if not overridden. */ + hw_read_addr(hw); + + /* Multiple device interfaces mode requires a second MAC address. */ + if (hw->dev_count > 1) { + memcpy(sw->other_addr, hw->override_addr, ETH_ALEN); + read_other_addr(hw); + if (mac1addr[0] != ':') + get_mac_addr(hw_priv, mac1addr, OTHER_PORT); + } + + hw_setup(hw); + if (hw->ksz_switch) + sw_setup(hw); + else { + hw_priv->wol_support = WOL_SUPPORT; + hw_priv->wol_enable = 0; + } + + INIT_WORK(&hw_priv->mib_read, mib_read_work); + + /* 500 ms timeout */ + ksz_init_timer(&hw_priv->mib_timer_info, 500 * HZ / 1000, + mib_monitor); + + for (i = 0; i < hw->dev_count; i++) { + dev = alloc_etherdev(sizeof(struct dev_priv)); + if (!dev) + goto pcidev_init_reg_err; + SET_NETDEV_DEV(dev, &pdev->dev); + info->netdev[i] = dev; + + priv = netdev_priv(dev); + priv->adapter = hw_priv; + priv->id = net_device_present++; + + port = &priv->port; + port->port_cnt = port_count; + port->mib_port_cnt = mib_port_count; + port->first_port = i; + port->flow_ctrl = PHY_FLOW_CTRL; + + port->hw = hw; + port->linked = &hw->port_info[port->first_port]; + + for (cnt = 0, pi = i; cnt < port_count; cnt++, pi++) { + hw->port_info[pi].port_id = pi; + hw->port_info[pi].pdev = dev; + hw->port_info[pi].state = media_disconnected; + } + + dev->mem_start = (unsigned long) hw->io; + dev->mem_end = dev->mem_start + reg_len - 1; + dev->irq = pdev->irq; + if (MAIN_PORT == i) + eth_hw_addr_set(dev, hw_priv->hw.override_addr); + else { + u8 addr[ETH_ALEN]; + + ether_addr_copy(addr, sw->other_addr); + if (ether_addr_equal(sw->other_addr, hw->override_addr)) + addr[5] += port->first_port; + eth_hw_addr_set(dev, addr); + } + + dev->netdev_ops = &netdev_ops; + dev->ethtool_ops = &netdev_ethtool_ops; + + /* MTU range: 60 - 1894 */ + dev->min_mtu = ETH_ZLEN; + dev->max_mtu = MAX_RX_BUF_SIZE - + (ETH_HLEN + ETH_FCS_LEN + VLAN_HLEN); + + if (register_netdev(dev)) + goto pcidev_init_reg_err; + port_set_power_saving(port, true); + } + + pci_dev_get(hw_priv->pdev); + pci_set_drvdata(pdev, info); + return 0; + +pcidev_init_reg_err: + for (i = 0; i < hw->dev_count; i++) { + if (info->netdev[i]) { + netdev_free(info->netdev[i]); + info->netdev[i] = NULL; + } + } + +pcidev_init_mem_err: + ksz_free_mem(hw_priv); + kfree(hw->ksz_switch); + +pcidev_init_alloc_err: + iounmap(hw->io); + +pcidev_init_io_err: + kfree(info); + +pcidev_init_dev_err: + release_mem_region(reg_base, reg_len); + + return result; +} + +static void pcidev_exit(struct pci_dev *pdev) +{ + int i; + struct platform_info *info = pci_get_drvdata(pdev); + struct dev_info *hw_priv = &info->dev_info; + + release_mem_region(pci_resource_start(pdev, 0), + pci_resource_len(pdev, 0)); + for (i = 0; i < hw_priv->hw.dev_count; i++) { + if (info->netdev[i]) + netdev_free(info->netdev[i]); + } + if (hw_priv->hw.io) + iounmap(hw_priv->hw.io); + ksz_free_mem(hw_priv); + kfree(hw_priv->hw.ksz_switch); + pci_dev_put(hw_priv->pdev); + kfree(info); +} + +static int __maybe_unused pcidev_resume(struct device *dev_d) +{ + int i; + struct platform_info *info = dev_get_drvdata(dev_d); + struct dev_info *hw_priv = &info->dev_info; + struct ksz_hw *hw = &hw_priv->hw; + + device_wakeup_disable(dev_d); + + if (hw_priv->wol_enable) + hw_cfg_wol_pme(hw, 0); + for (i = 0; i < hw->dev_count; i++) { + if (info->netdev[i]) { + struct net_device *dev = info->netdev[i]; + + if (netif_running(dev)) { + netdev_open(dev); + netif_device_attach(dev); + } + } + } + return 0; +} + +static int __maybe_unused pcidev_suspend(struct device *dev_d) +{ + int i; + struct platform_info *info = dev_get_drvdata(dev_d); + struct dev_info *hw_priv = &info->dev_info; + struct ksz_hw *hw = &hw_priv->hw; + + /* Need to find a way to retrieve the device IP address. */ + static const u8 net_addr[] = { 192, 168, 1, 1 }; + + for (i = 0; i < hw->dev_count; i++) { + if (info->netdev[i]) { + struct net_device *dev = info->netdev[i]; + + if (netif_running(dev)) { + netif_device_detach(dev); + netdev_close(dev); + } + } + } + if (hw_priv->wol_enable) { + hw_enable_wol(hw, hw_priv->wol_enable, net_addr); + hw_cfg_wol_pme(hw, 1); + } + + device_wakeup_enable(dev_d); + return 0; +} + +static char pcidev_name[] = "ksz884xp"; + +static const struct pci_device_id pcidev_table[] = { + { PCI_VDEVICE(MICREL_KS, 0x8841) }, + { PCI_VDEVICE(MICREL_KS, 0x8842) }, + { } +}; + +MODULE_DEVICE_TABLE(pci, pcidev_table); + +static SIMPLE_DEV_PM_OPS(pcidev_pm_ops, pcidev_suspend, pcidev_resume); + +static struct pci_driver pci_device_driver = { + .driver.pm = &pcidev_pm_ops, + .name = pcidev_name, + .id_table = pcidev_table, + .probe = pcidev_init, + .remove = pcidev_exit +}; + +module_pci_driver(pci_device_driver); + +MODULE_DESCRIPTION("KSZ8841/2 PCI network driver"); +MODULE_AUTHOR("Tristram Ha "); +MODULE_LICENSE("GPL"); + +module_param_named(message, msg_enable, int, 0); +MODULE_PARM_DESC(message, "Message verbosity level (0=none, 31=all)"); + +module_param(macaddr, charp, 0); +module_param(mac1addr, charp, 0); +module_param(fast_aging, int, 0); +module_param(multi_dev, int, 0); +module_param(stp, int, 0); +MODULE_PARM_DESC(macaddr, "MAC address"); +MODULE_PARM_DESC(mac1addr, "Second MAC address"); +MODULE_PARM_DESC(fast_aging, "Fast aging"); +MODULE_PARM_DESC(multi_dev, "Multiple device interfaces"); +MODULE_PARM_DESC(stp, "STP support"); -- cgit v1.3.1