Message ID | 1442857975-30258-1-git-send-email-bjorn.andersson@sonymobile.com (mailing list archive) |
---|---|
State | Accepted, archived |
Delegated to: | Andy Gross |
Headers | show |
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@sonymobile.com> > --- > 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-arm-msm" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
Hi Bjorn, On 2015/9/22 1:52, 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@sonymobile.com> > --- > 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"); > I have a question: Do you have plan to add the nob to trigger wcnss firmware downloading which is also common for wifi and BT? Regards Yin, Fengwei -- To unsubscribe from this list: send the line "unsubscribe linux-arm-msm" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
On Thu 22 Oct 03:25 PDT 2015, yfw wrote: > Hi Bjorn, > > On 2015/9/22 1:52, Bjorn Andersson wrote: [..] > > I have a question: Do you have plan to add the nob to trigger wcnss firmware > downloading which is also common for wifi and BT? > In caf the wcnss driver is actually two drivers intermingled; * a SMD client driver, responsible for pushing NV, something related to calibration, some power properties and so on * a platform_driver implementing the wcnss specifics of the PIL through some hooks and providing the knob to trigger the PIL. The first driver is related to the "OS" running on the wcnss, so that should follow the life cycle of the SMD channel "WCNSS_CTRL". This is what this patch provides - it loads the NV every time the wcnss core is booted. For the second part, I strongly believe that the PIL implementation should deal with the specifics (e.g. regulator handling and xo_calibration), rather than having a piece bolted on elsewhere - so that's in the remoteproc-wcnss driver. Left is a mechanism to trigger the thing to boot and shutdown. One potential solution would be to have the module_init/exit call rproc_boot/shutdown from the WiFi & BT drivers. That way if one loads the wcn36xx driver, the core is booted. This would also fit quite nicely for other things - e.g. load the ALSA driver to trigger the ADSP loading. The problem here is that we're then forced to either have a method of deferring the rproc_boot() until the firmware is available or we always must compile these drivers as kernel modules. This because the file system isn't there during boot to provide the firmware. We do have the same thing in e.g. the Broadcom WiFi/BT solution and there seems to be discussions related to this. So for now, I punted and put a knob in the wcnss remoteproc driver. Regards, Bjorn -- To unsubscribe from this list: send the line "unsubscribe linux-arm-msm" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
Hi Bjorn, On 2015/10/22 21:50, Bjorn Andersson wrote: > On Thu 22 Oct 03:25 PDT 2015, yfw wrote: > >> Hi Bjorn, >> >> On 2015/9/22 1:52, Bjorn Andersson wrote: > [..] >> >> I have a question: Do you have plan to add the nob to trigger wcnss firmware >> downloading which is also common for wifi and BT? >> > > In caf the wcnss driver is actually two drivers intermingled; > * a SMD client driver, responsible for pushing NV, something related to > calibration, some power properties and so on > > * a platform_driver implementing the wcnss specifics of the PIL through > some hooks and providing the knob to trigger the PIL. > > The first driver is related to the "OS" running on the wcnss, so that > should follow the life cycle of the SMD channel "WCNSS_CTRL". This is > what this patch provides - it loads the NV every time the wcnss core is > booted. > > > For the second part, I strongly believe that the PIL implementation > should deal with the specifics (e.g. regulator handling and > xo_calibration), rather than having a piece bolted on elsewhere - so > that's in the remoteproc-wcnss driver. > Yes. I meant this remoteproc-wcnss driver. Will you try to upstream it? > > Left is a mechanism to trigger the thing to boot and shutdown. One > potential solution would be to have the module_init/exit call > rproc_boot/shutdown from the WiFi & BT drivers. That way if one loads > the wcn36xx driver, the core is booted. This would also fit quite nicely > for other things - e.g. load the ALSA driver to trigger the ADSP > loading. > > The problem here is that we're then forced to either have a method of > deferring the rproc_boot() until the firmware is available or we always > must compile these drivers as kernel modules. This because the > file system isn't there during boot to provide the firmware. The firmware file could be put to initrd. That should allow wcn wifi driver builtin. > > We do have the same thing in e.g. the Broadcom WiFi/BT solution and > there seems to be discussions related to this. > > > So for now, I punted and put a knob in the wcnss remoteproc driver. > > Regards, > Bjorn > Regards Yin, Fengwei -- To unsubscribe from this list: send the line "unsubscribe linux-arm-msm" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
On Fri, Oct 23, 2015 at 4:37 AM, yfw <fengwei.yin@linaro.org> wrote: [..] > Yes. I meant this remoteproc-wcnss driver. Will you try to upstream it? > https://lkml.org/lkml/2015/9/26/10 I thought I got you on the Cc on that series, sorry about that. I will resend it once we have smsm/smp2p so that the kbuild robot doesn't have a chance to shoot it down. (But no planned functional change so far) >> >> Left is a mechanism to trigger the thing to boot and shutdown. One >> potential solution would be to have the module_init/exit call >> rproc_boot/shutdown from the WiFi & BT drivers. That way if one loads >> the wcn36xx driver, the core is booted. This would also fit quite nicely >> for other things - e.g. load the ALSA driver to trigger the ADSP >> loading. >> >> The problem here is that we're then forced to either have a method of >> deferring the rproc_boot() until the firmware is available or we always >> must compile these drivers as kernel modules. This because the >> file system isn't there during boot to provide the firmware. > > The firmware file could be put to initrd. That should allow wcn wifi driver > builtin. > We have the same puzzle to solve for adsp, venus and the modem. And the combined size of all these makes it practically infeasible to do so. Regards, Bjorn -- To unsubscribe from this list: send the line "unsubscribe linux-arm-msm" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
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");
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@sonymobile.com> --- 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