162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Generic GPIO card-detect helper 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2011, Guennadi Liakhovetski <g.liakhovetski@gmx.de> 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/err.h> 962306a36Sopenharmony_ci#include <linux/gpio/consumer.h> 1062306a36Sopenharmony_ci#include <linux/interrupt.h> 1162306a36Sopenharmony_ci#include <linux/jiffies.h> 1262306a36Sopenharmony_ci#include <linux/mmc/host.h> 1362306a36Sopenharmony_ci#include <linux/mmc/slot-gpio.h> 1462306a36Sopenharmony_ci#include <linux/module.h> 1562306a36Sopenharmony_ci#include <linux/slab.h> 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci#include "slot-gpio.h" 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_cistruct mmc_gpio { 2062306a36Sopenharmony_ci struct gpio_desc *ro_gpio; 2162306a36Sopenharmony_ci struct gpio_desc *cd_gpio; 2262306a36Sopenharmony_ci irqreturn_t (*cd_gpio_isr)(int irq, void *dev_id); 2362306a36Sopenharmony_ci char *ro_label; 2462306a36Sopenharmony_ci char *cd_label; 2562306a36Sopenharmony_ci u32 cd_debounce_delay_ms; 2662306a36Sopenharmony_ci int cd_irq; 2762306a36Sopenharmony_ci}; 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_cistatic irqreturn_t mmc_gpio_cd_irqt(int irq, void *dev_id) 3062306a36Sopenharmony_ci{ 3162306a36Sopenharmony_ci /* Schedule a card detection after a debounce timeout */ 3262306a36Sopenharmony_ci struct mmc_host *host = dev_id; 3362306a36Sopenharmony_ci struct mmc_gpio *ctx = host->slot.handler_priv; 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci host->trigger_card_event = true; 3662306a36Sopenharmony_ci mmc_detect_change(host, msecs_to_jiffies(ctx->cd_debounce_delay_ms)); 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci return IRQ_HANDLED; 3962306a36Sopenharmony_ci} 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_ciint mmc_gpio_alloc(struct mmc_host *host) 4262306a36Sopenharmony_ci{ 4362306a36Sopenharmony_ci const char *devname = dev_name(host->parent); 4462306a36Sopenharmony_ci struct mmc_gpio *ctx; 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ci ctx = devm_kzalloc(host->parent, sizeof(*ctx), GFP_KERNEL); 4762306a36Sopenharmony_ci if (!ctx) 4862306a36Sopenharmony_ci return -ENOMEM; 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ci ctx->cd_debounce_delay_ms = 200; 5162306a36Sopenharmony_ci ctx->cd_label = devm_kasprintf(host->parent, GFP_KERNEL, "%s cd", devname); 5262306a36Sopenharmony_ci if (!ctx->cd_label) 5362306a36Sopenharmony_ci return -ENOMEM; 5462306a36Sopenharmony_ci ctx->ro_label = devm_kasprintf(host->parent, GFP_KERNEL, "%s ro", devname); 5562306a36Sopenharmony_ci if (!ctx->ro_label) 5662306a36Sopenharmony_ci return -ENOMEM; 5762306a36Sopenharmony_ci ctx->cd_irq = -EINVAL; 5862306a36Sopenharmony_ci host->slot.handler_priv = ctx; 5962306a36Sopenharmony_ci host->slot.cd_irq = -EINVAL; 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci return 0; 6262306a36Sopenharmony_ci} 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_civoid mmc_gpio_set_cd_irq(struct mmc_host *host, int irq) 6562306a36Sopenharmony_ci{ 6662306a36Sopenharmony_ci struct mmc_gpio *ctx = host->slot.handler_priv; 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci if (!ctx || irq < 0) 6962306a36Sopenharmony_ci return; 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci ctx->cd_irq = irq; 7262306a36Sopenharmony_ci} 7362306a36Sopenharmony_ciEXPORT_SYMBOL(mmc_gpio_set_cd_irq); 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ciint mmc_gpio_get_ro(struct mmc_host *host) 7662306a36Sopenharmony_ci{ 7762306a36Sopenharmony_ci struct mmc_gpio *ctx = host->slot.handler_priv; 7862306a36Sopenharmony_ci int cansleep; 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci if (!ctx || !ctx->ro_gpio) 8162306a36Sopenharmony_ci return -ENOSYS; 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci cansleep = gpiod_cansleep(ctx->ro_gpio); 8462306a36Sopenharmony_ci return cansleep ? 8562306a36Sopenharmony_ci gpiod_get_value_cansleep(ctx->ro_gpio) : 8662306a36Sopenharmony_ci gpiod_get_value(ctx->ro_gpio); 8762306a36Sopenharmony_ci} 8862306a36Sopenharmony_ciEXPORT_SYMBOL(mmc_gpio_get_ro); 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ciint mmc_gpio_get_cd(struct mmc_host *host) 9162306a36Sopenharmony_ci{ 9262306a36Sopenharmony_ci struct mmc_gpio *ctx = host->slot.handler_priv; 9362306a36Sopenharmony_ci int cansleep; 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci if (!ctx || !ctx->cd_gpio) 9662306a36Sopenharmony_ci return -ENOSYS; 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci cansleep = gpiod_cansleep(ctx->cd_gpio); 9962306a36Sopenharmony_ci return cansleep ? 10062306a36Sopenharmony_ci gpiod_get_value_cansleep(ctx->cd_gpio) : 10162306a36Sopenharmony_ci gpiod_get_value(ctx->cd_gpio); 10262306a36Sopenharmony_ci} 10362306a36Sopenharmony_ciEXPORT_SYMBOL(mmc_gpio_get_cd); 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_civoid mmc_gpiod_request_cd_irq(struct mmc_host *host) 10662306a36Sopenharmony_ci{ 10762306a36Sopenharmony_ci struct mmc_gpio *ctx = host->slot.handler_priv; 10862306a36Sopenharmony_ci int irq = -EINVAL; 10962306a36Sopenharmony_ci int ret; 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci if (host->slot.cd_irq >= 0 || !ctx || !ctx->cd_gpio) 11262306a36Sopenharmony_ci return; 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci /* 11562306a36Sopenharmony_ci * Do not use IRQ if the platform prefers to poll, e.g., because that 11662306a36Sopenharmony_ci * IRQ number is already used by another unit and cannot be shared. 11762306a36Sopenharmony_ci */ 11862306a36Sopenharmony_ci if (ctx->cd_irq >= 0) 11962306a36Sopenharmony_ci irq = ctx->cd_irq; 12062306a36Sopenharmony_ci else if (!(host->caps & MMC_CAP_NEEDS_POLL)) 12162306a36Sopenharmony_ci irq = gpiod_to_irq(ctx->cd_gpio); 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci if (irq >= 0) { 12462306a36Sopenharmony_ci if (!ctx->cd_gpio_isr) 12562306a36Sopenharmony_ci ctx->cd_gpio_isr = mmc_gpio_cd_irqt; 12662306a36Sopenharmony_ci ret = devm_request_threaded_irq(host->parent, irq, 12762306a36Sopenharmony_ci NULL, ctx->cd_gpio_isr, 12862306a36Sopenharmony_ci IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, 12962306a36Sopenharmony_ci ctx->cd_label, host); 13062306a36Sopenharmony_ci if (ret < 0) 13162306a36Sopenharmony_ci irq = ret; 13262306a36Sopenharmony_ci } 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci host->slot.cd_irq = irq; 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci if (irq < 0) 13762306a36Sopenharmony_ci host->caps |= MMC_CAP_NEEDS_POLL; 13862306a36Sopenharmony_ci} 13962306a36Sopenharmony_ciEXPORT_SYMBOL(mmc_gpiod_request_cd_irq); 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ciint mmc_gpio_set_cd_wake(struct mmc_host *host, bool on) 14262306a36Sopenharmony_ci{ 14362306a36Sopenharmony_ci int ret = 0; 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci if (!(host->caps & MMC_CAP_CD_WAKE) || 14662306a36Sopenharmony_ci host->slot.cd_irq < 0 || 14762306a36Sopenharmony_ci on == host->slot.cd_wake_enabled) 14862306a36Sopenharmony_ci return 0; 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci if (on) { 15162306a36Sopenharmony_ci ret = enable_irq_wake(host->slot.cd_irq); 15262306a36Sopenharmony_ci host->slot.cd_wake_enabled = !ret; 15362306a36Sopenharmony_ci } else { 15462306a36Sopenharmony_ci disable_irq_wake(host->slot.cd_irq); 15562306a36Sopenharmony_ci host->slot.cd_wake_enabled = false; 15662306a36Sopenharmony_ci } 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci return ret; 15962306a36Sopenharmony_ci} 16062306a36Sopenharmony_ciEXPORT_SYMBOL(mmc_gpio_set_cd_wake); 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci/* Register an alternate interrupt service routine for 16362306a36Sopenharmony_ci * the card-detect GPIO. 16462306a36Sopenharmony_ci */ 16562306a36Sopenharmony_civoid mmc_gpio_set_cd_isr(struct mmc_host *host, 16662306a36Sopenharmony_ci irqreturn_t (*isr)(int irq, void *dev_id)) 16762306a36Sopenharmony_ci{ 16862306a36Sopenharmony_ci struct mmc_gpio *ctx = host->slot.handler_priv; 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci WARN_ON(ctx->cd_gpio_isr); 17162306a36Sopenharmony_ci ctx->cd_gpio_isr = isr; 17262306a36Sopenharmony_ci} 17362306a36Sopenharmony_ciEXPORT_SYMBOL(mmc_gpio_set_cd_isr); 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci/** 17662306a36Sopenharmony_ci * mmc_gpiod_request_cd - request a gpio descriptor for card-detection 17762306a36Sopenharmony_ci * @host: mmc host 17862306a36Sopenharmony_ci * @con_id: function within the GPIO consumer 17962306a36Sopenharmony_ci * @idx: index of the GPIO to obtain in the consumer 18062306a36Sopenharmony_ci * @override_active_level: ignore %GPIO_ACTIVE_LOW flag 18162306a36Sopenharmony_ci * @debounce: debounce time in microseconds 18262306a36Sopenharmony_ci * 18362306a36Sopenharmony_ci * Note that this must be called prior to mmc_add_host() 18462306a36Sopenharmony_ci * otherwise the caller must also call mmc_gpiod_request_cd_irq(). 18562306a36Sopenharmony_ci * 18662306a36Sopenharmony_ci * Returns zero on success, else an error. 18762306a36Sopenharmony_ci */ 18862306a36Sopenharmony_ciint mmc_gpiod_request_cd(struct mmc_host *host, const char *con_id, 18962306a36Sopenharmony_ci unsigned int idx, bool override_active_level, 19062306a36Sopenharmony_ci unsigned int debounce) 19162306a36Sopenharmony_ci{ 19262306a36Sopenharmony_ci struct mmc_gpio *ctx = host->slot.handler_priv; 19362306a36Sopenharmony_ci struct gpio_desc *desc; 19462306a36Sopenharmony_ci int ret; 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci desc = devm_gpiod_get_index(host->parent, con_id, idx, GPIOD_IN); 19762306a36Sopenharmony_ci if (IS_ERR(desc)) 19862306a36Sopenharmony_ci return PTR_ERR(desc); 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ci /* Update default label if no con_id provided */ 20162306a36Sopenharmony_ci if (!con_id) 20262306a36Sopenharmony_ci gpiod_set_consumer_name(desc, ctx->cd_label); 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci if (debounce) { 20562306a36Sopenharmony_ci ret = gpiod_set_debounce(desc, debounce); 20662306a36Sopenharmony_ci if (ret < 0) 20762306a36Sopenharmony_ci ctx->cd_debounce_delay_ms = debounce / 1000; 20862306a36Sopenharmony_ci } 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci /* override forces default (active-low) polarity ... */ 21162306a36Sopenharmony_ci if (override_active_level && !gpiod_is_active_low(desc)) 21262306a36Sopenharmony_ci gpiod_toggle_active_low(desc); 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_ci /* ... or active-high */ 21562306a36Sopenharmony_ci if (host->caps2 & MMC_CAP2_CD_ACTIVE_HIGH) 21662306a36Sopenharmony_ci gpiod_toggle_active_low(desc); 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci ctx->cd_gpio = desc; 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci return 0; 22162306a36Sopenharmony_ci} 22262306a36Sopenharmony_ciEXPORT_SYMBOL(mmc_gpiod_request_cd); 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_cibool mmc_can_gpio_cd(struct mmc_host *host) 22562306a36Sopenharmony_ci{ 22662306a36Sopenharmony_ci struct mmc_gpio *ctx = host->slot.handler_priv; 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_ci return ctx->cd_gpio ? true : false; 22962306a36Sopenharmony_ci} 23062306a36Sopenharmony_ciEXPORT_SYMBOL(mmc_can_gpio_cd); 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci/** 23362306a36Sopenharmony_ci * mmc_gpiod_request_ro - request a gpio descriptor for write protection 23462306a36Sopenharmony_ci * @host: mmc host 23562306a36Sopenharmony_ci * @con_id: function within the GPIO consumer 23662306a36Sopenharmony_ci * @idx: index of the GPIO to obtain in the consumer 23762306a36Sopenharmony_ci * @debounce: debounce time in microseconds 23862306a36Sopenharmony_ci * 23962306a36Sopenharmony_ci * Returns zero on success, else an error. 24062306a36Sopenharmony_ci */ 24162306a36Sopenharmony_ciint mmc_gpiod_request_ro(struct mmc_host *host, const char *con_id, 24262306a36Sopenharmony_ci unsigned int idx, unsigned int debounce) 24362306a36Sopenharmony_ci{ 24462306a36Sopenharmony_ci struct mmc_gpio *ctx = host->slot.handler_priv; 24562306a36Sopenharmony_ci struct gpio_desc *desc; 24662306a36Sopenharmony_ci int ret; 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_ci desc = devm_gpiod_get_index(host->parent, con_id, idx, GPIOD_IN); 24962306a36Sopenharmony_ci if (IS_ERR(desc)) 25062306a36Sopenharmony_ci return PTR_ERR(desc); 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_ci /* Update default label if no con_id provided */ 25362306a36Sopenharmony_ci if (!con_id) 25462306a36Sopenharmony_ci gpiod_set_consumer_name(desc, ctx->ro_label); 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci if (debounce) { 25762306a36Sopenharmony_ci ret = gpiod_set_debounce(desc, debounce); 25862306a36Sopenharmony_ci if (ret < 0) 25962306a36Sopenharmony_ci return ret; 26062306a36Sopenharmony_ci } 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_ci if (host->caps2 & MMC_CAP2_RO_ACTIVE_HIGH) 26362306a36Sopenharmony_ci gpiod_toggle_active_low(desc); 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_ci ctx->ro_gpio = desc; 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci return 0; 26862306a36Sopenharmony_ci} 26962306a36Sopenharmony_ciEXPORT_SYMBOL(mmc_gpiod_request_ro); 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_cibool mmc_can_gpio_ro(struct mmc_host *host) 27262306a36Sopenharmony_ci{ 27362306a36Sopenharmony_ci struct mmc_gpio *ctx = host->slot.handler_priv; 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_ci return ctx->ro_gpio ? true : false; 27662306a36Sopenharmony_ci} 27762306a36Sopenharmony_ciEXPORT_SYMBOL(mmc_can_gpio_ro); 278