| // 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); |