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

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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-soc" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Linux Samsung SoC]     [Linux Rockchip SoC]     [Linux Actions SoC]     [Linux for Synopsys ARC Processors]     [Linux NFS]     [Linux NILFS]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]


  Powered by Linux