Skip to content
Permalink
Browse files

Add support for generic Coreboot rfkill implementations

Add a simple rfkill driver for generic rfkill implementations provided by
Coreboot-based platforms. The goal is to avoid needing to reimplement
vendor-specific interfaces.

Signed-off-by: Matthew Garrett <mjg59@google.com>
  • Loading branch information
mjg59 committed Nov 18, 2019
1 parent af42d34 commit c30407e040c069432a286e499fc979711f947dc5
Showing with 239 additions and 0 deletions.
  1. +11 −0 drivers/platform/x86/Kconfig
  2. +1 −0 drivers/platform/x86/Makefile
  3. +227 −0 drivers/platform/x86/coreboot-rfkill.c
@@ -1335,6 +1335,17 @@ config PCENGINES_APU2
To compile this driver as a module, choose M here: the module
will be called pcengines-apuv2.

config COREBOOT_RFKILL
tristate "RFKill support for Coreboot based platforms"
depends on ACPI && RFKILL
help
This driver provides support for the generic Coreboot rfkill
interface. On systems that provide this interface, this driver
will allow the system radios to be blocked and unblocked.

To compile this driver as a module, choose M here: the module
will be called coreboot-rfkill.

source "drivers/platform/x86/intel_speed_select_if/Kconfig"

endif # X86_PLATFORM_DEVICES
@@ -100,3 +100,4 @@ obj-$(CONFIG_I2C_MULTI_INSTANTIATE) += i2c-multi-instantiate.o
obj-$(CONFIG_INTEL_ATOMISP2_PM) += intel_atomisp2_pm.o
obj-$(CONFIG_PCENGINES_APU2) += pcengines-apuv2.o
obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += intel_speed_select_if/
obj-$(CONFIG_COREBOOT_RFKILL) += coreboot-rfkill.o
@@ -0,0 +1,227 @@
// SPDX-License-Identifier: GPL-2.0
//
// Copyright 2019 Google LLC.

#include <linux/module.h>
#include <linux/acpi.h>
#include <linux/rfkill.h>

enum coreboot_rfkill_radio_type {
COREBOOT_WLAN = 0,
COREBOOT_BLUETOOTH,
COREBOOT_UWB,
COREBOOT_WIMAX,
COREBOOT_WWAN,
COREBOOT_GPS,
COREBOOT_FM,
COREBOOT_NFC,
COREBOOT_MAX,
};

static const char *const coreboot_rfkill_radio_names[] = {
"wlan",
"bluetooth",
"uwb",
"wimax",
"wwan",
"gps",
"fm",
"nfc"
};

static const enum rfkill_type coreboot_rfkill_mapping[] = {
RFKILL_TYPE_WLAN,
RFKILL_TYPE_BLUETOOTH,
RFKILL_TYPE_UWB,
RFKILL_TYPE_WIMAX,
RFKILL_TYPE_WWAN,
RFKILL_TYPE_GPS,
RFKILL_TYPE_FM,
RFKILL_TYPE_NFC,
};

struct coreboot_rfkill_radio {
struct acpi_device *device;
struct rfkill *rfkill;
enum coreboot_rfkill_radio_type type;
bool registered;
};

struct coreboot_rfkill_data {
struct coreboot_rfkill_radio *radios[COREBOOT_MAX];
};


static struct coreboot_rfkill_radio *alloc_radio(struct acpi_device *device,
enum coreboot_rfkill_radio_type type)
{
struct coreboot_rfkill_radio *radio;

radio = kmalloc(sizeof(struct coreboot_rfkill_radio), GFP_KERNEL);
if (!radio)
return NULL;
radio->device = device;
radio->type = type;
return radio;
}

static void coreboot_rfkill_query(struct rfkill *rfkill, void *data)
{
struct coreboot_rfkill_radio *radio;
unsigned long long soft, hard;
acpi_status status;
bool soft_state, hard_state;

radio = (struct coreboot_rfkill_radio *)data;
status = acpi_evaluate_integer(radio->device->handle, "SSTA", NULL,
&soft);
if (ACPI_FAILURE(status))
return;

status = acpi_evaluate_integer(radio->device->handle, "HSTA", NULL,
&hard);
if (ACPI_FAILURE(status))
return;

soft_state = !(soft & (1 << radio->type));
hard_state = !(hard & (1 << radio->type));

rfkill_set_states(rfkill, soft_state, hard_state);
}

static int coreboot_rfkill_set_block(void *data, bool blocked)
{
struct coreboot_rfkill_radio *radio;
struct acpi_object_list input;
unsigned long long output;
union acpi_object param;
acpi_status status;

radio = (struct coreboot_rfkill_radio *)data;
status = acpi_evaluate_integer(radio->device->handle, "SSTA", NULL,
&output);
if (ACPI_FAILURE(status))
return -EINVAL;

output &= ~(1 << radio->type);
output |= ((!blocked) << radio->type);

param.type = ACPI_TYPE_INTEGER;
param.integer.value = output;
input.count = 1;
input.pointer = &param;

status = acpi_evaluate_object(radio->device->handle, "CNTL", &input,
NULL);
if (ACPI_FAILURE(status))
return -EINVAL;

return 0;
}

static const struct rfkill_ops coreboot_rfkill_ops = {
.query = coreboot_rfkill_query,
.set_block = coreboot_rfkill_set_block,
};

static int coreboot_rfkill_remove(struct acpi_device *device)
{
struct coreboot_rfkill_data *data;
int i;

data = dev_get_drvdata(&device->dev);

for (i = 0; i < COREBOOT_MAX; i++) {
if (data->radios[i] == NULL)
continue;

if (data->radios[i]->registered)
rfkill_unregister(data->radios[i]->rfkill);
rfkill_destroy(data->radios[i]->rfkill);
kfree(data->radios[i]);
}

kfree(data);

return 0;
}

static int coreboot_rfkill_add(struct acpi_device *device)
{
struct coreboot_rfkill_radio *radio;
struct coreboot_rfkill_data *data;
unsigned long long output;
struct rfkill *rfkill;
acpi_status status;
int i, ret;

data = kzalloc(sizeof(struct coreboot_rfkill_data), GFP_KERNEL);
if (!data)
return -ENOMEM;

dev_set_drvdata(&device->dev, data);

/* Find out what devices we have */
status = acpi_evaluate_integer(device->handle, "DEVS", NULL, &output);
if (ACPI_FAILURE(status))
return -ENODEV;

for (i = 0; i < COREBOOT_MAX; i++) {
if (output & (1 << i)) {
char *name;

radio = alloc_radio(device, i);
if (!radio) {
coreboot_rfkill_remove(device);
return -ENOMEM;
}

name = kasprintf(GFP_KERNEL, "coreboot-rfkill-%s",
coreboot_rfkill_radio_names[i]);
if (!name) {
coreboot_rfkill_remove(device);
return -ENOMEM;
}
rfkill = rfkill_alloc(name, &device->dev,
coreboot_rfkill_mapping[i],
&coreboot_rfkill_ops, radio);
if (!rfkill) {
kfree(name);
coreboot_rfkill_remove(device);
return -ENOMEM;
}

data->radios[i] = radio;
radio->rfkill = rfkill;

ret = rfkill_register(radio->rfkill);
if (ret) {
kfree(name);
coreboot_rfkill_remove(device);
return ret;
}
radio->registered = true;
kfree(name);
}
}

return 0;
}

static const struct acpi_device_id coreboot_rfkill_ids[] = {
{ "COR0001", 0},
{ "", 0 },
};

static struct acpi_driver coreboot_rfkill_driver = {
.name = "coreboot-rfkill",
.ids = coreboot_rfkill_ids,
.ops = {
.add = coreboot_rfkill_add,
.remove = coreboot_rfkill_remove,
},
.owner = THIS_MODULE,
};

module_acpi_driver(coreboot_rfkill_driver);
MODULE_LICENSE("GPL");

0 comments on commit c30407e

Please sign in to comment.
You can’t perform that action at this time.