diff options
Diffstat (limited to 'drivers/net/wireless/silabs/wfx/main.c')
| -rw-r--r-- | drivers/net/wireless/silabs/wfx/main.c | 491 | 
1 files changed, 491 insertions, 0 deletions
diff --git a/drivers/net/wireless/silabs/wfx/main.c b/drivers/net/wireless/silabs/wfx/main.c new file mode 100644 index 000000000000..b93b16b900c8 --- /dev/null +++ b/drivers/net/wireless/silabs/wfx/main.c @@ -0,0 +1,491 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Device probe and register. + * + * Copyright (c) 2017-2020, Silicon Laboratories, Inc. + * Copyright (c) 2010, ST-Ericsson + * Copyright (c) 2008, Johannes Berg <[email protected]> + * Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). + * Copyright (c) 2007-2009, Christian Lamparter <[email protected]> + * Copyright (c) 2006, Michael Wu <[email protected]> + * Copyright (c) 2004-2006 Jean-Baptiste Note <[email protected]>, et al. + */ +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_net.h> +#include <linux/gpio/consumer.h> +#include <linux/mmc/sdio_func.h> +#include <linux/spi/spi.h> +#include <linux/etherdevice.h> +#include <linux/firmware.h> + +#include "main.h" +#include "wfx.h" +#include "fwio.h" +#include "hwio.h" +#include "bus.h" +#include "bh.h" +#include "sta.h" +#include "key.h" +#include "scan.h" +#include "debug.h" +#include "data_tx.h" +#include "hif_tx_mib.h" +#include "hif_api_cmd.h" + +#define WFX_PDS_TLV_TYPE 0x4450 // "PD" (Platform Data) in ascii little-endian +#define WFX_PDS_MAX_CHUNK_SIZE 1500 + +MODULE_DESCRIPTION("Silicon Labs 802.11 Wireless LAN driver for WF200"); +MODULE_AUTHOR("Jérôme Pouiller <[email protected]>"); +MODULE_LICENSE("GPL"); + +#define RATETAB_ENT(_rate, _rateid, _flags) { \ +	.bitrate  = (_rate),   \ +	.hw_value = (_rateid), \ +	.flags    = (_flags),  \ +} + +static struct ieee80211_rate wfx_rates[] = { +	RATETAB_ENT(10,  0,  0), +	RATETAB_ENT(20,  1,  IEEE80211_RATE_SHORT_PREAMBLE), +	RATETAB_ENT(55,  2,  IEEE80211_RATE_SHORT_PREAMBLE), +	RATETAB_ENT(110, 3,  IEEE80211_RATE_SHORT_PREAMBLE), +	RATETAB_ENT(60,  6,  0), +	RATETAB_ENT(90,  7,  0), +	RATETAB_ENT(120, 8,  0), +	RATETAB_ENT(180, 9,  0), +	RATETAB_ENT(240, 10, 0), +	RATETAB_ENT(360, 11, 0), +	RATETAB_ENT(480, 12, 0), +	RATETAB_ENT(540, 13, 0), +}; + +#define CHAN2G(_channel, _freq, _flags) { \ +	.band = NL80211_BAND_2GHZ, \ +	.center_freq = (_freq),    \ +	.hw_value = (_channel),    \ +	.flags = (_flags),         \ +	.max_antenna_gain = 0,     \ +	.max_power = 30,           \ +} + +static struct ieee80211_channel wfx_2ghz_chantable[] = { +	CHAN2G(1,  2412, 0), +	CHAN2G(2,  2417, 0), +	CHAN2G(3,  2422, 0), +	CHAN2G(4,  2427, 0), +	CHAN2G(5,  2432, 0), +	CHAN2G(6,  2437, 0), +	CHAN2G(7,  2442, 0), +	CHAN2G(8,  2447, 0), +	CHAN2G(9,  2452, 0), +	CHAN2G(10, 2457, 0), +	CHAN2G(11, 2462, 0), +	CHAN2G(12, 2467, 0), +	CHAN2G(13, 2472, 0), +	CHAN2G(14, 2484, 0), +}; + +static const struct ieee80211_supported_band wfx_band_2ghz = { +	.channels = wfx_2ghz_chantable, +	.n_channels = ARRAY_SIZE(wfx_2ghz_chantable), +	.bitrates = wfx_rates, +	.n_bitrates = ARRAY_SIZE(wfx_rates), +	.ht_cap = { +		/* Receive caps */ +		.cap = IEEE80211_HT_CAP_GRN_FLD | IEEE80211_HT_CAP_SGI_20 | +		       IEEE80211_HT_CAP_MAX_AMSDU | (1 << IEEE80211_HT_CAP_RX_STBC_SHIFT), +		.ht_supported = 1, +		.ampdu_factor = IEEE80211_HT_MAX_AMPDU_16K, +		.ampdu_density = IEEE80211_HT_MPDU_DENSITY_NONE, +		.mcs = { +			.rx_mask = { 0xFF }, /* MCS0 to MCS7 */ +			.rx_highest = cpu_to_le16(72), +			.tx_params = IEEE80211_HT_MCS_TX_DEFINED, +		}, +	}, +}; + +static const struct ieee80211_iface_limit wdev_iface_limits[] = { +	{ .max = 1, .types = BIT(NL80211_IFTYPE_STATION) }, +	{ .max = 1, .types = BIT(NL80211_IFTYPE_AP) }, +}; + +static const struct ieee80211_iface_combination wfx_iface_combinations[] = { +	{ +		.num_different_channels = 2, +		.max_interfaces = 2, +		.limits = wdev_iface_limits, +		.n_limits = ARRAY_SIZE(wdev_iface_limits), +	} +}; + +static const struct ieee80211_ops wfx_ops = { +	.start                   = wfx_start, +	.stop                    = wfx_stop, +	.add_interface           = wfx_add_interface, +	.remove_interface        = wfx_remove_interface, +	.config                  = wfx_config, +	.tx                      = wfx_tx, +	.join_ibss               = wfx_join_ibss, +	.leave_ibss              = wfx_leave_ibss, +	.conf_tx                 = wfx_conf_tx, +	.hw_scan                 = wfx_hw_scan, +	.cancel_hw_scan          = wfx_cancel_hw_scan, +	.start_ap                = wfx_start_ap, +	.stop_ap                 = wfx_stop_ap, +	.sta_add                 = wfx_sta_add, +	.sta_remove              = wfx_sta_remove, +	.set_tim                 = wfx_set_tim, +	.set_key                 = wfx_set_key, +	.set_rts_threshold       = wfx_set_rts_threshold, +	.set_default_unicast_key = wfx_set_default_unicast_key, +	.bss_info_changed        = wfx_bss_info_changed, +	.configure_filter        = wfx_configure_filter, +	.ampdu_action            = wfx_ampdu_action, +	.flush                   = wfx_flush, +	.add_chanctx             = wfx_add_chanctx, +	.remove_chanctx          = wfx_remove_chanctx, +	.change_chanctx          = wfx_change_chanctx, +	.assign_vif_chanctx      = wfx_assign_vif_chanctx, +	.unassign_vif_chanctx    = wfx_unassign_vif_chanctx, +}; + +bool wfx_api_older_than(struct wfx_dev *wdev, int major, int minor) +{ +	if (wdev->hw_caps.api_version_major < major) +		return true; +	if (wdev->hw_caps.api_version_major > major) +		return false; +	if (wdev->hw_caps.api_version_minor < minor) +		return true; +	return false; +} + +/* The device needs data about the antenna configuration. This information in provided by PDS + * (Platform Data Set, this is the wording used in WF200 documentation) files. For hardware + * integrators, the full process to create PDS files is described here: + *   https://github.com/SiliconLabs/wfx-firmware/blob/master/PDS/README.md + * + * The PDS file is an array of Time-Length-Value structs. + */ + int wfx_send_pds(struct wfx_dev *wdev, u8 *buf, size_t len) +{ +	int ret, chunk_type, chunk_len, chunk_num = 0; + +	if (*buf == '{') { +		dev_err(wdev->dev, "PDS: malformed file (legacy format?)\n"); +		return -EINVAL; +	} +	while (len > 0) { +		chunk_type = get_unaligned_le16(buf + 0); +		chunk_len = get_unaligned_le16(buf + 2); +		if (chunk_len > len) { +			dev_err(wdev->dev, "PDS:%d: corrupted file\n", chunk_num); +			return -EINVAL; +		} +		if (chunk_type != WFX_PDS_TLV_TYPE) { +			dev_info(wdev->dev, "PDS:%d: skip unknown data\n", chunk_num); +			goto next; +		} +		if (chunk_len > WFX_PDS_MAX_CHUNK_SIZE) +			dev_warn(wdev->dev, "PDS:%d: unexpectedly large chunk\n", chunk_num); +		if (buf[4] != '{' || buf[chunk_len - 1] != '}') +			dev_warn(wdev->dev, "PDS:%d: unexpected content\n", chunk_num); + +		ret = wfx_hif_configuration(wdev, buf + 4, chunk_len - 4); +		if (ret > 0) { +			dev_err(wdev->dev, "PDS:%d: invalid data (unsupported options?)\n", chunk_num); +			return -EINVAL; +		} +		if (ret == -ETIMEDOUT) { +			dev_err(wdev->dev, "PDS:%d: chip didn't reply (corrupted file?)\n", chunk_num); +			return ret; +		} +		if (ret) { +			dev_err(wdev->dev, "PDS:%d: chip returned an unknown error\n", chunk_num); +			return -EIO; +		} +next: +		chunk_num++; +		len -= chunk_len; +		buf += chunk_len; +	} +	return 0; +} + +static int wfx_send_pdata_pds(struct wfx_dev *wdev) +{ +	int ret = 0; +	const struct firmware *pds; +	u8 *tmp_buf; + +	ret = request_firmware(&pds, wdev->pdata.file_pds, wdev->dev); +	if (ret) { +		dev_err(wdev->dev, "can't load antenna parameters (PDS file %s). The device may be unstable.\n", +			wdev->pdata.file_pds); +		return ret; +	} +	tmp_buf = kmemdup(pds->data, pds->size, GFP_KERNEL); +	if (!tmp_buf) { +		ret = -ENOMEM; +		goto release_fw; +	} +	ret = wfx_send_pds(wdev, tmp_buf, pds->size); +	kfree(tmp_buf); +release_fw: +	release_firmware(pds); +	return ret; +} + +static void wfx_free_common(void *data) +{ +	struct wfx_dev *wdev = data; + +	mutex_destroy(&wdev->tx_power_loop_info_lock); +	mutex_destroy(&wdev->rx_stats_lock); +	mutex_destroy(&wdev->conf_mutex); +	ieee80211_free_hw(wdev->hw); +} + +struct wfx_dev *wfx_init_common(struct device *dev, const struct wfx_platform_data *pdata, +				const struct wfx_hwbus_ops *hwbus_ops, void *hwbus_priv) +{ +	struct ieee80211_hw *hw; +	struct wfx_dev *wdev; + +	hw = ieee80211_alloc_hw(sizeof(struct wfx_dev), &wfx_ops); +	if (!hw) +		return NULL; + +	SET_IEEE80211_DEV(hw, dev); + +	ieee80211_hw_set(hw, TX_AMPDU_SETUP_IN_HW); +	ieee80211_hw_set(hw, AMPDU_AGGREGATION); +	ieee80211_hw_set(hw, CONNECTION_MONITOR); +	ieee80211_hw_set(hw, REPORTS_TX_ACK_STATUS); +	ieee80211_hw_set(hw, SUPPORTS_DYNAMIC_PS); +	ieee80211_hw_set(hw, SIGNAL_DBM); +	ieee80211_hw_set(hw, SUPPORTS_PS); +	ieee80211_hw_set(hw, MFP_CAPABLE); + +	hw->vif_data_size = sizeof(struct wfx_vif); +	hw->sta_data_size = sizeof(struct wfx_sta_priv); +	hw->queues = 4; +	hw->max_rates = 8; +	hw->max_rate_tries = 8; +	hw->extra_tx_headroom = sizeof(struct wfx_hif_msg) + sizeof(struct wfx_hif_req_tx) + +				4 /* alignment */ + 8 /* TKIP IV */; +	hw->wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) | +				     BIT(NL80211_IFTYPE_ADHOC) | +				     BIT(NL80211_IFTYPE_AP); +	hw->wiphy->probe_resp_offload = NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS | +					NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS2 | +					NL80211_PROBE_RESP_OFFLOAD_SUPPORT_P2P | +					NL80211_PROBE_RESP_OFFLOAD_SUPPORT_80211U; +	hw->wiphy->features |= NL80211_FEATURE_AP_SCAN; +	hw->wiphy->flags |= WIPHY_FLAG_AP_PROBE_RESP_OFFLOAD; +	hw->wiphy->flags |= WIPHY_FLAG_AP_UAPSD; +	hw->wiphy->max_ap_assoc_sta = HIF_LINK_ID_MAX; +	hw->wiphy->max_scan_ssids = 2; +	hw->wiphy->max_scan_ie_len = IEEE80211_MAX_DATA_LEN; +	hw->wiphy->n_iface_combinations = ARRAY_SIZE(wfx_iface_combinations); +	hw->wiphy->iface_combinations = wfx_iface_combinations; +	hw->wiphy->bands[NL80211_BAND_2GHZ] = devm_kmalloc(dev, sizeof(wfx_band_2ghz), GFP_KERNEL); +	if (!hw->wiphy->bands[NL80211_BAND_2GHZ]) +		goto err; + +	/* FIXME: also copy wfx_rates and wfx_2ghz_chantable */ +	memcpy(hw->wiphy->bands[NL80211_BAND_2GHZ], &wfx_band_2ghz, sizeof(wfx_band_2ghz)); + +	wdev = hw->priv; +	wdev->hw = hw; +	wdev->dev = dev; +	wdev->hwbus_ops = hwbus_ops; +	wdev->hwbus_priv = hwbus_priv; +	memcpy(&wdev->pdata, pdata, sizeof(*pdata)); +	of_property_read_string(dev->of_node, "silabs,antenna-config-file", &wdev->pdata.file_pds); +	wdev->pdata.gpio_wakeup = devm_gpiod_get_optional(dev, "wakeup", GPIOD_OUT_LOW); +	if (IS_ERR(wdev->pdata.gpio_wakeup)) +		goto err; + +	if (wdev->pdata.gpio_wakeup) +		gpiod_set_consumer_name(wdev->pdata.gpio_wakeup, "wfx wakeup"); + +	mutex_init(&wdev->conf_mutex); +	mutex_init(&wdev->rx_stats_lock); +	mutex_init(&wdev->tx_power_loop_info_lock); +	init_completion(&wdev->firmware_ready); +	INIT_DELAYED_WORK(&wdev->cooling_timeout_work, wfx_cooling_timeout_work); +	skb_queue_head_init(&wdev->tx_pending); +	init_waitqueue_head(&wdev->tx_dequeue); +	wfx_init_hif_cmd(&wdev->hif_cmd); + +	if (devm_add_action_or_reset(dev, wfx_free_common, wdev)) +		return NULL; + +	return wdev; + +err: +	ieee80211_free_hw(hw); +	return NULL; +} + +int wfx_probe(struct wfx_dev *wdev) +{ +	int i; +	int err; +	struct gpio_desc *gpio_saved; + +	/* During first part of boot, gpio_wakeup cannot yet been used. So prevent bh() to touch +	 * it. +	 */ +	gpio_saved = wdev->pdata.gpio_wakeup; +	wdev->pdata.gpio_wakeup = NULL; +	wdev->poll_irq = true; + +	wfx_bh_register(wdev); + +	err = wfx_init_device(wdev); +	if (err) +		goto bh_unregister; + +	wfx_bh_poll_irq(wdev); +	err = wait_for_completion_timeout(&wdev->firmware_ready, 1 * HZ); +	if (err <= 0) { +		if (err == 0) { +			dev_err(wdev->dev, "timeout while waiting for startup indication\n"); +			err = -ETIMEDOUT; +		} else if (err == -ERESTARTSYS) { +			dev_info(wdev->dev, "probe interrupted by user\n"); +		} +		goto bh_unregister; +	} + +	/* FIXME: fill wiphy::hw_version */ +	dev_info(wdev->dev, "started firmware %d.%d.%d \"%s\" (API: %d.%d, keyset: %02X, caps: 0x%.8X)\n", +		 wdev->hw_caps.firmware_major, wdev->hw_caps.firmware_minor, +		 wdev->hw_caps.firmware_build, wdev->hw_caps.firmware_label, +		 wdev->hw_caps.api_version_major, wdev->hw_caps.api_version_minor, +		 wdev->keyset, wdev->hw_caps.link_mode); +	snprintf(wdev->hw->wiphy->fw_version, +		 sizeof(wdev->hw->wiphy->fw_version), +		 "%d.%d.%d", +		 wdev->hw_caps.firmware_major, +		 wdev->hw_caps.firmware_minor, +		 wdev->hw_caps.firmware_build); + +	if (wfx_api_older_than(wdev, 1, 0)) { +		dev_err(wdev->dev, "unsupported firmware API version (expect 1 while firmware returns %d)\n", +			wdev->hw_caps.api_version_major); +		err = -EOPNOTSUPP; +		goto bh_unregister; +	} + +	if (wdev->hw_caps.link_mode == SEC_LINK_ENFORCED) { +		dev_err(wdev->dev, "chip require secure_link, but can't negotiate it\n"); +		goto bh_unregister; +	} + +	if (wdev->hw_caps.region_sel_mode) { +		wdev->hw->wiphy->regulatory_flags |= REGULATORY_DISABLE_BEACON_HINTS; +		wdev->hw->wiphy->bands[NL80211_BAND_2GHZ]->channels[11].flags |= +			IEEE80211_CHAN_NO_IR; +		wdev->hw->wiphy->bands[NL80211_BAND_2GHZ]->channels[12].flags |= +			IEEE80211_CHAN_NO_IR; +		wdev->hw->wiphy->bands[NL80211_BAND_2GHZ]->channels[13].flags |= +			IEEE80211_CHAN_DISABLED; +	} + +	dev_dbg(wdev->dev, "sending configuration file %s\n", wdev->pdata.file_pds); +	err = wfx_send_pdata_pds(wdev); +	if (err < 0 && err != -ENOENT) +		goto bh_unregister; + +	wdev->poll_irq = false; +	err = wdev->hwbus_ops->irq_subscribe(wdev->hwbus_priv); +	if (err) +		goto bh_unregister; + +	err = wfx_hif_use_multi_tx_conf(wdev, true); +	if (err) +		dev_err(wdev->dev, "misconfigured IRQ?\n"); + +	wdev->pdata.gpio_wakeup = gpio_saved; +	if (wdev->pdata.gpio_wakeup) { +		dev_dbg(wdev->dev, "enable 'quiescent' power mode with wakeup GPIO and PDS file %s\n", +			wdev->pdata.file_pds); +		gpiod_set_value_cansleep(wdev->pdata.gpio_wakeup, 1); +		wfx_control_reg_write(wdev, 0); +		wfx_hif_set_operational_mode(wdev, HIF_OP_POWER_MODE_QUIESCENT); +	} else { +		wfx_hif_set_operational_mode(wdev, HIF_OP_POWER_MODE_DOZE); +	} + +	for (i = 0; i < ARRAY_SIZE(wdev->addresses); i++) { +		eth_zero_addr(wdev->addresses[i].addr); +		err = of_get_mac_address(wdev->dev->of_node, wdev->addresses[i].addr); +		if (!err) +			wdev->addresses[i].addr[ETH_ALEN - 1] += i; +		else +			ether_addr_copy(wdev->addresses[i].addr, wdev->hw_caps.mac_addr[i]); +		if (!is_valid_ether_addr(wdev->addresses[i].addr)) { +			dev_warn(wdev->dev, "using random MAC address\n"); +			eth_random_addr(wdev->addresses[i].addr); +		} +		dev_info(wdev->dev, "MAC address %d: %pM\n", i, wdev->addresses[i].addr); +	} +	wdev->hw->wiphy->n_addresses = ARRAY_SIZE(wdev->addresses); +	wdev->hw->wiphy->addresses = wdev->addresses; + +	if (!wfx_api_older_than(wdev, 3, 8)) +		wdev->hw->wiphy->flags |= WIPHY_FLAG_SUPPORTS_TDLS; + +	err = ieee80211_register_hw(wdev->hw); +	if (err) +		goto irq_unsubscribe; + +	err = wfx_debug_init(wdev); +	if (err) +		goto ieee80211_unregister; + +	return 0; + +ieee80211_unregister: +	ieee80211_unregister_hw(wdev->hw); +irq_unsubscribe: +	wdev->hwbus_ops->irq_unsubscribe(wdev->hwbus_priv); +bh_unregister: +	wfx_bh_unregister(wdev); +	return err; +} + +void wfx_release(struct wfx_dev *wdev) +{ +	ieee80211_unregister_hw(wdev->hw); +	wfx_hif_shutdown(wdev); +	wdev->hwbus_ops->irq_unsubscribe(wdev->hwbus_priv); +	wfx_bh_unregister(wdev); +} + +static int __init wfx_core_init(void) +{ +	int ret = 0; + +	if (IS_ENABLED(CONFIG_SPI)) +		ret = spi_register_driver(&wfx_spi_driver); +	if (IS_ENABLED(CONFIG_MMC) && !ret) +		ret = sdio_register_driver(&wfx_sdio_driver); +	return ret; +} +module_init(wfx_core_init); + +static void __exit wfx_core_exit(void) +{ +	if (IS_ENABLED(CONFIG_MMC)) +		sdio_unregister_driver(&wfx_sdio_driver); +	if (IS_ENABLED(CONFIG_SPI)) +		spi_unregister_driver(&wfx_spi_driver); +} +module_exit(wfx_core_exit);  |