1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2015, Samsung Electronics Co., Ltd. |
4 | * |
5 | * Author: Marek Szyprowski <m.szyprowski@samsung.com> |
6 | * |
7 | * Simple eMMC hardware reset provider |
8 | */ |
9 | #include <linux/delay.h> |
10 | #include <linux/kernel.h> |
11 | #include <linux/init.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/reboot.h> |
19 | |
20 | #include <linux/mmc/host.h> |
21 | |
22 | #include "pwrseq.h" |
23 | |
24 | struct mmc_pwrseq_emmc { |
25 | struct mmc_pwrseq pwrseq; |
26 | struct notifier_block reset_nb; |
27 | struct gpio_desc *reset_gpio; |
28 | }; |
29 | |
30 | #define to_pwrseq_emmc(p) container_of(p, struct mmc_pwrseq_emmc, pwrseq) |
31 | |
32 | static void mmc_pwrseq_emmc_reset(struct mmc_host *host) |
33 | { |
34 | struct mmc_pwrseq_emmc *pwrseq = to_pwrseq_emmc(host->pwrseq); |
35 | |
36 | gpiod_set_value_cansleep(desc: pwrseq->reset_gpio, value: 1); |
37 | udelay(1); |
38 | gpiod_set_value_cansleep(desc: pwrseq->reset_gpio, value: 0); |
39 | udelay(200); |
40 | } |
41 | |
42 | static int mmc_pwrseq_emmc_reset_nb(struct notifier_block *this, |
43 | unsigned long mode, void *cmd) |
44 | { |
45 | struct mmc_pwrseq_emmc *pwrseq = container_of(this, |
46 | struct mmc_pwrseq_emmc, reset_nb); |
47 | gpiod_set_value(desc: pwrseq->reset_gpio, value: 1); |
48 | udelay(1); |
49 | gpiod_set_value(desc: pwrseq->reset_gpio, value: 0); |
50 | udelay(200); |
51 | |
52 | return NOTIFY_DONE; |
53 | } |
54 | |
55 | static const struct mmc_pwrseq_ops mmc_pwrseq_emmc_ops = { |
56 | .reset = mmc_pwrseq_emmc_reset, |
57 | }; |
58 | |
59 | static int mmc_pwrseq_emmc_probe(struct platform_device *pdev) |
60 | { |
61 | struct mmc_pwrseq_emmc *pwrseq; |
62 | struct device *dev = &pdev->dev; |
63 | |
64 | pwrseq = devm_kzalloc(dev, size: sizeof(*pwrseq), GFP_KERNEL); |
65 | if (!pwrseq) |
66 | return -ENOMEM; |
67 | |
68 | pwrseq->reset_gpio = devm_gpiod_get(dev, con_id: "reset" , flags: GPIOD_OUT_LOW); |
69 | if (IS_ERR(ptr: pwrseq->reset_gpio)) |
70 | return PTR_ERR(ptr: pwrseq->reset_gpio); |
71 | |
72 | if (!gpiod_cansleep(desc: pwrseq->reset_gpio)) { |
73 | /* |
74 | * register reset handler to ensure emmc reset also from |
75 | * emergency_reboot(), priority 255 is the highest priority |
76 | * so it will be executed before any system reboot handler. |
77 | */ |
78 | pwrseq->reset_nb.notifier_call = mmc_pwrseq_emmc_reset_nb; |
79 | pwrseq->reset_nb.priority = 255; |
80 | register_restart_handler(&pwrseq->reset_nb); |
81 | } else { |
82 | dev_notice(dev, "EMMC reset pin tied to a sleepy GPIO driver; reset on emergency-reboot disabled\n" ); |
83 | } |
84 | |
85 | pwrseq->pwrseq.ops = &mmc_pwrseq_emmc_ops; |
86 | pwrseq->pwrseq.dev = dev; |
87 | pwrseq->pwrseq.owner = THIS_MODULE; |
88 | platform_set_drvdata(pdev, data: pwrseq); |
89 | |
90 | return mmc_pwrseq_register(pwrseq: &pwrseq->pwrseq); |
91 | } |
92 | |
93 | static void mmc_pwrseq_emmc_remove(struct platform_device *pdev) |
94 | { |
95 | struct mmc_pwrseq_emmc *pwrseq = platform_get_drvdata(pdev); |
96 | |
97 | unregister_restart_handler(&pwrseq->reset_nb); |
98 | mmc_pwrseq_unregister(pwrseq: &pwrseq->pwrseq); |
99 | } |
100 | |
101 | static const struct of_device_id mmc_pwrseq_emmc_of_match[] = { |
102 | { .compatible = "mmc-pwrseq-emmc" ,}, |
103 | {/* sentinel */}, |
104 | }; |
105 | |
106 | MODULE_DEVICE_TABLE(of, mmc_pwrseq_emmc_of_match); |
107 | |
108 | static struct platform_driver mmc_pwrseq_emmc_driver = { |
109 | .probe = mmc_pwrseq_emmc_probe, |
110 | .remove_new = mmc_pwrseq_emmc_remove, |
111 | .driver = { |
112 | .name = "pwrseq_emmc" , |
113 | .of_match_table = mmc_pwrseq_emmc_of_match, |
114 | }, |
115 | }; |
116 | |
117 | module_platform_driver(mmc_pwrseq_emmc_driver); |
118 | MODULE_LICENSE("GPL v2" ); |
119 | |