mirror of
https://git.proxmox.com/git/mirror_ubuntu-kernels.git
synced 2025-12-22 09:18:42 +00:00
The qca8xxx switch supports 2 way to write reg values, a slow way using
mdio and a fast way by sending specially crafted mgmt packet to
read/write reg.
The fast way can support up to 32 bytes of data as eth packet are used
to send/receive.
This correctly works for almost the entire regmap of the switch but with
the use of some kernel selftests for dsa drivers it was found a funny
and interesting hw defect/limitation.
For some specific reg, bulk write won't work and will result in writing
only part of the requested regs resulting in half data written. This was
especially hard to track and discover due to the total strangeness of
the problem and also by the specific regs where this occurs.
This occurs in the specific regs of the ATU table, where multiple entry
needs to be written to compose the entire entry.
It was discovered that with a bulk write of 12 bytes on
QCA8K_REG_ATU_DATA0 only QCA8K_REG_ATU_DATA0 and QCA8K_REG_ATU_DATA2
were written, but QCA8K_REG_ATU_DATA1 was always zero.
Tcpdump was used to make sure the specially crafted packet was correct
and this was confirmed.
The problem was hard to track as the lack of QCA8K_REG_ATU_DATA1
resulted in an entry somehow possible as the first bytes of the mac
address are set in QCA8K_REG_ATU_DATA0 and the entry type is set in
QCA8K_REG_ATU_DATA2.
Funlly enough writing QCA8K_REG_ATU_DATA1 results in the same problem
with QCA8K_REG_ATU_DATA2 empty and QCA8K_REG_ATU_DATA1 and
QCA8K_REG_ATU_FUNC correctly written.
A speculation on the problem might be that there are some kind of
indirection internally when accessing these regs and they can't be
accessed all together, due to the fact that it's really a table mapped
somewhere in the switch SRAM.
Even more funny is the fact that every other reg was tested with all
kind of combination and they are not affected by this problem. Read
operation was also tested and always worked so it's not affected by this
problem.
The problem is not present if we limit writing a single reg at times.
To handle this hardware defect, enable use_single_write so that bulk
api can correctly split the write in multiple different operation
effectively reverting to a non-bulk write.
Cc: Mark Brown <broonie@kernel.org>
Fixes: c766e077d9 ("net: dsa: qca8k: convert to regmap read/write API")
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
Cc: stable@vger.kernel.org
Signed-off-by: David S. Miller <davem@davemloft.net>
2188 lines
54 KiB
C
2188 lines
54 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (C) 2009 Felix Fietkau <nbd@nbd.name>
|
|
* Copyright (C) 2011-2012 Gabor Juhos <juhosg@openwrt.org>
|
|
* Copyright (c) 2015, 2019, The Linux Foundation. All rights reserved.
|
|
* Copyright (c) 2016 John Crispin <john@phrozen.org>
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/phy.h>
|
|
#include <linux/netdevice.h>
|
|
#include <linux/bitfield.h>
|
|
#include <linux/regmap.h>
|
|
#include <net/dsa.h>
|
|
#include <linux/of_net.h>
|
|
#include <linux/of_mdio.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/mdio.h>
|
|
#include <linux/phylink.h>
|
|
#include <linux/gpio/consumer.h>
|
|
#include <linux/etherdevice.h>
|
|
#include <linux/dsa/tag_qca.h>
|
|
|
|
#include "qca8k.h"
|
|
#include "qca8k_leds.h"
|
|
|
|
static void
|
|
qca8k_split_addr(u32 regaddr, u16 *r1, u16 *r2, u16 *page)
|
|
{
|
|
regaddr >>= 1;
|
|
*r1 = regaddr & 0x1e;
|
|
|
|
regaddr >>= 5;
|
|
*r2 = regaddr & 0x7;
|
|
|
|
regaddr >>= 3;
|
|
*page = regaddr & 0x3ff;
|
|
}
|
|
|
|
static int
|
|
qca8k_mii_write_lo(struct mii_bus *bus, int phy_id, u32 regnum, u32 val)
|
|
{
|
|
int ret;
|
|
u16 lo;
|
|
|
|
lo = val & 0xffff;
|
|
ret = bus->write(bus, phy_id, regnum, lo);
|
|
if (ret < 0)
|
|
dev_err_ratelimited(&bus->dev,
|
|
"failed to write qca8k 32bit lo register\n");
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
qca8k_mii_write_hi(struct mii_bus *bus, int phy_id, u32 regnum, u32 val)
|
|
{
|
|
int ret;
|
|
u16 hi;
|
|
|
|
hi = (u16)(val >> 16);
|
|
ret = bus->write(bus, phy_id, regnum, hi);
|
|
if (ret < 0)
|
|
dev_err_ratelimited(&bus->dev,
|
|
"failed to write qca8k 32bit hi register\n");
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
qca8k_mii_read_lo(struct mii_bus *bus, int phy_id, u32 regnum, u32 *val)
|
|
{
|
|
int ret;
|
|
|
|
ret = bus->read(bus, phy_id, regnum);
|
|
if (ret < 0)
|
|
goto err;
|
|
|
|
*val = ret & 0xffff;
|
|
return 0;
|
|
|
|
err:
|
|
dev_err_ratelimited(&bus->dev,
|
|
"failed to read qca8k 32bit lo register\n");
|
|
*val = 0;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
qca8k_mii_read_hi(struct mii_bus *bus, int phy_id, u32 regnum, u32 *val)
|
|
{
|
|
int ret;
|
|
|
|
ret = bus->read(bus, phy_id, regnum);
|
|
if (ret < 0)
|
|
goto err;
|
|
|
|
*val = ret << 16;
|
|
return 0;
|
|
|
|
err:
|
|
dev_err_ratelimited(&bus->dev,
|
|
"failed to read qca8k 32bit hi register\n");
|
|
*val = 0;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
qca8k_mii_read32(struct mii_bus *bus, int phy_id, u32 regnum, u32 *val)
|
|
{
|
|
u32 hi, lo;
|
|
int ret;
|
|
|
|
*val = 0;
|
|
|
|
ret = qca8k_mii_read_lo(bus, phy_id, regnum, &lo);
|
|
if (ret < 0)
|
|
goto err;
|
|
|
|
ret = qca8k_mii_read_hi(bus, phy_id, regnum + 1, &hi);
|
|
if (ret < 0)
|
|
goto err;
|
|
|
|
*val = lo | hi;
|
|
|
|
err:
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
qca8k_mii_write32(struct mii_bus *bus, int phy_id, u32 regnum, u32 val)
|
|
{
|
|
if (qca8k_mii_write_lo(bus, phy_id, regnum, val) < 0)
|
|
return;
|
|
|
|
qca8k_mii_write_hi(bus, phy_id, regnum + 1, val);
|
|
}
|
|
|
|
static int
|
|
qca8k_set_page(struct qca8k_priv *priv, u16 page)
|
|
{
|
|
u16 *cached_page = &priv->mdio_cache.page;
|
|
struct mii_bus *bus = priv->bus;
|
|
int ret;
|
|
|
|
if (page == *cached_page)
|
|
return 0;
|
|
|
|
ret = bus->write(bus, 0x18, 0, page);
|
|
if (ret < 0) {
|
|
dev_err_ratelimited(&bus->dev,
|
|
"failed to set qca8k page\n");
|
|
return ret;
|
|
}
|
|
|
|
*cached_page = page;
|
|
usleep_range(1000, 2000);
|
|
return 0;
|
|
}
|
|
|
|
static void qca8k_rw_reg_ack_handler(struct dsa_switch *ds, struct sk_buff *skb)
|
|
{
|
|
struct qca8k_mgmt_eth_data *mgmt_eth_data;
|
|
struct qca8k_priv *priv = ds->priv;
|
|
struct qca_mgmt_ethhdr *mgmt_ethhdr;
|
|
u32 command;
|
|
u8 len, cmd;
|
|
int i;
|
|
|
|
mgmt_ethhdr = (struct qca_mgmt_ethhdr *)skb_mac_header(skb);
|
|
mgmt_eth_data = &priv->mgmt_eth_data;
|
|
|
|
command = get_unaligned_le32(&mgmt_ethhdr->command);
|
|
cmd = FIELD_GET(QCA_HDR_MGMT_CMD, command);
|
|
|
|
len = FIELD_GET(QCA_HDR_MGMT_LENGTH, command);
|
|
/* Special case for len of 15 as this is the max value for len and needs to
|
|
* be increased before converting it from word to dword.
|
|
*/
|
|
if (len == 15)
|
|
len++;
|
|
|
|
/* We can ignore odd value, we always round up them in the alloc function. */
|
|
len *= sizeof(u16);
|
|
|
|
/* Make sure the seq match the requested packet */
|
|
if (get_unaligned_le32(&mgmt_ethhdr->seq) == mgmt_eth_data->seq)
|
|
mgmt_eth_data->ack = true;
|
|
|
|
if (cmd == MDIO_READ) {
|
|
u32 *val = mgmt_eth_data->data;
|
|
|
|
*val = get_unaligned_le32(&mgmt_ethhdr->mdio_data);
|
|
|
|
/* Get the rest of the 12 byte of data.
|
|
* The read/write function will extract the requested data.
|
|
*/
|
|
if (len > QCA_HDR_MGMT_DATA1_LEN) {
|
|
__le32 *data2 = (__le32 *)skb->data;
|
|
int data_len = min_t(int, QCA_HDR_MGMT_DATA2_LEN,
|
|
len - QCA_HDR_MGMT_DATA1_LEN);
|
|
|
|
val++;
|
|
|
|
for (i = sizeof(u32); i <= data_len; i += sizeof(u32)) {
|
|
*val = get_unaligned_le32(data2);
|
|
val++;
|
|
data2++;
|
|
}
|
|
}
|
|
}
|
|
|
|
complete(&mgmt_eth_data->rw_done);
|
|
}
|
|
|
|
static struct sk_buff *qca8k_alloc_mdio_header(enum mdio_cmd cmd, u32 reg, u32 *val,
|
|
int priority, unsigned int len)
|
|
{
|
|
struct qca_mgmt_ethhdr *mgmt_ethhdr;
|
|
unsigned int real_len;
|
|
struct sk_buff *skb;
|
|
__le32 *data2;
|
|
u32 command;
|
|
u16 hdr;
|
|
int i;
|
|
|
|
skb = dev_alloc_skb(QCA_HDR_MGMT_PKT_LEN);
|
|
if (!skb)
|
|
return NULL;
|
|
|
|
/* Hdr mgmt length value is in step of word size.
|
|
* As an example to process 4 byte of data the correct length to set is 2.
|
|
* To process 8 byte 4, 12 byte 6, 16 byte 8...
|
|
*
|
|
* Odd values will always return the next size on the ack packet.
|
|
* (length of 3 (6 byte) will always return 8 bytes of data)
|
|
*
|
|
* This means that a value of 15 (0xf) actually means reading/writing 32 bytes
|
|
* of data.
|
|
*
|
|
* To correctly calculate the length we devide the requested len by word and
|
|
* round up.
|
|
* On the ack function we can skip the odd check as we already handle the
|
|
* case here.
|
|
*/
|
|
real_len = DIV_ROUND_UP(len, sizeof(u16));
|
|
|
|
/* We check if the result len is odd and we round up another time to
|
|
* the next size. (length of 3 will be increased to 4 as switch will always
|
|
* return 8 bytes)
|
|
*/
|
|
if (real_len % sizeof(u16) != 0)
|
|
real_len++;
|
|
|
|
/* Max reg value is 0xf(15) but switch will always return the next size (32 byte) */
|
|
if (real_len == 16)
|
|
real_len--;
|
|
|
|
skb_reset_mac_header(skb);
|
|
skb_set_network_header(skb, skb->len);
|
|
|
|
mgmt_ethhdr = skb_push(skb, QCA_HDR_MGMT_HEADER_LEN + QCA_HDR_LEN);
|
|
|
|
hdr = FIELD_PREP(QCA_HDR_XMIT_VERSION, QCA_HDR_VERSION);
|
|
hdr |= FIELD_PREP(QCA_HDR_XMIT_PRIORITY, priority);
|
|
hdr |= QCA_HDR_XMIT_FROM_CPU;
|
|
hdr |= FIELD_PREP(QCA_HDR_XMIT_DP_BIT, BIT(0));
|
|
hdr |= FIELD_PREP(QCA_HDR_XMIT_CONTROL, QCA_HDR_XMIT_TYPE_RW_REG);
|
|
|
|
command = FIELD_PREP(QCA_HDR_MGMT_ADDR, reg);
|
|
command |= FIELD_PREP(QCA_HDR_MGMT_LENGTH, real_len);
|
|
command |= FIELD_PREP(QCA_HDR_MGMT_CMD, cmd);
|
|
command |= FIELD_PREP(QCA_HDR_MGMT_CHECK_CODE,
|
|
QCA_HDR_MGMT_CHECK_CODE_VAL);
|
|
|
|
put_unaligned_le32(command, &mgmt_ethhdr->command);
|
|
|
|
if (cmd == MDIO_WRITE)
|
|
put_unaligned_le32(*val, &mgmt_ethhdr->mdio_data);
|
|
|
|
mgmt_ethhdr->hdr = htons(hdr);
|
|
|
|
data2 = skb_put_zero(skb, QCA_HDR_MGMT_DATA2_LEN + QCA_HDR_MGMT_PADDING_LEN);
|
|
if (cmd == MDIO_WRITE && len > QCA_HDR_MGMT_DATA1_LEN) {
|
|
int data_len = min_t(int, QCA_HDR_MGMT_DATA2_LEN,
|
|
len - QCA_HDR_MGMT_DATA1_LEN);
|
|
|
|
val++;
|
|
|
|
for (i = sizeof(u32); i <= data_len; i += sizeof(u32)) {
|
|
put_unaligned_le32(*val, data2);
|
|
data2++;
|
|
val++;
|
|
}
|
|
}
|
|
|
|
return skb;
|
|
}
|
|
|
|
static void qca8k_mdio_header_fill_seq_num(struct sk_buff *skb, u32 seq_num)
|
|
{
|
|
struct qca_mgmt_ethhdr *mgmt_ethhdr;
|
|
u32 seq;
|
|
|
|
seq = FIELD_PREP(QCA_HDR_MGMT_SEQ_NUM, seq_num);
|
|
mgmt_ethhdr = (struct qca_mgmt_ethhdr *)skb->data;
|
|
put_unaligned_le32(seq, &mgmt_ethhdr->seq);
|
|
}
|
|
|
|
static int qca8k_read_eth(struct qca8k_priv *priv, u32 reg, u32 *val, int len)
|
|
{
|
|
struct qca8k_mgmt_eth_data *mgmt_eth_data = &priv->mgmt_eth_data;
|
|
struct sk_buff *skb;
|
|
bool ack;
|
|
int ret;
|
|
|
|
skb = qca8k_alloc_mdio_header(MDIO_READ, reg, NULL,
|
|
QCA8K_ETHERNET_MDIO_PRIORITY, len);
|
|
if (!skb)
|
|
return -ENOMEM;
|
|
|
|
mutex_lock(&mgmt_eth_data->mutex);
|
|
|
|
/* Check mgmt_master if is operational */
|
|
if (!priv->mgmt_master) {
|
|
kfree_skb(skb);
|
|
mutex_unlock(&mgmt_eth_data->mutex);
|
|
return -EINVAL;
|
|
}
|
|
|
|
skb->dev = priv->mgmt_master;
|
|
|
|
reinit_completion(&mgmt_eth_data->rw_done);
|
|
|
|
/* Increment seq_num and set it in the mdio pkt */
|
|
mgmt_eth_data->seq++;
|
|
qca8k_mdio_header_fill_seq_num(skb, mgmt_eth_data->seq);
|
|
mgmt_eth_data->ack = false;
|
|
|
|
dev_queue_xmit(skb);
|
|
|
|
ret = wait_for_completion_timeout(&mgmt_eth_data->rw_done,
|
|
msecs_to_jiffies(QCA8K_ETHERNET_TIMEOUT));
|
|
|
|
*val = mgmt_eth_data->data[0];
|
|
if (len > QCA_HDR_MGMT_DATA1_LEN)
|
|
memcpy(val + 1, mgmt_eth_data->data + 1, len - QCA_HDR_MGMT_DATA1_LEN);
|
|
|
|
ack = mgmt_eth_data->ack;
|
|
|
|
mutex_unlock(&mgmt_eth_data->mutex);
|
|
|
|
if (ret <= 0)
|
|
return -ETIMEDOUT;
|
|
|
|
if (!ack)
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int qca8k_write_eth(struct qca8k_priv *priv, u32 reg, u32 *val, int len)
|
|
{
|
|
struct qca8k_mgmt_eth_data *mgmt_eth_data = &priv->mgmt_eth_data;
|
|
struct sk_buff *skb;
|
|
bool ack;
|
|
int ret;
|
|
|
|
skb = qca8k_alloc_mdio_header(MDIO_WRITE, reg, val,
|
|
QCA8K_ETHERNET_MDIO_PRIORITY, len);
|
|
if (!skb)
|
|
return -ENOMEM;
|
|
|
|
mutex_lock(&mgmt_eth_data->mutex);
|
|
|
|
/* Check mgmt_master if is operational */
|
|
if (!priv->mgmt_master) {
|
|
kfree_skb(skb);
|
|
mutex_unlock(&mgmt_eth_data->mutex);
|
|
return -EINVAL;
|
|
}
|
|
|
|
skb->dev = priv->mgmt_master;
|
|
|
|
reinit_completion(&mgmt_eth_data->rw_done);
|
|
|
|
/* Increment seq_num and set it in the mdio pkt */
|
|
mgmt_eth_data->seq++;
|
|
qca8k_mdio_header_fill_seq_num(skb, mgmt_eth_data->seq);
|
|
mgmt_eth_data->ack = false;
|
|
|
|
dev_queue_xmit(skb);
|
|
|
|
ret = wait_for_completion_timeout(&mgmt_eth_data->rw_done,
|
|
msecs_to_jiffies(QCA8K_ETHERNET_TIMEOUT));
|
|
|
|
ack = mgmt_eth_data->ack;
|
|
|
|
mutex_unlock(&mgmt_eth_data->mutex);
|
|
|
|
if (ret <= 0)
|
|
return -ETIMEDOUT;
|
|
|
|
if (!ack)
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
qca8k_regmap_update_bits_eth(struct qca8k_priv *priv, u32 reg, u32 mask, u32 write_val)
|
|
{
|
|
u32 val = 0;
|
|
int ret;
|
|
|
|
ret = qca8k_read_eth(priv, reg, &val, sizeof(val));
|
|
if (ret)
|
|
return ret;
|
|
|
|
val &= ~mask;
|
|
val |= write_val;
|
|
|
|
return qca8k_write_eth(priv, reg, &val, sizeof(val));
|
|
}
|
|
|
|
static int
|
|
qca8k_read_mii(struct qca8k_priv *priv, uint32_t reg, uint32_t *val)
|
|
{
|
|
struct mii_bus *bus = priv->bus;
|
|
u16 r1, r2, page;
|
|
int ret;
|
|
|
|
qca8k_split_addr(reg, &r1, &r2, &page);
|
|
|
|
mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
|
|
|
|
ret = qca8k_set_page(priv, page);
|
|
if (ret < 0)
|
|
goto exit;
|
|
|
|
ret = qca8k_mii_read32(bus, 0x10 | r2, r1, val);
|
|
|
|
exit:
|
|
mutex_unlock(&bus->mdio_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
qca8k_write_mii(struct qca8k_priv *priv, uint32_t reg, uint32_t val)
|
|
{
|
|
struct mii_bus *bus = priv->bus;
|
|
u16 r1, r2, page;
|
|
int ret;
|
|
|
|
qca8k_split_addr(reg, &r1, &r2, &page);
|
|
|
|
mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
|
|
|
|
ret = qca8k_set_page(priv, page);
|
|
if (ret < 0)
|
|
goto exit;
|
|
|
|
qca8k_mii_write32(bus, 0x10 | r2, r1, val);
|
|
|
|
exit:
|
|
mutex_unlock(&bus->mdio_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
qca8k_regmap_update_bits_mii(struct qca8k_priv *priv, uint32_t reg,
|
|
uint32_t mask, uint32_t write_val)
|
|
{
|
|
struct mii_bus *bus = priv->bus;
|
|
u16 r1, r2, page;
|
|
u32 val;
|
|
int ret;
|
|
|
|
qca8k_split_addr(reg, &r1, &r2, &page);
|
|
|
|
mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
|
|
|
|
ret = qca8k_set_page(priv, page);
|
|
if (ret < 0)
|
|
goto exit;
|
|
|
|
ret = qca8k_mii_read32(bus, 0x10 | r2, r1, &val);
|
|
if (ret < 0)
|
|
goto exit;
|
|
|
|
val &= ~mask;
|
|
val |= write_val;
|
|
qca8k_mii_write32(bus, 0x10 | r2, r1, val);
|
|
|
|
exit:
|
|
mutex_unlock(&bus->mdio_lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
qca8k_bulk_read(void *ctx, const void *reg_buf, size_t reg_len,
|
|
void *val_buf, size_t val_len)
|
|
{
|
|
int i, count = val_len / sizeof(u32), ret;
|
|
u32 reg = *(u32 *)reg_buf & U16_MAX;
|
|
struct qca8k_priv *priv = ctx;
|
|
|
|
if (priv->mgmt_master &&
|
|
!qca8k_read_eth(priv, reg, val_buf, val_len))
|
|
return 0;
|
|
|
|
/* loop count times and increment reg of 4 */
|
|
for (i = 0; i < count; i++, reg += sizeof(u32)) {
|
|
ret = qca8k_read_mii(priv, reg, val_buf + i);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
qca8k_bulk_gather_write(void *ctx, const void *reg_buf, size_t reg_len,
|
|
const void *val_buf, size_t val_len)
|
|
{
|
|
int i, count = val_len / sizeof(u32), ret;
|
|
u32 reg = *(u32 *)reg_buf & U16_MAX;
|
|
struct qca8k_priv *priv = ctx;
|
|
u32 *val = (u32 *)val_buf;
|
|
|
|
if (priv->mgmt_master &&
|
|
!qca8k_write_eth(priv, reg, val, val_len))
|
|
return 0;
|
|
|
|
/* loop count times, increment reg of 4 and increment val ptr to
|
|
* the next value
|
|
*/
|
|
for (i = 0; i < count; i++, reg += sizeof(u32), val++) {
|
|
ret = qca8k_write_mii(priv, reg, *val);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
qca8k_bulk_write(void *ctx, const void *data, size_t bytes)
|
|
{
|
|
return qca8k_bulk_gather_write(ctx, data, sizeof(u16), data + sizeof(u16),
|
|
bytes - sizeof(u16));
|
|
}
|
|
|
|
static int
|
|
qca8k_regmap_update_bits(void *ctx, uint32_t reg, uint32_t mask, uint32_t write_val)
|
|
{
|
|
struct qca8k_priv *priv = ctx;
|
|
|
|
if (!qca8k_regmap_update_bits_eth(priv, reg, mask, write_val))
|
|
return 0;
|
|
|
|
return qca8k_regmap_update_bits_mii(priv, reg, mask, write_val);
|
|
}
|
|
|
|
static struct regmap_config qca8k_regmap_config = {
|
|
.reg_bits = 16,
|
|
.val_bits = 32,
|
|
.reg_stride = 4,
|
|
.max_register = 0x16ac, /* end MIB - Port6 range */
|
|
.read = qca8k_bulk_read,
|
|
.write = qca8k_bulk_write,
|
|
.reg_update_bits = qca8k_regmap_update_bits,
|
|
.rd_table = &qca8k_readable_table,
|
|
.disable_locking = true, /* Locking is handled by qca8k read/write */
|
|
.cache_type = REGCACHE_NONE, /* Explicitly disable CACHE */
|
|
.max_raw_read = 32, /* mgmt eth can read up to 8 registers at time */
|
|
/* ATU regs suffer from a bug where some data are not correctly
|
|
* written. Disable bulk write to correctly write ATU entry.
|
|
*/
|
|
.use_single_write = true,
|
|
};
|
|
|
|
static int
|
|
qca8k_phy_eth_busy_wait(struct qca8k_mgmt_eth_data *mgmt_eth_data,
|
|
struct sk_buff *read_skb, u32 *val)
|
|
{
|
|
struct sk_buff *skb = skb_copy(read_skb, GFP_KERNEL);
|
|
bool ack;
|
|
int ret;
|
|
|
|
if (!skb)
|
|
return -ENOMEM;
|
|
|
|
reinit_completion(&mgmt_eth_data->rw_done);
|
|
|
|
/* Increment seq_num and set it in the copy pkt */
|
|
mgmt_eth_data->seq++;
|
|
qca8k_mdio_header_fill_seq_num(skb, mgmt_eth_data->seq);
|
|
mgmt_eth_data->ack = false;
|
|
|
|
dev_queue_xmit(skb);
|
|
|
|
ret = wait_for_completion_timeout(&mgmt_eth_data->rw_done,
|
|
QCA8K_ETHERNET_TIMEOUT);
|
|
|
|
ack = mgmt_eth_data->ack;
|
|
|
|
if (ret <= 0)
|
|
return -ETIMEDOUT;
|
|
|
|
if (!ack)
|
|
return -EINVAL;
|
|
|
|
*val = mgmt_eth_data->data[0];
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
qca8k_phy_eth_command(struct qca8k_priv *priv, bool read, int phy,
|
|
int regnum, u16 data)
|
|
{
|
|
struct sk_buff *write_skb, *clear_skb, *read_skb;
|
|
struct qca8k_mgmt_eth_data *mgmt_eth_data;
|
|
u32 write_val, clear_val = 0, val;
|
|
struct net_device *mgmt_master;
|
|
int ret, ret1;
|
|
bool ack;
|
|
|
|
if (regnum >= QCA8K_MDIO_MASTER_MAX_REG)
|
|
return -EINVAL;
|
|
|
|
mgmt_eth_data = &priv->mgmt_eth_data;
|
|
|
|
write_val = QCA8K_MDIO_MASTER_BUSY | QCA8K_MDIO_MASTER_EN |
|
|
QCA8K_MDIO_MASTER_PHY_ADDR(phy) |
|
|
QCA8K_MDIO_MASTER_REG_ADDR(regnum);
|
|
|
|
if (read) {
|
|
write_val |= QCA8K_MDIO_MASTER_READ;
|
|
} else {
|
|
write_val |= QCA8K_MDIO_MASTER_WRITE;
|
|
write_val |= QCA8K_MDIO_MASTER_DATA(data);
|
|
}
|
|
|
|
/* Prealloc all the needed skb before the lock */
|
|
write_skb = qca8k_alloc_mdio_header(MDIO_WRITE, QCA8K_MDIO_MASTER_CTRL, &write_val,
|
|
QCA8K_ETHERNET_PHY_PRIORITY, sizeof(write_val));
|
|
if (!write_skb)
|
|
return -ENOMEM;
|
|
|
|
clear_skb = qca8k_alloc_mdio_header(MDIO_WRITE, QCA8K_MDIO_MASTER_CTRL, &clear_val,
|
|
QCA8K_ETHERNET_PHY_PRIORITY, sizeof(clear_val));
|
|
if (!clear_skb) {
|
|
ret = -ENOMEM;
|
|
goto err_clear_skb;
|
|
}
|
|
|
|
read_skb = qca8k_alloc_mdio_header(MDIO_READ, QCA8K_MDIO_MASTER_CTRL, &clear_val,
|
|
QCA8K_ETHERNET_PHY_PRIORITY, sizeof(clear_val));
|
|
if (!read_skb) {
|
|
ret = -ENOMEM;
|
|
goto err_read_skb;
|
|
}
|
|
|
|
/* Actually start the request:
|
|
* 1. Send mdio master packet
|
|
* 2. Busy Wait for mdio master command
|
|
* 3. Get the data if we are reading
|
|
* 4. Reset the mdio master (even with error)
|
|
*/
|
|
mutex_lock(&mgmt_eth_data->mutex);
|
|
|
|
/* Check if mgmt_master is operational */
|
|
mgmt_master = priv->mgmt_master;
|
|
if (!mgmt_master) {
|
|
mutex_unlock(&mgmt_eth_data->mutex);
|
|
ret = -EINVAL;
|
|
goto err_mgmt_master;
|
|
}
|
|
|
|
read_skb->dev = mgmt_master;
|
|
clear_skb->dev = mgmt_master;
|
|
write_skb->dev = mgmt_master;
|
|
|
|
reinit_completion(&mgmt_eth_data->rw_done);
|
|
|
|
/* Increment seq_num and set it in the write pkt */
|
|
mgmt_eth_data->seq++;
|
|
qca8k_mdio_header_fill_seq_num(write_skb, mgmt_eth_data->seq);
|
|
mgmt_eth_data->ack = false;
|
|
|
|
dev_queue_xmit(write_skb);
|
|
|
|
ret = wait_for_completion_timeout(&mgmt_eth_data->rw_done,
|
|
QCA8K_ETHERNET_TIMEOUT);
|
|
|
|
ack = mgmt_eth_data->ack;
|
|
|
|
if (ret <= 0) {
|
|
ret = -ETIMEDOUT;
|
|
kfree_skb(read_skb);
|
|
goto exit;
|
|
}
|
|
|
|
if (!ack) {
|
|
ret = -EINVAL;
|
|
kfree_skb(read_skb);
|
|
goto exit;
|
|
}
|
|
|
|
ret = read_poll_timeout(qca8k_phy_eth_busy_wait, ret1,
|
|
!(val & QCA8K_MDIO_MASTER_BUSY), 0,
|
|
QCA8K_BUSY_WAIT_TIMEOUT * USEC_PER_MSEC, false,
|
|
mgmt_eth_data, read_skb, &val);
|
|
|
|
if (ret < 0 && ret1 < 0) {
|
|
ret = ret1;
|
|
goto exit;
|
|
}
|
|
|
|
if (read) {
|
|
reinit_completion(&mgmt_eth_data->rw_done);
|
|
|
|
/* Increment seq_num and set it in the read pkt */
|
|
mgmt_eth_data->seq++;
|
|
qca8k_mdio_header_fill_seq_num(read_skb, mgmt_eth_data->seq);
|
|
mgmt_eth_data->ack = false;
|
|
|
|
dev_queue_xmit(read_skb);
|
|
|
|
ret = wait_for_completion_timeout(&mgmt_eth_data->rw_done,
|
|
QCA8K_ETHERNET_TIMEOUT);
|
|
|
|
ack = mgmt_eth_data->ack;
|
|
|
|
if (ret <= 0) {
|
|
ret = -ETIMEDOUT;
|
|
goto exit;
|
|
}
|
|
|
|
if (!ack) {
|
|
ret = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
ret = mgmt_eth_data->data[0] & QCA8K_MDIO_MASTER_DATA_MASK;
|
|
} else {
|
|
kfree_skb(read_skb);
|
|
}
|
|
exit:
|
|
reinit_completion(&mgmt_eth_data->rw_done);
|
|
|
|
/* Increment seq_num and set it in the clear pkt */
|
|
mgmt_eth_data->seq++;
|
|
qca8k_mdio_header_fill_seq_num(clear_skb, mgmt_eth_data->seq);
|
|
mgmt_eth_data->ack = false;
|
|
|
|
dev_queue_xmit(clear_skb);
|
|
|
|
wait_for_completion_timeout(&mgmt_eth_data->rw_done,
|
|
QCA8K_ETHERNET_TIMEOUT);
|
|
|
|
mutex_unlock(&mgmt_eth_data->mutex);
|
|
|
|
return ret;
|
|
|
|
/* Error handling before lock */
|
|
err_mgmt_master:
|
|
kfree_skb(read_skb);
|
|
err_read_skb:
|
|
kfree_skb(clear_skb);
|
|
err_clear_skb:
|
|
kfree_skb(write_skb);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
qca8k_mdio_busy_wait(struct mii_bus *bus, u32 reg, u32 mask)
|
|
{
|
|
u16 r1, r2, page;
|
|
u32 val;
|
|
int ret, ret1;
|
|
|
|
qca8k_split_addr(reg, &r1, &r2, &page);
|
|
|
|
ret = read_poll_timeout(qca8k_mii_read_hi, ret1, !(val & mask), 0,
|
|
QCA8K_BUSY_WAIT_TIMEOUT * USEC_PER_MSEC, false,
|
|
bus, 0x10 | r2, r1 + 1, &val);
|
|
|
|
/* Check if qca8k_read has failed for a different reason
|
|
* before returnting -ETIMEDOUT
|
|
*/
|
|
if (ret < 0 && ret1 < 0)
|
|
return ret1;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
qca8k_mdio_write(struct qca8k_priv *priv, int phy, int regnum, u16 data)
|
|
{
|
|
struct mii_bus *bus = priv->bus;
|
|
u16 r1, r2, page;
|
|
u32 val;
|
|
int ret;
|
|
|
|
if (regnum >= QCA8K_MDIO_MASTER_MAX_REG)
|
|
return -EINVAL;
|
|
|
|
val = QCA8K_MDIO_MASTER_BUSY | QCA8K_MDIO_MASTER_EN |
|
|
QCA8K_MDIO_MASTER_WRITE | QCA8K_MDIO_MASTER_PHY_ADDR(phy) |
|
|
QCA8K_MDIO_MASTER_REG_ADDR(regnum) |
|
|
QCA8K_MDIO_MASTER_DATA(data);
|
|
|
|
qca8k_split_addr(QCA8K_MDIO_MASTER_CTRL, &r1, &r2, &page);
|
|
|
|
mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
|
|
|
|
ret = qca8k_set_page(priv, page);
|
|
if (ret)
|
|
goto exit;
|
|
|
|
qca8k_mii_write32(bus, 0x10 | r2, r1, val);
|
|
|
|
ret = qca8k_mdio_busy_wait(bus, QCA8K_MDIO_MASTER_CTRL,
|
|
QCA8K_MDIO_MASTER_BUSY);
|
|
|
|
exit:
|
|
/* even if the busy_wait timeouts try to clear the MASTER_EN */
|
|
qca8k_mii_write_hi(bus, 0x10 | r2, r1 + 1, 0);
|
|
|
|
mutex_unlock(&bus->mdio_lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
qca8k_mdio_read(struct qca8k_priv *priv, int phy, int regnum)
|
|
{
|
|
struct mii_bus *bus = priv->bus;
|
|
u16 r1, r2, page;
|
|
u32 val;
|
|
int ret;
|
|
|
|
if (regnum >= QCA8K_MDIO_MASTER_MAX_REG)
|
|
return -EINVAL;
|
|
|
|
val = QCA8K_MDIO_MASTER_BUSY | QCA8K_MDIO_MASTER_EN |
|
|
QCA8K_MDIO_MASTER_READ | QCA8K_MDIO_MASTER_PHY_ADDR(phy) |
|
|
QCA8K_MDIO_MASTER_REG_ADDR(regnum);
|
|
|
|
qca8k_split_addr(QCA8K_MDIO_MASTER_CTRL, &r1, &r2, &page);
|
|
|
|
mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
|
|
|
|
ret = qca8k_set_page(priv, page);
|
|
if (ret)
|
|
goto exit;
|
|
|
|
qca8k_mii_write_hi(bus, 0x10 | r2, r1 + 1, val);
|
|
|
|
ret = qca8k_mdio_busy_wait(bus, QCA8K_MDIO_MASTER_CTRL,
|
|
QCA8K_MDIO_MASTER_BUSY);
|
|
if (ret)
|
|
goto exit;
|
|
|
|
ret = qca8k_mii_read_lo(bus, 0x10 | r2, r1, &val);
|
|
|
|
exit:
|
|
/* even if the busy_wait timeouts try to clear the MASTER_EN */
|
|
qca8k_mii_write_hi(bus, 0x10 | r2, r1 + 1, 0);
|
|
|
|
mutex_unlock(&bus->mdio_lock);
|
|
|
|
if (ret >= 0)
|
|
ret = val & QCA8K_MDIO_MASTER_DATA_MASK;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
qca8k_internal_mdio_write(struct mii_bus *slave_bus, int phy, int regnum, u16 data)
|
|
{
|
|
struct qca8k_priv *priv = slave_bus->priv;
|
|
int ret;
|
|
|
|
/* Use mdio Ethernet when available, fallback to legacy one on error */
|
|
ret = qca8k_phy_eth_command(priv, false, phy, regnum, data);
|
|
if (!ret)
|
|
return 0;
|
|
|
|
return qca8k_mdio_write(priv, phy, regnum, data);
|
|
}
|
|
|
|
static int
|
|
qca8k_internal_mdio_read(struct mii_bus *slave_bus, int phy, int regnum)
|
|
{
|
|
struct qca8k_priv *priv = slave_bus->priv;
|
|
int ret;
|
|
|
|
/* Use mdio Ethernet when available, fallback to legacy one on error */
|
|
ret = qca8k_phy_eth_command(priv, true, phy, regnum, 0);
|
|
if (ret >= 0)
|
|
return ret;
|
|
|
|
ret = qca8k_mdio_read(priv, phy, regnum);
|
|
|
|
if (ret < 0)
|
|
return 0xffff;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
qca8k_legacy_mdio_write(struct mii_bus *slave_bus, int port, int regnum, u16 data)
|
|
{
|
|
port = qca8k_port_to_phy(port) % PHY_MAX_ADDR;
|
|
|
|
return qca8k_internal_mdio_write(slave_bus, port, regnum, data);
|
|
}
|
|
|
|
static int
|
|
qca8k_legacy_mdio_read(struct mii_bus *slave_bus, int port, int regnum)
|
|
{
|
|
port = qca8k_port_to_phy(port) % PHY_MAX_ADDR;
|
|
|
|
return qca8k_internal_mdio_read(slave_bus, port, regnum);
|
|
}
|
|
|
|
static int
|
|
qca8k_mdio_register(struct qca8k_priv *priv)
|
|
{
|
|
struct dsa_switch *ds = priv->ds;
|
|
struct device_node *mdio;
|
|
struct mii_bus *bus;
|
|
|
|
bus = devm_mdiobus_alloc(ds->dev);
|
|
if (!bus)
|
|
return -ENOMEM;
|
|
|
|
bus->priv = (void *)priv;
|
|
snprintf(bus->id, MII_BUS_ID_SIZE, "qca8k-%d.%d",
|
|
ds->dst->index, ds->index);
|
|
bus->parent = ds->dev;
|
|
bus->phy_mask = ~ds->phys_mii_mask;
|
|
ds->slave_mii_bus = bus;
|
|
|
|
/* Check if the devicetree declare the port:phy mapping */
|
|
mdio = of_get_child_by_name(priv->dev->of_node, "mdio");
|
|
if (of_device_is_available(mdio)) {
|
|
bus->name = "qca8k slave mii";
|
|
bus->read = qca8k_internal_mdio_read;
|
|
bus->write = qca8k_internal_mdio_write;
|
|
return devm_of_mdiobus_register(priv->dev, bus, mdio);
|
|
}
|
|
|
|
/* If a mapping can't be found the legacy mapping is used,
|
|
* using the qca8k_port_to_phy function
|
|
*/
|
|
bus->name = "qca8k-legacy slave mii";
|
|
bus->read = qca8k_legacy_mdio_read;
|
|
bus->write = qca8k_legacy_mdio_write;
|
|
return devm_mdiobus_register(priv->dev, bus);
|
|
}
|
|
|
|
static int
|
|
qca8k_setup_mdio_bus(struct qca8k_priv *priv)
|
|
{
|
|
u32 internal_mdio_mask = 0, external_mdio_mask = 0, reg;
|
|
struct device_node *ports, *port;
|
|
phy_interface_t mode;
|
|
int err;
|
|
|
|
ports = of_get_child_by_name(priv->dev->of_node, "ports");
|
|
if (!ports)
|
|
ports = of_get_child_by_name(priv->dev->of_node, "ethernet-ports");
|
|
|
|
if (!ports)
|
|
return -EINVAL;
|
|
|
|
for_each_available_child_of_node(ports, port) {
|
|
err = of_property_read_u32(port, "reg", ®);
|
|
if (err) {
|
|
of_node_put(port);
|
|
of_node_put(ports);
|
|
return err;
|
|
}
|
|
|
|
if (!dsa_is_user_port(priv->ds, reg))
|
|
continue;
|
|
|
|
of_get_phy_mode(port, &mode);
|
|
|
|
if (of_property_read_bool(port, "phy-handle") &&
|
|
mode != PHY_INTERFACE_MODE_INTERNAL)
|
|
external_mdio_mask |= BIT(reg);
|
|
else
|
|
internal_mdio_mask |= BIT(reg);
|
|
}
|
|
|
|
of_node_put(ports);
|
|
if (!external_mdio_mask && !internal_mdio_mask) {
|
|
dev_err(priv->dev, "no PHYs are defined.\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* The QCA8K_MDIO_MASTER_EN Bit, which grants access to PHYs through
|
|
* the MDIO_MASTER register also _disconnects_ the external MDC
|
|
* passthrough to the internal PHYs. It's not possible to use both
|
|
* configurations at the same time!
|
|
*
|
|
* Because this came up during the review process:
|
|
* If the external mdio-bus driver is capable magically disabling
|
|
* the QCA8K_MDIO_MASTER_EN and mutex/spin-locking out the qca8k's
|
|
* accessors for the time being, it would be possible to pull this
|
|
* off.
|
|
*/
|
|
if (!!external_mdio_mask && !!internal_mdio_mask) {
|
|
dev_err(priv->dev, "either internal or external mdio bus configuration is supported.\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (external_mdio_mask) {
|
|
/* Make sure to disable the internal mdio bus in cases
|
|
* a dt-overlay and driver reload changed the configuration
|
|
*/
|
|
|
|
return regmap_clear_bits(priv->regmap, QCA8K_MDIO_MASTER_CTRL,
|
|
QCA8K_MDIO_MASTER_EN);
|
|
}
|
|
|
|
return qca8k_mdio_register(priv);
|
|
}
|
|
|
|
static int
|
|
qca8k_setup_mac_pwr_sel(struct qca8k_priv *priv)
|
|
{
|
|
u32 mask = 0;
|
|
int ret = 0;
|
|
|
|
/* SoC specific settings for ipq8064.
|
|
* If more device require this consider adding
|
|
* a dedicated binding.
|
|
*/
|
|
if (of_machine_is_compatible("qcom,ipq8064"))
|
|
mask |= QCA8K_MAC_PWR_RGMII0_1_8V;
|
|
|
|
/* SoC specific settings for ipq8065 */
|
|
if (of_machine_is_compatible("qcom,ipq8065"))
|
|
mask |= QCA8K_MAC_PWR_RGMII1_1_8V;
|
|
|
|
if (mask) {
|
|
ret = qca8k_rmw(priv, QCA8K_REG_MAC_PWR_SEL,
|
|
QCA8K_MAC_PWR_RGMII0_1_8V |
|
|
QCA8K_MAC_PWR_RGMII1_1_8V,
|
|
mask);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int qca8k_find_cpu_port(struct dsa_switch *ds)
|
|
{
|
|
struct qca8k_priv *priv = ds->priv;
|
|
|
|
/* Find the connected cpu port. Valid port are 0 or 6 */
|
|
if (dsa_is_cpu_port(ds, 0))
|
|
return 0;
|
|
|
|
dev_dbg(priv->dev, "port 0 is not the CPU port. Checking port 6");
|
|
|
|
if (dsa_is_cpu_port(ds, 6))
|
|
return 6;
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int
|
|
qca8k_setup_of_pws_reg(struct qca8k_priv *priv)
|
|
{
|
|
const struct qca8k_match_data *data = priv->info;
|
|
struct device_node *node = priv->dev->of_node;
|
|
u32 val = 0;
|
|
int ret;
|
|
|
|
/* QCA8327 require to set to the correct mode.
|
|
* His bigger brother QCA8328 have the 172 pin layout.
|
|
* Should be applied by default but we set this just to make sure.
|
|
*/
|
|
if (priv->switch_id == QCA8K_ID_QCA8327) {
|
|
/* Set the correct package of 148 pin for QCA8327 */
|
|
if (data->reduced_package)
|
|
val |= QCA8327_PWS_PACKAGE148_EN;
|
|
|
|
ret = qca8k_rmw(priv, QCA8K_REG_PWS, QCA8327_PWS_PACKAGE148_EN,
|
|
val);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
if (of_property_read_bool(node, "qca,ignore-power-on-sel"))
|
|
val |= QCA8K_PWS_POWER_ON_SEL;
|
|
|
|
if (of_property_read_bool(node, "qca,led-open-drain")) {
|
|
if (!(val & QCA8K_PWS_POWER_ON_SEL)) {
|
|
dev_err(priv->dev, "qca,led-open-drain require qca,ignore-power-on-sel to be set.");
|
|
return -EINVAL;
|
|
}
|
|
|
|
val |= QCA8K_PWS_LED_OPEN_EN_CSR;
|
|
}
|
|
|
|
return qca8k_rmw(priv, QCA8K_REG_PWS,
|
|
QCA8K_PWS_LED_OPEN_EN_CSR | QCA8K_PWS_POWER_ON_SEL,
|
|
val);
|
|
}
|
|
|
|
static int
|
|
qca8k_parse_port_config(struct qca8k_priv *priv)
|
|
{
|
|
int port, cpu_port_index = -1, ret;
|
|
struct device_node *port_dn;
|
|
phy_interface_t mode;
|
|
struct dsa_port *dp;
|
|
u32 delay;
|
|
|
|
/* We have 2 CPU port. Check them */
|
|
for (port = 0; port < QCA8K_NUM_PORTS; port++) {
|
|
/* Skip every other port */
|
|
if (port != 0 && port != 6)
|
|
continue;
|
|
|
|
dp = dsa_to_port(priv->ds, port);
|
|
port_dn = dp->dn;
|
|
cpu_port_index++;
|
|
|
|
if (!of_device_is_available(port_dn))
|
|
continue;
|
|
|
|
ret = of_get_phy_mode(port_dn, &mode);
|
|
if (ret)
|
|
continue;
|
|
|
|
switch (mode) {
|
|
case PHY_INTERFACE_MODE_RGMII:
|
|
case PHY_INTERFACE_MODE_RGMII_ID:
|
|
case PHY_INTERFACE_MODE_RGMII_TXID:
|
|
case PHY_INTERFACE_MODE_RGMII_RXID:
|
|
case PHY_INTERFACE_MODE_SGMII:
|
|
delay = 0;
|
|
|
|
if (!of_property_read_u32(port_dn, "tx-internal-delay-ps", &delay))
|
|
/* Switch regs accept value in ns, convert ps to ns */
|
|
delay = delay / 1000;
|
|
else if (mode == PHY_INTERFACE_MODE_RGMII_ID ||
|
|
mode == PHY_INTERFACE_MODE_RGMII_TXID)
|
|
delay = 1;
|
|
|
|
if (!FIELD_FIT(QCA8K_PORT_PAD_RGMII_TX_DELAY_MASK, delay)) {
|
|
dev_err(priv->dev, "rgmii tx delay is limited to a max value of 3ns, setting to the max value");
|
|
delay = 3;
|
|
}
|
|
|
|
priv->ports_config.rgmii_tx_delay[cpu_port_index] = delay;
|
|
|
|
delay = 0;
|
|
|
|
if (!of_property_read_u32(port_dn, "rx-internal-delay-ps", &delay))
|
|
/* Switch regs accept value in ns, convert ps to ns */
|
|
delay = delay / 1000;
|
|
else if (mode == PHY_INTERFACE_MODE_RGMII_ID ||
|
|
mode == PHY_INTERFACE_MODE_RGMII_RXID)
|
|
delay = 2;
|
|
|
|
if (!FIELD_FIT(QCA8K_PORT_PAD_RGMII_RX_DELAY_MASK, delay)) {
|
|
dev_err(priv->dev, "rgmii rx delay is limited to a max value of 3ns, setting to the max value");
|
|
delay = 3;
|
|
}
|
|
|
|
priv->ports_config.rgmii_rx_delay[cpu_port_index] = delay;
|
|
|
|
/* Skip sgmii parsing for rgmii* mode */
|
|
if (mode == PHY_INTERFACE_MODE_RGMII ||
|
|
mode == PHY_INTERFACE_MODE_RGMII_ID ||
|
|
mode == PHY_INTERFACE_MODE_RGMII_TXID ||
|
|
mode == PHY_INTERFACE_MODE_RGMII_RXID)
|
|
break;
|
|
|
|
if (of_property_read_bool(port_dn, "qca,sgmii-txclk-falling-edge"))
|
|
priv->ports_config.sgmii_tx_clk_falling_edge = true;
|
|
|
|
if (of_property_read_bool(port_dn, "qca,sgmii-rxclk-falling-edge"))
|
|
priv->ports_config.sgmii_rx_clk_falling_edge = true;
|
|
|
|
if (of_property_read_bool(port_dn, "qca,sgmii-enable-pll")) {
|
|
priv->ports_config.sgmii_enable_pll = true;
|
|
|
|
if (priv->switch_id == QCA8K_ID_QCA8327) {
|
|
dev_err(priv->dev, "SGMII PLL should NOT be enabled for qca8327. Aborting enabling");
|
|
priv->ports_config.sgmii_enable_pll = false;
|
|
}
|
|
|
|
if (priv->switch_revision < 2)
|
|
dev_warn(priv->dev, "SGMII PLL should NOT be enabled for qca8337 with revision 2 or more.");
|
|
}
|
|
|
|
break;
|
|
default:
|
|
continue;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
qca8k_mac_config_setup_internal_delay(struct qca8k_priv *priv, int cpu_port_index,
|
|
u32 reg)
|
|
{
|
|
u32 delay, val = 0;
|
|
int ret;
|
|
|
|
/* Delay can be declared in 3 different way.
|
|
* Mode to rgmii and internal-delay standard binding defined
|
|
* rgmii-id or rgmii-tx/rx phy mode set.
|
|
* The parse logic set a delay different than 0 only when one
|
|
* of the 3 different way is used. In all other case delay is
|
|
* not enabled. With ID or TX/RXID delay is enabled and set
|
|
* to the default and recommended value.
|
|
*/
|
|
if (priv->ports_config.rgmii_tx_delay[cpu_port_index]) {
|
|
delay = priv->ports_config.rgmii_tx_delay[cpu_port_index];
|
|
|
|
val |= QCA8K_PORT_PAD_RGMII_TX_DELAY(delay) |
|
|
QCA8K_PORT_PAD_RGMII_TX_DELAY_EN;
|
|
}
|
|
|
|
if (priv->ports_config.rgmii_rx_delay[cpu_port_index]) {
|
|
delay = priv->ports_config.rgmii_rx_delay[cpu_port_index];
|
|
|
|
val |= QCA8K_PORT_PAD_RGMII_RX_DELAY(delay) |
|
|
QCA8K_PORT_PAD_RGMII_RX_DELAY_EN;
|
|
}
|
|
|
|
/* Set RGMII delay based on the selected values */
|
|
ret = qca8k_rmw(priv, reg,
|
|
QCA8K_PORT_PAD_RGMII_TX_DELAY_MASK |
|
|
QCA8K_PORT_PAD_RGMII_RX_DELAY_MASK |
|
|
QCA8K_PORT_PAD_RGMII_TX_DELAY_EN |
|
|
QCA8K_PORT_PAD_RGMII_RX_DELAY_EN,
|
|
val);
|
|
if (ret)
|
|
dev_err(priv->dev, "Failed to set internal delay for CPU port%d",
|
|
cpu_port_index == QCA8K_CPU_PORT0 ? 0 : 6);
|
|
}
|
|
|
|
static struct phylink_pcs *
|
|
qca8k_phylink_mac_select_pcs(struct dsa_switch *ds, int port,
|
|
phy_interface_t interface)
|
|
{
|
|
struct qca8k_priv *priv = ds->priv;
|
|
struct phylink_pcs *pcs = NULL;
|
|
|
|
switch (interface) {
|
|
case PHY_INTERFACE_MODE_SGMII:
|
|
case PHY_INTERFACE_MODE_1000BASEX:
|
|
switch (port) {
|
|
case 0:
|
|
pcs = &priv->pcs_port_0.pcs;
|
|
break;
|
|
|
|
case 6:
|
|
pcs = &priv->pcs_port_6.pcs;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return pcs;
|
|
}
|
|
|
|
static void
|
|
qca8k_phylink_mac_config(struct dsa_switch *ds, int port, unsigned int mode,
|
|
const struct phylink_link_state *state)
|
|
{
|
|
struct qca8k_priv *priv = ds->priv;
|
|
int cpu_port_index;
|
|
u32 reg;
|
|
|
|
switch (port) {
|
|
case 0: /* 1st CPU port */
|
|
if (state->interface != PHY_INTERFACE_MODE_RGMII &&
|
|
state->interface != PHY_INTERFACE_MODE_RGMII_ID &&
|
|
state->interface != PHY_INTERFACE_MODE_RGMII_TXID &&
|
|
state->interface != PHY_INTERFACE_MODE_RGMII_RXID &&
|
|
state->interface != PHY_INTERFACE_MODE_SGMII)
|
|
return;
|
|
|
|
reg = QCA8K_REG_PORT0_PAD_CTRL;
|
|
cpu_port_index = QCA8K_CPU_PORT0;
|
|
break;
|
|
case 1:
|
|
case 2:
|
|
case 3:
|
|
case 4:
|
|
case 5:
|
|
/* Internal PHY, nothing to do */
|
|
return;
|
|
case 6: /* 2nd CPU port / external PHY */
|
|
if (state->interface != PHY_INTERFACE_MODE_RGMII &&
|
|
state->interface != PHY_INTERFACE_MODE_RGMII_ID &&
|
|
state->interface != PHY_INTERFACE_MODE_RGMII_TXID &&
|
|
state->interface != PHY_INTERFACE_MODE_RGMII_RXID &&
|
|
state->interface != PHY_INTERFACE_MODE_SGMII &&
|
|
state->interface != PHY_INTERFACE_MODE_1000BASEX)
|
|
return;
|
|
|
|
reg = QCA8K_REG_PORT6_PAD_CTRL;
|
|
cpu_port_index = QCA8K_CPU_PORT6;
|
|
break;
|
|
default:
|
|
dev_err(ds->dev, "%s: unsupported port: %i\n", __func__, port);
|
|
return;
|
|
}
|
|
|
|
if (port != 6 && phylink_autoneg_inband(mode)) {
|
|
dev_err(ds->dev, "%s: in-band negotiation unsupported\n",
|
|
__func__);
|
|
return;
|
|
}
|
|
|
|
switch (state->interface) {
|
|
case PHY_INTERFACE_MODE_RGMII:
|
|
case PHY_INTERFACE_MODE_RGMII_ID:
|
|
case PHY_INTERFACE_MODE_RGMII_TXID:
|
|
case PHY_INTERFACE_MODE_RGMII_RXID:
|
|
qca8k_write(priv, reg, QCA8K_PORT_PAD_RGMII_EN);
|
|
|
|
/* Configure rgmii delay */
|
|
qca8k_mac_config_setup_internal_delay(priv, cpu_port_index, reg);
|
|
|
|
/* QCA8337 requires to set rgmii rx delay for all ports.
|
|
* This is enabled through PORT5_PAD_CTRL for all ports,
|
|
* rather than individual port registers.
|
|
*/
|
|
if (priv->switch_id == QCA8K_ID_QCA8337)
|
|
qca8k_write(priv, QCA8K_REG_PORT5_PAD_CTRL,
|
|
QCA8K_PORT_PAD_RGMII_RX_DELAY_EN);
|
|
break;
|
|
case PHY_INTERFACE_MODE_SGMII:
|
|
case PHY_INTERFACE_MODE_1000BASEX:
|
|
/* Enable SGMII on the port */
|
|
qca8k_write(priv, reg, QCA8K_PORT_PAD_SGMII_EN);
|
|
break;
|
|
default:
|
|
dev_err(ds->dev, "xMII mode %s not supported for port %d\n",
|
|
phy_modes(state->interface), port);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void qca8k_phylink_get_caps(struct dsa_switch *ds, int port,
|
|
struct phylink_config *config)
|
|
{
|
|
switch (port) {
|
|
case 0: /* 1st CPU port */
|
|
phy_interface_set_rgmii(config->supported_interfaces);
|
|
__set_bit(PHY_INTERFACE_MODE_SGMII,
|
|
config->supported_interfaces);
|
|
break;
|
|
|
|
case 1:
|
|
case 2:
|
|
case 3:
|
|
case 4:
|
|
case 5:
|
|
/* Internal PHY */
|
|
__set_bit(PHY_INTERFACE_MODE_GMII,
|
|
config->supported_interfaces);
|
|
__set_bit(PHY_INTERFACE_MODE_INTERNAL,
|
|
config->supported_interfaces);
|
|
break;
|
|
|
|
case 6: /* 2nd CPU port / external PHY */
|
|
phy_interface_set_rgmii(config->supported_interfaces);
|
|
__set_bit(PHY_INTERFACE_MODE_SGMII,
|
|
config->supported_interfaces);
|
|
__set_bit(PHY_INTERFACE_MODE_1000BASEX,
|
|
config->supported_interfaces);
|
|
break;
|
|
}
|
|
|
|
config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
|
|
MAC_10 | MAC_100 | MAC_1000FD;
|
|
|
|
config->legacy_pre_march2020 = false;
|
|
}
|
|
|
|
static void
|
|
qca8k_phylink_mac_link_down(struct dsa_switch *ds, int port, unsigned int mode,
|
|
phy_interface_t interface)
|
|
{
|
|
struct qca8k_priv *priv = ds->priv;
|
|
|
|
qca8k_port_set_status(priv, port, 0);
|
|
}
|
|
|
|
static void
|
|
qca8k_phylink_mac_link_up(struct dsa_switch *ds, int port, unsigned int mode,
|
|
phy_interface_t interface, struct phy_device *phydev,
|
|
int speed, int duplex, bool tx_pause, bool rx_pause)
|
|
{
|
|
struct qca8k_priv *priv = ds->priv;
|
|
u32 reg;
|
|
|
|
if (phylink_autoneg_inband(mode)) {
|
|
reg = QCA8K_PORT_STATUS_LINK_AUTO;
|
|
} else {
|
|
switch (speed) {
|
|
case SPEED_10:
|
|
reg = QCA8K_PORT_STATUS_SPEED_10;
|
|
break;
|
|
case SPEED_100:
|
|
reg = QCA8K_PORT_STATUS_SPEED_100;
|
|
break;
|
|
case SPEED_1000:
|
|
reg = QCA8K_PORT_STATUS_SPEED_1000;
|
|
break;
|
|
default:
|
|
reg = QCA8K_PORT_STATUS_LINK_AUTO;
|
|
break;
|
|
}
|
|
|
|
if (duplex == DUPLEX_FULL)
|
|
reg |= QCA8K_PORT_STATUS_DUPLEX;
|
|
|
|
if (rx_pause || dsa_is_cpu_port(ds, port))
|
|
reg |= QCA8K_PORT_STATUS_RXFLOW;
|
|
|
|
if (tx_pause || dsa_is_cpu_port(ds, port))
|
|
reg |= QCA8K_PORT_STATUS_TXFLOW;
|
|
}
|
|
|
|
reg |= QCA8K_PORT_STATUS_TXMAC | QCA8K_PORT_STATUS_RXMAC;
|
|
|
|
qca8k_write(priv, QCA8K_REG_PORT_STATUS(port), reg);
|
|
}
|
|
|
|
static struct qca8k_pcs *pcs_to_qca8k_pcs(struct phylink_pcs *pcs)
|
|
{
|
|
return container_of(pcs, struct qca8k_pcs, pcs);
|
|
}
|
|
|
|
static void qca8k_pcs_get_state(struct phylink_pcs *pcs,
|
|
struct phylink_link_state *state)
|
|
{
|
|
struct qca8k_priv *priv = pcs_to_qca8k_pcs(pcs)->priv;
|
|
int port = pcs_to_qca8k_pcs(pcs)->port;
|
|
u32 reg;
|
|
int ret;
|
|
|
|
ret = qca8k_read(priv, QCA8K_REG_PORT_STATUS(port), ®);
|
|
if (ret < 0) {
|
|
state->link = false;
|
|
return;
|
|
}
|
|
|
|
state->link = !!(reg & QCA8K_PORT_STATUS_LINK_UP);
|
|
state->an_complete = state->link;
|
|
state->duplex = (reg & QCA8K_PORT_STATUS_DUPLEX) ? DUPLEX_FULL :
|
|
DUPLEX_HALF;
|
|
|
|
switch (reg & QCA8K_PORT_STATUS_SPEED) {
|
|
case QCA8K_PORT_STATUS_SPEED_10:
|
|
state->speed = SPEED_10;
|
|
break;
|
|
case QCA8K_PORT_STATUS_SPEED_100:
|
|
state->speed = SPEED_100;
|
|
break;
|
|
case QCA8K_PORT_STATUS_SPEED_1000:
|
|
state->speed = SPEED_1000;
|
|
break;
|
|
default:
|
|
state->speed = SPEED_UNKNOWN;
|
|
break;
|
|
}
|
|
|
|
if (reg & QCA8K_PORT_STATUS_RXFLOW)
|
|
state->pause |= MLO_PAUSE_RX;
|
|
if (reg & QCA8K_PORT_STATUS_TXFLOW)
|
|
state->pause |= MLO_PAUSE_TX;
|
|
}
|
|
|
|
static int qca8k_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
|
|
phy_interface_t interface,
|
|
const unsigned long *advertising,
|
|
bool permit_pause_to_mac)
|
|
{
|
|
struct qca8k_priv *priv = pcs_to_qca8k_pcs(pcs)->priv;
|
|
int cpu_port_index, ret, port;
|
|
u32 reg, val;
|
|
|
|
port = pcs_to_qca8k_pcs(pcs)->port;
|
|
switch (port) {
|
|
case 0:
|
|
reg = QCA8K_REG_PORT0_PAD_CTRL;
|
|
cpu_port_index = QCA8K_CPU_PORT0;
|
|
break;
|
|
|
|
case 6:
|
|
reg = QCA8K_REG_PORT6_PAD_CTRL;
|
|
cpu_port_index = QCA8K_CPU_PORT6;
|
|
break;
|
|
|
|
default:
|
|
WARN_ON(1);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Enable/disable SerDes auto-negotiation as necessary */
|
|
val = neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED ?
|
|
0 : QCA8K_PWS_SERDES_AEN_DIS;
|
|
|
|
ret = qca8k_rmw(priv, QCA8K_REG_PWS, QCA8K_PWS_SERDES_AEN_DIS, val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Configure the SGMII parameters */
|
|
ret = qca8k_read(priv, QCA8K_REG_SGMII_CTRL, &val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
val |= QCA8K_SGMII_EN_SD;
|
|
|
|
if (priv->ports_config.sgmii_enable_pll)
|
|
val |= QCA8K_SGMII_EN_PLL | QCA8K_SGMII_EN_RX |
|
|
QCA8K_SGMII_EN_TX;
|
|
|
|
if (dsa_is_cpu_port(priv->ds, port)) {
|
|
/* CPU port, we're talking to the CPU MAC, be a PHY */
|
|
val &= ~QCA8K_SGMII_MODE_CTRL_MASK;
|
|
val |= QCA8K_SGMII_MODE_CTRL_PHY;
|
|
} else if (interface == PHY_INTERFACE_MODE_SGMII) {
|
|
val &= ~QCA8K_SGMII_MODE_CTRL_MASK;
|
|
val |= QCA8K_SGMII_MODE_CTRL_MAC;
|
|
} else if (interface == PHY_INTERFACE_MODE_1000BASEX) {
|
|
val &= ~QCA8K_SGMII_MODE_CTRL_MASK;
|
|
val |= QCA8K_SGMII_MODE_CTRL_BASEX;
|
|
}
|
|
|
|
qca8k_write(priv, QCA8K_REG_SGMII_CTRL, val);
|
|
|
|
/* From original code is reported port instability as SGMII also
|
|
* require delay set. Apply advised values here or take them from DT.
|
|
*/
|
|
if (interface == PHY_INTERFACE_MODE_SGMII)
|
|
qca8k_mac_config_setup_internal_delay(priv, cpu_port_index, reg);
|
|
/* For qca8327/qca8328/qca8334/qca8338 sgmii is unique and
|
|
* falling edge is set writing in the PORT0 PAD reg
|
|
*/
|
|
if (priv->switch_id == QCA8K_ID_QCA8327 ||
|
|
priv->switch_id == QCA8K_ID_QCA8337)
|
|
reg = QCA8K_REG_PORT0_PAD_CTRL;
|
|
|
|
val = 0;
|
|
|
|
/* SGMII Clock phase configuration */
|
|
if (priv->ports_config.sgmii_rx_clk_falling_edge)
|
|
val |= QCA8K_PORT0_PAD_SGMII_RXCLK_FALLING_EDGE;
|
|
|
|
if (priv->ports_config.sgmii_tx_clk_falling_edge)
|
|
val |= QCA8K_PORT0_PAD_SGMII_TXCLK_FALLING_EDGE;
|
|
|
|
if (val)
|
|
ret = qca8k_rmw(priv, reg,
|
|
QCA8K_PORT0_PAD_SGMII_RXCLK_FALLING_EDGE |
|
|
QCA8K_PORT0_PAD_SGMII_TXCLK_FALLING_EDGE,
|
|
val);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void qca8k_pcs_an_restart(struct phylink_pcs *pcs)
|
|
{
|
|
}
|
|
|
|
static const struct phylink_pcs_ops qca8k_pcs_ops = {
|
|
.pcs_get_state = qca8k_pcs_get_state,
|
|
.pcs_config = qca8k_pcs_config,
|
|
.pcs_an_restart = qca8k_pcs_an_restart,
|
|
};
|
|
|
|
static void qca8k_setup_pcs(struct qca8k_priv *priv, struct qca8k_pcs *qpcs,
|
|
int port)
|
|
{
|
|
qpcs->pcs.ops = &qca8k_pcs_ops;
|
|
qpcs->pcs.neg_mode = true;
|
|
|
|
/* We don't have interrupts for link changes, so we need to poll */
|
|
qpcs->pcs.poll = true;
|
|
qpcs->priv = priv;
|
|
qpcs->port = port;
|
|
}
|
|
|
|
static void qca8k_mib_autocast_handler(struct dsa_switch *ds, struct sk_buff *skb)
|
|
{
|
|
struct qca8k_mib_eth_data *mib_eth_data;
|
|
struct qca8k_priv *priv = ds->priv;
|
|
const struct qca8k_mib_desc *mib;
|
|
struct mib_ethhdr *mib_ethhdr;
|
|
__le32 *data2;
|
|
u8 port;
|
|
int i;
|
|
|
|
mib_ethhdr = (struct mib_ethhdr *)skb_mac_header(skb);
|
|
mib_eth_data = &priv->mib_eth_data;
|
|
|
|
/* The switch autocast every port. Ignore other packet and
|
|
* parse only the requested one.
|
|
*/
|
|
port = FIELD_GET(QCA_HDR_RECV_SOURCE_PORT, ntohs(mib_ethhdr->hdr));
|
|
if (port != mib_eth_data->req_port)
|
|
goto exit;
|
|
|
|
data2 = (__le32 *)skb->data;
|
|
|
|
for (i = 0; i < priv->info->mib_count; i++) {
|
|
mib = &ar8327_mib[i];
|
|
|
|
/* First 3 mib are present in the skb head */
|
|
if (i < 3) {
|
|
mib_eth_data->data[i] = get_unaligned_le32(mib_ethhdr->data + i);
|
|
continue;
|
|
}
|
|
|
|
/* Some mib are 64 bit wide */
|
|
if (mib->size == 2)
|
|
mib_eth_data->data[i] = get_unaligned_le64((__le64 *)data2);
|
|
else
|
|
mib_eth_data->data[i] = get_unaligned_le32(data2);
|
|
|
|
data2 += mib->size;
|
|
}
|
|
|
|
exit:
|
|
/* Complete on receiving all the mib packet */
|
|
if (refcount_dec_and_test(&mib_eth_data->port_parsed))
|
|
complete(&mib_eth_data->rw_done);
|
|
}
|
|
|
|
static int
|
|
qca8k_get_ethtool_stats_eth(struct dsa_switch *ds, int port, u64 *data)
|
|
{
|
|
struct dsa_port *dp = dsa_to_port(ds, port);
|
|
struct qca8k_mib_eth_data *mib_eth_data;
|
|
struct qca8k_priv *priv = ds->priv;
|
|
int ret;
|
|
|
|
mib_eth_data = &priv->mib_eth_data;
|
|
|
|
mutex_lock(&mib_eth_data->mutex);
|
|
|
|
reinit_completion(&mib_eth_data->rw_done);
|
|
|
|
mib_eth_data->req_port = dp->index;
|
|
mib_eth_data->data = data;
|
|
refcount_set(&mib_eth_data->port_parsed, QCA8K_NUM_PORTS);
|
|
|
|
mutex_lock(&priv->reg_mutex);
|
|
|
|
/* Send mib autocast request */
|
|
ret = regmap_update_bits(priv->regmap, QCA8K_REG_MIB,
|
|
QCA8K_MIB_FUNC | QCA8K_MIB_BUSY,
|
|
FIELD_PREP(QCA8K_MIB_FUNC, QCA8K_MIB_CAST) |
|
|
QCA8K_MIB_BUSY);
|
|
|
|
mutex_unlock(&priv->reg_mutex);
|
|
|
|
if (ret)
|
|
goto exit;
|
|
|
|
ret = wait_for_completion_timeout(&mib_eth_data->rw_done, QCA8K_ETHERNET_TIMEOUT);
|
|
|
|
exit:
|
|
mutex_unlock(&mib_eth_data->mutex);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static u32 qca8k_get_phy_flags(struct dsa_switch *ds, int port)
|
|
{
|
|
struct qca8k_priv *priv = ds->priv;
|
|
|
|
/* Communicate to the phy internal driver the switch revision.
|
|
* Based on the switch revision different values needs to be
|
|
* set to the dbg and mmd reg on the phy.
|
|
* The first 2 bit are used to communicate the switch revision
|
|
* to the phy driver.
|
|
*/
|
|
if (port > 0 && port < 6)
|
|
return priv->switch_revision;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static enum dsa_tag_protocol
|
|
qca8k_get_tag_protocol(struct dsa_switch *ds, int port,
|
|
enum dsa_tag_protocol mp)
|
|
{
|
|
return DSA_TAG_PROTO_QCA;
|
|
}
|
|
|
|
static void
|
|
qca8k_master_change(struct dsa_switch *ds, const struct net_device *master,
|
|
bool operational)
|
|
{
|
|
struct dsa_port *dp = master->dsa_ptr;
|
|
struct qca8k_priv *priv = ds->priv;
|
|
|
|
/* Ethernet MIB/MDIO is only supported for CPU port 0 */
|
|
if (dp->index != 0)
|
|
return;
|
|
|
|
mutex_lock(&priv->mgmt_eth_data.mutex);
|
|
mutex_lock(&priv->mib_eth_data.mutex);
|
|
|
|
priv->mgmt_master = operational ? (struct net_device *)master : NULL;
|
|
|
|
mutex_unlock(&priv->mib_eth_data.mutex);
|
|
mutex_unlock(&priv->mgmt_eth_data.mutex);
|
|
}
|
|
|
|
static int qca8k_connect_tag_protocol(struct dsa_switch *ds,
|
|
enum dsa_tag_protocol proto)
|
|
{
|
|
struct qca_tagger_data *tagger_data;
|
|
|
|
switch (proto) {
|
|
case DSA_TAG_PROTO_QCA:
|
|
tagger_data = ds->tagger_data;
|
|
|
|
tagger_data->rw_reg_ack_handler = qca8k_rw_reg_ack_handler;
|
|
tagger_data->mib_autocast_handler = qca8k_mib_autocast_handler;
|
|
|
|
break;
|
|
default:
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
qca8k_setup(struct dsa_switch *ds)
|
|
{
|
|
struct qca8k_priv *priv = ds->priv;
|
|
int cpu_port, ret, i;
|
|
u32 mask;
|
|
|
|
cpu_port = qca8k_find_cpu_port(ds);
|
|
if (cpu_port < 0) {
|
|
dev_err(priv->dev, "No cpu port configured in both cpu port0 and port6");
|
|
return cpu_port;
|
|
}
|
|
|
|
/* Parse CPU port config to be later used in phy_link mac_config */
|
|
ret = qca8k_parse_port_config(priv);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = qca8k_setup_mdio_bus(priv);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = qca8k_setup_of_pws_reg(priv);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = qca8k_setup_mac_pwr_sel(priv);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = qca8k_setup_led_ctrl(priv);
|
|
if (ret)
|
|
return ret;
|
|
|
|
qca8k_setup_pcs(priv, &priv->pcs_port_0, 0);
|
|
qca8k_setup_pcs(priv, &priv->pcs_port_6, 6);
|
|
|
|
/* Make sure MAC06 is disabled */
|
|
ret = regmap_clear_bits(priv->regmap, QCA8K_REG_PORT0_PAD_CTRL,
|
|
QCA8K_PORT0_PAD_MAC06_EXCHANGE_EN);
|
|
if (ret) {
|
|
dev_err(priv->dev, "failed disabling MAC06 exchange");
|
|
return ret;
|
|
}
|
|
|
|
/* Enable CPU Port */
|
|
ret = regmap_set_bits(priv->regmap, QCA8K_REG_GLOBAL_FW_CTRL0,
|
|
QCA8K_GLOBAL_FW_CTRL0_CPU_PORT_EN);
|
|
if (ret) {
|
|
dev_err(priv->dev, "failed enabling CPU port");
|
|
return ret;
|
|
}
|
|
|
|
/* Enable MIB counters */
|
|
ret = qca8k_mib_init(priv);
|
|
if (ret)
|
|
dev_warn(priv->dev, "mib init failed");
|
|
|
|
/* Initial setup of all ports */
|
|
for (i = 0; i < QCA8K_NUM_PORTS; i++) {
|
|
/* Disable forwarding by default on all ports */
|
|
ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(i),
|
|
QCA8K_PORT_LOOKUP_MEMBER, 0);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Enable QCA header mode on all cpu ports */
|
|
if (dsa_is_cpu_port(ds, i)) {
|
|
ret = qca8k_write(priv, QCA8K_REG_PORT_HDR_CTRL(i),
|
|
FIELD_PREP(QCA8K_PORT_HDR_CTRL_TX_MASK, QCA8K_PORT_HDR_CTRL_ALL) |
|
|
FIELD_PREP(QCA8K_PORT_HDR_CTRL_RX_MASK, QCA8K_PORT_HDR_CTRL_ALL));
|
|
if (ret) {
|
|
dev_err(priv->dev, "failed enabling QCA header mode");
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/* Disable MAC by default on all user ports */
|
|
if (dsa_is_user_port(ds, i))
|
|
qca8k_port_set_status(priv, i, 0);
|
|
}
|
|
|
|
/* Forward all unknown frames to CPU port for Linux processing
|
|
* Notice that in multi-cpu config only one port should be set
|
|
* for igmp, unknown, multicast and broadcast packet
|
|
*/
|
|
ret = qca8k_write(priv, QCA8K_REG_GLOBAL_FW_CTRL1,
|
|
FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_IGMP_DP_MASK, BIT(cpu_port)) |
|
|
FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_BC_DP_MASK, BIT(cpu_port)) |
|
|
FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_MC_DP_MASK, BIT(cpu_port)) |
|
|
FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_UC_DP_MASK, BIT(cpu_port)));
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Setup connection between CPU port & user ports
|
|
* Configure specific switch configuration for ports
|
|
*/
|
|
for (i = 0; i < QCA8K_NUM_PORTS; i++) {
|
|
/* CPU port gets connected to all user ports of the switch */
|
|
if (dsa_is_cpu_port(ds, i)) {
|
|
ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(i),
|
|
QCA8K_PORT_LOOKUP_MEMBER, dsa_user_ports(ds));
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
/* Individual user ports get connected to CPU port only */
|
|
if (dsa_is_user_port(ds, i)) {
|
|
ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(i),
|
|
QCA8K_PORT_LOOKUP_MEMBER,
|
|
BIT(cpu_port));
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Enable ARP Auto-learning by default */
|
|
ret = regmap_set_bits(priv->regmap, QCA8K_PORT_LOOKUP_CTRL(i),
|
|
QCA8K_PORT_LOOKUP_LEARN);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* For port based vlans to work we need to set the
|
|
* default egress vid
|
|
*/
|
|
ret = qca8k_rmw(priv, QCA8K_EGRESS_VLAN(i),
|
|
QCA8K_EGREES_VLAN_PORT_MASK(i),
|
|
QCA8K_EGREES_VLAN_PORT(i, QCA8K_PORT_VID_DEF));
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = qca8k_write(priv, QCA8K_REG_PORT_VLAN_CTRL0(i),
|
|
QCA8K_PORT_VLAN_CVID(QCA8K_PORT_VID_DEF) |
|
|
QCA8K_PORT_VLAN_SVID(QCA8K_PORT_VID_DEF));
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
/* The port 5 of the qca8337 have some problem in flood condition. The
|
|
* original legacy driver had some specific buffer and priority settings
|
|
* for the different port suggested by the QCA switch team. Add this
|
|
* missing settings to improve switch stability under load condition.
|
|
* This problem is limited to qca8337 and other qca8k switch are not affected.
|
|
*/
|
|
if (priv->switch_id == QCA8K_ID_QCA8337) {
|
|
switch (i) {
|
|
/* The 2 CPU port and port 5 requires some different
|
|
* priority than any other ports.
|
|
*/
|
|
case 0:
|
|
case 5:
|
|
case 6:
|
|
mask = QCA8K_PORT_HOL_CTRL0_EG_PRI0(0x3) |
|
|
QCA8K_PORT_HOL_CTRL0_EG_PRI1(0x4) |
|
|
QCA8K_PORT_HOL_CTRL0_EG_PRI2(0x4) |
|
|
QCA8K_PORT_HOL_CTRL0_EG_PRI3(0x4) |
|
|
QCA8K_PORT_HOL_CTRL0_EG_PRI4(0x6) |
|
|
QCA8K_PORT_HOL_CTRL0_EG_PRI5(0x8) |
|
|
QCA8K_PORT_HOL_CTRL0_EG_PORT(0x1e);
|
|
break;
|
|
default:
|
|
mask = QCA8K_PORT_HOL_CTRL0_EG_PRI0(0x3) |
|
|
QCA8K_PORT_HOL_CTRL0_EG_PRI1(0x4) |
|
|
QCA8K_PORT_HOL_CTRL0_EG_PRI2(0x6) |
|
|
QCA8K_PORT_HOL_CTRL0_EG_PRI3(0x8) |
|
|
QCA8K_PORT_HOL_CTRL0_EG_PORT(0x19);
|
|
}
|
|
qca8k_write(priv, QCA8K_REG_PORT_HOL_CTRL0(i), mask);
|
|
|
|
mask = QCA8K_PORT_HOL_CTRL1_ING(0x6) |
|
|
QCA8K_PORT_HOL_CTRL1_EG_PRI_BUF_EN |
|
|
QCA8K_PORT_HOL_CTRL1_EG_PORT_BUF_EN |
|
|
QCA8K_PORT_HOL_CTRL1_WRED_EN;
|
|
qca8k_rmw(priv, QCA8K_REG_PORT_HOL_CTRL1(i),
|
|
QCA8K_PORT_HOL_CTRL1_ING_BUF_MASK |
|
|
QCA8K_PORT_HOL_CTRL1_EG_PRI_BUF_EN |
|
|
QCA8K_PORT_HOL_CTRL1_EG_PORT_BUF_EN |
|
|
QCA8K_PORT_HOL_CTRL1_WRED_EN,
|
|
mask);
|
|
}
|
|
}
|
|
|
|
/* Special GLOBAL_FC_THRESH value are needed for ar8327 switch */
|
|
if (priv->switch_id == QCA8K_ID_QCA8327) {
|
|
mask = QCA8K_GLOBAL_FC_GOL_XON_THRES(288) |
|
|
QCA8K_GLOBAL_FC_GOL_XOFF_THRES(496);
|
|
qca8k_rmw(priv, QCA8K_REG_GLOBAL_FC_THRESH,
|
|
QCA8K_GLOBAL_FC_GOL_XON_THRES_MASK |
|
|
QCA8K_GLOBAL_FC_GOL_XOFF_THRES_MASK,
|
|
mask);
|
|
}
|
|
|
|
/* Setup our port MTUs to match power on defaults */
|
|
ret = qca8k_write(priv, QCA8K_MAX_FRAME_SIZE, ETH_FRAME_LEN + ETH_FCS_LEN);
|
|
if (ret)
|
|
dev_warn(priv->dev, "failed setting MTU settings");
|
|
|
|
/* Flush the FDB table */
|
|
qca8k_fdb_flush(priv);
|
|
|
|
/* Set min a max ageing value supported */
|
|
ds->ageing_time_min = 7000;
|
|
ds->ageing_time_max = 458745000;
|
|
|
|
/* Set max number of LAGs supported */
|
|
ds->num_lag_ids = QCA8K_NUM_LAGS;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct dsa_switch_ops qca8k_switch_ops = {
|
|
.get_tag_protocol = qca8k_get_tag_protocol,
|
|
.setup = qca8k_setup,
|
|
.get_strings = qca8k_get_strings,
|
|
.get_ethtool_stats = qca8k_get_ethtool_stats,
|
|
.get_sset_count = qca8k_get_sset_count,
|
|
.set_ageing_time = qca8k_set_ageing_time,
|
|
.get_mac_eee = qca8k_get_mac_eee,
|
|
.set_mac_eee = qca8k_set_mac_eee,
|
|
.port_enable = qca8k_port_enable,
|
|
.port_disable = qca8k_port_disable,
|
|
.port_change_mtu = qca8k_port_change_mtu,
|
|
.port_max_mtu = qca8k_port_max_mtu,
|
|
.port_stp_state_set = qca8k_port_stp_state_set,
|
|
.port_bridge_join = qca8k_port_bridge_join,
|
|
.port_bridge_leave = qca8k_port_bridge_leave,
|
|
.port_fast_age = qca8k_port_fast_age,
|
|
.port_fdb_add = qca8k_port_fdb_add,
|
|
.port_fdb_del = qca8k_port_fdb_del,
|
|
.port_fdb_dump = qca8k_port_fdb_dump,
|
|
.port_mdb_add = qca8k_port_mdb_add,
|
|
.port_mdb_del = qca8k_port_mdb_del,
|
|
.port_mirror_add = qca8k_port_mirror_add,
|
|
.port_mirror_del = qca8k_port_mirror_del,
|
|
.port_vlan_filtering = qca8k_port_vlan_filtering,
|
|
.port_vlan_add = qca8k_port_vlan_add,
|
|
.port_vlan_del = qca8k_port_vlan_del,
|
|
.phylink_get_caps = qca8k_phylink_get_caps,
|
|
.phylink_mac_select_pcs = qca8k_phylink_mac_select_pcs,
|
|
.phylink_mac_config = qca8k_phylink_mac_config,
|
|
.phylink_mac_link_down = qca8k_phylink_mac_link_down,
|
|
.phylink_mac_link_up = qca8k_phylink_mac_link_up,
|
|
.get_phy_flags = qca8k_get_phy_flags,
|
|
.port_lag_join = qca8k_port_lag_join,
|
|
.port_lag_leave = qca8k_port_lag_leave,
|
|
.master_state_change = qca8k_master_change,
|
|
.connect_tag_protocol = qca8k_connect_tag_protocol,
|
|
};
|
|
|
|
static int
|
|
qca8k_sw_probe(struct mdio_device *mdiodev)
|
|
{
|
|
struct qca8k_priv *priv;
|
|
int ret;
|
|
|
|
/* allocate the private data struct so that we can probe the switches
|
|
* ID register
|
|
*/
|
|
priv = devm_kzalloc(&mdiodev->dev, sizeof(*priv), GFP_KERNEL);
|
|
if (!priv)
|
|
return -ENOMEM;
|
|
|
|
priv->bus = mdiodev->bus;
|
|
priv->dev = &mdiodev->dev;
|
|
priv->info = of_device_get_match_data(priv->dev);
|
|
|
|
priv->reset_gpio = devm_gpiod_get_optional(priv->dev, "reset",
|
|
GPIOD_ASIS);
|
|
if (IS_ERR(priv->reset_gpio))
|
|
return PTR_ERR(priv->reset_gpio);
|
|
|
|
if (priv->reset_gpio) {
|
|
gpiod_set_value_cansleep(priv->reset_gpio, 1);
|
|
/* The active low duration must be greater than 10 ms
|
|
* and checkpatch.pl wants 20 ms.
|
|
*/
|
|
msleep(20);
|
|
gpiod_set_value_cansleep(priv->reset_gpio, 0);
|
|
}
|
|
|
|
/* Start by setting up the register mapping */
|
|
priv->regmap = devm_regmap_init(&mdiodev->dev, NULL, priv,
|
|
&qca8k_regmap_config);
|
|
if (IS_ERR(priv->regmap)) {
|
|
dev_err(priv->dev, "regmap initialization failed");
|
|
return PTR_ERR(priv->regmap);
|
|
}
|
|
|
|
priv->mdio_cache.page = 0xffff;
|
|
|
|
/* Check the detected switch id */
|
|
ret = qca8k_read_switch_id(priv);
|
|
if (ret)
|
|
return ret;
|
|
|
|
priv->ds = devm_kzalloc(&mdiodev->dev, sizeof(*priv->ds), GFP_KERNEL);
|
|
if (!priv->ds)
|
|
return -ENOMEM;
|
|
|
|
mutex_init(&priv->mgmt_eth_data.mutex);
|
|
init_completion(&priv->mgmt_eth_data.rw_done);
|
|
|
|
mutex_init(&priv->mib_eth_data.mutex);
|
|
init_completion(&priv->mib_eth_data.rw_done);
|
|
|
|
priv->ds->dev = &mdiodev->dev;
|
|
priv->ds->num_ports = QCA8K_NUM_PORTS;
|
|
priv->ds->priv = priv;
|
|
priv->ds->ops = &qca8k_switch_ops;
|
|
mutex_init(&priv->reg_mutex);
|
|
dev_set_drvdata(&mdiodev->dev, priv);
|
|
|
|
return dsa_register_switch(priv->ds);
|
|
}
|
|
|
|
static void
|
|
qca8k_sw_remove(struct mdio_device *mdiodev)
|
|
{
|
|
struct qca8k_priv *priv = dev_get_drvdata(&mdiodev->dev);
|
|
int i;
|
|
|
|
if (!priv)
|
|
return;
|
|
|
|
for (i = 0; i < QCA8K_NUM_PORTS; i++)
|
|
qca8k_port_set_status(priv, i, 0);
|
|
|
|
dsa_unregister_switch(priv->ds);
|
|
}
|
|
|
|
static void qca8k_sw_shutdown(struct mdio_device *mdiodev)
|
|
{
|
|
struct qca8k_priv *priv = dev_get_drvdata(&mdiodev->dev);
|
|
|
|
if (!priv)
|
|
return;
|
|
|
|
dsa_switch_shutdown(priv->ds);
|
|
|
|
dev_set_drvdata(&mdiodev->dev, NULL);
|
|
}
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
static void
|
|
qca8k_set_pm(struct qca8k_priv *priv, int enable)
|
|
{
|
|
int port;
|
|
|
|
for (port = 0; port < QCA8K_NUM_PORTS; port++) {
|
|
/* Do not enable on resume if the port was
|
|
* disabled before.
|
|
*/
|
|
if (!(priv->port_enabled_map & BIT(port)))
|
|
continue;
|
|
|
|
qca8k_port_set_status(priv, port, enable);
|
|
}
|
|
}
|
|
|
|
static int qca8k_suspend(struct device *dev)
|
|
{
|
|
struct qca8k_priv *priv = dev_get_drvdata(dev);
|
|
|
|
qca8k_set_pm(priv, 0);
|
|
|
|
return dsa_switch_suspend(priv->ds);
|
|
}
|
|
|
|
static int qca8k_resume(struct device *dev)
|
|
{
|
|
struct qca8k_priv *priv = dev_get_drvdata(dev);
|
|
|
|
qca8k_set_pm(priv, 1);
|
|
|
|
return dsa_switch_resume(priv->ds);
|
|
}
|
|
#endif /* CONFIG_PM_SLEEP */
|
|
|
|
static SIMPLE_DEV_PM_OPS(qca8k_pm_ops,
|
|
qca8k_suspend, qca8k_resume);
|
|
|
|
static const struct qca8k_info_ops qca8xxx_ops = {
|
|
.autocast_mib = qca8k_get_ethtool_stats_eth,
|
|
};
|
|
|
|
static const struct qca8k_match_data qca8327 = {
|
|
.id = QCA8K_ID_QCA8327,
|
|
.reduced_package = true,
|
|
.mib_count = QCA8K_QCA832X_MIB_COUNT,
|
|
.ops = &qca8xxx_ops,
|
|
};
|
|
|
|
static const struct qca8k_match_data qca8328 = {
|
|
.id = QCA8K_ID_QCA8327,
|
|
.mib_count = QCA8K_QCA832X_MIB_COUNT,
|
|
.ops = &qca8xxx_ops,
|
|
};
|
|
|
|
static const struct qca8k_match_data qca833x = {
|
|
.id = QCA8K_ID_QCA8337,
|
|
.mib_count = QCA8K_QCA833X_MIB_COUNT,
|
|
.ops = &qca8xxx_ops,
|
|
};
|
|
|
|
static const struct of_device_id qca8k_of_match[] = {
|
|
{ .compatible = "qca,qca8327", .data = &qca8327 },
|
|
{ .compatible = "qca,qca8328", .data = &qca8328 },
|
|
{ .compatible = "qca,qca8334", .data = &qca833x },
|
|
{ .compatible = "qca,qca8337", .data = &qca833x },
|
|
{ /* sentinel */ },
|
|
};
|
|
|
|
static struct mdio_driver qca8kmdio_driver = {
|
|
.probe = qca8k_sw_probe,
|
|
.remove = qca8k_sw_remove,
|
|
.shutdown = qca8k_sw_shutdown,
|
|
.mdiodrv.driver = {
|
|
.name = "qca8k",
|
|
.of_match_table = qca8k_of_match,
|
|
.pm = &qca8k_pm_ops,
|
|
},
|
|
};
|
|
|
|
mdio_module_driver(qca8kmdio_driver);
|
|
|
|
MODULE_AUTHOR("Mathieu Olivari, John Crispin <john@phrozen.org>");
|
|
MODULE_DESCRIPTION("Driver for QCA8K ethernet switch family");
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_ALIAS("platform:qca8k");
|