Skip to content
Open
275 changes: 257 additions & 18 deletions drivers/soc/qcom/qcom-geni-se.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
/* Disable MMIO tracing to prevent excessive logging of unwanted MMIO traces */
#define __DISABLE_TRACE_MMIO__

#define CREATE_TRACE_POINTS
#include <trace/events/qcom_geni_se.h>

EXPORT_TRACEPOINT_SYMBOL_GPL(geni_se_regs);

#include <linux/acpi.h>
#include <linux/bitfield.h>
#include <linux/clk.h>
Expand All @@ -19,6 +24,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>

/**
Expand Down Expand Up @@ -280,6 +287,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.
Expand Down Expand Up @@ -582,6 +595,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);
}

/**
Expand Down Expand Up @@ -625,7 +639,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;
}

Expand Down Expand Up @@ -906,30 +931,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;

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[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");

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);

Expand All @@ -951,6 +978,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;
Expand Down Expand Up @@ -995,6 +1044,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.
Expand Down
Loading