Cc: Prasad Malisetty <pmaliset@xxxxxxxxxxxxxx>
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@xxxxxxxxxx>
---
drivers/clk/qcom/gdsc.c | 41 +++++++++++++++++++++++++++++++++++++++++
drivers/clk/qcom/gdsc.h | 14 ++++++++++++++
2 files changed, 55 insertions(+)
diff --git a/drivers/clk/qcom/gdsc.c b/drivers/clk/qcom/gdsc.c
index 7e1dd8ccfa38..9913d1b70947 100644
--- a/drivers/clk/qcom/gdsc.c
+++ b/drivers/clk/qcom/gdsc.c
@@ -45,6 +45,7 @@
#define TIMEOUT_US 500
#define domain_to_gdsc(domain) container_of(domain, struct gdsc, pd)
+#define domain_to_pipe_clk_gdsc(domain) container_of(domain, struct pipe_clk_gdsc, base.pd)
enum gdsc_status {
GDSC_OFF,
@@ -549,3 +550,43 @@ int gdsc_gx_do_nothing_enable(struct generic_pm_domain *domain)
return 0;
}
EXPORT_SYMBOL_GPL(gdsc_gx_do_nothing_enable);
+
+/*
+ * Special operations for GDSCs with attached pipe clocks.
+ * The clock should be parked to safe source (tcxo) before turning off the GDSC
+ * and can be switched on as soon as the GDSC is on.
+ *
+ * We remove respective clock sources from clocks map and handle them manually.
+ */
+int gdsc_pipe_enable(struct generic_pm_domain *domain)
+{
+ struct pipe_clk_gdsc *sc = domain_to_pipe_clk_gdsc(domain);
+ int i, ret;
+
+ ret = gdsc_enable(domain);
+ if (ret)
+ return ret;
+
+ for (i = 0; i< sc->num_clocks; i++)
+ regmap_update_bits(sc->base.regmap, sc->clocks[i].reg,
+ BIT(sc->clocks[i].shift + sc->clocks[i].width) - BIT(sc->clocks[i].shift),
+ sc->clocks[i].on_value << sc->clocks[i].shift);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(gdsc_pipe_enable);
+
+int gdsc_pipe_disable(struct generic_pm_domain *domain)
+{
+ struct pipe_clk_gdsc *sc = domain_to_pipe_clk_gdsc(domain);
+ int i;
+
+ for (i = sc->num_clocks - 1; i >= 0; i--)
+ regmap_update_bits(sc->base.regmap, sc->clocks[i].reg,
+ BIT(sc->clocks[i].shift + sc->clocks[i].width) - BIT(sc->clocks[i].shift),
+ sc->clocks[i].off_value << sc->clocks[i].shift);
+
+ /* In case of an error do not try turning the clocks again. We can not be sure about the GDSC state. */
+ return gdsc_disable(domain);
+}
+EXPORT_SYMBOL_GPL(gdsc_pipe_disable);
diff --git a/drivers/clk/qcom/gdsc.h b/drivers/clk/qcom/gdsc.h
index d7cc4c21a9d4..b1a2f0abe41c 100644
--- a/drivers/clk/qcom/gdsc.h
+++ b/drivers/clk/qcom/gdsc.h
@@ -68,11 +68,25 @@ struct gdsc_desc {
size_t num;
};
+struct pipe_clk_gdsc {
+ struct gdsc base;
+ int num_clocks;
+ struct {
+ u32 reg;
+ u32 shift;
+ u32 width;
+ u32 off_value;
+ u32 on_value;
+ } clocks[];
+};
+
#ifdef CONFIG_QCOM_GDSC
int gdsc_register(struct gdsc_desc *desc, struct reset_controller_dev *,
struct regmap *);
void gdsc_unregister(struct gdsc_desc *desc);
int gdsc_gx_do_nothing_enable(struct generic_pm_domain *domain);
+int gdsc_pipe_enable(struct generic_pm_domain *domain);
+int gdsc_pipe_disable(struct generic_pm_domain *domain);
#else
static inline int gdsc_register(struct gdsc_desc *desc,
struct reset_controller_dev *rcdev,
--
2.34.1