1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * pwrseq_sd8787.c - power sequence support for Marvell SD8787 BT + Wifi chip |
4 | * |
5 | * Copyright (C) 2016 Matt Ranostay <matt@ranostay.consulting> |
6 | * |
7 | * Based on the original work pwrseq_simple.c |
8 | * Copyright (C) 2014 Linaro Ltd |
9 | * Author: Ulf Hansson <ulf.hansson@linaro.org> |
10 | */ |
11 | |
12 | #include <linux/delay.h> |
13 | #include <linux/init.h> |
14 | #include <linux/kernel.h> |
15 | #include <linux/platform_device.h> |
16 | #include <linux/module.h> |
17 | #include <linux/of.h> |
18 | #include <linux/slab.h> |
19 | #include <linux/device.h> |
20 | #include <linux/err.h> |
21 | #include <linux/gpio/consumer.h> |
22 | |
23 | #include <linux/mmc/host.h> |
24 | |
25 | #include "pwrseq.h" |
26 | |
27 | struct mmc_pwrseq_sd8787 { |
28 | struct mmc_pwrseq pwrseq; |
29 | struct gpio_desc *reset_gpio; |
30 | struct gpio_desc *pwrdn_gpio; |
31 | }; |
32 | |
33 | #define to_pwrseq_sd8787(p) container_of(p, struct mmc_pwrseq_sd8787, pwrseq) |
34 | |
35 | static void mmc_pwrseq_sd8787_pre_power_on(struct mmc_host *host) |
36 | { |
37 | struct mmc_pwrseq_sd8787 *pwrseq = to_pwrseq_sd8787(host->pwrseq); |
38 | |
39 | gpiod_set_value_cansleep(desc: pwrseq->reset_gpio, value: 1); |
40 | |
41 | msleep(msecs: 300); |
42 | gpiod_set_value_cansleep(desc: pwrseq->pwrdn_gpio, value: 1); |
43 | } |
44 | |
45 | static void mmc_pwrseq_sd8787_power_off(struct mmc_host *host) |
46 | { |
47 | struct mmc_pwrseq_sd8787 *pwrseq = to_pwrseq_sd8787(host->pwrseq); |
48 | |
49 | gpiod_set_value_cansleep(desc: pwrseq->pwrdn_gpio, value: 0); |
50 | gpiod_set_value_cansleep(desc: pwrseq->reset_gpio, value: 0); |
51 | } |
52 | |
53 | static void mmc_pwrseq_wilc1000_pre_power_on(struct mmc_host *host) |
54 | { |
55 | struct mmc_pwrseq_sd8787 *pwrseq = to_pwrseq_sd8787(host->pwrseq); |
56 | |
57 | /* The pwrdn_gpio is really CHIP_EN, reset_gpio is RESETN */ |
58 | gpiod_set_value_cansleep(desc: pwrseq->pwrdn_gpio, value: 1); |
59 | msleep(msecs: 5); |
60 | gpiod_set_value_cansleep(desc: pwrseq->reset_gpio, value: 1); |
61 | } |
62 | |
63 | static void mmc_pwrseq_wilc1000_power_off(struct mmc_host *host) |
64 | { |
65 | struct mmc_pwrseq_sd8787 *pwrseq = to_pwrseq_sd8787(host->pwrseq); |
66 | |
67 | gpiod_set_value_cansleep(desc: pwrseq->reset_gpio, value: 0); |
68 | gpiod_set_value_cansleep(desc: pwrseq->pwrdn_gpio, value: 0); |
69 | } |
70 | |
71 | static const struct mmc_pwrseq_ops mmc_pwrseq_sd8787_ops = { |
72 | .pre_power_on = mmc_pwrseq_sd8787_pre_power_on, |
73 | .power_off = mmc_pwrseq_sd8787_power_off, |
74 | }; |
75 | |
76 | static const struct mmc_pwrseq_ops mmc_pwrseq_wilc1000_ops = { |
77 | .pre_power_on = mmc_pwrseq_wilc1000_pre_power_on, |
78 | .power_off = mmc_pwrseq_wilc1000_power_off, |
79 | }; |
80 | |
81 | static const struct of_device_id mmc_pwrseq_sd8787_of_match[] = { |
82 | { .compatible = "mmc-pwrseq-sd8787" , .data = &mmc_pwrseq_sd8787_ops }, |
83 | { .compatible = "mmc-pwrseq-wilc1000" , .data = &mmc_pwrseq_wilc1000_ops }, |
84 | {/* sentinel */}, |
85 | }; |
86 | MODULE_DEVICE_TABLE(of, mmc_pwrseq_sd8787_of_match); |
87 | |
88 | static int mmc_pwrseq_sd8787_probe(struct platform_device *pdev) |
89 | { |
90 | struct mmc_pwrseq_sd8787 *pwrseq; |
91 | struct device *dev = &pdev->dev; |
92 | const struct of_device_id *match; |
93 | |
94 | pwrseq = devm_kzalloc(dev, size: sizeof(*pwrseq), GFP_KERNEL); |
95 | if (!pwrseq) |
96 | return -ENOMEM; |
97 | |
98 | match = of_match_node(matches: mmc_pwrseq_sd8787_of_match, node: pdev->dev.of_node); |
99 | |
100 | pwrseq->pwrdn_gpio = devm_gpiod_get(dev, con_id: "powerdown" , flags: GPIOD_OUT_LOW); |
101 | if (IS_ERR(ptr: pwrseq->pwrdn_gpio)) |
102 | return PTR_ERR(ptr: pwrseq->pwrdn_gpio); |
103 | |
104 | pwrseq->reset_gpio = devm_gpiod_get(dev, con_id: "reset" , flags: GPIOD_OUT_LOW); |
105 | if (IS_ERR(ptr: pwrseq->reset_gpio)) |
106 | return PTR_ERR(ptr: pwrseq->reset_gpio); |
107 | |
108 | pwrseq->pwrseq.dev = dev; |
109 | pwrseq->pwrseq.ops = match->data; |
110 | pwrseq->pwrseq.owner = THIS_MODULE; |
111 | platform_set_drvdata(pdev, data: pwrseq); |
112 | |
113 | return mmc_pwrseq_register(pwrseq: &pwrseq->pwrseq); |
114 | } |
115 | |
116 | static void mmc_pwrseq_sd8787_remove(struct platform_device *pdev) |
117 | { |
118 | struct mmc_pwrseq_sd8787 *pwrseq = platform_get_drvdata(pdev); |
119 | |
120 | mmc_pwrseq_unregister(pwrseq: &pwrseq->pwrseq); |
121 | } |
122 | |
123 | static struct platform_driver mmc_pwrseq_sd8787_driver = { |
124 | .probe = mmc_pwrseq_sd8787_probe, |
125 | .remove_new = mmc_pwrseq_sd8787_remove, |
126 | .driver = { |
127 | .name = "pwrseq_sd8787" , |
128 | .of_match_table = mmc_pwrseq_sd8787_of_match, |
129 | }, |
130 | }; |
131 | |
132 | module_platform_driver(mmc_pwrseq_sd8787_driver); |
133 | MODULE_LICENSE("GPL v2" ); |
134 | |