pi-linux/bsp/drivers/misc/sunxi-rf/sunxi-rfkill.c

283 lines
7.3 KiB
C

/* SPDX-License-Identifier: GPL-2.0-or-later */
/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */
/*
* Copyright(c) 2017-2018 Allwinnertech Co., Ltd.
*
* 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.
*/
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/gpio.h>
#include <linux/err.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/capability.h>
#include "internal.h"
#include "sunxi-rfkill.h"
#include <linux/pinctrl/consumer.h>
#define MODULE_CUR_VERSION "v1.1.12"
static struct sunxi_rfkill_platdata *rfkill_data;
struct miscdevice sunxi_rfkill_miscdev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "sunxi-rfkill",
};
void rfkill_chipen_set(int dev, int on_off)
{
/* Only wifi and bt both close, chip_en goes down,
* otherwise, set chip_en up to keep module work.
* dev : device to set power status. 0: wifi, 1: bt
* on_off: power status to set. 0: off, 1: on
*/
static int power_state;
bool set_val;
if (!rfkill_data)
return;
if (dev == WL_DEV_WIFI || dev == WL_DEV_BLUETOOTH) {
power_state &= ~(1 << dev);
power_state |= ((on_off > 0) << dev);
}
if (gpio_is_valid(rfkill_data->gpio_chip_en)) {
set_val = (power_state != 0) ?
rfkill_data->gpio_chip_en_assert :
!rfkill_data->gpio_chip_en_assert;
gpio_set_value(rfkill_data->gpio_chip_en, set_val);
}
}
void rfkill_poweren_set(int dev, int on_off)
{
/* Only wifi and bt both close, power_en goes down,
* otherwise, set power_en up to keep module work.
* dev : device to set power status. 0: wifi, 1: bt
* on_off: power status to set. 0: off, 1: on
*/
static int power_state;
bool set_val;
if (!rfkill_data)
return;
if (dev == WL_DEV_WIFI || dev == WL_DEV_BLUETOOTH) {
power_state &= ~(1 << dev);
power_state |= ((on_off > 0) << dev);
}
if (gpio_is_valid(rfkill_data->gpio_power_en)) {
set_val = (power_state != 0) ?
rfkill_data->gpio_power_en_assert :
!rfkill_data->gpio_power_en_assert;
gpio_set_value(rfkill_data->gpio_power_en, set_val);
}
}
static int rfkill_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct device *dev = &pdev->dev;
struct sunxi_rfkill_platdata *data;
#if (LINUX_VERSION_CODE <= KERNEL_VERSION(6, 2, 0))
enum of_gpio_flags config;
#else
u32 config = 0;
#endif
char *pctrl_name = PINCTRL_STATE_DEFAULT;
struct pinctrl_state *pctrl_state = NULL;
int ret = 0;
if (!dev)
return -ENOMEM;
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
dev_info(dev, "module version: %s\n", MODULE_CUR_VERSION);
data->pctrl = devm_pinctrl_get(dev);
if (IS_ERR(data->pctrl)) {
dev_dbg(dev, "no pinctrl state configured\n");
} else {
pctrl_state = pinctrl_lookup_state(data->pctrl, pctrl_name);
if (IS_ERR(pctrl_state)) {
dev_warn(dev, "pinctrl_lookup_state(%s) failed! return %ld\n",
pctrl_name, PTR_ERR(pctrl_state));
} else {
ret = pinctrl_select_state(data->pctrl, pctrl_state);
if (ret < 0) {
dev_warn(dev, "pinctrl_select_state(%s) failed! return %d\n",
pctrl_name, ret);
}
}
}
#if (LINUX_VERSION_CODE > KERNEL_VERSION(6, 2, 0))
data->gpio_chip_en = of_get_named_gpio(np, "chip_en", 0);
#else
data->gpio_chip_en = of_get_named_gpio_flags(np, "chip_en", 0, &config);
#endif
if (!gpio_is_valid(data->gpio_chip_en)) {
dev_dbg(dev, "no chip_en gpio configured\n");
} else {
#if (LINUX_VERSION_CODE > KERNEL_VERSION(6, 2, 0))
of_property_read_u32_index(np, "chip_en", 3, &config);
data->gpio_chip_en_assert = (config == GPIO_ACTIVE_LOW) ? 0 : 1;
#else
data->gpio_chip_en_assert = (config == OF_GPIO_ACTIVE_LOW) ? 0 : 1;
#endif
dev_info(dev, "chip_en gpio=%d assert=%d\n", data->gpio_chip_en, data->gpio_chip_en_assert);
ret = devm_gpio_request(dev, data->gpio_chip_en, "chip_en");
if (ret < 0) {
dev_err(dev, "can't request chip_en gpio %d\n",
data->gpio_chip_en);
return ret;
}
ret = gpio_direction_output(data->gpio_chip_en, !data->gpio_chip_en_assert);
if (ret < 0) {
dev_err(dev, "can't request output direction chip_en gpio %d\n",
data->gpio_chip_en);
return ret;
}
}
#if (LINUX_VERSION_CODE > KERNEL_VERSION(6, 2, 0))
data->gpio_power_en = of_get_named_gpio(np, "power_en", 0);
#else
data->gpio_power_en = of_get_named_gpio_flags(np, "power_en", 0, &config);
#endif
if (!gpio_is_valid(data->gpio_power_en)) {
dev_dbg(dev, "no power_en gpio configured\n");
} else {
#if (LINUX_VERSION_CODE > KERNEL_VERSION(6, 2, 0))
of_property_read_u32_index(np, "power_en", 3, &config);
data->gpio_power_en_assert = (config == GPIO_ACTIVE_LOW) ? 0 : 1;
#else
data->gpio_power_en_assert = (config == OF_GPIO_ACTIVE_LOW) ? 0 : 1;
#endif
dev_info(dev, "power_en gpio=%d assert=%d\n", data->gpio_power_en, data->gpio_power_en_assert);
ret = devm_gpio_request(dev, data->gpio_power_en, "power_en");
if (ret < 0) {
dev_err(dev, "can't request power_en gpio %d\n",
data->gpio_power_en);
return ret;
}
ret = gpio_direction_output(data->gpio_power_en, !data->gpio_power_en_assert);
if (ret < 0) {
dev_err(dev, "can't request output direction power_en gpio %d\n",
data->gpio_power_en);
return ret;
}
}
ret = misc_register(&sunxi_rfkill_miscdev);
if (ret) {
dev_err(dev, "sunxi-rfkill register driver as misc device error!\n");
return ret;
}
ret = sunxi_wlan_init(pdev);
if (ret)
goto err_wlan;
ret = sunxi_bt_init(pdev);
if (ret)
goto err_bt;
ret = sunxi_modem_init(pdev);
if (ret)
goto err_modem;
ret = sunxi_gnss_init(pdev);
if (ret)
goto err_gnss;
rfkill_data = data;
return 0;
err_gnss:
sunxi_modem_deinit(pdev);
err_modem:
sunxi_bt_deinit(pdev);
err_bt:
sunxi_wlan_deinit(pdev);
err_wlan:
misc_deregister(&sunxi_rfkill_miscdev);
return ret;
}
static int rfkill_remove(struct platform_device *pdev)
{
sunxi_gnss_deinit(pdev);
sunxi_modem_deinit(pdev);
sunxi_bt_deinit(pdev);
sunxi_wlan_deinit(pdev);
misc_deregister(&sunxi_rfkill_miscdev);
rfkill_data = NULL;
return 0;
}
static const struct of_device_id rfkill_ids[] = {
{ .compatible = "allwinner,sunxi-rfkill" },
{ /* Sentinel */ }
};
#if IS_ENABLED(CONFIG_PM)
static int sunxi_rfkill_suspend(struct device *dev)
{
dev_info(dev, "suspend entry\n");
sunxi_gnss_suspend(dev);
return 0;
}
static int sunxi_rfkill_resume(struct device *dev)
{
dev_info(dev, "resume entry\n");
sunxi_gnss_resume(dev);
return 0;
}
static const struct dev_pm_ops sunxi_rfkill_pm_ops = {
.suspend = sunxi_rfkill_suspend,
.resume = sunxi_rfkill_resume,
};
#endif
static struct platform_driver rfkill_driver = {
.probe = rfkill_probe,
.remove = rfkill_remove,
.driver = {
.owner = THIS_MODULE,
.name = "sunxi-rfkill",
.of_match_table = rfkill_ids,
#if IS_ENABLED(CONFIG_PM)
.pm = &sunxi_rfkill_pm_ops,
#endif
},
};
module_platform_driver(rfkill_driver);
MODULE_VERSION(MODULE_CUR_VERSION);
MODULE_DESCRIPTION("sunxi rfkill driver");
MODULE_LICENSE("GPL");