1616#include <linux/of.h>
1717#include <linux/of_platform.h>
1818#include <linux/platform_device.h>
19+ #include <linux/pm_opp.h>
1920
2021#include <linux/firmware/qcom/qcom_scm.h>
2122
@@ -112,6 +113,8 @@ struct qcom_ice {
112113 bool use_hwkm ;
113114 bool hwkm_init_complete ;
114115 u8 hwkm_version ;
116+ unsigned long core_clk_freq ;
117+ bool has_opp ;
115118};
116119
117120static bool qcom_ice_check_supported (struct qcom_ice * ice )
@@ -311,6 +314,10 @@ int qcom_ice_resume(struct qcom_ice *ice)
311314 struct device * dev = ice -> dev ;
312315 int err ;
313316
317+ /* Restore the ICE core clk freq */
318+ if (ice -> has_opp && ice -> core_clk_freq )
319+ dev_pm_opp_set_rate (ice -> dev , ice -> core_clk_freq );
320+
314321 err = clk_prepare_enable (ice -> core_clk );
315322 if (err ) {
316323 dev_err (dev , "Failed to enable core clock: %d\n" , err );
@@ -331,6 +338,11 @@ int qcom_ice_suspend(struct qcom_ice *ice)
331338{
332339 clk_disable_unprepare (ice -> iface_clk );
333340 clk_disable_unprepare (ice -> core_clk );
341+
342+ /* Drop the clock votes while suspend */
343+ if (ice -> has_opp )
344+ dev_pm_opp_set_rate (ice -> dev , 0 );
345+
334346 ice -> hwkm_init_complete = false;
335347
336348 return 0 ;
@@ -556,6 +568,51 @@ int qcom_ice_import_key(struct qcom_ice *ice,
556568}
557569EXPORT_SYMBOL_GPL (qcom_ice_import_key );
558570
571+ /**
572+ * qcom_ice_scale_clk() - Scale ICE clock for DVFS-aware operations
573+ * @ice: ICE driver data
574+ * @target_freq: requested frequency in Hz
575+ * @round_ceil: when true, selects nearest freq >= @target_freq;
576+ * otherwise, selects nearest freq <= @target_freq
577+ *
578+ * Selects an OPP frequency based on @target_freq and the rounding direction
579+ * specified by @round_ceil, then programs it using dev_pm_opp_set_rate(),
580+ * including any voltage or power-domain transitions handled by the OPP
581+ * framework. Updates ice->core_clk_freq on success.
582+ *
583+ * Return: 0 on success; -EOPNOTSUPP if no OPP table; or error from
584+ * dev_pm_opp_set_rate()/OPP lookup.
585+ */
586+ int qcom_ice_scale_clk (struct qcom_ice * ice , unsigned long target_freq ,
587+ bool round_ceil )
588+ {
589+ unsigned long ice_freq = target_freq ;
590+ struct dev_pm_opp * opp ;
591+ int ret ;
592+
593+ if (!ice -> has_opp )
594+ return - EOPNOTSUPP ;
595+
596+ if (round_ceil )
597+ opp = dev_pm_opp_find_freq_ceil (ice -> dev , & ice_freq );
598+ else
599+ opp = dev_pm_opp_find_freq_floor (ice -> dev , & ice_freq );
600+
601+ if (IS_ERR (opp ))
602+ return PTR_ERR (opp );
603+ dev_pm_opp_put (opp );
604+
605+ ret = dev_pm_opp_set_rate (ice -> dev , ice_freq );
606+ if (ret ) {
607+ dev_err (ice -> dev , "Unable to scale ICE clock rate\n" );
608+ return ret ;
609+ }
610+ ice -> core_clk_freq = ice_freq ;
611+
612+ return ret ;
613+ }
614+ EXPORT_SYMBOL_GPL (qcom_ice_scale_clk );
615+
559616static struct qcom_ice * qcom_ice_create (struct device * dev ,
560617 void __iomem * base )
561618{
@@ -731,6 +788,7 @@ static int qcom_ice_probe(struct platform_device *pdev)
731788{
732789 struct qcom_ice * engine ;
733790 void __iomem * base ;
791+ int err ;
734792
735793 base = devm_platform_ioremap_resource (pdev , 0 );
736794 if (IS_ERR (base )) {
@@ -742,6 +800,40 @@ static int qcom_ice_probe(struct platform_device *pdev)
742800 if (IS_ERR (engine ))
743801 return PTR_ERR (engine );
744802
803+ /* qcom_ice_create() may return NULL if scm calls are not available */
804+ if (!engine )
805+ return - EOPNOTSUPP ;
806+
807+ err = devm_pm_opp_set_clkname (& pdev -> dev , "core" );
808+ if (err && err != - ENOENT ) {
809+ dev_err (& pdev -> dev , "Unable to set core clkname to OPP-table\n" );
810+ return err ;
811+ }
812+
813+ /* OPP table is optional */
814+ err = devm_pm_opp_of_add_table (& pdev -> dev );
815+ if (err && err != - ENODEV ) {
816+ dev_err (& pdev -> dev , "Invalid OPP table in Device tree\n" );
817+ return err ;
818+ }
819+
820+ /*
821+ * The OPP table is optional. devm_pm_opp_of_add_table() returns
822+ * -ENODEV when no OPP table is present in DT, which is not treated
823+ * as an error. Therefore, track successful OPP registration only
824+ * when the return value is 0.
825+ */
826+ engine -> has_opp = (err == 0 );
827+ if (!engine -> has_opp )
828+ dev_info (& pdev -> dev , "ICE OPP table is not registered, please update your DT\n" );
829+
830+ /*
831+ * Store the core clock rate for suspend resume cycles,
832+ * against OPP aware DVFS operations. core_clk_freq will
833+ * have a valid value only for non-legacy bindings.
834+ */
835+ engine -> core_clk_freq = clk_get_rate (engine -> core_clk );
836+
745837 platform_set_drvdata (pdev , engine );
746838
747839 return 0 ;
0 commit comments