new file mode 100644
@@ -0,0 +1,584 @@
+// SPDX-License-Identifier: MIT
+/* Copyright(c) 2019-2021, Celeno Communications Ltd. */
+
+#include "chip.h"
+#include "main.h"
+#include "ops.h"
+#include "dfs/radar.h"
+#include "fw/msg_tx.h"
+#include "tx/tx.h"
+#include "reg/reg_access.h"
+#include "stats.h"
+#include "debugfs.h"
+#include "vendor_cmd.h"
+#include "chan_info.h"
+#include "tx/agg_cfm.h"
+#include "tx/single_cfm.h"
+#include "tx/bcmc_cfm.h"
+#include "tx/tx_queue.h"
+#include "rssi.h"
+#include "maintenance.h"
+#include "vns.h"
+#include "traffic.h"
+#include "ext/vlan_dscp.h"
+#include "sounding.h"
+#include "recovery.h"
+#include "rate_ctrl.h"
+#include "ext/dyn_mcast_rate.h"
+#include "ext/dyn_bcast_rate.h"
+#include "tx/tx_amsdu.h"
+#include "prot_mode.h"
+#include "utils/utils.h"
+#include "band.h"
+#include "phy/phy.h"
+#include "rf_boot.h"
+#include "dsp.h"
+#include "calib.h"
+#include "reg/reg_macsys_gcu.h"
+#include "dfs/dfs.h"
+#include "tx/sw_txhdr.h"
+#include "tx/tx_inject.h"
+#include "fem.h"
+#include "fw/fw_file.h"
+#include "cap.h"
+#include "tcv_config.h"
+#include "mac_addr.h"
+#include "hw_assert.h"
+#include "power_table.h"
+#include "noise.h"
+#include "twt.h"
+#include "fw/fw_dbg.h"
+#include "wrs/wrs_api.h"
+#ifdef CONFIG_CL_PCIE
+#include "fw/msg_rx.h"
+#include "bus/pci/irq.h"
+#include "reg/reg_ipc.h"
+#include "bus/pci/ipc.h"
+#endif
+
+MODULE_DESCRIPTION("Celeno 11ax driver for Linux");
+MODULE_VERSION("8.1.x");
+MODULE_AUTHOR("Copyright(c) 2021 Celeno Communications Ltd");
+MODULE_LICENSE("MIT");
+
+#define MAX_MU_CNT_LMAC 8
+#define MAX_MU_CNT_SMAC 8
+
+static struct ieee80211_ops cl_ops = {
+ .tx = cl_ops_tx,
+ .start = cl_ops_start,
+ .stop = cl_ops_stop,
+ .add_interface = cl_ops_add_interface,
+ .remove_interface = cl_ops_remove_interface,
+ .config = cl_ops_config,
+ .bss_info_changed = cl_ops_bss_info_changed,
+ .start_ap = cl_ops_start_ap,
+ .stop_ap = cl_ops_stop_ap,
+ .prepare_multicast = cl_ops_prepare_multicast,
+ .configure_filter = cl_ops_configure_filter,
+ .set_key = cl_ops_set_key,
+ .sw_scan_start = cl_ops_sw_scan_start,
+ .sta_state = cl_ops_sta_state,
+ .sta_notify = cl_ops_sta_notify,
+ .conf_tx = cl_ops_conf_tx,
+ .sta_rc_update = cl_ops_sta_rc_update,
+ .ampdu_action = cl_ops_ampdu_action,
+ .post_channel_switch = cl_ops_post_channel_switch,
+ .flush = cl_ops_flush,
+ .tx_frames_pending = cl_ops_tx_frames_pending,
+ .reconfig_complete = cl_ops_reconfig_complete,
+ .get_txpower = cl_ops_get_txpower,
+ .set_rts_threshold = cl_ops_set_rts_threshold,
+ .event_callback = cl_ops_event_callback,
+ .set_tim = cl_ops_set_tim,
+};
+
+static void cl_drv_workqueue_create(struct cl_hw *cl_hw)
+{
+ if (!cl_hw->drv_workqueue)
+ cl_hw->drv_workqueue = create_singlethread_workqueue("drv_workqueue");
+}
+
+static void cl_drv_workqueue_destroy(struct cl_hw *cl_hw)
+{
+ if (cl_hw->drv_workqueue) {
+ destroy_workqueue(cl_hw->drv_workqueue);
+ cl_hw->drv_workqueue = NULL;
+ }
+}
+
+static int cl_main_alloc(struct cl_hw *cl_hw)
+{
+ int ret = 0;
+
+ ret = cl_phy_data_alloc(cl_hw);
+ if (ret)
+ return ret;
+
+ ret = cl_calib_tables_alloc(cl_hw);
+ if (ret)
+ return ret;
+
+ ret = cl_power_table_alloc(cl_hw);
+ if (ret)
+ return ret;
+
+ return ret;
+}
+
+static void cl_main_free(struct cl_hw *cl_hw)
+{
+ cl_phy_data_free(cl_hw);
+ cl_calib_tables_free(cl_hw);
+ cl_power_table_free(cl_hw);
+}
+
+static void cl_free_hw(struct cl_hw *cl_hw)
+{
+ struct ieee80211_hw *hw = cl_hw->hw;
+
+ cl_tcv_config_free(cl_hw);
+
+ if (hw->wiphy->registered)
+ ieee80211_unregister_hw(hw);
+
+ cl_chip_unset_hw(cl_hw->chip, cl_hw);
+ ieee80211_free_hw(hw);
+}
+
+static void cl_free_chip(struct cl_chip *chip)
+{
+ cl_free_hw(chip->cl_hw_tcv0);
+ cl_free_hw(chip->cl_hw_tcv1);
+}
+
+static int cl_prepare_hw(struct cl_chip *chip, u8 tcv_idx,
+ const struct cl_driver_ops *drv_ops)
+{
+ struct cl_hw *cl_hw = NULL;
+ struct ieee80211_hw *hw;
+ int ret = 0;
+
+ hw = ieee80211_alloc_hw(sizeof(struct cl_hw), &cl_ops);
+ if (!hw) {
+ cl_dbg_chip_err(chip, ": ieee80211_alloc_hw failed\n");
+ return -ENOMEM;
+ }
+
+ cl_hw_init(chip, hw->priv, tcv_idx);
+
+ cl_hw = hw->priv;
+ cl_hw->hw = hw;
+ cl_hw->tcv_idx = tcv_idx;
+ cl_hw->chip = chip;
+
+ /*
+ * chip0, tcv0 --> 0
+ * chip0, tcv1 --> 1
+ * chip1, tcv0 --> 2
+ * chip1, tcv1 --> 3
+ */
+ cl_hw->idx = chip->idx * CHIP_MAX + tcv_idx;
+
+ cl_hw->drv_ops = drv_ops;
+
+ if (cl_hw_is_tcv0(cl_hw))
+ cl_hw->max_mu_cnt = MAX_MU_CNT_LMAC;
+ else
+ cl_hw->max_mu_cnt = MAX_MU_CNT_SMAC;
+
+ SET_IEEE80211_DEV(hw, chip->dev);
+
+ ret = cl_tcv_config_alloc(cl_hw);
+ if (ret)
+ goto out_free_hw;
+
+ ret = cl_hw_set_antennas(cl_hw);
+ if (ret)
+ goto out_free_hw;
+
+ ret = cl_tcv_config_read(cl_hw);
+ if (ret)
+ goto out_free_hw;
+
+ cl_chip_set_hw(chip, cl_hw);
+
+ ret = cl_mac_addr_set(cl_hw);
+ if (ret) {
+ cl_dbg_err(cl_hw, "cl_mac_addr_set failed\n");
+ goto out_free_hw;
+ }
+
+ if (cl_band_is_6g(cl_hw))
+ cl_hw->nl_band = NL80211_BAND_6GHZ;
+ else if (cl_band_is_5g(cl_hw))
+ cl_hw->nl_band = NL80211_BAND_5GHZ;
+ else
+ cl_hw->nl_band = NL80211_BAND_2GHZ;
+
+ cl_cap_dyn_params(cl_hw);
+ cl_vendor_cmds_init(hw->wiphy);
+
+ /*
+ * ieee80211_register_hw() will take care of calling wiphy_register() and
+ * also ieee80211_if_add() (because IFTYPE_STATION is supported)
+ * which will internally call register_netdev()
+ */
+ ret = ieee80211_register_hw(hw);
+ if (ret) {
+ cl_dbg_err(cl_hw, "ieee80211_register_hw failed\n");
+ goto out_free_hw;
+ }
+
+ if (hw->wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED) {
+ ret = regulatory_set_wiphy_regd(hw->wiphy, cl_hw->channel_info.rd);
+ if (ret)
+ cl_dbg_err(cl_hw, "regulatory failed\n");
+ }
+
+ cl_dbg_verbose(cl_hw, "cl_hw created\n");
+
+ return 0;
+
+out_free_hw:
+ cl_free_hw(cl_hw);
+
+ return ret;
+}
+
+void cl_main_off(struct cl_hw *cl_hw)
+{
+#ifdef CONFIG_CL_PCIE
+ cl_irq_disable(cl_hw, cl_hw->ipc_e2a_irq.all);
+ cl_ipc_stop(cl_hw);
+#endif
+
+ if (!test_bit(CL_DEV_INIT, &cl_hw->drv_flags)) {
+ cl_tx_off(cl_hw);
+ cl_rx_off(cl_hw);
+#ifdef CONFIG_CL_PCIE
+ cl_msg_rx_flush_all(cl_hw);
+#endif
+ }
+
+ cl_fw_file_cleanup(cl_hw);
+}
+
+static void _cl_main_deinit(struct cl_hw *cl_hw)
+{
+ /* First bring down all interfaces */
+ cl_vif_bring_all_interfaces_down(cl_hw);
+
+ cl_hw->is_stop_context = true;
+
+ cl_drv_workqueue_destroy(cl_hw);
+
+ cl_noise_close(cl_hw);
+ cl_maintenance_close(cl_hw);
+ cl_vns_close(cl_hw);
+ cl_rssi_assoc_exit(cl_hw);
+ cl_radar_close(cl_hw);
+ cl_sounding_close(cl_hw);
+ cl_chan_info_deinit(cl_hw);
+ cl_wrs_api_close(cl_hw);
+ cl_dfs_close(cl_hw);
+ cl_twt_close(cl_hw);
+ cl_tx_inject_close(cl_hw);
+ cl_dbgfs_unregister(cl_hw);
+ cl_main_off(cl_hw);
+ /* These 2 must be called after cl_tx_off() (which is called from cl_main_off) */
+ cl_tx_amsdu_txhdr_deinit(cl_hw);
+ cl_sw_txhdr_deinit(cl_hw);
+ cl_stats_deinit(cl_hw);
+ cl_main_free(cl_hw);
+ cl_fw_file_release(cl_hw);
+ cl_vendor_timer_close(cl_hw);
+#ifdef CONFIG_CL_PCIE
+ cl_ipc_deinit(cl_hw);
+#endif
+ cl_hw_deinit(cl_hw, cl_hw->tcv_idx);
+}
+
+void cl_main_deinit(struct cl_chip *chip)
+{
+ struct cl_hw *cl_hw_tcv0 = chip->cl_hw_tcv0;
+ struct cl_hw *cl_hw_tcv1 = chip->cl_hw_tcv1;
+
+ if (cl_chip_is_tcv1_enabled(chip) && cl_hw_tcv1)
+ _cl_main_deinit(cl_hw_tcv1);
+
+ if (cl_chip_is_tcv0_enabled(chip) && cl_hw_tcv0)
+ _cl_main_deinit(cl_hw_tcv0);
+
+ if (cl_hw_tcv1) {
+ cl_phy_off(cl_hw_tcv1);
+ cl_free_hw(cl_hw_tcv1);
+ }
+
+ if (cl_hw_tcv0) {
+ cl_phy_off(cl_hw_tcv0);
+ cl_free_hw(cl_hw_tcv0);
+ }
+}
+
+struct cl_controller_reg all_controller_reg = {
+ .breset = XMAC_BRESET,
+ .debug_enable = XMAC_DEBUG_ENABLE,
+ .dreset = XMAC_DRESET,
+ .ocd_halt_on_reset = XMAC_OCD_HALT_ON_RESET,
+ .run_stall = XMAC_RUN_STALL
+};
+
+void cl_main_reset(struct cl_chip *chip, struct cl_controller_reg *controller_reg)
+{
+ /* Release TRST & BReset to enable JTAG connection to FPGA A */
+ u32 regval;
+
+ /* 1. return to reset value */
+ regval = macsys_gcu_xt_control_get(chip);
+ regval |= controller_reg->ocd_halt_on_reset;
+ regval &= ~(controller_reg->dreset | controller_reg->run_stall | controller_reg->breset);
+ macsys_gcu_xt_control_set(chip, regval);
+
+ regval = macsys_gcu_xt_control_get(chip);
+ regval |= controller_reg->dreset;
+ macsys_gcu_xt_control_set(chip, regval);
+
+ /* 2. stall xtensa & release ocd */
+ regval = macsys_gcu_xt_control_get(chip);
+ regval |= controller_reg->run_stall;
+ regval &= ~controller_reg->ocd_halt_on_reset;
+ macsys_gcu_xt_control_set(chip, regval);
+
+ /* 3. breset release & debug enable */
+ regval = macsys_gcu_xt_control_get(chip);
+ regval |= (controller_reg->debug_enable | controller_reg->breset);
+ macsys_gcu_xt_control_set(chip, regval);
+
+ msleep(100);
+}
+
+int cl_main_on(struct cl_hw *cl_hw)
+{
+ struct cl_chip *chip = cl_hw->chip;
+ int ret;
+ u32 regval;
+
+ cl_hw->fw_active = false;
+
+ cl_txq_init(cl_hw);
+
+ cl_hw_assert_info_init(cl_hw);
+
+ if (cl_recovery_in_progress(cl_hw))
+ cl_main_reset(chip, &cl_hw->controller_reg);
+
+ ret = cl_fw_file_load(cl_hw);
+ if (ret) {
+ cl_dbg_err(cl_hw, "cl_fw_file_load failed %d\n", ret);
+ return ret;
+ }
+
+ /* Clear CL_DEV_FW_ERROR after firmware loaded */
+ clear_bit(CL_DEV_FW_ERROR, &cl_hw->drv_flags);
+
+#ifdef CONFIG_CL_PCIE
+ if (cl_recovery_in_progress(cl_hw))
+ cl_ipc_recovery(cl_hw);
+#endif
+
+ regval = macsys_gcu_xt_control_get(chip);
+
+ /* Set fw to run */
+ if (cl_hw->fw_active)
+ regval &= ~cl_hw->controller_reg.run_stall;
+
+#ifdef CONFIG_CL_PCIE
+ /* Ack all possibly pending IRQs */
+ ipc_xmac_2_host_ack_set(chip, cl_hw->ipc_e2a_irq.all);
+#endif
+
+ macsys_gcu_xt_control_set(chip, regval);
+
+#ifdef CONFIG_CL_PCIE
+ cl_irq_enable(cl_hw, cl_hw->ipc_e2a_irq.all);
+#endif
+
+ /*
+ * cl_irq_status_sync will set CL_DEV_FW_SYNC when fw raises IPC_IRQ_E2A_SYNC
+ * (indicate its ready to accept interrupts)
+ */
+ ret = wait_event_interruptible_timeout(cl_hw->fw_sync_wq,
+ test_and_clear_bit(CL_DEV_FW_SYNC,
+ &cl_hw->drv_flags),
+ msecs_to_jiffies(5000));
+
+ if (ret == 0) {
+ pr_err("[%s]: FW synchronization timeout.\n", __func__);
+ cl_hw_assert_check(cl_hw);
+ ret = -ETIMEDOUT;
+ goto out_free_cached_fw;
+ } else if (ret == -ERESTARTSYS) {
+ goto out_free_cached_fw;
+ }
+
+ return 0;
+
+out_free_cached_fw:
+ cl_fw_file_release(cl_hw);
+ return ret;
+}
+
+static int __cl_main_init(struct cl_hw *cl_hw)
+{
+ int ret = 0;
+
+ set_bit(CL_DEV_INIT, &cl_hw->drv_flags);
+
+ /* By default, set FEM mode to operational mode. */
+ cl_hw->fem_system_mode = FEM_MODE_OPERETIONAL;
+
+ cl_vif_init(cl_hw);
+
+ cl_drv_workqueue_create(cl_hw);
+
+ init_waitqueue_head(&cl_hw->wait_queue);
+ init_waitqueue_head(&cl_hw->fw_sync_wq);
+ init_waitqueue_head(&cl_hw->radio_wait_queue);
+
+ mutex_init(&cl_hw->dbginfo.mutex);
+ mutex_init(&cl_hw->msg_tx_mutex);
+ mutex_init(&cl_hw->set_channel_mutex);
+
+ spin_lock_init(&cl_hw->tx_lock_agg);
+ spin_lock_init(&cl_hw->tx_lock_cfm_agg);
+ spin_lock_init(&cl_hw->tx_lock_single);
+ spin_lock_init(&cl_hw->tx_lock_bcmc);
+
+#ifdef CONFIG_CL_PCIE
+ ret = cl_ipc_init(cl_hw);
+ if (ret) {
+ cl_dbg_err(cl_hw, "cl_ipc_init failed %d\n", ret);
+ return ret;
+ }
+#endif
+ ret = cl_main_on(cl_hw);
+ if (ret) {
+ cl_dbg_err(cl_hw, "cl_main_on failed %d\n", ret);
+#ifdef CONFIG_CL_PCIE
+ cl_ipc_deinit(cl_hw);
+#endif
+ return ret;
+ }
+
+ ret = cl_main_alloc(cl_hw);
+ if (ret)
+ goto out_free;
+
+ /* Reset firmware */
+ ret = cl_msg_tx_reset(cl_hw);
+ if (ret)
+ goto out_free;
+
+ cl_calib_power_read(cl_hw);
+ cl_dbgfs_register(cl_hw, "cl");
+ cl_sta_init(cl_hw);
+ cl_sw_txhdr_init(cl_hw);
+ cl_tx_amsdu_txhdr_init(cl_hw);
+ cl_tx_init(cl_hw);
+ cl_rx_init(cl_hw);
+ cl_prot_mode_init(cl_hw);
+ cl_radar_init(cl_hw);
+ cl_sounding_init(cl_hw);
+ cl_vlan_dscp_init(cl_hw);
+ cl_traffic_init(cl_hw);
+ cl_rsrc_mgmt_init(cl_hw);
+ cl_vns_init(cl_hw);
+ cl_maintenance_init(cl_hw);
+ cl_rssi_assoc_init(cl_hw);
+ cl_agg_cfm_init(cl_hw);
+ cl_single_cfm_init(cl_hw);
+ cl_bcmc_cfm_init(cl_hw);
+ cl_dyn_mcast_rate_init(cl_hw);
+ cl_dyn_bcast_rate_init(cl_hw);
+ cl_wrs_api_init(cl_hw);
+ cl_dfs_init(cl_hw);
+ cl_tx_inject_init(cl_hw);
+ cl_noise_init(cl_hw);
+ cl_twt_init(cl_hw);
+ cl_fw_dbg_trigger_based_init(cl_hw);
+ cl_stats_init(cl_hw);
+ cl_vendor_timer_init(cl_hw);
+
+ return 0;
+
+out_free:
+ cl_main_free(cl_hw);
+
+ return ret;
+}
+
+static int _cl_main_init(struct cl_chip *chip, struct cl_hw *cl_hw)
+{
+ int ret = 0;
+
+ if (cl_chip_is_tcv_enabled(chip, cl_hw->tcv_idx)) {
+ ret = __cl_main_init(cl_hw);
+ if (ret) {
+ cl_dbg_chip_err(chip, "TCV%u failed (%d)\n", cl_hw->tcv_idx, ret);
+ return ret;
+ }
+ } else {
+ ieee80211_unregister_hw(cl_hw->hw);
+ }
+
+ return ret;
+}
+
+int cl_main_init(struct cl_chip *chip, const struct cl_driver_ops *drv_ops)
+{
+ int ret = 0;
+
+ /* All cores needs to be reset first (once per chip) */
+ cl_main_reset(chip, &all_controller_reg);
+
+ ret = cl_prepare_hw(chip, TCV0, drv_ops);
+ if (ret) {
+ cl_dbg_chip_err(chip, "cl_prepare_hw for TCV0 failed %d\n", ret);
+ return ret;
+ }
+
+ ret = cl_prepare_hw(chip, TCV1, drv_ops);
+ if (ret) {
+ cl_dbg_chip_err(chip, "cl_prepare_hw for TCV1 failed %d\n", ret);
+ cl_free_hw(chip->cl_hw_tcv0);
+ return ret;
+ }
+
+ ret = cl_rf_boot(chip);
+ if (ret) {
+ cl_dbg_chip_err(chip, "cl_rf_boot failed %d\n", ret);
+ return ret;
+ }
+
+ ret = cl_dsp_load_regular(chip);
+ if (ret) {
+ cl_dbg_chip_err(chip, "cl_dsp_load_regular failed %d\n", ret);
+ return ret;
+ }
+
+ ret = _cl_main_init(chip, chip->cl_hw_tcv0);
+ if (ret) {
+ cl_free_chip(chip);
+ return ret;
+ }
+
+ ret = _cl_main_init(chip, chip->cl_hw_tcv1);
+ if (ret) {
+ _cl_main_deinit(chip->cl_hw_tcv0);
+ cl_free_chip(chip);
+ return ret;
+ }
+
+ return ret;
+}