* Re: [PATCH 3/3] thermal: rcar: add .get_trip_type/temp and .notify support
From: Kuninori Morimoto @ 2012-12-03 2:20 UTC (permalink / raw)
To: Zhang Rui; +Cc: Simon, Magnus, linux-pm, Kuninori Morimoto
In-Reply-To: <1354496556.1936.7.camel@rzhang1-mobl4>
Hi Zhang
> > +static int rcar_thermal_get_trip_temp(struct thermal_zone_device *zone,
> > + int trip, unsigned long *temp)
> > +{
> > + struct rcar_thermal_priv *priv = rcar_zone_to_priv(zone);
> > +
> > + /* see rcar_thermal_get_temp() */
> > + switch (trip) {
> > + case 0: /* +90 <= temp < +135 */
> > + *temp = MCELSIUS(90 - 1);
>
> what does the comment above mean?
> the system is supposed to run from 90C to 135C? but you're setting the
> critical trip point to 89C.
Oops, sorry my original patch used old kernel for test.
Then it needed this -1.
But now it was already solved by this patch.
29321357ac6db54eeb8574da1f6c3e0ce8cfbb60
(thermal: fix off-by-1 error in trip point trigger condition)
I can fix this, but which is best for you ?
v2 patch ? or additional patch ?
Best regards
---
Kuninori Morimoto
^ permalink raw reply
* Re: [PATCH 3/3] thermal: rcar: add .get_trip_type/temp and .notify support
From: Kuninori Morimoto @ 2012-12-03 2:11 UTC (permalink / raw)
To: Zhang Rui; +Cc: Simon, Magnus, linux-pm, Kuninori Morimoto
In-Reply-To: <1354496971.1936.12.camel@rzhang1-mobl4>
Hi Zhang
> Hi, Kuninori,
>
> On Sun, 2012-12-02 at 16:15 -0800, Kuninori Morimoto wrote:
> > Hi Zhang
> >
> > I couldn't find this patch on v3.8 list.
> > Is this patch not good ?
> >
> No, the patch looks okay.
> This patch set is sent too late for 3.8. Usually patches need to stay in
> linux-next for at least two weeks before they are pushing upstream,
> especially when there are some big changes made in the patch.
> so I just picked up the first two, and leave this one for 3.9.
OK. I see.
Thank you
Best regards
---
Kuninori Morimoto
^ permalink raw reply
* Re: [PATCH 3/3] thermal: rcar: add .get_trip_type/temp and .notify support
From: Zhang Rui @ 2012-12-03 1:09 UTC (permalink / raw)
To: Kuninori Morimoto; +Cc: Simon, Magnus, linux-pm, Kuninori Morimoto
In-Reply-To: <87obicq9c6.wl%kuninori.morimoto.gx@renesas.com>
Hi, Kuninori,
On Sun, 2012-12-02 at 16:15 -0800, Kuninori Morimoto wrote:
> Hi Zhang
>
> I couldn't find this patch on v3.8 list.
> Is this patch not good ?
>
No, the patch looks okay.
This patch set is sent too late for 3.8. Usually patches need to stay in
linux-next for at least two weeks before they are pushing upstream,
especially when there are some big changes made in the patch.
so I just picked up the first two, and leave this one for 3.9.
thanks,
rui
> > This patch adds .get_trip_type(), .get_trip_temp(), and .notify()
> > on rcar_thermal_zone_ops.
> > Driver will try platform power OFF if it reached to
> > critical temperature.
> >
> > Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
> > ---
> > drivers/thermal/rcar_thermal.c | 68 ++++++++++++++++++++++++++++++++++++++--
> > 1 file changed, 65 insertions(+), 3 deletions(-)
> >
> > diff --git a/drivers/thermal/rcar_thermal.c b/drivers/thermal/rcar_thermal.c
> > index 90db951..e1aedcc 100644
> > --- a/drivers/thermal/rcar_thermal.c
> > +++ b/drivers/thermal/rcar_thermal.c
> > @@ -22,10 +22,13 @@
> > #include <linux/io.h>
> > #include <linux/module.h>
> > #include <linux/platform_device.h>
> > +#include <linux/reboot.h>
> > #include <linux/slab.h>
> > #include <linux/spinlock.h>
> > #include <linux/thermal.h>
> >
> > +#define IDLE_INTERVAL 5000
> > +
> > #define THSCR 0x2c
> > #define THSSR 0x30
> >
> > @@ -176,8 +179,66 @@ static int rcar_thermal_get_temp(struct thermal_zone_device *zone,
> > return 0;
> > }
> >
> > +static int rcar_thermal_get_trip_type(struct thermal_zone_device *zone,
> > + int trip, enum thermal_trip_type *type)
> > +{
> > + struct rcar_thermal_priv *priv = rcar_zone_to_priv(zone);
> > +
> > + /* see rcar_thermal_get_temp() */
> > + switch (trip) {
> > + case 0: /* +90 <= temp < +135 */
> > + *type = THERMAL_TRIP_CRITICAL;
> > + break;
> > + default:
> > + dev_err(priv->dev, "rcar driver trip error\n");
> > + return -EINVAL;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int rcar_thermal_get_trip_temp(struct thermal_zone_device *zone,
> > + int trip, unsigned long *temp)
> > +{
> > + struct rcar_thermal_priv *priv = rcar_zone_to_priv(zone);
> > +
> > + /* see rcar_thermal_get_temp() */
> > + switch (trip) {
> > + case 0: /* +90 <= temp < +135 */
> > + *temp = MCELSIUS(90 - 1);
> > + break;
> > + default:
> > + dev_err(priv->dev, "rcar driver trip error\n");
> > + return -EINVAL;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int rcar_thermal_notify(struct thermal_zone_device *zone,
> > + int trip, enum thermal_trip_type type)
> > +{
> > + struct rcar_thermal_priv *priv = rcar_zone_to_priv(zone);
> > +
> > + switch (type) {
> > + case THERMAL_TRIP_CRITICAL:
> > + /* FIXME */
> > + dev_warn(priv->dev,
> > + "Thermal reached to critical temperature\n");
> > + machine_power_off();
> > + break;
> > + default:
> > + break;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > static struct thermal_zone_device_ops rcar_thermal_zone_ops = {
> > - .get_temp = rcar_thermal_get_temp,
> > + .get_temp = rcar_thermal_get_temp,
> > + .get_trip_type = rcar_thermal_get_trip_type,
> > + .get_trip_temp = rcar_thermal_get_trip_temp,
> > + .notify = rcar_thermal_notify,
> > };
> >
> > /*
> > @@ -211,8 +272,9 @@ static int rcar_thermal_probe(struct platform_device *pdev)
> > return -ENOMEM;
> > }
> >
> > - zone = thermal_zone_device_register("rcar_thermal", 0, 0, priv,
> > - &rcar_thermal_zone_ops, NULL, 0, 0);
> > + zone = thermal_zone_device_register("rcar_thermal", 1, 0, priv,
> > + &rcar_thermal_zone_ops, NULL, 0,
> > + IDLE_INTERVAL);
> > if (IS_ERR(zone)) {
> > dev_err(&pdev->dev, "thermal zone device is NULL\n");
> > return PTR_ERR(zone);
> > --
> > 1.7.9.5
> >
>
>
> Best regards
> ---
> Kuninori Morimoto
^ permalink raw reply
* Re: [PATCH 3/3] thermal: rcar: add .get_trip_type/temp and .notify support
From: Zhang Rui @ 2012-12-03 1:02 UTC (permalink / raw)
To: Kuninori Morimoto; +Cc: Simon, Magnus, linux-pm, Kuninori Morimoto
In-Reply-To: <87ehjhw0ty.wl%kuninori.morimoto.gx@renesas.com>
On Sun, 2012-11-25 at 18:32 -0800, Kuninori Morimoto wrote:
> This patch adds .get_trip_type(), .get_trip_temp(), and .notify()
> on rcar_thermal_zone_ops.
> Driver will try platform power OFF if it reached to
> critical temperature.
>
> Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
> ---
> drivers/thermal/rcar_thermal.c | 68 ++++++++++++++++++++++++++++++++++++++--
> 1 file changed, 65 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/thermal/rcar_thermal.c b/drivers/thermal/rcar_thermal.c
> index 90db951..e1aedcc 100644
> --- a/drivers/thermal/rcar_thermal.c
> +++ b/drivers/thermal/rcar_thermal.c
> @@ -22,10 +22,13 @@
> #include <linux/io.h>
> #include <linux/module.h>
> #include <linux/platform_device.h>
> +#include <linux/reboot.h>
> #include <linux/slab.h>
> #include <linux/spinlock.h>
> #include <linux/thermal.h>
>
> +#define IDLE_INTERVAL 5000
> +
> #define THSCR 0x2c
> #define THSSR 0x30
>
> @@ -176,8 +179,66 @@ static int rcar_thermal_get_temp(struct thermal_zone_device *zone,
> return 0;
> }
>
> +static int rcar_thermal_get_trip_type(struct thermal_zone_device *zone,
> + int trip, enum thermal_trip_type *type)
> +{
> + struct rcar_thermal_priv *priv = rcar_zone_to_priv(zone);
> +
> + /* see rcar_thermal_get_temp() */
> + switch (trip) {
> + case 0: /* +90 <= temp < +135 */
> + *type = THERMAL_TRIP_CRITICAL;
> + break;
> + default:
> + dev_err(priv->dev, "rcar driver trip error\n");
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> +static int rcar_thermal_get_trip_temp(struct thermal_zone_device *zone,
> + int trip, unsigned long *temp)
> +{
> + struct rcar_thermal_priv *priv = rcar_zone_to_priv(zone);
> +
> + /* see rcar_thermal_get_temp() */
> + switch (trip) {
> + case 0: /* +90 <= temp < +135 */
> + *temp = MCELSIUS(90 - 1);
what does the comment above mean?
the system is supposed to run from 90C to 135C? but you're setting the
critical trip point to 89C.
thanks,
rui
^ permalink raw reply
* Re: [PATCH 3/3] thermal: rcar: add .get_trip_type/temp and .notify support
From: Kuninori Morimoto @ 2012-12-03 0:15 UTC (permalink / raw)
To: Zhang Rui; +Cc: Simon, Magnus, linux-pm, Kuninori Morimoto
In-Reply-To: <87ehjhw0ty.wl%kuninori.morimoto.gx@renesas.com>
Hi Zhang
I couldn't find this patch on v3.8 list.
Is this patch not good ?
> This patch adds .get_trip_type(), .get_trip_temp(), and .notify()
> on rcar_thermal_zone_ops.
> Driver will try platform power OFF if it reached to
> critical temperature.
>
> Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
> ---
> drivers/thermal/rcar_thermal.c | 68 ++++++++++++++++++++++++++++++++++++++--
> 1 file changed, 65 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/thermal/rcar_thermal.c b/drivers/thermal/rcar_thermal.c
> index 90db951..e1aedcc 100644
> --- a/drivers/thermal/rcar_thermal.c
> +++ b/drivers/thermal/rcar_thermal.c
> @@ -22,10 +22,13 @@
> #include <linux/io.h>
> #include <linux/module.h>
> #include <linux/platform_device.h>
> +#include <linux/reboot.h>
> #include <linux/slab.h>
> #include <linux/spinlock.h>
> #include <linux/thermal.h>
>
> +#define IDLE_INTERVAL 5000
> +
> #define THSCR 0x2c
> #define THSSR 0x30
>
> @@ -176,8 +179,66 @@ static int rcar_thermal_get_temp(struct thermal_zone_device *zone,
> return 0;
> }
>
> +static int rcar_thermal_get_trip_type(struct thermal_zone_device *zone,
> + int trip, enum thermal_trip_type *type)
> +{
> + struct rcar_thermal_priv *priv = rcar_zone_to_priv(zone);
> +
> + /* see rcar_thermal_get_temp() */
> + switch (trip) {
> + case 0: /* +90 <= temp < +135 */
> + *type = THERMAL_TRIP_CRITICAL;
> + break;
> + default:
> + dev_err(priv->dev, "rcar driver trip error\n");
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> +static int rcar_thermal_get_trip_temp(struct thermal_zone_device *zone,
> + int trip, unsigned long *temp)
> +{
> + struct rcar_thermal_priv *priv = rcar_zone_to_priv(zone);
> +
> + /* see rcar_thermal_get_temp() */
> + switch (trip) {
> + case 0: /* +90 <= temp < +135 */
> + *temp = MCELSIUS(90 - 1);
> + break;
> + default:
> + dev_err(priv->dev, "rcar driver trip error\n");
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> +static int rcar_thermal_notify(struct thermal_zone_device *zone,
> + int trip, enum thermal_trip_type type)
> +{
> + struct rcar_thermal_priv *priv = rcar_zone_to_priv(zone);
> +
> + switch (type) {
> + case THERMAL_TRIP_CRITICAL:
> + /* FIXME */
> + dev_warn(priv->dev,
> + "Thermal reached to critical temperature\n");
> + machine_power_off();
> + break;
> + default:
> + break;
> + }
> +
> + return 0;
> +}
> +
> static struct thermal_zone_device_ops rcar_thermal_zone_ops = {
> - .get_temp = rcar_thermal_get_temp,
> + .get_temp = rcar_thermal_get_temp,
> + .get_trip_type = rcar_thermal_get_trip_type,
> + .get_trip_temp = rcar_thermal_get_trip_temp,
> + .notify = rcar_thermal_notify,
> };
>
> /*
> @@ -211,8 +272,9 @@ static int rcar_thermal_probe(struct platform_device *pdev)
> return -ENOMEM;
> }
>
> - zone = thermal_zone_device_register("rcar_thermal", 0, 0, priv,
> - &rcar_thermal_zone_ops, NULL, 0, 0);
> + zone = thermal_zone_device_register("rcar_thermal", 1, 0, priv,
> + &rcar_thermal_zone_ops, NULL, 0,
> + IDLE_INTERVAL);
> if (IS_ERR(zone)) {
> dev_err(&pdev->dev, "thermal zone device is NULL\n");
> return PTR_ERR(zone);
> --
> 1.7.9.5
>
Best regards
---
Kuninori Morimoto
^ permalink raw reply
* Re: [PATCH] SDIO / PM: Add empty bus-level suspend/resume callbacks
From: Chris Ball @ 2012-12-02 21:49 UTC (permalink / raw)
To: NeilBrown
Cc: Rafael J. Wysocki, lkml, linux-mmc, Linux PM mailing list,
Thierry Reding
In-Reply-To: <20121203065457.0bd12fc6@notabene.brown>
Hi,
On Sun, Dec 02 2012, NeilBrown wrote:
>> What about applying the appended patch (hopefully, the build warnings should
>> be fixed properly this time)?
>
> Looks good to me - thanks!
Thanks, both of you, pushed to mmc-next for 3.8.
- Chris.
--
Chris Ball <cjb@laptop.org> <http://printf.net/>
One Laptop Per Child
^ permalink raw reply
* Re: [PATCH] SDIO / PM: Add empty bus-level suspend/resume callbacks
From: Thierry Reding @ 2012-12-02 21:01 UTC (permalink / raw)
To: Rafael J. Wysocki
Cc: NeilBrown, Chris Ball, lkml, linux-mmc, Linux PM mailing list
In-Reply-To: <2147450.LFoExqrKEF@vostro.rjw.lan>
[-- Attachment #1: Type: text/plain, Size: 4339 bytes --]
On Sun, Dec 02, 2012 at 02:48:50PM +0100, Rafael J. Wysocki wrote:
> On Sunday, December 02, 2012 07:46:25 PM NeilBrown wrote:
> > On Mon, 26 Mar 2012 00:29:24 +0200 "Rafael J. Wysocki" <rjw@sisk.pl> wrote:
> >
> >
> > > Thanks for the confirmation.
> > >
> > > Below it goes again with a changelog and tags.
> > >
> > > I don't really think that SDIO does the right thing here overall, but that's
> > > all I can do to address the problem timely.
> > >
> > > Thanks,
> > > Rafael
> >
> > Hi Rafael,
> > I just discovered that this patch has since been reverted - with an 'ack'
> > from you:
> > ----------
> > commit d8e2ac330f65bcf47e8894fe5331a7e8ee019c06
> > Author: Thierry Reding <thierry.reding@avionic-design.de>
> > Date: Thu Aug 9 09:32:21 2012 +0000
> >
> > mmc: sdio: Fix PM_SLEEP related build warnings
> >
> > Power management callbacks defined by SIMPLE_DEV_PM_OPS are only used if
> > the PM_SLEEP Kconfig symbol has been defined. If not, the compiler will
> > complain about them being unused. However, since the callback for this
> > driver doesn't do anything it can just as well be dropped.
> >
> > Signed-off-by: Thierry Reding <thierry.reding@avionic-design.de>
> > Acked-by: Rafael J. Wysocki <rjw@sisk.pl>
> > Signed-off-by: Chris Ball <cjb@laptop.org>
> > -----------
> >
> > Unsurprisingly the problem which your patch fixed has come back.
> >
> > Do you think we could get the patch back in again. This time maybe we should
> > put some comments in there pointing out that having a function which does
> > nothing is very different from not having any function at all?
>
> Well, I agree. I didn't remember that the callback had been added for
> a purpose and hence my "ack" for that patch.
>
> What about applying the appended patch (hopefully, the build warnings should
> be fixed properly this time)?
>
> Rafael
>
>
> ---
> From: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
> Subject: SDIO / PM: Add empty bus-level suspend/resume callbacks
>
> Suspend methods provided by SDIO drivers are not supposed to be
> called by the PM core. Instead, when the SDIO core gets to suspend
> a device's ancestor, it calls the device driver's suspend routine.
> However, the PM core executes suspend callback routines directly for
> device drivers whose bus types don't provide suspend callbacks.
> In consequece, because the SDIO bus type doesn't provide a suspend
> callback, the SDIO drivers' suspend routines will be executed by the
> PM core (which shouldn't happen).
>
> To prevent this from happening, add empty system suspend/resume
> callbacks for the SDIO bus type.
>
> An analogous change had been made already by commit (e841a7c mmc:
> sdio: Use empty system suspend/resume callbacks at the bus level),
> but then it was reverted inadvertently by commit (d8e2ac3 mmc: sdio:
> Fix PM_SLEEP related build warnings) that attempted to fix build
> warnings introduced by commit e841a7c.
>
> Reported-by: NeilBrown <neilb@suse.de>
> Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
> ---
> drivers/mmc/core/sdio_bus.c | 14 ++++++++++++++
> 1 file changed, 14 insertions(+)
>
> Index: linux/drivers/mmc/core/sdio_bus.c
> ===================================================================
> --- linux.orig/drivers/mmc/core/sdio_bus.c
> +++ linux/drivers/mmc/core/sdio_bus.c
> @@ -193,7 +193,21 @@ static int sdio_bus_remove(struct device
> }
>
> #ifdef CONFIG_PM
> +
> +#ifdef CONFIG_PM_SLEEP
> +static int pm_no_operation(struct device *dev)
> +{
> + /*
> + * Prevent the PM core from calling SDIO device drivers' suspend
> + * callback routines, which it is not supposed to do, by using this
> + * empty function as the bus type suspend callaback for SDIO.
> + */
> + return 0;
> +}
> +#endif
> +
> static const struct dev_pm_ops sdio_bus_pm_ops = {
> + SET_SYSTEM_SLEEP_PM_OPS(pm_no_operation, pm_no_operation)
> SET_RUNTIME_PM_OPS(
> pm_generic_runtime_suspend,
> pm_generic_runtime_resume,
>
Hehe... if my memory serves me well, that's exactly (well, modulo the
comment) what my initial patch did before somebody suggested that the
empty callbacks should just be removed altogether. =)
Thierry
[-- Attachment #2: Type: application/pgp-signature, Size: 836 bytes --]
^ permalink raw reply
* Re: [PATCH] SDIO / PM: Add empty bus-level suspend/resume callbacks
From: NeilBrown @ 2012-12-02 19:54 UTC (permalink / raw)
To: Rafael J. Wysocki
Cc: Chris Ball, lkml, linux-mmc, Linux PM mailing list,
Thierry Reding
In-Reply-To: <2147450.LFoExqrKEF@vostro.rjw.lan>
[-- Attachment #1: Type: text/plain, Size: 4191 bytes --]
On Sun, 02 Dec 2012 14:48:50 +0100 "Rafael J. Wysocki" <rjw@sisk.pl> wrote:
> On Sunday, December 02, 2012 07:46:25 PM NeilBrown wrote:
> > On Mon, 26 Mar 2012 00:29:24 +0200 "Rafael J. Wysocki" <rjw@sisk.pl> wrote:
> >
> >
> > > Thanks for the confirmation.
> > >
> > > Below it goes again with a changelog and tags.
> > >
> > > I don't really think that SDIO does the right thing here overall, but that's
> > > all I can do to address the problem timely.
> > >
> > > Thanks,
> > > Rafael
> >
> > Hi Rafael,
> > I just discovered that this patch has since been reverted - with an 'ack'
> > from you:
> > ----------
> > commit d8e2ac330f65bcf47e8894fe5331a7e8ee019c06
> > Author: Thierry Reding <thierry.reding@avionic-design.de>
> > Date: Thu Aug 9 09:32:21 2012 +0000
> >
> > mmc: sdio: Fix PM_SLEEP related build warnings
> >
> > Power management callbacks defined by SIMPLE_DEV_PM_OPS are only used if
> > the PM_SLEEP Kconfig symbol has been defined. If not, the compiler will
> > complain about them being unused. However, since the callback for this
> > driver doesn't do anything it can just as well be dropped.
> >
> > Signed-off-by: Thierry Reding <thierry.reding@avionic-design.de>
> > Acked-by: Rafael J. Wysocki <rjw@sisk.pl>
> > Signed-off-by: Chris Ball <cjb@laptop.org>
> > -----------
> >
> > Unsurprisingly the problem which your patch fixed has come back.
> >
> > Do you think we could get the patch back in again. This time maybe we should
> > put some comments in there pointing out that having a function which does
> > nothing is very different from not having any function at all?
>
> Well, I agree. I didn't remember that the callback had been added for
> a purpose and hence my "ack" for that patch.
>
> What about applying the appended patch (hopefully, the build warnings should
> be fixed properly this time)?
Looks good to me - thanks!
NeilBrown
>
> Rafael
>
>
> ---
> From: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
> Subject: SDIO / PM: Add empty bus-level suspend/resume callbacks
>
> Suspend methods provided by SDIO drivers are not supposed to be
> called by the PM core. Instead, when the SDIO core gets to suspend
> a device's ancestor, it calls the device driver's suspend routine.
> However, the PM core executes suspend callback routines directly for
> device drivers whose bus types don't provide suspend callbacks.
> In consequece, because the SDIO bus type doesn't provide a suspend
> callback, the SDIO drivers' suspend routines will be executed by the
> PM core (which shouldn't happen).
>
> To prevent this from happening, add empty system suspend/resume
> callbacks for the SDIO bus type.
>
> An analogous change had been made already by commit (e841a7c mmc:
> sdio: Use empty system suspend/resume callbacks at the bus level),
> but then it was reverted inadvertently by commit (d8e2ac3 mmc: sdio:
> Fix PM_SLEEP related build warnings) that attempted to fix build
> warnings introduced by commit e841a7c.
>
> Reported-by: NeilBrown <neilb@suse.de>
> Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
> ---
> drivers/mmc/core/sdio_bus.c | 14 ++++++++++++++
> 1 file changed, 14 insertions(+)
>
> Index: linux/drivers/mmc/core/sdio_bus.c
> ===================================================================
> --- linux.orig/drivers/mmc/core/sdio_bus.c
> +++ linux/drivers/mmc/core/sdio_bus.c
> @@ -193,7 +193,21 @@ static int sdio_bus_remove(struct device
> }
>
> #ifdef CONFIG_PM
> +
> +#ifdef CONFIG_PM_SLEEP
> +static int pm_no_operation(struct device *dev)
> +{
> + /*
> + * Prevent the PM core from calling SDIO device drivers' suspend
> + * callback routines, which it is not supposed to do, by using this
> + * empty function as the bus type suspend callaback for SDIO.
> + */
> + return 0;
> +}
> +#endif
> +
> static const struct dev_pm_ops sdio_bus_pm_ops = {
> + SET_SYSTEM_SLEEP_PM_OPS(pm_no_operation, pm_no_operation)
> SET_RUNTIME_PM_OPS(
> pm_generic_runtime_suspend,
> pm_generic_runtime_resume,
>
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 828 bytes --]
^ permalink raw reply
* Re: [RFC PATCH 4/5] arm: omap2: support port power on lan95xx devices
From: Andy Green @ 2012-12-02 16:37 UTC (permalink / raw)
To: Ming Lei
Cc: Alan Stern, Greg Kroah-Hartman, Lan Tianyu, Sarah Sharp,
Rafael J. Wysocki, linux-pm-u79uwXL29TY76Z2rM5mHXA, Oliver Neukum,
linux-omap-u79uwXL29TY76Z2rM5mHXA,
linux-usb-u79uwXL29TY76Z2rM5mHXA, Roger Quadros, Felipe Balbi
In-Reply-To: <1354460467-28006-5-git-send-email-tom.leiming-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
On 02/12/12 23:01, the mail apparently from Ming Lei included:
Hi -
> This patch defines power controller for powering on/off LAN95xx
> USB hub and USB ethernet devices, and implements one match function
> to associate the power controller with related USB port device.
> The big problem of this approach is that it depends on the global
> device ADD/DEL notifier.
>
> Another idea of associating power controller with port device
> is by introducing usb port driver, and move all this port power
> control stuff from platform code to the port driver, which is just
> what I think of and looks doable. The problem of the idea is that
> port driver is per board, so maybe cause lots of platform sort of
> code to be put under drivers/usb/port/, but this approach can avoid
> global device ADD/DEL notifier.
>
> I'd like to get some feedback about which one is better or other choice,
> then I may do it in next cycle.
>
> Cc: Andy Green <andy.green-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
> Cc: Roger Quadros <rogerq-l0cyMroinI0@public.gmane.org>
> Cc: Alan Stern <stern-nwvwT67g6+6dFdvTe/nMLpVzexx5G7lz@public.gmane.org>
> Cc: Felipe Balbi <balbi-l0cyMroinI0@public.gmane.org>
> Signed-off-by: Ming Lei <tom.leiming-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
> ---
> arch/arm/mach-omap2/board-omap4panda.c | 99 +++++++++++++++++++++++++++++++-
> 1 file changed, 96 insertions(+), 3 deletions(-)
>
> diff --git a/arch/arm/mach-omap2/board-omap4panda.c b/arch/arm/mach-omap2/board-omap4panda.c
> index 5c8e9ce..3183832 100644
> --- a/arch/arm/mach-omap2/board-omap4panda.c
> +++ b/arch/arm/mach-omap2/board-omap4panda.c
> @@ -32,6 +32,8 @@
> #include <linux/usb/musb.h>
> #include <linux/wl12xx.h>
> #include <linux/platform_data/omap-abe-twl6040.h>
> +#include <linux/power_controller.h>
> +#include <linux/usb/port.h>
>
> #include <asm/hardware/gic.h>
> #include <asm/mach-types.h>
> @@ -154,6 +156,99 @@ static struct gpio panda_ehci_gpios[] __initdata = {
> { GPIO_HUB_NRESET, GPIOF_OUT_INIT_LOW, "hub_nreset" },
> };
>
> +static void ehci_hub_power_on(struct power_controller *pc, struct device *dev)
> +{
> + gpio_set_value(GPIO_HUB_NRESET, 1);
> + gpio_set_value(GPIO_HUB_POWER, 1);
> +}
You should wait a bit after applying power to the smsc chip before
letting go of nReset too. In the regulator-based implementation I sent
it's handled by delays encoded in the regulator structs.
> +static void ehci_hub_power_off(struct power_controller *pc, struct device *dev)
> +{
> + gpio_set_value(GPIO_HUB_NRESET, 0);
> + gpio_set_value(GPIO_HUB_POWER, 0);
> +}
> +
> +static struct usb_port_power_switch_data root_hub_port_data = {
> + .hub_tier = 0,
> + .port_number = 1,
> + .type = USB_PORT_CONNECT_TYPE_HARD_WIRED,
> +};
> +
> +static struct usb_port_power_switch_data smsc_hub_port_data = {
> + .hub_tier = 1,
> + .port_number = 1,
> + .type = USB_PORT_CONNECT_TYPE_HARD_WIRED,
> +};
> +
> +static struct power_controller pc = {
> + .name = "omap_hub_eth_pc",
> + .count = ATOMIC_INIT(0),
> + .power_on = ehci_hub_power_on,
> + .power_off = ehci_hub_power_off,
> +};
> +
> +static inline int omap_ehci_hub_port(struct device *dev)
> +{
> + /* we expect dev->parent points to ehcd controller */
> + if (dev->parent && !strcmp(dev_name(dev->parent), "ehci-omap.0"))
> + return 1;
> + return 0;
> +}
> +
> +static inline int dev_pc_match(struct device *dev)
> +{
> + struct device *anc;
> + int ret = 0;
> +
> + if (likely(strcmp(dev_name(dev), "port1")))
> + goto exit;
> +
> + if (dev->parent && (anc = dev->parent->parent)) {
> + if (omap_ehci_hub_port(anc)) {
> + ret = 1;
> + goto exit;
> + }
> +
> + /* is it port of lan95xx hub? */
> + if ((anc = anc->parent) && omap_ehci_hub_port(anc)) {
> + ret = 2;
> + goto exit;
> + }
> + }
> +exit:
> + return ret;
> +}
> +
> +/*
> + * Notifications of device registration
> + */
> +static int device_notify(struct notifier_block *nb, unsigned long action, void *data)
> +{
> + struct device *dev = data;
> + int ret;
> +
> + switch (action) {
> + case DEV_NOTIFY_ADD_DEVICE:
> + ret = dev_pc_match(dev);
> + if (likely(!ret))
> + goto exit;
> + if (ret == 1)
> + dev_pc_bind(&pc, dev, &root_hub_port_data, sizeof(root_hub_port_data));
> + else
> + dev_pc_bind(&pc, dev, &smsc_hub_port_data, sizeof(smsc_hub_port_data));
> + break;
> +
> + case DEV_NOTIFY_DEL_DEVICE:
> + break;
> + }
> +exit:
> + return 0;
> +}
> +
> +static struct notifier_block usb_port_nb = {
> + .notifier_call = device_notify,
> +};
> +
Some thoughts on trying to make this functionality specific to power
only and ehci hub port only:
- Quite a few boards have smsc95xx... they're all going to carry these
additions in the board file? Surely you'll have to generalize the
pieces that perform device_path business out of the omap4panda board
file at least. At that point the path matching code becomes generic
end-of-the-device-path matching code.
- How could these literals like "port1" etc be nicely provided by
Device Tree? In ARM-land there's pressure to eventually eliminate board
files completely and pass in everything from dtb. device_asset can
neatly grow DT bindings in a generic way, since the footprint in the
board file is some regulators that already have dt bindings and some
device_asset structs. Similarly there's pressure for magic code to
service a board (rather than SoC) to go elsewhere than the board file.
- Shouldn't this take care of enabling and disabling the ULPI PHY
clock on Panda too? There's no purpose leaving it running if the one
thing the ULPI PHY is connected to is depowered, and when you do power
it, on Panda you will reset the ULPI PHY at the same time anyway (smsc
reset and ULPI PHY reset are connected together on Panda). Then you can
eliminate omap4_ehci_init() in the board file.
-Andy
> static void __init omap4_ehci_init(void)
> {
> int ret;
> @@ -178,12 +273,10 @@ static void __init omap4_ehci_init(void)
>
> gpio_export(GPIO_HUB_POWER, 0);
> gpio_export(GPIO_HUB_NRESET, 0);
> - gpio_set_value(GPIO_HUB_NRESET, 1);
>
> usbhs_init(&usbhs_bdata);
>
> - /* enable power to hub */
> - gpio_set_value(GPIO_HUB_POWER, 1);
> + dev_register_notifier(&usb_port_nb);
> }
>
> static struct omap_musb_board_data musb_board_data = {
>
--
Andy Green | TI Landing Team Leader
Linaro.org │ Open source software for ARM SoCs | Follow Linaro
http://facebook.com/pages/Linaro/155974581091106 -
http://twitter.com/#!/linaroorg - http://linaro.org/linaro-blog
--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* Re: [RFC PATCH 2/5] driver core: introduce global device ADD/DEL notifier
From: Andy Green @ 2012-12-02 16:13 UTC (permalink / raw)
To: Ming Lei
Cc: Alan Stern, Greg Kroah-Hartman, Lan Tianyu, Sarah Sharp,
Rafael J. Wysocki, linux-pm, Oliver Neukum, linux-omap, linux-usb,
Roger Quadros, Felipe Balbi
In-Reply-To: <1354460467-28006-3-git-send-email-tom.leiming@gmail.com>
On 02/12/12 23:01, the mail apparently from Ming Lei included:
> The global device ADD/DEL notifier is introduced so that
> some platform code can bind some device resource to the
> device. When this platform code runs, there is no any bus
> information about the device, so have to resort to the
> global notifier.
>
> Cc: Andy Green <andy.green@linaro.org>
> Cc: Roger Quadros <rogerq@ti.com>
> Cc: Alan Stern <stern@rowland.harvard.edu>
> Cc: Felipe Balbi <balbi@ti.com>
> Signed-off-by: Ming Lei <tom.leiming@gmail.com>
> ---
> drivers/base/core.c | 21 +++++++++++++++++++++
> include/linux/device.h | 13 +++++++++++++
> 2 files changed, 34 insertions(+)
>
> diff --git a/drivers/base/core.c b/drivers/base/core.c
> index a235085..37f11ff 100644
> --- a/drivers/base/core.c
> +++ b/drivers/base/core.c
> @@ -43,6 +43,9 @@ static __init int sysfs_deprecated_setup(char *arg)
> early_param("sysfs.deprecated", sysfs_deprecated_setup);
> #endif
>
> +/* global device nofifier */
> +struct blocking_notifier_head dev_notifier;
> +
> int (*platform_notify)(struct device *dev) = NULL;
> int (*platform_notify_remove)(struct device *dev) = NULL;
> static struct kobject *dev_kobj;
> @@ -1041,6 +1044,8 @@ int device_add(struct device *dev)
> if (platform_notify)
> platform_notify(dev);
>
> + blocking_notifier_call_chain(&dev_notifier, DEV_NOTIFY_ADD_DEVICE, dev);
> +
> error = device_create_file(dev, &uevent_attr);
> if (error)
> goto attrError;
> @@ -1228,6 +1233,7 @@ void device_del(struct device *dev)
> device_pm_remove(dev);
> driver_deferred_probe_del(dev);
>
> + blocking_notifier_call_chain(&dev_notifier, DEV_NOTIFY_DEL_DEVICE, dev);
> /* Notify the platform of the removal, in case they
> * need to do anything...
> */
> @@ -1376,6 +1382,8 @@ struct device *device_find_child(struct device *parent, void *data,
>
> int __init devices_init(void)
> {
> + BLOCKING_INIT_NOTIFIER_HEAD(&dev_notifier);
> +
> devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL);
> if (!devices_kset)
> return -ENOMEM;
> @@ -1995,6 +2003,19 @@ int dev_printk(const char *level, const struct device *dev,
> }
> EXPORT_SYMBOL(dev_printk);
>
> +/* The notifier should be avoided as far as possible */
> +int dev_register_notifier(struct notifier_block *nb)
> +{
> + return blocking_notifier_chain_register(&dev_notifier, nb);
> +}
> +EXPORT_SYMBOL_GPL(dev_register_notifier);
> +
> +int dev_unregister_notifier(struct notifier_block *nb)
> +{
> + return blocking_notifier_chain_unregister(&dev_notifier, nb);
> +}
> +EXPORT_SYMBOL_GPL(dev_unregister_notifier);
> +
> #define define_dev_printk_level(func, kern_level) \
> int func(const struct device *dev, const char *fmt, ...) \
> { \
> diff --git a/include/linux/device.h b/include/linux/device.h
> index 43dcda9..aeb54f6 100644
> --- a/include/linux/device.h
> +++ b/include/linux/device.h
> @@ -179,6 +179,19 @@ extern int bus_unregister_notifier(struct bus_type *bus,
> #define BUS_NOTIFY_UNBOUND_DRIVER 0x00000006 /* driver is unbound
> from the device */
>
> +/* All 2 notifers below get called with the target struct device *
> + * as an argument. Note that those functions are likely to be called
> + * with the device lock held in the core, so be careful.
> + */
> +#define DEV_NOTIFY_ADD_DEVICE 0x00000001 /* device added */
> +#define DEV_NOTIFY_DEL_DEVICE 0x00000002 /* device removed */
> +extern int dev_register_notifier(struct notifier_block *nb);
> +extern int dev_unregister_notifier(struct notifier_block *nb);
> +
> +extern struct kset *bus_get_kset(struct bus_type *bus);
> +extern struct klist *bus_get_device_klist(struct bus_type *bus);
> +
> +
> extern struct kset *bus_get_kset(struct bus_type *bus);
> extern struct klist *bus_get_device_klist(struct bus_type *bus);
Device de/registraton time is not necessarily a good choice for the
notification point. At boot time, platform_devices may be being
registered with no sign of driver and anything getting enabled in the
notifier without further filtering (at each notifier...) will then
always be enabled the whole session whether in use or not.
probe() and remove() are more interesting because at that point the
driver is present and we're definitely instantiating the thing or
finished with its instantiation, and it's valid for non-usb devices too.
In the one case you're servicing in the series, usb hub port, the device
is very unusual in that it has no driver. However if you're going to
add a global notifier in generic device code, you should have it do the
right thing for normal device case so other people can use it... how I
solved this for hub port was to simply normalize hub port by introducing
a stub driver for it, so you get a probe / remove like anything else.
-Andy
--
Andy Green | TI Landing Team Leader
Linaro.org │ Open source software for ARM SoCs | Follow Linaro
http://facebook.com/pages/Linaro/155974581091106 -
http://twitter.com/#!/linaroorg - http://linaro.org/linaro-blog
--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* Re: [RFC PATCH 1/5] Device Power: introduce power controller
From: Andy Green @ 2012-12-02 16:02 UTC (permalink / raw)
To: Ming Lei
Cc: Alan Stern, Greg Kroah-Hartman, Lan Tianyu, Sarah Sharp,
Rafael J. Wysocki, linux-pm-u79uwXL29TY76Z2rM5mHXA, Oliver Neukum,
linux-omap-u79uwXL29TY76Z2rM5mHXA,
linux-usb-u79uwXL29TY76Z2rM5mHXA, Roger Quadros, Felipe Balbi
In-Reply-To: <1354460467-28006-2-git-send-email-tom.leiming-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
On 02/12/12 23:01, the mail apparently from Ming Lei included:
> Power controller is an abstract on simple power on/off switch.
>
> One power controller can bind to more than one device, which
> provides power logically, for example, we can think one usb port
> in hub provides power to the usb device attached to the port, even
> though the power is supplied actually by other ways, eg. the usb
> hub is a self-power device. From hardware view, more than one
> device can share one power domain, and power controller can power
> on if one of these devices need to provide power, and power off if
> all these devices don't need to provide power.
What stops us using struct regulator here? If you have child regulators
supplied by a parent supply, isn't that the right semantic already
without introducing a whole new thing? Apologies if I missed the point.
-Andy
> Cc: "Rafael J. Wysocki" <rjw-KKrjLPT3xs0@public.gmane.org>
> Cc: Andy Green <andy.green-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
> Cc: Roger Quadros <rogerq-l0cyMroinI0@public.gmane.org>
> Cc: Alan Stern <stern-nwvwT67g6+6dFdvTe/nMLpVzexx5G7lz@public.gmane.org>
> Cc: Felipe Balbi <balbi-l0cyMroinI0@public.gmane.org>
> Signed-off-by: Ming Lei <tom.leiming-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
> ---
> drivers/base/power/Makefile | 1 +
> drivers/base/power/power_controller.c | 110 +++++++++++++++++++++++++++++++++
> include/linux/power_controller.h | 48 ++++++++++++++
> kernel/power/Kconfig | 6 ++
> 4 files changed, 165 insertions(+)
> create mode 100644 drivers/base/power/power_controller.c
> create mode 100644 include/linux/power_controller.h
>
> diff --git a/drivers/base/power/Makefile b/drivers/base/power/Makefile
> index 2e58ebb..c08bfc9 100644
> --- a/drivers/base/power/Makefile
> +++ b/drivers/base/power/Makefile
> @@ -5,5 +5,6 @@ obj-$(CONFIG_PM_TRACE_RTC) += trace.o
> obj-$(CONFIG_PM_OPP) += opp.o
> obj-$(CONFIG_PM_GENERIC_DOMAINS) += domain.o domain_governor.o
> obj-$(CONFIG_HAVE_CLK) += clock_ops.o
> +obj-$(CONFIG_POWER_CONTROLLER) += power_controller.o
>
> ccflags-$(CONFIG_DEBUG_DRIVER) := -DDEBUG
> diff --git a/drivers/base/power/power_controller.c b/drivers/base/power/power_controller.c
> new file mode 100644
> index 0000000..d7671fb
> --- /dev/null
> +++ b/drivers/base/power/power_controller.c
> @@ -0,0 +1,110 @@
> +#include <linux/init.h>
> +#include <linux/kernel.h>
> +#include <linux/power_controller.h>
> +#include <linux/slab.h>
> +#include <linux/err.h>
> +#include <linux/export.h>
> +
> +static DEFINE_MUTEX(pc_lock);
> +
> +static void pc_devm_release(struct device *dev, void *res)
> +{
> +}
> +
> +static int pc_devm_match(struct device *dev, void *res, void *match_data)
> +{
> + struct pc_dev_data *data = res;
> +
> + return (data->magic == (unsigned long)pc_devm_release);
> +}
> +
> +static struct pc_dev_data *dev_pc_data(struct device *dev)
> +{
> + struct pc_dev_data *data;
> +
> + data = devres_find(dev, pc_devm_release, pc_devm_match, NULL);
> + return data;
> +}
> +
> +static int pc_add_devm_data(struct device *dev, struct power_controller *pc,
> + void *dev_data, int dev_data_size)
> +{
> + struct pc_dev_data *data;
> + int ret = 0;
> +
> + mutex_lock(&pc_lock);
> +
> + /* each device should only have one power controller resource */
> + data = dev_pc_data(dev);
> + if (data) {
> + ret = 1;
> + goto exit;
> + }
> +
> + data = devres_alloc(pc_devm_release, sizeof(struct pc_dev_data) +
> + dev_data_size, GFP_KERNEL);
> + if (!data) {
> + ret = -ENOMEM;
> + goto exit;
> + }
> +
> + data->magic = (unsigned long)pc_devm_release;
> + data->pc = pc;
> + data->dev_data = &data[1];
> + memcpy(data->dev_data, dev_data, dev_data_size);
> + devres_add(dev, data);
> +
> +exit:
> + mutex_unlock(&pc_lock);
> + return ret;
> +}
> +
> +static struct power_controller *dev_pc(struct device *dev)
> +{
> + struct pc_dev_data *data = dev_pc_data(dev);
> +
> + if (data)
> + return data->pc;
> + return NULL;
> +}
> +
> +struct pc_dev_data *dev_pc_get_data(struct device *dev)
> +{
> + struct pc_dev_data *data = dev_pc_data(dev);
> + return data;
> +}
> +EXPORT_SYMBOL(dev_pc_get_data);
> +
> +int dev_pc_bind(struct power_controller *pc, struct device *dev,
> + void *dev_data, int dev_data_size)
> +{
> + return pc_add_devm_data(dev, pc, dev_data, dev_data_size);
> +}
> +EXPORT_SYMBOL(dev_pc_bind);
> +
> +void dev_pc_unbind(struct power_controller *pc, struct device *dev)
> +{
> +}
> +EXPORT_SYMBOL(dev_pc_unbind);
> +
> +void dev_pc_power_on(struct device *dev)
> +{
> + struct power_controller *pc = dev_pc(dev);
> +
> + if (!pc) return;
> +
> + if (atomic_inc_return(&pc->count) == 1)
> + pc->power_on(pc, dev);
> +}
> +EXPORT_SYMBOL(dev_pc_power_on);
> +
> +void dev_pc_power_off(struct device *dev)
> +{
> + struct power_controller *pc = dev_pc(dev);
> +
> + if (!pc) return;
> +
> + if (!atomic_dec_return(&pc->count))
> + pc->power_off(pc, dev);
> +}
> +EXPORT_SYMBOL(dev_pc_power_off);
> diff --git a/include/linux/power_controller.h b/include/linux/power_controller.h
> new file mode 100644
> index 0000000..772f6d7
> --- /dev/null
> +++ b/include/linux/power_controller.h
> @@ -0,0 +1,48 @@
> +#ifndef _LINUX_POWER_CONTROLLER_H
> +#define _LINUX_POWER_CONTROLLER_H
> +
> +#include <linux/device.h>
> +
> +/*
> + * One power controller provides simple power on and power off.
> + *
> + * One power controller can bind to more than one device, which
> + * provides power logically, for example, we can think one usb port
> + * in hub provides power to the usb device attached to the port, even
> + * though the power is supplied actually by other ways, eg. the usb
> + * hub is a self-power device. From hardware view, more than one
> + * device can share one power domain, and power controller can power
> + * on if one of these devices need to provide power, and power off if
> + * all these devices don't need to provide power.
> + *
> + * The abstract is introduced to hide the implementation details of
> + * power controller, and only let platform code handle the details.
> + */
> +struct power_controller {
> + atomic_t count; /* Number of users with power "on" */
> + char *name;
> + void (*power_off)(struct power_controller *pc, struct device *dev);
> + void (*power_on)(struct power_controller *pc, struct device *dev);
> +};
> +
> +struct pc_dev_data {
> + unsigned long magic;
> + struct power_controller *pc;
> + void *dev_data;
> +};
> +
> +#ifdef CONFIG_POWER_CONTROLLER
> +extern struct pc_dev_data *dev_pc_get_data(struct device *dev);
> +extern int dev_pc_bind(struct power_controller *pc, struct device *dev, void *dev_data, int dev_data_size);
> +extern void dev_pc_unbind(struct power_controller *pc, struct device *dev);
> +extern void dev_pc_power_on(struct device *dev);
> +extern void dev_pc_power_off(struct device *dev);
> +#else
> +static inline struct pc_dev_data *dev_pc_get_data(struct device *dev){return NULL;}
> +static inline int dev_pc_bind(struct power_controller *pc, struct device *dev, void *dev_data,
> + int dev_data_size){return 0;}
> +static inline void dev_pc_unbind(struct power_controller *pc, struct device *dev){}
> +static inline void dev_pc_power_on(struct device *dev){}
> +static inline void dev_pc_power_off(struct device *dev){}
> +#endif
> +#endif /* _LINUX_POWER_CONTROLLER_H */
> diff --git a/kernel/power/Kconfig b/kernel/power/Kconfig
> index 5dfdc9e..51803a9 100644
> --- a/kernel/power/Kconfig
> +++ b/kernel/power/Kconfig
> @@ -255,6 +255,12 @@ config PM_OPP
> implementations a ready to use framework to manage OPPs.
> For more information, read <file:Documentation/power/opp.txt>
>
> +config POWER_CONTROLLER
> + bool "Power Controller"
> + ---help---
> + Power Controller is an abstract on power switch which can be
> + shared by more than more devices.
> +
> config PM_CLK
> def_bool y
> depends on PM && HAVE_CLK
>
--
Andy Green | TI Landing Team Leader
Linaro.org │ Open source software for ARM SoCs | Follow Linaro
http://facebook.com/pages/Linaro/155974581091106 -
http://twitter.com/#!/linaroorg - http://linaro.org/linaro-blog
--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* [RFC PATCH 4/5] arm: omap2: support port power on lan95xx devices
From: Ming Lei @ 2012-12-02 15:01 UTC (permalink / raw)
To: Alan Stern, Greg Kroah-Hartman
Cc: Lan Tianyu, Sarah Sharp, Rafael J. Wysocki, linux-pm,
Oliver Neukum, linux-omap, linux-usb, Ming Lei, Andy Green,
Roger Quadros, Felipe Balbi
In-Reply-To: <1354460467-28006-1-git-send-email-tom.leiming@gmail.com>
This patch defines power controller for powering on/off LAN95xx
USB hub and USB ethernet devices, and implements one match function
to associate the power controller with related USB port device.
The big problem of this approach is that it depends on the global
device ADD/DEL notifier.
Another idea of associating power controller with port device
is by introducing usb port driver, and move all this port power
control stuff from platform code to the port driver, which is just
what I think of and looks doable. The problem of the idea is that
port driver is per board, so maybe cause lots of platform sort of
code to be put under drivers/usb/port/, but this approach can avoid
global device ADD/DEL notifier.
I'd like to get some feedback about which one is better or other choice,
then I may do it in next cycle.
Cc: Andy Green <andy.green@linaro.org>
Cc: Roger Quadros <rogerq@ti.com>
Cc: Alan Stern <stern@rowland.harvard.edu>
Cc: Felipe Balbi <balbi@ti.com>
Signed-off-by: Ming Lei <tom.leiming@gmail.com>
---
arch/arm/mach-omap2/board-omap4panda.c | 99 +++++++++++++++++++++++++++++++-
1 file changed, 96 insertions(+), 3 deletions(-)
diff --git a/arch/arm/mach-omap2/board-omap4panda.c b/arch/arm/mach-omap2/board-omap4panda.c
index 5c8e9ce..3183832 100644
--- a/arch/arm/mach-omap2/board-omap4panda.c
+++ b/arch/arm/mach-omap2/board-omap4panda.c
@@ -32,6 +32,8 @@
#include <linux/usb/musb.h>
#include <linux/wl12xx.h>
#include <linux/platform_data/omap-abe-twl6040.h>
+#include <linux/power_controller.h>
+#include <linux/usb/port.h>
#include <asm/hardware/gic.h>
#include <asm/mach-types.h>
@@ -154,6 +156,99 @@ static struct gpio panda_ehci_gpios[] __initdata = {
{ GPIO_HUB_NRESET, GPIOF_OUT_INIT_LOW, "hub_nreset" },
};
+static void ehci_hub_power_on(struct power_controller *pc, struct device *dev)
+{
+ gpio_set_value(GPIO_HUB_NRESET, 1);
+ gpio_set_value(GPIO_HUB_POWER, 1);
+}
+
+static void ehci_hub_power_off(struct power_controller *pc, struct device *dev)
+{
+ gpio_set_value(GPIO_HUB_NRESET, 0);
+ gpio_set_value(GPIO_HUB_POWER, 0);
+}
+
+static struct usb_port_power_switch_data root_hub_port_data = {
+ .hub_tier = 0,
+ .port_number = 1,
+ .type = USB_PORT_CONNECT_TYPE_HARD_WIRED,
+};
+
+static struct usb_port_power_switch_data smsc_hub_port_data = {
+ .hub_tier = 1,
+ .port_number = 1,
+ .type = USB_PORT_CONNECT_TYPE_HARD_WIRED,
+};
+
+static struct power_controller pc = {
+ .name = "omap_hub_eth_pc",
+ .count = ATOMIC_INIT(0),
+ .power_on = ehci_hub_power_on,
+ .power_off = ehci_hub_power_off,
+};
+
+static inline int omap_ehci_hub_port(struct device *dev)
+{
+ /* we expect dev->parent points to ehcd controller */
+ if (dev->parent && !strcmp(dev_name(dev->parent), "ehci-omap.0"))
+ return 1;
+ return 0;
+}
+
+static inline int dev_pc_match(struct device *dev)
+{
+ struct device *anc;
+ int ret = 0;
+
+ if (likely(strcmp(dev_name(dev), "port1")))
+ goto exit;
+
+ if (dev->parent && (anc = dev->parent->parent)) {
+ if (omap_ehci_hub_port(anc)) {
+ ret = 1;
+ goto exit;
+ }
+
+ /* is it port of lan95xx hub? */
+ if ((anc = anc->parent) && omap_ehci_hub_port(anc)) {
+ ret = 2;
+ goto exit;
+ }
+ }
+exit:
+ return ret;
+}
+
+/*
+ * Notifications of device registration
+ */
+static int device_notify(struct notifier_block *nb, unsigned long action, void *data)
+{
+ struct device *dev = data;
+ int ret;
+
+ switch (action) {
+ case DEV_NOTIFY_ADD_DEVICE:
+ ret = dev_pc_match(dev);
+ if (likely(!ret))
+ goto exit;
+ if (ret == 1)
+ dev_pc_bind(&pc, dev, &root_hub_port_data, sizeof(root_hub_port_data));
+ else
+ dev_pc_bind(&pc, dev, &smsc_hub_port_data, sizeof(smsc_hub_port_data));
+ break;
+
+ case DEV_NOTIFY_DEL_DEVICE:
+ break;
+ }
+exit:
+ return 0;
+}
+
+static struct notifier_block usb_port_nb = {
+ .notifier_call = device_notify,
+};
+
static void __init omap4_ehci_init(void)
{
int ret;
@@ -178,12 +273,10 @@ static void __init omap4_ehci_init(void)
gpio_export(GPIO_HUB_POWER, 0);
gpio_export(GPIO_HUB_NRESET, 0);
- gpio_set_value(GPIO_HUB_NRESET, 1);
usbhs_init(&usbhs_bdata);
- /* enable power to hub */
- gpio_set_value(GPIO_HUB_POWER, 1);
+ dev_register_notifier(&usb_port_nb);
}
static struct omap_musb_board_data musb_board_data = {
--
1.7.9.5
^ permalink raw reply related
* [RFC PATCH 2/5] driver core: introduce global device ADD/DEL notifier
From: Ming Lei @ 2012-12-02 15:01 UTC (permalink / raw)
To: Alan Stern, Greg Kroah-Hartman
Cc: Lan Tianyu, Sarah Sharp, Rafael J. Wysocki, linux-pm,
Oliver Neukum, linux-omap, linux-usb, Ming Lei, Andy Green,
Roger Quadros, Felipe Balbi
In-Reply-To: <1354460467-28006-1-git-send-email-tom.leiming@gmail.com>
The global device ADD/DEL notifier is introduced so that
some platform code can bind some device resource to the
device. When this platform code runs, there is no any bus
information about the device, so have to resort to the
global notifier.
Cc: Andy Green <andy.green@linaro.org>
Cc: Roger Quadros <rogerq@ti.com>
Cc: Alan Stern <stern@rowland.harvard.edu>
Cc: Felipe Balbi <balbi@ti.com>
Signed-off-by: Ming Lei <tom.leiming@gmail.com>
---
drivers/base/core.c | 21 +++++++++++++++++++++
include/linux/device.h | 13 +++++++++++++
2 files changed, 34 insertions(+)
diff --git a/drivers/base/core.c b/drivers/base/core.c
index a235085..37f11ff 100644
--- a/drivers/base/core.c
+++ b/drivers/base/core.c
@@ -43,6 +43,9 @@ static __init int sysfs_deprecated_setup(char *arg)
early_param("sysfs.deprecated", sysfs_deprecated_setup);
#endif
+/* global device nofifier */
+struct blocking_notifier_head dev_notifier;
+
int (*platform_notify)(struct device *dev) = NULL;
int (*platform_notify_remove)(struct device *dev) = NULL;
static struct kobject *dev_kobj;
@@ -1041,6 +1044,8 @@ int device_add(struct device *dev)
if (platform_notify)
platform_notify(dev);
+ blocking_notifier_call_chain(&dev_notifier, DEV_NOTIFY_ADD_DEVICE, dev);
+
error = device_create_file(dev, &uevent_attr);
if (error)
goto attrError;
@@ -1228,6 +1233,7 @@ void device_del(struct device *dev)
device_pm_remove(dev);
driver_deferred_probe_del(dev);
+ blocking_notifier_call_chain(&dev_notifier, DEV_NOTIFY_DEL_DEVICE, dev);
/* Notify the platform of the removal, in case they
* need to do anything...
*/
@@ -1376,6 +1382,8 @@ struct device *device_find_child(struct device *parent, void *data,
int __init devices_init(void)
{
+ BLOCKING_INIT_NOTIFIER_HEAD(&dev_notifier);
+
devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL);
if (!devices_kset)
return -ENOMEM;
@@ -1995,6 +2003,19 @@ int dev_printk(const char *level, const struct device *dev,
}
EXPORT_SYMBOL(dev_printk);
+/* The notifier should be avoided as far as possible */
+int dev_register_notifier(struct notifier_block *nb)
+{
+ return blocking_notifier_chain_register(&dev_notifier, nb);
+}
+EXPORT_SYMBOL_GPL(dev_register_notifier);
+
+int dev_unregister_notifier(struct notifier_block *nb)
+{
+ return blocking_notifier_chain_unregister(&dev_notifier, nb);
+}
+EXPORT_SYMBOL_GPL(dev_unregister_notifier);
+
#define define_dev_printk_level(func, kern_level) \
int func(const struct device *dev, const char *fmt, ...) \
{ \
diff --git a/include/linux/device.h b/include/linux/device.h
index 43dcda9..aeb54f6 100644
--- a/include/linux/device.h
+++ b/include/linux/device.h
@@ -179,6 +179,19 @@ extern int bus_unregister_notifier(struct bus_type *bus,
#define BUS_NOTIFY_UNBOUND_DRIVER 0x00000006 /* driver is unbound
from the device */
+/* All 2 notifers below get called with the target struct device *
+ * as an argument. Note that those functions are likely to be called
+ * with the device lock held in the core, so be careful.
+ */
+#define DEV_NOTIFY_ADD_DEVICE 0x00000001 /* device added */
+#define DEV_NOTIFY_DEL_DEVICE 0x00000002 /* device removed */
+extern int dev_register_notifier(struct notifier_block *nb);
+extern int dev_unregister_notifier(struct notifier_block *nb);
+
+extern struct kset *bus_get_kset(struct bus_type *bus);
+extern struct klist *bus_get_device_klist(struct bus_type *bus);
+
+
extern struct kset *bus_get_kset(struct bus_type *bus);
extern struct klist *bus_get_device_klist(struct bus_type *bus);
--
1.7.9.5
^ permalink raw reply related
* [RFC PATCH 5/5] usb: omap ehci: remove all regulator control from ehci omap
From: Ming Lei @ 2012-12-02 15:01 UTC (permalink / raw)
To: Alan Stern, Greg Kroah-Hartman
Cc: Lan Tianyu, Sarah Sharp, Rafael J. Wysocki, linux-pm,
Oliver Neukum, linux-omap, linux-usb, Andy Green, Roger Quadros,
Felipe Balbi, Ming Lei
In-Reply-To: <1354460467-28006-1-git-send-email-tom.leiming@gmail.com>
From: Andy Green <andy.green@linaro.org>
This series migrates it to the hub driver as suggested by
Felipe Balbi.
Cc: Andy Green <andy.green@linaro.org>
Cc: Roger Quadros <rogerq@ti.com>
Cc: Alan Stern <stern@rowland.harvard.edu>
Cc: Felipe Balbi <balbi@ti.com>
Signed-off-by: Andy Green <andy.green@linaro.org>
Signed-off-by: Ming Lei <tom.leiming@gmail.com>
---
drivers/usb/host/ehci-omap.c | 36 ------------------------------------
1 file changed, 36 deletions(-)
diff --git a/drivers/usb/host/ehci-omap.c b/drivers/usb/host/ehci-omap.c
index ac17a7c..d25e39e 100644
--- a/drivers/usb/host/ehci-omap.c
+++ b/drivers/usb/host/ehci-omap.c
@@ -39,7 +39,6 @@
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/usb/ulpi.h>
-#include <linux/regulator/consumer.h>
#include <linux/pm_runtime.h>
#include <linux/gpio.h>
#include <linux/clk.h>
@@ -150,19 +149,6 @@ static int omap_ehci_init(struct usb_hcd *hcd)
return rc;
}
-static void disable_put_regulator(
- struct ehci_hcd_omap_platform_data *pdata)
-{
- int i;
-
- for (i = 0 ; i < OMAP3_HS_USB_PORTS ; i++) {
- if (pdata->regulator[i]) {
- regulator_disable(pdata->regulator[i]);
- regulator_put(pdata->regulator[i]);
- }
- }
-}
-
/* configure so an HC device and id are always provided */
/* always called with process context; sleeping is OK */
@@ -176,14 +162,11 @@ static void disable_put_regulator(
static int ehci_hcd_omap_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
- struct ehci_hcd_omap_platform_data *pdata = dev->platform_data;
struct resource *res;
struct usb_hcd *hcd;
void __iomem *regs;
int ret = -ENODEV;
int irq;
- int i;
- char supply[7];
if (usb_disabled())
return -ENODEV;
@@ -224,23 +207,6 @@ static int ehci_hcd_omap_probe(struct platform_device *pdev)
hcd->rsrc_len = resource_size(res);
hcd->regs = regs;
- /* get ehci regulator and enable */
- for (i = 0 ; i < OMAP3_HS_USB_PORTS ; i++) {
- if (pdata->port_mode[i] != OMAP_EHCI_PORT_MODE_PHY) {
- pdata->regulator[i] = NULL;
- continue;
- }
- snprintf(supply, sizeof(supply), "hsusb%d", i);
- pdata->regulator[i] = regulator_get(dev, supply);
- if (IS_ERR(pdata->regulator[i])) {
- pdata->regulator[i] = NULL;
- dev_dbg(dev,
- "failed to get ehci port%d regulator\n", i);
- } else {
- regulator_enable(pdata->regulator[i]);
- }
- }
-
pm_runtime_enable(dev);
pm_runtime_get_sync(dev);
@@ -266,7 +232,6 @@ static int ehci_hcd_omap_probe(struct platform_device *pdev)
return 0;
err_pm_runtime:
- disable_put_regulator(pdata);
pm_runtime_put_sync(dev);
usb_put_hcd(hcd);
@@ -291,7 +256,6 @@ static int ehci_hcd_omap_remove(struct platform_device *pdev)
struct ehci_hcd_omap_platform_data *pdata = dev->platform_data;
usb_remove_hcd(hcd);
- disable_put_regulator(dev->platform_data);
iounmap(hcd->regs);
usb_put_hcd(hcd);
--
1.7.9.5
^ permalink raw reply related
* [RFC PATCH 3/5] USB: hub: apply power controller on usb port
From: Ming Lei @ 2012-12-02 15:01 UTC (permalink / raw)
To: Alan Stern, Greg Kroah-Hartman
Cc: Lan Tianyu, Sarah Sharp, Rafael J. Wysocki,
linux-pm-u79uwXL29TY76Z2rM5mHXA, Oliver Neukum,
linux-omap-u79uwXL29TY76Z2rM5mHXA,
linux-usb-u79uwXL29TY76Z2rM5mHXA, Ming Lei, Andy Green,
Roger Quadros, Felipe Balbi
In-Reply-To: <1354460467-28006-1-git-send-email-tom.leiming-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
This patch applies the power controller on usb port, so that
hub driver can power on one port which isn't provided power
by bus.
Cc: Andy Green <andy.green-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
Cc: Roger Quadros <rogerq-l0cyMroinI0@public.gmane.org>
Cc: Alan Stern <stern-nwvwT67g6+6dFdvTe/nMLpVzexx5G7lz@public.gmane.org>
Cc: Felipe Balbi <balbi-l0cyMroinI0@public.gmane.org>
Signed-off-by: Ming Lei <tom.leiming-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
---
drivers/usb/core/hub.c | 31 ++++++++++++++++++++++++++++---
include/linux/usb/port.h | 16 ++++++++++++++++
2 files changed, 44 insertions(+), 3 deletions(-)
create mode 100644 include/linux/usb/port.h
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index a815fd2..f8075d7 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -26,6 +26,8 @@
#include <linux/mutex.h>
#include <linux/freezer.h>
#include <linux/random.h>
+#include <linux/power_controller.h>
+#include <linux/usb/port.h>
#include <asm/uaccess.h>
#include <asm/byteorder.h>
@@ -848,8 +850,15 @@ static unsigned hub_power_on(struct usb_hub *hub, bool do_delay)
else
dev_dbg(hub->intfdev, "trying to enable port power on "
"non-switchable hub\n");
- for (port1 = 1; port1 <= hub->descriptor->bNbrPorts; port1++)
+ for (port1 = 1; port1 <= hub->descriptor->bNbrPorts; port1++) {
+ struct usb_port *port = hub->ports[port1 - 1];
+ struct pc_dev_data *pc_data = dev_pc_get_data(&port->dev);
+
+ /* The power supply for this port isn't managed by bus only */
+ if (pc_data)
+ dev_pc_power_on(&port->dev);
set_port_feature(hub->hdev, port1, USB_PORT_FEAT_POWER);
+ }
/* Wait at least 100 msec for power to become stable */
delay = max(pgood_delay, (unsigned) 100);
@@ -1541,10 +1550,20 @@ static int hub_configure(struct usb_hub *hub,
if (hub->has_indicators && blinkenlights)
hub->indicator [0] = INDICATOR_CYCLE;
- for (i = 0; i < hdev->maxchild; i++)
+ for (i = 0; i < hdev->maxchild; i++) {
if (usb_hub_create_port_device(hub, i + 1) < 0)
dev_err(hub->intfdev,
"couldn't create port%d device.\n", i + 1);
+ else {
+ struct usb_port *port = hub->ports[i];
+ struct pc_dev_data *pc_data = dev_pc_get_data(&port->dev);
+ if (pc_data && pc_data->dev_data) {
+ struct usb_port_power_switch_data *up =
+ pc_data->dev_data;
+ usb_set_hub_port_connect_type(hdev, i + 1, up->type);
+ }
+ }
+ }
hub_activate(hub, HUB_INIT);
return 0;
@@ -1587,8 +1606,14 @@ static void hub_disconnect(struct usb_interface *intf)
usb_set_intfdata (intf, NULL);
- for (i = 0; i < hdev->maxchild; i++)
+ for (i = 0; i < hdev->maxchild; i++) {
+ struct usb_port *port = hub->ports[i];
+ struct pc_dev_data *pc_data = dev_pc_get_data(&port->dev);
+ if (pc_data)
+ dev_pc_power_off(&port->dev);
+
usb_hub_remove_port_device(hub, i + 1);
+ }
hub->hdev->maxchild = 0;
if (hub->hdev->speed == USB_SPEED_HIGH)
diff --git a/include/linux/usb/port.h b/include/linux/usb/port.h
new file mode 100644
index 0000000..a853d5e
--- /dev/null
+++ b/include/linux/usb/port.h
@@ -0,0 +1,16 @@
+#ifndef __USB_CORE_PORT_H
+#define __USB_CORE_PORT_H
+
+#include <linux/usb.h>
+
+/*
+ * Only used for describing power switch which provide power to
+ * hardwired self-powered device which attached to the port
+ */
+struct usb_port_power_switch_data {
+ int hub_tier; /* root hub is zero, next tier is 1, ... */
+ int port_number;
+ enum usb_port_connect_type type;
+};
+
+#endif
--
1.7.9.5
--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related
* [RFC PATCH 1/5] Device Power: introduce power controller
From: Ming Lei @ 2012-12-02 15:01 UTC (permalink / raw)
To: Alan Stern, Greg Kroah-Hartman
Cc: Lan Tianyu, Sarah Sharp, Rafael J. Wysocki, linux-pm,
Oliver Neukum, linux-omap, linux-usb, Ming Lei, Andy Green,
Roger Quadros, Felipe Balbi
In-Reply-To: <1354460467-28006-1-git-send-email-tom.leiming@gmail.com>
Power controller is an abstract on simple power on/off switch.
One power controller can bind to more than one device, which
provides power logically, for example, we can think one usb port
in hub provides power to the usb device attached to the port, even
though the power is supplied actually by other ways, eg. the usb
hub is a self-power device. From hardware view, more than one
device can share one power domain, and power controller can power
on if one of these devices need to provide power, and power off if
all these devices don't need to provide power.
Cc: "Rafael J. Wysocki" <rjw@sisk.pl>
Cc: Andy Green <andy.green@linaro.org>
Cc: Roger Quadros <rogerq@ti.com>
Cc: Alan Stern <stern@rowland.harvard.edu>
Cc: Felipe Balbi <balbi@ti.com>
Signed-off-by: Ming Lei <tom.leiming@gmail.com>
---
drivers/base/power/Makefile | 1 +
drivers/base/power/power_controller.c | 110 +++++++++++++++++++++++++++++++++
include/linux/power_controller.h | 48 ++++++++++++++
kernel/power/Kconfig | 6 ++
4 files changed, 165 insertions(+)
create mode 100644 drivers/base/power/power_controller.c
create mode 100644 include/linux/power_controller.h
diff --git a/drivers/base/power/Makefile b/drivers/base/power/Makefile
index 2e58ebb..c08bfc9 100644
--- a/drivers/base/power/Makefile
+++ b/drivers/base/power/Makefile
@@ -5,5 +5,6 @@ obj-$(CONFIG_PM_TRACE_RTC) += trace.o
obj-$(CONFIG_PM_OPP) += opp.o
obj-$(CONFIG_PM_GENERIC_DOMAINS) += domain.o domain_governor.o
obj-$(CONFIG_HAVE_CLK) += clock_ops.o
+obj-$(CONFIG_POWER_CONTROLLER) += power_controller.o
ccflags-$(CONFIG_DEBUG_DRIVER) := -DDEBUG
diff --git a/drivers/base/power/power_controller.c b/drivers/base/power/power_controller.c
new file mode 100644
index 0000000..d7671fb
--- /dev/null
+++ b/drivers/base/power/power_controller.c
@@ -0,0 +1,110 @@
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/power_controller.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+#include <linux/export.h>
+
+static DEFINE_MUTEX(pc_lock);
+
+static void pc_devm_release(struct device *dev, void *res)
+{
+}
+
+static int pc_devm_match(struct device *dev, void *res, void *match_data)
+{
+ struct pc_dev_data *data = res;
+
+ return (data->magic == (unsigned long)pc_devm_release);
+}
+
+static struct pc_dev_data *dev_pc_data(struct device *dev)
+{
+ struct pc_dev_data *data;
+
+ data = devres_find(dev, pc_devm_release, pc_devm_match, NULL);
+ return data;
+}
+
+static int pc_add_devm_data(struct device *dev, struct power_controller *pc,
+ void *dev_data, int dev_data_size)
+{
+ struct pc_dev_data *data;
+ int ret = 0;
+
+ mutex_lock(&pc_lock);
+
+ /* each device should only have one power controller resource */
+ data = dev_pc_data(dev);
+ if (data) {
+ ret = 1;
+ goto exit;
+ }
+
+ data = devres_alloc(pc_devm_release, sizeof(struct pc_dev_data) +
+ dev_data_size, GFP_KERNEL);
+ if (!data) {
+ ret = -ENOMEM;
+ goto exit;
+ }
+
+ data->magic = (unsigned long)pc_devm_release;
+ data->pc = pc;
+ data->dev_data = &data[1];
+ memcpy(data->dev_data, dev_data, dev_data_size);
+ devres_add(dev, data);
+
+exit:
+ mutex_unlock(&pc_lock);
+ return ret;
+}
+
+static struct power_controller *dev_pc(struct device *dev)
+{
+ struct pc_dev_data *data = dev_pc_data(dev);
+
+ if (data)
+ return data->pc;
+ return NULL;
+}
+
+struct pc_dev_data *dev_pc_get_data(struct device *dev)
+{
+ struct pc_dev_data *data = dev_pc_data(dev);
+ return data;
+}
+EXPORT_SYMBOL(dev_pc_get_data);
+
+int dev_pc_bind(struct power_controller *pc, struct device *dev,
+ void *dev_data, int dev_data_size)
+{
+ return pc_add_devm_data(dev, pc, dev_data, dev_data_size);
+}
+EXPORT_SYMBOL(dev_pc_bind);
+
+void dev_pc_unbind(struct power_controller *pc, struct device *dev)
+{
+}
+EXPORT_SYMBOL(dev_pc_unbind);
+
+void dev_pc_power_on(struct device *dev)
+{
+ struct power_controller *pc = dev_pc(dev);
+
+ if (!pc) return;
+
+ if (atomic_inc_return(&pc->count) == 1)
+ pc->power_on(pc, dev);
+}
+EXPORT_SYMBOL(dev_pc_power_on);
+
+void dev_pc_power_off(struct device *dev)
+{
+ struct power_controller *pc = dev_pc(dev);
+
+ if (!pc) return;
+
+ if (!atomic_dec_return(&pc->count))
+ pc->power_off(pc, dev);
+}
+EXPORT_SYMBOL(dev_pc_power_off);
diff --git a/include/linux/power_controller.h b/include/linux/power_controller.h
new file mode 100644
index 0000000..772f6d7
--- /dev/null
+++ b/include/linux/power_controller.h
@@ -0,0 +1,48 @@
+#ifndef _LINUX_POWER_CONTROLLER_H
+#define _LINUX_POWER_CONTROLLER_H
+
+#include <linux/device.h>
+
+/*
+ * One power controller provides simple power on and power off.
+ *
+ * One power controller can bind to more than one device, which
+ * provides power logically, for example, we can think one usb port
+ * in hub provides power to the usb device attached to the port, even
+ * though the power is supplied actually by other ways, eg. the usb
+ * hub is a self-power device. From hardware view, more than one
+ * device can share one power domain, and power controller can power
+ * on if one of these devices need to provide power, and power off if
+ * all these devices don't need to provide power.
+ *
+ * The abstract is introduced to hide the implementation details of
+ * power controller, and only let platform code handle the details.
+ */
+struct power_controller {
+ atomic_t count; /* Number of users with power "on" */
+ char *name;
+ void (*power_off)(struct power_controller *pc, struct device *dev);
+ void (*power_on)(struct power_controller *pc, struct device *dev);
+};
+
+struct pc_dev_data {
+ unsigned long magic;
+ struct power_controller *pc;
+ void *dev_data;
+};
+
+#ifdef CONFIG_POWER_CONTROLLER
+extern struct pc_dev_data *dev_pc_get_data(struct device *dev);
+extern int dev_pc_bind(struct power_controller *pc, struct device *dev, void *dev_data, int dev_data_size);
+extern void dev_pc_unbind(struct power_controller *pc, struct device *dev);
+extern void dev_pc_power_on(struct device *dev);
+extern void dev_pc_power_off(struct device *dev);
+#else
+static inline struct pc_dev_data *dev_pc_get_data(struct device *dev){return NULL;}
+static inline int dev_pc_bind(struct power_controller *pc, struct device *dev, void *dev_data,
+ int dev_data_size){return 0;}
+static inline void dev_pc_unbind(struct power_controller *pc, struct device *dev){}
+static inline void dev_pc_power_on(struct device *dev){}
+static inline void dev_pc_power_off(struct device *dev){}
+#endif
+#endif /* _LINUX_POWER_CONTROLLER_H */
diff --git a/kernel/power/Kconfig b/kernel/power/Kconfig
index 5dfdc9e..51803a9 100644
--- a/kernel/power/Kconfig
+++ b/kernel/power/Kconfig
@@ -255,6 +255,12 @@ config PM_OPP
implementations a ready to use framework to manage OPPs.
For more information, read <file:Documentation/power/opp.txt>
+config POWER_CONTROLLER
+ bool "Power Controller"
+ ---help---
+ Power Controller is an abstract on power switch which can be
+ shared by more than more devices.
+
config PM_CLK
def_bool y
depends on PM && HAVE_CLK
--
1.7.9.5
^ permalink raw reply related
* [RFC PATCH 0/5] USB: prepare for support port power off on non-ACPI device
From: Ming Lei @ 2012-12-02 15:01 UTC (permalink / raw)
To: Alan Stern, Greg Kroah-Hartman
Cc: Lan Tianyu, Sarah Sharp, Rafael J. Wysocki,
linux-pm-u79uwXL29TY76Z2rM5mHXA, Oliver Neukum,
linux-omap-u79uwXL29TY76Z2rM5mHXA,
linux-usb-u79uwXL29TY76Z2rM5mHXA
Hi,
This patch set trys to prepare for support of usb port power
off mechanism on non-ACPI devices. The port power off mechasnism
has been discussed for some time[1][2], and support for ACPI
devices has been in shape:
- usb port device is introduced
- port connect type is introduced and set via ACPI
- usb root port power can be switched on/off via ACPI
The above has been merged to upstream, and Tianyu is pushing
patches[3] to enable auto port power feature.
So it is time to discuss how to support it for non-ACPI usb
devices, and there are many embedded system in which some of
usb devices are hardwired(often self-powered) into board, such as,
beagle board, pandaboard, Arndaleboard, etc. So powering on/off
these devices may involve platform dependent way, just like
ACPI power control is used in Tianyu's patch.
This patch set introduces power controller to hide the power
switch implementation detail to usb subsytem, in theroy it can
be used to other devices and subsystem, and the power controller
instance is stored as device's resource. So the power controller
can be used to power on/off the power supply from the usb port,
and makes support of port power off possible on non-ACPI devices.
Associating power controller with one device(such as usb port)
which providing power logically is not an easy thing, because
platform dependent knowledge(port topology, hardware power domain
on the port, ...) has to be applied and the usb port device isn't
a platform device and it is created during hub probe(). So looks
there is no general approach to do it. This patchset introduces
one global device ADD/DEL notifier in driver core, so when a new
device is added, one match function in platform code is called to
check if the device can be associated with the power controller.
This patchset implements the match function on OMAP4 based Pandaboard,
and defines the power controller to control power for lan95xx hub
and ethernet device.
Another idea of associating power controller with port device
is by introducing usb port driver, and move all this port power
control stuff from platform code to port driver, which is just
what I think of and looks doable. I'd like to get some feedback
about which one is better, then I can do it in next cycle.
Also the two things on Pandaboard have been done at the same time:
- powering on the two devices can be delayed to the ehci-omap
root hub probing.
- the regulatores which are used for these two hardwired and
self-powered devices are moved out from ehci-omap driver
In fact, Andy Green has been working[4][5] on the above two things,
but which isn't the main purpose of these patches, and they might be
seen as byproduct of the patchset. Inevitably, there are some overlap
between Andy's work and this patchset, hope we can compare, discuss
and figure out the perfect solution.
arch/arm/mach-omap2/board-omap4panda.c | 99 +++++++++++++++++++++++++++-
drivers/base/core.c | 21 ++++++
drivers/base/power/Makefile | 1 +
drivers/base/power/power_controller.c | 110 ++++++++++++++++++++++++++++++++
drivers/usb/core/hub.c | 31 ++++++++-
drivers/usb/host/ehci-omap.c | 36 -----------
include/linux/device.h | 13 ++++
include/linux/power_controller.h | 48 ++++++++++++++
include/linux/usb/port.h | 16 +++++
kernel/power/Kconfig | 6 ++
10 files changed, 339 insertions(+), 42 deletions(-)
Thanks,
--
Ming Lei
[1], http://marc.info/?t=134818354900003&r=1&w=2
[2], http://marc.info/?t=134303384500003&r=1&w=2
[3], http://marc.info/?l=linux-usb&m=135314427413307&w=2
[4], http://marc.info/?t=135393394700003&r=1&w=2
[5], http://www.spinics.net/lists/linux-omap/msg83191.html
--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* [PATCH 33/34] thermal: rcar: fixup the unit of temperature
From: Zhang Rui @ 2012-12-02 14:45 UTC (permalink / raw)
To: linux-pm; +Cc: Kuninori Morimoto, Zhang Rui
In-Reply-To: <1354459508-3707-1-git-send-email-rui.zhang@intel.com>
From: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
The unit of temperature is Milli-Celsius.
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Signed-off-by: Zhang Rui <rui.zhang@intel.com>
---
drivers/thermal/rcar_thermal.c | 4 +++-
1 files changed, 3 insertions(+), 1 deletions(-)
diff --git a/drivers/thermal/rcar_thermal.c b/drivers/thermal/rcar_thermal.c
index 81dce23..f2678ff 100644
--- a/drivers/thermal/rcar_thermal.c
+++ b/drivers/thermal/rcar_thermal.c
@@ -43,6 +43,8 @@ struct rcar_thermal_priv {
u32 comp;
};
+#define MCELSIUS(temp) ((temp) * 1000)
+
/*
* basic functions
*/
@@ -169,7 +171,7 @@ static int rcar_thermal_get_temp(struct thermal_zone_device *zone,
}
}
- *temp = tmp;
+ *temp = MCELSIUS(tmp);
return 0;
}
--
1.7.7.6
^ permalink raw reply related
* [PATCH 34/34] thermal: rcar: add rcar_zone_to_priv() macro
From: Zhang Rui @ 2012-12-02 14:45 UTC (permalink / raw)
To: linux-pm; +Cc: Kuninori Morimoto, Zhang Rui
In-Reply-To: <1354459508-3707-1-git-send-email-rui.zhang@intel.com>
From: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
This patch adds rcar_zone_to_priv()
which is a helper macro for gettign private data.
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Signed-off-by: Zhang Rui <rui.zhang@intel.com>
---
drivers/thermal/rcar_thermal.c | 3 ++-
1 files changed, 2 insertions(+), 1 deletions(-)
diff --git a/drivers/thermal/rcar_thermal.c b/drivers/thermal/rcar_thermal.c
index f2678ff..90db951 100644
--- a/drivers/thermal/rcar_thermal.c
+++ b/drivers/thermal/rcar_thermal.c
@@ -44,6 +44,7 @@ struct rcar_thermal_priv {
};
#define MCELSIUS(temp) ((temp) * 1000)
+#define rcar_zone_to_priv(zone) (zone->devdata)
/*
* basic functions
@@ -98,7 +99,7 @@ static void rcar_thermal_bset(struct rcar_thermal_priv *priv, u32 reg,
static int rcar_thermal_get_temp(struct thermal_zone_device *zone,
unsigned long *temp)
{
- struct rcar_thermal_priv *priv = zone->devdata;
+ struct rcar_thermal_priv *priv = rcar_zone_to_priv(zone);
int val, min, max, tmp;
tmp = -200; /* default */
--
1.7.7.6
^ permalink raw reply related
* [PATCH 32/34] thermal: cpu cooling: allow module builds
From: Zhang Rui @ 2012-12-02 14:45 UTC (permalink / raw)
To: linux-pm; +Cc: Eduardo Valentin, Zhang Rui
In-Reply-To: <1354459508-3707-1-git-send-email-rui.zhang@intel.com>
From: Eduardo Valentin <eduardo.valentin@ti.com>
As thermal drivers can be built as modules and also
the thermal framework itself, building cpu cooling
only as built-in can cause linking errors. For instance:
* Generic Thermal sysfs driver
*
Generic Thermal sysfs driver (THERMAL) [M/n/y/?] m
generic cpu cooling support (CPU_THERMAL) [N/y/?] (NEW) y
with the following drive:
CONFIG_OMAP_BANDGAP=m
generates:
ERROR: "cpufreq_cooling_unregister" [drivers/staging/omap-thermal/omap-thermal.ko] undefined!
ERROR: "cpufreq_cooling_register" [drivers/staging/omap-thermal/omap-thermal.ko] undefined!
This patch changes cpu cooling driver to allow it
to be built as module.
Reported-by: Enric Balletbo i Serra <eballetbo@gmail.com>
Signed-off-by: Eduardo Valentin <eduardo.valentin@ti.com>
Reviewed-by: Durgadoss R <durgadoss.r@intel.com>
Signed-off-by: Zhang Rui <rui.zhang@intel.com>
---
drivers/thermal/Kconfig | 2 +-
include/linux/cpu_cooling.h | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index d96da07..8636fae 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -67,7 +67,7 @@ config USER_SPACE
Enable this to let the user space manage the platform thermals.
config CPU_THERMAL
- bool "generic cpu cooling support"
+ tristate "generic cpu cooling support"
depends on CPU_FREQ
select CPU_FREQ_TABLE
help
diff --git a/include/linux/cpu_cooling.h b/include/linux/cpu_cooling.h
index b30cc79c..40b4ef5 100644
--- a/include/linux/cpu_cooling.h
+++ b/include/linux/cpu_cooling.h
@@ -29,7 +29,7 @@
#define CPUFREQ_COOLING_START 0
#define CPUFREQ_COOLING_STOP 1
-#ifdef CONFIG_CPU_THERMAL
+#if defined(CONFIG_CPU_THERMAL) || defined(CONFIG_CPU_THERMAL_MODULE)
/**
* cpufreq_cooling_register - function to create cpufreq cooling device.
* @clip_cpus: cpumask of cpus where the frequency constraints will happen
--
1.7.7.6
^ permalink raw reply related
* [PATCH 31/34] thermal: cpu cooling: use const parameter while registering
From: Zhang Rui @ 2012-12-02 14:45 UTC (permalink / raw)
To: linux-pm; +Cc: Eduardo Valentin, Zhang Rui
In-Reply-To: <1354459508-3707-1-git-send-email-rui.zhang@intel.com>
From: Eduardo Valentin <eduardo.valentin@ti.com>
There are predefined cpu_masks that are const data structures.
This patch changes the cpu cooling register function so that
those const cpu_masks can be used, without compilation warnings.
include/linux/cpumask.h
* The following particular system cpumasks and operations manage
* possible, present, active and online cpus.
*
* cpu_possible_mask- has bit 'cpu' set iff cpu is populatable
* cpu_present_mask - has bit 'cpu' set iff cpu is populated
* cpu_online_mask - has bit 'cpu' set iff cpu available to scheduler
* cpu_active_mask - has bit 'cpu' set iff cpu available to migration
*
Signed-off-by: Eduardo Valentin <eduardo.valentin@ti.com>
Signed-off-by: Zhang Rui <rui.zhang@intel.com>
---
drivers/thermal/cpu_cooling.c | 2 +-
include/linux/cpu_cooling.h | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c
index 6f94c2c..836828e 100644
--- a/drivers/thermal/cpu_cooling.c
+++ b/drivers/thermal/cpu_cooling.c
@@ -313,7 +313,7 @@ static struct notifier_block thermal_cpufreq_notifier_block = {
* @clip_cpus: cpumask of cpus where the frequency constraints will happen.
*/
struct thermal_cooling_device *cpufreq_cooling_register(
- struct cpumask *clip_cpus)
+ const struct cpumask *clip_cpus)
{
struct thermal_cooling_device *cool_dev;
struct cpufreq_cooling_device *cpufreq_dev = NULL;
diff --git a/include/linux/cpu_cooling.h b/include/linux/cpu_cooling.h
index 8515301..b30cc79c 100644
--- a/include/linux/cpu_cooling.h
+++ b/include/linux/cpu_cooling.h
@@ -35,7 +35,7 @@
* @clip_cpus: cpumask of cpus where the frequency constraints will happen
*/
struct thermal_cooling_device *cpufreq_cooling_register(
- struct cpumask *clip_cpus);
+ const struct cpumask *clip_cpus);
/**
* cpufreq_cooling_unregister - function to remove cpufreq cooling device.
@@ -44,7 +44,7 @@ struct thermal_cooling_device *cpufreq_cooling_register(
void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev);
#else /* !CONFIG_CPU_THERMAL */
static inline struct thermal_cooling_device *cpufreq_cooling_register(
- struct cpumask *clip_cpus)
+ const struct cpumask *clip_cpus)
{
return NULL;
}
--
1.7.7.6
^ permalink raw reply related
* [PATCH 30/34] Thermal: Add ST-Ericsson DB8500 thermal properties and platform data.
From: Zhang Rui @ 2012-12-02 14:45 UTC (permalink / raw)
To: linux-pm; +Cc: hongbo.zhang, Zhang Rui
In-Reply-To: <1354459508-3707-1-git-send-email-rui.zhang@intel.com>
From: "hongbo.zhang" <hongbo.zhang@linaro.com>
This patch adds device tree properties for ST-Ericsson DB8500 thermal driver,
also adds the platform data to support the old fashion.
Signed-off-by: hongbo.zhang <hongbo.zhang@linaro.com>
Reviewed-by: Viresh Kumar <viresh.kumar@linaro.org>
Acked-by: Linus Walleij <linus.walleij@linaro.org>
Signed-off-by: Zhang Rui <rui.zhang@intel.com>
---
arch/arm/boot/dts/dbx5x0.dtsi | 14 ++++++++
arch/arm/boot/dts/snowball.dts | 31 +++++++++++++++++
arch/arm/configs/u8500_defconfig | 2 +
arch/arm/mach-ux500/board-mop500.c | 64 ++++++++++++++++++++++++++++++++++++
4 files changed, 111 insertions(+), 0 deletions(-)
diff --git a/arch/arm/boot/dts/dbx5x0.dtsi b/arch/arm/boot/dts/dbx5x0.dtsi
index 4b0e0ca..731086b 100644
--- a/arch/arm/boot/dts/dbx5x0.dtsi
+++ b/arch/arm/boot/dts/dbx5x0.dtsi
@@ -203,6 +203,14 @@
reg = <0x80157450 0xC>;
};
+ thermal@801573c0 {
+ compatible = "stericsson,db8500-thermal";
+ reg = <0x801573c0 0x40>;
+ interrupts = <21 0x4>, <22 0x4>;
+ interrupt-names = "IRQ_HOTMON_LOW", "IRQ_HOTMON_HIGH";
+ status = "disabled";
+ };
+
db8500-prcmu-regulators {
compatible = "stericsson,db8500-prcmu-regulator";
@@ -660,5 +668,11 @@
ranges = <0 0x50000000 0x4000000>;
status = "disabled";
};
+
+ cpufreq-cooling {
+ compatible = "stericsson,db8500-cpufreq-cooling";
+ status = "disabled";
+ };
+
};
};
diff --git a/arch/arm/boot/dts/snowball.dts b/arch/arm/boot/dts/snowball.dts
index 702c0ba..c6f85f0 100644
--- a/arch/arm/boot/dts/snowball.dts
+++ b/arch/arm/boot/dts/snowball.dts
@@ -99,6 +99,33 @@
status = "okay";
};
+ prcmu@80157000 {
+ thermal@801573c0 {
+ num-trips = <4>;
+
+ trip0-temp = <70000>;
+ trip0-type = "active";
+ trip0-cdev-num = <1>;
+ trip0-cdev-name0 = "thermal-cpufreq-0";
+
+ trip1-temp = <75000>;
+ trip1-type = "active";
+ trip1-cdev-num = <1>;
+ trip1-cdev-name0 = "thermal-cpufreq-0";
+
+ trip2-temp = <80000>;
+ trip2-type = "active";
+ trip2-cdev-num = <1>;
+ trip2-cdev-name0 = "thermal-cpufreq-0";
+
+ trip3-temp = <85000>;
+ trip3-type = "critical";
+ trip3-cdev-num = <0>;
+
+ status = "okay";
+ };
+ };
+
external-bus@50000000 {
status = "okay";
@@ -183,5 +210,9 @@
reg = <0x33>;
};
};
+
+ cpufreq-cooling {
+ status = "okay";
+ };
};
};
diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig
index da68454..250625d 100644
--- a/arch/arm/configs/u8500_defconfig
+++ b/arch/arm/configs/u8500_defconfig
@@ -69,6 +69,8 @@ CONFIG_GPIO_TC3589X=y
CONFIG_POWER_SUPPLY=y
CONFIG_AB8500_BM=y
CONFIG_AB8500_BATTERY_THERM_ON_BATCTRL=y
+CONFIG_THERMAL=y
+CONFIG_CPU_THERMAL=y
CONFIG_MFD_STMPE=y
CONFIG_MFD_TC3589X=y
CONFIG_AB5500_CORE=y
diff --git a/arch/arm/mach-ux500/board-mop500.c b/arch/arm/mach-ux500/board-mop500.c
index 416d436..b03216b 100644
--- a/arch/arm/mach-ux500/board-mop500.c
+++ b/arch/arm/mach-ux500/board-mop500.c
@@ -16,6 +16,7 @@
#include <linux/io.h>
#include <linux/i2c.h>
#include <linux/platform_data/i2c-nomadik.h>
+#include <linux/platform_data/db8500_thermal.h>
#include <linux/gpio.h>
#include <linux/amba/bus.h>
#include <linux/amba/pl022.h>
@@ -229,6 +230,67 @@ static struct ab8500_platform_data ab8500_platdata = {
};
/*
+ * Thermal Sensor
+ */
+
+static struct resource db8500_thsens_resources[] = {
+ {
+ .name = "IRQ_HOTMON_LOW",
+ .start = IRQ_PRCMU_HOTMON_LOW,
+ .end = IRQ_PRCMU_HOTMON_LOW,
+ .flags = IORESOURCE_IRQ,
+ },
+ {
+ .name = "IRQ_HOTMON_HIGH",
+ .start = IRQ_PRCMU_HOTMON_HIGH,
+ .end = IRQ_PRCMU_HOTMON_HIGH,
+ .flags = IORESOURCE_IRQ,
+ },
+};
+
+static struct db8500_thsens_platform_data db8500_thsens_data = {
+ .trip_points[0] = {
+ .temp = 70000,
+ .type = THERMAL_TRIP_ACTIVE,
+ .cdev_name = {
+ [0] = "thermal-cpufreq-0",
+ },
+ },
+ .trip_points[1] = {
+ .temp = 75000,
+ .type = THERMAL_TRIP_ACTIVE,
+ .cdev_name = {
+ [0] = "thermal-cpufreq-0",
+ },
+ },
+ .trip_points[2] = {
+ .temp = 80000,
+ .type = THERMAL_TRIP_ACTIVE,
+ .cdev_name = {
+ [0] = "thermal-cpufreq-0",
+ },
+ },
+ .trip_points[3] = {
+ .temp = 85000,
+ .type = THERMAL_TRIP_CRITICAL,
+ },
+ .num_trips = 4,
+};
+
+static struct platform_device u8500_thsens_device = {
+ .name = "db8500-thermal",
+ .resource = db8500_thsens_resources,
+ .num_resources = ARRAY_SIZE(db8500_thsens_resources),
+ .dev = {
+ .platform_data = &db8500_thsens_data,
+ },
+};
+
+static struct platform_device u8500_cpufreq_cooling_device = {
+ .name = "db8500-cpufreq-cooling",
+};
+
+/*
* TPS61052
*/
@@ -583,6 +645,8 @@ static struct platform_device *snowball_platform_devs[] __initdata = {
&snowball_key_dev,
&snowball_sbnet_dev,
&snowball_gpio_en_3v3_regulator_dev,
+ &u8500_thsens_device,
+ &u8500_cpufreq_cooling_device,
};
static void __init mop500_init_machine(void)
--
1.7.7.6
^ permalink raw reply related
* [PATCH 29/34] Thermal: Add ST-Ericsson DB8500 thermal driver.
From: Zhang Rui @ 2012-12-02 14:45 UTC (permalink / raw)
To: linux-pm; +Cc: hongbo.zhang, Zhang Rui
In-Reply-To: <1354459508-3707-1-git-send-email-rui.zhang@intel.com>
From: "hongbo.zhang" <hongbo.zhang@linaro.com>
This driver is based on the thermal management framework in thermal_sys.c. A
thermal zone device is created with the trip points to which cooling devices
can be bound, the current cooling device is cpufreq, e.g. CPU frequency is
clipped down to cool the CPU, and other cooling devices can be added and bound
to the trip points dynamically. The platform specific PRCMU interrupts are
used to active thermal update when trip points are reached.
Signed-off-by: hongbo.zhang <hongbo.zhang@linaro.com>
Reviewed-by: Viresh Kumar <viresh.kumar@linaro.org>
Reviewed-by: Francesco Lavra <francescolavra.fl@gmail.com>
Signed-off-by: Zhang Rui <rui.zhang@intel.com>
---
.../devicetree/bindings/thermal/db8500-thermal.txt | 44 ++
drivers/thermal/Kconfig | 20 +
drivers/thermal/Makefile | 2 +
drivers/thermal/db8500_cpufreq_cooling.c | 108 ++++
drivers/thermal/db8500_thermal.c | 531 ++++++++++++++++++++
include/linux/platform_data/db8500_thermal.h | 38 ++
6 files changed, 743 insertions(+), 0 deletions(-)
create mode 100644 Documentation/devicetree/bindings/thermal/db8500-thermal.txt
create mode 100644 drivers/thermal/db8500_cpufreq_cooling.c
create mode 100644 drivers/thermal/db8500_thermal.c
create mode 100644 include/linux/platform_data/db8500_thermal.h
diff --git a/Documentation/devicetree/bindings/thermal/db8500-thermal.txt b/Documentation/devicetree/bindings/thermal/db8500-thermal.txt
new file mode 100644
index 0000000..2e1c06f
--- /dev/null
+++ b/Documentation/devicetree/bindings/thermal/db8500-thermal.txt
@@ -0,0 +1,44 @@
+* ST-Ericsson DB8500 Thermal
+
+** Thermal node properties:
+
+- compatible : "stericsson,db8500-thermal";
+- reg : address range of the thermal sensor registers;
+- interrupts : interrupts generated from PRCMU;
+- interrupt-names : "IRQ_HOTMON_LOW" and "IRQ_HOTMON_HIGH";
+- num-trips : number of total trip points, this is required, set it 0 if none,
+ if greater than 0, the following properties must be defined;
+- tripN-temp : temperature of trip point N, should be in ascending order;
+- tripN-type : type of trip point N, should be one of "active" "passive" "hot"
+ "critical";
+- tripN-cdev-num : number of the cooling devices which can be bound to trip
+ point N, this is required if trip point N is defined, set it 0 if none,
+ otherwise the following cooling device names must be defined;
+- tripN-cdev-nameM : name of the No. M cooling device of trip point N;
+
+Usually the num-trips and tripN-*** are separated in board related dts files.
+
+Example:
+thermal@801573c0 {
+ compatible = "stericsson,db8500-thermal";
+ reg = <0x801573c0 0x40>;
+ interrupts = <21 0x4>, <22 0x4>;
+ interrupt-names = "IRQ_HOTMON_LOW", "IRQ_HOTMON_HIGH";
+
+ num-trips = <3>;
+
+ trip0-temp = <75000>;
+ trip0-type = "active";
+ trip0-cdev-num = <1>;
+ trip0-cdev-name0 = "thermal-cpufreq-0";
+
+ trip1-temp = <80000>;
+ trip1-type = "active";
+ trip1-cdev-num = <2>;
+ trip1-cdev-name0 = "thermal-cpufreq-0";
+ trip1-cdev-name1 = "thermal-fan";
+
+ trip2-temp = <85000>;
+ trip2-type = "critical";
+ trip2-cdev-num = <0>;
+}
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index 99b6587..d96da07 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -101,5 +101,25 @@ config EXYNOS_THERMAL
If you say yes here you get support for TMU (Thermal Managment
Unit) on SAMSUNG EXYNOS series of SoC.
+config DB8500_THERMAL
+ bool "DB8500 thermal management"
+ depends on ARCH_U8500
+ default y
+ help
+ Adds DB8500 thermal management implementation according to the thermal
+ management framework. A thermal zone with several trip points will be
+ created. Cooling devices can be bound to the trip points to cool this
+ thermal zone if trip points reached.
+
+config DB8500_CPUFREQ_COOLING
+ tristate "DB8500 cpufreq cooling"
+ depends on ARCH_U8500
+ depends on CPU_THERMAL
+ default y
+ help
+ Adds DB8500 cpufreq cooling devices, and these cooling devices can be
+ bound to thermal zone trip points. When a trip point reached, the
+ bound cpufreq cooling device turns active to set CPU frequency low to
+ cool down the CPU.
endif
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
index 0b6b048..d8da683 100644
--- a/drivers/thermal/Makefile
+++ b/drivers/thermal/Makefile
@@ -16,3 +16,5 @@ obj-$(CONFIG_CPU_THERMAL) += cpu_cooling.o
obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o
obj-$(CONFIG_RCAR_THERMAL) += rcar_thermal.o
obj-$(CONFIG_EXYNOS_THERMAL) += exynos_thermal.o
+obj-$(CONFIG_DB8500_THERMAL) += db8500_thermal.o
+obj-$(CONFIG_DB8500_CPUFREQ_COOLING) += db8500_cpufreq_cooling.o
diff --git a/drivers/thermal/db8500_cpufreq_cooling.c b/drivers/thermal/db8500_cpufreq_cooling.c
new file mode 100644
index 0000000..4cf8e72
--- /dev/null
+++ b/drivers/thermal/db8500_cpufreq_cooling.c
@@ -0,0 +1,108 @@
+/*
+ * db8500_cpufreq_cooling.c - DB8500 cpufreq works as cooling device.
+ *
+ * Copyright (C) 2012 ST-Ericsson
+ * Copyright (C) 2012 Linaro Ltd.
+ *
+ * Author: Hongbo Zhang <hongbo.zhang@linaro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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/cpu_cooling.h>
+#include <linux/cpufreq.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+static int db8500_cpufreq_cooling_probe(struct platform_device *pdev)
+{
+ struct thermal_cooling_device *cdev;
+ struct cpumask mask_val;
+
+ /* make sure cpufreq driver has been initialized */
+ if (!cpufreq_frequency_get_table(0))
+ return -EPROBE_DEFER;
+
+ cpumask_set_cpu(0, &mask_val);
+ cdev = cpufreq_cooling_register(&mask_val);
+
+ if (IS_ERR_OR_NULL(cdev)) {
+ dev_err(&pdev->dev, "Failed to register cooling device\n");
+ return PTR_ERR(cdev);
+ }
+
+ platform_set_drvdata(pdev, cdev);
+
+ dev_info(&pdev->dev, "Cooling device registered: %s\n", cdev->type);
+
+ return 0;
+}
+
+static int db8500_cpufreq_cooling_remove(struct platform_device *pdev)
+{
+ struct thermal_cooling_device *cdev = platform_get_drvdata(pdev);
+
+ cpufreq_cooling_unregister(cdev);
+
+ return 0;
+}
+
+static int db8500_cpufreq_cooling_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ return -ENOSYS;
+}
+
+static int db8500_cpufreq_cooling_resume(struct platform_device *pdev)
+{
+ return -ENOSYS;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id db8500_cpufreq_cooling_match[] = {
+ { .compatible = "stericsson,db8500-cpufreq-cooling" },
+ {},
+};
+#else
+#define db8500_cpufreq_cooling_match NULL
+#endif
+
+static struct platform_driver db8500_cpufreq_cooling_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "db8500-cpufreq-cooling",
+ .of_match_table = db8500_cpufreq_cooling_match,
+ },
+ .probe = db8500_cpufreq_cooling_probe,
+ .suspend = db8500_cpufreq_cooling_suspend,
+ .resume = db8500_cpufreq_cooling_resume,
+ .remove = db8500_cpufreq_cooling_remove,
+};
+
+static int __init db8500_cpufreq_cooling_init(void)
+{
+ return platform_driver_register(&db8500_cpufreq_cooling_driver);
+}
+
+static void __exit db8500_cpufreq_cooling_exit(void)
+{
+ platform_driver_unregister(&db8500_cpufreq_cooling_driver);
+}
+
+/* Should be later than db8500_cpufreq_register */
+late_initcall(db8500_cpufreq_cooling_init);
+module_exit(db8500_cpufreq_cooling_exit);
+
+MODULE_AUTHOR("Hongbo Zhang <hongbo.zhang@stericsson.com>");
+MODULE_DESCRIPTION("DB8500 cpufreq cooling driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/thermal/db8500_thermal.c b/drivers/thermal/db8500_thermal.c
new file mode 100644
index 0000000..ec71ade
--- /dev/null
+++ b/drivers/thermal/db8500_thermal.c
@@ -0,0 +1,531 @@
+/*
+ * db8500_thermal.c - DB8500 Thermal Management Implementation
+ *
+ * Copyright (C) 2012 ST-Ericsson
+ * Copyright (C) 2012 Linaro Ltd.
+ *
+ * Author: Hongbo Zhang <hongbo.zhang@linaro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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/cpu_cooling.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/dbx500-prcmu.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_data/db8500_thermal.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/thermal.h>
+
+#define PRCMU_DEFAULT_MEASURE_TIME 0xFFF
+#define PRCMU_DEFAULT_LOW_TEMP 0
+
+struct db8500_thermal_zone {
+ struct thermal_zone_device *therm_dev;
+ struct mutex th_lock;
+ struct work_struct therm_work;
+ struct db8500_thsens_platform_data *trip_tab;
+ enum thermal_device_mode mode;
+ enum thermal_trend trend;
+ unsigned long cur_temp_pseudo;
+ unsigned int cur_index;
+};
+
+/* Local function to check if thermal zone matches cooling devices */
+static int db8500_thermal_match_cdev(struct thermal_cooling_device *cdev,
+ struct db8500_trip_point *trip_point)
+{
+ int i;
+
+ if (!strlen(cdev->type))
+ return -EINVAL;
+
+ for (i = 0; i < COOLING_DEV_MAX; i++) {
+ if (!strcmp(trip_point->cdev_name[i], cdev->type))
+ return 0;
+ }
+
+ return -ENODEV;
+}
+
+/* Callback to bind cooling device to thermal zone */
+static int db8500_cdev_bind(struct thermal_zone_device *thermal,
+ struct thermal_cooling_device *cdev)
+{
+ struct db8500_thermal_zone *pzone = thermal->devdata;
+ struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
+ unsigned long max_state, upper, lower;
+ int i, ret = -EINVAL;
+
+ cdev->ops->get_max_state(cdev, &max_state);
+
+ for (i = 0; i < ptrips->num_trips; i++) {
+ if (db8500_thermal_match_cdev(cdev, &ptrips->trip_points[i]))
+ continue;
+
+ upper = lower = i > max_state ? max_state : i;
+
+ ret = thermal_zone_bind_cooling_device(thermal, i, cdev,
+ upper, lower);
+
+ dev_info(&cdev->device, "%s bind to %d: %d-%s\n", cdev->type,
+ i, ret, ret ? "fail" : "succeed");
+ }
+
+ return ret;
+}
+
+/* Callback to unbind cooling device from thermal zone */
+static int db8500_cdev_unbind(struct thermal_zone_device *thermal,
+ struct thermal_cooling_device *cdev)
+{
+ struct db8500_thermal_zone *pzone = thermal->devdata;
+ struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
+ int i, ret = -EINVAL;
+
+ for (i = 0; i < ptrips->num_trips; i++) {
+ if (db8500_thermal_match_cdev(cdev, &ptrips->trip_points[i]))
+ continue;
+
+ ret = thermal_zone_unbind_cooling_device(thermal, i, cdev);
+
+ dev_info(&cdev->device, "%s unbind from %d: %s\n", cdev->type,
+ i, ret ? "fail" : "succeed");
+ }
+
+ return ret;
+}
+
+/* Callback to get current temperature */
+static int db8500_sys_get_temp(struct thermal_zone_device *thermal,
+ unsigned long *temp)
+{
+ struct db8500_thermal_zone *pzone = thermal->devdata;
+
+ /*
+ * TODO: There is no PRCMU interface to get temperature data currently,
+ * so a pseudo temperature is returned , it works for thermal framework
+ * and this will be fixed when the PRCMU interface is available.
+ */
+ *temp = pzone->cur_temp_pseudo;
+
+ return 0;
+}
+
+/* Callback to get temperature changing trend */
+static int db8500_sys_get_trend(struct thermal_zone_device *thermal,
+ int trip, enum thermal_trend *trend)
+{
+ struct db8500_thermal_zone *pzone = thermal->devdata;
+
+ *trend = pzone->trend;
+
+ return 0;
+}
+
+/* Callback to get thermal zone mode */
+static int db8500_sys_get_mode(struct thermal_zone_device *thermal,
+ enum thermal_device_mode *mode)
+{
+ struct db8500_thermal_zone *pzone = thermal->devdata;
+
+ mutex_lock(&pzone->th_lock);
+ *mode = pzone->mode;
+ mutex_unlock(&pzone->th_lock);
+
+ return 0;
+}
+
+/* Callback to set thermal zone mode */
+static int db8500_sys_set_mode(struct thermal_zone_device *thermal,
+ enum thermal_device_mode mode)
+{
+ struct db8500_thermal_zone *pzone = thermal->devdata;
+
+ mutex_lock(&pzone->th_lock);
+
+ pzone->mode = mode;
+ if (mode == THERMAL_DEVICE_ENABLED)
+ schedule_work(&pzone->therm_work);
+
+ mutex_unlock(&pzone->th_lock);
+
+ return 0;
+}
+
+/* Callback to get trip point type */
+static int db8500_sys_get_trip_type(struct thermal_zone_device *thermal,
+ int trip, enum thermal_trip_type *type)
+{
+ struct db8500_thermal_zone *pzone = thermal->devdata;
+ struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
+
+ if (trip >= ptrips->num_trips)
+ return -EINVAL;
+
+ *type = ptrips->trip_points[trip].type;
+
+ return 0;
+}
+
+/* Callback to get trip point temperature */
+static int db8500_sys_get_trip_temp(struct thermal_zone_device *thermal,
+ int trip, unsigned long *temp)
+{
+ struct db8500_thermal_zone *pzone = thermal->devdata;
+ struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
+
+ if (trip >= ptrips->num_trips)
+ return -EINVAL;
+
+ *temp = ptrips->trip_points[trip].temp;
+
+ return 0;
+}
+
+/* Callback to get critical trip point temperature */
+static int db8500_sys_get_crit_temp(struct thermal_zone_device *thermal,
+ unsigned long *temp)
+{
+ struct db8500_thermal_zone *pzone = thermal->devdata;
+ struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
+ int i;
+
+ for (i = ptrips->num_trips - 1; i > 0; i--) {
+ if (ptrips->trip_points[i].type == THERMAL_TRIP_CRITICAL) {
+ *temp = ptrips->trip_points[i].temp;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static struct thermal_zone_device_ops thdev_ops = {
+ .bind = db8500_cdev_bind,
+ .unbind = db8500_cdev_unbind,
+ .get_temp = db8500_sys_get_temp,
+ .get_trend = db8500_sys_get_trend,
+ .get_mode = db8500_sys_get_mode,
+ .set_mode = db8500_sys_set_mode,
+ .get_trip_type = db8500_sys_get_trip_type,
+ .get_trip_temp = db8500_sys_get_trip_temp,
+ .get_crit_temp = db8500_sys_get_crit_temp,
+};
+
+static void db8500_thermal_update_config(struct db8500_thermal_zone *pzone,
+ unsigned int idx, enum thermal_trend trend,
+ unsigned long next_low, unsigned long next_high)
+{
+ prcmu_stop_temp_sense();
+
+ pzone->cur_index = idx;
+ pzone->cur_temp_pseudo = (next_low + next_high)/2;
+ pzone->trend = trend;
+
+ prcmu_config_hotmon((u8)(next_low/1000), (u8)(next_high/1000));
+ prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME);
+}
+
+static irqreturn_t prcmu_low_irq_handler(int irq, void *irq_data)
+{
+ struct db8500_thermal_zone *pzone = irq_data;
+ struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
+ unsigned int idx = pzone->cur_index;
+ unsigned long next_low, next_high;
+
+ if (unlikely(idx == 0))
+ /* Meaningless for thermal management, ignoring it */
+ return IRQ_HANDLED;
+
+ if (idx == 1) {
+ next_high = ptrips->trip_points[0].temp;
+ next_low = PRCMU_DEFAULT_LOW_TEMP;
+ } else {
+ next_high = ptrips->trip_points[idx-1].temp;
+ next_low = ptrips->trip_points[idx-2].temp;
+ }
+ idx -= 1;
+
+ db8500_thermal_update_config(pzone, idx, THERMAL_TREND_DROPPING,
+ next_low, next_high);
+
+ dev_dbg(&pzone->therm_dev->device,
+ "PRCMU set max %ld, min %ld\n", next_high, next_low);
+
+ schedule_work(&pzone->therm_work);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t prcmu_high_irq_handler(int irq, void *irq_data)
+{
+ struct db8500_thermal_zone *pzone = irq_data;
+ struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
+ unsigned int idx = pzone->cur_index;
+ unsigned long next_low, next_high;
+
+ if (idx < ptrips->num_trips - 1) {
+ next_high = ptrips->trip_points[idx+1].temp;
+ next_low = ptrips->trip_points[idx].temp;
+ idx += 1;
+
+ db8500_thermal_update_config(pzone, idx, THERMAL_TREND_RAISING,
+ next_low, next_high);
+
+ dev_dbg(&pzone->therm_dev->device,
+ "PRCMU set max %ld, min %ld\n", next_high, next_low);
+ } else if (idx == ptrips->num_trips - 1)
+ pzone->cur_temp_pseudo = ptrips->trip_points[idx].temp + 1;
+
+ schedule_work(&pzone->therm_work);
+
+ return IRQ_HANDLED;
+}
+
+static void db8500_thermal_work(struct work_struct *work)
+{
+ enum thermal_device_mode cur_mode;
+ struct db8500_thermal_zone *pzone;
+
+ pzone = container_of(work, struct db8500_thermal_zone, therm_work);
+
+ mutex_lock(&pzone->th_lock);
+ cur_mode = pzone->mode;
+ mutex_unlock(&pzone->th_lock);
+
+ if (cur_mode == THERMAL_DEVICE_DISABLED)
+ return;
+
+ thermal_zone_device_update(pzone->therm_dev);
+ dev_dbg(&pzone->therm_dev->device, "thermal work finished.\n");
+}
+
+#ifdef CONFIG_OF
+static struct db8500_thsens_platform_data*
+ db8500_thermal_parse_dt(struct platform_device *pdev)
+{
+ struct db8500_thsens_platform_data *ptrips;
+ struct device_node *np = pdev->dev.of_node;
+ char prop_name[32];
+ const char *tmp_str;
+ u32 tmp_data;
+ int i, j;
+
+ ptrips = devm_kzalloc(&pdev->dev, sizeof(*ptrips), GFP_KERNEL);
+ if (!ptrips)
+ return NULL;
+
+ if (of_property_read_u32(np, "num-trips", &tmp_data))
+ goto err_parse_dt;
+
+ if (tmp_data > THERMAL_MAX_TRIPS)
+ goto err_parse_dt;
+
+ ptrips->num_trips = tmp_data;
+
+ for (i = 0; i < ptrips->num_trips; i++) {
+ sprintf(prop_name, "trip%d-temp", i);
+ if (of_property_read_u32(np, prop_name, &tmp_data))
+ goto err_parse_dt;
+
+ ptrips->trip_points[i].temp = tmp_data;
+
+ sprintf(prop_name, "trip%d-type", i);
+ if (of_property_read_string(np, prop_name, &tmp_str))
+ goto err_parse_dt;
+
+ if (!strcmp(tmp_str, "active"))
+ ptrips->trip_points[i].type = THERMAL_TRIP_ACTIVE;
+ else if (!strcmp(tmp_str, "passive"))
+ ptrips->trip_points[i].type = THERMAL_TRIP_PASSIVE;
+ else if (!strcmp(tmp_str, "hot"))
+ ptrips->trip_points[i].type = THERMAL_TRIP_HOT;
+ else if (!strcmp(tmp_str, "critical"))
+ ptrips->trip_points[i].type = THERMAL_TRIP_CRITICAL;
+ else
+ goto err_parse_dt;
+
+ sprintf(prop_name, "trip%d-cdev-num", i);
+ if (of_property_read_u32(np, prop_name, &tmp_data))
+ goto err_parse_dt;
+
+ if (tmp_data > COOLING_DEV_MAX)
+ goto err_parse_dt;
+
+ for (j = 0; j < tmp_data; j++) {
+ sprintf(prop_name, "trip%d-cdev-name%d", i, j);
+ if (of_property_read_string(np, prop_name, &tmp_str))
+ goto err_parse_dt;
+
+ if (strlen(tmp_str) >= THERMAL_NAME_LENGTH)
+ goto err_parse_dt;
+
+ strcpy(ptrips->trip_points[i].cdev_name[j], tmp_str);
+ }
+ }
+ return ptrips;
+
+err_parse_dt:
+ dev_err(&pdev->dev, "Parsing device tree data error.\n");
+ return NULL;
+}
+#else
+static inline struct db8500_thsens_platform_data*
+ db8500_thermal_parse_dt(struct platform_device *pdev)
+{
+ return NULL;
+}
+#endif
+
+static int db8500_thermal_probe(struct platform_device *pdev)
+{
+ struct db8500_thermal_zone *pzone = NULL;
+ struct db8500_thsens_platform_data *ptrips = NULL;
+ struct device_node *np = pdev->dev.of_node;
+ int low_irq, high_irq, ret = 0;
+ unsigned long dft_low, dft_high;
+
+ if (np)
+ ptrips = db8500_thermal_parse_dt(pdev);
+ else
+ ptrips = dev_get_platdata(&pdev->dev);
+
+ if (!ptrips)
+ return -EINVAL;
+
+ pzone = devm_kzalloc(&pdev->dev, sizeof(*pzone), GFP_KERNEL);
+ if (!pzone)
+ return -ENOMEM;
+
+ mutex_init(&pzone->th_lock);
+ mutex_lock(&pzone->th_lock);
+
+ pzone->mode = THERMAL_DEVICE_DISABLED;
+ pzone->trip_tab = ptrips;
+
+ INIT_WORK(&pzone->therm_work, db8500_thermal_work);
+
+ low_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_LOW");
+ if (low_irq < 0) {
+ dev_err(&pdev->dev, "Get IRQ_HOTMON_LOW failed.\n");
+ return low_irq;
+ }
+
+ ret = devm_request_threaded_irq(&pdev->dev, low_irq, NULL,
+ prcmu_low_irq_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT,
+ "dbx500_temp_low", pzone);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Failed to allocate temp low irq.\n");
+ return ret;
+ }
+
+ high_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_HIGH");
+ if (high_irq < 0) {
+ dev_err(&pdev->dev, "Get IRQ_HOTMON_HIGH failed.\n");
+ return high_irq;
+ }
+
+ ret = devm_request_threaded_irq(&pdev->dev, high_irq, NULL,
+ prcmu_high_irq_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT,
+ "dbx500_temp_high", pzone);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Failed to allocate temp high irq.\n");
+ return ret;
+ }
+
+ pzone->therm_dev = thermal_zone_device_register("db8500_thermal_zone",
+ ptrips->num_trips, 0, pzone, &thdev_ops, NULL, 0, 0);
+
+ if (IS_ERR_OR_NULL(pzone->therm_dev)) {
+ dev_err(&pdev->dev, "Register thermal zone device failed.\n");
+ return PTR_ERR(pzone->therm_dev);
+ }
+ dev_info(&pdev->dev, "Thermal zone device registered.\n");
+
+ dft_low = PRCMU_DEFAULT_LOW_TEMP;
+ dft_high = ptrips->trip_points[0].temp;
+
+ db8500_thermal_update_config(pzone, 0, THERMAL_TREND_STABLE,
+ dft_low, dft_high);
+
+ platform_set_drvdata(pdev, pzone);
+ pzone->mode = THERMAL_DEVICE_ENABLED;
+ mutex_unlock(&pzone->th_lock);
+
+ return 0;
+}
+
+static int db8500_thermal_remove(struct platform_device *pdev)
+{
+ struct db8500_thermal_zone *pzone = platform_get_drvdata(pdev);
+
+ thermal_zone_device_unregister(pzone->therm_dev);
+ cancel_work_sync(&pzone->therm_work);
+ mutex_destroy(&pzone->th_lock);
+
+ return 0;
+}
+
+static int db8500_thermal_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ struct db8500_thermal_zone *pzone = platform_get_drvdata(pdev);
+
+ flush_work(&pzone->therm_work);
+ prcmu_stop_temp_sense();
+
+ return 0;
+}
+
+static int db8500_thermal_resume(struct platform_device *pdev)
+{
+ struct db8500_thermal_zone *pzone = platform_get_drvdata(pdev);
+ struct db8500_thsens_platform_data *ptrips = pzone->trip_tab;
+ unsigned long dft_low, dft_high;
+
+ dft_low = PRCMU_DEFAULT_LOW_TEMP;
+ dft_high = ptrips->trip_points[0].temp;
+
+ db8500_thermal_update_config(pzone, 0, THERMAL_TREND_STABLE,
+ dft_low, dft_high);
+
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id db8500_thermal_match[] = {
+ { .compatible = "stericsson,db8500-thermal" },
+ {},
+};
+#else
+#define db8500_thermal_match NULL
+#endif
+
+static struct platform_driver db8500_thermal_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "db8500-thermal",
+ .of_match_table = db8500_thermal_match,
+ },
+ .probe = db8500_thermal_probe,
+ .suspend = db8500_thermal_suspend,
+ .resume = db8500_thermal_resume,
+ .remove = db8500_thermal_remove,
+};
+
+module_platform_driver(db8500_thermal_driver);
+
+MODULE_AUTHOR("Hongbo Zhang <hongbo.zhang@stericsson.com>");
+MODULE_DESCRIPTION("DB8500 thermal driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/platform_data/db8500_thermal.h b/include/linux/platform_data/db8500_thermal.h
new file mode 100644
index 0000000..3bf6090
--- /dev/null
+++ b/include/linux/platform_data/db8500_thermal.h
@@ -0,0 +1,38 @@
+/*
+ * db8500_thermal.h - DB8500 Thermal Management Implementation
+ *
+ * Copyright (C) 2012 ST-Ericsson
+ * Copyright (C) 2012 Linaro Ltd.
+ *
+ * Author: Hongbo Zhang <hongbo.zhang@linaro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 _DB8500_THERMAL_H_
+#define _DB8500_THERMAL_H_
+
+#include <linux/thermal.h>
+
+#define COOLING_DEV_MAX 8
+
+struct db8500_trip_point {
+ unsigned long temp;
+ enum thermal_trip_type type;
+ char cdev_name[COOLING_DEV_MAX][THERMAL_NAME_LENGTH];
+};
+
+struct db8500_thsens_platform_data {
+ struct db8500_trip_point trip_points[THERMAL_MAX_TRIPS];
+ int num_trips;
+};
+
+#endif /* _DB8500_THERMAL_H_ */
--
1.7.7.6
^ permalink raw reply related
* [PATCH 28/34] drivers/thermal/Makefile refactor
From: Zhang Rui @ 2012-12-02 14:45 UTC (permalink / raw)
To: linux-pm; +Cc: Zhang Rui
In-Reply-To: <1354459508-3707-1-git-send-email-rui.zhang@intel.com>
Signed-off-by: Zhang Rui <rui.zhang@intel.com>
---
drivers/thermal/Makefile | 18 ++++++++++++------
1 files changed, 12 insertions(+), 6 deletions(-)
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
index e5f7974..0b6b048 100644
--- a/drivers/thermal/Makefile
+++ b/drivers/thermal/Makefile
@@ -3,10 +3,16 @@
#
obj-$(CONFIG_THERMAL) += thermal_sys.o
-obj-$(CONFIG_CPU_THERMAL) += cpu_cooling.o
-obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o
+
+# governors
+obj-$(CONFIG_FAIR_SHARE) += fair_share.o
+obj-$(CONFIG_STEP_WISE) += step_wise.o
+obj-$(CONFIG_USER_SPACE) += user_space.o
+
+# cpufreq cooling
+obj-$(CONFIG_CPU_THERMAL) += cpu_cooling.o
+
+# platform thermal drivers
+obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o
obj-$(CONFIG_RCAR_THERMAL) += rcar_thermal.o
-obj-$(CONFIG_EXYNOS_THERMAL) += exynos_thermal.o
-obj-$(CONFIG_FAIR_SHARE) += fair_share.o
-obj-$(CONFIG_STEP_WISE) += step_wise.o
-obj-$(CONFIG_USER_SPACE) += user_space.o
+obj-$(CONFIG_EXYNOS_THERMAL) += exynos_thermal.o
--
1.7.7.6
^ permalink raw reply related
* [PATCH 27/34] Exynos: Add missing dependency
From: Zhang Rui @ 2012-12-02 14:45 UTC (permalink / raw)
To: linux-pm; +Cc: Zhang Rui
In-Reply-To: <1354459508-3707-1-git-send-email-rui.zhang@intel.com>
CPU_FREQ_TABLE depends on CPU_FREQ. Selecting CPU_FREQ_TABLE without checking
for dependencies gives the following compilation warnings:
warning: (ARCH_TEGRA_2x_SOC && ARCH_TEGRA_3x_SOC && UX500_SOC_DB8500 &&
CPU_THERMAL && EXYNOS_THERMAL) selects CPU_FREQ_TABLE which has unmet
direct dependencies (ARCH_HAS_CPUFREQ && CPU_FREQ)
Based-on-patch-by: Sachin Kamat <sachin.kamat@linaro.org>
Signed-off-by: Zhang Rui <rui.zhang@intel.com>
---
drivers/thermal/Kconfig | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index 937a23d..99b6587 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -96,7 +96,7 @@ config RCAR_THERMAL
config EXYNOS_THERMAL
tristate "Temperature sensor on Samsung EXYNOS"
depends on (ARCH_EXYNOS4 || ARCH_EXYNOS5)
- select CPU_FREQ_TABLE
+ depends on CPU_THERMAL
help
If you say yes here you get support for TMU (Thermal Managment
Unit) on SAMSUNG EXYNOS series of SoC.
--
1.7.7.6
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox