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