Re: [PATCH v2 3/3] soc/tegra: bpmp: Implement generic PM domains

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On 29 March 2017 at 18:34, Thierry Reding <thierry.reding@xxxxxxxxx> wrote:
> From: Thierry Reding <treding@xxxxxxxxxx>
>
> The BPMP firmware, found on Tegra186 and later, provides an ABI that can
> be used to enable and disable power to several power partitions in Tegra
> SoCs. The ABI allows for enumeration of the available power partitions,
> so the driver can be reused on future generations, provided the BPMP ABI
> remains stable.
>
> Based on work by Stefan Kristiansson <stefank@xxxxxxxxxx> and Mikko
> Perttunen <mperttunen@xxxxxxxxxx>.
>
> Signed-off-by: Thierry Reding <treding@xxxxxxxxxx>

Reviewed-by: Ulf Hansson <ulf.hansson@xxxxxxxxxx>

Kind regards
Uffe

> ---
> Changes in v2:
> - take into account PG_STATE_RUNNING in tegra_bpmp_powergate_is_powered()
> - check return value of pm_genpd_init() and clean up on failure
> - simplify error unwinding in tegra_bpmp_init_powergates()
>
>  drivers/firmware/tegra/bpmp.c      |   4 +
>  drivers/soc/tegra/Kconfig          |   5 +
>  drivers/soc/tegra/Makefile         |   1 +
>  drivers/soc/tegra/powergate-bpmp.c | 359 +++++++++++++++++++++++++++++++++++++
>  include/soc/tegra/bpmp.h           |  12 ++
>  5 files changed, 381 insertions(+)
>  create mode 100644 drivers/soc/tegra/powergate-bpmp.c
>
> diff --git a/drivers/firmware/tegra/bpmp.c b/drivers/firmware/tegra/bpmp.c
> index 84e4c9a58a0c..f11c7025b4a1 100644
> --- a/drivers/firmware/tegra/bpmp.c
> +++ b/drivers/firmware/tegra/bpmp.c
> @@ -810,6 +810,10 @@ static int tegra_bpmp_probe(struct platform_device *pdev)
>         if (err < 0)
>                 goto free_mrq;
>
> +       err = tegra_bpmp_init_powergates(bpmp);
> +       if (err < 0)
> +               goto free_mrq;
> +
>         platform_set_drvdata(pdev, bpmp);
>
>         return 0;
> diff --git a/drivers/soc/tegra/Kconfig b/drivers/soc/tegra/Kconfig
> index 208d6edb3fdb..e052664439a1 100644
> --- a/drivers/soc/tegra/Kconfig
> +++ b/drivers/soc/tegra/Kconfig
> @@ -106,3 +106,8 @@ config SOC_TEGRA_PMC
>
>  config SOC_TEGRA_PMC_TEGRA186
>         bool
> +
> +config SOC_TEGRA_POWERGATE_BPMP
> +       def_bool y
> +       depends on PM_GENERIC_DOMAINS
> +       depends on TEGRA_BPMP
> diff --git a/drivers/soc/tegra/Makefile b/drivers/soc/tegra/Makefile
> index b4425e4319ff..17f2e25e7954 100644
> --- a/drivers/soc/tegra/Makefile
> +++ b/drivers/soc/tegra/Makefile
> @@ -3,3 +3,4 @@ obj-y += fuse/
>  obj-y += common.o
>  obj-$(CONFIG_SOC_TEGRA_PMC) += pmc.o
>  obj-$(CONFIG_SOC_TEGRA_PMC_TEGRA186) += pmc-tegra186.o
> +obj-$(CONFIG_SOC_TEGRA_POWERGATE_BPMP) += powergate-bpmp.o
> diff --git a/drivers/soc/tegra/powergate-bpmp.c b/drivers/soc/tegra/powergate-bpmp.c
> new file mode 100644
> index 000000000000..8fc356039401
> --- /dev/null
> +++ b/drivers/soc/tegra/powergate-bpmp.c
> @@ -0,0 +1,359 @@
> +/*
> + * Copyright (c) 2016-2017, NVIDIA CORPORATION. All rights reserved
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2, as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope 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/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_domain.h>
> +#include <linux/slab.h>
> +#include <linux/version.h>
> +
> +#include <soc/tegra/bpmp.h>
> +#include <soc/tegra/bpmp-abi.h>
> +
> +struct tegra_powergate_info {
> +       unsigned int id;
> +       char *name;
> +};
> +
> +struct tegra_powergate {
> +       struct generic_pm_domain genpd;
> +       struct tegra_bpmp *bpmp;
> +       unsigned int id;
> +};
> +
> +static inline struct tegra_powergate *
> +to_tegra_powergate(struct generic_pm_domain *genpd)
> +{
> +       return container_of(genpd, struct tegra_powergate, genpd);
> +}
> +
> +static int tegra_bpmp_powergate_set_state(struct tegra_bpmp *bpmp,
> +                                         unsigned int id, u32 state)
> +{
> +       struct mrq_pg_request request;
> +       struct tegra_bpmp_message msg;
> +
> +       memset(&request, 0, sizeof(request));
> +       request.cmd = CMD_PG_SET_STATE;
> +       request.id = id;
> +       request.set_state.state = state;
> +
> +       memset(&msg, 0, sizeof(msg));
> +       msg.mrq = MRQ_PG;
> +       msg.tx.data = &request;
> +       msg.tx.size = sizeof(request);
> +
> +       return tegra_bpmp_transfer(bpmp, &msg);
> +}
> +
> +static int tegra_bpmp_powergate_get_state(struct tegra_bpmp *bpmp,
> +                                         unsigned int id)
> +{
> +       struct mrq_pg_response response;
> +       struct mrq_pg_request request;
> +       struct tegra_bpmp_message msg;
> +       int err;
> +
> +       memset(&request, 0, sizeof(request));
> +       request.cmd = CMD_PG_GET_STATE;
> +       request.id = id;
> +
> +       memset(&response, 0, sizeof(response));
> +
> +       memset(&msg, 0, sizeof(msg));
> +       msg.mrq = MRQ_PG;
> +       msg.tx.data = &request;
> +       msg.tx.size = sizeof(request);
> +       msg.rx.data = &response;
> +       msg.rx.size = sizeof(response);
> +
> +       err = tegra_bpmp_transfer(bpmp, &msg);
> +       if (err < 0)
> +               return PG_STATE_OFF;
> +
> +       return response.get_state.state;
> +}
> +
> +static int tegra_bpmp_powergate_get_max_id(struct tegra_bpmp *bpmp)
> +{
> +       struct mrq_pg_response response;
> +       struct mrq_pg_request request;
> +       struct tegra_bpmp_message msg;
> +       int err;
> +
> +       memset(&request, 0, sizeof(request));
> +       request.cmd = CMD_PG_GET_MAX_ID;
> +
> +       memset(&response, 0, sizeof(response));
> +
> +       memset(&msg, 0, sizeof(msg));
> +       msg.mrq = MRQ_PG;
> +       msg.tx.data = &request;
> +       msg.tx.size = sizeof(request);
> +       msg.rx.data = &response;
> +       msg.rx.size = sizeof(response);
> +
> +       err = tegra_bpmp_transfer(bpmp, &msg);
> +       if (err < 0)
> +               return err;
> +
> +       return response.get_max_id.max_id;
> +}
> +
> +static char *tegra_bpmp_powergate_get_name(struct tegra_bpmp *bpmp,
> +                                          unsigned int id)
> +{
> +       struct mrq_pg_response response;
> +       struct mrq_pg_request request;
> +       struct tegra_bpmp_message msg;
> +       int err;
> +
> +       memset(&request, 0, sizeof(request));
> +       request.cmd = CMD_PG_GET_NAME;
> +       request.id = id;
> +
> +       memset(&response, 0, sizeof(response));
> +
> +       memset(&msg, 0, sizeof(msg));
> +       msg.mrq = MRQ_PG;
> +       msg.tx.data = &request;
> +       msg.tx.size = sizeof(request);
> +       msg.rx.data = &response;
> +       msg.rx.size = sizeof(response);
> +
> +       err = tegra_bpmp_transfer(bpmp, &msg);
> +       if (err < 0)
> +               return NULL;
> +
> +       return kstrdup(response.get_name.name, GFP_KERNEL);
> +}
> +
> +static inline bool tegra_bpmp_powergate_is_powered(struct tegra_bpmp *bpmp,
> +                                                  unsigned int id)
> +{
> +       return tegra_bpmp_powergate_get_state(bpmp, id) != PG_STATE_OFF;
> +}
> +
> +static int tegra_powergate_power_on(struct generic_pm_domain *domain)
> +{
> +       struct tegra_powergate *powergate = to_tegra_powergate(domain);
> +       struct tegra_bpmp *bpmp = powergate->bpmp;
> +
> +       return tegra_bpmp_powergate_set_state(bpmp, powergate->id,
> +                                             PG_STATE_ON);
> +}
> +
> +static int tegra_powergate_power_off(struct generic_pm_domain *domain)
> +{
> +       struct tegra_powergate *powergate = to_tegra_powergate(domain);
> +       struct tegra_bpmp *bpmp = powergate->bpmp;
> +
> +       return tegra_bpmp_powergate_set_state(bpmp, powergate->id,
> +                                             PG_STATE_OFF);
> +}
> +
> +static struct tegra_powergate *
> +tegra_powergate_add(struct tegra_bpmp *bpmp,
> +                   const struct tegra_powergate_info *info)
> +{
> +       struct tegra_powergate *powergate;
> +       bool off;
> +       int err;
> +
> +       off = !tegra_bpmp_powergate_is_powered(bpmp, info->id);
> +
> +       powergate = devm_kzalloc(bpmp->dev, sizeof(*powergate), GFP_KERNEL);
> +       if (!powergate)
> +               return ERR_PTR(-ENOMEM);
> +
> +       powergate->id = info->id;
> +       powergate->bpmp = bpmp;
> +
> +       powergate->genpd.name = kstrdup(info->name, GFP_KERNEL);
> +       powergate->genpd.power_on = tegra_powergate_power_on;
> +       powergate->genpd.power_off = tegra_powergate_power_off;
> +
> +       err = pm_genpd_init(&powergate->genpd, NULL, off);
> +       if (err < 0) {
> +               kfree(powergate->genpd.name);
> +               return ERR_PTR(err);
> +       }
> +
> +       return powergate;
> +}
> +
> +static void tegra_powergate_remove(struct tegra_powergate *powergate)
> +{
> +       struct generic_pm_domain *genpd = &powergate->genpd;
> +       struct tegra_bpmp *bpmp = powergate->bpmp;
> +       int err;
> +
> +       err = pm_genpd_remove(genpd);
> +       if (err < 0)
> +               dev_err(bpmp->dev, "failed to remove power domain %s: %d\n",
> +                       genpd->name, err);
> +
> +       kfree(genpd->name);
> +}
> +
> +static int
> +tegra_bpmp_probe_powergates(struct tegra_bpmp *bpmp,
> +                           struct tegra_powergate_info **powergatesp)
> +{
> +       struct tegra_powergate_info *powergates;
> +       unsigned int max_id, id, count = 0;
> +       unsigned int num_holes = 0;
> +       int err;
> +
> +       err = tegra_bpmp_powergate_get_max_id(bpmp);
> +       if (err < 0)
> +               return err;
> +
> +       max_id = err;
> +
> +       dev_dbg(bpmp->dev, "maximum powergate ID: %u\n", max_id);
> +
> +       powergates = kcalloc(max_id + 1, sizeof(*powergates), GFP_KERNEL);
> +       if (!powergates)
> +               return -ENOMEM;
> +
> +       for (id = 0; id <= max_id; id++) {
> +               struct tegra_powergate_info *info = &powergates[count];
> +
> +               info->name = tegra_bpmp_powergate_get_name(bpmp, id);
> +               if (!info->name || info->name[0] == '\0') {
> +                       num_holes++;
> +                       continue;
> +               }
> +
> +               info->id = id;
> +               count++;
> +       }
> +
> +       dev_dbg(bpmp->dev, "holes: %u\n", num_holes);
> +
> +       *powergatesp = powergates;
> +
> +       return count;
> +}
> +
> +static int tegra_bpmp_add_powergates(struct tegra_bpmp *bpmp,
> +                                    struct tegra_powergate_info *powergates,
> +                                    unsigned int count)
> +{
> +       struct genpd_onecell_data *genpd = &bpmp->genpd;
> +       struct generic_pm_domain **domains;
> +       struct tegra_powergate *powergate;
> +       unsigned int i;
> +       int err;
> +
> +       domains = kcalloc(count, sizeof(*domains), GFP_KERNEL);
> +       if (!domains)
> +               return -ENOMEM;
> +
> +       for (i = 0; i < count; i++) {
> +               powergate = tegra_powergate_add(bpmp, &powergates[i]);
> +               if (IS_ERR(powergate)) {
> +                       err = PTR_ERR(powergate);
> +                       goto remove;
> +               }
> +
> +               dev_dbg(bpmp->dev, "added power domain %s\n",
> +                       powergate->genpd.name);
> +               domains[i] = &powergate->genpd;
> +       }
> +
> +       genpd->num_domains = count;
> +       genpd->domains = domains;
> +
> +       return 0;
> +
> +remove:
> +       while (i--) {
> +               powergate = to_tegra_powergate(domains[i]);
> +               tegra_powergate_remove(powergate);
> +       }
> +
> +       kfree(genpd->domains);
> +       return err;
> +}
> +
> +static void tegra_bpmp_remove_powergates(struct tegra_bpmp *bpmp)
> +{
> +       struct genpd_onecell_data *genpd = &bpmp->genpd;
> +       unsigned int i = genpd->num_domains;
> +       struct tegra_powergate *powergate;
> +
> +       while (i--) {
> +               dev_dbg(bpmp->dev, "removing power domain %s\n",
> +                       genpd->domains[i]->name);
> +               powergate = to_tegra_powergate(genpd->domains[i]);
> +               tegra_powergate_remove(powergate);
> +       }
> +}
> +
> +static struct generic_pm_domain *
> +tegra_powergate_xlate(struct of_phandle_args *spec, void *data)
> +{
> +       struct generic_pm_domain *domain = ERR_PTR(-ENOENT);
> +       struct genpd_onecell_data *genpd = data;
> +       unsigned int i;
> +
> +       for (i = 0; i < genpd->num_domains; i++) {
> +               struct tegra_powergate *powergate;
> +
> +               powergate = to_tegra_powergate(genpd->domains[i]);
> +               if (powergate->id == spec->args[0]) {
> +                       domain = &powergate->genpd;
> +                       break;
> +               }
> +       }
> +
> +       return domain;
> +}
> +
> +int tegra_bpmp_init_powergates(struct tegra_bpmp *bpmp)
> +{
> +       struct device_node *np = bpmp->dev->of_node;
> +       struct tegra_powergate_info *powergates;
> +       struct device *dev = bpmp->dev;
> +       unsigned int count, i;
> +       int err;
> +
> +       err = tegra_bpmp_probe_powergates(bpmp, &powergates);
> +       if (err < 0)
> +               return err;
> +
> +       count = err;
> +
> +       dev_dbg(dev, "%u power domains probed\n", count);
> +
> +       err = tegra_bpmp_add_powergates(bpmp, powergates, count);
> +       if (err < 0)
> +               goto free;
> +
> +       bpmp->genpd.xlate = tegra_powergate_xlate;
> +
> +       err = of_genpd_add_provider_onecell(np, &bpmp->genpd);
> +       if (err < 0) {
> +               dev_err(dev, "failed to add power domain provider: %d\n", err);
> +               tegra_bpmp_remove_powergates(bpmp);
> +       }
> +
> +free:
> +       for (i = 0; i < count; i++)
> +               kfree(powergates[i].name);
> +
> +       kfree(powergates);
> +       return err;
> +}
> diff --git a/include/soc/tegra/bpmp.h b/include/soc/tegra/bpmp.h
> index 13dcd44e91bb..9ba65222bd3f 100644
> --- a/include/soc/tegra/bpmp.h
> +++ b/include/soc/tegra/bpmp.h
> @@ -15,6 +15,7 @@
>  #define __SOC_TEGRA_BPMP_H
>
>  #include <linux/mailbox_client.h>
> +#include <linux/pm_domain.h>
>  #include <linux/reset-controller.h>
>  #include <linux/semaphore.h>
>  #include <linux/types.h>
> @@ -91,6 +92,8 @@ struct tegra_bpmp {
>         unsigned int num_clocks;
>
>         struct reset_controller_dev rstc;
> +
> +       struct genpd_onecell_data genpd;
>  };
>
>  struct tegra_bpmp *tegra_bpmp_get(struct device *dev);
> @@ -138,4 +141,13 @@ static inline int tegra_bpmp_init_resets(struct tegra_bpmp *bpmp)
>  }
>  #endif
>
> +#if IS_ENABLED(CONFIG_SOC_TEGRA_POWERGATE_BPMP)
> +int tegra_bpmp_init_powergates(struct tegra_bpmp *bpmp);
> +#else
> +static inline int tegra_bpmp_init_powergates(struct tegra_bpmp *bpmp)
> +{
> +       return 0;
> +}
> +#endif
> +
>  #endif /* __SOC_TEGRA_BPMP_H */
> --
> 2.12.0
>
--
To unsubscribe from this list: send the line "unsubscribe linux-tegra" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [ARM Kernel]     [Linux ARM]     [Linux ARM MSM]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux