1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
2fd0ea65dSGuennadi Liakhovetski /*
3fd0ea65dSGuennadi Liakhovetski * Generic GPIO card-detect helper
4fd0ea65dSGuennadi Liakhovetski *
5fd0ea65dSGuennadi Liakhovetski * Copyright (C) 2011, Guennadi Liakhovetski <g.liakhovetski@gmx.de>
6fd0ea65dSGuennadi Liakhovetski */
7fd0ea65dSGuennadi Liakhovetski
8fd0ea65dSGuennadi Liakhovetski #include <linux/err.h>
9842f4bddSAdrian Hunter #include <linux/gpio/consumer.h>
10fd0ea65dSGuennadi Liakhovetski #include <linux/interrupt.h>
11fd0ea65dSGuennadi Liakhovetski #include <linux/jiffies.h>
12fd0ea65dSGuennadi Liakhovetski #include <linux/mmc/host.h>
13fd0ea65dSGuennadi Liakhovetski #include <linux/mmc/slot-gpio.h>
14fd0ea65dSGuennadi Liakhovetski #include <linux/module.h>
15fd0ea65dSGuennadi Liakhovetski #include <linux/slab.h>
16fd0ea65dSGuennadi Liakhovetski
177f133de1SUlf Hansson #include "slot-gpio.h"
187f133de1SUlf Hansson
19fd0ea65dSGuennadi Liakhovetski struct mmc_gpio {
20842f4bddSAdrian Hunter struct gpio_desc *ro_gpio;
21842f4bddSAdrian Hunter struct gpio_desc *cd_gpio;
22c7ea834dSNeilBrown irqreturn_t (*cd_gpio_isr)(int irq, void *dev_id);
235aa7dad3SGuennadi Liakhovetski char *ro_label;
24ec5af091SLinus Walleij char *cd_label;
25bfd694d5SShawn Lin u32 cd_debounce_delay_ms;
2688f94c78SHeiner Kallweit int cd_irq;
27fd0ea65dSGuennadi Liakhovetski };
28fd0ea65dSGuennadi Liakhovetski
mmc_gpio_cd_irqt(int irq,void * dev_id)29fd0ea65dSGuennadi Liakhovetski static irqreturn_t mmc_gpio_cd_irqt(int irq, void *dev_id)
30fd0ea65dSGuennadi Liakhovetski {
31fd0ea65dSGuennadi Liakhovetski /* Schedule a card detection after a debounce timeout */
32451c8957SGuennadi Liakhovetski struct mmc_host *host = dev_id;
33bfd694d5SShawn Lin struct mmc_gpio *ctx = host->slot.handler_priv;
34451c8957SGuennadi Liakhovetski
35fa372a51SMarkus Mayer host->trigger_card_event = true;
36bfd694d5SShawn Lin mmc_detect_change(host, msecs_to_jiffies(ctx->cd_debounce_delay_ms));
37451c8957SGuennadi Liakhovetski
38fd0ea65dSGuennadi Liakhovetski return IRQ_HANDLED;
39fd0ea65dSGuennadi Liakhovetski }
40fd0ea65dSGuennadi Liakhovetski
mmc_gpio_alloc(struct mmc_host * host)417f133de1SUlf Hansson int mmc_gpio_alloc(struct mmc_host *host)
42a7d1a1ebSGuennadi Liakhovetski {
434877b81fSAndy Shevchenko const char *devname = dev_name(host->parent);
444877b81fSAndy Shevchenko struct mmc_gpio *ctx;
45a7d1a1ebSGuennadi Liakhovetski
464877b81fSAndy Shevchenko ctx = devm_kzalloc(host->parent, sizeof(*ctx), GFP_KERNEL);
474877b81fSAndy Shevchenko if (!ctx)
484877b81fSAndy Shevchenko return -ENOMEM;
494877b81fSAndy Shevchenko
50bfd694d5SShawn Lin ctx->cd_debounce_delay_ms = 200;
514877b81fSAndy Shevchenko ctx->cd_label = devm_kasprintf(host->parent, GFP_KERNEL, "%s cd", devname);
52ec5af091SLinus Walleij if (!ctx->cd_label)
53ec5af091SLinus Walleij return -ENOMEM;
544877b81fSAndy Shevchenko ctx->ro_label = devm_kasprintf(host->parent, GFP_KERNEL, "%s ro", devname);
55ec5af091SLinus Walleij if (!ctx->ro_label)
56ec5af091SLinus Walleij return -ENOMEM;
5788f94c78SHeiner Kallweit ctx->cd_irq = -EINVAL;
58a7d1a1ebSGuennadi Liakhovetski host->slot.handler_priv = ctx;
59df8aca16SUlf Hansson host->slot.cd_irq = -EINVAL;
60a7d1a1ebSGuennadi Liakhovetski
614877b81fSAndy Shevchenko return 0;
62a7d1a1ebSGuennadi Liakhovetski }
63a7d1a1ebSGuennadi Liakhovetski
mmc_gpio_set_cd_irq(struct mmc_host * host,int irq)6488f94c78SHeiner Kallweit void mmc_gpio_set_cd_irq(struct mmc_host *host, int irq)
6588f94c78SHeiner Kallweit {
6688f94c78SHeiner Kallweit struct mmc_gpio *ctx = host->slot.handler_priv;
6788f94c78SHeiner Kallweit
6888f94c78SHeiner Kallweit if (!ctx || irq < 0)
6988f94c78SHeiner Kallweit return;
7088f94c78SHeiner Kallweit
7188f94c78SHeiner Kallweit ctx->cd_irq = irq;
7288f94c78SHeiner Kallweit }
7388f94c78SHeiner Kallweit EXPORT_SYMBOL(mmc_gpio_set_cd_irq);
7488f94c78SHeiner Kallweit
mmc_gpio_get_ro(struct mmc_host * host)755aa7dad3SGuennadi Liakhovetski int mmc_gpio_get_ro(struct mmc_host *host)
765aa7dad3SGuennadi Liakhovetski {
775aa7dad3SGuennadi Liakhovetski struct mmc_gpio *ctx = host->slot.handler_priv;
7822437814SAlexander Stein int cansleep;
795aa7dad3SGuennadi Liakhovetski
80842f4bddSAdrian Hunter if (!ctx || !ctx->ro_gpio)
815aa7dad3SGuennadi Liakhovetski return -ENOSYS;
825aa7dad3SGuennadi Liakhovetski
8322437814SAlexander Stein cansleep = gpiod_cansleep(ctx->ro_gpio);
8422437814SAlexander Stein return cansleep ?
8522437814SAlexander Stein gpiod_get_value_cansleep(ctx->ro_gpio) :
8622437814SAlexander Stein gpiod_get_value(ctx->ro_gpio);
875aa7dad3SGuennadi Liakhovetski }
885aa7dad3SGuennadi Liakhovetski EXPORT_SYMBOL(mmc_gpio_get_ro);
895aa7dad3SGuennadi Liakhovetski
mmc_gpio_get_cd(struct mmc_host * host)90befe4048SGuennadi Liakhovetski int mmc_gpio_get_cd(struct mmc_host *host)
91befe4048SGuennadi Liakhovetski {
92befe4048SGuennadi Liakhovetski struct mmc_gpio *ctx = host->slot.handler_priv;
9352af318cSEvan Green int cansleep;
94befe4048SGuennadi Liakhovetski
95842f4bddSAdrian Hunter if (!ctx || !ctx->cd_gpio)
96befe4048SGuennadi Liakhovetski return -ENOSYS;
97befe4048SGuennadi Liakhovetski
9852af318cSEvan Green cansleep = gpiod_cansleep(ctx->cd_gpio);
9952af318cSEvan Green return cansleep ?
10052af318cSEvan Green gpiod_get_value_cansleep(ctx->cd_gpio) :
10152af318cSEvan Green gpiod_get_value(ctx->cd_gpio);
102befe4048SGuennadi Liakhovetski }
103befe4048SGuennadi Liakhovetski EXPORT_SYMBOL(mmc_gpio_get_cd);
104befe4048SGuennadi Liakhovetski
mmc_gpiod_request_cd_irq(struct mmc_host * host)105740a221eSAdrian Hunter void mmc_gpiod_request_cd_irq(struct mmc_host *host)
10626652671SAdrian Hunter {
10726652671SAdrian Hunter struct mmc_gpio *ctx = host->slot.handler_priv;
1085f3a8601SMasahiro Yamada int irq = -EINVAL;
1095f3a8601SMasahiro Yamada int ret;
11026652671SAdrian Hunter
11126652671SAdrian Hunter if (host->slot.cd_irq >= 0 || !ctx || !ctx->cd_gpio)
11226652671SAdrian Hunter return;
11326652671SAdrian Hunter
11426652671SAdrian Hunter /*
1155f3a8601SMasahiro Yamada * Do not use IRQ if the platform prefers to poll, e.g., because that
1165f3a8601SMasahiro Yamada * IRQ number is already used by another unit and cannot be shared.
11726652671SAdrian Hunter */
11888f94c78SHeiner Kallweit if (ctx->cd_irq >= 0)
11988f94c78SHeiner Kallweit irq = ctx->cd_irq;
12088f94c78SHeiner Kallweit else if (!(host->caps & MMC_CAP_NEEDS_POLL))
1215f3a8601SMasahiro Yamada irq = gpiod_to_irq(ctx->cd_gpio);
12226652671SAdrian Hunter
12326652671SAdrian Hunter if (irq >= 0) {
124c7ea834dSNeilBrown if (!ctx->cd_gpio_isr)
125c7ea834dSNeilBrown ctx->cd_gpio_isr = mmc_gpio_cd_irqt;
126b4cc580bSUlf Hansson ret = devm_request_threaded_irq(host->parent, irq,
127c7ea834dSNeilBrown NULL, ctx->cd_gpio_isr,
12826652671SAdrian Hunter IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
12926652671SAdrian Hunter ctx->cd_label, host);
13026652671SAdrian Hunter if (ret < 0)
13126652671SAdrian Hunter irq = ret;
13226652671SAdrian Hunter }
13326652671SAdrian Hunter
13426652671SAdrian Hunter host->slot.cd_irq = irq;
13526652671SAdrian Hunter
13626652671SAdrian Hunter if (irq < 0)
13726652671SAdrian Hunter host->caps |= MMC_CAP_NEEDS_POLL;
13826652671SAdrian Hunter }
139740a221eSAdrian Hunter EXPORT_SYMBOL(mmc_gpiod_request_cd_irq);
14026652671SAdrian Hunter
mmc_gpio_set_cd_wake(struct mmc_host * host,bool on)14136f1d7e8SAdrian Hunter int mmc_gpio_set_cd_wake(struct mmc_host *host, bool on)
14236f1d7e8SAdrian Hunter {
14336f1d7e8SAdrian Hunter int ret = 0;
14436f1d7e8SAdrian Hunter
14536f1d7e8SAdrian Hunter if (!(host->caps & MMC_CAP_CD_WAKE) ||
14636f1d7e8SAdrian Hunter host->slot.cd_irq < 0 ||
14736f1d7e8SAdrian Hunter on == host->slot.cd_wake_enabled)
14836f1d7e8SAdrian Hunter return 0;
14936f1d7e8SAdrian Hunter
15036f1d7e8SAdrian Hunter if (on) {
15136f1d7e8SAdrian Hunter ret = enable_irq_wake(host->slot.cd_irq);
15236f1d7e8SAdrian Hunter host->slot.cd_wake_enabled = !ret;
15336f1d7e8SAdrian Hunter } else {
15436f1d7e8SAdrian Hunter disable_irq_wake(host->slot.cd_irq);
15536f1d7e8SAdrian Hunter host->slot.cd_wake_enabled = false;
15636f1d7e8SAdrian Hunter }
15736f1d7e8SAdrian Hunter
15836f1d7e8SAdrian Hunter return ret;
15936f1d7e8SAdrian Hunter }
16036f1d7e8SAdrian Hunter EXPORT_SYMBOL(mmc_gpio_set_cd_wake);
16136f1d7e8SAdrian Hunter
162c7ea834dSNeilBrown /* Register an alternate interrupt service routine for
163c7ea834dSNeilBrown * the card-detect GPIO.
164c7ea834dSNeilBrown */
mmc_gpio_set_cd_isr(struct mmc_host * host,irqreturn_t (* isr)(int irq,void * dev_id))165c7ea834dSNeilBrown void mmc_gpio_set_cd_isr(struct mmc_host *host,
166c7ea834dSNeilBrown irqreturn_t (*isr)(int irq, void *dev_id))
167c7ea834dSNeilBrown {
168c7ea834dSNeilBrown struct mmc_gpio *ctx = host->slot.handler_priv;
169c7ea834dSNeilBrown
170c7ea834dSNeilBrown WARN_ON(ctx->cd_gpio_isr);
171c7ea834dSNeilBrown ctx->cd_gpio_isr = isr;
172c7ea834dSNeilBrown }
173c7ea834dSNeilBrown EXPORT_SYMBOL(mmc_gpio_set_cd_isr);
174c7ea834dSNeilBrown
175d65b5ae8SShawn Guo /**
176740a221eSAdrian Hunter * mmc_gpiod_request_cd - request a gpio descriptor for card-detection
177740a221eSAdrian Hunter * @host: mmc host
178740a221eSAdrian Hunter * @con_id: function within the GPIO consumer
179740a221eSAdrian Hunter * @idx: index of the GPIO to obtain in the consumer
180740a221eSAdrian Hunter * @override_active_level: ignore %GPIO_ACTIVE_LOW flag
181740a221eSAdrian Hunter * @debounce: debounce time in microseconds
182740a221eSAdrian Hunter *
183a622bb0aSLinus Walleij * Note that this must be called prior to mmc_add_host()
184740a221eSAdrian Hunter * otherwise the caller must also call mmc_gpiod_request_cd_irq().
185740a221eSAdrian Hunter *
186740a221eSAdrian Hunter * Returns zero on success, else an error.
187740a221eSAdrian Hunter */
mmc_gpiod_request_cd(struct mmc_host * host,const char * con_id,unsigned int idx,bool override_active_level,unsigned int debounce)188740a221eSAdrian Hunter int mmc_gpiod_request_cd(struct mmc_host *host, const char *con_id,
189740a221eSAdrian Hunter unsigned int idx, bool override_active_level,
190d0052ad9SMichał Mirosław unsigned int debounce)
191740a221eSAdrian Hunter {
192df8aca16SUlf Hansson struct mmc_gpio *ctx = host->slot.handler_priv;
193740a221eSAdrian Hunter struct gpio_desc *desc;
194740a221eSAdrian Hunter int ret;
195740a221eSAdrian Hunter
1969fbc6950SLinus Walleij desc = devm_gpiod_get_index(host->parent, con_id, idx, GPIOD_IN);
197740a221eSAdrian Hunter if (IS_ERR(desc))
198740a221eSAdrian Hunter return PTR_ERR(desc);
199740a221eSAdrian Hunter
2008792b0a0SAndy Shevchenko /* Update default label if no con_id provided */
2018792b0a0SAndy Shevchenko if (!con_id)
2028792b0a0SAndy Shevchenko gpiod_set_consumer_name(desc, ctx->cd_label);
2038792b0a0SAndy Shevchenko
204740a221eSAdrian Hunter if (debounce) {
205740a221eSAdrian Hunter ret = gpiod_set_debounce(desc, debounce);
206740a221eSAdrian Hunter if (ret < 0)
2071b09d9c2SMarek Szyprowski ctx->cd_debounce_delay_ms = debounce / 1000;
208740a221eSAdrian Hunter }
209740a221eSAdrian Hunter
2100f7c815dSMichał Mirosław /* override forces default (active-low) polarity ... */
2110f7c815dSMichał Mirosław if (override_active_level && !gpiod_is_active_low(desc))
2120f7c815dSMichał Mirosław gpiod_toggle_active_low(desc);
2130f7c815dSMichał Mirosław
2140f7c815dSMichał Mirosław /* ... or active-high */
2150f7c815dSMichał Mirosław if (host->caps2 & MMC_CAP2_CD_ACTIVE_HIGH)
2160f7c815dSMichał Mirosław gpiod_toggle_active_low(desc);
2170f7c815dSMichał Mirosław
218740a221eSAdrian Hunter ctx->cd_gpio = desc;
219740a221eSAdrian Hunter
220740a221eSAdrian Hunter return 0;
221740a221eSAdrian Hunter }
222740a221eSAdrian Hunter EXPORT_SYMBOL(mmc_gpiod_request_cd);
223740a221eSAdrian Hunter
224*c0a16ff4SHans de Goede /**
225*c0a16ff4SHans de Goede * mmc_gpiod_set_cd_config - set config for card-detection GPIO
226*c0a16ff4SHans de Goede * @host: mmc host
227*c0a16ff4SHans de Goede * @config: Generic pinconf config (from pinconf_to_config_packed())
228*c0a16ff4SHans de Goede *
229*c0a16ff4SHans de Goede * This can be used by mmc host drivers to fixup a card-detection GPIO's config
230*c0a16ff4SHans de Goede * (e.g. set PIN_CONFIG_BIAS_PULL_UP) after acquiring the GPIO descriptor
231*c0a16ff4SHans de Goede * through mmc_gpiod_request_cd().
232*c0a16ff4SHans de Goede *
233*c0a16ff4SHans de Goede * Returns:
234*c0a16ff4SHans de Goede * 0 on success, or a negative errno value on error.
235*c0a16ff4SHans de Goede */
mmc_gpiod_set_cd_config(struct mmc_host * host,unsigned long config)236*c0a16ff4SHans de Goede int mmc_gpiod_set_cd_config(struct mmc_host *host, unsigned long config)
237*c0a16ff4SHans de Goede {
238*c0a16ff4SHans de Goede struct mmc_gpio *ctx = host->slot.handler_priv;
239*c0a16ff4SHans de Goede
240*c0a16ff4SHans de Goede return gpiod_set_config(ctx->cd_gpio, config);
241*c0a16ff4SHans de Goede }
242*c0a16ff4SHans de Goede EXPORT_SYMBOL(mmc_gpiod_set_cd_config);
243*c0a16ff4SHans de Goede
mmc_can_gpio_cd(struct mmc_host * host)24450fcbbbbSShawn Lin bool mmc_can_gpio_cd(struct mmc_host *host)
24550fcbbbbSShawn Lin {
24650fcbbbbSShawn Lin struct mmc_gpio *ctx = host->slot.handler_priv;
24750fcbbbbSShawn Lin
24850fcbbbbSShawn Lin return ctx->cd_gpio ? true : false;
24950fcbbbbSShawn Lin }
25050fcbbbbSShawn Lin EXPORT_SYMBOL(mmc_can_gpio_cd);
25150fcbbbbSShawn Lin
252740a221eSAdrian Hunter /**
2539d2fa242SLinus Walleij * mmc_gpiod_request_ro - request a gpio descriptor for write protection
2549d2fa242SLinus Walleij * @host: mmc host
2559d2fa242SLinus Walleij * @con_id: function within the GPIO consumer
2569d2fa242SLinus Walleij * @idx: index of the GPIO to obtain in the consumer
2579d2fa242SLinus Walleij * @debounce: debounce time in microseconds
2589d2fa242SLinus Walleij *
2599d2fa242SLinus Walleij * Returns zero on success, else an error.
2609d2fa242SLinus Walleij */
mmc_gpiod_request_ro(struct mmc_host * host,const char * con_id,unsigned int idx,unsigned int debounce)2619d2fa242SLinus Walleij int mmc_gpiod_request_ro(struct mmc_host *host, const char *con_id,
262d0052ad9SMichał Mirosław unsigned int idx, unsigned int debounce)
2639d2fa242SLinus Walleij {
264df8aca16SUlf Hansson struct mmc_gpio *ctx = host->slot.handler_priv;
2659d2fa242SLinus Walleij struct gpio_desc *desc;
2669d2fa242SLinus Walleij int ret;
2679d2fa242SLinus Walleij
2689d2fa242SLinus Walleij desc = devm_gpiod_get_index(host->parent, con_id, idx, GPIOD_IN);
2699d2fa242SLinus Walleij if (IS_ERR(desc))
2709d2fa242SLinus Walleij return PTR_ERR(desc);
2719d2fa242SLinus Walleij
2728792b0a0SAndy Shevchenko /* Update default label if no con_id provided */
2738792b0a0SAndy Shevchenko if (!con_id)
2748792b0a0SAndy Shevchenko gpiod_set_consumer_name(desc, ctx->ro_label);
2758792b0a0SAndy Shevchenko
2769d2fa242SLinus Walleij if (debounce) {
2779d2fa242SLinus Walleij ret = gpiod_set_debounce(desc, debounce);
2789d2fa242SLinus Walleij if (ret < 0)
2799d2fa242SLinus Walleij return ret;
2809d2fa242SLinus Walleij }
2819d2fa242SLinus Walleij
2829073d10bSMichał Mirosław if (host->caps2 & MMC_CAP2_RO_ACTIVE_HIGH)
2839073d10bSMichał Mirosław gpiod_toggle_active_low(desc);
2849073d10bSMichał Mirosław
2859d2fa242SLinus Walleij ctx->ro_gpio = desc;
2869d2fa242SLinus Walleij
2879d2fa242SLinus Walleij return 0;
2889d2fa242SLinus Walleij }
2899d2fa242SLinus Walleij EXPORT_SYMBOL(mmc_gpiod_request_ro);
29085f9ef8cSMasahiro Yamada
mmc_can_gpio_ro(struct mmc_host * host)29185f9ef8cSMasahiro Yamada bool mmc_can_gpio_ro(struct mmc_host *host)
29285f9ef8cSMasahiro Yamada {
29385f9ef8cSMasahiro Yamada struct mmc_gpio *ctx = host->slot.handler_priv;
29485f9ef8cSMasahiro Yamada
29585f9ef8cSMasahiro Yamada return ctx->ro_gpio ? true : false;
29685f9ef8cSMasahiro Yamada }
29785f9ef8cSMasahiro Yamada EXPORT_SYMBOL(mmc_can_gpio_ro);
298