1/* 2 * Copyright (C) 2014 Linaro Ltd 3 * 4 * Author: Ulf Hansson <ulf.hansson@linaro.org> 5 * 6 * License terms: GNU General Public License (GPL) version 2 7 * 8 * Simple MMC power sequence management 9 */ 10#include <linux/clk.h> 11#include <linux/kernel.h> 12#include <linux/slab.h> 13#include <linux/device.h> 14#include <linux/err.h> 15#include <linux/of_gpio.h> 16#include <linux/gpio/consumer.h> 17 18#include <linux/mmc/host.h> 19 20#include "pwrseq.h" 21 22struct mmc_pwrseq_simple { 23 struct mmc_pwrseq pwrseq; 24 bool clk_enabled; 25 struct clk *ext_clk; 26 struct gpio_descs *reset_gpios; 27}; 28 29static void mmc_pwrseq_simple_set_gpios_value(struct mmc_pwrseq_simple *pwrseq, 30 int value) 31{ 32 int i; 33 struct gpio_descs *reset_gpios = pwrseq->reset_gpios; 34 int values[reset_gpios->ndescs]; 35 36 for (i = 0; i < reset_gpios->ndescs; i++) 37 values[i] = value; 38 39 gpiod_set_array_value_cansleep(reset_gpios->ndescs, reset_gpios->desc, 40 values); 41} 42 43static void mmc_pwrseq_simple_pre_power_on(struct mmc_host *host) 44{ 45 struct mmc_pwrseq_simple *pwrseq = container_of(host->pwrseq, 46 struct mmc_pwrseq_simple, pwrseq); 47 48 if (!IS_ERR(pwrseq->ext_clk) && !pwrseq->clk_enabled) { 49 clk_prepare_enable(pwrseq->ext_clk); 50 pwrseq->clk_enabled = true; 51 } 52 53 mmc_pwrseq_simple_set_gpios_value(pwrseq, 1); 54} 55 56static void mmc_pwrseq_simple_post_power_on(struct mmc_host *host) 57{ 58 struct mmc_pwrseq_simple *pwrseq = container_of(host->pwrseq, 59 struct mmc_pwrseq_simple, pwrseq); 60 61 mmc_pwrseq_simple_set_gpios_value(pwrseq, 0); 62} 63 64static void mmc_pwrseq_simple_power_off(struct mmc_host *host) 65{ 66 struct mmc_pwrseq_simple *pwrseq = container_of(host->pwrseq, 67 struct mmc_pwrseq_simple, pwrseq); 68 69 mmc_pwrseq_simple_set_gpios_value(pwrseq, 1); 70 71 if (!IS_ERR(pwrseq->ext_clk) && pwrseq->clk_enabled) { 72 clk_disable_unprepare(pwrseq->ext_clk); 73 pwrseq->clk_enabled = false; 74 } 75} 76 77static void mmc_pwrseq_simple_free(struct mmc_host *host) 78{ 79 struct mmc_pwrseq_simple *pwrseq = container_of(host->pwrseq, 80 struct mmc_pwrseq_simple, pwrseq); 81 82 gpiod_put_array(pwrseq->reset_gpios); 83 84 if (!IS_ERR(pwrseq->ext_clk)) 85 clk_put(pwrseq->ext_clk); 86 87 kfree(pwrseq); 88} 89 90static struct mmc_pwrseq_ops mmc_pwrseq_simple_ops = { 91 .pre_power_on = mmc_pwrseq_simple_pre_power_on, 92 .post_power_on = mmc_pwrseq_simple_post_power_on, 93 .power_off = mmc_pwrseq_simple_power_off, 94 .free = mmc_pwrseq_simple_free, 95}; 96 97struct mmc_pwrseq *mmc_pwrseq_simple_alloc(struct mmc_host *host, 98 struct device *dev) 99{ 100 struct mmc_pwrseq_simple *pwrseq; 101 int ret = 0; 102 103 pwrseq = kzalloc(sizeof(*pwrseq), GFP_KERNEL); 104 if (!pwrseq) 105 return ERR_PTR(-ENOMEM); 106 107 pwrseq->ext_clk = clk_get(dev, "ext_clock"); 108 if (IS_ERR(pwrseq->ext_clk) && 109 PTR_ERR(pwrseq->ext_clk) != -ENOENT) { 110 ret = PTR_ERR(pwrseq->ext_clk); 111 goto free; 112 } 113 114 pwrseq->reset_gpios = gpiod_get_array(dev, "reset", GPIOD_OUT_HIGH); 115 if (IS_ERR(pwrseq->reset_gpios)) { 116 ret = PTR_ERR(pwrseq->reset_gpios); 117 goto clk_put; 118 } 119 120 pwrseq->pwrseq.ops = &mmc_pwrseq_simple_ops; 121 122 return &pwrseq->pwrseq; 123clk_put: 124 if (!IS_ERR(pwrseq->ext_clk)) 125 clk_put(pwrseq->ext_clk); 126free: 127 kfree(pwrseq); 128 return ERR_PTR(ret); 129} 130