| /* Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 and |
| * only version 2 as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| */ |
| |
| #include <linux/clk.h> |
| #include <linux/device.h> |
| #include <linux/dmapool.h> |
| #include <linux/fs.h> |
| #include <linux/genalloc.h> |
| #include <linux/init.h> |
| #include <linux/kernel.h> |
| #include <linux/mm.h> |
| #include <linux/module.h> |
| #include <linux/of.h> |
| #include <linux/platform_device.h> |
| #include <linux/rbtree.h> |
| #include <linux/uaccess.h> |
| #include <mach/msm_bus.h> |
| #include <mach/msm_bus_board.h> |
| #include "ipa_i.h" |
| #include "ipa_rm_i.h" |
| |
| #define IPA_SUMMING_THRESHOLD (0x10) |
| #define IPA_PIPE_MEM_START_OFST (0x0) |
| #define IPA_PIPE_MEM_SIZE (0x0) |
| #define IPA_MOBILE_AP_MODE(x) (x == IPA_MODE_MOBILE_AP_ETH || \ |
| x == IPA_MODE_MOBILE_AP_WAN || \ |
| x == IPA_MODE_MOBILE_AP_WLAN) |
| #define IPA_CNOC_CLK_RATE (75 * 1000 * 1000UL) |
| #define IPA_V1_CLK_RATE (92.31 * 1000 * 1000UL) |
| #define IPA_V1_1_CLK_RATE (100 * 1000 * 1000UL) |
| #define IPA_DEFAULT_HEADER_LENGTH (8) |
| #define IPA_DMA_POOL_SIZE (512) |
| #define IPA_DMA_POOL_ALIGNMENT (4) |
| #define IPA_DMA_POOL_BOUNDARY (1024) |
| #define IPA_ROUTING_RULE_BYTE_SIZE (4) |
| #define IPA_BAM_CNFG_BITS_VAL (0x7FFFE004) |
| |
| #define IPA_AGGR_MAX_STR_LENGTH (10) |
| |
| #define IPA_AGGR_STR_IN_BYTES(str) \ |
| (strnlen((str), IPA_AGGR_MAX_STR_LENGTH - 1) + 1) |
| |
| static struct ipa_plat_drv_res ipa_res = {0, }; |
| static struct of_device_id ipa_plat_drv_match[] = { |
| { |
| .compatible = "qcom,ipa", |
| }, |
| |
| { |
| } |
| }; |
| |
| static struct clk *ipa_clk_src; |
| static struct clk *ipa_clk; |
| static struct clk *sys_noc_ipa_axi_clk; |
| static struct clk *ipa_cnoc_clk; |
| static struct clk *ipa_inactivity_clk; |
| |
| static struct msm_bus_vectors ipa_init_vectors[] = { |
| { |
| .src = MSM_BUS_MASTER_IPA, |
| .dst = MSM_BUS_SLAVE_EBI_CH0, |
| .ab = 0, |
| .ib = 0, |
| }, |
| }; |
| |
| static struct msm_bus_vectors ipa_max_perf_vectors[] = { |
| { |
| .src = MSM_BUS_MASTER_IPA, |
| .dst = MSM_BUS_SLAVE_EBI_CH0, |
| .ab = 50000000, |
| .ib = 960000000, |
| }, |
| }; |
| |
| static struct msm_bus_paths ipa_usecases[] = { |
| { |
| ARRAY_SIZE(ipa_init_vectors), |
| ipa_init_vectors, |
| }, |
| { |
| ARRAY_SIZE(ipa_max_perf_vectors), |
| ipa_max_perf_vectors, |
| }, |
| }; |
| |
| static struct msm_bus_scale_pdata ipa_bus_client_pdata = { |
| ipa_usecases, |
| ARRAY_SIZE(ipa_usecases), |
| .name = "ipa", |
| }; |
| |
| static uint32_t ipa_bus_hdl; |
| |
| static struct device *ipa_dev; |
| struct ipa_context *ipa_ctx; |
| |
| static bool polling_mode; |
| module_param(polling_mode, bool, 0644); |
| MODULE_PARM_DESC(polling_mode, |
| "1 - pure polling mode; 0 - interrupt+polling mode"); |
| static uint polling_delay_ms = 50; |
| module_param(polling_delay_ms, uint, 0644); |
| MODULE_PARM_DESC(polling_delay_ms, "set to desired delay between polls"); |
| static bool hdr_tbl_lcl = 1; |
| module_param(hdr_tbl_lcl, bool, 0644); |
| MODULE_PARM_DESC(hdr_tbl_lcl, "where hdr tbl resides 1-local; 0-system"); |
| static bool ip4_rt_tbl_lcl = 1; |
| module_param(ip4_rt_tbl_lcl, bool, 0644); |
| MODULE_PARM_DESC(ip4_rt_tbl_lcl, |
| "where ip4 rt tables reside 1-local; 0-system"); |
| static bool ip6_rt_tbl_lcl = 1; |
| module_param(ip6_rt_tbl_lcl, bool, 0644); |
| MODULE_PARM_DESC(ip6_rt_tbl_lcl, |
| "where ip6 rt tables reside 1-local; 0-system"); |
| static bool ip4_flt_tbl_lcl = 1; |
| module_param(ip4_flt_tbl_lcl, bool, 0644); |
| MODULE_PARM_DESC(ip4_flt_tbl_lcl, |
| "where ip4 flt tables reside 1-local; 0-system"); |
| static bool ip6_flt_tbl_lcl = 1; |
| module_param(ip6_flt_tbl_lcl, bool, 0644); |
| MODULE_PARM_DESC(ip6_flt_tbl_lcl, |
| "where ip6 flt tables reside 1-local; 0-system"); |
| |
| static int ipa_load_pipe_connection(struct platform_device *pdev, |
| enum a2_mux_pipe_direction pipe_dir, |
| struct a2_mux_pipe_connection *pdata); |
| |
| static int ipa_update_connections_info(struct device_node *node, |
| struct a2_mux_pipe_connection *pipe_connection); |
| |
| static void ipa_set_aggregation_params(void); |
| |
| static int ipa_open(struct inode *inode, struct file *filp) |
| { |
| struct ipa_context *ctx = NULL; |
| |
| IPADBG("ENTER\n"); |
| ctx = container_of(inode->i_cdev, struct ipa_context, cdev); |
| filp->private_data = ctx; |
| |
| return 0; |
| } |
| |
| static long ipa_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) |
| { |
| int retval = 0; |
| u32 pyld_sz; |
| u8 header[128] = { 0 }; |
| u8 *param = NULL; |
| struct ipa_ioc_nat_alloc_mem nat_mem; |
| struct ipa_ioc_v4_nat_init nat_init; |
| struct ipa_ioc_v4_nat_del nat_del; |
| size_t sz; |
| |
| IPADBG("cmd=%x nr=%d\n", cmd, _IOC_NR(cmd)); |
| |
| if (_IOC_TYPE(cmd) != IPA_IOC_MAGIC) |
| return -ENOTTY; |
| if (_IOC_NR(cmd) >= IPA_IOCTL_MAX) |
| return -ENOTTY; |
| |
| switch (cmd) { |
| case IPA_IOC_ALLOC_NAT_MEM: |
| if (copy_from_user((u8 *)&nat_mem, (u8 *)arg, |
| sizeof(struct ipa_ioc_nat_alloc_mem))) { |
| retval = -EFAULT; |
| break; |
| } |
| |
| if (allocate_nat_device(&nat_mem)) { |
| retval = -EFAULT; |
| break; |
| } |
| if (copy_to_user((u8 *)arg, (u8 *)&nat_mem, |
| sizeof(struct ipa_ioc_nat_alloc_mem))) { |
| retval = -EFAULT; |
| break; |
| } |
| break; |
| case IPA_IOC_V4_INIT_NAT: |
| if (copy_from_user((u8 *)&nat_init, (u8 *)arg, |
| sizeof(struct ipa_ioc_v4_nat_init))) { |
| retval = -EFAULT; |
| break; |
| } |
| if (ipa_nat_init_cmd(&nat_init)) { |
| retval = -EFAULT; |
| break; |
| } |
| break; |
| |
| case IPA_IOC_NAT_DMA: |
| if (copy_from_user(header, (u8 *)arg, |
| sizeof(struct ipa_ioc_nat_dma_cmd))) { |
| retval = -EFAULT; |
| break; |
| } |
| |
| pyld_sz = |
| sizeof(struct ipa_ioc_nat_dma_cmd) + |
| ((struct ipa_ioc_nat_dma_cmd *)header)->entries * |
| sizeof(struct ipa_ioc_nat_dma_one); |
| param = kzalloc(pyld_sz, GFP_KERNEL); |
| if (!param) { |
| retval = -ENOMEM; |
| break; |
| } |
| |
| if (copy_from_user(param, (u8 *)arg, pyld_sz)) { |
| retval = -EFAULT; |
| break; |
| } |
| |
| if (ipa_nat_dma_cmd((struct ipa_ioc_nat_dma_cmd *)param)) { |
| retval = -EFAULT; |
| break; |
| } |
| break; |
| |
| case IPA_IOC_V4_DEL_NAT: |
| if (copy_from_user((u8 *)&nat_del, (u8 *)arg, |
| sizeof(struct ipa_ioc_v4_nat_del))) { |
| retval = -EFAULT; |
| break; |
| } |
| if (ipa_nat_del_cmd(&nat_del)) { |
| retval = -EFAULT; |
| break; |
| } |
| break; |
| |
| case IPA_IOC_ADD_HDR: |
| if (copy_from_user(header, (u8 *)arg, |
| sizeof(struct ipa_ioc_add_hdr))) { |
| retval = -EFAULT; |
| break; |
| } |
| pyld_sz = |
| sizeof(struct ipa_ioc_add_hdr) + |
| ((struct ipa_ioc_add_hdr *)header)->num_hdrs * |
| sizeof(struct ipa_hdr_add); |
| param = kzalloc(pyld_sz, GFP_KERNEL); |
| if (!param) { |
| retval = -ENOMEM; |
| break; |
| } |
| if (copy_from_user(param, (u8 *)arg, pyld_sz)) { |
| retval = -EFAULT; |
| break; |
| } |
| if (ipa_add_hdr((struct ipa_ioc_add_hdr *)param)) { |
| retval = -EFAULT; |
| break; |
| } |
| if (copy_to_user((u8 *)arg, param, pyld_sz)) { |
| retval = -EFAULT; |
| break; |
| } |
| break; |
| |
| case IPA_IOC_DEL_HDR: |
| if (copy_from_user(header, (u8 *)arg, |
| sizeof(struct ipa_ioc_del_hdr))) { |
| retval = -EFAULT; |
| break; |
| } |
| pyld_sz = |
| sizeof(struct ipa_ioc_del_hdr) + |
| ((struct ipa_ioc_del_hdr *)header)->num_hdls * |
| sizeof(struct ipa_hdr_del); |
| param = kzalloc(pyld_sz, GFP_KERNEL); |
| if (!param) { |
| retval = -ENOMEM; |
| break; |
| } |
| if (copy_from_user(param, (u8 *)arg, pyld_sz)) { |
| retval = -EFAULT; |
| break; |
| } |
| if (ipa_del_hdr((struct ipa_ioc_del_hdr *)param)) { |
| retval = -EFAULT; |
| break; |
| } |
| if (copy_to_user((u8 *)arg, param, pyld_sz)) { |
| retval = -EFAULT; |
| break; |
| } |
| break; |
| |
| case IPA_IOC_ADD_RT_RULE: |
| if (copy_from_user(header, (u8 *)arg, |
| sizeof(struct ipa_ioc_add_rt_rule))) { |
| retval = -EFAULT; |
| break; |
| } |
| pyld_sz = |
| sizeof(struct ipa_ioc_add_rt_rule) + |
| ((struct ipa_ioc_add_rt_rule *)header)->num_rules * |
| sizeof(struct ipa_rt_rule_add); |
| param = kzalloc(pyld_sz, GFP_KERNEL); |
| if (!param) { |
| retval = -ENOMEM; |
| break; |
| } |
| if (copy_from_user(param, (u8 *)arg, pyld_sz)) { |
| retval = -EFAULT; |
| break; |
| } |
| if (ipa_add_rt_rule((struct ipa_ioc_add_rt_rule *)param)) { |
| retval = -EFAULT; |
| break; |
| } |
| if (copy_to_user((u8 *)arg, param, pyld_sz)) { |
| retval = -EFAULT; |
| break; |
| } |
| break; |
| |
| case IPA_IOC_DEL_RT_RULE: |
| if (copy_from_user(header, (u8 *)arg, |
| sizeof(struct ipa_ioc_del_rt_rule))) { |
| retval = -EFAULT; |
| break; |
| } |
| pyld_sz = |
| sizeof(struct ipa_ioc_del_rt_rule) + |
| ((struct ipa_ioc_del_rt_rule *)header)->num_hdls * |
| sizeof(struct ipa_rt_rule_del); |
| param = kzalloc(pyld_sz, GFP_KERNEL); |
| if (!param) { |
| retval = -ENOMEM; |
| break; |
| } |
| if (copy_from_user(param, (u8 *)arg, pyld_sz)) { |
| retval = -EFAULT; |
| break; |
| } |
| if (ipa_del_rt_rule((struct ipa_ioc_del_rt_rule *)param)) { |
| retval = -EFAULT; |
| break; |
| } |
| if (copy_to_user((u8 *)arg, param, pyld_sz)) { |
| retval = -EFAULT; |
| break; |
| } |
| break; |
| |
| case IPA_IOC_ADD_FLT_RULE: |
| if (copy_from_user(header, (u8 *)arg, |
| sizeof(struct ipa_ioc_add_flt_rule))) { |
| retval = -EFAULT; |
| break; |
| } |
| pyld_sz = |
| sizeof(struct ipa_ioc_add_flt_rule) + |
| ((struct ipa_ioc_add_flt_rule *)header)->num_rules * |
| sizeof(struct ipa_flt_rule_add); |
| param = kzalloc(pyld_sz, GFP_KERNEL); |
| if (!param) { |
| retval = -ENOMEM; |
| break; |
| } |
| if (copy_from_user(param, (u8 *)arg, pyld_sz)) { |
| retval = -EFAULT; |
| break; |
| } |
| if (ipa_add_flt_rule((struct ipa_ioc_add_flt_rule *)param)) { |
| retval = -EFAULT; |
| break; |
| } |
| if (copy_to_user((u8 *)arg, param, pyld_sz)) { |
| retval = -EFAULT; |
| break; |
| } |
| break; |
| |
| case IPA_IOC_DEL_FLT_RULE: |
| if (copy_from_user(header, (u8 *)arg, |
| sizeof(struct ipa_ioc_del_flt_rule))) { |
| retval = -EFAULT; |
| break; |
| } |
| pyld_sz = |
| sizeof(struct ipa_ioc_del_flt_rule) + |
| ((struct ipa_ioc_del_flt_rule *)header)->num_hdls * |
| sizeof(struct ipa_flt_rule_del); |
| param = kzalloc(pyld_sz, GFP_KERNEL); |
| if (!param) { |
| retval = -ENOMEM; |
| break; |
| } |
| if (copy_from_user(param, (u8 *)arg, pyld_sz)) { |
| retval = -EFAULT; |
| break; |
| } |
| if (ipa_del_flt_rule((struct ipa_ioc_del_flt_rule *)param)) { |
| retval = -EFAULT; |
| break; |
| } |
| if (copy_to_user((u8 *)arg, param, pyld_sz)) { |
| retval = -EFAULT; |
| break; |
| } |
| break; |
| |
| case IPA_IOC_COMMIT_HDR: |
| retval = ipa_commit_hdr(); |
| break; |
| case IPA_IOC_RESET_HDR: |
| retval = ipa_reset_hdr(); |
| break; |
| case IPA_IOC_COMMIT_RT: |
| retval = ipa_commit_rt(arg); |
| break; |
| case IPA_IOC_RESET_RT: |
| retval = ipa_reset_rt(arg); |
| break; |
| case IPA_IOC_COMMIT_FLT: |
| retval = ipa_commit_flt(arg); |
| break; |
| case IPA_IOC_RESET_FLT: |
| retval = ipa_reset_flt(arg); |
| break; |
| case IPA_IOC_DUMP: |
| ipa_dump(); |
| break; |
| case IPA_IOC_GET_RT_TBL: |
| if (copy_from_user(header, (u8 *)arg, |
| sizeof(struct ipa_ioc_get_rt_tbl))) { |
| retval = -EFAULT; |
| break; |
| } |
| if (ipa_get_rt_tbl((struct ipa_ioc_get_rt_tbl *)header)) { |
| retval = -EFAULT; |
| break; |
| } |
| if (copy_to_user((u8 *)arg, header, |
| sizeof(struct ipa_ioc_get_rt_tbl))) { |
| retval = -EFAULT; |
| break; |
| } |
| break; |
| case IPA_IOC_PUT_RT_TBL: |
| retval = ipa_put_rt_tbl(arg); |
| break; |
| case IPA_IOC_GET_HDR: |
| if (copy_from_user(header, (u8 *)arg, |
| sizeof(struct ipa_ioc_get_hdr))) { |
| retval = -EFAULT; |
| break; |
| } |
| if (ipa_get_hdr((struct ipa_ioc_get_hdr *)header)) { |
| retval = -EFAULT; |
| break; |
| } |
| if (copy_to_user((u8 *)arg, header, |
| sizeof(struct ipa_ioc_get_hdr))) { |
| retval = -EFAULT; |
| break; |
| } |
| break; |
| case IPA_IOC_PUT_HDR: |
| retval = ipa_put_hdr(arg); |
| break; |
| case IPA_IOC_SET_FLT: |
| retval = ipa_cfg_filter(arg); |
| break; |
| case IPA_IOC_COPY_HDR: |
| if (copy_from_user(header, (u8 *)arg, |
| sizeof(struct ipa_ioc_copy_hdr))) { |
| retval = -EFAULT; |
| break; |
| } |
| if (ipa_copy_hdr((struct ipa_ioc_copy_hdr *)header)) { |
| retval = -EFAULT; |
| break; |
| } |
| if (copy_to_user((u8 *)arg, header, |
| sizeof(struct ipa_ioc_copy_hdr))) { |
| retval = -EFAULT; |
| break; |
| } |
| break; |
| case IPA_IOC_QUERY_INTF: |
| if (copy_from_user(header, (u8 *)arg, |
| sizeof(struct ipa_ioc_query_intf))) { |
| retval = -EFAULT; |
| break; |
| } |
| if (ipa_query_intf((struct ipa_ioc_query_intf *)header)) { |
| retval = -1; |
| break; |
| } |
| if (copy_to_user((u8 *)arg, header, |
| sizeof(struct ipa_ioc_query_intf))) { |
| retval = -EFAULT; |
| break; |
| } |
| break; |
| case IPA_IOC_QUERY_INTF_TX_PROPS: |
| sz = sizeof(struct ipa_ioc_query_intf_tx_props); |
| if (copy_from_user(header, (u8 *)arg, sz)) { |
| retval = -EFAULT; |
| break; |
| } |
| pyld_sz = sz + ((struct ipa_ioc_query_intf_tx_props *) |
| header)->num_tx_props * |
| sizeof(struct ipa_ioc_tx_intf_prop); |
| param = kzalloc(pyld_sz, GFP_KERNEL); |
| if (!param) { |
| retval = -ENOMEM; |
| break; |
| } |
| if (copy_from_user(param, (u8 *)arg, pyld_sz)) { |
| retval = -EFAULT; |
| break; |
| } |
| if (ipa_query_intf_tx_props( |
| (struct ipa_ioc_query_intf_tx_props *)param)) { |
| retval = -1; |
| break; |
| } |
| if (copy_to_user((u8 *)arg, param, pyld_sz)) { |
| retval = -EFAULT; |
| break; |
| } |
| break; |
| case IPA_IOC_QUERY_INTF_RX_PROPS: |
| sz = sizeof(struct ipa_ioc_query_intf_rx_props); |
| if (copy_from_user(header, (u8 *)arg, sz)) { |
| retval = -EFAULT; |
| break; |
| } |
| pyld_sz = sz + ((struct ipa_ioc_query_intf_rx_props *) |
| header)->num_rx_props * |
| sizeof(struct ipa_ioc_rx_intf_prop); |
| param = kzalloc(pyld_sz, GFP_KERNEL); |
| if (!param) { |
| retval = -ENOMEM; |
| break; |
| } |
| if (copy_from_user(param, (u8 *)arg, pyld_sz)) { |
| retval = -EFAULT; |
| break; |
| } |
| if (ipa_query_intf_rx_props( |
| (struct ipa_ioc_query_intf_rx_props *)param)) { |
| retval = -1; |
| break; |
| } |
| if (copy_to_user((u8 *)arg, param, pyld_sz)) { |
| retval = -EFAULT; |
| break; |
| } |
| break; |
| case IPA_IOC_PULL_MSG: |
| if (copy_from_user(header, (u8 *)arg, |
| sizeof(struct ipa_msg_meta))) { |
| retval = -EFAULT; |
| break; |
| } |
| pyld_sz = sizeof(struct ipa_msg_meta) + |
| ((struct ipa_msg_meta *)header)->msg_len; |
| param = kzalloc(pyld_sz, GFP_KERNEL); |
| if (!param) { |
| retval = -ENOMEM; |
| break; |
| } |
| if (copy_from_user(param, (u8 *)arg, pyld_sz)) { |
| retval = -EFAULT; |
| break; |
| } |
| if (ipa_pull_msg((struct ipa_msg_meta *)param, |
| (char *)param + sizeof(struct ipa_msg_meta), |
| ((struct ipa_msg_meta *)param)->msg_len) != |
| ((struct ipa_msg_meta *)param)->msg_len) { |
| retval = -1; |
| break; |
| } |
| if (copy_to_user((u8 *)arg, param, pyld_sz)) { |
| retval = -EFAULT; |
| break; |
| } |
| break; |
| default: /* redundant, as cmd was checked against MAXNR */ |
| return -ENOTTY; |
| } |
| kfree(param); |
| |
| return retval; |
| } |
| |
| /** |
| * ipa_setup_dflt_rt_tables() - Setup default routing tables |
| * |
| * Return codes: |
| * 0: success |
| * -ENOMEM: failed to allocate memory |
| * -EPERM: failed to add the tables |
| */ |
| int ipa_setup_dflt_rt_tables(void) |
| { |
| struct ipa_ioc_add_rt_rule *rt_rule; |
| struct ipa_rt_rule_add *rt_rule_entry; |
| |
| rt_rule = |
| kzalloc(sizeof(struct ipa_ioc_add_rt_rule) + 1 * |
| sizeof(struct ipa_rt_rule_add), GFP_KERNEL); |
| if (!rt_rule) { |
| IPAERR("fail to alloc mem\n"); |
| return -ENOMEM; |
| } |
| /* setup a default v4 route to point to A5 */ |
| rt_rule->num_rules = 1; |
| rt_rule->commit = 1; |
| rt_rule->ip = IPA_IP_v4; |
| strlcpy(rt_rule->rt_tbl_name, IPA_DFLT_RT_TBL_NAME, |
| IPA_RESOURCE_NAME_MAX); |
| |
| rt_rule_entry = &rt_rule->rules[0]; |
| rt_rule_entry->at_rear = 1; |
| rt_rule_entry->rule.dst = IPA_CLIENT_A5_LAN_WAN_CONS; |
| rt_rule_entry->rule.hdr_hdl = ipa_ctx->excp_hdr_hdl; |
| |
| if (ipa_add_rt_rule(rt_rule)) { |
| IPAERR("fail to add dflt v4 rule\n"); |
| kfree(rt_rule); |
| return -EPERM; |
| } |
| IPADBG("dflt v4 rt rule hdl=%x\n", rt_rule_entry->rt_rule_hdl); |
| ipa_ctx->dflt_v4_rt_rule_hdl = rt_rule_entry->rt_rule_hdl; |
| |
| /* setup a default v6 route to point to A5 */ |
| rt_rule->ip = IPA_IP_v6; |
| if (ipa_add_rt_rule(rt_rule)) { |
| IPAERR("fail to add dflt v6 rule\n"); |
| kfree(rt_rule); |
| return -EPERM; |
| } |
| IPADBG("dflt v6 rt rule hdl=%x\n", rt_rule_entry->rt_rule_hdl); |
| ipa_ctx->dflt_v6_rt_rule_hdl = rt_rule_entry->rt_rule_hdl; |
| |
| /* |
| * because these tables are the very first to be added, they will both |
| * have the same index (0) which is essential for programming the |
| * "route" end-point config |
| */ |
| |
| kfree(rt_rule); |
| |
| return 0; |
| } |
| |
| static int ipa_setup_exception_path(void) |
| { |
| struct ipa_ioc_add_hdr *hdr; |
| struct ipa_hdr_add *hdr_entry; |
| struct ipa_route route = { 0 }; |
| int ret; |
| |
| /* install the basic exception header */ |
| hdr = kzalloc(sizeof(struct ipa_ioc_add_hdr) + 1 * |
| sizeof(struct ipa_hdr_add), GFP_KERNEL); |
| if (!hdr) { |
| IPAERR("fail to alloc exception hdr\n"); |
| return -ENOMEM; |
| } |
| hdr->num_hdrs = 1; |
| hdr->commit = 1; |
| hdr_entry = &hdr->hdr[0]; |
| strlcpy(hdr_entry->name, IPA_DFLT_HDR_NAME, IPA_RESOURCE_NAME_MAX); |
| |
| /* |
| * only single stream for MBIM supported and no exception packets |
| * expected so set default header to zero |
| * for IPA HW 1.1 and up the default header length is 8 (exception) |
| */ |
| if (ipa_ctx->ipa_hw_type == IPA_HW_v1_0) { |
| hdr_entry->hdr_len = 1; |
| hdr_entry->hdr[0] = 0; |
| } else { |
| hdr_entry->hdr_len = IPA_DEFAULT_HEADER_LENGTH; |
| } |
| |
| /* |
| * SW does not know anything about default exception header so |
| * we don't set it. IPA HW will use it as a template |
| */ |
| if (ipa_add_hdr(hdr)) { |
| IPAERR("fail to add exception hdr\n"); |
| ret = -EPERM; |
| goto bail; |
| } |
| |
| if (hdr_entry->status) { |
| IPAERR("fail to add exception hdr\n"); |
| ret = -EPERM; |
| goto bail; |
| } |
| |
| ipa_ctx->excp_hdr_hdl = hdr_entry->hdr_hdl; |
| |
| /* exception packets goto LAN-WAN pipe from IPA to A5 */ |
| route.route_def_pipe = IPA_A5_LAN_WAN_IN; |
| route.route_def_hdr_table = !ipa_ctx->hdr_tbl_lcl; |
| |
| if (ipa_cfg_route(&route)) { |
| IPAERR("fail to add exception hdr\n"); |
| ret = -EPERM; |
| goto bail; |
| } |
| |
| ret = 0; |
| bail: |
| kfree(hdr); |
| return ret; |
| } |
| |
| static int ipa_handle_tx_poll_for_pipe(struct ipa_sys_context *sys, |
| bool process_all) |
| { |
| struct ipa_tx_pkt_wrapper *tx_pkt, *t; |
| struct sps_iovec iov; |
| unsigned long irq_flags; |
| int ret; |
| int cnt = 0; |
| |
| do { |
| iov.addr = 0; |
| ret = sps_get_iovec(sys->ep->ep_hdl, &iov); |
| if (ret) { |
| pr_err("%s: sps_get_iovec failed %d\n", __func__, ret); |
| break; |
| } |
| if (!iov.addr) |
| break; |
| spin_lock_irqsave(&sys->spinlock, irq_flags); |
| tx_pkt = list_first_entry(&sys->head_desc_list, |
| struct ipa_tx_pkt_wrapper, link); |
| spin_unlock_irqrestore(&sys->spinlock, irq_flags); |
| |
| switch (tx_pkt->cnt) { |
| case 1: |
| ipa_wq_write_done(&tx_pkt->work); |
| ++cnt; |
| break; |
| case 0xFFFF: |
| /* reached end of set */ |
| spin_lock_irqsave(&sys->spinlock, irq_flags); |
| list_for_each_entry_safe(tx_pkt, t, |
| &sys->wait_desc_list, link) { |
| list_del(&tx_pkt->link); |
| list_add(&tx_pkt->link, &sys->head_desc_list); |
| } |
| tx_pkt = |
| list_first_entry(&sys->head_desc_list, |
| struct ipa_tx_pkt_wrapper, link); |
| spin_unlock_irqrestore(&sys->spinlock, irq_flags); |
| ipa_wq_write_done(&tx_pkt->work); |
| ++cnt; |
| break; |
| default: |
| /* keep looping till reach the end of the set */ |
| spin_lock_irqsave(&sys->spinlock, |
| irq_flags); |
| list_del(&tx_pkt->link); |
| list_add_tail(&tx_pkt->link, |
| &sys->wait_desc_list); |
| spin_unlock_irqrestore(&sys->spinlock, |
| irq_flags); |
| ++cnt; |
| break; |
| } |
| } while (process_all); |
| |
| return cnt; |
| } |
| |
| static void ipa_poll_function(struct work_struct *work) |
| { |
| int ret; |
| int tx_pipes[] = { IPA_A5_CMD, IPA_A5_LAN_WAN_OUT, |
| IPA_A5_WLAN_AMPDU_OUT }; |
| int i; |
| int num_tx_pipes; |
| int cnt; |
| |
| num_tx_pipes = sizeof(tx_pipes) / sizeof(tx_pipes[0]); |
| |
| if (!IPA_MOBILE_AP_MODE(ipa_ctx->mode)) |
| num_tx_pipes--; |
| |
| do { |
| cnt = 0; |
| |
| /* check all the system pipes for tx comp and rx avail */ |
| if (ipa_ctx->sys[IPA_A5_LAN_WAN_IN].ep->valid) |
| cnt |= ipa_handle_rx_core(false); |
| |
| for (i = 0; i < num_tx_pipes; i++) |
| if (ipa_ctx->sys[tx_pipes[i]].ep->valid) |
| cnt |= ipa_handle_tx_poll_for_pipe( |
| &ipa_ctx->sys[tx_pipes[i]], |
| false); |
| } while (cnt); |
| |
| /* re-post the poll work */ |
| INIT_DELAYED_WORK(&ipa_ctx->poll_work, ipa_poll_function); |
| ret = schedule_delayed_work_on(smp_processor_id(), &ipa_ctx->poll_work, |
| msecs_to_jiffies(polling_delay_ms)); |
| |
| return; |
| } |
| |
| static int ipa_setup_a5_pipes(void) |
| { |
| struct ipa_sys_connect_params sys_in; |
| int result = 0; |
| |
| /* CMD OUT (A5->IPA) */ |
| memset(&sys_in, 0, sizeof(struct ipa_sys_connect_params)); |
| sys_in.client = IPA_CLIENT_A5_CMD_PROD; |
| sys_in.desc_fifo_sz = IPA_SYS_DESC_FIFO_SZ; |
| sys_in.ipa_ep_cfg.mode.mode = IPA_DMA; |
| sys_in.ipa_ep_cfg.mode.dst = IPA_CLIENT_A5_LAN_WAN_CONS; |
| if (ipa_setup_sys_pipe(&sys_in, &ipa_ctx->clnt_hdl_cmd)) { |
| IPAERR(":setup sys pipe failed.\n"); |
| result = -EPERM; |
| goto fail_cmd; |
| } |
| |
| /* Start polling, only if needed */ |
| if (ipa_ctx->polling_mode) { |
| INIT_DELAYED_WORK(&ipa_ctx->poll_work, ipa_poll_function); |
| result = |
| schedule_delayed_work_on(smp_processor_id(), |
| &ipa_ctx->poll_work, |
| msecs_to_jiffies(polling_delay_ms)); |
| if (!result) { |
| IPAERR(":schedule delayed work failed.\n"); |
| goto fail_schedule_delayed_work; |
| } |
| } |
| |
| if (ipa_setup_exception_path()) { |
| IPAERR(":fail to setup excp path\n"); |
| result = -EPERM; |
| goto fail_schedule_delayed_work; |
| } |
| |
| if (ipa_ctx->ipa_hw_type != IPA_HW_v1_0) { |
| if (ipa_setup_dflt_rt_tables()) { |
| IPAERR(":fail to setup dflt routes\n"); |
| result = -EPERM; |
| goto fail_schedule_delayed_work; |
| } |
| } |
| |
| /* LAN-WAN IN (IPA->A5) */ |
| memset(&sys_in, 0, sizeof(struct ipa_sys_connect_params)); |
| sys_in.client = IPA_CLIENT_A5_LAN_WAN_CONS; |
| sys_in.desc_fifo_sz = IPA_SYS_DESC_FIFO_SZ; |
| sys_in.ipa_ep_cfg.hdr.hdr_a5_mux = 1; |
| sys_in.ipa_ep_cfg.hdr.hdr_len = 8; /* size of A5 exception hdr */ |
| if (ipa_setup_sys_pipe(&sys_in, &ipa_ctx->clnt_hdl_data_in)) { |
| IPAERR(":setup sys pipe failed.\n"); |
| result = -EPERM; |
| goto fail_schedule_delayed_work; |
| } |
| /* LAN-WAN OUT (A5->IPA) */ |
| memset(&sys_in, 0, sizeof(struct ipa_sys_connect_params)); |
| sys_in.client = IPA_CLIENT_A5_LAN_WAN_PROD; |
| sys_in.desc_fifo_sz = IPA_SYS_DESC_FIFO_SZ; |
| sys_in.ipa_ep_cfg.mode.mode = IPA_BASIC; |
| sys_in.ipa_ep_cfg.mode.dst = IPA_CLIENT_A5_LAN_WAN_CONS; |
| if (ipa_setup_sys_pipe(&sys_in, &ipa_ctx->clnt_hdl_data_out)) { |
| IPAERR(":setup sys pipe failed.\n"); |
| result = -EPERM; |
| goto fail_data_out; |
| } |
| |
| return 0; |
| |
| fail_data_out: |
| ipa_teardown_sys_pipe(ipa_ctx->clnt_hdl_data_in); |
| fail_schedule_delayed_work: |
| if (ipa_ctx->dflt_v6_rt_rule_hdl) |
| __ipa_del_rt_rule(ipa_ctx->dflt_v6_rt_rule_hdl); |
| if (ipa_ctx->dflt_v4_rt_rule_hdl) |
| __ipa_del_rt_rule(ipa_ctx->dflt_v4_rt_rule_hdl); |
| if (ipa_ctx->excp_hdr_hdl) |
| __ipa_del_hdr(ipa_ctx->excp_hdr_hdl); |
| ipa_teardown_sys_pipe(ipa_ctx->clnt_hdl_cmd); |
| fail_cmd: |
| return result; |
| } |
| |
| static void ipa_teardown_a5_pipes(void) |
| { |
| cancel_delayed_work(&ipa_ctx->poll_work); |
| ipa_teardown_sys_pipe(ipa_ctx->clnt_hdl_data_out); |
| ipa_teardown_sys_pipe(ipa_ctx->clnt_hdl_data_in); |
| __ipa_del_rt_rule(ipa_ctx->dflt_v6_rt_rule_hdl); |
| __ipa_del_rt_rule(ipa_ctx->dflt_v4_rt_rule_hdl); |
| __ipa_del_hdr(ipa_ctx->excp_hdr_hdl); |
| ipa_teardown_sys_pipe(ipa_ctx->clnt_hdl_cmd); |
| } |
| |
| static int ipa_load_pipe_connection(struct platform_device *pdev, |
| enum a2_mux_pipe_direction pipe_dir, |
| struct a2_mux_pipe_connection *pdata) |
| { |
| struct device_node *node = pdev->dev.of_node; |
| int rc = 0; |
| |
| if (!pdata || !pdev) |
| goto err; |
| |
| /* retrieve device tree parameters */ |
| for_each_child_of_node(pdev->dev.of_node, node) |
| { |
| const char *str; |
| |
| rc = of_property_read_string(node, "label", &str); |
| if (rc) { |
| IPAERR("Cannot read string\n"); |
| goto err; |
| } |
| |
| /* Check if connection type is supported */ |
| if (strncmp(str, "a2-to-ipa", 10) |
| && strncmp(str, "ipa-to-a2", 10)) |
| goto err; |
| |
| if (strnstr(str, "a2-to-ipa", strnlen("a2-to-ipa", 10)) |
| && IPA_TO_A2 == pipe_dir) |
| continue; /* skip to the next pipe */ |
| else if (strnstr(str, "ipa-to-a2", strnlen("ipa-to-a2", 10)) |
| && A2_TO_IPA == pipe_dir) |
| continue; /* skip to the next pipe */ |
| |
| |
| rc = ipa_update_connections_info(node, pdata); |
| if (rc) |
| goto err; |
| } |
| |
| return 0; |
| err: |
| IPAERR("%s: failed\n", __func__); |
| |
| return rc; |
| } |
| |
| static int ipa_update_connections_info(struct device_node *node, |
| struct a2_mux_pipe_connection *pipe_connection) |
| { |
| u32 rc; |
| char *key; |
| uint32_t val; |
| enum ipa_pipe_mem_type mem_type; |
| |
| if (!pipe_connection || !node) |
| goto err; |
| |
| key = "qcom,src-bam-physical-address"; |
| rc = of_property_read_u32(node, key, &val); |
| if (rc) |
| goto err; |
| pipe_connection->src_phy_addr = val; |
| |
| key = "qcom,ipa-bam-mem-type"; |
| rc = of_property_read_u32(node, key, &mem_type); |
| if (rc) |
| goto err; |
| pipe_connection->mem_type = mem_type; |
| |
| key = "qcom,src-bam-pipe-index"; |
| rc = of_property_read_u32(node, key, &val); |
| if (rc) |
| goto err; |
| pipe_connection->src_pipe_index = val; |
| |
| key = "qcom,dst-bam-physical-address"; |
| rc = of_property_read_u32(node, key, &val); |
| if (rc) |
| goto err; |
| pipe_connection->dst_phy_addr = val; |
| |
| key = "qcom,dst-bam-pipe-index"; |
| rc = of_property_read_u32(node, key, &val); |
| if (rc) |
| goto err; |
| pipe_connection->dst_pipe_index = val; |
| |
| key = "qcom,data-fifo-offset"; |
| rc = of_property_read_u32(node, key, &val); |
| if (rc) |
| goto err; |
| pipe_connection->data_fifo_base_offset = val; |
| |
| key = "qcom,data-fifo-size"; |
| rc = of_property_read_u32(node, key, &val); |
| if (rc) |
| goto err; |
| pipe_connection->data_fifo_size = val; |
| |
| key = "qcom,descriptor-fifo-offset"; |
| rc = of_property_read_u32(node, key, &val); |
| if (rc) |
| goto err; |
| pipe_connection->desc_fifo_base_offset = val; |
| |
| key = "qcom,descriptor-fifo-size"; |
| rc = of_property_read_u32(node, key, &val); |
| if (rc) |
| goto err; |
| |
| pipe_connection->desc_fifo_size = val; |
| |
| return 0; |
| err: |
| IPAERR("%s: Error in name %s key %s\n", __func__, node->full_name, key); |
| |
| return rc; |
| } |
| |
| /** |
| * ipa_get_a2_mux_pipe_info() - Exposes A2 parameters fetched from DTS |
| * |
| * @pipe_dir: pipe direction |
| * @pipe_connect: connect structure containing the parameters fetched from DTS |
| * |
| * Return codes: |
| * 0: success |
| * -EFAULT: invalid parameters |
| */ |
| int ipa_get_a2_mux_pipe_info(enum a2_mux_pipe_direction pipe_dir, |
| struct a2_mux_pipe_connection *pipe_connect) |
| { |
| if (!pipe_connect) { |
| IPAERR("ipa_get_a2_mux_pipe_info switch null args\n"); |
| return -EFAULT; |
| } |
| |
| switch (pipe_dir) { |
| case A2_TO_IPA: |
| *pipe_connect = ipa_res.a2_to_ipa_pipe; |
| break; |
| case IPA_TO_A2: |
| *pipe_connect = ipa_res.ipa_to_a2_pipe; |
| break; |
| default: |
| IPAERR("ipa_get_a2_mux_pipe_info switch in default\n"); |
| return -EFAULT; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * ipa_get_a2_mux_bam_info() - Exposes A2 parameters fetched from |
| * DTS |
| * |
| * @a2_bam_mem_base: A2 BAM Memory base |
| * @a2_bam_mem_size: A2 BAM Memory size |
| * @a2_bam_irq: A2 BAM IRQ |
| * |
| * Return codes: |
| * 0: success |
| * -EFAULT: invalid parameters |
| */ |
| int ipa_get_a2_mux_bam_info(u32 *a2_bam_mem_base, u32 *a2_bam_mem_size, |
| u32 *a2_bam_irq) |
| { |
| if (!a2_bam_mem_base || !a2_bam_mem_size || !a2_bam_irq) { |
| IPAERR("ipa_get_a2_mux_bam_info null args\n"); |
| return -EFAULT; |
| } |
| |
| *a2_bam_mem_base = ipa_res.a2_bam_mem_base; |
| *a2_bam_mem_size = ipa_res.a2_bam_mem_size; |
| *a2_bam_irq = ipa_res.a2_bam_irq; |
| |
| return 0; |
| } |
| |
| static void ipa_set_aggregation_params(void) |
| { |
| struct ipa_ep_cfg_aggr agg_params; |
| struct ipa_ep_cfg_hdr hdr_params; |
| u32 producer_hdl = 0; |
| u32 consumer_hdl = 0; |
| |
| rmnet_bridge_get_client_handles(&producer_hdl, &consumer_hdl); |
| |
| /* configure aggregation on producer */ |
| memset(&agg_params, 0, sizeof(struct ipa_ep_cfg_aggr)); |
| agg_params.aggr_en = IPA_ENABLE_AGGR; |
| agg_params.aggr = ipa_ctx->aggregation_type; |
| agg_params.aggr_byte_limit = ipa_ctx->aggregation_byte_limit; |
| agg_params.aggr_time_limit = ipa_ctx->aggregation_time_limit; |
| ipa_cfg_ep_aggr(producer_hdl, &agg_params); |
| |
| if (ipa_ctx->ipa_hw_type == IPA_HW_v1_0) { |
| /* configure header on producer */ |
| memset(&hdr_params, 0, sizeof(struct ipa_ep_cfg_hdr)); |
| hdr_params.hdr_len = 1; |
| ipa_cfg_ep_hdr(producer_hdl, &hdr_params); |
| } |
| |
| /* configure deaggregation on consumer */ |
| memset(&agg_params, 0, sizeof(struct ipa_ep_cfg_aggr)); |
| agg_params.aggr_en = IPA_ENABLE_DEAGGR; |
| agg_params.aggr = ipa_ctx->aggregation_type; |
| ipa_cfg_ep_aggr(consumer_hdl, &agg_params); |
| } |
| |
| /* |
| * The following device attributes are for configuring the aggregation |
| * attributes when the driver is already running. |
| * The attributes are for configuring the aggregation type |
| * (MBIM_16/MBIM_32/TLP), the aggregation byte limit and the aggregation |
| * time limit. |
| */ |
| static ssize_t ipa_show_aggregation_type(struct device *dev, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| ssize_t ret_val; |
| char str[IPA_AGGR_MAX_STR_LENGTH]; |
| |
| if (!buf) { |
| IPAERR("buffer for ipa_show_aggregation_type is NULL\n"); |
| return -EINVAL; |
| } |
| |
| memset(str, 0, sizeof(str)); |
| |
| switch (ipa_ctx->aggregation_type) { |
| case IPA_MBIM_16: |
| strlcpy(str, "MBIM_16", IPA_AGGR_STR_IN_BYTES("MBIM_16")); |
| break; |
| case IPA_MBIM_32: |
| strlcpy(str, "MBIM_32", IPA_AGGR_STR_IN_BYTES("MBIM_32")); |
| break; |
| case IPA_TLP: |
| strlcpy(str, "TLP", IPA_AGGR_STR_IN_BYTES("TLP")); |
| break; |
| default: |
| strlcpy(str, "NONE", IPA_AGGR_STR_IN_BYTES("NONE")); |
| break; |
| } |
| |
| ret_val = scnprintf(buf, PAGE_SIZE, "%s\n", str); |
| |
| return ret_val; |
| } |
| |
| static ssize_t ipa_store_aggregation_type(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| char str[IPA_AGGR_MAX_STR_LENGTH], *pstr; |
| |
| if (!buf) { |
| IPAERR("buffer for ipa_store_aggregation_type is NULL\n"); |
| return -EINVAL; |
| } |
| |
| strlcpy(str, buf, sizeof(str)); |
| pstr = strim(str); |
| |
| if (!strncmp(pstr, "MBIM_16", IPA_AGGR_STR_IN_BYTES("MBIM_16"))) |
| ipa_ctx->aggregation_type = IPA_MBIM_16; |
| else if (!strncmp(pstr, "MBIM_32", IPA_AGGR_STR_IN_BYTES("MBIM_32"))) |
| ipa_ctx->aggregation_type = IPA_MBIM_32; |
| else if (!strncmp(pstr, "TLP", IPA_AGGR_STR_IN_BYTES("TLP"))) |
| ipa_ctx->aggregation_type = IPA_TLP; |
| else { |
| IPAERR("ipa_store_aggregation_type wrong input\n"); |
| return -EINVAL; |
| } |
| |
| ipa_set_aggregation_params(); |
| |
| return count; |
| } |
| |
| static DEVICE_ATTR(aggregation_type, S_IWUSR | S_IRUSR, |
| ipa_show_aggregation_type, |
| ipa_store_aggregation_type); |
| |
| static ssize_t ipa_show_aggregation_byte_limit(struct device *dev, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| ssize_t ret_val; |
| |
| if (!buf) { |
| IPAERR("buffer for ipa_show_aggregation_byte_limit is NULL\n"); |
| return -EINVAL; |
| } |
| |
| ret_val = scnprintf(buf, PAGE_SIZE, "%u\n", |
| ipa_ctx->aggregation_byte_limit); |
| |
| return ret_val; |
| } |
| |
| static ssize_t ipa_store_aggregation_byte_limit(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| char str[IPA_AGGR_MAX_STR_LENGTH]; |
| char *pstr; |
| u32 ret = 0; |
| |
| if (!buf) { |
| IPAERR("buffer for ipa_store_aggregation_byte_limit is NULL\n"); |
| return -EINVAL; |
| } |
| |
| strlcpy(str, buf, sizeof(str)); |
| pstr = strim(str); |
| |
| if (kstrtouint(pstr, IPA_AGGR_MAX_STR_LENGTH, &ret)) { |
| IPAERR("ipa_store_aggregation_byte_limit wrong input\n"); |
| return -EINVAL; |
| } |
| |
| ipa_ctx->aggregation_byte_limit = ret; |
| |
| ipa_set_aggregation_params(); |
| |
| return count; |
| } |
| |
| static DEVICE_ATTR(aggregation_byte_limit, S_IWUSR | S_IRUSR, |
| ipa_show_aggregation_byte_limit, |
| ipa_store_aggregation_byte_limit); |
| |
| static ssize_t ipa_show_aggregation_time_limit(struct device *dev, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| ssize_t ret_val; |
| |
| if (!buf) { |
| IPAERR("buffer for ipa_show_aggregation_time_limit is NULL\n"); |
| return -EINVAL; |
| } |
| |
| ret_val = scnprintf(buf, |
| PAGE_SIZE, |
| "%u\n", |
| ipa_ctx->aggregation_time_limit); |
| |
| return ret_val; |
| } |
| |
| static ssize_t ipa_store_aggregation_time_limit(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| char str[IPA_AGGR_MAX_STR_LENGTH], *pstr; |
| u32 ret = 0; |
| |
| if (!buf) { |
| IPAERR("buffer for ipa_store_aggregation_time_limit is NULL\n"); |
| return -EINVAL; |
| } |
| |
| strlcpy(str, buf, sizeof(str)); |
| pstr = strim(str); |
| |
| if (kstrtouint(pstr, IPA_AGGR_MAX_STR_LENGTH, &ret)) { |
| IPAERR("ipa_store_aggregation_time_limit wrong input\n"); |
| return -EINVAL; |
| } |
| |
| ipa_ctx->aggregation_time_limit = ret; |
| |
| ipa_set_aggregation_params(); |
| |
| return count; |
| } |
| |
| static DEVICE_ATTR(aggregation_time_limit, S_IWUSR | S_IRUSR, |
| ipa_show_aggregation_time_limit, |
| ipa_store_aggregation_time_limit); |
| |
| static const struct file_operations ipa_drv_fops = { |
| .owner = THIS_MODULE, |
| .open = ipa_open, |
| .read = ipa_read, |
| .unlocked_ioctl = ipa_ioctl, |
| }; |
| |
| static int ipa_get_clks(struct device *dev) |
| { |
| ipa_cnoc_clk = clk_get(dev, "iface_clk"); |
| if (IS_ERR(ipa_cnoc_clk)) { |
| ipa_cnoc_clk = NULL; |
| IPAERR("fail to get cnoc clk\n"); |
| return -ENODEV; |
| } |
| |
| ipa_clk_src = clk_get(dev, "core_src_clk"); |
| if (IS_ERR(ipa_clk_src)) { |
| ipa_clk_src = NULL; |
| IPAERR("fail to get ipa clk src\n"); |
| return -ENODEV; |
| } |
| |
| ipa_clk = clk_get(dev, "core_clk"); |
| if (IS_ERR(ipa_clk)) { |
| ipa_clk = NULL; |
| IPAERR("fail to get ipa clk\n"); |
| return -ENODEV; |
| } |
| |
| sys_noc_ipa_axi_clk = clk_get(dev, "bus_clk"); |
| if (IS_ERR(sys_noc_ipa_axi_clk)) { |
| sys_noc_ipa_axi_clk = NULL; |
| IPAERR("fail to get sys_noc_ipa_axi clk\n"); |
| return -ENODEV; |
| } |
| |
| ipa_inactivity_clk = clk_get(dev, "inactivity_clk"); |
| if (IS_ERR(ipa_inactivity_clk)) { |
| ipa_inactivity_clk = NULL; |
| IPAERR("fail to get inactivity clk\n"); |
| return -ENODEV; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * ipa_enable_clks() - Turn on IPA clocks |
| * |
| * Return codes: |
| * None |
| */ |
| void ipa_enable_clks(void) |
| { |
| if (ipa_cnoc_clk) { |
| clk_prepare(ipa_cnoc_clk); |
| clk_enable(ipa_cnoc_clk); |
| clk_set_rate(ipa_cnoc_clk, IPA_CNOC_CLK_RATE); |
| } else { |
| WARN_ON(1); |
| } |
| |
| if (ipa_clk_src) |
| if (ipa_res.ipa_hw_type == IPA_HW_v1_0) |
| clk_set_rate(ipa_clk_src, IPA_V1_CLK_RATE); |
| else if (ipa_res.ipa_hw_type == IPA_HW_v1_1) |
| clk_set_rate(ipa_clk_src, IPA_V1_1_CLK_RATE); |
| else |
| WARN_ON(1); |
| else |
| WARN_ON(1); |
| |
| if (ipa_clk) |
| clk_prepare(ipa_clk); |
| else |
| WARN_ON(1); |
| |
| if (sys_noc_ipa_axi_clk) |
| clk_prepare(sys_noc_ipa_axi_clk); |
| else |
| WARN_ON(1); |
| |
| if (ipa_inactivity_clk) |
| clk_prepare(ipa_inactivity_clk); |
| else |
| WARN_ON(1); |
| |
| if (ipa_clk) |
| clk_enable(ipa_clk); |
| else |
| WARN_ON(1); |
| |
| if (sys_noc_ipa_axi_clk) |
| clk_enable(sys_noc_ipa_axi_clk); |
| else |
| WARN_ON(1); |
| |
| if (ipa_inactivity_clk) |
| clk_enable(ipa_inactivity_clk); |
| else |
| WARN_ON(1); |
| |
| if (msm_bus_scale_client_update_request(ipa_bus_hdl, 1)) |
| WARN_ON(1); |
| } |
| |
| /** |
| * ipa_disable_clks() - Turn off IPA clocks |
| * |
| * Return codes: |
| * None |
| */ |
| void ipa_disable_clks(void) |
| { |
| if (ipa_inactivity_clk) |
| clk_disable_unprepare(ipa_inactivity_clk); |
| else |
| WARN_ON(1); |
| |
| if (sys_noc_ipa_axi_clk) |
| clk_disable_unprepare(sys_noc_ipa_axi_clk); |
| else |
| WARN_ON(1); |
| |
| if (ipa_clk) |
| clk_disable_unprepare(ipa_clk); |
| else |
| WARN_ON(1); |
| |
| if (ipa_cnoc_clk) |
| clk_disable_unprepare(ipa_cnoc_clk); |
| else |
| WARN_ON(1); |
| |
| if (msm_bus_scale_client_update_request(ipa_bus_hdl, 0)) |
| WARN_ON(1); |
| } |
| |
| static int ipa_setup_bam_cfg(const struct ipa_plat_drv_res *res) |
| { |
| void *bam_cnfg_bits; |
| |
| if ((ipa_ctx->ipa_hw_type == IPA_HW_v1_0) || |
| (ipa_ctx->ipa_hw_type == IPA_HW_v1_1)) { |
| bam_cnfg_bits = ioremap(res->ipa_mem_base + |
| IPA_BAM_REG_BASE_OFST, |
| IPA_BAM_REMAP_SIZE); |
| if (!bam_cnfg_bits) |
| return -ENOMEM; |
| ipa_write_reg(bam_cnfg_bits, IPA_BAM_CNFG_BITS_OFST, |
| IPA_BAM_CNFG_BITS_VAL); |
| iounmap(bam_cnfg_bits); |
| } |
| return 0; |
| } |
| |
| static int ipa_init_flt_block(void) |
| { |
| int result = 0; |
| |
| /* |
| * SW workaround for Improper Filter Behaviour when neiher Global nor |
| * Pipe Rules are present => configure dummy global filter rule |
| * always which results in a miss |
| */ |
| struct ipa_ioc_add_flt_rule *rules; |
| struct ipa_flt_rule_add *rule; |
| struct ipa_ioc_get_rt_tbl rt_lookup; |
| enum ipa_ip_type ip; |
| |
| if (ipa_ctx->ipa_hw_type == IPA_HW_v1_1) { |
| size_t sz = sizeof(struct ipa_ioc_add_flt_rule) + |
| sizeof(struct ipa_flt_rule_add); |
| |
| rules = kmalloc(sz, GFP_KERNEL); |
| if (rules == NULL) { |
| IPAERR("fail to alloc mem for dummy filter rule\n"); |
| return -ENOMEM; |
| } |
| |
| for (ip = IPA_IP_v4; ip < IPA_IP_MAX; ip++) { |
| memset(&rt_lookup, 0, |
| sizeof(struct ipa_ioc_get_rt_tbl)); |
| rt_lookup.ip = ip; |
| strlcpy(rt_lookup.name, IPA_DFLT_RT_TBL_NAME, |
| IPA_RESOURCE_NAME_MAX); |
| ipa_get_rt_tbl(&rt_lookup); |
| ipa_put_rt_tbl(rt_lookup.hdl); |
| |
| memset(rules, 0, sz); |
| rule = &rules->rules[0]; |
| rules->commit = 1; |
| rules->ip = ip; |
| rules->global = 1; |
| rules->num_rules = 1; |
| rule->at_rear = 1; |
| if (ip == IPA_IP_v4) { |
| rule->rule.attrib.attrib_mask = |
| IPA_FLT_PROTOCOL; |
| rule->rule.attrib.u.v4.protocol = |
| IPA_INVALID_L4_PROTOCOL; |
| } else if (ip == IPA_IP_v6) { |
| rule->rule.attrib.attrib_mask = |
| IPA_FLT_NEXT_HDR; |
| rule->rule.attrib.u.v6.next_hdr = |
| IPA_INVALID_L4_PROTOCOL; |
| } else { |
| result = -EINVAL; |
| WARN_ON(1); |
| break; |
| } |
| rule->rule.action = IPA_PASS_TO_ROUTING; |
| rule->rule.rt_tbl_hdl = rt_lookup.hdl; |
| |
| if (ipa_add_flt_rule(rules) || rules->rules[0].status) { |
| result = -EINVAL; |
| WARN_ON(1); |
| break; |
| } |
| } |
| kfree(rules); |
| } |
| return result; |
| } |
| |
| /** |
| * ipa_init() - Initialize the IPA Driver |
| *@resource_p: contain platform specific values from DST file |
| * |
| * Function initialization process: |
| * - Allocate memory for the driver context data struct |
| * - Initializing the ipa_ctx with: |
| * 1)parsed values from the dts file |
| * 2)parameters passed to the module initialization |
| * 3)read HW values(such as core memory size) |
| * - Map IPA core registers to CPU memory |
| * - Restart IPA core(HW reset) |
| * - Register IPA BAM to SPS driver and get a BAM handler |
| * - Set configuration for IPA BAM via BAM_CNFG_BITS |
| * - Initialize the look-aside caches(kmem_cache/slab) for filter, |
| * routing and IPA-tree |
| * - Create memory pool with 4 objects for DMA operations(each object |
| * is 512Bytes long), this object will be use for tx(A5->IPA) |
| * - Initialize lists head(routing,filter,hdr,system pipes) |
| * - Initialize mutexes (for ipa_ctx and NAT memory mutexes) |
| * - Initialize spinlocks (for list related to A5<->IPA pipes) |
| * - Initialize 2 single-threaded work-queue named "ipa rx wq" and "ipa tx wq" |
| * - Initialize Red-Black-Tree(s) for handles of header,routing rule, |
| * routing table ,filtering rule |
| * - Setup all A5<->IPA pipes by calling to ipa_setup_a5_pipes |
| * - Preparing the descriptors for System pipes |
| * - Initialize the filter block by committing IPV4 and IPV6 default rules |
| * - Create empty routing table in system memory(no committing) |
| * - Initialize pipes memory pool with ipa_pipe_mem_init for supported platforms |
| * - Create a char-device for IPA |
| * - Initialize IPA RM (resource manager) |
| */ |
| static int ipa_init(const struct ipa_plat_drv_res *resource_p) |
| { |
| int result = 0; |
| int i; |
| struct sps_bam_props bam_props = { 0 }; |
| struct ipa_flt_tbl *flt_tbl; |
| struct ipa_rt_tbl_set *rset; |
| |
| IPADBG("IPA init\n"); |
| |
| ipa_ctx = kzalloc(sizeof(*ipa_ctx), GFP_KERNEL); |
| if (!ipa_ctx) { |
| IPAERR(":kzalloc err.\n"); |
| result = -ENOMEM; |
| goto fail_mem; |
| } |
| |
| IPADBG("polling_mode=%u delay_ms=%u\n", polling_mode, polling_delay_ms); |
| ipa_ctx->polling_mode = polling_mode; |
| IPADBG("hdr_lcl=%u ip4_rt=%u ip6_rt=%u ip4_flt=%u ip6_flt=%u\n", |
| hdr_tbl_lcl, ip4_rt_tbl_lcl, ip6_rt_tbl_lcl, ip4_flt_tbl_lcl, |
| ip6_flt_tbl_lcl); |
| ipa_ctx->hdr_tbl_lcl = hdr_tbl_lcl; |
| ipa_ctx->ip4_rt_tbl_lcl = ip4_rt_tbl_lcl; |
| ipa_ctx->ip6_rt_tbl_lcl = ip6_rt_tbl_lcl; |
| ipa_ctx->ip4_flt_tbl_lcl = ip4_flt_tbl_lcl; |
| ipa_ctx->ip6_flt_tbl_lcl = ip6_flt_tbl_lcl; |
| |
| ipa_ctx->ipa_wrapper_base = resource_p->ipa_mem_base; |
| ipa_ctx->ipa_hw_type = resource_p->ipa_hw_type; |
| ipa_ctx->ipa_hw_mode = resource_p->ipa_hw_mode; |
| |
| /* setup IPA register access */ |
| ipa_ctx->mmio = ioremap(resource_p->ipa_mem_base + IPA_REG_BASE_OFST, |
| resource_p->ipa_mem_size); |
| if (!ipa_ctx->mmio) { |
| IPAERR(":ipa-base ioremap err.\n"); |
| result = -EFAULT; |
| goto fail_remap; |
| } |
| /* do POR programming to setup HW */ |
| result = ipa_init_hw(); |
| if (result) { |
| IPAERR(":error initializing driver.\n"); |
| result = -ENODEV; |
| goto fail_init_hw; |
| } |
| |
| if (ipa_ctx->ipa_hw_type == IPA_HW_v1_0) { |
| /* setup chicken bits */ |
| result = ipa_set_single_ndp_per_mbim(true); |
| if (result) { |
| IPAERR(":failed to set single ndp per mbim.\n"); |
| result = -EFAULT; |
| goto fail_init_hw; |
| } |
| |
| result = ipa_set_hw_timer_fix_for_mbim_aggr(true); |
| if (result) { |
| IPAERR(":failed to set HW timer fix for MBIM agg.\n"); |
| result = -EFAULT; |
| goto fail_init_hw; |
| } |
| } |
| |
| |
| /* read how much SRAM is available for SW use */ |
| if (ipa_ctx->ipa_hw_type == IPA_HW_v1_0) |
| ipa_ctx->smem_sz = ipa_read_reg(ipa_ctx->mmio, |
| IPA_SHARED_MEM_SIZE_OFST_v1); |
| else |
| ipa_ctx->smem_sz = ipa_read_reg(ipa_ctx->mmio, |
| IPA_SHARED_MEM_SIZE_OFST_v2); |
| |
| if (IPA_RAM_END_OFST > ipa_ctx->smem_sz) { |
| IPAERR("SW expect more core memory, needed %d, avail %d\n", |
| IPA_RAM_END_OFST, ipa_ctx->smem_sz); |
| result = -ENOMEM; |
| goto fail_init_hw; |
| } |
| /* register IPA with SPS driver */ |
| bam_props.phys_addr = resource_p->bam_mem_base; |
| bam_props.virt_addr = ioremap(resource_p->bam_mem_base, |
| resource_p->bam_mem_size); |
| if (!bam_props.virt_addr) { |
| IPAERR(":bam-base ioremap err.\n"); |
| result = -EFAULT; |
| goto fail_bam_remap; |
| } |
| bam_props.virt_size = resource_p->bam_mem_size; |
| bam_props.irq = resource_p->bam_irq; |
| bam_props.num_pipes = IPA_NUM_PIPES; |
| bam_props.summing_threshold = IPA_SUMMING_THRESHOLD; |
| bam_props.event_threshold = IPA_EVENT_THRESHOLD; |
| |
| result = sps_register_bam_device(&bam_props, &ipa_ctx->bam_handle); |
| if (result) { |
| IPAERR(":bam register err.\n"); |
| result = -ENODEV; |
| goto fail_bam_register; |
| } |
| |
| if (ipa_setup_bam_cfg(resource_p)) { |
| IPAERR(":bam cfg err.\n"); |
| result = -ENODEV; |
| goto fail_flt_rule_cache; |
| } |
| |
| /* set up the default op mode */ |
| ipa_ctx->mode = IPA_MODE_MOBILE_AP_WAN; |
| |
| /* init the lookaside cache */ |
| ipa_ctx->flt_rule_cache = kmem_cache_create("IPA FLT", |
| sizeof(struct ipa_flt_entry), 0, 0, NULL); |
| if (!ipa_ctx->flt_rule_cache) { |
| IPAERR(":ipa flt cache create failed\n"); |
| result = -ENOMEM; |
| goto fail_flt_rule_cache; |
| } |
| ipa_ctx->rt_rule_cache = kmem_cache_create("IPA RT", |
| sizeof(struct ipa_rt_entry), 0, 0, NULL); |
| if (!ipa_ctx->rt_rule_cache) { |
| IPAERR(":ipa rt cache create failed\n"); |
| result = -ENOMEM; |
| goto fail_rt_rule_cache; |
| } |
| ipa_ctx->hdr_cache = kmem_cache_create("IPA HDR", |
| sizeof(struct ipa_hdr_entry), 0, 0, NULL); |
| if (!ipa_ctx->hdr_cache) { |
| IPAERR(":ipa hdr cache create failed\n"); |
| result = -ENOMEM; |
| goto fail_hdr_cache; |
| } |
| ipa_ctx->hdr_offset_cache = |
| kmem_cache_create("IPA HDR OFF", sizeof(struct ipa_hdr_offset_entry), |
| 0, 0, NULL); |
| if (!ipa_ctx->hdr_offset_cache) { |
| IPAERR(":ipa hdr off cache create failed\n"); |
| result = -ENOMEM; |
| goto fail_hdr_offset_cache; |
| } |
| ipa_ctx->rt_tbl_cache = kmem_cache_create("IPA RT TBL", |
| sizeof(struct ipa_rt_tbl), 0, 0, NULL); |
| if (!ipa_ctx->rt_tbl_cache) { |
| IPAERR(":ipa rt tbl cache create failed\n"); |
| result = -ENOMEM; |
| goto fail_rt_tbl_cache; |
| } |
| ipa_ctx->tx_pkt_wrapper_cache = |
| kmem_cache_create("IPA TX PKT WRAPPER", |
| sizeof(struct ipa_tx_pkt_wrapper), 0, 0, NULL); |
| if (!ipa_ctx->tx_pkt_wrapper_cache) { |
| IPAERR(":ipa tx pkt wrapper cache create failed\n"); |
| result = -ENOMEM; |
| goto fail_tx_pkt_wrapper_cache; |
| } |
| ipa_ctx->rx_pkt_wrapper_cache = |
| kmem_cache_create("IPA RX PKT WRAPPER", |
| sizeof(struct ipa_rx_pkt_wrapper), 0, 0, NULL); |
| if (!ipa_ctx->rx_pkt_wrapper_cache) { |
| IPAERR(":ipa rx pkt wrapper cache create failed\n"); |
| result = -ENOMEM; |
| goto fail_rx_pkt_wrapper_cache; |
| } |
| ipa_ctx->tree_node_cache = |
| kmem_cache_create("IPA TREE", sizeof(struct ipa_tree_node), 0, 0, |
| NULL); |
| if (!ipa_ctx->tree_node_cache) { |
| IPAERR(":ipa tree node cache create failed\n"); |
| result = -ENOMEM; |
| goto fail_tree_node_cache; |
| } |
| |
| /* |
| * setup DMA pool 4 byte aligned, don't cross 1k boundaries, nominal |
| * size 512 bytes |
| * This is an issue with IPA HW v1.0 only. |
| */ |
| if (ipa_ctx->ipa_hw_type == IPA_HW_v1_0) { |
| ipa_ctx->one_kb_no_straddle_pool = dma_pool_create("ipa_1k", |
| NULL, |
| IPA_DMA_POOL_SIZE, IPA_DMA_POOL_ALIGNMENT, |
| IPA_DMA_POOL_BOUNDARY); |
| if (!ipa_ctx->one_kb_no_straddle_pool) { |
| IPAERR("cannot setup 1kb alloc DMA pool.\n"); |
| result = -ENOMEM; |
| goto fail_dma_pool; |
| } |
| } |
| |
| ipa_ctx->glob_flt_tbl[IPA_IP_v4].in_sys = !ipa_ctx->ip4_flt_tbl_lcl; |
| ipa_ctx->glob_flt_tbl[IPA_IP_v6].in_sys = !ipa_ctx->ip6_flt_tbl_lcl; |
| |
| /* init the various list heads */ |
| INIT_LIST_HEAD(&ipa_ctx->glob_flt_tbl[IPA_IP_v4].head_flt_rule_list); |
| INIT_LIST_HEAD(&ipa_ctx->glob_flt_tbl[IPA_IP_v6].head_flt_rule_list); |
| INIT_LIST_HEAD(&ipa_ctx->hdr_tbl.head_hdr_entry_list); |
| for (i = 0; i < IPA_HDR_BIN_MAX; i++) { |
| INIT_LIST_HEAD(&ipa_ctx->hdr_tbl.head_offset_list[i]); |
| INIT_LIST_HEAD(&ipa_ctx->hdr_tbl.head_free_offset_list[i]); |
| } |
| INIT_LIST_HEAD(&ipa_ctx->rt_tbl_set[IPA_IP_v4].head_rt_tbl_list); |
| INIT_LIST_HEAD(&ipa_ctx->rt_tbl_set[IPA_IP_v6].head_rt_tbl_list); |
| for (i = 0; i < IPA_NUM_PIPES; i++) { |
| flt_tbl = &ipa_ctx->flt_tbl[i][IPA_IP_v4]; |
| INIT_LIST_HEAD(&flt_tbl->head_flt_rule_list); |
| flt_tbl->in_sys = !ipa_ctx->ip4_flt_tbl_lcl; |
| |
| flt_tbl = &ipa_ctx->flt_tbl[i][IPA_IP_v6]; |
| INIT_LIST_HEAD(&flt_tbl->head_flt_rule_list); |
| flt_tbl->in_sys = !ipa_ctx->ip6_flt_tbl_lcl; |
| } |
| |
| rset = &ipa_ctx->reap_rt_tbl_set[IPA_IP_v4]; |
| INIT_LIST_HEAD(&rset->head_rt_tbl_list); |
| rset = &ipa_ctx->reap_rt_tbl_set[IPA_IP_v6]; |
| INIT_LIST_HEAD(&rset->head_rt_tbl_list); |
| |
| INIT_LIST_HEAD(&ipa_ctx->intf_list); |
| INIT_LIST_HEAD(&ipa_ctx->msg_list); |
| INIT_LIST_HEAD(&ipa_ctx->pull_msg_list); |
| init_waitqueue_head(&ipa_ctx->msg_waitq); |
| mutex_init(&ipa_ctx->msg_lock); |
| |
| mutex_init(&ipa_ctx->lock); |
| mutex_init(&ipa_ctx->nat_mem.lock); |
| |
| for (i = 0; i < IPA_A5_SYS_MAX; i++) { |
| INIT_LIST_HEAD(&ipa_ctx->sys[i].head_desc_list); |
| spin_lock_init(&ipa_ctx->sys[i].spinlock); |
| if (i != IPA_A5_WLAN_AMPDU_OUT) |
| ipa_ctx->sys[i].ep = &ipa_ctx->ep[i]; |
| else |
| ipa_ctx->sys[i].ep = &ipa_ctx->ep[WLAN_AMPDU_TX_EP]; |
| INIT_LIST_HEAD(&ipa_ctx->sys[i].wait_desc_list); |
| } |
| |
| ipa_ctx->rx_wq = create_singlethread_workqueue("ipa rx wq"); |
| if (!ipa_ctx->rx_wq) { |
| IPAERR(":fail to create rx wq\n"); |
| result = -ENOMEM; |
| goto fail_rx_wq; |
| } |
| |
| ipa_ctx->tx_wq = create_singlethread_workqueue("ipa tx wq"); |
| if (!ipa_ctx->tx_wq) { |
| IPAERR(":fail to create tx wq\n"); |
| result = -ENOMEM; |
| goto fail_tx_wq; |
| } |
| |
| ipa_ctx->hdr_hdl_tree = RB_ROOT; |
| ipa_ctx->rt_rule_hdl_tree = RB_ROOT; |
| ipa_ctx->rt_tbl_hdl_tree = RB_ROOT; |
| ipa_ctx->flt_rule_hdl_tree = RB_ROOT; |
| ipa_ctx->tag_tree = RB_ROOT; |
| |
| atomic_set(&ipa_ctx->ipa_active_clients, 0); |
| |
| result = ipa_bridge_init(); |
| if (result) { |
| IPAERR("ipa bridge init err.\n"); |
| result = -ENODEV; |
| goto fail_bridge_init; |
| } |
| |
| /* setup the A5-IPA pipes */ |
| if (ipa_setup_a5_pipes()) { |
| IPAERR(":failed to setup IPA-A5 pipes.\n"); |
| result = -ENODEV; |
| goto fail_a5_pipes; |
| } |
| |
| ipa_replenish_rx_cache(); |
| |
| if (ipa_init_flt_block()) { |
| IPAERR("fail to setup dummy filter rules\n"); |
| result = -ENODEV; |
| goto fail_empty_rt_tbl; |
| } |
| |
| /* |
| * setup an empty routing table in system memory, this will be used |
| * to delete a routing table cleanly and safely |
| */ |
| ipa_ctx->empty_rt_tbl_mem.size = IPA_ROUTING_RULE_BYTE_SIZE; |
| |
| ipa_ctx->empty_rt_tbl_mem.base = |
| dma_alloc_coherent(NULL, ipa_ctx->empty_rt_tbl_mem.size, |
| &ipa_ctx->empty_rt_tbl_mem.phys_base, |
| GFP_KERNEL); |
| if (!ipa_ctx->empty_rt_tbl_mem.base) { |
| IPAERR("DMA buff alloc fail %d bytes for empty routing tbl\n", |
| ipa_ctx->empty_rt_tbl_mem.size); |
| result = -ENOMEM; |
| goto fail_empty_rt_tbl; |
| } |
| memset(ipa_ctx->empty_rt_tbl_mem.base, 0, |
| ipa_ctx->empty_rt_tbl_mem.size); |
| |
| /* setup the IPA pipe mem pool */ |
| ipa_pipe_mem_init(resource_p->ipa_pipe_mem_start_ofst, |
| resource_p->ipa_pipe_mem_size); |
| |
| ipa_ctx->class = class_create(THIS_MODULE, DRV_NAME); |
| |
| result = alloc_chrdev_region(&ipa_ctx->dev_num, 0, 1, DRV_NAME); |
| if (result) { |
| IPAERR("alloc_chrdev_region err.\n"); |
| result = -ENODEV; |
| goto fail_alloc_chrdev_region; |
| } |
| |
| ipa_ctx->dev = device_create(ipa_ctx->class, NULL, ipa_ctx->dev_num, |
| ipa_ctx, DRV_NAME); |
| if (IS_ERR(ipa_ctx->dev)) { |
| IPAERR(":device_create err.\n"); |
| result = -ENODEV; |
| goto fail_device_create; |
| } |
| |
| cdev_init(&ipa_ctx->cdev, &ipa_drv_fops); |
| ipa_ctx->cdev.owner = THIS_MODULE; |
| ipa_ctx->cdev.ops = &ipa_drv_fops; /* from LDD3 */ |
| |
| result = cdev_add(&ipa_ctx->cdev, ipa_ctx->dev_num, 1); |
| if (result) { |
| IPAERR(":cdev_add err=%d\n", -result); |
| result = -ENODEV; |
| goto fail_cdev_add; |
| } |
| |
| /* default aggregation parameters */ |
| ipa_ctx->aggregation_type = IPA_MBIM_16; |
| ipa_ctx->aggregation_byte_limit = 1; |
| ipa_ctx->aggregation_time_limit = 0; |
| |
| /* gate IPA clocks */ |
| if (ipa_ctx->ipa_hw_mode == IPA_HW_MODE_NORMAL) |
| ipa_disable_clks(); |
| |
| /* Initialize IPA RM (resource manager) */ |
| result = ipa_rm_initialize(); |
| if (result) { |
| IPAERR(":cdev_add err=%d\n", -result); |
| result = -ENODEV; |
| goto fail_ipa_rm_init; |
| } |
| |
| a2_mux_init(); |
| |
| IPADBG(":IPA driver init OK.\n"); |
| |
| return 0; |
| |
| fail_ipa_rm_init: |
| cdev_del(&ipa_ctx->cdev); |
| fail_cdev_add: |
| device_destroy(ipa_ctx->class, ipa_ctx->dev_num); |
| fail_device_create: |
| unregister_chrdev_region(ipa_ctx->dev_num, 1); |
| fail_alloc_chrdev_region: |
| if (ipa_ctx->pipe_mem_pool) |
| gen_pool_destroy(ipa_ctx->pipe_mem_pool); |
| dma_free_coherent(NULL, |
| ipa_ctx->empty_rt_tbl_mem.size, |
| ipa_ctx->empty_rt_tbl_mem.base, |
| ipa_ctx->empty_rt_tbl_mem.phys_base); |
| fail_empty_rt_tbl: |
| ipa_cleanup_rx(); |
| ipa_teardown_a5_pipes(); |
| fail_a5_pipes: |
| ipa_bridge_cleanup(); |
| fail_bridge_init: |
| destroy_workqueue(ipa_ctx->tx_wq); |
| fail_tx_wq: |
| destroy_workqueue(ipa_ctx->rx_wq); |
| fail_rx_wq: |
| /* |
| * DMA pool need to be released only for IPA HW v1.0 only. |
| */ |
| if (ipa_ctx->ipa_hw_type == IPA_HW_v1_0) |
| dma_pool_destroy(ipa_ctx->one_kb_no_straddle_pool); |
| fail_dma_pool: |
| kmem_cache_destroy(ipa_ctx->tree_node_cache); |
| fail_tree_node_cache: |
| kmem_cache_destroy(ipa_ctx->rx_pkt_wrapper_cache); |
| fail_rx_pkt_wrapper_cache: |
| kmem_cache_destroy(ipa_ctx->tx_pkt_wrapper_cache); |
| fail_tx_pkt_wrapper_cache: |
| kmem_cache_destroy(ipa_ctx->rt_tbl_cache); |
| fail_rt_tbl_cache: |
| kmem_cache_destroy(ipa_ctx->hdr_offset_cache); |
| fail_hdr_offset_cache: |
| kmem_cache_destroy(ipa_ctx->hdr_cache); |
| fail_hdr_cache: |
| kmem_cache_destroy(ipa_ctx->rt_rule_cache); |
| fail_rt_rule_cache: |
| kmem_cache_destroy(ipa_ctx->flt_rule_cache); |
| fail_flt_rule_cache: |
| sps_deregister_bam_device(ipa_ctx->bam_handle); |
| fail_bam_register: |
| iounmap(bam_props.virt_addr); |
| fail_bam_remap: |
| fail_init_hw: |
| iounmap(ipa_ctx->mmio); |
| fail_remap: |
| kfree(ipa_ctx); |
| ipa_ctx = NULL; |
| fail_mem: |
| /* gate IPA clocks */ |
| if (ipa_ctx->ipa_hw_mode == IPA_HW_MODE_NORMAL) |
| ipa_disable_clks(); |
| return result; |
| } |
| |
| static int ipa_plat_drv_probe(struct platform_device *pdev_p) |
| { |
| int result = 0; |
| struct resource *resource_p; |
| |
| IPADBG("IPA plat drv probe\n"); |
| |
| /* initialize ipa_res */ |
| ipa_res.ipa_pipe_mem_start_ofst = IPA_PIPE_MEM_START_OFST; |
| ipa_res.ipa_pipe_mem_size = IPA_PIPE_MEM_SIZE; |
| ipa_res.ipa_hw_type = 0; |
| ipa_res.ipa_hw_mode = 0; |
| |
| result = ipa_load_pipe_connection(pdev_p, |
| A2_TO_IPA, |
| &ipa_res.a2_to_ipa_pipe); |
| if (0 != result) |
| IPAERR(":ipa_load_pipe_connection failed!\n"); |
| |
| result = ipa_load_pipe_connection(pdev_p, IPA_TO_A2, |
| &ipa_res.ipa_to_a2_pipe); |
| if (0 != result) |
| IPAERR(":ipa_load_pipe_connection failed!\n"); |
| |
| /* Get IPA wrapper address */ |
| resource_p = platform_get_resource_byname(pdev_p, IORESOURCE_MEM, |
| "ipa-base"); |
| |
| if (!resource_p) { |
| IPAERR(":get resource failed for ipa-base!\n"); |
| return -ENODEV; |
| } else { |
| ipa_res.ipa_mem_base = resource_p->start; |
| ipa_res.ipa_mem_size = resource_size(resource_p); |
| } |
| |
| /* Get IPA BAM address */ |
| resource_p = platform_get_resource_byname(pdev_p, IORESOURCE_MEM, |
| "bam-base"); |
| |
| if (!resource_p) { |
| IPAERR(":get resource failed for bam-base!\n"); |
| return -ENODEV; |
| } else { |
| ipa_res.bam_mem_base = resource_p->start; |
| ipa_res.bam_mem_size = resource_size(resource_p); |
| } |
| |
| /* Get IPA A2 BAM address */ |
| resource_p = platform_get_resource_byname(pdev_p, IORESOURCE_MEM, |
| "a2-bam-base"); |
| |
| if (!resource_p) { |
| IPAERR(":get resource failed for a2-bam-base!\n"); |
| return -ENODEV; |
| } else { |
| ipa_res.a2_bam_mem_base = resource_p->start; |
| ipa_res.a2_bam_mem_size = resource_size(resource_p); |
| } |
| |
| /* Get IPA pipe mem start ofst */ |
| resource_p = platform_get_resource_byname(pdev_p, IORESOURCE_MEM, |
| "ipa-pipe-mem"); |
| |
| if (!resource_p) { |
| IPADBG(":get resource failed for ipa-pipe-mem\n"); |
| } else { |
| ipa_res.ipa_pipe_mem_start_ofst = resource_p->start; |
| ipa_res.ipa_pipe_mem_size = resource_size(resource_p); |
| } |
| |
| /* Get IPA IRQ number */ |
| resource_p = platform_get_resource_byname(pdev_p, IORESOURCE_IRQ, |
| "ipa-irq"); |
| |
| if (!resource_p) { |
| IPAERR(":get resource failed for ipa-irq!\n"); |
| return -ENODEV; |
| } else { |
| ipa_res.ipa_irq = resource_p->start; |
| } |
| |
| /* Get IPA BAM IRQ number */ |
| resource_p = platform_get_resource_byname(pdev_p, IORESOURCE_IRQ, |
| "bam-irq"); |
| |
| if (!resource_p) { |
| IPAERR(":get resource failed for bam-irq!\n"); |
| return -ENODEV; |
| } else { |
| ipa_res.bam_irq = resource_p->start; |
| } |
| |
| /* Get IPA A2 BAM IRQ number */ |
| resource_p = platform_get_resource_byname(pdev_p, IORESOURCE_IRQ, |
| "a2-bam-irq"); |
| |
| if (!resource_p) { |
| IPAERR(":get resource failed for a2-bam-irq!\n"); |
| return -ENODEV; |
| } else { |
| ipa_res.a2_bam_irq = resource_p->start; |
| } |
| |
| /* Get IPA HW Version */ |
| result = of_property_read_u32(pdev_p->dev.of_node, "qcom,ipa-hw-ver", |
| &ipa_res.ipa_hw_type); |
| |
| if ((result) || (ipa_res.ipa_hw_type == 0)) { |
| IPAERR(":get resource failed for ipa-hw-ver!\n"); |
| return -ENODEV; |
| } |
| IPADBG(": found ipa_res.ipa_hw_type = %d", ipa_res.ipa_hw_type); |
| |
| /* Get IPA HW mode */ |
| result = of_property_read_u32(pdev_p->dev.of_node, "ipa-hw-mode", |
| &ipa_res.ipa_hw_mode); |
| |
| if (result) |
| IPADBG("using default (IPA_MODE_NORMAL) for ipa-hw-mode\n"); |
| else |
| IPADBG(": found ipa_res.ipa_hw_mode = %d", ipa_res.ipa_hw_mode); |
| |
| IPADBG(":ipa_mem_base = 0x%x, ipa_mem_size = 0x%x\n", |
| ipa_res.ipa_mem_base, ipa_res.ipa_mem_size); |
| IPADBG(":bam_mem_base = 0x%x, bam_mem_size = 0x%x\n", |
| ipa_res.bam_mem_base, ipa_res.bam_mem_size); |
| IPADBG(":pipe_mem_start_ofst = 0x%x, pipe_mem_size = 0x%x\n", |
| ipa_res.ipa_pipe_mem_start_ofst, ipa_res.ipa_pipe_mem_size); |
| |
| IPADBG(":ipa_irq = %d\n", ipa_res.ipa_irq); |
| IPADBG(":bam_irq = %d\n", ipa_res.bam_irq); |
| |
| /* stash the IPA dev ptr */ |
| ipa_dev = &pdev_p->dev; |
| |
| if (ipa_res.ipa_hw_mode == IPA_HW_MODE_NORMAL) { |
| /* get IPA clocks */ |
| if (ipa_get_clks(ipa_dev) != 0) { |
| IPAERR(":fail to get clk handle's!\n"); |
| return -ENODEV; |
| } |
| |
| /* get BUS handle */ |
| ipa_bus_hdl = |
| msm_bus_scale_register_client(&ipa_bus_client_pdata); |
| if (!ipa_bus_hdl) { |
| IPAERR(":fail to register with bus mgr!\n"); |
| return -ENODEV; |
| } |
| |
| /* enable IPA clocks */ |
| ipa_enable_clks(); |
| } |
| |
| /* Proceed to real initialization */ |
| result = ipa_init(&ipa_res); |
| if (result) |
| IPAERR("ipa_init failed\n"); |
| |
| result = device_create_file(&pdev_p->dev, |
| &dev_attr_aggregation_type); |
| if (result) |
| IPAERR("failed to create device file\n"); |
| |
| result = device_create_file(&pdev_p->dev, |
| &dev_attr_aggregation_byte_limit); |
| if (result) |
| IPAERR("failed to create device file\n"); |
| |
| result = device_create_file(&pdev_p->dev, |
| &dev_attr_aggregation_time_limit); |
| if (result) |
| IPAERR("failed to create device file\n"); |
| |
| return result; |
| } |
| |
| static struct platform_driver ipa_plat_drv = { |
| .probe = ipa_plat_drv_probe, |
| .driver = { |
| .name = DRV_NAME, |
| .owner = THIS_MODULE, |
| .of_match_table = ipa_plat_drv_match, |
| }, |
| }; |
| |
| struct ipa_context *ipa_get_ctx(void) |
| { |
| return ipa_ctx; |
| } |
| |
| static int __init ipa_module_init(void) |
| { |
| int result = 0; |
| |
| IPADBG("IPA module init\n"); |
| |
| /* Register as a platform device driver */ |
| platform_driver_register(&ipa_plat_drv); |
| |
| ipa_debugfs_init(); |
| |
| return result; |
| } |
| subsys_initcall(ipa_module_init); |
| |
| MODULE_LICENSE("GPL v2"); |
| MODULE_DESCRIPTION("IPA HW device driver"); |
| |