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 | static 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(ptr: reset_gpios)) { |
42 | unsigned long *values; |
43 | int nvalues = reset_gpios->ndescs; |
44 | |
45 | values = bitmap_alloc(nbits: nvalues, GFP_KERNEL); |
46 | if (!values) |
47 | return; |
48 | |
49 | if (value) |
50 | bitmap_fill(dst: values, nbits: nvalues); |
51 | else |
52 | bitmap_zero(dst: values, nbits: nvalues); |
53 | |
54 | gpiod_set_array_value_cansleep(array_size: nvalues, desc_array: reset_gpios->desc, |
55 | array_info: reset_gpios->info, value_bitmap: values); |
56 | |
57 | bitmap_free(bitmap: values); |
58 | } |
59 | } |
60 | |
61 | static 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(ptr: pwrseq->ext_clk) && !pwrseq->clk_enabled) { |
66 | clk_prepare_enable(clk: pwrseq->ext_clk); |
67 | pwrseq->clk_enabled = true; |
68 | } |
69 | |
70 | mmc_pwrseq_simple_set_gpios_value(pwrseq, value: 1); |
71 | } |
72 | |
73 | static 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, value: 0); |
78 | |
79 | if (pwrseq->post_power_on_delay_ms) |
80 | msleep(msecs: pwrseq->post_power_on_delay_ms); |
81 | } |
82 | |
83 | static 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, value: 1); |
88 | |
89 | if (pwrseq->power_off_delay_us) |
90 | usleep_range(min: pwrseq->power_off_delay_us, |
91 | max: 2 * pwrseq->power_off_delay_us); |
92 | |
93 | if (!IS_ERR(ptr: pwrseq->ext_clk) && pwrseq->clk_enabled) { |
94 | clk_disable_unprepare(clk: pwrseq->ext_clk); |
95 | pwrseq->clk_enabled = false; |
96 | } |
97 | } |
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 | static 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, size: sizeof(*pwrseq), GFP_KERNEL); |
117 | if (!pwrseq) |
118 | return -ENOMEM; |
119 | |
120 | pwrseq->ext_clk = devm_clk_get(dev, id: "ext_clock" ); |
121 | if (IS_ERR(ptr: pwrseq->ext_clk) && PTR_ERR(ptr: pwrseq->ext_clk) != -ENOENT) |
122 | return dev_err_probe(dev, err: PTR_ERR(ptr: pwrseq->ext_clk), fmt: "external clock not ready\n" ); |
123 | |
124 | pwrseq->reset_gpios = devm_gpiod_get_array(dev, con_id: "reset" , |
125 | flags: GPIOD_OUT_HIGH); |
126 | if (IS_ERR(ptr: pwrseq->reset_gpios) && |
127 | PTR_ERR(ptr: pwrseq->reset_gpios) != -ENOENT && |
128 | PTR_ERR(ptr: pwrseq->reset_gpios) != -ENOSYS) { |
129 | return dev_err_probe(dev, err: PTR_ERR(ptr: pwrseq->reset_gpios), fmt: "reset GPIOs not ready\n" ); |
130 | } |
131 | |
132 | device_property_read_u32(dev, propname: "post-power-on-delay-ms" , |
133 | val: &pwrseq->post_power_on_delay_ms); |
134 | device_property_read_u32(dev, propname: "power-off-delay-us" , |
135 | val: &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, data: pwrseq); |
141 | |
142 | return mmc_pwrseq_register(pwrseq: &pwrseq->pwrseq); |
143 | } |
144 | |
145 | static void 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->pwrseq); |
150 | } |
151 | |
152 | static struct platform_driver mmc_pwrseq_simple_driver = { |
153 | .probe = mmc_pwrseq_simple_probe, |
154 | .remove_new = mmc_pwrseq_simple_remove, |
155 | .driver = { |
156 | .name = "pwrseq_simple" , |
157 | .of_match_table = mmc_pwrseq_simple_of_match, |
158 | }, |
159 | }; |
160 | |
161 | module_platform_driver(mmc_pwrseq_simple_driver); |
162 | MODULE_LICENSE("GPL v2" ); |
163 | |