From 9ba9a18604e329936a4438382f8a2fe274923ff6 Mon Sep 17 00:00:00 2001 From: Hamad Kadmany Date: Sat, 6 Feb 2016 13:43:06 +0200 Subject: msm_11ad: Add 11ad platform driver Platform driver used to handle msm specific platform requirement for 11ad chipset connected to msm platform Takes care of platform support like: - power switch through dedicated GPIO - bus frequency voting - SMMU attachment Change-Id: I09c54ea747a5b4e0688b1b7d96e83ef134bb4215 Signed-off-by: Hamad Kadmany Signed-off-by: Maya Erez [merez@codeaurora.org: fix merge conflicts] Signed-off-by: Maya Erez --- .../devicetree/bindings/arm/msm/wil6210.txt | 1 + drivers/platform/msm/Kconfig | 12 + drivers/platform/msm/Makefile | 1 + drivers/platform/msm/msm_11ad/Makefile | 9 + drivers/platform/msm/msm_11ad/msm_11ad.c | 426 +++++++++++++++++++++ drivers/platform/msm/msm_11ad/msm_11ad.h | 27 ++ 6 files changed, 476 insertions(+) create mode 100644 drivers/platform/msm/msm_11ad/Makefile create mode 100644 drivers/platform/msm/msm_11ad/msm_11ad.c create mode 100644 drivers/platform/msm/msm_11ad/msm_11ad.h diff --git a/Documentation/devicetree/bindings/arm/msm/wil6210.txt b/Documentation/devicetree/bindings/arm/msm/wil6210.txt index 599edf47a463..30642691dcb1 100644 --- a/Documentation/devicetree/bindings/arm/msm/wil6210.txt +++ b/Documentation/devicetree/bindings/arm/msm/wil6210.txt @@ -11,6 +11,7 @@ Required properties: - compatible: "qcom,wil6210" - qcom,smmu-support: Boolean flag indicating whether PCIe has SMMU support - qcom,pcie-parent: phandle for the PCIe root complex to which 11ad card is connected +- qcom,wigig-en: Enable GPIO connected to 11ad card - Refer to "Documentation/devicetree/bindings/arm/msm/msm_bus.txt" for the below optional properties: - qcom,msm-bus,name diff --git a/drivers/platform/msm/Kconfig b/drivers/platform/msm/Kconfig index 0daa88715111..a91552d33514 100644 --- a/drivers/platform/msm/Kconfig +++ b/drivers/platform/msm/Kconfig @@ -78,4 +78,16 @@ config GPIO_USB_DETECT USB driver of VBUS presence/disconnection using the power_supply framework. +config MSM_11AD + tristate "Platform driver for 11ad chip" + depends on PCI + depends on PCI_MSM + default n + ---help--- + This module adds required platform support for wireless adapter based on + Qualcomm Technologies, Inc. 11ad chip, integrated into MSM platform + + If you choose to build it as a module, it will be called + msm_11ad_proxy. + endmenu diff --git a/drivers/platform/msm/Makefile b/drivers/platform/msm/Makefile index ef84024ab474..299fc6f38c26 100644 --- a/drivers/platform/msm/Makefile +++ b/drivers/platform/msm/Makefile @@ -6,3 +6,4 @@ obj-$(CONFIG_QPNP_COINCELL) += qpnp-coincell.o obj-$(CONFIG_SPS) += sps/ obj-$(CONFIG_EP_PCIE) += ep_pcie/ obj-$(CONFIG_GPIO_USB_DETECT) += gpio-usbdetect.o +obj-$(CONFIG_MSM_11AD) += msm_11ad/ diff --git a/drivers/platform/msm/msm_11ad/Makefile b/drivers/platform/msm/msm_11ad/Makefile new file mode 100644 index 000000000000..477104540987 --- /dev/null +++ b/drivers/platform/msm/msm_11ad/Makefile @@ -0,0 +1,9 @@ +obj-$(CONFIG_MSM_11AD) += msm_11ad_proxy.o + +msm_11ad_proxy-y := msm_11ad.o +subdir-ccflags-y += -D__CHECK_ENDIAN__ + +# need to locate wil_platform.h +WIL_11AD_PATH = drivers/net/wireless/ath/wil6210 +subdir-ccflags-y += -I$(WIL_11AD_PATH) + diff --git a/drivers/platform/msm/msm_11ad/msm_11ad.c b/drivers/platform/msm/msm_11ad/msm_11ad.c new file mode 100644 index 000000000000..ac7b4dabd80b --- /dev/null +++ b/drivers/platform/msm/msm_11ad/msm_11ad.c @@ -0,0 +1,426 @@ +/* Copyright (c) 2015, 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "wil_platform.h" +#include "msm_11ad.h" + +#define WIGIG_VENDOR (0x1ae9) +#define WIGIG_DEVICE (0x0310) + +#define SMMU_BASE 0x10000000 /* Device address range base */ +#define SMMU_SIZE 0x40000000 /* Device address range size */ + +struct device; + +static const char * const gpio_en_name = "qcom,wigig-en"; + +struct msm11ad_ctx { + struct list_head list; + struct device *dev; /* for platform device */ + int gpio_en; /* card enable */ + + /* pci device */ + u32 rc_index; /* PCIE root complex index */ + struct pci_dev *pcidev; + struct pci_saved_state *pristine_state; + + /* SMMU */ + bool use_smmu; /* have SMMU enabled? */ + struct dma_iommu_mapping *mapping; + + /* bus frequency scaling */ + struct msm_bus_scale_pdata *bus_scale; + u32 msm_bus_handle; +}; + +static LIST_HEAD(dev_list); + +static struct msm11ad_ctx *pcidev2ctx(struct pci_dev *pcidev) +{ + struct msm11ad_ctx *ctx; + + list_for_each_entry(ctx, &dev_list, list) { + if (ctx->pcidev == pcidev) + return ctx; + } + return NULL; +} + +static int ops_suspend(void *handle) +{ + int rc; + struct msm11ad_ctx *ctx = handle; + struct pci_dev *pcidev; + + pr_info("%s(%p)\n", __func__, handle); + if (!ctx) { + pr_err("No context\n"); + return -ENODEV; + } + pcidev = ctx->pcidev; + rc = pci_save_state(pcidev); + if (rc) { + dev_err(ctx->dev, "pci_save_state failed :%d\n", rc); + return rc; + } + rc = msm_pcie_pm_control(MSM_PCIE_SUSPEND, pcidev->bus->number, + pcidev, NULL, 0); + if (rc) { + dev_err(ctx->dev, "msm_pcie_pm_control(SUSPEND) failed :%d\n", + rc); + return rc; + } + gpio_direction_output(ctx->gpio_en, 0); + return rc; +} + +static int ops_resume(void *handle) +{ + int rc; + struct msm11ad_ctx *ctx = handle; + struct pci_dev *pcidev; + + pr_info("%s(%p)\n", __func__, handle); + if (!ctx) { + pr_err("No context\n"); + return -ENODEV; + } + + pcidev = ctx->pcidev; + gpio_direction_output(ctx->gpio_en, 1); + rc = msm_pcie_pm_control(MSM_PCIE_RESUME, pcidev->bus->number, + pcidev, NULL, 0); + if (rc) { + dev_err(ctx->dev, "msm_pcie_pm_control(RESUME) failed :%d\n", + rc); + return rc; + } + pci_restore_state(pcidev); + + return rc; +} + +static int msm_11ad_smmu_init(struct msm11ad_ctx *ctx) +{ + int disable_htw = 1; + int rc; + + if (!ctx->use_smmu) + return 0; + + ctx->mapping = arm_iommu_create_mapping(&platform_bus_type, + SMMU_BASE, SMMU_SIZE, 0); + if (IS_ERR_OR_NULL(ctx->mapping)) { + rc = PTR_ERR(ctx->mapping) ?: -ENODEV; + dev_err(ctx->dev, "Failed to create IOMMU mapping (%d)\n", rc); + return rc; + } + dev_info(ctx->dev, "IOMMU mapping created: %p\n", ctx->mapping); + + rc = iommu_domain_set_attr(ctx->mapping->domain, + DOMAIN_ATTR_COHERENT_HTW_DISABLE, + &disable_htw); + if (rc) { + /* This error can be ignored and not considered fatal, + * but let the users know this happened + */ + dev_err(ctx->dev, "Warning: disable coherent HTW failed (%d)\n", + rc); + } + + rc = arm_iommu_attach_device(&ctx->pcidev->dev, ctx->mapping); + if (rc) { + dev_err(ctx->dev, "arm_iommu_attach_device failed (%d)\n", rc); + goto release_mapping; + } + dev_info(ctx->dev, "attached to IOMMU\n"); + + return 0; +release_mapping: + arm_iommu_release_mapping(ctx->mapping); + ctx->mapping = NULL; + return rc; +} + +static int msm_11ad_probe(struct platform_device *pdev) +{ + struct msm11ad_ctx *ctx; + struct device *dev = &pdev->dev; + struct device_node *of_node = dev->of_node; + struct device_node *rc_node; + struct pci_dev *pcidev = NULL; + int rc; + + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + ctx->dev = dev; + + /*== parse ==*/ + + /* Information pieces: + * - of_node stands for "wil6210": + * wil6210: qcom,wil6210 { + * compatible = "qcom,wil6210"; + * qcom,pcie-parent = <&pcie1>; + * qcom,wigig-en = <&tlmm 94 0>; (ctx->gpio_en) + * qcom,msm-bus,name = "wil6210"; + * qcom,msm-bus,num-cases = <2>; + * qcom,msm-bus,num-paths = <1>; + * qcom,msm-bus,vectors-KBps = + * <100 512 0 0>, + * <100 512 600000 800000>; + * qcom,smmu-support; + *}; + * rc_node stands for "qcom,pcie", selected entries: + * cell-index = <1>; (ctx->rc_index) + * iommus = <&anoc0_smmu>; + * qcom,smmu-exist; + */ + ctx->gpio_en = of_get_named_gpio(of_node, gpio_en_name, 0); + if (ctx->gpio_en < 0) { + dev_err(ctx->dev, "GPIO <%s> not found\n", gpio_en_name); + return ctx->gpio_en; + } + rc_node = of_parse_phandle(of_node, "qcom,pcie-parent", 0); + if (!rc_node) { + dev_err(ctx->dev, "Parent PCIE device not found\n"); + return -EINVAL; + } + rc = of_property_read_u32(rc_node, "cell-index", &ctx->rc_index); + if (rc < 0) { + dev_err(ctx->dev, "Parent PCIE device index not found\n"); + return -EINVAL; + } + ctx->use_smmu = of_property_read_bool(of_node, "qcom,smmu-support"); + ctx->bus_scale = msm_bus_cl_get_pdata(pdev); + + /*== execute ==*/ + /* turn device on */ + rc = gpio_request(ctx->gpio_en, gpio_en_name); + if (rc < 0) { + dev_err(ctx->dev, "failed to request GPIO %d <%s>\n", + ctx->gpio_en, gpio_en_name); + goto out_req; + } + rc = gpio_direction_output(ctx->gpio_en, 1); + if (rc < 0) { + dev_err(ctx->dev, "failed to set GPIO %d <%s>\n", ctx->gpio_en, + gpio_en_name); + goto out_set; + } + /* enumerate it on PCIE */ + rc = msm_pcie_enumerate(ctx->rc_index); + if (rc < 0) { + dev_err(ctx->dev, "Parent PCIE enumeration failed\n"); + goto out_rc; + } + /* search for PCIE device in our domain */ + do { + pcidev = pci_get_device(WIGIG_VENDOR, WIGIG_DEVICE, pcidev); + if (!pcidev) + break; + + if (pci_domain_nr(pcidev->bus) == ctx->rc_index) + break; + } while (true); + if (!pcidev) { + rc = -ENODEV; + dev_err(ctx->dev, "Wigig device %4x:%4x not found\n", + WIGIG_VENDOR, WIGIG_DEVICE); + goto out_rc; + } + ctx->pcidev = pcidev; + rc = pci_save_state(pcidev); + if (rc) { + dev_err(ctx->dev, "pci_save_state failed :%d\n", rc); + goto out_rc; + } + ctx->pristine_state = pci_store_saved_state(pcidev); + + /* report */ + dev_info(ctx->dev, "msm_11ad discovered. %p {\n" + " gpio_en = %d\n" + " rc_index = %d\n" + " use_smmu = %d\n" + " pcidev = %p\n" + "}\n", ctx, ctx->gpio_en, ctx->rc_index, ctx->use_smmu, + ctx->pcidev); + + platform_set_drvdata(pdev, ctx); + + list_add_tail(&ctx->list, &dev_list); + ops_suspend(ctx); + + return 0; +out_rc: + gpio_direction_output(ctx->gpio_en, 0); +out_set: + gpio_free(ctx->gpio_en); +out_req: + ctx->gpio_en = -EINVAL; + return rc; +} + +static int msm_11ad_remove(struct platform_device *pdev) +{ + struct msm11ad_ctx *ctx = platform_get_drvdata(pdev); + + list_del(&ctx->list); + dev_info(ctx->dev, "%s: pdev %p pcidev %p\n", __func__, pdev, + ctx->pcidev); + kfree(ctx->pristine_state); + + msm_bus_cl_clear_pdata(ctx->bus_scale); + pci_dev_put(ctx->pcidev); + gpio_direction_output(ctx->gpio_en, 0); + gpio_free(ctx->gpio_en); + return 0; +} + +static const struct of_device_id msm_11ad_of_match[] = { + { .compatible = "qcom,wil6210", }, + {}, +}; + +static struct platform_driver msm_11ad_driver = { + .driver = { + .name = "msm_11ad", + .of_match_table = msm_11ad_of_match, + }, + .probe = msm_11ad_probe, + .remove = msm_11ad_remove, +}; +module_platform_driver(msm_11ad_driver); + +/* hooks for the wil6210 driver */ +static int ops_bus_request(void *handle, u32 kbps /* KBytes/Sec */) +{ + struct msm11ad_ctx *ctx = (struct msm11ad_ctx *)handle; + int rc, i; + int vote = 0; /* vote 0 in case requested kbps cannot be satisfied */ + struct msm_bus_paths *usecase; + u32 usecase_kbps; + u32 min_kbps = ~0; + + /* find the lowest usecase that is bigger than requested kbps */ + for (i = 0; i < ctx->bus_scale->num_usecases; i++) { + usecase = &ctx->bus_scale->usecase[i]; + /* assume we have single path (vectors[0]). If we ever + * have multiple paths, need to define the behavior */ + usecase_kbps = div64_u64(usecase->vectors[0].ib, 1000); + if (usecase_kbps >= kbps && usecase_kbps < min_kbps) { + min_kbps = usecase_kbps; + vote = i; + } + } + + rc = msm_bus_scale_client_update_request(ctx->msm_bus_handle, vote); + if (rc) + dev_err(ctx->dev, + "Failed msm_bus voting. kbps=%d vote=%d, rc=%d\n", + kbps, vote, rc); + + return rc; +} + +static void ops_uninit(void *handle) +{ + struct msm11ad_ctx *ctx = (struct msm11ad_ctx *)handle; + + if (ctx->msm_bus_handle) { + msm_bus_scale_unregister_client(ctx->msm_bus_handle); + ctx->msm_bus_handle = 0; + } + + if (ctx->use_smmu) { + arm_iommu_detach_device(&ctx->pcidev->dev); + arm_iommu_release_mapping(ctx->mapping); + ctx->mapping = NULL; + } + ops_suspend(ctx); +} + +void *msm_11ad_dev_init(struct device *dev, struct wil_platform_ops *ops) +{ + struct pci_dev *pcidev = to_pci_dev(dev); + struct msm11ad_ctx *ctx = pcidev2ctx(pcidev); + + if (!ctx) { + pr_err("Context not found for pcidev %p\n", pcidev); + return NULL; + } + + /* bus scale */ + ctx->msm_bus_handle = + msm_bus_scale_register_client(ctx->bus_scale); + if (!ctx->msm_bus_handle) { + dev_err(ctx->dev, "Failed msm_bus registration\n"); + return NULL; + } + dev_info(ctx->dev, "msm_bus handle 0x%x\n", ctx->msm_bus_handle); + /* smmu */ + if (msm_11ad_smmu_init(ctx)) { + msm_bus_scale_unregister_client(ctx->msm_bus_handle); + ctx->msm_bus_handle = 0; + return NULL; + } + + /* fill ops */ + memset(ops, 0, sizeof(*ops)); + ops->bus_request = ops_bus_request; + ops->suspend = ops_suspend; + ops->resume = ops_resume; + ops->uninit = ops_uninit; + + return ctx; +} +EXPORT_SYMBOL(msm_11ad_dev_init); + +int msm_11ad_modinit(void) +{ + struct msm11ad_ctx *ctx = list_first_entry_or_null(&dev_list, + struct msm11ad_ctx, + list); + + if (ctx->pristine_state) { + /* in old kernels, pci_load_saved_state() is not exported; + * so use pci_load_and_free_saved_state() + * and re-allocate ctx->saved_state again + */ + pci_load_and_free_saved_state(ctx->pcidev, + &ctx->pristine_state); + ctx->pristine_state = pci_store_saved_state(ctx->pcidev); + } + + return ops_resume(ctx); +} +EXPORT_SYMBOL(msm_11ad_modinit); + +void msm_11ad_modexit(void) +{ +} +EXPORT_SYMBOL(msm_11ad_modexit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Platform driver for qcom 11ad card"); + diff --git a/drivers/platform/msm/msm_11ad/msm_11ad.h b/drivers/platform/msm/msm_11ad/msm_11ad.h new file mode 100644 index 000000000000..455cdb9935cf --- /dev/null +++ b/drivers/platform/msm/msm_11ad/msm_11ad.h @@ -0,0 +1,27 @@ +/* Copyright (c) 2015, 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. + */ +#ifndef __MSM_11AD_H__ +#define __MSM_11AD_H__ + +struct device; +struct wil_platform_ops; + +/* call when binding to device, during probe() */ +void *msm_11ad_dev_init(struct device *dev, struct wil_platform_ops *ops); + +/* call on insmod */ +int msm_11ad_modinit(void); + +/* call on rmmod */ +void msm_11ad_modexit(void); + +#endif /* __MSM_11AD_H__ */ -- cgit v1.2.3