Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d41f8b55 authored by Hamad Kadmany's avatar Hamad Kadmany
Browse files

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: default avatarHamad Kadmany <hkadmany@codeaurora.org>
parent fd854d74
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -10,6 +10,8 @@ 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
@@ -21,6 +23,8 @@ Example:
	wil6210: qcom,wil6210 {
		compatible = "qcom,wil6210";
		qcom,smmu-support;
		qcom,pcie-parent = <&pcie1>;
		qcom,wigig-en = <&tlmm 94 0>;
		qcom,msm-bus,name = "wil6210";
		qcom,msm-bus,num-cases = <2>;
		qcom,msm-bus,num-paths = <1>;
+12 −0
Original line number Diff line number Diff line
@@ -156,6 +156,18 @@ config QPNP_HAPTIC
	  on the Qualcomm Technologies' QPNP PMICs. It uses the android
	  timed-output 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.

source "drivers/platform/msm/spmi/Kconfig"

endmenu
+1 −0
Original line number Diff line number Diff line
@@ -15,3 +15,4 @@ obj-$(CONFIG_IPA) += ipa/
obj-$(CONFIG_QPNP_HAPTIC) += qpnp-haptic.o
obj-$(CONFIG_USB_BAM) += usb_bam.o
obj-$(CONFIG_MSM_AVTIMER) += avtimer.o
obj-$(CONFIG_MSM_11AD) += msm_11ad/
+9 −0
Original line number Diff line number Diff line
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)
+426 −0
Original line number Diff line number Diff line
/* 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");
Loading