diff mbox

[v3,1/6] clk: qcom: Add support for GDSCs

Message ID 1426832483-27026-2-git-send-email-rnayak@codeaurora.org (mailing list archive)
State Superseded, archived
Headers show

Commit Message

Rajendra Nayak March 20, 2015, 6:21 a.m. UTC
From: Stephen Boyd <sboyd@codeaurora.org>

GDSCs (Global Distributed Switch Controllers) are responsible for
safely collapsing and restoring power to peripherals in the SoC.
These are best modelled as power domains using genpd and given
the registers are scattered throughout the clock controller register
space, its best to have the support added through the clock driver.

Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
Signed-off-by: Rajendra Nayak <rnayak@codeaurora.org>
---
 drivers/clk/qcom/Kconfig  |   5 ++
 drivers/clk/qcom/Makefile |   1 +
 drivers/clk/qcom/gdsc.c   | 166 ++++++++++++++++++++++++++++++++++++++++++++++
 drivers/clk/qcom/gdsc.h   |  46 +++++++++++++
 4 files changed, 218 insertions(+)
 create mode 100644 drivers/clk/qcom/gdsc.c
 create mode 100644 drivers/clk/qcom/gdsc.h

Comments

Srinivas Kandagatla March 20, 2015, 7:38 a.m. UTC | #1
Minor Nit picks,

On 20/03/15 06:21, Rajendra Nayak wrote:
> From: Stephen Boyd <sboyd@codeaurora.org>
>
> GDSCs (Global Distributed Switch Controllers) are responsible for
> safely collapsing and restoring power to peripherals in the SoC.
> These are best modelled as power domains using genpd and given
> the registers are scattered throughout the clock controller register
> space, its best to have the support added through the clock driver.
>
> Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
> Signed-off-by: Rajendra Nayak <rnayak@codeaurora.org>
> ---
>   drivers/clk/qcom/Kconfig  |   5 ++
>   drivers/clk/qcom/Makefile |   1 +
>   drivers/clk/qcom/gdsc.c   | 166 ++++++++++++++++++++++++++++++++++++++++++++++
>   drivers/clk/qcom/gdsc.h   |  46 +++++++++++++
>   4 files changed, 218 insertions(+)
>   create mode 100644 drivers/clk/qcom/gdsc.c
>   create mode 100644 drivers/clk/qcom/gdsc.h
>
...
> +
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/export.h>
?? Do you need this?

> +#include <linux/jiffies.h>
> +#include <linux/regmap.h>
> +#include <linux/slab.h>
> +#include "gdsc.h"
> +
> +#define PWR_ON_MASK		BIT(31)
> +#define EN_REST_WAIT_MASK	GENMASK(23, 20)
> +#define EN_FEW_WAIT_MASK	GENMASK(19, 16)
> +#define CLK_DIS_WAIT_MASK	GENMASK(15, 12)
> +#define SW_OVERRIDE_MASK	BIT(2)
> +#define HW_CONTROL_MASK		BIT(1)
> +#define SW_COLLAPSE_MASK	BIT(0)
> +
> +/* Wait 2^n CXO cycles between all states. Here, n=2 (4 cycles). */
> +#define EN_REST_WAIT_VAL	(0x2 << 20)
> +#define EN_FEW_WAIT_VAL		(0x8 << 16)
> +#define CLK_DIS_WAIT_VAL	(0x2 << 12)
> +
> +#define TIMEOUT_US		100
> +
> +static int gdsc_is_enabled(struct gdsc *sc)
> +{
> +	u32 val;
> +	int ret;
> +
> +	ret = regmap_read(sc->regmap, sc->gdscr, &val);
> +	if (ret)
> +		return ret;
Line after this would be good.
> +	return !!(val & PWR_ON_MASK);
> +}
> +
> +static int gdsc_toggle_logic(struct gdsc *sc, bool en)
> +{
> +	int ret;
> +	u32 val = en ? 0 : SW_COLLAPSE_MASK;
> +	u32 check = en ? PWR_ON_MASK : 0;
> +	unsigned long timeout;
> +
> +	ret = regmap_update_bits(sc->regmap, sc->gdscr, SW_COLLAPSE_MASK, val);
> +	if (ret)
> +		return ret;
> +
> +	timeout = jiffies + usecs_to_jiffies(TIMEOUT_US);
> +	do {
> +		ret = regmap_read(sc->regmap, sc->gdscr, &val);
> +		if (ret)
> +			return ret;
> +		if ((val & PWR_ON_MASK) == check)
> +			return 0;
> +	} while (time_before(jiffies, timeout));
> +
> +	ret =  regmap_read(sc->regmap, sc->gdscr, &val);
> +	if (ret)
> +		return ret;
New line here.
> +	if ((val & PWR_ON_MASK) == check)
> +		return 0;
> +
> +	return -ETIMEDOUT;
> +}
> +
...

> +
> +int gdsc_register(struct device *dev, struct gdsc **scs, size_t num,
> +		  struct regmap *regmap)
> +{
> +	int i, ret;
> +	struct genpd_onecell_data *data;
> +
> +	if (!num || !scs || !dev || !dev->of_node)
> +		return 0;
Should it not return -EINVAL here?

> +
> +	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
> +	if (!data)
> +		return -ENOMEM;
> +
> +	data->domains = devm_kzalloc(dev, sizeof(*data->domains) * num,
> +				     GFP_KERNEL);
> +	if (!data->domains)
> +		return -ENOMEM;
> +
> +	data->num_domains = num;
> +	for (i = 0; i < num; i++) {
> +		if (!scs[i])
> +			continue;
> +		scs[i]->regmap = regmap;
> +		ret = gdsc_init(scs[i]);
> +		if (ret)
> +			return ret;
> +		data->domains[i] = &scs[i]->pd;
> +	}
New line?
> +	return of_genpd_add_provider_onecell(dev->of_node, data);
> +}
> +
> +void gdsc_unregister(struct device *dev)
> +{
> +	of_genpd_del_provider(dev->of_node);
> +}
> diff --git a/drivers/clk/qcom/gdsc.h b/drivers/clk/qcom/gdsc.h
> new file mode 100644
> index 0000000..02e2990
> --- /dev/null
> +++ b/drivers/clk/qcom/gdsc.h
> @@ -0,0 +1,46 @@
> +/*
> + * Copyright (c) 2015, 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.
> + */
> +
> +#ifndef __QCOM_GDSC_H__
> +#define __QCOM_GDSC_H__
> +
> +#include <linux/pm_domain.h>
> +
> +struct regmap;
> +
> +/**
> + * struct gdsc - Globally Distributed Switch Controller
> + * @pd: generic power domain
> + * @regmap: regmap for MMIO accesses
> + * @gdscr: gsdc control register
> + */
> +struct gdsc {
> +	struct generic_pm_domain	pd;
> +	struct regmap			*regmap;
> +	unsigned int			gdscr;
> +};
> +
> +#define domain_to_gdsc(domain) container_of(domain, struct gdsc, pd)
> +
> +#ifdef CONFIG_QCOM_GDSC
> +int gdsc_register(struct device *, struct gdsc **, size_t n, struct regmap *);
> +void gdsc_unregister(struct device *);
> +#else
> +int gdsc_register(struct device *d, struct gdsc **g, size_t n, struct regmap *r)

static inline here??? else it would result in muliple definitions?

> +{
> +	return 0;
return -ENOSYS; I
> +}
> +
> +void gdsc_unregister(struct device *d) {};
same static inline...
Braces should be in next line too.
> +#endif /* CONFIG_QCOM_GDSC */
> +#endif /* __QCOM_GDSC_H__ */
>


--srini
--
To unsubscribe from this list: send the line "unsubscribe linux-arm-msm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Rajendra Nayak March 20, 2015, 9:51 a.m. UTC | #2
> ...
>> +
>> +#include <linux/delay.h>
>> +#include <linux/err.h>
>> +#include <linux/export.h>
> ?? Do you need this?

Maybe not, now that the EXPORT_SYMBOL() is gone.
I will remove it.

>
>> +#include <linux/jiffies.h>
>> +#include <linux/regmap.h>
>> +#include <linux/slab.h>
>> +#include "gdsc.h"
>> +
>> +#define PWR_ON_MASK        BIT(31)
>> +#define EN_REST_WAIT_MASK    GENMASK(23, 20)
>> +#define EN_FEW_WAIT_MASK    GENMASK(19, 16)
>> +#define CLK_DIS_WAIT_MASK    GENMASK(15, 12)
>> +#define SW_OVERRIDE_MASK    BIT(2)
>> +#define HW_CONTROL_MASK        BIT(1)
>> +#define SW_COLLAPSE_MASK    BIT(0)
>> +
>> +/* Wait 2^n CXO cycles between all states. Here, n=2 (4 cycles). */
>> +#define EN_REST_WAIT_VAL    (0x2 << 20)
>> +#define EN_FEW_WAIT_VAL        (0x8 << 16)
>> +#define CLK_DIS_WAIT_VAL    (0x2 << 12)
>> +
>> +#define TIMEOUT_US        100
>> +
>> +static int gdsc_is_enabled(struct gdsc *sc)
>> +{
>> +    u32 val;
>> +    int ret;
>> +
>> +    ret = regmap_read(sc->regmap, sc->gdscr, &val);
>> +    if (ret)
>> +        return ret;
> Line after this would be good.
>> +    return !!(val & PWR_ON_MASK);
>> +}
>> +
>> +static int gdsc_toggle_logic(struct gdsc *sc, bool en)
>> +{
>> +    int ret;
>> +    u32 val = en ? 0 : SW_COLLAPSE_MASK;
>> +    u32 check = en ? PWR_ON_MASK : 0;
>> +    unsigned long timeout;
>> +
>> +    ret = regmap_update_bits(sc->regmap, sc->gdscr, SW_COLLAPSE_MASK,
>> val);
>> +    if (ret)
>> +        return ret;
>> +
>> +    timeout = jiffies + usecs_to_jiffies(TIMEOUT_US);
>> +    do {
>> +        ret = regmap_read(sc->regmap, sc->gdscr, &val);
>> +        if (ret)
>> +            return ret;
>> +        if ((val & PWR_ON_MASK) == check)
>> +            return 0;
>> +    } while (time_before(jiffies, timeout));
>> +
>> +    ret =  regmap_read(sc->regmap, sc->gdscr, &val);
>> +    if (ret)
>> +        return ret;
> New line here.
>> +    if ((val & PWR_ON_MASK) == check)
>> +        return 0;
>> +
>> +    return -ETIMEDOUT;
>> +}
>> +
> ...
>
>> +
>> +int gdsc_register(struct device *dev, struct gdsc **scs, size_t num,
>> +          struct regmap *regmap)
>> +{
>> +    int i, ret;
>> +    struct genpd_onecell_data *data;
>> +
>> +    if (!num || !scs || !dev || !dev->of_node)
>> +        return 0;
> Should it not return -EINVAL here?

so we have gdsc_register() getting called from qcom_cc_really_probe()
and we might have devices with no gdscs as part of gcc. So the way to 
identify those is the num and scs aren't really initialized.
I should probably move this check into the common clk probe function
and not call gdsc_register() if these are not inited, and return a
-EINVAL for (!dev || !dev->of_node) here.

>
>> +
>> +    data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
>> +    if (!data)
>> +        return -ENOMEM;
>> +
>> +    data->domains = devm_kzalloc(dev, sizeof(*data->domains) * num,
>> +                     GFP_KERNEL);
>> +    if (!data->domains)
>> +        return -ENOMEM;
>> +
>> +    data->num_domains = num;
>> +    for (i = 0; i < num; i++) {
>> +        if (!scs[i])
>> +            continue;
>> +        scs[i]->regmap = regmap;
>> +        ret = gdsc_init(scs[i]);
>> +        if (ret)
>> +            return ret;
>> +        data->domains[i] = &scs[i]->pd;
>> +    }
> New line?
>> +    return of_genpd_add_provider_onecell(dev->of_node, data);
>> +}
>> +
>> +void gdsc_unregister(struct device *dev)
>> +{
>> +    of_genpd_del_provider(dev->of_node);
>> +}
>> diff --git a/drivers/clk/qcom/gdsc.h b/drivers/clk/qcom/gdsc.h
>> new file mode 100644
>> index 0000000..02e2990
>> --- /dev/null
>> +++ b/drivers/clk/qcom/gdsc.h
>> @@ -0,0 +1,46 @@
>> +/*
>> + * Copyright (c) 2015, 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.
>> + */
>> +
>> +#ifndef __QCOM_GDSC_H__
>> +#define __QCOM_GDSC_H__
>> +
>> +#include <linux/pm_domain.h>
>> +
>> +struct regmap;
>> +
>> +/**
>> + * struct gdsc - Globally Distributed Switch Controller
>> + * @pd: generic power domain
>> + * @regmap: regmap for MMIO accesses
>> + * @gdscr: gsdc control register
>> + */
>> +struct gdsc {
>> +    struct generic_pm_domain    pd;
>> +    struct regmap            *regmap;
>> +    unsigned int            gdscr;
>> +};
>> +
>> +#define domain_to_gdsc(domain) container_of(domain, struct gdsc, pd)
>> +
>> +#ifdef CONFIG_QCOM_GDSC
>> +int gdsc_register(struct device *, struct gdsc **, size_t n, struct
>> regmap *);
>> +void gdsc_unregister(struct device *);
>> +#else
>> +int gdsc_register(struct device *d, struct gdsc **g, size_t n, struct
>> regmap *r)
>
> static inline here??? else it would result in muliple definitions?

It shouldn't result in mutiple defs, but static inline would be good.
I will fix it.

>
>> +{
>> +    return 0;
> return -ENOSYS;

This should work once I put checks for non inited desc->num_gdscs as
part of  qcom_cc_really_probe().

Thanks for the review. I'll post a v4 with the changes.
regards,
Rajendra

>> +}
>> +
>> +void gdsc_unregister(struct device *d) {};
> same static inline...
> Braces should be in next line too.
>> +#endif /* CONFIG_QCOM_GDSC */
>> +#endif /* __QCOM_GDSC_H__ */
>>
>
>
> --srini
Rajendra Nayak March 20, 2015, 10:01 a.m. UTC | #3
[]...
>>> @@ -0,0 +1,46 @@
>>> +/*
>>> + * Copyright (c) 2015, 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.
>>> + */
>>> +
>>> +#ifndef __QCOM_GDSC_H__
>>> +#define __QCOM_GDSC_H__
>>> +
>>> +#include <linux/pm_domain.h>
>>> +
>>> +struct regmap;

I realized, after Srini pointed out on irc that this fwd declaration
is a complete waste. I'll remove it when I do a repost.

>>> +
>>> +/**
>>> + * struct gdsc - Globally Distributed Switch Controller
>>> + * @pd: generic power domain
>>> + * @regmap: regmap for MMIO accesses
>>> + * @gdscr: gsdc control register
>>> + */
>>> +struct gdsc {
>>> +    struct generic_pm_domain    pd;
>>> +    struct regmap            *regmap;
>>> +    unsigned int            gdscr;
>>> +};
>>> +
>>> +#define domain_to_gdsc(domain) container_of(domain, struct gdsc, pd)
>>> +
>>> +#ifdef CONFIG_QCOM_GDSC
>>> +int gdsc_register(struct device *, struct gdsc **, size_t n, struct
>>> regmap *);
>>> +void gdsc_unregister(struct device *);
>>> +#else
>>> +int gdsc_register(struct device *d, struct gdsc **g, size_t n, struct
>>> regmap *r)
--
To unsubscribe from this list: send the line "unsubscribe linux-arm-msm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Stephen Boyd March 23, 2015, 11:23 p.m. UTC | #4
On 03/20, Rajendra Nayak wrote:
> []...
> >>>@@ -0,0 +1,46 @@
> >>>+/*
> >>>+ * Copyright (c) 2015, 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.
> >>>+ */
> >>>+
> >>>+#ifndef __QCOM_GDSC_H__
> >>>+#define __QCOM_GDSC_H__
> >>>+
> >>>+#include <linux/pm_domain.h>
> >>>+
> >>>+struct regmap;
> 
> I realized, after Srini pointed out on irc that this fwd declaration
> is a complete waste. I'll remove it when I do a repost.
> 

Why? It's there so that struct regmap is declared instead of
being declared inside struct gdsc.

> >>>+
> >>>+/**
> >>>+ * struct gdsc - Globally Distributed Switch Controller
> >>>+ * @pd: generic power domain
> >>>+ * @regmap: regmap for MMIO accesses
> >>>+ * @gdscr: gsdc control register
> >>>+ */
> >>>+struct gdsc {
> >>>+    struct generic_pm_domain    pd;
> >>>+    struct regmap            *regmap;
> >>>+    unsigned int            gdscr;
> >>>+};
> >>>+
> >>>+#define domain_to_gdsc(domain) container_of(domain, struct gdsc, pd)
> >>>+
> >>>+#ifdef CONFIG_QCOM_GDSC
> >>>+int gdsc_register(struct device *, struct gdsc **, size_t n, struct
> >>>regmap *);
> >>>+void gdsc_unregister(struct device *);
> >>>+#else
> >>>+int gdsc_register(struct device *d, struct gdsc **g, size_t n, struct
> >>>regmap *r)
Rajendra Nayak March 24, 2015, 3:10 a.m. UTC | #5
On 03/24/2015 04:53 AM, Stephen Boyd wrote:
> On 03/20, Rajendra Nayak wrote:
>> []...
>>>>> @@ -0,0 +1,46 @@
>>>>> +/*
>>>>> + * Copyright (c) 2015, 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.
>>>>> + */
>>>>> +
>>>>> +#ifndef __QCOM_GDSC_H__
>>>>> +#define __QCOM_GDSC_H__
>>>>> +
>>>>> +#include <linux/pm_domain.h>
>>>>> +
>>>>> +struct regmap;
>>
>> I realized, after Srini pointed out on irc that this fwd declaration
>> is a complete waste. I'll remove it when I do a repost.
>>
>
> Why? It's there so that struct regmap is declared instead of
> being declared inside struct gdsc.

ah, sorry. I completely overlooked the fact that there isn;t a
#include <linux/regmap.h>

>
>>>>> +
>>>>> +/**
>>>>> + * struct gdsc - Globally Distributed Switch Controller
>>>>> + * @pd: generic power domain
>>>>> + * @regmap: regmap for MMIO accesses
>>>>> + * @gdscr: gsdc control register
>>>>> + */
>>>>> +struct gdsc {
>>>>> +    struct generic_pm_domain    pd;
>>>>> +    struct regmap            *regmap;
>>>>> +    unsigned int            gdscr;
>>>>> +};
>>>>> +
>>>>> +#define domain_to_gdsc(domain) container_of(domain, struct gdsc, pd)
>>>>> +
>>>>> +#ifdef CONFIG_QCOM_GDSC
>>>>> +int gdsc_register(struct device *, struct gdsc **, size_t n, struct
>>>>> regmap *);
>>>>> +void gdsc_unregister(struct device *);
>>>>> +#else
>>>>> +int gdsc_register(struct device *d, struct gdsc **g, size_t n, struct
>>>>> regmap *r)
>
diff mbox

Patch

diff --git a/drivers/clk/qcom/Kconfig b/drivers/clk/qcom/Kconfig
index 48d5151..f436bcf 100644
--- a/drivers/clk/qcom/Kconfig
+++ b/drivers/clk/qcom/Kconfig
@@ -38,6 +38,11 @@  config IPQ_LCC_806X
 	  Say Y if you want to use audio devices such as i2s, pcm,
 	  S/PDIF, etc.
 
+config QCOM_GDSC
+	bool
+	select PM_GENERIC_DOMAINS if PM
+	depends on COMMON_CLK_QCOM
+
 config MSM_GCC_8660
 	tristate "MSM8660 Global Clock Controller"
 	depends on COMMON_CLK_QCOM
diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile
index 50b337a..fe62523 100644
--- a/drivers/clk/qcom/Makefile
+++ b/drivers/clk/qcom/Makefile
@@ -9,6 +9,7 @@  clk-qcom-y += clk-branch.o
 clk-qcom-y += clk-regmap-divider.o
 clk-qcom-y += clk-regmap-mux.o
 clk-qcom-y += reset.o
+clk-qcom-$(CONFIG_QCOM_GDSC) += gdsc.o
 
 obj-$(CONFIG_APQ_GCC_8084) += gcc-apq8084.o
 obj-$(CONFIG_APQ_MMCC_8084) += mmcc-apq8084.o
diff --git a/drivers/clk/qcom/gdsc.c b/drivers/clk/qcom/gdsc.c
new file mode 100644
index 0000000..ae23373
--- /dev/null
+++ b/drivers/clk/qcom/gdsc.c
@@ -0,0 +1,166 @@ 
+/*
+ * Copyright (c) 2015, 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.
+ */
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/export.h>
+#include <linux/jiffies.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include "gdsc.h"
+
+#define PWR_ON_MASK		BIT(31)
+#define EN_REST_WAIT_MASK	GENMASK(23, 20)
+#define EN_FEW_WAIT_MASK	GENMASK(19, 16)
+#define CLK_DIS_WAIT_MASK	GENMASK(15, 12)
+#define SW_OVERRIDE_MASK	BIT(2)
+#define HW_CONTROL_MASK		BIT(1)
+#define SW_COLLAPSE_MASK	BIT(0)
+
+/* Wait 2^n CXO cycles between all states. Here, n=2 (4 cycles). */
+#define EN_REST_WAIT_VAL	(0x2 << 20)
+#define EN_FEW_WAIT_VAL		(0x8 << 16)
+#define CLK_DIS_WAIT_VAL	(0x2 << 12)
+
+#define TIMEOUT_US		100
+
+static int gdsc_is_enabled(struct gdsc *sc)
+{
+	u32 val;
+	int ret;
+
+	ret = regmap_read(sc->regmap, sc->gdscr, &val);
+	if (ret)
+		return ret;
+	return !!(val & PWR_ON_MASK);
+}
+
+static int gdsc_toggle_logic(struct gdsc *sc, bool en)
+{
+	int ret;
+	u32 val = en ? 0 : SW_COLLAPSE_MASK;
+	u32 check = en ? PWR_ON_MASK : 0;
+	unsigned long timeout;
+
+	ret = regmap_update_bits(sc->regmap, sc->gdscr, SW_COLLAPSE_MASK, val);
+	if (ret)
+		return ret;
+
+	timeout = jiffies + usecs_to_jiffies(TIMEOUT_US);
+	do {
+		ret = regmap_read(sc->regmap, sc->gdscr, &val);
+		if (ret)
+			return ret;
+		if ((val & PWR_ON_MASK) == check)
+			return 0;
+	} while (time_before(jiffies, timeout));
+
+	ret =  regmap_read(sc->regmap, sc->gdscr, &val);
+	if (ret)
+		return ret;
+	if ((val & PWR_ON_MASK) == check)
+		return 0;
+
+	return -ETIMEDOUT;
+}
+
+static int gdsc_enable(struct generic_pm_domain *domain)
+{
+	struct gdsc *sc = domain_to_gdsc(domain);
+	int ret;
+
+	ret = gdsc_toggle_logic(sc, true);
+	if (ret)
+		return ret;
+	/*
+	 * If clocks to this power domain were already on, they will take an
+	 * additional 4 clock cycles to re-enable after the power domain is
+	 * enabled. Delay to account for this. A delay is also needed to ensure
+	 * clocks are not enabled within 400ns of enabling power to the
+	 * memories.
+	 */
+	udelay(1);
+
+	return 0;
+}
+
+static int gdsc_disable(struct generic_pm_domain *domain)
+{
+	struct gdsc *sc = domain_to_gdsc(domain);
+
+	return gdsc_toggle_logic(sc, false);
+}
+
+static int gdsc_init(struct gdsc *sc)
+{
+	u32 mask, val;
+	int on, ret;
+
+	/*
+	 * Disable HW trigger: collapse/restore occur based on registers writes.
+	 * Disable SW override: Use hardware state-machine for sequencing.
+	 * Configure wait time between states.
+	 */
+	mask = HW_CONTROL_MASK | SW_OVERRIDE_MASK |
+	       EN_REST_WAIT_MASK | EN_FEW_WAIT_MASK | CLK_DIS_WAIT_MASK;
+	val = EN_REST_WAIT_VAL | EN_FEW_WAIT_VAL | CLK_DIS_WAIT_VAL;
+	ret = regmap_update_bits(sc->regmap, sc->gdscr, mask, val);
+	if (ret)
+		return ret;
+
+	on = gdsc_is_enabled(sc);
+	if (on < 0)
+		return on;
+
+	pm_genpd_init(&sc->pd, NULL, !on);
+	sc->pd.power_off = gdsc_disable;
+	sc->pd.power_on = gdsc_enable;
+
+	return 0;
+}
+
+int gdsc_register(struct device *dev, struct gdsc **scs, size_t num,
+		  struct regmap *regmap)
+{
+	int i, ret;
+	struct genpd_onecell_data *data;
+
+	if (!num || !scs || !dev || !dev->of_node)
+		return 0;
+
+	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->domains = devm_kzalloc(dev, sizeof(*data->domains) * num,
+				     GFP_KERNEL);
+	if (!data->domains)
+		return -ENOMEM;
+
+	data->num_domains = num;
+	for (i = 0; i < num; i++) {
+		if (!scs[i])
+			continue;
+		scs[i]->regmap = regmap;
+		ret = gdsc_init(scs[i]);
+		if (ret)
+			return ret;
+		data->domains[i] = &scs[i]->pd;
+	}
+	return of_genpd_add_provider_onecell(dev->of_node, data);
+}
+
+void gdsc_unregister(struct device *dev)
+{
+	of_genpd_del_provider(dev->of_node);
+}
diff --git a/drivers/clk/qcom/gdsc.h b/drivers/clk/qcom/gdsc.h
new file mode 100644
index 0000000..02e2990
--- /dev/null
+++ b/drivers/clk/qcom/gdsc.h
@@ -0,0 +1,46 @@ 
+/*
+ * Copyright (c) 2015, 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.
+ */
+
+#ifndef __QCOM_GDSC_H__
+#define __QCOM_GDSC_H__
+
+#include <linux/pm_domain.h>
+
+struct regmap;
+
+/**
+ * struct gdsc - Globally Distributed Switch Controller
+ * @pd: generic power domain
+ * @regmap: regmap for MMIO accesses
+ * @gdscr: gsdc control register
+ */
+struct gdsc {
+	struct generic_pm_domain	pd;
+	struct regmap			*regmap;
+	unsigned int			gdscr;
+};
+
+#define domain_to_gdsc(domain) container_of(domain, struct gdsc, pd)
+
+#ifdef CONFIG_QCOM_GDSC
+int gdsc_register(struct device *, struct gdsc **, size_t n, struct regmap *);
+void gdsc_unregister(struct device *);
+#else
+int gdsc_register(struct device *d, struct gdsc **g, size_t n, struct regmap *r)
+{
+	return 0;
+}
+
+void gdsc_unregister(struct device *d) {};
+#endif /* CONFIG_QCOM_GDSC */
+#endif /* __QCOM_GDSC_H__ */