blob: 7ef3dc624915a8d9060325f0720a326959c99f9b [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* PCIe rfkill driver for ASR1803 SoCs
*
* Copyright (c) 2021 ASRMicro Inc.
*/
#include <linux/debugfs.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/pci.h>
#include <linux/pcie8x_rfkill.h>
#include <linux/pm.h>
#include <linux/pm_runtime.h>
#include <linux/clk.h>
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#include <linux/of_platform.h>
#include <linux/edge_wakeup_mmp.h>
#include <linux/regulator/consumer.h>
#define PCIE8X_DEV_NAME "pcie-rfkill"
static DEFINE_MUTEX(pcie8x_pwr_mutex);
static void pcie_rfkill_plat_data_init(
struct pcie_rfkill_plat_data *pdata)
{
/* all intems are invalid just after alloc */
pdata->gpio_power_down = -1; /* alias of wlan_en */
pdata->gpio_reset = -1;
pdata->gpio_reset2 = -1;
pdata->gpio_edge_wakeup = -1;
pdata->gpio_3v3_en = -1;
pdata->gpio_1v8_en = -1;
pdata->gpio2_3v3_en = -1;
pdata->gpio2_1v8_en = -1;
pdata->edge_requested = 0;
/* power status, unknown status at first */
pdata->is_on = -1;
pdata->is_on2 = -1;
}
static struct pcie_rfkill_plat_data
*pcie_rfkill_plat_data_alloc(struct platform_device *pdev)
{
struct pcie_rfkill_plat_data *pdata;
/* create a new one and init it */
pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
if (pdata) {
pcie_rfkill_plat_data_init(pdata);
return pdata;
}
return NULL;
}
static int pcie_3v3_1v8_ctrl(int gpio_3v3_en, int gpio_1v8_en, int on)
{
if (gpio_3v3_en >= 0) {
if (gpio_request(gpio_3v3_en, "pcie_wlan 3v3 on")) {
pr_err("gpio %d request failed\n", gpio_3v3_en);
return -1;
} else {
if (on)
gpio_direction_output(gpio_3v3_en, 1);
else
gpio_direction_output(gpio_3v3_en, 0);
gpio_free(gpio_3v3_en);
}
}
if (gpio_1v8_en >= 0) {
if (gpio_request(gpio_1v8_en, "pcie_wlan 1v8 on")) {
pr_err("gpio %d request failed\n", gpio_1v8_en);
return -1;
} else {
if (on)
gpio_direction_output(gpio_1v8_en, 1);
else
gpio_direction_output(gpio_1v8_en, 0);
gpio_free(gpio_1v8_en);
}
}
return 0;
}
static void pcie_regulator(struct pcie_rfkill_plat_data *pdata, int on)
{
if (on) {
if (pdata->wib_3v3) {
if (regulator_set_voltage(pdata->wib_3v3, 3300000, 3300000))
printk(KERN_DEBUG "fail to set regulator wib_3v3 to 3.3v\n");
if (!regulator_is_enabled(pdata->wib_3v3)) {
if (regulator_enable(pdata->wib_3v3))
printk(KERN_DEBUG "fail to enable regulator wib_3v3\n");
}
}
} else {
if (pdata->wib_3v3) {
if (regulator_is_enabled(pdata->wib_3v3)) {
if (regulator_disable(pdata->wib_3v3))
printk(KERN_DEBUG "fail to disable regulator wib_3v3\n");
}
}
}
}
static int slot0_pwr_ctrl(struct pcie_rfkill_plat_data *pdata, int on)
{
int gpio_reset = pdata->gpio_reset;
if(gpio_reset < 0){
return -1;
}
if (on) {
if (!gpio_request(gpio_reset, "pcie_dev reset")) {
gpio_direction_output(gpio_reset, 0);
gpio_free(gpio_reset);
} else {
pr_err("pcie slot0 perst request failed %d.\n",
gpio_reset);
}
mdelay(10);
pcie_3v3_1v8_ctrl(pdata->gpio_3v3_en, pdata->gpio_1v8_en, 0);
mdelay(50);
pcie_3v3_1v8_ctrl(pdata->gpio_3v3_en, pdata->gpio_1v8_en, 1);
mdelay(10);
} else {
pcie_3v3_1v8_ctrl(pdata->gpio_3v3_en, pdata->gpio_1v8_en, 0);
if (!gpio_request(gpio_reset, "pcie_dev reset")) {
gpio_direction_output(gpio_reset, 0);
gpio_free(gpio_reset);
} else {
pr_err("pcie slot0 perst request failed %d.\n",
gpio_reset);
}
msleep(500); /*Toff-time, min: 500*/
}
return 0;
}
static int slot1_pwr_ctrl(struct pcie_rfkill_plat_data *pdata, int on)
{
int gpio_reset2 = pdata->gpio_reset2;
if(gpio_reset2 < 0){
return -1;
}
if (on) {
if (!gpio_request(gpio_reset2, "pcie_dev reset")) {
gpio_direction_output(gpio_reset2, 0);
gpio_free(gpio_reset2);
} else {
pr_err("pcie slot1 perst request failed %d.\n",
gpio_reset2);
}
mdelay(10);
pcie_3v3_1v8_ctrl(pdata->gpio2_3v3_en, pdata->gpio2_1v8_en, 0);
mdelay(50);
pcie_3v3_1v8_ctrl(pdata->gpio2_3v3_en, pdata->gpio2_1v8_en, 1);
mdelay(10);
} else {
pcie_3v3_1v8_ctrl(pdata->gpio2_3v3_en, pdata->gpio2_1v8_en, 0);
if (!gpio_request(gpio_reset2, "pcie_dev reset")) {
gpio_direction_output(gpio_reset2, 0);
gpio_free(gpio_reset2);
} else {
pr_err("pcie slot1 perst gpio request failed %d.\n",
gpio_reset2);
}
msleep(500); /*Toff-time, min: 500*/
}
return 0;
}
static int pcie_slot1_pwr_on(struct pcie_rfkill_plat_data *pdata)
{
int ret = 0;
if (pdata->is_on2)
return 0;
pr_info("%s: on=%d\n", __func__, 1);
pcie_regulator(pdata, 1);
slot1_pwr_ctrl(pdata, 1);
pdata->is_on2 = 1;
pr_info("pcie_dev set_power on end\n");
return ret;
}
static int pcie_slot1_pwr_off(struct pcie_rfkill_plat_data *pdata)
{
int ret = 0;
if (!pdata->is_on2)
return 0;
pr_info("pcie slot1 set_power off start\n");
slot1_pwr_ctrl(pdata, 0);
pcie_regulator(pdata, 0);
pdata->is_on2 = 0;
pr_info("pcie_dev set_power off end\n");
return ret;
}
static int pcie_slot0_pwr_on(struct pcie_rfkill_plat_data *pdata)
{
int ret = 0;
if (pdata->is_on)
return 0;
pr_info("%s: on=%d\n", __func__, 1);
pcie_regulator(pdata, 1);
slot0_pwr_ctrl(pdata, 1);
pdata->is_on = 1;
pr_info("pcie_dev set_power on end\n");
return ret;
}
static int pcie_slot0_pwr_off(struct pcie_rfkill_plat_data *pdata)
{
int ret = 0;
if (!pdata->is_on)
return 0;
pr_info("pcie slot0 set_power off start\n");
slot0_pwr_ctrl(pdata, 0);
pcie_regulator(pdata, 0);
pdata->is_on = 0;
pr_info("pcie_dev set_power off end\n");
return ret;
}
static ssize_t pcie_slot0_pwr_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int len = 0;
struct pcie_rfkill_plat_data *pdata = dev->platform_data;
mutex_lock(&pcie8x_pwr_mutex);
len = sprintf(buf, "PCIe Device is power %s\n",
pdata->is_on ? "on" : "off");
mutex_unlock(&pcie8x_pwr_mutex);
return (ssize_t)len;
}
static ssize_t pcie_slot0_pwr_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
int pwr_ctrl;
int valid_ctrl = 0;
struct pcie_rfkill_plat_data *pdata = dev->platform_data;
mutex_lock(&pcie8x_pwr_mutex);
if (sscanf(buf, "%d", &pwr_ctrl) == 1) {
if ((pwr_ctrl == 1) || (pwr_ctrl == 0))
valid_ctrl = 1;
}
if (valid_ctrl != 1) {
pr_err("Please input valid ctrl: 0: Close, 1: Open\n");
mutex_unlock(&pcie8x_pwr_mutex);
return size;
}
if (pwr_ctrl)
pcie_slot0_pwr_on(pdata);
else
pcie_slot0_pwr_off(pdata);
pr_info("Now PCIe Device is power %s\n", pdata->is_on ? "on" : "off");
mutex_unlock(&pcie8x_pwr_mutex);
return size;
}
static ssize_t pcie_slot1_pwr_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int len = 0;
struct pcie_rfkill_plat_data *pdata = dev->platform_data;
mutex_lock(&pcie8x_pwr_mutex);
len = sprintf(buf, "PCIe Device is power %s\n",
pdata->is_on2 ? "on" : "off");
mutex_unlock(&pcie8x_pwr_mutex);
return (ssize_t)len;
}
static ssize_t pcie_slot1_pwr_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
int pwr_ctrl;
int valid_ctrl = 0;
struct pcie_rfkill_plat_data *pdata = dev->platform_data;
mutex_lock(&pcie8x_pwr_mutex);
if (sscanf(buf, "%d", &pwr_ctrl) == 1) {
if ((pwr_ctrl == 1) || (pwr_ctrl == 0))
valid_ctrl = 1;
}
if (valid_ctrl != 1) {
pr_err("Please input valid ctrl: 0: Close, 1: Open\n");
mutex_unlock(&pcie8x_pwr_mutex);
return size;
}
if (pwr_ctrl)
pcie_slot1_pwr_on(pdata);
else
pcie_slot1_pwr_off(pdata);
pr_info("Now PCIe Device is power %s\n", pdata->is_on2 ? "on" : "off");
mutex_unlock(&pcie8x_pwr_mutex);
return size;
}
static DEVICE_ATTR(pwr_ctrl2, 0660,
pcie_slot1_pwr_show, pcie_slot1_pwr_store);
static DEVICE_ATTR(pwr_ctrl, 0660,
pcie_slot0_pwr_show, pcie_slot0_pwr_store);
#ifdef CONFIG_OF
static const struct of_device_id pcie8x_rfkill_of_match[] = {
{
.compatible = "mrvl,pcie-rfkill",
},
{},
};
MODULE_DEVICE_TABLE(of, pcie8x_rfkill_of_match);
static int pcie8x_rfkill_probe_dt(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct pcie_rfkill_plat_data *pdata = pdev->dev.platform_data;
const char *wifi_device;
struct regulator *wib_3v3;
int gpio;
/* Get PD/RST pins status */
pdata->pinctrl = devm_pinctrl_get(&pdev->dev);
if (IS_ERR(pdata->pinctrl)) {
pdata->pinctrl = NULL;
dev_warn(&pdev->dev, "could not get PD/RST pinctrl.\n");
} else {
pdata->pin_off = pinctrl_lookup_state(pdata->pinctrl, "off");
if (IS_ERR(pdata->pin_off)) {
pdata->pin_off = NULL;
dev_err(&pdev->dev, "could not get off pinstate.\n");
}
pdata->pin_on = pinctrl_lookup_state(pdata->pinctrl, "on");
if (IS_ERR(pdata->pin_on)) {
pdata->pin_on = NULL;
dev_err(&pdev->dev, "could not get on pinstate.\n");
}
}
if (pdata->pinctrl && pdata->pin_off)
pinctrl_select_state(pdata->pinctrl, pdata->pin_off);
gpio = of_get_named_gpio(np, "rst-gpio", 0);
if (unlikely(gpio < 0)) {
pr_debug("pcie rfkill: rst-gpio undefined\n");
pdata->gpio_reset = -1;
} else {
pdata->gpio_reset = gpio;
}
gpio = of_get_named_gpio(np, "rst-gpio2", 0);
if (unlikely(gpio < 0)) {
pr_debug("pcie rfkill: rst-gpio2 undefined\n");
pdata->gpio_reset2 = -1;
} else {
pdata->gpio_reset2 = gpio;
}
gpio = of_get_named_gpio(np, "3v3-ldo-gpio", 0);
if (unlikely(gpio < 0)) {
pr_debug("pcie rfkill: 3v3-ldo-gpio undefined\n");
pdata->gpio_3v3_en = -1;
} else {
pdata->gpio_3v3_en = gpio;
}
gpio = of_get_named_gpio(np, "3v3-ldo-gpio2", 0);
if (unlikely(gpio < 0)) {
pr_debug("pcie rfkill: 3v3-ldo-gpio2 undefined\n");
pdata->gpio2_3v3_en = -1;
} else {
pdata->gpio2_3v3_en = gpio;
}
gpio = of_get_named_gpio(np, "1v8-ldo-gpio", 0);
if (unlikely(gpio < 0)) {
pr_debug("pcie rfkill: 1v8-ldo-gpio undefined\n");
pdata->gpio_1v8_en = -1;
} else {
pdata->gpio_1v8_en = gpio;
}
gpio = of_get_named_gpio(np, "1v8-ldo-gpio2", 0);
if (unlikely(gpio < 0)) {
pr_debug("pcie rfkill: 1v8-ldo-gpio2 undefined\n");
pdata->gpio2_1v8_en = -1;
} else {
pdata->gpio2_1v8_en = gpio;
}
if (of_property_read_string(np, "wifi-device", &wifi_device)) {
wifi_device = "dummy";
}
if (strcmp(wifi_device, "slot1") == 0)
pdata->pwr_ctrl = slot1_pwr_ctrl;
else if (strcmp(wifi_device, "slot0") == 0)
pdata->pwr_ctrl = slot0_pwr_ctrl;
else
pdata->pwr_ctrl = slot0_pwr_ctrl;
/* get regulators from dt */
wib_3v3 = regulator_get_optional(&pdev->dev, "wib_3v3");
if (IS_ERR_OR_NULL(wib_3v3)) {
printk(KERN_DEBUG "%s: the regulator for wib_3v3 not found\n",
__func__);
} else {
pdata->wib_3v3 = wib_3v3;
}
return 0;
}
#else
static int pcie8x_rfkill_probe_dt(struct platform_device *pdev)
{
return 0;
}
#endif
static int pcie8x_rfkill_probe(struct platform_device *pdev)
{
struct pcie_rfkill_plat_data *pdata = NULL;
/* flag: whether pdata is passed from platfrom_data */
int pdata_passed = 1;
const struct of_device_id *match = NULL;
int ret = -1;
/* make sure pcie_rfkill_plat_data is valid */
pdata = pdev->dev.platform_data;
if (!pdata) {
/* if platfrom data do not pass the struct to us */
pdata_passed = 0;
pdata = pcie_rfkill_plat_data_alloc(pdev);
if (!pdata) {
pr_err("can't get pcie_rfkill_plat_data struct during probe\n");
goto err_pdata;
}
pdev->dev.platform_data = pdata;
}
/* set value to pcie_rfkill_plat_data if DT pass them to us */
#ifdef CONFIG_OF
match = of_match_device(of_match_ptr(pcie8x_rfkill_of_match),
&pdev->dev);
#endif
if (match) {
ret = pcie8x_rfkill_probe_dt(pdev);
if (ret)
goto err_dt;
}
pdata->is_on = 0;
pdata->is_on2 = 0;
pcie_slot0_pwr_off(pdata);
pcie_slot1_pwr_off(pdata);
mdelay(100);
pcie_slot0_pwr_on(pdata);
pcie_slot1_pwr_on(pdata);
device_create_file(&pdev->dev, &dev_attr_pwr_ctrl);
device_create_file(&pdev->dev, &dev_attr_pwr_ctrl2);
return 0;
err_dt:
if (!pdata_passed)
pdev->dev.platform_data = NULL;
err_pdata:
return ret;
}
static int pcie8x_rfkill_suspend(struct platform_device *pdev,
pm_message_t pm_state)
{
return 0;
}
static int pcie8x_rfkill_resume(struct platform_device *pdev)
{
return 0;
}
static struct platform_driver pcie8x_rfkill_platform_driver = {
.probe = pcie8x_rfkill_probe,
.driver = {
.name = PCIE8X_DEV_NAME,
.owner = THIS_MODULE,
#ifdef CONFIG_OF
.of_match_table = pcie8x_rfkill_of_match,
#endif
},
.suspend = pcie8x_rfkill_suspend,
.resume = pcie8x_rfkill_resume,
};
static int __init pcie8x_rfkill_init(void)
{
return platform_driver_register(&pcie8x_rfkill_platform_driver);
}
device_initcall(pcie8x_rfkill_init);