| /* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 and |
| * only version 2 as published by the Free Software Foundation. |
| * |
| * 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. |
| */ |
| |
| #define pr_fmt(fmt) "cnss_sdio:%s:%d:: " fmt, __func__, __LINE__ |
| |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/init.h> |
| #include <linux/of.h> |
| #include <linux/platform_device.h> |
| #include <linux/regulator/consumer.h> |
| #include <linux/delay.h> |
| #include <linux/err.h> |
| #include <linux/slab.h> |
| #include <linux/mmc/sdio_func.h> |
| #include <linux/mmc/sdio_ids.h> |
| #include <linux/mmc/card.h> |
| #include <linux/mmc/host.h> |
| #include <linux/io.h> |
| #include <soc/qcom/subsystem_restart.h> |
| #include <soc/qcom/subsystem_notif.h> |
| #include <soc/qcom/ramdump.h> |
| #include <soc/qcom/memory_dump.h> |
| #include <net/cnss.h> |
| #include "cnss_common.h" |
| #include <linux/pm_qos.h> |
| #include <linux/msm-bus.h> |
| #include <linux/msm-bus-board.h> |
| #include <linux/gpio.h> |
| #include <linux/of_gpio.h> |
| |
| #define WLAN_VREG_NAME "vdd-wlan" |
| #define WLAN_VREG_DSRC_NAME "vdd-wlan-dsrc" |
| #define WLAN_VREG_IO_NAME "vdd-wlan-io" |
| #define WLAN_VREG_XTAL_NAME "vdd-wlan-xtal" |
| #define WLAN_GPIO_CAPTSF_NAME "qcom,cap-tsf-gpio" |
| |
| #define WLAN_VREG_IO_MAX 1800000 |
| #define WLAN_VREG_IO_MIN 1800000 |
| #define WLAN_VREG_XTAL_MAX 1800000 |
| #define WLAN_VREG_XTAL_MIN 1800000 |
| #define POWER_ON_DELAY 4 |
| |
| /* Values for Dynamic Ramdump Collection*/ |
| #define CNSS_DUMP_FORMAT_VER 0x11 |
| #define CNSS_DUMP_MAGIC_VER_V2 0x42445953 |
| #define CNSS_DUMP_NAME "CNSS_WLAN_SDIO" |
| #define CNSS_PINCTRL_SLEEP_STATE "sleep" |
| #define CNSS_PINCTRL_ACTIVE_STATE "active" |
| |
| #define CNSS_HW_SLEEP 0 |
| #define CNSS_HW_ACTIVE 1 |
| |
| struct cnss_sdio_regulator { |
| struct regulator *wlan_io; |
| struct regulator *wlan_xtal; |
| struct regulator *wlan_vreg; |
| struct regulator *wlan_vreg_dsrc; |
| }; |
| |
| struct cnss_sdio_info { |
| struct cnss_sdio_wlan_driver *wdrv; |
| struct sdio_func *func; |
| struct mmc_card *card; |
| struct mmc_host *host; |
| struct device *dev; |
| const struct sdio_device_id *id; |
| bool skip_wlan_en_toggle; |
| bool cnss_hw_state; |
| struct cnss_cap_tsf_info cap_tsf_info; |
| }; |
| |
| struct cnss_ssr_info { |
| struct subsys_device *subsys; |
| struct subsys_desc subsysdesc; |
| void *subsys_handle; |
| struct ramdump_device *ramdump_dev; |
| unsigned long ramdump_size; |
| void *ramdump_addr; |
| phys_addr_t ramdump_phys; |
| struct msm_dump_data dump_data; |
| bool ramdump_dynamic; |
| char subsys_name[10]; |
| }; |
| |
| struct cnss_wlan_pinctrl_info { |
| bool is_antenna_shared; |
| struct pinctrl *pinctrl; |
| struct pinctrl_state *sleep; |
| struct pinctrl_state *active; |
| }; |
| |
| struct cnss_sdio_bus_bandwidth { |
| struct msm_bus_scale_pdata *bus_scale_table; |
| u32 bus_client; |
| int current_bandwidth_vote; |
| }; |
| |
| static struct cnss_sdio_data { |
| struct cnss_sdio_regulator regulator; |
| struct platform_device *pdev; |
| struct cnss_sdio_info cnss_sdio_info; |
| struct cnss_ssr_info ssr_info; |
| struct pm_qos_request qos_request; |
| struct cnss_wlan_pinctrl_info pinctrl_info; |
| struct cnss_sdio_bus_bandwidth bus_bandwidth; |
| struct cnss_dev_platform_ops platform_ops; |
| } *cnss_pdata; |
| |
| #define WLAN_RECOVERY_DELAY 1 |
| /* cnss sdio subsytem device name, required property */ |
| #define CNSS_SUBSYS_NAME_KEY "subsys-name" |
| |
| /* SDIO manufacturer ID and Codes */ |
| #define MANUFACTURER_ID_AR6320_BASE 0x500 |
| #define MANUFACTURER_ID_QCA9377_BASE 0x700 |
| #define MANUFACTURER_ID_QCA9379_BASE 0x800 |
| #define MANUFACTURER_CODE 0x271 |
| |
| static const struct sdio_device_id ar6k_id_table[] = { |
| {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0x0))}, |
| {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0x1))}, |
| {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0x2))}, |
| {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0x3))}, |
| {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0x4))}, |
| {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0x5))}, |
| {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0x6))}, |
| {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0x7))}, |
| {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0x8))}, |
| {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0x9))}, |
| {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0xA))}, |
| {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0xB))}, |
| {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0xC))}, |
| {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0xD))}, |
| {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0xE))}, |
| {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_AR6320_BASE | 0xF))}, |
| {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0x0))}, |
| {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0x1))}, |
| {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0x2))}, |
| {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0x3))}, |
| {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0x4))}, |
| {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0x5))}, |
| {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0x6))}, |
| {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0x7))}, |
| {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0x8))}, |
| {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0x9))}, |
| {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0xA))}, |
| {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0xB))}, |
| {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0xC))}, |
| {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0xD))}, |
| {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0xE))}, |
| {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9377_BASE | 0xF))}, |
| {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9379_BASE | 0x0))}, |
| {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9379_BASE | 0x1))}, |
| {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9379_BASE | 0x2))}, |
| {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9379_BASE | 0x3))}, |
| {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9379_BASE | 0x4))}, |
| {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9379_BASE | 0x5))}, |
| {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9379_BASE | 0x6))}, |
| {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9379_BASE | 0x7))}, |
| {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9379_BASE | 0x8))}, |
| {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9379_BASE | 0x9))}, |
| {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9379_BASE | 0xA))}, |
| {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9379_BASE | 0xB))}, |
| {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9379_BASE | 0xC))}, |
| {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9379_BASE | 0xD))}, |
| {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9379_BASE | 0xE))}, |
| {SDIO_DEVICE(MANUFACTURER_CODE, (MANUFACTURER_ID_QCA9379_BASE | 0xF))}, |
| {}, |
| }; |
| MODULE_DEVICE_TABLE(sdio, ar6k_id_table); |
| |
| void cnss_sdio_request_pm_qos_type(int latency_type, u32 qos_val) |
| { |
| if (!cnss_pdata) |
| return; |
| |
| pr_debug("PM QoS value: %d\n", qos_val); |
| pm_qos_add_request(&cnss_pdata->qos_request, latency_type, qos_val); |
| } |
| EXPORT_SYMBOL(cnss_sdio_request_pm_qos_type); |
| |
| int cnss_sdio_request_bus_bandwidth(int bandwidth) |
| { |
| int ret; |
| struct cnss_sdio_bus_bandwidth *bus_bandwidth; |
| |
| if (!cnss_pdata) |
| return -ENODEV; |
| |
| bus_bandwidth = &cnss_pdata->bus_bandwidth; |
| if (!bus_bandwidth->bus_client) |
| return -EINVAL; |
| |
| switch (bandwidth) { |
| case CNSS_BUS_WIDTH_NONE: |
| case CNSS_BUS_WIDTH_LOW: |
| case CNSS_BUS_WIDTH_MEDIUM: |
| case CNSS_BUS_WIDTH_HIGH: |
| ret = msm_bus_scale_client_update_request( |
| bus_bandwidth->bus_client, bandwidth); |
| if (!ret) { |
| bus_bandwidth->current_bandwidth_vote = bandwidth; |
| } else { |
| pr_debug( |
| "could not set bus bandwidth %d, ret = %d\n", |
| bandwidth, ret); |
| } |
| break; |
| default: |
| pr_debug("Invalid request %d\n", bandwidth); |
| ret = -EINVAL; |
| } |
| |
| return ret; |
| } |
| |
| void cnss_sdio_request_pm_qos(u32 qos_val) |
| { |
| if (!cnss_pdata) |
| return; |
| |
| pr_debug("PM QoS value: %d\n", qos_val); |
| pm_qos_add_request( |
| &cnss_pdata->qos_request, |
| PM_QOS_CPU_DMA_LATENCY, qos_val); |
| } |
| EXPORT_SYMBOL(cnss_sdio_request_pm_qos); |
| |
| void cnss_sdio_remove_pm_qos(void) |
| { |
| if (!cnss_pdata) |
| return; |
| |
| pm_qos_remove_request(&cnss_pdata->qos_request); |
| pr_debug("PM QoS removed\n"); |
| } |
| EXPORT_SYMBOL(cnss_sdio_remove_pm_qos); |
| |
| static int cnss_put_hw_resources(struct device *dev) |
| { |
| int ret = -EINVAL; |
| struct cnss_sdio_info *info; |
| struct mmc_host *host; |
| |
| if (!cnss_pdata) |
| return ret; |
| |
| info = &cnss_pdata->cnss_sdio_info; |
| |
| if (info->skip_wlan_en_toggle) { |
| pr_debug("HW doesn't support wlan toggling\n"); |
| return 0; |
| } |
| |
| if (info->cnss_hw_state == CNSS_HW_SLEEP) { |
| pr_debug("HW resources are already released\n"); |
| return 0; |
| } |
| |
| host = info->host; |
| |
| if (!host) { |
| pr_err("MMC host is invalid\n"); |
| return ret; |
| } |
| |
| if (!cnss_pdata->regulator.wlan_vreg) { |
| pr_debug("wlan_vreg regulator is invalid\n"); |
| return 0; |
| } |
| |
| ret = mmc_power_save_host(host); |
| if (ret) { |
| pr_err("Failed to Power Save Host err:%d\n", |
| ret); |
| return ret; |
| } |
| |
| regulator_disable(cnss_pdata->regulator.wlan_vreg); |
| info->cnss_hw_state = CNSS_HW_SLEEP; |
| |
| return ret; |
| } |
| |
| static int cnss_get_hw_resources(struct device *dev) |
| { |
| int ret = -EINVAL; |
| struct mmc_host *host; |
| struct cnss_sdio_info *info; |
| |
| if (!cnss_pdata) |
| return ret; |
| |
| info = &cnss_pdata->cnss_sdio_info; |
| |
| if (info->skip_wlan_en_toggle) { |
| pr_debug("HW doesn't support wlan toggling\n"); |
| return 0; |
| } |
| |
| if (info->cnss_hw_state == CNSS_HW_ACTIVE) { |
| pr_debug("HW resources are already active\n"); |
| return 0; |
| } |
| |
| host = info->host; |
| |
| if (!host) { |
| pr_err("MMC Host is Invalid; Enumeration Failed\n"); |
| return ret; |
| } |
| |
| if (!cnss_pdata->regulator.wlan_vreg) { |
| pr_debug("wlan_vreg regulator is invalid\n"); |
| return 0; |
| } |
| |
| ret = regulator_enable(cnss_pdata->regulator.wlan_vreg); |
| if (ret) { |
| pr_err("Failed to enable wlan vreg\n"); |
| return ret; |
| } |
| |
| ret = mmc_power_restore_host(host); |
| if (ret) { |
| pr_err("Failed to restore host power ret:%d\n", |
| ret); |
| regulator_disable(cnss_pdata->regulator.wlan_vreg); |
| return ret; |
| } |
| |
| info->cnss_hw_state = CNSS_HW_ACTIVE; |
| return ret; |
| } |
| |
| static int cnss_sdio_shutdown(const struct subsys_desc *subsys, bool force_stop) |
| { |
| struct cnss_sdio_info *cnss_info; |
| struct cnss_sdio_wlan_driver *wdrv; |
| int ret = 0; |
| |
| if (!cnss_pdata) |
| return -ENODEV; |
| |
| cnss_info = &cnss_pdata->cnss_sdio_info; |
| wdrv = cnss_info->wdrv; |
| if (!wdrv) |
| return 0; |
| if (!wdrv->shutdown) |
| return 0; |
| |
| wdrv->shutdown(cnss_info->func); |
| ret = cnss_put_hw_resources(cnss_info->dev); |
| |
| if (ret) |
| pr_err("Failed to put hw resources\n"); |
| |
| return ret; |
| } |
| |
| static int cnss_sdio_powerup(const struct subsys_desc *subsys) |
| { |
| struct cnss_sdio_info *cnss_info; |
| struct cnss_sdio_wlan_driver *wdrv; |
| int ret = 0; |
| |
| if (!cnss_pdata) |
| return -ENODEV; |
| |
| cnss_info = &cnss_pdata->cnss_sdio_info; |
| wdrv = cnss_info->wdrv; |
| |
| if (!wdrv) |
| return 0; |
| |
| if (!wdrv->reinit) |
| return 0; |
| |
| ret = cnss_get_hw_resources(cnss_info->dev); |
| if (ret) { |
| pr_err("Failed to power up HW\n"); |
| return ret; |
| } |
| |
| ret = wdrv->reinit(cnss_info->func, cnss_info->id); |
| if (ret) |
| pr_err("wlan reinit error=%d\n", ret); |
| |
| return ret; |
| } |
| |
| static void cnss_sdio_crash_shutdown(const struct subsys_desc *subsys) |
| { |
| struct cnss_sdio_info *cnss_info; |
| struct cnss_sdio_wlan_driver *wdrv; |
| |
| if (!cnss_pdata) |
| return; |
| |
| cnss_info = &cnss_pdata->cnss_sdio_info; |
| wdrv = cnss_info->wdrv; |
| if (wdrv && wdrv->crash_shutdown) |
| wdrv->crash_shutdown(cnss_info->func); |
| } |
| |
| static int cnss_sdio_ramdump(int enable, const struct subsys_desc *subsys) |
| { |
| struct cnss_ssr_info *ssr_info; |
| struct ramdump_segment segment; |
| int ret; |
| |
| if (!cnss_pdata) |
| return -ENODEV; |
| |
| if (!cnss_pdata->ssr_info.ramdump_size) |
| return -ENOENT; |
| |
| if (!enable) |
| return 0; |
| |
| ssr_info = &cnss_pdata->ssr_info; |
| |
| memset(&segment, 0, sizeof(segment)); |
| segment.v_address = ssr_info->ramdump_addr; |
| segment.size = ssr_info->ramdump_size; |
| ret = do_ramdump(ssr_info->ramdump_dev, &segment, 1); |
| if (ret) |
| pr_err("do_ramdump failed error=%d\n", ret); |
| return ret; |
| } |
| |
| static int cnss_subsys_init(void) |
| { |
| struct cnss_ssr_info *ssr_info; |
| int ret = 0; |
| |
| if (!cnss_pdata) |
| return -ENODEV; |
| |
| ssr_info = &cnss_pdata->ssr_info; |
| ssr_info->subsysdesc.name = ssr_info->subsys_name; |
| ssr_info->subsysdesc.owner = THIS_MODULE; |
| ssr_info->subsysdesc.shutdown = cnss_sdio_shutdown; |
| ssr_info->subsysdesc.powerup = cnss_sdio_powerup; |
| ssr_info->subsysdesc.ramdump = cnss_sdio_ramdump; |
| ssr_info->subsysdesc.crash_shutdown = cnss_sdio_crash_shutdown; |
| ssr_info->subsysdesc.dev = &cnss_pdata->pdev->dev; |
| ssr_info->subsys = subsys_register(&ssr_info->subsysdesc); |
| if (IS_ERR(ssr_info->subsys)) { |
| ret = PTR_ERR(ssr_info->subsys); |
| ssr_info->subsys = NULL; |
| dev_err(&cnss_pdata->pdev->dev, "Failed to subsys_register error=%d\n", |
| ret); |
| goto err_subsys_reg; |
| } |
| ssr_info->subsys_handle = subsystem_get(ssr_info->subsysdesc.name); |
| if (IS_ERR(ssr_info->subsys_handle)) { |
| ret = PTR_ERR(ssr_info->subsys_handle); |
| ssr_info->subsys_handle = NULL; |
| dev_err(&cnss_pdata->pdev->dev, "Failed to subsystem_get error=%d\n", |
| ret); |
| goto err_subsys_get; |
| } |
| return 0; |
| err_subsys_get: |
| subsys_unregister(ssr_info->subsys); |
| ssr_info->subsys = NULL; |
| err_subsys_reg: |
| return ret; |
| } |
| |
| static void cnss_subsys_exit(void) |
| { |
| struct cnss_ssr_info *ssr_info; |
| |
| if (!cnss_pdata) |
| return; |
| |
| ssr_info = &cnss_pdata->ssr_info; |
| if (ssr_info->subsys_handle) |
| subsystem_put(ssr_info->subsys_handle); |
| ssr_info->subsys_handle = NULL; |
| if (ssr_info->subsys) |
| subsys_unregister(ssr_info->subsys); |
| ssr_info->subsys = NULL; |
| } |
| |
| static int cnss_configure_dump_table(struct cnss_ssr_info *ssr_info) |
| { |
| struct msm_dump_entry dump_entry; |
| int ret; |
| |
| ssr_info->dump_data.addr = ssr_info->ramdump_phys; |
| ssr_info->dump_data.len = ssr_info->ramdump_size; |
| ssr_info->dump_data.version = CNSS_DUMP_FORMAT_VER; |
| ssr_info->dump_data.magic = CNSS_DUMP_MAGIC_VER_V2; |
| strlcpy(ssr_info->dump_data.name, CNSS_DUMP_NAME, |
| sizeof(ssr_info->dump_data.name)); |
| |
| dump_entry.id = MSM_DUMP_DATA_CNSS_WLAN; |
| dump_entry.addr = virt_to_phys(&ssr_info->dump_data); |
| |
| ret = msm_dump_data_register(MSM_DUMP_TABLE_APPS, &dump_entry); |
| if (ret) |
| pr_err("Dump table setup failed: %d\n", ret); |
| |
| return ret; |
| } |
| |
| static int cnss_configure_ramdump(void) |
| { |
| struct cnss_ssr_info *ssr_info; |
| int ret = 0; |
| struct resource *res; |
| const char *name; |
| u32 ramdump_size = 0; |
| struct device *dev; |
| |
| if (!cnss_pdata) |
| return -ENODEV; |
| |
| dev = &cnss_pdata->pdev->dev; |
| |
| ssr_info = &cnss_pdata->ssr_info; |
| |
| ret = of_property_read_string(dev->of_node, CNSS_SUBSYS_NAME_KEY, |
| &name); |
| if (ret) { |
| pr_err("cnss missing DT key '%s'\n", |
| CNSS_SUBSYS_NAME_KEY); |
| ret = -ENODEV; |
| goto err_subsys_name_query; |
| } |
| |
| strlcpy(ssr_info->subsys_name, name, sizeof(ssr_info->subsys_name)); |
| |
| if (of_property_read_u32(dev->of_node, "qcom,wlan-ramdump-dynamic", |
| &ramdump_size) == 0) { |
| ssr_info->ramdump_addr = dma_alloc_coherent(dev, ramdump_size, |
| &ssr_info->ramdump_phys, |
| GFP_KERNEL); |
| if (ssr_info->ramdump_addr) |
| ssr_info->ramdump_size = ramdump_size; |
| ssr_info->ramdump_dynamic = true; |
| } else { |
| res = platform_get_resource_byname(cnss_pdata->pdev, |
| IORESOURCE_MEM, "ramdump"); |
| if (res) { |
| ssr_info->ramdump_phys = res->start; |
| ramdump_size = resource_size(res); |
| ssr_info->ramdump_addr = ioremap(ssr_info->ramdump_phys, |
| ramdump_size); |
| if (ssr_info->ramdump_addr) |
| ssr_info->ramdump_size = ramdump_size; |
| ssr_info->ramdump_dynamic = false; |
| } |
| } |
| |
| pr_info("ramdump addr: %p, phys: %pa subsys:'%s'\n", |
| ssr_info->ramdump_addr, &ssr_info->ramdump_phys, |
| ssr_info->subsys_name); |
| |
| if (ssr_info->ramdump_size == 0) { |
| pr_info("CNSS ramdump will not be collected\n"); |
| return 0; |
| } |
| |
| if (ssr_info->ramdump_dynamic) { |
| ret = cnss_configure_dump_table(ssr_info); |
| if (ret) |
| goto err_configure_dump_table; |
| } |
| |
| ssr_info->ramdump_dev = create_ramdump_device(ssr_info->subsys_name, |
| dev); |
| if (!ssr_info->ramdump_dev) { |
| ret = -ENOMEM; |
| pr_err("ramdump dev create failed: error=%d\n", |
| ret); |
| goto err_configure_dump_table; |
| } |
| |
| return 0; |
| |
| err_configure_dump_table: |
| if (ssr_info->ramdump_dynamic) |
| dma_free_coherent(dev, ssr_info->ramdump_size, |
| ssr_info->ramdump_addr, |
| ssr_info->ramdump_phys); |
| else |
| iounmap(ssr_info->ramdump_addr); |
| |
| ssr_info->ramdump_addr = NULL; |
| ssr_info->ramdump_size = 0; |
| err_subsys_name_query: |
| return ret; |
| } |
| |
| static void cnss_ramdump_cleanup(void) |
| { |
| struct cnss_ssr_info *ssr_info; |
| struct device *dev; |
| |
| if (!cnss_pdata) |
| return; |
| |
| dev = &cnss_pdata->pdev->dev; |
| ssr_info = &cnss_pdata->ssr_info; |
| if (ssr_info->ramdump_addr) { |
| if (ssr_info->ramdump_dynamic) |
| dma_free_coherent(dev, ssr_info->ramdump_size, |
| ssr_info->ramdump_addr, |
| ssr_info->ramdump_phys); |
| else |
| iounmap(ssr_info->ramdump_addr); |
| } |
| |
| ssr_info->ramdump_addr = NULL; |
| if (ssr_info->ramdump_dev) |
| destroy_ramdump_device(ssr_info->ramdump_dev); |
| ssr_info->ramdump_dev = NULL; |
| } |
| |
| void *cnss_sdio_get_virt_ramdump_mem(unsigned long *size) |
| { |
| if (!cnss_pdata || !cnss_pdata->pdev) |
| return NULL; |
| |
| *size = cnss_pdata->ssr_info.ramdump_size; |
| |
| return cnss_pdata->ssr_info.ramdump_addr; |
| } |
| |
| void cnss_sdio_device_self_recovery(void) |
| { |
| cnss_sdio_shutdown(NULL, false); |
| msleep(WLAN_RECOVERY_DELAY); |
| cnss_sdio_powerup(NULL); |
| } |
| |
| void cnss_sdio_device_crashed(void) |
| { |
| struct cnss_ssr_info *ssr_info; |
| |
| if (!cnss_pdata) |
| return; |
| ssr_info = &cnss_pdata->ssr_info; |
| if (ssr_info->subsys) { |
| subsys_set_crash_status(ssr_info->subsys, true); |
| subsystem_restart_dev(ssr_info->subsys); |
| } |
| } |
| |
| static void cnss_sdio_recovery_work_handler(struct work_struct *recovery) |
| { |
| cnss_sdio_device_self_recovery(); |
| } |
| |
| DECLARE_WORK(cnss_sdio_recovery_work, cnss_sdio_recovery_work_handler); |
| |
| void cnss_sdio_schedule_recovery_work(void) |
| { |
| schedule_work(&cnss_sdio_recovery_work); |
| } |
| |
| /** |
| * cnss_get_restart_level() - cnss get restart level API |
| * |
| * Wlan sdio function driver uses this API to get the current |
| * subsystem restart level. |
| * |
| * Return: CNSS_RESET_SOC - "SYSTEM", restart system |
| * CNSS_RESET_SUBSYS_COUPLED - "RELATED",restart subsystem |
| */ |
| int cnss_get_restart_level(void) |
| { |
| struct cnss_ssr_info *ssr_info; |
| int level; |
| |
| if (!cnss_pdata) |
| return CNSS_RESET_SOC; |
| ssr_info = &cnss_pdata->ssr_info; |
| if (!ssr_info->subsys) |
| return CNSS_RESET_SOC; |
| level = subsys_get_restart_level(ssr_info->subsys); |
| switch (level) { |
| case RESET_SOC: |
| return CNSS_RESET_SOC; |
| case RESET_SUBSYS_COUPLED: |
| return CNSS_RESET_SUBSYS_COUPLED; |
| default: |
| return CNSS_RESET_SOC; |
| } |
| } |
| EXPORT_SYMBOL(cnss_get_restart_level); |
| |
| static inline int cnss_get_tsf_cap_irq(struct device *dev) |
| { |
| int irq = -EINVAL; |
| int gpio; |
| |
| if (!dev) |
| return -ENODEV; |
| |
| gpio = of_get_named_gpio(dev->of_node, WLAN_GPIO_CAPTSF_NAME, 0); |
| if (gpio >= 0) |
| irq = gpio_to_irq(gpio); |
| |
| return irq; |
| } |
| |
| static int cnss_sdio_register_tsf_captured_handler(irq_handler_t handler, |
| void *ctx) |
| { |
| struct cnss_cap_tsf_info *tsf_info; |
| |
| if (!cnss_pdata) |
| return -ENODEV; |
| |
| tsf_info = &cnss_pdata->cnss_sdio_info.cap_tsf_info; |
| if (tsf_info->irq_num < 0) |
| return -ENOTSUPP; |
| |
| tsf_info->irq_handler = handler; |
| tsf_info->context = ctx; |
| return 0; |
| } |
| |
| static int cnss_sdio_unregister_tsf_captured_handler(void *ctx) |
| { |
| struct cnss_cap_tsf_info *tsf_info; |
| |
| if (!cnss_pdata) |
| return -ENODEV; |
| |
| tsf_info = &cnss_pdata->cnss_sdio_info.cap_tsf_info; |
| if (tsf_info->irq_num < 0) |
| return -ENOTSUPP; |
| |
| if (ctx == tsf_info->context) { |
| tsf_info->irq_handler = NULL; |
| tsf_info->context = NULL; |
| } |
| return 0; |
| } |
| |
| static irqreturn_t cnss_sdio_tsf_captured_handler(int irq, void *ctx) |
| { |
| struct cnss_cap_tsf_info *tsf_info; |
| |
| if (!cnss_pdata) |
| return IRQ_HANDLED; |
| |
| tsf_info = &cnss_pdata->cnss_sdio_info.cap_tsf_info; |
| if (tsf_info->irq_num < 0 || tsf_info->irq_num != irq || |
| !tsf_info->irq_handler || !tsf_info->context) |
| return IRQ_HANDLED; |
| |
| return tsf_info->irq_handler(irq, tsf_info->context); |
| } |
| |
| static void cnss_sdio_tsf_init(struct device *dev, |
| struct cnss_cap_tsf_info *tsf_info) |
| { |
| int ret, irq; |
| |
| tsf_info->irq_num = -EINVAL; |
| tsf_info->irq_handler = NULL; |
| tsf_info->context = NULL; |
| |
| irq = cnss_get_tsf_cap_irq(dev); |
| if (irq < 0) { |
| dev_err(dev, "%s: fail to get irq: %d\n", __func__, irq); |
| return; |
| } |
| |
| ret = request_irq(irq, cnss_sdio_tsf_captured_handler, |
| IRQF_SHARED | IRQF_TRIGGER_RISING, dev_name(dev), |
| (void *)tsf_info); |
| dev_err(dev, "%s: request irq[%d] for dev: %s, result: %d\n", |
| __func__, irq, dev_name(dev), ret); |
| if (!ret) |
| tsf_info->irq_num = irq; |
| } |
| |
| static void cnss_sdio_tsf_deinit(struct cnss_cap_tsf_info *tsf_info) |
| { |
| int irq = tsf_info->irq_num; |
| |
| if (irq < 0) |
| return; |
| |
| free_irq(irq, (void *)tsf_info); |
| |
| tsf_info->irq_num = -EINVAL; |
| tsf_info->irq_handler = NULL; |
| tsf_info->context = NULL; |
| } |
| |
| static void cnss_sdio_set_platform_ops(struct device *dev) |
| { |
| struct cnss_dev_platform_ops *pf_ops = &cnss_pdata->platform_ops; |
| |
| pf_ops->power_up = cnss_sdio_power_up; |
| pf_ops->power_down = cnss_sdio_power_down; |
| pf_ops->device_crashed = cnss_sdio_device_crashed; |
| pf_ops->get_virt_ramdump_mem = cnss_sdio_get_virt_ramdump_mem; |
| pf_ops->device_self_recovery = cnss_sdio_device_self_recovery; |
| pf_ops->get_wlan_mac_address = cnss_sdio_get_wlan_mac_address; |
| pf_ops->set_wlan_mac_address = cnss_sdio_set_wlan_mac_address; |
| pf_ops->schedule_recovery_work = cnss_sdio_schedule_recovery_work; |
| pf_ops->request_bus_bandwidth = cnss_sdio_request_bus_bandwidth; |
| pf_ops->register_tsf_captured_handler = |
| cnss_sdio_register_tsf_captured_handler; |
| pf_ops->unregister_tsf_captured_handler = |
| cnss_sdio_unregister_tsf_captured_handler; |
| dev->platform_data = pf_ops; |
| } |
| |
| static int cnss_sdio_wlan_inserted(struct sdio_func *func, |
| const struct sdio_device_id *id) |
| { |
| struct cnss_sdio_info *info; |
| |
| if (!cnss_pdata) |
| return -ENODEV; |
| |
| info = &cnss_pdata->cnss_sdio_info; |
| |
| info->func = func; |
| info->card = func->card; |
| info->host = func->card->host; |
| info->id = id; |
| info->dev = &func->dev; |
| cnss_sdio_set_platform_ops(info->dev); |
| |
| cnss_put_hw_resources(cnss_pdata->cnss_sdio_info.dev); |
| |
| pr_info("SDIO Device is Probed\n"); |
| return 0; |
| } |
| |
| static void cnss_sdio_wlan_removed(struct sdio_func *func) |
| { |
| struct cnss_sdio_info *info; |
| |
| if (!cnss_pdata) |
| return; |
| |
| info = &cnss_pdata->cnss_sdio_info; |
| |
| info->host = NULL; |
| info->card = NULL; |
| info->func = NULL; |
| info->id = NULL; |
| } |
| |
| #if defined(CONFIG_PM) |
| static int cnss_sdio_wlan_suspend(struct device *dev) |
| { |
| struct cnss_sdio_wlan_driver *wdrv; |
| struct cnss_sdio_bus_bandwidth *bus_bandwidth; |
| struct sdio_func *func; |
| |
| int error = 0; |
| |
| if (!cnss_pdata) |
| return -ENODEV; |
| |
| bus_bandwidth = &cnss_pdata->bus_bandwidth; |
| if (bus_bandwidth->bus_client) { |
| msm_bus_scale_client_update_request( |
| bus_bandwidth->bus_client, CNSS_BUS_WIDTH_NONE); |
| } |
| |
| func = cnss_pdata->cnss_sdio_info.func; |
| wdrv = cnss_pdata->cnss_sdio_info.wdrv; |
| if (!wdrv) { |
| /* This can happen when no wlan driver loaded (no register to |
| * platform driver). |
| */ |
| sdio_set_host_pm_flags(func, MMC_PM_KEEP_POWER); |
| pr_debug("wlan driver not registered\n"); |
| return 0; |
| } |
| if (wdrv->suspend) { |
| error = wdrv->suspend(dev); |
| if (error) |
| pr_err("wlan suspend failed error=%d\n", error); |
| } |
| |
| return error; |
| } |
| |
| static int cnss_sdio_wlan_resume(struct device *dev) |
| { |
| struct cnss_sdio_wlan_driver *wdrv; |
| struct cnss_sdio_bus_bandwidth *bus_bandwidth; |
| int error = 0; |
| |
| if (!cnss_pdata) |
| return -ENODEV; |
| |
| bus_bandwidth = &cnss_pdata->bus_bandwidth; |
| if (bus_bandwidth->bus_client) { |
| msm_bus_scale_client_update_request( |
| bus_bandwidth->bus_client, |
| bus_bandwidth->current_bandwidth_vote); |
| } |
| |
| wdrv = cnss_pdata->cnss_sdio_info.wdrv; |
| if (!wdrv) { |
| /* This can happen when no wlan driver loaded (no register to |
| * platform driver). |
| */ |
| pr_debug("wlan driver not registered\n"); |
| return 0; |
| } |
| if (wdrv->resume) { |
| error = wdrv->resume(dev); |
| if (error) |
| pr_err("wlan resume failed error=%d\n", error); |
| } |
| return error; |
| } |
| #endif |
| |
| #if defined(CONFIG_PM) |
| static const struct dev_pm_ops cnss_ar6k_device_pm_ops = { |
| .suspend = cnss_sdio_wlan_suspend, |
| .resume = cnss_sdio_wlan_resume, |
| }; |
| #endif /* CONFIG_PM */ |
| |
| static struct sdio_driver cnss_ar6k_driver = { |
| .name = "cnss_ar6k_wlan", |
| .id_table = ar6k_id_table, |
| .probe = cnss_sdio_wlan_inserted, |
| .remove = cnss_sdio_wlan_removed, |
| #if defined(CONFIG_PM) |
| .drv = { |
| .pm = &cnss_ar6k_device_pm_ops, |
| } |
| #endif |
| }; |
| |
| static int cnss_set_pinctrl_state(struct cnss_sdio_data *pdata, bool state) |
| { |
| struct cnss_wlan_pinctrl_info *info = &pdata->pinctrl_info; |
| |
| if (!info->is_antenna_shared) |
| return 0; |
| |
| if (!info->pinctrl) |
| return -EIO; |
| |
| return state ? pinctrl_select_state(info->pinctrl, info->active) : |
| pinctrl_select_state(info->pinctrl, info->sleep); |
| } |
| |
| int cnss_sdio_configure_spdt(bool state) |
| { |
| if (!cnss_pdata) |
| return -ENODEV; |
| |
| return cnss_set_pinctrl_state(cnss_pdata, state); |
| } |
| EXPORT_SYMBOL(cnss_sdio_configure_spdt); |
| |
| /** |
| * cnss_sdio_wlan_register_driver() - cnss wlan register API |
| * @driver: sdio wlan driver interface from wlan driver. |
| * |
| * wlan sdio function driver uses this API to register callback |
| * functions to cnss_sido platform driver. The callback will |
| * be invoked by corresponding wrapper function of this cnss |
| * platform driver. |
| */ |
| int cnss_sdio_wlan_register_driver(struct cnss_sdio_wlan_driver *driver) |
| { |
| struct cnss_sdio_info *cnss_info; |
| struct device *dev; |
| int error = -EINVAL; |
| |
| if (!cnss_pdata) |
| return -ENODEV; |
| |
| cnss_info = &cnss_pdata->cnss_sdio_info; |
| dev = cnss_info->dev; |
| |
| if (cnss_info->wdrv) { |
| pr_debug("wdrv already existed\n"); |
| return error; |
| } |
| |
| if (!driver) |
| return error; |
| |
| error = cnss_get_hw_resources(dev); |
| if (error) { |
| pr_err("Failed to restore power err:%d\n", error); |
| return error; |
| } |
| |
| error = cnss_set_pinctrl_state(cnss_pdata, PINCTRL_ACTIVE); |
| if (error) { |
| pr_err("Fail to set pinctrl to active state\n"); |
| cnss_put_hw_resources(dev); |
| goto put_hw; |
| } |
| |
| /* The HW resources are released in unregister logic if probe fails */ |
| error = driver->probe ? driver->probe(cnss_info->func, |
| cnss_info->id) : error; |
| if (error) { |
| pr_err("wlan probe failed error=%d\n", error); |
| /** |
| * Check memory leak in skb pre-alloc memory pool |
| * Reset the skb memory pool |
| */ |
| goto pinctrl_sleep; |
| } |
| |
| cnss_info->wdrv = driver; |
| |
| return error; |
| |
| pinctrl_sleep: |
| cnss_set_pinctrl_state(cnss_pdata, PINCTRL_SLEEP); |
| put_hw: |
| return error; |
| } |
| EXPORT_SYMBOL(cnss_sdio_wlan_register_driver); |
| |
| /** |
| * cnss_sdio_wlan_unregister_driver() - cnss wlan unregister API |
| * @driver: sdio wlan driver interface from wlan driver. |
| * |
| * wlan sdio function driver uses this API to detach it from cnss_sido |
| * platform driver. |
| */ |
| void |
| cnss_sdio_wlan_unregister_driver(struct cnss_sdio_wlan_driver *driver) |
| { |
| struct cnss_sdio_info *cnss_info; |
| struct cnss_sdio_bus_bandwidth *bus_bandwidth; |
| |
| if (!cnss_pdata) |
| return; |
| |
| bus_bandwidth = &cnss_pdata->bus_bandwidth; |
| if (bus_bandwidth->bus_client) { |
| msm_bus_scale_client_update_request( |
| bus_bandwidth->bus_client, CNSS_BUS_WIDTH_NONE); |
| } |
| |
| cnss_info = &cnss_pdata->cnss_sdio_info; |
| if (!cnss_info->wdrv) { |
| pr_err("driver not registered\n"); |
| return; |
| } |
| |
| if (!driver) |
| return; |
| |
| if (!driver->remove) |
| return; |
| |
| driver->remove(cnss_info->func); |
| |
| cnss_info->wdrv = NULL; |
| cnss_set_pinctrl_state(cnss_pdata, PINCTRL_SLEEP); |
| cnss_put_hw_resources(cnss_info->dev); |
| } |
| EXPORT_SYMBOL(cnss_sdio_wlan_unregister_driver); |
| |
| /** |
| * cnss_wlan_query_oob_status() - cnss wlan query oob status API |
| * |
| * Wlan sdio function driver uses this API to check whether oob is |
| * supported in platform driver. |
| * |
| * Return: 0 means oob is supported, others means unsupported. |
| */ |
| int cnss_wlan_query_oob_status(void) |
| { |
| return -EINVAL; |
| } |
| EXPORT_SYMBOL(cnss_wlan_query_oob_status); |
| |
| /** |
| * cnss_wlan_register_oob_irq_handler() - cnss wlan register oob callback API |
| * @handler: oob callback function pointer which registered to platform driver. |
| * @pm_oob : parameter which registered to platform driver. |
| * |
| * Wlan sdio function driver uses this API to register oob callback |
| * function to platform driver. |
| * |
| * Return: 0 means register successfully, others means failure. |
| */ |
| int cnss_wlan_register_oob_irq_handler(oob_irq_handler_t handler, void *pm_oob) |
| { |
| return -EINVAL; |
| } |
| EXPORT_SYMBOL(cnss_wlan_register_oob_irq_handler); |
| |
| /** |
| * cnss_wlan_unregister_oob_irq_handler() - unregister oob callback API |
| * @pm_oob: parameter which unregistered from platform driver. |
| * |
| * Wlan sdio function driver uses this API to unregister oob callback |
| * function from platform driver. |
| * |
| * Return: 0 means unregister successfully, others means failure. |
| */ |
| int cnss_wlan_unregister_oob_irq_handler(void *pm_oob) |
| { |
| return -EINVAL; |
| } |
| EXPORT_SYMBOL(cnss_wlan_unregister_oob_irq_handler); |
| |
| static void cnss_sdio_reset_platform_ops(void) |
| { |
| struct cnss_dev_platform_ops *pf_ops = &cnss_pdata->platform_ops; |
| struct cnss_sdio_info *sdio_info = &cnss_pdata->cnss_sdio_info; |
| |
| memset(pf_ops, 0, sizeof(struct cnss_dev_platform_ops)); |
| if (sdio_info->dev) |
| sdio_info->dev->platform_data = NULL; |
| } |
| |
| static int cnss_sdio_wlan_init(void) |
| { |
| int error = 0; |
| |
| error = sdio_register_driver(&cnss_ar6k_driver); |
| if (error) { |
| cnss_sdio_reset_platform_ops(); |
| pr_err("registered fail error=%d\n", error); |
| } else { |
| pr_debug("registered success\n"); |
| } |
| |
| return error; |
| } |
| |
| static void cnss_sdio_wlan_exit(void) |
| { |
| if (!cnss_pdata) |
| return; |
| |
| cnss_sdio_reset_platform_ops(); |
| sdio_unregister_driver(&cnss_ar6k_driver); |
| } |
| |
| static void cnss_sdio_deinit_bus_bandwidth(void) |
| { |
| struct cnss_sdio_bus_bandwidth *bus_bandwidth; |
| |
| bus_bandwidth = &cnss_pdata->bus_bandwidth; |
| if (bus_bandwidth->bus_client) { |
| msm_bus_scale_client_update_request(bus_bandwidth->bus_client, |
| CNSS_BUS_WIDTH_NONE); |
| msm_bus_scale_unregister_client(bus_bandwidth->bus_client); |
| } |
| } |
| |
| static int cnss_sdio_configure_wlan_enable_regulator(void) |
| { |
| int error; |
| struct device *dev = &cnss_pdata->pdev->dev; |
| |
| if (of_get_property( |
| cnss_pdata->pdev->dev.of_node, |
| WLAN_VREG_NAME "-supply", NULL)) { |
| cnss_pdata->regulator.wlan_vreg = regulator_get( |
| &cnss_pdata->pdev->dev, WLAN_VREG_NAME); |
| if (IS_ERR(cnss_pdata->regulator.wlan_vreg)) { |
| error = PTR_ERR(cnss_pdata->regulator.wlan_vreg); |
| dev_err(dev, "VDD-VREG get failed error=%d\n", error); |
| return error; |
| } |
| |
| error = regulator_enable(cnss_pdata->regulator.wlan_vreg); |
| if (error) { |
| dev_err(dev, "VDD-VREG enable failed error=%d\n", |
| error); |
| goto err_vdd_vreg_regulator; |
| } |
| } |
| |
| return 0; |
| |
| err_vdd_vreg_regulator: |
| regulator_put(cnss_pdata->regulator.wlan_vreg); |
| |
| return error; |
| } |
| |
| static int cnss_sdio_configure_wlan_enable_dsrc_regulator(void) |
| { |
| int error; |
| struct device *dev = &cnss_pdata->pdev->dev; |
| |
| if (of_get_property( |
| cnss_pdata->pdev->dev.of_node, |
| WLAN_VREG_DSRC_NAME "-supply", NULL)) { |
| cnss_pdata->regulator.wlan_vreg_dsrc = regulator_get( |
| &cnss_pdata->pdev->dev, WLAN_VREG_DSRC_NAME); |
| if (IS_ERR(cnss_pdata->regulator.wlan_vreg_dsrc)) { |
| error = PTR_ERR(cnss_pdata->regulator.wlan_vreg_dsrc); |
| dev_err(dev, "VDD-VREG-DSRC get failed error=%d\n", |
| error); |
| return error; |
| } |
| |
| error = regulator_enable(cnss_pdata->regulator.wlan_vreg_dsrc); |
| if (error) { |
| dev_err(dev, "VDD-VREG-DSRC enable failed error=%d\n", |
| error); |
| goto err_vdd_vreg_dsrc_regulator; |
| } |
| } |
| |
| return 0; |
| |
| err_vdd_vreg_dsrc_regulator: |
| regulator_put(cnss_pdata->regulator.wlan_vreg_dsrc); |
| |
| return error; |
| } |
| |
| static int cnss_sdio_configure_regulator(void) |
| { |
| int error; |
| struct device *dev = &cnss_pdata->pdev->dev; |
| |
| if (of_get_property( |
| cnss_pdata->pdev->dev.of_node, |
| WLAN_VREG_IO_NAME "-supply", NULL)) { |
| cnss_pdata->regulator.wlan_io = regulator_get( |
| &cnss_pdata->pdev->dev, WLAN_VREG_IO_NAME); |
| if (IS_ERR(cnss_pdata->regulator.wlan_io)) { |
| error = PTR_ERR(cnss_pdata->regulator.wlan_io); |
| dev_err(dev, "VDD-IO get failed error=%d\n", error); |
| return error; |
| } |
| |
| error = regulator_set_voltage( |
| cnss_pdata->regulator.wlan_io, |
| WLAN_VREG_IO_MIN, WLAN_VREG_IO_MAX); |
| if (error) { |
| dev_err(dev, "VDD-IO set failed error=%d\n", error); |
| goto err_vdd_io_regulator; |
| } else { |
| error = regulator_enable(cnss_pdata->regulator.wlan_io); |
| if (error) { |
| dev_err(dev, "VDD-IO enable failed error=%d\n", |
| error); |
| goto err_vdd_io_regulator; |
| } |
| } |
| } |
| |
| if (of_get_property( |
| cnss_pdata->pdev->dev.of_node, |
| WLAN_VREG_XTAL_NAME "-supply", NULL)) { |
| cnss_pdata->regulator.wlan_xtal = regulator_get( |
| &cnss_pdata->pdev->dev, WLAN_VREG_XTAL_NAME); |
| if (IS_ERR(cnss_pdata->regulator.wlan_xtal)) { |
| error = PTR_ERR(cnss_pdata->regulator.wlan_xtal); |
| dev_err(dev, "VDD-XTAL get failed error=%d\n", error); |
| goto err_vdd_xtal_regulator; |
| } |
| |
| error = regulator_set_voltage( |
| cnss_pdata->regulator.wlan_xtal, |
| WLAN_VREG_XTAL_MIN, WLAN_VREG_XTAL_MAX); |
| if (error) { |
| dev_err(dev, "VDD-XTAL set failed error=%d\n", error); |
| goto err_vdd_xtal_regulator; |
| } else { |
| error = regulator_enable( |
| cnss_pdata->regulator.wlan_xtal); |
| if (error) { |
| dev_err(dev, "VDD-XTAL enable failed err=%d\n", |
| error); |
| goto err_vdd_xtal_regulator; |
| } |
| } |
| } |
| |
| return 0; |
| |
| err_vdd_xtal_regulator: |
| regulator_put(cnss_pdata->regulator.wlan_xtal); |
| err_vdd_io_regulator: |
| regulator_put(cnss_pdata->regulator.wlan_io); |
| return error; |
| } |
| |
| static void cnss_sdio_release_resource(void) |
| { |
| if (cnss_pdata->regulator.wlan_xtal) |
| regulator_put(cnss_pdata->regulator.wlan_xtal); |
| if (cnss_pdata->regulator.wlan_vreg) |
| regulator_put(cnss_pdata->regulator.wlan_vreg); |
| if (cnss_pdata->regulator.wlan_io) |
| regulator_put(cnss_pdata->regulator.wlan_io); |
| if (cnss_pdata->regulator.wlan_vreg_dsrc) |
| regulator_put(cnss_pdata->regulator.wlan_vreg_dsrc); |
| } |
| |
| static int cnss_sdio_pinctrl_init(struct cnss_sdio_data *pdata, |
| struct platform_device *pdev) |
| { |
| int ret = 0; |
| struct device *dev = &pdev->dev; |
| struct cnss_wlan_pinctrl_info *info = &pdata->pinctrl_info; |
| |
| if (!of_find_property(dev->of_node, "qcom,is-antenna-shared", NULL)) |
| return 0; |
| |
| info->is_antenna_shared = true; |
| info->pinctrl = devm_pinctrl_get(dev); |
| if ((IS_ERR_OR_NULL(info->pinctrl))) { |
| dev_err(dev, "%s: Failed to get pinctrl\n", __func__); |
| return PTR_ERR(info->pinctrl); |
| } |
| |
| info->sleep = pinctrl_lookup_state(info->pinctrl, |
| CNSS_PINCTRL_SLEEP_STATE); |
| if (IS_ERR_OR_NULL(info->sleep)) { |
| dev_err(dev, "%s: Fail to get sleep state for pin\n", __func__); |
| ret = PTR_ERR(info->sleep); |
| goto release_pinctrl; |
| } |
| |
| info->active = pinctrl_lookup_state(info->pinctrl, |
| CNSS_PINCTRL_ACTIVE_STATE); |
| if (IS_ERR_OR_NULL(info->active)) { |
| dev_err(dev, "%s: Fail to get active state for pin\n", |
| __func__); |
| ret = PTR_ERR(info->active); |
| goto release_pinctrl; |
| } |
| |
| ret = cnss_set_pinctrl_state(pdata, PINCTRL_SLEEP); |
| |
| if (ret) { |
| dev_err(dev, "%s: Fail to set pin in sleep state\n", __func__); |
| goto release_pinctrl; |
| } |
| |
| return ret; |
| |
| release_pinctrl: |
| devm_pinctrl_put(info->pinctrl); |
| info->is_antenna_shared = false; |
| return ret; |
| } |
| |
| static int cnss_sdio_init_bus_bandwidth(void) |
| { |
| int ret = 0; |
| struct cnss_sdio_bus_bandwidth *bus_bandwidth; |
| struct device *dev = &cnss_pdata->pdev->dev; |
| |
| bus_bandwidth = &cnss_pdata->bus_bandwidth; |
| bus_bandwidth->bus_scale_table = msm_bus_cl_get_pdata(cnss_pdata->pdev); |
| if (!bus_bandwidth->bus_scale_table) { |
| dev_err(dev, "Failed to get the bus scale platform data\n"); |
| ret = -EINVAL; |
| } |
| |
| bus_bandwidth->bus_client = msm_bus_scale_register_client( |
| bus_bandwidth->bus_scale_table); |
| if (!bus_bandwidth->bus_client) { |
| dev_err(dev, "Failed to register with bus_scale client\n"); |
| ret = -EINVAL; |
| } |
| |
| return ret; |
| } |
| |
| static int cnss_sdio_probe(struct platform_device *pdev) |
| { |
| int error; |
| struct device *dev = &pdev->dev; |
| struct cnss_sdio_info *info; |
| |
| if (pdev->dev.of_node) { |
| cnss_pdata = devm_kzalloc( |
| &pdev->dev, sizeof(*cnss_pdata), GFP_KERNEL); |
| if (!cnss_pdata) |
| return -ENOMEM; |
| } else { |
| cnss_pdata = pdev->dev.platform_data; |
| } |
| |
| if (!cnss_pdata) |
| return -EINVAL; |
| |
| cnss_pdata->pdev = pdev; |
| info = &cnss_pdata->cnss_sdio_info; |
| |
| error = cnss_sdio_pinctrl_init(cnss_pdata, pdev); |
| if (error) { |
| dev_err(&pdev->dev, "Fail to configure pinctrl err:%d\n", |
| error); |
| return error; |
| } |
| |
| error = cnss_sdio_configure_regulator(); |
| if (error) { |
| dev_err(&pdev->dev, "Failed to configure voltage regulator error=%d\n", |
| error); |
| return error; |
| } |
| |
| if (of_get_property( |
| cnss_pdata->pdev->dev.of_node, |
| WLAN_VREG_NAME "-supply", NULL)) { |
| error = cnss_sdio_configure_wlan_enable_regulator(); |
| if (error) { |
| dev_err(&pdev->dev, |
| "Failed to enable wlan enable regulator error=%d\n", |
| error); |
| goto err_wlan_enable_regulator; |
| } |
| } |
| |
| if (of_get_property( |
| cnss_pdata->pdev->dev.of_node, |
| WLAN_VREG_DSRC_NAME "-supply", NULL)) { |
| error = cnss_sdio_configure_wlan_enable_dsrc_regulator(); |
| if (error) { |
| dev_err(&pdev->dev, |
| "Failed to enable wlan dsrc enable regulator\n"); |
| goto err_wlan_dsrc_enable_regulator; |
| } |
| } |
| |
| info->skip_wlan_en_toggle = of_property_read_bool(dev->of_node, |
| "qcom,skip-wlan-en-toggle"); |
| info->cnss_hw_state = CNSS_HW_ACTIVE; |
| |
| cnss_sdio_tsf_init(dev, &info->cap_tsf_info); |
| |
| error = cnss_sdio_wlan_init(); |
| if (error) { |
| dev_err(&pdev->dev, "cnss wlan init failed error=%d\n", error); |
| goto err_wlan_dsrc_enable_regulator; |
| } |
| |
| error = cnss_configure_ramdump(); |
| if (error) { |
| dev_err(&pdev->dev, "Failed to configure ramdump error=%d\n", |
| error); |
| goto err_ramdump_create; |
| } |
| |
| error = cnss_subsys_init(); |
| if (error) { |
| dev_err(&pdev->dev, "Failed to cnss_subsys_init error=%d\n", |
| error); |
| goto err_subsys_init; |
| } |
| |
| if (of_property_read_bool( |
| pdev->dev.of_node, "qcom,cnss-enable-bus-bandwidth")) { |
| error = cnss_sdio_init_bus_bandwidth(); |
| if (error) { |
| dev_err(&pdev->dev, "Failed to init bus bandwidth\n"); |
| goto err_bus_bandwidth_init; |
| } |
| } |
| |
| dev_info(&pdev->dev, "CNSS SDIO Driver registered"); |
| return 0; |
| |
| err_bus_bandwidth_init: |
| cnss_subsys_exit(); |
| err_subsys_init: |
| cnss_ramdump_cleanup(); |
| err_ramdump_create: |
| cnss_sdio_wlan_exit(); |
| err_wlan_dsrc_enable_regulator: |
| info->cnss_hw_state = CNSS_HW_SLEEP; |
| regulator_put(cnss_pdata->regulator.wlan_vreg_dsrc); |
| err_wlan_enable_regulator: |
| regulator_put(cnss_pdata->regulator.wlan_xtal); |
| regulator_put(cnss_pdata->regulator.wlan_io); |
| cnss_pdata = NULL; |
| return error; |
| } |
| |
| static int cnss_sdio_remove(struct platform_device *pdev) |
| { |
| struct cnss_sdio_info *info; |
| struct cnss_cap_tsf_info *tsf_info; |
| |
| if (!cnss_pdata) |
| return -ENODEV; |
| |
| info = &cnss_pdata->cnss_sdio_info; |
| tsf_info = &info->cap_tsf_info; |
| |
| cnss_sdio_tsf_deinit(tsf_info); |
| cnss_sdio_deinit_bus_bandwidth(); |
| cnss_sdio_wlan_exit(); |
| cnss_subsys_exit(); |
| cnss_ramdump_cleanup(); |
| cnss_put_hw_resources(info->dev); |
| cnss_sdio_release_resource(); |
| cnss_pdata = NULL; |
| return 0; |
| } |
| |
| int cnss_sdio_set_wlan_mac_address(const u8 *in, u32 len) |
| { |
| return 0; |
| } |
| |
| u8 *cnss_sdio_get_wlan_mac_address(u32 *num) |
| { |
| *num = 0; |
| return NULL; |
| } |
| |
| int cnss_sdio_power_down(struct device *dev) |
| { |
| return 0; |
| } |
| |
| int cnss_sdio_power_up(struct device *dev) |
| { |
| return 0; |
| } |
| |
| static const struct of_device_id cnss_sdio_dt_match[] = { |
| {.compatible = "qcom,cnss_sdio"}, |
| {} |
| }; |
| MODULE_DEVICE_TABLE(of, cnss_sdio_dt_match); |
| |
| static struct platform_driver cnss_sdio_driver = { |
| .probe = cnss_sdio_probe, |
| .remove = cnss_sdio_remove, |
| .driver = { |
| .name = "cnss_sdio", |
| .owner = THIS_MODULE, |
| .of_match_table = cnss_sdio_dt_match, |
| }, |
| }; |
| |
| static int __init cnss_sdio_init(void) |
| { |
| return platform_driver_register(&cnss_sdio_driver); |
| } |
| |
| static void __exit cnss_sdio_exit(void) |
| { |
| platform_driver_unregister(&cnss_sdio_driver); |
| } |
| |
| module_init(cnss_sdio_init); |
| module_exit(cnss_sdio_exit); |
| |
| MODULE_LICENSE("GPL v2"); |
| MODULE_DESCRIPTION(DEVICE "CNSS SDIO Driver"); |