summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHamad Kadmany <hkadmany@codeaurora.org>2016-02-06 13:43:06 +0200
committerDavid Keitel <dkeitel@codeaurora.org>2016-03-23 19:57:49 -0700
commit9ba9a18604e329936a4438382f8a2fe274923ff6 (patch)
tree03e9bfd72432a14dab26136a6467fcd8bf6782c7
parent28a5c53fcb2e79f94feab8f78303d6d6be249846 (diff)
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 <hkadmany@codeaurora.org> Signed-off-by: Maya Erez <merez@codeaurora.org> [merez@codeaurora.org: fix merge conflicts] Signed-off-by: Maya Erez <merez@codeaurora.org>
-rw-r--r--Documentation/devicetree/bindings/arm/msm/wil6210.txt1
-rw-r--r--drivers/platform/msm/Kconfig12
-rw-r--r--drivers/platform/msm/Makefile1
-rw-r--r--drivers/platform/msm/msm_11ad/Makefile9
-rw-r--r--drivers/platform/msm/msm_11ad/msm_11ad.c426
-rw-r--r--drivers/platform/msm/msm_11ad/msm_11ad.h27
6 files changed, 476 insertions, 0 deletions
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 <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/msm_pcie.h>
+#include <asm/dma-iommu.h>
+#include <linux/msm-bus.h>
+#include <linux/iommu.h>
+#include <linux/version.h>
+#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__ */