Re: [PATCH] soc: qcom: Introduce WCNSS_CTRL SMD client

From: yfw
Date: Tue Sep 22 2015 - 14:03:30 EST


On Mon, Sep 21, 2015 at 10:52:55AM -0700, Bjorn Andersson wrote:
> The WCNSS_CTRL SMD client is used for among other things upload nv
> firmware to a newly booted WCNSS chip.
>
> Signed-off-by: Bjorn Andersson <bjorn.andersson@xxxxxxxxxxxxxx>
> ---
> This driver probes on the WCNSS_CTRL SMD channel as it comes up upon loading
> the wcnss firmware, it currenly request version information from the wcnss and
> downloads the nv binary.
>
> This is needed for bringing up the individual functions of the wcnss chip.
>
> drivers/soc/qcom/Kconfig | 7 ++
> drivers/soc/qcom/Makefile | 1 +
> drivers/soc/qcom/wcnss_ctrl.c | 272 ++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 280 insertions(+)
> create mode 100644 drivers/soc/qcom/wcnss_ctrl.c
>
> diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
> index ba47b70f4d85..453ceb1af682 100644
> --- a/drivers/soc/qcom/Kconfig
> +++ b/drivers/soc/qcom/Kconfig
> @@ -48,3 +48,10 @@ config QCOM_SMEM
> Say y here to enable support for the Qualcomm Shared Memory Manager.
> The driver provides an interface to items in a heap shared among all
> processors in a Qualcomm platform.
> +
> +config QCOM_WCNSS_CTRL
> + tristate "Qualcomm WCNSS control driver"
> + depends on QCOM_SMD
> + help
> + Client driver for the WCNSS_CTRL SMD channel, used to download nv
> + firmware to a newly booted WCNSS chip.
> diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
> index 10a93d168e0e..9823103ea843 100644
> --- a/drivers/soc/qcom/Makefile
> +++ b/drivers/soc/qcom/Makefile
> @@ -3,3 +3,4 @@ obj-$(CONFIG_QCOM_PM) += spm.o
> obj-$(CONFIG_QCOM_SMD) += smd.o
> obj-$(CONFIG_QCOM_SMD_RPM) += smd-rpm.o
> obj-$(CONFIG_QCOM_SMEM) += smem.o
> +obj-$(CONFIG_QCOM_WCNSS_CTRL) += wcnss_ctrl.o
> diff --git a/drivers/soc/qcom/wcnss_ctrl.c b/drivers/soc/qcom/wcnss_ctrl.c
> new file mode 100644
> index 000000000000..7a986f881d5c
> --- /dev/null
> +++ b/drivers/soc/qcom/wcnss_ctrl.c
> @@ -0,0 +1,272 @@
> +/*
> + * Copyright (c) 2015, Sony Mobile Communications Inc.
> + *
> + * 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/firmware.h>
> +#include <linux/module.h>
> +#include <linux/slab.h>
> +#include <linux/soc/qcom/smd.h>
> +
> +#define WCNSS_REQUEST_TIMEOUT (5 * HZ)
> +
> +#define NV_FRAGMENT_SIZE 3072
> +#define NVBIN_FILE "wlan/prima/WCNSS_qcom_wlan_nv.bin"
> +
> +/**
> + * struct wcnss_ctrl - driver context
> + * @dev: device handle
> + * @channel: SMD channel handle
> + * @ack: completion for outstanding requests
> + * @ack_status: status of the outstanding request
> + * @download_nv_work: worker for uploading nv binary
> + */
> +struct wcnss_ctrl {
> + struct device *dev;
> + struct qcom_smd_channel *channel;
> +
> + struct completion ack;
> + int ack_status;
> +
> + struct work_struct download_nv_work;
> +};
> +
> +/* message types */
> +enum {
> + WCNSS_VERSION_REQ = 0x01000000,
> + WCNSS_VERSION_RESP,
> + WCNSS_DOWNLOAD_NV_REQ,
> + WCNSS_DOWNLOAD_NV_RESP,
> + WCNSS_UPLOAD_CAL_REQ,
> + WCNSS_UPLOAD_CAL_RESP,
> + WCNSS_DOWNLOAD_CAL_REQ,
> + WCNSS_DOWNLOAD_CAL_RESP,
> +};
> +
> +/**
> + * struct wcnss_msg_hdr - common packet header for requests and responses
> + * @type: packet message type
> + * @len: total length of the packet, including this header
> + */
> +struct wcnss_msg_hdr {
> + u32 type;
> + u32 len;
> +} __packed;
> +
> +/**
> + * struct wcnss_version_resp - version request response
> + * @hdr: common packet wcnss_msg_hdr header
> + */
> +struct wcnss_version_resp {
> + struct wcnss_msg_hdr hdr;
> + u8 major;
> + u8 minor;
> + u8 version;
> + u8 revision;
> +} __packed;
> +
> +/**
> + * struct wcnss_download_nv_req - firmware fragment request
> + * @hdr: common packet wcnss_msg_hdr header
> + * @seq: sequence number of this fragment
> + * @last: boolean indicator of this being the last fragment of the binary
> + * @frag_size: length of this fragment
> + * @fragment: fragment data
> + */
> +struct wcnss_download_nv_req {
> + struct wcnss_msg_hdr hdr;
> + u16 seq;
> + u16 last;
> + u32 frag_size;
> + u8 fragment[];
> +} __packed;
> +
> +/**
> + * struct wcnss_download_nv_resp - firmware download response
> + * @hdr: common packet wcnss_msg_hdr header
> + * @status: boolean to indicate success of the download
> + */
> +struct wcnss_download_nv_resp {
> + struct wcnss_msg_hdr hdr;
> + u8 status;
> +} __packed;
> +
> +/**
> + * wcnss_ctrl_smd_callback() - handler from SMD responses
> + * @qsdev: smd device handle
> + * @data: pointer to the incoming data packet
> + * @count: size of the incoming data packet
> + *
> + * Handles any incoming packets from the remote WCNSS_CTRL service.
> + */
> +static int wcnss_ctrl_smd_callback(struct qcom_smd_device *qsdev,
> + const void *data,
> + size_t count)
> +{
> + struct wcnss_ctrl *wcnss = dev_get_drvdata(&qsdev->dev);
> + const struct wcnss_download_nv_resp *nvresp;
> + const struct wcnss_version_resp *version;
> + const struct wcnss_msg_hdr *hdr = data;
> +
> + switch (hdr->type) {
> + case WCNSS_VERSION_RESP:
> + if (count != sizeof(*version)) {
> + dev_err(wcnss->dev,
> + "invalid size of version response\n");
> + break;
> + }
> +
> + version = data;
> + dev_info(wcnss->dev, "WCNSS Version %d.%d %d.%d\n",
> + version->major, version->minor,
> + version->version, version->revision);
> +
> + schedule_work(&wcnss->download_nv_work);
> + break;
> + case WCNSS_DOWNLOAD_NV_RESP:
> + if (count != sizeof(*nvresp)) {
> + dev_err(wcnss->dev,
> + "invalid size of download response\n");
> + break;
> + }
> +
> + nvresp = data;
> + wcnss->ack_status = nvresp->status;
> + complete(&wcnss->ack);
> + break;
> + default:
> + dev_info(wcnss->dev, "unknown message type %d\n", hdr->type);
> + break;
> + }
> +
> + return 0;
> +}
> +
> +/**
> + * wcnss_request_version() - send a version request to WCNSS
> + * @wcnss: wcnss ctrl driver context
> + */
> +static int wcnss_request_version(struct wcnss_ctrl *wcnss)
> +{
> + struct wcnss_msg_hdr msg;
> +
> + msg.type = WCNSS_VERSION_REQ;
> + msg.len = sizeof(msg);
> +
> + return qcom_smd_send(wcnss->channel, &msg, sizeof(msg));
> +}
> +
> +/**
> + * wcnss_download_nv() - send nv binary to WCNSS
> + * @work: work struct to acquire wcnss context
> + */
> +static void wcnss_download_nv(struct work_struct *work)
> +{
> + struct wcnss_ctrl *wcnss = container_of(work, struct wcnss_ctrl, download_nv_work);
> + struct wcnss_download_nv_req *req;
> + const struct firmware *fw;
> + const void *data;
> + ssize_t left;
> + int ret;
> +
> + req = kzalloc(sizeof(*req) + NV_FRAGMENT_SIZE, GFP_KERNEL);
> + if (!req)
> + return;
> +
> + ret = request_firmware(&fw, NVBIN_FILE, wcnss->dev);
> + if (ret) {
> + dev_err(wcnss->dev, "Failed to load nv file %s: %d\n",
> + NVBIN_FILE, ret);
> + goto free_req;
> + }
> +
> + data = fw->data;
> + left = fw->size;
> +
> + req->hdr.type = WCNSS_DOWNLOAD_NV_REQ;
> + req->hdr.len = sizeof(*req) + NV_FRAGMENT_SIZE;
> +
> + req->last = 0;
> + req->frag_size = NV_FRAGMENT_SIZE;
> +
> + req->seq = 0;
> + do {
> + if (left <= NV_FRAGMENT_SIZE) {
> + req->last = 1;
> + req->frag_size = left;
> + req->hdr.len = sizeof(*req) + left;
> + }
> +
> + memcpy(req->fragment, data, req->frag_size);
> +
> + ret = qcom_smd_send(wcnss->channel, req, req->hdr.len);
> + if (ret) {
> + dev_err(wcnss->dev, "failed to send smd packet\n");
> + goto release_fw;
> + }
> +
> + /* Increment for next fragment */
> + req->seq++;
> +
> + data += req->hdr.len;
> + left -= NV_FRAGMENT_SIZE;
> + } while (left > 0);
> +
> + ret = wait_for_completion_timeout(&wcnss->ack, WCNSS_REQUEST_TIMEOUT);
> + if (!ret)
> + dev_err(wcnss->dev, "timeout waiting for nv upload ack\n");
> + else if (wcnss->ack_status != 1)
> + dev_err(wcnss->dev, "nv upload response failed err: %d\n",
> + wcnss->ack_status);
> +
> +release_fw:
> + release_firmware(fw);
> +free_req:
> + kfree(req);
> +}
> +
> +static int wcnss_ctrl_probe(struct qcom_smd_device *sdev)
> +{
> + struct wcnss_ctrl *wcnss;
> +
> + wcnss = devm_kzalloc(&sdev->dev, sizeof(*wcnss), GFP_KERNEL);
> + if (!wcnss)
> + return -ENOMEM;
> +
> + wcnss->dev = &sdev->dev;
> + wcnss->channel = sdev->channel;
> +
> + init_completion(&wcnss->ack);
> + INIT_WORK(&wcnss->download_nv_work, wcnss_download_nv);
> +
> + dev_set_drvdata(&sdev->dev, wcnss);
> +
> + return wcnss_request_version(wcnss);
> +}
> +
> +static const struct qcom_smd_id wcnss_ctrl_smd_match[] = {
> + { .name = "WCNSS_CTRL" },
> + {}
> +};
> +
> +static struct qcom_smd_driver wcnss_ctrl_driver = {
> + .probe = wcnss_ctrl_probe,
> + .callback = wcnss_ctrl_smd_callback,
> + .smd_match_table = wcnss_ctrl_smd_match,
> + .driver = {
> + .name = "qcom_wcnss_ctrl",
> + .owner = THIS_MODULE,
> + },
> +};
> +
> +module_qcom_smd_driver(wcnss_ctrl_driver);
> +
> +MODULE_DESCRIPTION("Qualcomm WCNSS control driver");
> +MODULE_LICENSE("GPL v2");
> --
> 1.8.2.2
>

Hi Bjorn,
Looks good to me.

Regards
Yin, Fengwei

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/