Commit | Line | Data |
---|---|---|
8c96f89c UH |
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 | */ | |
c13045b1 | 10 | #include <linux/clk.h> |
d97a1e5d | 11 | #include <linux/init.h> |
8c96f89c | 12 | #include <linux/kernel.h> |
d97a1e5d SK |
13 | #include <linux/platform_device.h> |
14 | #include <linux/module.h> | |
8c96f89c UH |
15 | #include <linux/slab.h> |
16 | #include <linux/device.h> | |
17 | #include <linux/err.h> | |
862b5dcf | 18 | #include <linux/gpio/consumer.h> |
958e6612 HG |
19 | #include <linux/delay.h> |
20 | #include <linux/property.h> | |
8c96f89c UH |
21 | |
22 | #include <linux/mmc/host.h> | |
23 | ||
24 | #include "pwrseq.h" | |
25 | ||
26 | struct mmc_pwrseq_simple { | |
27 | struct mmc_pwrseq pwrseq; | |
c13045b1 | 28 | bool clk_enabled; |
958e6612 | 29 | u32 post_power_on_delay_ms; |
c13045b1 | 30 | struct clk *ext_clk; |
ce037275 | 31 | struct gpio_descs *reset_gpios; |
8c96f89c UH |
32 | }; |
33 | ||
5b96fea7 SK |
34 | #define to_pwrseq_simple(p) container_of(p, struct mmc_pwrseq_simple, pwrseq) |
35 | ||
934f1f48 JMC |
36 | static void mmc_pwrseq_simple_set_gpios_value(struct mmc_pwrseq_simple *pwrseq, |
37 | int value) | |
38 | { | |
ce037275 | 39 | struct gpio_descs *reset_gpios = pwrseq->reset_gpios; |
934f1f48 | 40 | |
64a67d47 MF |
41 | if (!IS_ERR(reset_gpios)) { |
42 | int i; | |
43 | int values[reset_gpios->ndescs]; | |
ce037275 | 44 | |
64a67d47 MF |
45 | for (i = 0; i < reset_gpios->ndescs; i++) |
46 | values[i] = value; | |
47 | ||
48 | gpiod_set_array_value_cansleep( | |
49 | reset_gpios->ndescs, reset_gpios->desc, values); | |
50 | } | |
934f1f48 JMC |
51 | } |
52 | ||
862b5dcf UH |
53 | static void mmc_pwrseq_simple_pre_power_on(struct mmc_host *host) |
54 | { | |
5b96fea7 | 55 | struct mmc_pwrseq_simple *pwrseq = to_pwrseq_simple(host->pwrseq); |
862b5dcf | 56 | |
c13045b1 JMC |
57 | if (!IS_ERR(pwrseq->ext_clk) && !pwrseq->clk_enabled) { |
58 | clk_prepare_enable(pwrseq->ext_clk); | |
59 | pwrseq->clk_enabled = true; | |
60 | } | |
61 | ||
934f1f48 | 62 | mmc_pwrseq_simple_set_gpios_value(pwrseq, 1); |
862b5dcf UH |
63 | } |
64 | ||
65 | static void mmc_pwrseq_simple_post_power_on(struct mmc_host *host) | |
66 | { | |
5b96fea7 | 67 | struct mmc_pwrseq_simple *pwrseq = to_pwrseq_simple(host->pwrseq); |
862b5dcf | 68 | |
934f1f48 | 69 | mmc_pwrseq_simple_set_gpios_value(pwrseq, 0); |
958e6612 HG |
70 | |
71 | if (pwrseq->post_power_on_delay_ms) | |
72 | msleep(pwrseq->post_power_on_delay_ms); | |
862b5dcf UH |
73 | } |
74 | ||
c13045b1 JMC |
75 | static void mmc_pwrseq_simple_power_off(struct mmc_host *host) |
76 | { | |
5b96fea7 | 77 | struct mmc_pwrseq_simple *pwrseq = to_pwrseq_simple(host->pwrseq); |
c13045b1 JMC |
78 | |
79 | mmc_pwrseq_simple_set_gpios_value(pwrseq, 1); | |
80 | ||
81 | if (!IS_ERR(pwrseq->ext_clk) && pwrseq->clk_enabled) { | |
82 | clk_disable_unprepare(pwrseq->ext_clk); | |
83 | pwrseq->clk_enabled = false; | |
84 | } | |
85 | } | |
86 | ||
ffedbd22 | 87 | static const struct mmc_pwrseq_ops mmc_pwrseq_simple_ops = { |
862b5dcf UH |
88 | .pre_power_on = mmc_pwrseq_simple_pre_power_on, |
89 | .post_power_on = mmc_pwrseq_simple_post_power_on, | |
c13045b1 | 90 | .power_off = mmc_pwrseq_simple_power_off, |
8c96f89c UH |
91 | }; |
92 | ||
d97a1e5d SK |
93 | static const struct of_device_id mmc_pwrseq_simple_of_match[] = { |
94 | { .compatible = "mmc-pwrseq-simple",}, | |
95 | {/* sentinel */}, | |
96 | }; | |
97 | MODULE_DEVICE_TABLE(of, mmc_pwrseq_simple_of_match); | |
98 | ||
99 | static int mmc_pwrseq_simple_probe(struct platform_device *pdev) | |
8c96f89c UH |
100 | { |
101 | struct mmc_pwrseq_simple *pwrseq; | |
d97a1e5d | 102 | struct device *dev = &pdev->dev; |
934f1f48 | 103 | |
d97a1e5d | 104 | pwrseq = devm_kzalloc(dev, sizeof(*pwrseq), GFP_KERNEL); |
8c96f89c | 105 | if (!pwrseq) |
d97a1e5d | 106 | return -ENOMEM; |
8c96f89c | 107 | |
d97a1e5d SK |
108 | pwrseq->ext_clk = devm_clk_get(dev, "ext_clock"); |
109 | if (IS_ERR(pwrseq->ext_clk) && PTR_ERR(pwrseq->ext_clk) != -ENOENT) | |
110 | return PTR_ERR(pwrseq->ext_clk); | |
c13045b1 | 111 | |
d97a1e5d SK |
112 | pwrseq->reset_gpios = devm_gpiod_get_array(dev, "reset", |
113 | GPIOD_OUT_HIGH); | |
64a67d47 MF |
114 | if (IS_ERR(pwrseq->reset_gpios) && |
115 | PTR_ERR(pwrseq->reset_gpios) != -ENOENT && | |
116 | PTR_ERR(pwrseq->reset_gpios) != -ENOSYS) { | |
d97a1e5d | 117 | return PTR_ERR(pwrseq->reset_gpios); |
862b5dcf UH |
118 | } |
119 | ||
958e6612 HG |
120 | device_property_read_u32(dev, "post-power-on-delay-ms", |
121 | &pwrseq->post_power_on_delay_ms); | |
122 | ||
d97a1e5d | 123 | pwrseq->pwrseq.dev = dev; |
8c96f89c | 124 | pwrseq->pwrseq.ops = &mmc_pwrseq_simple_ops; |
d97a1e5d SK |
125 | pwrseq->pwrseq.owner = THIS_MODULE; |
126 | platform_set_drvdata(pdev, pwrseq); | |
8c96f89c | 127 | |
d97a1e5d | 128 | return mmc_pwrseq_register(&pwrseq->pwrseq); |
8c96f89c | 129 | } |
d97a1e5d SK |
130 | |
131 | static int mmc_pwrseq_simple_remove(struct platform_device *pdev) | |
132 | { | |
133 | struct mmc_pwrseq_simple *pwrseq = platform_get_drvdata(pdev); | |
134 | ||
135 | mmc_pwrseq_unregister(&pwrseq->pwrseq); | |
136 | ||
137 | return 0; | |
138 | } | |
139 | ||
140 | static struct platform_driver mmc_pwrseq_simple_driver = { | |
141 | .probe = mmc_pwrseq_simple_probe, | |
142 | .remove = mmc_pwrseq_simple_remove, | |
143 | .driver = { | |
144 | .name = "pwrseq_simple", | |
145 | .of_match_table = mmc_pwrseq_simple_of_match, | |
146 | }, | |
147 | }; | |
148 | ||
149 | module_platform_driver(mmc_pwrseq_simple_driver); | |
150 | MODULE_LICENSE("GPL v2"); |