linux/drivers/mmc/core/pwrseq_simple.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 *  Copyright (C) 2014 Linaro Ltd
   4 *
   5 * Author: Ulf Hansson <ulf.hansson@linaro.org>
   6 *
   7 *  Simple MMC power sequence management
   8 */
   9#include <linux/clk.h>
  10#include <linux/init.h>
  11#include <linux/kernel.h>
  12#include <linux/platform_device.h>
  13#include <linux/module.h>
  14#include <linux/slab.h>
  15#include <linux/device.h>
  16#include <linux/err.h>
  17#include <linux/gpio/consumer.h>
  18#include <linux/delay.h>
  19#include <linux/property.h>
  20
  21#include <linux/mmc/host.h>
  22
  23#include "pwrseq.h"
  24
  25struct mmc_pwrseq_simple {
  26        struct mmc_pwrseq pwrseq;
  27        bool clk_enabled;
  28        u32 post_power_on_delay_ms;
  29        u32 power_off_delay_us;
  30        struct clk *ext_clk;
  31        struct gpio_descs *reset_gpios;
  32};
  33
  34#define to_pwrseq_simple(p) container_of(p, struct mmc_pwrseq_simple, pwrseq)
  35
  36static void mmc_pwrseq_simple_set_gpios_value(struct mmc_pwrseq_simple *pwrseq,
  37                                              int value)
  38{
  39        struct gpio_descs *reset_gpios = pwrseq->reset_gpios;
  40
  41        if (!IS_ERR(reset_gpios)) {
  42                unsigned long *values;
  43                int nvalues = reset_gpios->ndescs;
  44
  45                values = bitmap_alloc(nvalues, GFP_KERNEL);
  46                if (!values)
  47                        return;
  48
  49                if (value)
  50                        bitmap_fill(values, nvalues);
  51                else
  52                        bitmap_zero(values, nvalues);
  53
  54                gpiod_set_array_value_cansleep(nvalues, reset_gpios->desc,
  55                                               reset_gpios->info, values);
  56
  57                kfree(values);
  58        }
  59}
  60
  61static void mmc_pwrseq_simple_pre_power_on(struct mmc_host *host)
  62{
  63        struct mmc_pwrseq_simple *pwrseq = to_pwrseq_simple(host->pwrseq);
  64
  65        if (!IS_ERR(pwrseq->ext_clk) && !pwrseq->clk_enabled) {
  66                clk_prepare_enable(pwrseq->ext_clk);
  67                pwrseq->clk_enabled = true;
  68        }
  69
  70        mmc_pwrseq_simple_set_gpios_value(pwrseq, 1);
  71}
  72
  73static void mmc_pwrseq_simple_post_power_on(struct mmc_host *host)
  74{
  75        struct mmc_pwrseq_simple *pwrseq = to_pwrseq_simple(host->pwrseq);
  76
  77        mmc_pwrseq_simple_set_gpios_value(pwrseq, 0);
  78
  79        if (pwrseq->post_power_on_delay_ms)
  80                msleep(pwrseq->post_power_on_delay_ms);
  81}
  82
  83static void mmc_pwrseq_simple_power_off(struct mmc_host *host)
  84{
  85        struct mmc_pwrseq_simple *pwrseq = to_pwrseq_simple(host->pwrseq);
  86
  87        mmc_pwrseq_simple_set_gpios_value(pwrseq, 1);
  88
  89        if (pwrseq->power_off_delay_us)
  90                usleep_range(pwrseq->power_off_delay_us,
  91                        2 * pwrseq->power_off_delay_us);
  92
  93        if (!IS_ERR(pwrseq->ext_clk) && pwrseq->clk_enabled) {
  94                clk_disable_unprepare(pwrseq->ext_clk);
  95                pwrseq->clk_enabled = false;
  96        }
  97}
  98
  99static const struct mmc_pwrseq_ops mmc_pwrseq_simple_ops = {
 100        .pre_power_on = mmc_pwrseq_simple_pre_power_on,
 101        .post_power_on = mmc_pwrseq_simple_post_power_on,
 102        .power_off = mmc_pwrseq_simple_power_off,
 103};
 104
 105static const struct of_device_id mmc_pwrseq_simple_of_match[] = {
 106        { .compatible = "mmc-pwrseq-simple",},
 107        {/* sentinel */},
 108};
 109MODULE_DEVICE_TABLE(of, mmc_pwrseq_simple_of_match);
 110
 111static int mmc_pwrseq_simple_probe(struct platform_device *pdev)
 112{
 113        struct mmc_pwrseq_simple *pwrseq;
 114        struct device *dev = &pdev->dev;
 115
 116        pwrseq = devm_kzalloc(dev, sizeof(*pwrseq), GFP_KERNEL);
 117        if (!pwrseq)
 118                return -ENOMEM;
 119
 120        pwrseq->ext_clk = devm_clk_get(dev, "ext_clock");
 121        if (IS_ERR(pwrseq->ext_clk) && PTR_ERR(pwrseq->ext_clk) != -ENOENT)
 122                return PTR_ERR(pwrseq->ext_clk);
 123
 124        pwrseq->reset_gpios = devm_gpiod_get_array(dev, "reset",
 125                                                        GPIOD_OUT_HIGH);
 126        if (IS_ERR(pwrseq->reset_gpios) &&
 127            PTR_ERR(pwrseq->reset_gpios) != -ENOENT &&
 128            PTR_ERR(pwrseq->reset_gpios) != -ENOSYS) {
 129                return PTR_ERR(pwrseq->reset_gpios);
 130        }
 131
 132        device_property_read_u32(dev, "post-power-on-delay-ms",
 133                                 &pwrseq->post_power_on_delay_ms);
 134        device_property_read_u32(dev, "power-off-delay-us",
 135                                 &pwrseq->power_off_delay_us);
 136
 137        pwrseq->pwrseq.dev = dev;
 138        pwrseq->pwrseq.ops = &mmc_pwrseq_simple_ops;
 139        pwrseq->pwrseq.owner = THIS_MODULE;
 140        platform_set_drvdata(pdev, pwrseq);
 141
 142        return mmc_pwrseq_register(&pwrseq->pwrseq);
 143}
 144
 145static int mmc_pwrseq_simple_remove(struct platform_device *pdev)
 146{
 147        struct mmc_pwrseq_simple *pwrseq = platform_get_drvdata(pdev);
 148
 149        mmc_pwrseq_unregister(&pwrseq->pwrseq);
 150
 151        return 0;
 152}
 153
 154static struct platform_driver mmc_pwrseq_simple_driver = {
 155        .probe = mmc_pwrseq_simple_probe,
 156        .remove = mmc_pwrseq_simple_remove,
 157        .driver = {
 158                .name = "pwrseq_simple",
 159                .of_match_table = mmc_pwrseq_simple_of_match,
 160        },
 161};
 162
 163module_platform_driver(mmc_pwrseq_simple_driver);
 164MODULE_LICENSE("GPL v2");
 165