aboutsummaryrefslogtreecommitdiff
path: root/net/rfkill
diff options
context:
space:
mode:
Diffstat (limited to 'net/rfkill')
-rw-r--r--net/rfkill/Kconfig24
-rw-r--r--net/rfkill/Makefile2
-rw-r--r--net/rfkill/core.c144
-rw-r--r--net/rfkill/input.c12
-rw-r--r--net/rfkill/rfkill-gpio.c181
-rw-r--r--net/rfkill/rfkill-regulator.c155
6 files changed, 445 insertions, 73 deletions
diff --git a/net/rfkill/Kconfig b/net/rfkill/Kconfig
index eaf76587645..4c10e7e6c9f 100644
--- a/net/rfkill/Kconfig
+++ b/net/rfkill/Kconfig
@@ -18,7 +18,27 @@ config RFKILL_LEDS
default y
config RFKILL_INPUT
- bool "RF switch input support" if EMBEDDED
+ bool "RF switch input support" if EXPERT
depends on RFKILL
depends on INPUT = y || RFKILL = INPUT
- default y if !EMBEDDED
+ default y if !EXPERT
+
+config RFKILL_REGULATOR
+ tristate "Generic rfkill regulator driver"
+ depends on RFKILL || !RFKILL
+ depends on REGULATOR
+ help
+ This options enable controlling radio transmitters connected to
+ voltage regulator using the regulator framework.
+
+ To compile this driver as a module, choose M here: the module will
+ be called rfkill-regulator.
+
+config RFKILL_GPIO
+ tristate "GPIO RFKILL driver"
+ depends on RFKILL && GPIOLIB
+ default n
+ help
+ If you say yes here you get support of a generic gpio RFKILL
+ driver. The platform should fill in the appropriate fields in the
+ rfkill_gpio_platform_data structure and pass that to the driver.
diff --git a/net/rfkill/Makefile b/net/rfkill/Makefile
index 66210535269..311768783f4 100644
--- a/net/rfkill/Makefile
+++ b/net/rfkill/Makefile
@@ -5,3 +5,5 @@
rfkill-y += core.o
rfkill-$(CONFIG_RFKILL_INPUT) += input.o
obj-$(CONFIG_RFKILL) += rfkill.o
+obj-$(CONFIG_RFKILL_REGULATOR) += rfkill-regulator.o
+obj-$(CONFIG_RFKILL_GPIO) += rfkill-gpio.o
diff --git a/net/rfkill/core.c b/net/rfkill/core.c
index 0198191b756..b3b16c070a7 100644
--- a/net/rfkill/core.c
+++ b/net/rfkill/core.c
@@ -14,9 +14,7 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the
- * Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/kernel.h>
@@ -29,6 +27,7 @@
#include <linux/rfkill.h>
#include <linux/sched.h>
#include <linux/spinlock.h>
+#include <linux/device.h>
#include <linux/miscdevice.h>
#include <linux/wait.h>
#include <linux/poll.h>
@@ -149,6 +148,20 @@ static void rfkill_led_trigger_activate(struct led_classdev *led)
rfkill_led_trigger_event(rfkill);
}
+const char *rfkill_get_led_trigger_name(struct rfkill *rfkill)
+{
+ return rfkill->led_trigger.name;
+}
+EXPORT_SYMBOL(rfkill_get_led_trigger_name);
+
+void rfkill_set_led_trigger_name(struct rfkill *rfkill, const char *name)
+{
+ BUG_ON(!rfkill);
+
+ rfkill->ledtrigname = name;
+}
+EXPORT_SYMBOL(rfkill_set_led_trigger_name);
+
static int rfkill_led_trigger_register(struct rfkill *rfkill)
{
rfkill->led_trigger.name = rfkill->ledtrigname
@@ -235,7 +248,7 @@ static bool __rfkill_set_hw_state(struct rfkill *rfkill,
else
rfkill->state &= ~RFKILL_BLOCK_HW;
*change = prev != blocked;
- any = rfkill->state & RFKILL_BLOCK_ANY;
+ any = !!(rfkill->state & RFKILL_BLOCK_ANY);
spin_unlock_irqrestore(&rfkill->lock, flags);
rfkill_led_trigger_event(rfkill);
@@ -255,6 +268,7 @@ static bool __rfkill_set_hw_state(struct rfkill *rfkill,
static void rfkill_set_block(struct rfkill *rfkill, bool blocked)
{
unsigned long flags;
+ bool prev, curr;
int err;
if (unlikely(rfkill->dev.power.power_state.event & PM_EVENT_SLEEP))
@@ -269,6 +283,8 @@ static void rfkill_set_block(struct rfkill *rfkill, bool blocked)
rfkill->ops->query(rfkill, rfkill->data);
spin_lock_irqsave(&rfkill->lock, flags);
+ prev = rfkill->state & RFKILL_BLOCK_SW;
+
if (rfkill->state & RFKILL_BLOCK_SW)
rfkill->state |= RFKILL_BLOCK_SW_PREV;
else
@@ -298,10 +314,13 @@ static void rfkill_set_block(struct rfkill *rfkill, bool blocked)
}
rfkill->state &= ~RFKILL_BLOCK_SW_SETCALL;
rfkill->state &= ~RFKILL_BLOCK_SW_PREV;
+ curr = rfkill->state & RFKILL_BLOCK_SW;
spin_unlock_irqrestore(&rfkill->lock, flags);
rfkill_led_trigger_event(rfkill);
- rfkill_event(rfkill);
+
+ if (prev != curr)
+ rfkill_event(rfkill);
}
#ifdef CONFIG_RFKILL_INPUT
@@ -324,7 +343,7 @@ static void __rfkill_switch_all(const enum rfkill_type type, bool blocked)
rfkill_global_states[type].cur = blocked;
list_for_each_entry(rfkill, &rfkill_list, node) {
- if (rfkill->type != type)
+ if (rfkill->type != type && type != RFKILL_TYPE_ALL)
continue;
rfkill_set_block(rfkill, blocked);
@@ -555,18 +574,18 @@ void rfkill_set_states(struct rfkill *rfkill, bool sw, bool hw)
}
EXPORT_SYMBOL(rfkill_set_states);
-static ssize_t rfkill_name_show(struct device *dev,
- struct device_attribute *attr,
- char *buf)
+static ssize_t name_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
{
struct rfkill *rfkill = to_rfkill(dev);
return sprintf(buf, "%s\n", rfkill->name);
}
+static DEVICE_ATTR_RO(name);
static const char *rfkill_get_type_str(enum rfkill_type type)
{
- BUILD_BUG_ON(NUM_RFKILL_TYPES != RFKILL_TYPE_FM + 1);
+ BUILD_BUG_ON(NUM_RFKILL_TYPES != RFKILL_TYPE_NFC + 1);
switch (type) {
case RFKILL_TYPE_WLAN:
@@ -583,59 +602,59 @@ static const char *rfkill_get_type_str(enum rfkill_type type)
return "gps";
case RFKILL_TYPE_FM:
return "fm";
+ case RFKILL_TYPE_NFC:
+ return "nfc";
default:
BUG();
}
}
-static ssize_t rfkill_type_show(struct device *dev,
- struct device_attribute *attr,
- char *buf)
+static ssize_t type_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
{
struct rfkill *rfkill = to_rfkill(dev);
return sprintf(buf, "%s\n", rfkill_get_type_str(rfkill->type));
}
+static DEVICE_ATTR_RO(type);
-static ssize_t rfkill_idx_show(struct device *dev,
- struct device_attribute *attr,
- char *buf)
+static ssize_t index_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
{
struct rfkill *rfkill = to_rfkill(dev);
return sprintf(buf, "%d\n", rfkill->idx);
}
+static DEVICE_ATTR_RO(index);
-static ssize_t rfkill_persistent_show(struct device *dev,
- struct device_attribute *attr,
- char *buf)
+static ssize_t persistent_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
{
struct rfkill *rfkill = to_rfkill(dev);
return sprintf(buf, "%d\n", rfkill->persistent);
}
+static DEVICE_ATTR_RO(persistent);
-static ssize_t rfkill_hard_show(struct device *dev,
- struct device_attribute *attr,
- char *buf)
+static ssize_t hard_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
{
struct rfkill *rfkill = to_rfkill(dev);
return sprintf(buf, "%d\n", (rfkill->state & RFKILL_BLOCK_HW) ? 1 : 0 );
}
+static DEVICE_ATTR_RO(hard);
-static ssize_t rfkill_soft_show(struct device *dev,
- struct device_attribute *attr,
- char *buf)
+static ssize_t soft_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
{
struct rfkill *rfkill = to_rfkill(dev);
return sprintf(buf, "%d\n", (rfkill->state & RFKILL_BLOCK_SW) ? 1 : 0 );
}
-static ssize_t rfkill_soft_store(struct device *dev,
- struct device_attribute *attr,
- const char *buf, size_t count)
+static ssize_t soft_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
{
struct rfkill *rfkill = to_rfkill(dev);
unsigned long state;
@@ -644,7 +663,7 @@ static ssize_t rfkill_soft_store(struct device *dev,
if (!capable(CAP_NET_ADMIN))
return -EPERM;
- err = strict_strtoul(buf, 0, &state);
+ err = kstrtoul(buf, 0, &state);
if (err)
return err;
@@ -655,8 +674,9 @@ static ssize_t rfkill_soft_store(struct device *dev,
rfkill_set_block(rfkill, state);
mutex_unlock(&rfkill_global_mutex);
- return err ?: count;
+ return count;
}
+static DEVICE_ATTR_RW(soft);
static u8 user_state_from_blocked(unsigned long state)
{
@@ -668,18 +688,16 @@ static u8 user_state_from_blocked(unsigned long state)
return RFKILL_USER_STATE_UNBLOCKED;
}
-static ssize_t rfkill_state_show(struct device *dev,
- struct device_attribute *attr,
- char *buf)
+static ssize_t state_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
{
struct rfkill *rfkill = to_rfkill(dev);
return sprintf(buf, "%d\n", user_state_from_blocked(rfkill->state));
}
-static ssize_t rfkill_state_store(struct device *dev,
- struct device_attribute *attr,
- const char *buf, size_t count)
+static ssize_t state_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
{
struct rfkill *rfkill = to_rfkill(dev);
unsigned long state;
@@ -688,7 +706,7 @@ static ssize_t rfkill_state_store(struct device *dev,
if (!capable(CAP_NET_ADMIN))
return -EPERM;
- err = strict_strtoul(buf, 0, &state);
+ err = kstrtoul(buf, 0, &state);
if (err)
return err;
@@ -700,34 +718,29 @@ static ssize_t rfkill_state_store(struct device *dev,
rfkill_set_block(rfkill, state == RFKILL_USER_STATE_SOFT_BLOCKED);
mutex_unlock(&rfkill_global_mutex);
- return err ?: count;
+ return count;
}
+static DEVICE_ATTR_RW(state);
-static ssize_t rfkill_claim_show(struct device *dev,
- struct device_attribute *attr,
- char *buf)
+static ssize_t claim_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
{
return sprintf(buf, "%d\n", 0);
}
-
-static ssize_t rfkill_claim_store(struct device *dev,
- struct device_attribute *attr,
- const char *buf, size_t count)
-{
- return -EOPNOTSUPP;
-}
-
-static struct device_attribute rfkill_dev_attrs[] = {
- __ATTR(name, S_IRUGO, rfkill_name_show, NULL),
- __ATTR(type, S_IRUGO, rfkill_type_show, NULL),
- __ATTR(index, S_IRUGO, rfkill_idx_show, NULL),
- __ATTR(persistent, S_IRUGO, rfkill_persistent_show, NULL),
- __ATTR(state, S_IRUGO|S_IWUSR, rfkill_state_show, rfkill_state_store),
- __ATTR(claim, S_IRUGO|S_IWUSR, rfkill_claim_show, rfkill_claim_store),
- __ATTR(soft, S_IRUGO|S_IWUSR, rfkill_soft_show, rfkill_soft_store),
- __ATTR(hard, S_IRUGO, rfkill_hard_show, NULL),
- __ATTR_NULL
+static DEVICE_ATTR_RO(claim);
+
+static struct attribute *rfkill_dev_attrs[] = {
+ &dev_attr_name.attr,
+ &dev_attr_type.attr,
+ &dev_attr_index.attr,
+ &dev_attr_persistent.attr,
+ &dev_attr_state.attr,
+ &dev_attr_claim.attr,
+ &dev_attr_soft.attr,
+ &dev_attr_hard.attr,
+ NULL,
};
+ATTRIBUTE_GROUPS(rfkill_dev);
static void rfkill_release(struct device *dev)
{
@@ -776,7 +789,8 @@ void rfkill_resume_polling(struct rfkill *rfkill)
if (!rfkill->ops->poll)
return;
- schedule_work(&rfkill->poll_work.work);
+ queue_delayed_work(system_power_efficient_wq,
+ &rfkill->poll_work, 0);
}
EXPORT_SYMBOL(rfkill_resume_polling);
@@ -807,7 +821,7 @@ static int rfkill_resume(struct device *dev)
static struct class rfkill_class = {
.name = "rfkill",
.dev_release = rfkill_release,
- .dev_attrs = rfkill_dev_attrs,
+ .dev_groups = rfkill_dev_groups,
.dev_uevent = rfkill_dev_uevent,
.suspend = rfkill_suspend,
.resume = rfkill_resume,
@@ -881,7 +895,8 @@ static void rfkill_poll(struct work_struct *work)
*/
rfkill->ops->poll(rfkill, rfkill->data);
- schedule_delayed_work(&rfkill->poll_work,
+ queue_delayed_work(system_power_efficient_wq,
+ &rfkill->poll_work,
round_jiffies_relative(POLL_INTERVAL));
}
@@ -945,7 +960,8 @@ int __must_check rfkill_register(struct rfkill *rfkill)
INIT_WORK(&rfkill->sync_work, rfkill_sync_work);
if (rfkill->ops->poll)
- schedule_delayed_work(&rfkill->poll_work,
+ queue_delayed_work(system_power_efficient_wq,
+ &rfkill->poll_work,
round_jiffies_relative(POLL_INTERVAL));
if (!rfkill->persistent || rfkill_epo_lock_active) {
@@ -1024,7 +1040,6 @@ static int rfkill_fop_open(struct inode *inode, struct file *file)
* start getting events from elsewhere but hold mtx to get
* startup events added first
*/
- list_add(&data->list, &rfkill_fds);
list_for_each_entry(rfkill, &rfkill_list, node) {
ev = kzalloc(sizeof(*ev), GFP_KERNEL);
@@ -1033,6 +1048,7 @@ static int rfkill_fop_open(struct inode *inode, struct file *file)
rfkill_fill_event(&ev->ev, rfkill, RFKILL_OP_ADD);
list_add_tail(&ev->list, &data->events);
}
+ list_add(&data->list, &rfkill_fds);
mutex_unlock(&data->mtx);
mutex_unlock(&rfkill_global_mutex);
diff --git a/net/rfkill/input.c b/net/rfkill/input.c
index 1bca6d49ec9..b85107b5ef6 100644
--- a/net/rfkill/input.c
+++ b/net/rfkill/input.c
@@ -15,6 +15,7 @@
#include <linux/input.h>
#include <linux/slab.h>
+#include <linux/moduleparam.h>
#include <linux/workqueue.h>
#include <linux/init.h>
#include <linux/rfkill.h>
@@ -147,11 +148,9 @@ static unsigned long rfkill_ratelimit(const unsigned long last)
static void rfkill_schedule_ratelimited(void)
{
- if (delayed_work_pending(&rfkill_op_work))
- return;
- schedule_delayed_work(&rfkill_op_work,
- rfkill_ratelimit(rfkill_last_scheduled));
- rfkill_last_scheduled = jiffies;
+ if (schedule_delayed_work(&rfkill_op_work,
+ rfkill_ratelimit(rfkill_last_scheduled)))
+ rfkill_last_scheduled = jiffies;
}
static void rfkill_schedule_global_op(enum rfkill_sched_op op)
@@ -163,8 +162,7 @@ static void rfkill_schedule_global_op(enum rfkill_sched_op op)
rfkill_op_pending = true;
if (op == RFKILL_GLOBAL_OP_EPO && !rfkill_is_epo_lock_active()) {
/* bypass the limiter for EPO */
- cancel_delayed_work(&rfkill_op_work);
- schedule_delayed_work(&rfkill_op_work, 0);
+ mod_delayed_work(system_wq, &rfkill_op_work, 0);
rfkill_last_scheduled = jiffies;
} else
rfkill_schedule_ratelimited();
diff --git a/net/rfkill/rfkill-gpio.c b/net/rfkill/rfkill-gpio.c
new file mode 100644
index 00000000000..14c98e48f26
--- /dev/null
+++ b/net/rfkill/rfkill-gpio.c
@@ -0,0 +1,181 @@
+/*
+ * Copyright (c) 2011, NVIDIA Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <linux/gpio.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/rfkill.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/slab.h>
+#include <linux/acpi.h>
+#include <linux/gpio/consumer.h>
+
+#include <linux/rfkill-gpio.h>
+
+struct rfkill_gpio_data {
+ const char *name;
+ enum rfkill_type type;
+ struct gpio_desc *reset_gpio;
+ struct gpio_desc *shutdown_gpio;
+
+ struct rfkill *rfkill_dev;
+ struct clk *clk;
+
+ bool clk_enabled;
+};
+
+static int rfkill_gpio_set_power(void *data, bool blocked)
+{
+ struct rfkill_gpio_data *rfkill = data;
+
+ if (!blocked && !IS_ERR(rfkill->clk) && !rfkill->clk_enabled)
+ clk_enable(rfkill->clk);
+
+ gpiod_set_value_cansleep(rfkill->shutdown_gpio, !blocked);
+ gpiod_set_value_cansleep(rfkill->reset_gpio, !blocked);
+
+ if (blocked && !IS_ERR(rfkill->clk) && rfkill->clk_enabled)
+ clk_disable(rfkill->clk);
+
+ rfkill->clk_enabled = blocked;
+
+ return 0;
+}
+
+static const struct rfkill_ops rfkill_gpio_ops = {
+ .set_block = rfkill_gpio_set_power,
+};
+
+static int rfkill_gpio_acpi_probe(struct device *dev,
+ struct rfkill_gpio_data *rfkill)
+{
+ const struct acpi_device_id *id;
+
+ id = acpi_match_device(dev->driver->acpi_match_table, dev);
+ if (!id)
+ return -ENODEV;
+
+ rfkill->name = dev_name(dev);
+ rfkill->type = (unsigned)id->driver_data;
+
+ return 0;
+}
+
+static int rfkill_gpio_probe(struct platform_device *pdev)
+{
+ struct rfkill_gpio_platform_data *pdata = pdev->dev.platform_data;
+ struct rfkill_gpio_data *rfkill;
+ struct gpio_desc *gpio;
+ int ret;
+
+ rfkill = devm_kzalloc(&pdev->dev, sizeof(*rfkill), GFP_KERNEL);
+ if (!rfkill)
+ return -ENOMEM;
+
+ if (ACPI_HANDLE(&pdev->dev)) {
+ ret = rfkill_gpio_acpi_probe(&pdev->dev, rfkill);
+ if (ret)
+ return ret;
+ } else if (pdata) {
+ rfkill->name = pdata->name;
+ rfkill->type = pdata->type;
+ } else {
+ return -ENODEV;
+ }
+
+ rfkill->clk = devm_clk_get(&pdev->dev, NULL);
+
+ gpio = devm_gpiod_get_index(&pdev->dev, "reset", 0);
+ if (!IS_ERR(gpio)) {
+ ret = gpiod_direction_output(gpio, 0);
+ if (ret)
+ return ret;
+ rfkill->reset_gpio = gpio;
+ }
+
+ gpio = devm_gpiod_get_index(&pdev->dev, "shutdown", 1);
+ if (!IS_ERR(gpio)) {
+ ret = gpiod_direction_output(gpio, 0);
+ if (ret)
+ return ret;
+ rfkill->shutdown_gpio = gpio;
+ }
+
+ /* Make sure at-least one of the GPIO is defined and that
+ * a name is specified for this instance
+ */
+ if ((!rfkill->reset_gpio && !rfkill->shutdown_gpio) || !rfkill->name) {
+ dev_err(&pdev->dev, "invalid platform data\n");
+ return -EINVAL;
+ }
+
+ rfkill->rfkill_dev = rfkill_alloc(rfkill->name, &pdev->dev,
+ rfkill->type, &rfkill_gpio_ops,
+ rfkill);
+ if (!rfkill->rfkill_dev)
+ return -ENOMEM;
+
+ ret = rfkill_register(rfkill->rfkill_dev);
+ if (ret < 0)
+ return ret;
+
+ platform_set_drvdata(pdev, rfkill);
+
+ dev_info(&pdev->dev, "%s device registered.\n", rfkill->name);
+
+ return 0;
+}
+
+static int rfkill_gpio_remove(struct platform_device *pdev)
+{
+ struct rfkill_gpio_data *rfkill = platform_get_drvdata(pdev);
+
+ rfkill_unregister(rfkill->rfkill_dev);
+ rfkill_destroy(rfkill->rfkill_dev);
+
+ return 0;
+}
+
+#ifdef CONFIG_ACPI
+static const struct acpi_device_id rfkill_acpi_match[] = {
+ { "BCM2E1A", RFKILL_TYPE_BLUETOOTH },
+ { "BCM2E39", RFKILL_TYPE_BLUETOOTH },
+ { "BCM2E3D", RFKILL_TYPE_BLUETOOTH },
+ { "BCM4752", RFKILL_TYPE_GPS },
+ { "LNV4752", RFKILL_TYPE_GPS },
+ { },
+};
+#endif
+
+static struct platform_driver rfkill_gpio_driver = {
+ .probe = rfkill_gpio_probe,
+ .remove = rfkill_gpio_remove,
+ .driver = {
+ .name = "rfkill_gpio",
+ .owner = THIS_MODULE,
+ .acpi_match_table = ACPI_PTR(rfkill_acpi_match),
+ },
+};
+
+module_platform_driver(rfkill_gpio_driver);
+
+MODULE_DESCRIPTION("gpio rfkill");
+MODULE_AUTHOR("NVIDIA");
+MODULE_LICENSE("GPL");
diff --git a/net/rfkill/rfkill-regulator.c b/net/rfkill/rfkill-regulator.c
new file mode 100644
index 00000000000..cf5b145902e
--- /dev/null
+++ b/net/rfkill/rfkill-regulator.c
@@ -0,0 +1,155 @@
+/*
+ * rfkill-regulator.c - Regulator consumer driver for rfkill
+ *
+ * Copyright (C) 2009 Guiming Zhuo <gmzhuo@gmail.com>
+ * Copyright (C) 2011 Antonio Ospite <ospite@studenti.unina.it>
+ *
+ * Implementation inspired by leds-regulator driver.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/rfkill.h>
+#include <linux/rfkill-regulator.h>
+
+struct rfkill_regulator_data {
+ struct rfkill *rf_kill;
+ bool reg_enabled;
+
+ struct regulator *vcc;
+};
+
+static int rfkill_regulator_set_block(void *data, bool blocked)
+{
+ struct rfkill_regulator_data *rfkill_data = data;
+ int ret = 0;
+
+ pr_debug("%s: blocked: %d\n", __func__, blocked);
+
+ if (blocked) {
+ if (rfkill_data->reg_enabled) {
+ regulator_disable(rfkill_data->vcc);
+ rfkill_data->reg_enabled = false;
+ }
+ } else {
+ if (!rfkill_data->reg_enabled) {
+ ret = regulator_enable(rfkill_data->vcc);
+ if (!ret)
+ rfkill_data->reg_enabled = true;
+ }
+ }
+
+ pr_debug("%s: regulator_is_enabled after set_block: %d\n", __func__,
+ regulator_is_enabled(rfkill_data->vcc));
+
+ return ret;
+}
+
+static struct rfkill_ops rfkill_regulator_ops = {
+ .set_block = rfkill_regulator_set_block,
+};
+
+static int rfkill_regulator_probe(struct platform_device *pdev)
+{
+ struct rfkill_regulator_platform_data *pdata = pdev->dev.platform_data;
+ struct rfkill_regulator_data *rfkill_data;
+ struct regulator *vcc;
+ struct rfkill *rf_kill;
+ int ret = 0;
+
+ if (pdata == NULL) {
+ dev_err(&pdev->dev, "no platform data\n");
+ return -ENODEV;
+ }
+
+ if (pdata->name == NULL || pdata->type == 0) {
+ dev_err(&pdev->dev, "invalid name or type in platform data\n");
+ return -EINVAL;
+ }
+
+ vcc = regulator_get_exclusive(&pdev->dev, "vrfkill");
+ if (IS_ERR(vcc)) {
+ dev_err(&pdev->dev, "Cannot get vcc for %s\n", pdata->name);
+ ret = PTR_ERR(vcc);
+ goto out;
+ }
+
+ rfkill_data = kzalloc(sizeof(*rfkill_data), GFP_KERNEL);
+ if (rfkill_data == NULL) {
+ ret = -ENOMEM;
+ goto err_data_alloc;
+ }
+
+ rf_kill = rfkill_alloc(pdata->name, &pdev->dev,
+ pdata->type,
+ &rfkill_regulator_ops, rfkill_data);
+ if (rf_kill == NULL) {
+ ret = -ENOMEM;
+ goto err_rfkill_alloc;
+ }
+
+ if (regulator_is_enabled(vcc)) {
+ dev_dbg(&pdev->dev, "Regulator already enabled\n");
+ rfkill_data->reg_enabled = true;
+ }
+ rfkill_data->vcc = vcc;
+ rfkill_data->rf_kill = rf_kill;
+
+ ret = rfkill_register(rf_kill);
+ if (ret) {
+ dev_err(&pdev->dev, "Cannot register rfkill device\n");
+ goto err_rfkill_register;
+ }
+
+ platform_set_drvdata(pdev, rfkill_data);
+ dev_info(&pdev->dev, "%s initialized\n", pdata->name);
+
+ return 0;
+
+err_rfkill_register:
+ rfkill_destroy(rf_kill);
+err_rfkill_alloc:
+ kfree(rfkill_data);
+err_data_alloc:
+ regulator_put(vcc);
+out:
+ return ret;
+}
+
+static int rfkill_regulator_remove(struct platform_device *pdev)
+{
+ struct rfkill_regulator_data *rfkill_data = platform_get_drvdata(pdev);
+ struct rfkill *rf_kill = rfkill_data->rf_kill;
+
+ rfkill_unregister(rf_kill);
+ rfkill_destroy(rf_kill);
+ regulator_put(rfkill_data->vcc);
+ kfree(rfkill_data);
+
+ return 0;
+}
+
+static struct platform_driver rfkill_regulator_driver = {
+ .probe = rfkill_regulator_probe,
+ .remove = rfkill_regulator_remove,
+ .driver = {
+ .name = "rfkill-regulator",
+ .owner = THIS_MODULE,
+ },
+};
+
+module_platform_driver(rfkill_regulator_driver);
+
+MODULE_AUTHOR("Guiming Zhuo <gmzhuo@gmail.com>");
+MODULE_AUTHOR("Antonio Ospite <ospite@studenti.unina.it>");
+MODULE_DESCRIPTION("Regulator consumer driver for rfkill");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:rfkill-regulator");