Branch data Line data Source code
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 : : 25 : : struct 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 : : 36 : 0 : static void mmc_pwrseq_simple_set_gpios_value(struct mmc_pwrseq_simple *pwrseq, 37 : : int value) 38 : : { 39 : 0 : struct gpio_descs *reset_gpios = pwrseq->reset_gpios; 40 : : 41 [ # # ]: 0 : if (!IS_ERR(reset_gpios)) { 42 : : unsigned long *values; 43 : 0 : int nvalues = reset_gpios->ndescs; 44 : : 45 : 0 : values = bitmap_alloc(nvalues, GFP_KERNEL); 46 [ # # ]: 0 : if (!values) 47 : 0 : return; 48 : : 49 [ # # ]: 0 : if (value) 50 : : bitmap_fill(values, nvalues); 51 : : else 52 : : bitmap_zero(values, nvalues); 53 : : 54 : 0 : gpiod_set_array_value_cansleep(nvalues, reset_gpios->desc, 55 : : reset_gpios->info, values); 56 : : 57 : 0 : kfree(values); 58 : : } 59 : : } 60 : : 61 : 0 : static void mmc_pwrseq_simple_pre_power_on(struct mmc_host *host) 62 : : { 63 : 0 : struct mmc_pwrseq_simple *pwrseq = to_pwrseq_simple(host->pwrseq); 64 : : 65 [ # # # # ]: 0 : if (!IS_ERR(pwrseq->ext_clk) && !pwrseq->clk_enabled) { 66 : 0 : clk_prepare_enable(pwrseq->ext_clk); 67 : 0 : pwrseq->clk_enabled = true; 68 : : } 69 : : 70 : 0 : mmc_pwrseq_simple_set_gpios_value(pwrseq, 1); 71 : 0 : } 72 : : 73 : 0 : static void mmc_pwrseq_simple_post_power_on(struct mmc_host *host) 74 : : { 75 : 0 : struct mmc_pwrseq_simple *pwrseq = to_pwrseq_simple(host->pwrseq); 76 : : 77 : 0 : mmc_pwrseq_simple_set_gpios_value(pwrseq, 0); 78 : : 79 [ # # ]: 0 : if (pwrseq->post_power_on_delay_ms) 80 : 0 : msleep(pwrseq->post_power_on_delay_ms); 81 : 0 : } 82 : : 83 : 0 : static void mmc_pwrseq_simple_power_off(struct mmc_host *host) 84 : : { 85 : 0 : struct mmc_pwrseq_simple *pwrseq = to_pwrseq_simple(host->pwrseq); 86 : : 87 : 0 : mmc_pwrseq_simple_set_gpios_value(pwrseq, 1); 88 : : 89 [ # # ]: 0 : if (pwrseq->power_off_delay_us) 90 : 0 : usleep_range(pwrseq->power_off_delay_us, 91 : 0 : 2 * pwrseq->power_off_delay_us); 92 : : 93 [ # # # # ]: 0 : if (!IS_ERR(pwrseq->ext_clk) && pwrseq->clk_enabled) { 94 : : clk_disable_unprepare(pwrseq->ext_clk); 95 : 0 : pwrseq->clk_enabled = false; 96 : : } 97 : 0 : } 98 : : 99 : : static 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 : : 105 : : static const struct of_device_id mmc_pwrseq_simple_of_match[] = { 106 : : { .compatible = "mmc-pwrseq-simple",}, 107 : : {/* sentinel */}, 108 : : }; 109 : : MODULE_DEVICE_TABLE(of, mmc_pwrseq_simple_of_match); 110 : : 111 : 0 : static int mmc_pwrseq_simple_probe(struct platform_device *pdev) 112 : : { 113 : : struct mmc_pwrseq_simple *pwrseq; 114 : 0 : struct device *dev = &pdev->dev; 115 : : 116 : : pwrseq = devm_kzalloc(dev, sizeof(*pwrseq), GFP_KERNEL); 117 [ # # ]: 0 : if (!pwrseq) 118 : : return -ENOMEM; 119 : : 120 : 0 : pwrseq->ext_clk = devm_clk_get(dev, "ext_clock"); 121 [ # # # # ]: 0 : if (IS_ERR(pwrseq->ext_clk) && PTR_ERR(pwrseq->ext_clk) != -ENOENT) 122 : : return PTR_ERR(pwrseq->ext_clk); 123 : : 124 : 0 : pwrseq->reset_gpios = devm_gpiod_get_array(dev, "reset", 125 : : GPIOD_OUT_HIGH); 126 [ # # # # ]: 0 : if (IS_ERR(pwrseq->reset_gpios) && 127 [ # # ]: 0 : PTR_ERR(pwrseq->reset_gpios) != -ENOENT && 128 : : PTR_ERR(pwrseq->reset_gpios) != -ENOSYS) { 129 : : return PTR_ERR(pwrseq->reset_gpios); 130 : : } 131 : : 132 : 0 : device_property_read_u32(dev, "post-power-on-delay-ms", 133 : : &pwrseq->post_power_on_delay_ms); 134 : 0 : device_property_read_u32(dev, "power-off-delay-us", 135 : : &pwrseq->power_off_delay_us); 136 : : 137 : 0 : pwrseq->pwrseq.dev = dev; 138 : 0 : pwrseq->pwrseq.ops = &mmc_pwrseq_simple_ops; 139 : 0 : pwrseq->pwrseq.owner = THIS_MODULE; 140 : : platform_set_drvdata(pdev, pwrseq); 141 : : 142 : 0 : return mmc_pwrseq_register(&pwrseq->pwrseq); 143 : : } 144 : : 145 : 0 : static int mmc_pwrseq_simple_remove(struct platform_device *pdev) 146 : : { 147 : : struct mmc_pwrseq_simple *pwrseq = platform_get_drvdata(pdev); 148 : : 149 : 0 : mmc_pwrseq_unregister(&pwrseq->pwrseq); 150 : : 151 : 0 : return 0; 152 : : } 153 : : 154 : : static 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 : : 163 : 404 : module_platform_driver(mmc_pwrseq_simple_driver); 164 : : MODULE_LICENSE("GPL v2");