Merge branch 'mr-origin-77' into HEAD

parents ef903bb1 980137a5
......@@ -9,6 +9,7 @@
#include "dt-bindings/leds/common.h"
#include "dt-bindings/pwm/pwm.h"
#include "dt-bindings/usb/pd.h"
#include "dt-bindings/rfkill/rfkill.h"
#include "imx8mq.dtsi"
#define GPIO_CONTROL
......@@ -40,7 +41,7 @@ chosen {
gpio-keys {
compatible = "gpio-keys";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_keys>, <&pinctrl_hsw>;
pinctrl-0 = <&pinctrl_keys>;
vol-down {
label = "VOL_DOWN";
......@@ -362,6 +363,30 @@ rfkill-wwan {
};
#endif
rfkill-hks {
compatible = "rfkill-hks";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_hsw>;
camera {
gpios = <&gpio4 1 GPIO_ACTIVE_LOW>;
name = "camera";
type = <RFKILL_CAMERA>;
};
wlan {
gpios = <&gpio4 10 GPIO_ACTIVE_LOW>;
name = "wlan";
type = <RFKILL_WLAN>;
};
wwan {
gpios = <&gpio4 0 GPIO_ACTIVE_LOW>;
name = "wwan";
type = <RFKILL_WWAN>;
};
};
sound {
compatible = "simple-audio-card";
pinctrl-0 = <&pinctrl_hp>;
......
......@@ -246,8 +246,9 @@ CONFIG_CFG80211_WEXT=y
CONFIG_MAC80211=m
CONFIG_MAC80211_LEDS=y
CONFIG_MAC80211_DEBUGFS=y
CONFIG_RFKILL=m
CONFIG_RFKILL=y
CONFIG_RFKILL_GPIO=m
CONFIG_RFKILL_HKS=y
CONFIG_NET_9P=y
CONFIG_NET_9P_VIRTIO=y
CONFIG_FAILOVER=y
......
/* SPDX-License-Identifier: GPL-2.0 */
/*
* This header provides constants for rfkill keys bindings.
*/
#ifndef _DT_BINDINGS_RFKILL_H
#define _DT_BINDINGS_RFKILL_H
#define RFKILL_ALL 0
#define RFKILL_WLAN 1
#define RFKILL_BLUETOOTH 2
#define RFKILL_UWB 3
#define RFKILL_WIMAX 4
#define RFKILL_WWAN 5
#define RFKILL_GPS 6
#define RFKILL_FM 7
#define RFKILL_NFC 8
#define RFKILL_CAMERA 9
#endif /* _DT_BINDINGS_RFKILL_H */
......@@ -82,6 +82,22 @@ struct rfkill * __must_check rfkill_alloc(const char *name,
const struct rfkill_ops *ops,
void *ops_data);
/**
* devm_rfkill_alloc - resource managed rfkill_alloc()
* @name: name of the struct -- the string is not copied internally
* @parent: device that has rf switch on it
* @type: type of the switch (RFKILL_TYPE_*)
* @ops: rfkill methods
* @ops_data: data passed to each method
*
* See rfkill_alloc() for more information.
*/
struct rfkill * __must_check devm_rfkill_alloc(const char *name,
struct device *parent,
const enum rfkill_type type,
const struct rfkill_ops *ops,
void *ops_data);
/**
* rfkill_register - Register a rfkill structure.
* @rfkill: rfkill structure to be registered
......
......@@ -38,6 +38,7 @@
* @RFKILL_TYPE_GPS: switch is on a GPS device.
* @RFKILL_TYPE_FM: switch is on a FM radio device.
* @RFKILL_TYPE_NFC: switch is on an NFC device.
* @RFKILL_TYPE_NFC: switch is on a camera device.
* @NUM_RFKILL_TYPES: number of defined rfkill types
*/
enum rfkill_type {
......@@ -50,6 +51,7 @@ enum rfkill_type {
RFKILL_TYPE_GPS,
RFKILL_TYPE_FM,
RFKILL_TYPE_NFC,
RFKILL_TYPE_CAMERA,
NUM_RFKILL_TYPES,
};
......
......@@ -32,3 +32,9 @@ config RFKILL_GPIO
help
If you say yes here you get support of a generic gpio RFKILL
driver.
config RFKILL_HKS
tristate "RFKILL driver for hardware kill swiches"
depends on RFKILL
depends on GPIOLIB
default n
......@@ -7,3 +7,4 @@ rfkill-y += core.o
rfkill-$(CONFIG_RFKILL_INPUT) += input.o
obj-$(CONFIG_RFKILL) += rfkill.o
obj-$(CONFIG_RFKILL_GPIO) += rfkill-gpio.o
obj-$(CONFIG_RFKILL_HKS) += rfkill-hks.o
......@@ -648,6 +648,7 @@ static const char * const rfkill_types[] = {
"gps",
"fm",
"nfc",
"camera",
};
enum rfkill_type rfkill_find_type(const char *name)
......@@ -960,6 +961,37 @@ struct rfkill * __must_check rfkill_alloc(const char *name,
}
EXPORT_SYMBOL(rfkill_alloc);
static void devm_rfkill_consume(struct device *dev, void *res)
{
struct rfkill *rfkill = *(struct rfkill **)res;
rfkill_destroy(rfkill);
}
struct rfkill * __must_check devm_rfkill_alloc(const char *name,
struct device *parent,
const enum rfkill_type type,
const struct rfkill_ops *ops,
void *ops_data)
{
struct rfkill **ptr, *rfkill;
ptr = devres_alloc(devm_rfkill_consume, sizeof(*ptr), GFP_KERNEL);
if (!ptr)
return ERR_PTR(-ENOMEM);
rfkill = rfkill_alloc(name, parent, type, ops, ops_data);
if (!IS_ERR(rfkill)) {
*ptr = rfkill;
devres_add(parent, ptr);
} else {
devres_free(ptr);
}
return rfkill;
}
EXPORT_SYMBOL_GPL(devm_rfkill_alloc);
static void rfkill_poll(struct work_struct *work)
{
struct rfkill *rfkill;
......
// SPDX-License-Identifier: GPL-2.0-only
/*
* Driver for rfkill status on GPIO lines capable of generating
* interrupts.
*
* Copyright 2020 Purism SPC
*
* Somewhat based on gpio-keys which is:
*
* Copyright 2005 Phil Blundell
* Copyright 2010, 2011 David Jander <david@protonic.nl>
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/rfkill.h>
#include <linux/platform_device.h>
#include <linux/workqueue.h>
#include <linux/gpio.h>
#include <linux/gpio/consumer.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/spinlock.h>
#include <dt-bindings/rfkill/rfkill.h>
struct rfkill_hks_switch_pdata {
int gpio;
int active_low;
int debounce_interval;
enum rfkill_type type;
const char *name;
};
struct rfkill_hks_pdata {
const struct rfkill_hks_switch_pdata *switches;
int nswitches;
const char *name;
};
struct rfkill_hks_data {
const struct rfkill_hks_switch_pdata *hks;
struct device *dev;
struct gpio_desc *gpiod;
struct rfkill *rfkill;
struct delayed_work work;
unsigned int software_debounce; /* in msecs, for GPIO-driven buttons */
unsigned int irq;
spinlock_t lock;
};
struct rfkill_hks {
const struct rfkill_hks_pdata *pdata;
struct rfkill_hks_data data[];
};
static void rfkill_hks_gpio_report_event(struct rfkill_hks_data *sdata)
{
int blocked;
blocked = gpiod_get_value_cansleep(sdata->gpiod);
if (blocked < 0) {
dev_err(sdata->dev,
"failed to get gpio state: %d\n", blocked);
return;
}
dev_dbg(sdata->dev, "HKS %s blocked: %d\n", sdata->hks->name, blocked);
rfkill_set_hw_state(sdata->rfkill, blocked);
}
static void rfkill_hks_gpio_work_func(struct work_struct *work)
{
struct rfkill_hks_data *sdata =
container_of(work, struct rfkill_hks_data, work.work);
rfkill_hks_gpio_report_event(sdata);
}
static irqreturn_t rfkill_hks_gpio_isr(int irq, void *dev_id)
{
struct rfkill_hks_data *sdata = dev_id;
BUG_ON(irq != sdata->irq);
mod_delayed_work(system_wq,
&sdata->work,
msecs_to_jiffies(sdata->software_debounce));
return IRQ_HANDLED;
}
static void rfkill_hks_quiesce_key(void *data)
{
struct rfkill_hks_data *sdata = data;
cancel_delayed_work_sync(&sdata->work);
}
static int rfkill_hks_set(void *data, bool blocked)
{
struct rfkill_hks_data *sdata = data;
/*
* Nothing to do here
*/
dev_dbg (sdata->dev, "%s: rfkill %s, blocked: %d\n", __func__, sdata->hks->name, blocked);
return 0;
}
static const struct rfkill_ops rfkill_hks_ops = {
.set_block = rfkill_hks_set,
};
static int rfkill_hks_setup_rfkill(struct platform_device *pdev,
struct rfkill_hks *ddata,
const struct rfkill_hks_switch_pdata *hks,
int idx,
struct fwnode_handle *child)
{
const char *desc = hks->name ? hks->name : "rfkill_hks";
struct device *dev = &pdev->dev;
struct rfkill_hks_data *sdata = &ddata->data[idx];
bool active_low;
int ret;
sdata->hks = hks;
spin_lock_init(&sdata->lock);
sdata->dev = dev;
sdata->gpiod = devm_fwnode_gpiod_get(dev, child,
NULL, GPIOD_IN, desc);
if (IS_ERR(sdata->gpiod)) {
ret = PTR_ERR(sdata->gpiod);
if (ret != -EPROBE_DEFER)
dev_err(dev, "failed to get gpio: %d\n",
ret);
return ret;
}
active_low = gpiod_is_active_low(sdata->gpiod);
if (hks->debounce_interval) {
ret = gpiod_set_debounce(sdata->gpiod,
hks->debounce_interval * 1000);
/* use timer if gpiolib doesn't provide debounce */
if (ret < 0)
sdata->software_debounce =
hks->debounce_interval;
}
ret = gpiod_to_irq(sdata->gpiod);
if (ret < 0) {
dev_err(dev,
"Unable to get irq number for GPIO %d, error %d\n",
hks->gpio, ret);
return ret;
}
sdata->irq = ret;
INIT_DELAYED_WORK(&sdata->work, rfkill_hks_gpio_work_func);
/*
* Install custom action to cancel release timer and
* workqueue item.
*/
ret = devm_add_action(dev, rfkill_hks_quiesce_key, sdata);
if (ret) {
dev_err(dev, "failed to register quiesce action, error: %d\n",
ret);
return ret;
}
ret = devm_request_any_context_irq(dev, sdata->irq,
rfkill_hks_gpio_isr,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
desc, sdata);
if (ret < 0) {
dev_err(dev, "Unable to claim irq %d; error %d\n",
sdata->irq, ret);
return ret;
}
sdata->rfkill = devm_rfkill_alloc(sdata->hks->name,
dev,
sdata->hks->type,
&rfkill_hks_ops,
sdata);
if (!sdata->rfkill)
return -ENOMEM;
ret = rfkill_register(sdata->rfkill);
if (ret)
return ret;
rfkill_hks_gpio_report_event (sdata);
return 0;
}
/*
* Translate properties into platform_data
*/
static struct rfkill_hks_pdata *
rfkill_hks_get_devtree_pdata(struct device *dev)
{
struct rfkill_hks_pdata *pdata;
struct rfkill_hks_switch_pdata *hks;
struct fwnode_handle *child;
int nswitches;
nswitches = device_get_child_node_count(dev);
if (nswitches == 0)
return ERR_PTR(-ENODEV);
pdata = devm_kzalloc(dev,
sizeof(*pdata) + nswitches * sizeof(*hks),
GFP_KERNEL);
if (!pdata)
return ERR_PTR(-ENOMEM);
hks = (struct rfkill_hks_switch_pdata *)(pdata + 1);
pdata->switches = hks;
pdata->nswitches = nswitches;
device_for_each_child_node(dev, child) {
int ret;
fwnode_property_read_string(child, "name", &hks->name);
ret = fwnode_property_read_u32(child, "type", &hks->type);
if (ret) {
dev_err(dev, "Missing rfkill type for %s", hks->name);
return ERR_PTR(ret);
}
if (hks->type >= NUM_RFKILL_TYPES) {
dev_err(dev, "Inalid rfkill type %d for %s", hks->type, hks->name);
return ERR_PTR(-EINVAL);
}
if (fwnode_property_read_u32(child, "debounce-interval",
&hks->debounce_interval))
hks->debounce_interval = 5;
hks++;
}
return pdata;
}
static const struct of_device_id rfkill_hks_of_match[] = {
{ .compatible = "rfkill-hks", },
{ },
};
MODULE_DEVICE_TABLE(of, rfkill_hks_of_match);
static int rfkill_hks_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
const struct rfkill_hks_pdata *pdata = dev_get_platdata(dev);
struct fwnode_handle *child = NULL;
struct rfkill_hks *ddata;
int i, ret;
if (!pdata) {
pdata = rfkill_hks_get_devtree_pdata(dev);
if (IS_ERR(pdata))
return PTR_ERR(pdata);
}
ddata = devm_kzalloc(dev, struct_size(ddata, data, pdata->nswitches),
GFP_KERNEL);
if (!ddata) {
dev_err(dev, "failed to allocate state\n");
return -ENOMEM;
}
ddata->pdata = pdata;
platform_set_drvdata(pdev, ddata);
for (i = 0; i < pdata->nswitches; i++) {
const struct rfkill_hks_switch_pdata *hks = &pdata->switches[i];
if (!dev_get_platdata(dev)) {
child = device_get_next_child_node(dev, child);
if (!child) {
dev_err(dev,
"missing child device node for entry %d\n",
i);
return -EINVAL;
}
}
ret = rfkill_hks_setup_rfkill(pdev, ddata, hks, i, child);
if (ret) {
fwnode_handle_put(child);
return ret;
}
}
fwnode_handle_put(child);
return 0;
}
static struct platform_driver rfkill_hks_device_driver = {
.probe = rfkill_hks_probe,
.driver = {
.name = "rfkill-hks",
.of_match_table = rfkill_hks_of_match,
}
};
static int __init rfkill_hks_init(void)
{
return platform_driver_register(&rfkill_hks_device_driver);
}
static void __exit rfkill_hks_exit(void)
{
platform_driver_unregister(&rfkill_hks_device_driver);
}
late_initcall(rfkill_hks_init);
module_exit(rfkill_hks_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Guido Günther <agx@sigxcpu.org>");
MODULE_DESCRIPTION("Hardware kill switch rfkill driver");
MODULE_ALIAS("platform:rfkill-hks");
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment