xref: /openbmc/linux/drivers/mmc/core/slot-gpio.c (revision ba2929159000dc7015cc01cdf7bb72542e19952a)
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