/* * Copyright (c) 2014 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "wil_platform.h" #include "wil_platform_msm.h" /** * struct wil_platform_msm - wil6210 msm platform module info * * @dev: device object * @msm_bus_handle: handle for using msm_bus API * @pdata: bus scale info retrieved from DT */ struct wil_platform_msm { struct device *dev; uint32_t msm_bus_handle; struct msm_bus_scale_pdata *pdata; }; #define KBTOB(a) (a * 1000ULL) /** * wil_platform_get_pdata() - Generate bus client data from device tree * provided by clients. * * dev: device object * 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. */ static struct msm_bus_scale_pdata *wil_platform_get_pdata( struct device *dev, struct device_node *of_node) { struct msm_bus_scale_pdata *pdata; struct msm_bus_paths *usecase; int i, j, ret, len; unsigned int num_usecases, num_paths, mem_size; const uint32_t *vec_arr; struct msm_bus_vectors *vectors; /* first read num_usecases and num_paths so we can calculate * amount of memory to allocate */ ret = of_property_read_u32(of_node, "qcom,msm-bus,num-cases", &num_usecases); if (ret) { dev_err(dev, "Error: num-usecases not found\n"); return NULL; } ret = of_property_read_u32(of_node, "qcom,msm-bus,num-paths", &num_paths); if (ret) { dev_err(dev, "Error: num_paths not found\n"); return NULL; } /* pdata memory layout: * msm_bus_scale_pdata * msm_bus_paths[num_usecases] * msm_bus_vectors[num_usecases][num_paths] */ mem_size = sizeof(struct msm_bus_scale_pdata) + sizeof(struct msm_bus_paths) * num_usecases + sizeof(struct msm_bus_vectors) * num_usecases * num_paths; pdata = kzalloc(mem_size, GFP_KERNEL); if (!pdata) return NULL; ret = of_property_read_string(of_node, "qcom,msm-bus,name", &pdata->name); if (ret) { dev_err(dev, "Error: Client name not found\n"); goto err; } if (of_property_read_bool(of_node, "qcom,msm-bus,active-only")) { pdata->active_only = 1; } else { dev_info(dev, "active_only flag absent.\n"); dev_info(dev, "Using dual context by default\n"); } pdata->num_usecases = num_usecases; pdata->usecase = (struct msm_bus_paths *)(pdata + 1); vec_arr = of_get_property(of_node, "qcom,msm-bus,vectors-KBps", &len); if (vec_arr == NULL) { dev_err(dev, "Error: Vector array not found\n"); goto err; } if (len != num_usecases * num_paths * sizeof(uint32_t) * 4) { dev_err(dev, "Error: Length-error on getting vectors\n"); goto err; } vectors = (struct msm_bus_vectors *)(pdata->usecase + num_usecases); for (i = 0; i < num_usecases; i++) { usecase = &pdata->usecase[i]; usecase->num_paths = num_paths; usecase->vectors = &vectors[i]; for (j = 0; j < num_paths; j++) { int index = ((i * num_paths) + j) * 4; usecase->vectors[j].src = be32_to_cpu(vec_arr[index]); usecase->vectors[j].dst = be32_to_cpu(vec_arr[index + 1]); usecase->vectors[j].ab = (uint64_t) KBTOB(be32_to_cpu(vec_arr[index + 2])); usecase->vectors[j].ib = (uint64_t) KBTOB(be32_to_cpu(vec_arr[index + 3])); } } return pdata; err: kfree(pdata); return NULL; } /* wil_platform API (callbacks) */ static int wil_platform_bus_request(void *handle, uint32_t kbps /* KBytes/Sec */) { int rc, i; struct wil_platform_msm *msm = (struct wil_platform_msm *)handle; int vote = 0; /* vote 0 in case requested kbps cannot be satisfied */ struct msm_bus_paths *usecase; uint32_t usecase_kbps; uint32_t min_kbps = ~0; /* find the lowest usecase that is bigger than requested kbps */ for (i = 0; i < msm->pdata->num_usecases; i++) { usecase = &msm->pdata->usecase[i]; /* assume we have single path (vectors[0]). If we ever * have multiple paths, need to define the behavior */ usecase_kbps = div64_u64(usecase->vectors[0].ib, 1000); if (usecase_kbps >= kbps && usecase_kbps < min_kbps) { min_kbps = usecase_kbps; vote = i; } } rc = msm_bus_scale_client_update_request(msm->msm_bus_handle, vote); if (rc) dev_err(msm->dev, "Failed msm_bus voting. kbps=%d vote=%d, rc=%d\n", kbps, vote, rc); else /* TOOD: remove */ dev_info(msm->dev, "msm_bus_scale_client_update_request succeeded. kbps=%d vote=%d\n", kbps, vote); return rc; } static void wil_platform_uninit(void *handle) { struct wil_platform_msm *msm = (struct wil_platform_msm *)handle; dev_info(msm->dev, "wil_platform_uninit\n"); if (msm->msm_bus_handle) msm_bus_scale_unregister_client(msm->msm_bus_handle); kfree(msm->pdata); kfree(msm); } static int wil_platform_msm_bus_register(struct wil_platform_msm *msm, struct device_node *node) { msm->pdata = wil_platform_get_pdata(msm->dev, node); if (!msm->pdata) { dev_err(msm->dev, "Failed getting DT info\n"); return -EINVAL; } msm->msm_bus_handle = msm_bus_scale_register_client(msm->pdata); if (!msm->msm_bus_handle) { dev_err(msm->dev, "Failed msm_bus registration\n"); return -EINVAL; } dev_info(msm->dev, "msm_bus registration succeeded! handle 0x%x\n", msm->msm_bus_handle); return 0; } /** * wil_platform_msm_init() - wil6210 msm platform module init * * The function must be called before all other functions in this module. * It returns a handle which is used with the rest of the API * */ void *wil_platform_msm_init(struct device *dev, struct wil_platform_ops *ops) { struct device_node *of_node; struct wil_platform_msm *msm; int rc; of_node = of_find_compatible_node(NULL, NULL, "qcom,wil6210"); if (!of_node) { /* this could mean non-msm platform */ dev_err(dev, "DT node not found\n"); return NULL; } msm = kzalloc(sizeof(*msm), GFP_KERNEL); if (!msm) return NULL; msm->dev = dev; /* register with msm_bus module for scaling requests */ rc = wil_platform_msm_bus_register(msm, of_node); if (rc) goto cleanup; memset(ops, 0, sizeof(*ops)); ops->bus_request = wil_platform_bus_request; ops->uninit = wil_platform_uninit; return (void *)msm; cleanup: kfree(msm); return NULL; }