1// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
2/*
3 * Copyright (C) 2012-2014, 2018-2019 Intel Corporation
4 * Copyright (C) 2017 Intel Deutschland GmbH
5 */
6#include <linux/leds.h>
7#include "iwl-io.h"
8#include "iwl-csr.h"
9#include "mvm.h"
10
11static void iwl_mvm_send_led_fw_cmd(struct iwl_mvm *mvm, bool on)
12{
13	struct iwl_led_cmd led_cmd = {
14		.status = cpu_to_le32(on),
15	};
16	struct iwl_host_cmd cmd = {
17		.id = WIDE_ID(LONG_GROUP, LEDS_CMD),
18		.len = { sizeof(led_cmd), },
19		.data = { &led_cmd, },
20		.flags = CMD_ASYNC,
21	};
22	int err;
23
24	if (!iwl_mvm_firmware_running(mvm))
25		return;
26
27	err = iwl_mvm_send_cmd(mvm, &cmd);
28
29	if (err)
30		IWL_WARN(mvm, "LED command failed: %d\n", err);
31}
32
33static void iwl_mvm_led_set(struct iwl_mvm *mvm, bool on)
34{
35	if (fw_has_capa(&mvm->fw->ucode_capa,
36			IWL_UCODE_TLV_CAPA_LED_CMD_SUPPORT)) {
37		iwl_mvm_send_led_fw_cmd(mvm, on);
38		return;
39	}
40
41	iwl_write32(mvm->trans, CSR_LED_REG,
42		    on ? CSR_LED_REG_TURN_ON : CSR_LED_REG_TURN_OFF);
43}
44
45static void iwl_led_brightness_set(struct led_classdev *led_cdev,
46				   enum led_brightness brightness)
47{
48	struct iwl_mvm *mvm = container_of(led_cdev, struct iwl_mvm, led);
49
50	iwl_mvm_led_set(mvm, brightness > 0);
51}
52
53int iwl_mvm_leds_init(struct iwl_mvm *mvm)
54{
55	int mode = iwlwifi_mod_params.led_mode;
56	int ret;
57
58	switch (mode) {
59	case IWL_LED_BLINK:
60		IWL_ERR(mvm, "Blink led mode not supported, used default\n");
61		fallthrough;
62	case IWL_LED_DEFAULT:
63	case IWL_LED_RF_STATE:
64		mode = IWL_LED_RF_STATE;
65		break;
66	case IWL_LED_DISABLE:
67		IWL_INFO(mvm, "Led disabled\n");
68		return 0;
69	default:
70		return -EINVAL;
71	}
72
73	mvm->led.name = kasprintf(GFP_KERNEL, "%s-led",
74				   wiphy_name(mvm->hw->wiphy));
75	if (!mvm->led.name)
76		return -ENOMEM;
77
78	mvm->led.brightness_set = iwl_led_brightness_set;
79	mvm->led.max_brightness = 1;
80
81	if (mode == IWL_LED_RF_STATE)
82		mvm->led.default_trigger =
83			ieee80211_get_radio_led_name(mvm->hw);
84
85	ret = led_classdev_register(mvm->trans->dev, &mvm->led);
86	if (ret) {
87		kfree(mvm->led.name);
88		IWL_INFO(mvm, "Failed to enable led\n");
89		return ret;
90	}
91
92	mvm->init_status |= IWL_MVM_INIT_STATUS_LEDS_INIT_COMPLETE;
93	return 0;
94}
95
96void iwl_mvm_leds_sync(struct iwl_mvm *mvm)
97{
98	if (!(mvm->init_status & IWL_MVM_INIT_STATUS_LEDS_INIT_COMPLETE))
99		return;
100
101	/*
102	 * if we control through the register, we're doing it
103	 * even when the firmware isn't up, so no need to sync
104	 */
105	if (mvm->trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_8000)
106		return;
107
108	iwl_mvm_led_set(mvm, mvm->led.brightness > 0);
109}
110
111void iwl_mvm_leds_exit(struct iwl_mvm *mvm)
112{
113	if (!(mvm->init_status & IWL_MVM_INIT_STATUS_LEDS_INIT_COMPLETE))
114		return;
115
116	led_classdev_unregister(&mvm->led);
117	kfree(mvm->led.name);
118	mvm->init_status &= ~IWL_MVM_INIT_STATUS_LEDS_INIT_COMPLETE;
119}
120