| /* Copyright (c) 2011-2017, The Linux Foundation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 and |
| * only version 2 as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| */ |
| |
| /* Smart-Peripheral-Switch (SPS) Module. */ |
| |
| #include <linux/types.h> /* u32 */ |
| #include <linux/kernel.h> /* pr_info() */ |
| #include <linux/module.h> /* module_init() */ |
| #include <linux/slab.h> /* kzalloc() */ |
| #include <linux/mutex.h> /* mutex */ |
| #include <linux/device.h> /* device */ |
| #include <linux/fs.h> /* alloc_chrdev_region() */ |
| #include <linux/list.h> /* list_head */ |
| #include <linux/memory.h> /* memset */ |
| #include <linux/io.h> /* ioremap() */ |
| #include <linux/clk.h> /* clk_enable() */ |
| #include <linux/platform_device.h> /* platform_get_resource_byname() */ |
| #include <linux/debugfs.h> |
| #include <linux/uaccess.h> |
| #include <linux/of.h> |
| #include <linux/of_device.h> |
| |
| #include "sps_bam.h" |
| #include "spsi.h" |
| #include "sps_core.h" |
| |
| #define SPS_DRV_NAME "msm_sps" /* must match the platform_device name */ |
| |
| /** |
| * SPS driver state |
| */ |
| struct sps_drv *sps; |
| |
| u32 d_type; |
| bool enhd_pipe; |
| bool imem; |
| enum sps_bam_type bam_type; |
| enum sps_bam_type bam_types[] = {SPS_BAM_LEGACY, SPS_BAM_NDP, SPS_BAM_NDP_4K}; |
| |
| static void sps_device_de_init(void); |
| |
| #ifdef CONFIG_DEBUG_FS |
| u8 debugfs_record_enabled; |
| u8 logging_option; |
| u8 debug_level_option; |
| u8 print_limit_option; |
| u8 reg_dump_option; |
| u32 testbus_sel; |
| u32 bam_pipe_sel; |
| u32 desc_option; |
| /* |
| * Specifies range of log level from level 0 to level 3 to have fine-granularity |
| * for logging to serve all BAM use cases. |
| */ |
| u32 log_level_sel; |
| |
| static char *debugfs_buf; |
| static u32 debugfs_buf_size; |
| static u32 debugfs_buf_used; |
| static int wraparound; |
| static struct mutex sps_debugfs_lock; |
| |
| struct dentry *dent; |
| struct dentry *dfile_info; |
| struct dentry *dfile_logging_option; |
| struct dentry *dfile_debug_level_option; |
| struct dentry *dfile_print_limit_option; |
| struct dentry *dfile_reg_dump_option; |
| struct dentry *dfile_testbus_sel; |
| struct dentry *dfile_bam_pipe_sel; |
| struct dentry *dfile_desc_option; |
| struct dentry *dfile_bam_addr; |
| struct dentry *dfile_log_level_sel; |
| |
| static struct sps_bam *phy2bam(phys_addr_t phys_addr); |
| |
| /* record debug info for debugfs */ |
| void sps_debugfs_record(const char *msg) |
| { |
| mutex_lock(&sps_debugfs_lock); |
| if (debugfs_record_enabled) { |
| if (debugfs_buf_used + MAX_MSG_LEN >= debugfs_buf_size) { |
| debugfs_buf_used = 0; |
| wraparound = true; |
| } |
| debugfs_buf_used += scnprintf(debugfs_buf + debugfs_buf_used, |
| debugfs_buf_size - debugfs_buf_used, msg); |
| |
| if (wraparound) |
| scnprintf(debugfs_buf + debugfs_buf_used, |
| debugfs_buf_size - debugfs_buf_used, |
| "\n**** end line of sps log ****\n\n"); |
| } |
| mutex_unlock(&sps_debugfs_lock); |
| } |
| |
| /* read the recorded debug info to userspace */ |
| static ssize_t sps_read_info(struct file *file, char __user *ubuf, |
| size_t count, loff_t *ppos) |
| { |
| int ret = 0; |
| int size; |
| |
| mutex_lock(&sps_debugfs_lock); |
| if (debugfs_record_enabled) { |
| if (wraparound) |
| size = debugfs_buf_size - MAX_MSG_LEN; |
| else |
| size = debugfs_buf_used; |
| |
| ret = simple_read_from_buffer(ubuf, count, ppos, |
| debugfs_buf, size); |
| } |
| mutex_unlock(&sps_debugfs_lock); |
| |
| return ret; |
| } |
| |
| /* |
| * set the buffer size (in KB) for debug info |
| */ |
| static ssize_t sps_set_info(struct file *file, const char __user *buf, |
| size_t count, loff_t *ppos) |
| { |
| unsigned long missing; |
| char str[MAX_MSG_LEN]; |
| int i; |
| u32 buf_size_kb = 0; |
| u32 new_buf_size; |
| u32 size = sizeof(str) < count ? sizeof(str) : count; |
| |
| memset(str, 0, sizeof(str)); |
| missing = copy_from_user(str, buf, size); |
| if (missing) |
| return -EFAULT; |
| |
| for (i = 0; i < sizeof(str) && (str[i] >= '0') && (str[i] <= '9'); ++i) |
| buf_size_kb = (buf_size_kb * 10) + (str[i] - '0'); |
| |
| pr_info("sps:debugfs: input buffer size is %dKB\n", buf_size_kb); |
| |
| if ((logging_option == 0) || (logging_option == 2)) { |
| pr_info("sps:debugfs: need to first turn on recording.\n"); |
| return -EFAULT; |
| } |
| |
| if (buf_size_kb < 1) { |
| pr_info("sps:debugfs:buffer size should be no less than 1KB\n"); |
| return -EFAULT; |
| } |
| |
| if (buf_size_kb > (INT_MAX/SZ_1K)) { |
| pr_err("sps:debugfs: buffer size is too large\n"); |
| return -EFAULT; |
| } |
| |
| new_buf_size = buf_size_kb * SZ_1K; |
| |
| mutex_lock(&sps_debugfs_lock); |
| if (debugfs_record_enabled) { |
| if (debugfs_buf_size == new_buf_size) { |
| /* need do nothing */ |
| pr_info( |
| "sps:debugfs: input buffer size is the same as before.\n" |
| ); |
| mutex_unlock(&sps_debugfs_lock); |
| return count; |
| } |
| /* release the current buffer */ |
| debugfs_record_enabled = false; |
| debugfs_buf_used = 0; |
| wraparound = false; |
| kfree(debugfs_buf); |
| debugfs_buf = NULL; |
| } |
| |
| /* allocate new buffer */ |
| debugfs_buf_size = new_buf_size; |
| |
| debugfs_buf = kzalloc(debugfs_buf_size, GFP_KERNEL); |
| if (!debugfs_buf) { |
| debugfs_buf_size = 0; |
| pr_err("sps:fail to allocate memory for debug_fs.\n"); |
| mutex_unlock(&sps_debugfs_lock); |
| return -ENOMEM; |
| } |
| |
| debugfs_buf_used = 0; |
| wraparound = false; |
| debugfs_record_enabled = true; |
| mutex_unlock(&sps_debugfs_lock); |
| |
| return count; |
| } |
| |
| const struct file_operations sps_info_ops = { |
| .read = sps_read_info, |
| .write = sps_set_info, |
| }; |
| |
| /* return the current logging option to userspace */ |
| static ssize_t sps_read_logging_option(struct file *file, char __user *ubuf, |
| size_t count, loff_t *ppos) |
| { |
| char value[MAX_MSG_LEN]; |
| int nbytes; |
| |
| nbytes = snprintf(value, MAX_MSG_LEN, "%d\n", logging_option); |
| |
| return simple_read_from_buffer(ubuf, count, ppos, value, nbytes); |
| } |
| |
| /* |
| * set the logging option |
| */ |
| static ssize_t sps_set_logging_option(struct file *file, const char __user *buf, |
| size_t count, loff_t *ppos) |
| { |
| unsigned long missing; |
| char str[MAX_MSG_LEN]; |
| int i; |
| u8 option = 0; |
| u32 size = sizeof(str) < count ? sizeof(str) : count; |
| |
| memset(str, 0, sizeof(str)); |
| missing = copy_from_user(str, buf, size); |
| if (missing) |
| return -EFAULT; |
| |
| for (i = 0; i < sizeof(str) && (str[i] >= '0') && (str[i] <= '9'); ++i) |
| option = (option * 10) + (str[i] - '0'); |
| |
| pr_info("sps:debugfs: try to change logging option to %d\n", option); |
| |
| if (option > 3) { |
| pr_err("sps:debugfs: invalid logging option:%d\n", option); |
| return count; |
| } |
| |
| mutex_lock(&sps_debugfs_lock); |
| if (((option == 0) || (option == 2)) && |
| ((logging_option == 1) || (logging_option == 3))) { |
| debugfs_record_enabled = false; |
| kfree(debugfs_buf); |
| debugfs_buf = NULL; |
| debugfs_buf_used = 0; |
| debugfs_buf_size = 0; |
| wraparound = false; |
| } |
| |
| logging_option = option; |
| mutex_unlock(&sps_debugfs_lock); |
| |
| return count; |
| } |
| |
| const struct file_operations sps_logging_option_ops = { |
| .read = sps_read_logging_option, |
| .write = sps_set_logging_option, |
| }; |
| |
| /* |
| * input the bam physical address |
| */ |
| static ssize_t sps_set_bam_addr(struct file *file, const char __user *buf, |
| size_t count, loff_t *ppos) |
| { |
| unsigned long missing; |
| char str[MAX_MSG_LEN]; |
| u32 i; |
| u32 bam_addr = 0; |
| struct sps_bam *bam; |
| u32 num_pipes = 0; |
| void *vir_addr; |
| u32 size = sizeof(str) < count ? sizeof(str) : count; |
| |
| memset(str, 0, sizeof(str)); |
| missing = copy_from_user(str, buf, size); |
| if (missing) |
| return -EFAULT; |
| |
| for (i = 0; i < sizeof(str) && (str[i] >= '0') && (str[i] <= '9'); ++i) |
| bam_addr = (bam_addr * 10) + (str[i] - '0'); |
| |
| pr_info("sps:debugfs:input BAM physical address:0x%x\n", bam_addr); |
| |
| bam = phy2bam(bam_addr); |
| |
| if (bam == NULL) { |
| pr_err("sps:debugfs:BAM 0x%x is not registered.", bam_addr); |
| return count; |
| } |
| vir_addr = &bam->base; |
| num_pipes = bam->props.num_pipes; |
| if (log_level_sel <= SPS_IPC_MAX_LOGLEVEL) |
| bam->ipc_loglevel = log_level_sel; |
| |
| switch (reg_dump_option) { |
| case 1: /* output all registers of this BAM */ |
| print_bam_reg(bam->base); |
| for (i = 0; i < num_pipes; i++) |
| print_bam_pipe_reg(bam->base, i); |
| break; |
| case 2: /* output BAM-level registers */ |
| print_bam_reg(bam->base); |
| break; |
| case 3: /* output selected BAM-level registers */ |
| print_bam_selected_reg(vir_addr, bam->props.ee); |
| break; |
| case 4: /* output selected registers of all pipes */ |
| for (i = 0; i < num_pipes; i++) |
| print_bam_pipe_selected_reg(vir_addr, i); |
| break; |
| case 5: /* output selected registers of selected pipes */ |
| for (i = 0; i < num_pipes; i++) |
| if (bam_pipe_sel & (1UL << i)) |
| print_bam_pipe_selected_reg(vir_addr, i); |
| break; |
| case 6: /* output selected registers of typical pipes */ |
| print_bam_pipe_selected_reg(vir_addr, 4); |
| print_bam_pipe_selected_reg(vir_addr, 5); |
| break; |
| case 7: /* output desc FIFO of all pipes */ |
| for (i = 0; i < num_pipes; i++) |
| print_bam_pipe_desc_fifo(vir_addr, i, 0); |
| break; |
| case 8: /* output desc FIFO of selected pipes */ |
| for (i = 0; i < num_pipes; i++) |
| if (bam_pipe_sel & (1UL << i)) |
| print_bam_pipe_desc_fifo(vir_addr, i, 0); |
| break; |
| case 9: /* output desc FIFO of typical pipes */ |
| print_bam_pipe_desc_fifo(vir_addr, 4, 0); |
| print_bam_pipe_desc_fifo(vir_addr, 5, 0); |
| break; |
| case 10: /* output selected registers and desc FIFO of all pipes */ |
| for (i = 0; i < num_pipes; i++) { |
| print_bam_pipe_selected_reg(vir_addr, i); |
| print_bam_pipe_desc_fifo(vir_addr, i, 0); |
| } |
| break; |
| case 11: /* output selected registers and desc FIFO of selected pipes */ |
| for (i = 0; i < num_pipes; i++) |
| if (bam_pipe_sel & (1UL << i)) { |
| print_bam_pipe_selected_reg(vir_addr, i); |
| print_bam_pipe_desc_fifo(vir_addr, i, 0); |
| } |
| break; |
| case 12: /* output selected registers and desc FIFO of typical pipes */ |
| print_bam_pipe_selected_reg(vir_addr, 4); |
| print_bam_pipe_desc_fifo(vir_addr, 4, 0); |
| print_bam_pipe_selected_reg(vir_addr, 5); |
| print_bam_pipe_desc_fifo(vir_addr, 5, 0); |
| break; |
| case 13: /* output BAM_TEST_BUS_REG */ |
| if (testbus_sel) |
| print_bam_test_bus_reg(vir_addr, testbus_sel); |
| else { |
| pr_info("sps:output TEST_BUS_REG for all TEST_BUS_SEL"); |
| print_bam_test_bus_reg(vir_addr, testbus_sel); |
| } |
| break; |
| case 14: /* output partial desc FIFO of selected pipes */ |
| if (desc_option == 0) |
| desc_option = 1; |
| for (i = 0; i < num_pipes; i++) |
| if (bam_pipe_sel & (1UL << i)) |
| print_bam_pipe_desc_fifo(vir_addr, i, |
| desc_option); |
| break; |
| case 15: /* output partial data blocks of descriptors */ |
| for (i = 0; i < num_pipes; i++) |
| if (bam_pipe_sel & (1UL << i)) |
| print_bam_pipe_desc_fifo(vir_addr, i, 100); |
| break; |
| case 16: /* output all registers of selected pipes */ |
| for (i = 0; i < num_pipes; i++) |
| if (bam_pipe_sel & (1UL << i)) |
| print_bam_pipe_reg(bam->base, i); |
| break; |
| case 91: /* |
| * output testbus register, BAM global regisers |
| * and registers of all pipes |
| */ |
| print_bam_test_bus_reg(vir_addr, testbus_sel); |
| print_bam_selected_reg(vir_addr, bam->props.ee); |
| for (i = 0; i < num_pipes; i++) |
| print_bam_pipe_selected_reg(vir_addr, i); |
| break; |
| case 92: /* |
| * output testbus register, BAM global regisers |
| * and registers of selected pipes |
| */ |
| print_bam_test_bus_reg(vir_addr, testbus_sel); |
| print_bam_selected_reg(vir_addr, bam->props.ee); |
| for (i = 0; i < num_pipes; i++) |
| if (bam_pipe_sel & (1UL << i)) |
| print_bam_pipe_selected_reg(vir_addr, i); |
| break; |
| case 93: /* |
| * output registers and partial desc FIFOs |
| * of selected pipes: format 1 |
| */ |
| if (desc_option == 0) |
| desc_option = 1; |
| print_bam_test_bus_reg(vir_addr, testbus_sel); |
| print_bam_selected_reg(vir_addr, bam->props.ee); |
| for (i = 0; i < num_pipes; i++) |
| if (bam_pipe_sel & (1UL << i)) |
| print_bam_pipe_selected_reg(vir_addr, i); |
| for (i = 0; i < num_pipes; i++) |
| if (bam_pipe_sel & (1UL << i)) |
| print_bam_pipe_desc_fifo(vir_addr, i, |
| desc_option); |
| break; |
| case 94: /* |
| * output registers and partial desc FIFOs |
| * of selected pipes: format 2 |
| */ |
| if (desc_option == 0) |
| desc_option = 1; |
| print_bam_test_bus_reg(vir_addr, testbus_sel); |
| print_bam_selected_reg(vir_addr, bam->props.ee); |
| for (i = 0; i < num_pipes; i++) |
| if (bam_pipe_sel & (1UL << i)) { |
| print_bam_pipe_selected_reg(vir_addr, i); |
| print_bam_pipe_desc_fifo(vir_addr, i, |
| desc_option); |
| } |
| break; |
| case 95: /* |
| * output registers and desc FIFOs |
| * of selected pipes: format 1 |
| */ |
| print_bam_test_bus_reg(vir_addr, testbus_sel); |
| print_bam_selected_reg(vir_addr, bam->props.ee); |
| for (i = 0; i < num_pipes; i++) |
| if (bam_pipe_sel & (1UL << i)) |
| print_bam_pipe_selected_reg(vir_addr, i); |
| for (i = 0; i < num_pipes; i++) |
| if (bam_pipe_sel & (1UL << i)) |
| print_bam_pipe_desc_fifo(vir_addr, i, 0); |
| break; |
| case 96: /* |
| * output registers and desc FIFOs |
| * of selected pipes: format 2 |
| */ |
| print_bam_test_bus_reg(vir_addr, testbus_sel); |
| print_bam_selected_reg(vir_addr, bam->props.ee); |
| for (i = 0; i < num_pipes; i++) |
| if (bam_pipe_sel & (1UL << i)) { |
| print_bam_pipe_selected_reg(vir_addr, i); |
| print_bam_pipe_desc_fifo(vir_addr, i, 0); |
| } |
| break; |
| case 97: /* |
| * output registers, desc FIFOs and partial data blocks |
| * of selected pipes: format 1 |
| */ |
| print_bam_test_bus_reg(vir_addr, testbus_sel); |
| print_bam_selected_reg(vir_addr, bam->props.ee); |
| for (i = 0; i < num_pipes; i++) |
| if (bam_pipe_sel & (1UL << i)) |
| print_bam_pipe_selected_reg(vir_addr, i); |
| for (i = 0; i < num_pipes; i++) |
| if (bam_pipe_sel & (1UL << i)) |
| print_bam_pipe_desc_fifo(vir_addr, i, 0); |
| for (i = 0; i < num_pipes; i++) |
| if (bam_pipe_sel & (1UL << i)) |
| print_bam_pipe_desc_fifo(vir_addr, i, 100); |
| break; |
| case 98: /* |
| * output registers, desc FIFOs and partial data blocks |
| * of selected pipes: format 2 |
| */ |
| print_bam_test_bus_reg(vir_addr, testbus_sel); |
| print_bam_selected_reg(vir_addr, bam->props.ee); |
| for (i = 0; i < num_pipes; i++) |
| if (bam_pipe_sel & (1UL << i)) { |
| print_bam_pipe_selected_reg(vir_addr, i); |
| print_bam_pipe_desc_fifo(vir_addr, i, 0); |
| print_bam_pipe_desc_fifo(vir_addr, i, 100); |
| } |
| break; |
| case 99: /* output all registers, desc FIFOs and partial data blocks */ |
| print_bam_test_bus_reg(vir_addr, testbus_sel); |
| print_bam_reg(bam->base); |
| for (i = 0; i < num_pipes; i++) |
| print_bam_pipe_reg(bam->base, i); |
| print_bam_selected_reg(vir_addr, bam->props.ee); |
| for (i = 0; i < num_pipes; i++) |
| print_bam_pipe_selected_reg(vir_addr, i); |
| for (i = 0; i < num_pipes; i++) |
| print_bam_pipe_desc_fifo(vir_addr, i, 0); |
| for (i = 0; i < num_pipes; i++) |
| print_bam_pipe_desc_fifo(vir_addr, i, 100); |
| break; |
| default: |
| pr_info("sps:no dump option is chosen yet."); |
| } |
| |
| return count; |
| } |
| |
| const struct file_operations sps_bam_addr_ops = { |
| .write = sps_set_bam_addr, |
| }; |
| |
| static void sps_debugfs_init(void) |
| { |
| debugfs_record_enabled = false; |
| logging_option = 0; |
| debug_level_option = 0; |
| print_limit_option = 0; |
| reg_dump_option = 0; |
| testbus_sel = 0; |
| bam_pipe_sel = 0; |
| desc_option = 0; |
| debugfs_buf_size = 0; |
| debugfs_buf_used = 0; |
| wraparound = false; |
| log_level_sel = SPS_IPC_MAX_LOGLEVEL + 1; |
| |
| dent = debugfs_create_dir("sps", 0); |
| if (IS_ERR(dent)) { |
| pr_err("sps:fail to create the folder for debug_fs.\n"); |
| return; |
| } |
| |
| dfile_info = debugfs_create_file("info", 0664, dent, 0, |
| &sps_info_ops); |
| if (!dfile_info || IS_ERR(dfile_info)) { |
| pr_err("sps:fail to create the file for debug_fs info.\n"); |
| goto info_err; |
| } |
| |
| dfile_logging_option = debugfs_create_file("logging_option", 0664, |
| dent, 0, &sps_logging_option_ops); |
| if (!dfile_logging_option || IS_ERR(dfile_logging_option)) { |
| pr_err("sps:fail to create debug_fs for logging_option.\n"); |
| goto logging_option_err; |
| } |
| |
| dfile_debug_level_option = debugfs_create_u8("debug_level_option", |
| 0664, dent, &debug_level_option); |
| if (!dfile_debug_level_option || IS_ERR(dfile_debug_level_option)) { |
| pr_err("sps:fail to create debug_fs for debug_level_option.\n"); |
| goto debug_level_option_err; |
| } |
| |
| dfile_print_limit_option = debugfs_create_u8("print_limit_option", |
| 0664, dent, &print_limit_option); |
| if (!dfile_print_limit_option || IS_ERR(dfile_print_limit_option)) { |
| pr_err("sps:fail to create debug_fs for print_limit_option.\n"); |
| goto print_limit_option_err; |
| } |
| |
| dfile_reg_dump_option = debugfs_create_u8("reg_dump_option", 0664, |
| dent, ®_dump_option); |
| if (!dfile_reg_dump_option || IS_ERR(dfile_reg_dump_option)) { |
| pr_err("sps:fail to create debug_fs for reg_dump_option.\n"); |
| goto reg_dump_option_err; |
| } |
| |
| dfile_testbus_sel = debugfs_create_u32("testbus_sel", 0664, |
| dent, &testbus_sel); |
| if (!dfile_testbus_sel || IS_ERR(dfile_testbus_sel)) { |
| pr_err("sps:fail to create debug_fs file for testbus_sel.\n"); |
| goto testbus_sel_err; |
| } |
| |
| dfile_bam_pipe_sel = debugfs_create_u32("bam_pipe_sel", 0664, |
| dent, &bam_pipe_sel); |
| if (!dfile_bam_pipe_sel || IS_ERR(dfile_bam_pipe_sel)) { |
| pr_err("sps:fail to create debug_fs file for bam_pipe_sel.\n"); |
| goto bam_pipe_sel_err; |
| } |
| |
| dfile_desc_option = debugfs_create_u32("desc_option", 0664, |
| dent, &desc_option); |
| if (!dfile_desc_option || IS_ERR(dfile_desc_option)) { |
| pr_err("sps:fail to create debug_fs file for desc_option.\n"); |
| goto desc_option_err; |
| } |
| |
| dfile_bam_addr = debugfs_create_file("bam_addr", 0664, |
| dent, 0, &sps_bam_addr_ops); |
| if (!dfile_bam_addr || IS_ERR(dfile_bam_addr)) { |
| pr_err("sps:fail to create the file for debug_fs bam_addr.\n"); |
| goto bam_addr_err; |
| } |
| |
| dfile_log_level_sel = debugfs_create_u32("log_level_sel", 0664, |
| dent, &log_level_sel); |
| if (!dfile_log_level_sel || IS_ERR(dfile_log_level_sel)) { |
| pr_err("sps:fail to create debug_fs file for log_level_sel.\n"); |
| goto bam_log_level_err; |
| } |
| |
| mutex_init(&sps_debugfs_lock); |
| |
| return; |
| |
| bam_log_level_err: |
| debugfs_remove(dfile_bam_addr); |
| bam_addr_err: |
| debugfs_remove(dfile_desc_option); |
| desc_option_err: |
| debugfs_remove(dfile_bam_pipe_sel); |
| bam_pipe_sel_err: |
| debugfs_remove(dfile_testbus_sel); |
| testbus_sel_err: |
| debugfs_remove(dfile_reg_dump_option); |
| reg_dump_option_err: |
| debugfs_remove(dfile_print_limit_option); |
| print_limit_option_err: |
| debugfs_remove(dfile_debug_level_option); |
| debug_level_option_err: |
| debugfs_remove(dfile_logging_option); |
| logging_option_err: |
| debugfs_remove(dfile_info); |
| info_err: |
| debugfs_remove(dent); |
| } |
| |
| static void sps_debugfs_exit(void) |
| { |
| debugfs_remove(dfile_info); |
| debugfs_remove(dfile_logging_option); |
| debugfs_remove(dfile_debug_level_option); |
| debugfs_remove(dfile_print_limit_option); |
| debugfs_remove(dfile_reg_dump_option); |
| debugfs_remove(dfile_testbus_sel); |
| debugfs_remove(dfile_bam_pipe_sel); |
| debugfs_remove(dfile_desc_option); |
| debugfs_remove(dfile_bam_addr); |
| debugfs_remove(dent); |
| debugfs_remove(dfile_log_level_sel); |
| kfree(debugfs_buf); |
| debugfs_buf = NULL; |
| } |
| #endif |
| |
| /* Get the debug info of BAM registers and descriptor FIFOs */ |
| int sps_get_bam_debug_info(unsigned long dev, u32 option, u32 para, |
| u32 tb_sel, u32 desc_sel) |
| { |
| int res = 0; |
| struct sps_bam *bam; |
| u32 i; |
| u32 num_pipes = 0; |
| void *vir_addr; |
| |
| if (dev == 0) { |
| SPS_ERR(sps, |
| "sps:%s:device handle should not be 0.\n", __func__); |
| return SPS_ERROR; |
| } |
| |
| if (sps == NULL || !sps->is_ready) { |
| SPS_DBG3(sps, "sps:%s:sps driver is not ready.\n", __func__); |
| return -EPROBE_DEFER; |
| } |
| |
| mutex_lock(&sps->lock); |
| /* Search for the target BAM device */ |
| bam = sps_h2bam(dev); |
| if (bam == NULL) { |
| pr_err("sps:Can't find any BAM with handle 0x%lx.", dev); |
| mutex_unlock(&sps->lock); |
| return SPS_ERROR; |
| } |
| mutex_unlock(&sps->lock); |
| |
| vir_addr = &bam->base; |
| num_pipes = bam->props.num_pipes; |
| |
| SPS_DUMP("sps:<bam-addr> dump BAM:%pa.\n", &bam->props.phys_addr); |
| |
| switch (option) { |
| case 1: /* output all registers of this BAM */ |
| print_bam_reg(bam->base); |
| for (i = 0; i < num_pipes; i++) |
| print_bam_pipe_reg(bam->base, i); |
| break; |
| case 2: /* output BAM-level registers */ |
| print_bam_reg(bam->base); |
| break; |
| case 3: /* output selected BAM-level registers */ |
| print_bam_selected_reg(vir_addr, bam->props.ee); |
| break; |
| case 4: /* output selected registers of all pipes */ |
| for (i = 0; i < num_pipes; i++) |
| print_bam_pipe_selected_reg(vir_addr, i); |
| break; |
| case 5: /* output selected registers of selected pipes */ |
| for (i = 0; i < num_pipes; i++) |
| if (para & (1UL << i)) |
| print_bam_pipe_selected_reg(vir_addr, i); |
| break; |
| case 6: /* output selected registers of typical pipes */ |
| print_bam_pipe_selected_reg(vir_addr, 4); |
| print_bam_pipe_selected_reg(vir_addr, 5); |
| break; |
| case 7: /* output desc FIFO of all pipes */ |
| for (i = 0; i < num_pipes; i++) |
| print_bam_pipe_desc_fifo(vir_addr, i, 0); |
| break; |
| case 8: /* output desc FIFO of selected pipes */ |
| for (i = 0; i < num_pipes; i++) |
| if (para & (1UL << i)) |
| print_bam_pipe_desc_fifo(vir_addr, i, 0); |
| break; |
| case 9: /* output desc FIFO of typical pipes */ |
| print_bam_pipe_desc_fifo(vir_addr, 4, 0); |
| print_bam_pipe_desc_fifo(vir_addr, 5, 0); |
| break; |
| case 10: /* output selected registers and desc FIFO of all pipes */ |
| for (i = 0; i < num_pipes; i++) { |
| print_bam_pipe_selected_reg(vir_addr, i); |
| print_bam_pipe_desc_fifo(vir_addr, i, 0); |
| } |
| break; |
| case 11: /* output selected registers and desc FIFO of selected pipes */ |
| for (i = 0; i < num_pipes; i++) |
| if (para & (1UL << i)) { |
| print_bam_pipe_selected_reg(vir_addr, i); |
| print_bam_pipe_desc_fifo(vir_addr, i, 0); |
| } |
| break; |
| case 12: /* output selected registers and desc FIFO of typical pipes */ |
| print_bam_pipe_selected_reg(vir_addr, 4); |
| print_bam_pipe_desc_fifo(vir_addr, 4, 0); |
| print_bam_pipe_selected_reg(vir_addr, 5); |
| print_bam_pipe_desc_fifo(vir_addr, 5, 0); |
| break; |
| case 13: /* output BAM_TEST_BUS_REG */ |
| if (tb_sel) |
| print_bam_test_bus_reg(vir_addr, tb_sel); |
| else |
| pr_info("sps:TEST_BUS_SEL should NOT be zero."); |
| break; |
| case 14: /* output partial desc FIFO of selected pipes */ |
| if (desc_sel == 0) |
| desc_sel = 1; |
| for (i = 0; i < num_pipes; i++) |
| if (para & (1UL << i)) |
| print_bam_pipe_desc_fifo(vir_addr, i, |
| desc_sel); |
| break; |
| case 15: /* output partial data blocks of descriptors */ |
| for (i = 0; i < num_pipes; i++) |
| if (para & (1UL << i)) |
| print_bam_pipe_desc_fifo(vir_addr, i, 100); |
| break; |
| case 16: /* output all registers of selected pipes */ |
| for (i = 0; i < num_pipes; i++) |
| if (para & (1UL << i)) |
| print_bam_pipe_reg(bam->base, i); |
| break; |
| case 91: /* |
| * output testbus register, BAM global regisers |
| * and registers of all pipes |
| */ |
| print_bam_test_bus_reg(vir_addr, tb_sel); |
| print_bam_selected_reg(vir_addr, bam->props.ee); |
| for (i = 0; i < num_pipes; i++) |
| print_bam_pipe_selected_reg(vir_addr, i); |
| break; |
| case 92: /* |
| * output testbus register, BAM global regisers |
| * and registers of selected pipes |
| */ |
| print_bam_test_bus_reg(vir_addr, tb_sel); |
| print_bam_selected_reg(vir_addr, bam->props.ee); |
| for (i = 0; i < num_pipes; i++) |
| if (para & (1UL << i)) |
| print_bam_pipe_selected_reg(vir_addr, i); |
| break; |
| case 93: /* |
| * output registers and partial desc FIFOs |
| * of selected pipes: format 1 |
| */ |
| if (desc_sel == 0) |
| desc_sel = 1; |
| print_bam_test_bus_reg(vir_addr, tb_sel); |
| print_bam_selected_reg(vir_addr, bam->props.ee); |
| for (i = 0; i < num_pipes; i++) |
| if (para & (1UL << i)) |
| print_bam_pipe_selected_reg(vir_addr, i); |
| for (i = 0; i < num_pipes; i++) |
| if (para & (1UL << i)) |
| print_bam_pipe_desc_fifo(vir_addr, i, |
| desc_sel); |
| break; |
| case 94: /* |
| * output registers and partial desc FIFOs |
| * of selected pipes: format 2 |
| */ |
| if (desc_sel == 0) |
| desc_sel = 1; |
| print_bam_test_bus_reg(vir_addr, tb_sel); |
| print_bam_selected_reg(vir_addr, bam->props.ee); |
| for (i = 0; i < num_pipes; i++) |
| if (para & (1UL << i)) { |
| print_bam_pipe_selected_reg(vir_addr, i); |
| print_bam_pipe_desc_fifo(vir_addr, i, |
| desc_sel); |
| } |
| break; |
| case 95: /* |
| * output registers and desc FIFOs |
| * of selected pipes: format 1 |
| */ |
| print_bam_test_bus_reg(vir_addr, tb_sel); |
| print_bam_selected_reg(vir_addr, bam->props.ee); |
| for (i = 0; i < num_pipes; i++) |
| if (para & (1UL << i)) |
| print_bam_pipe_selected_reg(vir_addr, i); |
| for (i = 0; i < num_pipes; i++) |
| if (para & (1UL << i)) |
| print_bam_pipe_desc_fifo(vir_addr, i, 0); |
| break; |
| case 96: /* |
| * output registers and desc FIFOs |
| * of selected pipes: format 2 |
| */ |
| print_bam_test_bus_reg(vir_addr, tb_sel); |
| print_bam_selected_reg(vir_addr, bam->props.ee); |
| for (i = 0; i < num_pipes; i++) |
| if (para & (1UL << i)) { |
| print_bam_pipe_selected_reg(vir_addr, i); |
| print_bam_pipe_desc_fifo(vir_addr, i, 0); |
| } |
| break; |
| case 97: /* |
| * output registers, desc FIFOs and partial data blocks |
| * of selected pipes: format 1 |
| */ |
| print_bam_test_bus_reg(vir_addr, tb_sel); |
| print_bam_selected_reg(vir_addr, bam->props.ee); |
| for (i = 0; i < num_pipes; i++) |
| if (para & (1UL << i)) |
| print_bam_pipe_selected_reg(vir_addr, i); |
| for (i = 0; i < num_pipes; i++) |
| if (para & (1UL << i)) |
| print_bam_pipe_desc_fifo(vir_addr, i, 0); |
| for (i = 0; i < num_pipes; i++) |
| if (para & (1UL << i)) |
| print_bam_pipe_desc_fifo(vir_addr, i, 100); |
| break; |
| case 98: /* |
| * output registers, desc FIFOs and partial data blocks |
| * of selected pipes: format 2 |
| */ |
| print_bam_test_bus_reg(vir_addr, tb_sel); |
| print_bam_selected_reg(vir_addr, bam->props.ee); |
| for (i = 0; i < num_pipes; i++) |
| if (para & (1UL << i)) { |
| print_bam_pipe_selected_reg(vir_addr, i); |
| print_bam_pipe_desc_fifo(vir_addr, i, 0); |
| print_bam_pipe_desc_fifo(vir_addr, i, 100); |
| } |
| break; |
| case 99: /* output all registers, desc FIFOs and partial data blocks */ |
| print_bam_test_bus_reg(vir_addr, tb_sel); |
| print_bam_reg(bam->base); |
| for (i = 0; i < num_pipes; i++) |
| print_bam_pipe_reg(bam->base, i); |
| print_bam_selected_reg(vir_addr, bam->props.ee); |
| for (i = 0; i < num_pipes; i++) |
| print_bam_pipe_selected_reg(vir_addr, i); |
| for (i = 0; i < num_pipes; i++) |
| print_bam_pipe_desc_fifo(vir_addr, i, 0); |
| for (i = 0; i < num_pipes; i++) |
| print_bam_pipe_desc_fifo(vir_addr, i, 100); |
| break; |
| default: |
| pr_info("sps:no option is chosen yet."); |
| } |
| |
| return res; |
| } |
| EXPORT_SYMBOL(sps_get_bam_debug_info); |
| |
| /** |
| * Initialize SPS device |
| * |
| * This function initializes the SPS device. |
| * |
| * @return 0 on success, negative value on error |
| * |
| */ |
| static int sps_device_init(void) |
| { |
| int result; |
| int success; |
| #ifdef CONFIG_SPS_SUPPORT_BAMDMA |
| struct sps_bam_props bamdma_props = {0}; |
| #endif |
| |
| SPS_DBG3(sps, "sps:%s.", __func__); |
| |
| success = false; |
| |
| result = sps_mem_init(sps->pipemem_phys_base, sps->pipemem_size); |
| if (result) { |
| SPS_ERR(sps, "sps:%s:SPS memory init failed", __func__); |
| goto exit_err; |
| } |
| |
| INIT_LIST_HEAD(&sps->bams_q); |
| mutex_init(&sps->lock); |
| |
| if (sps_rm_init(&sps->connection_ctrl, sps->options)) { |
| SPS_ERR(sps, "sps:%s:Fail to init SPS resource manager", |
| __func__); |
| goto exit_err; |
| } |
| |
| result = sps_bam_driver_init(sps->options); |
| if (result) { |
| SPS_ERR(sps, "sps:%s:SPS BAM driver init failed", __func__); |
| goto exit_err; |
| } |
| |
| /* Initialize the BAM DMA device */ |
| #ifdef CONFIG_SPS_SUPPORT_BAMDMA |
| bamdma_props.phys_addr = sps->bamdma_bam_phys_base; |
| bamdma_props.virt_addr = ioremap(sps->bamdma_bam_phys_base, |
| sps->bamdma_bam_size); |
| |
| if (!bamdma_props.virt_addr) { |
| SPS_ERR(sps, "sps:%s:Fail to IO map BAM-DMA BAM registers.\n", |
| __func__); |
| goto exit_err; |
| } |
| |
| SPS_DBG3(sps, "sps:bamdma_bam.phys=%pa.virt=0x%p.", |
| &bamdma_props.phys_addr, |
| bamdma_props.virt_addr); |
| |
| bamdma_props.periph_phys_addr = sps->bamdma_dma_phys_base; |
| bamdma_props.periph_virt_size = sps->bamdma_dma_size; |
| bamdma_props.periph_virt_addr = ioremap(sps->bamdma_dma_phys_base, |
| sps->bamdma_dma_size); |
| |
| if (!bamdma_props.periph_virt_addr) { |
| SPS_ERR(sps, "sps:%s:Fail to IO map BAM-DMA peripheral reg.\n", |
| __func__); |
| goto exit_err; |
| } |
| |
| SPS_DBG3(sps, "sps:bamdma_dma.phys=%pa.virt=0x%p.", |
| &bamdma_props.periph_phys_addr, |
| bamdma_props.periph_virt_addr); |
| |
| bamdma_props.irq = sps->bamdma_irq; |
| |
| bamdma_props.event_threshold = 0x10; /* Pipe event threshold */ |
| bamdma_props.summing_threshold = 0x10; /* BAM event threshold */ |
| |
| bamdma_props.options = SPS_BAM_OPT_BAMDMA; |
| bamdma_props.restricted_pipes = sps->bamdma_restricted_pipes; |
| |
| result = sps_dma_init(&bamdma_props); |
| if (result) { |
| SPS_ERR(sps, "sps:%s:SPS BAM DMA driver init failed", __func__); |
| goto exit_err; |
| } |
| #endif /* CONFIG_SPS_SUPPORT_BAMDMA */ |
| |
| result = sps_map_init(NULL, sps->options); |
| if (result) { |
| SPS_ERR(sps, |
| "sps:%s:SPS connection mapping init failed", __func__); |
| goto exit_err; |
| } |
| |
| success = true; |
| exit_err: |
| if (!success) { |
| #ifdef CONFIG_SPS_SUPPORT_BAMDMA |
| sps_device_de_init(); |
| #endif |
| return SPS_ERROR; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * De-initialize SPS device |
| * |
| * This function de-initializes the SPS device. |
| * |
| * @return 0 on success, negative value on error |
| * |
| */ |
| static void sps_device_de_init(void) |
| { |
| SPS_DBG3(sps, "sps:%s.", __func__); |
| |
| if (sps != NULL) { |
| #ifdef CONFIG_SPS_SUPPORT_BAMDMA |
| sps_dma_de_init(); |
| #endif |
| /* Are there any remaining BAM registrations? */ |
| if (!list_empty(&sps->bams_q)) |
| SPS_ERR(sps, |
| "sps:%s:BAMs are still registered", __func__); |
| |
| sps_map_de_init(); |
| |
| kfree(sps); |
| } |
| |
| sps_mem_de_init(); |
| } |
| |
| /** |
| * Initialize client state context |
| * |
| * This function initializes a client state context struct. |
| * |
| * @client - Pointer to client state context |
| * |
| * @return 0 on success, negative value on error |
| * |
| */ |
| static int sps_client_init(struct sps_pipe *client) |
| { |
| SPS_DBG(sps, "sps:%s.", __func__); |
| |
| if (client == NULL) |
| return -EINVAL; |
| |
| /* |
| * NOTE: Cannot store any state within the SPS driver because |
| * the driver init function may not have been called yet. |
| */ |
| memset(client, 0, sizeof(*client)); |
| sps_rm_config_init(&client->connect); |
| |
| client->client_state = SPS_STATE_DISCONNECT; |
| client->bam = NULL; |
| |
| return 0; |
| } |
| |
| /** |
| * De-initialize client state context |
| * |
| * This function de-initializes a client state context struct. |
| * |
| * @client - Pointer to client state context |
| * |
| * @return 0 on success, negative value on error |
| * |
| */ |
| static int sps_client_de_init(struct sps_pipe *client) |
| { |
| SPS_DBG(sps, "sps:%s.", __func__); |
| |
| if (client->client_state != SPS_STATE_DISCONNECT) { |
| SPS_ERR(sps, "sps:De-init client in connected state: 0x%x", |
| client->client_state); |
| return SPS_ERROR; |
| } |
| |
| client->bam = NULL; |
| client->map = NULL; |
| memset(&client->connect, 0, sizeof(client->connect)); |
| |
| return 0; |
| } |
| |
| /** |
| * Find the BAM device from the physical address |
| * |
| * This function finds a BAM device in the BAM registration list that |
| * matches the specified physical address. |
| * |
| * @phys_addr - physical address of the BAM |
| * |
| * @return - pointer to the BAM device struct, or NULL on error |
| * |
| */ |
| static struct sps_bam *phy2bam(phys_addr_t phys_addr) |
| { |
| struct sps_bam *bam; |
| |
| SPS_DBG2(sps, "sps:%s.", __func__); |
| |
| list_for_each_entry(bam, &sps->bams_q, list) { |
| if (bam->props.phys_addr == phys_addr) |
| return bam; |
| } |
| |
| return NULL; |
| } |
| |
| /** |
| * Find the handle of a BAM device based on the physical address |
| * |
| * This function finds a BAM device in the BAM registration list that |
| * matches the specified physical address, and returns its handle. |
| * |
| * @phys_addr - physical address of the BAM |
| * |
| * @h - device handle of the BAM |
| * |
| * @return 0 on success, negative value on error |
| * |
| */ |
| int sps_phy2h(phys_addr_t phys_addr, unsigned long *handle) |
| { |
| struct sps_bam *bam; |
| |
| SPS_DBG2(sps, "sps:%s.", __func__); |
| |
| if (sps == NULL || !sps->is_ready) { |
| SPS_DBG3(sps, "sps:%s:sps driver is not ready.\n", __func__); |
| return -EPROBE_DEFER; |
| } |
| |
| if (handle == NULL) { |
| SPS_ERR(sps, "sps:%s:handle is NULL.\n", __func__); |
| return SPS_ERROR; |
| } |
| |
| list_for_each_entry(bam, &sps->bams_q, list) { |
| if (bam->props.phys_addr == phys_addr) { |
| *handle = (uintptr_t) bam; |
| return 0; |
| } |
| } |
| |
| SPS_ERR(sps, |
| "sps: BAM device %pa is not registered yet.\n", &phys_addr); |
| |
| return -ENODEV; |
| } |
| EXPORT_SYMBOL(sps_phy2h); |
| |
| /** |
| * Setup desc/data FIFO for bam-to-bam connection |
| * |
| * @mem_buffer - Pointer to struct for allocated memory properties. |
| * |
| * @addr - address of FIFO |
| * |
| * @size - FIFO size |
| * |
| * @use_offset - use address offset instead of absolute address |
| * |
| * @return 0 on success, negative value on error |
| * |
| */ |
| int sps_setup_bam2bam_fifo(struct sps_mem_buffer *mem_buffer, |
| u32 addr, u32 size, int use_offset) |
| { |
| SPS_DBG1(sps, "sps:%s.", __func__); |
| |
| if ((mem_buffer == NULL) || (size == 0)) { |
| SPS_ERR(sps, "sps:%s:invalid buffer address or size.", |
| __func__); |
| return SPS_ERROR; |
| } |
| |
| if (sps == NULL || !sps->is_ready) { |
| SPS_DBG3(sps, "sps:%s:sps driver is not ready.\n", __func__); |
| return -EPROBE_DEFER; |
| } |
| |
| if (use_offset) { |
| if ((addr + size) <= sps->pipemem_size) |
| mem_buffer->phys_base = sps->pipemem_phys_base + addr; |
| else { |
| SPS_ERR(sps, |
| "sps:%s:requested mem is out of pipe mem range.\n", |
| __func__); |
| return SPS_ERROR; |
| } |
| } else { |
| if (addr >= sps->pipemem_phys_base && |
| (addr + size) <= (sps->pipemem_phys_base |
| + sps->pipemem_size)) |
| mem_buffer->phys_base = addr; |
| else { |
| SPS_ERR(sps, |
| "sps:%s:requested mem is out of pipe mem range.\n", |
| __func__); |
| return SPS_ERROR; |
| } |
| } |
| |
| mem_buffer->base = spsi_get_mem_ptr(mem_buffer->phys_base); |
| mem_buffer->size = size; |
| |
| memset(mem_buffer->base, 0, mem_buffer->size); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(sps_setup_bam2bam_fifo); |
| |
| /** |
| * Find the BAM device from the handle |
| * |
| * This function finds a BAM device in the BAM registration list that |
| * matches the specified device handle. |
| * |
| * @h - device handle of the BAM |
| * |
| * @return - pointer to the BAM device struct, or NULL on error |
| * |
| */ |
| struct sps_bam *sps_h2bam(unsigned long h) |
| { |
| struct sps_bam *bam; |
| |
| SPS_DBG1(sps, "sps:%s: BAM handle:0x%lx.", __func__, h); |
| |
| if (h == SPS_DEV_HANDLE_MEM || h == SPS_DEV_HANDLE_INVALID) |
| return NULL; |
| |
| list_for_each_entry(bam, &sps->bams_q, list) { |
| if ((uintptr_t) bam == h) |
| return bam; |
| } |
| |
| SPS_ERR(sps, "sps:Can't find BAM device for handle 0x%lx.", h); |
| |
| return NULL; |
| } |
| |
| /** |
| * Lock BAM device |
| * |
| * This function obtains the BAM spinlock on the client's connection. |
| * |
| * @pipe - pointer to client pipe state |
| * |
| * @return pointer to BAM device struct, or NULL on error |
| * |
| */ |
| static struct sps_bam *sps_bam_lock(struct sps_pipe *pipe) |
| { |
| struct sps_bam *bam; |
| u32 pipe_index; |
| |
| bam = pipe->bam; |
| if (bam == NULL) { |
| SPS_ERR(sps, "sps:%s:Connection is not in connected state.", |
| __func__); |
| return NULL; |
| } |
| |
| spin_lock_irqsave(&bam->connection_lock, bam->irqsave_flags); |
| |
| /* Verify client owns this pipe */ |
| pipe_index = pipe->pipe_index; |
| if (pipe_index >= bam->props.num_pipes || |
| pipe != bam->pipes[pipe_index]) { |
| SPS_ERR(bam, |
| "sps:Client not owner of BAM %pa pipe: %d (max %d)", |
| &bam->props.phys_addr, pipe_index, |
| bam->props.num_pipes); |
| spin_unlock_irqrestore(&bam->connection_lock, |
| bam->irqsave_flags); |
| return NULL; |
| } |
| |
| return bam; |
| } |
| |
| /** |
| * Unlock BAM device |
| * |
| * This function releases the BAM spinlock on the client's connection. |
| * |
| * @bam - pointer to BAM device struct |
| * |
| */ |
| static inline void sps_bam_unlock(struct sps_bam *bam) |
| { |
| spin_unlock_irqrestore(&bam->connection_lock, bam->irqsave_flags); |
| } |
| |
| /** |
| * Connect an SPS connection end point |
| * |
| */ |
| int sps_connect(struct sps_pipe *h, struct sps_connect *connect) |
| { |
| struct sps_pipe *pipe = h; |
| unsigned long dev; |
| struct sps_bam *bam; |
| int result; |
| |
| if (h == NULL) { |
| SPS_ERR(sps, "sps:%s:pipe is NULL.\n", __func__); |
| return SPS_ERROR; |
| } else if (connect == NULL) { |
| SPS_ERR(sps, "sps:%s:connection is NULL.\n", __func__); |
| return SPS_ERROR; |
| } |
| |
| if (sps == NULL) |
| return -ENODEV; |
| |
| if (!sps->is_ready) { |
| SPS_ERR(sps, "sps:%s:sps driver is not ready.\n", __func__); |
| return -EAGAIN; |
| } |
| |
| if ((connect->lock_group != SPSRM_CLEAR) |
| && (connect->lock_group > BAM_MAX_P_LOCK_GROUP_NUM)) { |
| SPS_ERR(sps, |
| "sps:%s:The value of pipe lock group is invalid.\n", |
| __func__); |
| return SPS_ERROR; |
| } |
| |
| mutex_lock(&sps->lock); |
| /* |
| * Must lock the BAM device at the top level function, so must |
| * determine which BAM is the target for the connection |
| */ |
| if (connect->mode == SPS_MODE_SRC) |
| dev = connect->source; |
| else |
| dev = connect->destination; |
| |
| bam = sps_h2bam(dev); |
| if (bam == NULL) { |
| SPS_ERR(sps, "sps:Invalid BAM device handle: 0x%lx", dev); |
| result = SPS_ERROR; |
| goto exit_err; |
| } |
| |
| mutex_lock(&bam->lock); |
| SPS_DBG2(bam, "sps:sps_connect: bam %pa src 0x%lx dest 0x%lx mode %s", |
| BAM_ID(bam), |
| connect->source, |
| connect->destination, |
| connect->mode == SPS_MODE_SRC ? "SRC" : "DEST"); |
| |
| /* Allocate resources for the specified connection */ |
| pipe->connect = *connect; |
| result = sps_rm_state_change(pipe, SPS_STATE_ALLOCATE); |
| if (result) { |
| mutex_unlock(&bam->lock); |
| goto exit_err; |
| } |
| |
| /* Configure the connection */ |
| result = sps_rm_state_change(pipe, SPS_STATE_CONNECT); |
| mutex_unlock(&bam->lock); |
| if (result) { |
| sps_disconnect(h); |
| goto exit_err; |
| } |
| |
| exit_err: |
| mutex_unlock(&sps->lock); |
| |
| return result; |
| } |
| EXPORT_SYMBOL(sps_connect); |
| |
| /** |
| * Disconnect an SPS connection end point |
| * |
| * This function disconnects an SPS connection end point. |
| * The SPS hardware associated with that end point will be disabled. |
| * For a connection involving system memory (SPS_DEV_HANDLE_MEM), all |
| * connection resources are deallocated. For a peripheral-to-peripheral |
| * connection, the resources associated with the connection will not be |
| * deallocated until both end points are closed. |
| * |
| * The client must call sps_connect() for the handle before calling |
| * this function. |
| * |
| * @h - client context for SPS connection end point |
| * |
| * @return 0 on success, negative value on error |
| * |
| */ |
| int sps_disconnect(struct sps_pipe *h) |
| { |
| struct sps_pipe *pipe = h; |
| struct sps_pipe *check; |
| struct sps_bam *bam; |
| int result; |
| |
| if (pipe == NULL) { |
| SPS_ERR(sps, "sps:%s:Invalid pipe.", __func__); |
| return SPS_ERROR; |
| } |
| |
| bam = pipe->bam; |
| if (bam == NULL) { |
| SPS_ERR(sps, |
| "sps:%s:BAM device of this pipe is NULL.", __func__); |
| return SPS_ERROR; |
| } |
| |
| SPS_DBG2(bam, |
| "sps:sps_disconnect: bam %pa src 0x%lx dest 0x%lx mode %s", |
| BAM_ID(bam), |
| pipe->connect.source, |
| pipe->connect.destination, |
| pipe->connect.mode == SPS_MODE_SRC ? "SRC" : "DEST"); |
| |
| result = SPS_ERROR; |
| /* Cross-check client with map table */ |
| if (pipe->connect.mode == SPS_MODE_SRC) |
| check = pipe->map->client_src; |
| else |
| check = pipe->map->client_dest; |
| |
| if (check != pipe) { |
| SPS_ERR(sps, "sps:%s:Client context is corrupt", __func__); |
| goto exit_err; |
| } |
| |
| /* Disconnect the BAM pipe */ |
| mutex_lock(&bam->lock); |
| result = sps_rm_state_change(pipe, SPS_STATE_DISCONNECT); |
| mutex_unlock(&bam->lock); |
| if (result) |
| goto exit_err; |
| |
| sps_rm_config_init(&pipe->connect); |
| result = 0; |
| |
| exit_err: |
| |
| return result; |
| } |
| EXPORT_SYMBOL(sps_disconnect); |
| |
| /** |
| * Register an event object for an SPS connection end point |
| * |
| */ |
| int sps_register_event(struct sps_pipe *h, struct sps_register_event *reg) |
| { |
| struct sps_pipe *pipe = h; |
| struct sps_bam *bam; |
| int result; |
| |
| if (h == NULL) { |
| SPS_ERR(sps, "sps:%s:pipe is NULL.\n", __func__); |
| return SPS_ERROR; |
| } else if (reg == NULL) { |
| SPS_ERR(sps, "sps:%s:registered event is NULL.\n", __func__); |
| return SPS_ERROR; |
| } |
| |
| if (sps == NULL) |
| return -ENODEV; |
| |
| if (!sps->is_ready) { |
| SPS_ERR(sps, "sps:%s:sps driver not ready.\n", __func__); |
| return -EAGAIN; |
| } |
| |
| bam = sps_bam_lock(pipe); |
| if (bam == NULL) |
| return SPS_ERROR; |
| |
| SPS_DBG2(bam, "sps:%s; events:%d.\n", __func__, reg->options); |
| |
| result = sps_bam_pipe_reg_event(bam, pipe->pipe_index, reg); |
| sps_bam_unlock(bam); |
| if (result) |
| SPS_ERR(bam, |
| "sps:Fail to register event for BAM %pa pipe %d", |
| &pipe->bam->props.phys_addr, pipe->pipe_index); |
| |
| return result; |
| } |
| EXPORT_SYMBOL(sps_register_event); |
| |
| /** |
| * Enable an SPS connection end point |
| * |
| */ |
| int sps_flow_on(struct sps_pipe *h) |
| { |
| struct sps_pipe *pipe = h; |
| struct sps_bam *bam; |
| int result = 0; |
| |
| if (h == NULL) { |
| SPS_ERR(sps, "sps:%s:pipe is NULL.\n", __func__); |
| return SPS_ERROR; |
| } |
| |
| bam = sps_bam_lock(pipe); |
| if (bam == NULL) |
| return SPS_ERROR; |
| |
| SPS_DBG2(bam, "sps:%s.\n", __func__); |
| |
| bam_pipe_halt(&bam->base, pipe->pipe_index, false); |
| |
| sps_bam_unlock(bam); |
| |
| return result; |
| } |
| EXPORT_SYMBOL(sps_flow_on); |
| |
| /** |
| * Disable an SPS connection end point |
| * |
| */ |
| int sps_flow_off(struct sps_pipe *h, enum sps_flow_off mode) |
| { |
| struct sps_pipe *pipe = h; |
| struct sps_bam *bam; |
| int result = 0; |
| |
| if (h == NULL) { |
| SPS_ERR(sps, "sps:%s:pipe is NULL.\n", __func__); |
| return SPS_ERROR; |
| } |
| |
| bam = sps_bam_lock(pipe); |
| if (bam == NULL) |
| return SPS_ERROR; |
| |
| SPS_DBG2(bam, "sps:%s.\n", __func__); |
| |
| bam_pipe_halt(&bam->base, pipe->pipe_index, true); |
| |
| sps_bam_unlock(bam); |
| |
| return result; |
| } |
| EXPORT_SYMBOL(sps_flow_off); |
| |
| /** |
| * Check if the flags on a descriptor/iovec are valid |
| * |
| * @flags - flags on a descriptor/iovec |
| * |
| * @return 0 on success, negative value on error |
| * |
| */ |
| static int sps_check_iovec_flags(u32 flags) |
| { |
| if ((flags & SPS_IOVEC_FLAG_NWD) && |
| !(flags & (SPS_IOVEC_FLAG_EOT | SPS_IOVEC_FLAG_CMD))) { |
| SPS_ERR(sps, |
| "sps:%s:NWD is only valid with EOT or CMD.\n", |
| __func__); |
| return SPS_ERROR; |
| } else if ((flags & SPS_IOVEC_FLAG_EOT) && |
| (flags & SPS_IOVEC_FLAG_CMD)) { |
| SPS_ERR(sps, |
| "sps:%s:EOT and CMD are not allowed to coexist.\n", |
| __func__); |
| return SPS_ERROR; |
| } else if (!(flags & SPS_IOVEC_FLAG_CMD) && |
| (flags & (SPS_IOVEC_FLAG_LOCK | SPS_IOVEC_FLAG_UNLOCK))) { |
| static char err_msg[] = |
| "pipe lock/unlock flags are only valid with Command Descriptor"; |
| SPS_ERR(sps, "sps:%s.\n", err_msg); |
| return SPS_ERROR; |
| } else if ((flags & SPS_IOVEC_FLAG_LOCK) && |
| (flags & SPS_IOVEC_FLAG_UNLOCK)) { |
| static char err_msg[] = |
| "Can't lock and unlock a pipe by the same Command Descriptor"; |
| SPS_ERR(sps, "sps:%s.\n", err_msg); |
| return SPS_ERROR; |
| } else if ((flags & SPS_IOVEC_FLAG_IMME) && |
| (flags & SPS_IOVEC_FLAG_CMD)) { |
| SPS_ERR(sps, |
| "sps:%s:Immediate and CMD are not allowed to coexist.\n", |
| __func__); |
| return SPS_ERROR; |
| } else if ((flags & SPS_IOVEC_FLAG_IMME) && |
| (flags & SPS_IOVEC_FLAG_NWD)) { |
| SPS_ERR(sps, |
| "sps:%s:Immediate and NWD are not allowed to coexist.\n", |
| __func__); |
| return SPS_ERROR; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * Perform a DMA transfer on an SPS connection end point |
| * |
| */ |
| int sps_transfer(struct sps_pipe *h, struct sps_transfer *transfer) |
| { |
| struct sps_pipe *pipe = h; |
| struct sps_bam *bam; |
| int result; |
| struct sps_iovec *iovec; |
| int i; |
| |
| if (h == NULL) { |
| SPS_ERR(sps, "sps:%s:pipe is NULL.\n", __func__); |
| return SPS_ERROR; |
| } else if (transfer == NULL) { |
| SPS_ERR(sps, "sps:%s:transfer is NULL.\n", __func__); |
| return SPS_ERROR; |
| } else if (transfer->iovec == NULL) { |
| SPS_ERR(sps, "sps:%s:iovec list is NULL.\n", __func__); |
| return SPS_ERROR; |
| } else if (transfer->iovec_count == 0) { |
| SPS_ERR(sps, "sps:%s:iovec list is empty.\n", __func__); |
| return SPS_ERROR; |
| } else if (transfer->iovec_phys == 0) { |
| SPS_ERR(sps, |
| "sps:%s:iovec list address is invalid.\n", __func__); |
| return SPS_ERROR; |
| } |
| |
| /* Verify content of IOVECs */ |
| iovec = transfer->iovec; |
| for (i = 0; i < transfer->iovec_count; i++) { |
| u32 flags = iovec->flags; |
| |
| if (iovec->size > SPS_IOVEC_MAX_SIZE) { |
| SPS_ERR(sps, |
| "sps:%s:iovec size is invalid.\n", __func__); |
| return SPS_ERROR; |
| } |
| |
| if (sps_check_iovec_flags(flags)) |
| return SPS_ERROR; |
| |
| iovec++; |
| } |
| |
| bam = sps_bam_lock(pipe); |
| if (bam == NULL) |
| return SPS_ERROR; |
| |
| SPS_DBG(bam, "sps:%s.\n", __func__); |
| |
| result = sps_bam_pipe_transfer(bam, pipe->pipe_index, transfer); |
| |
| sps_bam_unlock(bam); |
| |
| return result; |
| } |
| EXPORT_SYMBOL(sps_transfer); |
| |
| /** |
| * Perform a single DMA transfer on an SPS connection end point |
| * |
| */ |
| int sps_transfer_one(struct sps_pipe *h, phys_addr_t addr, u32 size, |
| void *user, u32 flags) |
| { |
| struct sps_pipe *pipe = h; |
| struct sps_bam *bam; |
| int result; |
| |
| if (h == NULL) { |
| SPS_ERR(sps, "sps:%s:pipe is NULL.\n", __func__); |
| return SPS_ERROR; |
| } |
| |
| if (sps_check_iovec_flags(flags)) |
| return SPS_ERROR; |
| |
| bam = sps_bam_lock(pipe); |
| if (bam == NULL) |
| return SPS_ERROR; |
| |
| SPS_DBG(bam, "sps:%s.\n", __func__); |
| |
| result = sps_bam_pipe_transfer_one(bam, pipe->pipe_index, |
| SPS_GET_LOWER_ADDR(addr), size, user, |
| DESC_FLAG_WORD(flags, addr)); |
| |
| sps_bam_unlock(bam); |
| |
| return result; |
| } |
| EXPORT_SYMBOL(sps_transfer_one); |
| |
| /** |
| * Read event queue for an SPS connection end point |
| * |
| */ |
| int sps_get_event(struct sps_pipe *h, struct sps_event_notify *notify) |
| { |
| struct sps_pipe *pipe = h; |
| struct sps_bam *bam; |
| int result; |
| |
| if (h == NULL) { |
| SPS_ERR(sps, "sps:%s:pipe is NULL.\n", __func__); |
| return SPS_ERROR; |
| } else if (notify == NULL) { |
| SPS_ERR(sps, "sps:%s:event_notify is NULL.\n", __func__); |
| return SPS_ERROR; |
| } |
| |
| bam = sps_bam_lock(pipe); |
| if (bam == NULL) |
| return SPS_ERROR; |
| |
| SPS_DBG1(bam, "sps:%s.\n", __func__); |
| |
| result = sps_bam_pipe_get_event(bam, pipe->pipe_index, notify); |
| sps_bam_unlock(bam); |
| |
| return result; |
| } |
| EXPORT_SYMBOL(sps_get_event); |
| |
| /** |
| * Determine whether an SPS connection end point FIFO is empty |
| * |
| */ |
| int sps_is_pipe_empty(struct sps_pipe *h, u32 *empty) |
| { |
| struct sps_pipe *pipe = h; |
| struct sps_bam *bam; |
| int result; |
| |
| if (h == NULL) { |
| SPS_ERR(sps, "sps:%s:pipe is NULL.\n", __func__); |
| return SPS_ERROR; |
| } else if (empty == NULL) { |
| SPS_ERR(sps, "sps:%s:result pointer is NULL.\n", __func__); |
| return SPS_ERROR; |
| } |
| |
| bam = sps_bam_lock(pipe); |
| if (bam == NULL) |
| return SPS_ERROR; |
| |
| SPS_DBG1(bam, "sps:%s.\n", __func__); |
| |
| result = sps_bam_pipe_is_empty(bam, pipe->pipe_index, empty); |
| sps_bam_unlock(bam); |
| |
| return result; |
| } |
| EXPORT_SYMBOL(sps_is_pipe_empty); |
| |
| /** |
| * Get number of free transfer entries for an SPS connection end point |
| * |
| */ |
| int sps_get_free_count(struct sps_pipe *h, u32 *count) |
| { |
| struct sps_pipe *pipe = h; |
| struct sps_bam *bam; |
| int result; |
| |
| if (h == NULL) { |
| SPS_ERR(sps, "sps:%s:pipe is NULL.\n", __func__); |
| return SPS_ERROR; |
| } else if (count == NULL) { |
| SPS_ERR(sps, "sps:%s:result pointer is NULL.\n", __func__); |
| return SPS_ERROR; |
| } |
| |
| bam = sps_bam_lock(pipe); |
| if (bam == NULL) |
| return SPS_ERROR; |
| |
| SPS_DBG(bam, "sps:%s.\n", __func__); |
| |
| result = sps_bam_get_free_count(bam, pipe->pipe_index, count); |
| sps_bam_unlock(bam); |
| |
| return result; |
| } |
| EXPORT_SYMBOL(sps_get_free_count); |
| |
| /** |
| * Reset an SPS BAM device |
| * |
| */ |
| int sps_device_reset(unsigned long dev) |
| { |
| struct sps_bam *bam; |
| int result; |
| |
| if (dev == 0) { |
| SPS_ERR(sps, |
| "sps:%s:device handle should not be 0.\n", __func__); |
| return SPS_ERROR; |
| } |
| |
| if (sps == NULL || !sps->is_ready) { |
| SPS_DBG3(sps, "sps:%s:sps driver is not ready.\n", __func__); |
| return -EPROBE_DEFER; |
| } |
| |
| mutex_lock(&sps->lock); |
| /* Search for the target BAM device */ |
| bam = sps_h2bam(dev); |
| if (bam == NULL) { |
| SPS_ERR(sps, "sps:Invalid BAM device handle: 0x%lx", dev); |
| result = SPS_ERROR; |
| goto exit_err; |
| } |
| |
| SPS_DBG3(bam, "sps:%s.\n", __func__); |
| |
| mutex_lock(&bam->lock); |
| result = sps_bam_reset(bam); |
| mutex_unlock(&bam->lock); |
| if (result) { |
| SPS_ERR(sps, "sps:Fail to reset BAM device: 0x%lx", dev); |
| goto exit_err; |
| } |
| |
| exit_err: |
| mutex_unlock(&sps->lock); |
| |
| return result; |
| } |
| EXPORT_SYMBOL(sps_device_reset); |
| |
| /** |
| * Get the configuration parameters for an SPS connection end point |
| * |
| */ |
| int sps_get_config(struct sps_pipe *h, struct sps_connect *config) |
| { |
| struct sps_pipe *pipe = h; |
| |
| if (h == NULL) { |
| SPS_ERR(sps, "sps:%s:pipe is NULL.\n", __func__); |
| return SPS_ERROR; |
| } else if (config == NULL) { |
| SPS_ERR(sps, "sps:%s:config pointer is NULL.\n", __func__); |
| return SPS_ERROR; |
| } |
| |
| if (pipe->bam == NULL) |
| SPS_DBG(sps, "sps:%s.\n", __func__); |
| else |
| SPS_DBG(pipe->bam, |
| "sps:%s; BAM: %pa; pipe index:%d; options:0x%x.\n", |
| __func__, BAM_ID(pipe->bam), pipe->pipe_index, |
| pipe->connect.options); |
| |
| /* Copy current client connection state */ |
| *config = pipe->connect; |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(sps_get_config); |
| |
| /** |
| * Set the configuration parameters for an SPS connection end point |
| * |
| */ |
| int sps_set_config(struct sps_pipe *h, struct sps_connect *config) |
| { |
| struct sps_pipe *pipe = h; |
| struct sps_bam *bam; |
| int result; |
| |
| if (h == NULL) { |
| SPS_ERR(sps, "sps:%s:pipe is NULL.\n", __func__); |
| return SPS_ERROR; |
| } else if (config == NULL) { |
| SPS_ERR(sps, "sps:%s:config pointer is NULL.\n", __func__); |
| return SPS_ERROR; |
| } |
| |
| bam = sps_bam_lock(pipe); |
| if (bam == NULL) { |
| SPS_ERR(sps, "sps:%s:BAM is NULL.\n", __func__); |
| return SPS_ERROR; |
| } |
| |
| SPS_DBG(bam, "sps:%s; BAM: %pa; pipe index:%d, config-options:0x%x.\n", |
| __func__, BAM_ID(bam), pipe->pipe_index, config->options); |
| |
| result = sps_bam_pipe_set_params(bam, pipe->pipe_index, |
| config->options); |
| if (result == 0) |
| pipe->connect.options = config->options; |
| sps_bam_unlock(bam); |
| |
| return result; |
| } |
| EXPORT_SYMBOL(sps_set_config); |
| |
| /** |
| * Set ownership of an SPS connection end point |
| * |
| */ |
| int sps_set_owner(struct sps_pipe *h, enum sps_owner owner, |
| struct sps_satellite *connect) |
| { |
| struct sps_pipe *pipe = h; |
| struct sps_bam *bam; |
| int result; |
| |
| if (h == NULL) { |
| SPS_ERR(sps, "sps:%s:pipe is NULL.\n", __func__); |
| return SPS_ERROR; |
| } else if (connect == NULL) { |
| SPS_ERR(sps, "sps:%s:connection is NULL.\n", __func__); |
| return SPS_ERROR; |
| } |
| |
| if (owner != SPS_OWNER_REMOTE) { |
| SPS_ERR(sps, "sps:Unsupported ownership state: %d", owner); |
| return SPS_ERROR; |
| } |
| |
| bam = sps_bam_lock(pipe); |
| if (bam == NULL) |
| return SPS_ERROR; |
| |
| SPS_DBG(bam, "sps:%s; BAM: %pa; pipe index:%d.\n", |
| __func__, BAM_ID(bam), pipe->pipe_index); |
| |
| result = sps_bam_set_satellite(bam, pipe->pipe_index); |
| if (result) |
| goto exit_err; |
| |
| /* Return satellite connect info */ |
| if (connect == NULL) |
| goto exit_err; |
| |
| if (pipe->connect.mode == SPS_MODE_SRC) { |
| connect->dev = pipe->map->src.bam_phys; |
| connect->pipe_index = pipe->map->src.pipe_index; |
| } else { |
| connect->dev = pipe->map->dest.bam_phys; |
| connect->pipe_index = pipe->map->dest.pipe_index; |
| } |
| connect->config = SPS_CONFIG_SATELLITE; |
| connect->options = (enum sps_option) 0; |
| |
| exit_err: |
| sps_bam_unlock(bam); |
| |
| return result; |
| } |
| EXPORT_SYMBOL(sps_set_owner); |
| |
| /** |
| * Allocate memory from the SPS Pipe-Memory. |
| * |
| */ |
| int sps_alloc_mem(struct sps_pipe *h, enum sps_mem mem, |
| struct sps_mem_buffer *mem_buffer) |
| { |
| if (sps == NULL) |
| return -ENODEV; |
| |
| if (!sps->is_ready) { |
| SPS_ERR(sps, "sps:%s:sps driver is not ready.", __func__); |
| return -EAGAIN; |
| } |
| |
| if (mem_buffer == NULL || mem_buffer->size == 0) { |
| SPS_ERR(sps, "sps:%s:invalid memory buffer address or size", |
| __func__); |
| return SPS_ERROR; |
| } |
| |
| if (h == NULL) |
| SPS_DBG2(sps, |
| "sps:%s:allocate pipe memory before setup pipe", |
| __func__); |
| else |
| SPS_DBG2(sps, |
| "sps:allocate pipe memory for pipe %d", h->pipe_index); |
| |
| mem_buffer->phys_base = sps_mem_alloc_io(mem_buffer->size); |
| if (mem_buffer->phys_base == SPS_ADDR_INVALID) { |
| SPS_ERR(sps, "sps:%s:invalid address of allocated memory", |
| __func__); |
| return SPS_ERROR; |
| } |
| |
| mem_buffer->base = spsi_get_mem_ptr(mem_buffer->phys_base); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(sps_alloc_mem); |
| |
| /** |
| * Free memory from the SPS Pipe-Memory. |
| * |
| */ |
| int sps_free_mem(struct sps_pipe *h, struct sps_mem_buffer *mem_buffer) |
| { |
| SPS_DBG(sps, "sps:%s.", __func__); |
| |
| if (mem_buffer == NULL || mem_buffer->phys_base == SPS_ADDR_INVALID) { |
| SPS_ERR(sps, "sps:%s:invalid memory to free", __func__); |
| return SPS_ERROR; |
| } |
| |
| if (h == NULL) |
| SPS_DBG2(sps, "sps:%s:free pipe memory.", __func__); |
| else |
| SPS_DBG2(sps, |
| "sps:free pipe memory for pipe %d.", h->pipe_index); |
| |
| sps_mem_free_io(mem_buffer->phys_base, mem_buffer->size); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(sps_free_mem); |
| |
| /** |
| * Get the number of unused descriptors in the descriptor FIFO |
| * of a pipe |
| * |
| */ |
| int sps_get_unused_desc_num(struct sps_pipe *h, u32 *desc_num) |
| { |
| struct sps_pipe *pipe = h; |
| struct sps_bam *bam; |
| int result; |
| |
| if (h == NULL) { |
| SPS_ERR(sps, "sps:%s:pipe is NULL.\n", __func__); |
| return SPS_ERROR; |
| } else if (desc_num == NULL) { |
| SPS_ERR(sps, "sps:%s:result pointer is NULL.\n", __func__); |
| return SPS_ERROR; |
| } |
| |
| bam = sps_bam_lock(pipe); |
| if (bam == NULL) |
| return SPS_ERROR; |
| |
| SPS_DBG(bam, "sps:%s; BAM: %pa; pipe index:%d.\n", |
| __func__, BAM_ID(bam), pipe->pipe_index); |
| |
| result = sps_bam_pipe_get_unused_desc_num(bam, pipe->pipe_index, |
| desc_num); |
| |
| sps_bam_unlock(bam); |
| |
| return result; |
| } |
| EXPORT_SYMBOL(sps_get_unused_desc_num); |
| |
| /** |
| * Vote for or relinquish BAM DMA clock |
| * |
| */ |
| int sps_ctrl_bam_dma_clk(bool clk_on) |
| { |
| int ret; |
| |
| if (sps == NULL || !sps->is_ready) { |
| SPS_DBG3(sps, "sps:%s:sps driver is not ready.\n", __func__); |
| return -EPROBE_DEFER; |
| } |
| |
| if (clk_on == true) { |
| SPS_DBG1(sps, "%s", "sps:vote for bam dma clk.\n"); |
| ret = clk_prepare_enable(sps->bamdma_clk); |
| if (ret) { |
| SPS_ERR(sps, |
| "sps:fail to enable bamdma_clk:ret=%d\n", ret); |
| return ret; |
| } |
| } else { |
| SPS_DBG1(sps, "%s", "sps:relinquish bam dma clk.\n"); |
| clk_disable_unprepare(sps->bamdma_clk); |
| } |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(sps_ctrl_bam_dma_clk); |
| |
| /** |
| * Register a BAM device |
| * |
| */ |
| int sps_register_bam_device(const struct sps_bam_props *bam_props, |
| unsigned long *dev_handle) |
| { |
| struct sps_bam *bam = NULL; |
| void __iomem *virt_addr = NULL; |
| char bam_name[MAX_MSG_LEN]; |
| u32 manage; |
| int ok; |
| int result; |
| |
| if (bam_props == NULL) { |
| SPS_ERR(sps, "sps:%s:bam_props is NULL.\n", __func__); |
| return SPS_ERROR; |
| } else if (dev_handle == NULL) { |
| SPS_ERR(sps, "sps:%s:device handle is NULL.\n", __func__); |
| return SPS_ERROR; |
| } |
| |
| if (sps == NULL) { |
| pr_err("sps:%s:sps driver is not ready.\n", __func__); |
| return -EPROBE_DEFER; |
| } |
| |
| SPS_DBG3(sps, "sps:%s: Client requests to register BAM %pa.\n", |
| __func__, &bam_props->phys_addr); |
| |
| /* BAM-DMA is registered internally during power-up */ |
| if ((!sps->is_ready) && !(bam_props->options & SPS_BAM_OPT_BAMDMA)) { |
| SPS_ERR(sps, "sps:%s:sps driver not ready.\n", __func__); |
| return -EAGAIN; |
| } |
| |
| /* Check BAM parameters */ |
| manage = bam_props->manage & SPS_BAM_MGR_ACCESS_MASK; |
| if (manage != SPS_BAM_MGR_NONE) { |
| if (bam_props->virt_addr == NULL && bam_props->virt_size == 0) { |
| SPS_ERR(sps, "sps:Invalid properties for BAM: %pa", |
| &bam_props->phys_addr); |
| return SPS_ERROR; |
| } |
| } |
| if ((bam_props->manage & SPS_BAM_MGR_DEVICE_REMOTE) == 0) { |
| /* BAM global is configured by local processor */ |
| if (bam_props->summing_threshold == 0) { |
| SPS_ERR(sps, |
| "sps:Invalid device ctrl properties for BAM: %pa", |
| &bam_props->phys_addr); |
| return SPS_ERROR; |
| } |
| } |
| manage = bam_props->manage & |
| (SPS_BAM_MGR_PIPE_NO_CONFIG | SPS_BAM_MGR_PIPE_NO_CTRL); |
| |
| /* In case of error */ |
| *dev_handle = SPS_DEV_HANDLE_INVALID; |
| result = SPS_ERROR; |
| |
| mutex_lock(&sps->lock); |
| /* Is this BAM already registered? */ |
| bam = phy2bam(bam_props->phys_addr); |
| if (bam != NULL) { |
| mutex_unlock(&sps->lock); |
| SPS_ERR(sps, "sps:BAM is already registered: %pa", |
| &bam->props.phys_addr); |
| result = -EEXIST; |
| bam = NULL; /* Avoid error clean-up kfree(bam) */ |
| goto exit_err; |
| } |
| |
| /* Perform virtual mapping if required */ |
| if ((bam_props->manage & SPS_BAM_MGR_ACCESS_MASK) != |
| SPS_BAM_MGR_NONE && bam_props->virt_addr == NULL) { |
| /* Map the memory region */ |
| virt_addr = ioremap(bam_props->phys_addr, bam_props->virt_size); |
| if (virt_addr == NULL) { |
| SPS_ERR(sps, |
| "sps:Unable to map BAM IO mem:%pa size:0x%x", |
| &bam_props->phys_addr, bam_props->virt_size); |
| goto exit_err; |
| } |
| } |
| |
| bam = kzalloc(sizeof(*bam), GFP_KERNEL); |
| if (bam == NULL) { |
| SPS_ERR(sps, |
| "sps:Unable to allocate BAM device state: size is %zu", |
| sizeof(*bam)); |
| goto exit_err; |
| } |
| memset(bam, 0, sizeof(*bam)); |
| |
| mutex_init(&bam->lock); |
| mutex_lock(&bam->lock); |
| |
| /* Copy configuration to BAM device descriptor */ |
| bam->props = *bam_props; |
| if (virt_addr != NULL) |
| bam->props.virt_addr = virt_addr; |
| |
| snprintf(bam_name, sizeof(bam_name), "sps_bam_%pa_0", |
| &bam->props.phys_addr); |
| bam->ipc_log0 = ipc_log_context_create(SPS_IPC_LOGPAGES, |
| bam_name, 0); |
| if (!bam->ipc_log0) |
| SPS_ERR(sps, "%s : unable to create IPC Logging 0 for bam %pa", |
| __func__, &bam->props.phys_addr); |
| |
| snprintf(bam_name, sizeof(bam_name), "sps_bam_%pa_1", |
| &bam->props.phys_addr); |
| bam->ipc_log1 = ipc_log_context_create(SPS_IPC_LOGPAGES, |
| bam_name, 0); |
| if (!bam->ipc_log1) |
| SPS_ERR(sps, "%s : unable to create IPC Logging 1 for bam %pa", |
| __func__, &bam->props.phys_addr); |
| |
| snprintf(bam_name, sizeof(bam_name), "sps_bam_%pa_2", |
| &bam->props.phys_addr); |
| bam->ipc_log2 = ipc_log_context_create(SPS_IPC_LOGPAGES, |
| bam_name, 0); |
| if (!bam->ipc_log2) |
| SPS_ERR(sps, "%s : unable to create IPC Logging 2 for bam %pa", |
| __func__, &bam->props.phys_addr); |
| |
| snprintf(bam_name, sizeof(bam_name), "sps_bam_%pa_3", |
| &bam->props.phys_addr); |
| bam->ipc_log3 = ipc_log_context_create(SPS_IPC_LOGPAGES, |
| bam_name, 0); |
| if (!bam->ipc_log3) |
| SPS_ERR(sps, "%s : unable to create IPC Logging 3 for bam %pa", |
| __func__, &bam->props.phys_addr); |
| |
| snprintf(bam_name, sizeof(bam_name), "sps_bam_%pa_4", |
| &bam->props.phys_addr); |
| bam->ipc_log4 = ipc_log_context_create(SPS_IPC_LOGPAGES, |
| bam_name, 0); |
| if (!bam->ipc_log4) |
| SPS_ERR(sps, "%s : unable to create IPC Logging 4 for bam %pa", |
| __func__, &bam->props.phys_addr); |
| |
| if (bam_props->ipc_loglevel) |
| bam->ipc_loglevel = bam_props->ipc_loglevel; |
| else |
| bam->ipc_loglevel = SPS_IPC_DEFAULT_LOGLEVEL; |
| |
| ok = sps_bam_device_init(bam); |
| mutex_unlock(&bam->lock); |
| if (ok) { |
| SPS_ERR(bam, "sps:Fail to init BAM device: phys %pa", |
| &bam->props.phys_addr); |
| goto exit_err; |
| } |
| |
| /* Add BAM to the list */ |
| list_add_tail(&bam->list, &sps->bams_q); |
| *dev_handle = (uintptr_t) bam; |
| |
| result = 0; |
| exit_err: |
| mutex_unlock(&sps->lock); |
| |
| if (result) { |
| if (bam != NULL) { |
| if (virt_addr != NULL) |
| iounmap(bam->props.virt_addr); |
| kfree(bam); |
| } |
| |
| return result; |
| } |
| |
| /* If this BAM is attached to a BAM-DMA, init the BAM-DMA device */ |
| #ifdef CONFIG_SPS_SUPPORT_BAMDMA |
| if ((bam->props.options & SPS_BAM_OPT_BAMDMA)) { |
| if (sps_dma_device_init((uintptr_t) bam)) { |
| bam->props.options &= ~SPS_BAM_OPT_BAMDMA; |
| sps_deregister_bam_device((uintptr_t) bam); |
| SPS_ERR(bam, "sps:Fail to init BAM-DMA BAM: phys %pa", |
| &bam->props.phys_addr); |
| return SPS_ERROR; |
| } |
| } |
| #endif /* CONFIG_SPS_SUPPORT_BAMDMA */ |
| |
| SPS_INFO(bam, "sps:BAM %pa is registered.", &bam->props.phys_addr); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(sps_register_bam_device); |
| |
| /** |
| * Deregister a BAM device |
| * |
| */ |
| int sps_deregister_bam_device(unsigned long dev_handle) |
| { |
| struct sps_bam *bam; |
| int n; |
| |
| if (dev_handle == 0) { |
| SPS_ERR(sps, "sps:%s:device handle should not be 0.\n", |
| __func__); |
| return SPS_ERROR; |
| } |
| |
| bam = sps_h2bam(dev_handle); |
| if (bam == NULL) { |
| SPS_ERR(sps, "sps:%s:did not find a BAM for this handle", |
| __func__); |
| return SPS_ERROR; |
| } |
| |
| SPS_DBG3(sps, "sps:%s: SPS deregister BAM: phys %pa.", |
| __func__, &bam->props.phys_addr); |
| |
| if (bam->props.options & SPS_BAM_HOLD_MEM) { |
| for (n = 0; n < BAM_MAX_PIPES; n++) |
| if (bam->desc_cache_pointers[n] != NULL) |
| kfree(bam->desc_cache_pointers[n]); |
| } |
| |
| /* If this BAM is attached to a BAM-DMA, init the BAM-DMA device */ |
| #ifdef CONFIG_SPS_SUPPORT_BAMDMA |
| if ((bam->props.options & SPS_BAM_OPT_BAMDMA)) { |
| mutex_lock(&bam->lock); |
| (void)sps_dma_device_de_init((uintptr_t) bam); |
| bam->props.options &= ~SPS_BAM_OPT_BAMDMA; |
| mutex_unlock(&bam->lock); |
| } |
| #endif |
| |
| /* Remove the BAM from the registration list */ |
| mutex_lock(&sps->lock); |
| list_del(&bam->list); |
| mutex_unlock(&sps->lock); |
| |
| /* De-init the BAM and free resources */ |
| mutex_lock(&bam->lock); |
| sps_bam_device_de_init(bam); |
| mutex_unlock(&bam->lock); |
| ipc_log_context_destroy(bam->ipc_log1); |
| ipc_log_context_destroy(bam->ipc_log2); |
| if (bam->props.virt_size) |
| (void)iounmap(bam->props.virt_addr); |
| |
| kfree(bam); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(sps_deregister_bam_device); |
| |
| /** |
| * Get processed I/O vector (completed transfers) |
| * |
| */ |
| int sps_get_iovec(struct sps_pipe *h, struct sps_iovec *iovec) |
| { |
| struct sps_pipe *pipe = h; |
| struct sps_bam *bam; |
| int result; |
| |
| if (h == NULL) { |
| SPS_ERR(sps, "sps:%s:pipe is NULL.\n", __func__); |
| return SPS_ERROR; |
| } else if (iovec == NULL) { |
| SPS_ERR(sps, "sps:%s:iovec pointer is NULL.\n", __func__); |
| return SPS_ERROR; |
| } |
| |
| bam = sps_bam_lock(pipe); |
| if (bam == NULL) { |
| SPS_ERR(sps, "sps:%s:BAM is not found by handle.\n", __func__); |
| return SPS_ERROR; |
| } |
| |
| SPS_DBG(bam, "sps:%s; BAM: %pa; pipe index:%d.\n", |
| __func__, BAM_ID(bam), pipe->pipe_index); |
| |
| /* Get an iovec from the BAM pipe descriptor FIFO */ |
| result = sps_bam_pipe_get_iovec(bam, pipe->pipe_index, iovec); |
| sps_bam_unlock(bam); |
| |
| return result; |
| } |
| EXPORT_SYMBOL(sps_get_iovec); |
| |
| /** |
| * Perform timer control |
| * |
| */ |
| int sps_timer_ctrl(struct sps_pipe *h, |
| struct sps_timer_ctrl *timer_ctrl, |
| struct sps_timer_result *timer_result) |
| { |
| struct sps_pipe *pipe = h; |
| struct sps_bam *bam; |
| int result; |
| |
| if (h == NULL) { |
| SPS_ERR(sps, "sps:%s:pipe is NULL.\n", __func__); |
| return SPS_ERROR; |
| } else if (timer_ctrl == NULL) { |
| SPS_ERR(sps, "sps:%s:timer_ctrl pointer is NULL.\n", __func__); |
| return SPS_ERROR; |
| } else if (timer_result == NULL) { |
| SPS_DBG(sps, "sps:%s:no result to return.\n", __func__); |
| } |
| |
| bam = sps_bam_lock(pipe); |
| if (bam == NULL) { |
| SPS_ERR(sps, "sps:%s:BAM is not found by handle.\n", __func__); |
| return SPS_ERROR; |
| } |
| |
| SPS_DBG2(bam, "sps:%s; BAM: %pa; pipe index:%d.\n", |
| __func__, BAM_ID(bam), pipe->pipe_index); |
| |
| /* Perform the BAM pipe timer control operation */ |
| result = sps_bam_pipe_timer_ctrl(bam, pipe->pipe_index, timer_ctrl, |
| timer_result); |
| sps_bam_unlock(bam); |
| |
| return result; |
| } |
| EXPORT_SYMBOL(sps_timer_ctrl); |
| |
| /* |
| * Reset a BAM pipe |
| */ |
| int sps_pipe_reset(unsigned long dev, u32 pipe) |
| { |
| struct sps_bam *bam; |
| |
| if (!dev) { |
| SPS_ERR(sps, "sps:%s:BAM handle is NULL.\n", __func__); |
| return SPS_ERROR; |
| } |
| |
| if (pipe >= BAM_MAX_PIPES) { |
| SPS_ERR(sps, "sps:%s:pipe index is invalid.\n", __func__); |
| return SPS_ERROR; |
| } |
| |
| bam = sps_h2bam(dev); |
| if (bam == NULL) { |
| SPS_ERR(sps, "sps:%s:BAM is not found by handle.\n", __func__); |
| return SPS_ERROR; |
| } |
| |
| SPS_DBG2(bam, "sps:%s; BAM: %pa; pipe index:%d.\n", |
| __func__, BAM_ID(bam), pipe); |
| |
| bam_pipe_reset(&bam->base, pipe); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(sps_pipe_reset); |
| |
| /* |
| * Disable a BAM pipe |
| */ |
| int sps_pipe_disable(unsigned long dev, u32 pipe) |
| { |
| struct sps_bam *bam; |
| |
| if (!dev) { |
| SPS_ERR(sps, "sps:%s:BAM handle is NULL.\n", __func__); |
| return SPS_ERROR; |
| } |
| |
| if (pipe >= BAM_MAX_PIPES) { |
| SPS_ERR(sps, "sps:%s:pipe index is invalid.\n", __func__); |
| return SPS_ERROR; |
| } |
| |
| bam = sps_h2bam(dev); |
| if (bam == NULL) { |
| SPS_ERR(sps, "sps:%s:BAM is not found by handle.\n", __func__); |
| return SPS_ERROR; |
| } |
| |
| SPS_DBG(bam, "sps:%s; BAM: %pa; pipe index:%d.\n", |
| __func__, BAM_ID(bam), pipe); |
| |
| bam_disable_pipe(&bam->base, pipe); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(sps_pipe_disable); |
| |
| /* |
| * Check pending descriptors in the descriptor FIFO |
| * of a pipe |
| */ |
| int sps_pipe_pending_desc(unsigned long dev, u32 pipe, bool *pending) |
| { |
| |
| struct sps_bam *bam; |
| |
| if (!dev) { |
| SPS_ERR(sps, "sps:%s:BAM handle is NULL.\n", __func__); |
| return SPS_ERROR; |
| } |
| |
| if (pipe >= BAM_MAX_PIPES) { |
| SPS_ERR(sps, "sps:%s:pipe index is invalid.\n", __func__); |
| return SPS_ERROR; |
| } |
| |
| if (!pending) { |
| SPS_ERR(sps, "sps:%s:input flag is NULL.\n", __func__); |
| return SPS_ERROR; |
| } |
| |
| bam = sps_h2bam(dev); |
| if (bam == NULL) { |
| SPS_ERR(sps, "sps:%s:BAM is not found by handle.\n", __func__); |
| return SPS_ERROR; |
| } |
| |
| SPS_DBG(bam, "sps:%s; BAM: %pa; pipe index:%d.\n", |
| __func__, BAM_ID(bam), pipe); |
| |
| *pending = sps_bam_pipe_pending_desc(bam, pipe); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(sps_pipe_pending_desc); |
| |
| /* |
| * Process any pending IRQ of a BAM |
| */ |
| int sps_bam_process_irq(unsigned long dev) |
| { |
| struct sps_bam *bam; |
| int ret = 0; |
| |
| if (!dev) { |
| SPS_ERR(sps, "sps:%s:BAM handle is NULL.\n", __func__); |
| return SPS_ERROR; |
| } |
| |
| bam = sps_h2bam(dev); |
| if (bam == NULL) { |
| SPS_ERR(sps, "sps:%s:BAM is not found by handle.\n", __func__); |
| return SPS_ERROR; |
| } |
| |
| SPS_DBG1(bam, "sps:%s; BAM: %pa.\n", __func__, BAM_ID(bam)); |
| |
| ret = sps_bam_check_irq(bam); |
| |
| return ret; |
| } |
| EXPORT_SYMBOL(sps_bam_process_irq); |
| |
| /* |
| * Get address info of a BAM |
| */ |
| int sps_get_bam_addr(unsigned long dev, phys_addr_t *base, |
| u32 *size) |
| { |
| struct sps_bam *bam; |
| |
| if (!dev) { |
| SPS_ERR(sps, "sps:%s:BAM handle is NULL.\n", __func__); |
| return SPS_ERROR; |
| } |
| |
| bam = sps_h2bam(dev); |
| if (bam == NULL) { |
| SPS_ERR(sps, "sps:%s:BAM is not found by handle.\n", __func__); |
| return SPS_ERROR; |
| } |
| |
| *base = bam->props.phys_addr; |
| *size = bam->props.virt_size; |
| |
| SPS_DBG2(bam, "sps:%s; BAM: %pa; base:%pa; size:%d.\n", |
| __func__, BAM_ID(bam), base, *size); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(sps_get_bam_addr); |
| |
| /* |
| * Inject a ZLT with EOT for a BAM pipe |
| */ |
| int sps_pipe_inject_zlt(unsigned long dev, u32 pipe_index) |
| { |
| struct sps_bam *bam; |
| int rc; |
| |
| if (!dev) { |
| SPS_ERR(sps, "sps:%s:BAM handle is NULL.\n", __func__); |
| return SPS_ERROR; |
| } |
| |
| if (pipe_index >= BAM_MAX_PIPES) { |
| SPS_ERR(sps, "sps:%s:pipe index is invalid.\n", __func__); |
| return SPS_ERROR; |
| } |
| |
| bam = sps_h2bam(dev); |
| if (bam == NULL) { |
| SPS_ERR(sps, "sps:%s:BAM is not found by handle.\n", __func__); |
| return SPS_ERROR; |
| } |
| |
| SPS_DBG(bam, "sps:%s; BAM: %pa; pipe index:%d.\n", |
| __func__, BAM_ID(bam), pipe_index); |
| |
| rc = sps_bam_pipe_inject_zlt(bam, pipe_index); |
| if (rc) |
| SPS_ERR(bam, "sps:%s:failed to inject a ZLT.\n", __func__); |
| |
| return rc; |
| } |
| EXPORT_SYMBOL(sps_pipe_inject_zlt); |
| |
| /** |
| * Allocate client state context |
| * |
| */ |
| struct sps_pipe *sps_alloc_endpoint(void) |
| { |
| struct sps_pipe *ctx = NULL; |
| |
| SPS_DBG(sps, "sps:%s.", __func__); |
| |
| ctx = kzalloc(sizeof(struct sps_pipe), GFP_KERNEL); |
| if (ctx == NULL) { |
| SPS_ERR(sps, "sps:%s:Fail to allocate pipe context.", |
| __func__); |
| return NULL; |
| } |
| |
| sps_client_init(ctx); |
| |
| return ctx; |
| } |
| EXPORT_SYMBOL(sps_alloc_endpoint); |
| |
| /** |
| * Free client state context |
| * |
| */ |
| int sps_free_endpoint(struct sps_pipe *ctx) |
| { |
| int res; |
| |
| SPS_DBG(sps, "sps:%s.", __func__); |
| |
| if (ctx == NULL) { |
| SPS_ERR(sps, "sps:%s:pipe is NULL.\n", __func__); |
| return SPS_ERROR; |
| } |
| |
| res = sps_client_de_init(ctx); |
| |
| if (res == 0) |
| kfree(ctx); |
| |
| return res; |
| } |
| EXPORT_SYMBOL(sps_free_endpoint); |
| |
| /** |
| * Platform Driver. |
| */ |
| static int get_platform_data(struct platform_device *pdev) |
| { |
| struct resource *resource; |
| struct msm_sps_platform_data *pdata; |
| |
| SPS_DBG3(sps, "sps:%s.", __func__); |
| |
| pdata = pdev->dev.platform_data; |
| |
| if (pdata == NULL) { |
| SPS_ERR(sps, "sps:%s:inavlid platform data.\n", __func__); |
| sps->bamdma_restricted_pipes = 0; |
| return -EINVAL; |
| } |
| sps->bamdma_restricted_pipes = pdata->bamdma_restricted_pipes; |
| SPS_DBG3(sps, "sps:bamdma_restricted_pipes=0x%x.\n", |
| sps->bamdma_restricted_pipes); |
| |
| resource = platform_get_resource_byname(pdev, IORESOURCE_MEM, |
| "pipe_mem"); |
| if (resource) { |
| sps->pipemem_phys_base = resource->start; |
| sps->pipemem_size = resource_size(resource); |
| SPS_DBG3(sps, "sps:pipemem.base=%pa,size=0x%x.\n", |
| &sps->pipemem_phys_base, |
| sps->pipemem_size); |
| } |
| |
| #ifdef CONFIG_SPS_SUPPORT_BAMDMA |
| resource = platform_get_resource_byname(pdev, IORESOURCE_MEM, |
| "bamdma_bam"); |
| if (resource) { |
| sps->bamdma_bam_phys_base = resource->start; |
| sps->bamdma_bam_size = resource_size(resource); |
| SPS_DBG(sps, "sps:bamdma_bam.base=%pa,size=0x%x.", |
| &sps->bamdma_bam_phys_base, |
| sps->bamdma_bam_size); |
| } |
| |
| resource = platform_get_resource_byname(pdev, IORESOURCE_MEM, |
| "bamdma_dma"); |
| if (resource) { |
| sps->bamdma_dma_phys_base = resource->start; |
| sps->bamdma_dma_size = resource_size(resource); |
| SPS_DBG(sps, "sps:bamdma_dma.base=%pa,size=0x%x.", |
| &sps->bamdma_dma_phys_base, |
| sps->bamdma_dma_size); |
| } |
| |
| resource = platform_get_resource_byname(pdev, IORESOURCE_IRQ, |
| "bamdma_irq"); |
| if (resource) { |
| sps->bamdma_irq = resource->start; |
| SPS_DBG(sps, "sps:bamdma_irq=%d.", sps->bamdma_irq); |
| } |
| #endif |
| |
| return 0; |
| } |
| |
| /** |
| * Read data from device tree |
| */ |
| static int get_device_tree_data(struct platform_device *pdev) |
| { |
| #ifdef CONFIG_SPS_SUPPORT_BAMDMA |
| struct resource *resource; |
| |
| SPS_DBG(sps, "sps:%s.", __func__); |
| |
| if (of_property_read_u32((&pdev->dev)->of_node, |
| "qcom,bam-dma-res-pipes", |
| &sps->bamdma_restricted_pipes)) |
| SPS_DBG(sps, |
| "sps:%s:No restricted bamdma pipes on this target.\n", |
| __func__); |
| else |
| SPS_DBG(sps, "sps:bamdma_restricted_pipes=0x%x.", |
| sps->bamdma_restricted_pipes); |
| |
| resource = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
| if (resource) { |
| sps->bamdma_bam_phys_base = resource->start; |
| sps->bamdma_bam_size = resource_size(resource); |
| SPS_DBG(sps, "sps:bamdma_bam.base=%pa,size=0x%x.", |
| &sps->bamdma_bam_phys_base, |
| sps->bamdma_bam_size); |
| } else { |
| SPS_ERR(sps, "sps:%s:BAM DMA BAM mem unavailable.", __func__); |
| return -ENODEV; |
| } |
| |
| resource = platform_get_resource(pdev, IORESOURCE_MEM, 1); |
| if (resource) { |
| sps->bamdma_dma_phys_base = resource->start; |
| sps->bamdma_dma_size = resource_size(resource); |
| SPS_DBG(sps, "sps:bamdma_dma.base=%pa,size=0x%x.", |
| &sps->bamdma_dma_phys_base, |
| sps->bamdma_dma_size); |
| } else { |
| SPS_ERR(sps, "sps:%s:BAM DMA mem unavailable.", __func__); |
| return -ENODEV; |
| } |
| |
| resource = platform_get_resource(pdev, IORESOURCE_MEM, 2); |
| if (resource) { |
| imem = true; |
| sps->pipemem_phys_base = resource->start; |
| sps->pipemem_size = resource_size(resource); |
| SPS_DBG(sps, "sps:pipemem.base=%pa,size=0x%x.", |
| &sps->pipemem_phys_base, |
| sps->pipemem_size); |
| } else { |
| imem = false; |
| SPS_DBG(sps, "sps:%s:No pipe memory on this target.\n", |
| __func__); |
| } |
| |
| resource = platform_get_resource(pdev, IORESOURCE_IRQ, 0); |
| if (resource) { |
| sps->bamdma_irq = resource->start; |
| SPS_DBG(sps, "sps:bamdma_irq=%d.", sps->bamdma_irq); |
| } else { |
| SPS_ERR(sps, "sps:%s:BAM DMA IRQ unavailable.", __func__); |
| return -ENODEV; |
| } |
| #endif |
| |
| if (of_property_read_u32((&pdev->dev)->of_node, |
| "qcom,device-type", |
| &d_type)) { |
| d_type = 3; |
| SPS_DBG3(sps, "sps:default device type %d.\n", d_type); |
| } else |
| SPS_DBG3(sps, "sps:device type is %d.", d_type); |
| |
| enhd_pipe = of_property_read_bool((&pdev->dev)->of_node, |
| "qcom,pipe-attr-ee"); |
| SPS_DBG3(sps, "sps:PIPE_ATTR_EE is %s supported.\n", |
| (enhd_pipe ? "" : "not")); |
| |
| return 0; |
| } |
| |
| static const struct of_device_id msm_sps_match[] = { |
| { .compatible = "qcom,msm_sps", |
| .data = &bam_types[SPS_BAM_NDP] |
| }, |
| { .compatible = "qcom,msm_sps_4k", |
| .data = &bam_types[SPS_BAM_NDP_4K] |
| }, |
| {} |
| }; |
| |
| static int msm_sps_probe(struct platform_device *pdev) |
| { |
| int ret = -ENODEV; |
| |
| SPS_DBG3(sps, "sps:%s.", __func__); |
| |
| if (pdev->dev.of_node) { |
| const struct of_device_id *match; |
| |
| if (get_device_tree_data(pdev)) { |
| SPS_ERR(sps, |
| "sps:%s:Fail to get data from device tree.", |
| __func__); |
| return -ENODEV; |
| } |
| SPS_DBG(sps, "%s", "sps:get data from device tree."); |
| |
| match = of_match_device(msm_sps_match, &pdev->dev); |
| if (match) { |
| bam_type = *((enum sps_bam_type *)(match->data)); |
| SPS_DBG3(sps, "sps:BAM type is:%d\n", bam_type); |
| } else { |
| bam_type = SPS_BAM_NDP; |
| SPS_DBG3(sps, "sps:use default BAM type:%d\n", |
| bam_type); |
| } |
| } else { |
| d_type = 0; |
| if (get_platform_data(pdev)) { |
| SPS_ERR(sps, "sps:%s:Fail to get platform data.", |
| __func__); |
| return -ENODEV; |
| } |
| SPS_DBG(sps, "%s", "sps:get platform data."); |
| bam_type = SPS_BAM_LEGACY; |
| } |
| |
| /* Create Device */ |
| sps->dev_class = class_create(THIS_MODULE, SPS_DRV_NAME); |
| |
| ret = alloc_chrdev_region(&sps->dev_num, 0, 1, SPS_DRV_NAME); |
| if (ret) { |
| SPS_ERR(sps, "sps:%s:alloc_chrdev_region err.", __func__); |
| goto alloc_chrdev_region_err; |
| } |
| |
| sps->dev = device_create(sps->dev_class, NULL, sps->dev_num, sps, |
| SPS_DRV_NAME); |
| if (IS_ERR(sps->dev)) { |
| SPS_ERR(sps, "sps:%s:device_create err.", __func__); |
| goto device_create_err; |
| } |
| |
| if (pdev->dev.of_node) |
| sps->dev->of_node = pdev->dev.of_node; |
| |
| if (!d_type) { |
| sps->pmem_clk = clk_get(sps->dev, "mem_clk"); |
| if (IS_ERR(sps->pmem_clk)) { |
| if (PTR_ERR(sps->pmem_clk) == -EPROBE_DEFER) |
| ret = -EPROBE_DEFER; |
| else |
| SPS_ERR(sps, "sps:%s:fail to get pmem_clk.", |
| __func__); |
| goto pmem_clk_err; |
| } else { |
| ret = clk_prepare_enable(sps->pmem_clk); |
| if (ret) { |
| SPS_ERR(sps, |
| "sps:%s:failed to enable pmem_clk.", |
| __func__); |
| goto pmem_clk_en_err; |
| } |
| } |
| } |
| |
| #ifdef CONFIG_SPS_SUPPORT_BAMDMA |
| sps->dfab_clk = clk_get(sps->dev, "dfab_clk"); |
| if (IS_ERR(sps->dfab_clk)) { |
| if (PTR_ERR(sps->dfab_clk) == -EPROBE_DEFER) |
| ret = -EPROBE_DEFER; |
| else |
| SPS_ERR(sps, "sps:%s:fail to get dfab_clk.", __func__); |
| goto dfab_clk_err; |
| } else { |
| ret = clk_set_rate(sps->dfab_clk, 64000000); |
| if (ret) { |
| SPS_ERR(sps, "sps:%s:failed to set dfab_clk rate.", |
| __func__); |
| clk_put(sps->dfab_clk); |
| goto dfab_clk_err; |
| } |
| } |
| |
| sps->bamdma_clk = clk_get(sps->dev, "dma_bam_pclk"); |
| if (IS_ERR(sps->bamdma_clk)) { |
| if (PTR_ERR(sps->bamdma_clk) == -EPROBE_DEFER) |
| ret = -EPROBE_DEFER; |
| else |
| SPS_ERR(sps, "sps:%s:fail to get bamdma_clk.", |
| __func__); |
| clk_put(sps->dfab_clk); |
| goto dfab_clk_err; |
| } else { |
| ret = clk_prepare_enable(sps->bamdma_clk); |
| if (ret) { |
| SPS_ERR(sps, "sps:failed to enable bamdma_clk. ret=%d", |
| ret); |
| clk_put(sps->bamdma_clk); |
| clk_put(sps->dfab_clk); |
| goto dfab_clk_err; |
| } |
| } |
| |
| ret = clk_prepare_enable(sps->dfab_clk); |
| if (ret) { |
| SPS_ERR(sps, "sps:failed to enable dfab_clk. ret=%d", ret); |
| clk_disable_unprepare(sps->bamdma_clk); |
| clk_put(sps->bamdma_clk); |
| clk_put(sps->dfab_clk); |
| goto dfab_clk_err; |
| } |
| #endif |
| ret = sps_device_init(); |
| if (ret) { |
| SPS_ERR(sps, "sps:%s:sps_device_init err.", __func__); |
| |
| #ifdef CONFIG_SPS_SUPPORT_BAMDMA |
| clk_disable_unprepare(sps->dfab_clk); |
| clk_disable_unprepare(sps->bamdma_clk); |
| clk_put(sps->bamdma_clk); |
| clk_put(sps->dfab_clk); |
| #endif |
| goto dfab_clk_err; |
| } |
| #ifdef CONFIG_SPS_SUPPORT_BAMDMA |
| clk_disable_unprepare(sps->dfab_clk); |
| clk_disable_unprepare(sps->bamdma_clk); |
| #endif |
| sps->is_ready = true; |
| |
| SPS_INFO(sps, "%s", "sps:sps is ready.\n"); |
| |
| return 0; |
| dfab_clk_err: |
| if (!d_type) |
| clk_disable_unprepare(sps->pmem_clk); |
| pmem_clk_en_err: |
| if (!d_type) |
| clk_put(sps->pmem_clk); |
| pmem_clk_err: |
| device_destroy(sps->dev_class, sps->dev_num); |
| device_create_err: |
| unregister_chrdev_region(sps->dev_num, 1); |
| alloc_chrdev_region_err: |
| class_destroy(sps->dev_class); |
| |
| return ret; |
| } |
| |
| static int msm_sps_remove(struct platform_device *pdev) |
| { |
| SPS_DBG3(sps, "sps:%s.\n", __func__); |
| |
| device_destroy(sps->dev_class, sps->dev_num); |
| unregister_chrdev_region(sps->dev_num, 1); |
| class_destroy(sps->dev_class); |
| sps_device_de_init(); |
| |
| clk_put(sps->dfab_clk); |
| if (!d_type) |
| clk_put(sps->pmem_clk); |
| clk_put(sps->bamdma_clk); |
| |
| return 0; |
| } |
| |
| static struct platform_driver msm_sps_driver = { |
| .probe = msm_sps_probe, |
| .driver = { |
| .name = SPS_DRV_NAME, |
| .owner = THIS_MODULE, |
| .of_match_table = msm_sps_match, |
| }, |
| .remove = msm_sps_remove, |
| }; |
| |
| /** |
| * Module Init. |
| */ |
| static int __init sps_init(void) |
| { |
| int ret; |
| |
| #ifdef CONFIG_DEBUG_FS |
| sps_debugfs_init(); |
| #endif |
| |
| pr_debug("sps:%s.", __func__); |
| |
| /* Allocate the SPS driver state struct */ |
| sps = kzalloc(sizeof(*sps), GFP_KERNEL); |
| if (sps == NULL) |
| return -ENOMEM; |
| |
| sps->ipc_log0 = ipc_log_context_create(SPS_IPC_LOGPAGES, |
| "sps_ipc_log0", 0); |
| if (!sps->ipc_log0) |
| pr_err("Failed to create IPC log0\n"); |
| sps->ipc_log1 = ipc_log_context_create(SPS_IPC_LOGPAGES, |
| "sps_ipc_log1", 0); |
| if (!sps->ipc_log1) |
| pr_err("Failed to create IPC log1\n"); |
| sps->ipc_log2 = ipc_log_context_create(SPS_IPC_LOGPAGES, |
| "sps_ipc_log2", 0); |
| if (!sps->ipc_log2) |
| pr_err("Failed to create IPC log2\n"); |
| sps->ipc_log3 = ipc_log_context_create(SPS_IPC_LOGPAGES, |
| "sps_ipc_log3", 0); |
| if (!sps->ipc_log3) |
| pr_err("Failed to create IPC log3\n"); |
| sps->ipc_log4 = ipc_log_context_create(SPS_IPC_LOGPAGES * |
| SPS_IPC_REG_DUMP_FACTOR, "sps_ipc_log4", 0); |
| if (!sps->ipc_log4) |
| pr_err("Failed to create IPC log4\n"); |
| |
| ret = platform_driver_register(&msm_sps_driver); |
| |
| return ret; |
| } |
| |
| /** |
| * Module Exit. |
| */ |
| static void __exit sps_exit(void) |
| { |
| pr_debug("sps:%s.", __func__); |
| |
| platform_driver_unregister(&msm_sps_driver); |
| |
| if (sps != NULL) { |
| kfree(sps); |
| sps = NULL; |
| } |
| |
| #ifdef CONFIG_DEBUG_FS |
| sps_debugfs_exit(); |
| #endif |
| } |
| |
| arch_initcall(sps_init); |
| module_exit(sps_exit); |
| |
| MODULE_LICENSE("GPL v2"); |
| MODULE_DESCRIPTION("Smart Peripheral Switch (SPS)"); |