Loading Documentation/devicetree/bindings/pil/pil-femto-modem.txt 0 → 100644 +92 −0 Original line number Diff line number Diff line Qualcomm Femtocell (FSM99XX) Peripheral Image Loader pil-femto-modem is a peripheral image loader (PIL) driver. It is used for loading firmware images on multiple modems resident on the FSM99XX platform. The top-level device tree entry has the following properties: Required properties: - compatible: Must be "qcom,pil-femto-modem". - reg: Pair of physical base address and region size of the QDSP6[0] control registers. - reg-names: "qdsp6_base" is required. - qcom,firmware-name: Base name of the MBA firmware image ("mba"). Maximum size is 4 characters. - qcom,max-num-modems: Number of modem child nodes specified. One child node per modem that must be loaded is required. Required properties: - compatible: Must be "qcom,pil-femto-modem-desc". - reg: Pair of physical base address and region size of the Relay Message Buffer (RMB) registers for this modem. - reg-names: "rmb_base" is required. - qcom,firmware-name: Base filename of the firmware image (e.g. "mdm0"). Maximum size is 4 characters. - qcom,modem-id: Unique ID of this modem. This is not a logical index. - qcom,max-num-images: The maximum number of images that will be loaded to this modem. Optional properties: - qcom,pil-skip-entry-check: Some entry pointers are virtual addresses, which will cause the PIL entry address check to fail. This parameter will disable the check. Example: qcom,modem-femto@fbc00000 { compatible = "qcom,pil-femto-modem"; #address-cells=<1>; #size-cells=<1>; ranges; reg = <0xfbc00000 0x100>; reg-names = "qdsp6_base"; qcom,firmware-name = "mba"; qcom,max-num-modems = <5>; qcom,modem@fd4a7000 { compatible = "qcom,pil-femto-modem-desc"; reg = <0xfd4a7000 0x20>; reg-names = "rmb_base"; qcom,firmware-name = "mdm0"; qcom,modem-id = <0>; qcom,max-num-images = <1>; }; qcom,modem@fd4a7030 { compatible = "qcom,pil-femto-modem-desc"; reg = <0xfd4a7030 0x20>; reg-names = "rmb_base"; qcom,firmware-name = "mdm1"; qcom,modem-id = <1>; qcom,max-num-images = <1>; }; qcom,modem@fd4a7060 { compatible = "qcom,pil-femto-modem-desc"; reg = <0xfd4a7060 0x20>; reg-names = "rmb_base"; qcom,firmware-name = "mdm2"; qcom,modem-id = <2>; qcom,max-num-images = <1>; }; qcom,modem@fd4a7090 { compatible = "qcom,pil-femto-modem-desc"; reg = <0xfd4a7090 0x20>; reg-names = "rmb_base"; qcom,firmware-name = "mdm3"; qcom,modem-id = <3>; qcom,max-num-images = <1>; }; qcom,modem@fd4a70c0 { compatible = "qcom,pil-femto-modem-desc"; reg = <0xfd4a70c0 0x20>; reg-names = "rmb_base"; qcom,firmware-name = "mdm4"; qcom,modem-id = <4>; qcom,max-num-images = <8>; qcom,pil-skip-entry-check; }; }; arch/arm/mach-msm/Kconfig +9 −0 Original line number Diff line number Diff line Loading @@ -1255,6 +1255,15 @@ config MSM_PIL_PRONTO PRONTO is the wireless subsystem processor used in bluetooth, wireless LAN, and FM software applications. config MSM_PIL_FEMTO tristate "FSM99XX Boot Support" depends on MSM_PIL && ARCH_FSM9900 help Support for loading and booting firmware images for multiple modems on the FSM9900 family targets. Select Y if you want the modems to boot. If unsure, select N. config MSM_SCM bool "Secure Channel Manager (SCM) support" default n Loading arch/arm/mach-msm/Makefile +1 −0 Original line number Diff line number Diff line Loading @@ -55,6 +55,7 @@ obj-$(CONFIG_MSM_PIL_GSS) += pil-gss.o obj-$(CONFIG_MSM_PIL_PRONTO) += pil-pronto.o obj-$(CONFIG_MSM_PIL_VENUS) += pil-venus.o obj-$(CONFIG_MSM_PIL_VPU) += pil-vpu.o obj-$(CONFIG_MSM_PIL_FEMTO) += pil-q6v5.o pil-msa.o pil-femto-modem.o obj-$(CONFIG_MSM_FIQ_SUPPORT) += fiq_glue.o obj-$(CONFIG_MSM_BAM_DMUX) += bam_dmux.o obj-$(CONFIG_MSM_SMD_LOGGING) += smem_log.o Loading arch/arm/mach-msm/pil-femto-modem.c 0 → 100644 +767 −0 Original line number Diff line number Diff line /* Copyright (c) 2013, The Linux Foundation. All rights reserved. * * 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/kernel.h> #include <linux/init.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/io.h> #include <linux/iopoll.h> #include <linux/err.h> #include <linux/of.h> #include <linux/of_platform.h> #include <linux/stat.h> #include <linux/slab.h> #include <linux/device.h> #include <linux/sysfs.h> #include <asm/page.h> #include "peripheral-loader.h" #include "pil-q6v5.h" #include "pil-msa.h" #define MAX_MODEM_ID 4 /* These macros assist with creating sysfs attributes */ #define MAKE_RO_ATTR(_name, _ptr, _index)\ _ptr->kobj_attr_##_name.attr.name = __stringify(_name);\ _ptr->kobj_attr_##_name.attr.mode = S_IRUSR;\ _ptr->kobj_attr_##_name.show = show_##_name;\ _ptr->kobj_attr_##_name.store = NULL;\ sysfs_attr_init(&_ptr->kobj_attr_##_name.attr);\ _ptr->attr_grp.attrs[_index] = &_ptr->kobj_attr_##_name.attr; #define MAKE_RW_ATTR(_name, _ptr, _index)\ _ptr->kobj_attr_##_name.attr.name = __stringify(_name);\ _ptr->kobj_attr_##_name.attr.mode = S_IRUSR|S_IWUSR; \ _ptr->kobj_attr_##_name.show = show_##_name;\ _ptr->kobj_attr_##_name.store = store_##_name;\ sysfs_attr_init(&_ptr->kobj_attr_##_name.attr);\ _ptr->attr_grp.attrs[_index] = &_ptr->kobj_attr_##_name.attr; enum modem_status { MODEM_STATUS_OFFLINE = 0, MODEM_STATUS_MBA_LOADING, MODEM_STATUS_MBA_RUNNING, MODEM_STATUS_MBA_ERROR, MODEM_STATUS_PMI_LOADING, MODEM_STATUS_PMI_ERROR, MODEM_STATUS_PMI_LOADED, MODEM_STATUS_PMI_NOT_FOUND, MODEM_STATUS_ADVANCE_FAILED, MODEM_STATUS_LAST }; static char *modem_status_str[MODEM_STATUS_LAST] = { "OFFLINE", "MBA LOADING", "MBA RUNNING", "MBA ERROR", "PMI LOADING", "PMI ERROR", "PMI LOADED", "PMI NOT FOUND", "ADVANCE FAILED" }; struct modem_pil_data { struct mba_data image; const char *name; u32 num_images; u32 id; /* sysfs */ struct kobject *kobj; struct attribute_group attr_grp; struct kobj_attribute kobj_attr_status; struct kobj_attribute kobj_attr_imgs_loaded; struct kobj_attribute kobj_attr_last_img_loaded; struct kobj_attribute kobj_attr_img_loading; u32 status; u32 imgs_loaded; int last_img_loaded; int img_loading; }; struct femto_modem_data { struct q6v5_data *q6; struct modem_pil_data *modem; u32 max_num_modems; u32 disc_modems; /* sysfs */ struct kobject *kobj; struct attribute_group attr_grp; struct kobj_attribute kobj_attr_mba_status; struct kobj_attribute kobj_attr_enable; u32 mba_status; u8 enable; }; #define SET_SYSFS_VALUE(_ptr, _attr, _val)\ do {\ _ptr->_attr = (_val);\ sysfs_notify(_ptr->kobj, NULL, __stringify(_attr));\ } while (0) #define RMB_MBA_COMMAND 0x08 #define RMB_MBA_STATUS 0x0C #define CMD_RMB_ADVANCE 0x03 #define STATUS_RMB_UPDATE_ACK 0x06 #define RMB_ADVANCE_COMPLETE 0xFE #define POLL_INTERVAL_US 50 #define TIMEOUT_US 1000000 static void *pil_femto_modem_map_fw_mem(phys_addr_t paddr, size_t size) { /* Due to certain memory areas on the platform requiring 32-bit wide * accesses, we must cache the firmware to avoid bus errors. */ return ioremap_cached(paddr, size); } static int pil_femto_modem_send_rmb_advance(void __iomem *rmb_base, u32 id) { int ret; u32 cmd = CMD_RMB_ADVANCE; int status; if (!rmb_base) return -EINVAL; /* Prepare the command */ cmd |= id << 8; /* Sent the MBA command */ writel_relaxed(cmd, rmb_base + RMB_MBA_COMMAND); /* Wait for MBA status. */ ret = readl_poll_timeout(rmb_base + RMB_MBA_STATUS, status, ((status < 0) || (status == STATUS_RMB_UPDATE_ACK)), POLL_INTERVAL_US, TIMEOUT_US); if (ret) return ret; if (status != STATUS_RMB_UPDATE_ACK) return -EINVAL; return ret; } static int pil_femto_modem_stop(struct femto_modem_data *drv) { if (!drv) return -EINVAL; /* Only need to shutdown the Q6 PIL descriptor, because shutting down * the others does nothing. */ pil_shutdown(&drv->q6->desc); return 0; } static int pil_femto_modem_start(struct femto_modem_data *drv) { int ret; u32 index; /* MBA must load, else we can't load any firmware images */ SET_SYSFS_VALUE(drv, mba_status, MODEM_STATUS_MBA_LOADING); ret = pil_boot(&drv->q6->desc); if (ret) { SET_SYSFS_VALUE(drv, mba_status, MODEM_STATUS_MBA_ERROR); return ret; } SET_SYSFS_VALUE(drv, mba_status, MODEM_STATUS_MBA_RUNNING); /* Load the other modem images, if possible. */ for (index = 0; index < drv->max_num_modems; index++) { struct modem_pil_data *modem = &drv->modem[index]; int img; char *pmi_name = kzalloc((strlen(modem->name) + 5), GFP_KERNEL); struct mba_data *image = &modem->image; if (!pmi_name) { ret = -ENOMEM; SET_SYSFS_VALUE(modem, status, MODEM_STATUS_PMI_ERROR); pil_shutdown(&drv->q6->desc); break; } /* Initialize stats */ SET_SYSFS_VALUE(modem, imgs_loaded, 0); SET_SYSFS_VALUE(modem, last_img_loaded, -1); SET_SYSFS_VALUE(modem, img_loading, -1); /* Try to load each image. */ for (img = 0; img < modem->num_images; img++) { SET_SYSFS_VALUE(modem, status, MODEM_STATUS_PMI_LOADING); SET_SYSFS_VALUE(modem, img_loading, img); /* The filename for each image is name_nn, where nn is * the 2 digit image index. */ pmi_name[0] = '\0'; snprintf(pmi_name, (strlen(modem->name) + 5), "%s_%02u", modem->name, img); /* Have to change the descriptor name so it boots the * correct file. */ image->desc.name = pmi_name; /* Try to boot the image. */ ret = pil_boot(&image->desc); if (ret) { SET_SYSFS_VALUE(modem, status, MODEM_STATUS_PMI_NOT_FOUND); if (!modem->id) { /* This is a catastrophic failure. * Modem 0 must load. */ pil_shutdown(&drv->q6->desc); break; } else /* This image didn't load, but it's not * a catastrophic failure; continue * loading. */ ret = 0; } else { /* Update stats */ SET_SYSFS_VALUE(modem, last_img_loaded, img); SET_SYSFS_VALUE(modem, img_loading, -1); SET_SYSFS_VALUE(modem, imgs_loaded, modem->imgs_loaded + 1); SET_SYSFS_VALUE(modem, status, MODEM_STATUS_PMI_LOADED); } } if (modem->imgs_loaded == 0) SET_SYSFS_VALUE(modem, status, MODEM_STATUS_PMI_ERROR); /* Free the allocated name */ kfree(pmi_name); if (index == (drv->max_num_modems - 1)) /* Tell the MBA this was the last RMB */ ret = pil_femto_modem_send_rmb_advance( image->rmb_base, RMB_ADVANCE_COMPLETE); else { /* Tell the MBA to move to the next RMB. * Note that the MBA needs the actual id of the * modem specified in the device tree, * not the logical index. */ ret = pil_femto_modem_send_rmb_advance( image->rmb_base, drv->modem[(index + 1)].id); if (ret) { /* This is a catastrophic failure; we've * gotten out of sync with the MBA. */ SET_SYSFS_VALUE(modem, status, MODEM_STATUS_ADVANCE_FAILED); pil_shutdown(&drv->q6->desc); break; } } } return ret; } /* * The following are for sysfs */ #define TO_DRV(attr, elem) \ container_of(attr, struct femto_modem_data, elem) static ssize_t show_mba_status(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct femto_modem_data *drv = TO_DRV(attr, kobj_attr_mba_status); if (!drv) return -EINVAL; return scnprintf(buf, PAGE_SIZE, "%s\n", modem_status_str[drv->mba_status]); } static ssize_t store_enable(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { struct femto_modem_data *drv = TO_DRV(attr, kobj_attr_enable); u8 enable_val; int ret; if (!drv) return -EINVAL; if (kstrtou8(buf, 0, &enable_val)) return -EINVAL; if (enable_val > 1) return -EINVAL; /* Only start/stop if it's different. */ if (enable_val != drv->enable) { if (enable_val) ret = pil_femto_modem_start(drv); else ret = pil_femto_modem_stop(drv); if (ret) return ret; SET_SYSFS_VALUE(drv, enable, enable_val); } return count; } static ssize_t show_enable(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct femto_modem_data *drv = TO_DRV(attr, kobj_attr_enable); if (!drv) return -EINVAL; return scnprintf(buf, PAGE_SIZE, "%s\n", (drv->enable ? "ENABLED" : "DISABLED")); } #define TO_MODEM(attr, elem) \ container_of(attr, struct modem_pil_data, elem) static ssize_t show_status(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct modem_pil_data *modem = TO_MODEM(attr, kobj_attr_status); if (!modem) return -EINVAL; return scnprintf(buf, PAGE_SIZE, "%s\n", modem_status_str[modem->status]); } static ssize_t show_imgs_loaded(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct modem_pil_data *modem = TO_MODEM(attr, kobj_attr_imgs_loaded); if (!modem) return -EINVAL; return scnprintf(buf, PAGE_SIZE, "%u\n", modem->imgs_loaded); } static ssize_t show_last_img_loaded(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct modem_pil_data *modem = TO_MODEM(attr, kobj_attr_last_img_loaded); if (!modem) return -EINVAL; if (modem->last_img_loaded < 0) return scnprintf(buf, PAGE_SIZE, "NONE\n"); else return scnprintf(buf, PAGE_SIZE, "%s_%02d\n", modem->name, modem->last_img_loaded); } static ssize_t show_img_loading(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct modem_pil_data *modem = TO_MODEM(attr, kobj_attr_img_loading); if (!modem) return -EINVAL; if (modem->img_loading < 0) return scnprintf(buf, PAGE_SIZE, "NONE\n"); else return scnprintf(buf, PAGE_SIZE, "%s_%02d\n", modem->name, modem->img_loading); } static int pil_femto_modem_create_sysfs(struct femto_modem_data *drv) { int ret = 0; if (!drv) return -EINVAL; /* Create the sysfs kobj */ drv->kobj = kobject_create_and_add("femto_modem", kernel_kobj); if (!drv->kobj) return -ENOMEM; /* Allocate memory for the group */ drv->attr_grp.attrs = kzalloc(sizeof(struct attribute *) * 2, GFP_KERNEL); if (!drv->attr_grp.attrs) { ret = -ENOMEM; goto cleanup_kobj; } /* Create the attributes and add them to the group */ MAKE_RO_ATTR(mba_status, drv, 0); MAKE_RW_ATTR(enable, drv, 1); /* Create sysfs group*/ ret = sysfs_create_group(drv->kobj, &drv->attr_grp); if (ret) goto cleanup_grp; drv->mba_status = MODEM_STATUS_OFFLINE; drv->enable = 0; return ret; cleanup_grp: kzfree(drv->attr_grp.attrs); drv->attr_grp.attrs = NULL; cleanup_kobj: kobject_put(drv->kobj); drv->kobj = NULL; return ret; } static int pil_femto_modem_desc_create_sysfs(struct femto_modem_data *drv, struct modem_pil_data *modem) { int ret = 0; char sysfs_dirname_str[10]; if (!drv || !modem) return -EINVAL; /* Generate the name for the directory */ sysfs_dirname_str[0] = '\0'; snprintf(sysfs_dirname_str, sizeof(sysfs_dirname_str), "modem%d", modem->id); /* Create the kobj as a child of the overall driver kobj */ modem->kobj = kobject_create_and_add(sysfs_dirname_str, drv->kobj); if (!modem->kobj) return -ENOMEM; /* Allocate memory for the group */ modem->attr_grp.attrs = kzalloc(sizeof(struct attribute *) * 4, GFP_KERNEL); if (!modem->attr_grp.attrs) { ret = -ENOMEM; goto cleanup_pil_kobj; } /* Create the attributes and add them to the group */ MAKE_RO_ATTR(status, modem, 0); MAKE_RO_ATTR(imgs_loaded, modem, 1); MAKE_RO_ATTR(last_img_loaded, modem, 2); MAKE_RO_ATTR(img_loading, modem, 3); /* Create sysfs group*/ ret = sysfs_create_group(modem->kobj, &modem->attr_grp); if (ret) goto cleanup_grp_mem; /* Initialize the values */ SET_SYSFS_VALUE(modem, status, MODEM_STATUS_OFFLINE); SET_SYSFS_VALUE(modem, imgs_loaded, 0); SET_SYSFS_VALUE(modem, last_img_loaded, -1); SET_SYSFS_VALUE(modem, img_loading, -1); return ret; cleanup_grp_mem: kzfree(modem->attr_grp.attrs); modem->attr_grp.attrs = NULL; cleanup_pil_kobj: kobject_put(modem->kobj); modem->kobj = NULL; return ret; } static int pil_femto_modem_desc_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct device_node *node; struct femto_modem_data *drv; struct modem_pil_data *modem; struct pil_desc *mba_desc; struct resource *res; struct mba_data *mba; void __iomem *rmb; int ret; u32 id; bool skip_entry; if (!dev->of_node) { dev_err(dev, "%s: device tree information missing\n", __func__); return -ENODEV; } node = dev->of_node; if (dev->parent == NULL) { dev_err(dev, "%s: parent device missing\n", __func__); return -ENODEV; } drv = dev_get_drvdata(dev->parent); if (drv == NULL) { dev_err(dev, "%s: driver data not found in parent device\n", __func__); return -ENODEV; } /* Make sure there are not more modems than specified */ if (drv->disc_modems == drv->max_num_modems) { dev_err(dev, "%s: found more than max of %u modems.\n", __func__, drv->max_num_modems); return -EINVAL; } ret = of_property_read_u32(node, "qcom,modem-id", &id); if (ret) return ret; /* Sanity check id */ if (id > MAX_MODEM_ID) return -EINVAL; modem = &drv->modem[drv->disc_modems]; modem->id = id; /* Retrieve the RMB base */ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rmb_base"); rmb = devm_request_and_ioremap(dev, res); if (!rmb) return -ENOMEM; /* The q6 structure should always point to modem[0] RMB regs */ if (!modem->id && drv->q6) drv->q6->rmb_base = rmb; /* Retrieve the firmware name */ ret = of_property_read_string(node, "qcom,firmware-name", &modem->name); if (ret) return ret; /* Retrieve the maximum number of images for this modem */ ret = of_property_read_u32(node, "qcom,max-num-images", &modem->num_images); if (ret) return ret; /* Read the skip entry check flag */ skip_entry = of_property_read_bool(node, "qcom,pil-skip-entry-check"); /* Initialize the image attributes */ mba = &modem->image; mba->rmb_base = rmb; /* Why isn't there one descriptor per file? Because, the pil_desc_init * function has a built-in maximum of 10, meaning it will fail after 10 * descriptors have been allocated. Since there could be many more than * 10 images loaded by this driver, it is necessary to have only one * PIL descriptor per modem and reuse it for each image. */ mba_desc = &mba->desc; mba_desc->name = modem->name; mba_desc->dev = dev; mba_desc->ops = &pil_msa_mba_ops; mba_desc->owner = THIS_MODULE; mba_desc->proxy_timeout = 0; mba_desc->flags = skip_entry ? PIL_SKIP_ENTRY_CHECK : 0; mba_desc->map_fw_mem = pil_femto_modem_map_fw_mem; mba_desc->unmap_fw_mem = NULL; ret = pil_desc_init(mba_desc); if (ret) return ret; platform_set_drvdata(pdev, modem); /* Create the sysfs attributes */ ret = pil_femto_modem_desc_create_sysfs(drv, modem); if (ret) pil_desc_release(mba_desc); drv->disc_modems++; return ret; } static int pil_femto_modem_desc_driver_exit( struct platform_device *pdev) { struct modem_pil_data *modem = (struct modem_pil_data *)platform_get_drvdata(pdev); if (modem) pil_desc_release(&modem->image.desc); return 0; } static int pil_femto_modem_driver_probe( struct platform_device *pdev) { struct femto_modem_data *drv; struct q6v5_data *q6; struct pil_desc *q6_desc; struct device_node *p_node = pdev->dev.of_node; int ret = 0; drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL); if (!drv) return -ENOMEM; platform_set_drvdata(pdev, drv); /* Retrieve the maximum number of modems */ ret = of_property_read_u32(p_node, "qcom,max-num-modems", &drv->max_num_modems); if (ret) return ret; /* Max number of modems must be greater than zero */ if (!drv->max_num_modems) return -EINVAL; /* Allocate memory for modem structs */ drv->modem = devm_kzalloc(&pdev->dev, (sizeof(*(drv->modem)) * drv->max_num_modems), GFP_KERNEL); if (!drv->modem) return -ENOMEM; /* This controls the loading of the MBA firmware to Q6[0] */ q6 = pil_q6v5_init(pdev); if (IS_ERR(q6)) return PTR_ERR(q6); drv->q6 = q6; /* This is needed for legacy code. Always on. */ q6->self_auth = 1; q6_desc = &q6->desc; q6_desc->ops = &pil_msa_pbl_ops; q6_desc->owner = THIS_MODULE; q6_desc->proxy_timeout = 0; q6_desc->map_fw_mem = pil_femto_modem_map_fw_mem; q6_desc->unmap_fw_mem = NULL; /* For this target, PBL interactions are different. */ pil_msa_pbl_ops.proxy_vote = NULL; pil_msa_pbl_ops.proxy_unvote = NULL; /* Initialize the number of discovered modems. */ drv->disc_modems = 0; /* Parse the device tree to get RMB regs and filenames for each modem */ ret = of_platform_populate(p_node, NULL, NULL, &pdev->dev); if (ret) return ret; /* Initialize the PIL descriptor */ ret = pil_desc_init(q6_desc); if (ret) return ret; /* Create the sysfs attributes */ ret = pil_femto_modem_create_sysfs(drv); if (ret) pil_desc_release(q6_desc); return ret; } static int pil_femto_modem_driver_exit( struct platform_device *pdev) { struct femto_modem_data *drv = platform_get_drvdata(pdev); pil_desc_release(&drv->q6->desc); return 0; } static struct of_device_id pil_femto_modem_match_table[] = { { .compatible = "qcom,pil-femto-modem" }, {} }; MODULE_DEVICE_TABLE(of, pil_femto_modem_match_table); static struct of_device_id pil_femto_modem_desc_match_table[] = { { .compatible = "qcom,pil-femto-modem-desc" }, {} }; MODULE_DEVICE_TABLE(of, pil_femto_modem_desc_match_table); static struct platform_driver pil_femto_modem_driver = { .probe = pil_femto_modem_driver_probe, .remove = pil_femto_modem_driver_exit, .driver = { .name = "pil-femto-modem", .of_match_table = pil_femto_modem_match_table, .owner = THIS_MODULE, }, }; static struct platform_driver pil_femto_modem_desc_driver = { .probe = pil_femto_modem_desc_probe, .remove = pil_femto_modem_desc_driver_exit, .driver = { .name = "pil-femto-modem-desc", .of_match_table = pil_femto_modem_desc_match_table, .owner = THIS_MODULE, }, }; static int __init pil_femto_modem_init(void) { int result; result = platform_driver_register(&pil_femto_modem_driver); if (result) return result; return platform_driver_register(&pil_femto_modem_desc_driver); } module_init(pil_femto_modem_init); static void __exit pil_femto_modem_exit(void) { platform_driver_unregister(&pil_femto_modem_desc_driver); platform_driver_unregister(&pil_femto_modem_driver); } module_exit(pil_femto_modem_exit); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("Support for booting FSM99XX modems"); Loading
Documentation/devicetree/bindings/pil/pil-femto-modem.txt 0 → 100644 +92 −0 Original line number Diff line number Diff line Qualcomm Femtocell (FSM99XX) Peripheral Image Loader pil-femto-modem is a peripheral image loader (PIL) driver. It is used for loading firmware images on multiple modems resident on the FSM99XX platform. The top-level device tree entry has the following properties: Required properties: - compatible: Must be "qcom,pil-femto-modem". - reg: Pair of physical base address and region size of the QDSP6[0] control registers. - reg-names: "qdsp6_base" is required. - qcom,firmware-name: Base name of the MBA firmware image ("mba"). Maximum size is 4 characters. - qcom,max-num-modems: Number of modem child nodes specified. One child node per modem that must be loaded is required. Required properties: - compatible: Must be "qcom,pil-femto-modem-desc". - reg: Pair of physical base address and region size of the Relay Message Buffer (RMB) registers for this modem. - reg-names: "rmb_base" is required. - qcom,firmware-name: Base filename of the firmware image (e.g. "mdm0"). Maximum size is 4 characters. - qcom,modem-id: Unique ID of this modem. This is not a logical index. - qcom,max-num-images: The maximum number of images that will be loaded to this modem. Optional properties: - qcom,pil-skip-entry-check: Some entry pointers are virtual addresses, which will cause the PIL entry address check to fail. This parameter will disable the check. Example: qcom,modem-femto@fbc00000 { compatible = "qcom,pil-femto-modem"; #address-cells=<1>; #size-cells=<1>; ranges; reg = <0xfbc00000 0x100>; reg-names = "qdsp6_base"; qcom,firmware-name = "mba"; qcom,max-num-modems = <5>; qcom,modem@fd4a7000 { compatible = "qcom,pil-femto-modem-desc"; reg = <0xfd4a7000 0x20>; reg-names = "rmb_base"; qcom,firmware-name = "mdm0"; qcom,modem-id = <0>; qcom,max-num-images = <1>; }; qcom,modem@fd4a7030 { compatible = "qcom,pil-femto-modem-desc"; reg = <0xfd4a7030 0x20>; reg-names = "rmb_base"; qcom,firmware-name = "mdm1"; qcom,modem-id = <1>; qcom,max-num-images = <1>; }; qcom,modem@fd4a7060 { compatible = "qcom,pil-femto-modem-desc"; reg = <0xfd4a7060 0x20>; reg-names = "rmb_base"; qcom,firmware-name = "mdm2"; qcom,modem-id = <2>; qcom,max-num-images = <1>; }; qcom,modem@fd4a7090 { compatible = "qcom,pil-femto-modem-desc"; reg = <0xfd4a7090 0x20>; reg-names = "rmb_base"; qcom,firmware-name = "mdm3"; qcom,modem-id = <3>; qcom,max-num-images = <1>; }; qcom,modem@fd4a70c0 { compatible = "qcom,pil-femto-modem-desc"; reg = <0xfd4a70c0 0x20>; reg-names = "rmb_base"; qcom,firmware-name = "mdm4"; qcom,modem-id = <4>; qcom,max-num-images = <8>; qcom,pil-skip-entry-check; }; };
arch/arm/mach-msm/Kconfig +9 −0 Original line number Diff line number Diff line Loading @@ -1255,6 +1255,15 @@ config MSM_PIL_PRONTO PRONTO is the wireless subsystem processor used in bluetooth, wireless LAN, and FM software applications. config MSM_PIL_FEMTO tristate "FSM99XX Boot Support" depends on MSM_PIL && ARCH_FSM9900 help Support for loading and booting firmware images for multiple modems on the FSM9900 family targets. Select Y if you want the modems to boot. If unsure, select N. config MSM_SCM bool "Secure Channel Manager (SCM) support" default n Loading
arch/arm/mach-msm/Makefile +1 −0 Original line number Diff line number Diff line Loading @@ -55,6 +55,7 @@ obj-$(CONFIG_MSM_PIL_GSS) += pil-gss.o obj-$(CONFIG_MSM_PIL_PRONTO) += pil-pronto.o obj-$(CONFIG_MSM_PIL_VENUS) += pil-venus.o obj-$(CONFIG_MSM_PIL_VPU) += pil-vpu.o obj-$(CONFIG_MSM_PIL_FEMTO) += pil-q6v5.o pil-msa.o pil-femto-modem.o obj-$(CONFIG_MSM_FIQ_SUPPORT) += fiq_glue.o obj-$(CONFIG_MSM_BAM_DMUX) += bam_dmux.o obj-$(CONFIG_MSM_SMD_LOGGING) += smem_log.o Loading
arch/arm/mach-msm/pil-femto-modem.c 0 → 100644 +767 −0 Original line number Diff line number Diff line /* Copyright (c) 2013, The Linux Foundation. All rights reserved. * * 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/kernel.h> #include <linux/init.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/io.h> #include <linux/iopoll.h> #include <linux/err.h> #include <linux/of.h> #include <linux/of_platform.h> #include <linux/stat.h> #include <linux/slab.h> #include <linux/device.h> #include <linux/sysfs.h> #include <asm/page.h> #include "peripheral-loader.h" #include "pil-q6v5.h" #include "pil-msa.h" #define MAX_MODEM_ID 4 /* These macros assist with creating sysfs attributes */ #define MAKE_RO_ATTR(_name, _ptr, _index)\ _ptr->kobj_attr_##_name.attr.name = __stringify(_name);\ _ptr->kobj_attr_##_name.attr.mode = S_IRUSR;\ _ptr->kobj_attr_##_name.show = show_##_name;\ _ptr->kobj_attr_##_name.store = NULL;\ sysfs_attr_init(&_ptr->kobj_attr_##_name.attr);\ _ptr->attr_grp.attrs[_index] = &_ptr->kobj_attr_##_name.attr; #define MAKE_RW_ATTR(_name, _ptr, _index)\ _ptr->kobj_attr_##_name.attr.name = __stringify(_name);\ _ptr->kobj_attr_##_name.attr.mode = S_IRUSR|S_IWUSR; \ _ptr->kobj_attr_##_name.show = show_##_name;\ _ptr->kobj_attr_##_name.store = store_##_name;\ sysfs_attr_init(&_ptr->kobj_attr_##_name.attr);\ _ptr->attr_grp.attrs[_index] = &_ptr->kobj_attr_##_name.attr; enum modem_status { MODEM_STATUS_OFFLINE = 0, MODEM_STATUS_MBA_LOADING, MODEM_STATUS_MBA_RUNNING, MODEM_STATUS_MBA_ERROR, MODEM_STATUS_PMI_LOADING, MODEM_STATUS_PMI_ERROR, MODEM_STATUS_PMI_LOADED, MODEM_STATUS_PMI_NOT_FOUND, MODEM_STATUS_ADVANCE_FAILED, MODEM_STATUS_LAST }; static char *modem_status_str[MODEM_STATUS_LAST] = { "OFFLINE", "MBA LOADING", "MBA RUNNING", "MBA ERROR", "PMI LOADING", "PMI ERROR", "PMI LOADED", "PMI NOT FOUND", "ADVANCE FAILED" }; struct modem_pil_data { struct mba_data image; const char *name; u32 num_images; u32 id; /* sysfs */ struct kobject *kobj; struct attribute_group attr_grp; struct kobj_attribute kobj_attr_status; struct kobj_attribute kobj_attr_imgs_loaded; struct kobj_attribute kobj_attr_last_img_loaded; struct kobj_attribute kobj_attr_img_loading; u32 status; u32 imgs_loaded; int last_img_loaded; int img_loading; }; struct femto_modem_data { struct q6v5_data *q6; struct modem_pil_data *modem; u32 max_num_modems; u32 disc_modems; /* sysfs */ struct kobject *kobj; struct attribute_group attr_grp; struct kobj_attribute kobj_attr_mba_status; struct kobj_attribute kobj_attr_enable; u32 mba_status; u8 enable; }; #define SET_SYSFS_VALUE(_ptr, _attr, _val)\ do {\ _ptr->_attr = (_val);\ sysfs_notify(_ptr->kobj, NULL, __stringify(_attr));\ } while (0) #define RMB_MBA_COMMAND 0x08 #define RMB_MBA_STATUS 0x0C #define CMD_RMB_ADVANCE 0x03 #define STATUS_RMB_UPDATE_ACK 0x06 #define RMB_ADVANCE_COMPLETE 0xFE #define POLL_INTERVAL_US 50 #define TIMEOUT_US 1000000 static void *pil_femto_modem_map_fw_mem(phys_addr_t paddr, size_t size) { /* Due to certain memory areas on the platform requiring 32-bit wide * accesses, we must cache the firmware to avoid bus errors. */ return ioremap_cached(paddr, size); } static int pil_femto_modem_send_rmb_advance(void __iomem *rmb_base, u32 id) { int ret; u32 cmd = CMD_RMB_ADVANCE; int status; if (!rmb_base) return -EINVAL; /* Prepare the command */ cmd |= id << 8; /* Sent the MBA command */ writel_relaxed(cmd, rmb_base + RMB_MBA_COMMAND); /* Wait for MBA status. */ ret = readl_poll_timeout(rmb_base + RMB_MBA_STATUS, status, ((status < 0) || (status == STATUS_RMB_UPDATE_ACK)), POLL_INTERVAL_US, TIMEOUT_US); if (ret) return ret; if (status != STATUS_RMB_UPDATE_ACK) return -EINVAL; return ret; } static int pil_femto_modem_stop(struct femto_modem_data *drv) { if (!drv) return -EINVAL; /* Only need to shutdown the Q6 PIL descriptor, because shutting down * the others does nothing. */ pil_shutdown(&drv->q6->desc); return 0; } static int pil_femto_modem_start(struct femto_modem_data *drv) { int ret; u32 index; /* MBA must load, else we can't load any firmware images */ SET_SYSFS_VALUE(drv, mba_status, MODEM_STATUS_MBA_LOADING); ret = pil_boot(&drv->q6->desc); if (ret) { SET_SYSFS_VALUE(drv, mba_status, MODEM_STATUS_MBA_ERROR); return ret; } SET_SYSFS_VALUE(drv, mba_status, MODEM_STATUS_MBA_RUNNING); /* Load the other modem images, if possible. */ for (index = 0; index < drv->max_num_modems; index++) { struct modem_pil_data *modem = &drv->modem[index]; int img; char *pmi_name = kzalloc((strlen(modem->name) + 5), GFP_KERNEL); struct mba_data *image = &modem->image; if (!pmi_name) { ret = -ENOMEM; SET_SYSFS_VALUE(modem, status, MODEM_STATUS_PMI_ERROR); pil_shutdown(&drv->q6->desc); break; } /* Initialize stats */ SET_SYSFS_VALUE(modem, imgs_loaded, 0); SET_SYSFS_VALUE(modem, last_img_loaded, -1); SET_SYSFS_VALUE(modem, img_loading, -1); /* Try to load each image. */ for (img = 0; img < modem->num_images; img++) { SET_SYSFS_VALUE(modem, status, MODEM_STATUS_PMI_LOADING); SET_SYSFS_VALUE(modem, img_loading, img); /* The filename for each image is name_nn, where nn is * the 2 digit image index. */ pmi_name[0] = '\0'; snprintf(pmi_name, (strlen(modem->name) + 5), "%s_%02u", modem->name, img); /* Have to change the descriptor name so it boots the * correct file. */ image->desc.name = pmi_name; /* Try to boot the image. */ ret = pil_boot(&image->desc); if (ret) { SET_SYSFS_VALUE(modem, status, MODEM_STATUS_PMI_NOT_FOUND); if (!modem->id) { /* This is a catastrophic failure. * Modem 0 must load. */ pil_shutdown(&drv->q6->desc); break; } else /* This image didn't load, but it's not * a catastrophic failure; continue * loading. */ ret = 0; } else { /* Update stats */ SET_SYSFS_VALUE(modem, last_img_loaded, img); SET_SYSFS_VALUE(modem, img_loading, -1); SET_SYSFS_VALUE(modem, imgs_loaded, modem->imgs_loaded + 1); SET_SYSFS_VALUE(modem, status, MODEM_STATUS_PMI_LOADED); } } if (modem->imgs_loaded == 0) SET_SYSFS_VALUE(modem, status, MODEM_STATUS_PMI_ERROR); /* Free the allocated name */ kfree(pmi_name); if (index == (drv->max_num_modems - 1)) /* Tell the MBA this was the last RMB */ ret = pil_femto_modem_send_rmb_advance( image->rmb_base, RMB_ADVANCE_COMPLETE); else { /* Tell the MBA to move to the next RMB. * Note that the MBA needs the actual id of the * modem specified in the device tree, * not the logical index. */ ret = pil_femto_modem_send_rmb_advance( image->rmb_base, drv->modem[(index + 1)].id); if (ret) { /* This is a catastrophic failure; we've * gotten out of sync with the MBA. */ SET_SYSFS_VALUE(modem, status, MODEM_STATUS_ADVANCE_FAILED); pil_shutdown(&drv->q6->desc); break; } } } return ret; } /* * The following are for sysfs */ #define TO_DRV(attr, elem) \ container_of(attr, struct femto_modem_data, elem) static ssize_t show_mba_status(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct femto_modem_data *drv = TO_DRV(attr, kobj_attr_mba_status); if (!drv) return -EINVAL; return scnprintf(buf, PAGE_SIZE, "%s\n", modem_status_str[drv->mba_status]); } static ssize_t store_enable(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { struct femto_modem_data *drv = TO_DRV(attr, kobj_attr_enable); u8 enable_val; int ret; if (!drv) return -EINVAL; if (kstrtou8(buf, 0, &enable_val)) return -EINVAL; if (enable_val > 1) return -EINVAL; /* Only start/stop if it's different. */ if (enable_val != drv->enable) { if (enable_val) ret = pil_femto_modem_start(drv); else ret = pil_femto_modem_stop(drv); if (ret) return ret; SET_SYSFS_VALUE(drv, enable, enable_val); } return count; } static ssize_t show_enable(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct femto_modem_data *drv = TO_DRV(attr, kobj_attr_enable); if (!drv) return -EINVAL; return scnprintf(buf, PAGE_SIZE, "%s\n", (drv->enable ? "ENABLED" : "DISABLED")); } #define TO_MODEM(attr, elem) \ container_of(attr, struct modem_pil_data, elem) static ssize_t show_status(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct modem_pil_data *modem = TO_MODEM(attr, kobj_attr_status); if (!modem) return -EINVAL; return scnprintf(buf, PAGE_SIZE, "%s\n", modem_status_str[modem->status]); } static ssize_t show_imgs_loaded(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct modem_pil_data *modem = TO_MODEM(attr, kobj_attr_imgs_loaded); if (!modem) return -EINVAL; return scnprintf(buf, PAGE_SIZE, "%u\n", modem->imgs_loaded); } static ssize_t show_last_img_loaded(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct modem_pil_data *modem = TO_MODEM(attr, kobj_attr_last_img_loaded); if (!modem) return -EINVAL; if (modem->last_img_loaded < 0) return scnprintf(buf, PAGE_SIZE, "NONE\n"); else return scnprintf(buf, PAGE_SIZE, "%s_%02d\n", modem->name, modem->last_img_loaded); } static ssize_t show_img_loading(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct modem_pil_data *modem = TO_MODEM(attr, kobj_attr_img_loading); if (!modem) return -EINVAL; if (modem->img_loading < 0) return scnprintf(buf, PAGE_SIZE, "NONE\n"); else return scnprintf(buf, PAGE_SIZE, "%s_%02d\n", modem->name, modem->img_loading); } static int pil_femto_modem_create_sysfs(struct femto_modem_data *drv) { int ret = 0; if (!drv) return -EINVAL; /* Create the sysfs kobj */ drv->kobj = kobject_create_and_add("femto_modem", kernel_kobj); if (!drv->kobj) return -ENOMEM; /* Allocate memory for the group */ drv->attr_grp.attrs = kzalloc(sizeof(struct attribute *) * 2, GFP_KERNEL); if (!drv->attr_grp.attrs) { ret = -ENOMEM; goto cleanup_kobj; } /* Create the attributes and add them to the group */ MAKE_RO_ATTR(mba_status, drv, 0); MAKE_RW_ATTR(enable, drv, 1); /* Create sysfs group*/ ret = sysfs_create_group(drv->kobj, &drv->attr_grp); if (ret) goto cleanup_grp; drv->mba_status = MODEM_STATUS_OFFLINE; drv->enable = 0; return ret; cleanup_grp: kzfree(drv->attr_grp.attrs); drv->attr_grp.attrs = NULL; cleanup_kobj: kobject_put(drv->kobj); drv->kobj = NULL; return ret; } static int pil_femto_modem_desc_create_sysfs(struct femto_modem_data *drv, struct modem_pil_data *modem) { int ret = 0; char sysfs_dirname_str[10]; if (!drv || !modem) return -EINVAL; /* Generate the name for the directory */ sysfs_dirname_str[0] = '\0'; snprintf(sysfs_dirname_str, sizeof(sysfs_dirname_str), "modem%d", modem->id); /* Create the kobj as a child of the overall driver kobj */ modem->kobj = kobject_create_and_add(sysfs_dirname_str, drv->kobj); if (!modem->kobj) return -ENOMEM; /* Allocate memory for the group */ modem->attr_grp.attrs = kzalloc(sizeof(struct attribute *) * 4, GFP_KERNEL); if (!modem->attr_grp.attrs) { ret = -ENOMEM; goto cleanup_pil_kobj; } /* Create the attributes and add them to the group */ MAKE_RO_ATTR(status, modem, 0); MAKE_RO_ATTR(imgs_loaded, modem, 1); MAKE_RO_ATTR(last_img_loaded, modem, 2); MAKE_RO_ATTR(img_loading, modem, 3); /* Create sysfs group*/ ret = sysfs_create_group(modem->kobj, &modem->attr_grp); if (ret) goto cleanup_grp_mem; /* Initialize the values */ SET_SYSFS_VALUE(modem, status, MODEM_STATUS_OFFLINE); SET_SYSFS_VALUE(modem, imgs_loaded, 0); SET_SYSFS_VALUE(modem, last_img_loaded, -1); SET_SYSFS_VALUE(modem, img_loading, -1); return ret; cleanup_grp_mem: kzfree(modem->attr_grp.attrs); modem->attr_grp.attrs = NULL; cleanup_pil_kobj: kobject_put(modem->kobj); modem->kobj = NULL; return ret; } static int pil_femto_modem_desc_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct device_node *node; struct femto_modem_data *drv; struct modem_pil_data *modem; struct pil_desc *mba_desc; struct resource *res; struct mba_data *mba; void __iomem *rmb; int ret; u32 id; bool skip_entry; if (!dev->of_node) { dev_err(dev, "%s: device tree information missing\n", __func__); return -ENODEV; } node = dev->of_node; if (dev->parent == NULL) { dev_err(dev, "%s: parent device missing\n", __func__); return -ENODEV; } drv = dev_get_drvdata(dev->parent); if (drv == NULL) { dev_err(dev, "%s: driver data not found in parent device\n", __func__); return -ENODEV; } /* Make sure there are not more modems than specified */ if (drv->disc_modems == drv->max_num_modems) { dev_err(dev, "%s: found more than max of %u modems.\n", __func__, drv->max_num_modems); return -EINVAL; } ret = of_property_read_u32(node, "qcom,modem-id", &id); if (ret) return ret; /* Sanity check id */ if (id > MAX_MODEM_ID) return -EINVAL; modem = &drv->modem[drv->disc_modems]; modem->id = id; /* Retrieve the RMB base */ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rmb_base"); rmb = devm_request_and_ioremap(dev, res); if (!rmb) return -ENOMEM; /* The q6 structure should always point to modem[0] RMB regs */ if (!modem->id && drv->q6) drv->q6->rmb_base = rmb; /* Retrieve the firmware name */ ret = of_property_read_string(node, "qcom,firmware-name", &modem->name); if (ret) return ret; /* Retrieve the maximum number of images for this modem */ ret = of_property_read_u32(node, "qcom,max-num-images", &modem->num_images); if (ret) return ret; /* Read the skip entry check flag */ skip_entry = of_property_read_bool(node, "qcom,pil-skip-entry-check"); /* Initialize the image attributes */ mba = &modem->image; mba->rmb_base = rmb; /* Why isn't there one descriptor per file? Because, the pil_desc_init * function has a built-in maximum of 10, meaning it will fail after 10 * descriptors have been allocated. Since there could be many more than * 10 images loaded by this driver, it is necessary to have only one * PIL descriptor per modem and reuse it for each image. */ mba_desc = &mba->desc; mba_desc->name = modem->name; mba_desc->dev = dev; mba_desc->ops = &pil_msa_mba_ops; mba_desc->owner = THIS_MODULE; mba_desc->proxy_timeout = 0; mba_desc->flags = skip_entry ? PIL_SKIP_ENTRY_CHECK : 0; mba_desc->map_fw_mem = pil_femto_modem_map_fw_mem; mba_desc->unmap_fw_mem = NULL; ret = pil_desc_init(mba_desc); if (ret) return ret; platform_set_drvdata(pdev, modem); /* Create the sysfs attributes */ ret = pil_femto_modem_desc_create_sysfs(drv, modem); if (ret) pil_desc_release(mba_desc); drv->disc_modems++; return ret; } static int pil_femto_modem_desc_driver_exit( struct platform_device *pdev) { struct modem_pil_data *modem = (struct modem_pil_data *)platform_get_drvdata(pdev); if (modem) pil_desc_release(&modem->image.desc); return 0; } static int pil_femto_modem_driver_probe( struct platform_device *pdev) { struct femto_modem_data *drv; struct q6v5_data *q6; struct pil_desc *q6_desc; struct device_node *p_node = pdev->dev.of_node; int ret = 0; drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL); if (!drv) return -ENOMEM; platform_set_drvdata(pdev, drv); /* Retrieve the maximum number of modems */ ret = of_property_read_u32(p_node, "qcom,max-num-modems", &drv->max_num_modems); if (ret) return ret; /* Max number of modems must be greater than zero */ if (!drv->max_num_modems) return -EINVAL; /* Allocate memory for modem structs */ drv->modem = devm_kzalloc(&pdev->dev, (sizeof(*(drv->modem)) * drv->max_num_modems), GFP_KERNEL); if (!drv->modem) return -ENOMEM; /* This controls the loading of the MBA firmware to Q6[0] */ q6 = pil_q6v5_init(pdev); if (IS_ERR(q6)) return PTR_ERR(q6); drv->q6 = q6; /* This is needed for legacy code. Always on. */ q6->self_auth = 1; q6_desc = &q6->desc; q6_desc->ops = &pil_msa_pbl_ops; q6_desc->owner = THIS_MODULE; q6_desc->proxy_timeout = 0; q6_desc->map_fw_mem = pil_femto_modem_map_fw_mem; q6_desc->unmap_fw_mem = NULL; /* For this target, PBL interactions are different. */ pil_msa_pbl_ops.proxy_vote = NULL; pil_msa_pbl_ops.proxy_unvote = NULL; /* Initialize the number of discovered modems. */ drv->disc_modems = 0; /* Parse the device tree to get RMB regs and filenames for each modem */ ret = of_platform_populate(p_node, NULL, NULL, &pdev->dev); if (ret) return ret; /* Initialize the PIL descriptor */ ret = pil_desc_init(q6_desc); if (ret) return ret; /* Create the sysfs attributes */ ret = pil_femto_modem_create_sysfs(drv); if (ret) pil_desc_release(q6_desc); return ret; } static int pil_femto_modem_driver_exit( struct platform_device *pdev) { struct femto_modem_data *drv = platform_get_drvdata(pdev); pil_desc_release(&drv->q6->desc); return 0; } static struct of_device_id pil_femto_modem_match_table[] = { { .compatible = "qcom,pil-femto-modem" }, {} }; MODULE_DEVICE_TABLE(of, pil_femto_modem_match_table); static struct of_device_id pil_femto_modem_desc_match_table[] = { { .compatible = "qcom,pil-femto-modem-desc" }, {} }; MODULE_DEVICE_TABLE(of, pil_femto_modem_desc_match_table); static struct platform_driver pil_femto_modem_driver = { .probe = pil_femto_modem_driver_probe, .remove = pil_femto_modem_driver_exit, .driver = { .name = "pil-femto-modem", .of_match_table = pil_femto_modem_match_table, .owner = THIS_MODULE, }, }; static struct platform_driver pil_femto_modem_desc_driver = { .probe = pil_femto_modem_desc_probe, .remove = pil_femto_modem_desc_driver_exit, .driver = { .name = "pil-femto-modem-desc", .of_match_table = pil_femto_modem_desc_match_table, .owner = THIS_MODULE, }, }; static int __init pil_femto_modem_init(void) { int result; result = platform_driver_register(&pil_femto_modem_driver); if (result) return result; return platform_driver_register(&pil_femto_modem_desc_driver); } module_init(pil_femto_modem_init); static void __exit pil_femto_modem_exit(void) { platform_driver_unregister(&pil_femto_modem_desc_driver); platform_driver_unregister(&pil_femto_modem_driver); } module_exit(pil_femto_modem_exit); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("Support for booting FSM99XX modems");