#include <linux/of_net.h>
 #include <linux/micrel_phy.h>
 #include <net/dsa.h>
+#include <net/pkt_cls.h>
 #include <net/switchdev.h>
 
 #include "ksz_common.h"
 #include "ksz9477.h"
 #include "lan937x.h"
 
+#define KSZ_CBS_ENABLE ((MTI_SCHEDULE_STRICT_PRIO << MTI_SCHEDULE_MODE_S) | \
+                       (MTI_SHAPING_SRP << MTI_SHAPING_S))
+#define KSZ_CBS_DISABLE ((MTI_SCHEDULE_WRR << MTI_SCHEDULE_MODE_S) |\
+                        (MTI_SHAPING_OFF << MTI_SHAPING_S))
 #define MIB_COUNTER_NUM 0x20
 
 struct ksz_stats_raw {
        .change_mtu = ksz9477_change_mtu,
        .phylink_mac_link_up = ksz9477_phylink_mac_link_up,
        .config_cpu_port = ksz9477_config_cpu_port,
+       .tc_cbs_set_cinc = ksz9477_tc_cbs_set_cinc,
        .enable_stp_addr = ksz9477_enable_stp_addr,
        .reset = ksz9477_reset_switch,
        .init = ksz9477_switch_init,
        .change_mtu = lan937x_change_mtu,
        .phylink_mac_link_up = ksz9477_phylink_mac_link_up,
        .config_cpu_port = lan937x_config_cpu_port,
+       .tc_cbs_set_cinc = lan937x_tc_cbs_set_cinc,
        .enable_stp_addr = ksz9477_enable_stp_addr,
        .reset = lan937x_reset_switch,
        .init = lan937x_switch_init,
                .port_cnt = 3,          /* total port count */
                .port_nirqs = 3,
                .num_tx_queues = 4,
+               .tc_cbs_supported = true,
                .ops = &ksz9477_dev_ops,
                .mib_names = ksz9477_mib_names,
                .mib_cnt = ARRAY_SIZE(ksz9477_mib_names),
                .port_cnt = 7,          /* total physical port count */
                .port_nirqs = 4,
                .num_tx_queues = 4,
+               .tc_cbs_supported = true,
                .ops = &ksz9477_dev_ops,
                .phy_errata_9477 = true,
                .mib_names = ksz9477_mib_names,
                .port_cnt = 3,          /* total port count */
                .port_nirqs = 3,
                .num_tx_queues = 4,
+               .tc_cbs_supported = true,
                .ops = &ksz9477_dev_ops,
                .mib_names = ksz9477_mib_names,
                .mib_cnt = ARRAY_SIZE(ksz9477_mib_names),
                .port_cnt = 7,          /* total physical port count */
                .port_nirqs = 3,
                .num_tx_queues = 4,
+               .tc_cbs_supported = true,
                .ops = &ksz9477_dev_ops,
                .phy_errata_9477 = true,
                .mib_names = ksz9477_mib_names,
                .port_cnt = 5,          /* total physical port count */
                .port_nirqs = 6,
                .num_tx_queues = 8,
+               .tc_cbs_supported = true,
                .ops = &lan937x_dev_ops,
                .mib_names = ksz9477_mib_names,
                .mib_cnt = ARRAY_SIZE(ksz9477_mib_names),
                .port_cnt = 6,          /* total physical port count */
                .port_nirqs = 6,
                .num_tx_queues = 8,
+               .tc_cbs_supported = true,
                .ops = &lan937x_dev_ops,
                .mib_names = ksz9477_mib_names,
                .mib_cnt = ARRAY_SIZE(ksz9477_mib_names),
                .port_cnt = 8,          /* total physical port count */
                .port_nirqs = 6,
                .num_tx_queues = 8,
+               .tc_cbs_supported = true,
                .ops = &lan937x_dev_ops,
                .mib_names = ksz9477_mib_names,
                .mib_cnt = ARRAY_SIZE(ksz9477_mib_names),
                .port_cnt = 5,          /* total physical port count */
                .port_nirqs = 6,
                .num_tx_queues = 8,
+               .tc_cbs_supported = true,
                .ops = &lan937x_dev_ops,
                .mib_names = ksz9477_mib_names,
                .mib_cnt = ARRAY_SIZE(ksz9477_mib_names),
                .port_cnt = 8,          /* total physical port count */
                .port_nirqs = 6,
                .num_tx_queues = 8,
+               .tc_cbs_supported = true,
                .ops = &lan937x_dev_ops,
                .mib_names = ksz9477_mib_names,
                .mib_cnt = ARRAY_SIZE(ksz9477_mib_names),
        return 0;
 }
 
+/* Bandwidth is calculated by idle slope/transmission speed. Then the Bandwidth
+ * is converted to Hex-decimal using the successive multiplication method. On
+ * every step, integer part is taken and decimal part is carry forwarded.
+ */
+static int cinc_cal(s32 idle_slope, s32 send_slope, u32 *bw)
+{
+       u32 cinc = 0;
+       u32 txrate;
+       u32 rate;
+       u8 temp;
+       u8 i;
+
+       txrate = idle_slope - send_slope;
+
+       if (!txrate)
+               return -EINVAL;
+
+       rate = idle_slope;
+
+       /* 24 bit register */
+       for (i = 0; i < 6; i++) {
+               rate = rate * 16;
+
+               temp = rate / txrate;
+
+               rate %= txrate;
+
+               cinc = ((cinc << 4) | temp);
+       }
+
+       *bw = cinc;
+
+       return 0;
+}
+
+static int ksz_setup_tc_cbs(struct dsa_switch *ds, int port,
+                           struct tc_cbs_qopt_offload *qopt)
+{
+       struct ksz_device *dev = ds->priv;
+       int ret;
+       u32 bw;
+
+       if (!dev->info->tc_cbs_supported)
+               return -EOPNOTSUPP;
+
+       if (qopt->queue > dev->info->num_tx_queues)
+               return -EINVAL;
+
+       /* Queue Selection */
+       ret = ksz_pwrite32(dev, port, REG_PORT_MTI_QUEUE_INDEX__4, qopt->queue);
+       if (ret)
+               return ret;
+
+       if (!qopt->enable)
+               return ksz_pwrite8(dev, port, REG_PORT_MTI_QUEUE_CTRL_0,
+                                  KSZ_CBS_DISABLE);
+
+       /* High Credit */
+       ret = ksz_pwrite16(dev, port, REG_PORT_MTI_HI_WATER_MARK,
+                          qopt->hicredit);
+       if (ret)
+               return ret;
+
+       /* Low Credit */
+       ret = ksz_pwrite16(dev, port, REG_PORT_MTI_LO_WATER_MARK,
+                          qopt->locredit);
+       if (ret)
+               return ret;
+
+       /* Credit Increment Register */
+       ret = cinc_cal(qopt->idleslope, qopt->sendslope, &bw);
+       if (ret)
+               return ret;
+
+       if (dev->dev_ops->tc_cbs_set_cinc) {
+               ret = dev->dev_ops->tc_cbs_set_cinc(dev, port, bw);
+               if (ret)
+                       return ret;
+       }
+
+       return ksz_pwrite8(dev, port, REG_PORT_MTI_QUEUE_CTRL_0,
+                          KSZ_CBS_ENABLE);
+}
+
+static int ksz_setup_tc(struct dsa_switch *ds, int port,
+                       enum tc_setup_type type, void *type_data)
+{
+       switch (type) {
+       case TC_SETUP_QDISC_CBS:
+               return ksz_setup_tc_cbs(ds, port, type_data);
+       default:
+               return -EOPNOTSUPP;
+       }
+}
+
 static const struct dsa_switch_ops ksz_switch_ops = {
        .get_tag_protocol       = ksz_get_tag_protocol,
        .connect_tag_protocol   = ksz_connect_tag_protocol,
        .port_hwtstamp_set      = ksz_hwtstamp_set,
        .port_txtstamp          = ksz_port_txtstamp,
        .port_rxtstamp          = ksz_port_rxtstamp,
+       .port_setup_tc          = ksz_setup_tc,
 };
 
 struct ksz_device *ksz_switch_alloc(struct device *base, void *priv)
 
        int port_cnt;
        u8 port_nirqs;
        u8 num_tx_queues;
+       bool tc_cbs_supported;
        const struct ksz_dev_ops *ops;
        bool phy_errata_9477;
        bool ksz87xx_eee_link_erratum;
                                    struct phy_device *phydev, int speed,
                                    int duplex, bool tx_pause, bool rx_pause);
        void (*setup_rgmii_delay)(struct ksz_device *dev, int port);
+       int (*tc_cbs_set_cinc)(struct ksz_device *dev, int port, u32 val);
        void (*config_cpu_port)(struct dsa_switch *ds);
        int (*enable_stp_addr)(struct ksz_device *dev);
        int (*reset)(struct ksz_device *dev);
 #define KSZ8_LEGAL_PACKET_SIZE         1518
 #define KSZ9477_MAX_FRAME_SIZE         9000
 
+/* CBS related registers */
+#define REG_PORT_MTI_QUEUE_INDEX__4    0x0900
+
+#define REG_PORT_MTI_QUEUE_CTRL_0      0x0914
+
+#define MTI_SCHEDULE_MODE_M            0x3
+#define MTI_SCHEDULE_MODE_S            6
+#define MTI_SCHEDULE_STRICT_PRIO       0
+#define MTI_SCHEDULE_WRR               2
+#define MTI_SHAPING_M                  0x3
+#define MTI_SHAPING_S                  4
+#define MTI_SHAPING_OFF                        0
+#define MTI_SHAPING_SRP                        1
+#define MTI_SHAPING_TIME_AWARE         2
+
+#define REG_PORT_MTI_HI_WATER_MARK     0x0916
+#define REG_PORT_MTI_LO_WATER_MARK     0x0918
+
 /* Regmap tables generation */
 #define KSZ_SPI_OP_RD          3
 #define KSZ_SPI_OP_WR          2