mirror of
https://github.com/BobTheBlinker/android_kernel_motorola_sm6375.git
synced 2026-10-07 20:33:58 -04:00
Add Qorvo UWB driver
Add Qorvo UWB driver for Qorvo UWB chipset QM35725s Change-Id: I77124d7d242863fd8c8a602db7d4cb59aa96ef57 Signed-off-by: zhuangnk1 <zhuangnk1@lenovo.com> Reviewed-on: https://gerrit.mot.com/2687911 SME-Granted: SME Approvals Granted SLTApproved: Slta Waiver Tested-by: Jira Key Reviewed-by: Tao Sun <suntao2@motorola.com> Submit-Approved: Jira Key
This commit is contained in:
parent
9c5a392f7d
commit
11786f6aae
43 changed files with 9394 additions and 0 deletions
9
drivers/uwb/qm35/Android.mk
Normal file
9
drivers/uwb/qm35/Android.mk
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
DLKM_DIR := motorola/kernel/modules
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := qm35.ko
|
||||
LOCAL_MODULE_TAGS := optional
|
||||
LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT)
|
||||
LOCAL_ADDITIONAL_DEPENDENCIES := $(KERNEL_MODULES_OUT)/mmi_info.ko
|
||||
include $(DLKM_DIR)/AndroidKernelModule.mk
|
||||
27
drivers/uwb/qm35/Kbuild
Normal file
27
drivers/uwb/qm35/Kbuild
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# add -Wall to try to catch everything we can.
|
||||
EXTRA_CFLAGS += -Wall
|
||||
EXTRA_CFLAGS += -I$(ANDROID_BUILD_TOP)/motorola/kernel/modules/include
|
||||
|
||||
obj-m := qm35.o
|
||||
|
||||
qm35-y := \
|
||||
qm35-spi.o \
|
||||
qm35_rb.o \
|
||||
qmrom_spi.o \
|
||||
libqmrom/src/qmrom_common.o \
|
||||
libqmrom/src/qm357xx_rom_common.o \
|
||||
libqmrom/src/qm357xx_rom_b0.o \
|
||||
libqmrom/src/qm357xx_rom_c0.o \
|
||||
libqmrom/src/qmrom_log.o \
|
||||
libfwupdater/src/fwupdater.o \
|
||||
hsspi.o \
|
||||
hsspi_uci.o \
|
||||
hsspi_log.o \
|
||||
hsspi_coredump.o \
|
||||
debug.o \
|
||||
hsspi_test.o
|
||||
|
||||
qm35-y += debug_qmrom.o
|
||||
qm35-y += qm35-trace.o
|
||||
|
||||
KBUILD_EXTRA_SYMBOLS += $(CURDIR)/$(KBUILD_EXTMOD)/../../mmi_info/Module.symvers
|
||||
17
drivers/uwb/qm35/Makefile
Normal file
17
drivers/uwb/qm35/Makefile
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
all: modules
|
||||
|
||||
modules:
|
||||
$(MAKE) -C $(KERNEL_SRC) M=$(M) modules $(KBUILD_OPTIONS)
|
||||
|
||||
modules_install:
|
||||
$(MAKE) INSTALL_MOD_STRIP=1 -C $(KERNEL_SRC) M=$(M) modules_install
|
||||
|
||||
%:
|
||||
$(MAKE) -C $(KERNEL_SRC) M=$(M) $@ $(KBUILD_OPTIONS)
|
||||
|
||||
clean:
|
||||
rm -f *.o *.ko *.mod.c *.mod.o *~ .*.cmd Module.symvers
|
||||
rm -rf .tmp_versions
|
||||
|
||||
KBUILD_EXTRA_SYMBOLS := $(M)/../../mmi_info/Module.symvers
|
||||
|
||||
513
drivers/uwb/qm35/debug.c
Normal file
513
drivers/uwb/qm35/debug.c
Normal file
|
|
@ -0,0 +1,513 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* This file is part of the QM35 UCI stack for linux.
|
||||
*
|
||||
* Copyright (c) 2022 Qorvo US, Inc.
|
||||
*
|
||||
* This software is provided under the GNU General Public License, version 2
|
||||
* (GPLv2), as well as under a Qorvo commercial license.
|
||||
*
|
||||
* You may choose to use this software under the terms of the GPLv2 License,
|
||||
* version 2 ("GPLv2"), as published by the Free Software Foundation.
|
||||
* You should have received a copy of the GPLv2 along with this program. If
|
||||
* not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This program is distributed under the GPLv2 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 GPLv2 for more
|
||||
* details.
|
||||
*
|
||||
* If you cannot meet the requirements of the GPLv2, you may not use this
|
||||
* software for any purpose without first obtaining a commercial license from
|
||||
* Qorvo.
|
||||
* Please contact Qorvo to inquire about licensing terms.
|
||||
*
|
||||
* QM35 LOG layer HSSPI Protocol
|
||||
*/
|
||||
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/poll.h>
|
||||
#include <linux/fsnotify.h>
|
||||
|
||||
#include <linux/uwb/qmrom.h>
|
||||
#include <linux/uwb/qmrom_spi.h>
|
||||
|
||||
#include "qm35.h"
|
||||
#include "debug.h"
|
||||
#include "hsspi_test.h"
|
||||
|
||||
//#if IS_ENABLED(CONFIG_QM35_SPI_DEBUG_FW)
|
||||
extern void debug_rom_code_init(struct debug *debug);
|
||||
//#endif
|
||||
|
||||
static const struct file_operations debug_enable_fops;
|
||||
static const struct file_operations debug_log_level_fops;
|
||||
static const struct file_operations debug_test_hsspi_sleep_fops;
|
||||
|
||||
static void *priv_from_file(const struct file *filp)
|
||||
{
|
||||
return filp->f_path.dentry->d_inode->i_private;
|
||||
}
|
||||
|
||||
static ssize_t debug_enable_write(struct file *filp, const char __user *buff,
|
||||
size_t count, loff_t *off)
|
||||
{
|
||||
struct debug *debug;
|
||||
u8 enabled;
|
||||
|
||||
debug = priv_from_file(filp);
|
||||
|
||||
if (kstrtou8_from_user(buff, count, 10, &enabled))
|
||||
return -EFAULT;
|
||||
|
||||
if (debug->trace_ops)
|
||||
debug->trace_ops->enable_set(debug, enabled == 1 ? 1 : 0);
|
||||
else
|
||||
return -ENOSYS;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t debug_enable_read(struct file *filp, char __user *buff,
|
||||
size_t count, loff_t *off)
|
||||
{
|
||||
char enabled[2];
|
||||
struct debug *debug;
|
||||
|
||||
debug = priv_from_file(filp);
|
||||
|
||||
if (debug->trace_ops)
|
||||
enabled[0] = debug->trace_ops->enable_get(debug) + '0';
|
||||
else
|
||||
return -ENOSYS;
|
||||
|
||||
enabled[1] = '\n';
|
||||
|
||||
return simple_read_from_buffer(buff, count, off, enabled,
|
||||
sizeof(enabled));
|
||||
}
|
||||
|
||||
static ssize_t debug_log_level_write(struct file *filp, const char __user *buff,
|
||||
size_t count, loff_t *off)
|
||||
{
|
||||
u8 log_level = 0;
|
||||
struct log_module *log_module;
|
||||
|
||||
log_module = priv_from_file(filp);
|
||||
if (kstrtou8_from_user(buff, count, 10, &log_level))
|
||||
return -EFAULT;
|
||||
|
||||
if (log_module->debug->trace_ops)
|
||||
log_module->debug->trace_ops->level_set(log_module->debug,
|
||||
log_module, log_level);
|
||||
else
|
||||
return -ENOSYS;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t debug_log_level_read(struct file *filp, char __user *buff,
|
||||
size_t count, loff_t *off)
|
||||
{
|
||||
char log_level[2];
|
||||
struct log_module *log_module;
|
||||
|
||||
log_module = priv_from_file(filp);
|
||||
|
||||
if (log_module->debug->trace_ops)
|
||||
log_level[0] = log_module->debug->trace_ops->level_get(
|
||||
log_module->debug, log_module) +
|
||||
'0';
|
||||
else
|
||||
return -ENOSYS;
|
||||
|
||||
log_level[1] = '\n';
|
||||
|
||||
return simple_read_from_buffer(buff, count, off, log_level,
|
||||
sizeof(log_level));
|
||||
}
|
||||
|
||||
static ssize_t debug_test_hsspi_sleep_write(struct file *filp,
|
||||
const char __user *buff,
|
||||
size_t count, loff_t *off)
|
||||
{
|
||||
int sleep_inter_frame_ms;
|
||||
|
||||
if (kstrtoint_from_user(buff, count, 10, &sleep_inter_frame_ms))
|
||||
return -EFAULT;
|
||||
|
||||
hsspi_test_set_inter_frame_ms(sleep_inter_frame_ms);
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t debug_traces_read(struct file *filp, char __user *buff,
|
||||
size_t count, loff_t *off)
|
||||
{
|
||||
char *entry;
|
||||
rb_entry_size_t entry_size;
|
||||
//struct qm35_ctx *qm35_hdl;
|
||||
uint16_t ret;
|
||||
struct debug *debug;
|
||||
|
||||
debug = priv_from_file(filp);
|
||||
//qm35_hdl = container_of(debug, struct qm35_ctx, debug);
|
||||
|
||||
if (!debug->trace_ops)
|
||||
return -ENOSYS;
|
||||
|
||||
entry_size = debug->trace_ops->trace_get_next_size(debug);
|
||||
if (!entry_size) {
|
||||
if (filp->f_flags & O_NONBLOCK)
|
||||
return 0;
|
||||
|
||||
ret = wait_event_interruptible(
|
||||
debug->wq,
|
||||
(entry_size =
|
||||
debug->trace_ops->trace_get_next_size(debug)));
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (entry_size > count)
|
||||
return -EMSGSIZE;
|
||||
|
||||
entry = debug->trace_ops->trace_get_next(debug, &entry_size);
|
||||
if (!entry)
|
||||
return 0;
|
||||
|
||||
ret = copy_to_user(buff, entry, entry_size);
|
||||
|
||||
kfree(entry);
|
||||
|
||||
return ret ? -EFAULT : entry_size;
|
||||
}
|
||||
|
||||
static __poll_t debug_traces_poll(struct file *filp,
|
||||
struct poll_table_struct *wait)
|
||||
{
|
||||
struct debug *debug;
|
||||
__poll_t mask = 0;
|
||||
|
||||
debug = priv_from_file(filp);
|
||||
|
||||
poll_wait(filp, &debug->wq, wait);
|
||||
|
||||
if (debug->trace_ops && debug->trace_ops->trace_next_avail(debug))
|
||||
mask |= POLLIN;
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
static int debug_traces_open(struct inode *inodep, struct file *filep)
|
||||
{
|
||||
struct debug *debug;
|
||||
|
||||
debug = priv_from_file(filep);
|
||||
|
||||
mutex_lock(&debug->pv_filp_lock);
|
||||
if (debug->pv_filp) {
|
||||
mutex_unlock(&debug->pv_filp_lock);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
debug->pv_filp = filep;
|
||||
|
||||
if (debug->trace_ops)
|
||||
debug->trace_ops->trace_reset(debug);
|
||||
|
||||
mutex_unlock(&debug->pv_filp_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int debug_traces_release(struct inode *inodep, struct file *filep)
|
||||
{
|
||||
struct debug *debug;
|
||||
|
||||
debug = priv_from_file(filep);
|
||||
|
||||
mutex_lock(&debug->pv_filp_lock);
|
||||
debug->pv_filp = NULL;
|
||||
mutex_unlock(&debug->pv_filp_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t debug_coredump_read(struct file *filep, char __user *buff,
|
||||
size_t count, loff_t *off)
|
||||
{
|
||||
//struct qm35_ctx *qm35_hdl;
|
||||
struct debug *debug;
|
||||
char *cd;
|
||||
size_t cd_len = 0;
|
||||
|
||||
debug = priv_from_file(filep);
|
||||
//qm35_hdl = container_of(debug, struct qm35_ctx, debug);
|
||||
|
||||
if (!debug->coredump_ops)
|
||||
return -ENOSYS;
|
||||
|
||||
cd = debug->coredump_ops->coredump_get(debug, &cd_len);
|
||||
|
||||
return simple_read_from_buffer(buff, count, off, cd, cd_len);
|
||||
}
|
||||
|
||||
static ssize_t debug_coredump_write(struct file *filp, const char __user *buff,
|
||||
size_t count, loff_t *off)
|
||||
{
|
||||
struct debug *debug;
|
||||
u8 force;
|
||||
|
||||
debug = priv_from_file(filp);
|
||||
|
||||
if (kstrtou8_from_user(buff, count, 10, &force))
|
||||
return -EFAULT;
|
||||
|
||||
if (debug->coredump_ops && force != 0)
|
||||
debug->coredump_ops->coredump_force(debug);
|
||||
else if (force == 0)
|
||||
pr_warn("qm35: write non null value to force coredump\n");
|
||||
else
|
||||
return -ENOSYS;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t debug_hw_reset_write(struct file *filp, const char __user *buff,
|
||||
size_t count, loff_t *off)
|
||||
{
|
||||
struct qm35_ctx *qm35_hdl;
|
||||
struct debug *debug;
|
||||
int ret;
|
||||
u8 reset;
|
||||
|
||||
debug = priv_from_file(filp);
|
||||
qm35_hdl = container_of(debug, struct qm35_ctx, debug);
|
||||
|
||||
if (kstrtou8_from_user(buff, count, 10, &reset))
|
||||
return -EFAULT;
|
||||
|
||||
ret = -1;
|
||||
if (reset != 0) {
|
||||
pr_info("qm35: resetting chip...\n");
|
||||
ret = qm35_reset_sync(qm35_hdl);
|
||||
} else
|
||||
pr_warn("qm35: write non null value to force a hw reset\n");
|
||||
|
||||
if (ret)
|
||||
return -ENOSYS;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static const struct file_operations debug_enable_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.write = debug_enable_write,
|
||||
.read = debug_enable_read,
|
||||
};
|
||||
|
||||
static const struct file_operations debug_log_level_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.write = debug_log_level_write,
|
||||
.read = debug_log_level_read
|
||||
};
|
||||
|
||||
static const struct file_operations debug_test_hsspi_sleep_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.write = debug_test_hsspi_sleep_write
|
||||
};
|
||||
|
||||
static const struct file_operations debug_traces_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = debug_traces_open,
|
||||
.release = debug_traces_release,
|
||||
.read = debug_traces_read,
|
||||
.poll = debug_traces_poll,
|
||||
.llseek = no_llseek,
|
||||
};
|
||||
|
||||
static const struct file_operations debug_coredump_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.read = debug_coredump_read,
|
||||
.write = debug_coredump_write,
|
||||
};
|
||||
|
||||
static const struct file_operations debug_hw_reset_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.write = debug_hw_reset_write,
|
||||
};
|
||||
|
||||
int debug_create_module_entry(struct debug *debug,
|
||||
struct log_module *log_module)
|
||||
{
|
||||
struct dentry *dir;
|
||||
struct dentry *file;
|
||||
|
||||
dir = debugfs_create_dir(log_module->name, debug->fw_dir);
|
||||
if (!dir) {
|
||||
pr_err("qm35: failed to create /sys/kernel/debug/uwb0/%s\n",
|
||||
log_module->name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
file = debugfs_create_file("log_level", 0644, dir, log_module,
|
||||
&debug_log_level_fops);
|
||||
if (!file) {
|
||||
pr_err("qm35: failed to create /sys/kernel/debug/uwb0/%s/log_level\n",
|
||||
log_module->name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
pr_info("qm35 debug: created /sys/kernel/debug/uwb0/%s/log_level\n",
|
||||
log_module->name);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void debug_new_trace_available(struct debug *debug)
|
||||
{
|
||||
if (debug->pv_filp)
|
||||
fsnotify_modify(debug->pv_filp);
|
||||
|
||||
wake_up_interruptible(&debug->wq);
|
||||
}
|
||||
|
||||
static int debug_devid_show(struct seq_file *s, void *unused)
|
||||
{
|
||||
struct debug *debug = (struct debug *)s->private;
|
||||
uint16_t dev_id;
|
||||
int rc;
|
||||
|
||||
if (debug->trace_ops && debug->trace_ops->get_dev_id) {
|
||||
rc = debug->trace_ops->get_dev_id(debug, &dev_id);
|
||||
if (rc < 0)
|
||||
return -EIO;
|
||||
seq_printf(s, "deca%04x\n", dev_id);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int debug_socid_show(struct seq_file *s, void *unused)
|
||||
{
|
||||
struct debug *debug = (struct debug *)s->private;
|
||||
uint8_t soc_id[QM357XX_ROM_SOC_ID_LEN];
|
||||
int rc;
|
||||
|
||||
if (debug->trace_ops && debug->trace_ops->get_soc_id) {
|
||||
rc = debug->trace_ops->get_soc_id(debug, soc_id);
|
||||
if (rc < 0)
|
||||
return -EIO;
|
||||
seq_printf(s, "%*phN\n", QM357XX_ROM_SOC_ID_LEN, soc_id);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEFINE_SHOW_ATTRIBUTE(debug_devid);
|
||||
DEFINE_SHOW_ATTRIBUTE(debug_socid);
|
||||
|
||||
void debug_soc_info_available(struct debug *debug)
|
||||
{
|
||||
struct dentry *file;
|
||||
|
||||
file = debugfs_create_file("dev_id", 0444, debug->chip_dir, debug,
|
||||
&debug_devid_fops);
|
||||
if (!file) {
|
||||
pr_err("qm35: failed to create /sys/kernel/debug/uwb0/fw/dev_id\n");
|
||||
goto unregister;
|
||||
}
|
||||
|
||||
file = debugfs_create_file("soc_id", 0444, debug->chip_dir, debug,
|
||||
&debug_socid_fops);
|
||||
if (!file) {
|
||||
pr_err("qm35: failed to create /sys/kernel/debug/uwb0/fw/soc_id\n");
|
||||
goto unregister;
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
unregister:
|
||||
debugfs_remove_recursive(debug->chip_dir);
|
||||
}
|
||||
|
||||
int debug_init_root(struct debug *debug, struct dentry *root)
|
||||
{
|
||||
debug->root_dir = debugfs_create_dir("uwb0", root);
|
||||
if (!debug->root_dir) {
|
||||
pr_err("qm35: failed to create /sys/kernel/debug/uwb0\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int debug_init(struct debug *debug)
|
||||
{
|
||||
struct dentry *file;
|
||||
|
||||
init_waitqueue_head(&debug->wq);
|
||||
mutex_init(&debug->pv_filp_lock);
|
||||
debug->pv_filp = NULL;
|
||||
|
||||
debug->fw_dir = debugfs_create_dir("fw", debug->root_dir);
|
||||
if (!debug->fw_dir) {
|
||||
pr_err("qm35: failed to create /sys/kernel/debug/uwb0/fw\n");
|
||||
goto unregister;
|
||||
}
|
||||
|
||||
debug->chip_dir = debugfs_create_dir("chip", debug->root_dir);
|
||||
if (!debug->chip_dir) {
|
||||
pr_err("qm35: failed to create /sys/kernel/debug/uwb0/chip\n");
|
||||
goto unregister;
|
||||
}
|
||||
|
||||
file = debugfs_create_file("hw_reset", 0444, debug->chip_dir, debug,
|
||||
&debug_hw_reset_fops);
|
||||
if (!file) {
|
||||
pr_err("qm35: failed to create /sys/kernel/debug/uwb0/chip/hw_reset\n");
|
||||
goto unregister;
|
||||
}
|
||||
|
||||
file = debugfs_create_file("enable", 0644, debug->fw_dir, debug,
|
||||
&debug_enable_fops);
|
||||
if (!file) {
|
||||
pr_err("qm35: failed to create /sys/kernel/debug/uwb0/fw/enable\n");
|
||||
goto unregister;
|
||||
}
|
||||
|
||||
file = debugfs_create_file("traces", 0444, debug->fw_dir, debug,
|
||||
&debug_traces_fops);
|
||||
if (!file) {
|
||||
pr_err("qm35: failed to create /sys/kernel/debug/uwb0/fw/traces\n");
|
||||
goto unregister;
|
||||
}
|
||||
|
||||
file = debugfs_create_file("coredump", 0444, debug->fw_dir, debug,
|
||||
&debug_coredump_fops);
|
||||
if (!file) {
|
||||
pr_err("qm35: failed to create /sys/kernel/debug/uwb0/fw/coredump\n");
|
||||
goto unregister;
|
||||
}
|
||||
|
||||
file = debugfs_create_file("test_sleep_hsspi_ms", 0200, debug->fw_dir,
|
||||
debug, &debug_test_hsspi_sleep_fops);
|
||||
if (!file) {
|
||||
pr_err("qm35: failed to create /sys/kernel/debug/uwb0/fw/test_sleep_hsspi\n");
|
||||
goto unregister;
|
||||
}
|
||||
|
||||
//#if IS_ENABLED(CONFIG_QM35_SPI_DEBUG_FW)
|
||||
debug_rom_code_init(debug);
|
||||
//#endif
|
||||
|
||||
return 0;
|
||||
|
||||
unregister:
|
||||
debug_deinit(debug);
|
||||
return -1;
|
||||
}
|
||||
|
||||
void debug_deinit(struct debug *debug)
|
||||
{
|
||||
wake_up_interruptible(&debug->wq);
|
||||
debugfs_remove_recursive(debug->root_dir);
|
||||
}
|
||||
90
drivers/uwb/qm35/debug.h
Normal file
90
drivers/uwb/qm35/debug.h
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
|
||||
/*
|
||||
* This file is part of the QM35 UCI stack for linux.
|
||||
*
|
||||
* Copyright (c) 2022 Qorvo US, Inc.
|
||||
*
|
||||
* This software is provided under the GNU General Public License, version 2
|
||||
* (GPLv2), as well as under a Qorvo commercial license.
|
||||
*
|
||||
* You may choose to use this software under the terms of the GPLv2 License,
|
||||
* version 2 ("GPLv2"), as published by the Free Software Foundation.
|
||||
* You should have received a copy of the GPLv2 along with this program. If
|
||||
* not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This program is distributed under the GPLv2 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 GPLv2 for more
|
||||
* details.
|
||||
*
|
||||
* If you cannot meet the requirements of the GPLv2, you may not use this
|
||||
* software for any purpose without first obtaining a commercial license from
|
||||
* Qorvo.
|
||||
* Please contact Qorvo to inquire about licensing terms.
|
||||
*
|
||||
* QM35 LOG layer HSSPI Protocol
|
||||
*/
|
||||
|
||||
#ifndef __DEBUG_H__
|
||||
#define __DEBUG_H__
|
||||
|
||||
#include <linux/fs.h>
|
||||
#include <linux/mutex.h>
|
||||
#include "qm35_rb.h"
|
||||
|
||||
struct debug;
|
||||
struct log_module;
|
||||
|
||||
struct debug_trace_ops {
|
||||
void (*enable_set)(struct debug *dbg, int enable);
|
||||
int (*enable_get)(struct debug *dbg);
|
||||
void (*level_set)(struct debug *dbg, struct log_module *log_module,
|
||||
int lvl);
|
||||
int (*level_get)(struct debug *dbg, struct log_module *log_module);
|
||||
char *(*trace_get_next)(struct debug *dbg, rb_entry_size_t *len);
|
||||
rb_entry_size_t (*trace_get_next_size)(struct debug *dbg);
|
||||
bool (*trace_next_avail)(struct debug *dbg);
|
||||
void (*trace_reset)(struct debug *dbg);
|
||||
int (*get_dev_id)(struct debug *dbg, uint16_t *dev_id);
|
||||
int (*get_soc_id)(struct debug *dbg, uint8_t *soc_id);
|
||||
};
|
||||
|
||||
struct debug_coredump_ops {
|
||||
char *(*coredump_get)(struct debug *dbg, size_t *len);
|
||||
int (*coredump_force)(struct debug *dbg);
|
||||
};
|
||||
|
||||
struct debug {
|
||||
struct dentry *root_dir;
|
||||
struct dentry *fw_dir;
|
||||
struct dentry *chip_dir;
|
||||
const struct debug_trace_ops *trace_ops;
|
||||
const struct debug_coredump_ops *coredump_ops;
|
||||
struct wait_queue_head wq;
|
||||
struct file *pv_filp;
|
||||
struct mutex pv_filp_lock;
|
||||
struct firmware *certificate;
|
||||
};
|
||||
|
||||
// TODO move this from here to a commom place for both log layer and debug
|
||||
struct log_module {
|
||||
uint8_t id;
|
||||
uint8_t lvl;
|
||||
char name[64];
|
||||
struct completion *read_done;
|
||||
struct debug *debug;
|
||||
};
|
||||
|
||||
int debug_init_root(struct debug *debug, struct dentry *root);
|
||||
int debug_init(struct debug *debug);
|
||||
void debug_deinit(struct debug *debug);
|
||||
|
||||
int debug_create_module_entry(struct debug *debug,
|
||||
struct log_module *log_module);
|
||||
|
||||
void debug_new_trace_available(struct debug *debug);
|
||||
|
||||
void debug_soc_info_available(struct debug *debug);
|
||||
|
||||
#endif // __DEBUG_H__
|
||||
462
drivers/uwb/qm35/debug_qmrom.c
Normal file
462
drivers/uwb/qm35/debug_qmrom.c
Normal file
|
|
@ -0,0 +1,462 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* This file is part of the QM35 UCI stack for linux.
|
||||
*
|
||||
* Copyright (c) 2022 Qorvo US, Inc.
|
||||
*
|
||||
* This software is provided under the GNU General Public License, version 2
|
||||
* (GPLv2), as well as under a Qorvo commercial license.
|
||||
*
|
||||
* You may choose to use this software under the terms of the GPLv2 License,
|
||||
* version 2 ("GPLv2"), as published by the Free Software Foundation.
|
||||
* You should have received a copy of the GPLv2 along with this program. If
|
||||
* not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This program is distributed under the GPLv2 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 GPLv2 for more
|
||||
* details.
|
||||
*
|
||||
* If you cannot meet the requirements of the GPLv2, you may not use this
|
||||
* software for any purpose without first obtaining a commercial license from
|
||||
* Qorvo.
|
||||
* Please contact Qorvo to inquire about licensing terms.
|
||||
*
|
||||
* QM35 LOG layer HSSPI Protocol
|
||||
*/
|
||||
|
||||
#include <linux/version.h>
|
||||
#include <linux/printk.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/fsnotify.h>
|
||||
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0))
|
||||
#include <linux/kernel_read_file.h>
|
||||
#endif
|
||||
#include <linux/poll.h>
|
||||
#include <linux/vmalloc.h>
|
||||
|
||||
#include <linux/uwb/qmrom.h>
|
||||
#include <linux/uwb/qmrom_log.h>
|
||||
#include <linux/uwb/qmrom_spi.h>
|
||||
#include <linux/uwb/qmrom_utils.h>
|
||||
|
||||
#include <linux/uwb/fwupdater.h>
|
||||
|
||||
#include "qm35.h"
|
||||
#include "debug.h"
|
||||
|
||||
#define FWUPDATER_SPI_SPEED_HZ 20000000
|
||||
|
||||
extern int fu_spi_speed_hz;
|
||||
extern int qmrom_spi_speed_hz;
|
||||
|
||||
#define QMROM_RETRIES 10
|
||||
|
||||
static void *priv_from_file(const struct file *filp)
|
||||
{
|
||||
return filp->f_path.dentry->d_inode->i_private;
|
||||
}
|
||||
|
||||
static struct qm35_ctx *rom_test_prepare(struct file *filp)
|
||||
{
|
||||
struct debug *debug = priv_from_file(filp);
|
||||
struct qm35_ctx *qm35_hdl = container_of(debug, struct qm35_ctx, debug);
|
||||
|
||||
qm35_hsspi_stop(qm35_hdl);
|
||||
qmrom_set_log_device(&qm35_hdl->spi->dev, LOG_DBG);
|
||||
return qm35_hdl;
|
||||
}
|
||||
|
||||
static void rom_test_unprepare(struct qm35_ctx *qm35_hdl)
|
||||
{
|
||||
qmrom_set_log_device(&qm35_hdl->spi->dev, LOG_WARN);
|
||||
qm35_hsspi_start(qm35_hdl);
|
||||
}
|
||||
|
||||
static struct firmware *file2firmware(const char *filename)
|
||||
{
|
||||
struct firmware *firmware =
|
||||
kmalloc(sizeof(struct firmware), GFP_KERNEL | __GFP_ZERO);
|
||||
if (!firmware) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0))
|
||||
{
|
||||
ssize_t bytes_read = kernel_read_file_from_path(
|
||||
filename, 0, (void **)&firmware->data, INT_MAX,
|
||||
&firmware->size, READING_FIRMWARE);
|
||||
if (bytes_read < 0) {
|
||||
pr_err("kernel_read_file_from_path(%s) returned %d\n",
|
||||
filename, (int)bytes_read);
|
||||
goto fail;
|
||||
}
|
||||
if (bytes_read != firmware->size) {
|
||||
pr_err("kernel_read_file_from_path returned %zu; expected %zu\n",
|
||||
bytes_read, firmware->size);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
#else
|
||||
{
|
||||
loff_t size = 0;
|
||||
int ret = kernel_read_file_from_path(filename,
|
||||
(void **)&firmware->data,
|
||||
&size, INT_MAX,
|
||||
READING_FIRMWARE);
|
||||
if (ret < 0) {
|
||||
pr_err("kernel_read_file_from_path(%s) returned %d\n",
|
||||
filename, ret);
|
||||
goto fail;
|
||||
}
|
||||
firmware->size = (size_t)size;
|
||||
}
|
||||
#endif
|
||||
|
||||
print_hex_dump(KERN_DEBUG, "Bin file:", DUMP_PREFIX_ADDRESS, 16, 1,
|
||||
firmware->data, 16, false);
|
||||
return firmware;
|
||||
|
||||
fail:
|
||||
if (firmware) {
|
||||
if (firmware->data)
|
||||
vfree(firmware->data);
|
||||
kfree(firmware);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static ssize_t rom_probe(struct file *filp, const char __user *buff,
|
||||
size_t count, loff_t *off)
|
||||
{
|
||||
struct qm35_ctx *qm35_hdl = rom_test_prepare(filp);
|
||||
struct qmrom_handle *h;
|
||||
|
||||
pr_info("Starting the probe test...\n");
|
||||
h = qmrom_init(&qm35_hdl->spi->dev, qm35_hdl, qm35_hdl->gpio_ss_rdy,
|
||||
qmrom_spi_speed_hz, QMROM_RETRIES,
|
||||
qmrom_spi_reset_device, DEVICE_GEN_QM357XX);
|
||||
if (!h) {
|
||||
pr_err("qmrom_init failed\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
pr_info("chip_revision %#2x\n", h->chip_rev);
|
||||
pr_info("device version %#02x\n", h->device_version);
|
||||
if (h->chip_rev != 0xa001) {
|
||||
pr_info("lcs_state %u\n", h->qm357xx_soc_info.lcs_state);
|
||||
print_hex_dump(KERN_DEBUG, "soc_id:", DUMP_PREFIX_NONE, 16, 1,
|
||||
h->qm357xx_soc_info.soc_id,
|
||||
sizeof(h->qm357xx_soc_info.soc_id), false);
|
||||
print_hex_dump(KERN_DEBUG, "uuid:", DUMP_PREFIX_NONE, 16, 1,
|
||||
h->qm357xx_soc_info.uuid,
|
||||
sizeof(h->qm357xx_soc_info.uuid), false);
|
||||
}
|
||||
qmrom_deinit(h);
|
||||
qmrom_spi_reset_device(qm35_hdl);
|
||||
qmrom_msleep(100);
|
||||
|
||||
end:
|
||||
rom_test_unprepare(qm35_hdl);
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t rom_flash_dbg_cert(struct file *filp, const char __user *buff,
|
||||
size_t count, loff_t *off)
|
||||
{
|
||||
struct qm35_ctx *qm35_hdl = rom_test_prepare(filp);
|
||||
struct firmware *certificate = NULL;
|
||||
struct qmrom_handle *h = NULL;
|
||||
char *filename;
|
||||
int err;
|
||||
|
||||
filename = kmalloc(count, GFP_KERNEL);
|
||||
if (!filename) {
|
||||
count = -ENOMEM;
|
||||
goto end;
|
||||
}
|
||||
|
||||
err = copy_from_user(filename, buff, count);
|
||||
if (err) {
|
||||
pr_err("copy_from_user failed with error %d\n", err);
|
||||
count = err;
|
||||
goto end;
|
||||
}
|
||||
filename[count - 1] = '\0';
|
||||
certificate = file2firmware(filename);
|
||||
if (!certificate || certificate->size != DEBUG_CERTIFICATE_SIZE) {
|
||||
pr_err("%s: file retrieval failed, abort (%s)\n", __func__,
|
||||
certificate ? "wrong size" : "not found");
|
||||
count = -1;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Flash the debug certificate */
|
||||
pr_info("Flashing debug certificate %s...\n", filename);
|
||||
|
||||
h = qmrom_init(&qm35_hdl->spi->dev, qm35_hdl, qm35_hdl->gpio_ss_rdy,
|
||||
qmrom_spi_speed_hz, QMROM_RETRIES,
|
||||
qmrom_spi_reset_device, DEVICE_GEN_QM357XX);
|
||||
if (!h) {
|
||||
pr_err("qmrom_init failed\n");
|
||||
goto end;
|
||||
}
|
||||
err = qm357xx_rom_flash_dbg_cert(h, certificate);
|
||||
if (err)
|
||||
pr_err("Flashing debug certificate %s failed with %d!\n",
|
||||
filename, err);
|
||||
else
|
||||
pr_info("Flashing debug certificate %s succeeded!\n", filename);
|
||||
|
||||
end:
|
||||
if (h)
|
||||
qmrom_deinit(h);
|
||||
if (certificate) {
|
||||
if (certificate->data)
|
||||
vfree(certificate->data);
|
||||
kfree(certificate);
|
||||
}
|
||||
qmrom_spi_reset_device(qm35_hdl);
|
||||
qmrom_msleep(100);
|
||||
|
||||
rom_test_unprepare(qm35_hdl);
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t rom_erase_dbg_cert(struct file *filp, const char __user *buff,
|
||||
size_t count, loff_t *off)
|
||||
{
|
||||
struct qm35_ctx *qm35_hdl = rom_test_prepare(filp);
|
||||
struct qmrom_handle *h = NULL;
|
||||
int err;
|
||||
|
||||
pr_info("Erasing debug certificate...\n");
|
||||
|
||||
h = qmrom_init(&qm35_hdl->spi->dev, qm35_hdl, qm35_hdl->gpio_ss_rdy,
|
||||
qmrom_spi_speed_hz, QMROM_RETRIES,
|
||||
qmrom_spi_reset_device, DEVICE_GEN_QM357XX);
|
||||
if (!h) {
|
||||
pr_err("qmrom_init failed\n");
|
||||
goto end;
|
||||
}
|
||||
err = qm357xx_rom_erase_dbg_cert(h);
|
||||
if (err)
|
||||
pr_err("Erasing debug certificate failed with %d!\n", err);
|
||||
else
|
||||
pr_info("Erasing debug certificate succeeded!\n");
|
||||
|
||||
end:
|
||||
if (h)
|
||||
qmrom_deinit(h);
|
||||
|
||||
qmrom_spi_reset_device(qm35_hdl);
|
||||
qmrom_msleep(100);
|
||||
|
||||
rom_test_unprepare(qm35_hdl);
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t rom_flash_fw(struct file *filp, const char __user *buff,
|
||||
size_t count, loff_t *off)
|
||||
{
|
||||
struct qm35_ctx *qm35_hdl = rom_test_prepare(filp);
|
||||
struct firmware *fw = NULL;
|
||||
struct qmrom_handle *h = NULL;
|
||||
char *filename;
|
||||
int rc;
|
||||
|
||||
filename = kmalloc(count, GFP_KERNEL);
|
||||
if (!filename) {
|
||||
count = -ENOMEM;
|
||||
goto end;
|
||||
}
|
||||
|
||||
rc = copy_from_user(filename, buff, count);
|
||||
if (rc) {
|
||||
pr_err("copy_from_user failed with error %d\n", rc);
|
||||
goto end;
|
||||
}
|
||||
filename[count - 1] = '\0';
|
||||
fw = file2firmware(filename);
|
||||
if (!fw) {
|
||||
pr_err("%s: file %s retrieval failed, abort\n", __func__,
|
||||
filename);
|
||||
rc = -1;
|
||||
goto end;
|
||||
}
|
||||
|
||||
pr_info("Flashing image %s (%pK->data %pK)...\n", filename, fw,
|
||||
fw->data);
|
||||
|
||||
h = qmrom_init(&qm35_hdl->spi->dev, qm35_hdl, qm35_hdl->gpio_ss_rdy,
|
||||
qmrom_spi_speed_hz, QMROM_RETRIES,
|
||||
qmrom_spi_reset_device, DEVICE_GEN_QM357XX);
|
||||
if (!h) {
|
||||
pr_err("qmrom_init failed\n");
|
||||
rc = -1;
|
||||
goto end;
|
||||
}
|
||||
rc = qm357xx_rom_flash_fw(h, fw);
|
||||
if (rc)
|
||||
pr_err("Flashing firmware %s failed with %d!\n", filename, rc);
|
||||
else
|
||||
pr_info("Flashing firmware %s succeeded!\n", filename);
|
||||
|
||||
end:
|
||||
kfree(filename);
|
||||
if (h)
|
||||
qmrom_deinit(h);
|
||||
if (fw) {
|
||||
if (fw->data)
|
||||
vfree(fw->data);
|
||||
kfree(fw);
|
||||
}
|
||||
rom_test_unprepare(qm35_hdl);
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t rom_flash_fw_pkg(struct file *filp, const char __user *buff,
|
||||
size_t count, loff_t *off)
|
||||
{
|
||||
struct qm35_ctx *qm35_hdl = rom_test_prepare(filp);
|
||||
const struct firmware *fw = NULL;
|
||||
struct qmrom_handle *h = NULL;
|
||||
char *filename = NULL, *fw_data;
|
||||
uint32_t fw_size;
|
||||
int rc;
|
||||
|
||||
if (fu_spi_speed_hz == 0)
|
||||
fu_spi_speed_hz = FWUPDATER_SPI_SPEED_HZ;
|
||||
|
||||
filename = kmalloc(count, GFP_KERNEL);
|
||||
if (!filename) {
|
||||
count = -ENOMEM;
|
||||
goto end;
|
||||
}
|
||||
rc = copy_from_user(filename, buff, count);
|
||||
if (rc) {
|
||||
pr_err("copy_from_user failed with error %d\n", rc);
|
||||
goto end;
|
||||
}
|
||||
filename[count - 1] = '\0';
|
||||
fw = file2firmware(filename);
|
||||
if (!fw || !fw->data) {
|
||||
pr_err("%s: file %s retrieval failed (%s), abort\n", __func__,
|
||||
filename, fw ? "no data read" : "not found");
|
||||
rc = -1;
|
||||
goto end;
|
||||
}
|
||||
|
||||
pr_info("Flashing fw_updater...\n");
|
||||
|
||||
h = qmrom_init(&qm35_hdl->spi->dev, qm35_hdl, qm35_hdl->gpio_ss_rdy,
|
||||
qmrom_spi_speed_hz, QMROM_RETRIES,
|
||||
qmrom_spi_reset_device, DEVICE_GEN_QM357XX);
|
||||
if (!h) {
|
||||
pr_err("qmrom_init failed\n");
|
||||
rc = -1;
|
||||
goto end;
|
||||
}
|
||||
rc = qm357xx_rom_flash_fw(h, fw);
|
||||
if (rc) {
|
||||
pr_err("Flashing fw_updater failed with %d!\n", rc);
|
||||
goto end;
|
||||
}
|
||||
|
||||
pr_info("Flashing fw_updater succeeded, flashing the fw package now...\n");
|
||||
qmrom_spi_set_freq(fu_spi_speed_hz);
|
||||
|
||||
rc = qm357xx_rom_fw_macro_pkg_get_fw_idx(fw, 1, &fw_size, &fw_data);
|
||||
if (rc) {
|
||||
pr_err("%s: FW MACRO PACKAGE corupted = %d\n", __func__, rc);
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (*(uint32_t *)fw_data == CRYPTO_FIRMWARE_PACK_MAGIC_VALUE) {
|
||||
run_fwupdater(h, fw_data, fw_size);
|
||||
} else {
|
||||
rc = -EINVAL;
|
||||
pr_err("FW PACKAGE not found - %04x! fw_size = %d\n",
|
||||
*(uint32_t *)fw_data, fw_size);
|
||||
goto end;
|
||||
}
|
||||
pr_info("FW package flashing done, rebooting the QM...\n");
|
||||
|
||||
end:
|
||||
qmrom_spi_reset_device(qm35_hdl);
|
||||
|
||||
if (filename)
|
||||
kfree(filename);
|
||||
if (h)
|
||||
qmrom_deinit(h);
|
||||
if (fw) {
|
||||
if (fw->data)
|
||||
vfree(fw->data);
|
||||
kfree(fw);
|
||||
}
|
||||
rom_test_unprepare(qm35_hdl);
|
||||
return count;
|
||||
}
|
||||
|
||||
static const struct file_operations rom_probe_fops = { .owner = THIS_MODULE,
|
||||
.write = rom_probe };
|
||||
|
||||
static const struct file_operations rom_flash_dbg_cert_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.write = rom_flash_dbg_cert
|
||||
};
|
||||
|
||||
static const struct file_operations rom_erase_dbg_cert_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.write = rom_erase_dbg_cert
|
||||
};
|
||||
|
||||
static const struct file_operations rom_flash_fw_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.write = rom_flash_fw
|
||||
};
|
||||
|
||||
static const struct file_operations rom_flash_fw_pkg_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.write = rom_flash_fw_pkg
|
||||
};
|
||||
|
||||
void debug_rom_code_init(struct debug *debug)
|
||||
{
|
||||
struct dentry *file;
|
||||
file = debugfs_create_file("rom_probe", 0200, debug->fw_dir, debug,
|
||||
&rom_probe_fops);
|
||||
if (!file) {
|
||||
pr_err("qm35: failed to create uwb0/fw/rom_probe\n");
|
||||
return;
|
||||
}
|
||||
|
||||
file = debugfs_create_file("rom_flash_dbg_cert", 0200, debug->fw_dir,
|
||||
debug, &rom_flash_dbg_cert_fops);
|
||||
if (!file) {
|
||||
pr_err("qm35: failed to create uwb0/fw/rom_flash_dbg_cert\n");
|
||||
return;
|
||||
}
|
||||
|
||||
file = debugfs_create_file("rom_erase_dbg_cert", 0200, debug->fw_dir,
|
||||
debug, &rom_erase_dbg_cert_fops);
|
||||
if (!file) {
|
||||
pr_err("qm35: failed to create uwb0/fw/rom_erase_dbg_cert\n");
|
||||
return;
|
||||
}
|
||||
|
||||
file = debugfs_create_file("rom_flash_fw", 0200, debug->fw_dir, debug,
|
||||
&rom_flash_fw_fops);
|
||||
if (!file) {
|
||||
pr_err("qm35: failed to create uwb0/fw/rom_flash_fw\n");
|
||||
return;
|
||||
}
|
||||
|
||||
file = debugfs_create_file("rom_flash_fw_pkg", 0200, debug->fw_dir,
|
||||
debug, &rom_flash_fw_pkg_fops);
|
||||
if (!file) {
|
||||
pr_err("qm35: failed to create uwb0/fw/rom_flash_fw_pkg\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
744
drivers/uwb/qm35/hsspi.c
Normal file
744
drivers/uwb/qm35/hsspi.c
Normal file
|
|
@ -0,0 +1,744 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
/*
|
||||
* This file is part of the QM35 UCI stack for linux.
|
||||
*
|
||||
* Copyright (c) 2021 Qorvo US, Inc.
|
||||
*
|
||||
* This software is provided under the GNU General Public License, version 2
|
||||
* (GPLv2), as well as under a Qorvo commercial license.
|
||||
*
|
||||
* You may choose to use this software under the terms of the GPLv2 License,
|
||||
* version 2 ("GPLv2"), as published by the Free Software Foundation.
|
||||
* You should have received a copy of the GPLv2 along with this program. If
|
||||
* not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This program is distributed under the GPLv2 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 GPLv2 for more
|
||||
* details.
|
||||
*
|
||||
* If you cannot meet the requirements of the GPLv2, you may not use this
|
||||
* software for any purpose without first obtaining a commercial license from
|
||||
* Qorvo.
|
||||
* Please contact Qorvo to inquire about licensing terms.
|
||||
*
|
||||
* QM35 HSSPI Protocol
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/delay.h>
|
||||
|
||||
#include "qm35-trace.h"
|
||||
#include "hsspi.h"
|
||||
|
||||
/* STC HOST flags */
|
||||
#define STC_HOST_WR BIT(7)
|
||||
#define STC_HOST_PRD BIT(6)
|
||||
#define STC_HOST_RD BIT(5)
|
||||
|
||||
/* STC SOC flags */
|
||||
#define STC_SOC_ODW BIT(7)
|
||||
#define STC_SOC_OA BIT(6)
|
||||
#define STC_SOC_RDY BIT(5)
|
||||
#define STC_SOC_ERR BIT(4)
|
||||
|
||||
#define SS_READY_TIMEOUT_MS (250)
|
||||
|
||||
#define MAX_SUCCESSIVE_ERRORS (5)
|
||||
#define SPI_CS_SETUP_DELAY_US (5)
|
||||
|
||||
#ifdef HSSPI_MANUAL_CS_SETUP
|
||||
#define HSSPI_MANUAL_CS_SETUP_US SPI_CS_SETUP_DELAY_US
|
||||
#endif
|
||||
|
||||
extern int spi_speed_hz;
|
||||
|
||||
struct hsspi_work {
|
||||
struct list_head list;
|
||||
enum hsspi_work_type type;
|
||||
union {
|
||||
struct {
|
||||
struct hsspi_block *blk;
|
||||
struct hsspi_layer *layer;
|
||||
} tx;
|
||||
struct completion *completion;
|
||||
};
|
||||
};
|
||||
|
||||
int test_sleep_after_ss_ready_us = 0;
|
||||
|
||||
static inline bool layer_id_is_valid(struct hsspi *hsspi, u8 ul)
|
||||
{
|
||||
return (ul < ARRAY_SIZE(hsspi->layers));
|
||||
}
|
||||
|
||||
/**
|
||||
* get_work() - get a work from the list
|
||||
*
|
||||
* @hsspi: &struct hsspi
|
||||
*
|
||||
* Return: a &struct hsspi_work
|
||||
* The work can be:
|
||||
* - a TX work enqueued by hsspi_send
|
||||
* - a COMPLETION work used for synchronization (always allocated on stack)
|
||||
*/
|
||||
static struct hsspi_work *get_work(struct hsspi *hsspi)
|
||||
{
|
||||
struct hsspi_work *hw;
|
||||
|
||||
spin_lock(&hsspi->lock);
|
||||
|
||||
hw = list_first_entry_or_null(&hsspi->work_list, struct hsspi_work,
|
||||
list);
|
||||
if (hw)
|
||||
list_del(&hw->list);
|
||||
|
||||
spin_unlock(&hsspi->lock);
|
||||
|
||||
trace_hsspi_get_work(&hsspi->spi->dev, hw ? hw->type : -1);
|
||||
return hw;
|
||||
}
|
||||
|
||||
/**
|
||||
* is_txrx_waiting() - is there something to do
|
||||
*
|
||||
* @hsspi: &struct hsspi
|
||||
*
|
||||
* Return: True if there is a TX work available or if the SS_IRQ flag
|
||||
* is set. False otherwise.
|
||||
*/
|
||||
static bool is_txrx_waiting(struct hsspi *hsspi)
|
||||
{
|
||||
enum hsspi_state state;
|
||||
bool is_empty;
|
||||
|
||||
spin_lock(&hsspi->lock);
|
||||
|
||||
is_empty = list_empty(&hsspi->work_list);
|
||||
state = hsspi->state;
|
||||
|
||||
spin_unlock(&hsspi->lock);
|
||||
|
||||
trace_hsspi_is_txrx_waiting(&hsspi->spi->dev, is_empty, state);
|
||||
/*
|
||||
* There is no work in the list but we must check SS_IRQ to
|
||||
* know if we need to make an empty TX transfer with PRE_READ
|
||||
* flag set.
|
||||
*/
|
||||
return !is_empty || ((state == HSSPI_RUNNING) &&
|
||||
test_bit(HSSPI_FLAGS_SS_IRQ, hsspi->flags));
|
||||
}
|
||||
|
||||
/**
|
||||
* hsspi_wait_ss_ready() - waits for ss_ready to be up
|
||||
*
|
||||
* @hsspi: &struct hsspi
|
||||
*
|
||||
* Return: 0 if ss_ready is up and EAGAIN if not.
|
||||
*/
|
||||
static int hsspi_wait_ss_ready(struct hsspi *hsspi)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!test_bit(HSSPI_FLAGS_SS_BUSY, hsspi->flags)) {
|
||||
/* The ss_ready went low, so the fw is not busy anymore,
|
||||
* if the ss_ready is high, we can proceed, else,
|
||||
* either the fw went to sleep or crashed, in any case
|
||||
* we need to wait for it to be ready again.
|
||||
*/
|
||||
clear_bit(HSSPI_FLAGS_SS_READY, hsspi->flags);
|
||||
if (gpiod_get_value(hsspi->gpio_ss_rdy)) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check if the QM went to sleep and wake it up if it did */
|
||||
if (!gpiod_get_value(hsspi->gpio_exton)) {
|
||||
hsspi->wakeup(hsspi);
|
||||
}
|
||||
|
||||
ret = wait_event_interruptible_timeout(
|
||||
hsspi->wq_ready,
|
||||
test_and_clear_bit(HSSPI_FLAGS_SS_READY, hsspi->flags),
|
||||
msecs_to_jiffies(SS_READY_TIMEOUT_MS));
|
||||
if (ret == 0) {
|
||||
dev_warn(&hsspi->spi->dev,
|
||||
"timed out waiting for ss_ready(%d)\n",
|
||||
test_bit(HSSPI_FLAGS_SS_READY, hsspi->flags));
|
||||
return -EAGAIN;
|
||||
}
|
||||
if (ret < 0) {
|
||||
dev_err(&hsspi->spi->dev,
|
||||
"Error %d while waiting for ss_ready\n", ret);
|
||||
return ret;
|
||||
}
|
||||
/* WA: QM35 C0 have a very short (<100ns) ss_ready toggle
|
||||
* in the ROM code when waking up from S4. If we transfer immediately,
|
||||
* the ROM code will enter its command mode and we'll end up
|
||||
* communicating with the ROM code instead of the firmware.
|
||||
*/
|
||||
if (!gpiod_get_value(hsspi->gpio_ss_rdy))
|
||||
return -EAGAIN;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef HSSPI_MANUAL_CS_SETUP
|
||||
int hsspi_set_cs_level(struct spi_device *spi, int level)
|
||||
{
|
||||
struct spi_transfer xfer[] = {
|
||||
{
|
||||
.cs_change = !level,
|
||||
.speed_hz = 1000000,
|
||||
},
|
||||
};
|
||||
|
||||
return spi_sync_transfer(spi, xfer, ARRAY_SIZE(xfer));
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* spi_xfer() - Single SPI transfer
|
||||
*
|
||||
* @hsspi: &struct hsspi
|
||||
* @tx: tx payload
|
||||
* @rx: rx payload
|
||||
* @length: payload length
|
||||
*/
|
||||
static int spi_xfer(struct hsspi *hsspi, const void *tx, void *rx,
|
||||
size_t length)
|
||||
{
|
||||
struct spi_transfer xfers[2] = {
|
||||
{
|
||||
.tx_buf = hsspi->host,
|
||||
.rx_buf = hsspi->soc,
|
||||
.len = sizeof(*(hsspi->host)),
|
||||
.speed_hz = spi_speed_hz,
|
||||
},
|
||||
{
|
||||
.tx_buf = tx,
|
||||
.rx_buf = rx,
|
||||
.len = length,
|
||||
.speed_hz = spi_speed_hz,
|
||||
},
|
||||
};
|
||||
int ret, retry = 5;
|
||||
|
||||
hsspi->soc->flags = 0;
|
||||
hsspi->soc->ul = 0;
|
||||
hsspi->soc->length = 0;
|
||||
|
||||
do {
|
||||
ret = hsspi_wait_ss_ready(hsspi);
|
||||
if (ret < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (test_sleep_after_ss_ready_us > 0)
|
||||
usleep_range(test_sleep_after_ss_ready_us,
|
||||
test_sleep_after_ss_ready_us + 1);
|
||||
|
||||
hsspi_set_spi_slave_busy(hsspi);
|
||||
#ifdef HSSPI_MANUAL_CS_SETUP
|
||||
hsspi_set_cs_level(hsspi->spi, 0);
|
||||
udelay(HSSPI_MANUAL_CS_SETUP_US);
|
||||
#endif
|
||||
ret = spi_sync_transfer(hsspi->spi, xfers, length ? 2 : 1);
|
||||
|
||||
trace_hsspi_spi_xfer(&hsspi->spi->dev, hsspi->host, hsspi->soc,
|
||||
ret);
|
||||
|
||||
if (ret) {
|
||||
dev_err(&hsspi->spi->dev, "spi_sync_transfer: %d\n",
|
||||
ret);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!(hsspi->soc->flags & STC_SOC_RDY) ||
|
||||
(hsspi->soc->flags & 0x0f)) {
|
||||
hsspi->wakeup(hsspi);
|
||||
ret = -EAGAIN;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* All looks good! */
|
||||
break;
|
||||
} while ((ret == -EAGAIN) && (--retry > 0));
|
||||
|
||||
if (!(hsspi->soc->flags & STC_SOC_RDY) || (hsspi->soc->flags & 0x0f)) {
|
||||
dev_err(&hsspi->spi->dev, "FW not ready (flags %#02x)\n",
|
||||
hsspi->soc->flags);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool check_soc_flag(const struct device *dev, const char *func_name,
|
||||
u8 soc_flags, bool is_tx)
|
||||
{
|
||||
u8 expected;
|
||||
bool res;
|
||||
|
||||
expected = is_tx ? 0x0 : STC_SOC_OA;
|
||||
res = (soc_flags & (STC_SOC_ERR | STC_SOC_OA)) == expected;
|
||||
|
||||
if (!res) {
|
||||
dev_warn(dev, "%s: bad soc flags %#hhx, expected %#hhx\n",
|
||||
func_name, soc_flags, expected);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* hsspi_rx() - request data from the QM35 on the HSSPI
|
||||
*
|
||||
* @hsspi: &struct hsspi
|
||||
* @ul: upper layer id
|
||||
* @length: length of data requested
|
||||
*/
|
||||
static int hsspi_rx(struct hsspi *hsspi, u8 ul, u16 length)
|
||||
{
|
||||
struct hsspi_layer *layer;
|
||||
struct hsspi_block *blk;
|
||||
int ret;
|
||||
|
||||
hsspi->host->flags = STC_HOST_RD;
|
||||
hsspi->host->ul = ul;
|
||||
hsspi->host->length = length;
|
||||
|
||||
if (layer_id_is_valid(hsspi, ul)) {
|
||||
spin_lock(&hsspi->lock);
|
||||
layer = hsspi->layers[ul];
|
||||
spin_unlock(&hsspi->lock);
|
||||
} else
|
||||
layer = NULL;
|
||||
|
||||
blk = layer ? layer->ops->get(layer, length) : NULL;
|
||||
if (blk) {
|
||||
ret = spi_xfer(hsspi, NULL, blk->data, blk->size);
|
||||
|
||||
layer->ops->received(layer, blk, ret);
|
||||
} else
|
||||
ret = spi_xfer(hsspi, NULL, NULL, 0);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (!check_soc_flag(&hsspi->spi->dev, __func__, hsspi->soc->flags,
|
||||
false)) {
|
||||
ret = -1;
|
||||
}
|
||||
|
||||
if ((hsspi->soc->ul != ul) || (hsspi->soc->length != length)) {
|
||||
dev_warn(&hsspi->spi->dev,
|
||||
"%s: received %hhu %hu but expecting %hhu %hu\n",
|
||||
__func__, hsspi->soc->ul, hsspi->soc->length, ul,
|
||||
length);
|
||||
ret = -1;
|
||||
}
|
||||
|
||||
if (!(hsspi->soc->flags & STC_SOC_ODW) &&
|
||||
test_and_clear_bit(HSSPI_FLAGS_SS_IRQ, hsspi->flags))
|
||||
hsspi->odw_cleared(hsspi);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* hsspi_tx() - send a hsspi block to the QM35 on the HSSPI
|
||||
*
|
||||
* @hsspi: &struct hsspi
|
||||
* @layer: &struct hsspi_layer
|
||||
* @blk: &struct hsspi_block
|
||||
*
|
||||
* It also adds PRD flag if SS_IRQ is set. Therefore it will try a RX
|
||||
* transfer accordingly.
|
||||
*/
|
||||
static int hsspi_tx(struct hsspi *hsspi, struct hsspi_layer *layer,
|
||||
struct hsspi_block *blk)
|
||||
{
|
||||
int ret;
|
||||
|
||||
hsspi->host->flags = STC_HOST_WR;
|
||||
hsspi->host->ul = layer->id;
|
||||
hsspi->host->length = blk->length;
|
||||
|
||||
if (test_bit(HSSPI_FLAGS_SS_IRQ, hsspi->flags))
|
||||
hsspi->host->flags |= STC_HOST_PRD;
|
||||
|
||||
ret = spi_xfer(hsspi, blk->data, NULL, blk->size);
|
||||
|
||||
layer->ops->sent(layer, blk, ret);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Ignore tx check flags */
|
||||
check_soc_flag(&hsspi->spi->dev, __func__, hsspi->soc->flags, true);
|
||||
|
||||
if (hsspi->host->flags & STC_HOST_PRD)
|
||||
return hsspi_rx(hsspi, hsspi->soc->ul, hsspi->soc->length);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* hsspi_pre_read() - send a PRE_READ with no TX and do RX
|
||||
*
|
||||
* @hsspi: &struct hsspi
|
||||
*
|
||||
* This function is a particular case of hsspi_tx with no layer or
|
||||
* blk.
|
||||
*
|
||||
*/
|
||||
static int hsspi_pre_read(struct hsspi *hsspi)
|
||||
{
|
||||
int ret;
|
||||
|
||||
hsspi->host->flags = STC_HOST_PRD;
|
||||
hsspi->host->ul = 0;
|
||||
hsspi->host->length = 0;
|
||||
|
||||
ret = spi_xfer(hsspi, NULL, NULL, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Ignore pre-read check flags */
|
||||
check_soc_flag(&hsspi->spi->dev, __func__, hsspi->soc->flags, true);
|
||||
|
||||
return hsspi_rx(hsspi, hsspi->soc->ul, hsspi->soc->length);
|
||||
}
|
||||
|
||||
/**
|
||||
* hsspi_thread_fn() - the thread that manage all SPI transfers
|
||||
* @data: the &struct hsspi
|
||||
*
|
||||
*/
|
||||
static int hsspi_thread_fn(void *data)
|
||||
{
|
||||
struct hsspi *hsspi = data;
|
||||
static int successive_errors;
|
||||
|
||||
successive_errors = 0;
|
||||
while (1) {
|
||||
struct hsspi_work *hw;
|
||||
int ret;
|
||||
|
||||
ret = wait_event_interruptible(hsspi->wq,
|
||||
is_txrx_waiting(hsspi) ||
|
||||
kthread_should_stop());
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (kthread_should_stop())
|
||||
break;
|
||||
|
||||
hw = get_work(hsspi);
|
||||
if (hw) {
|
||||
if (hw->type == HSSPI_WORK_COMPLETION) {
|
||||
complete(hw->completion);
|
||||
/* on the stack no need to free */
|
||||
continue;
|
||||
} else if (hw->type == HSSPI_WORK_TX) {
|
||||
ret = hsspi_tx(hsspi, hw->tx.layer, hw->tx.blk);
|
||||
kfree(hw);
|
||||
} else {
|
||||
dev_err(&hsspi->spi->dev,
|
||||
"unknown hsspi_work type: %d\n",
|
||||
hw->type);
|
||||
continue;
|
||||
}
|
||||
} else
|
||||
/* If there is no work, we are here because
|
||||
* SS_IRQ is set.
|
||||
*/
|
||||
ret = hsspi_pre_read(hsspi);
|
||||
|
||||
if (ret) {
|
||||
successive_errors++;
|
||||
|
||||
if (successive_errors > MAX_SUCCESSIVE_ERRORS) {
|
||||
dev_err(&hsspi->spi->dev,
|
||||
"Max successive errors %d reached, likely entered ROM code...\n",
|
||||
successive_errors);
|
||||
|
||||
/* When the device reboots, the ROM code might raise
|
||||
* ss_ready; if a SPI transfer is requested, the AP
|
||||
* will initiate the SPI xfer and the ROM code will
|
||||
* enter its command mode infinite loop...
|
||||
* No choice but rebooting the device.
|
||||
*/
|
||||
hsspi->reset_qm35(hsspi);
|
||||
successive_errors = 0;
|
||||
}
|
||||
} else {
|
||||
successive_errors = 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int hsspi_init(struct hsspi *hsspi, struct spi_device *spi)
|
||||
{
|
||||
memset(hsspi, 0, sizeof(*hsspi));
|
||||
|
||||
spin_lock_init(&hsspi->lock);
|
||||
INIT_LIST_HEAD(&hsspi->work_list);
|
||||
|
||||
hsspi->state = HSSPI_STOPPED;
|
||||
hsspi->spi = spi;
|
||||
|
||||
init_waitqueue_head(&hsspi->wq);
|
||||
init_waitqueue_head(&hsspi->wq_ready);
|
||||
|
||||
hsspi->host = kmalloc(sizeof(*(hsspi->host)), GFP_KERNEL | GFP_DMA);
|
||||
hsspi->soc = kmalloc(sizeof(*(hsspi->soc)), GFP_KERNEL | GFP_DMA);
|
||||
|
||||
hsspi->thread = kthread_create(hsspi_thread_fn, hsspi, "hsspi");
|
||||
if (IS_ERR(hsspi->thread))
|
||||
return PTR_ERR(hsspi->thread);
|
||||
|
||||
wake_up_process(hsspi->thread);
|
||||
|
||||
dev_info(&hsspi->spi->dev, "HSSPI initialized\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void hsspi_set_gpios(struct hsspi *hsspi, struct gpio_desc *gpio_ss_rdy,
|
||||
struct gpio_desc *gpio_exton)
|
||||
{
|
||||
hsspi->gpio_ss_rdy = gpio_ss_rdy;
|
||||
hsspi->gpio_exton = gpio_exton;
|
||||
}
|
||||
|
||||
int hsspi_deinit(struct hsspi *hsspi)
|
||||
{
|
||||
int i;
|
||||
|
||||
spin_lock(&hsspi->lock);
|
||||
|
||||
if (hsspi->state == HSSPI_RUNNING) {
|
||||
spin_unlock(&hsspi->lock);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(hsspi->layers); i++) {
|
||||
if (!hsspi->layers[i])
|
||||
continue;
|
||||
|
||||
dev_err(&hsspi->spi->dev,
|
||||
"HSSPI upper layer '%s' not unregistered\n",
|
||||
hsspi->layers[i]->name);
|
||||
spin_unlock(&hsspi->lock);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
spin_unlock(&hsspi->lock);
|
||||
|
||||
kthread_stop(hsspi->thread);
|
||||
|
||||
kfree(hsspi->host);
|
||||
kfree(hsspi->soc);
|
||||
|
||||
dev_info(&hsspi->spi->dev, "HSSPI uninitialized\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int hsspi_register(struct hsspi *hsspi, struct hsspi_layer *layer)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
if (!layer_id_is_valid(hsspi, layer->id))
|
||||
return -EINVAL;
|
||||
|
||||
spin_lock(&hsspi->lock);
|
||||
|
||||
if (hsspi->layers[layer->id])
|
||||
ret = -EBUSY;
|
||||
else
|
||||
hsspi->layers[layer->id] = layer;
|
||||
|
||||
spin_unlock(&hsspi->lock);
|
||||
|
||||
if (ret) {
|
||||
dev_err(&hsspi->spi->dev, "%s: '%s' ret: %d\n", __func__,
|
||||
layer->name, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
dev_dbg(&hsspi->spi->dev, "HSSPI upper layer '%s' registered\n",
|
||||
layer->name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int hsspi_unregister(struct hsspi *hsspi, struct hsspi_layer *layer)
|
||||
{
|
||||
DECLARE_COMPLETION_ONSTACK(complete);
|
||||
struct hsspi_work complete_work = {
|
||||
.type = HSSPI_WORK_COMPLETION,
|
||||
.completion = &complete,
|
||||
};
|
||||
int ret = 0;
|
||||
|
||||
if (!layer_id_is_valid(hsspi, layer->id))
|
||||
return -EINVAL;
|
||||
|
||||
spin_lock(&hsspi->lock);
|
||||
|
||||
if (hsspi->layers[layer->id] == layer) {
|
||||
hsspi->layers[layer->id] = NULL;
|
||||
|
||||
list_add_tail(&complete_work.list, &hsspi->work_list);
|
||||
} else
|
||||
ret = -EINVAL;
|
||||
|
||||
spin_unlock(&hsspi->lock);
|
||||
|
||||
if (ret) {
|
||||
dev_err(&hsspi->spi->dev, "%s: '%s' ret: %d\n", __func__,
|
||||
layer->name, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
wake_up_interruptible(&hsspi->wq);
|
||||
|
||||
/* when completed there is no more reference to layer in the
|
||||
* work_list or in the hsspi_thread_fn
|
||||
*/
|
||||
wait_for_completion(&complete);
|
||||
|
||||
dev_dbg(&hsspi->spi->dev, "HSSPI upper layer '%s' unregistered\n",
|
||||
layer->name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void hsspi_clear_spi_slave_busy(struct hsspi *hsspi)
|
||||
{
|
||||
clear_bit(HSSPI_FLAGS_SS_BUSY, hsspi->flags);
|
||||
}
|
||||
|
||||
void hsspi_set_spi_slave_busy(struct hsspi *hsspi)
|
||||
{
|
||||
set_bit(HSSPI_FLAGS_SS_BUSY, hsspi->flags);
|
||||
}
|
||||
|
||||
void hsspi_set_spi_slave_ready(struct hsspi *hsspi)
|
||||
{
|
||||
set_bit(HSSPI_FLAGS_SS_READY, hsspi->flags);
|
||||
|
||||
wake_up_interruptible(&hsspi->wq_ready);
|
||||
}
|
||||
|
||||
void hsspi_clear_spi_slave_ready(struct hsspi *hsspi)
|
||||
{
|
||||
clear_bit(HSSPI_FLAGS_SS_READY, hsspi->flags);
|
||||
}
|
||||
|
||||
void hsspi_set_output_data_waiting(struct hsspi *hsspi)
|
||||
{
|
||||
set_bit(HSSPI_FLAGS_SS_IRQ, hsspi->flags);
|
||||
|
||||
wake_up_interruptible(&hsspi->wq);
|
||||
}
|
||||
|
||||
int hsspi_init_block(struct hsspi_block *blk, u16 length)
|
||||
{
|
||||
void *data;
|
||||
|
||||
data = krealloc(blk->data, length, GFP_KERNEL | GFP_DMA);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
blk->data = data;
|
||||
blk->length = length;
|
||||
blk->size = length;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void hsspi_deinit_block(struct hsspi_block *blk)
|
||||
{
|
||||
kfree(blk->data);
|
||||
blk->data = NULL;
|
||||
}
|
||||
|
||||
int hsspi_send(struct hsspi *hsspi, struct hsspi_layer *layer,
|
||||
struct hsspi_block *blk)
|
||||
{
|
||||
struct hsspi_work *tx_work;
|
||||
int ret = 0;
|
||||
|
||||
if (!layer || !blk)
|
||||
return -EINVAL;
|
||||
|
||||
if (!layer_id_is_valid(hsspi, layer->id))
|
||||
return -EINVAL;
|
||||
|
||||
tx_work = kzalloc(sizeof(*tx_work), GFP_KERNEL);
|
||||
if (!tx_work)
|
||||
return -ENOMEM;
|
||||
|
||||
tx_work->type = HSSPI_WORK_TX;
|
||||
tx_work->tx.blk = blk;
|
||||
tx_work->tx.layer = layer;
|
||||
|
||||
spin_lock(&hsspi->lock);
|
||||
|
||||
if (hsspi->state == HSSPI_RUNNING) {
|
||||
if (hsspi->layers[layer->id] == layer)
|
||||
list_add_tail(&tx_work->list, &hsspi->work_list);
|
||||
else
|
||||
ret = -EINVAL;
|
||||
} else
|
||||
ret = -EAGAIN;
|
||||
|
||||
spin_unlock(&hsspi->lock);
|
||||
|
||||
if (ret) {
|
||||
kfree(tx_work);
|
||||
dev_err(&hsspi->spi->dev, "%s: %d\n", __func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
wake_up_interruptible(&hsspi->wq);
|
||||
|
||||
dev_dbg(&hsspi->spi->dev, "send %d bytes on HSSPI '%s' layer\n",
|
||||
blk->length, layer->name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void hsspi_start(struct hsspi *hsspi)
|
||||
{
|
||||
spin_lock(&hsspi->lock);
|
||||
|
||||
hsspi->state = HSSPI_RUNNING;
|
||||
|
||||
spin_unlock(&hsspi->lock);
|
||||
|
||||
wake_up_interruptible(&hsspi->wq);
|
||||
|
||||
dev_dbg(&hsspi->spi->dev, "HSSPI started\n");
|
||||
}
|
||||
|
||||
void hsspi_stop(struct hsspi *hsspi)
|
||||
{
|
||||
DECLARE_COMPLETION_ONSTACK(complete);
|
||||
struct hsspi_work complete_work = {
|
||||
.type = HSSPI_WORK_COMPLETION,
|
||||
.completion = &complete,
|
||||
};
|
||||
|
||||
spin_lock(&hsspi->lock);
|
||||
|
||||
hsspi->state = HSSPI_STOPPED;
|
||||
|
||||
list_add_tail(&complete_work.list, &hsspi->work_list);
|
||||
|
||||
spin_unlock(&hsspi->lock);
|
||||
|
||||
wake_up_interruptible(&hsspi->wq);
|
||||
|
||||
wait_for_completion(&complete);
|
||||
|
||||
dev_dbg(&hsspi->spi->dev, "HSSPI stopped\n");
|
||||
}
|
||||
325
drivers/uwb/qm35/hsspi.h
Normal file
325
drivers/uwb/qm35/hsspi.h
Normal file
|
|
@ -0,0 +1,325 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
|
||||
/*
|
||||
* This file is part of the QM35 UCI stack for linux.
|
||||
*
|
||||
* Copyright (c) 2021 Qorvo US, Inc.
|
||||
*
|
||||
* This software is provided under the GNU General Public License, version 2
|
||||
* (GPLv2), as well as under a Qorvo commercial license.
|
||||
*
|
||||
* You may choose to use this software under the terms of the GPLv2 License,
|
||||
* version 2 ("GPLv2"), as published by the Free Software Foundation.
|
||||
* You should have received a copy of the GPLv2 along with this program. If
|
||||
* not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This program is distributed under the GPLv2 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 GPLv2 for more
|
||||
* details.
|
||||
*
|
||||
* If you cannot meet the requirements of the GPLv2, you may not use this
|
||||
* software for any purpose without first obtaining a commercial license from
|
||||
* Qorvo.
|
||||
* Please contact Qorvo to inquire about licensing terms.
|
||||
*
|
||||
* QM35 HSSPI Protocol
|
||||
*/
|
||||
|
||||
#ifndef __HSSPI_H__
|
||||
#define __HSSPI_H__
|
||||
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/kthread.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/wait.h>
|
||||
|
||||
enum { UL_RESERVED,
|
||||
UL_BOOT_FLASH,
|
||||
UL_UCI_APP,
|
||||
UL_COREDUMP,
|
||||
UL_LOG,
|
||||
UL_TEST_HSSPI,
|
||||
UL_MAX_IDX };
|
||||
|
||||
struct stc_header {
|
||||
u8 flags;
|
||||
u8 ul;
|
||||
u16 length;
|
||||
} __packed;
|
||||
|
||||
enum hsspi_work_type {
|
||||
HSSPI_WORK_TX = 0,
|
||||
HSSPI_WORK_COMPLETION,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct hsspi_block - Memory block used by the HSSPI.
|
||||
* @data: pointer to some memory
|
||||
* @length: requested length of the data
|
||||
* @size: size of the data (could be greater than length)
|
||||
*
|
||||
* This structure represents the memory used by the HSSPI driver for
|
||||
* sending or receiving message. Upper layer must provides the HSSPI
|
||||
* driver a way to allocate such structure for reception and uses this
|
||||
* structure for sending function.
|
||||
*
|
||||
* The goal here is to prevent useless copy.
|
||||
*/
|
||||
struct hsspi_block {
|
||||
void *data;
|
||||
u16 length;
|
||||
u16 size;
|
||||
};
|
||||
|
||||
struct hsspi_layer;
|
||||
|
||||
/**
|
||||
* struct hsspi_layer_ops - Upper layer operations.
|
||||
*
|
||||
* @registered: Called when this upper layer is registered.
|
||||
* @unregistered: Called when unregistered.
|
||||
*
|
||||
* @get: Called when the HSSPI driver need some memory for
|
||||
* reception. This &struct hsspi_block will be give back to the upper
|
||||
* layer in the received callback.
|
||||
*
|
||||
* @received: Called when the HSSPI driver received some data for this
|
||||
* upper layer. In case of error, status is used to notify the upper
|
||||
* layer.
|
||||
*
|
||||
* @sent: Called when a &struct hsspi_block is sent by the HSSPI
|
||||
* driver. In case of error, status is used to notify the upper layer.
|
||||
*
|
||||
* Operation needed to be implemented by an upper layer. All ops are
|
||||
* called by the HSSPI driver and are mandatory.
|
||||
*/
|
||||
struct hsspi_layer_ops {
|
||||
int (*registered)(struct hsspi_layer *upper_layer);
|
||||
void (*unregistered)(struct hsspi_layer *upper_layer);
|
||||
|
||||
struct hsspi_block *(*get)(struct hsspi_layer *upper_layer, u16 length);
|
||||
void (*received)(struct hsspi_layer *upper_layer,
|
||||
struct hsspi_block *blk, int status);
|
||||
void (*sent)(struct hsspi_layer *upper_layer, struct hsspi_block *blk,
|
||||
int status);
|
||||
};
|
||||
|
||||
/**
|
||||
* struct hsspi_layer - HSSPI upper layer.
|
||||
* @name: Name of this upper layer.
|
||||
* @id: id (ul used in the STC header) of this upper layer
|
||||
* @ops: &struct hsspi_layer_ops
|
||||
*
|
||||
* Basic upper layer structure. Inherit from it to implement a
|
||||
* concrete upper layer.
|
||||
*/
|
||||
struct hsspi_layer {
|
||||
char *name;
|
||||
u8 id;
|
||||
const struct hsspi_layer_ops *ops;
|
||||
};
|
||||
|
||||
enum hsspi_flags {
|
||||
HSSPI_FLAGS_SS_IRQ = 0,
|
||||
HSSPI_FLAGS_SS_READY = 1,
|
||||
HSSPI_FLAGS_SS_BUSY = 2,
|
||||
HSSPI_FLAGS_MAX = 3,
|
||||
};
|
||||
|
||||
enum hsspi_state {
|
||||
HSSPI_RUNNING = 0,
|
||||
HSSPI_ERROR = 1,
|
||||
HSSPI_STOPPED = 2,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct hsspi - HSSPI driver.
|
||||
*
|
||||
* Some things need to be refine:
|
||||
* 1. a better way to disable/enable ss_irq or ss_ready GPIOs
|
||||
* 2. be able to change spi speed on the fly (for flashing purpose)
|
||||
*
|
||||
* Actually this structure should be abstract.
|
||||
*
|
||||
*/
|
||||
struct hsspi {
|
||||
spinlock_t lock; /* protect work_list, layers and state */
|
||||
struct list_head work_list;
|
||||
struct hsspi_layer *layers[UL_MAX_IDX];
|
||||
enum hsspi_state state;
|
||||
|
||||
DECLARE_BITMAP(flags, HSSPI_FLAGS_MAX);
|
||||
struct wait_queue_head wq;
|
||||
struct wait_queue_head wq_ready;
|
||||
struct task_struct *thread;
|
||||
|
||||
// re-enable SS_IRQ
|
||||
void (*odw_cleared)(struct hsspi *hsspi);
|
||||
|
||||
// wakeup QM35
|
||||
void (*wakeup)(struct hsspi *hsspi);
|
||||
|
||||
// reset QM35
|
||||
void (*reset_qm35)(struct hsspi *hsspi);
|
||||
|
||||
struct spi_device *spi;
|
||||
|
||||
struct stc_header *host, *soc;
|
||||
ktime_t next_cs_active_time;
|
||||
|
||||
struct gpio_desc *gpio_ss_rdy;
|
||||
struct gpio_desc *gpio_exton;
|
||||
};
|
||||
|
||||
/**
|
||||
* hsspi_init() - Initialiaze the HSSPI
|
||||
* @hsspi: pointer to a &struct hsspi
|
||||
* @spi: pointer to the &struct spi_device used by the HSSPI for SPI
|
||||
* transmission
|
||||
*
|
||||
* Initialize the HSSPI structure.
|
||||
*
|
||||
* Return: 0 if no error or -errno.
|
||||
*
|
||||
*/
|
||||
int hsspi_init(struct hsspi *hsspi, struct spi_device *spi);
|
||||
void hsspi_set_gpios(struct hsspi *hsspi, struct gpio_desc *gpio_ss_rdy,
|
||||
struct gpio_desc *gpio_exton);
|
||||
|
||||
/**
|
||||
* hsspi_deinit() - Initialiaze the HSSPI
|
||||
* @hsspi: pointer to a &struct hsspi
|
||||
*
|
||||
* Deinitialize the HSSPI structure.
|
||||
*
|
||||
* Return: 0 if no error or -errno
|
||||
*/
|
||||
int hsspi_deinit(struct hsspi *hsspi);
|
||||
|
||||
/**
|
||||
* hsspi_register() - Register an upper layer
|
||||
* @hsspi: pointer to a &struct hsspi
|
||||
* @layer: pointer to a &struct hsspi_layer
|
||||
*
|
||||
* Register an upper layer.
|
||||
*
|
||||
* Return: 0 if no error or -errno.
|
||||
*
|
||||
*/
|
||||
int hsspi_register(struct hsspi *hsspi, struct hsspi_layer *layer);
|
||||
|
||||
/**
|
||||
* hsspi_unregister() - Unregister an upper layer
|
||||
* @hsspi: pointer to a &struct hsspi
|
||||
* @layer: pointer to a &struct hsspi_layer
|
||||
*
|
||||
* Unregister an upper layer.
|
||||
*
|
||||
* Return: 0 if no error or -errno.
|
||||
*
|
||||
*/
|
||||
int hsspi_unregister(struct hsspi *hsspi, struct hsspi_layer *layer);
|
||||
|
||||
/**
|
||||
* hsspi_set_spi_slave_ready() - tell the hsspi that the ss_ready is active
|
||||
* @hsspi: pointer to a &struct hsspi
|
||||
*
|
||||
* This function is called in the ss_ready irq handler. It notices the
|
||||
* HSSPI driver that the QM is ready for transfer.
|
||||
*/
|
||||
void hsspi_set_spi_slave_ready(struct hsspi *hsspi);
|
||||
|
||||
/**
|
||||
* hsspi_clear_spi_slave_ready() - tell the hsspi that the ss_ready has
|
||||
* been lowered meaning that the fw is busy or asleep,
|
||||
* @hsspi: pointer to a &struct hsspi
|
||||
*/
|
||||
void hsspi_clear_spi_slave_ready(struct hsspi *hsspi);
|
||||
|
||||
/**
|
||||
* hsspi_set_spi_slave_busy() - tell the hsspi that the ss_ready has
|
||||
* not been lowered and raised again meaning that the fw is busy,
|
||||
* @hsspi: pointer to a &struct hsspi
|
||||
*/
|
||||
void hsspi_set_spi_slave_busy(struct hsspi *hsspi);
|
||||
|
||||
/**
|
||||
* hsspi_clear_spi_slave_busy() - tell the hsspi that the ss_ready has
|
||||
* been lowered and raised again meaning that the fw is not busy anymore,
|
||||
* @hsspi: pointer to a &struct hsspi
|
||||
*
|
||||
* This function is called in the ss_ready irq handler. It notices the
|
||||
* HSSPI driver that the QM has acknowledged the last SPI xfer.
|
||||
*/
|
||||
void hsspi_clear_spi_slave_busy(struct hsspi *hsspi);
|
||||
|
||||
/**
|
||||
* hsspi_set_output_data_waiting() - tell the hsspi that the ss_irq is active
|
||||
* @hsspi: pointer to a &struct hsspi
|
||||
*
|
||||
* This function is called in the ss_irq irq handler. It notices the
|
||||
* HSSPI dirver that the QM has some date to outpput.
|
||||
*
|
||||
* The HSSPI must work with or without the ss_irq gpio. The current
|
||||
* implementation is far from ideal regarding this requirement.
|
||||
*
|
||||
* W/o the gpio we should send repeatly some 0-length write transfer
|
||||
* in order to check for ODW flag in SOC STC header.
|
||||
*/
|
||||
void hsspi_set_output_data_waiting(struct hsspi *hsspi);
|
||||
|
||||
/**
|
||||
* hsspi_init_block() - allocate a block data that suits HSSPI.
|
||||
*
|
||||
* @blk: point to a &struct hsspi_block
|
||||
* @length: block length
|
||||
*
|
||||
* Return: 0 or -ENOMEM on error
|
||||
*/
|
||||
int hsspi_init_block(struct hsspi_block *blk, u16 length);
|
||||
|
||||
/**
|
||||
* hsspi_deinit_block() - deallocate a block data.
|
||||
*
|
||||
* @blk: point to a &struct hsspi_block
|
||||
*
|
||||
*/
|
||||
void hsspi_deinit_block(struct hsspi_block *blk);
|
||||
|
||||
/**
|
||||
* hsspi_send() - send a &struct hsspi_block for a &struct hsspi_layer
|
||||
* layer.
|
||||
*
|
||||
* @hsspi: pointer to a &struct hsspi
|
||||
* @layer: pointer to a &struct hsspi_layer
|
||||
* @blk: pointer to a &struct hsspi_block
|
||||
*
|
||||
* Send the block `blk` of the upper layer `layer` on the `hsspi`
|
||||
* driver.
|
||||
*
|
||||
* Return: 0 if no error or -errno.
|
||||
*
|
||||
*/
|
||||
int hsspi_send(struct hsspi *hsspi, struct hsspi_layer *layer,
|
||||
struct hsspi_block *blk);
|
||||
|
||||
/**
|
||||
* hsspi_start() - start the HSSPI
|
||||
*
|
||||
* @hsspi: pointer to a &struct hsspi
|
||||
*
|
||||
*/
|
||||
void hsspi_start(struct hsspi *hsspi);
|
||||
|
||||
/**
|
||||
* hsspi_stop() - stop the HSSPI
|
||||
*
|
||||
* @hsspi: pointer to a &struct hsspi
|
||||
*
|
||||
*/
|
||||
void hsspi_stop(struct hsspi *hsspi);
|
||||
|
||||
#endif // __HSSPI_H__
|
||||
349
drivers/uwb/qm35/hsspi_coredump.c
Normal file
349
drivers/uwb/qm35/hsspi_coredump.c
Normal file
|
|
@ -0,0 +1,349 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
/*
|
||||
* This file is part of the QM35 UCI stack for linux.
|
||||
*
|
||||
* Copyright (c) 2022 Qorvo US, Inc.
|
||||
*
|
||||
* This software is provided under the GNU General Public License, version 2
|
||||
* (GPLv2), as well as under a Qorvo commercial license.
|
||||
*
|
||||
* You may choose to use this software under the terms of the GPLv2 License,
|
||||
* version 2 ("GPLv2"), as published by the Free Software Foundation.
|
||||
* You should have received a copy of the GPLv2 along with this program. If
|
||||
* not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This program is distributed under the GPLv2 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 GPLv2 for more
|
||||
* details.
|
||||
*
|
||||
* If you cannot meet the requirements of the GPLv2, you may not use this
|
||||
* software for any purpose without first obtaining a commercial license from
|
||||
* Qorvo.
|
||||
* Please contact Qorvo to inquire about licensing terms.
|
||||
*
|
||||
* QM35 COREDUMP layer HSSPI Protocol
|
||||
*/
|
||||
|
||||
#include "qm35.h"
|
||||
#include "hsspi_coredump.h"
|
||||
|
||||
#define COREDUMP_HEADER_NTF 0x00
|
||||
#define COREDUMP_BODY_NTF 0x01
|
||||
#define COREDUMP_RCV_STATUS 0x02
|
||||
#define COREDUMP_FORCE_CMD 0x03
|
||||
|
||||
#define COREDUMP_RCV_NACK 0x00
|
||||
#define COREDUMP_RCV_ACK 0x01
|
||||
|
||||
#define COREDUMP_RCV_TIMER_TIMEOUT_S 2
|
||||
|
||||
struct __packed coredump_common_hdr {
|
||||
uint8_t cmd_id;
|
||||
};
|
||||
|
||||
struct __packed coredump_hdr_ntf {
|
||||
uint32_t size;
|
||||
uint16_t crc;
|
||||
};
|
||||
|
||||
struct __packed coredump_rcv_status {
|
||||
uint8_t ack;
|
||||
};
|
||||
|
||||
struct coredump_packet *coredump_packet_alloc(u16 length)
|
||||
{
|
||||
struct coredump_packet *p;
|
||||
|
||||
p = kzalloc(sizeof(*p), GFP_KERNEL);
|
||||
if (!p)
|
||||
return NULL;
|
||||
|
||||
if (hsspi_init_block(&p->blk, length)) {
|
||||
kfree(p);
|
||||
return NULL;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
void coredump_packet_free(struct coredump_packet *p)
|
||||
{
|
||||
hsspi_deinit_block(&p->blk);
|
||||
kfree(p);
|
||||
}
|
||||
|
||||
static int coredump_send_rcv_status(struct coredump_layer *layer, uint8_t ack)
|
||||
{
|
||||
struct coredump_packet *p;
|
||||
struct coredump_common_hdr hdr;
|
||||
struct coredump_rcv_status rcv;
|
||||
struct qm35_ctx *qm35_hdl;
|
||||
|
||||
pr_info("qm35: coredump: sending status %s\n",
|
||||
layer->coredump_status == COREDUMP_RCV_ACK ? "ACK" : "NACK");
|
||||
|
||||
qm35_hdl = container_of(layer, struct qm35_ctx, coredump_layer);
|
||||
|
||||
p = coredump_packet_alloc(sizeof(hdr) + sizeof(rcv));
|
||||
if (!p)
|
||||
return -ENOMEM;
|
||||
|
||||
hdr.cmd_id = COREDUMP_RCV_STATUS;
|
||||
rcv.ack = ack;
|
||||
|
||||
memcpy(p->blk.data, &hdr, sizeof(hdr));
|
||||
memcpy(p->blk.data + sizeof(hdr), &rcv, sizeof(rcv));
|
||||
|
||||
return hsspi_send(&qm35_hdl->hsspi, &qm35_hdl->coredump_layer.hlayer,
|
||||
&p->blk);
|
||||
}
|
||||
|
||||
static uint16_t coredump_get_checksum(struct coredump_layer *layer)
|
||||
{
|
||||
uint32_t idx;
|
||||
uint16_t crc = 0;
|
||||
|
||||
for (idx = 0; idx < layer->coredump_data_wr_idx; idx++) {
|
||||
uint8_t *val;
|
||||
|
||||
val = layer->coredump_data + idx;
|
||||
crc += *val;
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
static void corredump_on_expired_timer(struct timer_list *timer)
|
||||
{
|
||||
struct coredump_layer *layer =
|
||||
container_of(timer, struct coredump_layer, timer);
|
||||
|
||||
pr_warn("qm35: coredump receive timer expired\n");
|
||||
|
||||
coredump_send_rcv_status(layer, layer->coredump_status);
|
||||
}
|
||||
|
||||
static int coredump_registered(struct hsspi_layer *hlayer)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void coredump_unregistered(struct hsspi_layer *hlayer)
|
||||
{
|
||||
;
|
||||
}
|
||||
|
||||
static struct hsspi_block *coredump_get(struct hsspi_layer *hlayer, u16 length)
|
||||
{
|
||||
struct coredump_packet *p;
|
||||
|
||||
p = coredump_packet_alloc(length);
|
||||
if (!p)
|
||||
return NULL;
|
||||
|
||||
return &p->blk;
|
||||
}
|
||||
|
||||
static void coredump_header_ntf_received(struct coredump_layer *layer,
|
||||
struct coredump_hdr_ntf chn)
|
||||
{
|
||||
void *data;
|
||||
|
||||
pr_info("qm35: coredump: receiving coredump with len: %d and crc: 0x%x\n",
|
||||
chn.size, chn.crc);
|
||||
|
||||
layer->coredump_data_wr_idx = 0;
|
||||
layer->coredump_size = chn.size;
|
||||
layer->coredump_crc = chn.crc;
|
||||
layer->coredump_status = COREDUMP_RCV_NACK;
|
||||
|
||||
data = krealloc(layer->coredump_data, layer->coredump_size, GFP_KERNEL);
|
||||
if (ZERO_OR_NULL_PTR(data)) {
|
||||
layer->coredump_data = NULL;
|
||||
layer->coredump_size = 0;
|
||||
layer->coredump_crc = 0;
|
||||
pr_err("qm35: failed to allocate coredump mem: %px\n", data);
|
||||
} else {
|
||||
layer->coredump_data = data;
|
||||
}
|
||||
}
|
||||
|
||||
static int coredump_body_ntf_received(struct coredump_layer *layer,
|
||||
uint8_t *cch_body, uint16_t cch_body_size)
|
||||
{
|
||||
if (!layer->coredump_data) {
|
||||
pr_err("qm35: failed to save coredump, mem not allocated\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (cch_body_size + layer->coredump_data_wr_idx >
|
||||
layer->coredump_size) {
|
||||
pr_err("qm35: failed to save coredump, mem overflow: max size: %d, wr_idx: %d, cd size: %d\n",
|
||||
layer->coredump_size, layer->coredump_data_wr_idx,
|
||||
cch_body_size);
|
||||
return 1;
|
||||
}
|
||||
|
||||
memcpy(layer->coredump_data + layer->coredump_data_wr_idx, cch_body,
|
||||
cch_body_size);
|
||||
layer->coredump_data_wr_idx += cch_body_size;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void coredump_received(struct hsspi_layer *hlayer,
|
||||
struct hsspi_block *blk, int status)
|
||||
{
|
||||
struct coredump_common_hdr cch;
|
||||
struct coredump_hdr_ntf chn;
|
||||
uint8_t *cch_body;
|
||||
uint16_t cch_body_size;
|
||||
|
||||
struct coredump_packet *packet =
|
||||
container_of(blk, struct coredump_packet, blk);
|
||||
|
||||
struct coredump_layer *layer =
|
||||
container_of(hlayer, struct coredump_layer, hlayer);
|
||||
|
||||
if (status)
|
||||
goto out;
|
||||
|
||||
if (blk->length < sizeof(struct coredump_common_hdr)) {
|
||||
pr_err("qm35: coredump packet header too small: %d bytes\n",
|
||||
blk->length);
|
||||
goto out;
|
||||
}
|
||||
|
||||
del_timer_sync(&layer->timer);
|
||||
|
||||
memcpy(&cch, blk->data, sizeof(cch));
|
||||
cch_body = blk->data + sizeof(cch);
|
||||
cch_body_size = blk->length - sizeof(cch);
|
||||
|
||||
switch (cch.cmd_id) {
|
||||
case COREDUMP_HEADER_NTF:
|
||||
if (cch_body_size < sizeof(chn)) {
|
||||
pr_err("qm35: coredump packet header ntf too small: %d bytes\n",
|
||||
cch_body_size);
|
||||
break;
|
||||
}
|
||||
|
||||
memcpy(&chn, cch_body, sizeof(chn));
|
||||
coredump_header_ntf_received(layer, chn);
|
||||
break;
|
||||
|
||||
case COREDUMP_BODY_NTF:
|
||||
pr_info("qm35: coredump: saving coredump data with len: %d [%d/%d]\n",
|
||||
cch_body_size,
|
||||
layer->coredump_data_wr_idx + cch_body_size,
|
||||
layer->coredump_size);
|
||||
|
||||
if (coredump_body_ntf_received(layer, cch_body, cch_body_size))
|
||||
break;
|
||||
|
||||
if (layer->coredump_data_wr_idx == layer->coredump_size) {
|
||||
uint16_t crc = coredump_get_checksum(layer);
|
||||
|
||||
pr_info("qm35: coredump: calculated crc: 0x%x, header crc: 0x%x\n",
|
||||
crc, layer->coredump_crc);
|
||||
|
||||
if (crc == layer->coredump_crc)
|
||||
layer->coredump_status = COREDUMP_RCV_ACK;
|
||||
|
||||
coredump_send_rcv_status(layer, layer->coredump_status);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
mod_timer(&layer->timer,
|
||||
jiffies + COREDUMP_RCV_TIMER_TIMEOUT_S * HZ);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
pr_err("qm35: coredump: wrong cmd id received: 0x%x\n",
|
||||
cch.cmd_id);
|
||||
break;
|
||||
}
|
||||
|
||||
out:
|
||||
|
||||
coredump_packet_free(packet);
|
||||
}
|
||||
|
||||
static void coredump_sent(struct hsspi_layer *hlayer, struct hsspi_block *blk,
|
||||
int status)
|
||||
{
|
||||
struct coredump_packet *buf =
|
||||
container_of(blk, struct coredump_packet, blk);
|
||||
|
||||
coredump_packet_free(buf);
|
||||
}
|
||||
|
||||
static const struct hsspi_layer_ops coredump_ops = {
|
||||
.registered = coredump_registered,
|
||||
.unregistered = coredump_unregistered,
|
||||
.get = coredump_get,
|
||||
.received = coredump_received,
|
||||
.sent = coredump_sent,
|
||||
};
|
||||
|
||||
char *debug_coredump_get(struct debug *dbg, size_t *len)
|
||||
{
|
||||
char *data;
|
||||
struct qm35_ctx *qm35_hdl = container_of(dbg, struct qm35_ctx, debug);
|
||||
|
||||
*len = qm35_hdl->coredump_layer.coredump_data_wr_idx;
|
||||
data = qm35_hdl->coredump_layer.coredump_data;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
int debug_coredump_force(struct debug *dbg)
|
||||
{
|
||||
struct coredump_packet *p;
|
||||
struct coredump_common_hdr hdr = { .cmd_id = COREDUMP_FORCE_CMD };
|
||||
struct qm35_ctx *qm35_hdl;
|
||||
|
||||
pr_info("qm35: force coredump");
|
||||
|
||||
qm35_hdl = container_of(dbg, struct qm35_ctx, debug);
|
||||
|
||||
p = coredump_packet_alloc(sizeof(hdr));
|
||||
if (!p)
|
||||
return -ENOMEM;
|
||||
|
||||
memcpy(p->blk.data, &hdr, sizeof(hdr));
|
||||
|
||||
return hsspi_send(&qm35_hdl->hsspi, &qm35_hdl->coredump_layer.hlayer,
|
||||
&p->blk);
|
||||
}
|
||||
|
||||
static const struct debug_coredump_ops debug_coredump_ops = {
|
||||
.coredump_get = debug_coredump_get,
|
||||
.coredump_force = debug_coredump_force,
|
||||
};
|
||||
|
||||
int coredump_layer_init(struct coredump_layer *layer, struct debug *debug)
|
||||
{
|
||||
layer->hlayer.name = "QM35 COREDUMP";
|
||||
layer->hlayer.id = UL_COREDUMP;
|
||||
layer->hlayer.ops = &coredump_ops;
|
||||
|
||||
layer->coredump_data = NULL;
|
||||
layer->coredump_data_wr_idx = 0;
|
||||
layer->coredump_size = 0;
|
||||
layer->coredump_crc = 0;
|
||||
layer->coredump_status = 0;
|
||||
timer_setup(&layer->timer, corredump_on_expired_timer, 0);
|
||||
|
||||
debug->coredump_ops = &debug_coredump_ops;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void coredump_layer_deinit(struct coredump_layer *layer)
|
||||
{
|
||||
kfree(layer->coredump_data);
|
||||
}
|
||||
55
drivers/uwb/qm35/hsspi_coredump.h
Normal file
55
drivers/uwb/qm35/hsspi_coredump.h
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
|
||||
/*
|
||||
* This file is part of the QM35 UCI stack for linux.
|
||||
*
|
||||
* Copyright (c) 2022 Qorvo US, Inc.
|
||||
*
|
||||
* This software is provided under the GNU General Public License, version 2
|
||||
* (GPLv2), as well as under a Qorvo commercial license.
|
||||
*
|
||||
* You may choose to use this software under the terms of the GPLv2 License,
|
||||
* version 2 ("GPLv2"), as published by the Free Software Foundation.
|
||||
* You should have received a copy of the GPLv2 along with this program. If
|
||||
* not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This program is distributed under the GPLv2 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 GPLv2 for more
|
||||
* details.
|
||||
*
|
||||
* If you cannot meet the requirements of the GPLv2, you may not use this
|
||||
* software for any purpose without first obtaining a commercial license from
|
||||
* Qorvo.
|
||||
* Please contact Qorvo to inquire about licensing terms.
|
||||
*
|
||||
* QM35 COREDUMP layer HSSPI Protocol
|
||||
*/
|
||||
|
||||
#ifndef __HSSPI_COREDUMP_H__
|
||||
#define __HSSPI_COREDUMP_H__
|
||||
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/sched.h>
|
||||
|
||||
#include "hsspi.h"
|
||||
#include "debug.h"
|
||||
|
||||
struct coredump_packet {
|
||||
struct hsspi_block blk;
|
||||
};
|
||||
|
||||
struct coredump_layer {
|
||||
struct hsspi_layer hlayer;
|
||||
void *coredump_data;
|
||||
uint32_t coredump_data_wr_idx;
|
||||
uint32_t coredump_size;
|
||||
uint16_t coredump_crc;
|
||||
uint8_t coredump_status;
|
||||
struct timer_list timer;
|
||||
};
|
||||
|
||||
int coredump_layer_init(struct coredump_layer *coredump, struct debug *debug);
|
||||
void coredump_layer_deinit(struct coredump_layer *coredump);
|
||||
|
||||
#endif // __HSSPI_COREDUMP_H__
|
||||
463
drivers/uwb/qm35/hsspi_log.c
Normal file
463
drivers/uwb/qm35/hsspi_log.c
Normal file
|
|
@ -0,0 +1,463 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
/*
|
||||
* This file is part of the QM35 UCI stack for linux.
|
||||
*
|
||||
* Copyright (c) 2022 Qorvo US, Inc.
|
||||
*
|
||||
* This software is provided under the GNU General Public License, version 2
|
||||
* (GPLv2), as well as under a Qorvo commercial license.
|
||||
*
|
||||
* You may choose to use this software under the terms of the GPLv2 License,
|
||||
* version 2 ("GPLv2"), as published by the Free Software Foundation.
|
||||
* You should have received a copy of the GPLv2 along with this program. If
|
||||
* not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This program is distributed under the GPLv2 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 GPLv2 for more
|
||||
* details.
|
||||
*
|
||||
* If you cannot meet the requirements of the GPLv2, you may not use this
|
||||
* software for any purpose without first obtaining a commercial license from
|
||||
* Qorvo.
|
||||
* Please contact Qorvo to inquire about licensing terms.
|
||||
*
|
||||
* QM35 LOG layer HSSPI Protocol
|
||||
*/
|
||||
|
||||
#include <linux/uwb/qmrom.h>
|
||||
|
||||
#include "qm35.h"
|
||||
#include "hsspi_log.h"
|
||||
|
||||
#define LOG_CID_TRACE_NTF 0x0000
|
||||
#define LOG_CID_SET_LOG_LVL 0x0001
|
||||
#define LOG_CID_GET_LOG_LVL 0x0002
|
||||
#define LOG_CID_GET_LOG_SRC 0x0003
|
||||
|
||||
#define TRACE_RB_SIZE 0xFFFFF // 1MB
|
||||
|
||||
struct __packed log_packet_hdr {
|
||||
uint16_t cmd_id;
|
||||
uint16_t b_size;
|
||||
};
|
||||
|
||||
struct __packed log_level_set_cmd {
|
||||
struct log_packet_hdr hdr;
|
||||
uint8_t id;
|
||||
uint8_t lvl;
|
||||
};
|
||||
|
||||
struct __packed log_level_get_cmd {
|
||||
struct log_packet_hdr hdr;
|
||||
uint8_t id;
|
||||
};
|
||||
|
||||
struct log_packet *log_packet_alloc(u16 length)
|
||||
{
|
||||
struct log_packet *p;
|
||||
|
||||
p = kzalloc(sizeof(*p), GFP_KERNEL);
|
||||
if (!p)
|
||||
return NULL;
|
||||
|
||||
if (hsspi_init_block(&p->blk, length)) {
|
||||
kfree(p);
|
||||
return NULL;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
void log_packet_free(struct log_packet *p)
|
||||
{
|
||||
hsspi_deinit_block(&p->blk);
|
||||
kfree(p);
|
||||
}
|
||||
|
||||
static struct log_packet *encode_get_log_sources_packet(void)
|
||||
{
|
||||
struct log_packet *p;
|
||||
struct log_packet_hdr msg;
|
||||
|
||||
p = log_packet_alloc(sizeof(msg));
|
||||
if (!p)
|
||||
return p;
|
||||
|
||||
msg.cmd_id = LOG_CID_GET_LOG_SRC;
|
||||
msg.b_size = 0;
|
||||
|
||||
memcpy(p->blk.data, &msg, sizeof(msg));
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
static struct log_packet *encode_set_log_level_packet(uint8_t id, uint8_t lvl)
|
||||
{
|
||||
struct log_packet *p;
|
||||
struct log_level_set_cmd cmd;
|
||||
|
||||
p = log_packet_alloc(sizeof(cmd));
|
||||
if (!p)
|
||||
return p;
|
||||
|
||||
cmd.hdr.cmd_id = LOG_CID_SET_LOG_LVL;
|
||||
cmd.hdr.b_size = sizeof(cmd) - sizeof(cmd.hdr);
|
||||
cmd.id = id;
|
||||
cmd.lvl = lvl;
|
||||
|
||||
memcpy(p->blk.data, &cmd, sizeof(cmd));
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
static struct log_packet *encode_get_log_level_packet(uint8_t id)
|
||||
{
|
||||
struct log_packet *p;
|
||||
struct log_level_get_cmd cmd;
|
||||
|
||||
p = log_packet_alloc(sizeof(cmd));
|
||||
if (!p)
|
||||
return p;
|
||||
|
||||
cmd.hdr.cmd_id = LOG_CID_GET_LOG_LVL;
|
||||
cmd.hdr.b_size = sizeof(cmd) - sizeof(cmd.hdr);
|
||||
cmd.id = id;
|
||||
|
||||
memcpy(p->blk.data, &cmd, sizeof(cmd));
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
static int parse_log_sources_response(struct log_layer *layer, uint8_t *data,
|
||||
uint16_t data_size)
|
||||
{
|
||||
struct qm35_ctx *qm35_hdl;
|
||||
int idx = 0;
|
||||
|
||||
qm35_hdl = container_of(layer, struct qm35_ctx, log_layer);
|
||||
|
||||
layer->log_modules_count = *data++;
|
||||
|
||||
kfree(layer->log_modules);
|
||||
|
||||
layer->log_modules = kcalloc(layer->log_modules_count,
|
||||
sizeof(struct log_module), GFP_KERNEL);
|
||||
if (!layer->log_modules)
|
||||
return 1;
|
||||
|
||||
for (idx = 0; idx < layer->log_modules_count; idx++) {
|
||||
layer->log_modules[idx].debug = &qm35_hdl->debug;
|
||||
layer->log_modules[idx].id = *data++;
|
||||
layer->log_modules[idx].lvl = *data++;
|
||||
strcpy(layer->log_modules[idx].name, (char *)data);
|
||||
data += (strlen((char *)data) + 1);
|
||||
|
||||
debug_create_module_entry(&qm35_hdl->debug,
|
||||
&layer->log_modules[idx]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_get_log_lvl_response(struct log_layer *layer, uint8_t *data,
|
||||
uint16_t data_size)
|
||||
{
|
||||
uint8_t src_id, src_lvl;
|
||||
int idx = 0;
|
||||
|
||||
src_id = *data++;
|
||||
src_lvl = *data;
|
||||
|
||||
//qm35_hdl = container_of(layer, struct qm35_ctx, log_layer);
|
||||
|
||||
for (idx = 0; idx < layer->log_modules_count; idx++) {
|
||||
if (layer->log_modules[idx].id == src_id) {
|
||||
layer->log_modules[idx].lvl = src_lvl;
|
||||
|
||||
if (layer->log_modules[idx].read_done)
|
||||
complete(layer->log_modules[idx].read_done);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int log_registered(struct hsspi_layer *hlayer)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void log_unregistered(struct hsspi_layer *hlayer)
|
||||
{
|
||||
;
|
||||
}
|
||||
|
||||
static struct hsspi_block *log_get(struct hsspi_layer *hlayer, u16 length)
|
||||
{
|
||||
struct log_packet *p;
|
||||
|
||||
p = log_packet_alloc(length);
|
||||
if (!p)
|
||||
return NULL;
|
||||
|
||||
return &p->blk;
|
||||
}
|
||||
|
||||
static void log_received(struct hsspi_layer *hlayer, struct hsspi_block *blk,
|
||||
int status)
|
||||
{
|
||||
struct log_layer *layer;
|
||||
struct log_packet_hdr hdr;
|
||||
uint8_t *body;
|
||||
struct qm35_ctx *qm35_hdl;
|
||||
struct log_packet *p;
|
||||
|
||||
p = container_of(blk, struct log_packet, blk);
|
||||
|
||||
if (status)
|
||||
goto out;
|
||||
|
||||
layer = container_of(hlayer, struct log_layer, hlayer);
|
||||
qm35_hdl = container_of(layer, struct qm35_ctx, log_layer);
|
||||
|
||||
if (blk->length < sizeof(struct log_packet_hdr)) {
|
||||
pr_err("qm35: log packet header too small: %d bytes\n",
|
||||
blk->length);
|
||||
goto out;
|
||||
}
|
||||
|
||||
memcpy(&hdr, blk->data, sizeof(struct log_packet_hdr));
|
||||
body = blk->data + sizeof(struct log_packet_hdr);
|
||||
|
||||
if (blk->length < sizeof(struct log_packet_hdr) + hdr.b_size) {
|
||||
pr_err("qm35: incomplete log packet: %d/%d bytes\n",
|
||||
blk->length, hdr.b_size);
|
||||
goto out;
|
||||
}
|
||||
if (qm35_hdl->log_qm_traces)
|
||||
pr_info("qm35_log: %.*s\n", hdr.b_size - 2, body);
|
||||
|
||||
switch (hdr.cmd_id) {
|
||||
case LOG_CID_TRACE_NTF:
|
||||
rb_push(&layer->rb, body, hdr.b_size);
|
||||
debug_new_trace_available(&qm35_hdl->debug);
|
||||
break;
|
||||
case LOG_CID_SET_LOG_LVL:
|
||||
break;
|
||||
case LOG_CID_GET_LOG_LVL:
|
||||
parse_get_log_lvl_response(layer, body, hdr.b_size);
|
||||
break;
|
||||
case LOG_CID_GET_LOG_SRC:
|
||||
parse_log_sources_response(layer, body, hdr.b_size);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
out:
|
||||
log_packet_free(p);
|
||||
}
|
||||
|
||||
static void log_sent(struct hsspi_layer *hlayer, struct hsspi_block *blk,
|
||||
int status)
|
||||
{
|
||||
struct log_packet *p = container_of(blk, struct log_packet, blk);
|
||||
|
||||
log_packet_free(p);
|
||||
}
|
||||
|
||||
static const struct hsspi_layer_ops log_ops = {
|
||||
.registered = log_registered,
|
||||
.unregistered = log_unregistered,
|
||||
.get = log_get,
|
||||
.received = log_received,
|
||||
.sent = log_sent,
|
||||
};
|
||||
|
||||
static void log_enable_set(struct debug *dbg, int enable)
|
||||
{
|
||||
struct qm35_ctx *qm35_hdl;
|
||||
struct log_packet *p;
|
||||
int ret;
|
||||
|
||||
// TODO: what happens if enable is false?
|
||||
// if (!enable)
|
||||
// return;
|
||||
qm35_hdl = container_of(dbg, struct qm35_ctx, debug);
|
||||
|
||||
if (qm35_hdl->log_layer.enabled) {
|
||||
pr_warn("qm35: logging already enabled\n");
|
||||
return;
|
||||
}
|
||||
|
||||
p = encode_get_log_sources_packet();
|
||||
if (!p) {
|
||||
pr_err("failed to encode get log sources packet\n");
|
||||
return;
|
||||
}
|
||||
|
||||
ret = hsspi_send(&qm35_hdl->hsspi, &qm35_hdl->log_layer.hlayer,
|
||||
&p->blk);
|
||||
|
||||
if (ret) {
|
||||
pr_err("failed to send spi packet\n");
|
||||
log_packet_free(p);
|
||||
} else {
|
||||
qm35_hdl->log_layer.enabled = enable;
|
||||
}
|
||||
}
|
||||
|
||||
static int log_enable_get(struct debug *dbg)
|
||||
{
|
||||
struct qm35_ctx *qm35_hdl;
|
||||
|
||||
qm35_hdl = container_of(dbg, struct qm35_ctx, debug);
|
||||
|
||||
return qm35_hdl->log_layer.enabled;
|
||||
}
|
||||
|
||||
static void log_level_set(struct debug *dbg, struct log_module *log_module,
|
||||
int lvl)
|
||||
{
|
||||
struct qm35_ctx *qm35_hdl;
|
||||
struct log_packet *p;
|
||||
int ret;
|
||||
|
||||
qm35_hdl = container_of(dbg, struct qm35_ctx, debug);
|
||||
|
||||
p = encode_set_log_level_packet(log_module->id, lvl);
|
||||
if (!p) {
|
||||
pr_err("failed to encode set log level packet\n");
|
||||
return;
|
||||
}
|
||||
|
||||
ret = hsspi_send(&qm35_hdl->hsspi, &qm35_hdl->log_layer.hlayer,
|
||||
&p->blk);
|
||||
if (ret) {
|
||||
pr_err("failed to send spi packet\n");
|
||||
log_packet_free(p);
|
||||
}
|
||||
}
|
||||
|
||||
static int log_level_get(struct debug *dbg, struct log_module *log_module)
|
||||
{
|
||||
struct qm35_ctx *qm35_hdl;
|
||||
struct log_packet *p;
|
||||
int ret = 0;
|
||||
DECLARE_COMPLETION_ONSTACK(comp);
|
||||
|
||||
qm35_hdl = container_of(dbg, struct qm35_ctx, debug);
|
||||
|
||||
p = encode_get_log_level_packet(log_module->id);
|
||||
if (!p) {
|
||||
pr_err("failed to encode get log level packet\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
log_module->read_done = ∁
|
||||
|
||||
ret = hsspi_send(&qm35_hdl->hsspi, &qm35_hdl->log_layer.hlayer,
|
||||
&p->blk);
|
||||
if (ret) {
|
||||
pr_err("failed to send spi packet\n");
|
||||
log_packet_free(p);
|
||||
return 0;
|
||||
}
|
||||
|
||||
wait_for_completion(log_module->read_done);
|
||||
|
||||
return log_module->lvl;
|
||||
}
|
||||
|
||||
static char *log_trace_get_next(struct debug *dbg, rb_entry_size_t *len)
|
||||
{
|
||||
struct qm35_ctx *qm35_hdl;
|
||||
|
||||
qm35_hdl = container_of(dbg, struct qm35_ctx, debug);
|
||||
|
||||
return rb_pop(&qm35_hdl->log_layer.rb, len);
|
||||
}
|
||||
|
||||
static rb_entry_size_t log_trace_get_next_size(struct debug *dbg)
|
||||
{
|
||||
struct qm35_ctx *qm35_hdl;
|
||||
|
||||
qm35_hdl = container_of(dbg, struct qm35_ctx, debug);
|
||||
|
||||
return rb_next_size(&qm35_hdl->log_layer.rb);
|
||||
}
|
||||
|
||||
static bool log_trace_next_avail(struct debug *dbg)
|
||||
{
|
||||
struct qm35_ctx *qm35_hdl;
|
||||
|
||||
qm35_hdl = container_of(dbg, struct qm35_ctx, debug);
|
||||
|
||||
return rb_can_pop(&qm35_hdl->log_layer.rb);
|
||||
}
|
||||
|
||||
static void log_trace_reset(struct debug *dbg)
|
||||
{
|
||||
struct qm35_ctx *qm35_hdl;
|
||||
|
||||
qm35_hdl = container_of(dbg, struct qm35_ctx, debug);
|
||||
|
||||
rb_reset(&qm35_hdl->log_layer.rb);
|
||||
}
|
||||
|
||||
static int get_dev_id(struct debug *dbg, uint16_t *dev_id)
|
||||
{
|
||||
struct qm35_ctx *qm35_hdl;
|
||||
|
||||
qm35_hdl = container_of(dbg, struct qm35_ctx, debug);
|
||||
|
||||
return qm_get_dev_id(qm35_hdl, dev_id);
|
||||
}
|
||||
|
||||
static int get_soc_id(struct debug *dbg, uint8_t *soc_id)
|
||||
{
|
||||
struct qm35_ctx *qm35_hdl;
|
||||
|
||||
qm35_hdl = container_of(dbg, struct qm35_ctx, debug);
|
||||
|
||||
return qm_get_soc_id(qm35_hdl, soc_id);
|
||||
}
|
||||
|
||||
static const struct debug_trace_ops debug_trace_ops = {
|
||||
.enable_set = log_enable_set,
|
||||
.enable_get = log_enable_get,
|
||||
.level_set = log_level_set,
|
||||
.level_get = log_level_get,
|
||||
.trace_get_next = log_trace_get_next,
|
||||
.trace_get_next_size = log_trace_get_next_size,
|
||||
.trace_next_avail = log_trace_next_avail,
|
||||
.trace_reset = log_trace_reset,
|
||||
.get_dev_id = get_dev_id,
|
||||
.get_soc_id = get_soc_id,
|
||||
};
|
||||
|
||||
int log_layer_init(struct log_layer *log, struct debug *debug)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = rb_init(&log->rb, TRACE_RB_SIZE);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
log->hlayer.name = "QM35 LOG";
|
||||
log->hlayer.id = UL_LOG;
|
||||
log->hlayer.ops = &log_ops;
|
||||
log->enabled = false;
|
||||
log->log_modules = NULL;
|
||||
log->log_modules_count = 0;
|
||||
|
||||
debug->trace_ops = &debug_trace_ops;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void log_layer_deinit(struct log_layer *log)
|
||||
{
|
||||
kfree(log->log_modules);
|
||||
rb_deinit(&log->rb);
|
||||
}
|
||||
70
drivers/uwb/qm35/hsspi_log.h
Normal file
70
drivers/uwb/qm35/hsspi_log.h
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
|
||||
/*
|
||||
* This file is part of the QM35 UCI stack for linux.
|
||||
*
|
||||
* Copyright (c) 2022 Qorvo US, Inc.
|
||||
*
|
||||
* This software is provided under the GNU General Public License, version 2
|
||||
* (GPLv2), as well as under a Qorvo commercial license.
|
||||
*
|
||||
* You may choose to use this software under the terms of the GPLv2 License,
|
||||
* version 2 ("GPLv2"), as published by the Free Software Foundation.
|
||||
* You should have received a copy of the GPLv2 along with this program. If
|
||||
* not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This program is distributed under the GPLv2 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 GPLv2 for more
|
||||
* details.
|
||||
*
|
||||
* If you cannot meet the requirements of the GPLv2, you may not use this
|
||||
* software for any purpose without first obtaining a commercial license from
|
||||
* Qorvo.
|
||||
* Please contact Qorvo to inquire about licensing terms.
|
||||
*
|
||||
* QM35 LOG layer HSSPI Protocol
|
||||
*/
|
||||
|
||||
#ifndef __HSSPI_LOG_H__
|
||||
#define __HSSPI_LOG_H__
|
||||
|
||||
#include "hsspi.h"
|
||||
#include "debug.h"
|
||||
#include "qm35_rb.h"
|
||||
|
||||
struct log_packet {
|
||||
struct hsspi_block blk;
|
||||
struct completion *write_done;
|
||||
};
|
||||
|
||||
struct log_layer {
|
||||
struct hsspi_layer hlayer;
|
||||
uint8_t log_modules_count;
|
||||
struct log_module *log_modules;
|
||||
struct rb rb;
|
||||
bool enabled;
|
||||
};
|
||||
|
||||
/**
|
||||
* log_packet_alloc() - Allocate an LOG packet
|
||||
* @length: length of the LOG packet
|
||||
*
|
||||
* Allocate an LOG packet that can be used by the HSSPI driver in
|
||||
* order to send or receive an LOG packet.
|
||||
*
|
||||
* Return: a newly allocated &struct log_packet or NULL
|
||||
*/
|
||||
struct log_packet *log_packet_alloc(u16 length);
|
||||
|
||||
/**
|
||||
* log_packet_free() - Free an LOG packet
|
||||
* @p: pointer to the &struct log_packet to free
|
||||
*
|
||||
*/
|
||||
void log_packet_free(struct log_packet *p);
|
||||
|
||||
int log_layer_init(struct log_layer *log, struct debug *debug);
|
||||
void log_layer_deinit(struct log_layer *log);
|
||||
|
||||
#endif // __HSSPI_LOG_H__
|
||||
181
drivers/uwb/qm35/hsspi_test.c
Normal file
181
drivers/uwb/qm35/hsspi_test.c
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
/*
|
||||
* This file is part of the QM35 UCI stack for linux.
|
||||
*
|
||||
* Copyright (c) 2021 Qorvo US, Inc.
|
||||
*
|
||||
* This software is provided under the GNU General Public License, version 2
|
||||
* (GPLv2), as well as under a Qorvo commercial license.
|
||||
*
|
||||
* You may choose to use this software under the terms of the GPLv2 License,
|
||||
* version 2 ("GPLv2"), as published by the Free Software Foundation.
|
||||
* You should have received a copy of the GPLv2 along with this program. If
|
||||
* not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This program is distributed under the GPLv2 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 GPLv2 for more
|
||||
* details.
|
||||
*
|
||||
* If you cannot meet the requirements of the GPLv2, you may not use this
|
||||
* software for any purpose without first obtaining a commercial license from
|
||||
* Qorvo.
|
||||
* Please contact Qorvo to inquire about licensing terms.
|
||||
*
|
||||
* QM35 UCI layer HSSPI Protocol
|
||||
*/
|
||||
|
||||
#include "hsspi_test.h"
|
||||
#include "hsspi_uci.h"
|
||||
#include <linux/printk.h>
|
||||
#include <linux/delay.h>
|
||||
|
||||
int hsspi_test_registered(struct hsspi_layer *upper_layer);
|
||||
void hsspi_test_unregistered(struct hsspi_layer *upper_layer);
|
||||
struct hsspi_block *hsspi_test_get(struct hsspi_layer *upper_layer, u16 length);
|
||||
void hsspi_test_received(struct hsspi_layer *upper_layer,
|
||||
struct hsspi_block *blk, int status);
|
||||
void hsspi_test_sent(struct hsspi_layer *upper_layer, struct hsspi_block *blk,
|
||||
int status);
|
||||
|
||||
struct hsspi_layer_ops test_hsspi_layer_ops = {
|
||||
.registered = hsspi_test_registered,
|
||||
.unregistered = hsspi_test_unregistered,
|
||||
.get = hsspi_test_get,
|
||||
.received = hsspi_test_received,
|
||||
.sent = hsspi_test_sent,
|
||||
};
|
||||
|
||||
struct hsspi_layer test_hsspi_layer = {
|
||||
.name = "hsspi_test_layer",
|
||||
.id = UL_TEST_HSSPI,
|
||||
.ops = &test_hsspi_layer_ops,
|
||||
};
|
||||
|
||||
static struct hsspi *ghsspi;
|
||||
int sleep_inter_frame_ms;
|
||||
extern int test_sleep_after_ss_ready_us;
|
||||
|
||||
int hsspi_test_init(struct hsspi *hsspi)
|
||||
{
|
||||
ghsspi = hsspi;
|
||||
return hsspi_register(hsspi, &test_hsspi_layer);
|
||||
}
|
||||
|
||||
void hsspi_test_deinit(struct hsspi *hsspi)
|
||||
{
|
||||
hsspi_unregister(hsspi, &test_hsspi_layer);
|
||||
}
|
||||
|
||||
int hsspi_test_registered(struct hsspi_layer *upper_layer)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void hsspi_test_unregistered(struct hsspi_layer *upper_layer)
|
||||
{
|
||||
}
|
||||
|
||||
static int check_rx(const u8 *rx, int len)
|
||||
{
|
||||
int idx = 0, err = 0;
|
||||
for (; idx < len; idx++) {
|
||||
if (rx[idx] != (idx & 0xff)) {
|
||||
pr_err("hsspi test: check_rx rx[%u] != %u\n", idx,
|
||||
rx[idx]);
|
||||
print_hex_dump(KERN_DEBUG, "rx:", DUMP_PREFIX_ADDRESS,
|
||||
16, 1, rx, len, false);
|
||||
err = -5963;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
struct hsspi_block *hsspi_test_get(struct hsspi_layer *layer, u16 length)
|
||||
{
|
||||
struct hsspi_block *blk = kzalloc(sizeof(*blk) + length, GFP_KERNEL);
|
||||
if (blk) {
|
||||
blk->data = blk + 1;
|
||||
blk->size = length;
|
||||
blk->length = length;
|
||||
}
|
||||
return blk;
|
||||
}
|
||||
|
||||
void hsspi_test_set_inter_frame_ms(int ms)
|
||||
{
|
||||
sleep_inter_frame_ms = ms;
|
||||
}
|
||||
|
||||
void hsspi_test_received(struct hsspi_layer *layer, struct hsspi_block *blk,
|
||||
int status)
|
||||
{
|
||||
static uint64_t bytes, msgs, errors, bytes0, msgs0, errors0;
|
||||
static time64_t last_perf_dump;
|
||||
uint32_t rem;
|
||||
int error = check_rx(blk->data, blk->length) ? 1 : 0;
|
||||
time64_t now;
|
||||
errors += error;
|
||||
|
||||
if (!last_perf_dump) {
|
||||
last_perf_dump = ktime_get_seconds();
|
||||
}
|
||||
now = ktime_get_seconds();
|
||||
|
||||
/* inject latencies between each message and between the check
|
||||
* of ss-ready and the xfer.
|
||||
* The test is expected to fail if
|
||||
* sleep_inter_frame_ms > CONFIG_PM_RET_SLEEP_DELAY_US
|
||||
*/
|
||||
if (sleep_inter_frame_ms > 0) {
|
||||
static int delay_us = 0;
|
||||
test_sleep_after_ss_ready_us =
|
||||
sleep_inter_frame_ms * 1000 - delay_us;
|
||||
usleep_range(delay_us, delay_us + 1);
|
||||
delay_us += 100;
|
||||
if (delay_us > sleep_inter_frame_ms * 1000)
|
||||
delay_us = 0;
|
||||
} else {
|
||||
test_sleep_after_ss_ready_us = 0;
|
||||
}
|
||||
bytes += blk->length;
|
||||
msgs++;
|
||||
error |= hsspi_send(ghsspi, layer, blk);
|
||||
div_u64_rem(msgs, 100, &rem);
|
||||
if (error || (rem == 0))
|
||||
pr_info("hsspi test: bytes received %llu, msgs %llu, errors %llu\n",
|
||||
bytes, msgs, errors);
|
||||
if (now > last_perf_dump) {
|
||||
uint64_t dbytes = bytes >= bytes0 ? bytes - bytes0 :
|
||||
~0ULL - bytes0 + bytes;
|
||||
uint64_t dmsgs = msgs >= msgs0 ? msgs - msgs0 :
|
||||
~0ULL - msgs0 + msgs;
|
||||
uint64_t derrors = errors >= errors0 ? errors - errors0 :
|
||||
~0ULL - errors0 + errors;
|
||||
pr_info("hsspi test perfs: %llu B/s, %llu msgs/s, %llu errors/s\n",
|
||||
div_u64(dbytes, (now - last_perf_dump)),
|
||||
div_u64(dmsgs, (now - last_perf_dump)),
|
||||
div_u64(derrors, (now - last_perf_dump)));
|
||||
bytes0 = bytes;
|
||||
msgs0 = msgs;
|
||||
errors0 = errors;
|
||||
last_perf_dump = now;
|
||||
}
|
||||
}
|
||||
|
||||
void hsspi_test_sent(struct hsspi_layer *layer, struct hsspi_block *blk,
|
||||
int status)
|
||||
{
|
||||
static uint64_t bytes, msgs, errors;
|
||||
uint32_t rem;
|
||||
errors += status ? 1 : 0;
|
||||
msgs++;
|
||||
bytes += blk->length;
|
||||
div_u64_rem(msgs, 100, &rem);
|
||||
if (status || (rem == 0))
|
||||
pr_info("hsspi test: bytes sent %llu, msgs %llu, errors %llu\n",
|
||||
bytes, msgs, errors);
|
||||
kfree(blk);
|
||||
}
|
||||
39
drivers/uwb/qm35/hsspi_test.h
Normal file
39
drivers/uwb/qm35/hsspi_test.h
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
|
||||
/*
|
||||
* This file is part of the QM35 UCI stack for linux.
|
||||
*
|
||||
* Copyright (c) 2022 Qorvo US, Inc.
|
||||
*
|
||||
* This software is provided under the GNU General Public License, version 2
|
||||
* (GPLv2), as well as under a Qorvo commercial license.
|
||||
*
|
||||
* You may choose to use this software under the terms of the GPLv2 License,
|
||||
* version 2 ("GPLv2"), as published by the Free Software Foundation.
|
||||
* You should have received a copy of the GPLv2 along with this program. If
|
||||
* not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This program is distributed under the GPLv2 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 GPLv2 for more
|
||||
* details.
|
||||
*
|
||||
* If you cannot meet the requirements of the GPLv2, you may not use this
|
||||
* software for any purpose without first obtaining a commercial license from
|
||||
* Qorvo.
|
||||
* Please contact Qorvo to inquire about licensing terms.
|
||||
*
|
||||
* QM35 Ringbuffer
|
||||
*/
|
||||
|
||||
#ifndef __HSSPI_TEST_H___
|
||||
#define __HSSPI_TEST_H___
|
||||
|
||||
#include "hsspi.h"
|
||||
|
||||
int hsspi_test_init(struct hsspi *hsspi);
|
||||
void hsspi_test_deinit(struct hsspi *hsspi);
|
||||
|
||||
void hsspi_test_set_inter_frame_ms(int ms);
|
||||
|
||||
#endif /* __HSSPI_TEST_H___ */
|
||||
233
drivers/uwb/qm35/hsspi_uci.c
Normal file
233
drivers/uwb/qm35/hsspi_uci.c
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
/*
|
||||
* This file is part of the QM35 UCI stack for linux.
|
||||
*
|
||||
* Copyright (c) 2021 Qorvo US, Inc.
|
||||
*
|
||||
* This software is provided under the GNU General Public License, version 2
|
||||
* (GPLv2), as well as under a Qorvo commercial license.
|
||||
*
|
||||
* You may choose to use this software under the terms of the GPLv2 License,
|
||||
* version 2 ("GPLv2"), as published by the Free Software Foundation.
|
||||
* You should have received a copy of the GPLv2 along with this program. If
|
||||
* not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This program is distributed under the GPLv2 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 GPLv2 for more
|
||||
* details.
|
||||
*
|
||||
* If you cannot meet the requirements of the GPLv2, you may not use this
|
||||
* software for any purpose without first obtaining a commercial license from
|
||||
* Qorvo.
|
||||
* Please contact Qorvo to inquire about licensing terms.
|
||||
*
|
||||
* QM35 UCI layer HSSPI Protocol
|
||||
*/
|
||||
|
||||
#include "qm35.h"
|
||||
#include "hsspi_uci.h"
|
||||
|
||||
struct uci_packet *uci_packet_alloc(u16 length)
|
||||
{
|
||||
struct uci_packet *p;
|
||||
|
||||
p = kzalloc(sizeof(*p), GFP_KERNEL);
|
||||
if (!p)
|
||||
return NULL;
|
||||
|
||||
if (hsspi_init_block(&p->blk, length)) {
|
||||
kfree(p);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
p->data = p->blk.data;
|
||||
p->length = p->blk.length;
|
||||
return p;
|
||||
}
|
||||
|
||||
void uci_packet_free(struct uci_packet *p)
|
||||
{
|
||||
hsspi_deinit_block(&p->blk);
|
||||
kfree(p);
|
||||
}
|
||||
|
||||
static int uci_registered(struct hsspi_layer *layer)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void clear_rx_list(struct uci_layer *uci)
|
||||
{
|
||||
struct uci_packet *p;
|
||||
|
||||
mutex_lock(&uci->lock);
|
||||
|
||||
while (!list_empty(&uci->rx_list)) {
|
||||
p = list_first_entry(&uci->rx_list, struct uci_packet, list);
|
||||
|
||||
list_del(&p->list);
|
||||
|
||||
uci_packet_free(p);
|
||||
}
|
||||
|
||||
mutex_unlock(&uci->lock);
|
||||
|
||||
wake_up_interruptible(&uci->wq);
|
||||
}
|
||||
|
||||
static void uci_unregistered(struct hsspi_layer *hlayer)
|
||||
{
|
||||
struct uci_layer *uci = container_of(hlayer, struct uci_layer, hlayer);
|
||||
|
||||
clear_rx_list(uci);
|
||||
}
|
||||
|
||||
static struct hsspi_block *uci_get(struct hsspi_layer *hlayer, u16 length)
|
||||
{
|
||||
struct uci_packet *p;
|
||||
|
||||
p = uci_packet_alloc(length);
|
||||
if (!p)
|
||||
return NULL;
|
||||
|
||||
return &p->blk;
|
||||
}
|
||||
|
||||
static void uci_sent(struct hsspi_layer *hlayer, struct hsspi_block *blk,
|
||||
int status)
|
||||
{
|
||||
struct uci_packet *p = container_of(blk, struct uci_packet, blk);
|
||||
|
||||
p->status = status;
|
||||
complete(p->write_done);
|
||||
}
|
||||
|
||||
#define UCI_CONTROL_PACKET_PAYLOAD_SIZE_LOCATION (3)
|
||||
|
||||
static size_t get_payload_size_from_header(const u8 *header)
|
||||
{
|
||||
bool is_data_packet = ((header[0] >> 5) & 0x07) == 0;
|
||||
bool is_control_packet = !is_data_packet && ((header[0] >> 7) == 0);
|
||||
|
||||
if (is_control_packet)
|
||||
return header[UCI_CONTROL_PACKET_PAYLOAD_SIZE_LOCATION];
|
||||
|
||||
return (header[3] << 8) | header[2];
|
||||
}
|
||||
|
||||
#define UCI_PACKET_HEADER_SIZE (4)
|
||||
|
||||
static void uci_received(struct hsspi_layer *hlayer, struct hsspi_block *blk,
|
||||
int status)
|
||||
{
|
||||
struct uci_layer *uci = container_of(hlayer, struct uci_layer, hlayer);
|
||||
struct uci_packet *p = container_of(blk, struct uci_packet, blk);
|
||||
|
||||
if (status)
|
||||
uci_packet_free(p);
|
||||
else {
|
||||
struct uci_packet *next;
|
||||
size_t readn = 0;
|
||||
size_t payload_size;
|
||||
|
||||
while (1) {
|
||||
if (blk->length - readn < UCI_PACKET_HEADER_SIZE)
|
||||
// Incomplete UCI header
|
||||
break;
|
||||
|
||||
payload_size = get_payload_size_from_header(
|
||||
(u8 *)blk->data + readn);
|
||||
|
||||
if (blk->length - readn <=
|
||||
UCI_PACKET_HEADER_SIZE + payload_size)
|
||||
// blk contains no additional packet
|
||||
break;
|
||||
|
||||
next = kzalloc(sizeof(*next), GFP_KERNEL);
|
||||
if (!next)
|
||||
break;
|
||||
|
||||
next->data = p->blk.data + readn;
|
||||
next->length = UCI_PACKET_HEADER_SIZE + payload_size;
|
||||
|
||||
readn += next->length;
|
||||
|
||||
mutex_lock(&uci->lock);
|
||||
list_add_tail(&next->list, &uci->rx_list);
|
||||
mutex_unlock(&uci->lock);
|
||||
}
|
||||
|
||||
p->data = p->blk.data + readn;
|
||||
p->length = p->blk.length - readn;
|
||||
|
||||
mutex_lock(&uci->lock);
|
||||
list_add_tail(&p->list, &uci->rx_list);
|
||||
mutex_unlock(&uci->lock);
|
||||
|
||||
wake_up_interruptible(&uci->wq);
|
||||
}
|
||||
}
|
||||
|
||||
static const struct hsspi_layer_ops uci_ops = {
|
||||
.registered = uci_registered,
|
||||
.unregistered = uci_unregistered,
|
||||
.get = uci_get,
|
||||
.received = uci_received,
|
||||
.sent = uci_sent,
|
||||
};
|
||||
|
||||
int uci_layer_init(struct uci_layer *uci)
|
||||
{
|
||||
uci->hlayer.name = "UCI";
|
||||
uci->hlayer.id = UL_UCI_APP;
|
||||
uci->hlayer.ops = &uci_ops;
|
||||
|
||||
INIT_LIST_HEAD(&uci->rx_list);
|
||||
mutex_init(&uci->lock);
|
||||
init_waitqueue_head(&uci->wq);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void uci_layer_deinit(struct uci_layer *uci)
|
||||
{
|
||||
clear_rx_list(uci);
|
||||
}
|
||||
|
||||
bool uci_layer_has_data_available(struct uci_layer *uci)
|
||||
{
|
||||
bool ret;
|
||||
|
||||
mutex_lock(&uci->lock);
|
||||
ret = !list_empty(&uci->rx_list);
|
||||
mutex_unlock(&uci->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct uci_packet *uci_layer_read(struct uci_layer *uci, size_t max_size,
|
||||
bool non_blocking)
|
||||
{
|
||||
struct uci_packet *p;
|
||||
int ret;
|
||||
|
||||
if (!non_blocking) {
|
||||
ret = wait_event_interruptible(
|
||||
uci->wq, uci_layer_has_data_available(uci));
|
||||
if (ret)
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
mutex_lock(&uci->lock);
|
||||
p = list_first_entry_or_null(&uci->rx_list, struct uci_packet, list);
|
||||
if (p) {
|
||||
if (p->length > max_size)
|
||||
p = ERR_PTR(-EMSGSIZE);
|
||||
else
|
||||
list_del(&p->list);
|
||||
} else
|
||||
p = ERR_PTR(-EAGAIN);
|
||||
|
||||
mutex_unlock(&uci->lock);
|
||||
return p;
|
||||
}
|
||||
129
drivers/uwb/qm35/hsspi_uci.h
Normal file
129
drivers/uwb/qm35/hsspi_uci.h
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
|
||||
/*
|
||||
* This file is part of the QM35 UCI stack for linux.
|
||||
*
|
||||
* Copyright (c) 2021 Qorvo US, Inc.
|
||||
*
|
||||
* This software is provided under the GNU General Public License, version 2
|
||||
* (GPLv2), as well as under a Qorvo commercial license.
|
||||
*
|
||||
* You may choose to use this software under the terms of the GPLv2 License,
|
||||
* version 2 ("GPLv2"), as published by the Free Software Foundation.
|
||||
* You should have received a copy of the GPLv2 along with this program. If
|
||||
* not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This program is distributed under the GPLv2 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 GPLv2 for more
|
||||
* details.
|
||||
*
|
||||
* If you cannot meet the requirements of the GPLv2, you may not use this
|
||||
* software for any purpose without first obtaining a commercial license from
|
||||
* Qorvo.
|
||||
* Please contact Qorvo to inquire about licensing terms.
|
||||
*
|
||||
* QM35 UCI layer HSSPI Protocol
|
||||
*/
|
||||
|
||||
#ifndef __HSSPI_UCI_H__
|
||||
#define __HSSPI_UCI_H__
|
||||
|
||||
#include <linux/completion.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/wait.h>
|
||||
|
||||
#include "hsspi.h"
|
||||
|
||||
/**
|
||||
* struct uci_packet - UCI packet that implements a &struct hsspi_block.
|
||||
* @blk: &struct hsspi_block
|
||||
* @write_done: norify when the packet has been really send
|
||||
* @list: link with &struct uci_layer.rx_list
|
||||
* @status: status of the transfer
|
||||
*/
|
||||
struct uci_packet {
|
||||
struct hsspi_block blk;
|
||||
struct completion *write_done;
|
||||
struct list_head list;
|
||||
u8 *data;
|
||||
int length;
|
||||
int status;
|
||||
};
|
||||
|
||||
/**
|
||||
* uci_packet_alloc() - Allocate an UCI packet
|
||||
* @length: length of the UCI packet
|
||||
*
|
||||
* Allocate an UCI packet that can be used by the HSSPI driver in
|
||||
* order to send or receive an UCI packet.
|
||||
*
|
||||
* Return: a newly allocated &struct uci_packet or NULL
|
||||
*/
|
||||
struct uci_packet *uci_packet_alloc(u16 length);
|
||||
|
||||
/**
|
||||
* uci_packet_free() - Free an UCI packet
|
||||
* @p: pointer to the &struct uci_packet to free
|
||||
*
|
||||
*/
|
||||
void uci_packet_free(struct uci_packet *p);
|
||||
|
||||
/**
|
||||
* struct uci_layer - Implement an HSSPI Layer
|
||||
* @hlayer: &struct hsspi_layer
|
||||
* @rx_list: list of received UCI packets
|
||||
* @lock: protect the &struct uci_layer.rx_list
|
||||
* @wq: notify when the &struct uci_layer.rx_list is not empty
|
||||
*/
|
||||
struct uci_layer {
|
||||
struct hsspi_layer hlayer;
|
||||
struct list_head rx_list;
|
||||
struct mutex lock;
|
||||
wait_queue_head_t wq;
|
||||
};
|
||||
|
||||
/**
|
||||
* uci_layer_init() - Initialize UCI Layer
|
||||
*
|
||||
*/
|
||||
int uci_layer_init(struct uci_layer *uci);
|
||||
|
||||
/**
|
||||
* uci_layer_deinit() - Deinitialize UCI Layer
|
||||
*
|
||||
*/
|
||||
void uci_layer_deinit(struct uci_layer *uci);
|
||||
|
||||
/**
|
||||
* uci_layer_has_data_availal() - checks if the layer has some rx packets
|
||||
* @uci: pointer to &struct uci_layer
|
||||
*
|
||||
* Function that checks if the UCI layer has some data waiting to be read.
|
||||
*
|
||||
* Return: true if some data is available, false otherwise.
|
||||
*/
|
||||
bool uci_layer_has_data_available(struct uci_layer *uci);
|
||||
|
||||
/**
|
||||
* uci_layer_read() - get a packet from the rx_list
|
||||
* @uci: pointer to &struct uci_layer
|
||||
* @max_size: maximum size possible for the UCI packet
|
||||
* @non_blocking: true if non blocking, false otherwise
|
||||
*
|
||||
* This function returns an UCI packet if available in the
|
||||
* &struct uci_layer.rx_list. The max_size argument logic is due to the way
|
||||
* the /dev/uci is make. We should ensure that we return an entire
|
||||
* packet in uci_read, so we must get a packet only if the caller has
|
||||
* enougth room for it.
|
||||
*
|
||||
* Return: a &struct uci_packet if succeed,
|
||||
* -EINTR if it was interrupted (in blocking mode),
|
||||
* -ESMGSIZE if the available UCI packet is bigger than max_size,
|
||||
* -EAGAIN if there is no available UCI packet (in non blocking mode)
|
||||
*/
|
||||
struct uci_packet *uci_layer_read(struct uci_layer *uci, size_t max_size,
|
||||
bool non_blocking);
|
||||
|
||||
#endif // __HSSPI_UCI_H__
|
||||
245
drivers/uwb/qm35/libfwupdater/src/fwupdater.c
Normal file
245
drivers/uwb/qm35/libfwupdater/src/fwupdater.c
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
|
||||
#ifndef __KERNEL__
|
||||
#include <stddef.h>
|
||||
#endif
|
||||
|
||||
#include <linux/uwb/qmrom_spi.h>
|
||||
#include <linux/uwb/qmrom_log.h>
|
||||
#include <linux/uwb/qmrom_utils.h>
|
||||
#include <linux/uwb/spi_rom_protocol.h>
|
||||
|
||||
#include <linux/uwb/fwupdater.h>
|
||||
|
||||
/* Extract from C0 rom code */
|
||||
#define MAX_CERTIFICATE_SIZE 0x400
|
||||
#define MAX_CHUNK_SIZE 4096
|
||||
#define WAIT_SS_RDY_TIMEOUT 100
|
||||
#define RESULT_RETRIES 3
|
||||
#define RESULT_CMD_INTERVAL_MS 50
|
||||
|
||||
#define MIN(a, b) ((a) < (b) ? (a) : (b))
|
||||
|
||||
#ifndef __KERNEL__
|
||||
_Static_assert(sizeof(struct stc) + MAX_CERTIFICATE_SIZE < MAX_CHUNK_SIZE);
|
||||
#endif
|
||||
|
||||
static int send_data_chunks(struct qmrom_handle *handle, char *data,
|
||||
size_t size);
|
||||
|
||||
int run_fwupdater(struct qmrom_handle *handle, char *fwpkg_bin, size_t size)
|
||||
{
|
||||
int rc;
|
||||
|
||||
if (size < sizeof(struct fw_pkg_hdr_t) +
|
||||
sizeof(struct fw_pkg_img_hdr_t) +
|
||||
CRYPTO_IMAGES_CERT_PKG_SIZE +
|
||||
CRYPTO_FIRMWARE_CHUNK_MIN_SIZE) {
|
||||
LOG_ERR("%s cannot extract enough data from fw package binary\n",
|
||||
__func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
rc = send_data_chunks(handle, fwpkg_bin, size);
|
||||
if (rc) {
|
||||
LOG_ERR("Sending image failed with %d\n", rc);
|
||||
return rc;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int run_fwupdater_get_status(struct qmrom_handle *handle,
|
||||
struct stc *hstc, struct stc *sstc,
|
||||
struct fw_updater_status_t *status)
|
||||
{
|
||||
uint32_t i = 0;
|
||||
bool owa;
|
||||
|
||||
while (i++ < RESULT_RETRIES) {
|
||||
// Poll the QM
|
||||
qmrom_spi_wait_for_ready_line(handle->ss_rdy_handle,
|
||||
WAIT_SS_RDY_TIMEOUT);
|
||||
sstc->all = 0;
|
||||
hstc->all = 0;
|
||||
qmrom_spi_transfer(handle->spi_handle, (char *)sstc,
|
||||
(const char *)hstc, sizeof(hstc));
|
||||
// LOG_INFO("Poll received:\n");
|
||||
// hexdump(LOG_INFO, sstc, sizeof(sstc));
|
||||
/* Line might have been read ready before QM actually handled the previous cmd */
|
||||
qmrom_msleep(RESULT_CMD_INTERVAL_MS);
|
||||
|
||||
qmrom_spi_wait_for_ready_line(handle->ss_rdy_handle,
|
||||
WAIT_SS_RDY_TIMEOUT);
|
||||
sstc->all = 0;
|
||||
hstc->all = 0;
|
||||
hstc->host_flags.pre_read = 1;
|
||||
qmrom_spi_transfer(handle->spi_handle, (char *)sstc,
|
||||
(const char *)hstc, sizeof(hstc));
|
||||
// LOG_INFO("Pre-Read received:\n");
|
||||
// hexdump(LOG_INFO, sstc, sizeof(sstc));
|
||||
|
||||
/* Stops the loop when QM has a result to share */
|
||||
owa = sstc->soc_flags.out_waiting;
|
||||
qmrom_spi_wait_for_ready_line(handle->ss_rdy_handle,
|
||||
WAIT_SS_RDY_TIMEOUT);
|
||||
sstc->all = 0;
|
||||
hstc->all = 0;
|
||||
hstc->host_flags.read = 1;
|
||||
hstc->len = sizeof(*status);
|
||||
qmrom_spi_transfer(handle->spi_handle, (char *)sstc,
|
||||
(const char *)hstc,
|
||||
sizeof(hstc) + sizeof(*status));
|
||||
// LOG_INFO("Read received:\n");
|
||||
// hexdump(LOG_INFO, sstc, sizeof(hstc) + sizeof(uint32_t));
|
||||
if (owa) {
|
||||
memcpy(status, sstc->payload, sizeof(*status));
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!owa) {
|
||||
LOG_ERR("Timedout waiting for result\n");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct stc *prepare_hstc(bool copy_data, char *tx, char *data, size_t len)
|
||||
{
|
||||
struct stc *hstc = (struct stc *)(copy_data ? tx : data);
|
||||
hstc->all = 0;
|
||||
hstc->host_flags.write = 1;
|
||||
hstc->len = len;
|
||||
if (copy_data)
|
||||
memcpy(hstc->payload, data, len);
|
||||
return hstc;
|
||||
}
|
||||
|
||||
static int send_data_chunks(struct qmrom_handle *handle, char *data,
|
||||
size_t size)
|
||||
{
|
||||
struct fw_updater_status_t status;
|
||||
char *rx, *tx;
|
||||
struct stc *hstc, *sstc;
|
||||
size_t to_send;
|
||||
int rc = 0;
|
||||
bool copy_data = !qmrom_data_dma_able(data);
|
||||
|
||||
qmrom_alloc(rx, MAX_CHUNK_SIZE + sizeof(struct stc));
|
||||
qmrom_alloc(tx, MAX_CHUNK_SIZE + sizeof(struct stc));
|
||||
if (!rx || !tx) {
|
||||
LOG_ERR("Rx/Tx buffers allocation failure\n");
|
||||
if (rx)
|
||||
qmrom_free(rx);
|
||||
if (tx)
|
||||
qmrom_free(tx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
hstc = (struct stc *)tx;
|
||||
sstc = (struct stc *)rx;
|
||||
|
||||
/* Sending the fw package header */
|
||||
LOG_INFO("Sending the fw package header (copy_data is %d)\n", copy_data);
|
||||
hstc->all = 0;
|
||||
hstc->host_flags.write = 1;
|
||||
hstc->len = sizeof(struct fw_pkg_hdr_t);
|
||||
size -= sizeof(struct fw_pkg_hdr_t);
|
||||
memcpy(&tx[sizeof(struct stc)], data, sizeof(struct fw_pkg_hdr_t));
|
||||
/* Move the data to the next offset minus the stc footprint */
|
||||
data += sizeof(struct fw_pkg_hdr_t) - (copy_data ? 0 : sizeof(struct stc));
|
||||
qmrom_spi_wait_for_ready_line(handle->ss_rdy_handle,
|
||||
WAIT_SS_RDY_TIMEOUT);
|
||||
rc = qmrom_spi_transfer(handle->spi_handle, rx, tx,
|
||||
sizeof(struct fw_pkg_hdr_t) + sizeof(struct stc));
|
||||
if (rc)
|
||||
{
|
||||
LOG_ERR("First qmrom_spi_transfer failed with %d\n", rc);
|
||||
return rc;
|
||||
}
|
||||
// hexdump(LOG_INFO, tx + sizeof(struct stc), sizeof(struct fw_pkg_hdr_t));
|
||||
|
||||
/* Sending the image header */
|
||||
LOG_INFO("Sending the image header\n");
|
||||
hstc = prepare_hstc(copy_data, tx, data, sizeof(struct fw_pkg_img_hdr_t));
|
||||
size -= sizeof(struct fw_pkg_img_hdr_t);
|
||||
// hexdump(LOG_INFO, data + sizeof(struct stc),
|
||||
// sizeof(struct fw_pkg_img_hdr_t));
|
||||
data += sizeof(struct fw_pkg_img_hdr_t);
|
||||
qmrom_spi_wait_for_ready_line(handle->ss_rdy_handle,
|
||||
WAIT_SS_RDY_TIMEOUT);
|
||||
qmrom_spi_transfer(handle->spi_handle, rx, (const char *)hstc,
|
||||
sizeof(struct fw_pkg_img_hdr_t) +
|
||||
sizeof(struct stc));
|
||||
|
||||
/* Sending the cert chain */
|
||||
LOG_INFO("Sending the cert chain\n");
|
||||
#if MAX_CHUNK_SIZE < CRYPTO_IMAGES_CERT_PKG_SIZE
|
||||
hstc = prepare_hstc(true, tx, data, CRYPTO_IMAGES_CERT_KEY_SIZE);
|
||||
size -= CRYPTO_IMAGES_CERT_KEY_SIZE;
|
||||
data += CRYPTO_IMAGES_CERT_KEY_SIZE;
|
||||
qmrom_spi_wait_for_ready_line(handle->ss_rdy_handle,
|
||||
WAIT_SS_RDY_TIMEOUT);
|
||||
qmrom_spi_transfer(handle->spi_handle, rx, tx,
|
||||
CRYPTO_IMAGES_CERT_KEY_SIZE);
|
||||
|
||||
hstc = prepare_hstc(true, tx, data, CRYPTO_IMAGES_CERT_KEY_SIZE);
|
||||
size -= CRYPTO_IMAGES_CERT_KEY_SIZE;
|
||||
data += CRYPTO_IMAGES_CERT_KEY_SIZE;
|
||||
qmrom_spi_wait_for_ready_line(handle->ss_rdy_handle,
|
||||
WAIT_SS_RDY_TIMEOUT);
|
||||
qmrom_spi_transfer(handle->spi_handle, rx, tx,
|
||||
CRYPTO_IMAGES_CERT_KEY_SIZE);
|
||||
|
||||
hstc = prepare_hstc(true, tx, data, CRYPTO_IMAGES_CERT_CONTENT_SIZE);
|
||||
size -= CRYPTO_IMAGES_CERT_CONTENT_SIZE;
|
||||
data += CRYPTO_IMAGES_CERT_CONTENT_SIZE;
|
||||
qmrom_spi_wait_for_ready_line(handle->ss_rdy_handle,
|
||||
WAIT_SS_RDY_TIMEOUT);
|
||||
qmrom_spi_transfer(handle->spi_handle, rx, tx,
|
||||
CRYPTO_IMAGES_CERT_CONTENT_SIZE);
|
||||
#else
|
||||
hstc = prepare_hstc(copy_data, tx, data, CRYPTO_IMAGES_CERT_PKG_SIZE);
|
||||
size -= CRYPTO_IMAGES_CERT_PKG_SIZE;
|
||||
// hexdump(LOG_INFO, data + sizeof(struct stc), 100);
|
||||
data += CRYPTO_IMAGES_CERT_PKG_SIZE;
|
||||
qmrom_spi_wait_for_ready_line(handle->ss_rdy_handle,
|
||||
WAIT_SS_RDY_TIMEOUT);
|
||||
qmrom_spi_transfer(handle->spi_handle, rx, (const char *)hstc,
|
||||
CRYPTO_IMAGES_CERT_PKG_SIZE + sizeof(struct stc));
|
||||
#endif
|
||||
|
||||
/* Sending the fw image */
|
||||
LOG_INFO("Sending the image\n");
|
||||
do {
|
||||
to_send = MIN(MAX_CHUNK_SIZE, size);
|
||||
LOG_DBG("Sending a chunk of %zu bytes\n", to_send);
|
||||
hstc = prepare_hstc(copy_data, tx, data, to_send);
|
||||
qmrom_spi_wait_for_ready_line(handle->ss_rdy_handle,
|
||||
WAIT_SS_RDY_TIMEOUT);
|
||||
qmrom_spi_transfer(handle->spi_handle, rx, (const char *)hstc,
|
||||
to_send + sizeof(struct stc));
|
||||
data += to_send;
|
||||
size -= to_send;
|
||||
} while (size);
|
||||
|
||||
qmrom_spi_wait_for_ready_line(handle->ss_rdy_handle,
|
||||
WAIT_SS_RDY_TIMEOUT);
|
||||
|
||||
rc = run_fwupdater_get_status(handle, hstc, sstc, &status);
|
||||
if (rc) {
|
||||
LOG_ERR("run_fwupdater_get_status returned %d\n", rc);
|
||||
} else {
|
||||
if (status.status) {
|
||||
LOG_ERR("%s failed, fw updater status %#x (suberror %#x)\n",
|
||||
__func__, status.status, status.suberror);
|
||||
rc = status.status;
|
||||
} else {
|
||||
LOG_INFO("%s succeeded\n", __func__);
|
||||
}
|
||||
}
|
||||
|
||||
if (rx)
|
||||
qmrom_free(rx);
|
||||
if (tx)
|
||||
qmrom_free(tx);
|
||||
return rc;
|
||||
}
|
||||
403
drivers/uwb/qm35/libqmrom/src/qm357xx_rom_b0.c
Normal file
403
drivers/uwb/qm35/libqmrom/src/qm357xx_rom_b0.c
Normal file
|
|
@ -0,0 +1,403 @@
|
|||
// SPDX-License-Identifier: GPL-2.0 OR Apache-2.0
|
||||
/*
|
||||
* Copyright 2022 Qorvo US, Inc.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/uwb/qmrom.h>
|
||||
#include <linux/uwb/qmrom_spi.h>
|
||||
#include <linux/uwb/qmrom_log.h>
|
||||
#include <linux/uwb/qmrom_utils.h>
|
||||
#include <linux/uwb/spi_rom_protocol.h>
|
||||
|
||||
#define DEFAULT_SPI_CLOCKRATE 3000000
|
||||
#define CHIP_VERSION_CHIP_REV_PAYLOAD_OFFSET 1
|
||||
#define CHIP_VERSION_DEV_REV_PAYLOAD_OFFSET 3
|
||||
#define CHUNK_SIZE_B0 1008
|
||||
|
||||
enum B0_CMD {
|
||||
ROM_CMD_B0_SEC_LOAD_ICV_IMG_TO_RRAM = 0x0,
|
||||
ROM_CMD_B0_SEC_LOAD_OEM_IMG_TO_RRAM = 0x1,
|
||||
ROM_CMD_B0_GET_CHIP_VER = 0x2,
|
||||
ROM_CMD_B0_GET_SOC_INFO = 0x3,
|
||||
ROM_CMD_B0_ERASE_DBG_CERT = 0x4,
|
||||
ROM_CMD_B0_WRITE_DBG_CERT = 0x5,
|
||||
ROM_CMD_B0_SEC_IMAGE_DATA = 0xf,
|
||||
ROM_CMD_B0_CERT_DATA = 0x10,
|
||||
ROM_CMD_B0_DEBUG_CERT_SIZE = 0x11,
|
||||
};
|
||||
|
||||
enum B0_RESP {
|
||||
READY_FOR_CS_LOW_CMD = 0x00,
|
||||
WRONG_CS_LOW_CMD = 0x01,
|
||||
WAITING_FOR_NS_RRAM_FILE_SIZE = 0x02,
|
||||
WAITING_FOR_NS_SRAM_FILE_SIZE = 0x03,
|
||||
WAITING_FOR_NS_RRAM_FILE_DATA = 0x04,
|
||||
WAITING_FOR_NS_SRAM_FILE_DATA = 0x05,
|
||||
WAITING_FOR_SEC_FILE_DATA = 0x06,
|
||||
ERR_NS_SRAM_OR_RRAM_SIZE_CMD = 0x07,
|
||||
ERR_SEC_RRAM_SIZE_CMD = 0x08,
|
||||
ERR_WAITING_FOR_NS_IMAGE_DATA_CMD = 0x09,
|
||||
ERR_WAITING_FOR_SEC_IMAGE_DATA_CMD = 0x0A,
|
||||
ERR_IMAGE_SIZE_IS_ZERO = 0x0B,
|
||||
/* Got more data than expected size */
|
||||
ERR_IMAGE_SIZE_TOO_BIG = 0x0C,
|
||||
/* Image must divide in 16 without remainder */
|
||||
ERR_IMAGE_IS_NOT_16BYTES_MUL = 0x0D,
|
||||
ERR_GOT_DATA_MORE_THAN_ALLOWED = 0x0E,
|
||||
/* Remainder is allowed only for last packet */
|
||||
ERR_RRAM_DATA_REMAINDER_NOT_ALLOWED = 0x0F,
|
||||
ERR_WAITING_FOR_CERT_DATA_CMD = 0x10,
|
||||
WAITING_FOR_FIRST_KEY_CERT = 0x11,
|
||||
WAITING_FOR_SECOND_KEY_CERT = 0x12,
|
||||
WAITING_FOR_CONTENT_CERT = 0x13,
|
||||
WAITING_FOR_DEBUG_CERT_DATA = 0x14,
|
||||
ERR_FIRST_KEY_CERT_OR_FW_VER = 0x15,
|
||||
ERR_SECOND_KEY_CERT = 0x16,
|
||||
ERR_CONTENT_CERT_DOWNLOAD_ADDR = 0x17,
|
||||
/* If the content certificate contains to much images */
|
||||
ERR_TOO_MANY_IMAGES_IN_CONTENT_CERT = 0x18,
|
||||
ERR_ADDRESS_NOT_DIVIDED_BY_8 = 0x19,
|
||||
ERR_IMAGE_BOUNDARIES = 0x1A,
|
||||
/* Expected ICV type and got OEM */
|
||||
ERR_CERT_TYPE = 0x1B,
|
||||
ERR_PRODUCT_ID = 0x1C,
|
||||
ERR_RRAM_RANGE_OR_WRITE = 0x1D,
|
||||
WAITING_TO_DEBUG_CERTIFICATE_SIZE = 0x1E,
|
||||
ERR_DEBUG_CERT_SIZE = 0x1F,
|
||||
};
|
||||
|
||||
static int qm357xx_rom_b0_flash_debug_cert(struct qmrom_handle *handle,
|
||||
struct firmware *dbg_cert);
|
||||
static int qm357xx_rom_b0_erase_debug_cert(struct qmrom_handle *handle);
|
||||
static int
|
||||
qm357xx_rom_b0_flash_unstitched_fw(struct qmrom_handle *handle,
|
||||
const struct unstitched_firmware *all_fws);
|
||||
|
||||
static void qm357xx_rom_b0_poll_soc(struct qmrom_handle *handle)
|
||||
{
|
||||
int retries = handle->comms_retries;
|
||||
memset(handle->hstc, 0, sizeof(struct stc));
|
||||
qmrom_msleep(SPI_READY_TIMEOUT_MS);
|
||||
do {
|
||||
qmrom_spi_transfer(handle->spi_handle, (char *)handle->sstc,
|
||||
(const char *)handle->hstc,
|
||||
sizeof(struct stc) + handle->hstc->len);
|
||||
} while (retries-- && handle->sstc->raw_flags == 0);
|
||||
}
|
||||
|
||||
static int qm357xx_rom_b0_wait_ready(struct qmrom_handle *handle)
|
||||
{
|
||||
int retries = handle->comms_retries;
|
||||
int rc;
|
||||
|
||||
qm357xx_rom_b0_poll_soc(handle);
|
||||
|
||||
/* handle->sstc has been updated */
|
||||
while (retries-- &&
|
||||
handle->sstc->raw_flags != SPI_SH_READY_CMD_BIT_MASK) {
|
||||
if (handle->sstc->soc_flags.out_waiting) {
|
||||
qmrom_pre_read(handle);
|
||||
} else if (handle->sstc->soc_flags.out_active) {
|
||||
rc = qmrom_read(handle);
|
||||
if (rc)
|
||||
return rc;
|
||||
} else {
|
||||
/* error? */
|
||||
qm357xx_rom_b0_poll_soc(handle);
|
||||
}
|
||||
}
|
||||
|
||||
return handle->sstc->raw_flags == SPI_SH_READY_CMD_BIT_MASK ?
|
||||
0 :
|
||||
SPI_ERR_WAIT_READY_TIMEOUT;
|
||||
}
|
||||
|
||||
static int qm357xx_rom_b0_poll_cmd_resp(struct qmrom_handle *handle)
|
||||
{
|
||||
int retries = handle->comms_retries;
|
||||
|
||||
qm357xx_rom_b0_poll_soc(handle);
|
||||
do {
|
||||
if (handle->sstc->soc_flags.out_waiting) {
|
||||
qmrom_pre_read(handle);
|
||||
if (handle->sstc->len > 0xff) {
|
||||
/* likely the wrong endianness, A0? */
|
||||
return -1;
|
||||
}
|
||||
qmrom_read(handle);
|
||||
break;
|
||||
} else
|
||||
qm357xx_rom_b0_poll_soc(handle);
|
||||
} while (retries--);
|
||||
|
||||
return retries > 0 ? 0 : -1;
|
||||
}
|
||||
|
||||
int qm357xx_rom_b0_probe_device(struct qmrom_handle *handle)
|
||||
{
|
||||
int rc, i;
|
||||
uint8_t *soc_lcs_uuid;
|
||||
handle->is_be = false;
|
||||
check_stcs(__func__, __LINE__, handle);
|
||||
|
||||
if (handle->spi_speed == 0)
|
||||
qmrom_spi_set_freq(DEFAULT_SPI_CLOCKRATE);
|
||||
else
|
||||
qmrom_spi_set_freq(handle->spi_speed);
|
||||
|
||||
rc = qmrom_reboot_bootloader(handle);
|
||||
if (rc) {
|
||||
LOG_ERR("%s: cannot reset the device...\n", __func__);
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = qm357xx_rom_b0_wait_ready(handle);
|
||||
if (rc) {
|
||||
LOG_INFO("%s: maybe not a B0 device\n", __func__);
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = qm357xx_rom_write_cmd(handle, ROM_CMD_B0_GET_CHIP_VER);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
rc = qm357xx_rom_b0_poll_cmd_resp(handle);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
handle->chip_rev =
|
||||
SSTC2UINT16(handle, CHIP_VERSION_CHIP_REV_PAYLOAD_OFFSET) &
|
||||
0xFF;
|
||||
handle->device_version = bswap_16(
|
||||
SSTC2UINT16(handle, CHIP_VERSION_DEV_REV_PAYLOAD_OFFSET));
|
||||
if (handle->chip_rev != CHIP_REVISION_B0) {
|
||||
LOG_ERR("%s: wrong chip revision 0x%x\n", __func__,
|
||||
handle->chip_rev);
|
||||
handle->chip_rev = -1;
|
||||
return -1;
|
||||
}
|
||||
|
||||
rc = qm357xx_rom_b0_wait_ready(handle);
|
||||
if (rc) {
|
||||
LOG_ERR("%s: hmm something went wrong!!!\n", __func__);
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = qm357xx_rom_write_cmd(handle, ROM_CMD_B0_GET_SOC_INFO);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
rc = qm357xx_rom_b0_poll_cmd_resp(handle);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
/* skip the first byte */
|
||||
soc_lcs_uuid = &(handle->sstc->payload[1]);
|
||||
for (i = 0; i < QM357XX_ROM_SOC_ID_LEN; i++)
|
||||
handle->qm357xx_soc_info.soc_id[i] =
|
||||
soc_lcs_uuid[QM357XX_ROM_SOC_ID_LEN - i - 1];
|
||||
soc_lcs_uuid += QM357XX_ROM_SOC_ID_LEN;
|
||||
handle->qm357xx_soc_info.lcs_state = soc_lcs_uuid[0];
|
||||
soc_lcs_uuid += 1;
|
||||
for (i = 0; i < QM357XX_ROM_UUID_LEN; i++)
|
||||
handle->qm357xx_soc_info.uuid[i] =
|
||||
soc_lcs_uuid[QM357XX_ROM_UUID_LEN - i - 1];
|
||||
|
||||
/* Set device type */
|
||||
handle->dev_gen = DEVICE_GEN_QM357XX;
|
||||
/* Set rom ops */
|
||||
handle->qm357xx_rom_ops.flash_unstitched_fw =
|
||||
qm357xx_rom_b0_flash_unstitched_fw;
|
||||
handle->qm357xx_rom_ops.flash_debug_cert =
|
||||
qm357xx_rom_b0_flash_debug_cert;
|
||||
handle->qm357xx_rom_ops.erase_debug_cert =
|
||||
qm357xx_rom_b0_erase_debug_cert;
|
||||
|
||||
check_stcs(__func__, __LINE__, handle);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int qm357xx_rom_b0_flash_data(struct qmrom_handle *handle,
|
||||
struct firmware *fw, uint8_t cmd,
|
||||
uint8_t exp)
|
||||
{
|
||||
int rc, sent = 0;
|
||||
const char *bin_data = (const char *)fw->data;
|
||||
|
||||
check_stcs(__func__, __LINE__, handle);
|
||||
while (sent < fw->size) {
|
||||
uint32_t tx_bytes = fw->size - sent;
|
||||
if (tx_bytes > CHUNK_SIZE_B0)
|
||||
tx_bytes = CHUNK_SIZE_B0;
|
||||
|
||||
LOG_DBG("%s: poll soc...\n", __func__);
|
||||
check_stcs(__func__, __LINE__, handle);
|
||||
qm357xx_rom_b0_poll_soc(handle);
|
||||
qmrom_pre_read(handle);
|
||||
qmrom_read(handle);
|
||||
if (handle->sstc->payload[0] != exp) {
|
||||
LOG_ERR("%s: wrong data expected (%#x vs %#x)!!!\n",
|
||||
__func__, handle->sstc->payload[0] & 0xff, exp);
|
||||
if (handle->sstc->payload[0] ==
|
||||
ERR_FIRST_KEY_CERT_OR_FW_VER)
|
||||
return PEG_ERR_FIRST_KEY_CERT_OR_FW_VER;
|
||||
else
|
||||
return SPI_PROTO_WRONG_RESP;
|
||||
}
|
||||
|
||||
LOG_DBG("%s: sending %d command with %" PRIu32 " bytes\n",
|
||||
__func__, cmd, tx_bytes);
|
||||
rc = qm357xx_rom_write_size_cmd(handle, cmd, tx_bytes,
|
||||
bin_data);
|
||||
if (rc)
|
||||
return rc;
|
||||
sent += tx_bytes;
|
||||
bin_data += tx_bytes;
|
||||
check_stcs(__func__, __LINE__, handle);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
qm357xx_rom_b0_flash_unstitched_fw(struct qmrom_handle *handle,
|
||||
const struct unstitched_firmware *all_fws)
|
||||
{
|
||||
int rc = 0;
|
||||
uint8_t flash_cmd = handle->qm357xx_soc_info.lcs_state ==
|
||||
CC_BSV_SECURE_LCS ?
|
||||
ROM_CMD_B0_SEC_LOAD_OEM_IMG_TO_RRAM :
|
||||
ROM_CMD_B0_SEC_LOAD_ICV_IMG_TO_RRAM;
|
||||
|
||||
if (all_fws->key1_crt->data[HBK_LOC] == HBK_2E_ICV &&
|
||||
handle->qm357xx_soc_info.lcs_state != CC_BSV_CHIP_MANUFACTURE_LCS) {
|
||||
LOG_ERR("%s: Trying to flash an ICV fw on a non ICV platform\n",
|
||||
__func__);
|
||||
rc = -EINVAL;
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (all_fws->key1_crt->data[HBK_LOC] == HBK_2E_OEM &&
|
||||
handle->qm357xx_soc_info.lcs_state != CC_BSV_SECURE_LCS) {
|
||||
LOG_ERR("%s: Trying to flash an OEM fw on a non OEM platform\n",
|
||||
__func__);
|
||||
rc = -EINVAL;
|
||||
goto end;
|
||||
}
|
||||
|
||||
LOG_DBG("%s: starting...\n", __func__);
|
||||
check_stcs(__func__, __LINE__, handle);
|
||||
|
||||
rc = qm357xx_rom_b0_wait_ready(handle);
|
||||
if (rc)
|
||||
goto end;
|
||||
|
||||
check_stcs(__func__, __LINE__, handle);
|
||||
LOG_DBG("%s: sending flash_cmd %u command\n", __func__, flash_cmd);
|
||||
rc = qm357xx_rom_write_cmd(handle, flash_cmd);
|
||||
if (rc)
|
||||
goto end;
|
||||
|
||||
check_stcs(__func__, __LINE__, handle);
|
||||
rc = qm357xx_rom_b0_flash_data(handle, all_fws->key1_crt,
|
||||
ROM_CMD_B0_CERT_DATA,
|
||||
WAITING_FOR_FIRST_KEY_CERT);
|
||||
if (rc)
|
||||
goto end;
|
||||
|
||||
check_stcs(__func__, __LINE__, handle);
|
||||
rc = qm357xx_rom_b0_flash_data(handle, all_fws->key2_crt,
|
||||
ROM_CMD_B0_CERT_DATA,
|
||||
WAITING_FOR_SECOND_KEY_CERT);
|
||||
if (rc)
|
||||
goto end;
|
||||
|
||||
check_stcs(__func__, __LINE__, handle);
|
||||
rc = qm357xx_rom_b0_flash_data(handle, all_fws->fw_crt,
|
||||
ROM_CMD_B0_CERT_DATA,
|
||||
WAITING_FOR_CONTENT_CERT);
|
||||
if (rc)
|
||||
goto end;
|
||||
|
||||
check_stcs(__func__, __LINE__, handle);
|
||||
rc = qm357xx_rom_b0_flash_data(handle, all_fws->fw_img,
|
||||
ROM_CMD_B0_SEC_IMAGE_DATA,
|
||||
WAITING_FOR_SEC_FILE_DATA);
|
||||
|
||||
if (!rc)
|
||||
qmrom_msleep(SPI_READY_TIMEOUT_MS);
|
||||
|
||||
end:
|
||||
check_stcs(__func__, __LINE__, handle);
|
||||
qmrom_free(all_fws->fw_img);
|
||||
qmrom_free(all_fws->fw_crt);
|
||||
qmrom_free(all_fws->key1_crt);
|
||||
qmrom_free(all_fws->key2_crt);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int qm357xx_rom_b0_flash_debug_cert(struct qmrom_handle *handle,
|
||||
struct firmware *dbg_cert)
|
||||
{
|
||||
int rc;
|
||||
|
||||
LOG_DBG("%s: starting...\n", __func__);
|
||||
check_stcs(__func__, __LINE__, handle);
|
||||
|
||||
rc = qm357xx_rom_b0_wait_ready(handle);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
check_stcs(__func__, __LINE__, handle);
|
||||
LOG_DBG("%s: sending ROM_CMD_B0_WRITE_DBG_CERT command\n", __func__);
|
||||
rc = qm357xx_rom_write_cmd(handle, ROM_CMD_B0_WRITE_DBG_CERT);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
check_stcs(__func__, __LINE__, handle);
|
||||
LOG_DBG("%s: poll soc...\n", __func__);
|
||||
qm357xx_rom_b0_poll_soc(handle);
|
||||
qmrom_pre_read(handle);
|
||||
qmrom_read(handle);
|
||||
|
||||
check_stcs(__func__, __LINE__, handle);
|
||||
LOG_DBG("%s: sending ROM_CMD_B0_DEBUG_CERT_SIZE command\n", __func__);
|
||||
rc = qm357xx_rom_write_size_cmd(handle, ROM_CMD_B0_DEBUG_CERT_SIZE,
|
||||
sizeof(uint32_t),
|
||||
(const char *)&dbg_cert->size);
|
||||
if (handle->sstc->payload[0] != WAITING_TO_DEBUG_CERTIFICATE_SIZE) {
|
||||
LOG_ERR("%s: wrong debug cert size result (0x%x vs 0x%x)!!!\n",
|
||||
__func__, handle->sstc->payload[0] & 0xff,
|
||||
WAITING_TO_DEBUG_CERTIFICATE_SIZE);
|
||||
return SPI_PROTO_WRONG_RESP;
|
||||
}
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
rc = qm357xx_rom_b0_flash_data(handle, dbg_cert, ROM_CMD_B0_CERT_DATA,
|
||||
WAITING_FOR_DEBUG_CERT_DATA);
|
||||
check_stcs(__func__, __LINE__, handle);
|
||||
qmrom_msleep(SPI_READY_TIMEOUT_MS);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int qm357xx_rom_b0_erase_debug_cert(struct qmrom_handle *handle)
|
||||
{
|
||||
int rc;
|
||||
|
||||
LOG_INFO("%s: starting...\n", __func__);
|
||||
check_stcs(__func__, __LINE__, handle);
|
||||
|
||||
rc = qm357xx_rom_b0_wait_ready(handle);
|
||||
if (!rc)
|
||||
return rc;
|
||||
|
||||
LOG_DBG("%s: sending ROM_CMD_B0_ERASE_DBG_CERT command\n", __func__);
|
||||
rc = qm357xx_rom_write_cmd(handle, ROM_CMD_B0_ERASE_DBG_CERT);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
qmrom_msleep(SPI_READY_TIMEOUT_MS);
|
||||
check_stcs(__func__, __LINE__, handle);
|
||||
return 0;
|
||||
}
|
||||
528
drivers/uwb/qm35/libqmrom/src/qm357xx_rom_c0.c
Normal file
528
drivers/uwb/qm35/libqmrom/src/qm357xx_rom_c0.c
Normal file
|
|
@ -0,0 +1,528 @@
|
|||
// SPDX-License-Identifier: GPL-2.0 OR Apache-2.0
|
||||
/*
|
||||
* Copyright 2022 Qorvo US, Inc.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/uwb/qmrom.h>
|
||||
#include <linux/uwb/qmrom_spi.h>
|
||||
#include <linux/uwb/qmrom_log.h>
|
||||
#include <linux/uwb/qmrom_utils.h>
|
||||
#include <linux/uwb/spi_rom_protocol.h>
|
||||
|
||||
#define DEFAULT_SPI_CLOCKRATE 5000000
|
||||
#define CHIP_VERSION_CHIP_REV_PAYLOAD_OFFSET 4
|
||||
#define CHIP_VERSION_DEV_REV_PAYLOAD_OFFSET 6
|
||||
#define CHUNK_SIZE_C0 2040
|
||||
#define SPI_READY_TIMEOUT_MS_C0 200
|
||||
|
||||
#ifdef C0_WRITE_STATS
|
||||
#include <linux/ktime.h>
|
||||
#endif
|
||||
|
||||
#define SPI_SH_READY_CMD_BIT_MASK_C0 \
|
||||
(SPI_SH_READY_CMD_BIT_MASK >> 4 | SPI_SH_READY_CMD_BIT_MASK)
|
||||
|
||||
enum C0_CMD {
|
||||
ROM_CMD_C0_SEC_LOAD_ICV_IMG_TO_RRAM = 0x0,
|
||||
ROM_CMD_C0_SEC_LOAD_OEM_IMG_TO_RRAM = 0x1,
|
||||
ROM_CMD_C0_GET_CHIP_VER = 0x2,
|
||||
ROM_CMD_C0_GET_SOC_INFO = 0x3,
|
||||
ROM_CMD_C0_ERASE_DBG_CERT = 0x4,
|
||||
ROM_CMD_C0_USE_DIRECT_RRAM_WR = 0X5,
|
||||
ROM_CMD_C0_USE_INDIRECT_RRAM_WR = 0x6,
|
||||
ROM_CMD_C0_WRITE_DBG_CERT = 0x7,
|
||||
ROM_CMD_C0_SEC_IMAGE_DATA = 0x12,
|
||||
ROM_CMD_C0_CERT_DATA = 0x13,
|
||||
ROM_CMD_C0_DEBUG_CERT_SIZE = 0x14,
|
||||
};
|
||||
|
||||
enum C0_RESP {
|
||||
READY_FOR_CS_LOW_CMD = 0x00,
|
||||
WRONG_CS_LOW_CMD = 0x01,
|
||||
WAITING_FOR_NS_RRAM_FILE_SIZE = 0x02,
|
||||
WAITING_FOR_NS_SRAM_FILE_SIZE = 0x03,
|
||||
WAITING_FOR_NS_RRAM_FILE_DATA = 0x04,
|
||||
WAITING_FOR_NS_SRAM_FILE_DATA = 0x05,
|
||||
WAITING_FOR_SEC_FILE_DATA = 0x06,
|
||||
ERR_NS_SRAM_OR_RRAM_SIZE_CMD = 0x07,
|
||||
ERR_SEC_RRAM_SIZE_CMD = 0x08,
|
||||
ERR_WAITING_FOR_NS_IMAGE_DATA_CMD = 0x09,
|
||||
ERR_WAITING_FOR_SEC_IMAGE_DATA_CMD = 0x0A,
|
||||
ERR_IMAGE_SIZE_IS_ZERO = 0x0B,
|
||||
/* Got more data than expected size */
|
||||
ERR_IMAGE_SIZE_TOO_BIG = 0x0C,
|
||||
/* Image must divide in 16 without remainder */
|
||||
ERR_IMAGE_IS_NOT_16BYTES_MUL = 0x0D,
|
||||
ERR_GOT_DATA_MORE_THAN_ALLOWED = 0x0E,
|
||||
/* Remainder is allowed only for last packet */
|
||||
ERR_RRAM_DATA_REMAINDER_NOT_ALLOWED = 0x0F,
|
||||
ERR_WAITING_FOR_CERT_DATA_CMD = 0x10,
|
||||
WAITING_FOR_FIRST_KEY_CERT = 0x11,
|
||||
WAITING_FOR_SECOND_KEY_CERT = 0x12,
|
||||
WAITING_FOR_CONTENT_CERT = 0x13,
|
||||
WAITING_FOR_DEBUG_CERT_DATA = 0x14,
|
||||
ERR_FIRST_KEY_CERT_OR_FW_VER = 0x15,
|
||||
ERR_SECOND_KEY_CERT = 0x16,
|
||||
ERR_CONTENT_CERT_DOWNLOAD_ADDR = 0x17,
|
||||
/* If the content certificate contains to much images */
|
||||
ERR_TOO_MANY_IMAGES_IN_CONTENT_CERT = 0x18,
|
||||
ERR_ADDRESS_NOT_DIVIDED_BY_8 = 0x19,
|
||||
ERR_IMAGE_BOUNDARIES = 0x1A,
|
||||
/* Expected ICV type and got OEM */
|
||||
ERR_CERT_TYPE = 0x1B,
|
||||
ERR_PRODUCT_ID = 0x1C,
|
||||
ERR_RRAM_RANGE_OR_WRITE = 0x1D,
|
||||
WAITING_TO_DEBUG_CERTIFICATE_SIZE = 0x1E,
|
||||
ERR_DEBUG_CERT_SIZE = 0x1F,
|
||||
};
|
||||
|
||||
static int
|
||||
qm357xx_rom_c0_flash_unstitched_fw(struct qmrom_handle *handle,
|
||||
const struct unstitched_firmware *all_fws);
|
||||
static int qm357xx_rom_c0_flash_debug_cert(struct qmrom_handle *handle,
|
||||
struct firmware *dbg_cert);
|
||||
static int qm357xx_rom_c0_erase_debug_cert(struct qmrom_handle *handle);
|
||||
|
||||
#define qmrom_pre_read_c0(h) \
|
||||
({ \
|
||||
int rc; \
|
||||
qmrom_spi_wait_for_ready_line((h)->ss_rdy_handle, \
|
||||
SPI_READY_TIMEOUT_MS_C0); \
|
||||
rc = qmrom_pre_read((h)); \
|
||||
rc; \
|
||||
})
|
||||
#define qmrom_read_c0(h) \
|
||||
({ \
|
||||
int rc; \
|
||||
qmrom_spi_wait_for_ready_line((h)->ss_rdy_handle, \
|
||||
SPI_READY_TIMEOUT_MS_C0); \
|
||||
rc = qmrom_read((h)); \
|
||||
rc; \
|
||||
})
|
||||
#define qm357xx_rom_write_cmd_c0(h, cmd) \
|
||||
({ \
|
||||
int rc; \
|
||||
qmrom_spi_wait_for_ready_line((h)->ss_rdy_handle, \
|
||||
SPI_READY_TIMEOUT_MS_C0); \
|
||||
rc = qm357xx_rom_write_cmd((h), (cmd)); \
|
||||
rc; \
|
||||
})
|
||||
#define qm357xx_rom_write_cmd32_c0(h, cmd) \
|
||||
({ \
|
||||
int rc; \
|
||||
qmrom_spi_wait_for_ready_line((h)->ss_rdy_handle, \
|
||||
SPI_READY_TIMEOUT_MS_C0); \
|
||||
rc = qm357xx_rom_write_cmd32((h), (cmd)); \
|
||||
rc; \
|
||||
})
|
||||
#define qm357xx_rom_write_size_cmd_c0(h, cmd, ds, d) \
|
||||
({ \
|
||||
int rc; \
|
||||
qmrom_spi_wait_for_ready_line((h)->ss_rdy_handle, \
|
||||
SPI_READY_TIMEOUT_MS_C0); \
|
||||
rc = qm357xx_rom_write_size_cmd((h), (cmd), (ds), (d)); \
|
||||
rc; \
|
||||
})
|
||||
#define qm357xx_rom_write_size_cmd32_c0(h, cmd, ds, d) \
|
||||
({ \
|
||||
int rc; \
|
||||
qmrom_spi_wait_for_ready_line((h)->ss_rdy_handle, \
|
||||
SPI_READY_TIMEOUT_MS_C0); \
|
||||
rc = qm357xx_rom_write_size_cmd32((h), (cmd), (ds), (d)); \
|
||||
rc; \
|
||||
})
|
||||
|
||||
static void qm357xx_rom_c0_poll_soc(struct qmrom_handle *handle)
|
||||
{
|
||||
int retries = handle->comms_retries;
|
||||
memset(handle->hstc, 0, sizeof(struct stc));
|
||||
handle->sstc->raw_flags = 0;
|
||||
do {
|
||||
int rc = qmrom_spi_wait_for_ready_line(handle->ss_rdy_handle,
|
||||
SPI_READY_TIMEOUT_MS_C0);
|
||||
if (rc) {
|
||||
LOG_ERR("%s qmrom_spi_wait_for_ready_line failed\n",
|
||||
__func__);
|
||||
continue;
|
||||
}
|
||||
qmrom_spi_transfer(handle->spi_handle, (char *)handle->sstc,
|
||||
(const char *)handle->hstc,
|
||||
sizeof(struct stc) + handle->hstc->len);
|
||||
} while (retries-- && handle->sstc->raw_flags == 0);
|
||||
}
|
||||
|
||||
static int qm357xx_rom_c0_wait_ready(struct qmrom_handle *handle)
|
||||
{
|
||||
int retries = handle->comms_retries;
|
||||
|
||||
qm357xx_rom_c0_poll_soc(handle);
|
||||
|
||||
/* handle->sstc has been updated */
|
||||
while (retries-- &&
|
||||
handle->sstc->raw_flags != SPI_SH_READY_CMD_BIT_MASK_C0) {
|
||||
if (handle->sstc->soc_flags.out_waiting) {
|
||||
qmrom_pre_read_c0(handle);
|
||||
qmrom_read_c0(handle);
|
||||
} else if (handle->sstc->soc_flags.out_active) {
|
||||
return qmrom_read_c0(handle);
|
||||
} else
|
||||
qm357xx_rom_c0_poll_soc(handle);
|
||||
}
|
||||
|
||||
return handle->sstc->raw_flags == SPI_SH_READY_CMD_BIT_MASK_C0 ?
|
||||
0 :
|
||||
SPI_ERR_WAIT_READY_TIMEOUT;
|
||||
}
|
||||
|
||||
static int qm357xx_rom_c0_poll_cmd_resp(struct qmrom_handle *handle)
|
||||
{
|
||||
int retries = handle->comms_retries;
|
||||
|
||||
qm357xx_rom_c0_poll_soc(handle);
|
||||
do {
|
||||
if (handle->sstc->soc_flags.out_waiting) {
|
||||
qmrom_pre_read_c0(handle);
|
||||
return qmrom_read_c0(handle);
|
||||
} else
|
||||
qm357xx_rom_c0_poll_soc(handle);
|
||||
} while (retries--);
|
||||
if (retries <= 0)
|
||||
LOG_ERR("%s failed after %d replies\n", __func__,
|
||||
handle->comms_retries);
|
||||
|
||||
return retries > 0 ? 0 : -1;
|
||||
}
|
||||
|
||||
int qm357xx_rom_c0_probe_device(struct qmrom_handle *handle)
|
||||
{
|
||||
int rc, i;
|
||||
uint8_t *soc_lcs_uuid;
|
||||
|
||||
handle->is_be = false;
|
||||
|
||||
if (handle->spi_speed == 0)
|
||||
qmrom_spi_set_freq(DEFAULT_SPI_CLOCKRATE);
|
||||
else
|
||||
qmrom_spi_set_freq(handle->spi_speed);
|
||||
|
||||
rc = qmrom_reboot_bootloader(handle);
|
||||
if (rc) {
|
||||
LOG_ERR("%s: cannot reset the device...\n", __func__);
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = qm357xx_rom_c0_wait_ready(handle);
|
||||
if (rc) {
|
||||
LOG_INFO("%s: maybe not a C0 device\n", __func__);
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = qm357xx_rom_write_cmd32_c0(handle, ROM_CMD_C0_GET_CHIP_VER);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
rc = qm357xx_rom_c0_poll_cmd_resp(handle);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
handle->chip_rev =
|
||||
SSTC2UINT16(handle, CHIP_VERSION_CHIP_REV_PAYLOAD_OFFSET) &
|
||||
0xFF;
|
||||
handle->device_version = bswap_16(
|
||||
SSTC2UINT16(handle, CHIP_VERSION_DEV_REV_PAYLOAD_OFFSET));
|
||||
if ((handle->chip_rev != CHIP_REVISION_C0) &&
|
||||
((handle->chip_rev != CHIP_REVISION_C2))) {
|
||||
LOG_ERR("%s: wrong chip revision %#x\n", __func__,
|
||||
handle->chip_rev);
|
||||
handle->chip_rev = -1;
|
||||
return -1;
|
||||
}
|
||||
|
||||
rc = qm357xx_rom_c0_wait_ready(handle);
|
||||
if (rc) {
|
||||
LOG_ERR("%s: hmm something went wrong!!!\n", __func__);
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = qm357xx_rom_write_cmd32_c0(handle, ROM_CMD_C0_GET_SOC_INFO);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
rc = qm357xx_rom_c0_poll_cmd_resp(handle);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
/* skip the first 4 bytes */
|
||||
soc_lcs_uuid = &(handle->sstc->payload[4]);
|
||||
for (i = 0; i < QM357XX_ROM_SOC_ID_LEN; i++)
|
||||
handle->qm357xx_soc_info.soc_id[i] =
|
||||
soc_lcs_uuid[QM357XX_ROM_SOC_ID_LEN - i - 1];
|
||||
soc_lcs_uuid += QM357XX_ROM_SOC_ID_LEN;
|
||||
memcpy(&handle->qm357xx_soc_info.lcs_state, soc_lcs_uuid,
|
||||
sizeof(uint32_t));
|
||||
soc_lcs_uuid += 4;
|
||||
for (i = 0; i < QM357XX_ROM_UUID_LEN; i++)
|
||||
handle->qm357xx_soc_info.uuid[i] =
|
||||
soc_lcs_uuid[QM357XX_ROM_UUID_LEN - i - 1];
|
||||
|
||||
/* Set device type */
|
||||
handle->dev_gen = DEVICE_GEN_QM357XX;
|
||||
/* Set rom ops */
|
||||
handle->qm357xx_rom_ops.flash_unstitched_fw =
|
||||
qm357xx_rom_c0_flash_unstitched_fw;
|
||||
handle->qm357xx_rom_ops.flash_debug_cert =
|
||||
qm357xx_rom_c0_flash_debug_cert;
|
||||
handle->qm357xx_rom_ops.erase_debug_cert =
|
||||
qm357xx_rom_c0_erase_debug_cert;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef C0_WRITE_STATS
|
||||
static uint64_t total_bytes, total_time_ns;
|
||||
static uint32_t max_write_time_ns, min_write_time_ns = ~0;
|
||||
|
||||
static void update_write_max_chunk_stats(ktime_t start_time)
|
||||
{
|
||||
uint64_t elapsed_time_ns;
|
||||
|
||||
total_bytes += CHUNK_SIZE_C0;
|
||||
elapsed_time_ns = ktime_to_ns(ktime_sub(ktime_get(), start_time));
|
||||
total_time_ns += elapsed_time_ns;
|
||||
if (elapsed_time_ns > max_write_time_ns)
|
||||
max_write_time_ns = elapsed_time_ns;
|
||||
if (elapsed_time_ns < min_write_time_ns)
|
||||
min_write_time_ns = elapsed_time_ns;
|
||||
}
|
||||
|
||||
static void dump_stats(void)
|
||||
{
|
||||
uint32_t nb_chunks = div_u64(total_bytes, CHUNK_SIZE_C0);
|
||||
LOG_WARN(
|
||||
"C0 flashing time stats: %llu bytes over %llu us (chunk size %u, write timings: mean %u us, min %u us, max %u us)\n",
|
||||
total_bytes, div_u64(total_time_ns, 1000), CHUNK_SIZE_C0,
|
||||
(uint32_t)(div_u64((div_u64(total_time_ns, nb_chunks)), 1000)),
|
||||
min_write_time_ns / 1000, max_write_time_ns / 1000);
|
||||
}
|
||||
#endif
|
||||
|
||||
static int qm357xx_rom_c0_flash_data(struct qmrom_handle *handle,
|
||||
struct firmware *fw, uint8_t cmd,
|
||||
uint8_t resp, bool skip_last_check)
|
||||
{
|
||||
int rc, sent = 0;
|
||||
const char *bin_data = (const char *)fw->data;
|
||||
#ifdef C0_WRITE_STATS
|
||||
ktime_t start_time;
|
||||
#endif
|
||||
|
||||
while (sent < fw->size) {
|
||||
uint32_t tx_bytes = fw->size - sent;
|
||||
if (tx_bytes > CHUNK_SIZE_C0)
|
||||
tx_bytes = CHUNK_SIZE_C0;
|
||||
|
||||
LOG_DBG("%s: sending command %#x with %" PRIu32 " bytes\n",
|
||||
__func__, cmd, tx_bytes);
|
||||
#ifdef C0_WRITE_STATS
|
||||
start_time = ktime_get();
|
||||
#endif
|
||||
rc = qm357xx_rom_write_size_cmd32_c0(handle, cmd, tx_bytes,
|
||||
bin_data);
|
||||
if (rc)
|
||||
return rc;
|
||||
sent += tx_bytes;
|
||||
bin_data += tx_bytes;
|
||||
if (skip_last_check && sent == fw->size) {
|
||||
LOG_INFO("%s: flashing done, quitting now\n", __func__);
|
||||
break;
|
||||
}
|
||||
qm357xx_rom_c0_poll_soc(handle);
|
||||
#ifdef C0_WRITE_STATS
|
||||
if (tx_bytes == CHUNK_SIZE_C0)
|
||||
update_write_max_chunk_stats(start_time);
|
||||
#endif
|
||||
qmrom_pre_read_c0(handle);
|
||||
qmrom_read_c0(handle);
|
||||
if (handle->sstc->payload[0] != resp) {
|
||||
LOG_ERR("%s: wrong data result (%#x vs %#x)!!!\n",
|
||||
__func__, handle->sstc->payload[0] & 0xff,
|
||||
resp);
|
||||
if (handle->sstc->payload[0] ==
|
||||
ERR_FIRST_KEY_CERT_OR_FW_VER)
|
||||
return PEG_ERR_FIRST_KEY_CERT_OR_FW_VER;
|
||||
else
|
||||
return SPI_PROTO_WRONG_RESP;
|
||||
}
|
||||
}
|
||||
qmrom_msleep(SPI_READY_TIMEOUT_MS_C0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
qm357xx_rom_c0_flash_unstitched_fw(struct qmrom_handle *handle,
|
||||
const struct unstitched_firmware *all_fws)
|
||||
{
|
||||
int rc = 0;
|
||||
uint8_t flash_cmd = handle->qm357xx_soc_info.lcs_state ==
|
||||
CC_BSV_SECURE_LCS ?
|
||||
ROM_CMD_C0_SEC_LOAD_OEM_IMG_TO_RRAM :
|
||||
ROM_CMD_C0_SEC_LOAD_ICV_IMG_TO_RRAM;
|
||||
|
||||
if (all_fws->key1_crt->data[HBK_LOC] == HBK_2E_ICV &&
|
||||
handle->qm357xx_soc_info.lcs_state != CC_BSV_CHIP_MANUFACTURE_LCS) {
|
||||
LOG_ERR("%s: Trying to flash an ICV fw on a non ICV platform\n",
|
||||
__func__);
|
||||
rc = -EINVAL;
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (all_fws->key1_crt->data[HBK_LOC] == HBK_2E_OEM &&
|
||||
handle->qm357xx_soc_info.lcs_state != CC_BSV_SECURE_LCS) {
|
||||
LOG_ERR("%s: Trying to flash an OEM fw on a non OEM platform\n",
|
||||
__func__);
|
||||
rc = -EINVAL;
|
||||
goto end;
|
||||
}
|
||||
|
||||
LOG_DBG("%s: starting...\n", __func__);
|
||||
|
||||
/* Set RRAM write mode */
|
||||
rc = qm357xx_rom_c0_wait_ready(handle);
|
||||
if (rc)
|
||||
goto end;
|
||||
|
||||
if (handle->chip_rev != CHIP_REVISION_C2) {
|
||||
LOG_DBG("%s: sending ROM_CMD_C0_USE_INDIRECT_RRAM_WR command (chip_rev %x)\n",
|
||||
__func__, handle->chip_rev);
|
||||
rc = qm357xx_rom_write_cmd32_c0(
|
||||
handle, ROM_CMD_C0_USE_INDIRECT_RRAM_WR);
|
||||
if (rc)
|
||||
goto end;
|
||||
}
|
||||
|
||||
qm357xx_rom_c0_poll_soc(handle);
|
||||
qmrom_pre_read_c0(handle);
|
||||
qmrom_read_c0(handle);
|
||||
qm357xx_rom_c0_poll_soc(handle);
|
||||
|
||||
LOG_ERR("%s: sending flash_cmd %u command\n", __func__, flash_cmd);
|
||||
rc = qm357xx_rom_write_cmd32_c0(handle, flash_cmd);
|
||||
if (rc)
|
||||
goto end;
|
||||
|
||||
qm357xx_rom_c0_poll_cmd_resp(handle);
|
||||
if (handle->sstc->payload[0] != WAITING_FOR_FIRST_KEY_CERT) {
|
||||
LOG_ERR("%s: Waiting for WAITING_FOR_FIRST_KEY_CERT(%#x) but got %#x\n",
|
||||
__func__, WAITING_FOR_FIRST_KEY_CERT,
|
||||
handle->sstc->payload[0]);
|
||||
goto end;
|
||||
}
|
||||
|
||||
qm357xx_rom_c0_poll_soc(handle);
|
||||
|
||||
rc = qm357xx_rom_c0_flash_data(handle, all_fws->key1_crt,
|
||||
ROM_CMD_C0_CERT_DATA,
|
||||
WAITING_FOR_SECOND_KEY_CERT, false);
|
||||
if (rc)
|
||||
goto end;
|
||||
|
||||
rc = qm357xx_rom_c0_flash_data(handle, all_fws->key2_crt,
|
||||
ROM_CMD_C0_CERT_DATA,
|
||||
WAITING_FOR_CONTENT_CERT, false);
|
||||
if (rc)
|
||||
goto end;
|
||||
|
||||
rc = qm357xx_rom_c0_flash_data(handle, all_fws->fw_crt,
|
||||
ROM_CMD_C0_CERT_DATA,
|
||||
WAITING_FOR_SEC_FILE_DATA, false);
|
||||
if (rc)
|
||||
goto end;
|
||||
|
||||
rc = qm357xx_rom_c0_flash_data(handle, all_fws->fw_img,
|
||||
ROM_CMD_C0_SEC_IMAGE_DATA,
|
||||
WAITING_FOR_SEC_FILE_DATA, true);
|
||||
|
||||
#ifdef C0_WRITE_STATS
|
||||
dump_stats();
|
||||
#endif
|
||||
|
||||
end:
|
||||
qmrom_free(all_fws->fw_img);
|
||||
qmrom_free(all_fws->fw_crt);
|
||||
qmrom_free(all_fws->key1_crt);
|
||||
qmrom_free(all_fws->key2_crt);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int qm357xx_rom_c0_flash_debug_cert(struct qmrom_handle *handle,
|
||||
struct firmware *dbg_cert)
|
||||
{
|
||||
int rc;
|
||||
|
||||
LOG_DBG("%s: starting...\n", __func__);
|
||||
rc = qm357xx_rom_c0_wait_ready(handle);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
if (handle->chip_rev != CHIP_REVISION_C2) {
|
||||
LOG_DBG("%s: using ROM_CMD_C0_USE_INDIRECT_RRAM_WR command\n",
|
||||
__func__);
|
||||
rc = qm357xx_rom_write_cmd32_c0(
|
||||
handle, ROM_CMD_C0_USE_INDIRECT_RRAM_WR);
|
||||
if (rc)
|
||||
return rc;
|
||||
}
|
||||
|
||||
qm357xx_rom_c0_poll_soc(handle);
|
||||
qmrom_pre_read_c0(handle);
|
||||
qmrom_read_c0(handle);
|
||||
qm357xx_rom_c0_poll_soc(handle);
|
||||
|
||||
LOG_DBG("%s: sending ROM_CMD_C0_WRITE_DBG_CERT command\n", __func__);
|
||||
rc = qm357xx_rom_write_cmd32_c0(handle, ROM_CMD_C0_WRITE_DBG_CERT);
|
||||
if (rc)
|
||||
return rc;
|
||||
qm357xx_rom_c0_poll_cmd_resp(handle);
|
||||
if (handle->sstc->payload[0] != WAITING_TO_DEBUG_CERTIFICATE_SIZE) {
|
||||
LOG_ERR("%s: Waiting for WAITING_TO_DEBUG_CERTIFICATE_SIZE(%#x) but got %#x\n",
|
||||
__func__, WAITING_TO_DEBUG_CERTIFICATE_SIZE,
|
||||
handle->sstc->payload[0]);
|
||||
return rc;
|
||||
}
|
||||
|
||||
LOG_DBG("%s: sending ROM_CMD_C0_DEBUG_CERT_SIZE command\n", __func__);
|
||||
rc = qm357xx_rom_write_size_cmd32_c0(handle, ROM_CMD_C0_DEBUG_CERT_SIZE,
|
||||
sizeof(uint32_t),
|
||||
(const char *)&dbg_cert->size);
|
||||
qm357xx_rom_c0_poll_cmd_resp(handle);
|
||||
if (handle->sstc->payload[0] != WAITING_FOR_DEBUG_CERT_DATA) {
|
||||
LOG_ERR("%s: Waiting for WAITING_FOR_DEBUG_CERT_DATA(%#x) but got %#x\n",
|
||||
__func__, WAITING_FOR_DEBUG_CERT_DATA,
|
||||
handle->sstc->payload[0]);
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = qm357xx_rom_c0_flash_data(handle, dbg_cert, ROM_CMD_C0_CERT_DATA,
|
||||
WAITING_FOR_DEBUG_CERT_DATA, true);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int qm357xx_rom_c0_erase_debug_cert(struct qmrom_handle *handle)
|
||||
{
|
||||
int rc;
|
||||
|
||||
LOG_DBG("%s: starting...\n", __func__);
|
||||
|
||||
rc = qm357xx_rom_c0_wait_ready(handle);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
LOG_DBG("%s: sending ROM_CMD_C0_ERASE_DBG_CERT command\n", __func__);
|
||||
rc = qm357xx_rom_write_cmd32_c0(handle, ROM_CMD_C0_ERASE_DBG_CERT);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
qmrom_msleep(SPI_READY_TIMEOUT_MS_C0);
|
||||
return 0;
|
||||
}
|
||||
401
drivers/uwb/qm35/libqmrom/src/qm357xx_rom_common.c
Normal file
401
drivers/uwb/qm35/libqmrom/src/qm357xx_rom_common.c
Normal file
|
|
@ -0,0 +1,401 @@
|
|||
// SPDX-License-Identifier: GPL-2.0 OR Apache-2.0
|
||||
/*
|
||||
* Copyright 2022 Qorvo US, Inc.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/uwb/qmrom_utils.h>
|
||||
#include <linux/uwb/qmrom_log.h>
|
||||
#include <linux/uwb/qmrom_spi.h>
|
||||
#include <linux/uwb/qmrom.h>
|
||||
#include <linux/uwb/spi_rom_protocol.h>
|
||||
|
||||
#include <linux/uwb/qm357xx_fwpkg.h>
|
||||
|
||||
int qm357xx_rom_b0_probe_device(struct qmrom_handle *handle);
|
||||
int qm357xx_rom_c0_probe_device(struct qmrom_handle *handle);
|
||||
|
||||
int qm357xx_rom_write_cmd(struct qmrom_handle *handle, uint8_t cmd)
|
||||
{
|
||||
handle->hstc->all = 0;
|
||||
handle->hstc->host_flags.write = 1;
|
||||
handle->hstc->ul = 1;
|
||||
handle->hstc->len = 1;
|
||||
handle->hstc->payload[0] = cmd;
|
||||
|
||||
return qmrom_spi_transfer(handle->spi_handle, (char *)handle->sstc,
|
||||
(const char *)handle->hstc,
|
||||
sizeof(struct stc) + handle->hstc->len);
|
||||
}
|
||||
|
||||
int qm357xx_rom_write_cmd32(struct qmrom_handle *handle, uint32_t cmd)
|
||||
{
|
||||
handle->hstc->all = 0;
|
||||
handle->hstc->host_flags.write = 1;
|
||||
handle->hstc->ul = 1;
|
||||
handle->hstc->len = sizeof(cmd);
|
||||
memcpy(handle->hstc->payload, &cmd, sizeof(cmd));
|
||||
|
||||
return qmrom_spi_transfer(handle->spi_handle, (char *)handle->sstc,
|
||||
(const char *)handle->hstc,
|
||||
sizeof(struct stc) + handle->hstc->len);
|
||||
}
|
||||
|
||||
int qm357xx_rom_write_size_cmd(struct qmrom_handle *handle, uint8_t cmd,
|
||||
uint16_t data_size, const char *data)
|
||||
{
|
||||
handle->hstc->all = 0;
|
||||
handle->hstc->host_flags.write = 1;
|
||||
handle->hstc->ul = 1;
|
||||
handle->hstc->len = data_size + 1;
|
||||
handle->hstc->payload[0] = cmd;
|
||||
memcpy(&handle->hstc->payload[1], data, data_size);
|
||||
|
||||
return qmrom_spi_transfer(handle->spi_handle, (char *)handle->sstc,
|
||||
(const char *)handle->hstc,
|
||||
sizeof(struct stc) + handle->hstc->len);
|
||||
}
|
||||
|
||||
int qm357xx_rom_write_size_cmd32(struct qmrom_handle *handle, uint32_t cmd,
|
||||
uint16_t data_size, const char *data)
|
||||
{
|
||||
handle->hstc->all = 0;
|
||||
handle->hstc->host_flags.write = 1;
|
||||
handle->hstc->ul = 1;
|
||||
handle->hstc->len = data_size + sizeof(cmd);
|
||||
memcpy(handle->hstc->payload, &cmd, sizeof(cmd));
|
||||
memcpy(&handle->hstc->payload[sizeof(cmd)], data, data_size);
|
||||
|
||||
return qmrom_spi_transfer(handle->spi_handle, (char *)handle->sstc,
|
||||
(const char *)handle->hstc,
|
||||
sizeof(struct stc) + handle->hstc->len);
|
||||
}
|
||||
|
||||
/*
|
||||
* Unfortunately, B0 and C0 have different
|
||||
* APIs to get the chip version...
|
||||
*
|
||||
*/
|
||||
int qm357xx_rom_probe_device(struct qmrom_handle *handle)
|
||||
{
|
||||
int rc;
|
||||
|
||||
/* Test B0 first */
|
||||
//rc = qm357xx_rom_b0_probe_device(handle);
|
||||
//if (!rc)
|
||||
//return rc;
|
||||
|
||||
/* Test C0 next */
|
||||
rc = qm357xx_rom_c0_probe_device(handle);
|
||||
if (!rc)
|
||||
return rc;
|
||||
|
||||
/* None matched!!! */
|
||||
return -1;
|
||||
}
|
||||
|
||||
int qm357xx_rom_flash_dbg_cert(struct qmrom_handle *handle,
|
||||
struct firmware *dbg_cert)
|
||||
{
|
||||
if (!handle->qm357xx_rom_ops.flash_debug_cert) {
|
||||
LOG_ERR("%s: flash debug certificate not support on this device\n",
|
||||
__func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
return handle->qm357xx_rom_ops.flash_debug_cert(handle, dbg_cert);
|
||||
}
|
||||
|
||||
int qm357xx_rom_erase_dbg_cert(struct qmrom_handle *handle)
|
||||
{
|
||||
if (!handle->qm357xx_rom_ops.erase_debug_cert) {
|
||||
LOG_ERR("%s: erase debug certificate not support on this device\n",
|
||||
__func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
return handle->qm357xx_rom_ops.erase_debug_cert(handle);
|
||||
}
|
||||
|
||||
int qm357xx_rom_flash_fw(struct qmrom_handle *handle, const struct firmware *fw)
|
||||
{
|
||||
int rc = 0;
|
||||
struct unstitched_firmware all_fws = { 0 };
|
||||
|
||||
if (*(uint32_t *)&fw->data[0] ==
|
||||
CRYPTO_MACRO_FIRMWARE_PACK_MAGIC_VALUE) {
|
||||
//macro package detected
|
||||
//write the FW UPDATER
|
||||
rc = qm357xx_rom_unpack_fw_macro_pkg(fw, &all_fws);
|
||||
if (rc) {
|
||||
LOG_ERR("%s: Unpack macro FW package unsuccessful!\n",
|
||||
__func__);
|
||||
return rc;
|
||||
}
|
||||
} else {
|
||||
rc = qm357xx_rom_unstitch_fw(fw, &all_fws, handle->chip_rev);
|
||||
if (rc) {
|
||||
LOG_ERR("%s: Unstitched fw flashing not supported yet\n",
|
||||
__func__);
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
rc = qm357xx_rom_flash_unstitched_fw(handle, &all_fws);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int qm357xx_rom_flash_unstitched_fw(struct qmrom_handle *handle,
|
||||
const struct unstitched_firmware *fw)
|
||||
{
|
||||
if (!handle->qm357xx_rom_ops.flash_unstitched_fw) {
|
||||
LOG_ERR("%s: flash un-stitched firmware not supported on this device\n",
|
||||
__func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
return handle->qm357xx_rom_ops.flash_unstitched_fw(handle, fw);
|
||||
}
|
||||
|
||||
int qm357xx_rom_unstitch_fw(const struct firmware *fw,
|
||||
struct unstitched_firmware *unstitched_fw,
|
||||
enum chip_revision_e revision)
|
||||
{
|
||||
uint32_t tot_len = 0;
|
||||
uint32_t fw_img_sz = 0;
|
||||
uint32_t fw_crt_sz = 0;
|
||||
uint32_t key1_crt_sz = 0;
|
||||
uint32_t key2_crt_sz = 0;
|
||||
uint8_t *p_key1;
|
||||
uint8_t *p_key2;
|
||||
uint8_t *p_crt;
|
||||
uint8_t *p_fw;
|
||||
int ret = 0;
|
||||
|
||||
if (fw->size < 2 * sizeof(key1_crt_sz)) {
|
||||
LOG_ERR("%s: Not enough data (%zu) to unstitch\n", __func__,
|
||||
fw->size);
|
||||
return -EINVAL;
|
||||
}
|
||||
LOG_INFO("%s: Unstitching %zu bytes\n", __func__, fw->size);
|
||||
|
||||
/* key1 */
|
||||
key1_crt_sz = *(uint32_t *)&fw->data[tot_len];
|
||||
if (tot_len + key1_crt_sz + sizeof(key1_crt_sz) > fw->size) {
|
||||
LOG_ERR("%s: Invalid or corrupted stitched file at offset \
|
||||
%" PRIu32 " (key1)\n",
|
||||
__func__, tot_len);
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
tot_len += sizeof(key1_crt_sz);
|
||||
p_key1 = (uint8_t *)&fw->data[tot_len];
|
||||
tot_len += key1_crt_sz;
|
||||
|
||||
/* key2 */
|
||||
key2_crt_sz = *(uint32_t *)&fw->data[tot_len];
|
||||
if (tot_len + key2_crt_sz + sizeof(key2_crt_sz) > fw->size) {
|
||||
LOG_ERR("%s: Invalid or corrupted stitched file at offset \
|
||||
%" PRIu32 " (key2)\n",
|
||||
__func__, tot_len);
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
tot_len += sizeof(key2_crt_sz);
|
||||
p_key2 = (uint8_t *)&fw->data[tot_len];
|
||||
tot_len += key2_crt_sz;
|
||||
|
||||
/* cert */
|
||||
fw_crt_sz = *(uint32_t *)&fw->data[tot_len];
|
||||
if (tot_len + fw_crt_sz + sizeof(fw_crt_sz) > fw->size) {
|
||||
LOG_ERR("%s: Invalid or corrupted stitched file at offset \
|
||||
%" PRIu32 " (content cert)\n",
|
||||
__func__, tot_len);
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
tot_len += sizeof(fw_crt_sz);
|
||||
p_crt = (uint8_t *)&fw->data[tot_len];
|
||||
tot_len += fw_crt_sz;
|
||||
|
||||
/* fw */
|
||||
fw_img_sz = *(uint32_t *)&fw->data[tot_len];
|
||||
if (tot_len + fw_img_sz + sizeof(fw_img_sz) != fw->size) {
|
||||
LOG_ERR("%s: Invalid or corrupted stitched file at offset \
|
||||
%" PRIu32 " (firmnware)\n",
|
||||
__func__, tot_len);
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
tot_len += sizeof(fw_img_sz);
|
||||
p_fw = (uint8_t *)&fw->data[tot_len];
|
||||
|
||||
qmrom_alloc(unstitched_fw->fw_img, fw_img_sz + sizeof(struct firmware));
|
||||
if (unstitched_fw->fw_img == NULL) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
qmrom_alloc(unstitched_fw->fw_crt, fw_crt_sz + sizeof(struct firmware));
|
||||
if (unstitched_fw->fw_crt == NULL) {
|
||||
ret = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
|
||||
qmrom_alloc(unstitched_fw->key1_crt,
|
||||
key1_crt_sz + sizeof(struct firmware));
|
||||
if (unstitched_fw->key1_crt == NULL) {
|
||||
ret = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
|
||||
qmrom_alloc(unstitched_fw->key2_crt,
|
||||
key2_crt_sz + sizeof(struct firmware));
|
||||
if (unstitched_fw->key2_crt == NULL) {
|
||||
ret = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
|
||||
unstitched_fw->key1_crt->data =
|
||||
(const uint8_t *)(unstitched_fw->key1_crt + 1);
|
||||
unstitched_fw->key2_crt->data =
|
||||
(const uint8_t *)(unstitched_fw->key2_crt + 1);
|
||||
unstitched_fw->fw_crt->data =
|
||||
(const uint8_t *)(unstitched_fw->fw_crt + 1);
|
||||
unstitched_fw->fw_img->data =
|
||||
(const uint8_t *)(unstitched_fw->fw_img + 1);
|
||||
unstitched_fw->key1_crt->size = key1_crt_sz;
|
||||
unstitched_fw->key2_crt->size = key2_crt_sz;
|
||||
unstitched_fw->fw_crt->size = fw_crt_sz;
|
||||
unstitched_fw->fw_img->size = fw_img_sz;
|
||||
|
||||
memcpy((void *)unstitched_fw->key1_crt->data, p_key1, key1_crt_sz);
|
||||
memcpy((void *)unstitched_fw->key2_crt->data, p_key2, key2_crt_sz);
|
||||
memcpy((void *)unstitched_fw->fw_crt->data, p_crt, fw_crt_sz);
|
||||
memcpy((void *)unstitched_fw->fw_img->data, p_fw, fw_img_sz);
|
||||
return 0;
|
||||
|
||||
err:
|
||||
if (unstitched_fw->fw_img)
|
||||
qmrom_free(unstitched_fw->fw_img);
|
||||
if (unstitched_fw->fw_crt)
|
||||
qmrom_free(unstitched_fw->fw_crt);
|
||||
if (unstitched_fw->key1_crt)
|
||||
qmrom_free(unstitched_fw->key1_crt);
|
||||
if (unstitched_fw->key2_crt)
|
||||
qmrom_free(unstitched_fw->key2_crt);
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
int qm357xx_rom_fw_macro_pkg_get_fw_idx(const struct firmware *fw, int idx,
|
||||
uint32_t *fw_size, char **fw_data)
|
||||
{
|
||||
struct fw_macro_pkg_hdr_t *fw_macro_pkg_hdr =
|
||||
(struct fw_macro_pkg_hdr_t *)&fw->data[0];
|
||||
struct fw_img_desc_t *fw_pkg;
|
||||
|
||||
if (fw_macro_pkg_hdr->nb_descriptors >= 1 &&
|
||||
idx < fw_macro_pkg_hdr->nb_descriptors) {
|
||||
fw_pkg = (struct fw_img_desc_t *)(&fw_macro_pkg_hdr
|
||||
->img_desc[idx]);
|
||||
if (fw_pkg->offset + fw_pkg->length > fw->size) {
|
||||
LOG_ERR("Wrong FW PKG offset = %04x; len = %04x; idx = %d!\n",
|
||||
fw_pkg->offset, fw_pkg->length, idx);
|
||||
return -EINVAL;
|
||||
}
|
||||
} else {
|
||||
LOG_ERR("%s: No FW pkg found in macro package! nb_descriptors = %d\n",
|
||||
__func__, fw_macro_pkg_hdr->nb_descriptors);
|
||||
return -EINVAL;
|
||||
}
|
||||
*fw_size = fw_pkg->length;
|
||||
*fw_data = (char *)&fw->data[fw_pkg->offset];
|
||||
return 0;
|
||||
}
|
||||
|
||||
int qm357xx_rom_unpack_fw_macro_pkg(const struct firmware *fw,
|
||||
struct unstitched_firmware *all_fws)
|
||||
{
|
||||
int rc = 0;
|
||||
uint8_t *p_key1;
|
||||
uint8_t *p_key2;
|
||||
uint8_t *p_crt;
|
||||
uint8_t *p_fw;
|
||||
char *fw_data;
|
||||
uint32_t fw_size;
|
||||
struct fw_pkg_img_hdr_t *fw_pkg_img_hdr;
|
||||
|
||||
rc = qm357xx_rom_fw_macro_pkg_get_fw_idx(fw, 0, &fw_size, &fw_data);
|
||||
if (rc) {
|
||||
LOG_ERR("%s: FW MACRO PACKAGE corupted = %d\n", __func__, rc);
|
||||
goto err;
|
||||
}
|
||||
|
||||
fw_pkg_img_hdr =
|
||||
(struct fw_pkg_img_hdr_t *)(fw_data +
|
||||
sizeof(struct fw_pkg_hdr_t));
|
||||
|
||||
if (fw_pkg_img_hdr->magic != CRYPTO_FIRMWARE_IMAGE_MAGIC_VALUE) {
|
||||
LOG_ERR("%s: Invalid or corrupted file! magic = %04x\n",
|
||||
__func__, fw_pkg_img_hdr->magic);
|
||||
return -EINVAL;
|
||||
}
|
||||
qmrom_alloc(all_fws->fw_img,
|
||||
fw_pkg_img_hdr->descs[0].length + sizeof(struct firmware));
|
||||
if (all_fws->fw_img == NULL) {
|
||||
rc = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
qmrom_alloc(all_fws->key1_crt,
|
||||
CRYPTO_IMAGES_CERT_KEY_SIZE + sizeof(struct firmware));
|
||||
if (all_fws->key1_crt == NULL) {
|
||||
rc = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
|
||||
qmrom_alloc(all_fws->key2_crt,
|
||||
CRYPTO_IMAGES_CERT_KEY_SIZE + sizeof(struct firmware));
|
||||
if (all_fws->key2_crt == NULL) {
|
||||
rc = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
|
||||
qmrom_alloc(all_fws->fw_crt,
|
||||
CRYPTO_IMAGES_CERT_CONTENT_SIZE + sizeof(struct firmware));
|
||||
if (all_fws->fw_crt == NULL) {
|
||||
rc = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
|
||||
all_fws->key1_crt->data = (const uint8_t *)(all_fws->key1_crt + 1);
|
||||
all_fws->key2_crt->data = (const uint8_t *)(all_fws->key2_crt + 1);
|
||||
all_fws->fw_crt->data = (const uint8_t *)(all_fws->fw_crt + 1);
|
||||
all_fws->fw_img->data = (const uint8_t *)(all_fws->fw_img + 1);
|
||||
all_fws->key1_crt->size = CRYPTO_IMAGES_CERT_KEY_SIZE;
|
||||
all_fws->key2_crt->size = CRYPTO_IMAGES_CERT_KEY_SIZE;
|
||||
all_fws->fw_crt->size = CRYPTO_IMAGES_CERT_CONTENT_SIZE;
|
||||
all_fws->fw_img->size = fw_pkg_img_hdr->descs[0].length;
|
||||
|
||||
p_key1 = (uint8_t *)fw_pkg_img_hdr + fw_pkg_img_hdr->cert_chain_offset;
|
||||
p_key2 = p_key1 + CRYPTO_IMAGES_CERT_KEY_SIZE;
|
||||
p_crt = p_key2 + CRYPTO_IMAGES_CERT_KEY_SIZE;
|
||||
p_fw = (uint8_t *)fw_pkg_img_hdr + fw_pkg_img_hdr->descs[0].offset;
|
||||
|
||||
memcpy((void *)all_fws->key1_crt->data, p_key1,
|
||||
all_fws->key1_crt->size);
|
||||
memcpy((void *)all_fws->key2_crt->data, p_key2,
|
||||
all_fws->key2_crt->size);
|
||||
memcpy((void *)all_fws->fw_crt->data, p_crt, all_fws->fw_crt->size);
|
||||
memcpy((void *)all_fws->fw_img->data, p_fw, all_fws->fw_img->size);
|
||||
return 0;
|
||||
|
||||
err:
|
||||
if (all_fws->fw_img)
|
||||
qmrom_free(all_fws->fw_img);
|
||||
if (all_fws->fw_crt)
|
||||
qmrom_free(all_fws->fw_crt);
|
||||
if (all_fws->key1_crt)
|
||||
qmrom_free(all_fws->key1_crt);
|
||||
if (all_fws->key2_crt)
|
||||
qmrom_free(all_fws->key2_crt);
|
||||
|
||||
return rc;
|
||||
}
|
||||
1039
drivers/uwb/qm35/libqmrom/src/qm358xx_rom.c
Normal file
1039
drivers/uwb/qm35/libqmrom/src/qm358xx_rom.c
Normal file
File diff suppressed because it is too large
Load diff
200
drivers/uwb/qm35/libqmrom/src/qmrom_common.c
Normal file
200
drivers/uwb/qm35/libqmrom/src/qmrom_common.c
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
// SPDX-License-Identifier: GPL-2.0 OR Apache-2.0
|
||||
/*
|
||||
* Copyright 2022 Qorvo US, Inc.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/uwb/qmrom_utils.h>
|
||||
#include <linux/uwb/qmrom_log.h>
|
||||
#include <linux/uwb/qmrom_spi.h>
|
||||
#include <linux/uwb/qmrom.h>
|
||||
#include <linux/uwb/spi_rom_protocol.h>
|
||||
#include <linux/gpio.h>
|
||||
|
||||
int qm357xx_rom_probe_device(struct qmrom_handle *handle);
|
||||
int qm358xx_rom_probe_device(struct qmrom_handle *handle);
|
||||
|
||||
static void qmrom_free_stcs(struct qmrom_handle *h)
|
||||
{
|
||||
if (h->hstc)
|
||||
qmrom_free(h->hstc);
|
||||
if (h->sstc)
|
||||
qmrom_free(h->sstc);
|
||||
}
|
||||
|
||||
#ifdef CHECK_STCS
|
||||
void check_stcs(const char *func, int line, struct qmrom_handle *h)
|
||||
{
|
||||
uint32_t *buff = (uint32_t *)h->hstc;
|
||||
if (buff[MAX_STC_FRAME_LEN / sizeof(uint32_t)] != 0xfeeddeef) {
|
||||
LOG_ERR("%s:%d - hstc %pK corrupted\n", func, line,
|
||||
(void *)h->hstc);
|
||||
} else {
|
||||
LOG_ERR("%s:%d - hstc %pK safe\n", func, line, (void *)h->hstc);
|
||||
}
|
||||
buff = (uint32_t *)h->sstc;
|
||||
if (buff[MAX_STC_FRAME_LEN / sizeof(uint32_t)] != 0xfeeddeef) {
|
||||
LOG_ERR("%s:%d - sstc %pK corrupted\n", func, line,
|
||||
(void *)h->sstc);
|
||||
} else {
|
||||
LOG_ERR("%s:%d - sstc %pK safe\n", func, line, (void *)h->sstc);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static int qmrom_allocate_stcs(struct qmrom_handle *h)
|
||||
{
|
||||
int rc = 0;
|
||||
uint8_t *tx_buf = NULL, *rx_buf = NULL;
|
||||
|
||||
qmrom_alloc(tx_buf, MAX_STC_FRAME_LEN + sizeof(uint32_t));
|
||||
if (tx_buf == NULL) {
|
||||
rc = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
qmrom_alloc(rx_buf, MAX_STC_FRAME_LEN + sizeof(uint32_t));
|
||||
if (rx_buf == NULL) {
|
||||
qmrom_free(tx_buf);
|
||||
rc = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
#ifdef CHECK_STCS
|
||||
((uint32_t *)tx_buf)[MAX_STC_FRAME_LEN / sizeof(uint32_t)] = 0xfeeddeef;
|
||||
((uint32_t *)rx_buf)[MAX_STC_FRAME_LEN / sizeof(uint32_t)] = 0xfeeddeef;
|
||||
#endif
|
||||
h->hstc = (struct stc *)tx_buf;
|
||||
h->sstc = (struct stc *)rx_buf;
|
||||
return rc;
|
||||
out:
|
||||
qmrom_free_stcs(h);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int qmrom_pre_read(struct qmrom_handle *handle)
|
||||
{
|
||||
handle->hstc->all = 0;
|
||||
handle->hstc->host_flags.pre_read = 1;
|
||||
handle->hstc->ul = 1;
|
||||
handle->hstc->len = 0;
|
||||
handle->hstc->payload[0] = 0;
|
||||
return qmrom_spi_transfer(handle->spi_handle, (char *)handle->sstc,
|
||||
(const char *)handle->hstc,
|
||||
sizeof(struct stc) + handle->hstc->len);
|
||||
}
|
||||
|
||||
int qmrom_read(struct qmrom_handle *handle)
|
||||
{
|
||||
size_t rd_size = handle->sstc->len;
|
||||
if (rd_size > MAX_STC_FRAME_LEN)
|
||||
return SPI_ERR_INVALID_STC_LEN;
|
||||
LOG_DBG("%s: reading %zu bytes...\n", __func__, rd_size);
|
||||
memset(handle->hstc, 0, sizeof(struct stc) + rd_size);
|
||||
handle->hstc->host_flags.read = 1;
|
||||
handle->hstc->ul = 1;
|
||||
handle->hstc->len = handle->sstc->len;
|
||||
|
||||
return qmrom_spi_transfer(handle->spi_handle, (char *)handle->sstc,
|
||||
(const char *)handle->hstc,
|
||||
sizeof(struct stc) + rd_size);
|
||||
}
|
||||
|
||||
/*
|
||||
* Unfortunately, A0, B0 and C0 have different
|
||||
* APIs to get the chip version...
|
||||
*
|
||||
*/
|
||||
int qmrom_probe_device(struct qmrom_handle *handle,
|
||||
enum device_generation_e dev_gen_hint)
|
||||
{
|
||||
int rc = -1;
|
||||
|
||||
if (dev_gen_hint == DEVICE_GEN_QM358XX ||
|
||||
dev_gen_hint == DEVICE_GEN_UNKNOWN)
|
||||
rc = qm358xx_rom_probe_device(handle);
|
||||
if (rc && (dev_gen_hint == DEVICE_GEN_QM357XX ||
|
||||
dev_gen_hint == DEVICE_GEN_UNKNOWN))
|
||||
rc = qm357xx_rom_probe_device(handle);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
struct qmrom_handle *qmrom_init(void *spi_handle, void *reset_handle,
|
||||
void *ss_rdy_handle, int spi_speed,
|
||||
int comms_retries, reset_device_fn reset,
|
||||
enum device_generation_e dev_gen_hint)
|
||||
{
|
||||
struct qmrom_handle *handle;
|
||||
int rc;
|
||||
|
||||
qmrom_alloc(handle, sizeof(struct qmrom_handle));
|
||||
if (!handle) {
|
||||
LOG_ERR("%s: Couldn't allocate %zu bytes...\n", __func__,
|
||||
sizeof(struct qmrom_handle));
|
||||
return NULL;
|
||||
}
|
||||
rc = qmrom_allocate_stcs(handle);
|
||||
if (rc) {
|
||||
LOG_ERR("%s: Couldn't allocate stcs...\n", __func__);
|
||||
qmrom_free(handle);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
handle->spi_handle = spi_handle;
|
||||
handle->reset_handle = reset_handle;
|
||||
handle->ss_rdy_handle = ss_rdy_handle;
|
||||
handle->comms_retries = comms_retries;
|
||||
handle->chip_rev = CHIP_REVISION_UNKNOWN;
|
||||
handle->device_version = -1;
|
||||
handle->spi_speed = spi_speed;
|
||||
|
||||
handle->dev_ops.reset = reset;
|
||||
|
||||
rc = qmrom_probe_device(handle, dev_gen_hint);
|
||||
if (rc) {
|
||||
LOG_ERR("%s: qmrom_probe_device returned %d!\n", __func__, rc);
|
||||
qmrom_free_stcs(handle);
|
||||
qmrom_free(handle);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
check_stcs(__func__, __LINE__, handle);
|
||||
return handle;
|
||||
}
|
||||
|
||||
void qmrom_deinit(struct qmrom_handle *handle)
|
||||
{
|
||||
LOG_DBG("Deinitializing %pK\n", (void *)handle);
|
||||
qmrom_free_stcs(handle);
|
||||
qmrom_free(handle);
|
||||
}
|
||||
|
||||
int qmrom_reboot_bootloader(struct qmrom_handle *handle)
|
||||
{
|
||||
int rc;
|
||||
|
||||
qmrom_msleep(500);
|
||||
rc = qmrom_spi_set_cs_level(handle->spi_handle, 1);
|
||||
if (rc) {
|
||||
LOG_ERR("%s: spi_set_cs_level(0) failed with %d\n", __func__,
|
||||
rc);
|
||||
return rc;
|
||||
}
|
||||
qmrom_msleep(SPI_RST_LOW_DELAY_MS);
|
||||
|
||||
handle->dev_ops.reset(handle->reset_handle);
|
||||
|
||||
qmrom_msleep(SPI_RST_LOW_DELAY_MS);
|
||||
|
||||
rc = qmrom_spi_set_cs_level(handle->spi_handle, 0);
|
||||
if (rc) {
|
||||
LOG_ERR("%s: spi_set_cs_level(1) failed with %d\n", __func__,
|
||||
rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
qmrom_msleep(SPI_RST_LOW_DELAY_MS);
|
||||
|
||||
return 0;
|
||||
}
|
||||
78
drivers/uwb/qm35/libqmrom/src/qmrom_log.c
Normal file
78
drivers/uwb/qm35/libqmrom/src/qmrom_log.c
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* Copyright 2021 Qorvo US, Inc.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0 OR Apache-2.0
|
||||
*
|
||||
* This file is provided under the Apache License 2.0, or the
|
||||
* GNU General Public License v2.0.
|
||||
*
|
||||
*/
|
||||
#include <linux/uwb/qmrom_log.h>
|
||||
|
||||
#define LOG_PRINT(lvl, ...) \
|
||||
do { \
|
||||
switch (lvl) { \
|
||||
case LOG_ERR: \
|
||||
LOG_ERR(__VA_ARGS__); \
|
||||
break; \
|
||||
case LOG_WARN: \
|
||||
LOG_WARN(__VA_ARGS__); \
|
||||
break; \
|
||||
case LOG_INFO: \
|
||||
LOG_INFO(__VA_ARGS__); \
|
||||
break; \
|
||||
case LOG_DBG: \
|
||||
LOG_DBG(__VA_ARGS__); \
|
||||
break; \
|
||||
default: \
|
||||
break; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
enum log_level_e __log_level__ = LOG_DBG;
|
||||
|
||||
void set_log_level(enum log_level_e lvl)
|
||||
{
|
||||
__log_level__ = lvl;
|
||||
}
|
||||
|
||||
#ifdef __KERNEL__
|
||||
#include <linux/device.h>
|
||||
|
||||
struct device *__qmrom_log_dev__ = NULL;
|
||||
|
||||
void qmrom_set_log_device(struct device *dev, enum log_level_e lvl)
|
||||
{
|
||||
__qmrom_log_dev__ = dev;
|
||||
__log_level__ = lvl;
|
||||
}
|
||||
#endif
|
||||
|
||||
void hexdump(enum log_level_e lvl, void *_array, unsigned short length)
|
||||
{
|
||||
unsigned char *array = _array;
|
||||
int i;
|
||||
|
||||
if (lvl < __log_level__)
|
||||
return;
|
||||
|
||||
for (i = 0; i < length; i += 16) {
|
||||
int j = 0;
|
||||
for (; j < 15 && i + j + 1 < length; j++)
|
||||
LOG_PRINT(lvl, "0x%02x, ", array[i + j]);
|
||||
LOG_PRINT(lvl, "0x%02x\n", array[i + j]);
|
||||
}
|
||||
}
|
||||
|
||||
void hexrawdump(enum log_level_e lvl, void *_array, unsigned short length)
|
||||
{
|
||||
unsigned char *array = _array;
|
||||
int i;
|
||||
|
||||
if (lvl < __log_level__)
|
||||
return;
|
||||
|
||||
for (i = 0; i < length; i++) {
|
||||
LOG_PRINT(lvl, "%02x", array[i]);
|
||||
}
|
||||
}
|
||||
1046
drivers/uwb/qm35/qm35-spi.c
Normal file
1046
drivers/uwb/qm35/qm35-spi.c
Normal file
File diff suppressed because it is too large
Load diff
32
drivers/uwb/qm35/qm35-trace.c
Normal file
32
drivers/uwb/qm35/qm35-trace.c
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
/*
|
||||
* This file is part of the QM35 UCI stack for linux.
|
||||
*
|
||||
* Copyright (c) 2021 Qorvo US, Inc.
|
||||
*
|
||||
* This software is provided under the GNU General Public License, version 2
|
||||
* (GPLv2), as well as under a Qorvo commercial license.
|
||||
*
|
||||
* You may choose to use this software under the terms of the GPLv2 License,
|
||||
* version 2 ("GPLv2"), as published by the Free Software Foundation.
|
||||
* You should have received a copy of the GPLv2 along with this program. If
|
||||
* not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This program is distributed under the GPLv2 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 GPLv2 for more
|
||||
* details.
|
||||
*
|
||||
* If you cannot meet the requirements of the GPLv2, you may not use this
|
||||
* software for any purpose without first obtaining a commercial license from
|
||||
* Qorvo.
|
||||
* Please contact Qorvo to inquire about licensing terms.
|
||||
*
|
||||
* QM35 trace
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
|
||||
#define CREATE_TRACE_POINTS
|
||||
#include "qm35-trace.h"
|
||||
129
drivers/uwb/qm35/qm35-trace.h
Normal file
129
drivers/uwb/qm35/qm35-trace.h
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
|
||||
/*
|
||||
* This file is part of the QM35 UCI stack for linux.
|
||||
*
|
||||
* Copyright (c) 2021 Qorvo US, Inc.
|
||||
*
|
||||
* This software is provided under the GNU General Public License, version 2
|
||||
* (GPLv2), as well as under a Qorvo commercial license.
|
||||
*
|
||||
* You may choose to use this software under the terms of the GPLv2 License,
|
||||
* version 2 ("GPLv2"), as published by the Free Software Foundation.
|
||||
* You should have received a copy of the GPLv2 along with this program. If
|
||||
* not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This program is distributed under the GPLv2 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 GPLv2 for more
|
||||
* details.
|
||||
*
|
||||
* If you cannot meet the requirements of the GPLv2, you may not use this
|
||||
* software for any purpose without first obtaining a commercial license from
|
||||
* Qorvo.
|
||||
* Please contact Qorvo to inquire about licensing terms.
|
||||
*
|
||||
* QM35 trace
|
||||
*/
|
||||
|
||||
#undef TRACE_SYSTEM
|
||||
#define TRACE_SYSTEM qm35
|
||||
|
||||
#if !defined(_QM35_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
|
||||
#define _QM35_TRACE_H
|
||||
|
||||
#include <linux/tracepoint.h>
|
||||
|
||||
#include "hsspi.h"
|
||||
|
||||
#define show_work_type(type) \
|
||||
__print_symbolic(type, \
|
||||
{ \
|
||||
HSSPI_WORK_TX, \
|
||||
"TX", \
|
||||
}, \
|
||||
{ \
|
||||
HSSPI_WORK_COMPLETION, \
|
||||
"COMPLETION", \
|
||||
}, \
|
||||
{ \
|
||||
-1, \
|
||||
"no", \
|
||||
})
|
||||
|
||||
TRACE_DEFINE_ENUM(HSSPI_WORK_TX);
|
||||
TRACE_DEFINE_ENUM(HSSPI_WORK_COMPLETION);
|
||||
|
||||
TRACE_EVENT(hsspi_get_work, TP_PROTO(const struct device *dev, int type),
|
||||
TP_ARGS(dev, type),
|
||||
TP_STRUCT__entry(__string(dev, dev_name(dev)) __field(int, type)),
|
||||
TP_fast_assign(__assign_str(dev, dev_name(dev));
|
||||
__entry->type = type;),
|
||||
TP_printk("[%s]: %s work", __get_str(dev),
|
||||
show_work_type(__entry->type)));
|
||||
|
||||
#define show_hsspi_state(state) \
|
||||
__print_symbolic(state, \
|
||||
{ \
|
||||
HSSPI_RUNNING, \
|
||||
"running", \
|
||||
}, \
|
||||
{ \
|
||||
HSSPI_ERROR, \
|
||||
"error", \
|
||||
}, \
|
||||
{ \
|
||||
HSSPI_STOPPED, \
|
||||
"stopped", \
|
||||
})
|
||||
|
||||
TRACE_DEFINE_ENUM(HSSPI_RUNNING);
|
||||
TRACE_DEFINE_ENUM(HSSPI_ERROR);
|
||||
TRACE_DEFINE_ENUM(HSSPI_STOPPED);
|
||||
|
||||
TRACE_EVENT(hsspi_is_txrx_waiting,
|
||||
TP_PROTO(const struct device *dev, bool is_empty,
|
||||
enum hsspi_state state),
|
||||
TP_ARGS(dev, is_empty, state),
|
||||
TP_STRUCT__entry(__string(dev, dev_name(dev))
|
||||
__field(bool, is_empty)
|
||||
__field(enum hsspi_state, state)),
|
||||
TP_fast_assign(__assign_str(dev, dev_name(dev));
|
||||
__entry->is_empty = is_empty;
|
||||
__entry->state = state;),
|
||||
TP_printk("[%s]: is_empty: %d state: %s", __get_str(dev),
|
||||
__entry->is_empty, show_hsspi_state(__entry->state)));
|
||||
|
||||
#define STC_ENTRY(header) \
|
||||
__field(u8, header##flags) __field(u8, header##ul) \
|
||||
__field(u16, header##length)
|
||||
#define STC_ASSIGN(header, var) \
|
||||
__entry->header##flags = var->flags; \
|
||||
__entry->header##ul = var->ul; \
|
||||
__entry->header##length = var->length;
|
||||
#define STC_FMT "flags:0x%hhx ul:%hhd len:%hd"
|
||||
#define STC_ARG(header) \
|
||||
__entry->header##flags, __entry->header##ul, __entry->header##length
|
||||
|
||||
TRACE_EVENT(hsspi_spi_xfer,
|
||||
TP_PROTO(const struct device *dev, const struct stc_header *host,
|
||||
struct stc_header *soc, int ret),
|
||||
TP_ARGS(dev, host, soc, ret),
|
||||
TP_STRUCT__entry(__string(dev, dev_name(dev)) STC_ENTRY(host)
|
||||
STC_ENTRY(soc) __field(int, ret)),
|
||||
TP_fast_assign(__assign_str(dev, dev_name(dev));
|
||||
STC_ASSIGN(host, host); STC_ASSIGN(soc, soc);
|
||||
__entry->ret = ret;),
|
||||
TP_printk("[%s]: host " STC_FMT " | soc " STC_FMT " rc=%d",
|
||||
__get_str(dev), STC_ARG(host), STC_ARG(soc),
|
||||
__entry->ret));
|
||||
|
||||
#endif /* _QM35_TRACE_H */
|
||||
|
||||
/* This part must be outside protection */
|
||||
#undef TRACE_INCLUDE_PATH
|
||||
#define TRACE_INCLUDE_PATH ../../../../motorola/kernel/modules/drivers/uwb/qm35
|
||||
// clang-format off
|
||||
#define TRACE_INCLUDE_FILE qm35-trace
|
||||
// clang-format on
|
||||
#include <trace/define_trace.h>
|
||||
100
drivers/uwb/qm35/qm35.h
Normal file
100
drivers/uwb/qm35/qm35.h
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
|
||||
#ifndef __QM35_H___
|
||||
#define __QM35_H___
|
||||
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/miscdevice.h>
|
||||
|
||||
#include "uci_ioctls.h"
|
||||
#include "hsspi.h"
|
||||
#include "hsspi_uci.h"
|
||||
#include "hsspi_coredump.h"
|
||||
#include "hsspi_log.h"
|
||||
#include "debug.h"
|
||||
|
||||
#define DEBUG_CERTIFICATE_SIZE 2560
|
||||
#define QM_RESET_LOW_MS 2
|
||||
/*
|
||||
* value found using a SALAE
|
||||
*/
|
||||
#define QM_BOOT_MS 450
|
||||
#define QM_BEFORE_RESET_MS 450
|
||||
|
||||
#define DRV_VERSION "7.1.0-rc2"
|
||||
|
||||
struct regulator;
|
||||
|
||||
/**
|
||||
* struct qm35_ctx - QM35 driver context
|
||||
*
|
||||
*/
|
||||
struct qm35_ctx {
|
||||
unsigned int state;
|
||||
struct miscdevice uci_dev;
|
||||
struct spi_device *spi;
|
||||
struct gpio_desc *gpio_csn;
|
||||
struct gpio_desc *gpio_reset;
|
||||
struct gpio_desc *gpio_ss_rdy;
|
||||
struct gpio_desc *gpio_ss_irq;
|
||||
struct gpio_desc *gpio_exton;
|
||||
struct gpio_desc *gpio_wakeup;
|
||||
spinlock_t lock;
|
||||
bool out_data_wait;
|
||||
bool out_active;
|
||||
bool soc_error;
|
||||
struct hsspi hsspi;
|
||||
struct uci_layer uci_layer;
|
||||
struct coredump_layer coredump_layer;
|
||||
struct log_layer log_layer;
|
||||
struct debug debug;
|
||||
struct regulator *vdd1;
|
||||
struct regulator *vdd2;
|
||||
struct regulator *vdd3;
|
||||
struct regulator *vdd4;
|
||||
bool regulators_enabled;
|
||||
bool log_qm_traces;
|
||||
};
|
||||
|
||||
static inline unsigned int qm35_get_state(struct qm35_ctx *qm35_hdl)
|
||||
{
|
||||
return qm35_hdl->state;
|
||||
}
|
||||
|
||||
static inline void qm35_set_state(struct qm35_ctx *qm35_hdl, int state)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&qm35_hdl->lock, flags);
|
||||
qm35_hdl->state = state;
|
||||
spin_unlock_irqrestore(&qm35_hdl->lock, flags);
|
||||
}
|
||||
|
||||
static inline int qm35_reset(struct qm35_ctx *qm35_hdl, int timeout_ms)
|
||||
{
|
||||
if (qm35_hdl->gpio_reset) {
|
||||
qm35_set_state(qm35_hdl, QM35_CTRL_STATE_RESET);
|
||||
gpiod_set_value(qm35_hdl->gpio_reset, 1);
|
||||
usleep_range(timeout_ms * 1000, timeout_ms * 1000);
|
||||
gpiod_set_value(qm35_hdl->gpio_reset, 0);
|
||||
qm35_set_state(qm35_hdl, QM35_CTRL_STATE_UNKNOWN);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
int qm35_reset_sync(struct qm35_ctx *qm35_hdl);
|
||||
|
||||
int qm_get_dev_id(struct qm35_ctx *qm35_hdl, uint16_t *dev_id);
|
||||
int qm_get_soc_id(struct qm35_ctx *qm35_hdl, uint8_t *soc_id);
|
||||
|
||||
void qm35_hsspi_start(struct qm35_ctx *qm35_hdl);
|
||||
void qm35_hsspi_stop(struct qm35_ctx *qm35_hdl);
|
||||
|
||||
void qmrom_set_fwname(const char *name);
|
||||
|
||||
#endif /* __QM35_H___ */
|
||||
230
drivers/uwb/qm35/qm35_rb.c
Normal file
230
drivers/uwb/qm35/qm35_rb.c
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
/*
|
||||
* This file is part of the QM35 UCI stack for linux.
|
||||
*
|
||||
* Copyright (c) 2022 Qorvo US, Inc.
|
||||
*
|
||||
* This software is provided under the GNU General Public License, version 2
|
||||
* (GPLv2), as well as under a Qorvo commercial license.
|
||||
*
|
||||
* You may choose to use this software under the terms of the GPLv2 License,
|
||||
* version 2 ("GPLv2"), as published by the Free Software Foundation.
|
||||
* You should have received a copy of the GPLv2 along with this program. If
|
||||
* not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This program is distributed under the GPLv2 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 GPLv2 for more
|
||||
* details.
|
||||
*
|
||||
* If you cannot meet the requirements of the GPLv2, you may not use this
|
||||
* software for any purpose without first obtaining a commercial license from
|
||||
* Qorvo.
|
||||
* Please contact Qorvo to inquire about licensing terms.
|
||||
*
|
||||
* QM35 Ringbuffer
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/sched.h>
|
||||
|
||||
#include "qm35_rb.h"
|
||||
|
||||
static bool __rb_can_pop(struct rb *rb, uint32_t *tail)
|
||||
{
|
||||
// if tail equals with the head index, no data can be popped
|
||||
return *tail != rb->head;
|
||||
}
|
||||
|
||||
bool rb_can_pop(struct rb *rb)
|
||||
{
|
||||
bool can_pop = false;
|
||||
|
||||
mutex_lock(&rb->lock);
|
||||
can_pop = __rb_can_pop(rb, &rb->rdtail);
|
||||
mutex_unlock(&rb->lock);
|
||||
|
||||
return can_pop;
|
||||
}
|
||||
|
||||
static rb_entry_size_t __rb_next_size(struct rb *rb, uint32_t *tail)
|
||||
{
|
||||
rb_entry_size_t next_packet_size = 0;
|
||||
|
||||
// check if something is available to pop from the ring buffer
|
||||
if (!__rb_can_pop(rb, tail))
|
||||
return 0;
|
||||
|
||||
// read next packet size
|
||||
memcpy(&next_packet_size, rb->buf + *tail, sizeof(next_packet_size));
|
||||
|
||||
if (next_packet_size == 0) {
|
||||
if (*tail == 0)
|
||||
return 0;
|
||||
// if the next packet size is 0x00, it is an
|
||||
// indication that we hit the end marker so we should
|
||||
// start reading from the beginning
|
||||
*tail = 0;
|
||||
memcpy(&next_packet_size, rb->buf + *tail,
|
||||
sizeof(next_packet_size));
|
||||
}
|
||||
|
||||
return next_packet_size;
|
||||
}
|
||||
|
||||
rb_entry_size_t rb_next_size(struct rb *rb)
|
||||
{
|
||||
rb_entry_size_t next_packet_size = 0;
|
||||
|
||||
mutex_lock(&rb->lock);
|
||||
next_packet_size = __rb_next_size(rb, &rb->rdtail);
|
||||
mutex_unlock(&rb->lock);
|
||||
|
||||
return next_packet_size;
|
||||
}
|
||||
|
||||
static char *__rb_pop(struct rb *rb, rb_entry_size_t *len, uint32_t *tail)
|
||||
{
|
||||
char *trace;
|
||||
|
||||
*len = __rb_next_size(rb, tail);
|
||||
|
||||
if (*len == 0)
|
||||
return 0;
|
||||
|
||||
// advance ptr with sizeof(next_entry_size)
|
||||
*tail += sizeof(*len);
|
||||
|
||||
// allocate memory for data
|
||||
trace = kmalloc(*len, GFP_KERNEL);
|
||||
|
||||
// get the data
|
||||
memcpy(trace, rb->buf + *tail, *len);
|
||||
*tail += *len;
|
||||
|
||||
return trace;
|
||||
}
|
||||
|
||||
char *rb_pop(struct rb *rb, rb_entry_size_t *len)
|
||||
{
|
||||
char *entry;
|
||||
|
||||
mutex_lock(&rb->lock);
|
||||
entry = __rb_pop(rb, len, &rb->rdtail);
|
||||
mutex_unlock(&rb->lock);
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
static bool __rb_skip(struct rb *rb)
|
||||
{
|
||||
rb_entry_size_t next_entry_size;
|
||||
|
||||
// read next packet size
|
||||
memcpy(&next_entry_size, rb->buf + rb->tail, sizeof(next_entry_size));
|
||||
|
||||
if (next_entry_size == 0) {
|
||||
if (rb->tail == 0)
|
||||
return false;
|
||||
// if the next packet size is 0x00, it is an
|
||||
// indication that we hit the end marker so we should
|
||||
// start reading from the beginning
|
||||
rb->tail = 0;
|
||||
memcpy(&next_entry_size, rb->buf + rb->tail,
|
||||
sizeof(next_entry_size));
|
||||
}
|
||||
|
||||
// advance ptr with sizeof(next_entry_size)
|
||||
rb->tail += sizeof(next_entry_size) + next_entry_size;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int rb_push(struct rb *rb, const char *data, rb_entry_size_t len)
|
||||
{
|
||||
rb_entry_size_t entry_size;
|
||||
uint32_t available_to_end;
|
||||
|
||||
// doesn't make sense to push a packet with the payload len 0.
|
||||
if (len == 0)
|
||||
return 1;
|
||||
|
||||
mutex_lock(&rb->lock);
|
||||
|
||||
// calculate how much data we want to store
|
||||
// we add the size of the trace with the size of the size of the trace
|
||||
// because we want to store the size as well for reading
|
||||
entry_size = sizeof(len) + len;
|
||||
|
||||
// available data to the end of the ring buffer
|
||||
available_to_end = rb->size - rb->head;
|
||||
|
||||
// we check that by writing the buffer still has 2 bytes left
|
||||
// to write the buffer end marker 0x0000 if not, we just add
|
||||
// the marker and start from beginning
|
||||
if (available_to_end <= entry_size + sizeof(rb_entry_size_t)) {
|
||||
memset(rb->buf + rb->head, 0, sizeof(rb_entry_size_t));
|
||||
if (rb->tail >= rb->head)
|
||||
rb->tail = 0;
|
||||
if (rb->rdtail >= rb->head)
|
||||
rb->rdtail = 0;
|
||||
rb->head = 0;
|
||||
}
|
||||
|
||||
// check if writing the new data will cause the head index to
|
||||
// overrun the tail index
|
||||
while ((rb->head <= rb->tail) && (rb->head + entry_size >= rb->tail)) {
|
||||
bool equal_tails = rb->tail == rb->rdtail;
|
||||
// need to adjust the tail to point to the next log
|
||||
// not overwritten
|
||||
bool skipped = __rb_skip(rb);
|
||||
|
||||
if (equal_tails)
|
||||
rb->rdtail = rb->tail;
|
||||
|
||||
if (!skipped)
|
||||
break;
|
||||
}
|
||||
|
||||
// copy the size first
|
||||
memcpy(rb->buf + rb->head, &len, sizeof(len));
|
||||
// move the head by sizeof(trace->size) to be in place for copying the data
|
||||
rb->head += sizeof(len);
|
||||
// copy the data
|
||||
memcpy(rb->buf + rb->head, data, len);
|
||||
// update the head ptr to the next write
|
||||
rb->head += len;
|
||||
|
||||
mutex_unlock(&rb->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void rb_reset(struct rb *rb)
|
||||
{
|
||||
mutex_lock(&rb->lock);
|
||||
rb->rdtail = rb->tail;
|
||||
mutex_unlock(&rb->lock);
|
||||
}
|
||||
|
||||
int rb_init(struct rb *rb, uint32_t size)
|
||||
{
|
||||
rb->buf = kmalloc(size, GFP_KERNEL);
|
||||
if (!rb->buf)
|
||||
return -ENOMEM;
|
||||
|
||||
mutex_init(&rb->lock);
|
||||
rb->head = 0;
|
||||
rb->tail = 0;
|
||||
rb->rdtail = 0;
|
||||
rb->size = size;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void rb_deinit(struct rb *rb)
|
||||
{
|
||||
kfree(rb->buf);
|
||||
}
|
||||
55
drivers/uwb/qm35/qm35_rb.h
Normal file
55
drivers/uwb/qm35/qm35_rb.h
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
|
||||
/*
|
||||
* This file is part of the QM35 UCI stack for linux.
|
||||
*
|
||||
* Copyright (c) 2022 Qorvo US, Inc.
|
||||
*
|
||||
* This software is provided under the GNU General Public License, version 2
|
||||
* (GPLv2), as well as under a Qorvo commercial license.
|
||||
*
|
||||
* You may choose to use this software under the terms of the GPLv2 License,
|
||||
* version 2 ("GPLv2"), as published by the Free Software Foundation.
|
||||
* You should have received a copy of the GPLv2 along with this program. If
|
||||
* not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This program is distributed under the GPLv2 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 GPLv2 for more
|
||||
* details.
|
||||
*
|
||||
* If you cannot meet the requirements of the GPLv2, you may not use this
|
||||
* software for any purpose without first obtaining a commercial license from
|
||||
* Qorvo.
|
||||
* Please contact Qorvo to inquire about licensing terms.
|
||||
*
|
||||
* QM35 Ringbuffer
|
||||
*/
|
||||
|
||||
#ifndef __QM35_RB_H__
|
||||
#define __QM35_RB_H__
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
typedef uint16_t rb_entry_size_t;
|
||||
|
||||
struct rb {
|
||||
uint8_t *buf;
|
||||
uint32_t size;
|
||||
uint32_t head;
|
||||
uint32_t tail;
|
||||
uint32_t rdtail;
|
||||
struct mutex lock;
|
||||
};
|
||||
|
||||
bool rb_can_pop(struct rb *rb);
|
||||
rb_entry_size_t rb_next_size(struct rb *rb);
|
||||
|
||||
char *rb_pop(struct rb *rb, rb_entry_size_t *len);
|
||||
int rb_push(struct rb *rb, const char *data, rb_entry_size_t len);
|
||||
void rb_reset(struct rb *rb);
|
||||
|
||||
int rb_init(struct rb *rb, uint32_t size);
|
||||
void rb_deinit(struct rb *rb);
|
||||
|
||||
#endif // __QM35_RB_H__
|
||||
192
drivers/uwb/qm35/qmrom_spi.c
Normal file
192
drivers/uwb/qm35/qmrom_spi.c
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
/*
|
||||
* This file is part of the QM35 UCI stack for linux.
|
||||
*
|
||||
* Copyright (c) 2021 Qorvo US, Inc.
|
||||
*
|
||||
* This software is provided under the GNU General Public License, version 2
|
||||
* (GPLv2), as well as under a Qorvo commercial license.
|
||||
*
|
||||
* You may choose to use this software under the terms of the GPLv2 License,
|
||||
* version 2 ("GPLv2"), as published by the Free Software Foundation.
|
||||
* You should have received a copy of the GPLv2 along with this program. If
|
||||
* not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This program is distributed under the GPLv2 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 GPLv2 for more
|
||||
* details.
|
||||
*
|
||||
* If you cannot meet the requirements of the GPLv2, you may not use this
|
||||
* software for any purpose without first obtaining a commercial license from
|
||||
* Qorvo.
|
||||
* Please contact Qorvo to inquire about licensing terms.
|
||||
*
|
||||
* QM35 FW ROM protocol SPI ops
|
||||
*/
|
||||
|
||||
#include <linux/spi/spi.h>
|
||||
|
||||
#include <linux/uwb/qmrom_spi.h>
|
||||
#include <linux/uwb/spi_rom_protocol.h>
|
||||
|
||||
#include "qm35.h"
|
||||
|
||||
/* TODO Compile QM358XX code */
|
||||
int qm358xx_rom_probe_device(struct qmrom_handle *handle)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
static const char *fwname = NULL;
|
||||
static unsigned int speed_hz;
|
||||
extern int trace_spi_xfers;
|
||||
|
||||
void qmrom_set_fwname(const char *name)
|
||||
{
|
||||
fwname = name;
|
||||
}
|
||||
|
||||
int qmrom_spi_transfer(void *handle, char *rbuf, const char *wbuf, size_t size)
|
||||
{
|
||||
struct spi_device *spi = (struct spi_device *)handle;
|
||||
int rc;
|
||||
|
||||
struct spi_transfer xfer[] = {
|
||||
{
|
||||
.tx_buf = wbuf,
|
||||
.rx_buf = rbuf,
|
||||
.len = size,
|
||||
.speed_hz = qmrom_spi_get_freq(),
|
||||
},
|
||||
};
|
||||
|
||||
rc = spi_sync_transfer(spi, xfer, ARRAY_SIZE(xfer));
|
||||
|
||||
if (trace_spi_xfers) {
|
||||
print_hex_dump(KERN_DEBUG, "qm35 tx:", DUMP_PREFIX_NONE, 16, 1,
|
||||
wbuf, size, false);
|
||||
print_hex_dump(KERN_DEBUG, "qm35 rx:", DUMP_PREFIX_NONE, 16, 1,
|
||||
rbuf, size, false);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
#if 0
|
||||
int qmrom_spi_set_cs_level(void *handle, int level)
|
||||
{
|
||||
struct spi_device *spi = (struct spi_device *)handle;
|
||||
uint8_t dummy = 0;
|
||||
|
||||
struct spi_transfer xfer[] = {
|
||||
{
|
||||
.tx_buf = &dummy,
|
||||
.len = 1,
|
||||
.cs_change = !level,
|
||||
.speed_hz = qmrom_spi_get_freq(),
|
||||
},
|
||||
};
|
||||
|
||||
return spi_sync_transfer(spi, xfer, ARRAY_SIZE(xfer));
|
||||
}
|
||||
#else
|
||||
extern void qm35_set_csn_level(int level);
|
||||
int qmrom_spi_set_cs_level(void *handle, int level)
|
||||
{
|
||||
qm35_set_csn_level(level);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
int qmrom_spi_reset_device(void *reset_handle)
|
||||
{
|
||||
struct qm35_ctx *qm35_hdl = (struct qm35_ctx *)reset_handle;
|
||||
|
||||
return qm35_reset(qm35_hdl, SPI_RST_LOW_DELAY_MS);
|
||||
}
|
||||
|
||||
const struct firmware *qmrom_spi_get_firmware(void *handle,
|
||||
struct qmrom_handle *qmrom_h,
|
||||
bool *is_macro_pkg,
|
||||
bool use_prod_fw)
|
||||
{
|
||||
const struct firmware *fw;
|
||||
struct spi_device *spi = handle;
|
||||
const char *fw_name;
|
||||
int ret;
|
||||
uint32_t lcs_state = qmrom_h->qm357xx_soc_info.lcs_state;
|
||||
|
||||
if (!fwname) {
|
||||
if (lcs_state != CC_BSV_SECURE_LCS) {
|
||||
dev_warn(&spi->dev, "LCS state is not secure.");
|
||||
}
|
||||
|
||||
if (use_prod_fw)
|
||||
fw_name = "qm35_fw_pkg_prod.bin";
|
||||
else if (qmrom_h->chip_rev >= CHIP_REVISION_C0)
|
||||
fw_name = "qm35_fw_pkg.bin";
|
||||
else
|
||||
fw_name = "qm35_b0_fw_pkg.bin";
|
||||
*is_macro_pkg = true;
|
||||
} else {
|
||||
fw_name = fwname;
|
||||
}
|
||||
dev_info(&spi->dev, "Requesting fw %s!\n", fw_name);
|
||||
|
||||
ret = request_firmware(&fw, fw_name, &spi->dev);
|
||||
if (ret) {
|
||||
if (lcs_state != CC_BSV_SECURE_LCS) {
|
||||
dev_warn(&spi->dev, "LCS state is not secure.");
|
||||
}
|
||||
|
||||
/* Didn't get the macro package, try the stitched image */
|
||||
*is_macro_pkg = false;
|
||||
if (use_prod_fw)
|
||||
fw_name = "qm35_oem_prod.bin";
|
||||
else
|
||||
fw_name = "qm35_oem.bin";
|
||||
dev_info(&spi->dev, "Requesting fw %s!\n", fw_name);
|
||||
ret = request_firmware(&fw, fw_name, &spi->dev);
|
||||
if (!ret) {
|
||||
dev_info(&spi->dev, "Firmware size is %zu!\n",
|
||||
fw->size);
|
||||
return fw;
|
||||
}
|
||||
|
||||
release_firmware(fw);
|
||||
dev_err(&spi->dev,
|
||||
"request_firmware failed (ret=%d) for '%s'\n", ret,
|
||||
fw_name);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
dev_info(&spi->dev, "Firmware size is %zu!\n", fw->size);
|
||||
|
||||
return fw;
|
||||
}
|
||||
|
||||
void qmrom_spi_release_firmware(const struct firmware *fw)
|
||||
{
|
||||
release_firmware(fw);
|
||||
}
|
||||
|
||||
int qmrom_spi_wait_for_ready_line(void *handle, unsigned int timeout_ms)
|
||||
{
|
||||
int count_down = (int)timeout_ms;
|
||||
while (!gpiod_get_value(handle) && (--count_down >= 0)) {
|
||||
udelay(1000);
|
||||
}
|
||||
return gpiod_get_value(handle) ? 0 : -1;
|
||||
}
|
||||
|
||||
void qmrom_spi_set_freq(unsigned int freq)
|
||||
{
|
||||
speed_hz = freq;
|
||||
}
|
||||
|
||||
unsigned int qmrom_spi_get_freq()
|
||||
{
|
||||
return speed_hz;
|
||||
}
|
||||
26
drivers/uwb/qm35/uci_ioctls.h
Normal file
26
drivers/uwb/qm35/uci_ioctls.h
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
|
||||
#ifndef __UCI_IOCTLS_H___
|
||||
#define __UCI_IOCTLS_H___
|
||||
|
||||
#include <asm/ioctl.h>
|
||||
|
||||
#define UCI_DEV_NAME "uci"
|
||||
#define UCI_IOC_TYPE 'U'
|
||||
|
||||
#define QM35_CTRL_RESET _IOR(UCI_IOC_TYPE, 1, unsigned int)
|
||||
#define QM35_CTRL_GET_STATE _IOR(UCI_IOC_TYPE, 2, unsigned int)
|
||||
#define QM35_CTRL_FW_UPLOAD _IOR(UCI_IOC_TYPE, 3, unsigned int)
|
||||
#define QM35_CTRL_POWER _IOW(UCI_IOC_TYPE, 4, unsigned int)
|
||||
|
||||
/* qm35 states */
|
||||
enum { QM35_CTRL_STATE_UNKNOWN = 0x0000,
|
||||
QM35_CTRL_STATE_OFF = 0x0001,
|
||||
QM35_CTRL_STATE_RESET = 0x0002,
|
||||
QM35_CTRL_STATE_COREDUMP = 0x0004,
|
||||
QM35_CTRL_STATE_READY = 0x0008,
|
||||
QM35_CTRL_STATE_FW_DOWNLOADING = 0x0010,
|
||||
QM35_CTRL_STATE_UCI_APP = 0x0020,
|
||||
};
|
||||
|
||||
#endif /* __UCI_IOCTLS_H___ */
|
||||
10
include/linux/uwb/byteswap.h
Normal file
10
include/linux/uwb/byteswap.h
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// SPDX-License-Identifier: GPL-2.0 OR Apache-2.0
|
||||
/*
|
||||
* Copyright 2022 Qorvo US, Inc.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef BYTESWAP_H
|
||||
#define BYTESWAP_H
|
||||
#define bswap_16 __builtin_bswap16
|
||||
#endif
|
||||
62
include/linux/uwb/fwupdater.h
Normal file
62
include/linux/uwb/fwupdater.h
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
// SPDX-License-Identifier: GPL-2.0 OR Apache-2.0
|
||||
/*
|
||||
* Copyright 2021 Qorvo US, Inc.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __FWUPDATER_H__
|
||||
#define __FWUPDATER_H__
|
||||
|
||||
#ifndef __KERNEL__
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#else
|
||||
#include <linux/types.h>
|
||||
#endif
|
||||
|
||||
#include "qmrom.h"
|
||||
#include "qm357xx_fwpkg.h"
|
||||
|
||||
enum fw_pkg_error_e {
|
||||
FW_PKG_SUCESS = 0,
|
||||
FW_PKG_DOWNLOAD_ERROR,
|
||||
FW_PKG_MAGIC_NUM_INVALID,
|
||||
FW_PKG_VERSION_INVALID,
|
||||
FW_PKG_ENCRYPTION_MODE_INVALID,
|
||||
FW_PKG_IMG_HDR_MAGIC_NUM_INVALID,
|
||||
FW_PKG_IMG_HDR_VERSION_INVALID,
|
||||
FW_PKG_IMG_HDR_CERT_SIZE_INVALID,
|
||||
FW_PKG_IMG_HDR_IMG_NUM_INVALID,
|
||||
FW_PKG_PLD_CHK_MAGIC_NUM_INVALID,
|
||||
FW_PKG_PLD_CHK_VERSION_INVALID,
|
||||
FW_PKG_PLD_CHK_LENGTH_INVALID,
|
||||
FW_PKG_HDR_CRYPTO_ERROR,
|
||||
FW_PKG_HDR_CRYPTO_INVALID,
|
||||
FW_PKG_HDR_PAYLOAD_SIZE_INVALID,
|
||||
FW_PKG_IMG_HDR_SIZE_INVALID,
|
||||
FW_PKG_IMG_HDR_CRYPTO_ERROR,
|
||||
FW_PKG_IMG_HDR_NUMDESCS_INVALID,
|
||||
FW_PKG_CERT_CHAIN_SIZE_INVALID,
|
||||
FW_PKG_CERT_CHAIN_INVALID,
|
||||
FW_PKG_CERT_INSTALLED_FW_VERSION_INVALID,
|
||||
FW_PKG_CERT_KEY1_CHECK_INVALID,
|
||||
FW_PKG_CERT_KEY2_CHECK_INVALID,
|
||||
FW_PKG_CERT_CONTENT_CHECK_INVALID,
|
||||
FW_PKG_CERT_CHAIN_CRYPTO_ERROR,
|
||||
FW_PKG_CERT_CHAIN_IMG_RANGE_INVALID,
|
||||
FW_PKG_CERT_CHAIN_WRITE_ERROR,
|
||||
FW_PKG_IMG_CHUNK_CRYPTO_ERROR,
|
||||
FW_PKG_IMG_CHUNK_WRITE_ERROR,
|
||||
FW_PKG_IMG_FIXUP_FAILED
|
||||
};
|
||||
|
||||
/*! Firmware Update Status fields */
|
||||
struct fw_updater_status_t {
|
||||
uint32_t status;
|
||||
uint32_t suberror;
|
||||
} __attribute__((packed));
|
||||
|
||||
void run_fwupdater_unit_tests(void *spi_handle);
|
||||
int run_fwupdater(struct qmrom_handle *handle, char *fwpkg_bin, size_t size);
|
||||
|
||||
#endif /* __FWUPDATER_H__ */
|
||||
132
include/linux/uwb/qm357xx_fwpkg.h
Normal file
132
include/linux/uwb/qm357xx_fwpkg.h
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
// SPDX-License-Identifier: GPL-2.0 OR Apache-2.0
|
||||
/*
|
||||
* Copyright 2021 Qorvo US, Inc.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __QM357XX_FWPKG_H__
|
||||
#define __QM357XX_FWPKG_H__
|
||||
|
||||
/*! Pkg version word conversion macro */
|
||||
#define PKG_VER_TO_U32(maj, min) (((maj) << 16) | ((min) << 24))
|
||||
|
||||
/*! Pkg magic word conversion macro */
|
||||
#define MAGIC_STR_TO_U32(x) \
|
||||
((uint32_t)((x[3]) | (x[2] << 8) | (x[1] << 16) | (x[0] << 24)))
|
||||
|
||||
// Package size defines
|
||||
#define CRYPTO_FIRMWARE_PACK_ENC_DATA_SIZE 16
|
||||
#define CRYPTO_FIRMWARE_PACK_FW_VERSION_SIZE 32
|
||||
#define CRYPTO_FIRMWARE_PACK_TAG_SIZE 16
|
||||
|
||||
// Firmware image size define
|
||||
#define CRYPTO_FW_PKG_IMG_HDR_IMG_NUM_MAX 8
|
||||
|
||||
// Field values for macro firmware package
|
||||
#define CRYPTO_MACRO_FIRMWARE_PACK_MAGIC_VALUE MAGIC_STR_TO_U32("FWMP")
|
||||
|
||||
// Field values for firmware package
|
||||
#define CRYPTO_FIRMWARE_PACK_MAGIC_VALUE MAGIC_STR_TO_U32("CFWP")
|
||||
#define CRYPTO_FIRMWARE_PACK_VERSION PKG_VER_TO_U32(1, 0)
|
||||
#define CRYPTO_FIRMWARE_PACK_ENC_MODE_NOT_ENCRYPTED 0x0
|
||||
#define CRYPTO_FIRMWARE_PACK_ENC_ALGO_128BIT_AES_CTR 0x00
|
||||
#define CRYPTO_FIRMWARE_PACK_ENC_KEY_L2_SIZE 64
|
||||
|
||||
// Field values for firmware image
|
||||
#define CRYPTO_FIRMWARE_IMAGE_MAGIC_VALUE MAGIC_STR_TO_U32("IMGS")
|
||||
#define CRYPTO_FIRMWARE_IMAGE_VERSION PKG_VER_TO_U32(1, 0)
|
||||
|
||||
// Field values for firmware chunks
|
||||
#define CRYPTO_FIRMWARE_CHUNK_MAGIC_VALUE MAGIC_STR_TO_U32("CFWC")
|
||||
#define CRYPTO_FIRMWARE_CHUNK_VERSION PKG_VER_TO_U32(1, 0)
|
||||
#define CRYPTO_FIRMWARE_CHUNK_MIN_SIZE 16
|
||||
|
||||
// fw certificate sizes
|
||||
#define CRYPTO_IMAGES_CERT_KEY_SIZE 840
|
||||
#define CRYPTO_IMAGES_CERT_CONTENT_SIZE 868
|
||||
#define CRYPTO_IMAGES_CERT_PKG_SIZE \
|
||||
(2 * CRYPTO_IMAGES_CERT_KEY_SIZE + CRYPTO_IMAGES_CERT_CONTENT_SIZE)
|
||||
#define CRYPTO_IMAGES_NB_CERTS 3
|
||||
#define CRYPTO_IMAGES_MAX_NB_IMAGES 8
|
||||
|
||||
#define CRYPTO_FIRMWARE_IMAGE_HDR_TOTAL_SIZE \
|
||||
(sizeof(struct fw_pkg_img_hdr_t) + \
|
||||
CRYPTO_IMAGES_NB_CERTS * sizeof(struct fw_img_desc_t))
|
||||
|
||||
/*! Encryption mode enum. */
|
||||
enum fw_pkg_enc_mode_e {
|
||||
CRYPTO_FIRMWARE_PACK_ENC_MODE_NONE = 0x0,
|
||||
CRYPTO_FIRMWARE_PACK_ENC_MODE_ENCRYPTED = 0x1
|
||||
};
|
||||
|
||||
/*! Package type enum. */
|
||||
enum fw_pkg_package_type_e {
|
||||
CRYPTO_FIRMWARE_PACK_PACKAGE_TYPE_ICV = 0x01,
|
||||
CRYPTO_FIRMWARE_PACK_PACKAGE_TYPE_OEM = 0x02
|
||||
};
|
||||
|
||||
/*! IV type enum. */
|
||||
enum fw_pkg_iv_type_e {
|
||||
CRYPTO_FIRMWARE_PACK_IV_TYPE_HDR = 0x00,
|
||||
CRYPTO_FIRMWARE_PACK_IV_TYPE_IMG = 0x01
|
||||
};
|
||||
|
||||
/*! Firmware Package Header fields */
|
||||
struct fw_pkg_hdr_t {
|
||||
uint32_t magic; /**< Magic number. */
|
||||
uint32_t version; /**< Version. */
|
||||
uint8_t package_type; /**< Package type. */
|
||||
uint8_t enc_mode; /**< Encryption mode. */
|
||||
uint8_t enc_algo; /**< Encryption algorithm. */
|
||||
uint8_t reserved; /**< Reserved for alignment. */
|
||||
uint8_t enc_data
|
||||
[CRYPTO_FIRMWARE_PACK_ENC_DATA_SIZE]; /**< Encryption data. */
|
||||
uint8_t enc_key_l2
|
||||
[CRYPTO_FIRMWARE_PACK_ENC_KEY_L2_SIZE]; /**< Code encryption L2 key data. */
|
||||
uint8_t fw_version
|
||||
[CRYPTO_FIRMWARE_PACK_FW_VERSION_SIZE]; /**< Firwmare version included in the package. */
|
||||
uint32_t payload_len; /**< Payload length. */
|
||||
uint8_t tag[CRYPTO_FIRMWARE_PACK_TAG_SIZE]; /**< AES-CMAC Tag. */
|
||||
} __attribute__((packed));
|
||||
|
||||
/*! Firmware Image Metadata fields */
|
||||
struct fw_img_desc_t {
|
||||
uint32_t offset; /**< Offset. */
|
||||
uint32_t length; /**< Length. */
|
||||
} __attribute__((packed));
|
||||
|
||||
/*! Firmware Image Header fields */
|
||||
struct fw_pkg_img_hdr_t {
|
||||
uint32_t magic; /**< Magic number. */
|
||||
uint32_t version; /**< Version number. */
|
||||
uint32_t cert_chain_offset; /**< Certificate chain offset. */
|
||||
uint16_t cert_chain_length; /**< Certificate chain length. */
|
||||
uint8_t num_descs; /**< Number of images descriptors. */
|
||||
uint8_t reserved; /**< Reserved data. */
|
||||
struct fw_img_desc_t descs
|
||||
[CRYPTO_FW_PKG_IMG_HDR_IMG_NUM_MAX]; /**< Firmware image metadata. */
|
||||
} __attribute__((packed));
|
||||
|
||||
/*! Firmware Chunk fields */
|
||||
struct fw_pkg_payload_chunk_t {
|
||||
uint32_t magic; /**< Magic number. */
|
||||
uint32_t version; /**< Version number. */
|
||||
uint32_t length; /**< Length. */
|
||||
uint8_t payload[]; /**< Payload data pointer. */
|
||||
} __attribute__((packed));
|
||||
|
||||
/*! Firmware Macro Package Header fields */
|
||||
struct fw_macro_pkg_hdr_t {
|
||||
uint32_t magic; /**< Magic number. */
|
||||
uint32_t version; /**< Version. */
|
||||
uint8_t nb_descriptors;
|
||||
uint8_t reserved[3];
|
||||
struct fw_img_desc_t img_desc[];
|
||||
} __attribute__((packed));
|
||||
|
||||
#define MACRO_PKG_HASH_SIZE (32)
|
||||
#define COMPUTE_FW_MACRO_PKG_HDR_SIZE(ndescs) \
|
||||
(sizeof(struct fw_macro_pkg_hdr_t) + \
|
||||
ndescs * sizeof(struct fw_img_desc_t) + MACRO_PKG_HASH_SIZE)
|
||||
|
||||
#endif /* __QM357XX_FWPKG_H__ */
|
||||
93
include/linux/uwb/qm357xx_rom.h
Normal file
93
include/linux/uwb/qm357xx_rom.h
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
// SPDX-License-Identifier: GPL-2.0 OR Apache-2.0
|
||||
/*
|
||||
* Copyright 2021 Qorvo US, Inc.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __QM357XX_ROM_H__
|
||||
#define __QM357XX_ROM_H__
|
||||
|
||||
#define PEG_ERR_TIMEOUT PEG_ERR_BASE - 1
|
||||
#define PEG_ERR_ROM_NOT_READY PEG_ERR_BASE - 2
|
||||
#define PEG_ERR_SEND_CERT_WRITE PEG_ERR_BASE - 3
|
||||
#define PEG_ERR_WRONG_REVISION PEG_ERR_BASE - 4
|
||||
#define PEG_ERR_FIRST_KEY_CERT_OR_FW_VER PEG_ERR_BASE - 5
|
||||
|
||||
#define HBK_LOC 12
|
||||
typedef enum {
|
||||
HBK_2E_ICV = 0,
|
||||
HBK_2E_OEM = 1,
|
||||
HBK_1E_ICV_OEM = 2,
|
||||
} hbk_t;
|
||||
|
||||
#define ROM_VERSION_A0 0x01a0
|
||||
#define ROM_VERSION_B0 0xb000
|
||||
|
||||
#define QM357XX_ROM_SOC_ID_LEN 0x20
|
||||
#define QM357XX_ROM_UUID_LEN 0x10
|
||||
|
||||
/* Life cycle state definitions. */
|
||||
|
||||
/*! Defines the CM life-cycle state value. */
|
||||
#define CC_BSV_CHIP_MANUFACTURE_LCS 0x0
|
||||
/*! Defines the DM life-cycle state value. */
|
||||
#define CC_BSV_DEVICE_MANUFACTURE_LCS 0x1
|
||||
/*! Defines the Secure life-cycle state value. */
|
||||
#define CC_BSV_SECURE_LCS 0x5
|
||||
/*! Defines the RMA life-cycle state value. */
|
||||
#define CC_BSV_RMA_LCS 0x7
|
||||
|
||||
struct unstitched_firmware {
|
||||
struct firmware *fw_img;
|
||||
struct firmware *fw_crt;
|
||||
struct firmware *key1_crt;
|
||||
struct firmware *key2_crt;
|
||||
};
|
||||
|
||||
/* Those functions allow the libqmrom to call
|
||||
* revision specific functions
|
||||
*/
|
||||
typedef int (*flash_fw_fn)(struct qmrom_handle *handle,
|
||||
const struct firmware *fw);
|
||||
typedef int (*flash_unstitched_fw_fn)(struct qmrom_handle *handle,
|
||||
const struct unstitched_firmware *fw);
|
||||
typedef int (*flash_debug_cert_fn)(struct qmrom_handle *handle,
|
||||
struct firmware *dbg_cert);
|
||||
typedef int (*erase_debug_cert_fn)(struct qmrom_handle *handle);
|
||||
|
||||
struct qm357xx_rom_code_ops {
|
||||
flash_fw_fn flash_fw;
|
||||
flash_unstitched_fw_fn flash_unstitched_fw;
|
||||
flash_debug_cert_fn flash_debug_cert;
|
||||
erase_debug_cert_fn erase_debug_cert;
|
||||
};
|
||||
|
||||
struct qm357xx_soc_infos {
|
||||
uint8_t soc_id[QM357XX_ROM_SOC_ID_LEN];
|
||||
uint8_t uuid[QM357XX_ROM_UUID_LEN];
|
||||
uint32_t lcs_state;
|
||||
};
|
||||
|
||||
int qm357xx_rom_unstitch_fw(const struct firmware *fw,
|
||||
struct unstitched_firmware *unstitched_fw,
|
||||
enum chip_revision_e revision);
|
||||
int qm357xx_rom_fw_macro_pkg_get_fw_idx(const struct firmware *fw, int idx,
|
||||
uint32_t *fw_size, char **fw_fata);
|
||||
int qm357xx_rom_unpack_fw_macro_pkg(const struct firmware *fw,
|
||||
struct unstitched_firmware *all_fws);
|
||||
int qm357xx_rom_flash_dbg_cert(struct qmrom_handle *handle,
|
||||
struct firmware *dbg_cert);
|
||||
int qm357xx_rom_erase_dbg_cert(struct qmrom_handle *handle);
|
||||
int qm357xx_rom_flash_fw(struct qmrom_handle *handle,
|
||||
const struct firmware *fw);
|
||||
int qm357xx_rom_flash_unstitched_fw(struct qmrom_handle *handle,
|
||||
const struct unstitched_firmware *fw);
|
||||
|
||||
int qm357xx_rom_write_cmd(struct qmrom_handle *handle, uint8_t cmd);
|
||||
int qm357xx_rom_write_cmd32(struct qmrom_handle *handle, uint32_t cmd);
|
||||
int qm357xx_rom_write_size_cmd(struct qmrom_handle *handle, uint8_t cmd,
|
||||
uint16_t data_size, const char *data);
|
||||
int qm357xx_rom_write_size_cmd32(struct qmrom_handle *handle, uint32_t cmd,
|
||||
uint16_t data_size, const char *data);
|
||||
|
||||
#endif /* __QM357XX_ROM_H__ */
|
||||
174
include/linux/uwb/qm358xx_firmware_pack_handling.h
Normal file
174
include/linux/uwb/qm358xx_firmware_pack_handling.h
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
/*
|
||||
* Copyright 2021 Qorvo US, Inc.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0 OR Apache-2.0
|
||||
*
|
||||
* This file is provided under the Apache License 2.0, or the
|
||||
* GNU General Public License v2.0.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _QM358XX_FIRMWARE_PACK_HANDLING_H_
|
||||
#define _QM358XX_FIRMWARE_PACK_HANDLING_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/*!
|
||||
@addtogroup secureboot_firmware_pack_handling
|
||||
@{
|
||||
*/
|
||||
|
||||
/*! Cert magic word conversion macro */
|
||||
#define MAGIC_STR_TO_U32(x) ((x[3]) | (x[2] << 8) | (x[1] << 16) | (x[0] << 24))
|
||||
|
||||
/*! Cert version word conversion macro */
|
||||
#define CERT_VER_TO_U32(maj, min) (((maj) << 16) | ((min) << 24))
|
||||
|
||||
// Field values for firmware package
|
||||
#define CRYPTO_FIRMWARE_PACK_MAGIC_VALUE MAGIC_STR_TO_U32("CFWP")
|
||||
|
||||
// Package size defines
|
||||
#define CRYPTO_FIRMWARE_PACK_ENC_KEY_L2_ENC_SIZE 64u
|
||||
#define CRYPTO_FIRMWARE_PACK_ENC_DATA_SIZE 16u
|
||||
#define CRYPTO_FIRMWARE_PACK_FW_VERSION_SIZE 32u
|
||||
#define CRYPTO_FIRMWARE_PACK_TAG_SIZE 16u
|
||||
|
||||
// Firmware image size define
|
||||
#define CRYPTO_FW_PKG_IMG_HDR_IMG_NUM_MAX 8u
|
||||
|
||||
#define CRYPTO_FIRMWARE_PACK_HEADER_SIZE sizeof(fw_pkg_hdr_t)
|
||||
#define CRYPTO_FIRMWARE_IMAGE_CERT_SIZE sizeof(fw_pkg_img_hdr_t)
|
||||
|
||||
// Field values for firmware chunks
|
||||
#define CRYPTO_FIRMWARE_CHUNK_MAGIC_VALUE MAGIC_STR_TO_U32("CFWC")
|
||||
#define CRYPTO_FIRMWARE_CHUNK_VERSION CERT_VER_TO_U32(1, 0)
|
||||
#define CRYPTO_FIRMWARE_CHUNK_MIN_SIZE 16u
|
||||
#define CRYPTO_FIRMWARE_CHUNK_HDR_SIZE sizeof(fw_pkg_payload_chunk_hdr_t)
|
||||
|
||||
/*! Firmware Package Header fields */
|
||||
typedef struct {
|
||||
uint32_t magic; /**< Magic number. */
|
||||
uint32_t version; /**< Version. */
|
||||
uint8_t package_type; /**< Package type. */
|
||||
uint8_t enc_mode; /**< Encryption mode. */
|
||||
uint8_t enc_algo; /**< Encryption algorithm. */
|
||||
union {
|
||||
uint8_t enc_key_l2_cfg; /**< Code encryption L2 key config. */
|
||||
struct {
|
||||
uint8_t use : 1; /**< Use Kce_l2 for decryption. */
|
||||
uint8_t lock : 1; /**< Lock using Kce_l2 for decryption. */
|
||||
uint8_t : 6;
|
||||
} enc_key_l2_cfg_b;
|
||||
};
|
||||
uint8_t enc_data
|
||||
[CRYPTO_FIRMWARE_PACK_ENC_DATA_SIZE]; /**< Encryption data. */
|
||||
uint8_t enc_key_l2
|
||||
[CRYPTO_FIRMWARE_PACK_ENC_KEY_L2_ENC_SIZE]; /**< Code encryption L2 key data. */
|
||||
uint8_t fw_version
|
||||
[CRYPTO_FIRMWARE_PACK_FW_VERSION_SIZE]; /**< Firwmare version included in the package. */
|
||||
uint32_t payload_len; /**< Payload length. */
|
||||
uint8_t tag[CRYPTO_FIRMWARE_PACK_TAG_SIZE]; /**< AES-CMAC Tag. */
|
||||
} fw_pkg_hdr_t;
|
||||
|
||||
/*! Firmware Image Metadata fields */
|
||||
typedef struct {
|
||||
uint32_t offset; /**< Offset. */
|
||||
uint32_t length; /**< Length. */
|
||||
} fw_image_metadata_t;
|
||||
|
||||
/*! Firmware Image Header fields */
|
||||
typedef struct {
|
||||
uint32_t magic; /**< Magic number. */
|
||||
uint32_t version; /**< Version number. */
|
||||
uint32_t cert_chain_offset; /**< Certificate chain offset. */
|
||||
uint16_t cert_chain_length; /**< Certificate chain length. */
|
||||
uint8_t img_num; /**< Number of images. */
|
||||
uint8_t reserved; /**< Reserved data. */
|
||||
fw_image_metadata_t metadata
|
||||
[CRYPTO_FW_PKG_IMG_HDR_IMG_NUM_MAX]; /**< Firmware image metadata. */
|
||||
} fw_pkg_img_hdr_t;
|
||||
|
||||
/*! Firmware Chunk fields */
|
||||
typedef struct {
|
||||
uint32_t magic; /**< Magic number. */
|
||||
uint32_t version; /**< Version number. */
|
||||
uint32_t length; /**< Length. */
|
||||
} fw_pkg_payload_chunk_hdr_t;
|
||||
|
||||
#define CRYPTO_IMAGES_CERT_PUBLIC_KEY_SIZE 384
|
||||
#define CRYPTO_KEY_HASH_TRUNC_SIZE_BYTES 16u
|
||||
#define CRYPTO_SHA256_SIZE_BYTES 32u
|
||||
#define CRYPTO_IMAGES_CERT_MIN_IMAGE_NUMBER 1u
|
||||
#define CRYPTO_IMAGES_CERT_PKG_SIZE sizeof(cert_chain_struct)
|
||||
|
||||
/*! Certificate or file header struct */
|
||||
typedef struct {
|
||||
uint32_t magic; /**< Magic value. */
|
||||
uint32_t version; /**< Certificate version. */
|
||||
uint32_t size; /**< Certificate size. */
|
||||
} cert_common_header_struct;
|
||||
|
||||
/*! Key certificate fields */
|
||||
typedef struct {
|
||||
cert_common_header_struct
|
||||
header; /**< Pointer to key certificate header data. */
|
||||
uint8_t rsa_public_key
|
||||
[CRYPTO_IMAGES_CERT_PUBLIC_KEY_SIZE]; /**< RSA public key data. */
|
||||
uint32_t context_hash_id; /**< Context and hash ID combined in one uint32. */
|
||||
uint32_t customer_id; /**< Customer ID. */
|
||||
uint32_t product_id; /**< Product ID. */
|
||||
uint8_t sec_version; /**< Security version number. */
|
||||
uint8_t reserved[3]; /**< Reserved data. */
|
||||
uint32_t arb_version; /**< Anti-rollback version number. */
|
||||
uint8_t hash[CRYPTO_KEY_HASH_TRUNC_SIZE_BYTES]; /**< Content cert public key hash. */
|
||||
uint8_t rsa_signature
|
||||
[CRYPTO_IMAGES_CERT_PUBLIC_KEY_SIZE]; /**< RSA signature data. */
|
||||
} cert_key_struct;
|
||||
|
||||
/*! Content of metadata fields */
|
||||
typedef struct {
|
||||
uint8_t img_hash[CRYPTO_SHA256_SIZE_BYTES]; /**< Image hash result data. */
|
||||
uint32_t flash_addr; /**< Flash (RRAM) address. */
|
||||
uint32_t max_len; /**< Maximum length. */
|
||||
uint32_t len; /**< Length. */
|
||||
uint32_t load_verify_scheme; /**< Schema to load and verify firmware image. */
|
||||
uint32_t load_addr; /**< SRAM load address. */
|
||||
} cert_content_img_metadata_t;
|
||||
|
||||
/*! Content certificate fields */
|
||||
typedef struct {
|
||||
cert_common_header_struct
|
||||
header; /**< Pointer to content certificate header data. */
|
||||
uint8_t rsa_public_key
|
||||
[CRYPTO_IMAGES_CERT_PUBLIC_KEY_SIZE]; /**< RSA public key data. */
|
||||
uint32_t context; /**< Context. */
|
||||
uint32_t customer_id; /**< Customer ID. */
|
||||
uint32_t product_id; /**< Product ID. */
|
||||
uint8_t sec_version; /**< Security version number. */
|
||||
uint8_t reserved[3]; /**< Reserved data. */
|
||||
uint32_t arb_version; /**< Anti-rollback version number. */
|
||||
uint32_t img_num; /**< Number of images. */
|
||||
cert_content_img_metadata_t img_metadata
|
||||
[CRYPTO_IMAGES_CERT_MIN_IMAGE_NUMBER]; /**< Image metadata. */
|
||||
} cert_content_struct;
|
||||
|
||||
/*! Certificate chain fields */
|
||||
typedef struct {
|
||||
cert_key_struct cert_key; /**< Pointer to key certificate data. */
|
||||
cert_content_struct
|
||||
cert_content; /**< Pointer to content certificate data. */
|
||||
} cert_chain_struct;
|
||||
|
||||
/**
|
||||
@}
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* _QM358XX_FIRMWARE_PACK_HANDLING_H_ */
|
||||
70
include/linux/uwb/qm358xx_rom.h
Normal file
70
include/linux/uwb/qm358xx_rom.h
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
// SPDX-License-Identifier: GPL-2.0 OR Apache-2.0
|
||||
/*
|
||||
* Copyright 2021 Qorvo US, Inc.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __QM358XX_ROM_H__
|
||||
#define __QM358XX_ROM_H__
|
||||
|
||||
#define QM358XX_ROM_UUID_LEN 0x10
|
||||
|
||||
/* Life cycle state definitions. */
|
||||
#define LCS_TEST (0x00) /* Previously: CC_BSV_CHIP_MANUFACTURE_LCS */
|
||||
#define LCS_OEM (0x01) /* Previously: CC_BSV_DEVICE_MANUFACTURE_LCS */
|
||||
#define LCS_USER (0x02) /* Previously: CC_BSV_SECURE_LCS */
|
||||
#define LCS_RMA (0x03) /* Previously: CC_BSV_RMA_LCS */
|
||||
|
||||
typedef int (*gen_secrets_fn)(struct qmrom_handle *handle);
|
||||
typedef int (*load_asset_pkg_fn)(struct qmrom_handle *handle,
|
||||
const struct firmware *asset_pkg);
|
||||
typedef int (*load_fw_pkg_fn)(struct qmrom_handle *handle,
|
||||
const struct firmware *fw_pkg);
|
||||
typedef int (*load_secure_dbg_pkg_fn)(struct qmrom_handle *handle,
|
||||
struct firmware *dbg_pkg);
|
||||
typedef int (*erase_secure_dbg_pkg_fn)(struct qmrom_handle *handle);
|
||||
typedef int (*run_test_mode_fn)(struct qmrom_handle *handle);
|
||||
typedef int (*load_sram_fw_fn)(struct qmrom_handle *handle,
|
||||
const struct firmware *sram_fw);
|
||||
|
||||
struct qm358xx_rom_code_ops {
|
||||
gen_secrets_fn gen_secrets;
|
||||
load_asset_pkg_fn load_asset_pkg;
|
||||
load_fw_pkg_fn load_fw_pkg;
|
||||
load_secure_dbg_pkg_fn load_secure_dbg_pkg;
|
||||
erase_secure_dbg_pkg_fn erase_secure_dbg_pkg;
|
||||
run_test_mode_fn run_test_mode;
|
||||
load_sram_fw_fn load_sram_fw;
|
||||
};
|
||||
|
||||
struct qm358xx_soc_infos {
|
||||
uint8_t uuid[QM358XX_ROM_UUID_LEN];
|
||||
uint32_t customer_id;
|
||||
uint32_t product_id;
|
||||
uint32_t arb_ver_icv;
|
||||
uint32_t arb_ver_oem;
|
||||
uint8_t lcs_state;
|
||||
uint8_t working_model;
|
||||
uint8_t business_ctx;
|
||||
uint8_t secure_debug;
|
||||
uint8_t secure_boot_oem;
|
||||
uint8_t fw_encryption_oem;
|
||||
uint8_t lock_debug;
|
||||
uint8_t sec_ver_icv;
|
||||
uint8_t sec_ver_oem;
|
||||
uint8_t enc_l2_lock;
|
||||
};
|
||||
|
||||
int qm358xx_rom_gen_secrets(struct qmrom_handle *handle);
|
||||
int qm358xx_rom_load_asset_pkg(struct qmrom_handle *handle,
|
||||
struct firmware *asset_pkg);
|
||||
int qm358xx_rom_load_fw_pkg(struct qmrom_handle *handle,
|
||||
struct firmware *fw_pkg);
|
||||
int qm358xx_rom_load_secure_dbg_pkg(struct qmrom_handle *handle,
|
||||
struct firmware *dbg_pkg);
|
||||
int qm358xx_rom_erase_secure_dbg_pkg(struct qmrom_handle *handle);
|
||||
int qm358xx_rom_run_test_mode(struct qmrom_handle *handle);
|
||||
int qm358xx_rom_load_sram_fw(struct qmrom_handle *handle,
|
||||
struct firmware *sram_fw);
|
||||
|
||||
#endif /* __QM358XX_ROM_H__ */
|
||||
118
include/linux/uwb/qmrom.h
Normal file
118
include/linux/uwb/qmrom.h
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
// SPDX-License-Identifier: GPL-2.0 OR Apache-2.0
|
||||
/*
|
||||
* Copyright 2021 Qorvo US, Inc.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __QMROM_H__
|
||||
#define __QMROM_H__
|
||||
|
||||
#ifndef __KERNEL__
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <byteswap.h>
|
||||
#include <inttypes.h>
|
||||
#else
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/string.h>
|
||||
#define bswap_16 be16_to_cpu
|
||||
#define PRIu32 "u"
|
||||
#endif
|
||||
|
||||
#include "qmrom_error.h"
|
||||
|
||||
#undef CHECK_STCS
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wpedantic"
|
||||
|
||||
enum chip_revision_e {
|
||||
CHIP_REVISION_A0 = 0xA0,
|
||||
CHIP_REVISION_B0 = 0xB0,
|
||||
CHIP_REVISION_C0 = 0xC0,
|
||||
CHIP_REVISION_C2 = 0xC2,
|
||||
CHIP_REVISION_UNKNOWN = 0xFF
|
||||
};
|
||||
|
||||
enum device_generation_e {
|
||||
DEVICE_GEN_QM357XX,
|
||||
DEVICE_GEN_QM358XX,
|
||||
DEVICE_GEN_UNKNOWN = 0xFF
|
||||
};
|
||||
|
||||
struct qmrom_handle;
|
||||
|
||||
#include "qm357xx_rom.h"
|
||||
#include "qm358xx_rom.h"
|
||||
|
||||
#define SSTC2UINT32(handle, offset) \
|
||||
({ \
|
||||
uint32_t tmp = 0xbeefdeed; \
|
||||
if ((handle)->sstc->len >= (offset) + sizeof(tmp)) \
|
||||
memcpy(&tmp, &(handle)->sstc->payload[(offset)], \
|
||||
sizeof(tmp)); \
|
||||
tmp; \
|
||||
})
|
||||
|
||||
#define SSTC2UINT16(handle, offset) \
|
||||
({ \
|
||||
uint16_t tmp = 0xbeed; \
|
||||
if ((handle)->sstc->len >= (offset) + sizeof(tmp)) \
|
||||
memcpy(&tmp, &(handle)->sstc->payload[(offset)], \
|
||||
sizeof(tmp)); \
|
||||
tmp; \
|
||||
})
|
||||
|
||||
/* Those functions allow the libqmrom to call
|
||||
* device specific functions
|
||||
*/
|
||||
typedef int (*reset_device_fn)(void *handle);
|
||||
|
||||
struct device_ops {
|
||||
reset_device_fn reset;
|
||||
};
|
||||
|
||||
struct qmrom_handle {
|
||||
void *spi_handle;
|
||||
void *reset_handle;
|
||||
void *ss_rdy_handle;
|
||||
int comms_retries;
|
||||
enum device_generation_e dev_gen;
|
||||
enum chip_revision_e chip_rev;
|
||||
uint16_t device_version;
|
||||
struct device_ops dev_ops;
|
||||
int spi_speed;
|
||||
union {
|
||||
struct qm357xx_rom_code_ops qm357xx_rom_ops;
|
||||
struct qm358xx_rom_code_ops qm358xx_rom_ops;
|
||||
};
|
||||
struct stc *hstc;
|
||||
struct stc *sstc;
|
||||
union {
|
||||
struct qm357xx_soc_infos qm357xx_soc_info;
|
||||
struct qm358xx_soc_infos qm358xx_soc_info;
|
||||
};
|
||||
bool is_be;
|
||||
};
|
||||
|
||||
struct qmrom_handle *qmrom_init(void *spi_handle, void *reset_handle,
|
||||
void *ss_rdy_handle, int spi_speed,
|
||||
int comms_retries, reset_device_fn reset,
|
||||
enum device_generation_e dev_gen_hint);
|
||||
void qmrom_deinit(struct qmrom_handle *handle);
|
||||
int qmrom_reboot_bootloader(struct qmrom_handle *handle);
|
||||
|
||||
int qmrom_pre_read(struct qmrom_handle *handle);
|
||||
int qmrom_read(struct qmrom_handle *handle);
|
||||
|
||||
#ifdef CHECK_STCS
|
||||
void check_stcs(const char *func, int line, struct qmrom_handle *h);
|
||||
#else
|
||||
#define check_stcs(f, l, h)
|
||||
#endif
|
||||
#endif /* __QMROM_H__ */
|
||||
21
include/linux/uwb/qmrom_error.h
Normal file
21
include/linux/uwb/qmrom_error.h
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright 2021 Qorvo US, Inc.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0 OR Apache-2.0
|
||||
*
|
||||
* This file is provided under the Apache License 2.0, or the
|
||||
* GNU General Public License v2.0.
|
||||
*
|
||||
*/
|
||||
#ifndef __QMROM_ERROR_H__
|
||||
#define __QMROM_ERROR_H__
|
||||
|
||||
#define SPI_ERR_BASE -8000
|
||||
#define PEG_ERR_BASE -9000
|
||||
#define SPI_PROTO_ERR_BASE -10000
|
||||
|
||||
#define IS_PTR_ERROR(ptr) (((intptr_t)ptr) <= 0)
|
||||
#define PTR2ERROR(ptr) ((int)((intptr_t)ptr))
|
||||
#define ERROR2PTR(error) ((void *)((intptr_t)error))
|
||||
|
||||
#endif /* __QMROM_ERROR_H__ */
|
||||
94
include/linux/uwb/qmrom_log.h
Normal file
94
include/linux/uwb/qmrom_log.h
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Copyright 2021 Qorvo US, Inc.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0 OR Apache-2.0
|
||||
*
|
||||
* This file is provided under the Apache License 2.0, or the
|
||||
* GNU General Public License v2.0.
|
||||
*
|
||||
*/
|
||||
#ifndef __QMROM_LOGGER_H__
|
||||
#define __QMROM_LOGGER_H__
|
||||
|
||||
enum log_level_e {
|
||||
LOG_QUIET = 0,
|
||||
LOG_ERR,
|
||||
LOG_WARN,
|
||||
LOG_INFO,
|
||||
LOG_DBG,
|
||||
LOG_LVL_MAX = LOG_DBG,
|
||||
};
|
||||
|
||||
extern enum log_level_e __log_level__;
|
||||
|
||||
void set_log_level(enum log_level_e lvl);
|
||||
|
||||
static inline int is_debug_mode(void)
|
||||
{
|
||||
return __log_level__ >= LOG_DBG;
|
||||
}
|
||||
|
||||
static inline int is_log_level_allowed(enum log_level_e lvl)
|
||||
{
|
||||
return (__log_level__ >= lvl);
|
||||
}
|
||||
|
||||
void hexdump(enum log_level_e lvl, void *address, unsigned short length);
|
||||
void hexrawdump(enum log_level_e lvl, void *address, unsigned short length);
|
||||
|
||||
#ifndef __KERNEL__
|
||||
#include <stdio.h>
|
||||
#define LOG_ERR(...) \
|
||||
do { \
|
||||
if (__log_level__ > LOG_QUIET) \
|
||||
fprintf(stderr, __VA_ARGS__); \
|
||||
} while (0)
|
||||
#define LOG_WARN(...) \
|
||||
do { \
|
||||
if (__log_level__ > LOG_ERR) \
|
||||
fprintf(stderr, __VA_ARGS__); \
|
||||
} while (0)
|
||||
#define LOG_INFO(...) \
|
||||
do { \
|
||||
if (__log_level__ > LOG_WARN) \
|
||||
fprintf(stdout, __VA_ARGS__); \
|
||||
} while (0)
|
||||
#define LOG_DBG(...) \
|
||||
do { \
|
||||
if (__log_level__ > LOG_INFO) \
|
||||
fprintf(stdout, __VA_ARGS__); \
|
||||
} while (0)
|
||||
#else
|
||||
#include <linux/device.h>
|
||||
|
||||
extern struct device *__qmrom_log_dev__;
|
||||
|
||||
void qmrom_set_log_device(struct device *dev, enum log_level_e lvl);
|
||||
|
||||
#define LOG_ERR(...) \
|
||||
do { \
|
||||
if (__qmrom_log_dev__) \
|
||||
if (__log_level__ > LOG_QUIET) \
|
||||
dev_err(__qmrom_log_dev__, __VA_ARGS__); \
|
||||
} while (0)
|
||||
#define LOG_WARN(...) \
|
||||
do { \
|
||||
if (__qmrom_log_dev__) \
|
||||
if (__log_level__ > LOG_ERR) \
|
||||
dev_warn(__qmrom_log_dev__, __VA_ARGS__); \
|
||||
} while (0)
|
||||
#define LOG_INFO(...) \
|
||||
do { \
|
||||
if (__qmrom_log_dev__) \
|
||||
if (__log_level__ > LOG_WARN) \
|
||||
dev_info(__qmrom_log_dev__, __VA_ARGS__); \
|
||||
} while (0)
|
||||
#define LOG_DBG(...) \
|
||||
do { \
|
||||
if (__qmrom_log_dev__) \
|
||||
if (__log_level__ > LOG_INFO) \
|
||||
dev_dbg(__qmrom_log_dev__, __VA_ARGS__); \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
#endif /* __QMROM_LOGGER_H__ */
|
||||
62
include/linux/uwb/qmrom_spi.h
Normal file
62
include/linux/uwb/qmrom_spi.h
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Copyright 2021 Qorvo US, Inc.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0 OR Apache-2.0
|
||||
*
|
||||
* This file is provided under the Apache License 2.0, or the
|
||||
* GNU General Public License v2.0.
|
||||
*
|
||||
*/
|
||||
#ifndef __QMROM_SPI_H__
|
||||
#define __QMROM_SPI_H__
|
||||
|
||||
#include <stddef.h>
|
||||
#include "qmrom_error.h"
|
||||
#include "qmrom.h"
|
||||
|
||||
#ifndef __KERNEL__
|
||||
struct firmware {
|
||||
size_t size;
|
||||
const uint8_t *data;
|
||||
};
|
||||
#else
|
||||
#include <linux/firmware.h>
|
||||
#endif
|
||||
|
||||
#define DEFAULT_SPI_LATENCY_MS 2
|
||||
|
||||
#define SPI_ERR_NOCHAN SPI_ERR_BASE - 1
|
||||
#define SPI_ERR_INFNOTFOUND SPI_ERR_BASE - 2
|
||||
#define SPI_ERR_NOMEM SPI_ERR_BASE - 3
|
||||
#define SPI_ERR_READ_INCOMPLETE SPI_ERR_BASE - 4
|
||||
#define SPI_ERR_GPIO_WRITE_CMD_INCOMPLETE SPI_ERR_BASE - 5
|
||||
#define SPI_ERR_GPIO_READ_CMD_INCOMPLETE SPI_ERR_BASE - 6
|
||||
#define SPI_ERR_READY_LINE_TIMEOUT SPI_ERR_BASE - 7
|
||||
#define SPI_ERR_WRITE_INCOMPLETE SPI_ERR_BASE - 8
|
||||
#define SPI_ERR_RW_INCOMPLETE SPI_ERR_BASE - 9
|
||||
#define SPI_ERR_INVALID_STC_LEN SPI_ERR_BASE - 10
|
||||
#define SPI_ERR_WAIT_READY_TIMEOUT SPI_ERR_BASE - 11
|
||||
|
||||
/*Make sure that the error ranges don't overlap */
|
||||
#define SPI_ERR_LIB_BASE (SPI_ERR_BASE - 500)
|
||||
#define SPI_ERR_LIB(rc) (SPI_ERR_LIB_BASE - rc)
|
||||
|
||||
void *qmrom_spi_init(int spi_interface_index);
|
||||
void qmrom_spi_uninit(void *handle);
|
||||
int qmrom_spi_read(void *handle, char *buffer, size_t size);
|
||||
int qmrom_spi_write(void *handle, const char *buffer, size_t size);
|
||||
int qmrom_spi_transfer(void *handle, char *rbuf, const char *wbuf, size_t size);
|
||||
int qmrom_spi_set_cs_level(void *handle, int level);
|
||||
int qmrom_spi_reset_device(void *reset_handle);
|
||||
const struct firmware *qmrom_spi_get_firmware(void *handle,
|
||||
struct qmrom_handle *qmrom_h,
|
||||
bool *is_fw_updater,
|
||||
bool use_prod_fw);
|
||||
const struct firmware *qmrom_spi_get_firmware_package(void *handle,
|
||||
int lcs_state);
|
||||
void qmrom_spi_release_firmware(const struct firmware *fw);
|
||||
int qmrom_spi_wait_for_ready_line(void *handle, unsigned int timeout_ms);
|
||||
void qmrom_spi_set_freq(unsigned int freq);
|
||||
unsigned int qmrom_spi_get_freq(void);
|
||||
|
||||
#endif /* __QMROM_SPI_H__ */
|
||||
56
include/linux/uwb/qmrom_utils.h
Normal file
56
include/linux/uwb/qmrom_utils.h
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright 2021 Qorvo US, Inc.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0 OR Apache-2.0
|
||||
*
|
||||
* This file is provided under the Apache License 2.0, or the
|
||||
* GNU General Public License v2.0.
|
||||
*
|
||||
*/
|
||||
#ifndef __QMROM_UTILS_H__
|
||||
#define __QMROM_UTILS_H__
|
||||
|
||||
#ifndef __KERNEL__
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifndef __linux__
|
||||
extern void usleep(unsigned int us);
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#define qmrom_msleep(ms) \
|
||||
do { \
|
||||
usleep(ms * 1000); \
|
||||
} while (0)
|
||||
|
||||
#define qmrom_alloc(ptr, size) \
|
||||
do { \
|
||||
ptr = calloc(1, size); \
|
||||
} while (0)
|
||||
|
||||
#define qmrom_free free
|
||||
|
||||
#define qmrom_data_dma_able(d) true
|
||||
#else
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/mm.h>
|
||||
|
||||
#define qmrom_msleep(ms) \
|
||||
do { \
|
||||
usleep_range(ms * 1000, ms * 1000); \
|
||||
} while (0)
|
||||
|
||||
#define qmrom_alloc(ptr, size) \
|
||||
do { \
|
||||
ptr = kzalloc(size, GFP_KERNEL | GFP_DMA); \
|
||||
} while (0)
|
||||
|
||||
#define qmrom_free kfree
|
||||
|
||||
#define qmrom_data_dma_able(d) !is_vmalloc_addr(d)
|
||||
#endif
|
||||
#endif /* __QMROM_UTILS_H__ */
|
||||
92
include/linux/uwb/spi_rom_protocol.h
Normal file
92
include/linux/uwb/spi_rom_protocol.h
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* Copyright 2021 Qorvo US, Inc.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0 OR Apache-2.0
|
||||
*
|
||||
* This file is provided under the Apache License 2.0, or the
|
||||
* GNU General Public License v2.0.
|
||||
*
|
||||
*/
|
||||
#ifndef __SPI_ROM_PROTOCOL_H__
|
||||
#define __SPI_ROM_PROTOCOL_H__
|
||||
|
||||
#include "qmrom_error.h"
|
||||
#include "qmrom.h"
|
||||
|
||||
#ifndef __KERNEL__
|
||||
#include <stdint.h>
|
||||
#else
|
||||
#include <linux/types.h>
|
||||
#endif
|
||||
|
||||
#define SPI_PROTO_WRONG_RESP SPI_PROTO_ERR_BASE - 1
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wpedantic"
|
||||
struct stc {
|
||||
union {
|
||||
struct {
|
||||
union {
|
||||
struct {
|
||||
uint8_t reserved : 5;
|
||||
uint8_t read : 1; // Read indication: Set to one by the host to tell the SOC SPI driver that this transaction is a read, otherwise it is set to zero.
|
||||
uint8_t pre_read : 1; // Pre-read indication: Set to one by the host to tell the SOC SPI driver that the next transaction will be a read, otherwise it is set to zero
|
||||
uint8_t write : 1; // Write indication: Set to one by the host when it is doing a write transaction. Set to zero when the host is not doing a write.
|
||||
} host_flags;
|
||||
struct {
|
||||
uint8_t reserved : 4;
|
||||
uint8_t err : 1; // Error indication: This is set to one by the SOC SPI driver to tell the HOST that it has detected some error in the SPI protocol.
|
||||
uint8_t ready : 1; //
|
||||
uint8_t out_active : 1; // Output active indication: Set to one by the SOC SPI driver to tell the HOST that it is outputting data on MISO line and expecting that the host is doing a read transaction at this time. This is set to zero for all other transactions.
|
||||
uint8_t out_waiting : 1; // Output data waiting indication: Set to one by the SOC SPI driver to tell the HOST there is data awaiting reading. This is set to zero when there is no data pending output.
|
||||
} soc_flags;
|
||||
uint8_t raw_flags;
|
||||
};
|
||||
uint8_t ul;
|
||||
uint16_t len;
|
||||
};
|
||||
uint32_t all;
|
||||
};
|
||||
uint8_t payload[0];
|
||||
} __attribute__((packed));
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
/* Host to Soc (HS) masks */
|
||||
#define SPI_HS_WRITE_CMD_BIT_MASK 0x80
|
||||
#define SPI_HS_PRD_CMD_BIT_MASK 0x40
|
||||
#define SPI_HS_RD_CMD_BIT_MASK 0x20
|
||||
|
||||
/* Soc to Host (SH) masks */
|
||||
#define SPI_SH_ODW_BIT_MASK 0x80
|
||||
#define SPI_SH_ACTIVE_BIT_MASK 0x40
|
||||
#define SPI_SH_READY_CMD_BIT_MASK 0x20
|
||||
#define SPI_DEVICE_READY_FLAGS SPI_SH_ODW_BIT_MASK
|
||||
|
||||
/* Communication parameters */
|
||||
#define MAX_STC_FRAME_LEN 2048
|
||||
#define MAX_STC_PAYLOAD_LEN (MAX_STC_FRAME_LEN - sizeof(struct stc))
|
||||
#define SPI_NUM_READS_FOR_READY 1
|
||||
#define SPI_NUM_FAILS_RETRY 4
|
||||
#define SPI_ET_PROTOCOL 5
|
||||
#define SPI_RST_LOW_DELAY_MS 20
|
||||
#define SPI_INTERCMD_DELAY_MS 1
|
||||
#define SPI_DEVICE_POLL_RETRY 10
|
||||
#define SPI_READY_TIMEOUT_MS 50
|
||||
#define SPI_ET_VERSION_LOCATION 0x601f0000
|
||||
|
||||
/* ROM boot proto */
|
||||
#define SPI_ROM_READ_VERSION_SIZE_A0 3
|
||||
#define SPI_ROM_READ_IMAGE_CERT_SIZE_A0 3
|
||||
#define SPI_ROM_READ_IMAGE_SIZE_SIZE_A0 3
|
||||
|
||||
#define SPI_ROM_READ_VERSION_SIZE_B0 5
|
||||
#define SPI_ROM_READ_INFO_SIZE_B0 50
|
||||
#define SPI_ROM_READ_IMAGE_CERT_SIZE_B0 1
|
||||
#define SPI_ROM_READ_IMAGE_SIZE_SIZE_B0 1
|
||||
|
||||
#define SPI_ROM_WRITE_KEY_CERT_SIZE 6
|
||||
#define SPI_ROM_WRITE_IMAGE_CERT_SIZE 40
|
||||
#define SPI_ROM_WRITE_IMAGE_SIZE_SIZE 4
|
||||
#define SPI_ROM_DBG_CERT_SIZE_SIZE 5
|
||||
|
||||
#endif /* __SPI_ROM_PROTOCOL_H__ */
|
||||
Loading…
Reference in a new issue