aboutsummaryrefslogtreecommitdiff
path: root/drivers/net/wireless/ath/wcn36xx/wcn36xx-msm.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/wireless/ath/wcn36xx/wcn36xx-msm.c')
-rw-r--r--drivers/net/wireless/ath/wcn36xx/wcn36xx-msm.c273
1 files changed, 273 insertions, 0 deletions
diff --git a/drivers/net/wireless/ath/wcn36xx/wcn36xx-msm.c b/drivers/net/wireless/ath/wcn36xx/wcn36xx-msm.c
new file mode 100644
index 000000000000..647cf026d37c
--- /dev/null
+++ b/drivers/net/wireless/ath/wcn36xx/wcn36xx-msm.c
@@ -0,0 +1,273 @@
+/*
+ * Copyright (c) 2013 Eugene Krasnikov <k.eugene.e@gmail.com>
+ * Copyright (c) 2013 Qualcomm Atheros, Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <linux/completion.h>
+#include <linux/firmware.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/workqueue.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/clk.h>
+#include <linux/remoteproc.h>
+#include <linux/soc/qcom/smd.h>
+#include "wcn36xx.h"
+#include "wcnss_core.h"
+
+#define MAC_ADDR_0 "wlan/macaddr0"
+
+static int wcn36xx_msm_smsm_change_state(u32 clear_mask, u32 set_mask)
+{
+ return 0;
+}
+
+static int wcn36xx_msm_get_hw_mac(struct wcn36xx *wcn, u8 *addr)
+{
+ const struct firmware *addr_file = NULL;
+ int status;
+ u8 tmp[18];
+ static const u8 qcom_oui[3] = {0x00, 0x0A, 0xF5};
+ static const char *files = {MAC_ADDR_0};
+ struct wcn36xx_platform_data *pdata = wcn->wcn36xx_data;
+
+ status = request_firmware(&addr_file, files, &pdata->core->dev);
+
+ if (status < 0) {
+ /* Assign a random mac with Qualcomm oui */
+ dev_err(&pdata->core->dev, "Failed (%d) to read macaddress"
+ "file %s, using a random address instead", status, files);
+ memcpy(addr, qcom_oui, 3);
+ get_random_bytes(addr + 3, 3);
+ } else {
+ memset(tmp, 0, sizeof(tmp));
+ memcpy(tmp, addr_file->data, sizeof(tmp) - 1);
+ sscanf(tmp, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
+ &addr[0],
+ &addr[1],
+ &addr[2],
+ &addr[3],
+ &addr[4],
+ &addr[5]);
+
+ release_firmware(addr_file);
+ }
+
+ return 0;
+}
+
+static int wcn36xx_msm_smd_send_and_wait(struct wcn36xx *wcn, char *buf, size_t len)
+{
+ int ret = 0;
+ struct wcn36xx_platform_data *pdata = wcn->wcn36xx_data;
+
+ mutex_lock(&pdata->wlan_ctrl_lock);
+ ret = qcom_smd_send(pdata->wlan_ctrl_channel, buf, len);
+ if (ret) {
+ dev_err(wcn->dev, "wlan ctrl channel tx failed\n");
+ }
+ mutex_unlock(&pdata->wlan_ctrl_lock);
+
+ return ret;
+}
+
+static int wcn36xx_msm_smd_open(struct wcn36xx *wcn, void *rsp_cb)
+{
+ struct wcn36xx_platform_data *pdata = wcn->wcn36xx_data;
+
+ pdata->cb = rsp_cb;
+ return 0;
+}
+
+static void wcn36xx_msm_smd_close(struct wcn36xx *wcn)
+{
+ return;
+}
+
+static int wcn36xx_msm_get_chip_type(struct wcn36xx *wcn)
+{
+ struct wcn36xx_platform_data *pdata = wcn->wcn36xx_data;
+ return pdata->chip_type;
+}
+
+static struct wcn36xx_platform_data wcn36xx_data = {
+ .ctrl_ops = {
+ .open = wcn36xx_msm_smd_open,
+ .close = wcn36xx_msm_smd_close,
+ .tx = wcn36xx_msm_smd_send_and_wait,
+ .get_hw_mac = wcn36xx_msm_get_hw_mac,
+ .smsm_change_state = wcn36xx_msm_smsm_change_state,
+ .get_chip_type = wcn36xx_msm_get_chip_type,
+ },
+};
+
+static int qcom_smd_wlan_ctrl_probe(struct qcom_smd_device *sdev)
+{
+ pr_info("%s: enter\n", __func__);
+ mutex_init(&wcn36xx_data.wlan_ctrl_lock);
+ init_completion(&wcn36xx_data.wlan_ctrl_ack);
+
+ wcn36xx_data.sdev = sdev;
+
+ dev_set_drvdata(&sdev->dev, &wcn36xx_data);
+ wcn36xx_data.wlan_ctrl_channel = sdev->channel;
+
+ of_platform_populate(sdev->dev.of_node, NULL, NULL, &sdev->dev);
+
+ return 0;
+}
+
+static void qcom_smd_wlan_ctrl_remove(struct qcom_smd_device *sdev)
+{
+ of_platform_depopulate(&sdev->dev);
+}
+
+static int qcom_smd_wlan_ctrl_callback(struct qcom_smd_device *qsdev,
+ const void *data,
+ size_t count)
+{
+ struct wcn36xx_platform_data *pdata = dev_get_drvdata(&qsdev->dev);
+ void *buf = kzalloc(count, GFP_ATOMIC);
+ if (!buf) {
+ dev_err(&pdata->core->dev, "can't allocate buffer\n");
+ return -ENOMEM;
+ }
+
+ memcpy_fromio(buf, data, count);
+ pdata->cb(pdata->wcn, buf, count);
+ kfree(buf);
+
+ return 0;
+}
+
+static const struct of_device_id qcom_smd_wlan_ctrl_of_match[] = {
+ { .compatible = "qcom,wlan-ctrl" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, qcom_smd_wlan_ctrl_of_match);
+
+static struct qcom_smd_driver qcom_smd_wlan_ctrl_driver = {
+ .probe = qcom_smd_wlan_ctrl_probe,
+ .remove = qcom_smd_wlan_ctrl_remove,
+ .callback = qcom_smd_wlan_ctrl_callback,
+ .driver = {
+ .name = "qcom_smd_wlan_ctrl",
+ .owner = THIS_MODULE,
+ .of_match_table = qcom_smd_wlan_ctrl_of_match,
+ },
+};
+
+static const struct of_device_id wcn36xx_msm_match_table[] = {
+ { .compatible = "qcom,wcn3660", .data = (void *)WCN36XX_CHIP_3660 },
+ { .compatible = "qcom,wcn3680", .data = (void *)WCN36XX_CHIP_3680 },
+ { .compatible = "qcom,wcn3620", .data = (void *)WCN36XX_CHIP_3620 },
+ { }
+};
+MODULE_DEVICE_TABLE(of, wcn36xx_msm_match_table);
+
+static int wcn36xx_msm_probe(struct platform_device *pdev)
+{
+ int ret;
+ const struct of_device_id *of_id;
+ struct resource *r;
+ struct resource res[3];
+ static const char const *rnames[] = {
+ "wcnss_mmio", "wcnss_wlantx_irq", "wcnss_wlanrx_irq" };
+ static const int rtype[] = {
+ IORESOURCE_MEM, IORESOURCE_IRQ, IORESOURCE_IRQ };
+ struct device_node *dn;
+ int n;
+
+ wcnss_core_prepare(pdev);
+
+ dn = of_parse_phandle(pdev->dev.of_node, "rproc", 0);
+ if (!dn) {
+ dev_err(&pdev->dev, "No rproc specified in DT\n");
+ } else {
+ struct rproc *rproc= rproc_get_by_phandle(dn->phandle);
+ if (rproc)
+ rproc_boot(rproc);
+ else {
+ dev_err(&pdev->dev, "No rproc registered\n");
+ }
+ }
+
+ qcom_smd_driver_register(&qcom_smd_wlan_ctrl_driver);
+ wcnss_core_init();
+
+ of_id = of_match_node(wcn36xx_msm_match_table, pdev->dev.of_node);
+ if (!of_id)
+ return -EINVAL;
+
+ wcn36xx_data.chip_type = (enum wcn36xx_chip_type)of_id->data;
+
+ wcn36xx_data.core = platform_device_alloc("wcn36xx", -1);
+
+ for (n = 0; n < ARRAY_SIZE(rnames); n++) {
+ r = platform_get_resource_byname(pdev, rtype[n], rnames[n]);
+ if (!r) {
+ dev_err(&wcn36xx_data.core->dev,
+ "Missing resource %s'\n", rnames[n]);
+ ret = -ENOMEM;
+ return ret;
+ }
+ res[n] = *r;
+ }
+
+ platform_device_add_resources(wcn36xx_data.core, res, n);
+ wcn36xx_data.core->dev.platform_data = &wcn36xx_data;
+
+ platform_device_add(wcn36xx_data.core);
+
+ dev_info(&pdev->dev, "%s initialized\n", __func__);
+
+ return 0;
+}
+
+static int wcn36xx_msm_remove(struct platform_device *pdev)
+{
+ platform_device_del(wcn36xx_data.core);
+ platform_device_put(wcn36xx_data.core);
+ return 0;
+}
+
+static struct platform_driver wcn36xx_msm_driver = {
+ .probe = wcn36xx_msm_probe,
+ .remove = wcn36xx_msm_remove,
+ .driver = {
+ .name = "wcn36xx-msm",
+ .owner = THIS_MODULE,
+ .of_match_table = wcn36xx_msm_match_table,
+ },
+};
+
+static int __init wcn36xx_msm_init(void)
+{
+ return platform_driver_register(&wcn36xx_msm_driver);
+}
+module_init(wcn36xx_msm_init);
+
+static void __exit wcn36xx_msm_exit(void)
+{
+ platform_driver_unregister(&wcn36xx_msm_driver);
+}
+module_exit(wcn36xx_msm_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Eugene Krasnikov k.eugene.e@gmail.com");
+MODULE_FIRMWARE(MAC_ADDR_0);
+