| /* Copyright (c) 2015-2016, 2018, 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) "CAM-AHB %s:%d " fmt, __func__, __LINE__ |
| #define TRUE 1 |
| #include <linux/module.h> |
| #include <linux/msm-bus.h> |
| #include <linux/msm-bus-board.h> |
| #include <linux/of_platform.h> |
| #include <linux/pm_opp.h> |
| #include <linux/regulator/rpm-smd-regulator.h> |
| #include "cam_hw_ops.h" |
| |
| #ifdef CONFIG_CAM_AHB_DBG |
| #define CDBG(fmt, args...) pr_err(fmt, ##args) |
| #else |
| #define CDBG(fmt, args...) pr_debug(fmt, ##args) |
| #endif |
| |
| struct cam_ahb_client { |
| enum cam_ahb_clk_vote vote; |
| }; |
| |
| struct cam_bus_vector { |
| const char *name; |
| }; |
| |
| struct cam_ahb_client_data { |
| struct msm_bus_scale_pdata *pbus_data; |
| u32 ahb_client; |
| u32 ahb_clk_state; |
| struct msm_bus_vectors *paths; |
| struct msm_bus_paths *usecases; |
| struct cam_bus_vector *vectors; |
| u32 *votes; |
| u32 cnt; |
| u32 probe_done; |
| struct cam_ahb_client clients[CAM_AHB_CLIENT_MAX]; |
| struct mutex lock; |
| }; |
| |
| static struct cam_ahb_client_data data; |
| |
| static int get_vector_index(char *name) |
| { |
| int i = 0, rc = -1; |
| |
| for (i = 0; i < data.cnt; i++) { |
| if (strcmp(name, data.vectors[i].name) == 0) |
| return i; |
| } |
| |
| return rc; |
| } |
| |
| int cam_ahb_clk_init(struct platform_device *pdev) |
| { |
| int i = 0, cnt = 0, rc = 0, index = 0; |
| struct device_node *of_node; |
| |
| if (!pdev) { |
| pr_err("invalid pdev argument\n"); |
| return -EINVAL; |
| } |
| |
| of_node = pdev->dev.of_node; |
| data.cnt = of_property_count_strings(of_node, "bus-vectors"); |
| if (data.cnt == 0) { |
| pr_err("no vectors strings found in device tree, count=%d", |
| data.cnt); |
| return 0; |
| } |
| |
| cnt = of_property_count_u32_elems(of_node, "qcom,bus-votes"); |
| if (cnt == 0) { |
| pr_err("no vector values found in device tree, count=%d", cnt); |
| return 0; |
| } |
| |
| if (data.cnt != cnt) { |
| pr_err("vector mismatch num of strings=%u, num of values %d\n", |
| data.cnt, cnt); |
| return -EINVAL; |
| } |
| |
| CDBG("number of bus vectors: %d\n", data.cnt); |
| |
| data.vectors = devm_kzalloc(&pdev->dev, |
| sizeof(struct cam_bus_vector) * cnt, |
| GFP_KERNEL); |
| if (!data.vectors) |
| return -ENOMEM; |
| |
| for (i = 0; i < data.cnt; i++) { |
| rc = of_property_read_string_index(of_node, "bus-vectors", |
| i, &(data.vectors[i].name)); |
| CDBG("dbg: names[%d] = %s\n", i, data.vectors[i].name); |
| if (rc < 0) { |
| pr_err("failed\n"); |
| rc = -EINVAL; |
| goto err1; |
| } |
| } |
| |
| data.paths = devm_kzalloc(&pdev->dev, |
| sizeof(struct msm_bus_vectors) * cnt, |
| GFP_KERNEL); |
| if (!data.paths) { |
| rc = -ENOMEM; |
| goto err1; |
| } |
| |
| data.usecases = devm_kzalloc(&pdev->dev, |
| sizeof(struct msm_bus_paths) * cnt, |
| GFP_KERNEL); |
| if (!data.usecases) { |
| rc = -ENOMEM; |
| goto err2; |
| } |
| |
| data.pbus_data = devm_kzalloc(&pdev->dev, |
| sizeof(struct msm_bus_scale_pdata), |
| GFP_KERNEL); |
| if (!data.pbus_data) { |
| rc = -ENOMEM; |
| goto err3; |
| } |
| |
| data.votes = devm_kzalloc(&pdev->dev, sizeof(u32) * cnt, |
| GFP_KERNEL); |
| if (!data.votes) { |
| rc = -ENOMEM; |
| goto err4; |
| } |
| |
| rc = of_property_read_u32_array(of_node, "qcom,bus-votes", |
| data.votes, cnt); |
| |
| for (i = 0; i < data.cnt; i++) { |
| data.paths[i] = (struct msm_bus_vectors) { |
| MSM_BUS_MASTER_AMPSS_M0, |
| MSM_BUS_SLAVE_CAMERA_CFG, |
| 0, |
| data.votes[i] |
| }; |
| data.usecases[i] = (struct msm_bus_paths) { |
| .num_paths = 1, |
| .vectors = &data.paths[i], |
| }; |
| CDBG("dbg: votes[%d] = %u\n", i, data.votes[i]); |
| } |
| |
| *data.pbus_data = (struct msm_bus_scale_pdata) { |
| .name = "msm_camera_ahb", |
| .num_usecases = data.cnt, |
| .usecase = data.usecases, |
| }; |
| |
| data.ahb_client = |
| msm_bus_scale_register_client(data.pbus_data); |
| if (!data.ahb_client) { |
| pr_err("ahb vote registering failed\n"); |
| rc = -EINVAL; |
| goto err5; |
| } |
| |
| index = get_vector_index("suspend"); |
| if (index < 0) { |
| pr_err("svs vector not supported\n"); |
| rc = -EINVAL; |
| goto err6; |
| } |
| |
| /* request for svs in init */ |
| msm_bus_scale_client_update_request(data.ahb_client, |
| index); |
| data.ahb_clk_state = CAM_AHB_SUSPEND_VOTE; |
| data.probe_done = TRUE; |
| mutex_init(&data.lock); |
| |
| CDBG("dbg, done registering ahb votes\n"); |
| CDBG("dbg, clk state :%u, probe :%d\n", |
| data.ahb_clk_state, data.probe_done); |
| return rc; |
| |
| err6: |
| msm_bus_scale_unregister_client(data.ahb_client); |
| err5: |
| devm_kfree(&pdev->dev, data.votes); |
| data.votes = NULL; |
| err4: |
| devm_kfree(&pdev->dev, data.pbus_data); |
| data.pbus_data = NULL; |
| err3: |
| devm_kfree(&pdev->dev, data.usecases); |
| data.usecases = NULL; |
| err2: |
| devm_kfree(&pdev->dev, data.paths); |
| data.paths = NULL; |
| err1: |
| devm_kfree(&pdev->dev, data.vectors); |
| data.vectors = NULL; |
| return rc; |
| } |
| EXPORT_SYMBOL(cam_ahb_clk_init); |
| |
| static int cam_consolidate_ahb_vote(enum cam_ahb_clk_client id, |
| enum cam_ahb_clk_vote vote) |
| { |
| int i = 0; |
| u32 max = 0; |
| |
| CDBG("dbg: id :%u, vote : 0x%x\n", id, vote); |
| mutex_lock(&data.lock); |
| data.clients[id].vote = vote; |
| |
| if (vote == data.ahb_clk_state) { |
| CDBG("dbg: already at desired vote\n"); |
| mutex_unlock(&data.lock); |
| return 0; |
| } |
| |
| for (i = 0; i < CAM_AHB_CLIENT_MAX; i++) { |
| if (data.clients[i].vote > max) |
| max = data.clients[i].vote; |
| } |
| |
| CDBG("dbg: max vote : %u\n", max); |
| if (max != data.ahb_clk_state) { |
| msm_bus_scale_client_update_request(data.ahb_client, |
| max); |
| data.ahb_clk_state = max; |
| CDBG("dbg: state : %u, vector : %d\n", |
| data.ahb_clk_state, max); |
| } |
| mutex_unlock(&data.lock); |
| return 0; |
| } |
| |
| static int cam_ahb_get_voltage_level(unsigned int corner) |
| { |
| switch (corner) { |
| case RPM_REGULATOR_CORNER_NONE: |
| return CAM_AHB_SUSPEND_VOTE; |
| |
| case RPM_REGULATOR_CORNER_SVS_KRAIT: |
| case RPM_REGULATOR_CORNER_SVS_SOC: |
| return CAM_AHB_SVS_VOTE; |
| |
| case RPM_REGULATOR_CORNER_NORMAL: |
| return CAM_AHB_NOMINAL_VOTE; |
| |
| case RPM_REGULATOR_CORNER_SUPER_TURBO: |
| return CAM_AHB_TURBO_VOTE; |
| |
| case RPM_REGULATOR_CORNER_TURBO: |
| case RPM_REGULATOR_CORNER_RETENTION: |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| int cam_config_ahb_clk(struct device *dev, unsigned long freq, |
| enum cam_ahb_clk_client id, enum cam_ahb_clk_vote vote) |
| { |
| struct dev_pm_opp *opp; |
| unsigned int corner; |
| enum cam_ahb_clk_vote dyn_vote = vote; |
| int rc = -EINVAL; |
| |
| if (id >= CAM_AHB_CLIENT_MAX) { |
| pr_err("err: invalid argument\n"); |
| return -EINVAL; |
| } |
| |
| if (data.probe_done != TRUE) { |
| pr_err("ahb init is not done yet\n"); |
| return -EINVAL; |
| } |
| |
| CDBG("dbg: id :%u, vote : 0x%x\n", id, vote); |
| switch (dyn_vote) { |
| case CAM_AHB_SUSPEND_VOTE: |
| case CAM_AHB_SVS_VOTE: |
| case CAM_AHB_NOMINAL_VOTE: |
| case CAM_AHB_TURBO_VOTE: |
| break; |
| case CAM_AHB_DYNAMIC_VOTE: |
| if (!dev) { |
| pr_err("device is NULL\n"); |
| return -EINVAL; |
| } |
| opp = dev_pm_opp_find_freq_exact(dev, freq, true); |
| if (IS_ERR(opp)) { |
| pr_err("Error on OPP freq :%ld\n", freq); |
| return -EINVAL; |
| } |
| corner = dev_pm_opp_get_voltage(opp); |
| if (corner == 0) { |
| pr_err("Bad voltage corner for OPP freq :%ld\n", freq); |
| return -EINVAL; |
| } |
| dyn_vote = cam_ahb_get_voltage_level(corner); |
| if (dyn_vote < 0) { |
| pr_err("Bad vote requested\n"); |
| return -EINVAL; |
| } |
| break; |
| default: |
| pr_err("err: invalid vote argument\n"); |
| return -EINVAL; |
| } |
| |
| rc = cam_consolidate_ahb_vote(id, dyn_vote); |
| if (rc < 0) { |
| pr_err("%s: failed to vote for AHB\n", __func__); |
| goto end; |
| } |
| |
| end: |
| return rc; |
| } |
| EXPORT_SYMBOL(cam_config_ahb_clk); |