784 lines
19 KiB
C
784 lines
19 KiB
C
/* Copyright (c) 2013-2014, 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 modem_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, void *d)
|
|
{
|
|
/* Due to certain memory areas on the platform requiring 32-bit wide
|
|
* accesses, we must cache the firmware to avoid bus errors.
|
|
*/
|
|
return ioremap_cache(paddr, size);
|
|
}
|
|
|
|
static void pil_femto_modem_unmap_fw_mem(void *vaddr, size_t size, void *data)
|
|
{
|
|
iounmap(vaddr);
|
|
}
|
|
|
|
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 modem_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);
|
|
SET_SYSFS_VALUE(drv, mba_status,
|
|
MODEM_STATUS_MBA_ERROR);
|
|
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);
|
|
|
|
/* If the advance fails, the MBA is in an error state */
|
|
if (ret)
|
|
SET_SYSFS_VALUE(drv, mba_status,
|
|
MODEM_STATUS_MBA_ERROR);
|
|
} 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);
|
|
SET_SYSFS_VALUE(drv, mba_status,
|
|
MODEM_STATUS_MBA_ERROR);
|
|
|
|
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 modem_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_femto_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 = pil_femto_modem_unmap_fw_mem;
|
|
|
|
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_mss_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 = pil_femto_modem_unmap_fw_mem;
|
|
|
|
/* For this target, PBL interactions are different. */
|
|
pil_msa_mss_ops.proxy_vote = NULL;
|
|
pil_msa_mss_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");
|