12b27bdccSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
27b3115f2SLuciano Coelho /*
37b3115f2SLuciano Coelho  * This file is part of wl1271
47b3115f2SLuciano Coelho  *
57b3115f2SLuciano Coelho  * Copyright (C) 2010 Nokia Corporation
67b3115f2SLuciano Coelho  *
77b3115f2SLuciano Coelho  * Contact: Luciano Coelho <luciano.coelho@nokia.com>
87b3115f2SLuciano Coelho  */
97b3115f2SLuciano Coelho #include "testmode.h"
107b3115f2SLuciano Coelho 
11fa2648a3STony Lindgren #include <linux/pm_runtime.h>
127b3115f2SLuciano Coelho #include <linux/slab.h>
137b3115f2SLuciano Coelho #include <net/genetlink.h>
147b3115f2SLuciano Coelho 
15c31be25aSLuciano Coelho #include "wlcore.h"
167b3115f2SLuciano Coelho #include "debug.h"
177b3115f2SLuciano Coelho #include "acx.h"
187b3115f2SLuciano Coelho #include "io.h"
197b3115f2SLuciano Coelho 
207b3115f2SLuciano Coelho #define WL1271_TM_MAX_DATA_LENGTH 1024
217b3115f2SLuciano Coelho 
227b3115f2SLuciano Coelho enum wl1271_tm_commands {
237b3115f2SLuciano Coelho 	WL1271_TM_CMD_UNSPEC,
247b3115f2SLuciano Coelho 	WL1271_TM_CMD_TEST,
257b3115f2SLuciano Coelho 	WL1271_TM_CMD_INTERROGATE,
267b3115f2SLuciano Coelho 	WL1271_TM_CMD_CONFIGURE,
277b3115f2SLuciano Coelho 	WL1271_TM_CMD_NVS_PUSH,		/* Not in use. Keep to not break ABI */
287b3115f2SLuciano Coelho 	WL1271_TM_CMD_SET_PLT_MODE,
29fd92dc5dSArik Nemtsov 	WL1271_TM_CMD_RECOVER,		/* Not in use. Keep to not break ABI */
307b3115f2SLuciano Coelho 	WL1271_TM_CMD_GET_MAC,
317b3115f2SLuciano Coelho 
327b3115f2SLuciano Coelho 	__WL1271_TM_CMD_AFTER_LAST
337b3115f2SLuciano Coelho };
347b3115f2SLuciano Coelho #define WL1271_TM_CMD_MAX (__WL1271_TM_CMD_AFTER_LAST - 1)
357b3115f2SLuciano Coelho 
367b3115f2SLuciano Coelho enum wl1271_tm_attrs {
377b3115f2SLuciano Coelho 	WL1271_TM_ATTR_UNSPEC,
387b3115f2SLuciano Coelho 	WL1271_TM_ATTR_CMD_ID,
397b3115f2SLuciano Coelho 	WL1271_TM_ATTR_ANSWER,
407b3115f2SLuciano Coelho 	WL1271_TM_ATTR_DATA,
417b3115f2SLuciano Coelho 	WL1271_TM_ATTR_IE_ID,
427b3115f2SLuciano Coelho 	WL1271_TM_ATTR_PLT_MODE,
437b3115f2SLuciano Coelho 
447b3115f2SLuciano Coelho 	__WL1271_TM_ATTR_AFTER_LAST
457b3115f2SLuciano Coelho };
467b3115f2SLuciano Coelho #define WL1271_TM_ATTR_MAX (__WL1271_TM_ATTR_AFTER_LAST - 1)
477b3115f2SLuciano Coelho 
487b3115f2SLuciano Coelho static struct nla_policy wl1271_tm_policy[WL1271_TM_ATTR_MAX + 1] = {
497b3115f2SLuciano Coelho 	[WL1271_TM_ATTR_CMD_ID] =	{ .type = NLA_U32 },
507b3115f2SLuciano Coelho 	[WL1271_TM_ATTR_ANSWER] =	{ .type = NLA_U8 },
517b3115f2SLuciano Coelho 	[WL1271_TM_ATTR_DATA] =		{ .type = NLA_BINARY,
527b3115f2SLuciano Coelho 					  .len = WL1271_TM_MAX_DATA_LENGTH },
537b3115f2SLuciano Coelho 	[WL1271_TM_ATTR_IE_ID] =	{ .type = NLA_U32 },
547b3115f2SLuciano Coelho 	[WL1271_TM_ATTR_PLT_MODE] =	{ .type = NLA_U32 },
557b3115f2SLuciano Coelho };
567b3115f2SLuciano Coelho 
577b3115f2SLuciano Coelho 
wl1271_tm_cmd_test(struct wl1271 * wl,struct nlattr * tb[])587b3115f2SLuciano Coelho static int wl1271_tm_cmd_test(struct wl1271 *wl, struct nlattr *tb[])
597b3115f2SLuciano Coelho {
607b3115f2SLuciano Coelho 	int buf_len, ret, len;
617b3115f2SLuciano Coelho 	struct sk_buff *skb;
627b3115f2SLuciano Coelho 	void *buf;
637b3115f2SLuciano Coelho 	u8 answer = 0;
647b3115f2SLuciano Coelho 
657b3115f2SLuciano Coelho 	wl1271_debug(DEBUG_TESTMODE, "testmode cmd test");
667b3115f2SLuciano Coelho 
677b3115f2SLuciano Coelho 	if (!tb[WL1271_TM_ATTR_DATA])
687b3115f2SLuciano Coelho 		return -EINVAL;
697b3115f2SLuciano Coelho 
707b3115f2SLuciano Coelho 	buf = nla_data(tb[WL1271_TM_ATTR_DATA]);
717b3115f2SLuciano Coelho 	buf_len = nla_len(tb[WL1271_TM_ATTR_DATA]);
727b3115f2SLuciano Coelho 
737b3115f2SLuciano Coelho 	if (tb[WL1271_TM_ATTR_ANSWER])
747b3115f2SLuciano Coelho 		answer = nla_get_u8(tb[WL1271_TM_ATTR_ANSWER]);
757b3115f2SLuciano Coelho 
767b3115f2SLuciano Coelho 	if (buf_len > sizeof(struct wl1271_command))
777b3115f2SLuciano Coelho 		return -EMSGSIZE;
787b3115f2SLuciano Coelho 
797b3115f2SLuciano Coelho 	mutex_lock(&wl->mutex);
807b3115f2SLuciano Coelho 
814cc53383SIdo Yariv 	if (unlikely(wl->state != WLCORE_STATE_ON)) {
827b3115f2SLuciano Coelho 		ret = -EINVAL;
837b3115f2SLuciano Coelho 		goto out;
847b3115f2SLuciano Coelho 	}
857b3115f2SLuciano Coelho 
86*3447eebeSMinghao Chi 	ret = pm_runtime_resume_and_get(wl->dev);
87*3447eebeSMinghao Chi 	if (ret < 0)
887b3115f2SLuciano Coelho 		goto out;
897b3115f2SLuciano Coelho 
907b3115f2SLuciano Coelho 	ret = wl1271_cmd_test(wl, buf, buf_len, answer);
917b3115f2SLuciano Coelho 	if (ret < 0) {
927b3115f2SLuciano Coelho 		wl1271_warning("testmode cmd test failed: %d", ret);
937b3115f2SLuciano Coelho 		goto out_sleep;
947b3115f2SLuciano Coelho 	}
957b3115f2SLuciano Coelho 
967b3115f2SLuciano Coelho 	if (answer) {
97b0b09e31SYair Shapira 		/* If we got bip calibration answer print radio status */
98b0b09e31SYair Shapira 		struct wl1271_cmd_cal_p2g *params =
99b0b09e31SYair Shapira 			(struct wl1271_cmd_cal_p2g *) buf;
100b0b09e31SYair Shapira 
101b0b09e31SYair Shapira 		s16 radio_status = (s16) le16_to_cpu(params->radio_status);
102b0b09e31SYair Shapira 
103b0b09e31SYair Shapira 		if (params->test.id == TEST_CMD_P2G_CAL &&
104b0b09e31SYair Shapira 		    radio_status < 0)
105b0b09e31SYair Shapira 			wl1271_warning("testmode cmd: radio status=%d",
106b0b09e31SYair Shapira 					radio_status);
107b0b09e31SYair Shapira 		else
108b0b09e31SYair Shapira 			wl1271_info("testmode cmd: radio status=%d",
109b0b09e31SYair Shapira 					radio_status);
110b0b09e31SYair Shapira 
1117b3115f2SLuciano Coelho 		len = nla_total_size(buf_len);
1127b3115f2SLuciano Coelho 		skb = cfg80211_testmode_alloc_reply_skb(wl->hw->wiphy, len);
1137b3115f2SLuciano Coelho 		if (!skb) {
1147b3115f2SLuciano Coelho 			ret = -ENOMEM;
1157b3115f2SLuciano Coelho 			goto out_sleep;
1167b3115f2SLuciano Coelho 		}
1177b3115f2SLuciano Coelho 
118bf722d1dSYair Shapira 		if (nla_put(skb, WL1271_TM_ATTR_DATA, buf_len, buf)) {
119bf722d1dSYair Shapira 			kfree_skb(skb);
120bf722d1dSYair Shapira 			ret = -EMSGSIZE;
121bf722d1dSYair Shapira 			goto out_sleep;
122bf722d1dSYair Shapira 		}
123bf722d1dSYair Shapira 
1247b3115f2SLuciano Coelho 		ret = cfg80211_testmode_reply(skb);
1257b3115f2SLuciano Coelho 		if (ret < 0)
1267b3115f2SLuciano Coelho 			goto out_sleep;
1277b3115f2SLuciano Coelho 	}
1287b3115f2SLuciano Coelho 
1297b3115f2SLuciano Coelho out_sleep:
1309b71578dSTony Lindgren 	pm_runtime_mark_last_busy(wl->dev);
1319b71578dSTony Lindgren 	pm_runtime_put_autosuspend(wl->dev);
1327b3115f2SLuciano Coelho out:
1337b3115f2SLuciano Coelho 	mutex_unlock(&wl->mutex);
1347b3115f2SLuciano Coelho 
1357b3115f2SLuciano Coelho 	return ret;
1367b3115f2SLuciano Coelho }
1377b3115f2SLuciano Coelho 
wl1271_tm_cmd_interrogate(struct wl1271 * wl,struct nlattr * tb[])1387b3115f2SLuciano Coelho static int wl1271_tm_cmd_interrogate(struct wl1271 *wl, struct nlattr *tb[])
1397b3115f2SLuciano Coelho {
1407b3115f2SLuciano Coelho 	int ret;
1417b3115f2SLuciano Coelho 	struct wl1271_command *cmd;
1427b3115f2SLuciano Coelho 	struct sk_buff *skb;
1437b3115f2SLuciano Coelho 	u8 ie_id;
1447b3115f2SLuciano Coelho 
1457b3115f2SLuciano Coelho 	wl1271_debug(DEBUG_TESTMODE, "testmode cmd interrogate");
1467b3115f2SLuciano Coelho 
1477b3115f2SLuciano Coelho 	if (!tb[WL1271_TM_ATTR_IE_ID])
1487b3115f2SLuciano Coelho 		return -EINVAL;
1497b3115f2SLuciano Coelho 
1507b3115f2SLuciano Coelho 	ie_id = nla_get_u8(tb[WL1271_TM_ATTR_IE_ID]);
1517b3115f2SLuciano Coelho 
1527b3115f2SLuciano Coelho 	mutex_lock(&wl->mutex);
1537b3115f2SLuciano Coelho 
1544cc53383SIdo Yariv 	if (unlikely(wl->state != WLCORE_STATE_ON)) {
1557b3115f2SLuciano Coelho 		ret = -EINVAL;
1567b3115f2SLuciano Coelho 		goto out;
1577b3115f2SLuciano Coelho 	}
1587b3115f2SLuciano Coelho 
159*3447eebeSMinghao Chi 	ret = pm_runtime_resume_and_get(wl->dev);
160*3447eebeSMinghao Chi 	if (ret < 0)
1617b3115f2SLuciano Coelho 		goto out;
1627b3115f2SLuciano Coelho 
1637b3115f2SLuciano Coelho 	cmd = kzalloc(sizeof(*cmd), GFP_KERNEL);
1647b3115f2SLuciano Coelho 	if (!cmd) {
1657b3115f2SLuciano Coelho 		ret = -ENOMEM;
1667b3115f2SLuciano Coelho 		goto out_sleep;
1677b3115f2SLuciano Coelho 	}
1687b3115f2SLuciano Coelho 
1694b674144SIgal Chernobelsky 	ret = wl1271_cmd_interrogate(wl, ie_id, cmd,
1704b674144SIgal Chernobelsky 				     sizeof(struct acx_header), sizeof(*cmd));
1717b3115f2SLuciano Coelho 	if (ret < 0) {
1727b3115f2SLuciano Coelho 		wl1271_warning("testmode cmd interrogate failed: %d", ret);
1737b3115f2SLuciano Coelho 		goto out_free;
1747b3115f2SLuciano Coelho 	}
1757b3115f2SLuciano Coelho 
1767b3115f2SLuciano Coelho 	skb = cfg80211_testmode_alloc_reply_skb(wl->hw->wiphy, sizeof(*cmd));
1777b3115f2SLuciano Coelho 	if (!skb) {
1787b3115f2SLuciano Coelho 		ret = -ENOMEM;
1797b3115f2SLuciano Coelho 		goto out_free;
1807b3115f2SLuciano Coelho 	}
1817b3115f2SLuciano Coelho 
182bf722d1dSYair Shapira 	if (nla_put(skb, WL1271_TM_ATTR_DATA, sizeof(*cmd), cmd)) {
183bf722d1dSYair Shapira 		kfree_skb(skb);
184bf722d1dSYair Shapira 		ret = -EMSGSIZE;
185bf722d1dSYair Shapira 		goto out_free;
186bf722d1dSYair Shapira 	}
187bf722d1dSYair Shapira 
1887b3115f2SLuciano Coelho 	ret = cfg80211_testmode_reply(skb);
1897b3115f2SLuciano Coelho 	if (ret < 0)
1907b3115f2SLuciano Coelho 		goto out_free;
1917b3115f2SLuciano Coelho 
1927b3115f2SLuciano Coelho out_free:
1937b3115f2SLuciano Coelho 	kfree(cmd);
1947b3115f2SLuciano Coelho out_sleep:
1959b71578dSTony Lindgren 	pm_runtime_mark_last_busy(wl->dev);
1969b71578dSTony Lindgren 	pm_runtime_put_autosuspend(wl->dev);
1977b3115f2SLuciano Coelho out:
1987b3115f2SLuciano Coelho 	mutex_unlock(&wl->mutex);
1997b3115f2SLuciano Coelho 
2007b3115f2SLuciano Coelho 	return ret;
2017b3115f2SLuciano Coelho }
2027b3115f2SLuciano Coelho 
wl1271_tm_cmd_configure(struct wl1271 * wl,struct nlattr * tb[])2037b3115f2SLuciano Coelho static int wl1271_tm_cmd_configure(struct wl1271 *wl, struct nlattr *tb[])
2047b3115f2SLuciano Coelho {
2057b3115f2SLuciano Coelho 	int buf_len, ret;
2067b3115f2SLuciano Coelho 	void *buf;
2077b3115f2SLuciano Coelho 	u8 ie_id;
2087b3115f2SLuciano Coelho 
2097b3115f2SLuciano Coelho 	wl1271_debug(DEBUG_TESTMODE, "testmode cmd configure");
2107b3115f2SLuciano Coelho 
2117b3115f2SLuciano Coelho 	if (!tb[WL1271_TM_ATTR_DATA])
2127b3115f2SLuciano Coelho 		return -EINVAL;
2137b3115f2SLuciano Coelho 	if (!tb[WL1271_TM_ATTR_IE_ID])
2147b3115f2SLuciano Coelho 		return -EINVAL;
2157b3115f2SLuciano Coelho 
2167b3115f2SLuciano Coelho 	ie_id = nla_get_u8(tb[WL1271_TM_ATTR_IE_ID]);
2177b3115f2SLuciano Coelho 	buf = nla_data(tb[WL1271_TM_ATTR_DATA]);
2187b3115f2SLuciano Coelho 	buf_len = nla_len(tb[WL1271_TM_ATTR_DATA]);
2197b3115f2SLuciano Coelho 
2207b3115f2SLuciano Coelho 	if (buf_len > sizeof(struct wl1271_command))
2217b3115f2SLuciano Coelho 		return -EMSGSIZE;
2227b3115f2SLuciano Coelho 
2237b3115f2SLuciano Coelho 	mutex_lock(&wl->mutex);
2247b3115f2SLuciano Coelho 	ret = wl1271_cmd_configure(wl, ie_id, buf, buf_len);
2257b3115f2SLuciano Coelho 	mutex_unlock(&wl->mutex);
2267b3115f2SLuciano Coelho 
2277b3115f2SLuciano Coelho 	if (ret < 0) {
2287b3115f2SLuciano Coelho 		wl1271_warning("testmode cmd configure failed: %d", ret);
2297b3115f2SLuciano Coelho 		return ret;
2307b3115f2SLuciano Coelho 	}
2317b3115f2SLuciano Coelho 
2327b3115f2SLuciano Coelho 	return 0;
2337b3115f2SLuciano Coelho }
2347b3115f2SLuciano Coelho 
wl1271_tm_detect_fem(struct wl1271 * wl,struct nlattr * tb[])235ff324317SYair Shapira static int wl1271_tm_detect_fem(struct wl1271 *wl, struct nlattr *tb[])
236ff324317SYair Shapira {
237ff324317SYair Shapira 	/* return FEM type */
238ff324317SYair Shapira 	int ret, len;
239ff324317SYair Shapira 	struct sk_buff *skb;
240ff324317SYair Shapira 
241ff324317SYair Shapira 	ret = wl1271_plt_start(wl, PLT_FEM_DETECT);
242ff324317SYair Shapira 	if (ret < 0)
243ff324317SYair Shapira 		goto out;
244ff324317SYair Shapira 
245ff324317SYair Shapira 	mutex_lock(&wl->mutex);
246ff324317SYair Shapira 
247ff324317SYair Shapira 	len = nla_total_size(sizeof(wl->fem_manuf));
248ff324317SYair Shapira 	skb = cfg80211_testmode_alloc_reply_skb(wl->hw->wiphy, len);
249ff324317SYair Shapira 	if (!skb) {
250ff324317SYair Shapira 		ret = -ENOMEM;
251ff324317SYair Shapira 		goto out_mutex;
252ff324317SYair Shapira 	}
253ff324317SYair Shapira 
254ff324317SYair Shapira 	if (nla_put(skb, WL1271_TM_ATTR_DATA, sizeof(wl->fem_manuf),
255ff324317SYair Shapira 					      &wl->fem_manuf)) {
256ff324317SYair Shapira 		kfree_skb(skb);
257ff324317SYair Shapira 		ret = -EMSGSIZE;
258ff324317SYair Shapira 		goto out_mutex;
259ff324317SYair Shapira 	}
260ff324317SYair Shapira 
261ff324317SYair Shapira 	ret = cfg80211_testmode_reply(skb);
262ff324317SYair Shapira 
263ff324317SYair Shapira out_mutex:
264ff324317SYair Shapira 	mutex_unlock(&wl->mutex);
265ff324317SYair Shapira 
266ff324317SYair Shapira 	/* We always stop plt after DETECT mode */
267ff324317SYair Shapira 	wl1271_plt_stop(wl);
268ff324317SYair Shapira out:
269ff324317SYair Shapira 	return ret;
270ff324317SYair Shapira }
271ff324317SYair Shapira 
wl1271_tm_cmd_set_plt_mode(struct wl1271 * wl,struct nlattr * tb[])2727b3115f2SLuciano Coelho static int wl1271_tm_cmd_set_plt_mode(struct wl1271 *wl, struct nlattr *tb[])
2737b3115f2SLuciano Coelho {
2747b3115f2SLuciano Coelho 	u32 val;
2757b3115f2SLuciano Coelho 	int ret;
2767b3115f2SLuciano Coelho 
2777b3115f2SLuciano Coelho 	wl1271_debug(DEBUG_TESTMODE, "testmode cmd set plt mode");
2787b3115f2SLuciano Coelho 
2797b3115f2SLuciano Coelho 	if (!tb[WL1271_TM_ATTR_PLT_MODE])
2807b3115f2SLuciano Coelho 		return -EINVAL;
2817b3115f2SLuciano Coelho 
2827b3115f2SLuciano Coelho 	val = nla_get_u32(tb[WL1271_TM_ATTR_PLT_MODE]);
2837b3115f2SLuciano Coelho 
2847b3115f2SLuciano Coelho 	switch (val) {
2857019c80eSYair Shapira 	case PLT_OFF:
2867b3115f2SLuciano Coelho 		ret = wl1271_plt_stop(wl);
2877b3115f2SLuciano Coelho 		break;
2887019c80eSYair Shapira 	case PLT_ON:
289dd491ffbSYair Shapira 	case PLT_CHIP_AWAKE:
290dd491ffbSYair Shapira 		ret = wl1271_plt_start(wl, val);
291ff324317SYair Shapira 		break;
2927019c80eSYair Shapira 	case PLT_FEM_DETECT:
293ff324317SYair Shapira 		ret = wl1271_tm_detect_fem(wl, tb);
2947b3115f2SLuciano Coelho 		break;
2957b3115f2SLuciano Coelho 	default:
2967b3115f2SLuciano Coelho 		ret = -EINVAL;
2977b3115f2SLuciano Coelho 		break;
2987b3115f2SLuciano Coelho 	}
2997b3115f2SLuciano Coelho 
3007b3115f2SLuciano Coelho 	return ret;
3017b3115f2SLuciano Coelho }
3027b3115f2SLuciano Coelho 
wl12xx_tm_cmd_get_mac(struct wl1271 * wl,struct nlattr * tb[])3037b3115f2SLuciano Coelho static int wl12xx_tm_cmd_get_mac(struct wl1271 *wl, struct nlattr *tb[])
3047b3115f2SLuciano Coelho {
3057b3115f2SLuciano Coelho 	struct sk_buff *skb;
3067b3115f2SLuciano Coelho 	u8 mac_addr[ETH_ALEN];
3077b3115f2SLuciano Coelho 	int ret = 0;
3087b3115f2SLuciano Coelho 
3097b3115f2SLuciano Coelho 	mutex_lock(&wl->mutex);
3107b3115f2SLuciano Coelho 
3117b3115f2SLuciano Coelho 	if (!wl->plt) {
3127b3115f2SLuciano Coelho 		ret = -EINVAL;
3137b3115f2SLuciano Coelho 		goto out;
3147b3115f2SLuciano Coelho 	}
3157b3115f2SLuciano Coelho 
3167b3115f2SLuciano Coelho 	if (wl->fuse_oui_addr == 0 && wl->fuse_nic_addr == 0) {
3177b3115f2SLuciano Coelho 		ret = -EOPNOTSUPP;
3187b3115f2SLuciano Coelho 		goto out;
3197b3115f2SLuciano Coelho 	}
3207b3115f2SLuciano Coelho 
3217b3115f2SLuciano Coelho 	mac_addr[0] = (u8)(wl->fuse_oui_addr >> 16);
3227b3115f2SLuciano Coelho 	mac_addr[1] = (u8)(wl->fuse_oui_addr >> 8);
3237b3115f2SLuciano Coelho 	mac_addr[2] = (u8) wl->fuse_oui_addr;
3247b3115f2SLuciano Coelho 	mac_addr[3] = (u8)(wl->fuse_nic_addr >> 16);
3257b3115f2SLuciano Coelho 	mac_addr[4] = (u8)(wl->fuse_nic_addr >> 8);
3267b3115f2SLuciano Coelho 	mac_addr[5] = (u8) wl->fuse_nic_addr;
3277b3115f2SLuciano Coelho 
3287b3115f2SLuciano Coelho 	skb = cfg80211_testmode_alloc_reply_skb(wl->hw->wiphy, ETH_ALEN);
3297b3115f2SLuciano Coelho 	if (!skb) {
3307b3115f2SLuciano Coelho 		ret = -ENOMEM;
3317b3115f2SLuciano Coelho 		goto out;
3327b3115f2SLuciano Coelho 	}
3337b3115f2SLuciano Coelho 
334bf722d1dSYair Shapira 	if (nla_put(skb, WL1271_TM_ATTR_DATA, ETH_ALEN, mac_addr)) {
335bf722d1dSYair Shapira 		kfree_skb(skb);
336bf722d1dSYair Shapira 		ret = -EMSGSIZE;
337bf722d1dSYair Shapira 		goto out;
338bf722d1dSYair Shapira 	}
339bf722d1dSYair Shapira 
3407b3115f2SLuciano Coelho 	ret = cfg80211_testmode_reply(skb);
3417b3115f2SLuciano Coelho 	if (ret < 0)
3427b3115f2SLuciano Coelho 		goto out;
3437b3115f2SLuciano Coelho 
3447b3115f2SLuciano Coelho out:
3457b3115f2SLuciano Coelho 	mutex_unlock(&wl->mutex);
3467b3115f2SLuciano Coelho 	return ret;
3477b3115f2SLuciano Coelho }
3487b3115f2SLuciano Coelho 
wl1271_tm_cmd(struct ieee80211_hw * hw,struct ieee80211_vif * vif,void * data,int len)34952981cd7SDavid Spinadel int wl1271_tm_cmd(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
35052981cd7SDavid Spinadel 		  void *data, int len)
3517b3115f2SLuciano Coelho {
3527b3115f2SLuciano Coelho 	struct wl1271 *wl = hw->priv;
3537b3115f2SLuciano Coelho 	struct nlattr *tb[WL1271_TM_ATTR_MAX + 1];
354dd491ffbSYair Shapira 	u32 nla_cmd;
3557b3115f2SLuciano Coelho 	int err;
3567b3115f2SLuciano Coelho 
3578cb08174SJohannes Berg 	err = nla_parse_deprecated(tb, WL1271_TM_ATTR_MAX, data, len,
3588cb08174SJohannes Berg 				   wl1271_tm_policy, NULL);
3597b3115f2SLuciano Coelho 	if (err)
3607b3115f2SLuciano Coelho 		return err;
3617b3115f2SLuciano Coelho 
3627b3115f2SLuciano Coelho 	if (!tb[WL1271_TM_ATTR_CMD_ID])
3637b3115f2SLuciano Coelho 		return -EINVAL;
3647b3115f2SLuciano Coelho 
365dd491ffbSYair Shapira 	nla_cmd = nla_get_u32(tb[WL1271_TM_ATTR_CMD_ID]);
366dd491ffbSYair Shapira 
367dd491ffbSYair Shapira 	/* Only SET_PLT_MODE is allowed in case of mode PLT_CHIP_AWAKE */
368dd491ffbSYair Shapira 	if (wl->plt_mode == PLT_CHIP_AWAKE &&
369dd491ffbSYair Shapira 	    nla_cmd != WL1271_TM_CMD_SET_PLT_MODE)
370dd491ffbSYair Shapira 		return -EOPNOTSUPP;
371dd491ffbSYair Shapira 
372dd491ffbSYair Shapira 	switch (nla_cmd) {
3737b3115f2SLuciano Coelho 	case WL1271_TM_CMD_TEST:
3747b3115f2SLuciano Coelho 		return wl1271_tm_cmd_test(wl, tb);
3757b3115f2SLuciano Coelho 	case WL1271_TM_CMD_INTERROGATE:
3767b3115f2SLuciano Coelho 		return wl1271_tm_cmd_interrogate(wl, tb);
3777b3115f2SLuciano Coelho 	case WL1271_TM_CMD_CONFIGURE:
3787b3115f2SLuciano Coelho 		return wl1271_tm_cmd_configure(wl, tb);
3797b3115f2SLuciano Coelho 	case WL1271_TM_CMD_SET_PLT_MODE:
3807b3115f2SLuciano Coelho 		return wl1271_tm_cmd_set_plt_mode(wl, tb);
3817b3115f2SLuciano Coelho 	case WL1271_TM_CMD_GET_MAC:
3827b3115f2SLuciano Coelho 		return wl12xx_tm_cmd_get_mac(wl, tb);
3837b3115f2SLuciano Coelho 	default:
3847b3115f2SLuciano Coelho 		return -EOPNOTSUPP;
3857b3115f2SLuciano Coelho 	}
3867b3115f2SLuciano Coelho }
387