mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-07-22 03:27:30 -04:00
Merge branch '20260227061544.1785978-1-praveen.talari@oss.qualcomm.com' into drivers-for-7.2
Merge the refactoring and helper functions in the Qualcomm GENI Serial Engine driver through a topic branch. These changes will provide the ability to add support managing power and performance for the GENI instances in platforms where these are controlled as SCMI resources. The patches are merged through a topic branch to avoid conflicts with other changes, while making them available to other subsystems. Signed-off-by: Bjorn Andersson <andersson@kernel.org>
This commit is contained in:
@@ -19,6 +19,8 @@
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/pinctrl/consumer.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_domain.h>
|
||||
#include <linux/pm_opp.h>
|
||||
#include <linux/soc/qcom/geni-se.h>
|
||||
|
||||
/**
|
||||
@@ -280,6 +282,12 @@ struct se_fw_hdr {
|
||||
#define geni_setbits32(_addr, _v) writel(readl(_addr) | (_v), _addr)
|
||||
#define geni_clrbits32(_addr, _v) writel(readl(_addr) & ~(_v), _addr)
|
||||
|
||||
enum domain_idx {
|
||||
DOMAIN_IDX_POWER,
|
||||
DOMAIN_IDX_PERF,
|
||||
DOMAIN_IDX_MAX
|
||||
};
|
||||
|
||||
/**
|
||||
* geni_se_get_qup_hw_version() - Read the QUP wrapper Hardware version
|
||||
* @se: Pointer to the corresponding serial engine.
|
||||
@@ -582,6 +590,7 @@ static void geni_se_clks_off(struct geni_se *se)
|
||||
|
||||
clk_disable_unprepare(se->clk);
|
||||
clk_bulk_disable_unprepare(wrapper->num_clks, wrapper->clks);
|
||||
clk_disable_unprepare(se->core_clk);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -618,7 +627,18 @@ static int geni_se_clks_on(struct geni_se *se)
|
||||
|
||||
ret = clk_prepare_enable(se->clk);
|
||||
if (ret)
|
||||
clk_bulk_disable_unprepare(wrapper->num_clks, wrapper->clks);
|
||||
goto err_bulk_clks;
|
||||
|
||||
ret = clk_prepare_enable(se->core_clk);
|
||||
if (ret)
|
||||
goto err_se_clk;
|
||||
|
||||
return 0;
|
||||
|
||||
err_se_clk:
|
||||
clk_disable_unprepare(se->clk);
|
||||
err_bulk_clks:
|
||||
clk_bulk_disable_unprepare(wrapper->num_clks, wrapper->clks);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -899,30 +919,32 @@ EXPORT_SYMBOL_GPL(geni_se_rx_dma_unprep);
|
||||
|
||||
int geni_icc_get(struct geni_se *se, const char *icc_ddr)
|
||||
{
|
||||
int i, err;
|
||||
const char *icc_names[] = {"qup-core", "qup-config", icc_ddr};
|
||||
struct geni_icc_path *icc_paths = se->icc_paths;
|
||||
|
||||
if (has_acpi_companion(se->dev))
|
||||
return 0;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(se->icc_paths); i++) {
|
||||
if (!icc_names[i])
|
||||
continue;
|
||||
icc_paths[GENI_TO_CORE].path = devm_of_icc_get(se->dev, "qup-core");
|
||||
if (IS_ERR(icc_paths[GENI_TO_CORE].path))
|
||||
return dev_err_probe(se->dev, PTR_ERR(icc_paths[GENI_TO_CORE].path),
|
||||
"Failed to get 'qup-core' ICC path\n");
|
||||
|
||||
se->icc_paths[i].path = devm_of_icc_get(se->dev, icc_names[i]);
|
||||
if (IS_ERR(se->icc_paths[i].path))
|
||||
goto err;
|
||||
icc_paths[CPU_TO_GENI].path = devm_of_icc_get(se->dev, "qup-config");
|
||||
if (IS_ERR(icc_paths[CPU_TO_GENI].path))
|
||||
return dev_err_probe(se->dev, PTR_ERR(icc_paths[CPU_TO_GENI].path),
|
||||
"Failed to get 'qup-config' ICC path\n");
|
||||
|
||||
/* The DDR path is optional, depending on protocol and hw capabilities */
|
||||
icc_paths[GENI_TO_DDR].path = devm_of_icc_get(se->dev, "qup-memory");
|
||||
if (IS_ERR(icc_paths[GENI_TO_DDR].path)) {
|
||||
if (PTR_ERR(icc_paths[GENI_TO_DDR].path) == -ENODATA)
|
||||
icc_paths[GENI_TO_DDR].path = NULL;
|
||||
else
|
||||
return dev_err_probe(se->dev, PTR_ERR(icc_paths[GENI_TO_DDR].path),
|
||||
"Failed to get 'qup-memory' ICC path\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
err = PTR_ERR(se->icc_paths[i].path);
|
||||
if (err != -EPROBE_DEFER)
|
||||
dev_err_ratelimited(se->dev, "Failed to get ICC path '%s': %d\n",
|
||||
icc_names[i], err);
|
||||
return err;
|
||||
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(geni_icc_get);
|
||||
|
||||
@@ -944,6 +966,28 @@ int geni_icc_set_bw(struct geni_se *se)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(geni_icc_set_bw);
|
||||
|
||||
/**
|
||||
* geni_icc_set_bw_ab() - Set average bandwidth for all ICC paths and apply
|
||||
* @se: Pointer to the concerned serial engine.
|
||||
* @core_ab: Average bandwidth in kBps for GENI_TO_CORE path.
|
||||
* @cfg_ab: Average bandwidth in kBps for CPU_TO_GENI path.
|
||||
* @ddr_ab: Average bandwidth in kBps for GENI_TO_DDR path.
|
||||
*
|
||||
* Sets bandwidth values for all ICC paths and applies them. DDR path is
|
||||
* optional and only set if it exists.
|
||||
*
|
||||
* Return: 0 on success, negative error code on failure.
|
||||
*/
|
||||
int geni_icc_set_bw_ab(struct geni_se *se, u32 core_ab, u32 cfg_ab, u32 ddr_ab)
|
||||
{
|
||||
se->icc_paths[GENI_TO_CORE].avg_bw = core_ab;
|
||||
se->icc_paths[CPU_TO_GENI].avg_bw = cfg_ab;
|
||||
se->icc_paths[GENI_TO_DDR].avg_bw = ddr_ab;
|
||||
|
||||
return geni_icc_set_bw(se);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(geni_icc_set_bw_ab);
|
||||
|
||||
void geni_icc_set_tag(struct geni_se *se, u32 tag)
|
||||
{
|
||||
int i;
|
||||
@@ -988,6 +1032,196 @@ int geni_icc_disable(struct geni_se *se)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(geni_icc_disable);
|
||||
|
||||
/**
|
||||
* geni_se_resources_deactivate() - Deactivate GENI SE device resources
|
||||
* @se: Pointer to the geni_se structure
|
||||
*
|
||||
* Deactivates device resources for power saving: OPP rate to 0, pin control
|
||||
* to sleep state, turns off clocks, and disables interconnect. Skips ACPI devices.
|
||||
*
|
||||
* Return: 0 on success, negative error code on failure
|
||||
*/
|
||||
int geni_se_resources_deactivate(struct geni_se *se)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (has_acpi_companion(se->dev))
|
||||
return 0;
|
||||
|
||||
if (se->has_opp)
|
||||
dev_pm_opp_set_rate(se->dev, 0);
|
||||
|
||||
ret = pinctrl_pm_select_sleep_state(se->dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
geni_se_clks_off(se);
|
||||
|
||||
return geni_icc_disable(se);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(geni_se_resources_deactivate);
|
||||
|
||||
/**
|
||||
* geni_se_resources_activate() - Activate GENI SE device resources
|
||||
* @se: Pointer to the geni_se structure
|
||||
*
|
||||
* Activates device resources for operation: enables interconnect, prepares clocks,
|
||||
* and sets pin control to default state. Includes error cleanup. Skips ACPI devices.
|
||||
*
|
||||
* Unlike geni_se_resources_deactivate(), this function doesn't alter the
|
||||
* connected genpds' performance states, which must be additionally handled.
|
||||
*
|
||||
* Return: 0 on success, negative error code on failure
|
||||
*/
|
||||
int geni_se_resources_activate(struct geni_se *se)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (has_acpi_companion(se->dev))
|
||||
return 0;
|
||||
|
||||
ret = geni_icc_enable(se);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = geni_se_clks_on(se);
|
||||
if (ret)
|
||||
goto out_icc_disable;
|
||||
|
||||
ret = pinctrl_pm_select_default_state(se->dev);
|
||||
if (ret) {
|
||||
geni_se_clks_off(se);
|
||||
goto out_icc_disable;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
out_icc_disable:
|
||||
geni_icc_disable(se);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(geni_se_resources_activate);
|
||||
|
||||
/**
|
||||
* geni_se_set_perf_level() - Set performance level for GENI SE.
|
||||
* @se: Pointer to the struct geni_se instance.
|
||||
* @level: The desired performance level.
|
||||
*
|
||||
* Sets the performance level by directly calling dev_pm_opp_set_level
|
||||
* on the performance device associated with the SE.
|
||||
*
|
||||
* Return: 0 on success, or a negative error code on failure.
|
||||
*/
|
||||
int geni_se_set_perf_level(struct geni_se *se, unsigned long level)
|
||||
{
|
||||
return dev_pm_opp_set_level(se->pd_list->pd_devs[DOMAIN_IDX_PERF], level);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(geni_se_set_perf_level);
|
||||
|
||||
/**
|
||||
* geni_se_set_perf_opp() - Set performance OPP for GENI SE by frequency.
|
||||
* @se: Pointer to the struct geni_se instance.
|
||||
* @clk_freq: The requested clock frequency.
|
||||
*
|
||||
* Finds the nearest operating performance point (OPP) for the given
|
||||
* clock frequency and applies it to the SE's performance device.
|
||||
*
|
||||
* Return: 0 on success, or a negative error code on failure.
|
||||
*/
|
||||
int geni_se_set_perf_opp(struct geni_se *se, unsigned long clk_freq)
|
||||
{
|
||||
struct device *perf_dev = se->pd_list->pd_devs[DOMAIN_IDX_PERF];
|
||||
struct dev_pm_opp *opp;
|
||||
int ret;
|
||||
|
||||
opp = dev_pm_opp_find_freq_floor(perf_dev, &clk_freq);
|
||||
if (IS_ERR(opp)) {
|
||||
dev_err(se->dev, "failed to find opp for freq %lu\n", clk_freq);
|
||||
return PTR_ERR(opp);
|
||||
}
|
||||
|
||||
ret = dev_pm_opp_set_opp(perf_dev, opp);
|
||||
dev_pm_opp_put(opp);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(geni_se_set_perf_opp);
|
||||
|
||||
/**
|
||||
* geni_se_domain_attach() - Attach power domains to a GENI SE device.
|
||||
* @se: Pointer to the geni_se structure representing the GENI SE device.
|
||||
*
|
||||
* This function attaches the power domains ("power" and "perf") required
|
||||
* in the SCMI auto-VM environment to the GENI Serial Engine device. It
|
||||
* initializes se->pd_list with the attached domains.
|
||||
*
|
||||
* Return: 0 on success, or a negative error code on failure.
|
||||
*/
|
||||
int geni_se_domain_attach(struct geni_se *se)
|
||||
{
|
||||
struct dev_pm_domain_attach_data pd_data = {
|
||||
.pd_flags = PD_FLAG_DEV_LINK_ON,
|
||||
.pd_names = (const char*[]) { "power", "perf" },
|
||||
.num_pd_names = 2,
|
||||
};
|
||||
int ret;
|
||||
|
||||
ret = devm_pm_domain_attach_list(se->dev,
|
||||
&pd_data, &se->pd_list);
|
||||
if (ret == 0)
|
||||
return -ENODEV;
|
||||
else if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(geni_se_domain_attach);
|
||||
|
||||
/**
|
||||
* geni_se_resources_init() - Initialize resources for a GENI SE device.
|
||||
* @se: Pointer to the geni_se structure representing the GENI SE device.
|
||||
*
|
||||
* This function initializes various resources required by the GENI Serial Engine
|
||||
* (SE) device, including clock resources (core and SE clocks), interconnect
|
||||
* paths for communication.
|
||||
* It retrieves optional and mandatory clock resources, adds an OF-based
|
||||
* operating performance point (OPP) table, and sets up interconnect paths
|
||||
* with default bandwidths. The function also sets a flag (`has_opp`) to
|
||||
* indicate whether OPP support is available for the device.
|
||||
*
|
||||
* Return: 0 on success, or a negative errno on failure.
|
||||
*/
|
||||
int geni_se_resources_init(struct geni_se *se)
|
||||
{
|
||||
int ret;
|
||||
|
||||
se->core_clk = devm_clk_get_optional(se->dev, "core");
|
||||
if (IS_ERR(se->core_clk))
|
||||
return dev_err_probe(se->dev, PTR_ERR(se->core_clk),
|
||||
"Failed to get optional core clk\n");
|
||||
|
||||
se->clk = devm_clk_get(se->dev, "se");
|
||||
if (IS_ERR(se->clk) && !has_acpi_companion(se->dev))
|
||||
return dev_err_probe(se->dev, PTR_ERR(se->clk),
|
||||
"Failed to get SE clk\n");
|
||||
|
||||
ret = devm_pm_opp_set_clkname(se->dev, "se");
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = devm_pm_opp_of_add_table(se->dev);
|
||||
if (ret && ret != -ENODEV)
|
||||
return dev_err_probe(se->dev, ret, "Failed to add OPP table\n");
|
||||
|
||||
se->has_opp = (ret == 0);
|
||||
|
||||
ret = geni_icc_get(se, "qup-memory");
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return geni_icc_set_bw_ab(se, GENI_DEFAULT_BW, GENI_DEFAULT_BW, GENI_DEFAULT_BW);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(geni_se_resources_init);
|
||||
|
||||
/**
|
||||
* geni_find_protocol_fw() - Locate and validate SE firmware for a protocol.
|
||||
* @dev: Pointer to the device structure.
|
||||
|
||||
@@ -60,18 +60,24 @@ struct geni_icc_path {
|
||||
* @dev: Pointer to the Serial Engine device
|
||||
* @wrapper: Pointer to the parent QUP Wrapper core
|
||||
* @clk: Handle to the core serial engine clock
|
||||
* @core_clk: Auxiliary clock, which may be required by a protocol
|
||||
* @num_clk_levels: Number of valid clock levels in clk_perf_tbl
|
||||
* @clk_perf_tbl: Table of clock frequency input to serial engine clock
|
||||
* @icc_paths: Array of ICC paths for SE
|
||||
* @pd_list: Power domain list for managing power domains
|
||||
* @has_opp: Indicates if OPP is supported
|
||||
*/
|
||||
struct geni_se {
|
||||
void __iomem *base;
|
||||
struct device *dev;
|
||||
struct geni_wrapper *wrapper;
|
||||
struct clk *clk;
|
||||
struct clk *core_clk;
|
||||
unsigned int num_clk_levels;
|
||||
unsigned long *clk_perf_tbl;
|
||||
struct geni_icc_path icc_paths[3];
|
||||
struct dev_pm_domain_list *pd_list;
|
||||
bool has_opp;
|
||||
};
|
||||
|
||||
/* Common SE registers */
|
||||
@@ -528,12 +534,25 @@ void geni_se_rx_dma_unprep(struct geni_se *se, dma_addr_t iova, size_t len);
|
||||
int geni_icc_get(struct geni_se *se, const char *icc_ddr);
|
||||
|
||||
int geni_icc_set_bw(struct geni_se *se);
|
||||
int geni_icc_set_bw_ab(struct geni_se *se, u32 core_ab, u32 cfg_ab, u32 ddr_ab);
|
||||
void geni_icc_set_tag(struct geni_se *se, u32 tag);
|
||||
|
||||
int geni_icc_enable(struct geni_se *se);
|
||||
|
||||
int geni_icc_disable(struct geni_se *se);
|
||||
|
||||
int geni_se_resources_init(struct geni_se *se);
|
||||
|
||||
int geni_se_resources_activate(struct geni_se *se);
|
||||
|
||||
int geni_se_resources_deactivate(struct geni_se *se);
|
||||
|
||||
int geni_load_se_firmware(struct geni_se *se, enum geni_se_protocol_type protocol);
|
||||
|
||||
int geni_se_domain_attach(struct geni_se *se);
|
||||
|
||||
int geni_se_set_perf_level(struct geni_se *se, unsigned long level);
|
||||
|
||||
int geni_se_set_perf_opp(struct geni_se *se, unsigned long clk_freq);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user