| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Copyright (C) 2014 Linaro Ltd |
| * |
| * Author: Ulf Hansson <ulf.hansson@linaro.org> |
| * |
| * MMC power sequence management |
| */ |
| #include <linux/kernel.h> |
| #include <linux/err.h> |
| #include <linux/module.h> |
| #include <linux/of.h> |
| |
| #include <linux/mmc/host.h> |
| |
| #include "pwrseq.h" |
| |
| static DEFINE_MUTEX(pwrseq_list_mutex); |
| static LIST_HEAD(pwrseq_list); |
| |
| int mmc_pwrseq_alloc(struct mmc_host *host) |
| { |
| struct device_node *np; |
| struct mmc_pwrseq *p; |
| |
| np = of_parse_phandle(host->parent->of_node, "mmc-pwrseq", 0); |
| if (!np) |
| return 0; |
| |
| mutex_lock(&pwrseq_list_mutex); |
| list_for_each_entry(p, &pwrseq_list, pwrseq_node) { |
| if (device_match_of_node(p->dev, np)) { |
| if (!try_module_get(p->owner)) |
| dev_err(host->parent, |
| "increasing module refcount failed\n"); |
| else |
| host->pwrseq = p; |
| |
| break; |
| } |
| } |
| |
| of_node_put(np); |
| mutex_unlock(&pwrseq_list_mutex); |
| |
| if (!host->pwrseq) |
| return -EPROBE_DEFER; |
| |
| dev_info(host->parent, "allocated mmc-pwrseq\n"); |
| |
| return 0; |
| } |
| |
| void mmc_pwrseq_pre_power_on(struct mmc_host *host) |
| { |
| struct mmc_pwrseq *pwrseq = host->pwrseq; |
| |
| if (pwrseq && pwrseq->ops->pre_power_on) |
| pwrseq->ops->pre_power_on(host); |
| } |
| |
| void mmc_pwrseq_post_power_on(struct mmc_host *host) |
| { |
| struct mmc_pwrseq *pwrseq = host->pwrseq; |
| |
| if (pwrseq && pwrseq->ops->post_power_on) |
| pwrseq->ops->post_power_on(host); |
| } |
| |
| void mmc_pwrseq_power_off(struct mmc_host *host) |
| { |
| struct mmc_pwrseq *pwrseq = host->pwrseq; |
| |
| if (pwrseq && pwrseq->ops->power_off) |
| pwrseq->ops->power_off(host); |
| } |
| |
| void mmc_pwrseq_reset(struct mmc_host *host) |
| { |
| struct mmc_pwrseq *pwrseq = host->pwrseq; |
| |
| if (pwrseq && pwrseq->ops->reset) |
| pwrseq->ops->reset(host); |
| } |
| |
| void mmc_pwrseq_free(struct mmc_host *host) |
| { |
| struct mmc_pwrseq *pwrseq = host->pwrseq; |
| |
| if (pwrseq) { |
| module_put(pwrseq->owner); |
| host->pwrseq = NULL; |
| } |
| } |
| |
| int mmc_pwrseq_register(struct mmc_pwrseq *pwrseq) |
| { |
| if (!pwrseq || !pwrseq->ops || !pwrseq->dev) |
| return -EINVAL; |
| |
| mutex_lock(&pwrseq_list_mutex); |
| list_add(&pwrseq->pwrseq_node, &pwrseq_list); |
| mutex_unlock(&pwrseq_list_mutex); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(mmc_pwrseq_register); |
| |
| void mmc_pwrseq_unregister(struct mmc_pwrseq *pwrseq) |
| { |
| if (pwrseq) { |
| mutex_lock(&pwrseq_list_mutex); |
| list_del(&pwrseq->pwrseq_node); |
| mutex_unlock(&pwrseq_list_mutex); |
| } |
| } |
| EXPORT_SYMBOL_GPL(mmc_pwrseq_unregister); |