| /* 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. |
| */ |
| |
| #define pr_fmt(fmt) "AXI: %s(): " fmt, __func__ |
| |
| #include <linux/module.h> |
| #include <linux/slab.h> |
| #include <linux/string.h> |
| #include <linux/of.h> |
| #include <linux/of_device.h> |
| #include <linux/platform_device.h> |
| #include <mach/msm_bus.h> |
| #include <mach/msm_bus_board.h> |
| #include "msm_bus_core.h" |
| |
| #define KBTOB(a) (a * 1000ULL) |
| static const char * const hw_sel_name[] = {"RPM", "NoC", "BIMC", NULL}; |
| static const char * const mode_sel_name[] = {"Fixed", "Limiter", "Bypass", |
| "Regulator", NULL}; |
| |
| static int get_num(const char *const str[], const char *name) |
| { |
| int i = 0; |
| |
| do { |
| if (!strcmp(name, str[i])) |
| return i; |
| |
| i++; |
| } while (str[i] != NULL); |
| |
| pr_err("Error: string %s not found\n", name); |
| return -EINVAL; |
| } |
| |
| static struct msm_bus_scale_pdata *get_pdata(struct platform_device *pdev, |
| struct device_node *of_node) |
| { |
| struct msm_bus_scale_pdata *pdata = NULL; |
| struct msm_bus_paths *usecase = NULL; |
| int i = 0, j, ret, num_usecases = 0, num_paths, len; |
| const uint32_t *vec_arr = NULL; |
| bool mem_err = false; |
| |
| if (!pdev) { |
| pr_err("Error: Null Platform device\n"); |
| return NULL; |
| } |
| |
| pdata = devm_kzalloc(&pdev->dev, sizeof(struct msm_bus_scale_pdata), |
| GFP_KERNEL); |
| if (!pdata) { |
| pr_err("Error: Memory allocation for pdata failed\n"); |
| mem_err = true; |
| goto err; |
| } |
| |
| ret = of_property_read_string(of_node, "qcom,msm-bus,name", |
| &pdata->name); |
| if (ret) { |
| pr_err("Error: Client name not found\n"); |
| goto err; |
| } |
| |
| ret = of_property_read_u32(of_node, "qcom,msm-bus,num-cases", |
| &num_usecases); |
| if (ret) { |
| pr_err("Error: num-usecases not found\n"); |
| goto err; |
| } |
| |
| pdata->num_usecases = num_usecases; |
| |
| if (of_property_read_bool(of_node, "qcom,msm-bus,active-only")) |
| pdata->active_only = 1; |
| else { |
| pr_debug("active_only flag absent.\n"); |
| pr_debug("Using dual context by default\n"); |
| } |
| |
| usecase = devm_kzalloc(&pdev->dev, (sizeof(struct msm_bus_paths) * |
| pdata->num_usecases), GFP_KERNEL); |
| if (!usecase) { |
| pr_err("Error: Memory allocation for paths failed\n"); |
| mem_err = true; |
| goto err; |
| } |
| |
| ret = of_property_read_u32(of_node, "qcom,msm-bus,num-paths", |
| &num_paths); |
| if (ret) { |
| pr_err("Error: num_paths not found\n"); |
| goto err; |
| } |
| |
| vec_arr = of_get_property(of_node, "qcom,msm-bus,vectors-KBps", &len); |
| if (vec_arr == NULL) { |
| pr_err("Error: Vector array not found\n"); |
| goto err; |
| } |
| |
| if (len != num_usecases * num_paths * sizeof(uint32_t) * 4) { |
| pr_err("Error: Length-error on getting vectors\n"); |
| goto err; |
| } |
| |
| for (i = 0; i < num_usecases; i++) { |
| usecase[i].num_paths = num_paths; |
| usecase[i].vectors = devm_kzalloc(&pdev->dev, num_paths * |
| sizeof(struct msm_bus_vectors), GFP_KERNEL); |
| if (!usecase[i].vectors) { |
| mem_err = true; |
| pr_err("Error: Mem alloc failure in vectors\n"); |
| goto err; |
| } |
| |
| for (j = 0; j < num_paths; j++) { |
| int index = ((i * num_paths) + j) * 4; |
| usecase[i].vectors[j].src = be32_to_cpu(vec_arr[index]); |
| usecase[i].vectors[j].dst = |
| be32_to_cpu(vec_arr[index + 1]); |
| usecase[i].vectors[j].ab = (uint64_t) |
| KBTOB(be32_to_cpu(vec_arr[index + 2])); |
| usecase[i].vectors[j].ib = (uint64_t) |
| KBTOB(be32_to_cpu(vec_arr[index + 3])); |
| } |
| } |
| |
| pdata->usecase = usecase; |
| return pdata; |
| err: |
| if (mem_err) { |
| for (; i > 0; i--) |
| kfree(usecase[i-1].vectors); |
| |
| kfree(usecase); |
| kfree(pdata); |
| } |
| |
| return NULL; |
| } |
| |
| /** |
| * msm_bus_cl_get_pdata() - Generate bus client data from device tree |
| * provided by clients. |
| * |
| * of_node: Device tree node to extract information from |
| * |
| * The function returns a valid pointer to the allocated bus-scale-pdata |
| * if the vectors were correctly read from the client's device node. |
| * Any error in reading or parsing the device node will return NULL |
| * to the caller. |
| */ |
| struct msm_bus_scale_pdata *msm_bus_cl_get_pdata(struct platform_device *pdev) |
| { |
| struct device_node *of_node; |
| struct msm_bus_scale_pdata *pdata = NULL; |
| |
| if (!pdev) { |
| pr_err("Error: Null Platform device\n"); |
| return NULL; |
| } |
| |
| of_node = pdev->dev.of_node; |
| pdata = get_pdata(pdev, of_node); |
| if (!pdata) { |
| pr_err("Error getting bus pdata!\n"); |
| return NULL; |
| } |
| |
| return pdata; |
| } |
| EXPORT_SYMBOL(msm_bus_cl_get_pdata); |
| |
| /** |
| * msm_bus_cl_pdata_from_node() - Generate bus client data from device tree |
| * node provided by clients. This function should be used when a client |
| * driver needs to register multiple bus-clients from a single device-tree |
| * node associated with the platform-device. |
| * |
| * of_node: The subnode containing information about the bus scaling |
| * data |
| * |
| * pdev: Platform device associated with the device-tree node |
| * |
| * The function returns a valid pointer to the allocated bus-scale-pdata |
| * if the vectors were correctly read from the client's device node. |
| * Any error in reading or parsing the device node will return NULL |
| * to the caller. |
| */ |
| struct msm_bus_scale_pdata *msm_bus_pdata_from_node( |
| struct platform_device *pdev, struct device_node *of_node) |
| { |
| struct msm_bus_scale_pdata *pdata = NULL; |
| |
| if (!pdev) { |
| pr_err("Error: Null Platform device\n"); |
| return NULL; |
| } |
| |
| if (!of_node) { |
| pr_err("Error: Null of_node passed to bus driver\n"); |
| return NULL; |
| } |
| |
| pdata = get_pdata(pdev, of_node); |
| if (!pdata) { |
| pr_err("Error getting bus pdata!\n"); |
| return NULL; |
| } |
| |
| return pdata; |
| } |
| EXPORT_SYMBOL(msm_bus_pdata_from_node); |
| |
| /** |
| * msm_bus_cl_clear_pdata() - Clear pdata allocated from device-tree |
| * of_node: Device tree node to extract information from |
| */ |
| void msm_bus_cl_clear_pdata(struct msm_bus_scale_pdata *pdata) |
| { |
| int i; |
| |
| for (i = 0; i < pdata->num_usecases; i++) |
| kfree(pdata->usecase[i].vectors); |
| |
| kfree(pdata->usecase); |
| kfree(pdata); |
| } |
| EXPORT_SYMBOL(msm_bus_cl_clear_pdata); |
| |
| static int *get_arr(struct platform_device *pdev, |
| const struct device_node *node, const char *prop, |
| int *nports) |
| { |
| int size = 0, ret; |
| int *arr = NULL; |
| |
| if (of_get_property(node, prop, &size)) { |
| *nports = size / sizeof(int); |
| } else { |
| pr_debug("Property %s not available\n", prop); |
| *nports = 0; |
| return NULL; |
| } |
| |
| arr = devm_kzalloc(&pdev->dev, size, GFP_KERNEL); |
| if ((size > 0) && ZERO_OR_NULL_PTR(arr)) { |
| pr_err("Error: Failed to alloc mem for %s\n", prop); |
| return NULL; |
| } |
| |
| ret = of_property_read_u32_array(node, prop, (u32 *)arr, *nports); |
| if (ret) { |
| pr_err("Error in reading property: %s\n", prop); |
| goto err; |
| } |
| |
| return arr; |
| err: |
| devm_kfree(&pdev->dev, arr); |
| return NULL; |
| } |
| |
| static struct msm_bus_node_info *get_nodes(struct device_node *of_node, |
| struct platform_device *pdev, |
| struct msm_bus_fabric_registration *pdata) |
| { |
| struct msm_bus_node_info *info; |
| struct device_node *child_node = NULL; |
| int i = 0, ret; |
| u32 temp; |
| |
| for_each_child_of_node(of_node, child_node) { |
| i++; |
| } |
| |
| pdata->len = i; |
| info = (struct msm_bus_node_info *) |
| devm_kzalloc(&pdev->dev, sizeof(struct msm_bus_node_info) * |
| pdata->len, GFP_KERNEL); |
| if (ZERO_OR_NULL_PTR(info)) { |
| pr_err("Failed to alloc memory for nodes: %d\n", pdata->len); |
| goto err; |
| } |
| |
| i = 0; |
| child_node = NULL; |
| for_each_child_of_node(of_node, child_node) { |
| const char *sel_str; |
| |
| ret = of_property_read_string(child_node, "label", |
| &info[i].name); |
| if (ret) |
| pr_err("Error reading node label\n"); |
| |
| ret = of_property_read_u32(child_node, "cell-id", &info[i].id); |
| if (ret) { |
| pr_err("Error reading node id\n"); |
| goto err; |
| } |
| |
| if (of_property_read_bool(child_node, "qcom,gateway")) |
| info[i].gateway = 1; |
| |
| of_property_read_u32(child_node, "qcom,mas-hw-id", |
| &info[i].mas_hw_id); |
| |
| of_property_read_u32(child_node, "qcom,slv-hw-id", |
| &info[i].slv_hw_id); |
| info[i].masterp = get_arr(pdev, child_node, |
| "qcom,masterp", &info[i].num_mports); |
| /* No need to store number of qports */ |
| info[i].qport = get_arr(pdev, child_node, |
| "qcom,qport", &ret); |
| pdata->nmasters += info[i].num_mports; |
| |
| |
| info[i].slavep = get_arr(pdev, child_node, |
| "qcom,slavep", &info[i].num_sports); |
| pdata->nslaves += info[i].num_sports; |
| |
| |
| info[i].tier = get_arr(pdev, child_node, |
| "qcom,tier", &info[i].num_tiers); |
| |
| if (of_property_read_bool(child_node, "qcom,ahb")) |
| info[i].ahb = 1; |
| |
| ret = of_property_read_string(child_node, "qcom,hw-sel", |
| &sel_str); |
| if (ret) |
| info[i].hw_sel = 0; |
| else { |
| ret = get_num(hw_sel_name, sel_str); |
| if (ret < 0) { |
| pr_err("Invalid hw-sel\n"); |
| goto err; |
| } |
| |
| info[i].hw_sel = ret; |
| } |
| |
| of_property_read_u32(child_node, "qcom,buswidth", |
| &info[i].buswidth); |
| of_property_read_u32(child_node, "qcom,ws", &info[i].ws); |
| ret = of_property_read_u32(child_node, "qcom,thresh", |
| &temp); |
| if (!ret) |
| info[i].th = (uint64_t)KBTOB(temp); |
| |
| ret = of_property_read_u32(child_node, "qcom,bimc,bw", |
| &temp); |
| if (!ret) |
| info[i].bimc_bw = (uint64_t)KBTOB(temp); |
| |
| of_property_read_u32(child_node, "qcom,bimc,gp", |
| &info[i].bimc_gp); |
| of_property_read_u32(child_node, "qcom,bimc,thmp", |
| &info[i].bimc_thmp); |
| ret = of_property_read_string(child_node, "qcom,mode", |
| &sel_str); |
| if (ret) |
| info[i].mode = 0; |
| else { |
| ret = get_num(mode_sel_name, sel_str); |
| if (ret < 0) { |
| pr_err("Unknown mode :%s\n", sel_str); |
| goto err; |
| } |
| |
| info[i].mode = ret; |
| } |
| |
| info[i].dual_conf = |
| of_property_read_bool(child_node, "qcom,dual-conf"); |
| |
| ret = of_property_read_string(child_node, "qcom,mode-thresh", |
| &sel_str); |
| if (ret) |
| info[i].mode_thresh = 0; |
| else { |
| ret = get_num(mode_sel_name, sel_str); |
| if (ret < 0) { |
| pr_err("Unknown mode :%s\n", sel_str); |
| goto err; |
| } |
| |
| info[i].mode_thresh = ret; |
| MSM_BUS_DBG("AXI: THreshold mode set: %d\n", |
| info[i].mode_thresh); |
| } |
| |
| ret = of_property_read_string(child_node, "qcom,perm-mode", |
| &sel_str); |
| if (ret) |
| info[i].perm_mode = 0; |
| else { |
| ret = get_num(mode_sel_name, sel_str); |
| if (ret < 0) |
| goto err; |
| |
| info[i].perm_mode = 1 << ret; |
| } |
| |
| of_property_read_u32(child_node, "qcom,prio-lvl", |
| &info[i].prio_lvl); |
| of_property_read_u32(child_node, "qcom,prio-rd", |
| &info[i].prio_rd); |
| of_property_read_u32(child_node, "qcom,prio-wr", |
| &info[i].prio_wr); |
| of_property_read_u32(child_node, "qcom,prio0", &info[i].prio0); |
| of_property_read_u32(child_node, "qcom,prio1", &info[i].prio1); |
| ret = of_property_read_string(child_node, "qcom,slaveclk-dual", |
| &info[i].slaveclk[DUAL_CTX]); |
| if (!ret) |
| pr_debug("Got slaveclk_dual: %s\n", |
| info[i].slaveclk[DUAL_CTX]); |
| else |
| info[i].slaveclk[DUAL_CTX] = NULL; |
| |
| ret = of_property_read_string(child_node, |
| "qcom,slaveclk-active", &info[i].slaveclk[ACTIVE_CTX]); |
| if (!ret) |
| pr_debug("Got slaveclk_active\n"); |
| else |
| info[i].slaveclk[ACTIVE_CTX] = NULL; |
| |
| ret = of_property_read_string(child_node, "qcom,memclk-dual", |
| &info[i].memclk[DUAL_CTX]); |
| if (!ret) |
| pr_debug("Got memclk_dual\n"); |
| else |
| info[i].memclk[DUAL_CTX] = NULL; |
| |
| ret = of_property_read_string(child_node, "qcom,memclk-active", |
| &info[i].memclk[ACTIVE_CTX]); |
| if (!ret) |
| pr_debug("Got memclk_active\n"); |
| else |
| info[i].memclk[ACTIVE_CTX] = NULL; |
| |
| ret = of_property_read_string(child_node, "qcom,iface-clk-node", |
| &info[i].iface_clk_node); |
| if (!ret) |
| pr_debug("Got iface_clk_node\n"); |
| else |
| info[i].iface_clk_node = NULL; |
| |
| pr_debug("Node name: %s\n", info[i].name); |
| of_node_put(child_node); |
| i++; |
| } |
| |
| pr_debug("Bus %d added: %d masters\n", pdata->id, pdata->nmasters); |
| pr_debug("Bus %d added: %d slaves\n", pdata->id, pdata->nslaves); |
| return info; |
| err: |
| return NULL; |
| } |
| |
| struct msm_bus_fabric_registration |
| *msm_bus_of_get_fab_data(struct platform_device *pdev) |
| { |
| struct device_node *of_node; |
| struct msm_bus_fabric_registration *pdata; |
| bool mem_err = false; |
| int ret = 0; |
| const char *sel_str; |
| |
| if (!pdev) { |
| pr_err("Error: Null platform device\n"); |
| return NULL; |
| } |
| |
| of_node = pdev->dev.of_node; |
| pdata = devm_kzalloc(&pdev->dev, |
| sizeof(struct msm_bus_fabric_registration), GFP_KERNEL); |
| if (!pdata) { |
| pr_err("Error: Memory allocation for pdata failed\n"); |
| mem_err = true; |
| goto err; |
| } |
| |
| ret = of_property_read_string(of_node, "label", &pdata->name); |
| if (ret) { |
| pr_err("Error: label not found\n"); |
| goto err; |
| } |
| pr_debug("Fab_of: Read name: %s\n", pdata->name); |
| |
| ret = of_property_read_u32(of_node, "cell-id", |
| &pdata->id); |
| if (ret) { |
| pr_err("Error: num-usecases not found\n"); |
| goto err; |
| } |
| pr_debug("Fab_of: Read id: %u\n", pdata->id); |
| |
| if (of_property_read_bool(of_node, "qcom,ahb")) |
| pdata->ahb = 1; |
| |
| ret = of_property_read_string(of_node, "qcom,fabclk-dual", |
| &pdata->fabclk[DUAL_CTX]); |
| if (ret) { |
| pr_debug("fabclk_dual not available\n"); |
| pdata->fabclk[DUAL_CTX] = NULL; |
| } else |
| pr_debug("Fab_of: Read clk dual ctx: %s\n", |
| pdata->fabclk[DUAL_CTX]); |
| ret = of_property_read_string(of_node, "qcom,fabclk-active", |
| &pdata->fabclk[ACTIVE_CTX]); |
| if (ret) { |
| pr_debug("Error: fabclk_active not available\n"); |
| pdata->fabclk[ACTIVE_CTX] = NULL; |
| } else |
| pr_debug("Fab_of: Read clk act ctx: %s\n", |
| pdata->fabclk[ACTIVE_CTX]); |
| |
| ret = of_property_read_u32(of_node, "qcom,ntieredslaves", |
| &pdata->ntieredslaves); |
| if (ret) { |
| pr_err("Error: ntieredslaves not found\n"); |
| goto err; |
| } |
| |
| ret = of_property_read_u32(of_node, "qcom,qos-freq", &pdata->qos_freq); |
| if (ret) |
| pr_debug("qos_freq not available\n"); |
| |
| ret = of_property_read_string(of_node, "qcom,hw-sel", &sel_str); |
| if (ret) { |
| pr_err("Error: hw_sel not found\n"); |
| goto err; |
| } else { |
| ret = get_num(hw_sel_name, sel_str); |
| if (ret < 0) |
| goto err; |
| |
| pdata->hw_sel = ret; |
| } |
| |
| if (of_property_read_bool(of_node, "qcom,virt")) |
| pdata->virt = true; |
| |
| if (of_property_read_bool(of_node, "qcom,rpm-en")) |
| pdata->rpm_enabled = 1; |
| |
| pdata->info = get_nodes(of_node, pdev, pdata); |
| return pdata; |
| err: |
| return NULL; |
| } |
| EXPORT_SYMBOL(msm_bus_of_get_fab_data); |