Supporting multi-cs in spi drivers would require the chip_select & cs_gpiod members of struct spi_device to be an array. But changing the type of these members to array would break the spi driver functionality. To make the transition smoother introduced four new APIs to get/set the spi->chip_select & spi->cs_gpiod and replaced all spi->chip_select and spi->cs_gpiod references with get or set API calls. While adding multi-cs support in further patches the chip_select & cs_gpiod members of the spi_device structure would be converted to arrays & the "idx" parameter of the APIs would be used as array index i.e., spi->chip_select[idx] & spi->cs_gpiod[idx] respectively. Signed-off-by: Amit Kumar Mahapatra <amit.kumar-mahapatra@amd.com> Reviewed-by: Michal Simek <michal.simek@amd.com> Link: https://lore.kernel.org/r/20230310173217.3429788-3-amit.kumar-mahapatra@amd.com Signed-off-by: Mark Brown <broonie@kernel.org>
1167 lines
30 KiB
C
1167 lines
30 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2010 ASIX Electronics Corporation
|
|
* Copyright (c) 2020 Samsung Electronics Co., Ltd.
|
|
*
|
|
* ASIX AX88796C SPI Fast Ethernet Linux driver
|
|
*/
|
|
|
|
#define pr_fmt(fmt) "ax88796c: " fmt
|
|
|
|
#include "ax88796c_main.h"
|
|
#include "ax88796c_ioctl.h"
|
|
|
|
#include <linux/bitmap.h>
|
|
#include <linux/etherdevice.h>
|
|
#include <linux/iopoll.h>
|
|
#include <linux/lockdep.h>
|
|
#include <linux/mdio.h>
|
|
#include <linux/minmax.h>
|
|
#include <linux/module.h>
|
|
#include <linux/netdevice.h>
|
|
#include <linux/of.h>
|
|
#include <linux/phy.h>
|
|
#include <linux/skbuff.h>
|
|
#include <linux/spi/spi.h>
|
|
|
|
static int comp = IS_ENABLED(CONFIG_SPI_AX88796C_COMPRESSION);
|
|
static int msg_enable = NETIF_MSG_PROBE |
|
|
NETIF_MSG_LINK |
|
|
NETIF_MSG_RX_ERR |
|
|
NETIF_MSG_TX_ERR;
|
|
|
|
static const char *no_regs_list = "80018001,e1918001,8001a001,fc0d0000";
|
|
unsigned long ax88796c_no_regs_mask[AX88796C_REGDUMP_LEN / (sizeof(unsigned long) * 8)];
|
|
|
|
module_param(msg_enable, int, 0444);
|
|
MODULE_PARM_DESC(msg_enable, "Message mask (see linux/netdevice.h for bitmap)");
|
|
|
|
static int ax88796c_soft_reset(struct ax88796c_device *ax_local)
|
|
{
|
|
u16 temp;
|
|
int ret;
|
|
|
|
lockdep_assert_held(&ax_local->spi_lock);
|
|
|
|
AX_WRITE(&ax_local->ax_spi, PSR_RESET, P0_PSR);
|
|
AX_WRITE(&ax_local->ax_spi, PSR_RESET_CLR, P0_PSR);
|
|
|
|
ret = read_poll_timeout(AX_READ, ret,
|
|
(ret & PSR_DEV_READY),
|
|
0, jiffies_to_usecs(160 * HZ / 1000), false,
|
|
&ax_local->ax_spi, P0_PSR);
|
|
if (ret)
|
|
return ret;
|
|
|
|
temp = AX_READ(&ax_local->ax_spi, P4_SPICR);
|
|
if (ax_local->priv_flags & AX_CAP_COMP) {
|
|
AX_WRITE(&ax_local->ax_spi,
|
|
(temp | SPICR_RCEN | SPICR_QCEN), P4_SPICR);
|
|
ax_local->ax_spi.comp = 1;
|
|
} else {
|
|
AX_WRITE(&ax_local->ax_spi,
|
|
(temp & ~(SPICR_RCEN | SPICR_QCEN)), P4_SPICR);
|
|
ax_local->ax_spi.comp = 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ax88796c_reload_eeprom(struct ax88796c_device *ax_local)
|
|
{
|
|
int ret;
|
|
|
|
lockdep_assert_held(&ax_local->spi_lock);
|
|
|
|
AX_WRITE(&ax_local->ax_spi, EECR_RELOAD, P3_EECR);
|
|
|
|
ret = read_poll_timeout(AX_READ, ret,
|
|
(ret & PSR_DEV_READY),
|
|
0, jiffies_to_usecs(2 * HZ / 1000), false,
|
|
&ax_local->ax_spi, P0_PSR);
|
|
if (ret) {
|
|
dev_err(&ax_local->spi->dev,
|
|
"timeout waiting for reload eeprom\n");
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void ax88796c_set_hw_multicast(struct net_device *ndev)
|
|
{
|
|
struct ax88796c_device *ax_local = to_ax88796c_device(ndev);
|
|
int mc_count = netdev_mc_count(ndev);
|
|
u16 rx_ctl = RXCR_AB;
|
|
|
|
lockdep_assert_held(&ax_local->spi_lock);
|
|
|
|
memset(ax_local->multi_filter, 0, AX_MCAST_FILTER_SIZE);
|
|
|
|
if (ndev->flags & IFF_PROMISC) {
|
|
rx_ctl |= RXCR_PRO;
|
|
|
|
} else if (ndev->flags & IFF_ALLMULTI || mc_count > AX_MAX_MCAST) {
|
|
rx_ctl |= RXCR_AMALL;
|
|
|
|
} else if (mc_count == 0) {
|
|
/* just broadcast and directed */
|
|
} else {
|
|
u32 crc_bits;
|
|
int i;
|
|
struct netdev_hw_addr *ha;
|
|
|
|
netdev_for_each_mc_addr(ha, ndev) {
|
|
crc_bits = ether_crc(ETH_ALEN, ha->addr);
|
|
ax_local->multi_filter[crc_bits >> 29] |=
|
|
(1 << ((crc_bits >> 26) & 7));
|
|
}
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
AX_WRITE(&ax_local->ax_spi,
|
|
((ax_local->multi_filter[i * 2 + 1] << 8) |
|
|
ax_local->multi_filter[i * 2]), P3_MFAR(i));
|
|
}
|
|
}
|
|
|
|
AX_WRITE(&ax_local->ax_spi, rx_ctl, P2_RXCR);
|
|
}
|
|
|
|
static void ax88796c_set_mac_addr(struct net_device *ndev)
|
|
{
|
|
struct ax88796c_device *ax_local = to_ax88796c_device(ndev);
|
|
|
|
lockdep_assert_held(&ax_local->spi_lock);
|
|
|
|
AX_WRITE(&ax_local->ax_spi, ((u16)(ndev->dev_addr[4] << 8) |
|
|
(u16)ndev->dev_addr[5]), P3_MACASR0);
|
|
AX_WRITE(&ax_local->ax_spi, ((u16)(ndev->dev_addr[2] << 8) |
|
|
(u16)ndev->dev_addr[3]), P3_MACASR1);
|
|
AX_WRITE(&ax_local->ax_spi, ((u16)(ndev->dev_addr[0] << 8) |
|
|
(u16)ndev->dev_addr[1]), P3_MACASR2);
|
|
}
|
|
|
|
static void ax88796c_load_mac_addr(struct net_device *ndev)
|
|
{
|
|
struct ax88796c_device *ax_local = to_ax88796c_device(ndev);
|
|
u8 addr[ETH_ALEN];
|
|
u16 temp;
|
|
|
|
lockdep_assert_held(&ax_local->spi_lock);
|
|
|
|
/* Try the device tree first */
|
|
if (!platform_get_ethdev_address(&ax_local->spi->dev, ndev) &&
|
|
is_valid_ether_addr(ndev->dev_addr)) {
|
|
if (netif_msg_probe(ax_local))
|
|
dev_info(&ax_local->spi->dev,
|
|
"MAC address read from device tree\n");
|
|
return;
|
|
}
|
|
|
|
/* Read the MAC address from AX88796C */
|
|
temp = AX_READ(&ax_local->ax_spi, P3_MACASR0);
|
|
addr[5] = (u8)temp;
|
|
addr[4] = (u8)(temp >> 8);
|
|
|
|
temp = AX_READ(&ax_local->ax_spi, P3_MACASR1);
|
|
addr[3] = (u8)temp;
|
|
addr[2] = (u8)(temp >> 8);
|
|
|
|
temp = AX_READ(&ax_local->ax_spi, P3_MACASR2);
|
|
addr[1] = (u8)temp;
|
|
addr[0] = (u8)(temp >> 8);
|
|
|
|
if (is_valid_ether_addr(addr)) {
|
|
eth_hw_addr_set(ndev, addr);
|
|
if (netif_msg_probe(ax_local))
|
|
dev_info(&ax_local->spi->dev,
|
|
"MAC address read from ASIX chip\n");
|
|
return;
|
|
}
|
|
|
|
/* Use random address if none found */
|
|
if (netif_msg_probe(ax_local))
|
|
dev_info(&ax_local->spi->dev, "Use random MAC address\n");
|
|
eth_hw_addr_random(ndev);
|
|
}
|
|
|
|
static void ax88796c_proc_tx_hdr(struct tx_pkt_info *info, u8 ip_summed)
|
|
{
|
|
u16 pkt_len_bar = (~info->pkt_len & TX_HDR_SOP_PKTLENBAR);
|
|
|
|
/* Prepare SOP header */
|
|
info->sop.flags_len = info->pkt_len |
|
|
((ip_summed == CHECKSUM_NONE) ||
|
|
(ip_summed == CHECKSUM_UNNECESSARY) ? TX_HDR_SOP_DICF : 0);
|
|
|
|
info->sop.seq_lenbar = ((info->seq_num << 11) & TX_HDR_SOP_SEQNUM)
|
|
| pkt_len_bar;
|
|
cpu_to_be16s(&info->sop.flags_len);
|
|
cpu_to_be16s(&info->sop.seq_lenbar);
|
|
|
|
/* Prepare Segment header */
|
|
info->seg.flags_seqnum_seglen = TX_HDR_SEG_FS | TX_HDR_SEG_LS
|
|
| info->pkt_len;
|
|
|
|
info->seg.eo_so_seglenbar = pkt_len_bar;
|
|
|
|
cpu_to_be16s(&info->seg.flags_seqnum_seglen);
|
|
cpu_to_be16s(&info->seg.eo_so_seglenbar);
|
|
|
|
/* Prepare EOP header */
|
|
info->eop.seq_len = ((info->seq_num << 11) &
|
|
TX_HDR_EOP_SEQNUM) | info->pkt_len;
|
|
info->eop.seqbar_lenbar = ((~info->seq_num << 11) &
|
|
TX_HDR_EOP_SEQNUMBAR) | pkt_len_bar;
|
|
|
|
cpu_to_be16s(&info->eop.seq_len);
|
|
cpu_to_be16s(&info->eop.seqbar_lenbar);
|
|
}
|
|
|
|
static int
|
|
ax88796c_check_free_pages(struct ax88796c_device *ax_local, u8 need_pages)
|
|
{
|
|
u8 free_pages;
|
|
u16 tmp;
|
|
|
|
lockdep_assert_held(&ax_local->spi_lock);
|
|
|
|
free_pages = AX_READ(&ax_local->ax_spi, P0_TFBFCR) & TX_FREEBUF_MASK;
|
|
if (free_pages < need_pages) {
|
|
/* schedule free page interrupt */
|
|
tmp = AX_READ(&ax_local->ax_spi, P0_TFBFCR)
|
|
& TFBFCR_SCHE_FREE_PAGE;
|
|
AX_WRITE(&ax_local->ax_spi, tmp | TFBFCR_TX_PAGE_SET |
|
|
TFBFCR_SET_FREE_PAGE(need_pages),
|
|
P0_TFBFCR);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct sk_buff *
|
|
ax88796c_tx_fixup(struct net_device *ndev, struct sk_buff_head *q)
|
|
{
|
|
struct ax88796c_device *ax_local = to_ax88796c_device(ndev);
|
|
u8 spi_len = ax_local->ax_spi.comp ? 1 : 4;
|
|
struct sk_buff *skb;
|
|
struct tx_pkt_info info;
|
|
struct skb_data *entry;
|
|
u16 pkt_len;
|
|
u8 padlen, seq_num;
|
|
u8 need_pages;
|
|
int headroom;
|
|
int tailroom;
|
|
|
|
if (skb_queue_empty(q))
|
|
return NULL;
|
|
|
|
skb = skb_peek(q);
|
|
pkt_len = skb->len;
|
|
need_pages = (pkt_len + TX_OVERHEAD + 127) >> 7;
|
|
if (ax88796c_check_free_pages(ax_local, need_pages) != 0)
|
|
return NULL;
|
|
|
|
headroom = skb_headroom(skb);
|
|
tailroom = skb_tailroom(skb);
|
|
padlen = round_up(pkt_len, 4) - pkt_len;
|
|
seq_num = ++ax_local->seq_num & 0x1F;
|
|
|
|
info.pkt_len = pkt_len;
|
|
|
|
if (skb_cloned(skb) ||
|
|
(headroom < (TX_OVERHEAD + spi_len)) ||
|
|
(tailroom < (padlen + TX_EOP_SIZE))) {
|
|
size_t h = max((TX_OVERHEAD + spi_len) - headroom, 0);
|
|
size_t t = max((padlen + TX_EOP_SIZE) - tailroom, 0);
|
|
|
|
if (pskb_expand_head(skb, h, t, GFP_KERNEL))
|
|
return NULL;
|
|
}
|
|
|
|
info.seq_num = seq_num;
|
|
ax88796c_proc_tx_hdr(&info, skb->ip_summed);
|
|
|
|
/* SOP and SEG header */
|
|
memcpy(skb_push(skb, TX_OVERHEAD), &info.sop, TX_OVERHEAD);
|
|
|
|
/* Write SPI TXQ header */
|
|
memcpy(skb_push(skb, spi_len), ax88796c_tx_cmd_buf, spi_len);
|
|
|
|
/* Make 32-bit alignment */
|
|
skb_put(skb, padlen);
|
|
|
|
/* EOP header */
|
|
skb_put_data(skb, &info.eop, TX_EOP_SIZE);
|
|
|
|
skb_unlink(skb, q);
|
|
|
|
entry = (struct skb_data *)skb->cb;
|
|
memset(entry, 0, sizeof(*entry));
|
|
entry->len = pkt_len;
|
|
|
|
if (netif_msg_pktdata(ax_local)) {
|
|
char pfx[IFNAMSIZ + 7];
|
|
|
|
snprintf(pfx, sizeof(pfx), "%s: ", ndev->name);
|
|
|
|
netdev_info(ndev, "TX packet len %d, total len %d, seq %d\n",
|
|
pkt_len, skb->len, seq_num);
|
|
|
|
netdev_info(ndev, " SPI Header:\n");
|
|
print_hex_dump(KERN_INFO, pfx, DUMP_PREFIX_OFFSET, 16, 1,
|
|
skb->data, 4, 0);
|
|
|
|
netdev_info(ndev, " TX SOP:\n");
|
|
print_hex_dump(KERN_INFO, pfx, DUMP_PREFIX_OFFSET, 16, 1,
|
|
skb->data + 4, TX_OVERHEAD, 0);
|
|
|
|
netdev_info(ndev, " TX packet:\n");
|
|
print_hex_dump(KERN_INFO, pfx, DUMP_PREFIX_OFFSET, 16, 1,
|
|
skb->data + 4 + TX_OVERHEAD,
|
|
skb->len - TX_EOP_SIZE - 4 - TX_OVERHEAD, 0);
|
|
|
|
netdev_info(ndev, " TX EOP:\n");
|
|
print_hex_dump(KERN_INFO, pfx, DUMP_PREFIX_OFFSET, 16, 1,
|
|
skb->data + skb->len - 4, 4, 0);
|
|
}
|
|
|
|
return skb;
|
|
}
|
|
|
|
static int ax88796c_hard_xmit(struct ax88796c_device *ax_local)
|
|
{
|
|
struct ax88796c_pcpu_stats *stats;
|
|
struct sk_buff *tx_skb;
|
|
struct skb_data *entry;
|
|
unsigned long flags;
|
|
|
|
lockdep_assert_held(&ax_local->spi_lock);
|
|
|
|
stats = this_cpu_ptr(ax_local->stats);
|
|
tx_skb = ax88796c_tx_fixup(ax_local->ndev, &ax_local->tx_wait_q);
|
|
|
|
if (!tx_skb) {
|
|
this_cpu_inc(ax_local->stats->tx_dropped);
|
|
return 0;
|
|
}
|
|
entry = (struct skb_data *)tx_skb->cb;
|
|
|
|
AX_WRITE(&ax_local->ax_spi,
|
|
(TSNR_TXB_START | TSNR_PKT_CNT(1)), P0_TSNR);
|
|
|
|
axspi_write_txq(&ax_local->ax_spi, tx_skb->data, tx_skb->len);
|
|
|
|
if (((AX_READ(&ax_local->ax_spi, P0_TSNR) & TXNR_TXB_IDLE) == 0) ||
|
|
((ISR_TXERR & AX_READ(&ax_local->ax_spi, P0_ISR)) != 0)) {
|
|
/* Ack tx error int */
|
|
AX_WRITE(&ax_local->ax_spi, ISR_TXERR, P0_ISR);
|
|
|
|
this_cpu_inc(ax_local->stats->tx_dropped);
|
|
|
|
if (net_ratelimit())
|
|
netif_err(ax_local, tx_err, ax_local->ndev,
|
|
"TX FIFO error, re-initialize the TX bridge\n");
|
|
|
|
/* Reinitial tx bridge */
|
|
AX_WRITE(&ax_local->ax_spi, TXNR_TXB_REINIT |
|
|
AX_READ(&ax_local->ax_spi, P0_TSNR), P0_TSNR);
|
|
ax_local->seq_num = 0;
|
|
} else {
|
|
flags = u64_stats_update_begin_irqsave(&stats->syncp);
|
|
u64_stats_inc(&stats->tx_packets);
|
|
u64_stats_add(&stats->tx_bytes, entry->len);
|
|
u64_stats_update_end_irqrestore(&stats->syncp, flags);
|
|
}
|
|
|
|
entry->state = tx_done;
|
|
dev_kfree_skb(tx_skb);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static netdev_tx_t
|
|
ax88796c_start_xmit(struct sk_buff *skb, struct net_device *ndev)
|
|
{
|
|
struct ax88796c_device *ax_local = to_ax88796c_device(ndev);
|
|
|
|
skb_queue_tail(&ax_local->tx_wait_q, skb);
|
|
if (skb_queue_len(&ax_local->tx_wait_q) > TX_QUEUE_HIGH_WATER)
|
|
netif_stop_queue(ndev);
|
|
|
|
set_bit(EVENT_TX, &ax_local->flags);
|
|
schedule_work(&ax_local->ax_work);
|
|
|
|
return NETDEV_TX_OK;
|
|
}
|
|
|
|
static void
|
|
ax88796c_skb_return(struct ax88796c_device *ax_local,
|
|
struct sk_buff *skb, struct rx_header *rxhdr)
|
|
{
|
|
struct net_device *ndev = ax_local->ndev;
|
|
struct ax88796c_pcpu_stats *stats;
|
|
unsigned long flags;
|
|
int status;
|
|
|
|
stats = this_cpu_ptr(ax_local->stats);
|
|
|
|
do {
|
|
if (!(ndev->features & NETIF_F_RXCSUM))
|
|
break;
|
|
|
|
/* checksum error bit is set */
|
|
if ((rxhdr->flags & RX_HDR3_L3_ERR) ||
|
|
(rxhdr->flags & RX_HDR3_L4_ERR))
|
|
break;
|
|
|
|
/* Other types may be indicated by more than one bit. */
|
|
if ((rxhdr->flags & RX_HDR3_L4_TYPE_TCP) ||
|
|
(rxhdr->flags & RX_HDR3_L4_TYPE_UDP))
|
|
skb->ip_summed = CHECKSUM_UNNECESSARY;
|
|
} while (0);
|
|
|
|
flags = u64_stats_update_begin_irqsave(&stats->syncp);
|
|
u64_stats_inc(&stats->rx_packets);
|
|
u64_stats_add(&stats->rx_bytes, skb->len);
|
|
u64_stats_update_end_irqrestore(&stats->syncp, flags);
|
|
|
|
skb->dev = ndev;
|
|
skb->protocol = eth_type_trans(skb, ax_local->ndev);
|
|
|
|
netif_info(ax_local, rx_status, ndev, "< rx, len %zu, type 0x%x\n",
|
|
skb->len + sizeof(struct ethhdr), skb->protocol);
|
|
|
|
status = netif_rx(skb);
|
|
if (status != NET_RX_SUCCESS && net_ratelimit())
|
|
netif_info(ax_local, rx_err, ndev,
|
|
"netif_rx status %d\n", status);
|
|
}
|
|
|
|
static void
|
|
ax88796c_rx_fixup(struct ax88796c_device *ax_local, struct sk_buff *rx_skb)
|
|
{
|
|
struct rx_header *rxhdr = (struct rx_header *)rx_skb->data;
|
|
struct net_device *ndev = ax_local->ndev;
|
|
u16 len;
|
|
|
|
be16_to_cpus(&rxhdr->flags_len);
|
|
be16_to_cpus(&rxhdr->seq_lenbar);
|
|
be16_to_cpus(&rxhdr->flags);
|
|
|
|
if ((rxhdr->flags_len & RX_HDR1_PKT_LEN) !=
|
|
(~rxhdr->seq_lenbar & 0x7FF)) {
|
|
netif_err(ax_local, rx_err, ndev, "Header error\n");
|
|
|
|
this_cpu_inc(ax_local->stats->rx_frame_errors);
|
|
kfree_skb(rx_skb);
|
|
return;
|
|
}
|
|
|
|
if ((rxhdr->flags_len & RX_HDR1_MII_ERR) ||
|
|
(rxhdr->flags_len & RX_HDR1_CRC_ERR)) {
|
|
netif_err(ax_local, rx_err, ndev, "CRC or MII error\n");
|
|
|
|
this_cpu_inc(ax_local->stats->rx_crc_errors);
|
|
kfree_skb(rx_skb);
|
|
return;
|
|
}
|
|
|
|
len = rxhdr->flags_len & RX_HDR1_PKT_LEN;
|
|
if (netif_msg_pktdata(ax_local)) {
|
|
char pfx[IFNAMSIZ + 7];
|
|
|
|
snprintf(pfx, sizeof(pfx), "%s: ", ndev->name);
|
|
netdev_info(ndev, "RX data, total len %d, packet len %d\n",
|
|
rx_skb->len, len);
|
|
|
|
netdev_info(ndev, " Dump RX packet header:");
|
|
print_hex_dump(KERN_INFO, pfx, DUMP_PREFIX_OFFSET, 16, 1,
|
|
rx_skb->data, sizeof(*rxhdr), 0);
|
|
|
|
netdev_info(ndev, " Dump RX packet:");
|
|
print_hex_dump(KERN_INFO, pfx, DUMP_PREFIX_OFFSET, 16, 1,
|
|
rx_skb->data + sizeof(*rxhdr), len, 0);
|
|
}
|
|
|
|
skb_pull(rx_skb, sizeof(*rxhdr));
|
|
pskb_trim(rx_skb, len);
|
|
|
|
ax88796c_skb_return(ax_local, rx_skb, rxhdr);
|
|
}
|
|
|
|
static int ax88796c_receive(struct net_device *ndev)
|
|
{
|
|
struct ax88796c_device *ax_local = to_ax88796c_device(ndev);
|
|
struct skb_data *entry;
|
|
u16 w_count, pkt_len;
|
|
struct sk_buff *skb;
|
|
u8 pkt_cnt;
|
|
|
|
lockdep_assert_held(&ax_local->spi_lock);
|
|
|
|
/* check rx packet and total word count */
|
|
AX_WRITE(&ax_local->ax_spi, AX_READ(&ax_local->ax_spi, P0_RTWCR)
|
|
| RTWCR_RX_LATCH, P0_RTWCR);
|
|
|
|
pkt_cnt = AX_READ(&ax_local->ax_spi, P0_RXBCR2) & RXBCR2_PKT_MASK;
|
|
if (!pkt_cnt)
|
|
return 0;
|
|
|
|
pkt_len = AX_READ(&ax_local->ax_spi, P0_RCPHR) & 0x7FF;
|
|
|
|
w_count = round_up(pkt_len + 6, 4) >> 1;
|
|
|
|
skb = netdev_alloc_skb(ndev, w_count * 2);
|
|
if (!skb) {
|
|
AX_WRITE(&ax_local->ax_spi, RXBCR1_RXB_DISCARD, P0_RXBCR1);
|
|
this_cpu_inc(ax_local->stats->rx_dropped);
|
|
return 0;
|
|
}
|
|
entry = (struct skb_data *)skb->cb;
|
|
|
|
AX_WRITE(&ax_local->ax_spi, RXBCR1_RXB_START | w_count, P0_RXBCR1);
|
|
|
|
axspi_read_rxq(&ax_local->ax_spi,
|
|
skb_put(skb, w_count * 2), skb->len);
|
|
|
|
/* Check if rx bridge is idle */
|
|
if ((AX_READ(&ax_local->ax_spi, P0_RXBCR2) & RXBCR2_RXB_IDLE) == 0) {
|
|
if (net_ratelimit())
|
|
netif_err(ax_local, rx_err, ndev,
|
|
"Rx Bridge is not idle\n");
|
|
AX_WRITE(&ax_local->ax_spi, RXBCR2_RXB_REINIT, P0_RXBCR2);
|
|
|
|
entry->state = rx_err;
|
|
} else {
|
|
entry->state = rx_done;
|
|
}
|
|
|
|
AX_WRITE(&ax_local->ax_spi, ISR_RXPKT, P0_ISR);
|
|
|
|
ax88796c_rx_fixup(ax_local, skb);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int ax88796c_process_isr(struct ax88796c_device *ax_local)
|
|
{
|
|
struct net_device *ndev = ax_local->ndev;
|
|
int todo = 0;
|
|
u16 isr;
|
|
|
|
lockdep_assert_held(&ax_local->spi_lock);
|
|
|
|
isr = AX_READ(&ax_local->ax_spi, P0_ISR);
|
|
AX_WRITE(&ax_local->ax_spi, isr, P0_ISR);
|
|
|
|
netif_dbg(ax_local, intr, ndev, " ISR 0x%04x\n", isr);
|
|
|
|
if (isr & ISR_TXERR) {
|
|
netif_dbg(ax_local, intr, ndev, " TXERR interrupt\n");
|
|
AX_WRITE(&ax_local->ax_spi, TXNR_TXB_REINIT, P0_TSNR);
|
|
ax_local->seq_num = 0x1f;
|
|
}
|
|
|
|
if (isr & ISR_TXPAGES) {
|
|
netif_dbg(ax_local, intr, ndev, " TXPAGES interrupt\n");
|
|
set_bit(EVENT_TX, &ax_local->flags);
|
|
}
|
|
|
|
if (isr & ISR_LINK) {
|
|
netif_dbg(ax_local, intr, ndev, " Link change interrupt\n");
|
|
phy_mac_interrupt(ax_local->ndev->phydev);
|
|
}
|
|
|
|
if (isr & ISR_RXPKT) {
|
|
netif_dbg(ax_local, intr, ndev, " RX interrupt\n");
|
|
todo = ax88796c_receive(ax_local->ndev);
|
|
}
|
|
|
|
return todo;
|
|
}
|
|
|
|
static irqreturn_t ax88796c_interrupt(int irq, void *dev_instance)
|
|
{
|
|
struct ax88796c_device *ax_local;
|
|
struct net_device *ndev;
|
|
|
|
ndev = dev_instance;
|
|
if (!ndev) {
|
|
pr_err("irq %d for unknown device.\n", irq);
|
|
return IRQ_RETVAL(0);
|
|
}
|
|
ax_local = to_ax88796c_device(ndev);
|
|
|
|
disable_irq_nosync(irq);
|
|
|
|
netif_dbg(ax_local, intr, ndev, "Interrupt occurred\n");
|
|
|
|
set_bit(EVENT_INTR, &ax_local->flags);
|
|
schedule_work(&ax_local->ax_work);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static void ax88796c_work(struct work_struct *work)
|
|
{
|
|
struct ax88796c_device *ax_local =
|
|
container_of(work, struct ax88796c_device, ax_work);
|
|
|
|
mutex_lock(&ax_local->spi_lock);
|
|
|
|
if (test_bit(EVENT_SET_MULTI, &ax_local->flags)) {
|
|
ax88796c_set_hw_multicast(ax_local->ndev);
|
|
clear_bit(EVENT_SET_MULTI, &ax_local->flags);
|
|
}
|
|
|
|
if (test_bit(EVENT_INTR, &ax_local->flags)) {
|
|
AX_WRITE(&ax_local->ax_spi, IMR_MASKALL, P0_IMR);
|
|
|
|
while (ax88796c_process_isr(ax_local))
|
|
/* nothing */;
|
|
|
|
clear_bit(EVENT_INTR, &ax_local->flags);
|
|
|
|
AX_WRITE(&ax_local->ax_spi, IMR_DEFAULT, P0_IMR);
|
|
|
|
enable_irq(ax_local->ndev->irq);
|
|
}
|
|
|
|
if (test_bit(EVENT_TX, &ax_local->flags)) {
|
|
while (skb_queue_len(&ax_local->tx_wait_q)) {
|
|
if (!ax88796c_hard_xmit(ax_local))
|
|
break;
|
|
}
|
|
|
|
clear_bit(EVENT_TX, &ax_local->flags);
|
|
|
|
if (netif_queue_stopped(ax_local->ndev) &&
|
|
(skb_queue_len(&ax_local->tx_wait_q) < TX_QUEUE_LOW_WATER))
|
|
netif_wake_queue(ax_local->ndev);
|
|
}
|
|
|
|
mutex_unlock(&ax_local->spi_lock);
|
|
}
|
|
|
|
static void ax88796c_get_stats64(struct net_device *ndev,
|
|
struct rtnl_link_stats64 *stats)
|
|
{
|
|
struct ax88796c_device *ax_local = to_ax88796c_device(ndev);
|
|
u32 rx_frame_errors = 0, rx_crc_errors = 0;
|
|
u32 rx_dropped = 0, tx_dropped = 0;
|
|
unsigned int start;
|
|
int cpu;
|
|
|
|
for_each_possible_cpu(cpu) {
|
|
struct ax88796c_pcpu_stats *s;
|
|
u64 rx_packets, rx_bytes;
|
|
u64 tx_packets, tx_bytes;
|
|
|
|
s = per_cpu_ptr(ax_local->stats, cpu);
|
|
|
|
do {
|
|
start = u64_stats_fetch_begin(&s->syncp);
|
|
rx_packets = u64_stats_read(&s->rx_packets);
|
|
rx_bytes = u64_stats_read(&s->rx_bytes);
|
|
tx_packets = u64_stats_read(&s->tx_packets);
|
|
tx_bytes = u64_stats_read(&s->tx_bytes);
|
|
} while (u64_stats_fetch_retry(&s->syncp, start));
|
|
|
|
stats->rx_packets += rx_packets;
|
|
stats->rx_bytes += rx_bytes;
|
|
stats->tx_packets += tx_packets;
|
|
stats->tx_bytes += tx_bytes;
|
|
|
|
rx_dropped += s->rx_dropped;
|
|
tx_dropped += s->tx_dropped;
|
|
rx_frame_errors += s->rx_frame_errors;
|
|
rx_crc_errors += s->rx_crc_errors;
|
|
}
|
|
|
|
stats->rx_dropped = rx_dropped;
|
|
stats->tx_dropped = tx_dropped;
|
|
stats->rx_frame_errors = rx_frame_errors;
|
|
stats->rx_crc_errors = rx_crc_errors;
|
|
}
|
|
|
|
static void ax88796c_set_mac(struct ax88796c_device *ax_local)
|
|
{
|
|
u16 maccr;
|
|
|
|
maccr = (ax_local->link) ? MACCR_RXEN : 0;
|
|
|
|
switch (ax_local->speed) {
|
|
case SPEED_100:
|
|
maccr |= MACCR_SPEED_100;
|
|
break;
|
|
case SPEED_10:
|
|
case SPEED_UNKNOWN:
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
switch (ax_local->duplex) {
|
|
case DUPLEX_FULL:
|
|
maccr |= MACCR_SPEED_100;
|
|
break;
|
|
case DUPLEX_HALF:
|
|
case DUPLEX_UNKNOWN:
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
if (ax_local->flowctrl & AX_FC_ANEG &&
|
|
ax_local->phydev->autoneg) {
|
|
maccr |= ax_local->pause ? MACCR_RXFC_ENABLE : 0;
|
|
maccr |= !ax_local->pause != !ax_local->asym_pause ?
|
|
MACCR_TXFC_ENABLE : 0;
|
|
} else {
|
|
maccr |= (ax_local->flowctrl & AX_FC_RX) ? MACCR_RXFC_ENABLE : 0;
|
|
maccr |= (ax_local->flowctrl & AX_FC_TX) ? MACCR_TXFC_ENABLE : 0;
|
|
}
|
|
|
|
mutex_lock(&ax_local->spi_lock);
|
|
|
|
maccr |= AX_READ(&ax_local->ax_spi, P0_MACCR) &
|
|
~(MACCR_DUPLEX_FULL | MACCR_SPEED_100 |
|
|
MACCR_TXFC_ENABLE | MACCR_RXFC_ENABLE);
|
|
AX_WRITE(&ax_local->ax_spi, maccr, P0_MACCR);
|
|
|
|
mutex_unlock(&ax_local->spi_lock);
|
|
}
|
|
|
|
static void ax88796c_handle_link_change(struct net_device *ndev)
|
|
{
|
|
struct ax88796c_device *ax_local = to_ax88796c_device(ndev);
|
|
struct phy_device *phydev = ndev->phydev;
|
|
bool update = false;
|
|
|
|
if (phydev->link && (ax_local->speed != phydev->speed ||
|
|
ax_local->duplex != phydev->duplex ||
|
|
ax_local->pause != phydev->pause ||
|
|
ax_local->asym_pause != phydev->asym_pause)) {
|
|
ax_local->speed = phydev->speed;
|
|
ax_local->duplex = phydev->duplex;
|
|
ax_local->pause = phydev->pause;
|
|
ax_local->asym_pause = phydev->asym_pause;
|
|
update = true;
|
|
}
|
|
|
|
if (phydev->link != ax_local->link) {
|
|
if (!phydev->link) {
|
|
ax_local->speed = SPEED_UNKNOWN;
|
|
ax_local->duplex = DUPLEX_UNKNOWN;
|
|
}
|
|
|
|
ax_local->link = phydev->link;
|
|
update = true;
|
|
}
|
|
|
|
if (update)
|
|
ax88796c_set_mac(ax_local);
|
|
|
|
if (net_ratelimit())
|
|
phy_print_status(ndev->phydev);
|
|
}
|
|
|
|
static void ax88796c_set_csums(struct ax88796c_device *ax_local)
|
|
{
|
|
struct net_device *ndev = ax_local->ndev;
|
|
|
|
lockdep_assert_held(&ax_local->spi_lock);
|
|
|
|
if (ndev->features & NETIF_F_RXCSUM) {
|
|
AX_WRITE(&ax_local->ax_spi, COERCR0_DEFAULT, P4_COERCR0);
|
|
AX_WRITE(&ax_local->ax_spi, COERCR1_DEFAULT, P4_COERCR1);
|
|
} else {
|
|
AX_WRITE(&ax_local->ax_spi, 0, P4_COERCR0);
|
|
AX_WRITE(&ax_local->ax_spi, 0, P4_COERCR1);
|
|
}
|
|
|
|
if (ndev->features & NETIF_F_HW_CSUM) {
|
|
AX_WRITE(&ax_local->ax_spi, COETCR0_DEFAULT, P4_COETCR0);
|
|
AX_WRITE(&ax_local->ax_spi, COETCR1_TXPPPE, P4_COETCR1);
|
|
} else {
|
|
AX_WRITE(&ax_local->ax_spi, 0, P4_COETCR0);
|
|
AX_WRITE(&ax_local->ax_spi, 0, P4_COETCR1);
|
|
}
|
|
}
|
|
|
|
static int
|
|
ax88796c_open(struct net_device *ndev)
|
|
{
|
|
struct ax88796c_device *ax_local = to_ax88796c_device(ndev);
|
|
unsigned long irq_flag = 0;
|
|
int fc = AX_FC_NONE;
|
|
int ret;
|
|
u16 t;
|
|
|
|
ret = request_irq(ndev->irq, ax88796c_interrupt,
|
|
irq_flag, ndev->name, ndev);
|
|
if (ret) {
|
|
netdev_err(ndev, "unable to get IRQ %d (errno=%d).\n",
|
|
ndev->irq, ret);
|
|
return ret;
|
|
}
|
|
|
|
mutex_lock(&ax_local->spi_lock);
|
|
|
|
ret = ax88796c_soft_reset(ax_local);
|
|
if (ret < 0) {
|
|
free_irq(ndev->irq, ndev);
|
|
mutex_unlock(&ax_local->spi_lock);
|
|
return ret;
|
|
}
|
|
ax_local->seq_num = 0x1f;
|
|
|
|
ax88796c_set_mac_addr(ndev);
|
|
ax88796c_set_csums(ax_local);
|
|
|
|
/* Disable stuffing packet */
|
|
t = AX_READ(&ax_local->ax_spi, P1_RXBSPCR);
|
|
t &= ~RXBSPCR_STUF_ENABLE;
|
|
AX_WRITE(&ax_local->ax_spi, t, P1_RXBSPCR);
|
|
|
|
/* Enable RX packet process */
|
|
AX_WRITE(&ax_local->ax_spi, RPPER_RXEN, P1_RPPER);
|
|
|
|
t = AX_READ(&ax_local->ax_spi, P0_FER);
|
|
t |= FER_RXEN | FER_TXEN | FER_BSWAP | FER_IRQ_PULL;
|
|
AX_WRITE(&ax_local->ax_spi, t, P0_FER);
|
|
|
|
/* Setup LED mode */
|
|
AX_WRITE(&ax_local->ax_spi,
|
|
(LCR_LED0_EN | LCR_LED0_DUPLEX | LCR_LED1_EN |
|
|
LCR_LED1_100MODE), P2_LCR0);
|
|
AX_WRITE(&ax_local->ax_spi,
|
|
(AX_READ(&ax_local->ax_spi, P2_LCR1) & LCR_LED2_MASK) |
|
|
LCR_LED2_EN | LCR_LED2_LINK, P2_LCR1);
|
|
|
|
/* Disable PHY auto-polling */
|
|
AX_WRITE(&ax_local->ax_spi, PCR_PHYID(AX88796C_PHY_ID), P2_PCR);
|
|
|
|
/* Enable MAC interrupts */
|
|
AX_WRITE(&ax_local->ax_spi, IMR_DEFAULT, P0_IMR);
|
|
|
|
mutex_unlock(&ax_local->spi_lock);
|
|
|
|
/* Setup flow-control configuration */
|
|
phy_support_asym_pause(ax_local->phydev);
|
|
|
|
if (linkmode_test_bit(ETHTOOL_LINK_MODE_Pause_BIT,
|
|
ax_local->phydev->advertising) ||
|
|
linkmode_test_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT,
|
|
ax_local->phydev->advertising))
|
|
fc |= AX_FC_ANEG;
|
|
|
|
fc |= linkmode_test_bit(ETHTOOL_LINK_MODE_Pause_BIT,
|
|
ax_local->phydev->advertising) ? AX_FC_RX : 0;
|
|
fc |= (linkmode_test_bit(ETHTOOL_LINK_MODE_Pause_BIT,
|
|
ax_local->phydev->advertising) !=
|
|
linkmode_test_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT,
|
|
ax_local->phydev->advertising)) ? AX_FC_TX : 0;
|
|
ax_local->flowctrl = fc;
|
|
|
|
phy_start(ax_local->ndev->phydev);
|
|
|
|
netif_start_queue(ndev);
|
|
|
|
spi_message_init(&ax_local->ax_spi.rx_msg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
ax88796c_close(struct net_device *ndev)
|
|
{
|
|
struct ax88796c_device *ax_local = to_ax88796c_device(ndev);
|
|
|
|
phy_stop(ndev->phydev);
|
|
|
|
/* We lock the mutex early not only to protect the device
|
|
* against concurrent access, but also avoid waking up the
|
|
* queue in ax88796c_work(). phy_stop() needs to be called
|
|
* before because it locks the mutex to access SPI.
|
|
*/
|
|
mutex_lock(&ax_local->spi_lock);
|
|
|
|
netif_stop_queue(ndev);
|
|
|
|
/* No more work can be scheduled now. Make any pending work,
|
|
* including one already waiting for the mutex to be unlocked,
|
|
* NOP.
|
|
*/
|
|
netif_dbg(ax_local, ifdown, ndev, "clearing bits\n");
|
|
clear_bit(EVENT_SET_MULTI, &ax_local->flags);
|
|
clear_bit(EVENT_INTR, &ax_local->flags);
|
|
clear_bit(EVENT_TX, &ax_local->flags);
|
|
|
|
/* Disable MAC interrupts */
|
|
AX_WRITE(&ax_local->ax_spi, IMR_MASKALL, P0_IMR);
|
|
__skb_queue_purge(&ax_local->tx_wait_q);
|
|
ax88796c_soft_reset(ax_local);
|
|
|
|
mutex_unlock(&ax_local->spi_lock);
|
|
|
|
cancel_work_sync(&ax_local->ax_work);
|
|
|
|
free_irq(ndev->irq, ndev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
ax88796c_set_features(struct net_device *ndev, netdev_features_t features)
|
|
{
|
|
struct ax88796c_device *ax_local = to_ax88796c_device(ndev);
|
|
netdev_features_t changed = features ^ ndev->features;
|
|
|
|
if (!(changed & (NETIF_F_RXCSUM | NETIF_F_HW_CSUM)))
|
|
return 0;
|
|
|
|
ndev->features = features;
|
|
|
|
if (changed & (NETIF_F_RXCSUM | NETIF_F_HW_CSUM))
|
|
ax88796c_set_csums(ax_local);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct net_device_ops ax88796c_netdev_ops = {
|
|
.ndo_open = ax88796c_open,
|
|
.ndo_stop = ax88796c_close,
|
|
.ndo_start_xmit = ax88796c_start_xmit,
|
|
.ndo_get_stats64 = ax88796c_get_stats64,
|
|
.ndo_eth_ioctl = ax88796c_ioctl,
|
|
.ndo_set_mac_address = eth_mac_addr,
|
|
.ndo_set_features = ax88796c_set_features,
|
|
};
|
|
|
|
static int ax88796c_hard_reset(struct ax88796c_device *ax_local)
|
|
{
|
|
struct device *dev = (struct device *)&ax_local->spi->dev;
|
|
struct gpio_desc *reset_gpio;
|
|
|
|
/* reset info */
|
|
reset_gpio = gpiod_get(dev, "reset", 0);
|
|
if (IS_ERR(reset_gpio)) {
|
|
dev_err(dev, "Could not get 'reset' GPIO: %ld", PTR_ERR(reset_gpio));
|
|
return PTR_ERR(reset_gpio);
|
|
}
|
|
|
|
/* set reset */
|
|
gpiod_direction_output(reset_gpio, 1);
|
|
msleep(100);
|
|
gpiod_direction_output(reset_gpio, 0);
|
|
gpiod_put(reset_gpio);
|
|
msleep(20);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ax88796c_probe(struct spi_device *spi)
|
|
{
|
|
char phy_id[MII_BUS_ID_SIZE + 3];
|
|
struct ax88796c_device *ax_local;
|
|
struct net_device *ndev;
|
|
u16 temp;
|
|
int ret;
|
|
|
|
ndev = devm_alloc_etherdev(&spi->dev, sizeof(*ax_local));
|
|
if (!ndev)
|
|
return -ENOMEM;
|
|
|
|
SET_NETDEV_DEV(ndev, &spi->dev);
|
|
|
|
ax_local = to_ax88796c_device(ndev);
|
|
|
|
dev_set_drvdata(&spi->dev, ax_local);
|
|
ax_local->spi = spi;
|
|
ax_local->ax_spi.spi = spi;
|
|
|
|
ax_local->stats =
|
|
devm_netdev_alloc_pcpu_stats(&spi->dev,
|
|
struct ax88796c_pcpu_stats);
|
|
if (!ax_local->stats)
|
|
return -ENOMEM;
|
|
|
|
ax_local->ndev = ndev;
|
|
ax_local->priv_flags |= comp ? AX_CAP_COMP : 0;
|
|
ax_local->msg_enable = msg_enable;
|
|
mutex_init(&ax_local->spi_lock);
|
|
|
|
ax_local->mdiobus = devm_mdiobus_alloc(&spi->dev);
|
|
if (!ax_local->mdiobus)
|
|
return -ENOMEM;
|
|
|
|
ax_local->mdiobus->priv = ax_local;
|
|
ax_local->mdiobus->read = ax88796c_mdio_read;
|
|
ax_local->mdiobus->write = ax88796c_mdio_write;
|
|
ax_local->mdiobus->name = "ax88976c-mdiobus";
|
|
ax_local->mdiobus->phy_mask = (u32)~BIT(AX88796C_PHY_ID);
|
|
ax_local->mdiobus->parent = &spi->dev;
|
|
|
|
snprintf(ax_local->mdiobus->id, MII_BUS_ID_SIZE,
|
|
"ax88796c-%s.%u", dev_name(&spi->dev), spi_get_chipselect(spi, 0));
|
|
|
|
ret = devm_mdiobus_register(&spi->dev, ax_local->mdiobus);
|
|
if (ret < 0) {
|
|
dev_err(&spi->dev, "Could not register MDIO bus\n");
|
|
return ret;
|
|
}
|
|
|
|
if (netif_msg_probe(ax_local)) {
|
|
dev_info(&spi->dev, "AX88796C-SPI Configuration:\n");
|
|
dev_info(&spi->dev, " Compression : %s\n",
|
|
ax_local->priv_flags & AX_CAP_COMP ? "ON" : "OFF");
|
|
}
|
|
|
|
ndev->irq = spi->irq;
|
|
ndev->netdev_ops = &ax88796c_netdev_ops;
|
|
ndev->ethtool_ops = &ax88796c_ethtool_ops;
|
|
ndev->hw_features |= NETIF_F_HW_CSUM | NETIF_F_RXCSUM;
|
|
ndev->features |= NETIF_F_HW_CSUM | NETIF_F_RXCSUM;
|
|
ndev->needed_headroom = TX_OVERHEAD;
|
|
ndev->needed_tailroom = TX_EOP_SIZE;
|
|
|
|
mutex_lock(&ax_local->spi_lock);
|
|
|
|
/* ax88796c gpio reset */
|
|
ax88796c_hard_reset(ax_local);
|
|
|
|
/* Reset AX88796C */
|
|
ret = ax88796c_soft_reset(ax_local);
|
|
if (ret < 0) {
|
|
ret = -ENODEV;
|
|
mutex_unlock(&ax_local->spi_lock);
|
|
goto err;
|
|
}
|
|
/* Check board revision */
|
|
temp = AX_READ(&ax_local->ax_spi, P2_CRIR);
|
|
if ((temp & 0xF) != 0x0) {
|
|
dev_err(&spi->dev, "spi read failed: %d\n", temp);
|
|
ret = -ENODEV;
|
|
mutex_unlock(&ax_local->spi_lock);
|
|
goto err;
|
|
}
|
|
|
|
/*Reload EEPROM*/
|
|
ax88796c_reload_eeprom(ax_local);
|
|
|
|
ax88796c_load_mac_addr(ndev);
|
|
|
|
if (netif_msg_probe(ax_local))
|
|
dev_info(&spi->dev,
|
|
"irq %d, MAC addr %02X:%02X:%02X:%02X:%02X:%02X\n",
|
|
ndev->irq,
|
|
ndev->dev_addr[0], ndev->dev_addr[1],
|
|
ndev->dev_addr[2], ndev->dev_addr[3],
|
|
ndev->dev_addr[4], ndev->dev_addr[5]);
|
|
|
|
/* Disable power saving */
|
|
AX_WRITE(&ax_local->ax_spi, (AX_READ(&ax_local->ax_spi, P0_PSCR)
|
|
& PSCR_PS_MASK) | PSCR_PS_D0, P0_PSCR);
|
|
|
|
mutex_unlock(&ax_local->spi_lock);
|
|
|
|
INIT_WORK(&ax_local->ax_work, ax88796c_work);
|
|
|
|
skb_queue_head_init(&ax_local->tx_wait_q);
|
|
|
|
snprintf(phy_id, MII_BUS_ID_SIZE + 3, PHY_ID_FMT,
|
|
ax_local->mdiobus->id, AX88796C_PHY_ID);
|
|
ax_local->phydev = phy_connect(ax_local->ndev, phy_id,
|
|
ax88796c_handle_link_change,
|
|
PHY_INTERFACE_MODE_MII);
|
|
if (IS_ERR(ax_local->phydev)) {
|
|
ret = PTR_ERR(ax_local->phydev);
|
|
goto err;
|
|
}
|
|
ax_local->phydev->irq = PHY_POLL;
|
|
|
|
ret = devm_register_netdev(&spi->dev, ndev);
|
|
if (ret) {
|
|
dev_err(&spi->dev, "failed to register a network device\n");
|
|
goto err_phy_dis;
|
|
}
|
|
|
|
netif_info(ax_local, probe, ndev, "%s %s registered\n",
|
|
dev_driver_string(&spi->dev),
|
|
dev_name(&spi->dev));
|
|
phy_attached_info(ax_local->phydev);
|
|
|
|
return 0;
|
|
|
|
err_phy_dis:
|
|
phy_disconnect(ax_local->phydev);
|
|
err:
|
|
return ret;
|
|
}
|
|
|
|
static void ax88796c_remove(struct spi_device *spi)
|
|
{
|
|
struct ax88796c_device *ax_local = dev_get_drvdata(&spi->dev);
|
|
struct net_device *ndev = ax_local->ndev;
|
|
|
|
phy_disconnect(ndev->phydev);
|
|
|
|
netif_info(ax_local, probe, ndev, "removing network device %s %s\n",
|
|
dev_driver_string(&spi->dev),
|
|
dev_name(&spi->dev));
|
|
}
|
|
|
|
#ifdef CONFIG_OF
|
|
static const struct of_device_id ax88796c_dt_ids[] = {
|
|
{ .compatible = "asix,ax88796c" },
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, ax88796c_dt_ids);
|
|
#endif
|
|
|
|
static const struct spi_device_id asix_id[] = {
|
|
{ "ax88796c", 0 },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(spi, asix_id);
|
|
|
|
static struct spi_driver ax88796c_spi_driver = {
|
|
.driver = {
|
|
.name = DRV_NAME,
|
|
.of_match_table = of_match_ptr(ax88796c_dt_ids),
|
|
},
|
|
.probe = ax88796c_probe,
|
|
.remove = ax88796c_remove,
|
|
.id_table = asix_id,
|
|
};
|
|
|
|
static __init int ax88796c_spi_init(void)
|
|
{
|
|
int ret;
|
|
|
|
bitmap_zero(ax88796c_no_regs_mask, AX88796C_REGDUMP_LEN);
|
|
ret = bitmap_parse(no_regs_list, 35,
|
|
ax88796c_no_regs_mask, AX88796C_REGDUMP_LEN);
|
|
if (ret) {
|
|
bitmap_fill(ax88796c_no_regs_mask, AX88796C_REGDUMP_LEN);
|
|
pr_err("Invalid bitmap description, masking all registers\n");
|
|
}
|
|
|
|
return spi_register_driver(&ax88796c_spi_driver);
|
|
}
|
|
|
|
static __exit void ax88796c_spi_exit(void)
|
|
{
|
|
spi_unregister_driver(&ax88796c_spi_driver);
|
|
}
|
|
|
|
module_init(ax88796c_spi_init);
|
|
module_exit(ax88796c_spi_exit);
|
|
|
|
MODULE_AUTHOR("Łukasz Stelmach <l.stelmach@samsung.com>");
|
|
MODULE_DESCRIPTION("ASIX AX88796C SPI Ethernet driver");
|
|
MODULE_LICENSE("GPL");
|