]> git.proxmox.com Git - mirror_ubuntu-artful-kernel.git/commitdiff
Got workable wireless driver.
authorYin, Fengwei <fengwei.yin@linaro.org>
Fri, 17 Apr 2015 03:48:24 +0000 (11:48 +0800)
committerKhalid Elmously <khalid.elmously@canonical.com>
Wed, 14 Mar 2018 02:42:09 +0000 (02:42 +0000)
Signed-off-by: Yin, Fengwei <fengwei.yin@linaro.org>
drivers/net/wireless/ath/wcn36xx/Makefile
drivers/net/wireless/ath/wcn36xx/wcn36xx-msm.c
drivers/net/wireless/ath/wcn36xx/wcnss_core.c [new file with mode: 0644]
drivers/net/wireless/ath/wcn36xx/wcnss_core.h [new file with mode: 0644]

index e889f2c3019900e263dddcc32c5ece72106136e8..9f6370f0cabc199234128a9693f586d0202beeaf 100644 (file)
@@ -1,7 +1,10 @@
-obj-$(CONFIG_WCN36XX) := wcn36xx.o wcn36xx-msm.o
+obj-$(CONFIG_WCN36XX) := wcn36xx.o wcn36xx-platform.o
 wcn36xx-y +=   main.o \
                dxe.o \
                txrx.o \
                smd.o \
                pmc.o \
                debug.o
+
+wcn36xx-platform-y     += wcn36xx-msm.o\
+       wcnss_core.o
index c9250e0e9902371813439ebf8b473dd4b7af1a83..db2393a459e6162daebed807473991a5e097264b 100644 (file)
 #include <linux/firmware.h>
 #include <linux/module.h>
 #include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/regulator/rpm-smd-regulator.h>
 #include <linux/workqueue.h>
+#include <linux/of.h>
+#include <linux/clk.h>
 #include <soc/qcom/smd.h>
 #include <soc/qcom/smsm.h>
 #include "wcn36xx.h"
+#include "wcnss_core.h"
 
 #include <soc/qcom/subsystem_restart.h>
 #include <soc/qcom/subsystem_notif.h>
@@ -274,6 +279,8 @@ static int wcn36xx_msm_probe(struct platform_device *pdev)
        if (ret)
                return ret;
 
+       wcnss_core_prepare(pdev);
+
        if (IS_ERR_OR_NULL(pil))
                pil = subsystem_get("wcnss");
        if (IS_ERR_OR_NULL(pil))
@@ -303,15 +310,18 @@ static int wcn36xx_msm_probe(struct platform_device *pdev)
        }
 
        platform_device_add(wmsm.core);
+       wcnss_core_init();
 
        dev_info(&pdev->dev, "%s initialized\n", __func__);
 
        return 0;
 }
+
 static int wcn36xx_msm_remove(struct platform_device *pdev)
 {
         struct pinctrl_state *ps;
 
+       wcnss_core_deinit();
        platform_device_del(wmsm.core);
        platform_device_put(wmsm.core);
 
@@ -349,8 +359,6 @@ static void __exit wcn36xx_msm_exit(void)
        platform_driver_unregister(&wcn36xx_msm_driver);
        if (pil)
                subsystem_put(pil);
-
-
 }
 module_exit(wcn36xx_msm_exit);
 
diff --git a/drivers/net/wireless/ath/wcn36xx/wcnss_core.c b/drivers/net/wireless/ath/wcn36xx/wcnss_core.c
new file mode 100644 (file)
index 0000000..66e3144
--- /dev/null
@@ -0,0 +1,313 @@
+#include <linux/completion.h>
+#include <linux/firmware.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/regulator/rpm-smd-regulator.h>
+#include <linux/workqueue.h>
+#include <linux/of.h>
+#include <linux/clk.h>
+#include <soc/qcom/smd.h>
+#include <soc/qcom/smsm.h>
+#include "wcn36xx.h"
+#include "wcnss_core.h"
+
+static int wcnss_core_config(struct platform_device *pdev, void __iomem *base)
+{
+       int ret = 0;
+       u32 value;
+
+       value = readl_relaxed(base + SPARE_OFFSET);
+       value |= WCNSS_FW_DOWNLOAD_ENABLE;
+       writel_relaxed(value, base + SPARE_OFFSET);
+
+       writel_relaxed(0, base + PMU_OFFSET);
+       value = readl_relaxed(base + PMU_OFFSET);
+       value |= WCNSS_PMU_CFG_GC_BUS_MUX_SEL_TOP |
+                       WCNSS_PMU_CFG_IRIS_XO_EN;
+       writel_relaxed(value, base + PMU_OFFSET);
+
+       value &= ~(WCNSS_PMU_CFG_IRIS_XO_MODE);
+       value |= WCNSS_PMU_CFG_IRIS_XO_MODE_48;
+
+       writel_relaxed(value, base + PMU_OFFSET);
+
+       /* Reset IRIS */
+       value |= WCNSS_PMU_CFG_IRIS_RESET;
+       writel_relaxed(value, base + PMU_OFFSET);
+
+       while (readl_relaxed(base + PMU_OFFSET) &
+                       WCNSS_PMU_CFG_IRIS_RESET_STS)
+               cpu_relax();
+
+       /* reset IRIS reset bit */
+       value &= ~WCNSS_PMU_CFG_IRIS_RESET;
+       writel_relaxed(value, base + PMU_OFFSET);
+
+       /* start IRIS XO configuration */
+       value |= WCNSS_PMU_CFG_IRIS_XO_CFG;
+       writel_relaxed(value, base + PMU_OFFSET);
+
+       /* Wait for XO configuration to finish */
+       while (readl_relaxed(base + PMU_OFFSET) &
+                       WCNSS_PMU_CFG_IRIS_XO_CFG_STS)
+               cpu_relax();
+
+       /* Stop IRIS XO configuration */
+       value &= ~(WCNSS_PMU_CFG_GC_BUS_MUX_SEL_TOP |
+                       WCNSS_PMU_CFG_IRIS_XO_CFG);
+       writel_relaxed(value, base + PMU_OFFSET);
+
+        msleep(200);
+
+       return ret;
+}
+
+int wcnss_core_prepare(struct platform_device *pdev)
+{
+       int ret = 0;
+       struct resource *res;
+       void __iomem *wcnss_base;
+
+       res = platform_get_resource_byname(pdev,
+                        IORESOURCE_MEM, "pronto_phy_base");
+       if (!res) {
+               ret = -EIO;
+               dev_err(&pdev->dev, "resource pronto_phy_base failed\n");
+               return ret;
+       }
+
+       wcnss_base = devm_ioremap_resource(&pdev->dev, res);
+       if (IS_ERR(wcnss_base)) {
+               dev_err(&pdev->dev, "pronto_phy_base map failed\n");
+               return PTR_ERR(wcnss_base);
+       }
+
+       ret = wcnss_core_config(pdev, wcnss_base);
+       return ret;
+}
+
+static struct wcn36xx_ctrl_nv_data ctrl_nv_data;
+static void wcn36xx_download_notify(void *data, unsigned int event)
+{
+       struct wcn36xx_ctrl_nv_data *ctrl_nv_data = data;
+
+       switch (event) {
+       case SMD_EVENT_OPEN:
+               complete(&ctrl_nv_data->smd_open_compl);
+               schedule_work(&ctrl_nv_data->download_work);
+               break;
+       case SMD_EVENT_DATA:
+               schedule_work(&ctrl_nv_data->rx_work);
+               break;
+       case SMD_EVENT_CLOSE:
+       case SMD_EVENT_STATUS:
+       case SMD_EVENT_REOPEN_READY:
+               break;
+       default:
+               pr_err("%s: SMD_EVENT (%d) not supported\n",
+                       __func__, event);
+               break;
+       }
+}
+
+static unsigned char wcnss_fw_status(struct wcn36xx_ctrl_nv_data *data)
+{
+       int len = 0;
+       int rc = 0;
+
+       unsigned char fw_status = 0xFF;
+
+       len = smd_read_avail(data->smd_ch);
+       if (len < 1) {
+               pr_err("%s: invalid firmware status", __func__);
+               return fw_status;
+       }
+
+       rc = smd_read(data->smd_ch, &fw_status, 1);
+       if (rc < 0) {
+               pr_err("%s: incomplete data read from smd\n", __func__);
+               return fw_status;
+       }
+       return fw_status;
+}
+
+static void wcn36xx_nv_rx_work(struct work_struct *worker)
+{
+       struct wcn36xx_ctrl_nv_data *data =
+               container_of(worker, struct wcn36xx_ctrl_nv_data, rx_work);
+       int len = 0, ret = 0;
+       unsigned char buf[sizeof(struct wcnss_version)];
+       struct smd_msg_hdr *phdr;
+
+       len = smd_read_avail(data->smd_ch);
+       if (len > 4096) {
+               pr_err("%s: frame larger than allowed size\n", __func__);
+               smd_read(data->smd_ch, NULL, len);
+               return;
+       }
+
+       if (len < sizeof(struct smd_msg_hdr))
+               return;
+
+       ret = smd_read(data->smd_ch, buf, sizeof(struct smd_msg_hdr));
+       if (ret < sizeof(struct smd_msg_hdr)) {
+               pr_err("%s: incomplete header from smd\n", __func__);
+               return;
+       }
+
+       phdr = (struct smd_msg_hdr *)buf;
+
+       switch (phdr->msg_type) {
+       case WCNSS_NV_DOWNLOAD_RSP:
+               pr_info("fw_status: %d\n", wcnss_fw_status(data));
+               break;
+       }
+       return;
+}
+
+static int wcn36xx_nv_smd_tx(struct wcn36xx_ctrl_nv_data *nv_data, void *buf, int len)
+{
+       int ret = 0;
+
+       ret = smd_write_avail(nv_data->smd_ch);
+       if (ret < len) {
+               pr_err("wcnss: no space available. %d needed. Just %d avail\n",
+                       len, ret);
+               return -ENOSPC;
+       }
+       ret = smd_write(nv_data->smd_ch, buf, len);
+       if (ret < len) {
+               pr_err("wcnss: failed to write Command %d", len);
+               ret = -ENODEV;
+       }
+       return ret;
+}
+
+#define        NV_FILE_NAME    "wlan/prima/WCNSS_qcom_wlan_nv.bin"
+static void wcn36xx_nv_download_work(struct work_struct *worker)
+{
+       int ret = 0, i, retry = 3;
+       const struct firmware *nv = NULL;
+       struct wcn36xx_ctrl_nv_data *data =
+               container_of(worker, struct wcn36xx_ctrl_nv_data, download_work);
+       struct device *dev = &data->pdev->dev;
+       struct nvbin_dnld_req_msg *msg;
+       const void *nv_blob_start;
+       char *pkt = NULL;
+       int nv_blob_size = 0, fragments;
+
+       ret = request_firmware(&nv, NV_FILE_NAME, dev);
+       if (ret || !nv || !nv->data || !nv->size) {
+               dev_err(dev, "request firmware for %s (ret = %d)\n",
+                       NV_FILE_NAME, ret);
+               return;
+       }
+
+       nv_blob_start = nv->data + 4;
+       nv_blob_size = nv->size -4;
+
+       fragments = (nv_blob_size + NV_FRAGMENT_SIZE - 1)/NV_FRAGMENT_SIZE;
+
+       pkt = kzalloc(sizeof(struct nvbin_dnld_req_msg) + NV_FRAGMENT_SIZE,
+                       GFP_KERNEL);
+       if (!pkt) {
+               dev_err(dev, "allocation packet for nv download failed\n");
+               release_firmware(nv);
+       }
+
+       msg = (struct nvbin_dnld_req_msg *)pkt;
+       msg->hdr.msg_type = WCNSS_NV_DOWNLOAD_REQ;
+       msg->dnld_req_params.msg_flags = 0;
+
+       i = 0;
+       do {
+               int pkt_len = 0;
+
+               msg->dnld_req_params.frag_number = i;
+               if (nv_blob_size > NV_FRAGMENT_SIZE) {
+                       msg->dnld_req_params.msg_flags &=
+                               ~LAST_FRAGMENT;
+                       pkt_len = NV_FRAGMENT_SIZE;
+               } else {
+                       pkt_len = nv_blob_size;
+                       msg->dnld_req_params.msg_flags |=
+                               LAST_FRAGMENT | CAN_RECEIVE_CALDATA;
+               }
+
+               msg->dnld_req_params.nvbin_buffer_size = pkt_len;
+               msg->hdr.msg_len =
+                       sizeof(struct nvbin_dnld_req_msg) + pkt_len;
+
+               memcpy(pkt + sizeof(struct nvbin_dnld_req_msg),
+                       nv_blob_start + i * NV_FRAGMENT_SIZE, pkt_len);
+
+               ret = wcn36xx_nv_smd_tx(data, pkt, msg->hdr.msg_len);
+
+               while ((ret == -ENOSPC) && (retry++ <= 3)) {
+                       dev_err(dev, "smd_tx failed, %d times retry\n", retry);
+                       msleep(100);
+                       ret = wcn36xx_nv_smd_tx(data, pkt, msg->hdr.msg_len);
+               }
+
+               if (ret < 0) {
+                       dev_err(dev, "nv download failed\n");
+                       goto out;
+               }
+               i++;
+               nv_blob_size -= NV_FRAGMENT_SIZE;
+               msleep(100);
+       } while (nv_blob_size > 0);
+
+out:
+       kfree(pkt);
+       release_firmware(nv);
+       return;
+}
+
+static int wcnss_ctrl_remove(struct platform_device *pdev)
+{
+       smd_close(ctrl_nv_data.smd_ch);
+
+       return 0;
+}
+
+static int wcnss_ctrl_probe(struct platform_device *pdev)
+{
+       int ret = 0;
+
+       INIT_WORK(&ctrl_nv_data.rx_work, wcn36xx_nv_rx_work);
+       INIT_WORK(&ctrl_nv_data.download_work, wcn36xx_nv_download_work);
+       init_completion(&ctrl_nv_data.smd_open_compl);
+       ctrl_nv_data.pdev = pdev;
+
+       ret = smd_named_open_on_edge("WCNSS_CTRL", SMD_APPS_WCNSS,
+               &ctrl_nv_data.smd_ch, &ctrl_nv_data, wcn36xx_download_notify);
+       if (ret) {
+               dev_err(&pdev->dev, "wcnss_ctrl open failed\n");
+               return ret;
+       }
+       smd_disable_read_intr(ctrl_nv_data.smd_ch);
+       return ret;
+}
+
+/* platform device for WCNSS_CTRL SMD channel */
+static struct platform_driver wcnss_ctrl_driver = {
+       .driver = {
+               .name   = "WCNSS_CTRL",
+               .owner  = THIS_MODULE,
+       },
+       .probe  = wcnss_ctrl_probe,
+       .remove = wcnss_ctrl_remove,
+};
+
+void wcnss_core_init(void)
+{
+       platform_driver_register(&wcnss_ctrl_driver);
+}
+
+void wcnss_core_deinit(void)
+{
+       platform_driver_unregister(&wcnss_ctrl_driver);
+}
+
diff --git a/drivers/net/wireless/ath/wcn36xx/wcnss_core.h b/drivers/net/wireless/ath/wcn36xx/wcnss_core.h
new file mode 100644 (file)
index 0000000..d81fc47
--- /dev/null
@@ -0,0 +1,100 @@
+#ifndef        _WCNSS_CORE_H_
+#define        _WCNSS_CORE_H_
+
+#define        PMU_OFFSET      0x1004
+#define        SPARE_OFFSET    0x1088
+
+#define WCNSS_PMU_CFG_IRIS_XO_CFG          BIT(3)
+#define WCNSS_PMU_CFG_IRIS_XO_EN           BIT(4)
+#define WCNSS_PMU_CFG_GC_BUS_MUX_SEL_TOP   BIT(5)
+#define WCNSS_PMU_CFG_IRIS_XO_CFG_STS      BIT(6) /* 1: in progress, 0: done */
+
+#define WCNSS_PMU_CFG_IRIS_RESET           BIT(7)
+#define WCNSS_PMU_CFG_IRIS_RESET_STS       BIT(8) /* 1: in progress, 0: done */
+#define WCNSS_PMU_CFG_IRIS_XO_READ         BIT(9)
+#define WCNSS_PMU_CFG_IRIS_XO_READ_STS     BIT(10)
+#define        WCNSS_FW_DOWNLOAD_ENABLE           BIT(25)
+
+#define WCNSS_PMU_CFG_IRIS_XO_MODE         0x6
+#define WCNSS_PMU_CFG_IRIS_XO_MODE_48      (3 << 1)
+
+#define        NV_DOWNLOAD_TIMEOUT     500
+#define        NV_FRAGMENT_SIZE        3072
+#define MAX_CALIBRATED_DATA_SIZE  (64*1024)
+#define LAST_FRAGMENT        (1 << 0)
+#define MESSAGE_TO_FOLLOW    (1 << 1)
+#define CAN_RECEIVE_CALDATA  (1 << 15)
+#define WCNSS_RESP_SUCCESS   1
+#define WCNSS_RESP_FAIL      0
+
+
+#define        WCNSS_NV_DOWNLOAD_REQ   0x01000002
+#define        WCNSS_NV_DOWNLOAD_RSP   0x01000003
+
+struct wcn36xx_ctrl_nv_data {
+       struct workqueue_struct *wq;
+       struct work_struct      rx_work;
+       struct work_struct      download_work;
+       struct completion       smd_open_compl;
+       smd_channel_t           *smd_ch;
+       struct platform_device  *pdev;
+};
+
+struct smd_msg_hdr {
+       unsigned int msg_type;
+       unsigned int msg_len;
+};
+
+struct nvbin_dnld_req_params {
+       /* Fragment sequence number of the NV bin Image. NV Bin Image
+        * might not fit into one message due to size limitation of
+        * the SMD channel FIFO so entire NV blob is chopped into
+        * multiple fragments starting with seqeunce number 0. The
+        * last fragment is indicated by marking is_last_fragment field
+        * to 1. At receiving side, NV blobs would be concatenated
+        * together without any padding bytes in between.
+        */
+       unsigned short frag_number;
+
+       /* bit 0: When set to 1 it indicates that no more fragments will
+        * be sent.
+        * bit 1: When set, a new message will be followed by this message
+        * bit 2- bit 14:  Reserved
+        * bit 15: when set, it indicates that the sender is capable of
+        * receiving Calibrated data.
+        */
+       unsigned short msg_flags;
+
+       /* NV Image size (number of bytes) */
+       unsigned int nvbin_buffer_size;
+
+       /* Following the 'nvbin_buffer_size', there should be
+        * nvbin_buffer_size bytes of NV bin Image i.e.
+        * uint8[nvbin_buffer_size].
+        */
+};
+
+struct nvbin_dnld_req_msg {
+       /* Note: The length specified in nvbin_dnld_req_msg messages
+        * should be hdr.msg_len = sizeof(nvbin_dnld_req_msg) +
+        * nvbin_buffer_size.
+        */
+       struct smd_msg_hdr hdr;
+       struct nvbin_dnld_req_params dnld_req_params;
+};
+
+struct wcnss_version {
+       struct smd_msg_hdr hdr;
+       unsigned char  major;
+       unsigned char  minor;
+       unsigned char  version;
+       unsigned char  revision;
+};
+
+
+int wcnss_core_prepare(struct platform_device *pdev);
+void wcnss_core_init(void);
+void wcnss_core_deinit(void);
+
+#endif
+