| // 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 if the mgmt_conduit if is operational */ |
| if (!priv->mgmt_conduit) { |
| kfree_skb(skb); |
| mutex_unlock(&mgmt_eth_data->mutex); |
| return -EINVAL; |
| } |
| |
| skb->dev = priv->mgmt_conduit; |
| |
| 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 if the mgmt_conduit if is operational */ |
| if (!priv->mgmt_conduit) { |
| kfree_skb(skb); |
| mutex_unlock(&mgmt_eth_data->mutex); |
| return -EINVAL; |
| } |
| |
| skb->dev = priv->mgmt_conduit; |
| |
| 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; |
| struct qca8k_priv *priv = ctx; |
| u32 reg = *(u16 *)reg_buf; |
| |
| if (priv->mgmt_conduit && |
| !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; |
| struct qca8k_priv *priv = ctx; |
| u32 reg = *(u16 *)reg_buf; |
| u32 *val = (u32 *)val_buf; |
| |
| if (priv->mgmt_conduit && |
| !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 const 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_conduit; |
| 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; |
| } |
| |
| /* It seems that accessing the switch's internal PHYs via management |
| * packets still uses the MDIO bus within the switch internally, and |
| * these accesses can conflict with external MDIO accesses to other |
| * devices on the MDIO bus. |
| * We therefore need to lock the MDIO bus onto which the switch is |
| * connected. |
| */ |
| mutex_lock(&priv->bus->mdio_lock); |
| |
| /* 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_conduit is operational */ |
| mgmt_conduit = priv->mgmt_conduit; |
| if (!mgmt_conduit) { |
| mutex_unlock(&mgmt_eth_data->mutex); |
| mutex_unlock(&priv->bus->mdio_lock); |
| ret = -EINVAL; |
| goto err_mgmt_conduit; |
| } |
| |
| read_skb->dev = mgmt_conduit; |
| clear_skb->dev = mgmt_conduit; |
| write_skb->dev = mgmt_conduit; |
| |
| 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); |
| mutex_unlock(&priv->bus->mdio_lock); |
| |
| return ret; |
| |
| /* Error handling before lock */ |
| err_mgmt_conduit: |
| 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 *dev = ds->dev; |
| struct device_node *mdio; |
| struct mii_bus *bus; |
| int ret = 0; |
| |
| mdio = of_get_child_by_name(dev->of_node, "mdio"); |
| if (mdio && !of_device_is_available(mdio)) |
| goto out_put_node; |
| |
| bus = devm_mdiobus_alloc(dev); |
| if (!bus) { |
| ret = -ENOMEM; |
| goto out_put_node; |
| } |
| |
| priv->internal_mdio_bus = bus; |
| bus->priv = (void *)priv; |
| snprintf(bus->id, MII_BUS_ID_SIZE, "qca8k-%d.%d", |
| ds->dst->index, ds->index); |
| bus->parent = dev; |
| |
| if (mdio) { |
| /* Check if the device tree declares the port:phy mapping */ |
| bus->name = "qca8k user mii"; |
| bus->read = qca8k_internal_mdio_read; |
| bus->write = qca8k_internal_mdio_write; |
| } else { |
| /* If a mapping can't be found, the legacy mapping is used, |
| * using qca8k_port_to_phy() |
| */ |
| ds->user_mii_bus = bus; |
| bus->phy_mask = ~ds->phys_mii_mask; |
| bus->name = "qca8k-legacy user mii"; |
| bus->read = qca8k_legacy_mdio_read; |
| bus->write = qca8k_legacy_mdio_write; |
| } |
| |
| ret = devm_of_mdiobus_register(dev, bus, mdio); |
| |
| out_put_node: |
| of_node_put(mdio); |
| return ret; |
| } |
| |
| 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 ret; |
| |
| 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) { |
| ret = of_property_read_u32(port, "reg", ®); |
| if (ret) { |
| of_node_put(port); |
| of_node_put(ports); |
| return ret; |
| } |
| |
| 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 phylink_config *config, |
| phy_interface_t interface) |
| { |
| struct dsa_port *dp = dsa_phylink_to_port(config); |
| struct qca8k_priv *priv = dp->ds->priv; |
| struct phylink_pcs *pcs = NULL; |
| int port = dp->index; |
| |
| 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 phylink_config *config, unsigned int mode, |
| const struct phylink_link_state *state) |
| { |
| struct dsa_port *dp = dsa_phylink_to_port(config); |
| struct dsa_switch *ds = dp->ds; |
| struct qca8k_priv *priv; |
| int port = dp->index; |
| int cpu_port_index; |
| u32 reg; |
| |
| priv = ds->priv; |
| |
| 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; |
| } |
| |
| static void |
| qca8k_phylink_mac_link_down(struct phylink_config *config, unsigned int mode, |
| phy_interface_t interface) |
| { |
| struct dsa_port *dp = dsa_phylink_to_port(config); |
| struct qca8k_priv *priv = dp->ds->priv; |
| |
| qca8k_port_set_status(priv, dp->index, 0); |
| } |
| |
| static void |
| qca8k_phylink_mac_link_up(struct phylink_config *config, |
| struct phy_device *phydev, unsigned int mode, |
| phy_interface_t interface, int speed, int duplex, |
| bool tx_pause, bool rx_pause) |
| { |
| struct dsa_port *dp = dsa_phylink_to_port(config); |
| struct qca8k_priv *priv = dp->ds->priv; |
| int port = dp->index; |
| 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_port_is_cpu(dp)) |
| reg |= QCA8K_PORT_STATUS_RXFLOW; |
| |
| if (tx_pause || dsa_port_is_cpu(dp)) |
| 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_conduit_change(struct dsa_switch *ds, const struct net_device *conduit, |
| bool operational) |
| { |
| struct dsa_port *dp = conduit->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_conduit = operational ? (struct net_device *)conduit : 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 void qca8k_setup_hol_fixup(struct qca8k_priv *priv, int port) |
| { |
| u32 mask; |
| |
| switch (port) { |
| /* 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); |
| } |
| regmap_write(priv->regmap, QCA8K_REG_PORT_HOL_CTRL0(port), 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; |
| regmap_update_bits(priv->regmap, QCA8K_REG_PORT_HOL_CTRL1(port), |
| 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); |
| } |
| |
| static int |
| qca8k_setup(struct dsa_switch *ds) |
| { |
| struct qca8k_priv *priv = ds->priv; |
| struct dsa_port *dp; |
| int cpu_port, ret; |
| 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 */ |
| dsa_switch_for_each_port(dp, ds) { |
| /* Disable forwarding by default on all ports */ |
| ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(dp->index), |
| QCA8K_PORT_LOOKUP_MEMBER, 0); |
| if (ret) |
| return ret; |
| } |
| |
| /* Disable MAC by default on all user ports */ |
| dsa_switch_for_each_user_port(dp, ds) |
| qca8k_port_set_status(priv, dp->index, 0); |
| |
| /* Enable QCA header mode on all cpu ports */ |
| dsa_switch_for_each_cpu_port(dp, ds) { |
| ret = qca8k_write(priv, QCA8K_REG_PORT_HDR_CTRL(dp->index), |
| 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 on port %d", dp->index); |
| return ret; |
| } |
| } |
| |
| /* 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; |
| |
| /* CPU port gets connected to all user ports of the switch */ |
| ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(cpu_port), |
| QCA8K_PORT_LOOKUP_MEMBER, dsa_user_ports(ds)); |
| if (ret) |
| return ret; |
| |
| /* Setup connection between CPU port & user ports |
| * Individual user ports get connected to CPU port only |
| */ |
| dsa_switch_for_each_user_port(dp, ds) { |
| u8 port = dp->index; |
| |
| ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port), |
| QCA8K_PORT_LOOKUP_MEMBER, |
| BIT(cpu_port)); |
| if (ret) |
| return ret; |
| |
| ret = regmap_clear_bits(priv->regmap, QCA8K_PORT_LOOKUP_CTRL(port), |
| 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(port), |
| QCA8K_EGREES_VLAN_PORT_MASK(port), |
| QCA8K_EGREES_VLAN_PORT(port, QCA8K_PORT_VID_DEF)); |
| if (ret) |
| return ret; |
| |
| ret = qca8k_write(priv, QCA8K_REG_PORT_VLAN_CTRL0(port), |
| 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) |
| dsa_switch_for_each_available_port(dp, ds) |
| qca8k_setup_hol_fixup(priv, dp->index); |
| |
| /* 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 phylink_mac_ops qca8k_phylink_mac_ops = { |
| .mac_select_pcs = qca8k_phylink_mac_select_pcs, |
| .mac_config = qca8k_phylink_mac_config, |
| .mac_link_down = qca8k_phylink_mac_link_down, |
| .mac_link_up = qca8k_phylink_mac_link_up, |
| }; |
| |
| 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_pre_bridge_flags = qca8k_port_pre_bridge_flags, |
| .port_bridge_flags = qca8k_port_bridge_flags, |
| .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, |
| .get_phy_flags = qca8k_get_phy_flags, |
| .port_lag_join = qca8k_port_lag_join, |
| .port_lag_leave = qca8k_port_lag_leave, |
| .conduit_state_change = qca8k_conduit_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_OUT_HIGH); |
| if (IS_ERR(priv->reset_gpio)) |
| return PTR_ERR(priv->reset_gpio); |
| |
| if (priv->reset_gpio) { |
| /* 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; |
| priv->ds->phylink_mac_ops = &qca8k_phylink_mac_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"); |