// SPDX-License-Identifier: GPL-2.0-only /* * Generic GPIO card-detect helper * * Copyright (C) 2011, Guennadi Liakhovetski */ #include #include #include #include #include #include #include #include #include "slot-gpio.h" struct mmc_gpio { struct gpio_desc *ro_gpio; struct gpio_desc *cd_gpio; irqreturn_t (*cd_gpio_isr)(int irq, void *dev_id); char *ro_label; char *cd_label; u32 cd_debounce_delay_ms; int cd_irq; }; static irqreturn_t mmc_gpio_cd_irqt(int irq, void *dev_id) { /* Schedule a card detection after a debounce timeout */ struct mmc_host *host = dev_id; struct mmc_gpio *ctx = host->slot.handler_priv; host->trigger_card_event = true; mmc_detect_change(host, msecs_to_jiffies(ctx->cd_debounce_delay_ms)); return IRQ_HANDLED; } int mmc_gpio_alloc(struct mmc_host *host) { const char *devname = dev_name(host->parent); struct mmc_gpio *ctx; ctx = devm_kzalloc(host->parent, sizeof(*ctx), GFP_KERNEL); if (!ctx) return -ENOMEM; ctx->cd_debounce_delay_ms = 200; ctx->cd_label = devm_kasprintf(host->parent, GFP_KERNEL, "%s cd", devname); if (!ctx->cd_label) return -ENOMEM; ctx->ro_label = devm_kasprintf(host->parent, GFP_KERNEL, "%s ro", devname); if (!ctx->ro_label) return -ENOMEM; ctx->cd_irq = -EINVAL; host->slot.handler_priv = ctx; host->slot.cd_irq = -EINVAL; return 0; } void mmc_gpio_set_cd_irq(struct mmc_host *host, int irq) { struct mmc_gpio *ctx = host->slot.handler_priv; if (!ctx || irq < 0) return; ctx->cd_irq = irq; } EXPORT_SYMBOL(mmc_gpio_set_cd_irq); int mmc_gpio_get_ro(struct mmc_host *host) { struct mmc_gpio *ctx = host->slot.handler_priv; int cansleep; if (!ctx || !ctx->ro_gpio) return -ENOSYS; cansleep = gpiod_cansleep(ctx->ro_gpio); return cansleep ? gpiod_get_value_cansleep(ctx->ro_gpio) : gpiod_get_value(ctx->ro_gpio); } EXPORT_SYMBOL(mmc_gpio_get_ro); int mmc_gpio_get_cd(struct mmc_host *host) { struct mmc_gpio *ctx = host->slot.handler_priv; int cansleep; if (!ctx || !ctx->cd_gpio) return -ENOSYS; cansleep = gpiod_cansleep(ctx->cd_gpio); return cansleep ? gpiod_get_value_cansleep(ctx->cd_gpio) : gpiod_get_value(ctx->cd_gpio); } EXPORT_SYMBOL(mmc_gpio_get_cd); void mmc_gpiod_request_cd_irq(struct mmc_host *host) { struct mmc_gpio *ctx = host->slot.handler_priv; int irq = -EINVAL; int ret; if (host->slot.cd_irq >= 0 || !ctx || !ctx->cd_gpio) return; /* * Do not use IRQ if the platform prefers to poll, e.g., because that * IRQ number is already used by another unit and cannot be shared. */ if (ctx->cd_irq >= 0) irq = ctx->cd_irq; else if (!(host->caps & MMC_CAP_NEEDS_POLL)) irq = gpiod_to_irq(ctx->cd_gpio); if (irq >= 0) { if (!ctx->cd_gpio_isr) ctx->cd_gpio_isr = mmc_gpio_cd_irqt; ret = devm_request_threaded_irq(host->parent, irq, NULL, ctx->cd_gpio_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, ctx->cd_label, host); if (ret < 0) irq = ret; } host->slot.cd_irq = irq; if (irq < 0) host->caps |= MMC_CAP_NEEDS_POLL; } EXPORT_SYMBOL(mmc_gpiod_request_cd_irq); int mmc_gpio_set_cd_wake(struct mmc_host *host, bool on) { int ret = 0; if (!(host->caps & MMC_CAP_CD_WAKE) || host->slot.cd_irq < 0 || on == host->slot.cd_wake_enabled) return 0; if (on) { ret = enable_irq_wake(host->slot.cd_irq); host->slot.cd_wake_enabled = !ret; } else { disable_irq_wake(host->slot.cd_irq); host->slot.cd_wake_enabled = false; } return ret; } EXPORT_SYMBOL(mmc_gpio_set_cd_wake); /* Register an alternate interrupt service routine for * the card-detect GPIO. */ void mmc_gpio_set_cd_isr(struct mmc_host *host, irqreturn_t (*isr)(int irq, void *dev_id)) { struct mmc_gpio *ctx = host->slot.handler_priv; WARN_ON(ctx->cd_gpio_isr); ctx->cd_gpio_isr = isr; } EXPORT_SYMBOL(mmc_gpio_set_cd_isr); /** * mmc_gpiod_request_cd - request a gpio descriptor for card-detection * @host: mmc host * @con_id: function within the GPIO consumer * @idx: index of the GPIO to obtain in the consumer * @override_active_level: ignore %GPIO_ACTIVE_LOW flag * @debounce: debounce time in microseconds * * Note that this must be called prior to mmc_add_host() * otherwise the caller must also call mmc_gpiod_request_cd_irq(). * * Returns zero on success, else an error. */ int mmc_gpiod_request_cd(struct mmc_host *host, const char *con_id, unsigned int idx, bool override_active_level, unsigned int debounce) { struct mmc_gpio *ctx = host->slot.handler_priv; struct gpio_desc *desc; int ret; desc = devm_gpiod_get_index(host->parent, con_id, idx, GPIOD_IN); if (IS_ERR(desc)) return PTR_ERR(desc); /* Update default label if no con_id provided */ if (!con_id) gpiod_set_consumer_name(desc, ctx->cd_label); if (debounce) { ret = gpiod_set_debounce(desc, debounce); if (ret < 0) ctx->cd_debounce_delay_ms = debounce / 1000; } /* override forces default (active-low) polarity ... */ if (override_active_level && !gpiod_is_active_low(desc)) gpiod_toggle_active_low(desc); /* ... or active-high */ if (host->caps2 & MMC_CAP2_CD_ACTIVE_HIGH) gpiod_toggle_active_low(desc); ctx->cd_gpio = desc; return 0; } EXPORT_SYMBOL(mmc_gpiod_request_cd); bool mmc_can_gpio_cd(struct mmc_host *host) { struct mmc_gpio *ctx = host->slot.handler_priv; return ctx->cd_gpio ? true : false; } EXPORT_SYMBOL(mmc_can_gpio_cd); /** * mmc_gpiod_request_ro - request a gpio descriptor for write protection * @host: mmc host * @con_id: function within the GPIO consumer * @idx: index of the GPIO to obtain in the consumer * @debounce: debounce time in microseconds * * Returns zero on success, else an error. */ int mmc_gpiod_request_ro(struct mmc_host *host, const char *con_id, unsigned int idx, unsigned int debounce) { struct mmc_gpio *ctx = host->slot.handler_priv; struct gpio_desc *desc; int ret; desc = devm_gpiod_get_index(host->parent, con_id, idx, GPIOD_IN); if (IS_ERR(desc)) return PTR_ERR(desc); /* Update default label if no con_id provided */ if (!con_id) gpiod_set_consumer_name(desc, ctx->ro_label); if (debounce) { ret = gpiod_set_debounce(desc, debounce); if (ret < 0) return ret; } if (host->caps2 & MMC_CAP2_RO_ACTIVE_HIGH) gpiod_toggle_active_low(desc); ctx->ro_gpio = desc; return 0; } EXPORT_SYMBOL(mmc_gpiod_request_ro); bool mmc_can_gpio_ro(struct mmc_host *host) { struct mmc_gpio *ctx = host->slot.handler_priv; return ctx->ro_gpio ? true : false; } EXPORT_SYMBOL(mmc_can_gpio_ro);